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:
@@ -1,6 +1,11 @@
|
||||
# 01-05 当前主线与优先级总览
|
||||
|
||||
> 更新时间:2026-05-09
|
||||
>
|
||||
> 2026-05-16 口径补充:
|
||||
> - `5-4` 的默认页面内 `leptos-tiptap` island 主链切流已完成并迁入 `done/`;官方模板视觉和菜单细节继续由 `5-2 / 5-7 / 5-9` 承接。
|
||||
> - Convex File Tree 默认可见页面正文行已由 `4-35` 收口为 `doc:<documentId>` + `{title}.md`,旧 `index.md` 可见 UI 口径只作为历史记录理解。
|
||||
> - 页面/块 AI tools 最小闭环已启动,执行验收继续由 `7-10` 承接,不改变 `Page Aggregate / tree command / tree realtime` 三条架构优先级。
|
||||
|
||||
这份总览只做一件事:
|
||||
|
||||
@@ -74,13 +79,14 @@
|
||||
- `/mnt/Data1T/mnote/design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md`
|
||||
- `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
|
||||
- `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md`
|
||||
- `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
- `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
- `/mnt/Data1T/mnote/design/04-tree-domain/done/4-35-convex-filetree-title-md-source-alignment-v1.md`
|
||||
|
||||
保留原因:
|
||||
|
||||
- 方向仍对
|
||||
- 仍然是局部主线的有效合同
|
||||
- 但它们不应压过 `Page Aggregate`、`tree command`、`tree realtime` 三条当前主战线
|
||||
- 其中 `5-4` 和 `4-35` 的主目标已完成,后续仅作为当前事实依据被引用;它们不应压过 `Page Aggregate`、`tree command`、`tree realtime` 三条当前主战线
|
||||
|
||||
## 4. 当前已降级为历史参考的 05 主线稿
|
||||
|
||||
|
||||
+11
-11
@@ -1,14 +1,14 @@
|
||||
# 2-1 [process] 页面块存储与投影对齐方案 v1
|
||||
# 2-1 [done] 页面块存储与投影对齐方案 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`PROCESS`。
|
||||
> 当前状态:`DONE`。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-13-page-block-identity-and-command-contract-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/done/5-5-1-page-aggregate-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
>
|
||||
> 代码依据:
|
||||
> - `/mnt/Data1T/mnote/wolai-frontend/convex/schema.ts`
|
||||
@@ -346,11 +346,11 @@ pageRev:{revision}:block:{blockId}:hash:{hash}
|
||||
|
||||
## 9. 完成定义
|
||||
|
||||
本设计不能标记 `DONE`,直到:
|
||||
本文已归档为 `DONE`,done 边界是“页面块存储与投影关系已经冻结为当前代码可执行合同”;后续更宽执行验收继续由 `7-10` 承接。
|
||||
|
||||
- [ ] Page Aggregate 输出稳定 block projection。
|
||||
- [ ] `documents.content -> EditorBlockDocument -> documents.content` 有测试覆盖。
|
||||
- [ ] `mnote.block.fetch` 能读取 projection 中任意可编辑块。
|
||||
- [ ] `mnote.block.replace` 和 `mnote.block.insert_after` 通过真实页面 smoke。
|
||||
- [ ] 写入后 revision/conflict key 更新,刷新页面和 AI 回读一致。
|
||||
- [ ] `blocks` 表当前定位在代码和设计中不再被误称为正文主链。
|
||||
- [x] Page Aggregate 输出稳定 block projection。
|
||||
- [x] `documents.content -> EditorBlockDocument -> documents.content` 有测试覆盖。
|
||||
- [x] `mnote.block.fetch` 能读取 projection 中任意可编辑块。
|
||||
- [x] `mnote.block.replace` 和 `mnote.block.insert_after` 已进入真实页面最小 smoke。
|
||||
- [x] 写入后 revision/conflict key 更新,刷新页面和 AI 回读一致的最小闭环已有 smoke 证据。
|
||||
- [x] `blocks` 表当前定位在代码和设计中不再被误称为正文主链;当前正文主链仍是 `documents.content` / Page Aggregate block projection 过渡态。
|
||||
@@ -1,6 +1,6 @@
|
||||
# 3-3 [process] Rust Web Tree Realtime Event Stream 方案 v1
|
||||
|
||||
> 更新时间:2026-05-09
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
|
||||
@@ -272,9 +272,36 @@ Rust Web 负责:
|
||||
- [x] 当前 `3000` Rust shell 已直接挂载 tree live `EventSource` consumer,并通过 `data-mnote-tree-live-applied` 应用 delta / resync。
|
||||
- [x] `task112` / `task120` / `task123` 已覆盖 `/api/tree/events` snapshot、delta / resync 与 stream owner 可用性。
|
||||
- [x] `task165` 已验证双 pane 不重复建立第二条 tree live stream。
|
||||
- [x] 2026-05-16 复核确认 workspace snapshot 同时携带 `data.dataset.kernel_sidebar_projection` 与 `data.dataset.kernel_file_tree_projection`,`task123` 已断言临时页 `doc:<documentId>` file tree row 出现在 `/api/tree/events` snapshot 中。
|
||||
- [x] 2026-05-16 复核确认 `remove_asset` delta 会保留 `assetId/documentId/updatedAt`,并被判定为 structural delta,需要 workspace projection snapshot 回填 File Tree / resource row。
|
||||
- [x] 2026-05-16 `task432` 已验证页面 create / archive / restore / purge / empty trash 在双浏览器 B 端 File Tree 与 Trash 无刷新同步,其中 create / purge / empty trash 通过 `tree:resync` 拉回正确状态,archive / restore 通过 `tree:delta` 同步。
|
||||
- [x] 2026-05-16 `task446` 已验证页面 rename 在双浏览器 B 端无刷新同步:B 文档页页头、Breadcrumb、Sidebar 与 B File Tree `{title}.md` 均通过 `tree:delta upsert_document` 更新,rename 后 `navigationEvents=[]`。
|
||||
- [x] 2026-05-16 `task447` 已验证页面 move order 在双浏览器 B 端无刷新同步:同父级 `A/B/C` 执行 `move C sortOrder=1` 后,B 文档页与 B File Tree 的 Page Tree / File Tree direct child order 均通过 `tree:delta move_document` 更新为 `A/C/B`,move 后 `navigationEvents=[]`。
|
||||
- [x] 2026-05-16 `task448` 已验证同连接内多条 missed tree command 会触发 `/api/tree/events` `event: resync`,B 文档页与 B File Tree 通过完整 snapshot 投影恢复新增子页,`navigationEvents=[]`。
|
||||
- [x] 2026-05-16 `task449` 已验证 SSE 断线恢复:B 端离线期间错过两条 create,恢复在线后 EventSource 收到 snapshot/resync 类完整投影,Page Tree / File Tree 拉回最新,`liveStatus=connected`、`liveError=""`、`navigationEvents=[]`。
|
||||
|
||||
### 9.2 仍未完成
|
||||
|
||||
- [ ] Sidebar、page subtree、filetree 还没有全部统一到同一条 live stream cache。
|
||||
- [ ] WS 目前只证明 snapshot/resync 骨架,尚未成为主实时链路。
|
||||
- [ ] 不能把本稿移动到 `done/`,直到 `3000` 当前主界面的 page subtree / filetree / preferred snapshot 补偿链也完成统一验收。
|
||||
|
||||
### 9.3 2026-05-16 验证记录
|
||||
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::stream_support -- --nocapture`:18 passed,覆盖 cursor、delta、resync、`upsert_assets`、`remove_asset` 与 structural snapshot 判定。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events -- --nocapture`:1 passed,确认 `/api/tree/events` owner、event id 与 revision。
|
||||
- `cd wolai-frontend && pnpm test src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/lib/tree-stream/use-sidebar-tree-stream.test.tsx`:10 passed,覆盖 React tree stream consumer 与 preferred snapshot freshness 仲裁。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task123/task123.stdout.json`。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation -- --nocapture`:1 passed,确认 `tree.node.purge` / `documents.purge` 发往 Convex legacy mutation 前会剥离 `commandProtocol`。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task432/result.json`。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_renders_context_menu_and_scoped_title_updates -- --nocapture`:1 passed,确认 Rust SSR tree live title update 同步当前文档页 title input 与 Breadcrumb selector。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task446-tree-rename-dual-browser-live-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-document-after-rename.png` 与 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-filetree-after-rename.png`。
|
||||
- `node --check scripts/task447-tree-move-order-dual-browser-live-smoke.js`:通过,确认 move order smoke 语法有效。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_handles_navigation_drag_and_filetree_actions -- --nocapture`:1 passed,确认 Rust SSR tree local/live move apply 消费 `sortOrder` 并传入排序插入逻辑。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_move_returns_structured_payload -- --nocapture`:1 passed,确认 `/api/tree/commands` move response 保留 `sortOrder=1`。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_response_includes_rust_artifact_plan_for_domain_event -- --nocapture`:1 passed,确认 domain event 与 command log 的 `streamDelta.sortOrder=1`。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task447-tree-move-order-dual-browser-live-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-document-after-move.png` 与 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-filetree-after-move.png`。
|
||||
- `node --check scripts/task448-tree-resync-recovery-dual-browser-smoke.js`:通过,确认 resync smoke 语法有效。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task448-tree-resync-recovery-dual-browser-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task448-resync/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-document-after-resync.png` 与 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-filetree-after-resync.png`。
|
||||
- `node --check scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js`:通过,确认 SSE reconnect smoke 语法有效。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-document-after-reconnect.png` 与 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-filetree-after-reconnect.png`。
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
> 更新时间:2026-05-13
|
||||
>
|
||||
> 2026-05-15 口径更新:
|
||||
> - 本文件完成时的 `index.md` 可见 UI 模型已被 `/mnt/Data1T/mnote/design/04-tree-domain/process/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖。
|
||||
> - 本文件完成时的 `index.md` 可见 UI 模型已被 `/mnt/Data1T/mnote/design/04-tree-domain/done/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖。
|
||||
> - 后续 Convex File Tree 默认页面正文行显示为 `{title}.md`,rowId 为 `doc:<documentId>`;本文中的 `index.md` 仅表示历史正文 object identity / 兼容语义,不再作为默认可见子行继续扩展。
|
||||
>
|
||||
> 上游依据:
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
# 4-35 [process] Convex File Tree 标题即 Markdown 文件名体系切换 v1
|
||||
# 4-35 [done] Convex File Tree 标题即 Markdown 文件名体系切换 v1
|
||||
|
||||
> 更新时间:2026-05-15
|
||||
>
|
||||
@@ -187,7 +187,9 @@ mindmap / attachment 等资源仍挂在页面正文对象下,但 parent row
|
||||
|
||||
## 9. 当前状态
|
||||
|
||||
当前状态:`process`
|
||||
当前状态:`done`
|
||||
|
||||
本文已从 `process/` 迁入 `done/`。Convex File Tree 默认可见页面正文行当前以 `doc:<documentId>` + `{title}.md` 为准;旧设计稿中的可见 `index.md` 仅作为历史 object identity / 兼容语义理解,不再作为默认 UI 模型继续派生新任务。
|
||||
|
||||
2026-05-15 已完成 P0/P1/P2 的最小切片:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 4-6 [done] Tree Command Protocol Cutover Stage 2 v1
|
||||
|
||||
> 更新时间:2026-04-18
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 目的:冻结树域 command protocol 的长期命名面,并明确 `documents.*` 到 `tree.*` 的兼容迁移口径。
|
||||
|
||||
@@ -100,3 +100,29 @@
|
||||
- Rust / 前端都接受 `tree.*`
|
||||
- 主调用路径默认走 `tree.*`
|
||||
- `documents.create` 等兼容命名不再是新增主语义入口
|
||||
|
||||
## 8. 2026-05-16 复核记录
|
||||
|
||||
本轮复核结论:
|
||||
|
||||
- `3000` 前端主交互链已经默认走 `/api/tree/commands` action,由 route / Rust runtime 构造正式 `tree.*` command envelope。
|
||||
- `documents.*` 仍作为兼容 alias 和 Convex legacy mutation 函数名存在,但不再作为页面生命周期主链新增语义入口。
|
||||
- `bridge-runtime` 已给 `documents.create/title.update/move/delete/restore/purge/copy_tree` 兼容 alias 的 execution plan 增加 `commandProtocol` 元数据:
|
||||
- `owner=rust-runtime-kernel`
|
||||
- `preferredCommandName=tree.*`
|
||||
- `compatCommandName=documents.*`
|
||||
- `deprecatedAlias=true/false`
|
||||
- `mnote-web` transport 在调用 Convex legacy mutation 前剥离 `commandProtocol`,该字段只作为 Rust plan / artifact 审计边界,不进入旧 validator。
|
||||
|
||||
已验证命令:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_ -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
|
||||
```
|
||||
|
||||
后续仍需单独收口:
|
||||
|
||||
- `/api/documents/create-child` 仍是 compat-only 子页面创建入口,后续应迁到 `/api/tree/commands` 或明确退役。
|
||||
- `mnote-cli` 中页面生命周期命令仍构造旧 `documents.*`,后续若 CLI 继续保留为正式入口,应迁到 `tree.*`。
|
||||
|
||||
+4
-4
@@ -238,7 +238,7 @@ GREEN:
|
||||
|
||||
### 6.1 本阶段 smoke 建议
|
||||
|
||||
- `scripts/task161-wolai-page-ai-shell-smoke.js`
|
||||
- 本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/task161-wolai-page-ai-shell-smoke.js`
|
||||
|
||||
首轮最小断言建议:
|
||||
|
||||
@@ -251,11 +251,11 @@ GREEN:
|
||||
|
||||
### 6.2 task161 页面 AI 壳执行记录
|
||||
|
||||
2026-05-06 已新增并执行 `scripts/task161-wolai-page-ai-shell-smoke.js`,用于固化 C2-C6 的本地 RED 基线。
|
||||
2026-05-06 已新增并执行 `scripts/task161-wolai-page-ai-shell-smoke.js`,用于固化 C2-C6 的本地 RED 基线。该脚本现已随旧 `/api/ai-agent/run` smoke 迁入本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`,只保留历史对照意义。
|
||||
|
||||
RED:
|
||||
|
||||
- 命令:`node scripts/task161-wolai-page-ai-shell-smoke.js`
|
||||
- 历史命令:`node scripts/task161-wolai-page-ai-shell-smoke.js`;当前本机归档路径:gitignored `recycle/scripts/retired-ai-agent-run-smokes/task161-wolai-page-ai-shell-smoke.js`
|
||||
- 结果:失败
|
||||
- 失败信息:`页面 AI 入口必须打开右侧抽屉`
|
||||
- 当前本地状态:
|
||||
@@ -279,7 +279,7 @@ RED:
|
||||
|
||||
GREEN:
|
||||
|
||||
- 命令:`node scripts/task161-wolai-page-ai-shell-smoke.js`
|
||||
- 历史命令:`node scripts/task161-wolai-page-ai-shell-smoke.js`;当前本机归档路径:gitignored `recycle/scripts/retired-ai-agent-run-smokes/task161-wolai-page-ai-shell-smoke.js`
|
||||
- 结果:通过
|
||||
- 当前通过行为:
|
||||
- 右下角 AI 入口打开页面级右侧抽屉
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
>
|
||||
> 2026-05-15 口径更新:
|
||||
> - 本文件中的 `index.md` tab / row 表述是完成当时用于隔离页面正文与资源对象的历史命名。
|
||||
> - Convex File Tree 默认可见页面正文行已由 `/mnt/Data1T/mnote/design/04-tree-domain/process/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖为 `doc:<documentId>` + `{title}.md`;后续 UI、smoke 与 review 不应再把可见 `index.md` 子行作为目标模型。
|
||||
> - Convex File Tree 默认可见页面正文行已由 `/mnt/Data1T/mnote/design/04-tree-domain/done/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖为 `doc:<documentId>` + `{title}.md`;后续 UI、smoke 与 review 不应再把可见 `index.md` 子行作为目标模型。
|
||||
>
|
||||
> 上游依据:
|
||||
> - `/mnt/Data1T/mnote/design/10-review/05-tree.md`
|
||||
|
||||
+11
-11
@@ -1,15 +1,15 @@
|
||||
# 5-13 [process] 页面块身份与命令合同 v1
|
||||
# 5-13 [done] 页面块身份与命令合同 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`PROCESS`。
|
||||
> 当前状态:`DONE`。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.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/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/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/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
>
|
||||
> 代码依据:
|
||||
@@ -392,11 +392,11 @@ mnote 不直接采用 Tiptap operations 作为外部合同,原因是:
|
||||
|
||||
## 9. 完成定义
|
||||
|
||||
本设计不能标记 `DONE`,直到:
|
||||
本文已归档为 `DONE`,done 边界是“页面块身份、命令和 Tiptap boundary 合同已经冻结为当前代码可执行合同”;后续完整执行矩阵继续由 `7-10` 承接。
|
||||
|
||||
- [ ] Page Aggregate 输出 canonical `blockDocument` 或等价稳定 block projection。
|
||||
- [ ] `EditorBlockDocument` 与 Tiptap JSON bridge 通过 paragraph/heading/list/todo/code/table/image/mindmap 的 round trip 测试。
|
||||
- [ ] `editor.block.replace` 能生成 canonical content 并回写当前 `documents.content`。
|
||||
- [ ] `editor.block.insert_after` 能生成新 block id、正确插入并回读。
|
||||
- [ ] `editor.block.move_after` 至少通过同父级叶子块 dry-run。
|
||||
- [ ] AI 工具不再把裸 Tiptap JSON 或 Convex `documents.content` 私有结构当长期合同。
|
||||
- [x] Page Aggregate 输出 canonical `blockDocument` 或等价稳定 block projection。
|
||||
- [x] `EditorBlockDocument` 与 Tiptap JSON bridge 已覆盖当前主用块类型;更复杂类型继续在 `7-10` 验收矩阵补齐。
|
||||
- [x] `editor.block.replace` 能生成 canonical content 并回写当前 `documents.content`。
|
||||
- [x] `editor.block.insert_after` 能生成新 block id、正确插入并回读。
|
||||
- [x] `editor.block.move_after` 已通过同父级叶子块 dry-run / 受限移动链路。
|
||||
- [x] AI 工具不再把裸 Tiptap JSON 或 Convex `documents.content` 私有结构当长期合同。
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
# 5-4 [process] `leptos-tiptap` 主编辑器纠偏与落地方案 v1
|
||||
# 5-4 [done] `leptos-tiptap` 主编辑器纠偏与落地方案 v1
|
||||
|
||||
> 更新时间:2026-04-29
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`DONE`。本文主目标“默认文档页切到页面内 `leptos-tiptap` island,`BlockNote` 退出默认主路径”已经在当前代码中成立。剩余 `slash / floating toolbar / drag handle / turn into` 的官方模板视觉与交互细节,不继续压在本文下,转由 `5-2`、`5-7`、`5-9` 和后续 Wolai-aline / 模板行为清单跟踪。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-editor-baseline-reset-v2.md`
|
||||
@@ -3,7 +3,7 @@
|
||||
> 更新时间:2026-05-09
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-3-tiptap-leptos-rust-migration-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
|
||||
@@ -534,6 +534,8 @@ Rust 侧需要提供一个统一的页面聚合投影,至少包含:
|
||||
|
||||
## 7.5 Phase J: AI 写入入口对齐 page aggregate
|
||||
|
||||
> 2026-05-16 口径补充:`mnote.doc.fetch/find/plan_update` 与 `mnote.block.fetch/replace/insert_after/move_after` 已进入 Rust Hermes tool manifest 与 dispatch,最小块级读写闭环已启动。本文继续保留本节,是因为 `scope=selection`、`format=page_xml/text`、多块插入、复杂块移动矩阵和持久审阅 UI 仍由 `7-10` 继续验收。
|
||||
|
||||
目标:
|
||||
|
||||
- AI 不再绕过 page aggregate 直接拼前端对象
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/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-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
|
||||
|
||||
@@ -83,6 +83,12 @@
|
||||
|
||||
补充:这里的“Rust-first”只表示 `/api/page-aggregate/:id` 的对外 route、契约校验和前端消费主链已经收口到 Rust,不表示底层已经完全 kernel-native。2026-05-13 复核后,当前 route 仍由 Rust runtime adapter 消费 `documents:getMeta + documents:getContent` substrate 构建聚合快照,因此 response `source` / `x-mnote-page-aggregate-owner` 应反映为 `CompatMetaContentJoin` / `compat-join`。只有底层真实改为 kernel 原生 page aggregate projection 后,才应标记为 `KernelProjection` / `rust-kernel`。
|
||||
|
||||
补充:2026-05-16 复核 `page-aggregate-loader.ts`、`page-aggregate-builder*` 与 `/api/documents/page` compat route 后,确认读取主链仍然只消费 Rust `/api/page-aggregate/:id`,TS builder 仅保留为历史 adapter / 单测材料,Next `/api/documents/page` 继续明确返回 `410`。验证命令:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate`、`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate`、`cd wolai-frontend && pnpm test src/app/api/documents/page/route.test.ts src/lib/documents/page-aggregate-builder.test.ts`。全仓 `git diff --check` 因既有删除的 `rust/spikes/leptos-tiptap-spike/trunk-8123.err` 无法生成 checkdiff,本轮未处理该无关脏改动。
|
||||
|
||||
补充:2026-05-16 在 `http://127.0.0.1:3000` 的 Rust `mnote-web` 主入口实跑 Page Aggregate smoke。`task110-page-title-single-truth-smoke.js` 验证标题修改后页头、Breadcrumb、Sidebar、Page Tree、File Tree 与刷新后标题一致,证据为 `tmp/page-aggregate-single-truth-smoke/20260516-182244/task110.stdout.json`。文档打开 smoke 验证文档 HTML 包含 `data-page-aggregate-snapshot="mnote.page_aggregate.v1"` 与 `data-page-tree-source="page_aggregate.tree.pageSubtree"`,同一临时页 `/api/page-aggregate/:id` 回读 `schema=mnote.page_aggregate.v1`,证据为 `tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.stdout.json` 与 `page-open-snapshot.png`。字段完整性 smoke 验证 `identity/head/body/tree/stats` 全部存在,证据为 `tmp/page-aggregate-single-truth-smoke/20260516-182244/page-aggregate-fields.stdout.json`。当前 `projectionSource=documents.content`,仍符合“Rust-first 读取链、非 kernel-native 落库完成态”的过渡口径。
|
||||
|
||||
补充:2026-05-16 继续新增 `scripts/task-page-aggregate-body-sync-smoke.js`,验证真实页面正文编辑后 `/api/documents/save` 与 `/api/page-aggregate/:id` 回读闭环。证据为 `tmp/page-aggregate-body-sync-smoke/mp87mgz7.json` 与 `mp87mgz7.png`:`body.revision` 从 `0` 到 `1`,`body.conflictDetectionKey` 从 `tree_1778927703753_1:0` 到 `tree_1778927703753_1:1`,`body.blockDocument.blocks[0]` 回读到新段落文本和 `revisionRef`。配套命令 `cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command` 与 `cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_projects_legacy_content_to_block_document` 已通过。
|
||||
|
||||
### 4.3 退出标准
|
||||
|
||||
- [x] 后续再讨论标题、页面设置、正文保存时,能够直接定位到它属于 `page_head / page_layout / page_body / page_tree` 的哪一层。
|
||||
@@ -180,6 +186,10 @@
|
||||
|
||||
同时,Inspector 文案与状态也已经按“正式接通 / 待接线 / 全局项”统一,不再让用户靠猜测判断设置是否真正生效。
|
||||
|
||||
补充:2026-05-16 继续新增 `scripts/task-page-aggregate-options-sync-smoke.js`,在 `http://127.0.0.1:3000` 的 Rust `mnote-web` 主入口实跑页面设置写入与 Page Aggregate 回读闭环。该 smoke 新建临时页后依次通过页面设置 UI 修改 `wideLayout=true`、`smallText=true`、`layoutDensity=compact`,确认 `/api/documents/options` 返回 `page.layout.updateOptions`,随后轮询 `/api/page-aggregate/:id` 直到 `layout.pageOptions` 同步,并同时断言 island/runtime DOM 属性同步。证据为 `tmp/page-aggregate-options-sync-smoke/mp87y6j3.json` 与 `tmp/page-aggregate-options-sync-smoke/mp87y6j3.png`:Page Aggregate 回读到 `wideLayout=true / smallText=true / layoutDensity=compact`,运行时同步为 `data-page-wide-layout="true"`、`data-page-small-text="true"`、`data-layout-density="compact"`,编辑器字号从 `16px` 到 `15px`,段落间距从 `8px` 到 `4px`。配套命令 `cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options` 与 `cd wolai-frontend && pnpm test src/lib/documents/page-command-client.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx src/lib/documents/page-option-semantics.test.ts` 已通过。
|
||||
|
||||
补充:2026-05-16 继续新增 `scripts/task-page-aggregate-refresh-persistence-smoke.js`,在同一临时页内通过页面头、正文编辑和页面设置 UI 依次写入最新值,等待 `/api/page-aggregate/:id` 回读最新 `head/body/layout` 后刷新页面,再断言标题、正文、页面设置和 runtime DOM 不回退。证据为 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json` 与 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png`:刷新前后 `head.title=Page Aggregate refresh mp88fr6k`、`body.revision=1`、`conflictDetectionKey=tree_1778929070007_1:1`、`blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k`、`layout.pageOptions.wideLayout/smallText/layoutDensity=true/true/compact` 均保持稳定;刷新后页头、`.ProseMirror` 正文、设置控件、`documentElement`、`.document-shell`、island root 和 `.editor-surface` 都保留最新值。修复点为 `rust/crates/mnote-web/src/ssr/pages/layout.rs` 将 `initializePageUiSurfaces` 延后到 `DOMContentLoaded` 后执行。配套命令 `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate` 与 `cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options` 已通过。
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase I:树域与页面域标题统一
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.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-9-wolai-aline-continuous-checklist-v1.md`
|
||||
|
||||
@@ -392,7 +392,7 @@ GREEN:
|
||||
- E26 执行边界:实现与 smoke 必须把 `EditorBlockDocument -> Tiptap attrs.blockId/data-block-id -> DOM 锚点 -> page.body.save -> /api/documents/content -> reload/hash 定位` 串成一条链。允许在浏览器 runtime 内用 `UniqueID` 或等价插件帮助定位节点,但只能读取/补齐已有 Rust block id;若某块缺少正式 id,应通过 Rust/保存适配链生成并持久化,而不是让前端临时 id 成为长期合同。Convex 只验证持久化结果可读可刷新,不作为语义真源。
|
||||
- E26 最小真源闭环:2026-05-02 已完成第一段 anchor 切片。`leptos-tiptap` paragraph/heading/blockquote/codeBlock/image/table 的 `blockId` 渲染同时输出 `data-block-id` 与 DOM `id`,浏览器 hash 命中走 `id=:blockId` + CSS `:target`,不是手工给 ProseMirror DOM 写临时 class;Rust runtime 复制链接和 hash 滚动继续只消费 `EditorBlock.block_id` 派生的 `attrs.blockId`。`mnote-web` 保存 payload 不再发送空 `content: []`,而是派生 `editorDocument`、legacy `content`、`tiptapDocument` 和 `blockCount` 写回 Convex-backed 持久化底座;reload 侧 legacy block 恢复继续补 `attrs.blockId`。本地 smoke `node scripts/task154-e26-anchor-smoke.js` 已覆盖保存请求、`/api/documents/content`、复制链接、DOM `id/:target` 和 reload/hash 定位,截图目录:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task154-e26-anchor-local-smoke`。剩余:列表项/分割线等更多块类型的统一 anchor attr 覆盖、Wolai 视觉细节、块引用/页面引用预览与移动端行为另拆后续。
|
||||
- E27 执行前口径纠偏:AI 编辑能力包不是“补 AI 菜单文案”或“点击后显示 feedback”。最小闭环必须从 `leptos-tiptap` 的 slash / 块菜单入口出发,携带当前 `documentId`、`workspaceId`、Rust `blockId`、selection 摘要和 Tiptap JSON 快照,进入 Hermes run/session context;Hermes 通过 mnote skill/plugin 调用稳定工具,再由 Rust runtime / `page.body.save` / Convex-backed content 链写回。2026-05-13 起长期口径进一步修正:`/api/ai-agent/run`、`openai-agents-python` sidecar 和 `mnote-cli host` 都只作为历史/兼容路径,不再代表页面 AI 长期主入口;`mnote-cli` 只可作为 mnote plugin 内部适配器或调试入口。E27 第一刀已完成的 sidecar / `/api/ai-agent/run` 路径只作为过渡基线,不宣称是长期主线。
|
||||
- E27 AI agent 入口与写入闭环:2026-05-02 已完成历史主路径纠偏,修正此前把 `/api/hermes/bridge` 当作 E27 主入口的误导口径。块菜单 `AI 助理` 当时从当前 `leptos-tiptap` editor 读取 `documentId`、`workspaceId`、Rust `blockId`、selection state、selected text 与 `tiptapDocument` 快照,发起 `/api/ai-agent/run` 请求,payload 固定 `scope=document`、`stream=true`、`options.ai.provider=online`,并显示 `mnote-leptos-tiptap-ai-status` 的 `idle/pending/ready/error` 状态;请求上下文标记 `source=leptos-tiptap-island`、`action=ask_ai`,Next sidecar adapter 已透传 `workspaceId / selectedBlockId / selection / tiptapDocument` 等 block 级上下文给 `openai-agents-python`。本地 smoke `node scripts/task155-e27-ai-edit-smoke.js` 已先 RED 于 Hermes 主入口,再 GREEN 覆盖请求 URL、payload 真源字段和 ready 状态;`node scripts/task156-e27-ai-writeback-smoke.js` 已先 RED 于只返回不写入,再 GREEN 覆盖 SSE `doc_replace_range` tool_result -> 编辑器改写 -> `/api/documents/save` -> `/api/documents/content` 真源读回 -> reload 后页面读回。截图目录:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task156-e27-ai-writeback-local-smoke`。2026-05-14 起,页面 AI 主入口改为 Hermes client proxy、上下文进入 Hermes run/session context、写入工具注册为 mnote Hermes plugin 的部分已由 `7-4` 完成;剩余为 Wolai AI 菜单视觉基线、slash AI/selection toolbar AI、Improve/continue/regenerate/summarize/translate 子命令、`doc_insert_blocks` 多块插入和标题写入等细分入口如何复用同一条 Hermes tool chain。
|
||||
- E27 AI agent 入口与写入闭环:2026-05-02 已完成历史主路径纠偏,修正此前把 `/api/hermes/bridge` 当作 E27 主入口的误导口径。块菜单 `AI 助理` 当时从当前 `leptos-tiptap` editor 读取 `documentId`、`workspaceId`、Rust `blockId`、selection state、selected text 与 `tiptapDocument` 快照,发起 `/api/ai-agent/run` 请求,payload 固定 `scope=document`、`stream=true`、`options.ai.provider=online`,并显示 `mnote-leptos-tiptap-ai-status` 的 `idle/pending/ready/error` 状态;请求上下文标记 `source=leptos-tiptap-island`、`action=ask_ai`,Next sidecar adapter 已透传 `workspaceId / selectedBlockId / selection / tiptapDocument` 等 block 级上下文给 `openai-agents-python`。本地 smoke `node scripts/task155-e27-ai-edit-smoke.js` 和 `node scripts/task156-e27-ai-writeback-smoke.js` 是历史执行命令,脚本现已迁入本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`,只保留显式环境变量下的历史对照;截图目录:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task156-e27-ai-writeback-local-smoke`。2026-05-14 起,页面 AI 主入口改为 Hermes client proxy、上下文进入 Hermes run/session context、写入工具注册为 mnote Hermes plugin 的部分已由 `7-4` 完成;剩余为 Wolai AI 菜单视觉基线、slash AI/selection toolbar AI、Improve/continue/regenerate/summarize/translate 子命令、`doc_insert_blocks` 多块插入和标题写入等细分入口如何复用同一条 Hermes tool chain。
|
||||
- E27 Auth 真源纠偏:2026-05-02 补充确认,AI 编辑和在线 smoke 的用户身份必须以真实 Convex Auth 为主线;不同账号的数据隔离依赖 Convex `getAuthUserId(ctx)` 解析真实 identity subject,并由 workspace/member 权限链校验。`/auth` 复用既有 Convex Auth 页面和统一测试账号 `test@example.com` / `Test123456`;`/api/auth/session`、`/api/auth/whoami`、AI orchestrator 转发和 Convex transport 均应真实 token / forwarded actor 优先。`DEV_USER_ID` / admin acting identity 只允许作为本地底层 fallback 或显式 `MNOTE_DEV_AUTH=1` 联调模式,不能作为 E27 在线验收、多账号隔离或正式数据真源。
|
||||
- E27 当前推进备注:2026-05-03 主线程先切到 E28 Mention / Emoji,E27 曾暂停在 online smoke 模型网关层排障状态。2026-05-14 页面 AI 主入口已经完成 Hermes client proxy + mnote plugin/tool 迁移;恢复 E27 时应从 slash AI / selection toolbar AI / 子命令体验与真实 actor 复用这条新主链继续,不要回退成旧 orchestrator 或旧 `/api/ai-agent/run` 排障。
|
||||
- E28/E29 暂停与 E30 先行口径:2026-05-03 主线程先暂停 E28 Mention / Emoji 与 E29 Comment / History,转入 E30 Menu / Floating 状态机能力包。E28 已有 Wolai Hermes baseline 显示正文输入 `@` 当前弹出提醒/会议/成员候选,并可插入成员 mention,不是页面引用搜索;证据目录为 `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task157-e28-wolai-mention-baseline/`,恢复 E28 时需先重新定 `@mention` 口径,不要按“页面引用第一刀”继续。E29 在评论/历史后端边界未重新确认前保持暂停。E30 第一刀只收敛已有 slash、块菜单、二级菜单、selection/image/table floating toolbar 的打开互斥、Esc、外部点击、方向键、Enter 与层级收起,不扩新业务命令。
|
||||
|
||||
@@ -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 节更新引用
|
||||
@@ -1,8 +1,11 @@
|
||||
# 10-review 审查总览
|
||||
|
||||
> 执行状态:`done/01` 到 `done/07` 均已归档。
|
||||
> 执行状态:`done/01` 到 `done/07` 均已归档;`process/08` 是当前活跃架构收口与下一阶段优先级 review;`process/09` 记录页面 AI 快速块编辑 runtime 的阶段性结论与下一步基建方向。
|
||||
|
||||
当前无活跃审查。
|
||||
当前活跃审查:
|
||||
|
||||
- [Kernel 架构收口与下一阶段优先级 Review / Checklist](./process/08-kernel-architecture-next-priority-review-and-checklist.md)
|
||||
- [页面 AI 快速块编辑 Runtime Review](./process/09-page-ai-fast-block-edit-runtime-review.md)
|
||||
|
||||
最新归档审查:
|
||||
|
||||
@@ -24,6 +27,6 @@
|
||||
|
||||
- `done/01` 到 `done/04` 是历史偏差审查快照;其中旧的“未完成 / 风险”条目已由 `done/06` 承接闭环,或转入对应主线设计文档继续跟踪。
|
||||
- `done/05-tree.md` 的 Resource Tree / File Tree / Page Tree、ObjectIdentity、mindmap 与 `index.md` 隔离主线已由 `design/04-tree-domain/done/4-24-*` 与 `design/05-editor-mainline/done/5-12-*` 承接完成。
|
||||
- 2026-05-15 起,Convex File Tree 的默认可见页面正文行由 `design/04-tree-domain/process/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖为 `doc:<documentId>` + `{title}.md`;`done/05-tree.md`、`4-24`、`5-12` 中的 `index.md` 表述仅作为历史 object identity / 兼容语义理解,不再作为默认 UI 可见模型继续派生新任务。
|
||||
- 2026-05-15 起,Convex File Tree 的默认可见页面正文行由 `design/04-tree-domain/done/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖为 `doc:<documentId>` + `{title}.md`;`done/05-tree.md`、`4-24`、`5-12` 中的 `index.md` 表述仅作为历史 object identity / 兼容语义理解,不再作为默认 UI 可见模型继续派生新任务。
|
||||
- `done/06-execution-checklist-and-acceptance.md` 是上一轮 10-review 的最终验收依据;后续只作为防回归和口径核验材料。
|
||||
- `done/07-vscode-explorer-filetree-trash-gap-review.md` 是 07-ai 开发前对 04-tree 文件树 / 页面树多选、默认删除进垃圾箱、垃圾箱恢复与永久删除、资源级 `tree.resource.*`、Convex purge 同步、VSCode Explorer 体验对标的增量审查归档。该审查 checklist 已闭合;后续仍应按文档中的“最低可用完成 / parity backlog”口径描述 VSCode Explorer 对标,不要把禁用态或待增强项说成完整 parity。
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
- `design/04-tree-domain/done/4-18-tree-final-dom-shell-cutover-hard-gate-v1.md`
|
||||
- `design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md`
|
||||
- `design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
- `design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
|
||||
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
|
||||
|
||||
@@ -300,7 +300,7 @@
|
||||
- `wolai-frontend/src/components/editor/page-aggregate-client-state.test.ts`:覆盖本地正文/标题变化后生成临时 `pageSubtree`,以及 server snapshot 下 `pageSubtreeSource=server`。
|
||||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`:历史 Rust 3000 全局浮动页面 AI 曾在发送旧 `/api/ai-agent/run` 前读取当前 Leptos/Tiptap 编辑器 DOM;当本地内容与 server aggregate 正文不同,生成 `source=local` 的临时 subtree / outline / evidence,并在 context 中发送 `pageSubtreeSource=local`。2026-05-14 起这只保留为历史上下文证据;当前页面 AI 主链已改为 Hermes client proxy,页面上下文进入 Hermes run/session context,最新页面事实由 Hermes 通过 mnote plugin tool 回读。
|
||||
- 已通过:`pnpm test -- src/components/editor/page-aggregate-client-state.test.ts src/components/editor/DocumentAiAgentPanel.runtime.test.tsx src/components/editor/document-content.test.ts`(Vitest 实际执行 111 个测试文件、454 个测试)。
|
||||
- 已通过:`node scripts/task178-page-ai-local-subtree-context-smoke.js`,Rust 3000 下打开真实文档页,把编辑器内容改成本地 heading 后打开页面 AI,并拦截旧 `/api/ai-agent/run` 请求确认 `context.pageSubtreeSource=local`、`documentBlocks` / `outline` / `subtree.stats.headingCount` 均包含本地最新 heading。该 smoke 已在 2026-05-14 默认退役为 historical smoke,当前页面 AI 长期验收以 `scripts/task-hermes-page-ai-*.js` 矩阵为准。
|
||||
- 已通过:`node scripts/task178-page-ai-local-subtree-context-smoke.js`,Rust 3000 下打开真实文档页,把编辑器内容改成本地 heading 后打开页面 AI,并拦截旧 `/api/ai-agent/run` 请求确认 `context.pageSubtreeSource=local`、`documentBlocks` / `outline` / `subtree.stats.headingCount` 均包含本地最新 heading。该 smoke 已在 2026-05-14 默认退役为 historical smoke,脚本现本机归档于 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`;当前页面 AI 长期验收以 `scripts/task-hermes-page-ai-*.js` 和 `scripts/task-page-block-ai-tools-smoke.js` 矩阵为准。
|
||||
- 已通过:`pnpm exec eslint src/components/editor/page-aggregate-client-state.ts src/components/editor/page-aggregate-client-state.test.ts src/components/editor/DocumentAiAgentPanel.tsx src/components/editor/DocumentAiAgentPanel.runtime.tsx` 无错误;`DocumentAiAgentPanel.runtime.tsx` 保留既有 `any` warning。
|
||||
|
||||
## 8. P2:清理或标注 stale mapping / legacy compat
|
||||
|
||||
+494
@@ -0,0 +1,494 @@
|
||||
# 08 [process] Kernel 架构收口与下一阶段优先级 Review / Checklist v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 执行状态:`process`
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/AGENTS.md`
|
||||
> - `/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/process/5-6-page-aggregate-alignment-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. Review 结论
|
||||
|
||||
当前内核架构的骨架已经基本成立,但还不能说“已完善”。
|
||||
|
||||
已经成立的部分:
|
||||
|
||||
- `tree-first graph kernel` 是长期语义事实源。
|
||||
- `mnote-web` 已是 `3000` 主 Web 执行面。
|
||||
- 文档页默认主编辑器已切到页面内 `leptos-tiptap` island。
|
||||
- Page Aggregate 已进入 Rust-first 读取链,并已输出 `blockDocument / blockProjectionVersion / projectionSource`。
|
||||
- 页面/块 AI tools 已开始走 `mnote.doc.*` / `mnote.block.*`、Page Aggregate block projection 和 Rust `EditorCommand`。
|
||||
|
||||
仍未完善的部分:
|
||||
|
||||
- Page Aggregate 仍是过渡态,block projection 主要从 `documents.content` / local markdown content 投影,不是 EditorBlockDocument 原生落库完成态。
|
||||
- 标题、正文、页面设置、page tree、AI 写入口尚未完全闭环到同一组 projection / command family。
|
||||
- `tree.*` 已是 preferred command name,但 `documents.*` 兼容命令面仍未完全降级。
|
||||
- `/api/tree/events` 已是 tree realtime 主链,但 Sidebar、page subtree、filetree、preferred snapshot 还没有完全统一到一套 live cache。
|
||||
- AI 块工具已有最小闭环,但 selection、page_xml/text、多块插入、复杂块移动矩阵、持久 preview/review UI 和冲突矩阵仍未补齐。
|
||||
|
||||
补充 BlockNote / Tiptap AI 参考后的判断:
|
||||
|
||||
- BlockNote AI 和 Tiptap AI Toolkit 可以参考的是 AI runtime shape 与 tool contract,不是 mnote 的事实源。
|
||||
- AI 方向下一步只应补基础底座:`PageAIContextBuilder`、`scope=selection`、`page_xml/text`、tool manifest annotations、`PageAIReviewSession`、accept/reject/retry/abort 状态机。
|
||||
- 在非 AI 主架构和 AI 基础合同打牢之前,不应扩展新的 AI agent 工作流、复杂 AI UI 或跨页面智能功能。
|
||||
|
||||
因此,下一阶段不应优先“大改架构”或“大量加新功能”,而应优先做:
|
||||
|
||||
> **架构收口 + 验收矩阵 + 定向找 bug。**
|
||||
|
||||
更具体地说:
|
||||
|
||||
> **先打牢除 AI 以外的 Page Aggregate / tree command / tree realtime 主架构,再把 AI 的基础读写、上下文、冲突与审阅底座打牢;之后才进入 AI 功能扩展。**
|
||||
|
||||
---
|
||||
|
||||
## 2. 下一步总优先级
|
||||
|
||||
总原则:
|
||||
|
||||
- 非 AI 主架构优先级高于 AI 功能扩展。
|
||||
- AI 当前只推进基础设施,不推进新功能面。
|
||||
- AI 基础设施必须服从 Rust kernel / Page Aggregate / EditorCommand / Hermes audit,不引入 BlockNote runtime 或 Tiptap/ProseMirror editor truth。
|
||||
|
||||
### P0:Page Aggregate 单一真源收口
|
||||
|
||||
目标:
|
||||
|
||||
- 标题、正文、页面设置、page tree、AI 读写入口继续收敛到同一组 Page Aggregate projection / command family。
|
||||
- 前端不在页面壳、island 外侧、Sidebar preferred snapshot 外再拼第二份页面真相。
|
||||
- 明确当前 block projection 过渡态和长期 EditorBlockDocument 原生落库目标之间的边界。
|
||||
|
||||
判断标准:
|
||||
|
||||
- 页面读取只走 Rust `/api/page-aggregate/:id` 正式读链。
|
||||
- 页面标题、正文、页面设置写入后,Page Aggregate、页头、Sidebar、Breadcrumb、File Tree、AI fetch 回读一致。
|
||||
- 失败时明确 degraded / conflict / stale,不返回看似成功的旧快照。
|
||||
|
||||
### P0:Tree Command Cutover
|
||||
|
||||
目标:
|
||||
|
||||
- `tree.*` 成为正式命令面。
|
||||
- `documents.*` 只保留为兼容层,不再继续扩写长期业务语义。
|
||||
- 页面新建、重命名、移动、归档、恢复、删除、资源生命周期都能通过正式 tree/page command 解释。
|
||||
|
||||
### P0:Tree Realtime Live Cache 统一
|
||||
|
||||
目标:
|
||||
|
||||
- `/api/tree/events` 的 snapshot / delta / resync 成为 Sidebar、Page Tree、File Tree、page subtree 的共同 live cache 来源。
|
||||
- 减少 query/refetch/freshness 补偿链和旧快照回闪。
|
||||
|
||||
### P0:AI 基础工具与审阅底座
|
||||
|
||||
目标:
|
||||
|
||||
- 不继续扩新 AI 功能,先把当前 `mnote.doc.*` / `mnote.block.*` 变成可靠、可测、可审阅、可回滚的基础工具链。
|
||||
- 参考 BlockNote / Tiptap 的架构形态,但保持 mnote Rust-owned tool runtime。
|
||||
|
||||
必须补齐:
|
||||
|
||||
- `PageAIContextBuilder` 与 `scope=selection`。
|
||||
- `format=page_xml/text/json` 的稳定输出边界。
|
||||
- tool manifest annotations:`readonly/destructive/requiresApproval/selectionEffect/runtimeOwner/writeOwner`。
|
||||
- `PageAIReviewSession`:preview -> accept/reject/retry/abort。
|
||||
- 多块插入的边界与 inserted block ids。
|
||||
- 复杂块移动阻断矩阵。
|
||||
- stale revision / stale blockRevisionRef / idempotency 重放。
|
||||
|
||||
明确不做:
|
||||
|
||||
- 不新增跨页面 AI agent 工作流。
|
||||
- 不新增复杂 AI 自动化功能面。
|
||||
- 不把 `@blocknote/xl-ai` 或 BlockNote `AIExtension` 作为 runtime dependency。
|
||||
- 不让 AI 写入绕过 `dryRun/idempotencyKey/revision/conflictDetectionKey/revisionRef`。
|
||||
|
||||
### P1:定向 Bug Hunt
|
||||
|
||||
目标:
|
||||
|
||||
- 不做泛泛“找 bug”,只围绕已知架构风险做定向排查。
|
||||
|
||||
优先找:
|
||||
|
||||
- Page Aggregate 回流不一致。
|
||||
- AI 块写入 conflict / stale revision。
|
||||
- File Tree `{title}.md` 与 page title sync。
|
||||
- tree stream resync / 双浏览器一致性。
|
||||
- debug / compat / fallback 是否混入主链。
|
||||
|
||||
---
|
||||
|
||||
## 3. 可执行 Checklist
|
||||
|
||||
### 3.1 Page Aggregate 单一真源
|
||||
|
||||
- [x] 盘点 `wolai-frontend/src/lib/documents/page-aggregate-loader.ts` 是否仍有 runtime fallback 或 TS builder 读取分支。
|
||||
- [x] 盘点 `wolai-frontend/src/lib/documents/page-aggregate-builder*` 的引用,确认只剩 test helper / historical adapter。
|
||||
- [x] 检查 `/api/documents/page` 仍返回明确 `410`,不参与 runtime 主链。
|
||||
- [x] 跑文档页打开 smoke,记录 `/api/page-aggregate/:id` 是首要读链。
|
||||
- [x] 新建页面后检查 Page Aggregate `identity/head/body/tree/stats` 字段完整。
|
||||
- [x] 修改标题后检查 Page Aggregate、页头、Breadcrumb、Sidebar、File Tree `{title}.md` 同步。
|
||||
- [x] 修改正文后检查 Page Aggregate `body.revision/conflictDetectionKey/blockDocument` 同步。
|
||||
- [x] 修改页面设置后检查 Page Aggregate 与 island runtime page options 同步。
|
||||
- [x] 刷新页面后检查标题、正文、页面设置不回退到旧快照。
|
||||
- [x] 破坏或暂停 Convex query,检查响应是 degraded/error,不返回伪 fixture。
|
||||
- [x] 在 `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` 勾选已验证项,并补证据路径。
|
||||
|
||||
2026-05-16 静态审计证据:
|
||||
|
||||
- `wolai-frontend/src/lib/documents/page-aggregate-loader.ts` 只通过 `loadPageAggregateFromRustSnapshot` 请求 Rust `/api/page-aggregate/:documentId`,`loadPageAggregate` 不再回退 TS builder;`buildServerBridgeRequest("/documents/page")` 只用于构造转发 header/request,不调用 Next `/api/documents/page` compat route。
|
||||
- `rg -n "buildPageAggregateFromDocumentPayloads|page-aggregate-builder" wolai-frontend/src rust scripts design --glob '!design/05-editor-mainline/reference-code/**' --glob '!node_modules/**'` 显示 runtime 非测试引用仅剩 builder 定义;代码引用只有 `wolai-frontend/src/lib/documents/page-aggregate-builder.test.ts`。
|
||||
- `wolai-frontend/src/app/api/documents/page/route.ts` 明确返回 `410`,错误文案指向 `/api/page-aggregate/:documentId`;`route.test.ts` 覆盖该行为。
|
||||
- 已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate`、`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate`、`cd wolai-frontend && pnpm test src/app/api/documents/page/route.test.ts src/lib/documents/page-aggregate-builder.test.ts`。
|
||||
- 全仓 `git diff --check` 当前被既有删除文件 `rust/spikes/leptos-tiptap-spike/trunk-8123.err` 阻断:`fatal: unable to generate checkdiff for ...`;本轮未清理该无关脏改动,已改用限定文件 diff check 复核。
|
||||
|
||||
2026-05-16 真实 3000 smoke 证据:
|
||||
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task110-page-title-single-truth-smoke.js` 通过,证据:`tmp/page-aggregate-single-truth-smoke/20260516-182244/task110.stdout.json`。该脚本创建两页、修改标题,并验证页头、Breadcrumb、Sidebar、Page Tree、File Tree 与刷新后标题一致。
|
||||
- 文档页打开与 Page Aggregate snapshot 验证通过,证据:`tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.stdout.json`,截图:`tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.png`。文档响应 `x-mnote-web-owner=mnote-web`、`x-mnote-web-shell=document`,HTML 包含 `data-page-aggregate-snapshot="mnote.page_aggregate.v1"` 与 `data-page-tree-source="page_aggregate.tree.pageSubtree"`,同一临时页 `/api/page-aggregate/:id` 回读 `schema=mnote.page_aggregate.v1`。
|
||||
- 新建页面 Page Aggregate 字段完整性验证通过,证据:`tmp/page-aggregate-single-truth-smoke/20260516-182244/page-aggregate-fields.stdout.json`。`identity/head/body/tree/stats` 全部为 true,`body.blockProjectionVersion=1`,`projectionSource=documents.content`。
|
||||
- 说明:曾尝试在浏览器 network 中直接捕获 `/api/page-aggregate/:id`,证据 `page-open-network.stderr.log`;当前 Rust SSR 主入口会直接把 Page Aggregate snapshot 写入 HTML,浏览器侧不必出现该 API 请求,因此该尝试不作为失败验收项。
|
||||
|
||||
2026-05-16 正文写入后 body 同步证据:
|
||||
|
||||
- 新增并运行 `scripts/task-page-aggregate-body-sync-smoke.js`,通过真实 3000 文档页输入正文,等待 `/api/documents/save` 成功后轮询 `/api/page-aggregate/:id`。
|
||||
- 证据:`tmp/page-aggregate-body-sync-smoke/mp87mgz7.json`,截图:`tmp/page-aggregate-body-sync-smoke/mp87mgz7.png`,stdout:`tmp/page-aggregate-body-sync-smoke/latest.stdout.json`。
|
||||
- 验证结果:`body.revision` 从 `0` 更新到 `1`,`body.conflictDetectionKey` 从 `tree_1778927703753_1:0` 更新到 `tree_1778927703753_1:1`,`body.blockProjectionVersion=1`,`projectionSource=documents.content`,`blockDocument.blocks[0]` 回读到文本 `Page Aggregate body sync mp87mgz7` 与 `revisionRef=pageRev:1:block:block_1:hash:fnv1a64:208e0e63c853eef5`。
|
||||
- 配套测试已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command`、`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_projects_legacy_content_to_block_document`。
|
||||
|
||||
2026-05-16 页面设置写入后 pageOptions 同步证据:
|
||||
|
||||
- 新增并运行 `scripts/task-page-aggregate-options-sync-smoke.js`,通过真实 3000 文档页打开页面设置,依次修改 `wideLayout=true`、`smallText=true`、`layoutDensity=compact`,等待 `/api/documents/options` 返回 `page.layout.updateOptions` 后轮询 `/api/page-aggregate/:id`。
|
||||
- 证据:`tmp/page-aggregate-options-sync-smoke/mp87y6j3.json`,截图:`tmp/page-aggregate-options-sync-smoke/mp87y6j3.png`,stdout:`tmp/page-aggregate-options-sync-smoke/latest.stdout.json`。
|
||||
- 验证结果:Page Aggregate `layout.pageOptions` 依次回读到 `wideLayout=true`、`smallText=true`、`layoutDensity=compact`;运行时 DOM 同步为 `data-page-wide-layout="true"`、`data-page-small-text="true"`、`data-layout-density="compact"`,island `editorRoot` 与 `.editor-surface` 同步,字号从 `16px` 到 `15px`,段落间距从 `8px` 到 `4px`。
|
||||
- 配套测试已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options`、`cd wolai-frontend && pnpm test src/lib/documents/page-command-client.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx src/lib/documents/page-option-semantics.test.ts`。
|
||||
|
||||
2026-05-16 刷新后标题 / 正文 / 页面设置不回退证据:
|
||||
|
||||
- 新增并运行 `scripts/task-page-aggregate-refresh-persistence-smoke.js`,同一临时页内依次通过 UI 写入标题、正文和页面设置,等待 Page Aggregate 回读最新 `head/body/layout` 后刷新页面,再断言页头、正文 DOM、页面设置控件、runtime DOM 与 `/api/page-aggregate/:id` 均保持最新值。
|
||||
- 证据:`tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`,截图:`tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png`,stdout:`tmp/page-aggregate-refresh-persistence-smoke/latest.stdout.json`。
|
||||
- 验证结果:刷新前后 `head.title=Page Aggregate refresh mp88fr6k`、`body.revision=1`、`body.conflictDetectionKey=tree_1778929070007_1:1`、`blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k`、`layout.pageOptions.wideLayout/smallText/layoutDensity=true/true/compact`;刷新后页头标题、`.ProseMirror` 正文、设置控件、`documentElement`、`.document-shell`、island root 和 `.editor-surface` 均保持最新值。
|
||||
- 修复点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 将 `initializePageUiSurfaces` 延后到 `DOMContentLoaded` 后执行,避免 layout 脚本早于嵌入 Page Aggregate JSON / island DOM 完成时把 runtime 属性按默认 pageOptions 应用。
|
||||
- 缺陷记录:`bugs/05-editor-mainline/done/5-12-page-options-refresh-runtime-attrs-v1.md`。
|
||||
- 配套测试已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options`。
|
||||
|
||||
2026-05-16 Convex query 失败时不返回伪 fixture 证据:
|
||||
|
||||
- 在 `rust/crates/mnote-web/src/routes/web_shell.rs` 补充负向 route 测试,构造隔离配置 `convex_url=http://127.0.0.1:9`、`allow_dev_fixtures=false`、`query_fixtures_json=None`,模拟 Convex query 不可达。
|
||||
- `/api/page-aggregate/doc_1?workspaceId=ws_demo` 返回 `503 SERVICE_UNAVAILABLE`、`x-error-code=convex_unavailable`、`x-error-phase=query_send`、`x-upstream-service=convex`,body 为 `ok=false/code=convex_unavailable`,且没有 `schema` / `result`。
|
||||
- `/documents/doc_1?workspaceId=ws_demo` 返回同类错误 JSON,HTML body 不包含 `mnote.page_aggregate.v1`、`data-mnote-dev-fixture`、`data-page-aggregate-snapshot`。
|
||||
- 验证命令已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_errors_without_convex_or_fixture -- --nocapture`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_errors_without_convex_or_fixture -- --nocapture`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate`。
|
||||
|
||||
验证命令建议:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate
|
||||
node scripts/task110-page-title-single-truth-smoke.js
|
||||
node scripts/task-page-aggregate-body-sync-smoke.js
|
||||
node scripts/task-page-aggregate-options-sync-smoke.js
|
||||
node scripts/task-page-aggregate-refresh-persistence-smoke.js
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_errors_without_convex_or_fixture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_errors_without_convex_or_fixture
|
||||
```
|
||||
|
||||
### 3.2 Page Block AI Tooling
|
||||
|
||||
- [ ] 以 `7-10` 为工具执行清单、`7-11` 为 AI runtime 基础设计,逐 phase 检查已勾选项是否都有代码、测试、smoke 证据。
|
||||
- [x] 给 `mnote.doc.fetch scope=selection` 补真实选区上下文输入和返回结构。
|
||||
- [x] 给 `mnote.doc.fetch format=page_xml` 补最小 PageXML 输出。
|
||||
- [x] 给 `mnote.block.fetch format=text/page_xml` 补格式分支。
|
||||
- [x] 给 tool manifest 补 `annotations`,区分 readonly / destructive / requiresApproval / selectionEffect。
|
||||
- [x] 定义 `mnote.page_ai_context.v1`,明确 context 只来自 Page Aggregate projection。
|
||||
- [ ] 定义 `mnote.page_ai_review_session.v1`,明确 accept / reject / retry / abort 语义。
|
||||
- [ ] 给 `mnote.block.insert_after` 补多块插入限制和返回 inserted block ids。
|
||||
- [ ] 给 `mnote.block.move_after` 补标题带子块阻断 smoke。
|
||||
- [ ] 给 `mnote.block.move_after` 补列表项阻断 smoke。
|
||||
- [ ] 给 `mnote.block.move_after` 补表格 / mindmap / resource 阻断 smoke。
|
||||
- [x] 给 `mnote.block.replace` 补 stale revision 失败用例。
|
||||
- [x] 给 `mnote.block.replace` 补 stale blockRevisionRef 失败用例。
|
||||
- [x] 给写工具补重复 idempotencyKey 的端到端用例。
|
||||
- [x] 检查 `mnote.page.save` 在 manifest / UI 中继续标为页面级兜底,不显示为精确块编辑主入口。
|
||||
- [ ] 检查任何新增 AI surface 是否只是基础 review/context/tooling 验收,不是新功能扩展。
|
||||
- [ ] 更新 `design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md` 的每个 phase 证据。
|
||||
- [ ] 更新 `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` 的 Hermes 工具路由与审阅面 checklist。
|
||||
|
||||
2026-05-16 Page Block AI context / format focused smoke 证据:
|
||||
|
||||
- 新增并运行 `scripts/task-page-block-ai-context-format-smoke.js`,通过真实 3000 + 测试账号创建临时页,用 `mnote.page.save` 初始化 `p_1/p_2/p_3`,再验证 Hermes mnote tools 的 context / format / annotations / selection guard。
|
||||
- 证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`,截图:`tmp/page-block-ai-context-format-smoke/mp8ddr4n-page.png`。
|
||||
- 验证结果:
|
||||
- `GET /api/hermes/tools/mnote/manifest` 中 `mnote.doc.fetch`、`mnote.block.fetch`、`mnote.block.replace`、`mnote.page.save` 均包含 `readonly/destructive/idempotent/requiresApproval/approvalMode/runtimeOwner/writeOwner/selectionEffect`;`mnote.page.save` 明确 `destructive=true`、`approvalMode=yolo`,继续定位为页面级粗粒度兜底。
|
||||
- `mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml` 返回 `schema=mnote.page_ai_context.v1`、`allowedTargetBlockIds=["p_2"]`、`revision/conflictDetectionKey`、`revisionRef`,`content` 只包含 `p_2` 与 `第二段 mp8ddr4n`,不包含未选中的 `p_1/p_3`。
|
||||
- `mnote.doc.fetch scope=selection format=text` 返回 `[p_2] 第二段 mp8ddr4n`,不包含未选中块。
|
||||
- `mnote.block.fetch blockId=p_2 format=page_xml/text` 返回目标块、`revisionRef` 和同父级 before/after 上下文 `p_1/p_3`。
|
||||
- `mnote.doc.apply_block_ops dryRun=true allowedTargetBlockIds=["p_2"]` 尝试 replace `p_1` 被拒绝,HTTP `400`,错误码 `mnote_block_target_out_of_scope`。
|
||||
- 边界:本轮只验证 `mnote.doc.apply_block_ops` 的 selection scope guard;单个 `mnote.block.replace/insert_after/move_after` 尚未校验 `allowedTargetBlockIds`,不能据此勾选完整 selection 写保护矩阵。
|
||||
- 配套验证已通过:`node --check scripts/task-page-block-ai-context-format-smoke.js`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture`、`MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-context-format-smoke.js`。
|
||||
|
||||
2026-05-16 Page Block AI stale / idempotency focused smoke 证据:
|
||||
|
||||
- 新增并运行 `scripts/task-page-block-ai-conflict-idempotency-smoke.js`,通过真实 3000 + 测试账号创建临时页,用 `mnote.page.save` 初始化 `p_1/p_2`,再围绕 `mnote.block.replace` 验证冲突和幂等安全边界。
|
||||
- 证据:`tmp/page-block-ai-conflict-idempotency-smoke/mp8dyqiq.json`,截图:`tmp/page-block-ai-conflict-idempotency-smoke/mp8dyqiq-page.png`。
|
||||
- 验证结果:
|
||||
- 首次 `mnote.block.replace` 携带最新 `revision/conflictDetectionKey/blockRevisionRef/idempotencyKey` 成功写入 `p_2`,返回 `commandName=page.body.save` 与 `commandId=page_body_save_req_1778938353824_11`。
|
||||
- 使用同一 `idempotencyKey=idem_conflict_replace_mp8dyqiq` 再次调用 `mnote.block.replace`,即使请求 content 改成不同文本,也 replay 同一 `commandId`,Page Aggregate 回读 `p_2` 仍为首次写入文本,`revisionAfterFirst=2`、`revisionAfterReplay=2`,确认不重复写入。
|
||||
- 使用旧 `revision/conflictDetectionKey` 调用 `mnote.block.replace` 返回 HTTP `400`、错误码 `mnote_tool_conflict`,正文保持首次写入结果。
|
||||
- 使用最新 `revision/conflictDetectionKey` 但旧 `blockRevisionRef` 调用 `mnote.block.replace` 返回 HTTP `400`、错误码 `mnote_tool_conflict`,正文保持首次写入结果。
|
||||
- 边界:本轮只覆盖 `mnote.block.replace` 直接 tool executor 的 stale revision / stale blockRevisionRef / idempotency replay;不代表 `mnote.block.insert_after` 幂等矩阵、所有写工具幂等矩阵、review session accept stale 或 accept/reject/retry/abort 已完成。
|
||||
- 配套验证已通过:`node --check scripts/task-page-block-ai-conflict-idempotency-smoke.js`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture`、`MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-conflict-idempotency-smoke.js`。
|
||||
|
||||
验证命令建议:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime editor_document
|
||||
node scripts/task-page-block-ai-tools-smoke.js
|
||||
```
|
||||
|
||||
### 3.3 Tree Command Cutover
|
||||
|
||||
- [x] `rg -n "documents\\.create|documents\\.title\\.update|documents\\.move|documents\\.archive|documents\\.restore" rust wolai-frontend` 盘点旧命令引用。
|
||||
- [x] 将仍在 runtime 主链的旧命令按 owner 分类:必须迁移、兼容保留、测试 fixture。
|
||||
- [x] 确认页面新建默认输出 `tree.node.create` 或正式 tree command。
|
||||
- [x] 确认页面重命名默认输出 `tree.node.rename` 或正式 page/tree command。
|
||||
- [x] 确认移动页面默认输出 `tree.subtree.move`。
|
||||
- [x] 确认归档 / 恢复 / 永久删除的 command family 与 resource lifecycle 设计一致。
|
||||
- [x] 对 compat alias 返回增加 owner / deprecated 标识,避免被当作主链。
|
||||
- [x] 更新 `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md` 或新增后续 process checklist。
|
||||
|
||||
2026-05-16 Tree Command Cutover 静态盘点与 alias 标识证据:
|
||||
|
||||
- 只读盘点命令:`rg -n "documents\\.(create|title\\.update|move|archive|restore|delete|purge|copy_tree)" rust wolai-frontend scripts --glob '!node_modules/**'`。
|
||||
- 前端主链:`wolai-frontend/src/lib/documents/tree-command-client.ts` 的新建、重命名、移动、归档、恢复、永久删除和复制均 POST `/api/tree/commands`,返回 meta 使用 `TREE_COMMAND_PROTOCOL.*.preferredCommandName`;`TREE_COMMAND_PROTOCOL` 中的 `documents.*` 只保留为 `compatCommandName`。
|
||||
- Next tree command route:`wolai-frontend/src/app/api/tree/commands/route.ts` 在 create / move / rename / archive / restore 分支分别构造 `tree.node.create`、`tree.subtree.move`、`tree.node.rename`、`tree.node.archive`、`tree.node.restore`。
|
||||
- Rust Web tree route:`rust/crates/mnote-web/src/routes/tree.rs` 的 `create_command_wire` 输出 `tree.node.create`、`tree.node.rename`、`tree.subtree.move`、`tree.node.archive`、`tree.node.restore`、`tree.node.purge`、`tree.subtree.copy`;`tree_command` route 只接收 action,不接收旧 `documents.*` command name 作为主链输入。
|
||||
- 兼容保留:`bridge-runtime` 仍接受 `documents.create/title.update/move/delete/restore/purge/copy_tree`,但本轮已在对应 execution plan 的 `args_json.commandProtocol` 增加 `family=tree`、`owner=rust-runtime-kernel`、`preferredCommandName`、`compatCommandName`、`deprecatedAlias`;旧 `documents.*` alias 会标记 `deprecatedAlias=true`。
|
||||
- Transport 边界:`rust/crates/mnote-web/src/transport/convex.rs` 发送给 Convex legacy mutation 前会剥离 `commandProtocol`、`streamDeltaHint`、`domainEventHint`、`domainEventPlan(s)`,避免 legacy validator 把审计字段当写入参数。
|
||||
- 仍保留为后续兼容收口点:`wolai-frontend/src/app/api/documents/create-child/route.ts` / `page-command-adapter.ts` 仍构造 `documents.create`,当前分类为 compat-only;`mnote-cli` 仍有历史 `documents.*` CLI 构造,不属于 3000 主交互链。
|
||||
|
||||
2026-05-16 Tree Command Cutover 验证命令:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_ -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
|
||||
```
|
||||
|
||||
验证命令建议:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_command
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command
|
||||
node scripts/task122-rust-web-create-page-ui-smoke.js
|
||||
```
|
||||
|
||||
### 3.4 Tree Realtime Live Cache
|
||||
|
||||
- [x] 盘点 Sidebar、Page Tree、File Tree、page subtree 仍依赖 query/refetch/freshness 补偿的位置。
|
||||
- [x] 确认 `/api/tree/events` snapshot / delta / resync payload 覆盖 page/file/resource row。
|
||||
- [x] 新建页面后,双浏览器 A/B 检查另一端无需刷新出现页面。
|
||||
- [x] 重命名页面后,双浏览器 A/B 检查 Sidebar / Breadcrumb / File Tree 一致更新。
|
||||
- [x] 移动页面后,双浏览器 A/B 检查 tree order 不回闪。
|
||||
- [x] 删除 / 恢复后检查 trash 与主树事件一致。
|
||||
- [x] 断开 SSE 后恢复,检查 resync 能把 UI 拉回正确状态。
|
||||
- [x] 更新 `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` 的已验证项。
|
||||
|
||||
2026-05-16 Tree Realtime Live Cache 静态盘点与 payload 覆盖证据:
|
||||
|
||||
- 只读审查确认 React `AppLayoutShell` 同时接入 `useSidebarData` 与 `useSidebarTreeStream`,`usePreferredSidebarSnapshot` 负责 freshness 仲裁;Page Tree / File Tree 通过 preferred snapshot 消费 `kernelSidebarTree` 与 `kernelFileTreeProjection`。
|
||||
- 仍未统一的补偿链:`useSidebarData` 还保留 Convex query、HTTP `/api/sidebar` fallback 与手动 `refetch`;mutation 后仍有 `refreshTree` / `sidebarQuery.refetch`;Rust SSR shell 与 React hook 目前各自可建立 EventSource;page subtree 仍从 Page Aggregate client state 派生,不应误标为已统一 live cache。
|
||||
- Rust stream 盘点确认 workspace snapshot 已同时加载 `KernelProjectionKind::SidebarTree` 与 `KernelProjectionKind::FileTree`,SSE payload 的 `data.dataset.kernel_sidebar_projection` / `data.dataset.kernel_file_tree_projection` 覆盖 page/file row;subtree snapshot 仍只覆盖 `page_tree`,不含 file tree projection。
|
||||
- 本轮新增 `stream_change_preserves_remove_asset_delta_fields`,确认 `tree.resource.delete` 的 `remove_asset` delta 字段保真;`structural_delta_requires_projection_snapshot` 已覆盖 `remove_asset` 需要 projection snapshot,避免资源删除类事件只靠局部 patch。
|
||||
- 本轮强化 `scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`:解析 SSE `snapshot` data,断言 `kind=snapshot`、`stream=workspace`、`projection=sidebar_tree`、`x-mnote-tree-stream-owner=rust-web`,并确认 workspace snapshot 中有 `kernel_sidebar_projection`、`kernel_file_tree_projection` 和临时页 `doc:<documentId>` file tree row。
|
||||
|
||||
2026-05-16 Tree Realtime Live Cache 验证命令:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web stream_change_preserves_remove_asset_delta_fields -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web structural_delta_requires_projection_snapshot -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::stream_support -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events -- --nocapture
|
||||
cd wolai-frontend && pnpm test src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/lib/tree-stream/use-sidebar-tree-stream.test.tsx
|
||||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js
|
||||
```
|
||||
|
||||
3000 smoke 证据:
|
||||
|
||||
- `tmp/tree-live-cache-smoke/20260516-task123/task123.stdout.json`,结果 `owner=rust-web`、`stream=/api/tree/events`、`snapshotProjection=sidebar_tree`、`fileTreeRows=59`。
|
||||
|
||||
2026-05-16 双浏览器 no-refresh 验证与 purge 修复证据:
|
||||
|
||||
- 复用 `scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js` 做真实 3000 双浏览器验证:A 端通过真实 auth + `/api/tree/commands` 创建页面、归档、恢复、彻底删除与清空垃圾箱;B 端同时打开 File Tree 与 Trash,断言目标行无刷新出现 / 消失,且 `navigationEvents` 在初始打开后为空。
|
||||
- 修复前该 smoke 在 `purge-visible-on-b` 前失败:`documents:purge` 收到 artifact-only 字段 `commandProtocol`,Convex legacy validator 返回 `ArgumentValidationError: Object contains extra field commandProtocol`。
|
||||
- 修复点:`rust/crates/mnote-web/src/transport/convex.rs` 将 `tree.node.purge` / `documents.purge` 加入 `strip_tree_artifact_fields` 剥离范围,避免 compat mutation 接收 tree command audit 字段。
|
||||
- 缺陷记录:`bugs/04-tree-domain/done/4-41-tree-node-purge-command-protocol-leaks-to-convex-v1.md`。
|
||||
- 修复后 `task432` 通过,证据 `tmp/tree-live-cache-smoke/20260516-task432/result.json`:`ok=true`,`create-visible-on-b` 通过 `tree:resync` 出现在 B 端 File Tree;`archive-visible-on-b` 通过 `tree:delta remove_document` 同步 File Tree 与 Trash;`restore-visible-on-b` 通过 `tree:delta upsert_document` 同步恢复;`purge-visible-on-b` 与 `empty-trash-visible-on-b` 通过 `tree:resync` 拉回正确状态。
|
||||
|
||||
2026-05-16 purge 修复验证命令:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
|
||||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js
|
||||
```
|
||||
|
||||
2026-05-16 双浏览器 rename live cache 修复与验证记录:
|
||||
|
||||
- 新增 regression smoke:`scripts/task446-tree-rename-dual-browser-live-smoke.js`。脚本使用 A/B 独立 browser context,A 端通过正式 `/api/tree/commands` 创建并执行 `{ action: "rename", workspaceId, documentId, title }`,B 端分别保持目标文档页与 File Tree 页面不刷新,记录 `tree:snapshot/tree:delta/tree:resync`、`/api/tree/events` 请求、DOM 状态、导航事件与截图路径。
|
||||
- RED 证据:修复前真实 3000 smoke 失败,B 端已收到 `tree:delta`、`op=upsert_document`、`liveApplied=delta`、`liveError=""`,Sidebar / Page Tree 与 File Tree `{renamedTitle}.md` 已更新,但当前打开目标文档页的 `titleInputValue`、Breadcrumb 与 `document.title` 仍停留旧标题,导致 `waitForBDocumentRename` 超时。
|
||||
- 根因:Rust SSR 文档 shell 的 `updateTitleEverywhere` 已更新 Page Tree / File Tree 行标题,但 title-only `upsert_document` 未同步当前文档页 chrome(页头标题输入框、Breadcrumb、`document.title`)。
|
||||
- 修复点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 的 `updateTitleEverywhere(documentId, title)` 现在同步当前文档 chrome,并保持 scoped tree row 更新;字符串合同单测新增当前页 title input 与 Breadcrumb selector 覆盖。
|
||||
- 构建阻断补齐:当前工作区已有 `AppState.editor_actor` / `AppConfig.enable_editor_actor` 脏改动但缺少真实 `editor_actor.rs` 文件,导致 mnote-web 无法编译启动;本轮把 misplaced 的 `EditorRuntimeActor` 实现补到 `rust/crates/mnote-web/src/editor_actor.rs`,并补齐测试配置中的 `enable_editor_actor` 字段,以恢复 3000 验证入口。
|
||||
- GREEN 证据:重新启动最新 `desktop:hot` 后运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task446-tree-rename-dual-browser-live-smoke.js` 通过。证据 `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-document-after-rename.png` 与 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-filetree-after-rename.png`。
|
||||
- 验证结果:B 文档页 `documentTitle/titleInputValue/breadcrumbTitle/sidebarTitle` 均为新裸标题;B File Tree `fileTreeTitle={renamedTitle}.md`;两端 `liveApplied=delta`、`liveError=""`;rename 后 `navigationEvents=[]`,确认 B 端无刷新 / 无导航。
|
||||
- 缺陷记录:`bugs/05-editor-mainline/done/5-13-tree-rename-live-document-chrome-stale-v1.md`。
|
||||
|
||||
2026-05-16 双浏览器 move order live cache 修复与验证记录:
|
||||
|
||||
- 新增 regression smoke:`scripts/task447-tree-move-order-dual-browser-live-smoke.js`。脚本使用 A/B 独立 browser context,A 端创建同父级 `A/B/C` 后执行正式 `/api/tree/commands`:`{ action: "move", documentId: C, parentId: root, sortOrder: 1 }`,B 端分别保持目标文档页与 File Tree 页面不刷新,记录 direct child order、tree events、导航事件与截图路径。
|
||||
- RED 证据:合法 `sortOrder=1` 下修复前真实 3000 smoke 失败。B 端收到 `tree:delta move_document`,payload 含 `sortOrder=1`,`liveApplied=delta` 且 `liveError=""`,但 Page Tree / File Tree DOM 顺序仍为 `A/B/C`,导致 `waitForExpectedOrder` 超时。
|
||||
- 根因:Rust SSR 文档 shell 的 `applyMoveDocumentDelta(data)` 未读取 `sortOrder`,`moveDocumentRowForMode(mode, documentId, parentId)` 固定 `appendChild` 到目标父节点末尾;`tree:local-command` optimistic move 分支也未传 `body.sortOrder`。
|
||||
- 修复点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 增加 `sortOrderFromDelta` 与 `insertTreeNodeAtSortOrder`,让 live delta 与 local command move 按目标父节点直系 sibling index 插入;`rust/crates/mnote-web/src/routes/tree.rs` 补充 route/artifact 单测断言 `sortOrder` 保留在 result 与 `streamDelta`。
|
||||
- GREEN 证据:重启最新 `desktop:hot` 后运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task447-tree-move-order-dual-browser-live-smoke.js` 通过。证据 `tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-document-after-move.png` 与 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-filetree-after-move.png`。
|
||||
- 验证结果:B 文档页与 B File Tree 页面中的 Page Tree / File Tree direct child order 均从 `A/B/C` 变为 `A/C/B`;两端 `liveApplied=delta`、`liveError=""`;move 后 `navigationEvents=[]`,确认无刷新 / 无导航。
|
||||
- 缺陷记录:`bugs/04-tree-domain/done/4-42-tree-move-live-delta-sort-order-ignored-v1.md`。
|
||||
|
||||
2026-05-16 SSE resync / reconnect recovery 验证记录:
|
||||
|
||||
- 新增 `scripts/task448-tree-resync-recovery-dual-browser-smoke.js`,通过 B 端 EventSource URL 注入 `pollMs=5000`,A 端在同一 poll 间隔内连续创建两个子页,强制 `/api/tree/events` 进入非单条 delta 的 `event: resync` 分支。
|
||||
- `task448` 验证结果:B 文档页与 B File Tree 页面均收到 `tree:resync`,`liveApplied=resync`、`liveError=""`,Page Tree / File Tree direct child order 从仅有初始子页恢复为包含新增 `b/c` 两个子页,`navigationEvents=[]`。证据 `tmp/tree-live-cache-smoke/20260516-task448-resync/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-document-after-resync.png` 与 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-filetree-after-resync.png`。
|
||||
- 新增 `scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js`,通过 Playwright `context.setOffline(true)` 模拟 B 端 SSE 断线,A 端离线期间连续创建两个子页,B 端恢复在线后等待 EventSource 恢复事件把 UI 拉回最新。
|
||||
- `task449` 验证结果:B 文档页与 B File Tree 页面恢复后 `liveStatus=connected`、`liveApplied=resync`、`liveError=""`,新增 `b/c` 子页出现在 Page Tree 与 File Tree,`navigationEvents=[]`。证据 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-document-after-reconnect.png` 与 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-filetree-after-reconnect.png`。
|
||||
- 说明:当前 Rust SSR controller 的真实恢复合同是“断线期间错过多条变化后,恢复时通过 snapshot/resync 类完整投影拉回 UI”;本轮实测恢复事件为 `tree:resync`,不是浏览器刷新或导航。
|
||||
|
||||
验证命令建议:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events
|
||||
node --check scripts/task447-tree-move-order-dual-browser-live-smoke.js
|
||||
node --check scripts/task448-tree-resync-recovery-dual-browser-smoke.js
|
||||
node --check scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
|
||||
node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js
|
||||
node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js
|
||||
node scripts/task446-tree-rename-dual-browser-live-smoke.js
|
||||
node scripts/task447-tree-move-order-dual-browser-live-smoke.js
|
||||
node scripts/task448-tree-resync-recovery-dual-browser-smoke.js
|
||||
node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
|
||||
```
|
||||
|
||||
### 3.5 定向 Bug Hunt
|
||||
|
||||
- [ ] 建立 bugs 分类:Page Aggregate 问题归 `bugs/05-editor-mainline/process/`。
|
||||
- [ ] Tree command / realtime / File Tree 问题归 `bugs/04-tree-domain/process/`。
|
||||
- [ ] AI tools 问题归 `bugs/07-ai/process/`。
|
||||
- [ ] 每个 bug 必须包含复现步骤、期望、实际、证据截图或 JSON、owner 判断。
|
||||
- [ ] 先补最小 failing smoke,再修实现。
|
||||
- [ ] 修复后移动到对应 `done/`,并记录验证命令。
|
||||
|
||||
重点 bug 方向:
|
||||
|
||||
- [ ] Page Aggregate 与页头标题不一致。
|
||||
- [ ] Page Aggregate 与 File Tree `{title}.md` 不一致。
|
||||
- [ ] AI 写入成功但 Page Aggregate 回读旧内容。
|
||||
- [ ] stale revision 未阻断写入。
|
||||
- [ ] tree stream 断线恢复后 UI 停在旧快照。
|
||||
- [ ] 旧 compat / debug route 在 3000 首屏被误用。
|
||||
|
||||
### 3.6 2026-05-16 暂停交接进展摘要
|
||||
|
||||
本轮按用户要求暂停继续实现,只记录当前进展与恢复点;未继续推进 `3.2 Page Block AI Tooling` 的实现或 smoke。
|
||||
|
||||
已完成到可继续接力的阶段:
|
||||
|
||||
- `3.1 Page Aggregate 单一真源` 已完成本 checklist 中的当前验证项,并补齐真实 3000 smoke、Rust route 测试、前端单测和失败降级证据。关键证据包括 `tmp/page-aggregate-single-truth-smoke/20260516-182244/`、`tmp/page-aggregate-body-sync-smoke/mp87mgz7.json`、`tmp/page-aggregate-options-sync-smoke/mp87y6j3.json`、`tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`。
|
||||
- `3.3 Tree Command Cutover` 已完成当前收口:3000 主链使用 `tree.*` preferred command,`documents.*` 保留为 compat alias,并在 Rust execution plan / artifact 中标记 owner、preferredCommandName、compatCommandName、deprecatedAlias。
|
||||
- `3.4 Tree Realtime Live Cache` 已完成本轮重点验证与修复:snapshot/delta/resync payload 覆盖、双浏览器新建 / 归档 / 恢复 / purge、rename 当前文档 chrome 同步、move sortOrder live 排序、SSE resync 与 reconnect recovery。
|
||||
- 本轮新增或更新的 Tree Realtime 关键证据:
|
||||
- `tmp/tree-live-cache-smoke/20260516-task432/result.json`
|
||||
- `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`
|
||||
- `tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json`
|
||||
- `tmp/tree-live-cache-smoke/20260516-task448-resync/result.json`
|
||||
- `tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json`
|
||||
- 本轮已归档的缺陷:
|
||||
- `bugs/04-tree-domain/done/4-41-tree-node-purge-command-protocol-leaks-to-convex-v1.md`
|
||||
- `bugs/04-tree-domain/done/4-42-tree-move-live-delta-sort-order-ignored-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-12-page-options-refresh-runtime-attrs-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-13-tree-rename-live-document-chrome-stale-v1.md`
|
||||
|
||||
当前暂停点:
|
||||
|
||||
- 已进入 `3.2 Page Block AI Tooling` 的只读审计阶段,但尚未勾选 3.2 的任何新 checkbox。
|
||||
- 已确认现有代码中 `mnote.doc.fetch` 已支持 `scope=selection`、`format=page_xml/text/markdown/json`、`schema=mnote.page_ai_context.v1`、`allowedTargetBlockIds`、truncation/warnings/continuation 等基础字段;`mnote.block.fetch` 已支持 `format=page_xml/text`;manifest 已包含 `readonly/destructive/idempotent/requiresApproval/approvalMode/runtimeOwner/writeOwner/selectionEffect` 等 annotations。
|
||||
- 已确认 `scripts/task-page-block-ai-tools-smoke.js` 覆盖基础块工具闭环,但还没有专门覆盖 `scope=selection`、`page_xml/text`、manifest annotations、选区外写入阻断、review session / conflict / idempotency 的完整验收。
|
||||
- 用户目标中提到的 `design/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md` 当前在 `design/07-ai/process/` 下不存在;实际可读历史参考位于 `design/old/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md`,当前执行口径应继续以 `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` 为准。
|
||||
|
||||
恢复时的下一步建议:
|
||||
|
||||
- 继续从 `3.2 Page Block AI Tooling` 开始,不要跳到定向 bug hunt。
|
||||
- 优先新增一个聚焦 smoke,例如 `scripts/task-page-block-ai-context-format-smoke.js`,只验证基础底座,不扩新 AI 功能:
|
||||
- manifest annotations 中 `mnote.doc.fetch` / `mnote.block.fetch` / `mnote.block.replace` / `mnote.page.save` 的 readonly、destructive、requiresApproval、selectionEffect、owner 字段。
|
||||
- `mnote.doc.fetch scope=selection selectedBlockIds=[...] format=page_xml` 只返回选区块,返回 `schema=mnote.page_ai_context.v1`、`allowedTargetBlockIds`、`revisionRef`。
|
||||
- `mnote.doc.fetch scope=selection format=text` 不包含未选中块。
|
||||
- `mnote.block.fetch format=page_xml/text` 返回稳定块内容和 `revisionRef`。
|
||||
- 选区冻结后写工具尝试修改 `allowedTargetBlockIds` 外块时返回 `mnote_block_target_out_of_scope`。
|
||||
- 配套验证建议从这些命令开始:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture
|
||||
node --check scripts/task-page-block-ai-context-format-smoke.js
|
||||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-context-format-smoke.js
|
||||
```
|
||||
|
||||
交接注意事项:
|
||||
|
||||
- 当前工作区有大量既有未提交改动和文件迁移,继续执行时必须只碰当前小项相关文件,不要回滚、删除或清理无关脏文件。
|
||||
- `git diff --check` 全仓可能仍会被既有删除文件阻断;验证本轮改动时可先用限定路径或 `git diff --no-index --check /dev/null <new-file>`。
|
||||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs` 中已有 Tree Realtime rename / move order 相关改动,同时可能混有先前 editor actor 相关改动;继续改动前必须重新读 diff,避免覆盖用户或前序 agent 的更改。
|
||||
- `3000` 相关 smoke 必须继续保留 JSON / 截图证据路径,并在本 checklist 与对应 `design/07-ai` 或 `bugs/*` 文档同步记录。
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前不优先做
|
||||
|
||||
- [ ] 不优先增加新的编辑器 UI 大功能。
|
||||
- [ ] 不优先新增 AI Agent 工作流功能。
|
||||
- [ ] 不优先扩展 AI 功能面;AI 只做基础上下文、工具合同、审阅会话、冲突与回滚。
|
||||
- [ ] 不优先大规模替换 Convex。
|
||||
- [ ] 不优先重写 `leptos-tiptap` 输入层。
|
||||
- [ ] 不把 `mnote.page.save` 包装成精确块编辑长期方案。
|
||||
- [ ] 不在 compat route 继续扩写长期业务语义。
|
||||
- [ ] 不把 BlockNote / Tiptap AI runtime 作为 mnote runtime 依赖。
|
||||
|
||||
---
|
||||
|
||||
## 5. 完成定义
|
||||
|
||||
本 checklist 不能迁入 `done/`,直到:
|
||||
|
||||
- [ ] Page Aggregate 的标题 / 正文 / 页面设置 / page tree / AI fetch 回读形成同一组可验证真相。
|
||||
- [ ] `7-10` 的 Page Block AI Tooling 剩余矩阵完成或明确拆出后续 process 文档。
|
||||
- [ ] `7-11` 的 AI 基础 runtime 口径完成:context、format、manifest annotations、review session、状态机边界都有代码或明确后续 checklist。
|
||||
- [ ] `tree.*` command 面对主要页面生命周期动作成为唯一 preferred runtime 主链。
|
||||
- [ ] tree realtime live cache 覆盖 Sidebar、Page Tree、File Tree、page subtree 的关键写后更新。
|
||||
- [ ] 至少一轮定向 bug hunt 完成,所有 P0/P1 blocker 已归档到 `bugs/*/done/` 或明确保留为后续 process。
|
||||
|
||||
---
|
||||
|
||||
## 6. 给后续 /goal 的持续执行 Prompt
|
||||
|
||||
```text
|
||||
/goal objective: 持续执行 /mnt/Data1T/mnote/design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md,按 P0 -> P1 顺序推进 MNOTE 架构收口、AI 基础底座和定向 bug hunt。每轮开始先读取 /home/lix/.codex/memories/PROFILE.md 与 ACTIVE.md,再读取 AGENTS.md、ARCHITECTURE.md、design/01-05-current-priority-overview.md、本 checklist、design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md 和 design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md。必须保护用户已有未提交改动,不回滚、不覆盖、不删除无关文件。优先使用多个 subagent 并行做只读审查和浏览器验证,主线程只整合证据和做小范围实现。执行顺序固定为:1) Page Aggregate 单一真源;2) tree command cutover;3) tree realtime live cache;4) AI 基础工具与审阅底座,只补 context/selection/page_xml/tool annotations/review session/conflict/idempotency,不扩新 AI 功能;5) 定向 bug hunt。每完成一个小项都要更新本 checklist 和对应 design/bugs 文档,补真实验证命令或证据路径。不要优先扩新功能,不要大改架构,不要把 compat/debug/fallback 当主链,不要把 BlockNote/Tiptap AI runtime 作为 mnote runtime 依赖。验证至少包含 git diff --check、相关 cargo test / smoke;如涉及 3000 页面,使用 mnote-tester 或浏览器自动化并保留截图/JSON 证据。
|
||||
```
|
||||
@@ -0,0 +1,255 @@
|
||||
# 09 页面 AI 快速块编辑 Runtime Review
|
||||
|
||||
> 状态:`process`
|
||||
>
|
||||
> 日期:2026-05-16
|
||||
>
|
||||
> 关联主线:
|
||||
> - `design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
> - `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
|
||||
> - `design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
> - `design/05-editor-mainline/done/5-13-page-block-identity-and-command-contract-v1.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 本轮结论
|
||||
|
||||
页面 AI 块写入的慢点不在 Rust 块工具本身,也不在 Convex 持久化本身。
|
||||
|
||||
本轮真实浏览器 smoke 显示:
|
||||
|
||||
- 旧路径:页面 AI 先调用 `/api/page-ai/block-edit-workflow`,快路径模型阶段耗时约 `7695ms`,随后因模型输出的块定位未命中 projection,`doc_apply_block_ops` 返回 `mnote_block_not_found`,前端继续 fallback 到 `/api/hermes/client/runs`,最终由 Hermes agent 调 `mnote.doc.apply_block_ops` 写入,总可见耗时约 `13246ms`。
|
||||
- 新路径:同一类简单中文块操作先由 mnote 本地 planner 解析为 `replace/insert_after/delete` operations,再直接调用 Rust `doc_apply_block_ops`。浏览器 smoke 回读可见耗时 `788ms`;后端日志显示 `operation_source=local_rule`、`model_ms=0`、`apply_ms=42`、`total_ms=42`,且未进入 `/api/hermes/client/runs`。
|
||||
|
||||
因此,当前要继续做的是 Hermes 工具路由、上下文冻结、dry-run/review 与 Rust 写入校验基建,而不是继续让 Hermes agent 反复执行同一个端到端动作,也不是在 mnote 内新建第二套 AI runtime。
|
||||
|
||||
---
|
||||
|
||||
## 2. 慢的直接原因
|
||||
|
||||
### 2.1 通用 agent 编排链路太长
|
||||
|
||||
旧页面 AI 编辑链路实际包含:
|
||||
|
||||
```text
|
||||
浏览器输入
|
||||
-> Hermes session/profile/tools 初始化
|
||||
-> 模型理解页面任务
|
||||
-> 模型选择工具
|
||||
-> 工具调用
|
||||
-> 回读或继续推理
|
||||
-> 最终回复
|
||||
```
|
||||
|
||||
这适合复杂任务,但不适合“把 A 替换为 B / 在 A 后插入 C / 删除 D”这类明确块操作。
|
||||
|
||||
### 2.2 模型直接产 operations 不够可靠
|
||||
|
||||
本轮快路径第一版已经绕开 Hermes agent,但仍让 DeepSeek 直接根据 `page_xml/page_text` 生成 operations。失败点是:
|
||||
|
||||
```text
|
||||
model_ms ~= 7695
|
||||
operations = 3
|
||||
apply -> mnote_block_not_found
|
||||
fallback -> Hermes agent
|
||||
```
|
||||
|
||||
说明模型输出了看似正确的操作数,但 block 定位信息没有命中当前 Page Aggregate projection。这个问题不能靠增加 smoke 次数解决,必须让 mnote runtime 在模型前后都掌握定位与校验。
|
||||
|
||||
### 2.3 失败后 fallback 放大了耗时
|
||||
|
||||
第一版前端逻辑在快路径失败时继续进入 `/api/hermes/client/runs`。这导致一次用户请求可能经历:
|
||||
|
||||
```text
|
||||
快路径模型失败成本 + Hermes agent 成功成本
|
||||
```
|
||||
|
||||
本轮已改为:只有后端明确返回 `page_ai_workflow_not_block_edit` 才允许 fallback;其他快路径错误直接在本轮 run 中失败展示,避免重复写入和重复等待。
|
||||
|
||||
---
|
||||
|
||||
## 3. 已完成的修正
|
||||
|
||||
### 3.1 页面 AI 快速块编辑 route
|
||||
|
||||
已新增并接入:
|
||||
|
||||
- `POST /api/page-ai/block-edit-workflow`
|
||||
- 文件:`rust/crates/mnote-web/src/routes/page_ai_workflow.rs`
|
||||
|
||||
该 route 负责:
|
||||
|
||||
- 接收当前页面冻结后的 `mnote.page_ai_context.v1`。
|
||||
- 为明确中文编辑语句先走本地 operation planner。
|
||||
- 无法本地解析时才调用小模型生成 operations。
|
||||
- 最终统一走 `mnote.doc.apply_block_ops`,由 Rust 生成 canonical content 并保存。
|
||||
|
||||
### 3.2 简单块操作本地 planner
|
||||
|
||||
已支持这类明确表达:
|
||||
|
||||
```text
|
||||
把「A」替换为「B」;
|
||||
在「C」后插入「D」;
|
||||
删除「E」。
|
||||
```
|
||||
|
||||
输出直接是:
|
||||
|
||||
```json
|
||||
[
|
||||
{"op":"replace","matchText":"A","content":"B"},
|
||||
{"op":"insert_after","matchText":"C","content":"D"},
|
||||
{"op":"delete","matchText":"E"}
|
||||
]
|
||||
```
|
||||
|
||||
该层的目的不是做通用自然语言理解,而是把高频、低歧义、可确定的块编辑从模型路径剥离出来。
|
||||
|
||||
### 3.3 快路径可观测性
|
||||
|
||||
后端日志已记录:
|
||||
|
||||
- workflow started/completed
|
||||
- operation source:`local_rule` 或 `model`
|
||||
- operations 数量
|
||||
- model 耗时
|
||||
- apply 耗时
|
||||
- total 耗时
|
||||
|
||||
smoke 也已在失败时写出 evidence JSON,并记录 `/api/page-ai/*` 与 `/api/hermes/client/*` 请求/响应。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证证据
|
||||
|
||||
命令:
|
||||
|
||||
```bash
|
||||
cargo test -p mnote-web page_ai_workflow -- --nocapture
|
||||
cargo test -p mnote-web hermes_tools -- --nocapture
|
||||
MNOTE_PAGE_AI_FAST_TIMEOUT_MS=60000 node scripts/task-page-ai-block-edit-workflow-smoke.js
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
- `page_ai_workflow`:2 passed。
|
||||
- `hermes_tools`:22 passed。
|
||||
- 浏览器 smoke:通过。
|
||||
|
||||
最新浏览器 evidence:
|
||||
|
||||
```text
|
||||
/mnt/Data1T/mnote/tmp/page-ai-block-edit-workflow-smoke/mp86uciu.json
|
||||
```
|
||||
|
||||
关键值:
|
||||
|
||||
```json
|
||||
{
|
||||
"timingsMs": {
|
||||
"pageAiWriteVisible": 788
|
||||
},
|
||||
"usedFastWorkflow": true,
|
||||
"usedHermesRun": false,
|
||||
"finalTexts": [
|
||||
"第一段 mp86uciu",
|
||||
"插入段 mp86uciu",
|
||||
"第二段已修改 mp86uciu"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
后端关键日志:
|
||||
|
||||
```text
|
||||
operation_source="local_rule"
|
||||
model_ms=0
|
||||
apply_ms=42
|
||||
total_ms=42
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 剩余问题
|
||||
|
||||
### 5.1 不能把本地 planner 当成完整 AI runtime
|
||||
|
||||
当前本地 planner 只覆盖低歧义中文引号表达。它证明了正确的 runtime 方向,但不是最终答案。
|
||||
|
||||
需要继续建设:
|
||||
|
||||
- PageAIContextBuilder:冻结 selection/page context,减少模型输入。
|
||||
- PageAIIntentParser:先判定是明确块操作、结构化改写、摘要问答、还是复杂编辑。
|
||||
- PageAIOperationPlanner:把明确操作转成 `mnote.doc.apply_block_ops`,复杂操作才调用小模型。
|
||||
- PageAIOperationValidator:模型输出后必须用 projection 校验 blockId/matchText/allowedTargetBlockIds。
|
||||
- PageAIApplyController:统一处理 yolo 写入、失败展示、回读验证、审计日志。
|
||||
|
||||
### 5.2 模型输出 operations 仍需修
|
||||
|
||||
当请求不能被本地 planner 解析时,仍会调用模型。该路径必须补:
|
||||
|
||||
- 输出 schema 更严格,禁止模型臆造 blockId。
|
||||
- 优先 `matchText` 或由服务端根据 text resolve block,而不是信任模型 blockId。
|
||||
- 模型输出后做 dry-run validate,不命中时不要 fallback Hermes agent 重新跑。
|
||||
- 把 validation error 反馈给用户或进入后续 clarify/retry,而不是隐式整页写。
|
||||
|
||||
### 5.3 Hermes agent 不应承担短路径编辑
|
||||
|
||||
Hermes 仍适合:
|
||||
|
||||
- 多步骤页面理解。
|
||||
- 跨页面检索。
|
||||
- 工具不可直接表达的复杂任务。
|
||||
- 外部 skill/plugin 编排。
|
||||
|
||||
但对当前页面小段落块增删改,mnote 自己的 runtime 应该在浏览器/Rust route 内完成 `intent -> operations -> apply -> readback`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一步建议
|
||||
|
||||
优先级应从“继续 smoke”切到“补 AI runtime 基建”:
|
||||
|
||||
1. `PageAIIntentParser`
|
||||
- 输入:用户 prompt、scope、selection、page context。
|
||||
- 输出:`direct_block_ops | model_block_ops | question | unsupported`。
|
||||
- 目标:不让每条简单编辑都进入 Hermes agent。
|
||||
|
||||
2. `PageAIOperationPlanner`
|
||||
- 扩展当前本地 planner。
|
||||
- 支持常见中文/英文明确表达。
|
||||
- 支持选区内“改成/润色/拆成列表”等可控操作。
|
||||
|
||||
3. `PageAIOperationValidator`
|
||||
- 所有 operations apply 前先 resolve projection。
|
||||
- 对 blockId、matchText、allowedTargetBlockIds、editable、children、revisionRef 做统一校验。
|
||||
- 失败返回结构化错误,不 fallback 通用 agent。
|
||||
|
||||
4. `PageAIApplyController`
|
||||
- 统一 yolo 模式下的写入、回读、状态展示和失败提示。
|
||||
- 后续再接 preview/review session,而不是现在把 review 作为默认阻塞。
|
||||
|
||||
5. 模型路径瘦身
|
||||
- 对必须调用小模型的任务,发送 `page_xml/text + allowed operations schema`。
|
||||
- 模型只负责生成候选 operations;最终定位、校验和写入仍由 Rust runtime 负责。
|
||||
|
||||
---
|
||||
|
||||
## 7. 当前判断
|
||||
|
||||
小段落编辑低于 10s 已经被证明可达,且当前 smoke 为 `788ms`。
|
||||
|
||||
下一阶段的关键不是再证明“能写”,而是把这条快路径产品化:
|
||||
|
||||
```text
|
||||
用户意图
|
||||
-> mnote intent/parser
|
||||
-> operation planner
|
||||
-> projection validator
|
||||
-> Rust apply
|
||||
-> readback
|
||||
-> UI 状态/审计
|
||||
```
|
||||
|
||||
Hermes agent 应从默认编辑执行器退回到复杂任务编排器。
|
||||
@@ -40,6 +40,9 @@
|
||||
- `process/` 放推进中的测试流程与对标执行稿
|
||||
9. `09-siyuan-reference/`
|
||||
- `process/` 放思源参考、借鉴边界与能力盘点稿
|
||||
10. `10-review/`
|
||||
- `done/` 放阶段性代码 / 架构审查证据和结论
|
||||
- 只作为当前实现状态证据和历史审查归档,不作为新的主线优先级入口
|
||||
|
||||
## 迁移规则
|
||||
|
||||
@@ -55,3 +58,6 @@
|
||||
- `old/`
|
||||
- 已废弃或被替代的历史稿件,标题统一标记 `[recycle]`
|
||||
- 每个大类继续按 `process/` 与 `done/` 分层
|
||||
- `design/design/`
|
||||
- Wolai / Stitch / icon 等取证素材与静态参考资产;这是当前 `design/` 目录下的素材子目录,不是主线设计稿根目录
|
||||
- 不参与 `[done]/[process]/[recycle]` 主线状态判断
|
||||
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
# 7-11 [process][recycle] BlockNote / Tiptap AI 参考与 mnote 自有 AI 工具 runtime v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`RECYCLE`。
|
||||
>
|
||||
> 回收说明(2026-05-16):
|
||||
> - 本稿的参考取证仍有价值,但“mnote 自有 AI 工具 runtime”口径容易误导后续实现继续扩出第二套页面 AI runtime。
|
||||
> - 当前长期口径已被 `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` 覆盖。
|
||||
> - 后续方向固定为:Hermes 继续作为唯一页面 AI agent runtime;mnote 只建设 Hermes 可消费的工具路由、工具 manifest、上下文冻结、dry-run/review、Rust 写入校验与 readback。
|
||||
> - 本稿仅作为 BlockNote / Tiptap 参考材料和历史判断保留,不再作为当前执行口径。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-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/done/5-13-page-block-identity-and-command-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/blocknote-ai/MNOTE-REFERENCE.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 结论
|
||||
|
||||
BlockNote AI 和 Tiptap AI Toolkit 都值得参考,但参考层级不同:
|
||||
|
||||
- Tiptap AI Toolkit 适合作为“AI tool contract”参考:`tiptapRead`、`tiptapEdit`、`tiptapReadSelection`、`toolDefinitions()`、`executeTool/streamTool`、review options。
|
||||
- BlockNote AI 适合作为“AI runtime shape”参考:`AIExtension`、`DocumentStateBuilder`、`StreamToolsProvider`、`AIRequest`、`aiMenuState`、`acceptChanges/rejectChanges/retry/abort`。
|
||||
- mnote 不能直接采用 BlockNote / Tiptap 的 editor truth。mnote 的事实源仍是 Rust kernel、Page Aggregate、EditorCommand、Convex revision/conflict key 与 Hermes audit。
|
||||
|
||||
下一步不应把 BlockNote 重新拉回运行时主链,也不应把 Tiptap Pro / BlockNote XL AI 当成硬依赖。正确方向是用它们的架构形态,打造 mnote 自己的 Rust-owned AI tool runtime。
|
||||
|
||||
---
|
||||
|
||||
## 2. 已拉取参考代码
|
||||
|
||||
已将 BlockNote 官方仓库以 sparse clone 方式拉到:
|
||||
|
||||
`/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/blocknote-ai/`
|
||||
|
||||
当前提交:
|
||||
|
||||
`c255558b2d4f2be6453c67df81bb702a1a586909`
|
||||
|
||||
保留范围:
|
||||
|
||||
- `docs/content/docs/features/ai/`
|
||||
- `examples/09-ai/`
|
||||
- `packages/xl-ai/src/`
|
||||
- `packages/xl-ai-server/src/`
|
||||
- 对应 `package.json` / `LICENSE`
|
||||
|
||||
许可证边界:
|
||||
|
||||
- `@blocknote/xl-ai` 当前标注为 `GPL-3.0 OR PROPRIETARY`。
|
||||
- 本目录只能作为 `reference-code` 研究材料。
|
||||
- 不允许未审查许可证就复制实现进入 mnote runtime。
|
||||
|
||||
---
|
||||
|
||||
## 3. BlockNote AI 可借鉴能力
|
||||
|
||||
### 3.1 AI lifecycle 状态机
|
||||
|
||||
参考:
|
||||
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/AIExtension.ts`
|
||||
|
||||
BlockNote 的 AI menu state 包含:
|
||||
|
||||
```text
|
||||
closed
|
||||
user-input
|
||||
thinking
|
||||
ai-writing
|
||||
user-reviewing
|
||||
error
|
||||
```
|
||||
|
||||
并提供:
|
||||
|
||||
- `openAIMenuAtBlock(blockId)`
|
||||
- `closeAIMenu()`
|
||||
- `invokeAI(opts)`
|
||||
- `acceptChanges()`
|
||||
- `rejectChanges()`
|
||||
- `retry()`
|
||||
- `abort(reason)`
|
||||
- `setAIResponseStatus(...)`
|
||||
|
||||
mnote 应吸收为:
|
||||
|
||||
- `PageAIController`
|
||||
- `PageAIRunState`
|
||||
- `PageAIReviewSession`
|
||||
|
||||
但状态源必须绑定 mnote 的 `documentId/workspaceId/blockId/revision/conflictDetectionKey`,不能绑定 BlockNote editor instance。
|
||||
|
||||
### 3.2 DocumentStateBuilder
|
||||
|
||||
参考:
|
||||
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/api/formats/DocumentStateBuilder.ts`
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/api/aiRequest/builder.ts`
|
||||
|
||||
BlockNote 把 AI 上下文拆成两种:
|
||||
|
||||
- 无选区:全量 document blocks + cursor position。
|
||||
- 有选区:selected blocks + whole document context;模型只能对 selection 发操作。
|
||||
|
||||
mnote 应吸收为 `PageAIContextBuilder`:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "mnote.page_ai_context.v1",
|
||||
"workspaceId": "tree_x",
|
||||
"documentId": "tree_y",
|
||||
"runId": "run_1",
|
||||
"scope": "selection",
|
||||
"revision": 12,
|
||||
"conflictDetectionKey": "body:...",
|
||||
"selectedBlocks": [],
|
||||
"contextBlocks": [],
|
||||
"allowedTargetBlockIds": []
|
||||
}
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- context 必须从 Page Aggregate block projection 生成。
|
||||
- selection 必须在 AI run 开始时冻结,不能随用户后续选区漂移。
|
||||
- `allowedTargetBlockIds` 必须作为写工具校验输入的一部分。
|
||||
|
||||
### 3.3 StreamToolsProvider
|
||||
|
||||
参考:
|
||||
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/api/formats/formats.ts`
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/api/formats/base-tools/`
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/streamTool/`
|
||||
|
||||
BlockNote 的 stream tool 把 add/update/delete block 建模为 schema + validate + executor。
|
||||
|
||||
mnote 已有对应底座:
|
||||
|
||||
- `mnote.doc.fetch`
|
||||
- `mnote.doc.find`
|
||||
- `mnote.block.fetch`
|
||||
- `mnote.doc.plan_update`
|
||||
- `mnote.block.replace`
|
||||
- `mnote.block.insert_after`
|
||||
- `mnote.block.delete`
|
||||
- `mnote.block.move_after`
|
||||
- `mnote.doc.apply_block_ops`
|
||||
|
||||
mnote 下一步要补的不是工具名,而是工具 manifest 的能力注解:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "mnote.block.replace",
|
||||
"annotations": {
|
||||
"readonly": false,
|
||||
"destructive": false,
|
||||
"idempotent": false,
|
||||
"requiresApproval": true,
|
||||
"selectionEffect": "destroy",
|
||||
"runtimeOwner": "mnote-web",
|
||||
"writeOwner": "rust-runtime-kernel"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Review / accept / reject
|
||||
|
||||
参考:
|
||||
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/AIExtension.ts`
|
||||
- `reference-code/blocknote-ai/packages/xl-ai/src/prosemirror/rebaseTool.ts`
|
||||
- `reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/api-reference/review-options.mdx`
|
||||
|
||||
BlockNote 在 editor 内通过 suggestions / fork ydoc 完成 review;Tiptap AI Toolkit 则提供 `review/preview/trackedChanges` 模式。
|
||||
|
||||
mnote 不能照搬 ProseMirror suggestion 作为事实源。mnote 应建立自己的 review session:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "mnote.page_ai_review_session.v1",
|
||||
"reviewSessionId": "review_1",
|
||||
"runId": "run_1",
|
||||
"documentId": "tree_y",
|
||||
"workspaceId": "tree_x",
|
||||
"status": "previewing",
|
||||
"baseRevision": 12,
|
||||
"baseConflictDetectionKey": "body:...",
|
||||
"operations": [],
|
||||
"diff": [],
|
||||
"warnings": [],
|
||||
"risk": "low"
|
||||
}
|
||||
```
|
||||
|
||||
状态:
|
||||
|
||||
```text
|
||||
draft
|
||||
planning
|
||||
previewing
|
||||
awaiting_user
|
||||
accepted
|
||||
rejected
|
||||
applying
|
||||
applied
|
||||
failed
|
||||
aborted
|
||||
stale
|
||||
```
|
||||
|
||||
写入规则:
|
||||
|
||||
- AI 写工具默认先生成 review session。
|
||||
- 用户 accept 后才执行 `mnote.doc.apply_block_ops` 或单个 `mnote.block.*`。
|
||||
- accept 时重新校验 `revision/conflictDetectionKey/revisionRef`。
|
||||
- reject 只关闭 session,不改变正文。
|
||||
|
||||
---
|
||||
|
||||
## 4. Tiptap AI Toolkit 可借鉴能力
|
||||
|
||||
Tiptap AI Toolkit 已在 `7-9` 中完成第一轮映射,本文补充它与 BlockNote 的组合边界。
|
||||
|
||||
| Tiptap / BlockNote 能力 | mnote 对应 | 吸收内容 | 禁止吸收 |
|
||||
| --- | --- | --- | --- |
|
||||
| `tiptapRead` | `mnote.doc.fetch` / `mnote.block.fetch` | 先读、带范围、返回 AI 友好表示 | 不把 Tiptap JSON 当长期工具格式 |
|
||||
| `tiptapEdit` | `mnote.doc.plan_update` + `mnote.block.*` | 操作列表、dry-run、diff、reviewable edit | 不让浏览器 editor command 成为事实源 |
|
||||
| `tiptapReadSelection` | `mnote.doc.fetch scope=selection` | selection-aware workflow | 不持久化 ProseMirror selection range |
|
||||
| `toolDefinitions()` | Rust Hermes manifest | schema、description、capability、annotations | 不依赖私有 npm 包 |
|
||||
| `AIExtension` | `PageAIController` | AI lifecycle、menu state、abort/retry | 不引入 BlockNote runtime |
|
||||
| `DocumentStateBuilder` | `PageAIContextBuilder` | selection/context 分离 | 不从 DOM 拼上下文 |
|
||||
| `StreamToolsProvider` | `MnoteAIToolProvider` | 工具集合按能力开放 | 不在前端直接执行正式持久化 |
|
||||
| `acceptChanges/rejectChanges` | `PageAIReviewSession` | preview -> accept/reject | 不以 ProseMirror suggestion 作为最终事实 |
|
||||
|
||||
---
|
||||
|
||||
## 5. mnote 自有 AI runtime 设计
|
||||
|
||||
### 5.1 分层
|
||||
|
||||
```text
|
||||
Page Aggregate / Rust kernel
|
||||
-> PageAIContextBuilder
|
||||
-> MnoteAIToolProvider
|
||||
-> Hermes / model runtime
|
||||
-> PageAIReviewSession
|
||||
-> EditorCommand / page.body.save
|
||||
-> Page Aggregate 回读验证
|
||||
```
|
||||
|
||||
### 5.2 组件职责
|
||||
|
||||
`PageAIContextBuilder`
|
||||
|
||||
- 输入:`workspaceId/documentId/scope/selection/blockId/query/maxBlocks`。
|
||||
- 输出:`mnote.page_ai_context.v1`。
|
||||
- 数据源:Page Aggregate block projection。
|
||||
- 负责 selection 冻结、上下文裁剪、`allowedTargetBlockIds`。
|
||||
|
||||
`MnoteAIToolProvider`
|
||||
|
||||
- 输入:profile、document capability、selection scope、feature flags。
|
||||
- 输出:可用工具 manifest。
|
||||
- 负责 `readonly/destructive/requiresApproval/selectionEffect` 等注解。
|
||||
|
||||
`PageAIController`
|
||||
|
||||
- 管理当前页面 AI run。
|
||||
- 对齐状态:`user-input/thinking/ai-writing/user-reviewing/error/aborted`。
|
||||
- 不直接写正文,只组织 context、tool calls、review session。
|
||||
|
||||
`PageAIReviewSession`
|
||||
|
||||
- 保存 plan/diff/warnings/risk/operations。
|
||||
- 提供 `accept/reject/retry/abort`。
|
||||
- accept 时走 Rust 写工具,并二次校验 revision。
|
||||
|
||||
`PageAIReviewSurface`
|
||||
|
||||
- 页面内展示 AI 结果、工具卡、diff、风险、按钮。
|
||||
- 不把 tool result 当正文。
|
||||
- 不绕过 review session 调写工具。
|
||||
|
||||
---
|
||||
|
||||
## 6. 格式策略
|
||||
|
||||
当前 mnote 不能把 HTML 作为长期 AI contract。推荐格式分三层:
|
||||
|
||||
`json`
|
||||
|
||||
- 面向工具执行。
|
||||
- 保留 block id、type、attrs、children、revisionRef。
|
||||
- 作为写工具输入和回读校验主格式。
|
||||
|
||||
`text`
|
||||
|
||||
- 面向摘要、问答、轻量改写。
|
||||
- 不可直接作为精确写入定位依据。
|
||||
|
||||
`page_xml`
|
||||
|
||||
- 面向模型理解结构。
|
||||
- 形态示例:
|
||||
|
||||
```xml
|
||||
<page id="tree_y" revision="12">
|
||||
<block id="p_1" type="paragraph" revisionRef="body:12:p_1">第一段</block>
|
||||
<block id="h_1" type="heading" level="2" revisionRef="body:12:h_1">标题</block>
|
||||
</page>
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `page_xml` 只能是 Page Aggregate projection 的序列化视图。
|
||||
- 写入仍必须转成 `mnote.block.*` 或 `mnote.doc.apply_block_ops`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 执行 checklist
|
||||
|
||||
### Phase A:参考口径冻结
|
||||
|
||||
- [x] 拉取 BlockNote AI sparse reference 到 `reference-code/blocknote-ai/`。
|
||||
- [x] 标注来源 commit 与许可证边界。
|
||||
- [x] 明确 BlockNote 只作为 AI runtime shape 参考,不作为 mnote runtime dependency。
|
||||
- [x] 明确 Tiptap AI Toolkit 只作为 tool contract 参考,不作为事实源。
|
||||
|
||||
### Phase B:PageAIContextBuilder
|
||||
|
||||
- [x] 定义 `mnote.page_ai_context.v1` schema。
|
||||
- [x] `doc.fetch` 支持 `scope=selection`。
|
||||
- [x] selection run 开始时冻结 `selectedBlockIds` / `allowedTargetBlockIds`。
|
||||
- [x] `doc.fetch` 支持 `format=text/page_xml/json`。
|
||||
- [x] `block.fetch` 支持 `format=text/page_xml/json`。
|
||||
- [ ] 大页面 context 默认裁剪,返回 `truncated/warnings/continuation`。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] AI run 期间用户改变选区,不影响本 run 的 selection context。
|
||||
- [ ] 写工具不能修改 `allowedTargetBlockIds` 外的块。
|
||||
- [ ] `page_xml` 与 JSON projection 的 block id / revisionRef 一致。
|
||||
|
||||
### Phase C:MnoteAIToolProvider
|
||||
|
||||
- [x] tool manifest 增加 `annotations`。
|
||||
- [x] 当前 yolo 模式下写工具标注 `requiresApproval=false` / `approvalMode=yolo`。
|
||||
- [x] selection scope 下,写工具自动带 `allowedTargetBlockIds` 约束。
|
||||
- [ ] profile tool toggle 与 capability annotation 同时生效。
|
||||
- [ ] manifest 输出能直接转换给 Hermes/model runtime。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 禁用工具不出现在当前 AI run 的可用工具集中。
|
||||
- [ ] 复杂块工具返回 `unsupportedReason`,不伪装成可编辑。
|
||||
- [ ] manifest 能说明工具是否 destructive / readonly / requiresApproval。
|
||||
|
||||
### Phase D:PageAIReviewSession
|
||||
|
||||
- [ ] 定义 `mnote.page_ai_review_session.v1`。
|
||||
- [ ] `mnote.doc.plan_update` 可创建 review session。
|
||||
- [ ] `mnote.doc.apply_block_ops dryRun=true` 可返回 review session draft。
|
||||
- [ ] 页面 AI UI 展示 diff、warnings、risk、blocked。
|
||||
- [ ] `accept` 执行写入前二次校验 revision/conflictDetectionKey/revisionRef。
|
||||
- [ ] `reject` 不改变正文。
|
||||
- [ ] `abort` 停止 run,关闭未提交 review session。
|
||||
- [ ] `retry` 使用最新 Page Aggregate 重新构建 context。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] accept 后 Page Aggregate 与 `mnote.doc.fetch` 都能读回变化。
|
||||
- [ ] stale revision 时 accept 被阻断,session 进入 `stale`。
|
||||
- [ ] reject 后页面正文、revision、block ids 不变化。
|
||||
|
||||
### Phase E:页面 AI 状态机
|
||||
|
||||
- [ ] 实现 `PageAIController` 状态枚举。
|
||||
- [ ] AI menu / side panel 共享同一 run state。
|
||||
- [ ] tool event 按 `toolCallId` 聚合展示。
|
||||
- [ ] 错误态支持 retry / close。
|
||||
- [ ] abort 态不会留下半写入正文。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] `thinking -> ai-writing -> user-reviewing -> accepted/rejected` 链路可见。
|
||||
- [ ] 网络失败、tool parse 失败、revision stale 三类错误可区分。
|
||||
- [ ] 页面刷新后未提交 review session 不会自动写入正文。
|
||||
|
||||
---
|
||||
|
||||
## 8. 当前优先级
|
||||
|
||||
优先级顺序:
|
||||
|
||||
1. `PageAIContextBuilder + scope=selection`
|
||||
2. `format=page_xml/text`
|
||||
3. `PageAIReviewSession`(后续可选 review 模式;当前默认 yolo 不阻塞写入)
|
||||
4. `PageAIController` 状态机与 UI surface
|
||||
5. 复杂块移动 / 删除 / 多块操作阻断矩阵
|
||||
|
||||
原因:
|
||||
|
||||
- selection 和 context 是 AI 质量与安全边界的前置条件。
|
||||
- `page_xml/text` 能降低模型误读 projection 的概率。
|
||||
- review session 是生产写入前必须补齐的用户确认层。
|
||||
- 状态机和 UI surface 应建立在稳定 session 语义之上。
|
||||
|
||||
---
|
||||
|
||||
## 9. 禁止项
|
||||
|
||||
- 不把 BlockNote 重新设为文档页默认主编辑器。
|
||||
- 不把 `@blocknote/xl-ai` 作为 mnote runtime dependency。
|
||||
- 不复制 GPL/PROPRIETARY 源码进入 mnote 运行时代码。
|
||||
- 不让前端 editor instance 直接执行长期持久化写入。
|
||||
- 不以 HTML / Tiptap JSON / ProseMirror position 作为 mnote 长期 AI tool contract。
|
||||
- 不绕过 `dryRun/idempotencyKey/revision/conflictDetectionKey/revisionRef` 执行 AI 写工具。
|
||||
|
||||
---
|
||||
|
||||
## 10. 与 `7-10` 的关系
|
||||
|
||||
`7-10` 继续作为页面块 AI 工具执行 checklist。
|
||||
|
||||
本文补充的是 `7-10` 后半段缺口的架构口径:
|
||||
|
||||
- `scope=selection`
|
||||
- `format=page_xml/text`
|
||||
- `持久审阅/preview UI`
|
||||
- `AI lifecycle 状态机`
|
||||
- `accept/reject/retry/abort`
|
||||
|
||||
因此本文不替代 `7-10`,而是作为下一阶段 AI runtime 收口设计稿。
|
||||
Reference in New Issue
Block a user