# 10 [process] 当前 mnote 项目 AI / Page Aggregate 定向 Review v1 > 更新时间:2026-05-17 > > 执行状态:`process` > > 范围:当前主线中 Page Aggregate 单一真源、页面 AI 快速编辑、`mnote.doc.markdown_edit` 与 ACP / Hermes runtime 的源码级定向审查。 ## 1. 本轮结论 本轮没有发现 Page Aggregate 读取主链重新回退到 Next compat builder 的证据;`wolai-frontend/src/lib/documents/page-aggregate-loader.ts` 仍只走 Rust `/api/page-aggregate/:documentId`,`/api/documents/page` compat 读链保持 `410` 退场口径。 新的风险集中在 `07-ai` 的 markdown 编辑收敛实现: - `mnote.doc.markdown_edit` 已进入实际写入主线,并被 `/api/page-ai/block-edit-workflow` 调用。 - 该工具当前先在 markdown 字符串上顺序执行 search/replace,再把原始 operations 重新转换成 `mnote.doc.apply_block_ops`。 - 转换层仍按“单块文本包含 search”查找目标块,无法忠实表达 markdown 层已经算出的最终结果。 因此,当前 AI 编辑主线不能只看 `operationsApplied > 0` 或 route 返回 `ok=true`。需要优先补充 `markdown_edit` 的中文、多操作、同块、多块、full_content 与失败原子性测试。 ## 2. 发现的问题 ### R-01 P0:中文归一化匹配使用 byte index 当 char index 关联缺陷: - `bugs/07-ai/process/7-16-markdown-edit-normalized-search-byte-index-v1.md` 证据: - `rust/crates/mnote-web/src/hermes_tools/doc.rs:920-936` `search_replace` 的 Level 2 先在 `norm_line` 上用 `find()` 得到 UTF-8 byte offset,却把这个 offset 传给 `line.char_indices().nth(start)` 当作字符序号使用。中文、多字节标点或中英混排时,替换范围会偏移,可能替错片段或生成错误内容。 影响: - 页面 AI 对中文正文的“忽略空白 / 全半角差异”替换不可靠。 - 该问题位于 `mnote.doc.markdown_edit` 的基础 search/replace 层,会同时影响在线文档和本地 `.md` 文件。 ### R-02 P0:同一块内多次 markdown_edit 会被后续 block op 覆盖 关联缺陷: - `bugs/07-ai/process/7-17-markdown-edit-same-block-multi-op-overwrite-v1.md` 证据: - `rust/crates/mnote-web/src/hermes_tools/doc.rs:779-824` - `rust/crates/mnote-web/src/hermes_tools/doc.rs:854-878` - `rust/crates/mnote-web/src/hermes_tools/doc.rs:976-1008` `doc_markdown_edit` 已经在 `md` 变量中顺序应用了所有 search/replace,但 Convex 写入时没有使用这个最终 markdown。它重新读取原始 Page Aggregate blocks,并对每个 operation 用原始 block 文本生成一个 replace block op。 如果两个 operation 命中同一个 block,第二个 block op 的 `content` 仍从原始文本计算,会覆盖第一个 operation 的结果。也就是说,markdown 层算出的最终结果与实际落库结果可能不一致。 影响: - AI 一次请求中常见的“把同一段里的 A 改成 B,同时把 C 改成 D”可能只保留最后一次修改。 - 前端快路径仍可能显示“已通过页面 markdown 编辑快路径完成写入”,但正文只部分生效。 ## 3. 次级风险 - `doc_markdown_edit` 当前允许部分 operation 失败后继续写入已成功的子集,并返回 `ok=true`。如果这是有意设计,需要在 manifest / UI 中明确“非原子”;如果不是,应改为任一 operation 失败时不写入。 - `changedText` 通过 `operations.iter().take(applied)` 生成摘要;当前如果第一个 operation 失败、第二个成功,摘要会错误地展示第一个失败 operation。 - `/api/page-ai/block-edit-workflow` 的系统 prompt 已改成 search/replace,但仍强依赖模型精确复制 `pageText`;一旦模型输出跨块片段,Convex 写回层不能表达该编辑。 ## 4. 建议下一步 1. 先给 `search_replace` 补中文归一化替换单测,再修正 byte / char offset 映射。 2. 给 `doc_markdown_edit` 补同一 block 多 operation 的失败用例,明确应以最终 markdown 生成落库内容,或在转换层合并同块操作。 3. 明确 `markdown_edit` 的失败原子性:默认建议任一 operation 失败时不写入,除非请求显式允许 partial apply。 4. 将 `/api/page-ai/block-edit-workflow` 的验收从“route 成功”提升为“Page Aggregate 回读与预期最终 markdown 一致”。 ## 5. 本轮验证 静态检查范围: ```bash rg -n "markdown_edit|apply_block_ops|block-edit-workflow|local_rule|page_ai_workflow|mnote\\.doc\\.fetch" rust wolai-frontend scripts design/07-ai design/10-review --glob '!rust/target/**' --glob '!node_modules/**' rg -n "page-aggregate|pageAggregate|PageAggregate|blockDocument|projectionSource|documents/save|documents/options|/api/page-aggregate" rust/crates/mnote-web rust/crates/bridge-runtime wolai-frontend/src scripts --glob '!rust/target/**' --glob '!node_modules/**' ``` 本轮是 review / bug hunt,没有修改 runtime 源码。后续修复应至少补: ```bash cargo test --manifest-path rust/Cargo.toml -p mnote-web markdown_edit cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow ```