38 KiB
hermes-ui 施工蓝图
基线:
hermes-web-ui0.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
- 删除
locales/{zh-TW,ja,ko,fr,es,de,pt,ru}.ts - messages.ts 的
supportedLocales改为['en', 'zh'] - i18n/index.ts 的
normalize()简化为 zh 判断 + 默认 en - i18n-coverage.test.ts 的
rawMessages改为两项
硬规则保留:新增用户可见字符串必须同时写 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 简化
// 删 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 个路径是游戏美术资源,全进项目是灾难。
做法:
- 定位上游 commit(
pushed_at2026-09-16,default_branch=main),记录到packages/client/src/vendor/arkscope/PROVENANCE.md - 只 vendor 2 个组件:
ScrambleTitle(文字解码动画)—— 纯 DOM + CSS,无外部素材IsometricWave(类亚克力 hover)—— CSS filter + 位移,无外部素材
- 各自单独放到
packages/client/src/vendor/arkscope/<ComponentName>/,保留原文件头注释 + 上游 commit hash - 剥离
useAssetPreloader/particle3DModels/data/componentRegistry.js/router/views/showcases—— 这些是上游 demo 工程的东西,与组件无关 - 不搬:
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的preparescript 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 应校验:
- 必需文件存在:
AGENTS.md、ARCHITECTURE.md、DEVELOPMENT.md、docs/harness/* - 必需目录存在:
packages/client/src、packages/server/src、packages/skills、tests/{client,server,e2e,shared}、.github/workflows - 禁止目录不存在:
packages/desktop、packages/website、packages/ekko-agent(防回流) AGENTS.md≤ 120 行,且链接上述文档package.json有harness:check/test/test:coverage/test:e2e/buildARCHITECTURE.md记录了HERMES_WEB_UI_HOME(保留决策)- 禁止字符串扫描:
packages/、tests/、scripts/、bin/内不得出现ekko/hermes-web-ui(这是 §4.1 判定的机械化) - Chat 链路变更片段规则(保留,但清单去掉 coding-agents 相关)
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. 待你确认
- Phase 1-3 是否拆成 3 个独立 git 仓库历史,还是合成一条线性历史?(旧版有
docs/chat-chain-changes/的 PR 片段传统,影响 harness:check 的 diff 检测) packages/esp32-c3是否保留?本蓝图按"砍掉"写。若你要做 ESP32 语音硬件(O 组),需要保留并把mcu:*scripts 加回。hermes-ui是否要发布到 npm?决定update.ts在线更新逻辑是修好还是标注不可用。