Files
mnote/design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md
T

333 lines
14 KiB
Markdown
Raw Normal View History

# 7-18 [process] local-first agent 文件编辑控制面 v1
> 创建时间:2026-05-22
>
> 当前状态:`PROCESS`
>
> 上位依据:
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.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
- [ ] Page AI composer 可见 target chip,显示 workspace、target title、resourceKind、scope、readonly / dirty 状态。
- [ ] 点击 target chip 打开 target picker,可在当前焦点、打开 tabs、树选中项之间选择。
- [ ] 默认 target 来自 last focused editor / resource tab,不来自第一个 tab、URL documentId 或树选中项。
- [ ] 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。
### 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。