Files
mnote/design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md
T
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

335 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7-18 [process] local-first agent 文件编辑控制面 v1
> 创建时间:2026-05-22
>
> 当前状态:`PROCESS`
>
> 上位依据:
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/reference/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
>
> 覆盖旧稿:`/mnt/Data1T/mnote/design/old/07-ai/process/7-14-local-first-ai-markdown-editing-convergence-v1.md`
>
> 口径说明:旧 `7-14` 仍受 CLI Main / Lark Doc / tool-first 写入模型影响。CLI Main 可以参考 agent workflow 纪律,但不作为 MNote local-first Markdown 编辑架构主参考。
## 1. 第一结论
MNote 不提供普通 Markdown 正文编辑工具。
MNote 在 local-first 下只提供:
- 页面和文件定位。
- agent target 选择与冻结。
- `AiAccessScope`、allowed roots、allowed files、readonly / dirty / permission context。
- selection、当前光标附近上下文、相关资源引用。
- agent runtime session、审计、changed files / diff 回收。
- watcher、BufferStore、Page Aggregate、ProseMirror 前台同步。
Hermes / Reasonix 是已知 agent,使用自身文件 read / write / patch / diff 能力在授权目录内完成普通 `.md` 编辑。
## 2. 明确退役项
### 2.1 `mnote.doc.markdown_edit`
`mnote.doc.markdown_edit` 不再作为:
- local-first 普通 Markdown 编辑默认入口。
- local-first 普通 Markdown 编辑 fallback。
- remote fallback。
- 新 agent runtime 的推荐工具。
- 新 checklist 的验收目标。
已有代码和 smoke 若仍覆盖 `markdown_edit`,只代表历史在线 / compat 路径的回归证据;不得再由它反推出“local-first 应继续提供 Markdown 编辑工具”。
### 2.2 `mnote.doc.fetch`
`mnote.doc.fetch` 不作为普通正文读取主入口。
local-first 默认路径中,agent 直接读取授权文件。MNote 可以把 selection、文件摘要、block projection 或结构化上下文放进 run input,但不要求 agent 通过 `mnote.doc.fetch` 再读正文。
### 2.3 `page_ai_workflow` / `mnote.block.*` / `mnote.page.save`
- `page_ai_workflow` 只保留为 debug / 历史兼容门面,不进入 local-first 普通编辑主路径。
- `mnote.block.*` 只保留为复杂结构辅助,例如拖拽排序、精确块删除、非 Markdown 结构资源辅助。
- `mnote.page.save` 只保留为页面级兜底写入工具,不作为精确 Markdown 编辑入口。
## 3. 主路径数据流
```text
Page / active editor
-> Agent Target Resolver freezes selected target package
-> resolve canonical .md file / resource file
-> build AiAccessScope
-> freeze allowed roots / allowed files / readonly / dirty / selection
-> start Hermes / Reasonix run with file refs and context
-> agent native patch / diff / write inside allowed roots
-> MNote collects changed files / diff / audit
-> watcher observes file changes
-> BufferStore arbitrates clean / dirty / conflict state
-> Page Aggregate rebuilds projection
-> ProseMirror / tiptap island refreshes visible document
```
关键点:
- MNote 只给位置、权限和上下文,不给普通 Markdown 编辑工具。
- 多工作区、多 tab、多资源同时打开时,默认 target 只能来自最后获得编辑焦点的 editor / resource tab;不允许用“第一个 tab”“树选中项”或 URL documentId 静默猜测。
- 写入动作发生在 agent 自身文件能力内,并受 allowed roots / allowed files 限制。
- 前台同步由 watcher / BufferStore / Page Aggregate 接回,不由 agent 直接推 UI 状态。
- dirty buffer 时不得静默覆盖,必须进入冲突或 review 状态。
## 4. Agent Target Resolver
Agent Target Resolver 是 MNote 发送给 agent 前的唯一目标解析层。它的职责是把当前 UI 状态冻结为一份明确的 `targetPackage`,而不是让 agent 或 API 自行猜“当前页”。
### 4.1 选择原则
- **workspace 是权限边界**:默认一次 run 只属于一个 workspace;跨 workspace 必须由用户显式多选,并生成多个 `allowedRoots`
- **tab 是视图,不是真相**:tab 只帮助判断用户正在看的对象;最终 target 必须落到 `objectIdentity``resourceKind` 和 canonical path。
- **last focused editor 优先**:默认 target 是最后获得编辑焦点的 editor / resource tab。
- **selection 覆盖 whole file**:存在真实选区时,默认 scope 为 selection;没有选区时才是 whole file / current resource。
- **树选中项只作为候选**FileTree / PageTree selection 不自动成为 agent target,除非用户在 target picker 中选择“树选中项”。
- **歧义时不发送**:如果焦点不明确、target dirty 且会被写入、或跨 workspace 未显式确认,发送按钮进入需要选择状态。
### 4.2 targetPackage 合同
```json
{
"schema": "mnote.agent_target_package.v1",
"primaryTargetId": "target-1",
"targets": [
{
"targetId": "target-1",
"workspaceId": "local:design",
"workspaceSource": "local_folder",
"workspaceRoot": "/workspace/design",
"editorGroupId": "main",
"tabId": "tab-design-md",
"focusRank": 1,
"objectIdentity": "local_folder:/workspace/design/design.md",
"resourceKind": "markdown_page",
"title": "design.md",
"canonicalPath": "/workspace/design/design.md",
"uri": "file:///workspace/design/design.md",
"version": "mtime:size:hash",
"scope": "selection",
"selection": {
"text": "当前选区文本",
"anchor": "pm:block:p_1:0..12"
},
"dirty": false,
"readonly": false,
"ownerPage": null,
"relatedResources": []
}
],
"allowedRoots": ["/workspace/design"],
"allowedFiles": ["/workspace/design/design.md"],
"policy": {
"writeMode": "agent_native_file_patch",
"requiresConfirmation": false,
"crossWorkspace": false
}
}
```
### 4.3 resourceKind
`resourceKind` 至少需要区分:
- `markdown_page`:普通 `.md` 页面正文。
- `mindmap`mindmap 资源文件;可带 `ownerPage`,但不伪装成正文。
- `office`OnlyOffice 资源;默认只读上下文,除非明确进入 office 编辑 agent flow。
- `raw_file`:普通文件。
- `folder`:目录范围;只在用户显式选择“把这个文件夹作为上下文”时出现。
## 5. UI 设计
当前右侧页面 AI 抽屉已有 runtime / profile / context scope,但“上下文:当前页/选区/块/页面设置”不够表达多工作区、多 tab 和资源 tab。需要把它升级为明确的 target UI。
### 5.1 默认展示
AI composer 上方固定显示 target chip,不放到设置页深处:
```text
[工作区: design] [design.md] [当前选区] [可写]
```
如果当前焦点是截图中的右侧 mindmap,则显示:
```text
[工作区: local] [新页面165037 / KMIND] [mindmap] [可写]
```
chip 必须可点击,打开 target picker。chip 内容优先显示用户能识别的名称,不优先显示内部 id;hover / detail 再显示 path、workspace root、object identity。
### 5.2 target picker
点击 target chip 后打开轻量 popover,不跳转页面。结构为:
```text
发送给 Agent
当前焦点
● design.md markdown_page 当前选区 /workspace/design/design.md
打开的 Tab
○ 新页面223322 markdown_page 全文
○ design.md markdown_page 当前选区
○ 新页面165037 / KMIND mindmap 当前资源
树选中项
○ 3 个文件 只读上下文
范围
( ) 当前选区 ( ) 当前文件 ( ) 当前资源 ( ) 多选 tab ( ) 文件夹
[取消] [使用这个目标]
```
交互规则:
- 单选是默认;多选必须用户点击“多选 tab”或勾选多个候选。
- 多 workspace 候选默认折叠在各自 workspace 组下,跨 workspace 多选时出现权限提示。
- dirty target 显示 `未保存` 标记;如果 agent 可能写入,发送前需要确认。
- readonly target 显示 `只读`,发送按钮文案变成“只读提问”或禁用写入型 intent。
- resource target 不能显示成“当前页”;必须显示 `mindmap` / `office` / `raw file`
### 5.3 composer 中的 context scope
`context scope` select 不应继续承担 target 选择职责。它只控制 primary target 内部范围:
- `selection`:当前选区。
- `file`:当前文件。
- `resource`:当前资源文件。
- `related`:当前页面引用的相关资源,只读附加上下文。
不再把 `page / block / options` 混成同一层 target 选择。页面设置属于 target 的辅助上下文,不是普通 Markdown 编辑范围。
### 5.4 发送前状态
发送按钮按 targetPackage 状态变化:
- `发送`:单 target、clean、可写或只读问答。
- `选择目标`:没有明确 last focused target,或当前焦点和 URL / active tab 冲突。
- `确认发送`:跨 workspace、多 target、dirty write、folder scope。
- `只读提问`readonly target 或用户选择只读上下文。
发送后第一条用户消息旁边显示冻结的 target 摘要,避免 run 过程中用户切 tab 后误以为 agent 改了新 tab
```text
目标:design.md / 当前选区 / local_folder / 12:31:05 冻结
```
## 6. SQLite control-plane 职责
Rust SQLite control-plane 是默认控制面,负责:
- auth / session / membership。
- share grants / sync state。
- AI policy、workspace policy、allowed roots / allowed files。
- ACP / Hermes / Reasonix runtime session。
- targetPackage 审计与用户确认记录。
- permission decision、approval decision、tool / file access audit。
- changed files / diff / command context 记录。
它不持有普通 Markdown 正文真相。本地 `.md` 文件仍是正文真相。
## 7. 运行时输入合同
每次从页面触发 agent 编辑时,run input 至少应包含:
```json
{
"workspaceSource": "local_folder",
"targetPackage": {
"schema": "mnote.agent_target_package.v1",
"primaryTargetId": "target-1",
"targets": []
},
"currentFile": {
"path": "/workspace/page.md",
"uri": "file:///workspace/page.md",
"version": "mtime:size:hash"
},
"allowedRoots": ["/workspace"],
"allowedFiles": ["/workspace/page.md"],
"selection": {
"text": "当前选区文本",
"anchor": "可选的 editor/block/offset 引用"
},
"context": {
"readonly": false,
"dirty": false,
"resourceRefs": []
}
}
```
后续可以补充 page id、object identity、resource refs、outline、nearby context,但不得把普通正文写入重新包装成 `mnote.doc.markdown_edit`
## 8. 验收清单
### Phase A0target selection 与 UI
- [x] Page AI composer 可见 target chip,显示 workspace、target title、resourceKind、scope、readonly / dirty 状态。
- [x] 点击 target chip 打开 target picker,可在当前焦点、打开 tabs、树选中项之间选择。
- [x] 默认 target 来自 last focused editor / resource tab,不来自第一个 tab、URL documentId 或树选中项。
- [x] mindmap / office / raw file target 显示真实 resourceKind,不伪装成 markdown page。
- [ ] 多 tab / 跨 workspace / dirty write 时发送前需要显式确认。
- [ ] 发送后消息记录冻结 target 摘要,切换 tab 不影响已启动 run。
### Phase A:运行时输入与权限边界
- [ ] 从文档页发起 Hermes / Reasonix run 时,request body 包含 `currentFile``allowedRoots``allowedFiles`、selection 和 dirty / readonly context。
- [ ] run body 包含 `targetPackage`,且 `allowedRoots` / `allowedFiles` 从 targetPackage 推导。
- [ ] local-first 普通 Markdown 编辑 run 不包含 `mnote.doc.markdown_edit` 推荐工具。
- [ ] allowed roots 外文件写入被 agent runtime 或 MNote 审计层拒绝。
- [ ] readonly 页面不会启动可写 run,或只启动只读问答 run。
2026-06-01 进展:已补第一版 `mnote.agent_target_package.v1` 运行时输入。前端 Page AI run body 会从当前 `editorTarget` / `WorkspacePath` 生成 `targetPackage`;后端会 sanitize 该包,并把 local-first `aiAccessScope.allowedFiles` / `allowedFilePaths``agentRunEnvelope.allowedFiles` 从该包派生。`task502-page-ai-agent-selector-context-smoke.js` 已覆盖 page / mindmap / OnlyOffice target picker 与 payload 冻结;`task520-page-ai-raw-resource-target-smoke.js` 已覆盖真实 raw local resource tab,并断言 `objectIdentity` 不退化为 `[object Object]`。当前仍未完成跨 workspace 多选确认和真实 agent 写入回收 smoke,因此本文继续保持 `process`
### Phase B:写入回收与前台同步
- [ ] agent 修改当前 `.md` 后,MNote 能回收 changed files / diff。
- [ ] clean buffer 下 watcher -> BufferStore -> Page Aggregate -> ProseMirror 可见更新通过 browser smoke。
- [ ] dirty buffer 下外部 agent 写入不会静默覆盖,必须出现冲突或 review 状态。
- [ ] changed files / diff 与 session / run / actor 关联写入审计。
### Phase C:旧工具退役
- [ ] local-first 普通 Markdown 编辑 smoke 断言未调用 `/api/documents/save`
- [ ] local-first 普通 Markdown 编辑 smoke 断言未调用 `mnote.doc.markdown_edit`
- [ ] Hermes / Reasonix manifest 或 system prompt 不再鼓励普通 Markdown 编辑调用 `mnote.doc.markdown_edit`
- [ ]`markdown_edit` 测试只保留为历史 online / compat 回归,并在文档中标明不指导新实现。
### Phase D:结构辅助边界
- [ ] `mnote.block.*` 只在复杂结构辅助场景出现,例如结构块排序、非 Markdown 资源辅助。
- [ ] `mnote.page.save` 只作为页面级兜底写入,不作为默认精确编辑入口。
- [ ] `mnote.doc.fetch` 不作为 local-first 正文读取主入口;需要结构化上下文时由 MNote 在 run input 中提供摘要或 projection。
## 9. 不做事项
- 不参考 CLI Main 的 Lark Doc 在线文档写入架构来设计 local-first 普通 Markdown 编辑。
- 不新增 MNote 普通 Markdown 编辑工具。
- 不把 Convex 恢复为默认正文或 AI 会话全文主存储。
- 不实现 Phase C Review Mode / StreamApplyController / GhostTextOverlay。
- 不让前端维护第二份正文真相。
- 不把所有打开 tab 默认发送给 agent。
- 不把跨 workspace 自动合并为一个隐式上下文。
## 10. 归档条件
满足以下条件后,本稿可归档到 `design/07-ai/done/`
- Phase A0 target UI 有真实浏览器 smoke 证据。
- Phase A / B / C 至少各有一条真实浏览器或 targeted test 证据。
- local-first agent 文件编辑主路径已能在 clean buffer 下完成真实 `.md` 修改并同步回 tiptap。
- dirty conflict 不静默覆盖。
- 文档和 manifest 不再把 `mnote.doc.markdown_edit` 描述为 local-first fallback。