From bb2f190f501af9e9d5e41fc243c4979978a576d8 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Sun, 17 May 2026 20:11:39 +0800 Subject: [PATCH] feat: stabilize page AI ACP runtimes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现并稳定页面 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/ --- ...3-15-trash-workbench-sse-pollms-risk-v1.md | 51 +++ ...e-ai-acp-agent-runtime-unified-layer-v1.md | 373 ++++++++------- rust/crates/mnote-web/src/acp_bridge.rs | 116 +++-- rust/crates/mnote-web/src/acp_client.rs | 58 ++- rust/crates/mnote-web/src/acp_runtime.rs | 90 ++-- .../mnote-web/src/acp_session_manager.rs | 116 +++-- rust/crates/mnote-web/src/acp_types.rs | 198 +++++++- rust/crates/mnote-web/src/editor_actor.rs | 21 +- .../mnote-web/src/hermes_tools/block.rs | 7 +- rust/crates/mnote-web/src/hermes_tools/doc.rs | 40 +- .../mnote-web/src/routes/command_support.rs | 2 +- rust/crates/mnote-web/src/routes/gateway.rs | 2 +- .../mnote-web/src/routes/hermes_client.rs | 429 ++++++++++++++++-- .../mnote-web/src/routes/page_ai_workflow.rs | 5 +- rust/crates/mnote-web/src/routes/sse.rs | 24 +- rust/crates/mnote-web/src/ssr/pages/layout.rs | 46 +- rust/crates/mnote-web/src/transport/convex.rs | 18 +- rust/target/.rustc_info.json | 2 +- scripts/reasonix-acp-wrapper.mjs | 160 +++++-- 19 files changed, 1307 insertions(+), 451 deletions(-) create mode 100644 bugs/03-rust-web/done/3-15-trash-workbench-sse-pollms-risk-v1.md diff --git a/bugs/03-rust-web/done/3-15-trash-workbench-sse-pollms-risk-v1.md b/bugs/03-rust-web/done/3-15-trash-workbench-sse-pollms-risk-v1.md new file mode 100644 index 00000000..a7f2f46a --- /dev/null +++ b/bugs/03-rust-web/done/3-15-trash-workbench-sse-pollms-risk-v1.md @@ -0,0 +1,51 @@ +# 3-15 [done] 垃圾箱 workbench 残留 `pollMs=1000` 触发 Convex query 风险 + +> 创建时间:2026-05-17 +> +> 更新时间:2026-05-17 +> +> 状态:`done` + +## 问题 + +主页面树实时链路已在 `57ec8322` 切到 WebSocket push,但 `/trash` 渲染的垃圾箱 workbench 仍创建 `EventSource('/api/tree/events')` 并显式设置 `pollMs=1000`。 + +这条路径不是首页主链路,但用户打开垃圾箱页面或弹窗后,仍可能把 `/api/tree/events` 带回 1 秒轮询语义,重新放大 Convex `POST /api/query` 压力。 + +## 处理方案 + +1. 为 `/trash` 渲染测试增加断言:垃圾箱 workbench 不得输出 `pollMs=1000`。 +2. 修改垃圾箱 workbench 事件订阅,移除显式 `pollMs=1000`。 +3. 保留 `EventSource('/api/tree/events')` 作为兼容刷新通道,让 Rust SSE route 使用当前 push-driven 默认行为。 +4. 运行 `mnote-web` 相关单测,确认渲染输出不再包含旧轮询参数。 + +## 处理结果 + +- 已移除 `rust/crates/mnote-web/src/routes/gateway.rs` 中垃圾箱 workbench 的 `url.searchParams.set('pollMs', '1000')`。 +- 已保留 `/api/tree/events` 事件通道,避免破坏垃圾箱打开后根据 tree event 刷新的兼容行为。 +- 已增加测试断言,确保 `/trash?workspaceId=...` HTML 不再输出 `pollMs`。 + +## 验证 + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::gateway::tests::trash_entry_renders_real_workspace_trash_workbench -- --nocapture +``` + +结果:`1 passed`。 + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::gateway::tests:: -- --nocapture +``` + +结果:`18 passed`。 + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::sse::tests::live_poll_query_drops_bridge_pagination_cursor -- --nocapture +cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::sse::tests::sse_route_returns_workspace_snapshot_event -- --nocapture +``` + +结果:两个 SSE 单测均 `1 passed`。 + +## 备注 + +整组 `routes::sse::tests::` 批量运行时,两个长连接流测试超过 60 秒未自然结束,已中断;本次改动未触碰 SSE route 行为,已用相关短测试和 `gateway` 渲染测试覆盖本缺陷。 diff --git a/design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md b/design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md index 86963144..4c6e6394 100644 --- a/design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md +++ b/design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md @@ -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 标签页新增 `` 下拉框显示可用 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`("hermes_http"|"acp")、`runtime_name: Option`(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` 字段,`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 标签页新增 `