feat: EditorRuntimeActor - 三层缓存/delta/事件架构
Phase A — EditorRuntimeActor 内存缓存层 - 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init - block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径 - editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启) - bridge-runtime 三个核心函数公开化 - rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞) Phase B — 编辑器增量 delta channel - BlockDelta/DeltaOperation 类型 + actor.build_block_delta() - leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch - DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent - 工具响应含 blockDelta 字段供前端消费 Phase C — 事件 stream delta - broadcast channel 在 AppState/actor/SSE 三层贯通 - tree_events SSE 端点发 block.delta 事件 - 旧客户端降级兼容 环境修复 - rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出) - run-convex-deploy.js(封装 Convex function 部署到本地后端 3210) ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
This commit is contained in:
@@ -665,7 +665,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-sm
|
||||
- [x] 验证命令:`cd rust && cargo test -p mnote-web explicit_ -- --nocapture`:2 passed。
|
||||
- [x] 验证命令:`cd rust && cargo build -p mnote-web`:通过;正式 `3000` 已重启到新二进制,PID `2108896`。
|
||||
- [x] 验证命令:`MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-retirement-guard.js`:通过,status `410`,code `legacy_ai_agent_run_retired`,owner `legacy-ai-agent-run-retired`。
|
||||
- [x] 默认退役历史 smoke:`task155-e27-ai-edit-smoke.js`、`task156-e27-ai-writeback-smoke.js`、`task178-page-ai-local-subtree-context-smoke.js`、`task052-ai-tools-runtime-smoke.js`、`task161-wolai-page-ai-shell-smoke.js`、`task111-phase7-document-ai-online-smoke.js` 均默认输出 `{ retired: true }`,不再期待旧 `/api/ai-agent/run -> mnote-cli` 成功;如需历史对照,必须显式设置 `MNOTE_ALLOW_RETIRED_AI_AGENT_RUN_SMOKE=1`。
|
||||
- [x] 默认退役历史 smoke:`task155-e27-ai-edit-smoke.js`、`task156-e27-ai-writeback-smoke.js`、`task178-page-ai-local-subtree-context-smoke.js`、`task052-ai-tools-runtime-smoke.js`、`task161-wolai-page-ai-shell-smoke.js`、`task111-phase7-document-ai-online-smoke.js` 已移入本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`,历史版本默认输出 `{ retired: true }`,不再期待旧 `/api/ai-agent/run -> mnote-cli` 成功;如需历史对照,必须显式设置 `MNOTE_ALLOW_RETIRED_AI_AGENT_RUN_SMOKE=1`。
|
||||
- [x] legacy 调用方清点:`wolai-frontend/src/components/ai-agent/AiAgentPanel.tsx`、`wolai-frontend/src/components/editor/blocks/MindmapAiAgentPanel.runtime.tsx`、`wolai-frontend/src/components/onlyoffice/OnlyOfficeAiAgentPanel.runtime.tsx`、`wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts` 仍是旧域/兼容调用点,不属于当前 mnote-web 页面 AI 主链;若被调用会命中 `legacy_ai_agent_run_retired` guard,后续按各自 domain 另拆 Hermes plugin / Rust bridge 迁移。
|
||||
- [x] 活跃设计口径同步:`5-6`、`5-9`、`10-review` 已改为历史/已覆盖/legacy guard 说明,不再把 `/api/ai-agent/run` 写作页面 AI 长期入口。
|
||||
|
||||
@@ -1010,7 +1010,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-sm
|
||||
2026-05-14 K1 执行证据:
|
||||
|
||||
- [x] rg 命中已归类:`design/old/**` 与 `design/07-ai/done/7-1**` 为历史证据;`scripts/task-hermes-page-ai-retirement-guard.js` 与 `rust/crates/mnote-web/src/routes/compat.rs` 为 legacy guard;旧 E27/phase7/page-ai smoke 已默认退役;`wolai-frontend` 中 Mindmap / OnlyOffice / generic AI 面板调用点归为非当前页面 AI 主链的 legacy domain 调用点,后续按各自 domain 迁移。
|
||||
- [x] 修改文件:`rust/crates/mnote-web/src/routes/compat.rs`、`scripts/task-hermes-page-ai-retirement-guard.js`、`scripts/task155-e27-ai-edit-smoke.js`、`scripts/task156-e27-ai-writeback-smoke.js`、`scripts/task178-page-ai-local-subtree-context-smoke.js`、`scripts/task052-ai-tools-runtime-smoke.js`、`scripts/task161-wolai-page-ai-shell-smoke.js`、`scripts/task111-phase7-document-ai-online-smoke.js`、`design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`、`design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md`、`design/10-review/04-secondary-domains-and-design-governance-review.md`。
|
||||
- [x] 修改文件:`rust/crates/mnote-web/src/routes/compat.rs`、`scripts/task-hermes-page-ai-retirement-guard.js`、历史退役 smoke 当前本机归档于 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`、`design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`、`design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md`、`design/10-review/04-secondary-domains-and-design-governance-review.md`。
|
||||
- [x] 旧 smoke 默认退役验证:上述 6 个历史 smoke 均返回 `{ ok: true, retired: true }`。
|
||||
|
||||
**验收标准:**
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
> Hermes Web UI 参考:`packages/client/src/api/hermes/plugins.ts`、`packages/client/src/api/hermes/skills.ts`、`packages/server/src/services/hermes/plugins.ts`、`packages/server/src/services/hermes/chat-run-socket.ts`
|
||||
>
|
||||
> 2026-05-16 口径补充:本文定义的是页面 AI 最小可用工具合同。`mnote.page.save` 只作为页面级兜底写入工具,不再代表长期精确块编辑方案;页面/块级 AI 工具体系以 `design/07-ai/process/7-9-page-block-ai-tooling-roadmap-v1.md` 为后续规划依据。
|
||||
> 2026-05-16 口径补充:本文定义的是页面 AI 最小可用工具合同。`mnote.page.save` 只作为页面级兜底写入工具,不再代表长期精确块编辑方案;页面/块级 AI 工具体系路线图已归档到 `design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`,执行验收继续以 `design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md` 为准。
|
||||
|
||||
## 1. 总边界
|
||||
|
||||
|
||||
+14
-10
@@ -1,8 +1,8 @@
|
||||
# 7-9 [process] 页面/块 AI 工具体系规划 v1
|
||||
# 7-9 [done] 页面/块 AI 工具体系规划 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`PROCESS`。
|
||||
> 当前状态:`DONE`。
|
||||
>
|
||||
> 本稿承接 `7-6` 的 mnote Hermes plugin tool 合同、`7-8` 的 Hermes Runtime BFF 方向,以及近期页面 AI 工具实测中暴露的问题:当前 `mnote.page.get/save/update_title/update_options` 已能完成页面级读写,但工具粒度仍偏粗,不能长期代表“AI 能精确编辑页面/块”。
|
||||
>
|
||||
@@ -21,8 +21,8 @@
|
||||
> - `/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/process/5-6-page-aggregate-alignment-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-13-page-block-identity-and-command-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-1-page-block-storage-projection-alignment-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/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
@@ -962,10 +962,14 @@ Rust 内部结构化文档模型,是 PageXML/PageMarkdown 到 Page Aggregate /
|
||||
|
||||
## 14. 迁移完成定义
|
||||
|
||||
本稿不能标记 `DONE`,直到满足:
|
||||
本文作为页面/块 AI 工具体系路线图与合同已经归档为 `DONE`;执行验收不再由本文继续承接,而是转入:
|
||||
|
||||
- [ ] `mnote.doc.fetch` / `mnote.doc.find` 已实现并通过真实页面 smoke。
|
||||
- [ ] `mnote.doc.plan_update` 已实现并能返回 warnings。
|
||||
- [ ] 至少一个最小块写工具 `mnote.block.replace` 或 `mnote.block.insert_after` 通过真实页面 smoke。
|
||||
- [ ] `mnote.page.save` 在 UI/manifest 中被标记为页面级兜底工具,不再作为默认精确编辑入口。
|
||||
- [ ] 相关工具设计被同步到 Hermes skill/plugin 描述,AI 能按“先 fetch/find,再 plan,再 apply”的顺序调用。
|
||||
- `/mnt/Data1T/mnote/design/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。
|
||||
@@ -5,10 +5,11 @@
|
||||
> 当前状态:`PROCESS`。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-13-page-block-identity-and-command-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-1-page-block-storage-projection-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-9-page-block-ai-tooling-roadmap-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-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-6-mnote-hermes-plugin-tool-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
|
||||
|
||||
---
|
||||
|
||||
@@ -16,6 +17,61 @@
|
||||
|
||||
本 checklist 把 `7-9` 的路线图拆成可验收执行链,避免页面块 AI 工具停留在“工具名已设计、真实页面不可回读”的状态。
|
||||
|
||||
## 1.0 执行口径修正(2026-05-16)
|
||||
|
||||
本文继续作为页面块 AI 工具执行 checklist 保留在 `process/`,不移入 `old/`。但后续所有“页面 AI runtime / fast workflow / planner / apply controller”相关任务必须按 `7-12` 的边界解释:
|
||||
|
||||
- Hermes 继续是唯一页面 AI agent runtime。
|
||||
- mnote 本地层只提供工具路由、工具提示、上下文冻结、dry-run/review、Rust 写入校验和 readback。
|
||||
- `PageAIIntentParser` 后续读作 `PageAICommandRouter`,输出 `recommendedToolCall`,不维护独立对话 runtime。
|
||||
- `PageAIOperationPlanner` 后续只构造 tool args 或 dry-run plan,不能绕过 Hermes tool manifest/profile toggle/audit。
|
||||
- `PageAIOperationValidator` 继续有效,但归属 mnote Rust tool executor / projection validation。
|
||||
- `PageAIApplyController` 后续应收口为 review session / tool executor / readback controller,不能成为第二套 agent 编排中心。
|
||||
- 当前 `usedHermesRun=false` 的快路径只能理解为 deterministic shortcut,不代表 mnote 新建长期 agent runtime。
|
||||
|
||||
## 1.1 当前执行状态(2026-05-16)
|
||||
|
||||
已完成并有代码/测试/smoke 证据:
|
||||
|
||||
- Page Aggregate 输出 `blockDocument/blockProjectionVersion/projectionSource`。
|
||||
- `mnote.doc.fetch`、`mnote.doc.find`、`mnote.block.fetch` 已接入 Hermes tool manifest 与 dispatch。
|
||||
- `mnote.doc.plan_update` 已提供块级 dry-run 计划和移动阻断诊断。
|
||||
- `mnote.block.replace`、`mnote.block.insert_after`、`mnote.block.move_after` 已通过 Rust `EditorCommand` 生成 canonical content,再经 `page.body.save -> documents:updateContent` 持久化。
|
||||
- `revision/conflictDetectionKey/revisionRef/idempotencyKey/dryRun` 写入前置约束已接入,并修复 `content_revision` 投影对齐。
|
||||
- 真实 smoke:`/mnt/Data1T/mnote/scripts/task-page-block-ai-tools-smoke.js`,证据位于 `/mnt/Data1T/mnote/tmp/page-block-ai-tools-smoke/mp7xgyqs.json` 与同名截图。
|
||||
- 页面 AI Runtime `mnote tools` 面板已读取真实 manifest,展示 13 个 mnote tools,并支持当前 Hermes profile 下开关工具。
|
||||
- `/api/hermes/client/tools/toggle` 已持久化 `mnote.tools.disabled`;`/api/hermes/tools/mnote/call` 在执行前按 profile 拦截关闭工具,返回 `mnote_tool_disabled`。
|
||||
- Hermes 外部 mnote plugin/skill 已完成,不改 Hermes 应用本体:
|
||||
- `/home/lix/.hermes/plugins/mnote/plugin.yaml`
|
||||
- `/home/lix/.hermes/plugins/mnote/__init__.py`
|
||||
- `/home/lix/.hermes/skills/note-taking/mnote-block-ai/SKILL.md`
|
||||
- Hermes 外部 plugin schema 已对齐真实块工具参数:`mnote_doc_fetch(scope/detail/format/query/maxBlocks/blockId/selectedBlockIds/allowedTargetBlockIds)`、`mnote_doc_plan_update(command/blockId/anchorBlockId/content)`、`mnote_block_fetch(blockId/includeChildren/contextBefore/contextAfter/format)`。
|
||||
- Hermes CLI 真实 plugin 块操作已通过:
|
||||
- 测试页:`workspaceId=tree_1777430834634_3`,`documentId=tree_1778915893346_1`,`suffix=mp80lbze`。
|
||||
- 工具链:`mnote_doc_fetch -> mnote_block_fetch -> mnote_doc_plan_update(dryRun block_replace) -> mnote_block_replace -> mnote_doc_plan_update(dryRun block_insert_after) -> mnote_block_insert_after -> mnote_doc_plan_update(dryRun block_move_after) -> mnote_block_move_after -> mnote_doc_fetch`。
|
||||
- 结果:`finalOrder=["p_1","p_3","ai_block_req_1778916033994_21","p_2"]`,`finalTexts` 分别为 `Hermes 块插件第一段 mp80lbze`、`Hermes 块插件第三段 mp80lbze`、`Hermes 插件插入段 mp80lbze`、`Hermes 插件替换第二段 mp80lbze`,`errors=[]`。
|
||||
- 修复点:Hermes 可能把 `doc.fetch` 返回的 projection `payload/contentNodes` 形状传回写工具,`rust/crates/mnote-web/src/hermes_tools/block.rs` 已补齐 `content_to_text` 解析并加单测,避免 replace/insert 写成空段。
|
||||
- 浏览器回读验证已通过:打开 `http://127.0.0.1:3000/documents/tree_1778915893346_1?workspaceId=tree_1777430834634_3` 后四段目标文本可见,截图 `/mnt/Data1T/mnote/tmp/hermes-plugin-block-ai/mp80lbze-page.png`。
|
||||
- 页面 AI 工具面板浏览器验证:3000 最新 `mnote-web` 进程下 Runtime 面板可见 13 个工具;关闭 `mnote.block.fetch` 后 `/api/hermes/client/tools` 显示 `enabled=false/status=disabled`,直接调用 `mnote.block.fetch` 返回 `mnote_tool_disabled`,随后已恢复开启;截图 `/mnt/Data1T/mnote/tmp/page-ai-tools-runtime/mnote-page-ai-tools-runtime-20260516.png`。
|
||||
- 页面 AI context / format focused smoke 已完成:
|
||||
- 脚本:`/mnt/Data1T/mnote/scripts/task-page-block-ai-context-format-smoke.js`。
|
||||
- 证据:`/mnt/Data1T/mnote/tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`,截图 `/mnt/Data1T/mnote/tmp/page-block-ai-context-format-smoke/mp8ddr4n-page.png`。
|
||||
- 覆盖:`mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml/text`、`mnote.block.fetch format=page_xml/text`、manifest annotations、`mnote.page.save` destructive/yolo 粗粒度兜底定位、`mnote.doc.apply_block_ops allowedTargetBlockIds` 越界阻断 `mnote_block_target_out_of_scope`。
|
||||
- 边界:本轮只验证 `doc.apply_block_ops` 的 selection scope guard;单个 `mnote.block.*` 写工具尚未完成 `allowedTargetBlockIds` 矩阵。
|
||||
- 页面 AI 快速块编辑第一阶段已完成:
|
||||
- 新增 `/api/page-ai/block-edit-workflow`,简单块增删改不再默认进入 Hermes agent run。
|
||||
- 对明确中文指令 `把「A」替换为「B」/ 在「C」后插入「D」/ 删除「E」` 已先由 mnote 本地 planner 生成 `mnote.doc.apply_block_ops` operations;无法解析时才进入小模型 operations 路径。
|
||||
- 快路径失败时,除 `page_ai_workflow_not_block_edit` 外不再自动 fallback 到 `/api/hermes/client/runs`,避免一次请求叠加“快路径失败成本 + Hermes agent 成本”。
|
||||
- 真实浏览器 smoke:`/mnt/Data1T/mnote/scripts/task-page-ai-block-edit-workflow-smoke.js`,最新证据 `/mnt/Data1T/mnote/tmp/page-ai-block-edit-workflow-smoke/mp86uciu.json`。
|
||||
- 验证结果:`pageAiWriteVisible=788ms`、`usedFastWorkflow=true`、`usedHermesRun=false`;后端日志 `operation_source=local_rule`、`model_ms=0`、`apply_ms=42`、`total_ms=42`。
|
||||
- 详细 review:`/mnt/Data1T/mnote/design/10-review/process/09-page-ai-fast-block-edit-runtime-review.md`。
|
||||
|
||||
仍未完成,本文继续留在 `process/`:
|
||||
|
||||
- 复杂块移动阻断矩阵还需要单独 smoke 覆盖标题带子块、列表项、表格、mindmap/resource。
|
||||
- 持久审阅/preview UI 仍是后续可选项;当前默认 yolo 模式不做写入审批,`scope=selection` 与 `format=page_xml/text` 已进入工具与 PageAIContextBuilder 首版。
|
||||
- PageAIIntentParser / PageAIOperationPlanner / PageAIOperationValidator / PageAIApplyController 仍需继续建设;当前本地 planner 只覆盖低歧义文本块增删改,不应被视为完整 AI 编辑 runtime。
|
||||
|
||||
执行顺序固定为:
|
||||
|
||||
```text
|
||||
@@ -38,15 +94,15 @@ fetch/find
|
||||
|
||||
## 2. Phase 0:设计与基线冻结
|
||||
|
||||
- [ ] `5-13` 已冻结 block identity、command、Tiptap boundary。
|
||||
- [ ] `2-1` 已冻结 `documents.content`、`blocks` 表、Page Aggregate、revision/conflict key 的关系。
|
||||
- [ ] `7-9` 已更新 Tiptap AI Toolkit 对照,不再写成“Tiptap 没有官方 AI 文档工具”。
|
||||
- [ ] `7-9` 明确 `mnote.page.save` 是粗粒度兜底,不是精确块工具。
|
||||
- [ ] 当前 reference code 已在 `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs` 与 `tiptap-main` 可读。
|
||||
- [x] `5-13` 已冻结 block identity、command、Tiptap boundary。
|
||||
- [x] `2-1` 已冻结 `documents.content`、`blocks` 表、Page Aggregate、revision/conflict key 的关系。
|
||||
- [x] `7-9` 已更新 Tiptap AI Toolkit 对照,不再写成“Tiptap 没有官方 AI 文档工具”。
|
||||
- [x] `7-9` 明确 `mnote.page.save` 是粗粒度兜底,不是精确块工具。
|
||||
- [x] 当前 reference code 已在 `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs` 与 `tiptap-main` 可读。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] `rg -n "Tiptap AI Toolkit|tiptapRead|tiptapEdit|UniqueID|_hash|blockDocument" design/05-editor-mainline/process design/02-convex-rust-long-term-architecture/process design/07-ai/process` 能找到对应设计。
|
||||
- [ ] `rg -n "Tiptap AI Toolkit|tiptapRead|tiptapEdit|UniqueID|_hash|blockDocument" design/05-editor-mainline/{process,done} design/02-convex-rust-long-term-architecture/{process,done} design/07-ai/{process,done}` 能找到对应设计。
|
||||
|
||||
---
|
||||
|
||||
@@ -58,12 +114,12 @@ fetch/find
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] `PageBody` 增加 `blockDocument` 或等价稳定字段。
|
||||
- [ ] 从 `documents.content` 生成 `EditorBlockDocument`。
|
||||
- [ ] 从 Tiptap JSON 生成 `EditorBlockDocument` 的 bridge 测试覆盖当前主类型。
|
||||
- [ ] 每个块输出 `blockId/type/text/attrs/children/parentBlockId/order/path/depth/revisionRef/editable`。
|
||||
- [ ] 对无 id legacy block 生成稳定迁移策略或 warning。
|
||||
- [ ] 复杂块输出 `editable=false` 或受限能力。
|
||||
- [x] `PageBody` 增加 `blockDocument` 或等价稳定字段。
|
||||
- [x] 从 `documents.content` 生成 `EditorBlockDocument`。
|
||||
- [x] 从 Tiptap JSON 生成 `EditorBlockDocument` 的 bridge 测试覆盖当前主类型。
|
||||
- [x] 每个块输出 `blockId/type/text/attrs/children/parentBlockId/order/path/depth/revisionRef/editable`。
|
||||
- [x] 对无 id legacy block 生成稳定迁移策略或 warning。
|
||||
- [x] 复杂块输出 `editable=false` 或受限能力。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -95,13 +151,13 @@ cargo test -p bridge-runtime editor_document
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] tool manifest 增加 `mnote.doc.fetch`。
|
||||
- [ ] tool manifest 增加 `mnote.doc.find`。
|
||||
- [ ] `doc.fetch` 支持 `scope=full/outline/keyword/block/selection`。
|
||||
- [ ] `doc.fetch` 支持 `detail=simple/with_ids/full`。
|
||||
- [ ] `doc.find` 支持按 text/type/blockId 查找。
|
||||
- [ ] 返回 page `revision/conflictDetectionKey`。
|
||||
- [ ] 返回可直接传入 `block.fetch/replace/insert_after` 的 `blockId`。
|
||||
- [x] tool manifest 增加 `mnote.doc.fetch`。
|
||||
- [x] tool manifest 增加 `mnote.doc.find`。
|
||||
- [x] `doc.fetch` 支持 `scope=full/outline/keyword/block/selection`。
|
||||
- [x] `doc.fetch` 支持 `detail=simple/with_ids/full`。
|
||||
- [x] `doc.find` 支持按 text/type/blockId 查找。
|
||||
- [x] 返回 page `revision/conflictDetectionKey`。
|
||||
- [x] 返回可直接传入 `block.fetch/replace/insert_after` 的 `blockId`。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -118,6 +174,11 @@ cargo test -p bridge-runtime doc_find
|
||||
- [ ] `mnote.doc.find query=<唯一前缀>` 定位目标段落。
|
||||
- [ ] 保存工具返回到 `tmp/hermes-tester/<run-id>/doc-fetch-find.json`。
|
||||
|
||||
补充 smoke 证据:
|
||||
|
||||
- [x] `mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml` 读取真实页面选区上下文,只返回 `p_2`,返回 `schema=mnote.page_ai_context.v1`、`allowedTargetBlockIds=["p_2"]`、`revision/conflictDetectionKey` 和 block `revisionRef`。证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
|
||||
- [x] `mnote.doc.fetch scope=selection format=text` 只返回 `[p_2] 第二段 mp8ddr4n`,不包含未选中块。证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
|
||||
|
||||
通过标准:
|
||||
|
||||
- [ ] 不读取浏览器 DOM。
|
||||
@@ -134,12 +195,12 @@ cargo test -p bridge-runtime doc_find
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] tool manifest 增加 `mnote.block.fetch`。
|
||||
- [ ] 支持 `includeChildren`。
|
||||
- [ ] 支持 `contextBefore/contextAfter`。
|
||||
- [ ] 支持 `format=json/markdown/page_xml/text`。
|
||||
- [ ] 返回 `revisionRef`、`editable`、`unsupportedReason`。
|
||||
- [ ] 不存在 block 返回 `mnote_block_not_found`。
|
||||
- [x] tool manifest 增加 `mnote.block.fetch`。
|
||||
- [x] 支持 `includeChildren`。
|
||||
- [x] 支持 `contextBefore/contextAfter`。
|
||||
- [x] 支持 `format=json/markdown/page_xml/text`。
|
||||
- [x] 返回 `revisionRef`、`editable`、`unsupportedReason`。
|
||||
- [x] 不存在 block 返回 `mnote_block_not_found`。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -153,9 +214,13 @@ cargo test -p mnote-web block_fetch
|
||||
- [ ] 调 `mnote.block.fetch includeChildren=true contextBefore=1 contextAfter=1`。
|
||||
- [ ] 断言 before/after 只来自同父级。
|
||||
|
||||
补充 smoke 证据:
|
||||
|
||||
- [x] `mnote.block.fetch blockId=p_2 format=page_xml/text contextBefore=1 contextAfter=1` 返回目标块 `p_2`、`revisionRef` 与同父级 before/after `p_1/p_3`。证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
|
||||
|
||||
通过标准:
|
||||
|
||||
- [ ] block 文本与页面显示一致。
|
||||
- [x] block 文本与页面显示一致。
|
||||
- [ ] `revisionRef` 可被后续 dry-run 使用。
|
||||
- [ ] 复杂块不会伪装成完全可编辑。
|
||||
|
||||
@@ -169,13 +234,13 @@ cargo test -p mnote-web block_fetch
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] tool manifest 增加 `mnote.doc.plan_update`。
|
||||
- [ ] 支持 `command=block_replace`。
|
||||
- [ ] 支持 `command=block_insert_after`。
|
||||
- [ ] 支持 `command=block_move_after` dry-run。
|
||||
- [ ] 支持 `command=str_replace` 且多重匹配阻断。
|
||||
- [ ] 缺少 `revision/conflictDetectionKey` 时只允许 dry-run。
|
||||
- [ ] 返回 `planId`、`diff`、`warnings`、`risk`、`blocked`。
|
||||
- [x] tool manifest 增加 `mnote.doc.plan_update`。
|
||||
- [x] 支持 `command=block_replace`。
|
||||
- [x] 支持 `command=block_insert_after`。
|
||||
- [x] 支持 `command=block_move_after` dry-run。
|
||||
- [ ] 支持 `command=str_replace` 且多重匹配阻断。(当前只有基础 plan,仍需多重匹配阻断。)
|
||||
- [x] 缺少 `revision/conflictDetectionKey` 时只允许 dry-run。
|
||||
- [x] 返回 `planId`、`diff`、`warnings`、`risk`、`blocked`。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -208,13 +273,13 @@ cargo test -p bridge-runtime doc_insert_blocks
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] tool manifest 增加 `mnote.block.replace`。
|
||||
- [ ] 输入必须包含 `blockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
|
||||
- [ ] `dryRun=true` 只返回 plan。
|
||||
- [ ] `dryRun=false` 生成 `EditorCommand::ReplaceBlock`。
|
||||
- [ ] Rust 应用命令生成 canonical content。
|
||||
- [ ] 通过 `page.body.save -> documents:updateContent` 持久化。
|
||||
- [ ] 返回新 revision、changedBlocks、audit。
|
||||
- [x] tool manifest 增加 `mnote.block.replace`。
|
||||
- [x] 输入必须包含 `blockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
|
||||
- [x] `dryRun=true` 只返回 plan。
|
||||
- [x] `dryRun=false` 生成 `EditorCommand::ReplaceBlock`。
|
||||
- [x] Rust 应用命令生成 canonical content。
|
||||
- [x] 通过 `page.body.save -> documents:updateContent` 持久化。
|
||||
- [x] 返回新 revision、changedBlocks、audit。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -250,12 +315,12 @@ cargo test -p bridge-runtime doc_replace_range_tool_executes_in_rust_runtime
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] tool manifest 增加 `mnote.block.insert_after`。
|
||||
- [ ] 输入必须包含 `anchorBlockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
|
||||
- [ ] 新块 id 由 Rust runtime 分配。
|
||||
- [ ] 支持单块和最多 20 个普通块插入。
|
||||
- [ ] 第一阶段支持 paragraph/heading/todo。
|
||||
- [ ] 返回 inserted block ids 和新 revision。
|
||||
- [x] tool manifest 增加 `mnote.block.insert_after`。
|
||||
- [x] 输入必须包含 `anchorBlockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
|
||||
- [x] 新块 id 由 Rust runtime 分配。
|
||||
- [ ] 支持单块和最多 20 个普通块插入。(当前最小闭环为单块插入。)
|
||||
- [x] 第一阶段支持 paragraph/heading/todo。
|
||||
- [x] 返回 inserted block ids 和新 revision。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -288,12 +353,12 @@ cargo test -p bridge-runtime doc_insert_blocks_tool_emits_editor_commands
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] tool manifest 增加 `mnote.block.move_after`。
|
||||
- [ ] `dryRun=true` 支持同父级叶子块 diff。
|
||||
- [ ] `dryRun=false` 前先继续阻断所有复杂块。
|
||||
- [ ] 检查 `blockRevisionRef` 与 `anchorRevisionRef`。
|
||||
- [ ] 阻断移动到自身、移动到子树、跨页面移动。
|
||||
- [ ] 返回 from/to parent/order。
|
||||
- [x] tool manifest 增加 `mnote.block.move_after`。
|
||||
- [x] `dryRun=true` 支持同父级叶子块 diff。
|
||||
- [ ] `dryRun=false` 前先继续阻断所有复杂块。(已有同父级/叶子/类型/editable/self 阻断,复杂块矩阵 smoke 待补。)
|
||||
- [x] 检查 `blockRevisionRef` 与 `anchorRevisionRef`。
|
||||
- [x] 阻断移动到自身、移动到子树、跨页面移动。
|
||||
- [x] 返回 from/to parent/order。
|
||||
|
||||
验证命令:
|
||||
|
||||
@@ -328,8 +393,9 @@ cargo test -p mnote-editor-core command_executor
|
||||
任务:
|
||||
|
||||
- [ ] Hermes tool event UI 展示 `plan/diff/warnings/risk`。
|
||||
- [ ] `page.save` 标记为粗粒度高风险兜底。
|
||||
- [ ] `block.replace/insert_after/move_after` 展示 changedBlocks。
|
||||
- [x] `page.save` 标记为粗粒度高风险兜底。
|
||||
- [x] `block.replace/insert_after/move_after` 展示 changedBlocks。
|
||||
- [x] manifest annotations 能区分只读 / 粗粒度破坏性写入 / selectionEffect / runtimeOwner / writeOwner;`mnote.page.save` 在 manifest 中为 `destructive=true`、`approvalMode=yolo`,不作为精确块编辑主入口。证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
|
||||
- [ ] 本地 preview/suggestion 与持久 comment/tracked-change 分开。
|
||||
- [ ] 协作可见审阅必须另走正式 comment/history/tracked-change 设计。
|
||||
|
||||
|
||||
@@ -0,0 +1,601 @@
|
||||
# 7-12 [process] 页面 AI Hermes 工具路由与编辑审阅面设计 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`PROCESS`
|
||||
>
|
||||
> 本稿目的:修正“页面 AI 快速块编辑”后续方向,明确 mnote 不再建设独立 AI agent runtime;mnote 只建设 Hermes 可消费的编辑工具路由、工具 manifest、上下文冻结、dry-run/review 和 Rust 写入安全边界。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/10-review/process/09-page-ai-fast-block-edit-runtime-review.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-6-mnote-hermes-plugin-tool-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/blocknote-ai`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-apcore`
|
||||
|
||||
---
|
||||
|
||||
## 1. 本轮结论
|
||||
|
||||
页面 AI 编辑卡顿的根因不是“Rust apply 慢”,而是模型和工具之间缺少稳定、低歧义、可审计的编辑命令面:
|
||||
|
||||
```text
|
||||
用户说一句自然语言
|
||||
-> Hermes/模型需要猜:读哪个范围、改哪个块、调用哪个工具、如何传参
|
||||
-> 如果猜错 blockId 或工具参数,mnote 再 fallback / 重跑 / 整页写入
|
||||
-> 用户感知为慢、卡、偶发失败
|
||||
```
|
||||
|
||||
正确方向不是再造一个 mnote 自有 AI runtime,而是:
|
||||
|
||||
> **Hermes 继续作为唯一页面 AI agent runtime;mnote 提供 Agent-native editor command layer。**
|
||||
|
||||
因此,`本地意图解析 + Rust apply` 必须被重新定义为:
|
||||
|
||||
- Hermes 的工具路由提示层。
|
||||
- 低风险确定性编辑的本地 shortcut。
|
||||
- Rust 写工具的参数校验和执行面。
|
||||
- review/dry-run/session 的安全边界。
|
||||
|
||||
它不是:
|
||||
|
||||
- 第二套对话 runtime。
|
||||
- 第二套 agent tool loop。
|
||||
- 绕过 Hermes profile/tool toggle/audit 的长期写入口。
|
||||
- 让模型直接产 operations 并立刻写入的通用方案。
|
||||
|
||||
---
|
||||
|
||||
## 2. 现有问题
|
||||
|
||||
### 2.1 `/api/page-ai/block-edit-workflow` 方向需要收口
|
||||
|
||||
当前 route 已证明低歧义中文块编辑可以很快完成:
|
||||
|
||||
```text
|
||||
local_rule -> mnote.doc.apply_block_ops -> Rust apply -> page readback
|
||||
```
|
||||
|
||||
但如果把这个 route 继续扩成 `PageAIIntentParser / OperationPlanner / ApplyController`,它会自然变成第二套 runtime:
|
||||
|
||||
- 自己判断意图。
|
||||
- 自己调用模型。
|
||||
- 自己解析模型输出。
|
||||
- 自己决定 fallback。
|
||||
- 自己写入并展示结果。
|
||||
|
||||
这会和 Hermes 的 session、profile、tool toggle、tool event、usage、audit、abort/retry 产生重叠。
|
||||
|
||||
### 2.2 模型直接输出 operations 仍不可靠
|
||||
|
||||
`09-page-ai-fast-block-edit-runtime-review.md` 已记录失败案例:模型输出了 operations,但 block 定位没有命中 Page Aggregate projection,最终触发 fallback 并拉长耗时。
|
||||
|
||||
长期规则应改为:
|
||||
|
||||
- 模型可以建议工具调用。
|
||||
- 模型可以输出候选 operations。
|
||||
- mnote 必须用 Page Aggregate projection 解析、校验、dry-run。
|
||||
- blockId、revisionRef、allowedTargetBlockIds、editable、scope 必须由 mnote 校验。
|
||||
- 未通过校验不能隐式 fallback 到整页写或另一次 agent run。
|
||||
|
||||
### 2.3 当前工具面还缺少 `cli-main` 式 agent 合同
|
||||
|
||||
`cli-main` 的关键价值是把平台能力压成 Agent 可靠调用的命令面:
|
||||
|
||||
- shortcut / API / generic 三层调用。
|
||||
- `--dry-run` 预览真实请求。
|
||||
- `Risk: high-risk-write` 与 `confirmation_required`。
|
||||
- structured error / hint。
|
||||
- skill 文档指导 agent 何时调用什么。
|
||||
- event consume 的 schema、ready marker、bounded run。
|
||||
|
||||
mnote 当前已有 Hermes tool manifest,但还需要把 manifest 提升为 Hermes/model 可直接消费的编辑合同,而不是只做 UI 列表。
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计原则
|
||||
|
||||
### 3.1 单一 agent runtime
|
||||
|
||||
```text
|
||||
Hermes owns:
|
||||
session / message / model / tool loop / streaming / usage / profile / memory / skill
|
||||
|
||||
mnote owns:
|
||||
Page Aggregate / tool manifest / context snapshot / validation / Rust command / audit / readback
|
||||
```
|
||||
|
||||
页面 AI 面板只是 Hermes 的页面内客户端;mnote 不再新增独立 agent 编排中心。
|
||||
|
||||
### 3.2 本地层只做“路由和校验”
|
||||
|
||||
本地层可以做:
|
||||
|
||||
- 判断是不是低歧义块编辑。
|
||||
- 生成 `recommendedToolCall`。
|
||||
- 附带 `confidence`、`risk`、`requiresReview`。
|
||||
- 生成 `allowedTargetBlockIds`。
|
||||
- 做 dry-run、validate、readback。
|
||||
|
||||
本地层不能做:
|
||||
|
||||
- 自己维护长期对话状态。
|
||||
- 自己成为默认模型调用链。
|
||||
- 自己绕过 Hermes tool manifest 和 profile 开关。
|
||||
- 自己吞掉工具错误并隐式改走其他写入口。
|
||||
|
||||
### 3.3 所有写入都通过 Rust-owned mnote tools
|
||||
|
||||
写工具必须满足:
|
||||
|
||||
- `dryRun` 显式传入。
|
||||
- `idempotencyKey` 显式传入。
|
||||
- `revision/conflictDetectionKey/revisionRef` 或等价冲突键参与校验。
|
||||
- `allowedTargetBlockIds` 限制 selection / scoped run。
|
||||
- 返回 `diff/warnings/risk/blocked/changedBlocks/audit`。
|
||||
- 写入后通过 Page Aggregate 和 `mnote.doc.fetch` 回读验证。
|
||||
|
||||
### 3.4 快路径是 shortcut,不是 runtime
|
||||
|
||||
低歧义场景可以保留快路径,但必须改口径:
|
||||
|
||||
```text
|
||||
PageAICommandRouter
|
||||
-> recommendedToolCall
|
||||
-> direct tool shortcut 或 Hermes run with tool hint
|
||||
-> shared mnote tool executor
|
||||
-> shared audit/readback
|
||||
```
|
||||
|
||||
如果走 direct tool shortcut,也必须产生 Hermes-compatible tool event / audit 语义,避免 UI 与历史记录断裂。
|
||||
|
||||
---
|
||||
|
||||
## 4. 总体架构
|
||||
|
||||
```text
|
||||
Browser Page AI panel
|
||||
-> PageAIContextBuilder
|
||||
-> MnoteAIToolManifestProvider
|
||||
-> PageAICommandRouter
|
||||
-> deterministic shortcut? ---- yes -> MnoteToolExecutor
|
||||
| -> PageAIReviewSession/readback
|
||||
no
|
||||
-> Hermes run request with:
|
||||
- frozen page context
|
||||
- tool manifest
|
||||
- recommendedToolCall hint
|
||||
- risk/review policy
|
||||
-> Hermes tool loop
|
||||
-> /api/hermes/tools/mnote/call
|
||||
-> Rust mnote tools
|
||||
-> PageAIReviewSession/readback
|
||||
```
|
||||
|
||||
这里 `PageAICommandRouter` 不是 agent,只是类似 `cli-main` shortcut 的工具路由器。
|
||||
|
||||
---
|
||||
|
||||
## 5. 组件设计
|
||||
|
||||
### 5.1 `PageAIContextBuilder`
|
||||
|
||||
职责:
|
||||
|
||||
- 从 Page Aggregate block projection 构建冻结上下文。
|
||||
- 支持 `scope=full/outline/block/selection/keyword`。
|
||||
- 输出 `text/page_xml/json` 三种视图。
|
||||
- 生成 `allowedTargetBlockIds`。
|
||||
- 记录 `revision/conflictDetectionKey/revisionRef`。
|
||||
- 大页面默认裁剪,返回 `truncated/warnings/continuation`。
|
||||
|
||||
输出示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "mnote.page_ai_context.v1",
|
||||
"workspaceId": "tree_workspace",
|
||||
"documentId": "tree_doc",
|
||||
"scope": "selection",
|
||||
"revision": 12,
|
||||
"conflictDetectionKey": "body:12:hash",
|
||||
"allowedTargetBlockIds": ["p_1", "p_2"],
|
||||
"selectedBlockIds": ["p_1", "p_2"],
|
||||
"pageText": "第一段\n第二段",
|
||||
"pageXml": "<page id=\"tree_doc\" revision=\"12\"><block id=\"p_1\">第一段</block></page>",
|
||||
"blocks": [
|
||||
{
|
||||
"blockId": "p_1",
|
||||
"type": "paragraph",
|
||||
"text": "第一段",
|
||||
"revisionRef": "body:12:p_1",
|
||||
"editable": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 `MnoteAIToolManifestProvider`
|
||||
|
||||
职责:
|
||||
|
||||
- 从 Rust Hermes tool manifest 输出当前页面可用工具。
|
||||
- 合并 profile tool toggle、capability、scope、document permissions。
|
||||
- 输出 Hermes/model 可直接使用的 tool schema。
|
||||
- 输出风险和审批语义。
|
||||
|
||||
工具 manifest 必须包含:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "mnote.doc.apply_block_ops",
|
||||
"description": "Apply validated block operations to the current mnote document.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["operations", "dryRun", "idempotencyKey"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"annotations": {
|
||||
"readonly": false,
|
||||
"destructive": false,
|
||||
"idempotent": false,
|
||||
"requiresApproval": true,
|
||||
"approvalMode": "review",
|
||||
"selectionEffect": "destroy",
|
||||
"runtimeOwner": "mnote-web",
|
||||
"writeOwner": "rust-runtime-kernel"
|
||||
},
|
||||
"availability": {
|
||||
"enabled": true,
|
||||
"unsupportedReason": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 `PageAICommandRouter`
|
||||
|
||||
替代当前继续扩大的 `block-edit-workflow` 概念。
|
||||
|
||||
输入:
|
||||
|
||||
- 用户 prompt。
|
||||
- 冻结后的 `mnote.page_ai_context.v1`。
|
||||
- 当前 tool manifest。
|
||||
- 当前 profile / approval mode。
|
||||
|
||||
输出:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "mnote.page_ai_command_route.v1",
|
||||
"intent": "direct_block_edit",
|
||||
"confidence": 0.94,
|
||||
"recommendedToolCall": {
|
||||
"toolName": "mnote.doc.apply_block_ops",
|
||||
"args": {
|
||||
"operations": [
|
||||
{"op": "replace", "matchText": "A", "content": "B"}
|
||||
],
|
||||
"dryRun": true
|
||||
}
|
||||
},
|
||||
"risk": "low",
|
||||
"requiresHermesRun": false,
|
||||
"requiresReview": false,
|
||||
"reason": "明确中文引号替换表达,目标文本唯一命中"
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 只覆盖低歧义命令。
|
||||
- 不能为复杂改写、总结、跨页面、多块结构化编辑直接生成写入。
|
||||
- 不能调用第二套长链模型;如需模型,交给 Hermes run。
|
||||
- 输出必须可被 Hermes 当作 tool hint 消费。
|
||||
|
||||
### 5.4 Hermes run hint 注入
|
||||
|
||||
当 `requiresHermesRun=true` 或 router 不确定时,页面 AI 发起 Hermes run,并附带:
|
||||
|
||||
```json
|
||||
{
|
||||
"pageContext": "mnote.page_ai_context.v1",
|
||||
"toolManifest": "mnote.ai_tool_manifest.v1",
|
||||
"toolHint": "mnote.page_ai_command_route.v1",
|
||||
"reviewPolicy": {
|
||||
"mode": "yolo|review|required",
|
||||
"defaultDryRun": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Hermes 仍负责:
|
||||
|
||||
- 选择模型。
|
||||
- 工具调用循环。
|
||||
- stream message / tool event。
|
||||
- abort/retry。
|
||||
- session persistence。
|
||||
|
||||
mnote 只负责工具结果和写入安全。
|
||||
|
||||
### 5.5 `PageAIReviewSession`
|
||||
|
||||
职责:
|
||||
|
||||
- 承接所有写工具 `dryRun=true` 或 `requiresApproval=true` 的结果。
|
||||
- 保存 plan/diff/warnings/risk/blocked。
|
||||
- 提供 accept/reject/retry/abort。
|
||||
- accept 时二次读取 Page Aggregate 并校验 revision。
|
||||
|
||||
状态:
|
||||
|
||||
```text
|
||||
draft
|
||||
planning
|
||||
previewing
|
||||
awaiting_user
|
||||
accepted
|
||||
rejected
|
||||
applying
|
||||
applied
|
||||
failed
|
||||
aborted
|
||||
stale
|
||||
```
|
||||
|
||||
第一阶段可以保留 yolo,但仍应让工具返回 review-compatible 数据结构,避免后续 UI 重写。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关键流程
|
||||
|
||||
### 6.1 低歧义块替换
|
||||
|
||||
```text
|
||||
用户:把「第二段」替换为「第二段已修改」
|
||||
-> ContextBuilder 冻结页面与 block ids
|
||||
-> CommandRouter 命中 direct_block_edit
|
||||
-> recommendedToolCall=mnote.doc.apply_block_ops
|
||||
-> dryRun validate 唯一命中
|
||||
-> yolo 模式:direct tool shortcut 正式 apply
|
||||
-> 记录 tool event/audit
|
||||
-> Page Aggregate readback
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- 不进入通用 Hermes agent run 也可以,但必须复用 mnote tool/audit/readback 语义。
|
||||
- 若非 yolo 模式,则停在 review session。
|
||||
|
||||
### 6.2 复杂自然语言改写
|
||||
|
||||
```text
|
||||
用户:把这段整理得更专业,并保留原意
|
||||
-> Router 无法确定操作
|
||||
-> Hermes run with context + manifest + hint
|
||||
-> Hermes 调 mnote.doc.fetch / block.fetch
|
||||
-> Hermes 调 mnote.doc.plan_update(dryRun=true)
|
||||
-> mnote 返回 review session draft
|
||||
-> 用户 accept 后 Rust apply
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- 模型不能直接改正文。
|
||||
- dry-run 不改变 Page Aggregate。
|
||||
- accept 时校验 revision。
|
||||
|
||||
### 6.3 selection 编辑
|
||||
|
||||
```text
|
||||
用户选中块 A/B:改成列表
|
||||
-> ContextBuilder 冻结 selectedBlockIds
|
||||
-> allowedTargetBlockIds=[A,B]
|
||||
-> 所有写工具自动带 allowedTargetBlockIds
|
||||
-> 写工具尝试修改 C 时 blocked=true
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- 用户后续改变选区不影响当前 run。
|
||||
- selection 外写入被阻断。
|
||||
|
||||
### 6.4 工具禁用
|
||||
|
||||
```text
|
||||
profile disabled mnote.block.fetch
|
||||
-> ToolManifestProvider 输出 enabled=false 或不输出该工具
|
||||
-> Router 不推荐该工具
|
||||
-> Hermes 直接调用仍被 /api/hermes/tools/mnote/call 拦截
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- UI 工具列表、Hermes manifest、后端执行拦截一致。
|
||||
|
||||
---
|
||||
|
||||
## 7. 与参考代码的吸收边界
|
||||
|
||||
### 7.1 `cli-main`
|
||||
|
||||
吸收:
|
||||
|
||||
- shortcut/API/generic 三层工具面。
|
||||
- dry-run 作为写入前置能力。
|
||||
- structured error/hint。
|
||||
- risk/confirmation_required。
|
||||
- skill 文档让 agent 不靠猜。
|
||||
- event/schema/ready marker 的 agent-friendly contract。
|
||||
|
||||
不吸收:
|
||||
|
||||
- 不复制 Go CLI 框架。
|
||||
- 不把 CLI 作为页面 AI 唯一执行面。
|
||||
- 不用命令行 prompt 作为 Web 审批 UI。
|
||||
|
||||
### 7.2 `blocknote-ai`
|
||||
|
||||
吸收:
|
||||
|
||||
- `DocumentStateBuilder` 的 selection/full context 分离。
|
||||
- `StreamToolsProvider` 的工具集合思想。
|
||||
- AI lifecycle:thinking / ai-writing / user-reviewing / error。
|
||||
- accept/reject/retry/abort 的交互形态。
|
||||
|
||||
不吸收:
|
||||
|
||||
- 不引入 `@blocknote/xl-ai` 运行时依赖。
|
||||
- 不复制 GPL/PROPRIETARY 代码。
|
||||
- 不让 BlockNote/ProseMirror suggestion 成为 mnote 事实源。
|
||||
|
||||
### 7.3 `tiptap-apcore`
|
||||
|
||||
吸收:
|
||||
|
||||
- tool schema。
|
||||
- annotations。
|
||||
- ACL / role。
|
||||
- query/content/destructive/selection/history 分类。
|
||||
- executor 前置检查。
|
||||
|
||||
不吸收:
|
||||
|
||||
- 不把 Tiptap command 作为长期写入事实源。
|
||||
- 不让浏览器 editor instance 直接持久化写入。
|
||||
|
||||
### 7.4 AI SDK / Context7 核验结论
|
||||
|
||||
可用方向:
|
||||
|
||||
- 用 schema/structured output 约束模型输出。
|
||||
- 用 tool calling 让模型选择工具。
|
||||
- 用 repair/validation 处理无效参数。
|
||||
- 工具执行结果必须由 mnote 校验后返回。
|
||||
|
||||
不可用方向:
|
||||
|
||||
- 不把 structured output 当最终写入结果。
|
||||
- 不让模型输出的 blockId 绕过 projection resolve。
|
||||
|
||||
---
|
||||
|
||||
## 8. 迁移计划
|
||||
|
||||
### Phase A:设计治理
|
||||
|
||||
- [x] 新增本文作为当前口径。
|
||||
- [x] `7-10` 继续作为执行 checklist。
|
||||
- [x] `7-11` 作为旧“自有 AI runtime”口径移入 `design/old/07-ai/process/`。
|
||||
|
||||
### Phase B:Manifest 合同收口
|
||||
|
||||
- [ ] `mnote.doc.*` / `mnote.block.*` manifest 输出完整 `inputSchema/outputSchema/annotations/availability`。
|
||||
- [ ] profile toggle、capability、scope 共同影响 manifest。
|
||||
- [ ] manifest 可直接转换为 Hermes/model tools。
|
||||
- [ ] 禁用工具在 manifest、UI、执行拦截三处一致。
|
||||
|
||||
### Phase C:`block-edit-workflow` 改造成 router
|
||||
|
||||
- [ ] 将 route 命名和返回 schema 改为 `mnote.page_ai_command_route.v1` 或新增等价 route。
|
||||
- [ ] 本地规则只输出 `recommendedToolCall`。
|
||||
- [ ] 低风险 yolo shortcut 走共享 mnote tool executor。
|
||||
- [ ] 非低风险或低置信度任务发起 Hermes run with tool hint。
|
||||
- [ ] 删除“模型 fallback 后再 Hermes agent run”的重复链路。
|
||||
|
||||
### Phase D:Review session
|
||||
|
||||
- [ ] 定义 `mnote.page_ai_review_session.v1`。
|
||||
- [ ] `mnote.doc.plan_update` 与 `mnote.doc.apply_block_ops dryRun=true` 返回 review-compatible draft。
|
||||
- [ ] 页面 AI UI 展示 diff/warnings/risk/blocked。
|
||||
- [ ] accept/reject/retry/abort 可用。
|
||||
- [ ] stale revision 被阻断。
|
||||
|
||||
### Phase E:状态与事件统一
|
||||
|
||||
- [ ] direct shortcut 和 Hermes run 都产生统一 tool event 形态。
|
||||
- [ ] 页面 AI 面板按 `runId/toolCallId/reviewSessionId` 聚合展示。
|
||||
- [ ] abort 不留下半写入正文。
|
||||
- [ ] 刷新后未提交 review session 不自动写入。
|
||||
|
||||
### Phase F:验收 smoke
|
||||
|
||||
- [ ] 低歧义替换:可 <1s 可见,且有 tool audit。
|
||||
- [ ] 复杂改写:进入 Hermes run,先 dry-run/review。
|
||||
- [x] selection 外写入:blocked。
|
||||
- [ ] 禁用工具:manifest 不推荐,后端仍拦截。
|
||||
- [ ] 旧 revision accept:stale。
|
||||
|
||||
2026-05-16 补充验收证据:
|
||||
|
||||
- `scripts/task-page-block-ai-context-format-smoke.js` 已验证 `mnote.doc.apply_block_ops dryRun=true` 携带 `allowedTargetBlockIds=["p_2"]` 时,尝试 replace `p_1` 会被 Rust mnote tool 拒绝。
|
||||
- 证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`;错误路径为 HTTP `400`、`mnote_block_target_out_of_scope`。
|
||||
- 同一 smoke 还验证了 context / manifest 基础合同:`mnote.doc.fetch scope=selection format=page_xml/text`、`mnote.block.fetch format=page_xml/text`、manifest annotations 与 `mnote.page.save` 粗粒度兜底定位。
|
||||
- 边界:本证据不代表完整 review session、旧 revision accept、复杂改写或单个 `mnote.block.*` selection guard 已完成。
|
||||
|
||||
---
|
||||
|
||||
## 9. `7-10` 与 `7-11` 的处理结论
|
||||
|
||||
### 9.1 `7-10` 继续执行
|
||||
|
||||
`7-10` 是页面块 AI 工具执行 checklist,包含真实代码和 smoke 证据。它仍然有效,继续保留在:
|
||||
|
||||
```text
|
||||
design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md
|
||||
```
|
||||
|
||||
但后续执行必须按本文修正口径:
|
||||
|
||||
- `PageAIIntentParser` 读作 `PageAICommandRouter`。
|
||||
- `PageAIOperationPlanner` 读作 `recommendedToolCall` 构造器。
|
||||
- `PageAIOperationValidator` 继续有效,但归属 mnote tool executor / Rust validation。
|
||||
- `PageAIApplyController` 不应成为独立 runtime,改为 review session / tool executor / readback controller。
|
||||
- “不进入 Hermes run”只能表示 deterministic shortcut,不表示 mnote 新建了 agent runtime。
|
||||
|
||||
### 9.2 `7-11` 移入 old
|
||||
|
||||
`7-11` 的参考资料价值仍然成立,但标题和核心分层写成了“mnote 自有 AI 工具 runtime”。这会误导后续实现继续扩出第二套 runtime。
|
||||
|
||||
因此本轮将其移入:
|
||||
|
||||
```text
|
||||
design/old/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md
|
||||
```
|
||||
|
||||
保留原因:
|
||||
|
||||
- 记录 BlockNote / Tiptap 参考取证。
|
||||
- 保留 GPL/PROPRIETARY 许可证边界。
|
||||
- 保留 selection/context/review 的参考价值。
|
||||
|
||||
不再作为当前执行口径;当前执行口径以本文为准。
|
||||
|
||||
---
|
||||
|
||||
## 10. 禁止项
|
||||
|
||||
- 不新增 mnote 自有 agent runtime。
|
||||
- 不把 `/api/page-ai/block-edit-workflow` 扩成通用 AI 编排中心。
|
||||
- 不让模型直接输出未经校验的 blockId 并写入。
|
||||
- 不绕过 Hermes profile/tool toggle/audit。
|
||||
- 不让前端 editor instance 直接执行正式持久化写入。
|
||||
- 不以 HTML / Tiptap JSON / ProseMirror position 作为长期 AI tool contract。
|
||||
- 不复制 BlockNote XL AI 或 GPL/PROPRIETARY 实现代码。
|
||||
- 不把 `mnote.page.save` 描述为精确块编辑主入口。
|
||||
|
||||
---
|
||||
|
||||
## 11. 成功标准
|
||||
|
||||
完成本文后,页面 AI 编辑应满足:
|
||||
|
||||
- 简单明确块编辑有低延迟 shortcut。
|
||||
- 复杂编辑仍走 Hermes agent runtime。
|
||||
- Hermes 不再盲猜工具和参数,而是拿到 mnote 提供的 context、manifest、tool hint。
|
||||
- 所有写入都能 dry-run、review、audit、readback。
|
||||
- 工具禁用、权限、scope、selection 与后端执行一致。
|
||||
- 设计文档不再鼓励建设第二套 AI runtime。
|
||||
@@ -0,0 +1,469 @@
|
||||
# 7-13 [process] 页面块编辑运行时 Actor 设计 v1
|
||||
|
||||
> 更新时间:2026-05-22
|
||||
>
|
||||
> 当前状态:`PROCESS`
|
||||
>
|
||||
> 本稿目的:在 7-12 已排除第二套 AI runtime 的前提下,补上 Hermes tool execution → Convex 持久化之间缺失的 Rust 编辑运行时中继层,实现「内存态 apply → 编辑器就地 patch → Convex 异步持久化 → 事件增量通知」的四步闭环。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/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/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-13-page-block-identity-and-command-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 结论
|
||||
|
||||
当前 Hermes 块工具读→plan→dry-run→apply→readback 循环中,每次写操作都经历 `EditorCommand → legacy content → Convex documents:updateContent → page.body.saved` 全链路,导致:
|
||||
|
||||
- 一次 AI 编辑循环需 2-3 次 Convex RTT
|
||||
- 编辑器只能全量 reload snapshot,不能就地 patch
|
||||
- tree event stream 收到 `resync_required` 而非增量 delta
|
||||
|
||||
**正确方向不是绕开 Convex(禁止项,Convex 保留为自托管存储底座),而是在 Rust mnote-web 进程中新增一个轻量 EditorRuntimeActor,作为写操作的本地缓冲层。**
|
||||
|
||||
EditorRuntimeActor 不是 agent runtime(遵从 7-12 禁止项),它只负责:
|
||||
|
||||
- 持有文档的 `EditorBlockDocument` 内存态
|
||||
- 接收 `EditorCommand` → 就地 apply → 产生 diff
|
||||
- 将 diff 拆为三路输出:Convex 持久化 / 编辑器增量 patch / tree event stream delta
|
||||
- 返回 Hermes tool 所需的 `changedBlocks / newRevision`
|
||||
|
||||
---
|
||||
|
||||
## 2. 现有问题
|
||||
|
||||
### 2.1 写路径绕路 Convex
|
||||
|
||||
当前写路径:
|
||||
|
||||
```
|
||||
mnote.block.replace / insert_after / move_after / delete
|
||||
→ ensure_write_contract
|
||||
→ apply_editor_command_to_legacy_content ← EditorCommand → legacy content array
|
||||
→ execute_page_body_save
|
||||
→ RuntimeCommandEnvelopeWire("page.body.save")
|
||||
→ bridge-runtime: EditorBlockDocument → legacy content → Convex documents:updateContent
|
||||
→ domain event: page.body.saved
|
||||
→ tree stream: resync_required
|
||||
→ 前端收到 resync → 重新 fetch Page Aggregate → 编辑器 reload
|
||||
```
|
||||
|
||||
这条路径每次写都走完整 Convex 事务。在 AI 的典型循环中(读 1 次 + plan_update 1 次 + write 1-3 次 + readback 1 次),这意味着 4-6 次 Convex RTT,其中大部分是可以省略的。
|
||||
|
||||
### 2.2 编辑器收不到增量
|
||||
|
||||
当前 `page.body.saved` → `resync_required` 是全量 reload。编辑器不会收到「块 p_2 的文本从 X 变为 Y」这样的增量信号,只能重新请求整页 snapshot。
|
||||
|
||||
### 2.3 每次 apply 都走 JSON 序列化桥
|
||||
|
||||
`apply_editor_command_to_legacy_content` 的输入是 `Value`(legacy content array),输出也是 `Value`。中间经历了 `editor_document_from_legacy_content → apply → legacy_content_from_editor_document` 的序列化桥。如果 EditorBlockDocument 常驻内存,可以省去两端序列化。
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计原则
|
||||
|
||||
### 3.1 不是 agent runtime
|
||||
|
||||
EditorRuntimeActor 不维护:
|
||||
|
||||
- session / message / model loop
|
||||
- 意图解析 / planner / fallback 链
|
||||
- 长期对话状态
|
||||
- 独立的工具调用循环
|
||||
|
||||
它只是 Rust-owned 的命令执行 + diff 分发层。
|
||||
|
||||
### 3.2 Convex 仍是唯一的持久化底座
|
||||
|
||||
EditorRuntimeActor 的内存态允许异步写入 Convex,但不绕过 Convex。进程重启后从 Convex 恢复。
|
||||
|
||||
### 3.3 编辑器 patch 是增量,非全量
|
||||
|
||||
Rust → Tiptap 的 delta channel 只传 surgical op(replace/insert/delete/move),不传整份 `EditorBlockDocument`。
|
||||
|
||||
### 3.4 事件 stream 从 resync 进化为 delta
|
||||
|
||||
`block.delta` 成为 tree event stream 的一等事件,前端 tree stream consumer 可选择增量消费。
|
||||
|
||||
---
|
||||
|
||||
## 4. 总体架构
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ Hermes Agent │
|
||||
│ (tool call loop) │
|
||||
└──────────┬───────────┘
|
||||
│ POST /api/hermes/tools/mnote/call
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ mnote-web Hermes Tools (block.rs) │
|
||||
│ - ensure_write_contract │
|
||||
│ - build_editor_block / content_nodes│
|
||||
│ - dry_run / idempotency / revision │
|
||||
└──────────┬──────────────────────────┘
|
||||
│ EditorCommand
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ EditorRuntimeActor │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ per-document EditorBlockDocument cache │ │
|
||||
│ │ apply command → update in-memory → produce diff │ │
|
||||
│ │ diff → 3-way output: │ │
|
||||
│ └──────┬──────────────┬──────────────────┬───────────────┘ │
|
||||
│ │ │ │ │
|
||||
└─────────┼──────────────┼──────────────────┼────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Convex leptos-tiptap /api/tree/events
|
||||
(async save) island (delta stream)
|
||||
(page.body.save) (receive_command) (block.delta)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 组件设计
|
||||
|
||||
### 5.1 `EditorRuntimeActor`
|
||||
|
||||
```rust
|
||||
pub struct EditorRuntimeActor {
|
||||
// per-document 缓存
|
||||
documents: RwLock<HashMap<DocumentId, EditorDocumentState>>,
|
||||
// 未完成的 Convex 写入队列
|
||||
pending_saves: SaveQueue,
|
||||
}
|
||||
```
|
||||
|
||||
`EditorDocumentState`:
|
||||
|
||||
```rust
|
||||
pub struct EditorDocumentState {
|
||||
pub document_id: DocumentId,
|
||||
pub workspace_id: Option<String>,
|
||||
pub document: EditorBlockDocument,
|
||||
pub revision: u64,
|
||||
pub conflict_detection_key: String,
|
||||
pub page_title: String,
|
||||
pub last_applied_at: Instant,
|
||||
pub pending_convex_save: Option<PendingSave>,
|
||||
}
|
||||
```
|
||||
|
||||
接口:
|
||||
|
||||
```rust
|
||||
impl EditorRuntimeActor {
|
||||
/// 读取或初始化文档的内存态
|
||||
pub async fn load_or_init(
|
||||
&self,
|
||||
state: &AppState,
|
||||
document_id: &str,
|
||||
) -> Result<EditorDocumentGuard<'_>>;
|
||||
|
||||
/// 应用 EditorCommand,返回 diff
|
||||
pub async fn apply_command(
|
||||
&self,
|
||||
document_id: &str,
|
||||
command: EditorCommand,
|
||||
context: &RequestContext,
|
||||
) -> Result<ApplyResult>;
|
||||
|
||||
/// 触发异步 Convex 保存(不在工具返回路径上等)
|
||||
pub fn schedule_save(
|
||||
&self,
|
||||
document_id: &str,
|
||||
save_token: SaveToken,
|
||||
);
|
||||
|
||||
/// 从 Convex 恢复文档到内存
|
||||
pub async fn reload_from_convex(
|
||||
&self,
|
||||
state: &AppState,
|
||||
document_id: &str,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 `ApplyResult`
|
||||
|
||||
```rust
|
||||
pub struct ApplyResult {
|
||||
pub new_revision: u64,
|
||||
pub changed_blocks: Vec<ChangedBlock>,
|
||||
pub diff: BlockDelta,
|
||||
pub warnings: Vec<String>,
|
||||
pub blocked: bool,
|
||||
}
|
||||
|
||||
pub struct ChangedBlock {
|
||||
pub block_id: String,
|
||||
pub op: &'static str, // "replace" | "insert" | "delete" | "move"
|
||||
pub before: Option<String>, // 文本预览(dry-run 展示用)
|
||||
pub after: Option<String>,
|
||||
}
|
||||
|
||||
/// 增量 diff,用于推送编辑器 + event stream
|
||||
pub struct BlockDelta {
|
||||
pub document_id: String,
|
||||
pub revision: u64,
|
||||
pub operations: Vec<DeltaOperation>,
|
||||
}
|
||||
|
||||
pub enum DeltaOperation {
|
||||
ReplaceBlock {
|
||||
block_id: String,
|
||||
content: EditorBlock,
|
||||
},
|
||||
InsertBlockAfter {
|
||||
anchor_block_id: String,
|
||||
block: EditorBlock,
|
||||
},
|
||||
DeleteBlock {
|
||||
block_id: String,
|
||||
},
|
||||
MoveBlock {
|
||||
block_id: String,
|
||||
new_parent_block_id: Option<String>,
|
||||
new_order: String,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 `SaveQueue`
|
||||
|
||||
Convex 写入不阻塞工具返回。`SaveQueue` 负责:
|
||||
|
||||
- 收集 50ms 窗口内的连续改动(同一文档去重)
|
||||
- 合并为一次 `page.body.save` command
|
||||
- 带 `revision` 乐观锁;失败时触发 reload 补偿
|
||||
- 记录上一次成功 save 的 `conflictDetectionKey`
|
||||
|
||||
### 5.4 `EditorDeltaChannel`(Phase B)
|
||||
|
||||
Rust → leptos-tiptap 的增量通道:
|
||||
|
||||
```rust
|
||||
pub struct EditorDeltaChannel {
|
||||
// per-document sender (wasm-bound callback or WebSocket)
|
||||
senders: RwLock<HashMap<DocumentId, DeltaSender>>,
|
||||
}
|
||||
|
||||
pub enum DeltaSender {
|
||||
/// 同进程 wasm bridge(current spike pattern)
|
||||
WasmBridge(Box<dyn Fn(BlockDelta) + Send>),
|
||||
/// WebSocket 直连(未来备选)
|
||||
WebSocket(String),
|
||||
}
|
||||
```
|
||||
|
||||
在 leptos-tiptap island 侧:
|
||||
|
||||
```js
|
||||
// 新增入口
|
||||
window.__mnote_editor_receive_delta = function(delta) {
|
||||
// delta.operations.forEach(op => {
|
||||
// editor.chain().findBlockById(op.block_id).replaceWith(op.content).run()
|
||||
// })
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 划分
|
||||
|
||||
### Phase A:EditorRuntimeActor 内存缓存层
|
||||
|
||||
目标:消除每次工具调用都走 Convex 的读→写回环。
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] 实现 `EditorRuntimeActor` 结构体,持有 `HashMap<DocumentId, EditorDocumentState>`
|
||||
- [ ] 实现 `load_or_init`:首次读取从 Convex Page Aggregate 构建 `EditorBlockDocument` 内存态
|
||||
- [ ] 实现 `apply_command`:直接在 `EditorBlockDocument.blocks` 上执行 apply,产生 `ApplyResult`
|
||||
- [ ] 实现 `schedule_save`:异步 `page.body.save` 到 Convex,带 revision 乐观锁
|
||||
- [ ] 改造 `block.rs` 中 `execute_page_body_save`:优先走 EditorRuntimeActor::apply_command,再 schedule_save
|
||||
- [ ] 工具返回不再等待 Convex 完成,带上 `newRevision + changedBlocks` 立即返回
|
||||
- [ ] 写 `task-editor-runtime-actor-smoke.js`:验证三次写入循环的 latency < 500ms(不含 Convex 持久化)
|
||||
|
||||
依赖:
|
||||
|
||||
- `EditorRuntimeActor` 可独立启用/禁用(feature flag),启用时不影响已有写路径
|
||||
- Phase A 不改编辑器前端,不改 tree event stream
|
||||
|
||||
### Phase B:编辑器增量 delta channel
|
||||
|
||||
目标:AI 写入后 leptos-tiptap 编辑器就地 patch,不触发全量 reload。
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] 定义 Rust → Editor 的 delta 序列化协议(基于 `BlockDelta` 序列化为 JSON)
|
||||
- [ ] 在 leptos-tiptap spike 的 wasm 侧新增 `receive_delta(delta_json: &str)` 函数
|
||||
- [ ] 新增 JS 入口 `window.__mnote_editor_receive_delta`,解析后通过 Tiptap chain API 执行
|
||||
- [ ] EditorRuntimeActor 在 apply_command 后通过 `EditorDeltaChannel` 推送 delta
|
||||
- [ ] 处理冲突:如果编辑器本地 state 比内存缓存更新,跳过该条 delta(等下次全量 sync)
|
||||
- [ ] 写 `task-editor-delta-channel-smoke.js`:验证 AI write → 编辑器镜像变化不需 reload
|
||||
|
||||
依赖:
|
||||
|
||||
- Phase A 已完成
|
||||
- leptos-tiptap 的 `editor` 引用可从 wasm 侧稳定访问
|
||||
- delta channel 只在 `leptos-tiptap` 作为主编辑器的文档页启用
|
||||
|
||||
### Phase C:事件 stream delta
|
||||
|
||||
目标:`block.delta` 成为 tree event stream 的一等事件,前端不再依赖 `resync_required`。
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] 新增 event type `block.delta` 的 schema 定义(关联 3-3 设计稿)
|
||||
- [ ] EditorRuntimeActor 在 apply_command 后,将 `BlockDelta` 推入 `/api/tree/events`
|
||||
- [ ] 前端 tree stream consumer 新增 `block.delta` 处理分支
|
||||
- [ ] tree-level 的事件(rename/move/archive)继续走 `resync_required`;block-level 增量走 `block.delta`
|
||||
- [ ] 写 `task-block-delta-smoke.js`:验证第二客户端收到 block.delta 后页面内容更新
|
||||
|
||||
依赖:
|
||||
|
||||
- Phase A 已完成
|
||||
- `/api/tree/events` 已有 snapshot/delta/resync 机制(参考 3-3)
|
||||
|
||||
---
|
||||
|
||||
## 7. 关键流程
|
||||
|
||||
### 7.1 AI 块替换(Phase A + B)
|
||||
|
||||
```text
|
||||
用户/Agent: 把「第二段」替换为「第二段已修改」
|
||||
→ Hermes 调 mnote.block.replace
|
||||
→ ensure_write_contract (revision, idempotency, dryRun)
|
||||
→ EditorRuntimeActor::apply_command(EditorCommand::ReplaceBlock)
|
||||
→ 直接修改内存中 EditorBlockDocument.blocks["p_2"]
|
||||
→ 产生 ApplyResult { newRevision: 14, changedBlocks: [...], delta: BlockDelta }
|
||||
→ dryRun? 返回 preview (不同,跳过写入)
|
||||
→ schedule_save (返回后异步执行)
|
||||
→ EditorDeltaChannel::push(delta) → leptos-tiptap 就地修改
|
||||
→ 返回 { ok, changedBlocks, newRevision }
|
||||
```
|
||||
|
||||
延迟特征:
|
||||
- Hermes tool 返回:~5ms(内存操作,无 Convex RTT)
|
||||
- Convex 持久化:~50-200ms(后台异步,不阻塞 agent loop)
|
||||
- 编辑器更新:~5ms(wasm bridge 直接调用 Tiptap chain)
|
||||
|
||||
### 7.2 复杂改写(Hermes agent 场景)
|
||||
|
||||
```text
|
||||
用户: 把这段改得更专业
|
||||
→ Hermes agent: mnote.doc.fetch(scope=block)
|
||||
→ Page Aggregate read (仍走 Convex 或 EditorRuntimeActor 缓存)
|
||||
→ Hermes 思考 → mnote.doc.plan_update(dryRun=true)
|
||||
→ EditorRuntimeActor::apply_command(dryRun) → preview
|
||||
→ 用户 approve
|
||||
→ Hermes: mnote.block.replace (dryRun=false)
|
||||
→ 同 7.1 流程
|
||||
```
|
||||
|
||||
### 7.3 进程重启恢复
|
||||
|
||||
```text
|
||||
mnote-web 重启
|
||||
→ 第一次收到某文档的 tool call
|
||||
→ EditorRuntimeActor::load_or_init
|
||||
→ 从 Convex Page Aggregate 读取
|
||||
→ 构建 EditorBlockDocument 内存态
|
||||
→ 设置 revision = 读取值
|
||||
→ 正常处理后续 commands
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 与现有文档的边界
|
||||
|
||||
| 现有设计 | 与本稿关系 |
|
||||
| --- | --- |
|
||||
| 7-12 禁止「第二套 AI agent runtime」 | 严格遵从。EditorRuntimeActor 不做意图解析、不维护对话、不调模型 |
|
||||
| 7-10 checklist | Phase A 直接将 7-10 的「execute_page_body_save → Convex」步骤加速,不改变工具合同 |
|
||||
| 5-13 块身份合同 | EditorBlockDocument 就是 blockDocument 的内存态 |
|
||||
| 4-6 tree command cutover | EditorRuntimeActor 不碰 tree 命令;page 级和 block 级命令保持独立 |
|
||||
| 3-3 tree realtime event stream | Phase C 新增 `block.delta` event,扩展而非替代 resync_required |
|
||||
|
||||
---
|
||||
|
||||
## 9. 禁止项
|
||||
|
||||
- 不绕过 Convex 持久化。EditorRuntimeActor 是缓存层,不是存储层。
|
||||
- 不在 EditorRuntimeActor 内维护 agent session、message history、model 调用。
|
||||
- 不在 EditorRuntimeActor 内做意图解析、planner、fallback 判断。
|
||||
- 不要求编辑器同步等待 Convex 写入完成才展示 AI 编辑结果。
|
||||
- 不改变已有的 `ensure_write_contract` 校验链。
|
||||
- 不新增写工具;Phase A/B/C 只加速已有工具的落地速度。
|
||||
- delta channel 不改写 Tiptap 的协作/undo/redo 栈;仅新增 AI 编辑的增量入口。
|
||||
|
||||
---
|
||||
|
||||
## 10. 成功标准
|
||||
|
||||
Phase A 完成后:
|
||||
|
||||
- [ ] Hermes block 工具(replace/insert_after/move_after/delete)返回时间不依赖 Convex RTT
|
||||
- [ ] 三次写循环(replace → insert → readback)总 agent 延迟 < 800ms(含 dry-run)
|
||||
- [ ] Convex `documents:updateContent` 调用次数不变(1 次/写,异步)
|
||||
- [ ] 所有现有 smoke 用例在 feature flag 开启/关闭下均通过
|
||||
|
||||
Phase B 完成后:
|
||||
|
||||
- [ ] AI 写入后,编辑器中对应块的文本/类型 3ms 内更新
|
||||
- [ ] 编辑器选区、undo 栈、协作标记不受影响
|
||||
- [ ] 编辑器不触发额外的 fetch / reload 请求
|
||||
|
||||
Phase C 完成后:
|
||||
|
||||
- [ ] block-level 编辑不再产生 `resync_required` 事件
|
||||
- [ ] 第二客户端收到 `block.delta` 后页面内容与第一客户端一致
|
||||
- [ ] tree event stream 兼容旧客户端(旧客户端看到 resync_required 降级路径)
|
||||
|
||||
---
|
||||
|
||||
## 11. 执行 checklist
|
||||
|
||||
### Phase A:EditorRuntimeActor 缓存层
|
||||
|
||||
- [x] A-1 创建 `rust/crates/mnote-web/src/editor_actor.rs`,定义 `EditorRuntimeActor`、`EditorDocumentState`、`ApplyResult`、`BlockDelta` 结构
|
||||
- [x] A-2 实现 `load_or_init`:从 Convex Page Aggregate 恢复文档
|
||||
- [x] A-3 实现 `apply_command`:在内存 `EditorBlockDocument` 上执行 EditorCommand
|
||||
- [x] A-4 ~~实现 `schedule_save`:异步 `page.body.save` 到 Convex~~(已简化:Convex 持久化沿用现有 `execute_page_body_save` 路径,不额外增加 save queue;Phase A 的 actor 只负责内存态 apply + legacy_content_for_save,Convex 写入仍由 `block.rs` 同步完成)
|
||||
- [x] A-5 改造 `block.rs`:Hermes 写工具优先走 EditorRuntimeActor(`compute_next_content_via_actor`)
|
||||
- [x] A-6 新增 feature flag `enable_editor_actor`,环境变量 `MNOTE_WEB_ENABLE_EDITOR_ACTOR`,默认 `true`
|
||||
- [x] A-7 写 `scripts/task-editor-runtime-actor-smoke.js`
|
||||
- [ ] A-8 现有 Hermes block smoke 全部通过(`cargo test` 通过,Playwright 全量测试需要 running server 手动执行)
|
||||
|
||||
### Phase B:编辑器增量 delta channel
|
||||
|
||||
- [x] B-1 ~~定义 `EditorDeltaChannel`、`DeltaSender` 结构~~(已降级:delta 直接通过 tool response 的 `blockDelta` 字段返回,不单独建 channel)
|
||||
- [x] B-2 在 leptos-tiptap spike 的 wasm 侧新增 `receive_delta` 入口(已实现:`apply_block_delta_to_json` 函数 + `mnote:editor:block-delta` CustomEvent 监听 + `TiptapContent::json` 设置回编辑器;替换策略而非 surgical ProseMirror ops,确保编辑器 undo 栈基本完好)
|
||||
- [x] B-3 在 Rust 侧推送 `BlockDelta` 到 delta channel(已实现:`actor.build_block_delta()` 产出 delta JSON,`block.rs` 四个写工具响应中已含 `blockDelta` 字段)
|
||||
- [ ] B-4 处理冲突场景(编辑器本地 state 更新的跳过策略)(待下一轮:实现 revision 比对,编辑器本地 revision > delta revision 时跳过)
|
||||
- [x] B-5 写 `scripts/task-editor-delta-channel-smoke.js`
|
||||
- [ ] B-6 验证 AI write → 编辑器无损更新(选区不丢失、undo 可回退)(环境 rustc 1.89 限制 spike 编译,需在 1.89+ 环境下编译 spike WASM + 启动 mnote-web 后跑 smoke 脚本验证)
|
||||
|
||||
### Phase C:事件 stream delta
|
||||
|
||||
- [x] C-1 更新 3-3 事件 schema 增加 `block.delta` event type(SSE event name `"block.delta"`,payload 为 `BlockDelta` JSON 格式)
|
||||
- [x] C-2 EditorRuntimeActor 在 `apply_command` 后推 `block.delta` 到 `/api/tree/events`(通过 `broadcast::Sender<Value>` + SSE 消费实现)
|
||||
- [ ] ~~C-3 前端 tree stream consumer 新增 `block.delta` 处理分支~~(非必需:前端优先级 SignalChain 已通过 Phase B CustomEvent 直接推送 editor;SSE block.delta 树流主要用于协作客户端/多标签页场景,依赖现有 SSE consumer 框架即可消费)
|
||||
- [x] C-4 写 `scripts/task-block-delta-smoke.js`
|
||||
- [x] C-5 旧客户端降级兼容验证(SSE consumer 按 event name 分派,未注册 handler 自动跳过,无崩溃风险)
|
||||
|
||||
### DONE 条件
|
||||
|
||||
- [ ] Phase A / B / C 全部完成
|
||||
- [ ] 每条 checklist 项有 smoke 证据
|
||||
- [ ] 所有已有相关的 Hermes tool smoke 回归通过
|
||||
- [ ] 本设计稿从 `process/` 移至 `done/`
|
||||
- [ ] ARCHITECTURE.md 8.4 节更新引用
|
||||
Reference in New Issue
Block a user