Close the G7 gap from the v3 integration design.
Scenario: A user upgrades Hermes (or manually edits config.yaml and drops
the api_server platform block). The next `gateway start` silently produces
a Gateway that doesn't expose /v1/runs, and ClawPanel's chat/agent flows
fail with confusing errors.
Solution: Run a pre-start guardian that checks
`platforms.api_server.enabled: true` and auto-heals the file when
missing, with a timestamped backup so users can always roll back.
Backend (src-tauri/src/commands/hermes.rs):
- New pure helpers `config_has_api_server_enabled(raw)` and
`patch_yaml_ensure_api_server(raw)` working directly on YAML strings
(no serde_yaml dependency needed — we only touch 3 structural keys).
- `ensure_api_server_enabled(app)` wraps the helpers with filesystem
I/O: reads config.yaml, writes `config.yaml.bak-<epoch>` on mutation,
writes the patched content back, emits `hermes-config-patched` so
the frontend can show a transparent toast.
- Called from `hermes_gateway_action` on every `start` action. If
config.yaml doesn't exist, the guardian is a no-op (configure_hermes
creates a compliant file on first run).
- 7 new unit tests covering: truthy value variants (true/yes/on/1),
missing/disabled/commented scenarios, no-op when healthy,
appending a full platforms block, injecting into existing platforms,
replacing an explicitly disabled api_server subtree.
Web mode (scripts/dev-api.js):
- Mirrors the three helpers as `_hermesConfigHasApiServerEnabled`,
`_hermesPatchYamlEnsureApiServer`, `_hermesEnsureApiServerEnabled`.
- Called in `hermes_gateway_action` start path.
- Logs to console.warn instead of emitting a Tauri event.
Frontend (src/engines/hermes/pages/dashboard.js):
- Dashboard subscribes to `hermes-config-patched` and surfaces a toast
(6s duration) so the user knows the auto-heal happened and where the
backup lives.
Verified: cargo fmt / cargo clippy -D warnings / cargo test all green
(12 hermes tests pass total: 5 provider registry + 7 guardian).
`node --check scripts/dev-api.js` passes. `npm run build` green.
Users may need to configure custom environment variables for Hermes
(e.g. `TAVILY_API_KEY` for the tavily skill, `HTTP_PROXY`, SKILL_*
settings). Previously the only way to set these was to hand-edit
~/.hermes/.env, which risks clobbering the provider keys that
ClawPanel writes through configure_hermes.
This patch adds a dedicated editor UI backed by three new Tauri
commands that refuse to touch managed keys.
Backend (src-tauri/src/commands/hermes.rs):
- `hermes_env_read_unmanaged` — returns every KEY=VALUE pair whose key
is NOT in `hermes_providers::all_managed_env_keys()` (so provider
API keys, base URLs, `GATEWAY_ALLOW_ALL_USERS`, `API_SERVER_KEY`
stay hidden). Preserves file order, dedups.
- `hermes_env_set(key, value)` — validates key against `^[A-Z0-9_]+$`,
refuses managed keys, updates first occurrence or appends,
preserves comments/blanks.
- `hermes_env_delete(key)` — refuses managed keys, removes first
matching line, preserves other structure.
All three commands registered in `src-tauri/src/lib.rs`.
Frontend:
- New page `src/engines/hermes/pages/env-editor.js`:
- Header with "back to dashboard" link and warning banner listing
which keys are managed by ClawPanel.
- Table with Key / Value / Actions columns.
- Inline edit mode per row (save / cancel).
- "Add variable" button for new entries.
- Value column masks long secrets (`sk-a…xyz9`) so glances don't
leak credentials.
- Toast feedback on save / delete / validation errors.
- Inline Chinese copy (TODO: wire up i18n when the locales module
lands).
- Route registered at `/h/env` in `src/engines/hermes/index.js`.
- Dashboard "Model config" section now has a subtle link
".env 高级编辑 →" pointing to the new page.
API wiring:
- `src/lib/tauri-api.js`: added `hermesEnvReadUnmanaged`,
`hermesEnvSet`, `hermesEnvDelete`.
- `scripts/dev-api.js`: mirrors the three commands in Web mode with
a duplicated managed-key list (keep in sync with Rust's
`hermes_providers::all_managed_env_keys` as new providers land).
Verified: cargo fmt / cargo clippy -D warnings / cargo test / npm run
build all green. Dashboard chunk unchanged (24.30 kB); new env-editor
chunk is ~7 kB gzip.
Introduce an authoritative Hermes provider registry in Rust, mirroring
upstream `hermes-agent` data (PROVIDER_REGISTRY + _PROVIDER_MODELS), and
refactor the four critical Hermes commands to be provider-aware.
Closes the G1/G2/G3 blockers identified in the v3 integration design.
New module `src-tauri/src/commands/hermes_providers.rs`:
- Static catalog of 22 providers: 17 api_key (incl. DeepSeek, Gemini,
xAI, GLM/Z.AI, Kimi, MiniMax int'l + CN, Alibaba DashScope, Xiaomi,
Copilot PAT, HuggingFace, OpenRouter, Vercel AI Gateway, OpenCode
Zen/Go, Kilocode), 3 OAuth (Nous, OpenAI Codex, Qwen OAuth), 1
external_process (Copilot ACP), and `custom` placeholder.
- Each entry carries id, display name, auth_type, base_url, env var
priority list, transport, probe strategy, known models, aggregator
flag, and CLI auth hint.
- Helpers: `get_provider`, `primary_api_key_env`, `primary_base_url_env`,
`all_managed_env_keys`, `infer_provider_from_env_keys`,
`find_provider_by_model`.
- New Tauri command `hermes_list_providers` (cached client-side).
- 5 unit tests covering registry integrity and lookup semantics.
Refactored `src-tauri/src/commands/hermes.rs`:
- `configure_hermes`: writes `model.provider` in config.yaml and routes
API keys through the registry's `api_key_env_vars`. Skips key write
for OAuth providers (Hermes CLI manages auth.json itself). Clears
all managed keys on provider switch to avoid stale credentials.
- `hermes_read_config`: reads `model.provider`, then reverse-looks up
the matching API key from the registry's env var priority list.
Falls back to inferring provider from .env when config.yaml omits
the provider field.
- `hermes_update_model`: accepts optional `provider` param; writes
`model.provider` line into config.yaml (adds it when missing, updates
in place when present, removes it for `custom`).
- `hermes_fetch_models`: accepts optional `provider` param; uses
registry's probe strategy (`PROBE_NONE` returns static catalog for
OAuth providers instead of hitting the API).
Registration:
- `src-tauri/src/commands/mod.rs`: declare `hermes_providers` module.
- `src-tauri/src/lib.rs`: import + register `hermes_list_providers`.
Verified: cargo fmt / cargo clippy -D warnings / cargo test all green
(5 new unit tests pass).
* feat(diagnose): detect and inform about @homebridge/ciao cmd popup bug
On Windows, OpenClaw's transitive dependency @homebridge/ciao (<=1.3.6)
calls child_process.exec('arp -a ...') every 15-30 seconds without
passing windowsHide:true, causing a cmd.exe popup to flash.
This is an upstream library bug:
- Issue: homebridge/ciao#64
- PR: homebridge/ciao#65 (open, not merged)
ClawPanel deliberately chooses 'detect and inform' rather than silently
patching the user's node_modules. We respect the user's control over
their own machine.
Changes:
- src-tauri/src/commands/diagnose.rs: new check_ciao_windowshide_bug
command; scans openclaw's @homebridge/ciao/lib/NetworkManager.js and
reports whether the buggy exec pattern is present
- src-tauri/src/lib.rs: register the new command
- scripts/dev-api.js: Web-mode stub (returns affected:false since the
bug does not manifest off-Windows)
- src/lib/tauri-api.js: add api.checkCiaoWindowsHideBug
- src/lib/ciao-bug-warning.js: new module with toast + modal flow,
version-scoped dismiss (localStorage)
- src/locales/modules/ciaoBug.js: translations in 5 primary languages
- src/locales/index.js: register the ciaoBug module
- src/main.js: call checker 3s after splash hides
Non-Windows users see nothing; Windows users see a single warning toast
(version-dismissible) linking to three fix paths: wait for upstream,
apply patch-package, or edit NetworkManager.js manually.
* fix(diagnose): gate helper with cfg(windows), drop unneeded return
CI failures on Linux + macOS:
- openclaw_module_root was dead code when target_os != windows
since the only caller is the #[cfg(target_os = "windows")] block
inside check_ciao_windowshide_bug
- Explicit `return CiaoCheckResult {...};` in the non-Windows branch
triggered clippy::needless_return
Fix:
- Add #[cfg(target_os = "windows")] to openclaw_module_root so it
is not compiled on other platforms
- Convert the non-Windows early exit to a tail expression
Fix for #244 Hidden-start repeat loop:
cleanup_zombie_gateway_processes used a single /health probe to
decide whether a port-listening process is a zombie. When Gateway
reaches 'ready' but is still initializing (loading plugins, connecting
channels, warming up networks), a single probe can time out -> the
healthy Gateway gets killed -> start_service_impl Hidden-starts a new
one -> loop.
Add is_gateway_port_responsive_with_retry: 3 attempts with 800ms
interval before classifying as zombie. Maximum wait 2.4s for a
process that's genuinely healthy to show up as such.
Effect: healthy Gateway no longer misidentified during warm-up,
Hidden-start no longer triggered on an already-ready Gateway.
Refs #244
Root cause for #243 / #244 / #240: model edits trigger
api.restartGateway() with only 300ms debounce. Fast consecutive
edits stack up restart calls, creating zombie Gateway processes,
failed restarts, and CPU fan spikes.
Layer A (frontend):
- New src/lib/gateway-restart-queue.js: 3s debounce + single-flight
lock + reschedule on in-flight request
- Refactor src/pages/models.js doAutoSave: write config immediately,
schedule restart via queue with 'Apply now' toast button
- Subscribe to queue state for unified success/failure toast
- Add i18n: models.configQueued, models.applyNow
Layer B (backend):
- src-tauri/src/commands/config.rs: wrap restart_gateway /
reload_gateway with tokio::sync::Mutex + 2s cooldown
- Cargo.toml: add tokio 'sync' feature
- scripts/dev-api.js: same guard for Web mode (inflight promise
reuse + 2s cooldown)
Effects:
- 10 rapid edits within 3s -> 1 restart (was 10+ with races)
- Backend serializes concurrent restart calls, no zombie spawns
- User sees single 'Apply now' toast instead of restart storm
Refs #243#244#240
- Bump rand 0.9.2 -> 0.9.4 (addresses unsoundness alert)
- Update getrandom, fastrand, and other transitive deps via cargo update
Note: rand 0.8.5/0.7.3 remain as indirect deps of upstream crates
(Tauri/winit/etc) and cannot be forced to 0.9.x due to API breakage.
The vulnerable code path (custom logger calling rand::rng()) is not
exercised by ClawPanel.
feat: SkillHub skill store (SDK-based, no CLI dependency)
- Rust SDK (skillhub.rs): HTTP search, index fetch, zip download+extract
- Node.js SDK (skillhub-sdk.js): mirrors Rust SDK for Web/Docker mode
- Skills page: new "Store" tab with full index browse + client-side filter
- Remove 6 old CLI-dependent commands, add 3 SDK commands
- Migrate assistant.js skill tools from ClawHub CLI to SkillHub SDK
- Fix index decode error ({total,skills} wrapper vs bare array)
- Fix skill name display (API field 'name' vs 'display_name')
- Clean up 13 dead CSS rules from old skills hero/tips UI