# [recycle] 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` | `
`、``、``、`
`、`` | 用 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
目标
第一段文字
确认方案
重要说明
```
原则:
- `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": "## 目标 \n正文 ",
"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-`。
- [ ] `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//`。
- [ ] 失败时记录到 `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。