# 7-14 [process] 在线 / 本地文档 AI Markdown 编辑路径收敛 v2 > 创建时间:2026-05-16 > > 更新时间:2026-05-16(v3:深度参考 CLI Main skill 系统,补全成熟度采纳清单) > > 2026-05-18 口径补充: > - local-first workspace 已成为早期产品默认形态;本地 `.md` 是默认 AI 编辑目标,在线 Convex 文档降级为可选 cloud / sync / share source。 > - 本文早期把 `mnote.doc.markdown_edit` 描述为统一主路径;最新口径改为:local-first 普通 Markdown 编辑优先给 agent 授权文件引用,由 agent 使用自身成熟的 diff / apply_patch / 文件编辑能力完成;`mnote.doc.markdown_edit` 保留为 cloud / remote agent / compat fallback。 > - 页面内图片 / 附件上传已在 local source 下写入 `{mdBase}.assets/` 并保存相对 Markdown 路径,AI 后续处理附件引用时也应保留相对路径,不改写为 Convex media asset。 > > 当前状态:`PROCESS` > > 本稿目的: > 1. 纠正 7-9 / 7-10 / 7-12 / 7-13 中隐含的「块级编辑是 AI 唯一写入路径」假设 > 2. 基于 CLI Main 参考实现,确立 mnote 的「agent 原生文件 patch/diff 为 local-first 默认路径,MNote 文本级兼容工具 + 块级结构性操作为 fallback / 辅助」两层模型 > 3. 规划 BlockNote AI 流式/review 能力的远期方向(当前不实施) > 4. 统一 cloud / remote / compat 文档和本地 `.md` 文件的 AI 读取、权限、冲突与回读口径 > > 关联文档: > - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md` > - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md` > - `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` > - `/mnt/Data1T/mnote/design/07-ai/done/7-13-page-block-editor-runtime-actor-v1.md` > - `/mnt/Data1T/mnote/design/03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md` > - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-22-local-folder-convex-unified-tree-execution-checklist-v1.md` > - `/mnt/Data1T/mnote/bugs/07-ai/process/page-ai-block-edit-model-fallback-missing-content.md` > > 参考实现(已分析): > - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main/` — **主参考**:skill 系统、scope/detail 上下文、两层操作、工作流、参考文件模式、CI 校验 > - `skills/lark-doc/SKILL.md` + `references/` > - `skill-template/`(master-skill-template.md、skill-template.md) > - `shortcuts/doc/`、`internal/skillscheck/` > - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/blocknote-ai/packages/xl-ai/` — 远期参考:流式/review > - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-ai-autocomplete/` — 独立功能:内联补全 > > 上位依据: > - `/mnt/Data1T/mnote/ARCHITECTURE.md` > - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` --- ## 1. 结论 **在线 Convex 文档和本地 `.md` 文件本质上是同一个东西:一段 markdown 文本,Tiptap 只是块级 UI 表现层。** 进一步切到 local-first 后,本地 `.md` 已经是普通文件,因此不需要再为常规正文编辑发明一套 MNote 专用工具。当前 07-ai 设计把 AI 编辑契约钉死在 `EditorBlockDocument` 的块级操作上,导致三个连锁问题: 1. **AI 被迫在块级操作**:对「改写这段话」「补充一段总结」「调整语气」「把所有 TODO 改成 DONE」等自然请求,AI 必须产出 `{ op: "replace", blockId: "block_1", ... }` 格式的块操作,而不能直接产出修改后的文本或搜索替换对。这增加了 AI 的认知负担和出错概率。 2. **本地 `.md` 文件没有 AI 能力**:本地文件没有稳定的 `blockId`(每次解析重新分配),因此块级工具无法应用于本地文件。当前 Hermes 的 13 个工具没有一个是面向本地文件的。 3. **两套口径维护**:在线文档(块操作)和本地文档(无 AI 路径)长期走两套路径,维护成本翻倍。 ### 参考实现的验证 分析了三个参考实现后,确认 mnote 的方向是正确的: | 参考实现 | AI 编辑模型 | mnote 采纳 | |----------|-----------|-----------| | **CLI Main (Lark Doc)** | 文本级 `str_replace` + 块级 `block_replace/insert_after/delete/move_after` 两层操作 | ✅ **主参考**:两层模型 + AI skill 合同设计 | | **BlockNote AI** | 纯块级 `add/update/delete`(依赖 blockId),流式 apply + suggest/review | ✅ **远期参考**:流式/review 能力,Phase C 规划 | | **Tiptap AI Autocomplete** | 纯文本补全,单句接龙,无工具 | ⏳ 独立功能:内联 AI 补全(非本文讨论范围) | **CLI Main 最关键的设计决策**:它不把 AI 钉死在某一层。`str_replace` 用于文本级修改(不需要 blockId),`block_*` 用于精确块结构调整。格式上,**XML 用于精确编辑场景,Markdown 用于整篇导入/导出**。mnote 的不同之处在于:local-first 之后,默认对象就是本地 `.md` 文件,因此可以直接复用 Codex / Hermes / Reasonix 自身成熟的 diff、apply_patch、文件编辑能力;MNote 的职责收口为权限沙箱、文件引用解析、审计和刷新。 ### 正确方向 > mnote 的 AI 编辑路线:**本地授权文件 + agent 原生 patch/diff 为主;MNote 兼容工具为 cloud/remote/结构化辅助;BlockNote AI 的流式/review 只作为远期交互参考。** 具体: - **当前实施**(Phase A/B):local-first 页面 AI 只传当前文件引用、可选 selection 和用户指令;MNote 校验 `AiAccessScope` 后让 agent 在受限目录中使用原生 patch/diff 编辑 `.md`。`mnote.doc.markdown_edit` 作为兼容 / 远端代理 fallback,`mnote.block.*` 作为复杂结构辅助。 - **远期规划**(Phase C):BlockNote AI 的流式增量 apply + suggest/review 层。当前先设计,不实施。 --- ## 2. 参考实现详解 ### 2.1 CLI Main(Lark Doc)— 两层操作模型 CLI Main 的 `skills/lark-doc/SKILL.md` 是飞书文档的 AI skill 定义。它把文档操作分成两层: **文本级操作**(不需要 blockId): ``` docs +update --command str_replace --search "原文片段" --replace "新文本" ``` AI 只需要匹配文本,不需要知道块在哪儿。适用于所有内容修改场景。 **块级操作**(需要 blockId): ``` docs +update --command block_replace --block-id "block_1" --content "..." docs +update --command block_insert_after --block-id "block_1" --content "..." docs +update --command block_delete --block-id "block_1" docs +update --command block_move_after --block-id "block_3" --target-block-id "block_1" ``` 适用于精确块结构调整。AI 需要知道 `blockId`,因此依赖前置的 `docs +fetch` 返回带 id 的内容。 **格式选择规则**(CLI Main 的 skill 中内联): | 场景 | 格式 | 原因 | |------|------|------| | 精确块编辑(替换/插入/删除指定块) | XML | XML 保留块 id、类型、属性 | | 整篇文档导入/导出 | Markdown | 人类可读,和 `.md` 文件互通 | | AI 对话中的内容引用 | Markdown | 模型理解 markdown 远好于 XML | **对 mnote 的启示**: 1. **两层操作模型直接适用**。mnote 的 `markdown_edit` = CLI Main 的 `str_replace`(文本级),`apply_block_ops` = CLI Main 的 `block_*`(块级)。 2. **但 mnote 的主格式是 markdown 而非 XML**。在线文档的持久化格式(Convex `documents.content`)可以投影为 markdown,本地文件本身就是 markdown。所以我们不需要 XML 这一层——markdown 既是 AI 编辑格式,也是人类可读格式。 3. **CLI Main 的 skill 格式**(YAML frontmatter + Markdown body,定义 tool shortcuts + 上下文格式规范)值得学习。mnote 的 Hermes `mnote` plugin 可以用类似方式组织。 #### 2.1.1 Skill YAML 前端元数据 > 参考文件:`skill-template/master-skill-template.md`、`skill-template/skill-template.md`、`scripts/skill-format-check/index.js` 每个 skill 文件以 YAML frontmatter(`---` 包裹)开头: ```yaml --- name: lark-doc description: "飞书云文档 / Docx / 知识库 Wiki 文档(v2):创建、打开、读取..." metadata: requires: bins: ["lark-cli"] cliHelp: "lark-cli docs --api-version v2 --help; ..." --- ``` CI 自动化校验(`skill-format-check.yml` + `scripts/skill-format-check/index.js`)确保 `name` 和 `description` 必填。 **→ mnote 采纳**:Hermes 的 `mnote` plugin 已采用 YAML frontmatter(`/home/lix/.hermes/plugins/mnote/plugin.yaml`)。在此基础上规范化: - `name`:`mnote` - `description`:一段完整的 skill 描述,含覆盖的工具和适用场景 - `metadata.domain`:`mnote-doc`(文档域),后续可扩展 `mnote-tree`、`mnote-file` - CI 校验:确保每个 plugin 的 `plugin.yaml` 含必填字段 #### 2.1.2 Scope / Detail 上下文控制 > 参考文件:`skills/lark-doc/references/lark-doc-fetch.md`、`shortcuts/doc/docs_fetch_v2.go` CLI Main 的 `docs +fetch` 支持五个 `--scope` 级别,精确控制注入 AI 上下文的文档量: | `--scope` | 返回内容 | AI 场景 | |-----------|---------|--------| | `outline` | 标题树(h1-hN)+ blockId | "给我看目录" | | `section` | 指定标题下的完整节 | "精读第三章" | | `range` | blockId 区间 | "给我 100-200 行" | | `keyword` | 关键词周围最小片段 | "找提到 deployment 的地方" | | `full`(默认) | 全文 | 最后手段 | 三个 `--detail` 级别控制元数据量: | `--detail` | blockId | 样式属性 | AI 场景 | |-----------|---------|---------|--------| | `simple` | ❌ | ❌ | 只读、总结 | | `with-ids` | ✅ | ❌ | 需要精确寻址时 | | `full` | ✅ | ✅ | 即将编辑该节 | 片段包装:`` 和 `` 标记告知 AI"这是部分视图,非完整块"。 **→ mnote 采纳**(高优先级): ``` mnote.doc.fetch({ scope: "section" | "outline" | "keyword" | "full", detail: "simple" | "with_ids" | "full", format: "markdown", maxChars: 8000 }) ``` - `scope` 的四级映射:outline(标题树)、section(当前节)、keyword(搜索关键词)、full(全文) - `detail` 的三级映射:simple(纯文本)、with_ids(带块标记)、full(带属性) - 片段包装:markdown 中用 `` 注释标记部分视图边界 - 实现位置:`mnote-web` 的 `mnote.doc.fetch` handler #### 2.1.3 更新工作流:Code-Act Loop > 参考文件:`skills/lark-doc/references/style/lark-doc-update-workflow.md` CLI Main 的文档编辑遵循 **Plan → Execute → Observe → Iterate** 四步循环: ``` 1. Plan(先读后改): docs +fetch --scope section --detail full → 分析当前状态 → 制定操作序列 2. Execute(精准手术,不全量覆盖): 默认用 str_replace / block_insert_after / block_delete block_move_after 用于重排 overwrite 仅在有明确指令时使用 append + block_delete 组合优于 overwrite 3. Observe(每次写后回读): docs +fetch --scope section → 确认修改正确 4. Iterate(修复差异): 如发现偏差 → 回到 Plan ``` 更新命令决策树: ``` 需要修改文本内容(不改块结构)? → str_replace(文本级,不需要 blockId) 需要整段替换? → block_replace --block-id xxx(需要先 fetch --detail with_ids) 需要插入/删除? → block_insert_after / block_delete 需要重排结构? → block_move_after / block_copy_insert_after ``` **→ mnote 采纳**(当前 Hermes 的 `mnote` plugin 应内置此工作流): - Hermes `mnote` skill 的 `SKILL.md` 中内联 Code-Act Loop 指导 - `mnote.doc.markdown_edit`(文本级搜索替换)优先于 `mnote.doc.apply_block_ops`(块级精确操作) - 模型 system prompt 追加:"永远不要在不确定时使用全文覆盖;优先搜索替换;每次写入后回读确认" #### 2.1.4 参考文件分离模式 > 参考文件:`skills/lark-doc/references/`(24 个独立 .md 文件) CLI Main 把详细工具规格从主 `SKILL.md` 中分离到 `references/` 子目录: ``` skills/lark-doc/ SKILL.md ← 技能概述 + 决策表 + 路由规则 references/ lark-doc-fetch.md ← fetch 工具详细规格 lark-doc-update.md ← update 工具详细规格 lark-doc-xml.md ← XML 块语法参考 lark-doc-md.md ← Markdown 格式规则 style/ lark-doc-update-workflow.md ← 编辑工作流 lark-doc-style.md ← 写作风格指南 ``` **→ mnote 采纳**:Hermes `mnote` plugin 应采用相同结构: ``` ~/.hermes/plugins/mnote/ plugin.yaml ← YAML 前端元数据 SKILL.md ← 技能概述 + 工具决策表 references/ mnote-doc-fetch.md ← fetch 详细规格(scope/detail/format) mnote-doc-update.md ← 更新命令规格(search/replace + block ops) mnote-doc-workflow.md ← Code-Act Loop mnote-doc-context.md ← 上下文冻结与格式化规则 ``` #### 2.1.5 跨域资源路由 > 参考文件:`skills/lark-doc/SKILL.md`(嵌入式资源路由表) CLI Main 的 lark-doc skill 定义了嵌入式资源的显式路由表: ``` | 嵌入标签 | 提取字段 | 代理 skill | |-----------------------------|--------------------------|---------------| | | token → spreadsheet_token | lark-sheets | | | token → app_token | lark-base | | | board_token | lark-whiteboard| ``` **→ mnote 采纳**(远期,当文档内嵌入其他资源类型时): - 在线文档中嵌入的思维导图 → 路由到 `mnote-mindmap` skill - 嵌入的附件/文件 → 路由到 `mnote-file` skill - 当前 Phase A 不实施,预留路由表字段 #### 2.1.6 格式策略:XML vs Markdown > 参考文件:`skills/lark-doc/references/lark-doc-xml.md`、`lark-doc-md.md` CLI Main 的格式选择规则: | 场景 | 格式 | 原因 | |------|------|------| | 精确块编辑 | XML(默认) | 保留 blockId、样式属性、结构 | | 整篇导入/导出 | Markdown | 人类可读,和 `.md` 互通 | | AI 对话引用 | Markdown | 模型理解 markdown 远好于 XML | **→ mnote 采纳**:CLI Main 用 XML 做默认格式是因为飞书文档本身是 XML 存储。mnote 的在线文档持久化格式(Convex `documents.content`)和本地文件都是 markdown,因此 markdown 是 mnote 的默认和唯一 AI 格式。这是正确的差异化决策。 #### 2.1.7 Skill CI 校验 > 参考文件:`.github/workflows/skill-format-check.yml`、`scripts/skill-format-check/` CLI Main 的 CI 自动检查每个 `SKILL.md`: 1. 以 `---\n` 开头 2. YAML frontmatter 语法有效 3. `name` 和 `description` 必填 4. `metadata` 缺失仅为警告 **→ mnote 采纳**:对 Hermes `mnote` plugin 和所有 `~/.hermes/skills/note-taking/mnote-*/SKILL.md` 执行同等校验。在 CI 中增加 `skill-format-check` 步骤。 ### 2.2 BlockNote AI — 流式 apply + suggest/review BlockNote AI 的 `StreamTool` 实现了一套完整的 AI 编辑体验闭环: **核心机制**: ``` LLM 流式输出 partial JSON → StreamToolExecutor 逐 chunk 解析 → 匹配 operation type → validate → execute → ProseMirror 事务逐条 apply(带延迟,模拟"AI 正在打字") → suggestChanges 标记 AI 编辑为 suggestion(红绿对比) → 用户逐条 accept / reject → 确认后才写入持久层 ``` **关键组件**: | 组件 | 功能 | mnote 远期对标 | |------|------|---------------| | `StreamTool` | 单操作的定义:name + inputSchema + validate + execute | mnote 已有(`mnote.block.*` / `mnote.doc.*`),不需要重构 | | `StreamToolExecutor` | 流式解析 partial JSON → 逐条 enqueue → 按顺序 execute | Phase C 新增:`StreamApplyController` | | `suggestChanges` | ProseMirror suggestion marks:AI 编辑不直接落盘,先标记为待审阅 | Phase C 新增:在线文档的 `ReviewSession` | | `RebaseTool` | 协作场景:用户同时在编辑 → AI 操作 rebase 到最新文档状态 | Phase C 考虑(协作场景依赖 Convex 的 revision 乐观锁) | | `delayAgentStep` | 逐条 apply 之间加 50-200ms 延迟,给用户"AI 正在操作"的可见性 | Phase C 可选改善 | **BlockNote AI 不适合直接搬的原因**:它的所有操作都通过 `id`(blockId)寻址。`add` 需要 `referenceId`、`update` 需要 `id`、`delete` 需要 `id`。这意味着: - 本地 `.md` 文件无法使用(没有稳定 blockId) - 跨块内容修改("把所有 TODO 改成 DONE")需要模型逐一产出 N 个带 blockId 的操作 - 这和 mnote 的"markdown 优先"方向背道而驰 **但我们可以在 markdown_edit 之上叠加流式/review**:Phase C 时,`mnote.doc.markdown_edit` 的 apply 过程可以流式化——逐条 search/replace 执行后推送 BlockDelta,编辑器逐条渲染,用户可逐条撤回。这和 BlockNote AI 的体验效果一致,但底层是文本级操作而非块级操作。 ### 2.3 Tiptap AI Autocomplete — 内联补全(独立功能) 纯文本补全器:发送光标前文本 → 返回下一句。不是文档编辑方案。mnote 将来可以考虑作为独立的"内联 AI 补全"功能,和本文讨论的文档编辑工具分开设计。 --- ## 3. 当前问题 ### 3.1 块级编辑是 AI 的过度抽象 > 参考对照:CLI Main 证明 `str_replace` 足以覆盖大多数 AI 编辑场景,不需要强迫模型理解 blockId。 当前 AI 写入链路的问题不在于技术实现,而在于**模型必须理解 blockId 这个 UI 层的概念**: ``` 用户: "把第一段改简洁一些" → 当前路径:模型 → { op: "replace", blockId: "block_1", content: "..." } → 期望路径:模型 → { search: "第一段原文", replace: "改写后文本" } ``` 「把文章中所有 TODO 改成 DONE」这类跨块请求,当前需要逐一产出 N 个 `{ op: "replace", blockId: "..." }`。文本级 search/replace 只需一条:`{ search: "TODO", replace: "DONE" }`。 ### 3.2 本地文件没有 AI 写入路径 > 参考对照:CLI Main 的 skill 可以操作任何 `doc-token`(包括本地文件和云端文档),因为它用的是文本级 + XML 级工具,不依赖特定存储。 mnote 当前:Hermes 只知道 workspace/document 模型,不知道本地文件夹/文件模型。本地 `.md` 文件在 AI 视角完全不可见。 ### 3.3 两套体系互不相认 | | 在线 Convex 文档 | 本地 .md 文件 | |---|---|---| | 存储 | Convex `documents.content` | 文件系统 `*.md` | | AI 读取 | `mnote.doc.fetch`(block projection) | 无 | | AI 写入 | `mnote.block.*` / `mnote.doc.apply_block_ops` | 无 | | 写入粒度 | 块级(需要 blockId) | —(无 AI 路径) | | 设计覆盖 | 07-ai 全部文档 | 仅在 03-rust-web 讨论解析 | 两份体系在设计中互不相认。3-13 写明"不影响 Convex workspace 链路"——这是主动划清界限。**收敛是必须的,markdown 是共同分母。** --- ## 4. 设计原则 ### 4.1 Markdown 是 AI 编辑的第一公民 > 参考对照:CLI Main 用 Markdown 做整篇导入/导出和对话引用,用 XML 做精确块编辑。mnote 直接用 Markdown 做所有 AI 操作,因为 mnote 没有 XML 存储层。 AI 最自然的编辑方式是对文本进行操作。块是 UI 概念,不是 AI 概念。在线文档的持久化格式和本地文件的持久化格式都可以投影为 markdown。 ### 4.2 两层操作模型(兼容层) | 层 | 工具 | 寻址方式 | 适用场景 | 占比 | |----|------|---------|---------|------| | **文件级(主)** | agent 原生 `diff/apply_patch/文件编辑` | 授权文件引用 / selection | local-first 普通 Markdown 改写 | 80%+ | | **文本级(兼容)** | `mnote.doc.markdown_edit` | search/replace 文本对 | cloud / remote agent / 兼容旧页面 AI | 次要 | | **块级(辅助)** | `mnote.doc.apply_block_ops` | blockId / matchText | "把第三块拖到第一块后面"、"精确删除引用块" | <20% | ### 4.3 在线和本地的主写入路径 ``` 页面 AI / ACP → resolve_source(documentId) → Convex | LocalFS → local-first: 传授权文件引用给 agent runtime → agent 原生 patch/diff 写入文件 → MNote 做权限 / 审计 / refresh → cloud / remote fallback: mnote.doc.markdown_edit ``` 差异主要在执行层:local-first 直接让 agent 修改授权文件;cloud 或受限 remote runtime 无法直接访问本地文件时,再走 `mnote.doc.markdown_edit` 代理。这和 CLI Main 的 `docs +update` 可以操作任何 `doc-token` 的设计一致,只是 mnote 进一步把“编辑算法”让渡给 agent runtime。 ### 4.4 Diff 是内部实现细节 对兼容 `mnote.doc.markdown_edit` 来说,AI **不**产出 unified diff(行号/上下文极易出错),而是产出两种形式之一: | 形式 | 适用场景 | AI 负担 | |------|---------|---------| | `operations: [{ search, replace }]` | 局部修改 | 低:只需找原文片段 | | `full_content: "..."` | 小文档全文改写 | 低:直接写完整 markdown | 而在 local-first 主路径中,agent runtime 自己可以安全使用成熟的 diff / apply_patch / 直接文件编辑能力;MNote 只要求这些写入被限制在授权路径内,并把 changed files / diff 摘要收回审计。 --- ## 5. 工具合同 ### 5.1 `mnote.doc.fetch`(已有,增强) ```json { "toolName": "mnote.doc.fetch", "args": { "documentId": "tree_xxx 或 /path/to/file.md", "format": "markdown", "scope": "full", "query": "TODO", "maxChars": 8000 } } ``` 增强点: - `format: "markdown"` — 新增值。在线文档由 Page Aggregate 产出 PageMarkdown;本地文件直接返回 `.md` 原文。 - `documentId` — 自动检测 source:Convex workspace 文档 vs 本地文件系统路径。 返回: ```json { "ok": true, "source": "convex", "documentId": "tree_xxx", "revision": 3, "format": "markdown", "content": "# 标题\n\n段落内容...\n\n## 子标题\n\n...", "truncated": false, "charCount": 1234 } ``` ### 5.2 `mnote.doc.markdown_edit`(新增,兼容 / fallback) ```json { "toolName": "mnote.doc.markdown_edit", "args": { "documentId": "tree_xxx 或 /path/to/file.md", "operations": [ { "search": "原文片段", "replace": "新文本" }, { "search": "另一段", "replace": "改写后的内容" } ] } } ``` 服务端处理流程: ``` 1. resolve_source(documentId) → ConvexAdapter | LocalFSAdapter 2. 读取当前 markdown 全文 3. 逐条 search_replace(精确匹配 → fuzzy fallback) 4. 计算内部 diff(用于 BlockDelta 推送) 5. 写入目标 6. 返回结果 ``` 返回: ```json { "ok": true, "source": "convex", "documentId": "tree_xxx", "revision": { "before": 3, "after": 4 }, "operationsApplied": 2, "operationsFailed": 0, "failedOperations": [], "changedText": "已将「原文片段」替换为「新文本」\n已将「另一段」替换为「改写后的内容」", "blockDelta": { "documentId": "tree_xxx", "revision": 4, "operations": [...] } } ``` ### 5.3 `mnote.doc.apply_block_ops`(已有,辅助路径) 保留不变。用于精确块结构调整(拖拽排序、指定 blockId 的精确删除)。在 Hermes tool manifest 中 `markdown_edit` 排在 `apply_block_ops` 前面。 --- ## 6. 搜索替换语义 > 参考对照:CLI Main 的 `str_replace` 使用精确子串匹配。mnote 增加 fuzzy fallback 以提高模型输出容错率。 | 优先级 | 策略 | 说明 | |--------|------|------| | 1 | 精确匹配 | 原文字串精确匹配,区分大小写 | | 2 | 宽松匹配 | 忽略首尾空白、全角/半角差异后匹配 | | 3 | 段落 fuzzy | 按换行分段,每段独立 fuzzy match(允许 30% 字符差异) | | 4 | 失败 | 返回 `operationsFailed`,列出无法匹配的 search 和原因 | 多条 operation 按数组顺序串行执行。前一条的替换结果对后一条可见(和 sed 语义一致)。 **乐观锁**:执行前用 `revision` 检测。如果写入时 revision 已过期,返回冲突错误,让 AI 重新 fetch + edit。 --- ## 7. 远期规划:流式 apply + suggest/review(Phase C,当前不实施) > 参考对照:BlockNote AI 的 `StreamToolExecutor` + `suggestChanges` + `delayAgentStep`。本节仅做设计规划,不列入当前实施阶段。 ### 7.1 目标 当用户通过页面 AI 面板发起编辑后,不是等所有操作完成后一次性刷新,而是: 1. AI 逐条产出 search/replace 对(流式) 2. 服务端逐条 apply 并推送 BlockDelta 3. 编辑器逐条渲染修改(带延迟,模拟"AI 正在编辑") 4. 用户可逐条 accept / reject(suggestion 模式) 5. 确认后才最终写入 Convex(review 模式) ### 7.2 与 BlockNote AI 的差异 | | BlockNote AI | mnote Phase C | |---|---|---| | 操作粒度 | 块级(add/update/delete block) | 文本级(search/replace 文本对) | | blockId 依赖 | 必须 | 不需要(文本匹配) | | 本地文件支持 | 不支持(无稳定 blockId) | 支持(文本匹配不依赖 blockId) | | Suggestion 层 | ProseMirror `suggestChanges` marks | Tiptap 的 suggestion 扩展或自建 | | 写入时机 | 用户 accept 后统一写入 | 同 | ### 7.3 组件设计(草图) ``` StreamApplyController(新增) 输入:LLM 流式输出的 partial operations JSON 处理: 1. 解析 partial JSON → 提取已完成的 operation 2. 执行 search_replace → 产生 BlockDelta 3. 通过 SSE 推送 delta 到编辑器 4. 可选:注入 delay(50-200ms)模拟人类编辑节奏 输出:逐条 BlockDelta ReviewSession(新增,在线文档) 状态:pending → accepted | rejected 存储:Convex review_session 表或 documents 的 review 字段 生命周期: - AI 编辑 → 创建 ReviewSession → 所有修改标记为 pending - 用户逐条操作 → accept/reject → 更新 session 状态 - 全部处理或用户确认 → 最终写入 → 关闭 session GhostTextOverlay(新增,编辑器) 参考:Tiptap AI Autocomplete 的 ghost text 覆盖层 用于:展示 AI 修改前后的 diff(红删绿增),用户 hover 查看详情 ``` ### 7.4 分阶段实施顺序 | 阶段 | 内容 | 依赖 | |------|------|------| | C-1 | `StreamApplyController`:流式 apply + SSE 逐条推送 delta | 7-13 EditorRuntimeActor delta 通道 | | C-2 | `GhostTextOverlay`:编辑器内 diff 展示(红删绿增) | leptos-tiptap 的 decoration 能力 | | C-3 | `ReviewSession`:在线文档的 accept/reject session | Convex review 表设计 | | C-4 | `delayAgentStep`:可选的编辑节奏模拟 | C-1 完成 | **当前不做实施决策**。Phase C 的启动时机以 Phase A/B 完成后,编辑器能力和 Convex review 表设计就绪为前置条件。 --- ## 8. 实施阶段(当前) ### Phase A:文件引用主路径 + `mnote.doc.*` 兼容层 - [x] `mnote.doc.fetch` 增加 `format: "markdown"`(在线文档 Page Aggregate → PageMarkdown) - [x] `mnote.doc.fetch` 增加本地文件 source 路由(自动检测 Convex vs 文件系统路径) - [x] 实现 `resolve_source(documentId)` — 本地文件路径 `local_fs` vs 其余走 Convex - [ ] 页面 AI / ACP 普通正文编辑默认只传当前文件引用、可选 selection 和用户指令,不再默认构造完整 page context - [ ] 本地 agent runtime 在 `allowed_roots / allowed_file_paths` 内执行 patch/diff,并把 changed files / diff 摘要回传 MNote - [x] 实现 `search_replace(text, operations)` — 四级匹配策略(精确→宽松→段落 fuzzy→失败) - [x] 实现 Convex 写入 adapter(复用 `doc_apply_block_ops` 链路) - [x] 实现本地文件写入 adapter(`mnote.doc.markdown_edit` 检测到本地文件路径时直接 `fs::write` 写回,不经过 Convex) - [ ] 内部 diff 生成 + BlockDelta 推送(复用 7-13 delta 基础设施,待 Phase C 实现) - [x] Hermes tool manifest 注册 `mnote.doc.markdown_edit` - [x] Hermes `mnote` plugin 更新:`mnote_doc_fetch` schema + `mnote_doc_markdown_edit` 新增 - [x] 乐观锁:`revision` 冲突检测(`doc_apply_block_ops` 已有) - [x] 浏览器 smoke(在线文档 `format: "markdown"` + `markdown_edit` 搜索替换) - [x] 单元测试(`search_replace` 精确/失败/全文、`blocks_to_markdown` with_ids/heading 共 5 测试通过) - [x] 浏览器 smoke(本地 `.md` 文件读取 `mnote_doc_fetch` + 写入 `mnote_doc_markdown_edit`,`full_content` 创建 + `operations` 搜索替换 + 回读验证全部通过) ### Phase B:`page_ai_workflow.rs` 收口 - [x] 退役 `direct_block_edit_operations`(正则抠「」的快路径,代码保留但路由跳过) - [x] `/api/page-ai/block-edit-workflow` 不再被当作 local-first 主路径 - [x] 模型 system prompt 重构:从产块操作 JSON 改为产 search/replace 文本对 - [x] 补全 operation schema:`extract_markdown_operations_from_model_text` 处理新旧格式 - [x] 浏览器 smoke:`markdown_edit` 搜索替换通过,自然语言编辑路径可用 ### Phase C:流式/review(规划中,不实施) - [ ] 流式 apply(`StreamApplyController`) - [ ] suggest/review(`ReviewSession` + `GhostTextOverlay`) - [ ] `delayAgentStep` 编辑节奏模拟 > 见 §7。当前仅做设计规划,不实施。 --- ## 9. 与参考实现和其他设计稿的关系 ### 9.1 与 CLI Main 的关系 | CLI Main 概念 | mnote 对应 | |--------------|-----------| | `str_replace`(文本级) | `mnote.doc.markdown_edit`(兼容 / fallback) | | `block_replace/insert_after/delete/move_after`(块级) | `mnote.doc.apply_block_ops`(辅助路径) | | XML 用于精确编辑 | mnote 不用 XML(没有 XML 存储层),direct path 退役后全部走 markdown | | Markdown 用于导入/导出/对话引用 | mnote 全部 AI 交互走 markdown | | Skill 格式(YAML + Markdown + tool shortcuts) | Hermes `mnote` plugin(`plugin.yaml` + `SKILL.md` + tool manifest) | ### 9.2 与 BlockNote AI 的关系 | BlockNote AI 概念 | mnote 远期对应 | 状态 | |-------------------|---------------|------| | `StreamTool` + `StreamToolExecutor` | `StreamApplyController` | Phase C 规划 | | `suggestChanges` + `AIExtension` state machine | `ReviewSession` + `GhostTextOverlay` | Phase C 规划 | | `delayAgentStep` | 可选改善 | Phase C 规划 | | `RebaseTool` | revision 乐观锁(已有) | 当前已覆盖 | | 纯 blockId 寻址 | ❌ 不采用。mnote 用文本匹配 | — | ### 9.3 与其他设计稿的关系 | 设计稿 | 关系 | 修正状态 | |--------|------|---------| | 7-9 路线图 | 块级编辑降级为辅助,local-first 普通编辑改为授权文件 + agent patch/diff,markdown_edit 退到兼容层 | ✅ 已修正 | | 7-10 checklist | 新增 Phase 9 markdown_edit | ✅ 已修正 | | 7-12 工具路由 | PageAICommandRouter 主输出改为 markdown_edit | ✅ 已修正 | | 7-13 EditorRuntimeActor | 补充 markdown_edit 的 delta 适配 | ✅ 已修正 | | 3-13 本地 markdown | 新增 AI 工具接入章节 | ✅ 已修正 | --- ## 10. 禁止项 - 不删除 `mnote.block.*` 工具(保留为辅助路径)。 - 不强迫 local-first agent 产出 MNote 自定义 diff;Hermes / Reasonix 可使用自身成熟 patch / diff / apply_patch 能力,MNote 负责白名单权限、文件版本冲突和审计。 - 不要求本地文件有稳定的 `blockId`(本地文件没有 block identity)。 - 不在 markdown_edit 内部引入新的 AI 模型调用(diff 是确定性算法)。 - 不把 Convex `documents:updateContent` 重新提升为 local-first 正文主存储;`markdown_edit` 在 cloud / compat 场景可复用受控保存路径。 - 不把 `mnote.page.save` 重新描述为精确编辑主入口(它仍是兜底工具)。 - **不照搬 BlockNote AI 的纯 blockId 寻址模式**(与 mnote 的 markdown 优先策略冲突)。 --- ## 11. 成功标准 - [x] `mnote.doc.fetch(documentId, format: "markdown")` 对在线文档返回正确 markdown - [ ] `mnote.doc.fetch(documentId, format: "markdown")` 对本地 `.md` 文件返回正确 markdown(兼容路径) - [x] `mnote.doc.markdown_edit` 的简单搜索替换(1 条 operation)浏览器 smoke 通过 - [ ] `mnote.doc.markdown_edit` 的复杂改写(3+ 条 operations)成功率 > 80% - [ ] 本地 `.md` 文件通过页面 AI 面板可被读取和写入,且默认通过 agent 原生 patch/diff 完成 - [x] 在线文档的 markdown_edit 不增加 Convex RTT(和当前块操作持平) - [x] `direct_block_edit_operations` 已退役(路由跳过,代码保留) - [x] `page_ai_workflow.rs` 的 system prompt 已补全 search/replace schema - [x] Phase C(流式/review)的设计已冻结,不阻塞 A/B 实施 --- ## 12. CLI Main 成熟度采纳清单 以下清单按优先级排列,标注当前状态和对应 CLI Main 参考位置。 ### 12.1 当前 Phase A/B 必须完成的 | # | CLI Main 模式 | mnote 实施项 | 状态 | 参考文件 | |---|-------------|-------------|------|---------| | 1 | 两层操作模型 | local-first 默认 agent 原生 patch/diff;`markdown_edit`(compat fallback)+ `apply_block_ops`(结构辅助)已在 7-10 Phase 9 登记 | 📋 设计完成,已按 2-2 改口径 | `lark-doc-update.md` | | 2 | Scope 四级控制 | `mnote.doc.fetch` 增加 `scope: full/section/outline/keyword` | 🔜 Phase A | `lark-doc-fetch.md` | | 3 | Detail 三级控制 | `mnote.doc.fetch` 增加 `detail: simple/with_ids/full` | 🔜 Phase A | `lark-doc-fetch.md` | | 4 | 片段包装 | fetch 返回中标记 `` / `` 告知 AI 部分视图 | 🔜 Phase A | `lark-doc-fetch.md`(fragment/excerpt 模式) | | 5 | Code-Act Loop | Hermes `mnote` plugin SKILL.md 中内联 Plan→Execute→Observe→Iterate 指导 | ✅ 已写入 `~/.hermes/skills/note-taking/mnote-block-ai/SKILL.md` | `lark-doc-update-workflow.md` | | 6 | 更新命令决策树 | 模型 system prompt 追加"优先搜索替换,不全文覆盖"规则 | 🔜 Phase B | `lark-doc-update.md`(str_replace vs block_* 决策) | | 7 | 写后回读 | Hermes 每次写操作结束后自动 `mnote.doc.fetch` 回读 | 🔜 Phase B | `lark-doc-update-workflow.md` | | 8 | Markdown 优先格式策略 | mnote 默认和唯一 AI 格式为 markdown(不需要 XML) | ✅ 已确认 | `lark-doc-md.md`(对比 XML 路线) | ### 12.2 Phase A/B 完成后应补充的 | # | CLI Main 模式 | mnote 实施项 | 状态 | 参考文件 | |---|-------------|-------------|------|---------| | 9 | Skill YAML 规范化 | Hermes `mnote` plugin 的 `plugin.yaml` 增加 `description`、`metadata.domain` | ✅ 已更新 v0.2.0 | `master-skill-template.md` | | 10 | 参考文件分离 | `~/.hermes/skills/note-taking/mnote-block-ai/references/` 目录已创建,`mnote-doc-fetch.md` 已写入 | ✅ 已创建(fetch),其余按需补充 | `skills/lark-doc/references/` | | 11 | Skill CI 校验 | CI 步骤:检查所有 plugin.yaml 和 SKILL.md 的 YAML frontmatter 有效性 | 📋 待实施 | `skill-format-check.yml` | | 12 | 格式规则文档 | `references/mnote-doc-md.md`:定义 markdown 中 AI 应理解的特殊语法(任务列表标记 `- [ ]`、附件链接 `[file:...]` 等) | 📋 待实施 | `lark-doc-md.md` | | 13 | 上下文冻结规范 | `references/mnote-doc-context.md`:冻结时保留哪些字段、截断规则、revision 注入方式 | 📋 待实施 | `lark-doc-fetch.md`(fetch body 构建) | ### 12.3 远期规划(Phase C 及以后) | # | CLI Main 模式 | mnote 实施项 | 状态 | 参考文件 | |---|-------------|-------------|------|---------| | 14 | 跨域资源路由 | 文档内嵌思维导图/附件时,路由到对应 skill(见 §2.1.5) | ⏳ Phase C 后 | `lark-doc/SKILL.md`(路由表) | | 15 | 流式增量 apply | BlockNote AI 的 `StreamToolExecutor` 模式,见 §7 | ⏳ Phase C 规划 | `blocknote-ai/packages/xl-ai/` | | 16 | suggest/review | BlockNote AI 的 `suggestChanges` 模式,见 §7 | ⏳ Phase C 规划 | `blocknote-ai/packages/xl-ai/` | ### 12.4 与 BlockNote AI / Tiptap AI Autocomplete 的采纳清单 | # | 参考模式 | mnote 采纳 | 时机 | 参考文件 | |---|---------|-----------|------|---------| | B1 | `StreamTool` 流式 apply | `StreamApplyController`(§7.3) | Phase C | `blocknote-ai/packages/xl-ai/src/streamTool/` | | B2 | `suggestChanges` review 层 | `ReviewSession` + `GhostTextOverlay`(§7.3) | Phase C | `blocknote-ai/packages/xl-ai/src/AIExtension.ts` | | B3 | `delayAgentStep` 编辑节奏 | 可选改善 | Phase C | `blocknote-ai/packages/xl-ai/src/streamTool/` | | T1 | ghost text 内联补全 | 独立功能(非本文档范围) | 独立立项 | `tiptap-ai-autocomplete/src/` | ### 12.5 实施优先级总结 ``` Phase A(当前立即) ├── #2 Scope 四级控制 ← mnote.doc.fetch 增强 ├── #3 Detail 三级控制 ← mnote.doc.fetch 增强 ├── #4 片段包装 ← fetch 返回值增强 ├── #8 Markdown 优先 ← 已确认 └── 授权文件引用 + agent 原生 patch/diff 主路径;mnote.doc.markdown_edit 作为 compat fallback Phase B(Phase A 完成后) ├── #5 Code-Act Loop ← Hermes plugin SKILL.md ├── #6 更新决策树 ← 模型 system prompt └── #7 写后回读 ← Hermes tool loop 逻辑 Phase A/B 完成后补充 ├── #9-#13 Skill 规范化、参考文件分离、CI 校验、格式文档、上下文规范 Phase C(远期) ├── #14 跨域路由(Phase C 后) ├── #15 流式 apply(Phase C) └── #16 suggest/review(Phase C) ```