fix local office resource editing

- add local-folder OnlyOffice sign/callback writeback and edit-tab handling

- align main resource tabs, attachment edit menu, slash isolation, and filetree context behavior

- record Sidex/Hermes gap reviews and Reasonix task checklists
This commit is contained in:
lix-2026
2026-05-21 05:40:06 +08:00
parent a29d9868f6
commit eba1010191
50 changed files with 4309 additions and 283 deletions
@@ -0,0 +1,53 @@
# 7-30 ACP session/load 恢复 checklist v1
> 状态:process
>
> OwnerACP session lifecycle
>
> 参考:`reference-code/hermes-vscode-main/src/sessionManager.ts`
## 目标
让 MNote ACP runtime 在恢复已有 ACP session 时真正调用 adapter 的 `session/load`,避免只恢复 MNote 会话历史而产生 phantom ACP session。
## 允许修改范围
- `rust/crates/mnote-web/src/acp_session_manager.rs`
- `rust/crates/mnote-web/src/routes/hermes_client.rs`
- 与 session load/resume 直接相关的 Rust 测试
## 禁止事项
- 不改 Page AI 前端 UI。
- 不改 Convex schema。
- 不删除或覆盖现有 local AI session JSONL 逻辑。
## Checklist
- [x]`AcpSessionManager` 增加 `load_session(session_id, cwd)` 或等价能力,参数与 Hermes VSCode 一致:`session/load { sessionId, cwd, mcpServers: [] }`
- [x] load 成功时把当前 ACP session id 设置为 loaded idload 返回 null / error 时允许 fallback 到 `session/new`
- [ ] 明确区分 MNote session id 与 ACP adapter session id;需要持久化/返回时字段名不能混淆。
- [x] ACP run 启动处优先尝试恢复已知 ACP session id,再创建新 session。
- [x] 补 mock ACP 测试覆盖 `session/load` 成功、返回 null fallback、新 session 仍可 prompt。
- [x] 更新本文件“执行记录”。
## 验收命令
```bash
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_session_manager -- --test-threads=1
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web hermes_client_acp_resume -- --test-threads=1
```
## 执行记录
- 2026-05-20 22:06 — Reasonix Code 执行,Codex 复核并通过聚焦测试。
- `acp_types.rs`: 新增 `SessionLoadParams` / `SessionLoadResult` 类型。
- `acp_session_manager.rs`:
- 新增 `load_session(session_id, cwd)` → 调用 ACP `session/load`,成功时设置 internal session_idnull/error 返回 false。
- 新增 `ensure_session(cwd, stored_session_id)` → 1) 复用已有 session2) 有 stored id 时尝试 load3) fallback 到 `session/new`
- 新增 7 个单元测试覆盖所有路径:load 成功、null fallback、复用 active、无 stored 创建新 session、fallback 后 prompt 仍可工作。
- `hermes_client.rs`:
- `acp_stream_events()` 中从 payload 提取 `acpSessionId`,传给 `ensure_session()` 替代原来的 `create_session`
- 错误码从 `acp_session_create_failed` 改为 `acp_session_ensure_failed`
- Codex 复核:当前 `acpSessionId` 只从 run payload 读取,前端/session store 仍未稳定持久化并传回该字段,因此“session id 字段全链路区分与持久化”保留未完成。
- 验收:`cargo test -p mnote-web acp_session_manager -- --test-threads=1``cargo test -p mnote-web hermes_client_acp_resume -- --test-threads=1``cargo test -p mnote-web acp -- --test-threads=1` 通过。
@@ -0,0 +1,52 @@
# 7-31 ACP permission 决策闭环 checklist v1
> 状态:done
>
> OwnerACP incoming request / permission loop
>
> 参考:`reference-code/hermes-vscode-main/src/acpClient.ts`、`src/sessionManager.ts`
## 目标
`session/request_permission` 从"后端立即 unsupported/deny"改成"后端发出 pending request,前端 allow/deny,后端回写 JSON-RPC response"的真实闭环;超时仍默认 deny。
## 允许修改范围
- `rust/crates/mnote-web/src/acp_client.rs`
- `rust/crates/mnote-web/src/acp_session_manager.rs`
- `rust/crates/mnote-web/src/acp_bridge.rs`
- `rust/crates/mnote-web/src/routes/hermes_client.rs`
- 只允许为接线 permission 按需小改 `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- 相关 Rust 测试
## 禁止事项
- 不把权限结果存为新的长期正文/附件真相。
- 不默认 allow;超时、缺失 run、缺失 pending request 都必须 deny 或返回清晰错误。
- 不大改 Page AI drawer 视觉结构。
## Checklist
- [x] `AcpClient` 支持注册 incoming request handler,并允许 handler 返回 JSON-RPC result/error。
- [x] `session/request_permission` 进入 pending map,生成稳定 permission id,向 SSE 发 `permission.requested`
- [x] 增加 HTTP endpoint 或现有 endpoint 扩展:前端可按 run id / permission id 提交 allow/deny。
- [x] 提交 allow/deny 后,ACP JSON-RPC request 得到对应 response;前端收到 `permission.allowed` / `permission.denied`
- [x] 超时默认 deny,并向 UI 和 runtime event 写明 timeout。
- [ ] 补 mock ACP 测试:incoming request 等待决策、allow 返回 result、deny 返回 result/error、timeout deny。
- [x] 更新本文件"执行记录"。
## 验收命令
```bash
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_client -- --test-threads=1
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_bridge -- --test-threads=1
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --test-threads=1
```
## 执行记录
- 2026-05-20Reasonix 实施,Codex 复核并修正 JSON-RPC id 保真与 permission response 语义。
- 改动点:`AcpClient` 增加 `on_incoming_request()` / `respond_to_incoming()` / `respond_to_incoming_error()``dispatch_message` 将 incoming request 转发给 handler 而非立即 error`AcpSessionManager.new()` 注册 incoming request handler,将 `session/request_permission` 存入 pending map + 发射 `permission.requested` + 启动 60s 超时自动 deny`AcpSessionManager` 增加 `resolve_permission()` 方法;`acp_bridge` SSE 转换支持 `permission.requested``hermes_client.rs` 增加 `resolve_permission` HTTP endpoint`routes/mod.rs` 注册路由;`layout.rs``pageAiResolvePermission` 调用后端。
- Codex 修正:incoming JSON-RPC request id 使用原始 `serde_json::Value` 保存和回写,避免字符串 id 被改成 0;allow/deny 正常路径改为参考 Hermes VSCode 插件的 `{ outcome: "selected", optionId }`,并补 `permission_response_selects_allow_and_deny_options` 测试。
- 未完成:已有 `acp_client` incoming request 基础测试和 option 选择 helper 测试,但还缺“mock ACP 发 permission -> HTTP resolve endpoint -> agent 收到 selected option”的端到端测试,故该 checklist 的 mock 闭环项保留未完成。
- 验收:`cargo test -p mnote-web acp_client -- --test-threads=1``cargo test -p mnote-web acp_bridge -- --test-threads=1``cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --test-threads=1``cargo test -p mnote-web acp -- --test-threads=1` 通过。
@@ -0,0 +1,54 @@
# 7-32 ACP tool locations 与打开动作 checklist v1
> 状态:process
>
> OwnerACP tool event data + Page AI open action
>
> 参考:`reference-code/hermes-vscode-main/src/protocol.ts`、`src/chatPanel.ts`
## 目标
保留 ACP `tool_call.locations[]`,在 Page AI tool card 中展示位置,并对 read/edit 完成事件提供打开动作。默认打开到 MNote 主编辑器资源 tab / 当前工作区 tab,不默认新浏览器窗口。
## 允许修改范围
- `rust/crates/mnote-web/src/acp_types.rs`
- `rust/crates/mnote-web/src/acp_session_manager.rs`
- `rust/crates/mnote-web/src/acp_bridge.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- 相关 Rust 测试和必要 smoke 脚本
## 禁止事项
- 不重写资源 tab registry。
- 不新增与 Sidex workbench checklist 冲突的 editor group 模型。
- 不默认 `window.open`
- 不绕过 existing allowed root / local workspace access。
## Checklist
- [x] `SessionUpdate::ToolCall` 解析 `locations: [{ path }]`
- [x] `AcpSessionEvent::ToolCall` 携带 locations,并通过 SSE `tool.started` 发送。
- [x] `tool.completed` 能保留或关联此前相同 `toolCallId` 的 locations/kind/title。
- [x] Page AI tool card 展示 location 摘要。
- [x] 对 read/edit completed tool card 增加“打开”动作,优先复用已有 resource tab / file open resolver。
- [ ] 补测试覆盖 locations parse、SSE 数据、前端字符串断言或 smoke。
- [x] 更新本文件“执行记录”。
## 验收命令
```bash
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_tool -- --test-threads=1
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --test-threads=1
```
## 执行记录
- 2026-05-20 Reasonix Code 实现:
- `acp_types.rs`: 新增 `ToolLocation` 结构体(含 `path` 字段),`SessionUpdate::ToolCall` 新增 `locations: Vec<ToolLocation>`(含反序列化支持)。
- `acp_session_manager.rs`: `AcpSessionEvent::ToolCall` 新增 `locations: Vec<String>``session_update_to_event` 中映射 locations。
- `acp_bridge.rs`: SSE `tool.started` 事件数据包含 `locations` 数组,桥测试中验证。
- `layout.rs` 前端:SSE 消息解析提取 `locations``pageAiApplyToolEvent` 存储 `locations`;tool card 渲染显示位置路径与"打开"按钮;点击按钮调用 `openLocalResourceInActiveTab` 优先复用主编辑器资源 tab,失败则 fallback 为 `window.open`
- 测试覆盖:`test_session_update_tool_call_with_locations`parse)、`acp_tool_events_keep_detail_for_collapsible_ui` 验证 SSE locations 字段、`page_ai_uses_backend_acp_session_runtime_store` 验证布局完整性。
- Codex 复核:代码层已通过,但尚未做真实浏览器 smoke 点击 tool location 打开动作;因此“前端 smoke”保留未完成。
- 验收通过:`cargo test -p mnote-web acp_bridge -- --test-threads=1``cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --test-threads=1``cargo test -p mnote-web acp -- --test-threads=1`
@@ -0,0 +1,49 @@
# 7-33 ACP session info / plan UI checklist v1
> 状态:process
>
> OwnerACP metadata SSE + Page AI lightweight UI
>
> 参考:`reference-code/hermes-vscode-main/src/sessionManager.ts`、`src/webview/renderers.ts`
## 目标
把已解析但被丢弃的 `session_info_update``plan` 事件送到 Page AI UI:自动标题更新当前会话;plan/todo 以轻量状态面板或系统消息展示,不混入正文真相。
## 允许修改范围
- `rust/crates/mnote-web/src/acp_bridge.rs`
- `rust/crates/mnote-web/src/routes/hermes_client.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- 相关 Rust 测试
## 禁止事项
- 不改 ACP parser 类型,除非发现现有 PlanEntry 无法表达必要字段。
- 不实现完整 Phase C review session。
- 不把 plan/todo 写入页面正文。
## Checklist
- [x] `AcpSessionEvent::SessionInfoUpdate` 转成 `session.info.updated` SSE。
- [x] `AcpSessionEvent::PlanUpdate` 转成 `plan.updated` SSEdata 包含 entries。
- [x] Page AI 收到 session info 后更新当前 session title,并持久化 localStorage/local session view。
- [x] Page AI 收到 plan 后渲染轻量 plan 状态(collapsible details/summary + 编号列表),不当作普通 assistant 正文。
- [ ] tool raw output 中包含 todo JSON 时,可以复用 plan 区域或 tool card 状态显示。(风险可控,todo JSON 解析需额外确认格式约定;建议后续补)
- [x] 补 bridge 单测和 `SIDEBAR_TREE_JS` 字符串断言。
- [x] 更新本文件“执行记录”。
## 验收命令
```bash
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_bridge -- --test-threads=1
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --test-threads=1
```
## 执行记录
- 2026-05-20 Reasonix:桥接 SessionInfoUpdate → `session.info.updated` SSE、PlanUpdate → `plan.updated` SSE。
- 2026-05-20 Reasonix:前端 SSE handler 解析 session.info.updated 更新当前 session title 并持久化;plan.updated 在 pageAiMessages 末尾创建/更新 kind=plan 消息。
- 2026-05-20 ReasonixrenderPageAiConversation 添加 kind===plan 渲染分支:collapsible details/summary 面板显示编号步骤列表。
- 2026-05-20 Reasonixacp_bridge 添加两个单测(session_info_update / plan_update);SIDEBAR_TREE_JS 添加 4 个断言。
- 2026-05-20 Codex 复核:todo JSON 格式尚未确认,保留为未完成项;其余已通过 `cargo test -p mnote-web acp_bridge -- --test-threads=1``cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --test-threads=1``cargo test -p mnote-web acp -- --test-threads=1`
@@ -0,0 +1,99 @@
# 13 Hermes VSCode 插件对照审查 v1
> 状态:process
>
> 日期:2026-05-20
>
> 范围:`reference-code/hermes-vscode-main` 对照 MNote 当前 `mnote-web` Hermes / ACP / Page AI 实现。
## 1. 审查方法
本轮先以 Hermes VSCode 插件为参考实现,核查它对 ACP runtime、会话恢复、权限请求、tool call、上下文注入和 Webview 控制面的处理方式,再对照 MNote 当前 Rust Web 主链:
- 参考实现:`src/acpClient.ts``src/sessionManager.ts``src/protocol.ts``src/chatPanel.ts``src/sessionStore.ts``src/webview/main.ts``src/webview/renderers.ts``src/modelCatalog.ts``src/skillCatalog.ts`
- MNote 实现:`rust/crates/mnote-web/src/acp_client.rs``acp_session_manager.rs``acp_bridge.rs``routes/hermes_client.rs``ssr/pages/layout.rs`
- 审查口径:不把 VSCode UI 状态照搬为 MNote 真相源;MNote 继续遵守 local-first、Rust Web 承载、Page AI 文件引用 + agent 原生 patch/diff + watcher 同步主线。
## 2. Hermes VSCode 可借鉴模型
- `SessionManager.ensureSession()` 在已有 `storedSessionId` 时必须调用 ACP `session/load`。仅把旧 session id 写回内存会产生 phantom session,后续 `session/prompt` 可能静默失败。
- `AcpClient` 区分 JSON-RPC response、notification 和 incoming request`session/request_permission` 通过 handler 返回真实 allow / deny 结果。
- `session/update` 中的 `tool_call.locations[]` 会被解析保存;`tool_call_update completed` 后,`read` 打开 preview editor`edit` 打开 persistent editor。
- `session_info_update` 会更新会话标题;`plan` / todo tool 输出可渲染为任务 overlay,而不是丢弃。
- Webview 对 slash command 有 allowlistslash 响应渲染为居中的 system message,不混入普通 user / assistant 泡泡。
- model / skill picker 来自 Hermes 本地缓存和 `SKILL.md` 目录,作为 agent 控制面的一部分。
- Prompt 注入包含 cwd、active file、selection、open tabs、附件引用和 skills advisory,不把这些上下文当成长期正文真相。
## 3. MNote 当前状态
- Rust `AcpSessionManager` 已有 `session/new``session/prompt``session/cancel`、streaming dedup、typed event 和 usage / thought / tool / plan / session info 解析雏形。
- `acp_bridge.rs` 已把 TextDelta、ThoughtDelta、ToolCall、ToolCallUpdate、UsageUpdate、PermissionRequest 转成 SSE,但 `SessionInfoUpdate``PlanUpdate` 当前仍被丢弃。
- `layout.rs` Page AI 已有 session list/detail/search/resume/rename/delete、localStorage cache、Convex/local ACP runtime store、permission dialog、tool event UI、profile/skills 控制面和 usage summary。
- `routes/hermes_client.rs` 已有 local-first AI session JSONL、Convex ACP runtime store、run queue、abort 和 local agent audit。
- 当前 `resume_session` 只恢复 MNote/Convex/local 会话历史,不等同于对 ACP adapter 调用 `session/load` 注册底层 ACP session。
- 当前 `AcpClient` 对 incoming `session/request_permission` 仍立即返回 unsupported/deny,前端按钮没有真正回传 ACP 决策。
- 当前 `SessionUpdate::ToolCall` 类型未保留 `locations`,因此无法像 Hermes VSCode 一样把 read/edit 工具结果联动到主编辑器 tab。
## 4. P0 缺口
### P0-1 ACP `session/load` 恢复缺失
MNote 有会话列表、恢复按钮和 runtime store,但恢复更多是 MNote 会话历史层的恢复。对 ACP runtime 来说,若没有对 adapter 调用 `session/load { sessionId, cwd, mcpServers: [] }`,后续 prompt 可能对应不到 agent 侧真实 session。
建议:在 `AcpSessionManager` 增加 stored ACP session id / `load_session` / `ensure_session` 能力;在 `hermes_client` 的 ACP run 启动处尽量使用已保存 ACP session id 进行 load,失败再 `session/new`
### P0-2 权限请求缺少真实 allow / deny 闭环
前端存在 permission dialog,但后端 `AcpClient` 对 incoming request 立即返回 JSON-RPC error,再广播 denied。用户点击允许/拒绝不能影响当前 ACP 请求。
建议:`AcpClient` 增加 incoming request handler`AcpSessionManager``session/request_permission` 转成 pending request`routes/hermes_client` 提供按 run/session/permission id 决策的短生命周期接口;超时默认 deny。
### P0-3 tool `locations[]` 与 read/edit 打开联动缺失
Hermes VSCode 会保存 tool locations,并在 read/edit 完成后打开对应文件。MNote 当前 tool SSE 只有 raw input / output,不保留 `locations`,也没有把 tool 完成联动到 main editor resource tab / file tree target。
建议:扩展 ACP 类型、event 和 SSE 数据,保留 `locations`;前端对 completed read/edit 工具提供“打开”动作,优先复用现有 main editor resource tab,不默认新浏览器窗口。
### P0-4 `session_info_update` / `plan` 被桥接层丢弃
MNote 已在 `AcpSessionManager` 解析 `SessionInfoUpdate``PlanUpdate`,但 `acp_bridge` 当前返回 `None`。会话自动标题、agent plan/todo 不能进入 UI,也不能进入 runtime event 记录。
建议:转发 `session.info.updated``plan.updated` SSE;前端更新当前会话标题,并将 plan/todo 作为轻量状态面板或 tool/system message 渲染。
## 5. P1 缺口
- slash command 响应仍更像普通聊天消息,缺少参考实现中的 system message 视觉语义和 allowlist 明确边界。
- skills / model 控制面已存在,但未完全对齐 Hermes 本地 model cache 与 `SKILL.md` 目录;需要确认是否存在过期 profile / skill 混用。
- busy queue / interrupt cancel 已有队列和 stop,但与参考实现“第二条消息排队、interrupt 立即 unblock”的交互语义仍需 smoke 覆盖。
- Page AI session restore 需要区分 MNote session id、ACP adapter session id、run id,避免 UI 把任意历史 session 当作可继续底层 ACP 对话。
- tool raw output 中的 todo JSON 还没有独立 overlay,计划状态只能混在 tool card 中。
## 6. P2 缺口
- Webview markdown rendering / sanitization 参考实现使用 `marked` + `DOMPurify`MNote 当前对 assistant 消息仍偏纯文本逃逸,后续可评估富文本安全渲染。
- active file / selection / open tabs 上下文注入需要继续与 MNote editor group / resource tab 模型对齐。
- Hermes 插件中 attachments 以 `[Referenced file: path]` 进入 promptMNote local-first 已有文件 scope,但附件引用提示仍可更清晰。
- session store 的 max 20 sessions / max 300 messages 可作为 MNote localStorage/local JSONL 裁剪策略参考。
## 7. 不建议照搬
- 不照搬 VSCode Webview 的 DOM/CSS 结构;MNote 仍使用 Wolai 主题 Page AI drawer。
- 不把 VSCode `workspaceState` 当作 MNote 会话真相;local-first 下本地 JSONL / Convex control-plane / runtime registry 各自职责不变。
- 不把 Hermes 插件中的自动打开文件行为做成不可控跳转;MNote 应默认在 main editor resource tab 打开,并保留显式新窗口入口。
- 不在 UI 层新增第二套文件权限真相;权限和 allowed roots 仍由 Rust Web / local-first scope 决定。
- 不把 Page AI block-edit workflow 扩成普通 Markdown 主路径;local folder 继续走文件引用 + agent 原生 patch/diff + watcher 同步。
## 8. 执行拆分
本 review 先拆四份 P0 checklist,允许并行推进:
- `design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md`
- `design/07-ai/process/7-31-acp-permission-decision-loop-checklist-v1.md`
- `design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md`
- `design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md`
P0 收口后再拆 P1
- slash/system message 与 model/skill 控制面收口。
- queue / interrupt cancel browser smoke。
- assistant markdown 安全渲染评估。