Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion projects/AppStore
2 changes: 2 additions & 0 deletions projects/ZClaw/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,6 @@ The UI reads and writes two escaped, tab-separated files in `~/.zeroclaw`:

Tabs, newlines, and backslashes are escaped when written. These formats are implementation details rather than a public interchange format; preserve field ordering and escaping if another component needs to edit them.

The first provider record is the active Setup provider. Selecting another provider family moves its existing record to the front instead of recreating it from defaults, so values survive switching away and back. A preset is created only when that family has no saved record. Both TSV files are written through a mode-`0600` temporary file, flushed, and atomically renamed over the previous file.

Because credentials are stored as plain text, packaging and diagnostic tooling must not collect these files by default.
2 changes: 2 additions & 0 deletions projects/ZClaw/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ For a custom provider, the API URL must begin with `http://` or `https://`. Quic

Model Settings saves the selected provider and each API URL, API key, or model value as soon as the entry is confirmed with `Enter`. Confirmed values are restored the next time ZClaw starts, even if Quickstart has not been run.

Switching provider families preserves each family's saved values. Selecting a family that has been configured before restores that record; defaults are used only for a family with no saved record.

The generated ZeroClaw configuration binds the gateway to `127.0.0.1:42617`, requires pairing, uses a 180-second request timeout, and allows 600 seconds for long-running requests. The UI endpoint is reset to `http://127.0.0.1:42617/webhook`.

## Chat
Expand Down
141 changes: 105 additions & 36 deletions projects/ZClaw/main/ui/zclaw_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <pthread.h>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

namespace {
Expand Down Expand Up @@ -312,17 +313,26 @@ class ZClawApp
return zclaw::save_provider_configs(ZClawClient::providers_config_path(), providers_, error);
}

void report_save_error(const std::string &error)
{
append_ai_message(error.empty() ? "Could not save settings." : error.c_str());
}

bool save_setup_provider()
{
if (providers_.empty())
providers_.push_back(setup_provider_);
std::vector<ProviderConfig> candidate = providers_;
if (candidate.empty())
candidate.push_back(setup_provider_);
else
providers_[0] = setup_provider_;
candidate[0] = setup_provider_;
std::string error;
if (save_providers(&error))
return true;
append_ai_message(error.c_str());
return false;
ZClawClient::ensure_storage_dir();
if (!zclaw::save_provider_configs(ZClawClient::providers_config_path(), candidate, &error)) {
report_save_error(error);
return false;
}
providers_ = std::move(candidate);
return true;
}

void load_ui_config()
Expand All @@ -348,17 +358,20 @@ class ZClawApp
}
}

void save_ui_config()
bool save_ui_config()
{
ZClawClient::ensure_storage_dir();
std::ofstream file(ZClawClient::ui_config_path(), std::ios::trunc);
if (!file)
return;
file << zclaw::encode_config_field("webhook_url") << '\t' << zclaw::encode_config_field(ui_config_.webhook_url) << '\n'
<< zclaw::encode_config_field("agent_alias") << '\t' << zclaw::encode_config_field(ui_config_.agent_alias) << '\n'
<< zclaw::encode_config_field("webhook_secret") << '\t' << zclaw::encode_config_field(ui_config_.webhook_secret) << '\n'
<< zclaw::encode_config_field("bearer_token") << '\t' << zclaw::encode_config_field(ui_config_.bearer_token) << '\n'
<< zclaw::encode_config_field("setup_complete") << '\t' << (ui_config_.setup_complete ? "1" : "0") << '\n';
std::string contents;
contents += zclaw::encode_config_field("webhook_url") + '\t' + zclaw::encode_config_field(ui_config_.webhook_url) + '\n';
contents += zclaw::encode_config_field("agent_alias") + '\t' + zclaw::encode_config_field(ui_config_.agent_alias) + '\n';
contents += zclaw::encode_config_field("webhook_secret") + '\t' + zclaw::encode_config_field(ui_config_.webhook_secret) + '\n';
contents += zclaw::encode_config_field("bearer_token") + '\t' + zclaw::encode_config_field(ui_config_.bearer_token) + '\n';
contents += zclaw::encode_config_field("setup_complete") + '\t' + (ui_config_.setup_complete ? "1" : "0") + '\n';
std::string error;
if (zclaw::atomic_write_config(ZClawClient::ui_config_path(), contents, &error))
return true;
report_save_error(error);
return false;
}

static const char *provider_field_name(ProviderEditField field)
Expand Down Expand Up @@ -813,14 +826,16 @@ class ZClawApp

void apply_setup_edit(const std::string &value)
{
const ProviderConfig previous = setup_provider_;
if (setup_edit_field_ == SetupEditField::Uri)
setup_provider_.uri = value;
else if (setup_edit_field_ == SetupEditField::ApiKey)
setup_provider_.api_key = value;
else if (setup_edit_field_ == SetupEditField::Model)
setup_provider_.model = value;
setup_edit_field_ = SetupEditField::None;
save_setup_provider();
if (!save_setup_provider())
setup_provider_ = previous;
render_setup();
}

Expand Down Expand Up @@ -918,18 +933,23 @@ class ZClawApp
render_settings_main();
}

void open_setup_panel()
void sync_setup_provider_selection()
{
if (settings_panel_open() || settings_animating_)
return;
close_input_dialog();
if (setup_provider_.family == "openrouter") setup_provider_selected_ = 1;
else if (setup_provider_.family == "anthropic") setup_provider_selected_ = 2;
else if (setup_provider_.family == "ollama") setup_provider_selected_ = 3;
else if (setup_provider_.family == "deepseek") setup_provider_selected_ = 4;
else if (setup_provider_.family == "custom") setup_provider_selected_ = 5;
else setup_provider_selected_ = 0;
setup_provider_scroll_ = 0;
}

void open_setup_panel()
{
if (settings_panel_open() || settings_animating_)
return;
close_input_dialog();
sync_setup_provider_selection();
create_settings_panel();
if (first_run_needed())
render_setup_providers();
Expand Down Expand Up @@ -1499,11 +1519,17 @@ class ZClawApp

void finish_pairing_result(bool ok, const std::string &text, const UiConfig &config)
{
if (ok)
append_ai_message(text.c_str());
if (ok) {
const UiConfig previous = ui_config_;
ui_config_ = config;
save_ui_config();
if (!save_ui_config()) {
ui_config_ = previous;
}
}
append_ai_message(text.c_str());
if (!ok)
append_ai_message(text.c_str());
if (settings_panel_open() && settings_view_ == SettingsView::Authorization)
render_authorization();
}
Expand Down Expand Up @@ -1570,8 +1596,14 @@ class ZClawApp
provider.family = "openai-compatible";
provider.model = "model";
provider.uri = "https://api.example.com/v1";
const std::vector<ProviderConfig> previous = providers_;
providers_.push_back(provider);
save_providers();
std::string error;
if (!save_providers(&error)) {
providers_ = previous;
report_save_error(error);
return;
}
provider_selected_ = (int)providers_.size();
provider_detail_index_ = (int)providers_.size() - 1;
settings_selected_ = 0;
Expand All @@ -1582,8 +1614,17 @@ class ZClawApp
{
if (provider_detail_index_ < 0 || provider_detail_index_ >= (int)providers_.size())
return;
providers_.erase(providers_.begin() + provider_detail_index_);
save_providers();
const std::vector<ProviderConfig> previous_providers = providers_;
const ProviderConfig previous_setup = setup_provider_;
zclaw::erase_provider_config(&providers_, &setup_provider_,
static_cast<size_t>(provider_detail_index_), provider_preset(0));
std::string error;
if (!save_providers(&error)) {
providers_ = previous_providers;
setup_provider_ = previous_setup;
report_save_error(error);
return;
}
provider_detail_index_ = -1;
if (provider_selected_ > (int)providers_.size())
provider_selected_ = (int)providers_.size();
Expand Down Expand Up @@ -1628,10 +1669,19 @@ class ZClawApp
if (provider_edit_field_ == ProviderEditField::None)
return;

ProviderConfig &provider = providers_[provider_detail_index_];
const std::vector<ProviderConfig> previous_providers = providers_;
const ProviderConfig previous_setup = setup_provider_;
ProviderConfig provider = providers_[provider_detail_index_];
provider_field_value(provider, provider_edit_field_) = value;
provider_edit_field_ = ProviderEditField::None;
save_providers();
zclaw::replace_provider_config(&providers_, &setup_provider_,
static_cast<size_t>(provider_detail_index_), provider);
std::string error;
if (!save_providers(&error)) {
providers_ = previous_providers;
setup_provider_ = previous_setup;
report_save_error(error);
}
render_provider_detail();
}

Expand Down Expand Up @@ -1709,13 +1759,20 @@ class ZClawApp
void finish_setup_result(bool ok, const std::string &text, const UiConfig &config)
{
setup_in_flight_ = false;
bool persisted = ok;
if (ok)
append_ai_message(text.c_str());
if (ok) {
const UiConfig previous = ui_config_;
ui_config_ = config;
save_setup_provider();
save_ui_config();
if (!save_setup_provider() || !save_ui_config()) {
ui_config_ = previous;
persisted = false;
}
}
append_ai_message(text.c_str());
if (ok && settings_panel_open() && settings_view_ == SettingsView::Setup) {
if (!ok)
append_ai_message(text.c_str());
if (persisted && settings_panel_open() && settings_view_ == SettingsView::Setup) {
render_setup();
close_settings_panel();
}
Expand All @@ -1730,6 +1787,7 @@ class ZClawApp
if (setup_in_flight_)
return;
if (settings_selected_ == 0) {
sync_setup_provider_selection();
render_setup_providers();
} else if (settings_selected_ == setup_initialize_row()) {
start_setup();
Expand All @@ -1740,8 +1798,16 @@ class ZClawApp
}

if (settings_view_ == SettingsView::SetupProviders) {
setup_provider_ = provider_preset(setup_provider_selected_);
save_setup_provider();
const std::vector<ProviderConfig> previous_providers = providers_;
const ProviderConfig previous_setup = setup_provider_;
const ProviderConfig selected = provider_preset(setup_provider_selected_);
zclaw::activate_provider_config(&providers_, &setup_provider_, selected);
std::string error;
if (!save_providers(&error)) {
providers_ = previous_providers;
setup_provider_ = previous_setup;
report_save_error(error);
}
settings_selected_ = 0;
render_setup();
return;
Expand All @@ -1751,9 +1817,12 @@ class ZClawApp
if (settings_selected_ == 0) {
open_text_dialog("Pairing code", "", InputMode::PairingCode);
} else if (settings_selected_ == 4) {
const UiConfig previous = ui_config_;
ui_config_.bearer_token.clear();
save_ui_config();
append_ai_message("Authorization token cleared.");
if (save_ui_config())
append_ai_message("Authorization token cleared.");
else
ui_config_ = previous;
render_authorization();
}
return;
Expand Down
4 changes: 2 additions & 2 deletions projects/ZClaw/main/ui/zclaw_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,9 @@ bool apply_quickstart_config(UiConfig *config, ProviderConfig provider, std::str
!config_set("gateway.long_running_request_timeout_secs", "600", error) ||
!config_set(provider_prefix + ".model", provider.model, error))
return false;
if (!provider.uri.empty() && !config_set(provider_prefix + ".uri", provider.uri, error))
if (!config_set(provider_prefix + ".uri", provider.uri, error))
return false;
if (!provider.api_key.empty() && !config_set(provider_prefix + ".api_key", provider.api_key, error))
if (!config_set(provider_prefix + ".api_key", provider.api_key, error))
return false;

if (!ensure_agent(agent, error) ||
Expand Down
Loading
Loading