Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
976 lines
34 KiB
Markdown
976 lines
34 KiB
Markdown
# [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` | `<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。
|