Import the client, server, skills, tests, and build scripts as the starting point for the rebuild, then trim the surfaces this project does not ship: Electron desktop packaging, the marketing website, and the ESP32 firmware tree. CI keeps the web build, e2e, lockfile, release, and Docker workflows. Rename the package and CLI entry points to hermes-ui, drop the prepare hook so npm ci no longer triggers a build, and drop the removed tsconfig and nodemon references. Rewrite AGENTS.md, ARCHITECTURE.md, DEVELOPMENT.md, and the READMEs so the documented package map and hard rules match this repository. reference_code/ is ignored: it is a read-only snapshot, not an input to the build.
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在线更新逻辑是修好还是标注不可用。