Commit Graph

505 Commits

Author SHA1 Message Date
晴天
eccf91ed1e feat(hermes): add channel configuration editor 2026-05-23 01:51:08 +08:00
晴天
27b35b6298 fix(channels): normalize OpenClaw channel config policies 2026-05-23 01:14:42 +08:00
晴天
f4d644ea06 fix(web): support openclaw path conflict scan 2026-05-23 00:20:47 +08:00
晴天
1ae223a0b1 feat(channels): add gateway runtime status 2026-05-23 00:09:46 +08:00
晴天
718efe7e33 chore: ignore local worktrees 2026-05-22 23:50:23 +08:00
晴天
d0f0d1f6b7 fix(gateway): release 0.16.5 v0.16.5 2026-05-22 20:42:16 +08:00
github-actions[bot]
5aa4c9aa06 ci: update latest.json for v0.16.4 2026-05-22 12:00:01 +00:00
晴天
4d82395b90 fix(engine-select): release 0.16.4 v0.16.4 2026-05-22 19:46:07 +08:00
晴天
0e71cb938d ci: avoid release cache save failures 2026-05-22 15:59:33 +08:00
github-actions[bot]
c2510e64d9 ci: update latest.json for v0.16.3 2026-05-22 07:53:31 +00:00
晴天
76c4a56302 fix: stabilize gateway startup and kernel i18n v0.16.3 2026-05-22 15:38:56 +08:00
github-actions[bot]
7977608e5b ci: update latest.json for v0.16.2 2026-05-21 11:38:06 +00:00
晴天
41e688843e chore: release v0.16.2 v0.16.2 2026-05-21 19:23:43 +08:00
晴天
9d2dc8438e fix: apply pending correctness fixes
Cherry-pick the still-relevant fixes from recent draft PRs without pulling in stale release/docs changes:

- serialize dashboard data loads to avoid concurrent config self-heal writes
- preserve valid per-model default blocks during dashboard model self-heal
- pass structured humanizeError results directly to toast for model import scan failures
- align frontend kernel isLatest with suffix-aware recommended version ordering

Verification:
- node --test tests/*.test.js
- npm run build
2026-05-21 19:06:38 +08:00
github-actions[bot]
dbdf239430 ci: update latest.json for v0.16.1 2026-05-21 06:46:21 +00:00
晴天
14525c50e4 chore: release v0.16.1 v0.16.1 2026-05-21 14:33:33 +08:00
晴天
a50000a933 fix(gateway): support latest hello server version payload
OpenClaw 2026.5.18/2026.5.19 still uses Gateway WS protocol v4
(PROTOCOL_VERSION=4, MIN_CLIENT_PROTOCOL_VERSION=4), so there is no v5
handshake to implement. The compatibility break is the hello payload
shape: current upstream sends the runtime version at `hello.server.version`
while ClawPanel only read the old flat `hello.serverVersion` field.

Read both shapes so latest kernels keep populating wsClient.serverVersion,
which in turn keeps Dashboard display, kernel snapshot feature gates and
isLatest checks working after the WebSocket handshake succeeds.

Also bump the recommended OpenClaw targets to the current npm latests:
- official: 2026.5.19
- chinese: 2026.5.18-zh.1

Verification:
- node --test tests/kernel.test.js
- npm run build
- manual module check: simulated both hello.server.version and legacy
  hello.serverVersion payloads, both report serverVersion and protocol v4
2026-05-21 14:25:10 +08:00
github-actions[bot]
230b5e6dca ci: update latest.json for v0.16.0 2026-05-16 07:23:49 +00:00
晴天
14a5b1ee9a chore: release v0.16.0 v0.16.0 2026-05-16 15:11:02 +08:00
晴天
12cc9cd6ce fix(assistant): give assistant a Hermes identity, surface raw install hint, unblock CI
Three follow-ups the user spotted in one round.

assistant.js — assistant did not know it was on Hermes
  Both engines (OpenClaw and Hermes Agent) reuse the same /assistant
  page (engines/hermes/index.js comments it as "共用页面/引擎无关"),
  but getSystemPromptBase() hard-coded the OpenClaw self-introduction:
  "你帮助用户管理和排障 OpenClaw AI Agent 平台 / 你精通 OpenClaw 的架
  构…", followed by a CLI cheatsheet for `openclaw gateway start` and
  `openclaw config apply`. Result: under the Hermes engine, the
  assistant happily told users to run `openclaw doctor` and edit
  `~/.openclaw/openclaw.json` — neither of which exists in the Hermes
  world.

  Split into a per-engine dispatcher:
    getSystemPromptBase()
      └ if hermes  → getHermesSystemPromptBase()  (new)
      └ else       → getOpenclawSystemPromptBase() (renamed, same body)

  The new Hermes base prompt covers the facts that actually matter:
   - dual-process layout: Gateway 8642 (chat API, what ClawPanel
     mostly drives) vs Dashboard 9119 (admin/profiles/skills/oauth/
     kanban — must be started separately)
   - Profile system (independent workspaces, switchProfile restarts
     dashboard, multi-gateway view)
   - lazy_deps allowlist and why pre-installing matters
   - paths: ~/.hermes (data) and ~/.hermes-venv (interpreter), with a
     reminder that ~/.openclaw/clawpanel.json is the panel config
     shared with the OpenClaw engine — not Hermes data
   - Top-5 problem playbook (9119 not running, venv missing, channels
     hanging on first launch, gateway crashing, profile drift)
   - Explicit "do not give the user `openclaw …` commands"

  Two more spots in buildSystemPrompt() are also engine-aware now:
   - the "ClawPanel 工具能力" bullet list inside the soul-cache branch
   - the "跨平台路径" reminder (Hermes points to .hermes / .hermes-venv)

lazy-deps.js — "请确认目标资源是否仍存在" was masking the real hint
  When the user has not installed Hermes yet, Rust's
  `hermes_lazy_deps_features` returns the very actionable string
  "Hermes venv 未找到(~/.hermes-venv 不存在)。请先安装 Hermes。".
  humanize-error.js then sees "未找到", classifies the error as
  notFound, and replaces the message with the generic template
  "请确认目标资源是否仍存在" — which tells the user nothing about
  installing Hermes.

  Take humanizeError() but render `message + raw` instead of
  `message + hint`. The user now sees both the friendly title and the
  exact Rust-side instruction. Drop the unused humanizeErrorText
  import that this commit replaces.

config.rs — unblock CI (clippy too_many_arguments on existing code)
  The clippy gate has been red on main since e1eda2d ("import external
  client configs") because two helpers in commands/config.rs take >7
  positional parameters:
    - push_client_candidate (14 params)
    - scan_json_client_file (10 params)

  Both helpers exist purely to push a flat record into a Vec<Value>.
  Wrapping them in a struct just to satisfy clippy would force every
  caller to first build that struct, hurting readability. Suppress
  clippy::too_many_arguments locally on these two functions with an
  inline comment explaining why.

## Verification
- node --check + npm run build: clean
- cargo clippy --all-targets -- -D warnings: now compiles to
  "Finished `dev` profile" with zero errors/warnings (previously
  failed with two too_many_arguments)
- Playwright: import lazy-deps with api.hermesLazyDepsFeatures mocked
  to throw "Hermes venv 未找到 … 请先安装 Hermes。", rendered content
  contains "请先安装 Hermes" (hasRaw=true), does not contain the
  generic "请确认目标资源是否仍存在" (hasGenericNotFound=false), and
  does not contain "[object Object]"
2026-05-16 14:24:45 +08:00
晴天
4b0d8e5042 fix(ui): hide useless docker manager on desktop, auto-start hermes dashboard, polish layout
Four independent UI fixes the user spotted in one screenshot tour:

services.js — desktop docker manager
  ClawPanel is not a docker management tool. The "Docker 多实例管理"
  block on the OpenClaw services page only makes sense for users who
  deployed ClawPanel itself in Web mode (serve.js / dev-api) and want
  to orchestrate multiple OpenClaw containers from one panel.

  On desktop Tauri this block always degrades to either "未启用" with
  a connect ENOENT error (no docker daemon on the user box) or to a
  generic "unavailable" placeholder — pure visual noise. Skip the
  whole config-section in render() and bail out of loadDockerManager()
  when isTauriRuntime() is true. Web mode keeps the feature.

profiles.js — Hermes Profile manager could not load
  Profile API only exists on the Hermes Dashboard process at 9119,
  which the user has to start by hand. When it is offline, the page
  showed a raw "由于目标计算机积极拒绝, 无法连接 (10061)" error which
  is useless: users do not know they need to start a separate process.

  load() now does the same probe → auto-start dance that
  extensions.js / dashboard.js use for their 9119 links: probe first,
  call hermesDashboardStart() if not running, only then issue the
  /api/profiles request. If the start itself fails, we fall through to
  the original catch and humanize-error renders a real reason.

lazy-deps.js — "[object Object]" on load failure
  humanizeError() returns { message, hint, raw, action? }, not a
  string. The catch branch passed the object straight into
  escapeHtml(), so String(obj) coerced to "[object Object]". Switch
  to humanizeErrorText() which is exactly the (message + hint)
  one-liner string variant.

layout.css — header buttons crammed against the description
  Several pages (hermes profiles / lazy-deps / files / gateways /
  group-chat / kanban / oauth) put their header buttons inside
  <div class="config-actions"> nested in <div class="page-header">,
  but the existing flex layout rule only matched .page-actions. The
  buttons therefore stacked directly under .page-desc with zero
  visible gap. Add .config-actions to both the desktop flex selector
  and the @media (max-width:768px) column-stack selector so all
  these pages get the same title-left / actions-right layout.

## Verification
- npm run build (no warnings beyond the existing chunk size note)
- Playwright on /services in browser mode: docker section present;
  same page after mocking window.__TAURI_INTERNALS__: section gone
- Playwright on /lazy-deps: rendered content does not contain the
  string "[object Object]"
- Playwright dynamic-imports profiles / lazy-deps / files render():
  computed style on .page-header is display:flex, flex-direction:row,
  title and button share the same getBoundingClientRect().top, button
  pushed to the right edge (justify-content:space-between effective)
2026-05-16 13:46:24 +08:00
晴天
7d75486a53 feat(gateway): surface negotiated handshake protocol version in UI
Users have reported confusion about "when will ClawPanel update its
gateway protocol to v4". This is actually a misreading: ClawPanel v0.15+
already advertises `minProtocol=3, maxProtocol=4` in its connect frame,
and negotiates v4 transparently when the kernel is >= 2026.5.12. The
`v3|` prefix users were seeing in dev-api.js is the device signature
payload string schema version, which is a completely separate concept
from the handshake protocol version.

Make this visible and unambiguous:

UI
- Add a "Proto v4" badge next to the Gateway service name in
  /services once the WS handshake succeeds, with a tooltip explaining
  that this is the WS handshake protocol version (not the device
  signature payload v3 format).
- Add the same protocol info to the WebSocket row in /chat-debug.

API
- WsClient now exposes `negotiatedProtocol` which prefers the explicit
  field from the hello payload (`protocol` / `protocolVersion` /
  `negotiatedProtocol`) and falls back to inferring from serverVersion:
  kernels >= 2026.5.12 are reported as v4, older as v3. This matches
  the panel's advertised range of [3, 4].
- KernelSnapshot grows a `protocol` field so feature gates and UIs that
  already consume the snapshot can read it without touching wsClient.

Comments
- Expand the KERNEL_TARGET comment in feature-catalog.js to spell out
  the two-distinct-version-numbers rule explicitly.
- Add matching clarifying comments next to the `v3|...` payload string
  in both scripts/dev-api.js and src-tauri/src/commands/device.rs, so
  the next reader does not confuse payload schema with handshake.

## Verification
- node --check on all touched JS files
- npm run build
- cargo fmt --check && cargo check (clippy errors that surface are
  pre-existing debt in config.rs, untouched here)
- Playwright /services: mock wsClient state, observe `协议 v4` badge
  rendered with `rgba(99, 102, 241, 0.1)` background and accent color,
  for both the explicit-protocol path and the version-inferred path.
2026-05-16 13:02:08 +08:00
晴天
cb4c9bcdfc chore: 目录优化 2026-05-16 12:20:04 +08:00
晴天
a13c9ee6ba fix(models): stop auto-filling fallback chain and add clear-all button
The fallback editor's "Add" buttons appeared to do nothing because
applyDefaultModel auto-populated defaults.model.fallbacks with every
non-primary model whenever the list was empty (and the same for
defaults.models). After any save with an empty chain, the chain got
filled with all 17+ candidates; the candidate pool became "No candidate
models available" and any subsequent Add click hit the early return
`if (modelConfig.fallbacks.includes(full)) return`.

Even worse, the auto-fill made it impossible for users to keep an empty
fallback chain on purpose: deleting all chips would silently get
replaced with every model on the next debounced autosave.

Remove the auto-fill in applyDefaultModel. An empty fallback chain is a
valid configuration that means "no implicit fallback"; Gateway already
surfaces a clear primary-model error in that case. normalizeDefaultModel
Selection still cleans up invalid/duplicate entries.

Also add a small "Clear All" button next to the active chain title so
users can drop the entire fallback list in one click instead of removing
chips one by one (especially useful for the existing bloated 17-fallback
state created by the old auto-fill path).

## Verification
- node --check src/pages/models.js
- node --check src/locales/modules/models.js
- npm run build
- Playwright repro: open /#/models → Clear All → fallbacks on disk
  go from 17 → 0 → click Add on one candidate → fallbacks on disk are
  exactly that one entry, no longer auto-expanded back to 17.
2026-05-16 12:07:35 +08:00
晴天
207b1c7c55 fix(windows): make gateway terminal window actually visible
The previous implementation passed CREATE_NEW_CONSOLE to a Rust
StdCommand spawning cmd.exe directly, but Rust's default Stdio::inherit
copies the parent stdio handles into STARTUPINFO with
STARTF_USESTDHANDLES, which neutralizes CREATE_NEW_CONSOLE. The cmd
process then ran without a visible window (MainWindowHandle = 0), so
users only saw the OpenClaw node child started by runner.cmd in Task
Manager and got the impression that "the terminal does not pop up".

Wrap the launch in `cmd /c start "OpenClaw Gateway" /D <dir> cmd /D /K
runner.cmd` so the new console is created by the `start` builtin via a
fresh CreateProcess call without inherited stdio. The outer cmd /c
itself is short-lived and uses CREATE_NO_WINDOW so it does not flash a
window, and the inner cmd hosting runner.cmd reliably becomes a normal
visible terminal that the user can close to stop Gateway.

Because the visible terminal is now detached from our process tree,
spawn().id() can no longer be tracked. Instead, poll netstat after the
launch and record the listener PID as the active Gateway PID, which is
what the stop path actually needs to send a precise kill to.

## Verification
- cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check
- cargo check --manifest-path src-tauri/Cargo.toml
2026-05-16 12:07:04 +08:00
晴天
9742786f8c fix(models): normalize invalid primary model 2026-05-16 00:16:39 +08:00
晴天
c318ff3b37 fix(assistant): normalize OpenClaw context path 2026-05-15 23:37:28 +08:00
晴天
e1eda2db55 feat(models): import external client configs
Add a model client import flow that scans local Codex, Claude Code, Gemini CLI, and common environment variable configurations without reading or copying OAuth tokens.

The new backend command returns safe import candidates with provider metadata, model IDs, and API key environment-variable references. Tauri and Web/dev-api both implement the scanner, and Web mode keeps the scan local even when a remote instance is active.

The Models page now offers an import wizard that lets users select importable candidates, adds providers without overwriting existing keys, preserves secrets as ${ENV_VAR} references, and leaves OAuth-only Codex entries as guidance rather than direct OpenClaw imports.

## Verification
- node --check src/pages/models.js
- node --check src/lib/tauri-api.js
- node --check src/locales/modules/models.js
- node --check scripts/dev-api.js
- cargo fmt --check
- cargo check
- npm run build
2026-05-15 23:18:50 +08:00
晴天
f411386ab5 fix(ui): polish docker and model configuration views
Treat an unavailable local Docker socket with no containers as an optional Docker management capability instead of showing a meaningless offline default node.

Improve the model configuration layout for large model/fallback lists by wrapping controls, truncating long IDs safely, capping collapsed fallback chips, and making the fallback editor responsive.

Guard chat message insertion against route changes so async history/hosted output cannot insert into an unloaded chat DOM.

## Verification
- node --check src/pages/models.js
- node --check src/pages/services.js
- node --check src/pages/chat.js
- node --check src/locales/modules/services.js
- npm run build
2026-05-15 21:01:27 +08:00
晴天
322bf1a0a6 fix(dashboard): avoid startup skeleton deadlock
Delay heavy dashboard requests until after the first stat-card render so slow version checks, agent scans, MCP reads, backups, channel discovery, or log tail reads cannot occupy the backend before the initial paint.

Add a 1.2s first-paint fallback that replaces skeleton cards with safe unknown-state cards and logs a warning when dashboard APIs are still pending.

## Verification
- npm run build
2026-05-15 20:48:14 +08:00
晴天
2f7cd6d429 perf(ui): speed up dashboard startup rendering
Render the dashboard first wave without waiting for get_version_info, which may spawn CLI work and query the registry. Version data now updates the stat cards asynchronously after the core service/config data is shown.

Also shorten the desktop Gateway port probe before WebSocket connection from 20s/2s polling to 3s/300ms polling, relying on the WebSocket reconnect path instead of blocking startup for a long time.

## Verification
- npm run build
2026-05-15 20:15:06 +08:00
晴天
c592f47217 fix(windows): harden gateway terminal runner
Use a generated clawpanel-gateway.cmd runner for the visible Windows Gateway terminal instead of embedding the resolved CLI path directly in the cmd /K command string. This keeps the simple visible-terminal model while improving quoting behavior for standalone, npm shim, and .js CLI paths.

## Verification
- cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check
- cargo check --manifest-path src-tauri/Cargo.toml
- cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
- npm run build
2026-05-15 19:58:15 +08:00
晴天
807a1d87a9 fix(windows): launch gateway in visible terminal
Windows users can now start OpenClaw Gateway in a normal terminal window instead of relying on a hidden background process. Keeping the terminal open keeps Gateway running; closing it stops Gateway.

The backend guardian also treats a closed Windows Gateway terminal as an intentional stop, so it will not immediately pop a new terminal back up after the user closes it. macOS and Linux keep their existing startup behavior.

## Verification
- cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check
- cargo check --manifest-path src-tauri/Cargo.toml
- cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
- npm run build
2026-05-15 19:51:52 +08:00
晴天
6af9819b89 fix(ui): sync latest upgrade copy in locale bundles
The runtime locale JSON bundles still contained the older About/Services policy copy even after the module-level strings were updated, so the About page could continue to show the old "recommended only" message in Chinese.

This syncs the zh-CN/en locale bundles with the latest-upstream upgrade copy and mirrors the suffixed OpenClaw version comparison fix in the Web dev API.

## Verification
- npm run build
- node --check scripts/dev-api.js
- cargo check
2026-05-15 19:31:44 +08:00
晴天
dcafd29e51 fix(openclaw): allow upgrading kernel to latest patch
OpenClaw Chinese edition has advanced to 2026.5.12-zh.2 while the panel still recommended 2026.5.12-zh.1 and treated same-base zh patch versions as equivalent.

This updates the recommended Chinese kernel target and makes the version comparison detect suffix-level upgrades such as zh.1 -> zh.2 when both sides expose suffixes. It also adds explicit latest-upstream upgrade actions on the Services and About version cards so users can upgrade to the latest detected upstream version without going through the manual version picker.

## Changes
- update OpenClaw Chinese recommended target to 2026.5.12-zh.2
- detect same-base suffixed patch upgrades in version info
- add Services page "upgrade to latest" action and confirmation copy
- add About page latest-upstream action

## Verification
- npm run build
- cargo check
2026-05-15 19:08:42 +08:00
晴天
7f078a3c49 fix(ui): import term tooltip helpers in config pages
Gateway 配置页和模型配置页都会在 label 中渲染术语帮助按钮,并在页面渲染后调用 tooltip 绑定逻辑。

这两个页面之前引用了 termHelpHtml / attachTermTooltips,但没有导入对应 helper,导致 Gateway 配置页加载配置后直接抛出 ReferenceError,页面停在加载失败状态。

## 修复
- Gateway 配置页补充 term-tooltip helper import
- 模型配置页补充同一 helper import,避免进入编辑服务商弹窗时触发同类运行时错误

## 验证
- npm run build:PASS
2026-05-15 18:54:13 +08:00
晴天
8b690cb6a7 fix(openclaw): update recommended kernel target and delta protocol handling
面板原本只声明 maxProtocol=3 的握手范围,且 chat delta 处理只接受前缀扩展。
连接到 2026.5.12+ 内核时会出现握手兼容问题;agent 输出在内容回滚或重排场景也会
丢失最新文本,前端 streaming 气泡停留在旧前缀。

## Connect 握手协议范围
- 将桌面端 connect frame 的 maxProtocol 从 3 扩展到 4
- 将 Web 模式 connect frame 的 maxProtocol 从 3 扩展到 4
- minProtocol 继续保留 3,用于兼容历史内核
- 范围 [3, 4] 同时覆盖旧内核和 5.12+ 内核,不需要按版本分支

## Chat delta replace 语义
- 当 payload.replace=true 时,无条件覆盖当前 AI 文本缓存
- 保留前缀扩展场景的增量更新逻辑
- 修复内容回滚或重排时文本长度变短导致 UI 不刷新的问题

## 推荐内核版本
- official 推荐目标升到 2026.5.12
- chinese 推荐目标升到 2026.5.12-zh.1
- KERNEL_FLOOR 不变,继续兼容历史安装

## 验证
- cargo check:PASS
- npm run build:PASS
2026-05-15 18:30:04 +08:00
晴天
0b4ef11971 fix(hermes): restore gateway message sending dependencies
Hermes Gateway 能启动但消息发送链路仍可能不可用:工具安装环境缺少运行时依赖,
同时自定义端点场景下 provider 字段可能被省略,导致消息路由继续落到错误 provider。

## 补齐运行时依赖
- uv tool install 固定带上 HTTP 客户端依赖
- 补齐 OpenAI SDK 依赖
- 补齐 Gateway HTTP server 所需 aiohttp
- 补齐 browser/dialog 等工具链会触发的 websocket 依赖
- 安装日志同步展示完整安装参数,便于用户排查环境问题

## 明确 custom provider
- 自定义 base_url 时显式写入 provider: custom
- 如果 model 段已有 provider,则覆盖为 custom
- 如果 model 段缺少 provider,则补写 provider: custom
- 继续保留 OPENAI_API_KEY alias 自愈,确保辅助客户端和主流程读取同一份凭证

## 范围
- 桌面 Tauri 安装与配置自愈逻辑
- Web dev API 安装与配置自愈逻辑
2026-05-15 17:32:56 +08:00
晴天
583f5401ac fix(hermes): stabilize gateway provider routing and startup
Hermes Gateway 之前容易出现两类问题:配置里的 provider 与 base_url 不一致,
以及多个启动入口并发触发 Gateway start,导致日志里反复出现启动流程,运行状态也不稳定。

## Provider / base_url 自愈
- 归一化 provider URL(去掉尾斜杠和常见 API path 后缀)
- 当 openrouter provider 搭配自定义 base_url 时,自动切换为 custom provider
- 在读取配置、写入模型配置、启动 Gateway 前都执行一次自愈
- Web 模式同步实现相同逻辑,避免桌面端和浏览器端行为不一致

## API Key 别名兼容
- custom provider 优先读取 OPENAI_API_KEY
- 当 .env 只有 CUSTOM_API_KEY 时,自动补齐 OPENAI_API_KEY
- 避免辅助客户端读取不到凭证或落到错误 provider

## Gateway 启动互斥
- 增加 Gateway start guard,串行化启动流程
- 如果已有启动流程在进行中,直接复用健康检查结果
- 避免重复 Gateway 进程、重复日志和竞态状态覆盖

## 范围
- 桌面 Tauri 命令
- Web dev API 运行时
- Hermes provider 注册表
2026-05-15 17:32:38 +08:00
晴天
2256c2c711 fix(audit): 复查第三波 — Hermes 安装向导 i18n 化漏网中文
第三轮逐文件复查发现 hermes/pages/setup.js 还有大量用户首屏可见的
中文硬编码(之前两轮主要扫 timer/cleanup/syntax/listener 类 bug)。

## 涉及修复

### setup.js phase 标签(5 处)
phase indicator 是用户最早看到的 UI 元素:
```
{ id: 'detect',    label: '检测' }   →  t('engine.hermesPhaseDetect')
{ id: 'install',   label: '安装' }   →  t('engine.hermesPhaseInstall')
{ id: 'configure', label: '配置' }   →  t('engine.hermesPhaseConfigure')
{ id: 'gateway',   label: '启动' }   →  t('engine.hermesPhaseGateway')
{ id: 'complete',  label: '完成' }   →  t('engine.hermesPhaseComplete')
```
英文用户原本看到的是 5 个汉字,现在按系统语言显示
Detect / Install / Configure / Start / Complete。

### setup.js Provider 分组标题(4 组)
configure 阶段渲染 provider 选择按钮时分组标题:
```
'国际 · API Key'        →  hermesProviderGroupIntl
'国内 · API Key'        →  hermesProviderGroupCn
'聚合 / 路由'           →  hermesProviderGroupAggregator
'OAuth 登录(需终端)'   →  hermesProviderGroupOAuth
'<provider>:需运行'     →  hermesProviderOAuthRunHint
```

### setup.js Provider 加载失败 fallback 文案
Web 模式下 provider 列表加载失败时的 12 行多行 hint,原本只有中文,
现已 11 语言 i18n。

### setup.js 检测异常日志 prefix
```js
logs.push(`检测错误: ${e}`)  →  `[detect error] ${e?.message || e}`
```
日志输出(用户在向导首屏可见)跟其他英文 prefix 一致,便于日志聚合。

## 新增 i18n key(engine 模块)
- hermesPhaseDetect / Install / Configure / Gateway / Complete
- hermesProviderGroupIntl / Cn / Aggregator / OAuth
- hermesProviderOAuthRunHint
- hermesProvidersLoadFallback

11 语言全覆盖(zh-CN / zh-TW / en / ja / ko / vi / es / pt / ru / fr / de)。

## 复查范围(第三轮)
- src-tauri/src/commands/*.rs  unwrap 全 safe(regex / SystemTime / Mutex)
- src/lib/  humanize-error / engine-manager / ws-client / message-db / tts 全部健全
- src/components/  modal / toast 无问题
- scripts/dev-api.js  176 个 handler 无 duplicate / 关键命令全有
- src/pages/  没发现新硬编码(之前的 i18n 覆盖到位)
- src/engines/hermes/pages/  setup.js 是唯一遗漏的中文重灾区,已修

## 验证
- npm run build:PASS(1.46s)
- 总计三轮复查共修 11 个 bug + 5 个 i18n 漏网
2026-05-14 07:41:01 +08:00
晴天
d30714d406 fix(audit): 复查第二波 — alert/confirm 统一 + chat.js 潜伏语法 bug
## Bug #6 — chat.js 末尾多余 `}` 让 Vite build 卡死
src/engines/hermes/pages/chat.js

文件结尾 line 1635 已经闭合 render 函数,但 line 1636 多了一个孤立的
`}`,导致 esbuild 解析陷入回溯(build 卡在 transforming 47 modules
不退出)。删除多余括号后 build 1.55s 通过。

## Bug #7 — alert() / window.confirm 跨平台行为不一致
- src/engines/hermes/pages/setup.js  3 处 alert()
  · API Key 必填校验
  · 配置保存失败
  · Gateway 启动失败 fallback
- src/engines/hermes/pages/memory.js  2 处 window.confirm
  · 关闭未保存编辑窗
  · 取消编辑前的确认
- src/engines/hermes/pages/env-editor.js  1 处 confirm()
  · 删除环境变量

Tauri webview 在 macOS / Windows / Linux 三平台对原生 alert/confirm
处理不同(macOS 需要 webview 设置 navigation_handler,部分场景直接
忽略)。统一改用项目 components/toast + components/modal.showConfirm,
保证跨平台一致 + 可被样式化。

涉及函数 closeWithConfirm 改为 async 以等待 showConfirm Promise。

## Bug #8 — gateways.js 错误显示与其他页面风格不一致
src/engines/hermes/pages/gateways.js

`error = String(e?.message || e)` 直接显示 raw error 字符串,与
profiles/kanban/oauth 三个最近升级的页面不一致。改用 humanizeError
让用户看到友好提示 + 可折叠原始错误详情。

emoji ⚙️ 设置图标改用项目 svg-icons.js 的 settings SVG,与 Bug #5
emoji→SVG 重构保持一致。

## 验证
- npm run build:PASS(1.55s)
- 受影响场景:
  - 安装向导 API Key 缺失提示(toast 而不是阻塞 alert)
  - 记忆页未保存确认(modal 而不是平台原生 confirm)
  - 环境变量删除确认(modal 而不是平台原生 confirm)
  - 多 Gateway 加载失败错误展示(友好 + 可展开技术详情)
2026-05-14 07:24:47 +08:00
晴天
b9a7c043d2 fix(audit): 复查发现的 5 个 bug — 双 listener / duplicate stub / timer leak / i18n
逐项排错盘点(OpenClaw + Hermes 引擎 + 共享层全面复查)。

## Bug #1 — Hermes 引擎双 listener 数组混用
src/engines/hermes/index.js

onStateChange / onReadyChange 共用一个 `_listeners` 数组:
```js
onStateChange(fn) { _listeners.push(fn); ... }
onReadyChange(fn) { _listeners.push(fn); ... }   ← 同一数组
```

main.js 注册 sidebar 渲染回调时两个都注册:
```js
_engineStateUnsub = engine.onStateChange(() => renderSidebar(sidebar))
_engineReadyUnsub = engine.onReadyChange(() => renderSidebar(sidebar))
```

结果:每次 detectHermesStatus(15s 一次 poll)触发,sidebar 被
renderSidebar 调两遍。OpenClaw 引擎用的 lib/app-state.js 早就是分开
两个数组(_gwListeners + _listeners),Hermes 是退化实现。

修复:
- 拆成 _stateListeners / _readyListeners 两个数组
- 加 prevReady / prevRunning 做 diff,仅在状态实际变化时通知

## Bug #2 — Web 模式下 check_panel_update 永远返回 false
scripts/dev-api.js

`check_panel_update` 在 line 6785 有完整实现(fetch GitHub/Gitee
release API),但 line 8586 又 stub 了一次:
```js
check_panel_update() { return { hasUpdate: false } }
```

Object literal 后定义覆盖前定义,Web 模式下用户永远看不到「有新版」
提示,必须升级到桌面客户端才能查更新。

修复:删掉重复 stub,留注释说明真实实现位置。

esbuild 之前就在 build 里 warn `Duplicate key "check_panel_update"
in object literal`,现在 warning 也消失了。

## Bug #3 — engine-select.js setTimeout 不在 cleanup 时清
src/pages/engine-select.js

choose 动画里两个 setTimeout (600ms + 1300ms) 没保存 id,路由
cleanup 时不清。极端情况:用户点完 OpenClaw 后立刻点 secondary
"稍后再说" → 1300ms 后被强制 navigate 回原 targetRoute,把用户拉走。

修复:
- 模块级 _animTimers 数组追踪所有动画 setTimeout id
- cleanup 时 clearTimeout 全清
- stage.dataset 防御性访问(路由切走后 stage 可能已不在 DOM)

## Bug #4 — router.js 三处中文硬编码
src/router.js

之前页面 loading / 加载失败 / 重新加载按钮直接写死"加载中..."
"页面加载失败" "重新加载"。i18n 里 `common.loading` /
`common.pageLoadFailed` / `common.reloadRetry` 早已存在但没用上。

修复:import t() + 三处替换。

## Bug #5 — dashboard.js Promise 超时 Error 写死中文
src/pages/dashboard.js

`new Error(\`超时(${ms/1000}s)\`)` — 这条错误最后被 humanizeError
处理后展示给用户,本来应该走 i18n。改成英文 `Timed out after Xs`,
统一与日志聚合(其他地方都是英文)。

## 验证
- npm run build:PASS(1.85s, 无 duplicate-key warning)
- cargo fmt --check:PASS(无改动)
- 受影响场景:
  - 引擎切换 sidebar 性能(Hermes 双重渲染消失)
  - Web 模式更新提示(恢复正常)
  - engine-select 动画中途切走(不再被强拉回)
  - 加载/错误页(i18n 完整)
2026-05-14 07:17:28 +08:00
晴天
c264224e7c fix(rust): 修 CI 失败的 4 个 clippy + fmt 问题
之前 3 个 commit (cc19a07/1873e23/d97e196/6a0d874/bf55ca0) 推上去后 CI
全部 fail(三平台都 fail),原因不是 JS 改动而是历史 Rust 代码累积的
clippy/fmt 检查问题,本次一次性修齐让 CI 重新跑通。

## 4 个 clippy errors(-D warnings 模式)
1. `field 'name' is never read` (HermesAttachment::name)
   - 该字段是前端可选传入的附件原文件名,目前后端未消费
   - 加 #[allow(dead_code)] + 说明性 doc,保留字段供后续展开附件清单 UI

2. `unnecessary closure used to substitute value for Result::Err` × 2
   - hermes_dashboard_api_proxy 里
     `unwrap_or_else(|_| Value::String(body))` →
     `unwrap_or(Value::String(body))`
   - 闭包捕获 _ 但不用,直接用 unwrap_or

3. `manually reimplementing div_ceil`
   - base64_encode 里 `(bytes.len() + 2) / 3` →
     `bytes.len().div_ceil(3)`(标准库 1.73+ 支持)

## fmt 修复
hermes.rs 多处长 match arm + long argument list 不符合 rustfmt 默认风格,
跑 cargo fmt --all 自动修齐。

## 验证
✓ cargo fmt --all -- --check  PASS
✓ cargo check                 PASS
✓ cargo clippy --all-targets -- -D warnings  PASS(无 warning 无 error)

## 影响
- 不改变任何运行时行为
- 不影响前端 / 不影响 i18n / 不影响 Tauri 命令签名
- 纯代码风格 + lint 修复
2026-05-14 06:54:13 +08:00
晴天
bf55ca0135 refactor(hermes): emoji → SVG 图标,统一视觉语言
## 问题
新加的 hermes 页面里到处是 emoji(⚠️📁📋💬🔐🔗🖼️📝⚙️📦💬🔊🎙🔍✓ 等),
不同 OS 渲染样式差异大(macOS Apple Color Emoji vs Windows Segoe UI Emoji
vs Linux Noto Color Emoji),看着很不专业,也跟现有 SVG 图标系统割裂。

## 方案
新建 src/engines/hermes/lib/svg-icons.js — 集中所有需要的 SVG path:
  状态:alert-triangle, check, x, check-circle, x-circle, info
  文件:folder, folder-up, file, file-text, image, link-2, settings
  列表:clipboard-list, message-square, inbox
  安全:lock, shield, key
  媒体:volume, mic, search

导出 svgIcon(name, opts) — 渲染时统一 viewBox / stroke-width / currentColor,
size 可选覆盖。

## 替换覆盖(7 个页面 + 2 处 CSS)
- profiles.js:    ⚠️ → alert-triangle, 📁 → folder
- kanban.js:      ⚠️ → alert-triangle, 📋 → clipboard-list, 💬 → message-square
- oauth.js:       ⚠️ → alert-triangle, 🔐 → lock
- group-chat.js:  ⚠️ → alert-triangle
- logs.js:        ⚠️ → 纯文本 [ERROR](log raw 字段不需要图标)
- files.js:       📁🔗🖼️📝⚙️📄 全部 → folder/link-2/image/file-text/settings/file
                  ".." → folder-up
- lazy-deps.js:   7 个 category emoji → message-square/volume/mic/search/
                  shield/inbox/image
                  ✓ 装好标识 → check svg
                  📦 empty → inbox

CSS 适配:
- .page-inline-error-icon: 加 inline-flex + 自动 currentColor,svg 20×20
- .empty-state .empty-icon: 加 inline-flex + svg 跟 font-size 走(1em)

## 范围控制
本次只处理我最近 3 个 commit 里新加的页面 emoji。
历史代码(setup.js / services.js / chat.js 的 ✓ 等)暂不动 — 那是
跨工作流的复杂改动,单独评估再做,避免连锁影响 CI。

## 验证
✓ npm run build PASS(1.80s)
2026-05-14 06:50:14 +08:00
晴天
d97e196a48 fix(chat): 会话列表无区分度,让用户误以为是 mock 数据
## 问题
用户截图:会话列表 20+ 项全部显示「新对话 / QC-505 / 5月8日」,
完全相同,怀疑是写死的 mock 数据。

## 真相
数据其实是真实的(hermes sessions export 输出的 JSONL):
- title 全空 → fallback 到 t('chatNewSession') = "新对话"
- 用户用同一模型 QC-505 跑了所有会话
- 用户在同一天(5月8日)跑的
- 视觉冗余 100% → 看起来像写死

## 修复(让真实数据显示出区分度)
1. sessionDisplayTitle: 没标题时不再统一显示「新对话」,
   改为「未命名 · {id 后6位}」(每个会话不同后缀,一眼能区分)
2. meta 行加 messageCount —— 显示「N 条」让每个会话有自己的数字
3. 加 i18n 键 chatUntitledSession + chatSessionMsgCount(11 语完整)

## 效果
之前:
  新对话
  QC-505 5月8日
  新对话
  QC-505 5月8日
  ... × 20

之后:
  未命名 · a3f2b8
  QC-505 5月8日 28 条
  未命名 · 7c91d4
  QC-505 5月8日 14 条
  未命名 · e082f1
  QC-505 5月8日 3 条
  ...

用户立刻能看出每个会话是真实独立的,不会再怀疑 mock。
2026-05-14 06:40:41 +08:00
晴天
1873e23371 fix(critical): 5 个用户报告的真 bug 一次性修
## P0 Bug 1: chat.js `t is not defined` (页面渲染崩溃)
- chat.js 用了 30+ 处 t() 但**完全忘记 import i18n**
- 路由进入 /h/chat 立即抛 ReferenceError,整页渲染失败
- 修复:补 `import { t } from '../../../lib/i18n.js'`

## P0 Bug 2: i18n 嵌套对象解析失败 (所有 humanizeError 显示原始 key)
- 用户截图:`common.errorHint.generic` 直接显示而非翻译
- 根因:buildLocales() 不递归嵌套对象,把 common.error / errorHint /
  errorAction 这种嵌套 dict 当作 _() 翻译对象处理 →
  `result[lang].common.errorHint = 'errorHint'` (字符串)
- 影响:t('common.errorHint.network'/auth/timeout/...) 全部返回 key 自身
  → toast 用 humanizeError 的所有页面都看到原始 key
- 修复:buildLocales 加 _isTranslationObject + _materialize 递归

## P0 Bug 3: dev-api.js `Dynamic require of "node:fs" is not supported`
- 用户截图:文件管理器红字 "Dynamic require of node:fs"
- 根因:我之前在 hermes_fs_* / _validateFsPath / _hermesHome 用了
  `const fs = require('node:fs')` —— 但 Vite 插件运行在 ESM 上下文,
  CommonJS dynamic require 不支持
- 修复:删除所有 require('node:fs/path/os') —— 文件顶部已经 ESM
  `import fs from 'fs'` 等,直接复用即可

## P1 Bug 4: profiles/kanban/oauth fetch failed 错误丑陋
- 用户截图:3 个页面都显示红字 "fetch failed"(无任何上下文)
- 修复 1(dev-api.js):hermes_dashboard_api_proxy 把 ECONNREFUSED /
  fetch failed 转成 "Hermes Dashboard 未运行(端口 9119 无服务)。
  请在桌面端 ClawPanel 启动 Hermes Agent" — 含「未运行」关键字会被
  humanizeError 的 gatewayDown 正则匹配
- 修复 2(3 个页面):用 humanizeError 拆出 message + hint + 折叠 raw,
  渲染统一的 .page-inline-error 卡片(icon + 主行 + 副提示 + 技术详情
  details)。错误对象不再 stringify,保留给 humanizeError 完整识别

## P2 Bug 5: 重复的 require('node:path') in hermes_fs_list
- 同 Bug 3,循环里又 require 了一次 path,现已用顶部 import

## 影响面
 /h/chat 不再崩溃,profile switcher 正常显示
 所有用 humanizeError 的 toast 看到正确翻译(不再显示原始 key)
 文件管理器 Web 模式正常列出 ~/.hermes 目录
 Profile / Kanban / OAuth 显示「Hermes Dashboard 未运行」+
   「请前往仪表盘启动 Gateway 后重试」+ 折叠原始错误(点击展开)
 npm build pass
2026-05-14 06:35:20 +08:00
晴天
6a0d87479c feat(engine-select): Monolith 对角线全屏选择屏
替换原卡片网格为「左上 OpenClaw(石墨黑)vs 右下 Hermes(象牙白)」对角线全屏设计。
启动屏 / 引擎切换时给用户一个有冲击力的「选择时刻」。

## 核心设计
- position: fixed 跳出 #content 范围,覆盖整个 viewport(含 sidebar)
- 双三角形 clip-path: polygon (0 0, 0 100%, 100% 0) / (100% 0, 100% 100%, 0 100%)
- 内容用 absolute 定位到三角形质心(左上 11%/6.5% / 右下 11%/6.5%),永不重叠
- 微妙的 60px 网格纹 + 角落极光(紫蓝 / 橙金)+ 极细中线分割
- clamp(80px, 13vw, 200px) 巨字标题 + 序号 + Logo + tagline + 特性列表 + CTA

## 交互
- hover 联动:用 [data-hover] attribute 替代 :has(),兼容旧 WebKit
  - 鼠标悬停一侧 → 该侧亮起 + 内容平移 + CTA 反白;另一侧变暗 + 内容模糊缩小
- 点击三角形 → 三角形 clip-path 扩满(0.8s)→ 中心圆扩散(0.9s)→ 进入主页
- reveal 节点 attach 到 body,跨路由切换存活,新页面渲染后再淡出

## Both / Later 处理
- 两个次级选项保留,做成底部居中的玻璃 pill 链接(不抢戏)
- 不走对角线扩散动画,点击后直接 applyEngineSelection + navigate

## 兼容性
- prefers-reduced-motion: reduce → 关闭所有动画
- 移动端响应式:< 760px 调整字号 / 边距 / 角标
- 用 Vite define 注入的 __APP_VERSION__ 显示版本号(与 main.js / sidebar.js 一致)

## i18n
- engine.choiceTopBanner / choiceCtaEnter
- choiceOpenclaw{Tagline,Feat1,Feat2,Feat3,Category}
- choiceHermes{Tagline,Feat1,Feat2,Feat3,Category}
- choiceSecondary{Both,Later}
- 三语完整(zh-CN / en / zh-TW)

## 抽卡 prototype
保留 docs/engine-select-mockups/ 下的 V2 4 张设计 + 索引页(v2-monolith.html
即本次接入的最终版本)。
2026-05-14 06:22:32 +08:00
晴天
cc19a07999 fix(hermes): sidebar 缺失 group-chat / files 入口 + 补 folder 图标
复查发现 2 个真 bug:路由注册了但 sidebar 没入口,用户点不到。

## §N 群聊 - sidebar 缺入口
- /h/group-chat 已注册路由(debce2f),但 sidebar monitor section 没加
- 修复:紧跟 /h/chat 之后插入「群聊」(agents 图标)

## §L 文件管理器 - sidebar 缺入口
- /h/files 已注册路由(129d8c0),但 sidebar manage section 没加
- 修复:紧跟 /h/oauth 之后插入「文件管理器」(folder 图标)

## sidebar.js 补 folder 图标
- ICONS 表里没有 'folder',导致 /h/files sidebar 项无图标(fallback 到空字符串)
- 加进去(24x24 viewBox 的标准 folder svg)

## 复查覆盖
✓ 61 个 hermes_* Tauri 命令全部在 lib.rs invoke_handler 注册
✓ 63 个 invoke('hermes_*') 调用在 dev-api.js 都有 fallback (除 hermes_agent_run_stream 走 webStream)
✓ 6 个新页面所有 t('engine.*') 键在 engine.js 都有定义(含 zh-CN/en/zh-TW 三语)
✓ 19 个 Hermes 页面文件齐备(最大 chat.js 1626 行,最小 channels.js 占位 17 行)
✓ cargo check 干净(仅 1 个 unused field 警告,不影响)
✓ npm build ✓
2026-05-14 05:53:32 +08:00
晴天
6b118877f1 refactor(hermes): UX 小白化最后一波 - skills/memory/cron 升级 humanizeError
完成 Hermes 引擎所有页面的统一错误处理改造。

## skills.js (2 处)
- 加载技能失败 → humanizeError(e, t('engine.skillsLoadFailed'))
- 启用/禁用切换失败 → humanizeError(e, t('engine.skillsToggleFailed'))

## memory.js (1 处)
- 保存记忆失败 → humanizeError(e, t('engine.memorySaveFailed'))

## cron.js (2 处)
- 加载定时任务失败 → humanizeError(e, t('engine.cronLoadFailed'))
- 保存定时任务失败 → humanizeError(e, t('engine.cronSaveFailed'))

## i18n 新增 2 键
- cronLoadFailed / cronSaveFailed × 3 语言

## 现在 Hermes 全部 16 个页面均统一了错误处理
统计:sessions / extensions / env-editor / usage / lazy-deps / config /
       skills / memory / cron / chat / setup / dashboard / services /
       profiles / kanban / oauth / files / gateways / group-chat

剩余原始 raw error 显示的位置都是合理的:
- dashboard.js install modal 日志 — 用户在看安装日志,raw 信息有用
- setup.js fetch 模型 — 已分类处理 403/404/timeout
- chat.js RUNNING_SESSION 错误 — 已特殊处理
2026-05-14 05:46:44 +08:00
晴天
dcac1d6d21 refactor(hermes): UX 小白化收尾 - 6 个页面统一 humanizeError + 修 usage.js bug
把 raw error toast 升级为 humanizeError,让用户看到分类友好的错误提示
(自动加 action button 如「打开设置」「重试」「查看文档」)。

## sessions.js (5 处)
- 加载会话失败 → humanizeError(err, t('engine.sessionsLoadFailed'))
- 详情加载失败 → humanizeError(err, t('engine.sessionsDetailLoadFailed'))
- 切换会话失败 → humanizeError(err, t('engine.sessionsSwitchFailed'))
- 删除失败 → humanizeError(err, t('engine.chatDeleteFailed'))
- 导出失败 → humanizeError(err, t('engine.sessionsExportFailed'))

## extensions.js (5 处)
- dashboard 打开失败 (probe / start) - 2 处
- 主题保存失败 → humanizeError(err, t('engine.extensionsThemeSaveFailed'))
- 加载扩展失败 → humanizeError(err, t('engine.extensionsLoadFailed'))
- 重新扫描失败 → humanizeError(err, t('engine.extensionsRescanFailed'))

## env-editor.js (4 处)
- reveal / save / delete / load 错误 → humanizeError
- 文案保留 inline 中文(页面尚未 i18n 化)

## usage.js (1 bug + 1 升级)
- **修真 bug**: catch (_) {...} 内部却用了 err 引用 → 改为 catch (err) {...}
- 失败时 humanizeError(err, t('engine.usageLoadFailed'))

## lazy-deps.js (1 处)
- features API 失败显示 humanizeError + escapeHtml

## config.js (2 处)
- 加载配置失败 → humanizeError(err, t('engine.hermesConfigLoadFailed'))
- 保存配置失败 → humanizeError(err, t('engine.hermesConfigSaveFailed'))

## hermes.css 修 lint
- .hm-kanban-task-summary 加 line-clamp 标准属性(不只是 -webkit-line-clamp)

## i18n 新增 8 键
- sessionsLoadFailed / sessionsSwitchFailed / usageLoadFailed
- extensionsThemeSaveFailed / extensionsLoadFailed / extensionsRescanFailed
- hermesConfigLoadFailed / hermesConfigSaveFailed
- 全部 × 3 语言

## 累计影响
- 6 个 hermes 页面(sessions/extensions/env-editor/usage/lazy-deps/config)统一错误处理
- 修 usage.js 1 个真 bug(catch 参数名错配)
- 8 个新 i18n × 3 语言
- 1 个 CSS lint 警告修复
- npm build ✓
2026-05-14 05:45:33 +08:00