Files
toor 01d9e301fc
Build / build (push) Canceled after 0s
Playwright / browser (push) Canceled after 0s
initialize Electron desktop app and remove unused files
2026-10-25 01:09:01 +08:00

38 KiB
Raw Permalink Blame History

hermes-ui 施工蓝图

基线:hermes-web-ui 0.6.29(只读参考 /Users/jack/source/OtherProjectTemp/ekko-studio) 目标仓库:/Users/jack/source/MyProject/js/hermes-ui 本文所有"现状"结论均来自源码核实,行号为 0.6.29 快照。


0. 已确认决策

项 结论
包名 / 产品名 hermes-ui
前端 Vue 3 + <script setup lang="ts"> + Vite + Pinia + Vue Router(hash),全部沿用
后端 Koa 2 + TS + Socket.IO + better-sqlite3,全部沿用
实时通信 Socket.IO /chat-run 命名空间协议不变
UI 组件库 Naive UI 全量换 Arco Design Vue,不双库共存
特效层 ArkScope vendor 进项目,只做装饰性位面
桌面端 砍掉 packages/desktop 整包
官网 砍掉 packages/website
内置 skills 保留 packages/skills
IM 频道 保留(K 组多平台 + gateway 守护)
i18n 只保留 zh + en
品牌 不得出现 ekko / hermes-web-ui 字样;Hermes 保留
配置层标识 保留 HERMES_WEB_UI_* 前缀与 ~/.hermes-web-ui 数据目录
依赖 lockfile 不升级(版本不升,只做增删)
砍除功能组 I 组(Claude Code / Codex 编程代理)、J 组(ekko-agent)、P 组(Petdex)、Electron

1. 源码核实结论(与 01/02 文档有出入的地方)

以下 6 条与文档不一致,以本节为准。

1.1 这不是 npm workspaces

根 package.json 没有 workspaces 字段。packages/client 与 packages/server 都不含自己的 package.json,它们的依赖统一写在根 package.json 里。只有 packages/desktop 与 packages/ekko-agent 各自有 package.json + package-lock.json。

影响:01 文档"删除根 workspace 引用"这条不存在。实际要动的是根 package.json 的 scripts、bin、dependencies。

1.2 gateway 自启不需要迁移

01 文档称"gateway 自启原逻辑有一半长在 Electron 生命周期里,需迁到 Koa 启动钩子"。实际不需要迁:

  • index.ts 的 startRuntimeServicesBeforeListen() / startRuntimeServicesAfterListen() 里已经调用了 ensureProfileGatewaysRunning()。
  • Electron 侧只有 gateway-autostart.ts 的 shouldRecoverWindowsDesktopGatewayOrphans() 依赖 HERMES_DESKTOP,以及一个注册在 /api/desktop/shutdown 的关闭回调(index.ts)。

结论:自启逻辑零迁移成本。只需删掉 HERMES_DESKTOP 分支。

1.3 文档漏了两个包

包 内容 处置
packages/website 独立 vite 配置 + tsconfig.website.json + website-deploy.yml workflow + public/docs/ 静态文档站 砍掉
packages/skills 5 个内置 skill,被 skill-injector.ts 注入给 hermes 的 skills/ 目录 保留

两者都不在 02 文档的 A~Q 分组里。

1.4 I/J 组可安全砍除

run-chat/index.ts 的 dispatch 结构:

ensureBridgeReadyForChatRun() → handleBridgeRun() → return    // 主路径,先执行
if (isEkkoAgentExecution(data)) → handleEkkoAgentRun() → return  // J 组
→ handleCodingAgentRun()                                        // I 组兜底
  • bridge 分支在最前且有 return,砍掉后两条分支不影响它。
  • 全局 Agent 走 global-agent/server.ts → getChatRunServer().runAndWait(),砍 I/J 后自动只走桥。
  • 群聊走 group-chat/agent-clients.ts 的 AgentBridgeClient 直连,与 I/J 无关。

唯一要动的是 isCodingAgentExecution 的返回值语义(见 §5.4),它同时被 L289 / L423 / L707 用于 state.isWorking 判定。

1.5 Naive UI 渗透面是 100 个文件

02 文档说"27 个视图"。实际 from 'naive-ui' 共 105 处,分布在 100 个文件,包含 components/、stores/、composables/ 下的深层文件,不只是视图层。必须先建映射表,不能盲改。

1.6 ArkScope 的真实形态

triplepiers/ArkScope("对鹰角 UI 组件的拙劣复现",Vue 3 + Vite,无 LICENSE,仓库 322MB):

src/components/
  ScrambleTitle/          文字解码动画(塞壬唱片)—— 最轻,纯装饰 ✅
  IsometricWave/          类亚克力 hover 交互 —— 中等 ✅
  Particle2D/ Particle3D/ 点云斥力/旋转效果 —— 重,含 3D 模型 ❌ 风险高
  EndfieldSidebar/        悬停展开侧边栏 —— 与 AppSidebar 冲突 ❌
  OperatorCarousel/       干员轮播 —— 强依赖游戏素材,脱离素材无意义 ❌
  ArknightsMedia/ GameplayAlbum/ NoticeCarousel/ FullScreenNavigator/ NewsList/
    Endfield/*/ EndfieldSidebar/   全部强绑定游戏素材 ❌

结论:只有 ScrambleTitle 和 IsometricWave 是真正可脱离素材复用的装饰位面。其余组件的价值在于视觉手法(斜切遮罩、噪点、扫描线、Reveal & Blink),需要抽取手法而非搬运代码。

public/assets/ 下 131 个路径全是鹰角游戏美术资源(角色立绘、Banner、字体)—— 不进项目。


2. 仓库骨架(Phase 0)

2.1 目标结构

hermes-ui/
├── package.json                 # name: "hermes-ui",依赖表裁剪后重写
├── package-lock.json            # 保留旧版,Phase 2 一次性重生成
├── vite.config.ts               # root: packages/client,删除 naive-ui chunk
├── vitest.config.ts
├── playwright.config.ts
├── tsconfig.json / .app.json / .node.json
├── nodemon.json                 # watch 去掉 packages/ekko-agent/src
├── Dockerfile / docker-compose.yml / .dockerignore
├── AGENTS.md                    # 重写(继承硬规则,替换 package 地图)
├── ARCHITECTURE.md
├── DEVELOPMENT.md
├── docs/
│   ├── harness/{README,validation,worktree-runbook,pr-review}.md
│   └── chat-chain-changes/      # 桥接链路变更片段(见 §8.2)
├── packages/
│   ├── client/{public,src,index.html}
│   ├── server/src
│   └── skills/                  # 保留
├── scripts/
│   ├── build-server.mjs
│   ├── generate-openapi.mjs
│   └── harness-check.mjs        # 重写(见 §8.3)
└── tests/{client,server,e2e,shared}

2.2 拷贝来源与排除清单

从旧版拷 packages/client、packages/server、packages/skills、tests、scripts、bin、根配置文件。

明确不拷:

  • packages/desktop(整包)
  • packages/website(整包)
  • packages/ekko-agent(整包)
  • docs/(旧文档,含大量旧品牌;只拷 docs/harness/ 与 docs/chat-chain-changes/README.md)
  • packages/esp32-c3(O 组之外的固件,本项目不做硬件;若你后续要做 ESP32 语音硬件再单独拷)
  • public/coding-agents/ekko-agent.png

2.3 Phase 0 验收

cd /Users/jack/source/MyProject/js/hermes-ui
npm ci --ignore-scripts     # 用旧 lockfile 先装通
npm run build               # 必须过(此时仍含 desktop 相关脚本残留)

风险:package.json 的 prepare 脚本会触发 build("[ -d dist ] || npm run build"),npm ci 时可能自动跑 build。Phase 0 先把 prepare 摘掉,Phase 6 再恢复。


3. 阶段划分总览

Phase 名称 依赖 可独立验收的标志
0 仓库骨架搭建 — npm ci && npm run build 通过
1 去品牌 0 全仓 grep 无 ekko / hermes-web-ui
2 砍 I/J 组 0 聊天仍走桥,编程代理全消失
3 砍桌面端 + 官网 0 无 Electron 依赖,网关自启仍工作
4 ArkScope vendor + 样板页 1,3 登录页有装饰位面,风格基线定稿
5 Arco 迁移:聊天页 4 聊天全流程功能对等
6 Arco 迁移:批量 5 无 naive-ui 残留
7 测试与 CI 收口 2-6 全绿

Phase 4 必须早于 Phase 5/6:先定风格基线,再批量迁移,否则 100 个文件会被改两遍。


4. Phase 1 — 去品牌

4.1 判定标准

最终 grep -ri "ekko\|hermes-web-ui" 在以下范围内必须零命中(packages/、tests/、scripts/、bin/、根配置、.github/):

例外白名单(允许保留旧标识):

  • HERMES_WEB_UI_* / HERMES_WEBUI_STATE_DIR 环境变量名
  • ~/.hermes-web-ui 默认数据目录名
  • localStorage key:hermes_brightness / hermes_style / hermes_locale

这些是你主动决定保留的。改名会破坏已有部署脚本与数据目录。

4.2 改动落点清单

A. 根配置

文件 改动
package.json name → hermes-ui;repository / homepage 指向你自己的仓库或直接删;keywords 去 hermes-web;bin 改名 hermes-ui / hermes-ui-mcp
README.md / README_zh.md 重写
AGENTS.md 重写 package 地图(删 desktop / website / ekko-agent 条目)
ARCHITECTURE.md 删 "Electron desktop distribution" 段落与 Desktop Release Flow 整节
DEVELOPMENT.md L47 "Match existing Naive UI patterns and avoid adding a new UI library" → Arco 表述
Dockerfile / docker-compose.yml 镜像名去旧品牌
.dockerignore / .gitignore 检查旧品牌路径

B. packages/server/src

文件 改动
controllers/update.ts 重点。L12-13 预览目录名 hermes-web-ui-pereview、L365 hermes-web-ui/bin/hermes-web-ui.mjs、L728 pkg?.name !== 'hermes-web-ui'、L935/L1156 User-Agent、L1034/L1046 npm 全局安装 hermes-web-ui@latest、L1065/1076/1092 提示文案。全部改为 hermes-ui
config.ts L65 https://hermes-studio.ai 相关的默认下载地址;保留所有 HERMES_WEB_UI_*
services/hermes/agent-bridge/manager.ts L16 OpenRouter attribution URL(hermes-studio.ai → 你的地址或删)
lib/llm-prompt.ts 3 处 /Users/ekko/Desktop 是原作者本机路径,直接删除该分支逻辑
services/hermes/studio-mcp-autoinject.ts 文件名含 studio;检查内容是否含旧品牌串

C. packages/client

文件 改动
index.html L11 apple-mobile-web-app-title、L13 <title>Hermes Studio</title> → hermes-ui
public/manifest.webmanifest name / short_name
i18n/locales/{zh,en}.ts 所有旧品牌文案(约 5 处/语言,见下)
data/changelog.ts 旧版本更新日志里的品牌串。建议整体清空,从 0.6.29 重新起
public/coding-agents/ekko-agent.png 删除(Phase 2 一并处理)
components/hermes/chat/MessageList.vue 等 grep 命中的显示文案

D. tests/

tests/client/pwa-metadata.test.ts 断言 title/manifest,需同步改。 tests/ekko-agent/ 整目录 Phase 2 删除。

4.3 i18n 收敛到 zh + en

硬规则保留:新增用户可见字符串必须同时写 zh.ts 与 en.ts。i18n-coverage 测试是这个规则的机械保障,不要删。

4.4 Phase 1 验收

grep -ri "ekko" packages/ tests/ scripts/ bin/ .github/ *.json *.md Dockerfile*  # 零命中
grep -ri "hermes-web-ui" packages/ tests/ scripts/ bin/ .github/                    # 零命中
npm run test && npm run build

风险:update.ts 的 npm 全局安装路径改名后,如果你不发布 npm 包,在线更新功能实际不可用。建议 Phase 1 先把"检查更新"入口的文案改成"当前为源码运行",在线更新逻辑保留但标注不可用;等你决定发布方式再接。


5. Phase 2 — 砍 I 组 / J 组

5.1 删除文件

I 组(编程代理)

packages/client/src/views/hermes/CodingAgentsView.vue
packages/client/src/api/coding-agents.ts
packages/server/src/routes/coding-agents.ts
packages/server/src/routes/claude-code-proxy.ts
packages/server/src/routes/codex-proxy.ts
packages/server/src/controllers/coding-agents.ts
packages/server/src/services/coding-agents.ts
packages/server/src/services/agent-runner/          # 整个目录(含 stream-tee 网关)
packages/client/public/coding-agents/               # 整个目录

J 组(ekko-agent)

packages/server/src/services/ekko-agent/            # manager.ts + mcp.ts
packages/server/src/services/hermes/run-chat/handle-ekko-agent-run.ts
packages/ekko-agent/                                 # 整个包
tests/ekko-agent/                                    # 整个目录

相关测试

tests/server/coding-agents-*.test.ts           (4 个)
tests/server/coding-agent-run-manager-windows.test.ts
tests/server/agent-runner-gateway.test.ts
tests/server/handle-coding-agent-run.test.ts
tests/client/coding-agent-api-modes.test.ts

5.2 路由与导航清理

文件 改动
routes/index.ts 删 L11 codingAgentRoutes、L13 claudeCodeProxyRoutes、L14 codexProxyRoutes 的 import 与 L64/65/79 的 app.use
client/src/router/index.ts 删 name: 'hermes.codingAgents'(L160-164)及其路由块
AppSidebar.vue 删 L262-269 的导航项
components/hermes/usage/AgentBreakdown.vue 删 ekko/coding agent 分组渲染分支
components/hermes/chat/SessionListItem.vue 删代理类型图标分支
components/hermes/chat/ChatInput.vue / ChatPanel.vue / MessageList.vue / HistoryMessageList.vue 删 coding_agent_id 相关 UI 分支
stores/hermes/chat.ts 删 agent 选择器状态与 setter
shared/session-display.ts 删代理类型标签
components/hermes/workflow/types.ts、WorkflowAgentNode.vue、views/hermes/WorkflowView.vue、views/hermes/HistoryView.vue 删 coding_agent_id 字段与节点类型
services/workflow-manager.ts 删 coding_agent_id 持久化
services/usage-recorder.ts 删 ekko/coding agent 用量归类
services/hermes/run-chat/{types,session-command,load-state,abort}.ts 删 ChatCodingAgentId 与相关分支
controllers/hermes/sessions.ts 删代理相关字段

5.3 run-chat dispatch 简化

改造 run-chat/index.ts:

// 删 import handleCodingAgentRun / handleEkkoAgentRun
// 删 isCodingAgentExecution / isEkkoAgentExecution 两个函数
// 删末尾的两条 dispatch 分支,handleBridgeRun 之后直接结束

5.4 isWorking 判定的连带修改(关键)

isCodingAgentExecution 被三处用于 state.isWorking:

行 原逻辑 改后
L289 state.isWorking = !isCodingAgentExecution(source, data) state.isWorking = true
L423 if (!isCodingAgentExecution(source, data)) { ... } if (true) { ... } 或直接去掉条件
L707 state.isWorking = !isCodingAgentExecution(source, data) state.isWorking = true

不要留 isCodingAgentExecution 空壳函数。这是典型的"为兼容而留的开关",直接删。

5.5 Phase 2 验收

npm run test && npm run build
# 手工:聊天发一条消息 → 正常流式返回
# 手工:全局 Agent 跑一条 → 走桥正常
# 手工:群聊多 agent → 正常

风险:load-state.ts 与 session-store.ts 里历史会话可能存有 coding_agent_id。读侧改成"读到就忽略"即可,不做数据迁移。


6. Phase 3 — 砍桌面端 + 官网

6.1 删除

packages/desktop/                          # 整个包
packages/website/                          # 整个包
.github/workflows/desktop-release.yml
.github/workflows/desktop-manual-build.yml
.github/workflows/desktop-mac-update-manifest.yml
.github/workflows/desktop-runtime.yml
.github/workflows/website-deploy.yml
tsconfig.website.json

6.2 根 package.json scripts 清理

删:desktop:install、desktop:prepare-runtime、desktop:prepare-python、desktop:dev、build:desktop、build:desktop:mac、build:desktop:win、build:desktop:linux、dev:website、build:website、preview:website、mcu:*(若不保留 esp32)。

保留:start、dev、dev:client、dev:server、build、harness:check、preview、test、test:watch、test:coverage、test:e2e、test:e2e:ui、openapi:generate。

删 claude script(claude --dangerously-skip-permissions 是 I 组的配套)。

6.3 bin 目录

bin/hermes-web-ui.mjs、bin/hermes-studio-mcp.mjs、bin/hermes-web-ui-mcp.mjs → 重命名为 bin/hermes-ui.mjs + bin/hermes-ui-mcp.mjs,内部引用同步改。

6.4 server 侧清理

文件 改动
index.ts 删 registerDesktopShutdownRoute(L122-160)、desktopShutdownHandler 变量与 L397 赋值、L105 的 isDesktopEnv()。注意 L397 的 bindShutdown 还要保留给正常关停用
services/shutdown.ts L19 的 desktop 分支删除,保留主流程
controllers/auth.ts L65 requiresCredentialChange: process.env.HERMES_DESKTOP === 'true' → 改为常量 false 或直接删该字段
services/hermes/gateway-autostart.ts 删 shouldRecoverWindowsDesktopGatewayOrphans(L156-163)及其在 index.ts 的调用
services/hermes/agent-bridge/manager.ts L321 isDesktopEnv() 删除
services/hermes/studio-mcp-autoinject.ts L72 同上
services/hermes/agent-bridge/python/bridge_runtime.py L74 / L119 的 HERMES_DESKTOP 判断保留不动。这是 Windows 下隐藏控制台窗口的逻辑,删掉会导致 Windows 上 git 子进程弹黑窗。见 §9 红线

6.5 client 侧清理

文件 改动
utils/desktop-bridge.ts 整个文件删除
App.vue 删 desktopBridge import、isDesktopShell / isDesktopPetRoute / hasDesktopTitleBar、DesktopTitleBar 组件、WebPet
main.ts 删 desktopBridge 调用(L8/L20)
router/index.ts 删 L180-190 的 hermesDesktop 注入逻辑
utils/completion-notification.ts 删本地 desktopBridge 副本(L19/L38-40/L80/L105)
api/client.ts 删 L8/L14-15
components/layout/AppSidebar.vue 删 L29
components/layout/DesktopTitleBar.vue 整个文件删除
components/auth/DefaultCredentialPrompt.vue 删 L23
views/hermes/DesktopPetView.vue 删除

6.6 gateway 自启零迁移(§1.2 已核实)

不需要任何迁移工作。ensureProfileGatewaysRunning() 已在 Koa 启动钩子里。只需删掉 HERMES_DESKTOP 相关的 Windows 孤儿回收分支。

6.7 hermes CLI 改为自装

砍桌面端后 hermes 不再捆绑。改造点全部集中在 hermes-process.ts 与 hermes-path.ts:

  • resolveHermesBin() 已经是 HERMES_BIN → 'hermes' 的优先级链,保持不变
  • 删 bundledCliPythonForWindows()(L20-28)里的捆绑运行时假设,改为:HERMES_BIN 指向 hermes.exe 时,在同级/上级找 python.exe,找不到就用 hermes.exe 直调(hermes 官方 Windows 包自带 launcher,不一定需要 -m hermes_cli.main)
  • 新增:启动时探测 hermes 是否在 PATH,不在则前端显示引导页("请安装 hermes-agent,或设置 HERMES_BIN 环境变量")
  • 所有 CLI 调用继续走 execHermesWithBin / spawnHermesWithBin,参数数组,禁止 shell 拼接(红线)

6.8 Phase 3 验收

npm run test && npm run test:e2e && npm run build
# 手工:起服务 → 自动拉起 gateway → 聊天正常

风险:Windows 用户体验。桌面端原本把 Python + hermes 一起打包,砍掉后 Windows 用户必须自己装 Python + hermes-agent。这是既定决策,但引导文案必须写清楚,否则第一启动就是白屏 + 一堆报错。


7. Phase 4 — ArkScope vendor + 样板页(风格基线)

7.1 ArkScope vendor 方式

不作为 npm 依赖,不 clone 整仓。322MB 里 131 个路径是游戏美术资源,全进项目是灾难。

做法:

  1. 定位上游 commit(pushed_at 2026-09-16,default_branch = main),记录到 packages/client/src/vendor/arkscope/PROVENANCE.md
  2. 只 vendor 2 个组件:
    • ScrambleTitle(文字解码动画)—— 纯 DOM + CSS,无外部素材
    • IsometricWave(类亚克力 hover)—— CSS filter + 位移,无外部素材
  3. 各自单独放到 packages/client/src/vendor/arkscope/<ComponentName>/,保留原文件头注释 + 上游 commit hash
  4. 剥离 useAssetPreloader / particle3DModels / data/componentRegistry.js / router / views / showcases —— 这些是上游 demo 工程的东西,与组件无关
  5. 不搬:src/assets/**、public/**、src/style.css 全局样式(只取组件 scoped 样式)

PROVENANCE.md 必须写明:上游无 LICENSE,仅个人自用,不要分发。

7.2 使用边界(硬约束)

允许 禁止
登录页标题区装饰位面 聊天页(消息区、输入区、侧边栏)
Journey 图谱的背景层 任何表单页(Skills / Profiles / Settings / Models)
首屏 loading / 空状态 任何表格页(Usage / Logs / Jobs / Kanban)
任何需要点击交互密集的区域

判定标准:装饰位面不得承载信息、不得承载交互、不得影响可读性。鼠标穿透(pointer-events: none)是硬要求。

7.3 样板页(风格基线)

Phase 4 交付 1 个样板页 + 1 个样板组件,用于锁定视觉语言。在这两个定稿前不碰任何其他页面。

7.3.1 主题令牌

重写 styles/theme.ts → styles/arco-theme.ts,用 Arco 的 token 体系:

// 保留旧版克制灰阶的思路,但换成 Arco 变量
colorPrimary        #2f2f2f  (旧版 #333333 的延续)
colorBgLayout       #fafafa / #1a1a1a
colorBgContainer    #ffffff / #2a2a2a
borderRadius        6px
fontFamily          Inter, system-ui, sans-serif
fontFamilyMono      JetBrains Mono, Fira Code, monospace

暗色模式用 body[arco-theme='dark'] + CSS 变量覆盖,而不是 Arco 的 dark theme + overrides 双轨(旧版 App.vue 的 naiveTheme computed 删掉)。

index.html 的 FOUC 脚本保留机制(读 localStorage 决定 class),改为切 arco-theme 属性。

7.3.2 样板页:LoginView.vue

选它的理由:只用到输入框 + 按钮 + 少量文本,是最小可控面,且是 ArkScope 装饰位面的合法落点。

7.3.3 样板组件:AppSidebar.vue

选它的理由:它是全站导航唯一入口,Arco 的 Menu / Layout.Sider 语义在这里定型后,其余 20+ 视图的导航/布局写法全部照抄。

注意:AppSidebar.vue 里也有大量 naive-ui 引用,迁移它等于验证"深层文件 + 条件渲染(v-if="isSuperAdmin")"这类最难场景。

7.4 Arco 迁移映射表(Phase 4 建立,Phase 5/6 执行)

先建映射再改,不要边改边查。

Naive UI Arco Design Vue 备注
NConfigProvider ConfigProvider(或直接 body 属性) 暗色走 CSS 变量,不用它
NMessageProvider / useMessage Message API 不兼容,需包一层 composable
NDialogProvider / useDialog Modal 需 composable 包装
NNotificationProvider / useNotification Notification 需 composable 包装
NButton a-button prop 名基本一致
NInput a-input / a-textarea type=textarea → 独立组件
NSelect a-select options 语义不同,需转换
NDataTable a-table 列定义 API 差异最大,重点
NModal a-modal v-model:show 一致
NDrawer a-drawer
NPopover / NTooltip a-popover / a-tooltip trigger 语义有差异
NSwitch a-switch
NTag a-tag bordered 无对应,用 : bordered style
NCard a-card
NProgress a-progress
NEmpty a-empty
NCollapse a-collapse
NTabs a-tabs
NForm / NFormItem a-form / a-form-item 校验规则 API 不同,需适配
NGrid / NGridItem a-row / a-col
NIcon <Icon /> + @arco-design/web-vue/es/icon 需全局注册图标
NLayout / NLayoutSider a-layout / a-layout-sider
NScrollbar a-scrollbar
NTimeline a-timeline
NUpload a-upload
NTree a-tree 结构差异大

全局 Provider 方案:在 main.ts 用 app.use(ArcoVue) 全量注册(弃用 tree-shaking),换取迁移速度。后期如在意包体再优化。

Message / Dialog / Notification 是最大风险:Naive 的 useMessage() 是 hook,Arco 的 Message.info() 是命令式。需要写 composables/useMessage.ts / useDialog.ts / useNotification.ts 三个适配层,内部用模块级单例引用。全仓约 200+ 处调用。

7.5 Phase 4 验收

  • 登录页渲染正常,明暗切换无闪烁,ArkScope 位面只在登录页出现且鼠标穿透
  • 侧边栏菜单全部可用(可点进各视图,视图本身还没迁移,允许样式不完美)
  • npm run dev 无 console 报错
  • 记录 docs/ui-baseline.md:定稿的色板、圆角、间距、字体、组件用法约定

8. Phase 5 / 6 — Arco 批量迁移

8.1 Phase 5:聊天页(优先)

落点:

views/hermes/ChatView.vue
components/hermes/chat/ChatPanel.vue
components/hermes/chat/ChatInput.vue
components/hermes/chat/MessageList.vue
components/hermes/chat/MessageItem.vue
components/hermes/chat/SessionSearchModal.vue
components/hermes/chat/FilesPanel.vue
components/hermes/chat/HistoryMessageList.vue
stores/hermes/chat.ts

为什么先做聊天页:它是全站唯一的 Socket.IO 流式实时页,含审批弹窗、工具轨迹、思考块、Markdown 渲染、虚拟滚动、附件上传。最难的坑在这里踩完,后面 20 个 CRUD 页就是纯体力活。

已知高风险点:

  • 虚拟滚动 vue-virtual-scroller 与 Arco 表格/布局的配合(message-list-scroll-position 有测试)
  • 流式增量渲染:MessageItem.vue 每次 token 到达都 re-render,Arco 组件如果包裹在消息气泡里会引入不必要的 diff 开销。消息气泡内部尽量用原生 HTML,不用 Arco 组件
  • 审批/澄清弹窗:时序敏感(必须打断流式),Arco Modal 的 v-model 与流式状态竞争要测
  • Mermaid 渲染超时(有 markdown-rendering-mermaid-import-timeout.test.ts)

验收(必须逐条走):

  • 发消息 → 流式输出 → 完成
  • 工具调用轨迹展开/收起
  • 审批请求弹出 → 选择 → 继续
  • 澄清请求 → 回答
  • 中断(stop)
  • 重新生成 / 分支
  • 附件上传 + 引用
  • Markdown / 代码块 / KaTeX / Mermaid
  • 多标签页同时开着(chat-session-multitab.spec.ts)
  • 断线重连(chat-run-reconnect.test.ts)

8.2 Phase 6:其余 20 视图

按耦合度分批,每批独立提交:

批次 视图 特征
6a SettingsView ProfilesView ModelsView LogsView 纯表单 + 表格,最简单
6b SkillsView PluginsView McpManagerView MemoryView 卡片列表 + 抽屉
6c FilesView TerminalView 大量 DOM 操作与 xterm,注意别把 Arco 塞进来
6d UsageView PerformanceView SkillsUsageView JourneyView 图表/图谱 + 大量数据表格
6e KanbanView JobsView WorkflowView DevicesView 拖拽 + 复杂交互
6f ChannelsView GroupChatView GlobalAgentView IM 频道 + 群聊 + 全局 Agent
6g HistoryView VersionPreviewView 历史会话(含 §5.2 的代理字段清理)

每批的完成定义:该批视图内 grep -c "naive-ui" 归零 + npm run test:client 通过 + e2e 对应用例通过 + 明暗双主题人工过一遍。

Phase 6 收尾:

  • package.json 删 naive-ui 依赖
  • vite.config.ts 的 manualChunks 里 naive-ui → ui-vendor 改为 arco-design → ui-vendor;optimizeDeps.include 同步改
  • styles/variables.scss 与 theme.ts 的对齐测试(style-system.test.ts)断言的是 Naive 的 key,需重写为 Arco token 断言
  • 恢复 package.json 的 prepare script
  • AGENTS.md 的前端规则改为"使用 Arco Design Vue,新增控件优先 @arco-design/web-vue/es/icon"

9. 绝对不能动(红线)

9.1 packages/server/src/services/hermes/agent-bridge/python/ — 只读 vendored

6 个文件:bridge_server.py、bridge_broker.py、bridge_pool.py、bridge_runtime.py、bridge_transport.py、hermes_bridge.py

动之前必须先看懂 bridge_pool.py:

  • SessionDbHolder(L76-105):按路径缓存 SessionDB 实例,进程内单例复用,这是性能关键
  • RunRecord(L108+):run 生命周期记录
  • _bind_session_workspace_cwd(L46-64):把 session 绑到工作区 cwd,同时写 register_task_env_overrides,改错会让所有工具在错误目录执行

唯一例外:bridge_runtime.py L74 / L119 的 HERMES_DESKTOP 判断保留(Windows 隐藏控制台窗口)。新项目虽然是 Web 端,但 Windows 用户从命令行起 Node 仍会弹 git 黑窗。

9.2 /chat-run Socket.IO 事件契约

不得改动事件名与 payload 语义。客户端 store 与 run-chat/ 一一对应,改一边必须同步另一边,且要遵守 §11.2 的链路片段规则。

允许:在事件 payload 上加新字段(可选,客户端按需读取)。 禁止:改事件名、改字段名、改字段类型、改 run.started / run.output / run.completed / run.failed / approval.request / clarify.request 的触发时机。

9.3 hermes state.db 只读

~/.hermes/**/state.db 由 hermes 本体拥有。

  • 我们的 SQLite(packages/server/src/db/)是会话/消息/用量/工作流的唯一可写存储
  • 不做启动时导入同步(旧版有过往迁移逻辑,新项目不要引入)
  • profile 隔离靠注入 HERMES_HOME,不靠复制目录
  • Node 侧任何代码不得 open() hermes 的 state.db。需要读会话历史时走桥的 getHistory / getOutput,不直接读文件

9.4 其他

  • services/hermes/hermes-process.ts 的 execHermesWithBin / spawnHermesWithBin 是唯一 CLI 出口,禁止 shell 字符串拼接
  • services/hermes/group-chat/ 与 lib/context-compressor/、services/hermes/context-engine/ 同属聊天链路,改动受 §11.2 约束
  • 本地 API 路由必须注册在 proxy 兜底路由之前(routes/index.ts 注释)

10. 测试与 CI 策略

10.1 测试分层

层 位置 覆盖
单元 tests/server、tests/client service / store / 纯函数
契约 tests/server/chat-run-baseline.test.ts、group-chat-*-baseline.test.ts /chat-run 事件序列、群聊路由
端到端 tests/e2e Playwright + mock 后端

10.2 随 Phase 调整的测试

Phase 测试动作
0 全量拷贝,作为回归基线
1 改 pwa-metadata.test.ts;i18n-coverage.test.ts 收敛到 2 语言
2 删 I/J 组全部测试;chat-run-baseline.test.ts 补"无 coding agent 分支"断言
3 删 desktop 相关;e2e 中 desktop 用例移除
4 新增 arkscope-vendor.test.ts:断言 vendor 目录无游戏素材文件、无 src/assets 引用
5 保留全部 chat 相关测试;新增 message-item-arko.test.ts
6 每批视图补一个 *-view.test.ts;style-system.test.ts 重写为 Arco token 断言

新增机械保障(建议):

  • tests/client/no-naive-ui.test.ts:递归扫 packages/client/src,任何文件 import ... from 'naive-ui' 即失败。这是换库不回流的最强保障。
  • tests/client/i18n-parity.test.ts:zh 与 en 的 key 集合必须完全一致。

10.3 CI

保留 workflow:build.yml、playwright.yml、npm-lockfile-check.yml、docker-publish.yml 删除:desktop-*(4 个)、website-deploy.yml 新建或改造 build.yml:

- npm ci --ignore-scripts
- npm run harness:check
- npm run test:coverage
- npm run test:e2e
- npm run build

fetch-depth: 0 必须保留(harness:check 靠 git diff 判断链路变更)。

10.4 harness-check.mjs 重写

现有 harness-check.mjs 有 170+ 行在断言 desktop/website 的产物与文案(desktop-release.yml 的 dmg/exe/AppImage、installer.nsh 的 Stop-Process、updater 的 download.ekkolearnai.com、packages/desktop/build/icons/ 的 11 个图标文件…)。必须整体重写,不是删几行。

新 harness-check 应校验:

  1. 必需文件存在:AGENTS.md、ARCHITECTURE.md、DEVELOPMENT.md、docs/harness/*
  2. 必需目录存在:packages/client/src、packages/server/src、packages/skills、tests/{client,server,e2e,shared}、.github/workflows
  3. 禁止目录不存在:packages/desktop、packages/website、packages/ekko-agent(防回流)
  4. AGENTS.md ≤ 120 行,且链接上述文档
  5. package.json 有 harness:check / test / test:coverage / test:e2e / build
  6. ARCHITECTURE.md 记录了 HERMES_WEB_UI_HOME(保留决策)
  7. 禁止字符串扫描:packages/、tests/、scripts/、bin/ 内不得出现 ekko / hermes-web-ui(这是 §4.1 判定的机械化)
  8. Chat 链路变更片段规则(保留,但清单去掉 coding-agents 相关)
  9. build.yml 含 npm run harness:check 与 fetch-depth: 0

11. 风险登记

# 风险 影响 缓解
R1 Arco 迁移面 100 个文件,工作量远超预期 工期失控 Phase 4 先出映射表;6a→6g 分批;每批可独立停下来
R2 Message / Dialog / Notification 全局 hook → 命令式 API 200+ 调用点 先写 3 个 composable 适配层并冻结,批内只调用适配层
R3 流式消息气泡内混入 Arco 组件 → 性能退化 聊天卡顿 气泡内禁用 Arco 组件;Phase 5 用 e2e 测流式延迟
R4 ArkScope 上游无 LICENSE 且强绑游戏素材 法律风险 + 素材不可用 只 vendor 2 个无素材组件;PROVENANCE.md 标注;仅自用不分发
R5 harness-check.mjs 重写不彻底 CI 红灯或漏检 重写后手工核对每一处断言的必要性
R6 state.db 语义被误解导致双写 数据损坏 §9.3 写进 AGENTS.md;新增测试断言 Node 侧无 hermes db 打开
R7 Windows 用户失去捆绑 Python/hermes 首次启动白屏 §6.7 引导页;README 显著位置写安装步骤
R8 update.ts 在线更新在未发布 npm 前失效 用户点了报错 Phase 1 就把入口文案改为"源码运行模式"
R9 nodemon.json watch 残留 packages/ekko-agent/src 删包后 dev 启动异常 Phase 2 一并改
R10 e2e fixture 依赖被砍的 API test:e2e 红 每批 Phase 2/3 后必跑 e2e

12. 执行顺序建议

Phase 0  骨架           → npm ci && npm run build 通过
Phase 1  去品牌 + i18n  → grep 双零 + build 通过
Phase 2  砍 I/J 组      → 聊天/全局/群聊三条链路验证通过
Phase 3  砍桌面+官网    → e2e 全绿 + 网关自启验证
   ↓ 此时项目功能完整,只是界面还是旧的
Phase 4  ArkScope + 样板页 → docs/ui-baseline.md 定稿
Phase 5  聊天页          → 聊天全流程对等
Phase 6  6a→6g 批量     → 无 naive-ui 残留
Phase 7  测试/CI 收口     → harness:check 重写 + CI 绿

Phase 0-3 是"能跑",Phase 4-6 是"好看"。不要在 1-3 阶段碰任何样式代码——去品牌和砍功能时改样式,等于让 diff 审查失效。


13. 待你确认

  1. Phase 1-3 是否拆成 3 个独立 git 仓库历史,还是合成一条线性历史?(旧版有 docs/chat-chain-changes/ 的 PR 片段传统,影响 harness:check 的 diff 检测)
  2. packages/esp32-c3 是否保留?本蓝图按"砍掉"写。若你要做 ESP32 语音硬件(O 组),需要保留并把 mcu:* scripts 加回。
  3. hermes-ui 是否要发布到 npm?决定 update.ts 在线更新逻辑是修好还是标注不可用。