Document the VSCode-like local-first product shape, demote Convex to a control-plane role, and retire stale architecture drafts. Add local workspace migration/export references plus smoke coverage for no-Convex managed workspace startup, local markdown title/body/options persistence, asset upload behavior, and Convex fixture export. Verification: git diff --cached --check; node scripts/check-local-first-convex-guard.js --staged; node scripts/task444-convex-workspace-export-local-fixture-smoke.js; node scripts/task166-local-first-managed-workspace-no-convex-smoke.js; node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js
38 KiB
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本稿目的:
- 纠正 7-9 / 7-10 / 7-12 / 7-13 中隐含的「块级编辑是 AI 唯一写入路径」假设
- 基于 CLI Main 参考实现,确立 mnote 的「agent 原生文件 patch/diff 为 local-first 默认路径,MNote 文本级兼容工具 + 块级结构性操作为 fallback / 辅助」两层模型
- 规划 BlockNote AI 流式/review 能力的远期方向(当前不实施)
- 统一 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 的块级操作上,导致三个连锁问题:
-
AI 被迫在块级操作:对「改写这段话」「补充一段总结」「调整语气」「把所有 TODO 改成 DONE」等自然请求,AI 必须产出
{ op: "replace", blockId: "block_1", ... }格式的块操作,而不能直接产出修改后的文本或搜索替换对。这增加了 AI 的认知负担和出错概率。 -
本地
.md文件没有 AI 能力:本地文件没有稳定的blockId(每次解析重新分配),因此块级工具无法应用于本地文件。当前 Hermes 的 13 个工具没有一个是面向本地文件的。 -
两套口径维护:在线文档(块操作)和本地文档(无 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 "<xml>...</xml>"
docs +update --command block_insert_after --block-id "block_1" --content "<xml>...</xml>"
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 的启示:
- 两层操作模型直接适用。mnote 的
markdown_edit= CLI Main 的str_replace(文本级),apply_block_ops= CLI Main 的block_*(块级)。 - 但 mnote 的主格式是 markdown 而非 XML。在线文档的持久化格式(Convex
documents.content)可以投影为 markdown,本地文件本身就是 markdown。所以我们不需要 XML 这一层——markdown 既是 AI 编辑格式,也是人类可读格式。 - CLI Main 的 skill 格式(YAML frontmatter + Markdown body,定义 tool shortcuts + 上下文格式规范)值得学习。mnote 的 Hermes
mnoteplugin 可以用类似方式组织。
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(--- 包裹)开头:
---
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:mnotedescription:一段完整的 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 |
✅ | ✅ | 即将编辑该节 |
片段包装:<fragment requested-start="..." requested-end="..."> 和 <excerpt top-block-id="..." parent-block-path="..."> 标记告知 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 中用
<!-- fragment: section "标题" -->注释标记部分视图边界 - 实现位置:
mnote-web的mnote.doc.fetchhandler
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
mnoteskill 的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 |
|-----------------------------|--------------------------|---------------|
| <sheet token="..." ...> | token → spreadsheet_token | lark-sheets |
| <bitable token="..." ...> | token → app_token | lark-base |
| <whiteboard token="..."> | board_token | lark-whiteboard|
→ mnote 采纳(远期,当文档内嵌入其他资源类型时):
- 在线文档中嵌入的思维导图 → 路由到
mnote-mindmapskill - 嵌入的附件/文件 → 路由到
mnote-fileskill - 当前 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:
- 以
---\n开头 - YAML frontmatter 语法有效
name和description必填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(已有,增强)
{
"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 本地文件系统路径。
返回:
{
"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)
{
"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. 返回结果
返回:
{
"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 面板发起编辑后,不是等所有操作完成后一次性刷新,而是:
- AI 逐条产出 search/replace 对(流式)
- 服务端逐条 apply 并推送 BlockDelta
- 编辑器逐条渲染修改(带延迟,模拟"AI 正在编辑")
- 用户可逐条 accept / reject(suggestion 模式)
- 确认后才最终写入 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.* 兼容层
mnote.doc.fetch增加format: "markdown"(在线文档 Page Aggregate → PageMarkdown)mnote.doc.fetch增加本地文件 source 路由(自动检测 Convex vs 文件系统路径)- 实现
resolve_source(documentId)— 本地文件路径local_fsvs 其余走 Convex - 页面 AI / ACP 普通正文编辑默认只传当前文件引用、可选 selection 和用户指令,不再默认构造完整 page context
- 本地 agent runtime 在
allowed_roots / allowed_file_paths内执行 patch/diff,并把 changed files / diff 摘要回传 MNote - 实现
search_replace(text, operations)— 四级匹配策略(精确→宽松→段落 fuzzy→失败) - 实现 Convex 写入 adapter(复用
doc_apply_block_ops链路) - 实现本地文件写入 adapter(
mnote.doc.markdown_edit检测到本地文件路径时直接fs::write写回,不经过 Convex) - 内部 diff 生成 + BlockDelta 推送(复用 7-13 delta 基础设施,待 Phase C 实现)
- Hermes tool manifest 注册
mnote.doc.markdown_edit - Hermes
mnoteplugin 更新:mnote_doc_fetchschema +mnote_doc_markdown_edit新增 - 乐观锁:
revision冲突检测(doc_apply_block_ops已有) - 浏览器 smoke(在线文档
format: "markdown"+markdown_edit搜索替换) - 单元测试(
search_replace精确/失败/全文、blocks_to_markdownwith_ids/heading 共 5 测试通过) - 浏览器 smoke(本地
.md文件读取mnote_doc_fetch+ 写入mnote_doc_markdown_edit,full_content创建 +operations搜索替换 + 回读验证全部通过)
Phase B:page_ai_workflow.rs 收口
- 退役
direct_block_edit_operations(正则抠「」的快路径,代码保留但路由跳过) /api/page-ai/block-edit-workflow不再被当作 local-first 主路径- 模型 system prompt 重构:从产块操作 JSON 改为产 search/replace 文本对
- 补全 operation schema:
extract_markdown_operations_from_model_text处理新旧格式 - 浏览器 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. 成功标准
mnote.doc.fetch(documentId, format: "markdown")对在线文档返回正确 markdownmnote.doc.fetch(documentId, format: "markdown")对本地.md文件返回正确 markdown(兼容路径)mnote.doc.markdown_edit的简单搜索替换(1 条 operation)浏览器 smoke 通过mnote.doc.markdown_edit的复杂改写(3+ 条 operations)成功率 > 80%- 本地
.md文件通过页面 AI 面板可被读取和写入,且默认通过 agent 原生 patch/diff 完成 - 在线文档的 markdown_edit 不增加 Convex RTT(和当前块操作持平)
direct_block_edit_operations已退役(路由跳过,代码保留)page_ai_workflow.rs的 system prompt 已补全 search/replace schema- 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 返回中标记 <!-- fragment --> / <!-- excerpt --> 告知 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)