chore: land tree view-state, vault, Pi module split, and repo hygiene

Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
This commit is contained in:
Agent Board
2026-07-21 05:13:05 +08:00
parent 6f9c7d3b58
commit b798f628ee
264 changed files with 17480 additions and 17314 deletions
@@ -0,0 +1,36 @@
# [recycle] 7-1 [done] 页面 AI provider=hermes 仍命中旧 run route 导致 502
## 现象
- 当前 Rust 3000 页面壳里,页面 AI 默认 provider 是 `hermes`
- 发送消息时仍请求旧 `/api/ai-agent/run`
- Rust compat route 对显式 `provider=hermes` 返回 `ai_provider_bridge_unavailable`HTTP 状态为 502。
## 复现证据
- 页面壳默认 provider`rust/crates/mnote-web/src/ssr/pages/layout.rs`
- 页面 AI 发送旧 route`fetch('/api/ai-agent/run')`
- 旧 route 注册:`rust/crates/mnote-web/src/routes/mod.rs`
- 502 来源:`rust/crates/mnote-web/src/routes/compat.rs`
- 可复跑 smoke`scripts/task-hermes-page-ai-baseline-smoke.js`
## 归类
- owner`07-ai`
- 根因:页面 AI UI 仍走旧 mnote-cli / compat 主链,而默认 provider 已切到 Hermes。
- 修复方向:页面 AI 主链改为同源 `/api/hermes/client/*`,旧 `/api/ai-agent/run` 只保留 legacy guard。
## 验收
- [x] 页面 AI 打开后创建或恢复 Hermes session。
- [x] 发送消息不再请求 `/api/ai-agent/run`
- [x] 未配置 Hermes upstream 时返回稳定 `hermes_client_unconfigured`,不再出现旧 502。
- [x] 配置 Hermes upstream 后 run/event stream 由 Hermes 返回。
## 完成证据
- 代码主链:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 已改为 `/api/hermes/client/sessions``/api/hermes/client/runs``/api/hermes/client/events/{run_id}`
- 合同与路由:`rust/crates/mnote-web/src/routes/hermes_client.rs` 提供同源 Hermes client proxy。
- 测试:`cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web hermes_ -- --nocapture`14 passed。
- smoke`MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-smoke.js` 通过,并拦截旧 `/api/ai-agent/run` 证明新页面 AI 主链未调用旧 route。
- 说明:2026-05-14 已在正式 `3000` 入口复跑 Hermes 页面 AI smoke 矩阵,旧 `/api/ai-agent/run` 只保留 410 retirement guard。
@@ -0,0 +1,44 @@
# [recycle] 7-19 [done][bug] Hermes 工具指导仍优先 apply_block_ops 而非 markdown_edit v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 问题定义
`mnote.doc.markdown_edit` 已被设计为普通页面正文编辑的主路径,但 Hermes run 的工具指导仍要求简单正文编辑优先调用 `mnote_doc_apply_block_ops`
这导致模型策略与工具主线冲突。
## 2. 证据
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2351) 构造工具指导。
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2357) 要求上下文足够时直接调用 `mnote_doc_apply_block_ops`
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2360) 明确“简单小段落编辑”优先用 `apply_block_ops`
## 3. 影响
- Hermes 与 page AI fast-path 会走不同编辑工具。
- markdown 级 search/replace 的收敛设计无法成为唯一验收口径。
- `apply_block_ops` 的 revision / conflictDetectionKey 合同缺陷会被继续放大。
## 4. 建议修复
- 将普通正文 search/replace、局部段落替换、全文 markdown 替换统一引导到 `mnote_doc_markdown_edit`
- 仅在需要结构性块移动、资源块、复杂定位时才引导模型使用 `apply_block_ops``mnote.block.*`
- 同步更新 smoke:模型生成工具调用时应优先产出 `mnote_doc_markdown_edit`
## 5. 修复
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2456) 将 Hermes run instructions 改为:普通正文 search/replace、局部段落替换、全文 markdown 替换优先调用 `mnote_doc_markdown_edit`
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2494) 将 `blockEditingToolOrder` 首位调整为 `mnote_doc_markdown_edit``mnote_doc_apply_block_ops` 仅保留给 markdown_edit 不能表达的结构性块操作。
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:4559) 增加 run guidance 合同测试,防止普通正文编辑重新退回 `apply_block_ops` 优先口径。
## 6. 验证
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_client_run_guidance_prefers_markdown_edit_for_plain_body_edits -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_client_run_body -- --nocapture`
- 结果:2 passed
@@ -0,0 +1,48 @@
# [recycle] 7-20 [done][bug] page_ai_workflow 绕过 Hermes tool executor / audit / toggle v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 问题定义
`/api/page-ai/block-edit-workflow` 仍是独立模型调用链:直接读取 Hermes profile、调用 chat/completions、解析模型 JSON,并直接调用 Rust 工具函数。
它没有通过 `/api/hermes/tools/mnote/call` 的工具执行壳,因此绕过了 tool toggle、audit、统一幂等和统一调用追踪。
## 2. 证据
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:69) 直接调用上游模型。
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:91) 在 route 内构造 `ToolCallInput`
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:109) 直接调用 `doc::doc_markdown_edit`
## 3. 影响
- 关闭或限制 mnote tool 时,该 fast-path 仍可能写入。
- 工具调用审计与普通 Hermes run 不一致。
- 幂等、dryRun、runId、toolCallId 等字段无法统一治理。
## 4. 建议修复
- 将 fast-path 的工具写入改为调用统一 tool executor。
- fast-path 只负责 prompt / plan,不直接执行写入函数。
- 增加 smoke:当对应 tool disabled 时,`page_ai_workflow` 不得绕过限制写入。
## 5. 修复
- [hermes_tools.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_tools.rs:62) 将统一 mnote tool 执行壳抽为 `execute_mnote_tool_call`,保留 profile disabled、audit、idempotency、auth/workspace 校验和统一结果包装。
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:109) `block_edit_workflow` 的写入阶段改为调用统一 executor,不再直接调用 `doc::doc_markdown_edit`
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:657) 增加路由级测试:当 profile 禁用 `mnote.doc.markdown_edit` 时,页面 AI fast-path 必须返回 `mnote_tool_disabled`,不得绕过限制写入。
## 6. 验证
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web block_edit_workflow_respects_disabled_markdown_edit_tool -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture`
- 结果:3 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_call_rejects_profile_disabled_tool -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit_maps_normalized_search_to_block -- --nocapture`
- 结果:1 passed
@@ -0,0 +1,108 @@
# [recycle] 7-25 [done][bug] ACP Reasonix 页面 AI block_edit_workflow 在 markdown 命中后生成空 block_ops v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
>
> 关联缺陷:`7-24 在线 markdown_edit 写回不以最终 Markdown 为真源`
## 1. 用户可见症状
在页面 AI 中切换到 `ACP · Reasonix`,输入:
```text
检查你是否能读取到本页第一段,同时请修改第二段为:测试123
```
工具调用失败:
```text
mnote.page_ai.block_edit_workflow 失败 · page-ai-fast-mp9wm0qo
结果 mnote.doc.apply_block_ops operations 不能为空
```
## 2. 问题定义
`page_ai.block_edit_workflow` 当前已切到:
```text
模型输出 search/replace
-> mnote.doc.markdown_edit
-> 在线文档再转换为 mnote.doc.apply_block_ops
```
但在线写回层没有以最终 Markdown 为真源。它在 markdown 字符串中 search/replace 成功后,又用原始 `operations` 去原始 `blocks` 中反查目标块。如果反查不到块,就生成空 `block_ops`,随后仍调用 `mnote.doc.apply_block_ops`,最终抛出“operations 不能为空”。
## 3. 证据
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:69) 调用模型生成 markdown operations。
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:91) 构造 `mnote.doc.markdown_edit` 输入。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:811) 在 `md` 字符串上执行 search/replace。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:857) 调用 `build_block_ops_from_markdown_edit`
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:989) 只用 `block.text.contains(search)` 反查 block。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:1008) 可能返回空 `block_ops`
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:878) 即使 `block_ops` 为空,也继续调用 `doc_apply_block_ops`
- [block.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/block.rs:405) 对空 operations 报错:`mnote.doc.apply_block_ops operations 不能为空`
## 4. 根因判断
这条错误不是最终 root cause,只是下游暴露出来的症状。
真正根因是 markdown 层与 block ops 写回层存在二次定位:
1. `search_replace(&md, search, replace)` 可以通过精确、归一化或 fuzzy 匹配成功。
2. `build_block_ops_from_markdown_edit` 却只支持 `block.text.contains(search)`
3. 两套匹配规则不一致时,markdown 层显示已命中,block 层却生成空 operations。
4. 空 operations 没有在 `markdown_edit` 层转成语义化错误,而是继续传给 `apply_block_ops`
## 5. 影响
- ACP Reasonix 的页面编辑会向用户暴露底层 `apply_block_ops` 错误,而不是说明“markdown 命中但无法映射到块”。
- “读取第一段 + 修改第二段”这类混合任务可能进入 fast workflow,但该 workflow 只能表达写入,不保证先读后答。
- 如果模型给出的 `search` 是段落序号、fuzzy 片段、带格式文本或跨块文本,markdown 层可能成功,block 写回层仍失败。
## 6. 建议修复:建议以长期,彻底的修复优先,建议参考/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main中对全局替换和单块替换的判断逻辑。
短期:
- `doc_markdown_edit` 在调用 `doc_apply_block_ops` 前,如果 `applied > 0``block_ops.is_empty()`,应返回明确错误,例如 `mnote_doc_markdown_edit_block_mapping_empty`,并包含 failed operation 的 search 摘要。
- `page_ai_workflow` 应把该错误翻译为用户可理解的提示,不要暴露 `apply_block_ops operations 不能为空`
中期:
- `build_block_ops_from_markdown_edit` 必须复用 `search_replace` 的定位结果,或让 `search_replace` 返回原始命中范围 / paragraph / block 映射。
- 对“第一段/第二段”等序号型指令,应由模型输出对应段落原文作为 `search`,并增加回读校验。
长期:
-`7-24` 收口:在线 `markdown_edit` 应以最终 Markdown 为写回真源,或明确限制只支持可安全映射的单块精确替换。
## 7. 复现 / 验证建议
- 浏览器 smokeACP Reasonix,页面含至少两段正文,输入“检查第一段并修改第二段为:测试123”。
- 断言失败时错误码不能是 `mnote.doc.apply_block_ops operations 不能为空`,应是 markdown 到 block 映射失败的明确错误。
- 修复后回读 Page Aggregate,断言第二段实际变为 `测试123`,同时 AI 回复能正常说明已读取到第一段。
## 8. 修复
已修复:
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:862) 在 `mnote.doc.markdown_edit` 调用 `doc_apply_block_ops` 前拦截 `applied > 0 && block_ops.is_empty()`,返回 `mnote_doc_markdown_edit_block_mapping_empty`,不再泄露 `mnote.doc.apply_block_ops operations 不能为空`
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:928) 抽出 `search_replace_exact_or_normalized`,让 markdown 层与 block 映射层共享“精确 / 忽略空白 / 全半角归一化”规则。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:1038) 在线 block ops 构建不再用 `block.text.contains(search)`,改为对单块文本执行同一套安全匹配;fuzzy 仍只保留在 markdown 预处理层,避免写错块。
- [hermes_tools.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_tools.rs:1670) 增加回归测试,覆盖空映射语义错误与忽略空白后正确映射到 `p_2`
## 9. 验证
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit -- --nocapture
```
结果:4 个相关测试通过。
## 10. 剩余风险
本修复解决单块 search/replace 映射和空 ops 下沉;跨块 Markdown 重写、完整最终 Markdown 作为写回真源仍归属于 `7-24`
@@ -0,0 +1,64 @@
# [recycle] 7-31 [done][bug] 页面 AI block_edit_workflow 丢弃读取类回答摘要 v1
> 发现时间:2026-05-18
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 用户可见症状
在页面 AI 使用 `ACP · Reasonix` 输入:
```text
检查你是否能读取到本页第一段,同时请修改第二段为:测试123
```
`block_edit_workflow` 即使完成写入,前端也只显示固定文案:
```text
已通过页面 markdown 编辑快路径完成写入。
```
用户要求中的“是否能读取到第一段”没有被回答。
## 2. 根因
`page_ai_workflow.rs` 的模型提示已经要求模型输出:
```json
{"operations": [...], "summary": "..."}
```
但路由只提取 `operations`,丢弃了 `summary`,并在成功响应中固定返回“已通过页面 markdown 编辑快路径完成写入。”。
这会让读写混合请求退化为纯写入反馈。
## 3. 修复
- 新增 `MarkdownEditPlan`,同时解析模型输出中的 `operations``summary`
- `block_edit_workflow` 成功响应优先返回模型 `summary`,缺失时才使用固定 fallback。
- 系统提示明确要求:如果用户要求读取某段,`summary` 必须包含从 `page_text` 读取到的原文。
- 新增回归测试 `block_edit_workflow_surfaces_model_summary_for_read_and_edit_request`,覆盖“读取第一段 + 修改第二段”的真实快路径响应。
## 4. 验证
RED
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web block_edit_workflow_surfaces_model_summary_for_read_and_edit_request -- --nocapture
```
旧实现失败,`payload.message` 不包含 `已读取第一段:第一段`
GREEN
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture
```
结果:`5 passed`
## 5. 剩余边界
本修复只保证 fast workflow 成功响应不丢模型摘要;复杂多步推理、review session、流式 apply 仍属于 Phase C 冻结范围。
@@ -0,0 +1,45 @@
# [recycle] 7-34 [done][bug] 页面 AI ACP Runtime 默认值回跳 Hermes v1
> 发现时间:2026-05-18
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 用户可见症状
页面 AI 面板中选择 `ACP · Reasonix` 后,会自动回跳到 `ACP · Hermes`
用户期望:
- 默认 runtime 为 `ACP · Reasonix`
- 下拉仍可手动切换到 `ACP · Hermes`
## 2. 根因
页面 AI 的历史 runtime 语义仍把空 `acpRuntime` 当成旧 `Hermes HTTP` 默认值。当前主线已经退役 HTTP Hermes,但前端 session/localStorage 恢复、后端空 runtime 推导、以及 `/profiles` 尚未返回时的下拉渲染仍可能产生空 runtime。
空 runtime 进入旧逻辑后会被解释成 Hermes 路径,导致用户刚选择 Reasonix 后又被历史 session 或空 select value 覆盖。
## 3. 修复
- 前端 `pageAiAcpRuntime` 初始值改为 `reasonix`
- localStorage 版本提升到 `3`,避免旧缓存把 active runtime 覆盖回 Hermes。
- session 创建、持久化、停止 run、发送 run 默认都写入 `reasonix`
- 前端内置 `Reasonix / Hermes` 两个 ACP runtime fallback`/profiles` 慢或失败时也不会渲染空 select。
- 后端空 `acpRuntime` 默认归入 ACP 默认 runtime,默认值为 `reasonix`;旧 HTTP proxy 只保留在显式兼容开关后。
## 4. 验证
新增回归约束:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch -- --nocapture
```
覆盖:
- `pageAiAcpRuntime` 默认是 `reasonix`
- 下拉 options 固定包含 `reasonix` / `hermes`
- 不再包含旧的 `默认 (Hermes HTTP)` 空选项
- change handler 空值 fallback 到 `reasonix`