feat: stabilize page AI ACP runtimes
实现并稳定页面 AI 的 ACP Hermes / ACP Reasonix 运行路径。 主要内容: - 分离 profile 与 acpRuntime,ACP Hermes 按所选 Hermes profile 启动并注入 provider key。 - 修复 Reasonix ACP wrapper 的 API key 读取、ToolRegistry 注册、LoopEvent role 映射和 reasoning/final 分流。 - 修复 ACP agent_thought_chunk 被 untagged enum 误解析为 message.delta 的问题,补充 thought 相关单测。 - 补充页面 AI 浏览器验证 skill 证据到 7-15 设计稿,并记录严格验收标准。 - 同步提交当前仓库中已存在的 rust-web / Hermes tools / SSE / bug 文档相关改动。 验证: - node --check scripts/reasonix-acp-wrapper.mjs - cargo test -p mnote-web acp -- --nocapture - 页面 AI ACP 浏览器验证:tmp/page-ai-acp-browser-UAYwyM/
This commit is contained in:
@@ -603,28 +603,136 @@ AiAgentPanel 增加下拉框 + 切换逻辑:
|
||||
|
||||
### ✅ 已完成(2026-05-17)
|
||||
|
||||
#### Phase A — Rust ACP 基础设施
|
||||
|
||||
| Step | 文件 | 状态 | 测试 |
|
||||
|------|------|------|------|
|
||||
| 3 | `acp_client.rs` | 编译通过,475 行 | 6 单元测试通过 |
|
||||
| 4 | `acp_types.rs` | 编译通过,440 行 | 7 单元测试通过 |
|
||||
| 5 | `acp_session_manager.rs` | 编译通过,~530 行 | 4 测试通过(含事件派发) |
|
||||
| 6 | `acp_runtime.rs` | 编译通过,~330 行 | 5 测试通过,含真实 Hermes 连接测试 |
|
||||
| 7 | `acp_bridge.rs` + `hermes_client.rs` 修改 | 编译通过 | 新增 `acp_stream_events()` SSE 端点 |
|
||||
| 8 | `lib.rs` 模块注册 | 编译通过 | — |
|
||||
| 9 | `AppState` 集成 | 编译通过 | — |
|
||||
| 10 | `scripts/reasonix-acp-wrapper.mjs` | 已创建,~250 行 | 需安装 `npm install reasonix` 后测试 |
|
||||
| 12 | Profile 扩展 `configured_runtime_for_profile()` | 编译通过 | — |
|
||||
| 13 | HTTP proxy 标 `#[deprecated]` | 编译通过(6 个 warning) | — |
|
||||
| 3 | `acp_client.rs` | 编译通过,~487 行 | 6 单元测试通过,含 `initialize` 握手 |
|
||||
| 4 | `acp_types.rs` | 编译通过,显式按 `sessionUpdate` 判别 | 8 序列化/反序列化测试通过,含 `agent_thought_chunk` 不误判为 message |
|
||||
| 5 | `acp_session_manager.rs` | 编译通过,~530 行 | 5 测试通过(含事件派发、文本去重、thought → `ThoughtDelta`) |
|
||||
| 6 | `acp_runtime.rs` | 编译通过,~330 行 | 6 测试通过(含真实 Hermes CLI 连接) |
|
||||
| 7 | `acp_bridge.rs` | 编译通过,~260 行 | ACP→SSE 事件映射,含 `ThoughtDelta` → `thought.delta` 单测 |
|
||||
| 8 | `lib.rs` 模块注册 | 编译通过 | 5 个 ACP 模块声明 |
|
||||
| 9 | `AppState` 集成 | 编译通过 | `AcpRuntimeManager` 挂入 `AppState` |
|
||||
|
||||
#### Phase B — 后端集成
|
||||
|
||||
| Step | 文件 | 改动 | 验证 |
|
||||
|------|------|------|------|
|
||||
| 7 | `hermes_client.rs` | `create_run` ACP 分支、`acp_stream_events()` SSE 端点、`is_acp_profile()` 检测 | e2e confirmed |
|
||||
| 12 | `hermes_client.rs` | Profile 扩展 `configured_runtime_for_profile()` | — |
|
||||
| 12 | `hermes_client.rs` | `/api/hermes/client/profiles` 返回 `acpRuntimes` 数组(含 model/preset/apiKeyConfigured) | API confirmed |
|
||||
| 13 | `hermes_client.rs` | `configured_upstream_for_profile()` 标 `#[deprecated]`(后因 caller warning 移除) | — |
|
||||
|
||||
#### Phase C — Reasonix ACP Wrapper
|
||||
|
||||
| Step | 文件 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 10 | `scripts/reasonix-acp-wrapper.mjs` | ~400 行 | 自包含 NDJSON JSON-RPC 2.0 服务器,无依赖 `AcpServer` |
|
||||
| — | API key 加载 | `loadApiKey()` 先读 `DEEPSEEK_API_KEY`,再读 `~/.reasonix/config.json` 的 `apiKey`,最后兼容 `~/.reasonix/config.yaml` | 对齐 Reasonix CLI 当前配置路径,同时保留旧 fallback |
|
||||
| — | 工具注册修复 | ToolRegistry 使用 `fn` 字段;Reasonix 工具名用 `mnote_doc_fetch` / `mnote_doc_markdown_edit` 安全别名,再映射到 mnote-web 的 `mnote.doc.*` HTTP 工具 | 对齐 `DeepSeek-Reasonix-main/src/tools.ts`,避免 dotted tool name 与错误 `call` 字段导致工具不可调 |
|
||||
| — | 事件处理修复 | `ev.role` 替代 `ev.type` | CacheFirstLoop 的 LoopEvent 使用 `role` 字段(`assistant_delta`/`assistant_final`/`done`/`tool_call_delta`/`tool_start`/`tool`/`error`/`warning`/`status`) |
|
||||
| — | Reasoning / final 分流 | `reasoningDelta` 只发 `agent_thought_chunk`,不计入 assistant 正文输出;`assistant_final.content` 仍可在无 delta 正文时补发 | 避免只收到 reasoning 后吞掉最终正文 |
|
||||
|
||||
#### Phase D — 前端集成(Rust SSR)
|
||||
|
||||
| 改动 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| ACP 下拉选择器 | `layout.rs` | Agent 标签页新增 `<select data-page-ai-acp-runtime>`,3 个选项:默认 (Hermes HTTP)、ACP · Hermes、ACP · Reasonix |
|
||||
| 状态存储 | `layout.rs` | `pageAiAcpRuntime` + `pageAiAcpRuntimes` 从 `/api/hermes/client/profiles` 加载 |
|
||||
| 运行时切换 | `layout.rs` | `acpRuntime` 只表示运行时/传输层;Hermes ACP 继续保留当前 Hermes profile,Reasonix ACP 使用 `profile=reasonix` |
|
||||
| UI 自适应 | `layout.rs` | ACP Hermes 模式下继续显示 Hermes profile 下拉;ACP Reasonix 模式下隐藏 Hermes profile 下拉;Agent 面板显示 ACP 配置 |
|
||||
| Subtitle 更新 | `layout.rs` | 选择 ACP 后标题栏显示 `ACP · Reasonix` 或 `ACP · Hermes` |
|
||||
|
||||
#### Phase E — 浏览器验证 Skill
|
||||
|
||||
| 文件 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `/home/lix/.codex/skills/page-ai-browser-verify/SKILL.md` | ✅ 已创建 | 固化页面 AI 浏览器验证流程,后续遇到 ACP Hermes / ACP Reasonix / 回复质量问题时复用 |
|
||||
| `/home/lix/.codex/skills/page-ai-browser-verify/scripts/verify_mnote_page_ai_acp.js` | ✅ 已创建 | 真实登录、创建临时页、切换 ACP Hermes/Reasonix、捕获 `/api/hermes/client/runs`、截图,并断言 assistant 正文去空白后严格等于 marker |
|
||||
|
||||
验证命令:
|
||||
|
||||
```bash
|
||||
node /home/lix/.codex/skills/page-ai-browser-verify/scripts/verify_mnote_page_ai_acp.js
|
||||
```
|
||||
|
||||
最新证据(2026-05-17):
|
||||
|
||||
| 项 | 路径 |
|
||||
|---|---|
|
||||
| 结构化结果 | `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/result.json` |
|
||||
| ACP Hermes 截图 | `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/01-acp-hermes-reply.png` |
|
||||
| ACP Reasonix 截图 | `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/02-acp-reasonix-reply.png` |
|
||||
|
||||
验收标准已收紧:不能只判断 UI “包含 marker”;必须确认最终 assistant 正文是干净最终答案,不能出现推理解释、工具说明、`Theuserisasking...` 这类 glued reasoning 文本。
|
||||
|
||||
### 🐛 问题与解决状态(2026-05-17)
|
||||
|
||||
#### 问题 1:ACP Reasonix SSE 流返回空(`stop_reason=Error`)
|
||||
|
||||
| 项 | 详情 |
|
||||
|---|---|
|
||||
| **状态** | ✅ 已修复;后端 SSE 探针与前端真实浏览器验证均通过 |
|
||||
| **原现象** | `ACP prompt completed: stop_reason=Error`,耗时 ~300ms,SSE 流无任何 `message.delta` 事件 |
|
||||
| **已排除** | ✅ API key 有效(`CacheFirstLoop.step("hi")` 直接调用正常,LLM 返回中文回复) |
|
||||
| **已排除** | ✅ Wrapper 语法正确(`node --check` 通过,`initialize` + `session/new` 验证通过) |
|
||||
| **已排除** | ✅ SSE 转发管道 race condition(频道建立已移至 prompt 启动前) |
|
||||
| **已排除** | ✅ `message` 字段名匹配(`payload.get("message")` 修正) |
|
||||
| **根因** | `scripts/reasonix-acp-wrapper.mjs` 与 Reasonix 当前 API 不匹配:`ToolRegistry.register()` 需要 `fn` 而不是 `call`;`CacheFirstLoop.step()` 产出的是 `ev.role`,不是旧的 `ev.type`;本机 Reasonix API key 存在 `~/.reasonix/config.json`,旧 wrapper 只读 YAML |
|
||||
| **已修复** | wrapper 改为读取 `config.json`,工具注册改为 `fn`,工具名改为 Reasonix 安全别名,事件映射覆盖 `assistant_delta`、`assistant_final`、`done`、`tool_call_delta`、`tool_start`、`tool`、`error`、`warning`、`status`;`reasoningDelta` 只作为 thought,不作为 assistant 正文输出计数 |
|
||||
| **验证** | `node --check scripts/reasonix-acp-wrapper.mjs` 通过;`cargo test -p mnote-web acp -- --nocapture` 29 个测试通过;直接 JSON-RPC 探针 `initialize → session/new → session/prompt` 返回 `stopReason=end_turn`;3000 后端 SSE 探针 `ACP Reasonix` 收到 `message.delta` + `run.completed`;真实浏览器验证见 `tmp/page-ai-acp-browser-UAYwyM/` |
|
||||
|
||||
#### 问题 1b:ACP Hermes profile 选择丢失
|
||||
|
||||
| 项 | 详情 |
|
||||
|---|---|
|
||||
| **状态** | ✅ 已修复,后端真实 SSE 探针已通过 |
|
||||
| **原现象** | 选择 `ACP · Hermes` 后,前端把 `pageAiAcpRuntime` 当成 `profile` 发送,导致 profile 固定为 `hermes`,无法沿用既有 Hermes profile 选择 |
|
||||
| **根因** | `profile` 与 `acpRuntime` 两个概念混用:`profile` 应表示 Hermes agent/profile(如 `default`、`mnoteai`),`acpRuntime` 才表示运行时传输层(`hermes` / `reasonix`) |
|
||||
| **已修复** | 前端 `create_run` 发送 `{ profile: pageAiRunProfile(), acpRuntime }`;ACP Hermes 保留 profile 下拉;后端按 `acpRuntime` 进入 ACP 分支,并按本次 `profile` 启动 `hermes -p <profile> acp` |
|
||||
| **兼容处理** | ACP Hermes subprocess 会从所选 profile 的 `model.api_key` / `providers.<provider>.api_key` / `key_env` 注入 provider key 环境,避免旧 gateway 能读 profile key、ACP subprocess 却读不到的问题 |
|
||||
| **验证** | 3000 后端 SSE 探针:`ACP Hermes + default profile`、`ACP Hermes + mnoteai profile` 均收到 `message.delta` + `run.completed`,没有 `run.failed`;浏览器请求体确认 Hermes 为 `{ profile: "mnoteai", acpRuntime: "hermes" }` |
|
||||
|
||||
#### 问题 1c:浏览器截图显示 reasoning / thought 被当作最终回复
|
||||
|
||||
| 项 | 详情 |
|
||||
|---|---|
|
||||
| **状态** | ✅ 已修复,严格浏览器验证通过 |
|
||||
| **原现象** | 旧截图 `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-mp9ofvbc/01-acp-hermes-reply.png` 中,AI 气泡显示 `Theusersentabrowserverificationstring...`;Reasonix 旧 `result.json` 也出现 `Theuserisaskingmetorespond...ACP_REASONIX...`,说明上次只检查“包含 marker”的验收标准不合格 |
|
||||
| **根因** | Rust `SessionUpdate` 使用 `#[serde(untagged)]`,但 `AgentMessageChunk` 与 `AgentThoughtChunk` 字段形状相同(`sessionUpdate: String` + `content`),serde 会按枚举顺序先匹配 `AgentMessageChunk`,导致 `agent_thought_chunk` 被误转成 `message.delta` |
|
||||
| **已修复** | `acp_types.rs` 改为自定义 `Deserialize`,显式读取 `sessionUpdate` 后匹配 `agent_message_chunk` / `agent_thought_chunk` / tool / usage / plan 等变体;`acp_session_manager.rs` 补 `agent_thought_chunk` → `ThoughtDelta` 单测;`acp_bridge.rs` 补 `ThoughtDelta` → `thought.delta` 单测 |
|
||||
| **验证** | `cargo test -p mnote-web acp -- --nocapture`:29 passed;浏览器 skill 严格断言 Hermes / Reasonix 最新 assistant 正文分别严格等于 `ACP_HERMES_BROWSER_OK_mp9pfqf6`、`ACP_REASONIX_BROWSER_OK_mp9pfqf6`;截图见 `tmp/page-ai-acp-browser-UAYwyM/` |
|
||||
| **后续规则** | 页面 AI 浏览器验证必须同时看截图与正文断言;不能只用 DOM 包含 marker 作为通过条件 |
|
||||
|
||||
#### 问题 2:Hermes HTTP 路径 502
|
||||
|
||||
| 项 | 详情 |
|
||||
|---|---|
|
||||
| **现象** | `Hermes upstream 连接失败: error sending request for url (http://127.0.0.1:8644/v1/runs)` |
|
||||
| **判断** | 这是旧 Hermes HTTP proxy 路径的环境/profile 配置问题,不是 ACP Reasonix wrapper 问题 |
|
||||
| **原因** | `configured_upstream_for_profile()` 优先读取 `MNOTE_WEB_HERMES_UPSTREAM_URL` 或 profile 的 `API_SERVER_PORT`,当前解析到了 8644;实际 Hermes gateway 端口应与本机服务一致(设计稿预期为 8642) |
|
||||
| **解决** | 只读确认当前 mnote-web 启动环境和 Hermes profile 配置;将 `MNOTE_WEB_HERMES_UPSTREAM_URL` 或对应 profile `API_SERVER_PORT` 调整到实际 gateway 端口;不要在 ACP runtime 层硬编码端口 |
|
||||
|
||||
#### 问题 3:ACP 模式 Skills 面板内容区分(次要项)
|
||||
|
||||
| 项 | 详情 |
|
||||
|---|---|
|
||||
| **状态** | 🟡 次要项;不阻塞当前首要目标(ACP Hermes / ACP Reasonix 正常回复) |
|
||||
| **纠正** | Reasonix skills 不是空态;本机真实目录包含 `/home/lix/.reasonix/skills` |
|
||||
| **当前处理** | 后端 Reasonix skills 源应按 runtime 维度扫描:`/mnt/Data1T/mnote/.reasonix/skills`、`/mnt/Data1T/mnote/.agents/skills`、`/home/lix/.reasonix/skills`、`/home/lix/.agents/skills` |
|
||||
| **边界** | Skills 展示只是可见信息;当前还不能据此认为这些 skills 都已经注入 Reasonix ACP runtime 的工具系统 |
|
||||
| **下一步** | 后续若要把 Reasonix skills 变成可执行能力,需要明确 Reasonix skill schema → ToolRegistry 注册规则;当前优先保持只读展示与不误导 |
|
||||
|
||||
### 📋 待完成
|
||||
|
||||
| Step | 工作 | 前置 | 估算 |
|
||||
|------|------|------|------|
|
||||
| 11 | 前端运行时选择器(AiAgentPanel.tsx) | Step 7 | ~半天 |
|
||||
| 14 | e2e 验证:Hermes ACP + Reasonix ACP | Step 10 | ~半天 |
|
||||
| 15 | 压力测试:多会话、进程管理 | Step 14 | ~半天 |
|
||||
| — | 旧 Hermes HTTP proxy 502 环境配置确认 | 非 ACP 路径,仅影响旧 gateway | ~15min |
|
||||
| — | ACP 模式 Skills 可执行注入设计(Reasonix skill schema → ToolRegistry) | 问题3 | ~半天 |
|
||||
| 15 | 压力测试:多会话并发、进程管理稳定性 | Step 14 | ~半天 |
|
||||
| 16 | 退役旧 HTTP proxy 代码 | Step 14 稳定后 | ~1 天 |
|
||||
| 17 | 基准测试:缓存收益量化 | Step 10 | ~半天 |
|
||||
| 17 | 基准测试:Reasonix cache hit rate vs Hermes | Step 10 | ~半天 |
|
||||
|
||||
---
|
||||
|
||||
@@ -632,177 +740,138 @@ AiAgentPanel 增加下拉框 + 切换逻辑:
|
||||
|
||||
以下 checklist 按依赖关系排序,每个 step 标注了**参考文件**(可直接读的代码)、**产出文件**、**验证方法**。执行时从 step-1 开始,完成后由 AI 调用 `todo_write` 标记进度后进入下一步。
|
||||
|
||||
### Step 1:读取参考代码,熟悉 ACP 协议细节
|
||||
### [x] Step 1:读取参考代码,熟悉 ACP 协议细节
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 确认 ACP JSON-RPC 协议的方法名、字段名、事件类型,确保后续实现与 Hermes/Reasonix 兼容 |
|
||||
| **参考** | `reference-code/hermes-vscode-main/src/protocol.ts`(ACP 事件解析)、`reference-code/DeepSeek-Reasonix-main/src/acp/protocol.ts`(ACP 类型定义)、`reference-code/hermes-vscode-main/src/acpClient.ts`(ACP 客户端完整实现) |
|
||||
| **产出** | 无代码产出,仅阅读确认 |
|
||||
| **验证** | 能在脑中回答:`session/update` 有几种 `sessionUpdate` 变体?每个变体有哪些必选字段? `session/prompt` 的 params 结构是什么? |
|
||||
> ✅ 完成。已阅读 `acpClient.ts`(spawn/request/notification 模式)、`protocol.ts`(6 种 session/update 变体)、`acp.ts`(CacheFirstLoop + Eventizer 集成)。确认 ACP 使用 camelCase JSON 字段、NDJSON 流式传输。
|
||||
|
||||
### Step 2:确认 `run_command` 的 cwd 与项目根一致
|
||||
| **参考** | `reference-code/hermes-vscode-main/src/acpClient.ts`、`reference-code/DeepSeek-Reasonix-main/src/acp/protocol.ts`、`reference-code/hermes-vscode-main/src/protocol.ts` |
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 确保后续所有文件操作路径正确,不再触发 sandbox 偏移 |
|
||||
| **操作** | `run_command pwd` 确认输出为 `/mnt/Data1T/mnote` |
|
||||
| **验证** | 输出包含 `/mnt/Data1T/mnote` |
|
||||
### [x] Step 2:确认 `run_command` 的 cwd 与项目根一致
|
||||
|
||||
### Step 3:创建 `acp_client.rs` — ACP JSON-RPC 2.0 客户端
|
||||
> ✅ 完成。cwd = `/mnt/Data1T/mnote`,所有路径相对此目录。
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 实现通用的 ACP 协议传输层:spawn 子进程、读写 NDJSON、请求/响应/通知路由 |
|
||||
| **参考** | `reference-code/hermes-vscode-main/src/acpClient.ts`(完整参考,~220 行):spawn 逻辑 (L50-80)、onData 解析 (L120-180)、sendRequest (L95-110)、sendNotification (L115-118) |
|
||||
| **路径** | `rust/crates/mnote-web/src/acp_client.rs`(新建) |
|
||||
| **结构** | `pub struct AcpClient { child, writer, reader, pending: HashMap<u64, OneshotSender>, next_id }` |
|
||||
| **方法** | `spawn(bin, args) → Result`、`request(method, params) → Result<R>`、`notification(method, params)`、`on_notification(handler)`、`close()` |
|
||||
| **参考字段映射** | JSON-RPC `id` → `pending` key;响应匹配 `id`;通知匹配 `method` 字段 |
|
||||
| **细节** | stdin 用 `BufWriter`(行缓冲),stdout 用 `BufReader` + `lines()` 逐行读;后台 tokio task 处理 incoming 行;`request()` 返回 `oneshot::Receiver`;超时处理用 `tokio::time::timeout`(默认 5min) |
|
||||
| **验证** | 单元测试:mock stdin/stdout 子进程,发送 `session/new` 请求,验证收到响应;发送 notification,验证 handler 被调用 |
|
||||
### [x] Step 3:创建 `acp_client.rs` — ACP JSON-RPC 2.0 客户端
|
||||
|
||||
### Step 4:创建 ACP 协议类型定义 — `acp_types.rs`
|
||||
> ✅ 完成。`rust/crates/mnote-web/src/acp_client.rs`(~487 行)。实现了 `AcpClient` 结构体,含 `spawn()`、`request()`、`notification()`、`on_notification()`、`close()`。后台 tokio task 处理 NDJSON 行读取,`pending: HashMap<u64, oneshot::Sender>` 路由响应。**6 个单元测试通过**(含 `initialize` 握手 mock)。
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | ACP 协议的 Rust 类型定义,序列化/反序列化用 serde |
|
||||
| **参考** | `reference-code/DeepSeek-Reasonix-main/src/acp/protocol.ts`(ACP 类型定义,~80 行)、`reference-code/hermes-vscode-main/src/protocol.ts`(解析逻辑,~120 行) |
|
||||
| **路径** | `rust/crates/mnote-web/src/acp_types.rs`(新建) |
|
||||
| **结构** | `InitializeParams/Result`、`SessionNewParams/Result`、`SessionPromptParams/Result`、`SessionCancelParams`、`SessionUpdateParams`、`ContentBlock`(text/resource/image/audio)、`SessionUpdateKind`(enum 6 种变体) |
|
||||
| **字段注意** | Hermes 和 Reasonix 都使用 **camelCase** JSON 字段(`sessionUpdate`、`toolCallId`),对应 `#[serde(rename_all = "camelCase")]` |
|
||||
| **验证** | 单元测试:JSON 反序列化 `acp/dispatch.ts` 中的 `session/update` 案例、`session/prompt` 的请求/响应序列化-反序列化往返 |
|
||||
| **产出** | `rust/crates/mnote-web/src/acp_client.rs` |
|
||||
|
||||
### Step 5:创建 `acp_session_manager.rs` — 会话生命周期管理
|
||||
### [x] Step 4:创建 ACP 协议类型定义 — `acp_types.rs`
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 管理 ACP 会话生命周期:session/new → session/prompt → session/cancel,将 `session/update` 事件派发给 mnote-web 各模块 |
|
||||
| **参考** | `reference-code/hermes-vscode-main/src/sessionManager.ts`(完整参考,~294 行):SessionManager 类 (L37-294)、handleUpdate (L130-280)、sendPrompt (L80-128)、cancel (L280-294) |
|
||||
| **路径** | `rust/crates/mnote-web/src/acp_session_manager.rs`(新建) |
|
||||
| **结构** | `pub struct AcpSessionManager { runtimes: HashMap<String, AcpRuntimeConfig>, sessions: HashMap<String, AcpSession>, active_profile: String }`、`struct AcpSession { id, client, state, run_handle, page_context }` |
|
||||
| **方法** | `create_session(profile, page_context) → sessionId`(调 ACP `session/new`)、`run_prompt(session_id, prompt_blocks, on_event) → JoinHandle`(调 ACP `session/prompt`,注册 `session/update` handler)、`cancel(session_id)`(调 ACP `session/cancel`)、`switch_runtime(profile_name)`(切换 active_profile,关闭旧 sessions,创建新 runtime)、`SessionUpdateKind` 到 `AcpSessionEvent` 的映射 |
|
||||
| **事件映射** | `agent_message_chunk` → `AcpSessionEvent::TextDelta { text }`;`agent_thought_chunk` → `AcpSessionEvent::ThoughtDelta { text }`;`tool_call` → `AcpSessionEvent::ToolCall { id, title, kind, status }`;`tool_call_update` → `AcpSessionEvent::ToolCallUpdate { id, status, content? }`;`usage_update` → `AcpSessionEvent::UsageUpdate { used, size }`;`session_info_update` → `AcpSessionEvent::SessionInfo { title }` |
|
||||
| **去重逻辑** | 参考 `hermes-vscode-main/src/protocol.ts` 的 `deduplicateChunk()` 函数——ACP 会重发完整文本作为可靠性 fallback,需检测并丢弃重复 |
|
||||
| **验证** | 单元测试:构造 mock `AcpClient`,`create_session` → 验证发送 `session/new`;`run_prompt` → 验证发送 `session/prompt`;模拟 `session/update` notification,验证 `on_event` 回调被正确调用 |
|
||||
> ✅ 完成。`rust/crates/mnote-web/src/acp_types.rs`(~440 行)。定义了 `InitializeParams/Result`、`SessionNewParams/Result`、`SessionPromptParams/Result`、`ContentBlock`(4 变体)、`SessionUpdate`(7 变体含 Unknown 兜底)。全部 camelCase JSON。**7 个序列化往返测试通过**。
|
||||
|
||||
### Step 6:创建 `acp_runtime.rs` — 运行时管理
|
||||
| **产出** | `rust/crates/mnote-web/src/acp_types.rs` |
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 管理 agent runtime 进程的 spawn、健康检查、自动重启 |
|
||||
| **路径** | `rust/crates/mnote-web/src/acp_runtime.rs`(新建) |
|
||||
| **结构** | `pub struct AcpRuntimeManager { runtimes: HashMap<String, AcpRuntimeConfig>, active: Mutex<Option<String>> }`、`pub struct AcpRuntimeConfig { name, bin, args, env }` |
|
||||
| **方法** | `register_runtime(config)`、`spawn_runtime(name) → AcpClient`、`health_check(name) → bool`(spawn 进程 + 发送 `initialize` 请求,超时 5s)、`shutdown_runtime(name)`、`switch_to(name) → Result`(先 shutdown 当前 active,再 spawn 新的) |
|
||||
| **配置来源** | 从环境变量 / 配置文件读取(`MNOTE_WEB_ACP_RUNTIMES` JSON),当前固定配置:`hermes` → `{ bin: "hermes", args: ["acp"] }`、`reasonix` → `{ bin: "node", args: ["reasonix-acp-wrapper.mjs"] }` |
|
||||
| **profile 扩展** | 当前 `configured_upstream_for_profile()` 返回 Hermes HTTP URL;改为返回 `AcpRuntimeConfig`。已有 profile 系统(`active_profile_name`, `configured_upstream_for_profile`) 保持接口不变,内部实现切换 |
|
||||
| **验证** | 运行 `hermes acp`(需本地安装),发送 `session/new` 验证返回 `sessionId`;无 Hermes 环境时 mock 子进程验证健康检查逻辑 |
|
||||
### [x] Step 5:创建 `acp_session_manager.rs` — 会话生命周期管理
|
||||
|
||||
### Step 7:集成 ACP Session Manager 到 Hermes routes
|
||||
> ✅ 完成。`rust/crates/mnote-web/src/acp_session_manager.rs`(~530 行)。`AcpSessionManager` 含 `create_session()` → ACP `session/new`、`run_prompt()` → ACP `session/prompt` + callback、`cancel()` → `session/cancel`。文本去重(4 种模式匹配)、事件枚举(7 种 variants)。**5 个单元测试通过**(含事件派发、去重逻辑、thought chunk 映射)。
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 让 `hermes_client.rs` 的现有端点可以选择使用 ACP 而非 HTTP proxy |
|
||||
| **参考** | `hermes_client.rs` 中现有 `create_run` / `stream_events` / `abort_run` |
|
||||
| **操作** | 在 `hermes_client.rs` 中导入 `AcpRuntimeManager` 和 `AcpSessionManager`;当当前 profile 的 `runtime_type == "acp"` 时走 ACP 路径,否则走原有 HTTP proxy 路径;`create_run` → `acp_session_manager.run_prompt()`(替代 Hermes HTTP `POST /api/hermes/runs`);`stream_events` → 从 `AcpSessionManager` 的 `on_event` 回调中发出 SSE(替代 Hermes HTTP `GET /api/hermes/runs/{id}/events`);`abort_run` → `acp_session_manager.cancel()`(替代 Hermes HTTP `POST /api/hermes/runs/{id}/abort`) |
|
||||
| **SSE 桥接函数** | 新增 `fn acp_event_to_sse(event: AcpSessionEvent) → Option<SseEvent>`。映射表:`TextDelta("msg")` → `{ event: "message.delta", data: { delta: "msg" } }`;`ThoughtDelta("t")` → `{ event: "thought.delta", data: { delta: "t" } }`(新增);`ToolCall("id","title","kind","pending")` → `{ event: "tool.started", data: { tool: "title", preview: null } }`;`ToolCallUpdate("id","completed",content)` → `{ event: "tool.completed", data: { tool: "...", duration: null } }`;`UsageUpdate(used,size)` → `{ event: "usage.updated", data: { used, size } }`(新增);`SessionInfo(title)` → 忽略(mnote 前端不需要) |
|
||||
| **验证** | 用 `hermes acp`(本地已安装)做 e2e 测试:前端发消息 → ACP session/prompt → 收到 SSE 流 → 显示工具调用 → 显示最终回复 |
|
||||
| **产出** | `rust/crates/mnote-web/src/acp_session_manager.rs` |
|
||||
|
||||
### Step 8:编辑全局 Router 添加 ACP 模块
|
||||
### [x] Step 6:创建 `acp_runtime.rs` — 运行时管理
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 让 ACP 模块被编译,各模块之间可引用 |
|
||||
| **参考** | `rust/crates/mnote-web/src/routes/mod.rs` 中当前 Hermes routes 的注册方式 (nest at `hermes_base_path`) |
|
||||
| **操作** | 在 `mod.rs` 中添加 `mod acp_client;`、`mod acp_types;`、`mod acp_session_manager;`、`mod acp_runtime;`;初始化时创建 `AcpRuntimeManager`,注册 Hermes runtime 和 Reasonix runtime(如果配置存在);将 `AcpRuntimeManager` 放入 `AppState` 或 `Extension` |
|
||||
| **验证** | `cargo build` 通过 |
|
||||
> ✅ 完成。`rust/crates/mnote-web/src/acp_runtime.rs`(~330 行)。`AcpRuntimeManager` 支持 `from_env()` 从环境变量配置、`switch_to(name)` 切换运行时、`health_check()` 5s 超时检测。Reasonix wrapper 路径通过 `CARGO_MANIFEST_DIR` 自动解析绝对路径。**6 个单元测试通过**(含真实 Hermes CLI 连接)。
|
||||
|
||||
### Step 9:AppState 改造 — 加入 AcpRuntimeManager
|
||||
| **产出** | `rust/crates/mnote-web/src/acp_runtime.rs` |
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 让路由 handler 可以访问运行时管理器 |
|
||||
| **参考** | `rust/crates/mnote-web/src/app.rs` 中的 `AppState` 结构 |
|
||||
| **操作** | 在 `AppState` 中添加 `acp_runtime: Arc<AcpRuntimeManager>` 字段;`AppState::new()` 中根据配置注册 `hermes` 和/或 `reasonix` runtime |
|
||||
| **验证** | `cargo build` 通过;health endpoint 返回 ACP runtime 状态 |
|
||||
### [x] Step 7:集成 ACP Session Manager 到 Hermes routes
|
||||
|
||||
### Step 10:创建 Reasonix ACP wrapper 脚本
|
||||
> ✅ 完成。`hermes_client.rs` 中:
|
||||
> - `create_run`:新增 ACP 分支——`is_acp_profile()` 检测 → `register_acp_runtime()` → 存储 payload → 返回本地 runId
|
||||
> - `stream_events`:新增 `acp_stream_events()` 函数——`AcpRuntimeManager::switch_to()` 激活运行时 → `AcpSessionManager::create_session()` + `run_prompt()` → broadcast → mpsc → SSE `Body`
|
||||
> - `is_acp_profile("reasonix" | "hermes")` 返回 true
|
||||
> - SSE 桥接:`acp_event_to_sse()` 映射 7 种 ACP 事件到 SSE 格式
|
||||
> - 修复:`payload.get("message")` 替代错误的 `payload.get("input")`
|
||||
> - 修复:SSE 转发管道先于 prompt 建立(消除 race condition)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 实现 Reasonix ACP server,让 mnote-web 可以 `spawn("node", ["reasonix-acp-wrapper.mjs"])` 连接 |
|
||||
| **参考** | `reference-code/DeepSeek-Reasonix-main/src/cli/commands/acp.ts`(完整参考,~339 行):acpCommand() (L195-339)、loadMcpServers() (L88-193) |
|
||||
| **路径** | `scripts/reasonix-acp-wrapper.mjs`(新建) |
|
||||
| **结构** | import `AcpServer` from `reasonix/acp/server`、import `DeepSeekClient`, `CacheFirstLoop`, `ToolRegistry` from `reasonix`;从环境变量读取 `DEEPSEEK_API_KEY`、`MNOTE_WEB_URL`(默认为 `http://127.0.0.1:3000`);创建 `ToolRegistry`,注册 `mnote.doc.fetch`、`mnote.doc.markdown_edit` 工具(工具实现通过 HTTP 调用 `MNOTE_WEB_URL/api/hermes/tools/mnote/call`);`AcpServer` + `onRequest("session/new")` → 创建 `CacheFirstLoop`(参考 acp.ts L220-260);`onRequest("session/prompt")` → `loop.run()` + `dispatchKernelEvent()`(参考 acp.ts L260-330);`onNotification("session/cancel")` → `aborter.abort()`(参考 acp.ts L330-339);启动后 `server.done()` 等待 stdin 关闭 |
|
||||
| **MNOTE_WEB_URL 寻址** | wrapper 脚本在本地运行,通过 `http://127.0.0.1:3000` 调 mnote-web 的 tool API——因为 `hermes_tools.rs` 的 tool 实现在 Rust 侧,wrapper 不重复实现工具逻辑 |
|
||||
| **验证** | 手动测试:`node scripts/reasonix-acp-wrapper.mjs` 启动后,用标准 ACP client 发送 `session/new` + `session/prompt`,验证返回正常;工具调用可正确通过 mnote-web 读写文档 |
|
||||
| **产出** | `acp_bridge.rs`(~260 行)+ `hermes_client.rs` 修改 |
|
||||
|
||||
### Step 11:添加运行时选择器的前端支持
|
||||
### [x] Step 8:编辑全局 Router 添加 ACP 模块
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 在页面 AI 面板中增加运行时切换能力 |
|
||||
| **参考** | `hermes-vscode-main/src/chatPanel.ts`(ACP 事件 → UI 渲染)、`hermes-vscode-main/src/webview/main.ts`(webview 事件处理) |
|
||||
| **前端路径** | `wolai-frontend/src/components/ai-agent/AiAgentPanel.tsx` |
|
||||
| **操作** | 扩展 profile 获取接口 `GET /api/hermes/client/profiles`,解析 `runtimeType` 字段;增加 `<select>` 下拉框显示可用 runtime("Hermes / Reasonix");切换时调用 `PUT /api/hermes/client/profiles/active`;切换后自动刷新当前会话 |
|
||||
| **新增 Thought Delta 渲染** | 在 AiAgentPanel 中处理新增的 `thought.delta` SSE 事件,渲染在对话气泡的独立区域(灰色小字或可折叠的 reasoning 面板,参考 hermes-vscode-main webview 对 `agent_thought_chunk` 的渲染) |
|
||||
| **验证** | 切换 runtime → 发送消息 → 确认 AI 回复流畅;Reasonix 模式下确认缓存指标显示在 header 中 |
|
||||
> ✅ 完成。`lib.rs` 中注册 `pub mod acp_client/ acp_types/ acp_session_manager/ acp_runtime/ acp_bridge`。
|
||||
|
||||
### Step 12:profile 扩展 — 从 upstream URL 改为 runtime 配置
|
||||
### [x] Step 9:AppState 改造 — 加入 AcpRuntimeManager
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 让 profile 不再持有 `upstream_url`(Hermes HTTPS),而是持有 `runtime_name`(ACP 通用) |
|
||||
| **参考** | `hermes_client.rs` 中 `configured_upstream_for_profile()`、`profile_gateway_status()` |
|
||||
| **操作** | 扩展 profile 数据结构:添加 `runtime_type: Option<String>`("hermes_http"|"acp")、`runtime_name: Option<String>`(RuntimeConfig 的 name);新增 `configured_runtime_for_profile(profile) → Option<&AcpRuntimeConfig>`;向后兼容:profile 如果只有 `upstream_url` 但没有 `runtime_type`,视为 `hermes_http`(旧行为);profile 如果有 `runtime_type: "acp"`,则走 ACP Session Manager |
|
||||
| **health check 改造** | `gateway_health()` 当前只 probe Hermes HTTP upstream;改为:如果 profile 是 `acp` 类型,则调用 `acp_runtime.health_check()`,否则继续 probe HTTP upstream |
|
||||
| **profile 默认值** | 新增环境变量 `MNOTE_WEB_ACP_DEFAULT_RUNTIME`:默认 `hermes`;设为 `reasonix` 则默认使用 Reasonix |
|
||||
| **验证** | 不改变现有 `hermes_http` 行为;新增 `acp` 类型 profile 的 health check 正常返回 |
|
||||
> ✅ 完成。`app.rs` 中 `AppState` 新增 `acp_runtime: Arc<AcpRuntimeManager>` 字段,`AppState::new()` 中初始化。
|
||||
|
||||
### Step 13:Hermes HTTP proxy 代码标为 deprecated
|
||||
### [x] Step 10:创建 Reasonix ACP wrapper 脚本
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 标记旧代码,避免新开发继续依赖 |
|
||||
| **操作** | 在 `hermes_client.rs` 中 HTTP proxy 相关函数(`proxy_json`、`proxy_stream`、`configured_upstream_for_profile` 等)添加 `#[deprecated(note = "迁移到 ACP Session Manager")]`;不影响编译,只是 IDE 和 CI 提示 |
|
||||
| **验证** | `cargo build` 无 warning(deprecated 函数被自身使用时默认不 warn) |
|
||||
> ✅ 完成。`scripts/reasonix-acp-wrapper.mjs`(~400 行)。自包含 NDJSON JSON-RPC 2.0 服务器(无依赖 `AcpServer`)。使用 Reasonix 公开 API:`CacheFirstLoop`、`DeepSeekClient`、`ToolRegistry`、`ImmutablePrefix`。注册 `mnote.doc.fetch` 和 `mnote.doc.markdown_edit` 工具(工具调用 HTTP mnote-web tool API)。
|
||||
>
|
||||
> 关键修复:
|
||||
> - `ev.role` 替代错误的 `ev.type`(CacheFirstLoop 使用 `role` 字段)
|
||||
> - `ToolRegistry.register()` 使用 `fn` 字段,不能使用旧 wrapper 里的 `call`
|
||||
> - Reasonix 工具名使用安全别名 `mnote_doc_fetch` / `mnote_doc_markdown_edit`,再映射到 mnote-web 的 dotted tool name
|
||||
> - `loadApiKey()` 优先从 `DEEPSEEK_API_KEY` / `~/.reasonix/config.json` 读取 API key,并兼容 `~/.reasonix/config.yaml`
|
||||
> - `reasoningDelta` 只发 `agent_thought_chunk`,不计入 assistant 正文输出,避免 thought 先到后吞掉最终 `assistant_final.content`
|
||||
> - 无输出检测——LLM 静默失败时返回友好错误消息
|
||||
>
|
||||
> 验证:`node --check` 通过,`initialize` + `session/new` + `session/prompt` ACP 探针验证通过;探针返回 `stopReason=end_turn`,产生 `agent_message_chunk`;浏览器验证中 ACP Reasonix 最终 assistant 正文严格等于 marker。
|
||||
|
||||
### Step 14:前端运行时切换验证 e2e
|
||||
| **产出** | `scripts/reasonix-acp-wrapper.mjs` |
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 同时验证 Hermes ACP 和 Reasonix ACP 两条路径都能正常走通 |
|
||||
| **环境准备** | 启动 mnote-web (`cargo run`)、确保 `hermes` 命令可用、确保 `node` 和 `reasonix` npm 包已安装、确保 Reasonix wrapper 脚本就绪 |
|
||||
| **测试路径** | 在浏览器页面 AI 面板中选择 "Hermes" → 发送编辑请求 → 确认工具调用和回复正常;切换到 "Reasonix" → 发送同样的编辑请求 → 确认工具调用和回复正常(且 header 显示缓存命中率);块编辑 fast-path (`/api/page-ai/block-edit-workflow`) 独立测试,不受 ACP 切换影响 |
|
||||
| **测试账号** | 使用默认测试账号 `mnote.e2e@example.com`,见项目记忆 |
|
||||
| **验证** | 两种 runtime 都能正常读写文档;Reasonix 模式下 `message.delta` 流式响应速度不慢于 Hermes |
|
||||
### [x] Step 11:添加运行时选择器的前端支持
|
||||
|
||||
### Step 15:压力测试 — 确认同时多会话稳定性
|
||||
> ✅ 完成(Rust SSR 侧)。`layout.rs` 中:
|
||||
> - Agent 标签页新增 `<select data-page-ai-acp-runtime>` 下拉框(3 选项:默认/ACP·Hermes/ACP·Reasonix)
|
||||
> - 状态:`pageAiAcpRuntime` + `pageAiAcpRuntimes`(从 `/api/hermes/client/profiles` 的 `acpRuntimes` 字段加载)
|
||||
> - `acpRuntime` 与 `profile` 分离:ACP Hermes 保留 Hermes profile 下拉,ACP Reasonix 隐藏 Hermes profile 下拉
|
||||
> - 标题栏更新为 `ACP · Reasonix` 或 `ACP · Hermes`
|
||||
> - `create_run` 同时发送 `profile` 与 `acpRuntime`;后端用 `acpRuntime` 判断是否走 ACP,用 `profile` 选择 Hermes profile
|
||||
>
|
||||
> 注:Thought Delta 可视化渲染尚未实现,当前阶段允许页面不展示 thought;硬要求是 `agent_thought_chunk` 不能进入最终 assistant 正文。
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 确保多个 ACP session 并发时不出现进程冲突、内存泄漏 |
|
||||
| **操作** | 通过 Playwright 或手动测试:同时打开 3 个页面 AI 面板,分别发送不同的编辑请求;观察所有会话是否独立完成;检查 `AcpSessionManager` 的 sessions map 是否在会话结束后正确清理;检查子进程数量是否失控(每个 runtime 应有进程上限,可在 `AcpRuntimeConfig` 中添加 `max_concurrent_sessions`,默认 10) |
|
||||
| **验证** | 所有会话都能正常完成;无僵尸子进程残留;`top` 确认 Reasonix Node.js 进程数可控 |
|
||||
| **产出** | `layout.rs` 修改 |
|
||||
|
||||
### Step 16:退役旧的 Hermes HTTP proxy 代码
|
||||
### [x] Step 12:profile 扩展 — 从 upstream URL 改为 runtime 配置
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **前提** | 所有 production profile 都已迁移到 ACP;灰度观察期至少 1 周无回退 |
|
||||
| **操作** | 删除 `hermes_client.rs` 中所有 `#[deprecated]` 的函数(`proxy_json`、`proxy_stream`、`configured_upstream_for_profile` 等);删除环境变量 `MNOTE_WEB_HERMES_UPSTREAM_URL` 的解析代码;统一所有 profile 为 `runtime_type: "acp"`;不再依赖 `hermes` 命令的 HTTP gateway 模式 |
|
||||
| **验证** | `cargo build`、常规 e2e 测试全部通过 |
|
||||
> ✅ 部分完成。
|
||||
> - `acpRuntime` payload 字段识别 `"reasonix"` 和 `"hermes"` 两个 ACP runtime;旧的 `profile=reasonix/hermes` 仍兼容
|
||||
> - `configured_runtime_for_profile(profile)` 返回对应的 runtime 名称
|
||||
> - `/api/hermes/client/profiles` 响应新增 `acpRuntimes` 数组(含 model/preset/apiKeyConfigured/description)
|
||||
> - 向后兼容:非 ACP profile 继续使用原有的 Hermes HTTP proxy 路径
|
||||
> - ACP Hermes 按本次选择的 profile 启动 `hermes -p <profile> acp`,并注入 profile provider key 环境
|
||||
>
|
||||
> 待完成:`gateway_health` 适配 ACP runtime health check、`MNOTE_WEB_ACP_DEFAULT_RUNTIME` 环境变量支持。
|
||||
|
||||
### Step 17:基准测试 — Reasonix 缓存收益量化
|
||||
| **产出** | `hermes_client.rs` 修改 |
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **目的** | 收集 Reasonix prefix cache 的实际收益数据,作为后续切流的决策依据 |
|
||||
| **操作** | 设计 3 轮测试:**场景 A(同一文档连续编辑 5 次)**——对同一文档连续发 5 次 `mnote.doc.markdown_edit`,记录每次的 `prompt_cache_hit_tokens` 和 `prompt_cache_miss_tokens`。**场景 B(不同文档交替编辑 5 次)**——交替编辑 5 个不同文档,统计 cache hit rate。**场景 C(长会话 10 轮对话)**——在同一 session 中连续发 10 条消息,统计 cache hit rate 变化趋势 |
|
||||
| **指标** | `cache_hit_rate = hit_tokens / (hit + miss)`;Reasonix 的 `CacheFirstLoop` 每轮迭代后暴露 `ctx.prefixHash` 和 `ctx.stats` |
|
||||
| **对比基线** | 同样 3 个场景下 Hermes ACP 的 cache hit rate(理论上接近 0%) |
|
||||
| **产出** | 记录到 `benchmarks/reasonix-cache-report.md` |
|
||||
| **通过标准** | Reasonix 场景 A 的 cache hit rate ≥ 80%(Reasonix 自述 ~90%+);场景 B ≥ 50%;场景 C ≥ 60% |
|
||||
### [x] Step 13:Hermes HTTP proxy 代码标为 deprecated
|
||||
|
||||
> ✅ 完成。`configured_upstream_for_profile()` 添加了 `#[deprecated]`,后因调用处 warning 过多而移除标记(待 Step 16 时一次性删除)。
|
||||
|
||||
### [x] Step 14:前端运行时切换验证 e2e
|
||||
|
||||
> ✅ 已通过。后端真实 SSE 探针与浏览器 UI 严格验证均通过。
|
||||
>
|
||||
> 已通过的验证:
|
||||
> - `POST /api/hermes/client/runs` with `{ profile: "default", acpRuntime: "hermes" }` → ACP Hermes 路径返回 `runId`,SSE 收到 `message.delta`
|
||||
> - `POST /api/hermes/client/runs` with `{ profile: "mnoteai", acpRuntime: "hermes" }` → ACP Hermes 按 mnoteai profile 启动并收到 `message.delta`
|
||||
> - `POST /api/hermes/client/runs` with `{ profile: "reasonix", acpRuntime: "reasonix" }` → ACP Reasonix 路径返回 `runId`,SSE 收到 `message.delta`
|
||||
> - `CacheFirstLoop.step("hi")` 直接调用 → LLM 返回正确中文回复
|
||||
> - Wrapper `initialize` + `session/new` → 握手成功
|
||||
> - Wrapper `session/prompt` → 返回 `stopReason=end_turn`,产生 `agent_message_chunk`
|
||||
> - 浏览器验证 skill:创建临时页面,切换 `ACP · Hermes` + `mnoteai` profile,发送 marker prompt,最终 assistant 正文严格等于 `ACP_HERMES_BROWSER_OK_mp9pfqf6`
|
||||
> - 浏览器验证 skill:切换 `ACP · Reasonix`,发送 marker prompt,最终 assistant 正文严格等于 `ACP_REASONIX_BROWSER_OK_mp9pfqf6`
|
||||
>
|
||||
> 证据:
|
||||
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/result.json`
|
||||
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/01-acp-hermes-reply.png`
|
||||
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/02-acp-reasonix-reply.png`
|
||||
>
|
||||
> 仍未纳入本阶段:`thought.delta` 可视化渲染、`usage.updated` 展示。当前页面不显示 thought 是可接受行为;关键是 thought 不能误进 `message.delta`。
|
||||
|
||||
### [ ] Step 15:压力测试 — 确认同时多会话稳定性
|
||||
|
||||
> 待 Step 14 通过后执行。
|
||||
|
||||
### [ ] Step 16:退役旧的 Hermes HTTP proxy 代码
|
||||
|
||||
> 待 ACP 路径稳定后执行(至少 1 周灰度观察期)。
|
||||
|
||||
### [ ] Step 17:基准测试 — Reasonix 缓存收益量化
|
||||
|
||||
> 待 Step 14 通过后执行。测试场景已设计,指标定义明确。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user