# 7-9 [done] 页面/块 AI 工具体系规划 v1

> 更新时间：2026-05-16
>
> 当前状态：`DONE`。
>
> 本稿承接 `7-6` 的 mnote Hermes plugin tool 合同、`7-8` 的 Hermes Runtime BFF 方向，以及近期页面 AI 工具实测中暴露的问题：当前 `mnote.page.get/save/update_title/update_options` 已能完成页面级读写，但工具粒度仍偏粗，不能长期代表“AI 能精确编辑页面/块”。
>
> 核心参考：
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/hermes-web-ui-0.5.18`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/hermes-vscode-main`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/ai-toolkit-demos`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-main`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-apcore`
> - `/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`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-13-page-block-identity-and-command-contract-v1.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-1-page-block-storage-projection-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-6-mnote-hermes-plugin-tool-contract-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-8-page-ai-hermes-runtime-bff-next-v1.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`

---

## 1. 结论

不要把问题理解成二选一：

- 不是“等 Rust kernel / Page Aggregate / editor module 完全稳定后，才开始设计工具”。
- 也不是“现在立刻扩一批块级写工具，让 Hermes 直接调用当前 Convex / 前端私有 shape”。

正确路径是：

> **现在继续写工具体系设计，冻结 AI 面向的稳定工具合同；实现上分阶段推进，先做只读、定位、dry-run、plan 和最小单块写入。复杂块移动、块嵌入、多块批量改写、媒体块编排，要等 Rust block model、Page Aggregate command、Convex 持久化和 editor session 刷新链路达到硬门槛后再开放。**

当前 `mnote.page.save` 可以继续作为页面级兜底工具，但不能继续被描述为长期块编辑方案。长期工具体系必须基于稳定的 `Page Aggregate / EditorBlockDocument / Rust runtime command` 中间合同，而不是让 AI 直接操作前端 Tiptap JSON、Convex `documents.content` 私有结构或历史 `blocks` 表。

---

## 2. 为什么参考飞书 CLI

`cli-main` 的价值不在于“飞书有很多命令，所以 mnote 也应该堆很多工具”，而在于它把文档工具做成了三层：

1. **Shortcut 层**：面向人和 Agent 的高层命令，例如 `docs +fetch`、`docs +update`、`docs +media-insert`。
2. **稳定文档操作协议层**：用 `doc-format`、`scope`、`detail`、`command`、`block_id`、`revision_id`、`dry-run` 表达文档读写。
3. **底层 API adapter 层**：把稳定协议翻译成真实平台 API，例如 `docs_ai/v1/documents` 或 MCP tool call。

这正好对应 mnote 当前问题：

- Rust kernel、Page Aggregate、Convex、leptos-tiptap 还在收口。
- 如果现在让 Hermes 直接调用底层 shape，后续底层一变，skill/tool 就会失效。
- 如果先冻结一个 AI 面向的稳定文档操作协议，底层变动可以收口在 adapter。

因此 mnote 应参考的是飞书的“稳定投影 DSL + 少量高层命令 + dry-run 诊断层”，而不是照搬 Go CLI 或飞书 API。

---

## 3. `cli-main` 具体参考位置

参考根目录：

```text
/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main
```

优先参考下表，不需要整仓搬运：

| mnote 主题 | 参考文件 | 搜索点 | 可吸收内容 | 不采用内容 |
| --- | --- | --- | --- | --- |
| 文档工具注册 | `shortcuts/doc/shortcuts.go` | `Shortcuts()`、`docs +fetch`、`docs +update` | 工具分组、命令命名、文档域工具入口 | 不照搬 CLI 交互层 |
| 文档读取 v2 | `shortcuts/doc/docs_fetch_v2.go` | `executeFetchV2`、`buildReadOption`、`--detail`、`--scope` | `simple/with-ids/full`、`outline/range/keyword/section`、局部读取 | 不使用飞书 token/API |
| 文档更新 v2 | `shortcuts/doc/docs_update_v2.go` | `validCommandsV2`、`buildUpdateBody`、`revision-id`、`dry-run` | `str_replace/block_insert_after/block_replace/block_delete/block_move_after/append/overwrite` | 不直接把飞书 command 当 mnote 内部命令名 |
| v1 兼容更新 | `shortcuts/doc/docs_update.go` | `CallMCPTool`、`update-doc` | 兼容层与主线层并存时的隔离方式 | 不保留多套长期真相 |
| 更新前诊断 | `shortcuts/doc/docs_update_check.go` | `CheckDocsUpdateArgs`、`warning` | 工具调用前给 Agent 的静态语义警告 | 不只靠 LLM 自觉避免危险编辑 |
| 媒体插入编排 | `shortcuts/doc/doc_media_insert.go` | `dry-run`、`steps`、`batch_update` | 多步工具先 dry-run 展示计划，再执行 | 第一阶段不做完整媒体工具 |
| 文档 XML DSL | `skills/lark-doc/references/lark-doc-xml.md` | `<title>`、`<callout>`、`<grid>`、`<img>`、`<cite>` | 用 PageXML/PageMarkdown 屏蔽底层块结构 | 不采用飞书专有块类型作为 mnote 类型 |
| 文档更新说明 | `skills/lark-doc/references/lark-doc-update.md` | `str_replace`、`block_insert_after`、`revision`、`warnings` | 面向 Agent 的工具使用说明、返回结构 | 不把说明当实现 |
| 文档读取说明 | `skills/lark-doc/references/lark-doc-fetch.md` | `detail`、`scope`、`with-ids` | 读取前先定位、再编辑的 workflow | 不让 AI 默认整页读取超大正文 |
| 工具抽象 | `shortcuts/common/types.go` | `Shortcut`、`Validate`、`Execute`、`Risk` | 元信息、权限、风险、dry-run、validate 一体化 | 不复制 Go 框架 |
| dry-run 通用能力 | `internal/cmdutil/dryrun.go` | `DryRun` | 所有写工具都能返回计划和风险 | 不做纯 CLI 文本输出 |
| 风险提示 | `internal/cmdutil/risk.go` | `Risk` | 工具风险等级进入确认和审计 | 不用 CLI prompt 作为 Web 确认机制 |

快速定位命令：

```bash
cd /mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main
rg -n "validCommandsV2|buildReadOption|buildUpdateBody|revision|dry-run|CheckDocsUpdateArgs|Shortcut|Risk|CallMCPTool|scope|detail" shortcuts internal skills/lark-doc
```

---

## 4. 官方 Tiptap AI Toolkit 与补充参考的吸收边界

Tiptap AI Toolkit 的核心价值是把编辑器能力拆成 AI 可调用工具，而不是让外部系统直接暴露编辑器内部实现。Context7 与本地 `tiptap-docs` 公开文档能确认的主线能力包括：

- `toolDefinitions()` 向 AI SDK 暴露工具定义。
- `tiptapRead` 用高效格式读取文档，支持 range/chunk 类读取模型。
- `tiptapEdit` 用 operations 列表编辑文档。
- `tiptapReadSelection` 读取当前选区。
- `executeTool` / `streamTool` 把 AI 生成的 tool call 应用到 editor，并返回 `docChanged`、错误和工具结果。
- review options 支持 `disabled/review/preview/trackedChanges`，但 preview/suggestions 与持久 tracked changes 是两类语义。

需要修正之前口径：

- 不能再说 “Tiptap 没有官方 AI 文档工具”。它有官方 AI Toolkit 工具层。
- 也不能说 “Tiptap AI Toolkit 可以直接解决 mnote 块工具”。公开文档没有给出完整 `tiptapEdit.operations` schema，且它面向 Tiptap/ProseMirror 文档层，不覆盖 mnote 的 Rust kernel、Page Aggregate、Convex revision/conflict key、Hermes audit。
- 当前 npm registry 无法直接获取 `@tiptap-pro/ai-toolkit*` 完整实现源码；本仓可参考的是公开 docs 与 `tiptap-main/packages/server-ai-toolkit`、`extension-unique-id`、`extension-drag-handle`、`extension-node-range` 等开源代码。

这些能力应映射为 mnote 自己的工具层：

| Tiptap AI Toolkit | mnote 对应 | 吸收内容 | 不吸收内容 |
| --- | --- | --- | --- |
| `tiptapRead` | `mnote.doc.fetch` / `mnote.block.fetch` | 先读、带范围、返回适合 AI 的文档表示 | 不把 Tiptap JSON 作为长期工具格式 |
| `tiptapEdit` | `mnote.doc.plan_update` + `mnote.block.*` | 操作列表、reviewable edit、meta justification | 不让浏览器 editor command 成为事实源 |
| `tiptapReadSelection` | `mnote.doc.fetch scope=selection` | selection-aware workflow | 不直接持久化浏览器 selection range |
| `toolDefinitions()` | Rust Hermes manifest | schema、description、capability、annotation | 不依赖私有 npm 包作为运行时硬依赖 |

`tiptap-apcore` 的补充价值更偏工具基础设施。它已经把 Tiptap command 分成 query、format、content、destructive、selection、history，并为工具提供：

- `inputSchema` / `outputSchema`
- `readonly` / `destructive` / `idempotent` / `requiresApproval`
- ACL role：`readonly`、`editor`、`admin`
- `selectionEffect`
- executor 前置检查、ACL、query/command 分发

mnote 应吸收这些元数据，但命名和执行面必须改成 Rust-owned：

```json
{
  "name": "mnote.block.replace",
  "capabilityScope": ["page.write", "block.write"],
  "annotations": {
    "readonly": false,
    "destructive": false,
    "idempotent": false,
    "requiresApproval": true,
    "selectionEffect": "destroy"
  },
  "runtimeOwner": "mnote-web",
  "writeOwner": "rust-runtime-kernel"
}
```

`tiptap-ai-autocomplete` 的价值限于交互层：

- ghost text 定位。
- 选区 bubble menu。
- streaming preview。
- accept/reject 后再写入。

它不应进入 Hermes tool contract 的核心，只能作为页面 AI 面板、选区 AI 菜单和 preview UI 的参考。

`tiptap-main` 开源代码给 mnote 的补充约束：

- `extension-unique-id` 可作为 Tiptap runtime 节点 id 辅助，但不能替代 Rust `EditorBlock.block_id`。
- `server-ai-toolkit` 的 `_hash` 是 AI 编辑定位/变化检测辅助，不能替代业务 `blockId`；它可以参与 mnote `revisionRef` 的 hash 部分。
- `extension-drag-handle` 与 `extension-node-range` 说明块选择、拖拽、selection toolbar AI 应共享一套 node range 计算，而不是每个入口重新解析 DOM。
- schema awareness / editor context 应进入 `mnote.doc.fetch` 或工具 manifest 的上下文生成，避免 AI 猜测当前可用块类型。

---

## 5. 当前 mnote 稳定性判断

### 5.1 已经可以承载工具设计的边界

- Page Aggregate 读合同已经稳定为 `mnote.page_aggregate.v1`。
- 页面主读链已经可以通过 Rust `/api/page-aggregate/:id` 返回 meta/content/options/body snapshot。
- Hermes 页面 AI 已有 `mnote.*` 工具注册、dispatch、trace/audit、idempotency、dryRun 基础。
- 页面级工具 `mnote.page.get/save/update_title/update_options` 已能完成最小闭环。
- Rust `core-protocol` 已有工具规格方向，`bridge-runtime` 已出现 `doc_insert_blocks`、`doc_replace_range` 等工具/测试雏形。

这些足以支撑“工具合同设计”和“只读/定位/dry-run 工具实现”。

### 5.2 还不适合大规模开放块级写工具的边界

- 当前保存主链仍是 `documents.content` legacy JSON snapshot，不是 `EditorBlockDocument` 原生落库，也不是 Tiptap JSON 原生落库。
- 历史 `blocks` 表仍存在，但没有维护正文主链所需的 order、父子顺序和 `documents.content` 同步闭环，不能描述为当前正文事实源。
- Page Aggregate 当前 `body.content` 仍主要是 `documents:getContent` 的 projection 包装，不是强类型 block protocol。
- Page Aggregate 当前仍有 compat join 痕迹，不是所有页面正文语义都已经 kernel-native。
- 部分 Rust block/editor command 已有 runtime 映射，但 Convex 执行面、editor session 刷新和真实页面可见性还需要逐条验收。
- 块移动、块复制、块嵌入、块引用维护需要稳定块 id、revision、父子关系、排序、权限和冲突处理；当前不应直接给 Hermes 开生产写入口。
- `mnote.page.save` 当前是整页/追加/前置级写入，适合作为过渡和兜底，不适合承诺“精确块编辑”。
- `mnote.page.save` 当前要求 `dryRun/idempotencyKey`，但页面级 revision/conflict key 还不是强制写入门槛；块级写工具开放前必须补上强制 CAS 或等价冲突阻断。

### 5.3 设计上的硬边界

- AI 工具不得绕过 Rust runtime 直接写 Convex。
- AI 工具不得把前端 Tiptap JSON 当长期外部合同。
- AI 工具不得依赖浏览器临时 DOM id 或 runtime-only selection。
- 写工具必须有 `dryRun`、`idempotencyKey`、`revision` 或等价冲突键。
- 写工具必须返回 `warnings`，并能阻止明显错误的编辑假设。
- 写工具成功后必须能通过真实页面、`/api/page-aggregate` 和再次 `mnote.*.fetch/get` 三处验收。

---

## 6. 工具体系分层

### L0：内部 kernel / adapter 命令

只给 Rust runtime、Page Aggregate adapter、Convex bridge 使用，不直接暴露给 Hermes。

示例：

- `page.aggregate.get`
- `page.body.save`
- `page.body.apply_patch`
- `editor.block.insert_after`
- `editor.block.replace`
- `editor.block.delete`
- `editor.block.move_after`
- `tree.node.create`
- `tree.subtree.move`

要求：

- 可以随内核演进调整。
- 必须有测试覆盖。
- 由 L1 canonical tools 翻译调用。

### L1：AI 可调用 canonical tools

Hermes skill/plugin 对外暴露的稳定工具层。第一阶段只做少量、可解释、可验收工具。

建议命名：

- `mnote.doc.fetch`
- `mnote.doc.find`
- `mnote.doc.plan_update`
- `mnote.doc.apply_update`
- `mnote.block.fetch`
- `mnote.block.insert_after`
- `mnote.block.replace`
- `mnote.block.delete`
- `mnote.block.move_after`
- `mnote.page.append`
- `mnote.page.overwrite`

其中：

- `mnote.doc.fetch/find/plan_update` 可先做。
- `mnote.block.insert_after/replace` 是最小块写入切片。
- `mnote.block.delete/move_after` 等架构稳定后再做。
- `mnote.page.overwrite` 是高风险兜底，必须强确认和 dry-run。

### L2：workflow tools

面向用户任务的编排工具，不应第一阶段优先做。

示例：

- `mnote.workflow.write_weekly_report`
- `mnote.workflow.rewrite_section`
- `mnote.workflow.extract_action_items`
- `mnote.workflow.create_meeting_note`
- `mnote.workflow.generate_project_plan`

要求：

- L2 必须调用 L1，不直接写 L0。
- L2 的产物先走 `plan_update` / `dryRun`，用户确认后再执行。
- 不用 L2 掩盖 L1 工具合同不稳定的问题。

---

## 7. 稳定文档表示

### 7.1 PageMarkdown

适合纯文本、标题、列表、引用、代码、简单表格等常见 AI 输出。

用途：

- AI 生成大纲、摘要、会议纪要。
- `append/prepend/overwrite`。
- `str_replace` 简单替换。

限制：

- 不表达复杂属性、块引用、嵌入、资源块、页面块关系。
- 不作为唯一长期格式。

### 7.2 PageXML

参考飞书 DocxXML，但定义 mnote 自己的 PageXML。用于需要稳定 block id、属性、资源引用和结构化块的场景。

最小形态示例：

```xml
<page title="项目计划">
  <heading level="2" block-id="heading_1">目标</heading>
  <paragraph block-id="p_1">第一段文字</paragraph>
  <todo block-id="todo_1" checked="false">确认方案</todo>
  <callout tone="info">重要说明</callout>
</page>
```

原则：

- `block-id` 只引用 mnote 已存在或本次 dry-run 分配的新 id。
- PageXML 是 AI 外部合同，内部可翻译为 `EditorBlockDocument`。
- 不暴露 Tiptap 节点私有字段。
- 不复制飞书专有块；只取 DSL 思想。

### 7.3 EditorBlockDocument

Rust 内部结构化文档模型，是 PageXML/PageMarkdown 到 Page Aggregate / editor session 的中间形态。

要求：

- 持有稳定 `block_id`。
- 能表达 block type、text、attrs、children、parent、order、path、revisionRef。
- 能生成 diff plan。
- 能映射到当前 Convex-backed 保存链路。
- 能在 Tiptap JSON、legacy `documents.content` 与 Page Aggregate block projection 之间做受控转换。

---

## 8. Canonical tool schema 草案

### 8.1 `mnote.doc.fetch`

用途：读取当前文档或局部文档，返回可供 AI 定位和编辑的稳定投影。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "format": "markdown",
  "detail": "with_ids",
  "scope": "full",
  "startBlockId": null,
  "endBlockId": null,
  "keyword": null,
  "sectionTitle": null,
  "contextBefore": 2,
  "contextAfter": 2,
  "maxDepth": 6
}
```

字段约束：

- `format`: `markdown | page_xml | text | json`
- `detail`: `simple | with_ids | full`
- `scope`: `full | outline | range | keyword | section | selection | block`

返回：

```json
{
  "ok": true,
  "revision": "rev_12",
  "conflictDetectionKey": "doc_1:12",
  "format": "markdown",
  "detail": "with_ids",
  "scope": "section",
  "content": "## 目标 <!-- block:heading_1 -->\n正文 <!-- block:p_1 -->",
  "blocks": [
    { "blockId": "heading_1", "type": "heading", "text": "目标", "depth": 0, "revisionRef": "pageRev:rev_12:block:heading_1:hash:aaa" },
    { "blockId": "p_1", "type": "paragraph", "text": "正文", "depth": 1, "revisionRef": "pageRev:rev_12:block:p_1:hash:bbb" }
  ],
  "warnings": []
}
```

验收标准：

- [ ] `scope=full` 能返回当前页面正文，且 `with_ids` 包含稳定 `blockId`。
- [ ] `scope=outline` 只返回标题/层级和必要 block id。
- [ ] `scope=keyword` 返回命中块和前后上下文。
- [ ] 返回内容与 `/api/page-aggregate/:id` 的 block snapshot 一致。
- [ ] 不依赖浏览器 DOM。

### 8.2 `mnote.doc.find`

用途：在当前文档中查找关键词、块类型、标题或引用目标，帮助 AI 先定位再编辑。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "query": "待办",
  "match": "text",
  "limit": 20
}
```

返回：

```json
{
  "ok": true,
  "matches": [
    {
      "blockId": "todo_1",
      "type": "todo",
      "text": "确认待办",
      "path": ["项目计划", "本周"],
      "score": 0.92
    }
  ]
}
```

验收标准：

- [ ] 可以按文本查找。
- [ ] 可以按 block type 查找。
- [ ] 返回结果能直接作为 `block.replace/insert_after` 的 anchor。

### 8.3 `mnote.doc.plan_update`

用途：只生成变更计划和诊断，不实际写入。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "revision": "rev_12",
  "conflictDetectionKey": "doc_1:12",
  "command": "block_replace",
  "format": "markdown",
  "blockId": "p_1",
  "content": "替换后的段落",
  "dryRun": true
}
```

支持命令：

- `str_replace`
- `block_insert_after`
- `block_replace`
- `block_delete`
- `block_move_after`
- `append`
- `overwrite`

返回：

```json
{
  "ok": true,
  "dryRun": true,
  "revision": "rev_12",
  "plan": [
    {
      "op": "replace",
      "targetBlockId": "p_1",
      "before": "旧段落",
      "after": "替换后的段落"
    }
  ],
  "warnings": [],
  "requiresConfirmation": true,
  "risk": "medium"
}
```

诊断规则参考 `docs_update_check.go`：

- 替换目标匹配多个位置时，提示先用 `blockId` 精确定位。
- `str_replace` 找不到唯一匹配时，不执行。
- 跨多个块的自然语言替换必须转成 `block_delete + block_insert_after` 或 `block_replace`。
- `overwrite` 必须标高风险。
- 缺少 `revision` 时只允许 dry-run，不允许真实写入。

验收标准：

- [ ] 所有写命令都能先 dry-run。
- [ ] dry-run 不改变 Convex 内容、不触发 editor 内容变化。
- [ ] 返回的 `plan` 可被 UI 折叠展示。
- [ ] 明显不安全的编辑返回 `warnings` 或 `blocked=true`。

### 8.4 `mnote.doc.apply_update`

用途：执行已经 dry-run 过的文档变更计划。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "revision": "rev_12",
  "conflictDetectionKey": "doc_1:12",
  "idempotencyKey": "idem_1",
  "planId": "plan_1",
  "command": "block_replace",
  "format": "markdown",
  "blockId": "p_1",
  "content": "替换后的段落",
  "dryRun": false
}
```

执行要求：

- 必须校验 `revision`。
- 必须校验 `idempotencyKey`。
- 必须落 Rust runtime / Page Aggregate command。
- 成功后必须返回新 revision 和受影响 block id。

返回：

```json
{
  "ok": true,
  "revision": "rev_13",
  "changedBlocks": [
    { "blockId": "p_1", "op": "replace" }
  ],
  "audit": {
    "effect": "write",
    "commandName": "page.body.apply_update",
    "commandId": "cmd_1"
  }
}
```

验收标准：

- [ ] 执行后 `/api/page-aggregate/:id` 能读到更新。
- [ ] 当前打开页面能自动刷新或通过 editor session reload 看到更新。
- [ ] 再次 `mnote.doc.fetch` 能读到更新。
- [ ] 重复同一 `idempotencyKey` 不造成重复写入。
- [ ] revision 冲突返回 `mnote_tool_conflict`。

### 8.5 `mnote.block.fetch`

用途：读取单个块及可选上下文，作为 `tiptapReadSelection` / selection-aware editing 的稳定服务端版本。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "blockId": "p_1",
  "revision": "rev_12",
  "includeChildren": true,
  "contextBefore": 1,
  "contextAfter": 1,
  "format": "json"
}
```

字段约束：

- `blockId` 必须来自 `mnote.doc.fetch/find` 或 Page Aggregate block projection。
- `format`: `json | markdown | page_xml | text`
- `includeChildren` 默认 `true`，但复杂块可返回 `unsupportedReason`。
- `contextBefore/contextAfter` 只返回同父级上下文。

返回：

```json
{
  "ok": true,
  "revision": "rev_12",
  "block": {
    "blockId": "p_1",
    "type": "paragraph",
    "text": "正文",
    "attrs": {},
    "path": [2],
    "parentBlockId": null,
    "order": "00020000",
    "revisionRef": "pageRev:rev_12:block:p_1:hash:abc",
    "editable": true
  },
  "context": {
    "before": [],
    "after": [
      { "blockId": "p_2", "type": "paragraph", "text": "下一段" }
    ]
  },
  "warnings": []
}
```

验收标准：

- [ ] `block.fetch` 能读取 `doc.find` 返回的 block id。
- [ ] 返回 `revisionRef`，可被后续写工具用于冲突检测。
- [ ] 不存在 block 返回 `mnote_block_not_found`。
- [ ] 不可编辑块返回 `editable=false` 与 `unsupportedReason`。

### 8.6 `mnote.block.replace`

用途：最小精确块写工具，作为第一批块级写入候选。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "blockId": "p_1",
  "revision": "rev_12",
  "conflictDetectionKey": "doc_1:12",
  "blockRevisionRef": "pageRev:rev_12:block:p_1:hash:abc",
  "format": "markdown",
  "content": "替换后的块内容",
  "idempotencyKey": "idem_1",
  "dryRun": true
}
```

验收标准：

- [ ] 只替换目标 block，不影响相邻 block。
- [ ] block id 稳定，刷新后仍可定位。
- [ ] 支持 `dryRun=true`。
- [ ] 支持真实页面 smoke。

### 8.7 `mnote.block.insert_after`

用途：在指定块后插入一个或多个块。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "anchorBlockId": "p_1",
  "revision": "rev_12",
  "conflictDetectionKey": "doc_1:12",
  "anchorRevisionRef": "pageRev:rev_12:block:p_1:hash:abc",
  "format": "markdown",
  "content": "- 新待办",
  "idempotencyKey": "idem_2",
  "dryRun": true
}
```

验收标准：

- [ ] 插入位置准确。
- [ ] 新 block id 由 Rust/runtime 分配或确认，不由 AI 自造。
- [ ] 当前页面能看到新块。
- [ ] 再次 fetch 能拿到新 block id。

### 8.8 `mnote.block.move_after`

用途：移动一个块到同父级 anchor 块后。第一阶段仅作为受限结构性写工具开放。

入参：

```json
{
  "workspaceId": "ws_1",
  "documentId": "doc_1",
  "blockId": "p_3",
  "anchorBlockId": "p_1",
  "revision": "rev_12",
  "conflictDetectionKey": "doc_1:12",
  "blockRevisionRef": "pageRev:rev_12:block:p_3:hash:aaa",
  "anchorRevisionRef": "pageRev:rev_12:block:p_1:hash:bbb",
  "idempotencyKey": "idem_move_1",
  "dryRun": true
}
```

第一阶段允许：

- `blockId` 与 `anchorBlockId` 同父级。
- 普通 paragraph。
- 普通 heading 叶子块。
- 普通 todo 叶子块。

第一阶段阻断：

- 跨父级移动。
- 标题带子块整体移动。
- 列表项跨层级移动。
- 表格、resource、mindmap、page reference。
- 跨页面移动。

返回：

```json
{
  "ok": true,
  "dryRun": true,
  "risk": "medium",
  "diff": [
    {
      "op": "move_after",
      "blockId": "p_3",
      "anchorBlockId": "p_1",
      "from": { "parentBlockId": null, "order": "00030000" },
      "to": { "parentBlockId": null, "afterOrder": "00010000" }
    }
  ],
  "warnings": []
}
```

验收标准：

- [ ] dry-run 不改变页面。
- [ ] 正式写入后 moving block id 保持不变。
- [ ] 同父级顺序正确。
- [ ] 不支持场景返回 `blocked=true` 和明确 warning。
- [ ] 再次 `mnote.doc.fetch` 能读回新顺序。

---

## 9. 实施阶段

### Phase A：只读和定位

目标：

- 建立 `mnote.doc.fetch` 和 `mnote.doc.find`。
- 支持 `format`、`detail`、`scope`。
- 让 AI 能稳定读到 block id、标题路径、上下文。

可做：

- [ ] 从 Page Aggregate snapshot 生成 PageMarkdown。
- [ ] 从 Page Aggregate snapshot 生成 PageXML 最小子集。
- [ ] 支持 `scope=full/outline/keyword/block`。
- [ ] 支持 `detail=simple/with_ids/full`。
- [ ] Hermes tool event UI 展示 fetch/find 摘要。

验收：

- [ ] 真实登录页面中，Hermes 调用 `mnote.doc.fetch` 能读出当前页面。
- [ ] `with_ids` 返回的 block id 与 DOM `data-block-id` / Page Aggregate 一致。
- [ ] `keyword` 读取不会返回整页超大正文。
- [ ] 失败时返回权限/不存在/空页面的结构化错误。

### Phase B：dry-run / plan

目标：

- 建立 `mnote.doc.plan_update`。
- 写入前先给出可解释 diff、风险、warnings。

可做：

- [ ] 支持 `str_replace` dry-run。
- [ ] 支持 `block_replace` dry-run。
- [ ] 支持 `block_insert_after` dry-run。
- [ ] 支持 `append` dry-run。
- [ ] 实现静态诊断规则。

验收：

- [ ] dry-run 不改变页面内容。
- [ ] UI 可折叠展示 plan。
- [ ] 多重匹配、不存在 block、缺 revision 都返回明确 warning。

### Phase C：最小块级写入

目标：

- 开放 `mnote.block.replace` 和 `mnote.block.insert_after` 的真实写入。

前置硬门槛：

- [ ] Page Aggregate 中每个可编辑块都有稳定 `blockId`。
- [ ] Rust runtime 能把 block replace/insert 变更映射到持久化结构。
- [ ] Convex-backed 保存链能保存变更并刷新。
- [ ] 当前 editor session 能 reload 或 live refresh。
- [ ] smoke 覆盖 AI tool -> Rust -> Convex -> 页面可见 -> fetch 回读。

验收：

- [ ] AI 替换单个段落后，页面只变这一段。
- [ ] AI 在指定块后插入待办后，页面位置正确。
- [ ] 刷新页面后内容仍在。
- [ ] 版本冲突不会覆盖用户刚刚输入的内容。

### Phase D：多块和结构性写入

目标：

- 开放 `block_delete`、`block_move_after`、`block_copy_insert_after`。

前置硬门槛：

- [ ] 块父子关系、排序、缩进、折叠状态稳定。
- [ ] 删除/移动能处理子树。
- [ ] 冲突检测覆盖移动前后的邻居和父节点。
- [ ] 有撤销或可审计回滚策略。

验收：

- [ ] 移动标题块时，其子块处理规则明确且测试覆盖。
- [ ] 删除块需要确认，并返回被删除范围。
- [ ] 复制插入生成新 id，不复用旧 id。

### Phase E：workflow tools

目标：

- 基于 L1 工具做高层 AI 写作工作流。

可做：

- [ ] 生成会议纪要。
- [ ] 生成周报。
- [ ] 重写某一节。
- [ ] 提取待办并插入当前页。

验收：

- [ ] workflow 只调用 L1 canonical tools。
- [ ] 每个 workflow 都能先 plan，再执行。
- [ ] 不出现直接整页覆盖用户内容的默认行为。

---

## 10. 架构优先级判断

当前应并行推进，但优先级要明确：

1. **先稳定 Page Aggregate / block identity / editor save-refresh 链路**：这是块级写工具能否长期可靠的根。
2. **同时设计并冻结 L1 canonical tools 合同**：避免继续把临时页面保存扩展成长期能力。
3. **先实现只读和 dry-run**：这部分对底层写链依赖小，能立即改善 AI 可靠性。
4. **等硬门槛满足后开放最小块写入**：先 `replace` / `insert_after`，不要一口气做完整块操作。
5. **最后做 workflow**：工具层不稳定时，workflow 只会放大错误。

因此，回答“先优化稳定架构，还是继续写工具设计”：

> **架构稳定是实现复杂写工具的前置；工具设计现在就应该继续，而且必须用于反向约束架构稳定的验收标准。**

---

## 11. 近期不做清单

- [ ] 不让 Hermes 直接调用 Convex mutation。
- [ ] 不把 Tiptap JSON 暴露成 AI 长期工具入参。
- [ ] 不把 `mnote.page.save` 包装成所有块编辑的长期方案。
- [ ] 不先做大量 `workflow.*` 工具。
- [ ] 不做无 revision / 无 dry-run / 无 idempotency 的写工具。
- [ ] 不在工具里读取浏览器 DOM 来决定写入位置。
- [ ] 不用页面当前可见文本做唯一定位依据；必须支持 block id 或唯一匹配诊断。

---

## 12. 执行 checklist

### 12.1 设计冻结

- [ ] 确认 `mnote.doc.fetch/find/plan_update/apply_update` 命名。
- [ ] 确认 `PageMarkdown` 最小语法。
- [ ] 确认 `PageXML` 最小语法。
- [ ] 确认 `revision` 来源。
- [ ] 确认 `blockId` 来源只来自 Page Aggregate / Rust runtime。
- [ ] 确认 `warnings` / `risk` / `requiresConfirmation` 返回格式。

验收标准：

- [ ] `design/07-ai/process` 中有稳定工具合同。
- [ ] `7-6` 不再把 `mnote.page.save` 描述为长期块编辑合同。
- [ ] `5-6` 的 Page Aggregate checklist 能引用本稿作为 AI 写入门槛。

### 12.2 只读工具实现

- [ ] Rust tool manifest 增加 `mnote.doc.fetch`。
- [ ] Rust tool manifest 增加 `mnote.doc.find`。
- [ ] Rust tool manifest 增加 `mnote.block.fetch`。
- [ ] dispatch 调用 Page Aggregate snapshot，而不是 Convex 私有 shape。
- [ ] 支持 `detail=simple/with_ids/full`。
- [ ] 支持 `scope=full/outline/keyword/block`。
- [ ] 工具结果进入 Hermes tool event UI。

验收标准：

- [ ] `cargo test -p mnote-web hermes` 相关测试通过。
- [ ] 真实网页登录后，AI 能通过 `mnote.doc.fetch` 读取当前页。
- [ ] AI 能通过 `mnote.doc.find` 找到指定文本所在 block id。
- [ ] `/api/page-aggregate/:id` 与 tool 返回 block id 一致。

### 12.3 dry-run 实现

- [ ] 增加 `mnote.doc.plan_update`。
- [ ] 支持 `str_replace` 计划。
- [ ] 支持 `block_replace` 计划。
- [ ] 支持 `block_insert_after` 计划。
- [ ] 返回 `plan/warnings/risk/requiresConfirmation`。
- [ ] 静态诊断阻止多重匹配和缺 revision 的真实写入。

验收标准：

- [ ] dry-run 不改变页面。
- [ ] UI 能显示工具计划。
- [ ] 多重匹配返回 warning。
- [ ] 不存在 block 返回结构化错误。

### 12.4 最小块写入实现

- [ ] 打通 `mnote.block.replace`。
- [ ] 打通 `mnote.block.insert_after`。
- [ ] 写入统一走 Rust runtime / Page Aggregate command。
- [ ] 成功后触发当前 editor session reload 或 live refresh。
- [ ] 成功后返回新 revision 和 changedBlocks。

验收标准：

- [ ] AI 替换单块，页面立即可见。
- [ ] AI 插入新块，位置准确。
- [ ] 刷新后内容仍在。
- [ ] 再次 fetch 能读回变更。
- [ ] revision 冲突被拦截。

### 12.5 真实网页 smoke

- [ ] 新建测试页面，写入唯一前缀 `TEST-AI-TOOL-<timestamp>`。
- [ ] `mnote.doc.fetch scope=full detail=with_ids` 读取页面。
- [ ] `mnote.doc.find` 定位测试段落。
- [ ] `mnote.doc.plan_update command=block_replace dryRun=true` 生成计划。
- [ ] `mnote.block.replace dryRun=false` 替换段落。
- [ ] 页面截图证明内容可见。
- [ ] `/api/page-aggregate/:id` 证明内容持久化。
- [ ] `mnote.doc.fetch` 再次证明 AI 可读回。

验收标准：

- [ ] 证据目录写入 `tmp/hermes-tester/<run-id>/`。
- [ ] 失败时记录到 `bugs/07-ai/process/` 或真正 owner 分类。
- [ ] 通过后才能把对应 checklist 勾到 done。

---

## 13. 与当前页面 AI 的关系

当前已有工具继续保留：

- `mnote.page.get`
- `mnote.page.save`
- `mnote.page.update_title`
- `mnote.page.update_options`
- `mnote.artifact.*`

但口径调整为：

- `mnote.page.get` 是页面级读取，不是长期精确块读取。
- `mnote.page.save` 是页面级兜底写入，只适合 append/prepend/replace 等粗粒度操作。
- 精确编辑应迁移到 `mnote.doc.*` / `mnote.block.*`。
- 页面 AI 面板展示工具时，应把 `page.save` 标为高风险或粗粒度。

---

## 14. 迁移完成定义

本文作为页面/块 AI 工具体系路线图与合同已经归档为 `DONE`；执行验收不再由本文继续承接，而是转入：

- `/mnt/Data1T/mnote/design/old/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`

当前已成立的 done 边界：

- [x] `mnote.doc.fetch` / `mnote.doc.find` 已进入 Rust Hermes tool manifest 与 dispatch，并已有最小真实页面 smoke 证据。
- [x] `mnote.doc.plan_update` 已进入 dry-run 计划链，可返回 diff、warnings、risk、blocked。
- [x] `mnote.block.replace`、`mnote.block.insert_after`、`mnote.block.move_after` 已形成最小块写入闭环。
- [x] `mnote.page.save` 已在 UI/manifest 口径中降为页面级兜底工具，不再作为默认精确块编辑入口。
- [x] 后续执行项以 `7-10` 跟踪，仍包括 `scope=selection`、`format=page_xml/text`、多块插入、复杂块移动矩阵和持久审阅 UI。
