chore: land tree view-state, vault, Pi module split, and repo hygiene

Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
This commit is contained in:
Agent Board
2026-07-21 05:13:05 +08:00
parent 6f9c7d3b58
commit b798f628ee
264 changed files with 17480 additions and 17314 deletions
@@ -0,0 +1,708 @@
# [recycle] 7-12 [reference] 页面 AI Hermes 工具路由与编辑审阅面设计 v1
> 更新时间:2026-05-19
>
> 当前状态:`reference / frozen`
>
> 归档说明(2026-05-22):本文保留 Hermes 工具路由、manifest 和 review surface 的设计边界;Phase C Review Mode 继续冻结。`7-14` 已移入 `design/old/07-ai/process/7-14-local-first-ai-markdown-editing-convergence-v1.md`,当前 active AI 编辑口径以 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准。
>
> 本稿目的:修正“页面 AI 快速块编辑”后续方向,明确 mnote 不再建设独立 AI agent runtimemnote 只建设 Hermes 可消费的编辑工具路由、工具 manifest、上下文冻结、dry-run/review 和 Rust 写入安全边界。
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/10-review/done/09-page-ai-fast-block-edit-runtime-review.md`
> - `/mnt/Data1T/mnote/design/old/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/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-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`
---
## 0. 2026-05-18 状态更新
本稿仍作为 Hermes 工具路由与审阅面设计保留在 `process/`,但以下口径已经更新:
- `/api/page-ai/block-edit-workflow` 当前不再以 `local_rule -> apply_block_ops` 作为主路径;local-first 普通 Markdown 编辑默认给 Hermes / Reasonix 授权文件引用,由 agent 使用自身 patch / diff / 文件编辑能力写本地 `.md`
- `mnote.doc.markdown_edit` 不再作为 local-first 默认、fallback 或 remote fallback;本文后续出现的 `markdown_edit` 只代表历史 tool / review surface 证据,不指导新 agent 文件编辑主路径。当前 active 口径见 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md`
- `page_ai_workflow` 已复用统一 mnote tool executor,不再绕过 Hermes tool toggle / audit / write contract。
- `mnote.doc.apply_block_ops` / `mnote.block.*` 保留为结构性块操作辅助,不再作为普通正文 search/replace 的优先入口。
- 本稿中的 `PageAIReviewSession` 只定义 Phase C 的安全合同和状态机边界;当前 Phase C 仍冻结,不实施流式 apply 或新的审阅 UI。
- 当前已闭合缺陷见 `bugs/07-ai/done/7-18``7-25`
### 0.1 2026-05-21 Batch J 状态更新
- profile disabled tool list 已在 `/api/hermes/client/tools` listing、前端 UI、`execute_mnote_tool_call()` guard 三处同源,均读取 Hermes profile YAML 的 `mnote.tools.disabled`
- `capabilityScope` 已由 `7-34` 接入 `execute_mnote_tool_call()` 中心校验:缺省 scope 兼容旧调用方,显式声明但不足时拒绝执行;写 scope 可覆盖同前缀 read scope。
- `is_read_tool()` 仍是硬编码读工具列表;它与 manifest annotations 的同源化属于维护性缺口,后续可随 capabilityScope 校验一起处理。
- Phase C Review Mode 继续冻结,不因 `dryRun` / changedBlocks 已可用而提前实现新的审阅 UI。
## 1. 本轮结论
页面 AI 编辑卡顿的根因不是“Rust apply 慢”,而是模型和工具之间缺少稳定、低歧义、可审计的编辑命令面:
```text
用户说一句自然语言
-> Hermes/模型需要猜:读哪个范围、改哪个块、调用哪个工具、如何传参
-> 如果猜错 blockId 或工具参数,mnote 再 fallback / 重跑 / 整页写入
-> 用户感知为慢、卡、偶发失败
```
正确方向不是再造一个 mnote 自有 AI runtime,而是:
> **Hermes 继续作为唯一页面 AI agent runtimemnote 提供 Agent-native editor command layer。**
**新增(2026-05-16,按 7-14**:AI 编辑的主路径应降维到 markdown 文本层。`mnote.doc.markdown_edit`(搜索替换文本对)是 AI 写入的主入口,覆盖 80%+ 场景;`mnote.block.*` 保留为结构性辅助。在线 Convex 文档和本地 `.md` 文件通过 `mnote.doc.fetch(format: "markdown")` + `mnote.doc.markdown_edit` 共用同一条 AI 写入路径。
因此,`本地意图解析 + 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` 方向需要收口
**当前口径修正(2026-05-16,按 7-14**
- `direct_block_edit_operations`(正则抠「」内文本的快路径)应退役。这不是 AI,是命令行。
- `/api/page-ai/block-edit-workflow` 底层应切换到 `mnote.doc.markdown_edit`:用户自然语言 → 模型产出 search/replace 文本对 → markdown_edit apply。
- 不再维持 direct path / model fallback 双路径,markdown_edit 是唯一主路径。
当前 route 对低歧义中文的加速效果不应成为保留一条非 AI 路径的理由。快路径作为 deterministic shortcut 的定位不变,但其实现必须改为调用 `mnote.doc.markdown_edit`,而不是绕过模型直接拼 operations。
### 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` 概念。
**新增(2026-05-16,按 7-14**Router 的 `recommendedToolCall` 主输出改为 `mnote.doc.markdown_edit`search/replace 文本对),块级 `mnote.doc.apply_block_ops` 仅在明确的结构性编辑场景(拖拽排序等)下推荐。
输入:
- 用户 prompt。
- 冻结后的 `mnote.page_ai_context.v1`
- 当前 tool manifest。
- 当前 profile / approval mode。
输出:
```json
{
"schema": "mnote.page_ai_command_route.v1",
"intent": "markdown_edit",
"confidence": 0.94,
"recommendedToolCall": {
"toolName": "mnote.doc.markdown_edit",
"args": {
"operations": [
{"search": "原文片段", "replace": "新文本"}
]
}
},
"risk": "low",
"requiresHermesRun": false,
"requiresReview": false,
"reason": "明确文本替换表达,目标文本唯一命中"
}
```
规则:
- 默认推荐 `mnote.doc.markdown_edit`search/replace 文本对,AI 不需要理解 blockId)。
- 仅在明确的结构性编辑场景("把第三块拖到第一块后面")推荐 `mnote.doc.apply_block_ops`
- 不能为复杂改写、总结、跨页面直接生成写入。
- 不能调用第二套长链模型;如需模型,交给 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 重写。
最小稳定合同:
```json
{
"schema": "mnote.page_ai_review_session.v1",
"sessionId": "review_01",
"workspaceId": "tree_workspace",
"documentId": "tree_doc",
"runId": "run_01",
"toolCallId": "tool_01",
"traceId": "trace_01",
"state": "awaiting_user",
"mode": "review",
"source": {
"runtimeOwner": "mnote-web",
"writeOwner": "rust-runtime-kernel",
"toolName": "mnote.doc.markdown_edit"
},
"base": {
"revision": 12,
"conflictDetectionKey": "body:12:hash",
"allowedTargetBlockIds": ["p_1"]
},
"proposal": {
"format": "markdown",
"operations": [
{
"op": "replace",
"search": "旧文本",
"replace": "新文本"
}
],
"fullContent": null
},
"preview": {
"dryRun": true,
"changedBlocks": [
{
"blockId": "p_1",
"before": "旧文本",
"after": "新文本",
"revisionRef": "pageRev:12:block:p_1"
}
],
"diff": [],
"warnings": [],
"risk": "low",
"blocked": false
},
"actions": {
"accept": {
"requiresFreshRevision": true,
"requiresIdempotencyKey": true
},
"reject": true,
"retry": {
"allowed": true,
"requiresNewToolCallId": true
},
"abort": true
},
"audit": {
"createdAt": "2026-05-18T00:00:00Z",
"createdBy": "actor_01"
}
}
```
状态语义:
- `draft`:已创建 session,但尚未生成 dry-run preview。
- `planning`:正在构造 tool args 或请求模型生成候选。
- `previewing`:正在执行 `dryRun=true`
- `awaiting_user`preview 已完成,等待 accept / reject / retry / abort。
- `accepted`:用户已确认,等待正式 apply。
- `rejected`:用户拒绝,本 session 不可再写入。
- `applying`accept 后正在正式写入。
- `applied`:正式写入已完成,并已通过 Page Aggregate / `mnote.doc.fetch` 回读。
- `failed`preview 或 apply 失败,需保留 structured error / hint。
- `aborted`:用户或系统中断,不能继续写入。
- `stale`accept 时 revision / conflictDetectionKey / revisionRef 过期,必须重新 preview,不能直接 apply。
动作约束:
- `accept` 必须重新读取 Page Aggregate,并校验 `revision/conflictDetectionKey/revisionRef`;过期时转 `stale`
- `accept` 必须提供新的或既有合法 `idempotencyKey`,重复提交必须 replay 同一结果。
- `reject``abort` 不能产生写入。
- `retry` 不能复用旧 `toolCallId` 伪装成同一次写入;必须生成新 proposal 或新 dry-run preview。
- yolo 模式可以跳过 `awaiting_user` UI,但仍应生成同构的 review-compatible audit 数据。
---
## 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 lifecyclethinking / 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 BManifest 合同收口
- [ ] `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 DReview session
- [x] 定义 `mnote.page_ai_review_session.v1` 最小 schema 与状态机边界(2026-05-18 已补;Phase C UI 仍冻结)。
- [ ] `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 acceptstale。
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/old/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,481 @@
# [recycle] 7-17 [reference] ACP Session 与控制面账号作用域 / 分享合同 v1
> 创建时间:2026-05-18
>
> 当前状态:`reference / future`
>
> 归档说明(2026-05-21):本文冻结 ACP session 与控制面分享/账号作用域远期合同;当前只作为 future reference,不作为 MVP 后阶段 active implementation checklist。
>
> 2026-05-22 口径补充:本文标题中的 Convex 是历史控制面语境。当前默认控制面已由 Rust SQLite `control-plane` 承接;ACP/Hermes runtime session、auth、share grants、AI policy 默认不再依赖 Convex。下文涉及 Convex store 的内容只作为历史 / compat / sync replica 对照。
>
> 本稿目的:
> 1. 明确 ACP session 必须绑定当前账号权限与控制面作用域,不能以内存态绕过权限。
> 2. 冻结后续“复制会话 / 只读分享 / 多人共同会话”的产品语义,避免当前实现误扩。
> 3. 区分 MNote 产品层 AI session 与 ACP Hermes / ACP Reasonix 执行层 session。
> 4. 为后续项目基本完成后扩展共享能力预留 schema、API 和验证边界。
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-25-acp-session-runtime-enhancement-plan-v1.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-14-local-first-ai-markdown-editing-convergence-v1.md`
> - `/mnt/Data1T/mnote/recycle/design/07-ai/retired-http-hermes/`
---
## 0. 当前结论
ACP 默认主链已经替代旧 Hermes HTTP 主链,但这不意味着 ACP runtime session 可以成为产品层会话真相。
当前必须成立的规则:
- **Rust SQLite control-plane 持有产品层 AI session 的账号作用域真相;Convex 只作为历史 / compat / sync replica 对照,不应被写死成唯一真相。**
- **ACP runtime session 只是执行层会话**,可以被控制面 session 绑定或索引,但不能直接作为跨用户共享对象。
- **ACP session 记录必须带 `userId / workspaceId / documentId / sessionId / runId / actorId / acpRuntime / profile`**。
- **写入控制面 session store 失败时,不允许静默降级为可继续写入的内存 session**。这会破坏账号隔离、审计和后续分享语义。
- 当前只实现单用户页面 AI session 基础路径;复制、分享、多人共同会话全部是后续功能,不在当前阶段实施。
---
## 1. 术语边界
### 1.1 MNote AI Session
MNote AI Session 是产品层会话对象,长期应该由控制面持有;当前默认控制面是 Rust SQLite control-plane,目标不是把消息全文和权限真相永久绑死在 Convex。
它回答:
- 谁能看到这段 AI 会话?
- 这段会话属于哪个 workspace / document
- 它是否可被复制、分享、共同编辑?
- 每条 run 是哪个账号触发的?
- 哪些消息、工具调用、结果可以被其他用户看到?
MNote AI Session 的 id 可以稳定暴露给前端,例如 `mnote_{documentId}_{traceId}`,但它不是 ACP runtime 原生 session id。
### 1.2 ACP Runtime Session
ACP Runtime Session 是执行层对象,由 Hermes ACP 或 Reasonix ACP 子进程持有。
它回答:
- 某个 agent runtime 当前 prompt 属于哪个协议 session
- 运行时如何接收 `session/prompt`
- `session/update` 事件如何返回?
- 运行时内部是否有本地上下文、缓存、memory、tool state
ACP Runtime Session 不应作为产品层共享真相。它可以失效、重建、按用户隔离、按 runtime 隔离。
### 1.3 Run / Event / Tool Audit
Run 是一次用户触发的执行。
Event 是 runtime 或 mnote tool 在 run 中产生的结构化事件。
Tool Audit 是 mnote 侧工具执行与写入结果的审计记录,必须以触发账号为准,而不是 session owner 或 runtime owner。
---
## 2. 当前实现边界
当前代码可接受的最小边界:
- `POST /api/hermes/client/sessions` 在 ACP 默认路径下创建 SQLite control-plane runtime session 索引;Convex 仅保留显式 legacy compat。
- `POST /api/hermes/client/runs` 创建 run 并默认持久化到 SQLite control-plane runtime store。
- `GET /api/hermes/client/events/{runId}` 将 ACP event 转成页面 SSE,并追加 runtime event。
- `GET /api/hermes/client/gateway/health` 在 ACP 默认路径下返回 `transport=acp`,不再探测旧 Hermes HTTP gateway。
当前不应声称已完成:
- 跨账号共享会话。
- 多人共同编辑同一个 AI session。
- 复制会话后的上下文重建。
- 完整消息历史作为产品层真相。
- Hermes ACP 与 Reasonix ACP 的统一长期 resume 语义。
- 分享链接、权限继承、脱敏导出。
---
## 3. 数据模型草案
### 3.1 `ai_sessions`
后续需要从当前 `acp_runtime_runs` 中抽出产品层 session 表。默认建议本地全文 + 控制面 metadata 的双层模型:
- 会话全文默认落本地 `ai-sessions/private/*.jsonl``ai-sessions/shared/*/*.jsonl`
- 控制面只保存 metadata、分享关系、审计索引、同步状态
- 只有显式开启同步或分享时,才把必要副本推到远端
建议字段:
| 字段 | 说明 |
|---|---|
| `id` | 控制面记录 id |
| `session_id` | MNote 产品层 session id |
| `owner_user_id` | 会话创建者 |
| `workspace_id` | 所属 workspace,可为空但必须显式 |
| `document_id` | 所属页面 / 文档 |
| `title` | 会话标题 |
| `visibility` | `private` / `workspace_read` / `shared_link` / `collaborative` |
| `source` | `page_ai` / `local_file_ai` / 其他 |
| `created_at` / `updated_at` / `deleted_at` | 生命周期 |
权限规则:
- 默认 `private`
- 任何 query/mutation 必须先按当前控制面 auth 解析 user,再判断 `owner_user_id`、membership 或 share grant。
- 不允许只凭客户端传入的 `userId` 授权。
### 3.2 `ai_session_members`
多人共同会话需要独立成员表,不应把共享用户塞进 session JSON。
| 字段 | 说明 |
|---|---|
| `session_id` | 产品层 session |
| `user_id` | 成员 |
| `role` | `owner` / `editor` / `commenter` / `viewer` |
| `added_by` | 添加者 |
| `created_at` | 加入时间 |
| `revoked_at` | 撤销时间 |
角色规则:
- `viewer` 只能读可共享消息和可共享 artifact。
- `editor` 可以继续发起 run,但每次 run 的 `actor_id` 必须是本人。
- `owner` 可以管理成员和删除会话。
### 3.3 `ai_runtime_bindings`
产品层 session 与运行时 session 的绑定必须独立建模。
| 字段 | 说明 |
|---|---|
| `binding_id` | 绑定 id |
| `session_id` | MNote 产品层 session |
| `run_id` | 当前 run,可选 |
| `user_id` | 运行时所属用户 |
| `runtime` | `hermes` / `reasonix` |
| `profile` | Hermes profile 或 Reasonix preset |
| `runtime_session_id` | ACP 对端 session id |
| `status` | `active` / `closed` / `expired` / `failed` |
| `created_at` / `expires_at` | 生命周期 |
关键规则:
- 同一个 MNote session 可以有多个 runtime binding。
- 不同用户默认不能复用同一个 runtime binding。
- 切换 Hermes / Reasonix runtime 时必须创建新 binding。
- binding 是执行层缓存,不是会话权限真相。
### 3.4 `ai_session_messages`
后续如需完整历史,应单独建消息表,而不是只依赖 runtime events。
| 字段 | 说明 |
|---|---|
| `message_id` | 消息 id |
| `session_id` | 产品层 session |
| `run_id` | 关联 run |
| `actor_user_id` | 触发者,可为空仅限系统消息 |
| `role` | `user` / `assistant` / `tool` / `system` |
| `content` | 可展示文本 |
| `visibility` | `private` / `members` / `owner_only` |
| `redaction` | 脱敏状态 |
| `created_at` | 创建时间 |
当前阶段可以先不落完整 messages,但必须保证已有 run/event store 能按 user 过滤。
### 3.5 `ai_session_events`
Runtime event 和 tool event 应保留原始结构,但查询时必须做权限过滤。
| 字段 | 说明 |
|---|---|
| `event_id` | 事件 id |
| `session_id` | 产品层 session |
| `run_id` | run |
| `actor_user_id` | 触发者 |
| `runtime` | `hermes` / `reasonix` |
| `event_type` | `message.delta` / `tool.started` / `tool.completed` 等 |
| `payload` | 原始或规范化 payload |
| `visibility` | 默认 `owner_only`,明确脱敏后才可扩大 |
| `created_at` | 创建时间 |
---
## 4. 权限模型
### 4.1 单用户私有会话
当前阶段只要求这个模式稳定。
规则:
- 创建 session 时使用当前 SQLite control-plane auth/session 解析出的真实 user / actor id。
- `userId` 只可作为服务端派生字段,不可由浏览器任意指定。
- run / event / tool audit 都必须与同一 actor 绑定。
- 当前请求无法解析用户时,应返回 `401` 或稳定错误,而不是创建匿名共享 session。
### 4.2 复制会话
复制不是共享同一个 runtime session。
复制语义:
- 生成新的 `session_id`
- 新 session 的 `owner_user_id` 是复制者。
- 可以复制已脱敏、可共享的消息、摘要、artifact 引用。
- 不复制底层 `runtime_session_id`
- 不复制另一个用户的 Hermes profile memory、Reasonix cache handle、tool permission state。
- 复制后第一次继续对话时,为复制者创建新的 runtime binding。
适用场景:
- 用户把某段 AI 分析作为模板继续改。
- 从只读分享页复制到自己的 workspace。
### 4.3 只读分享
只读分享只读产品层 session 的可共享投影。
必须隐藏:
- 本地路径、profile 配置路径、API key 状态。
- tool args 中含有的敏感字段。
- 未授权页面内容、选区内容、私有 workspace 信息。
- actor 的内部 user id,除非产品明确展示成员身份。
分享链接不能恢复 ACP runtime session。
### 4.4 多人共同会话
多人共同会话是后续能力,不能直接复用当前 runtime session。
推荐语义:
```text
共享 MNote AI Session
-> 每个用户按自己的权限发起 run
-> 每次 run 记录 actor_user_id
-> runtime binding 默认按 actor_user_id 隔离
-> 前端展示同一个产品层 transcript
```
允许的实现策略:
- **每用户 runtime binding**:最安全,默认方案。每个成员继续对话时由自己的 runtime 处理。
- **共享 transcript 重建上下文**runtime 不共享,只把已授权 transcript 作为 prompt context 注入。
- **共享 runtime binding**:默认禁止。只有当 runtime 明确是服务端多租户安全实例,并且 tool permission 已按 actor 隔离时才可考虑。
---
## 5. ACP Hermes 与 ACP Reasonix 差异
### 5.1 ACP Hermes
特点:
- Hermes profile、memory、skills、tools 往往绑定本机用户配置。
- `mnoteai` profile 可能包含特定用户偏好、工具开关、记忆文件。
- Hermes ACP 的底层 session 适合“当前用户私有 agent runtime”。
规则:
- 默认按用户隔离 runtime binding。
- 共享会话不能让其他用户复用 owner 的 Hermes profile memory。
- 复制会话时只复制可展示 transcript,不复制 Hermes profile 状态。
- 如果后续允许团队 Hermes profile,必须单独建 workspace-level profile 权限模型。
### 5.2 ACP Reasonix
特点:
- Reasonix 强调 cache-first / preset / project cache。
- 缓存可能跨 prompt 复用,隐私边界比普通 stateless model 更敏感。
- Reasonix 的 session 与 cache handle 不应默认跨账号共享。
规则:
- 默认按 `userId + workspaceId + documentId + preset` 隔离缓存可见性。
- 分享 transcript 不等于分享 Reasonix cache。
- 多人共同会话如果要共享 Reasonix 缓存,必须先设计 cache grant。
- 只读分享不得暴露 cache hit/miss 细节,除非确认无隐私风险。
### 5.3 统一抽象
MNote 不应把 Hermes ACP / Reasonix ACP 的内部 session 当成统一真相。
统一层只定义:
- 产品层 session。
- run 与 actor。
- runtime binding。
- event/message 投影。
- 权限与分享合同。
---
## 6. API 草案
当前阶段不实现以下 API,只冻结形状。
### 6.1 Session
```http
GET /api/hermes/client/sessions
POST /api/hermes/client/sessions
GET /api/hermes/client/sessions/{sessionId}
DELETE /api/hermes/client/sessions/{sessionId}
POST /api/hermes/client/sessions/{sessionId}/rename
POST /api/hermes/client/sessions/{sessionId}/auto-title
```
要求:
- 所有接口服务端解析当前用户。
- 查询默认只返回当前用户有权限访问的 session。
- 删除默认软删除,不删除 runtime audit。
### 6.2 复制
```http
POST /api/hermes/client/sessions/{sessionId}/copy
```
请求:
```json
{
"targetWorkspaceId": "ws_1",
"targetDocumentId": "doc_2",
"copyMode": "summary_and_visible_messages"
}
```
响应:
```json
{
"ok": true,
"sessionId": "mnote_doc_2_copy_1",
"sourceSessionId": "mnote_doc_1_original",
"runtimeBindingCopied": false
}
```
### 6.3 分享
```http
POST /api/hermes/client/sessions/{sessionId}/shares
GET /api/hermes/client/sessions/{sessionId}/shares
DELETE /api/hermes/client/sessions/{sessionId}/shares/{shareId}
```
分享 grant 必须包含:
- `scope`: `user` / `workspace` / `link`
- `role`: `viewer` / `editor`
- `expiresAt`
- `redactionPolicy`
### 6.4 多人会话成员
```http
POST /api/hermes/client/sessions/{sessionId}/members
GET /api/hermes/client/sessions/{sessionId}/members
DELETE /api/hermes/client/sessions/{sessionId}/members/{userId}
```
---
## 7. 不变量
这些规则后续实现必须写成测试。
1. 账号 A 创建的 private session,账号 B 不能读取列表、详情、事件、消息。
2. 账号 B 复制账号 A 分享给他的 session 后,得到新的 `sessionId` 和新的 owner。
3. 复制后的 session 不包含源 session 的 `runtime_session_id`
4. 多人共同会话中,账号 B 发起 run 时 `actor_user_id=B`,不能写成 owner A。
5. 只读 viewer 不能发起 run。
6. Hermes profile memory 不随 session 分享。
7. Reasonix cache handle 不随 session 分享。
8. tool args 默认 `owner_only`,只有经过脱敏的摘要可进入 shared transcript。
9. Convex store 写入失败时,run 创建必须失败或返回明确可恢复错误,不能静默创建匿名内存会话。
10. 本地文件 AI session 分享必须重新核验目标用户是否能访问对应 local root;默认不支持跨用户分享本地文件内容。
---
## 8. 实施阶段
### Phase A:当前阶段,只做约束固化
状态:当前主线。
- ACP 默认主链可用。
- session/run/event 必须账号作用域写入 Rust SQLite control-planeConvex 只作为显式 compat / sync replica。
- 旧 Hermes HTTP 主链进入 `recycle`
- 不实现复制、分享、多人会话。
- 文档与测试明确禁止内存降级绕过权限。
### Phase B:项目基本完成后,补产品层 session 表
目标:
- 增加 `ai_sessions` / `ai_session_members` / `ai_runtime_bindings`
- 将现有 `acp_runtime_runs` 从“运行态索引”升级为 session 下的 run 记录。
- UI 从 localStorage 历史逐步迁到 Rust control-plane session 列表。
### Phase C:复制与只读分享
目标:
- 实现 session copy。
- 实现只读分享投影。
- 建立脱敏策略。
- 不实现多人共同编辑。
### Phase D:多人共同会话
目标:
- 成员管理。
- 多 actor transcript。
- 每 actor runtime binding。
- 共享上下文重建策略。
### Phase Eruntime-specific 高级策略
目标:
- Hermes team profile 权限模型。
- Reasonix cache grant / cache visibility。
- workspace-level AI runtime policy。
---
## 9. 当前代码注意事项
当前代码中 `acp_runtime_runs` / `acp_runtime_events` 仍是运行态索引,不是完整产品层 session 模型。
因此后续修改时:
- 不要把 `ACP_RUN_PAYLOADS``ACP_ACTIVE_RUNS` 视为权限真相。
- 不要因为 Convex 写入失败就退回“可继续写”的内存 session。
- 不要把 `profile=mnoteai` 当成 user identity。
- 不要让 `acpRuntime=hermes` 自动表示可访问 Hermes profile memory;仍需当前账号授权。
- 不要把 Reasonix cache 命中结果写入共享 transcript,除非经过脱敏和权限确认。
---
## 10. 验收清单
后续实现本稿时,至少需要以下验证:
- 两账号隔离 smokeA 创建 sessionB 列表不可见。
- 分享 smokeA 授权 B viewerB 可读脱敏 transcript,不可 run。
- editor smokeA 授权 B editorB 可 runevent actor 为 B。
- copy smokeB 复制 A 的分享 session,产生新 session,新 owner 为 B。
- runtime binding smoke:复制后没有复用源 runtime session id。
- Hermes ACP smoke:共享后不暴露 owner profile memory。
- Reasonix ACP smoke:共享后不暴露 cache handle。
- 权限失败 smokeConvex auth 缺失或 user 不匹配时,API 返回 401/403,不创建内存会话。
@@ -0,0 +1,150 @@
# [recycle] Reasonix Browser Test Contract v1
## 状态
- 状态:reference
- OwnerAI runtime / browser verification
- 背景:Reasonix 已能作为独立辅助 agent 执行只读审计,但浏览器验收若只输出自然语言结论,主控仍需重测,无法稳定节省 token。
## 目标
把 Reasonix 浏览器测试从“描述性结论”收口为“可审计证据包”。Codex/Hermes 主控只接受带截图、日志、网络、断言和环境前置条件的 Reasonix 浏览器结论。
## 非目标
- 不让 Reasonix 直接裁决业务 bug 是否完成。
- 不让 Reasonix 修改源码、提交 git、回滚文件。
- 不把浏览器测试结论写成长期产品事实源;长期事实仍以 bug/design/checklist 和真实验证日志为准。
## 合同
每个 Reasonix 浏览器测试任务必须产出以下文件:
```text
result.json
final.md
process-handoff.md
process-handoff.json
artifacts/
screenshots/
console.json
network.json
trace-or-steps.md
```
`result.json` 至少包含:
```json
{
"status": "passed | failed | blocked",
"run_id": "reasonix-...",
"target_url": "http://127.0.0.1:3000",
"environment": {
"dev_server_restarted": true,
"cache_cleared": true,
"browser_context": "isolated",
"auth_mode": "test-account",
"workspace_root": "/tmp/..."
},
"steps": [
{
"name": "upload first attachment",
"action": "drag file into editor",
"screenshot": "artifacts/screenshots/01-upload-first.png"
}
],
"assertions": [
{
"name": "attachment opens in tab after second upload",
"expected": "tab count increases and active tab kind is resource",
"actual": "click produced no tab change",
"passed": false,
"evidence": "artifacts/screenshots/04-click-after-second-upload.png"
}
],
"console_errors": [],
"network_failures": [],
"screenshots": [],
"modified_files": []
}
```
## 浏览器前置条件
- 测试前重启 `npm run dev:hot` 或复用主控明确提供的已重启服务。
- 使用全新 isolated browser context;清空 cookies、localStorage、sessionStorage、IndexedDB、Cache Storage。
- 默认使用 `http://127.0.0.1:3000/auth` 的测试账号快速登录。
- local-first 测试必须创建临时页面或临时 workspace,并记录路径。
- 所有上传文件路径必须记录,不能只写“上传文件”。
## 截图要求
用户可见 bug 至少保留:
- 初始状态截图。
- 关键动作后截图。
- 失败状态截图。
- 修复验证时的通过状态截图。
如果 bug 涉及 “点击无反应”,必须同时记录:
- 点击前 tab 列表。
- 点击后 tab 列表。
- 点击目标节点截图。
- console/network。
## 0524 试验集映射
`bugs/0524.md` 可作为第一批协同测试样本:
1. 我的空间拖入附件路径错误:
- 验收:上传后文件实际路径应在当前页面 bundle/下级,而不是与主文件夹同级。
- Reasonix 输出:文件树截图、实际磁盘路径、上传请求、编辑区附件截图。
2. 上传后文件冲突:
- 验收:本页面不误报自写冲突;新建页面不继承旧冲突状态。
- Reasonix 输出:冲突弹窗截图、页面切换后截图、console/network。
3. 隐藏本地 Markdown 标题设置:
- 验收:不同 workspace/source 的默认值区分,用户选择刷新后保持。
- Reasonix 输出:设置变更前后截图、刷新后截图、local/session storage 或后端请求证据。
4. Ctrl+Z/Ctrl+Y 删除链接:
- 验收:删除附件链接后 undo/redo 可恢复/再次删除。
- Reasonix 输出:键盘动作步骤、编辑区 DOM/截图、断言结果。
5. md 文件上传后黑色方块刷新变灰:
- 验收:上传前后和刷新后附件 icon 状态一致,或有明确缺失状态样式。
- Reasonix 输出:刷新前后截图、附件节点属性。
## 主控接受条件
Codex/Hermes 只有在以下条件满足时,才能把 Reasonix 浏览器结果作为验收证据:
- `result.json` 存在且能解析。
- 至少一张截图能直接显示用户可见状态。
- console/network 不为空时已被解释;为空时明确记录为空。
- `modified_files``[]``none`,确认 Reasonix 没有改源码。
- `process-handoff` 已 retain 到 Hindsight,主控可 recall。
## 失败处理
- 缺截图:结果降级为线索,不作为验收。
- 缺 console/network:UI 失败只可作为复现证据,不可作为根因证据。
- 没清缓存或没隔离 browser context:结果标记 `blocked``needs_rerun`
- Reasonix 修改源码:本次测试作废,主控检查 diff 后决定是否保留建议。
## 评价指标
- accepted_browser_findingsReasonix 浏览器发现最终被主控采纳的数量。
- false_browser_leadsReasonix 浏览器结论导致主控走错方向的数量。
- retest_required_ratio:主控必须完全重测的比例。
- artifact_read_time:主控读取证据包并形成判断所需时间。
- missing_artifact_count:缺失截图、console、network、断言的数量。
## Done Gate
- [ ] `reasonix-browser-tester` skill 已补入本合同的最小 artifact 要求。
- [ ] 使用 `bugs/0524.md` 中至少 1 个 bug 进行 Reasonix 浏览器试跑。
- [ ] 试跑后记录 accepted findings / false leads / retest required。
- [ ] 根据试跑结果更新本合同或 Reasonix skill。
## 2026-06-01 降级说明
本合同对应的全局 `reasonix-browser-tester` skill 已存在,并已包含 `result.json`、截图、console/network、isolated context、`modified_files` 等 artifact 要求。本文不再作为 MNote 产品 runtime 的 active process 项,降级为协作参考;后续若要继续试跑 `bugs/0524.md`,应在 Reasonix skill 维护流程或独立复盘文档中跟踪。
@@ -0,0 +1,150 @@
# [recycle] Reasonix Skill Maintainer v1
## 状态
- 状态:reference
- OwnerAI runtime / multi-agent collaboration
- 背景:Hindsight 解决了 Reasonix 过程可追溯问题,但不能自动让 Reasonix 下一次更好。需要一个主控驱动的最小自进化机制,由 Codex/Hermes 根据真实 run 修改 Reasonix skill、任务模板和 handoff 合同。
## 目标
建立“真实任务 -> 复盘 -> 修改 Reasonix skill/模板 -> 小 smoke -> 再试跑”的闭环,让 Reasonix 从通用辅助逐步变成可审计、低噪声、能节省主控 token 的 worker。
## 非目标
- 不让 Reasonix 自主修改自己的 skill。
- 不照搬 Hermes 的完整自进化机制。
- 不把一次失败就提升为长期规则。
- 不修改项目 `AGENTS.md`,除非用户明确要求。
## 角色边界
- Codex/Hermes:主控,负责判断哪些协作缺陷值得固化为 skill 规则。
- Reasonix:被维护的 worker,负责执行明确任务。
- Hindsight:热记忆,保存过程 handoff 和协作复盘。
- MemPalace:冷归档,保存长期可检索的会话摘要。
## 输入
每次维护 Reasonix skill 前,主控必须先读取:
- Reasonix run 输出目录:
- `result.json`
- `process-handoff.json`
- `process-handoff.md`
- `final.md`
- 必要时 `reasonix-transcript.jsonl`
- Hindsight recall
- `hindsight-embed -p agents memory recall reasonix "<run-id 或任务关键词>"`
- 当前相关 skill
- `reasonix-coding-worker`
- `reasonix-browser-tester`
- `reasonix-parallel-coding-flow`
- 新增 `reasonix-skill-maintainer`
## 复盘维度
每个 Reasonix run 至少评估:
- accepted_findings:被主控采纳的关键发现数。
- false_leads:误导主控的判断数。
- missing_evidence:缺失证据项。
- verification_strength:是否有真实命令、截图、日志、网络、diff。
- handoff_cleanlinesshandoff 是否能快速读懂。
- modified_files_accuracy:是否准确记录修改文件。
- retest_required:主控是否必须完全重测。
- token_saving_estimate:是否减少主控探索或验证成本。
## 维护决策
只有满足以下至少一项,才修改 Reasonix skill
- 同类缺陷出现两次以上。
- 单次缺陷导致主控明显走错方向。
- 缺失 artifact 使浏览器结果无法验收。
- `process-handoff` 的结构缺陷导致主控必须重读 transcript。
- 用户明确指出 Reasonix 协同方式需要调整。
不应修改 skill 的情况:
- 只是单次措辞不佳。
- 是当前任务本身不清楚,而不是 Reasonix 规则缺失。
- 可通过更好的任务书解决,不需要变成长期规则。
## 修改范围
优先顺序:
1. 修改任务书模板:成本最低,适合单类任务。
2. 修改 Reasonix skill:适合跨任务重复规则。
3. 修改 runner/extractor schema:适合结构化输出缺陷。
4. 修改 Codex/Hermes 主控 skill:适合复核顺序和验收口径。
每次只做最小修改,并记录:
- 修改前问题。
- 修改文件。
- 预期改善。
- smoke 验证方式。
## Smoke 验证
修改 Reasonix skill 后,必须至少做一个轻量 smoke:
- 只读任务优先,不改业务代码。
- 要求 Reasonix 产出新格式字段。
- 主控检查字段是否存在、是否可解析、是否减少噪声。
浏览器类 smoke 应检查:
- 是否有截图路径。
- 是否有 console/network 摘要。
- 是否有 `cache_cleared``browser_context`
- 是否有 `assertions[]`
## 0524 试跑计划
使用 `bugs/0524.md` 作为真实任务池,不一次性全交给 Reasonix:
1. 第一轮:只让 Reasonix 浏览器复现第 2 个 bug“上传后文件冲突”,不修代码。
- 目标:验证 browser artifact contract 是否足够。
2. 第二轮:让 Reasonix 只读审计第 1 个 bug“我的空间附件路径错误”。
- 目标:验证 coding-worker handoff 是否能给出可采纳根因。
3. 第三轮:Codex 修复其中一个 bug 后,让 Reasonix 做回归。
- 目标:评估 Reasonix 是否能减少主控浏览器验证 token。
每轮都记录:
- Reasonix 耗时。
- 主控读取 handoff 所需时间。
- 主控是否重测。
- 被采纳发现。
- 误导点。
- 需要修改的 skill 规则。
## 输出记录
建议每次维护后在对应 bug/design 中补一段:
```text
Reasonix 协同复盘:
- run id:
- 任务类型:
- accepted findings:
- false leads:
- missing artifacts:
- skill changes:
- next iteration:
```
## Done Gate
- [ ] 创建 `reasonix-skill-maintainer` skill。
- [ ] `reasonix-browser-tester` 已引用 browser artifact contract。
- [ ]`bugs/0524.md` 至少跑一轮真实 Reasonix 协同测试。
- [ ] 根据真实结果更新 skill 或明确“不需要修改”。
- [ ] 在 Hindsight / MemPalace 中保留协作复盘摘要。
## 2026-06-01 降级说明
全局 `reasonix-skill-maintainer` skill 已存在,并已要求读取 `result.json``process-handoff`、Hindsight recall,评估 `accepted_findings``false_leads``missing_evidence``retest_required``token_saving_estimate`。本文不再作为 MNote 产品 runtime 的 active process 项,降级为协作参考;后续真实 run 复盘直接走全局 skill。
@@ -0,0 +1,141 @@
# [recycle] 7-37 Claude Code / Reasonix Worker 评估协议:0524 Bug2 v1
## 目标
`bugs/0524.md` 第 2 条“本地 Markdown 上传/文件树拖入后误报文件冲突”作为真实任务,评估 Claude Code 与 Reasonix 作为 worker 的可用性:
1. 编码能力:是否能在相同基线下定位根因、做小范围修复、补测试并通过验证。
2. 网页测试能力:是否能在真实浏览器中复现/验收,产出可审计截图、console、network 和结构化结论。
Codex 仍是主控,负责设计、派发、复核、合并和最终验收。
## 公平性约束
- 两个 coding worker 使用从同一 `HEAD` 创建的独立 worktree,不共享当前主工作区未提交改动。
- 两个 coding worker 使用同一份任务书,允许修改范围一致,验收命令一致。
- 两个 browser worker 对同一个主工作区修复结果做只读验证,不允许改源码。
- 评分只基于可复核产物:diff、测试输出、process handoff、截图、console/network,不凭 final 文本。
- 若 worker 卡住、超时、无 handoff、无截图或修改范围失控,按实际情况扣分,不手工补完其证据。
## Coding Worker 任务切片
输入:
- bug`bugs/0524.md` 第 2 条。
- 参考设计:`design/05-editor-mainline/process/5-27-local-markdown-working-copy-conflict-contract-v1.md`
- 允许修改:
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/routes/local_folder_events.rs`
- `scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`
- 必要时新增/更新同目录小测试
- 禁止修改:无关设计、AGENTS、其它 bugs、git 提交、回滚用户改动。
验收:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web document_event_filter_rejects_resource_only_changes -- --nocapture`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_renders_local_markdown_with_same_sidebar_surfaces -- --nocapture`
- `cargo fmt --manifest-path rust/Cargo.toml --all --check`
- 若能启动浏览器 smoke,运行 `node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js` 并记录结果。
## Browser Worker 任务切片
输入:
- 被测主工作区当前修复结果。
- 入口:`http://127.0.0.1:3000`
- 登录:`/auth` 的“测试账号快速登录”。
必须验证:
- 打开本地 Markdown 页面,上传附件后,不出现 `mnote-editor-conflict-panel`
- 新建/切换页面后,旧冲突面板不残留到新页面。
- 向文件树文件夹拖入文件后,不出现 `external-change-conflict`
- 真实冲突路径仍可用:dirty 当前 Markdown 后外部修改同一 `.md` 文件,应显示冲突。
必须产出:
- `result.json`
- `process-handoff.md/json`
- 至少一张显示通过或失败状态的截图
- console/network 摘要
- `modified_files` 必须为空
## 评分维度
- `root_cause`:是否找到两个核心根因:document event 越界、冲突 DOM 残留。
- `patch_scope`:是否只改必要文件,未引入架构外补丁。
- `test_quality`:是否补了能失败/能防回归的测试。
- `verification`:是否实际运行命令并记录输出。
- `handoff_quality`:是否有过程证据,而不是只给最终摘要。
- `latency_cost`:完成时间、工具调用量、是否卡住。
- `mislead_risk`:错误假设、过度修改、把无关失败归因到本 bug 的风险。
## 初步观察
- Reasonix 只读分析较快完成,正确指出空 `documentId` 会污染 session,但对新建页时序有部分猜测,需要主控复核。
- Claude Code 修复后本轮可产出 handoff,明确指出冲突面板 DOM 生命周期缺口;耗时更长,但对第二个根因帮助明显。
- 后续需要用隔离 coding 任务和真实 browser 任务继续比较,而不是只比较只读分析。
## 0524 Bug2 本轮实测记录
### 主控基线
- 主控修复覆盖三个切片:
- document event channel 从 `rootUri` 收窄到 `rootUri#documentId`
-`documentId` 的资源事件不再进入正文冲突链路。
- editor view unmount 清理旧 session 冲突面板 DOM。
- 主控验证:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web document_event_filter_rejects_resource_only_changes -- --nocapture`:通过。
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_renders_local_markdown_with_same_sidebar_surfaces -- --nocapture`:通过。
- `cargo fmt --manifest-path rust/Cargo.toml --all --check`:通过。
- `node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js`:通过,真实冲突仍可用。
- `node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`:上传附件和文件树拖入 no-conflict 断言通过;后续 broken-link 检查失败,属于其它已知路径,不计入本 bug 修复失败。
### Coding Worker 对比
- Reasonix coding
- run id`reasonix-2026-05-24T07-44-15-864Z-6898e686`
- worktree`/mnt/Data1T/mnote-wt-0524b-reasonix-coding`
- 正确抓到空 `documentId` 事件污染正文 session。
- 补了前端 `if (!documentId) return;` 和后端映射测试。
- 漏掉冲突面板 DOM 残留,也没有做 `rootUri#documentId` channel 隔离。
- 自报未跑 Playwright,适合作为“定位主因 + 小补丁”证据,不足以直接合并。
- Claude Code coding
- run id`claudecode-2026-05-24T07-44-15-975Z-e03aaf1f`
- worktree`/mnt/Data1T/mnote-wt-0524b-claudecode-coding`
- 正确抓到空 `documentId` 事件污染。
- 额外抓到冲突面板 DOM 生命周期问题。
- 修复位置是 `releaseDocumentSession`,比主控采用的 `unmountEditorViewBinding` 更晚,仍可能存在切换瞬间残留风险。
- 未做 `rootUri#documentId` channel 隔离,也未补 task479 no-conflict smoke。
- 编码帮助大于 Reasonix,但仍需主控补边界。
### Browser Worker 对比
- Claude Code browser
- run id`claudecode-2026-05-24T07-47-11-262Z-46f574f0`
- artifact`/tmp/mnote-0524-bug2-browser-worker-1779608936242/`
- 产出 `result.json`、截图、console/network。
- 上传附件 no-conflict、文件树拖入 no-conflict、dirty + 外部修改真实冲突均通过。
- `modified_files=[]`
- 该轮可作为较高质量浏览器验收证据。
- Reasonix browser
- run id`reasonix-2026-05-24T07-47-11-249Z-43f061bb`
- artifact`/tmp/mnote-0524-bug2-browser-worker-0524T0800/`
- 产出 `result.json`、截图、console/network。
- 上传附件 no-conflict、文件树拖入 no-conflict 通过。
- runner status 为 `error` / handoff status 为 `failed`,但 `result.json.status=passed`,状态合同不一致。
-`/api/local-folder/events` 500 判断为真实冲突阻塞;主控 `task451` 和 Claude Code browser 已证明真实冲突仍可验证,因此该判断有误导风险。
- 可作为部分 UI 证据,但不能单独作为最终验收。
### 可审计性结论
- Hindsight recall 可召回本轮 Claude Code 与 Reasonix 过程 handoff,但 Reasonix recall 混入了无关项目结果,主控不能只看 recall 摘要。
- Claude Code 的 browser artifact 更适合验收:`result.json`、截图路径、console/network、`modified_files=[]` 之间一致。
- Reasonix 的 browser artifact 可作为辅助证据,但 runner `status=error` 与 artifact `status=passed` 冲突时,必须降级为线索。
- 本轮公平测试说明:worker 能节省主控定位和浏览器操作 token,但不能替代主控做架构边界、diff 取舍和最终验收。
## 当前评分倾向
- 编码能力:Claude Code 略优,能补出 DOM 生命周期根因;Reasonix 更快但更像最小局部补丁。
- 浏览器测试能力:Claude Code 本轮明显优于 Reasonix,证据更完整且能覆盖真实冲突;Reasonix 产物可审计但状态和结论自相矛盾。
- 主控策略:以后可让 Reasonix 做低风险定位/快速 smoke,让 Claude Code 做更完整的 browser artifact;两者结论都必须由 Codex 复核 diff、截图和命令输出。
@@ -0,0 +1,170 @@
# [recycle] 7-68 WeKnora 知识库页面 UI 参考
状态:reference
范围:只记录 WeKnora 知识库页面可嫁接的页面结构、状态与 MNote 字段映射;不修改 MNote runtime,不修改 WeKnora 源码。
## 结论
7-68 的知识库页不应继续保留 MNote 原来的简陋“知识库设置面板”形态。更合适的嫁接目标是复用 WeKnora 的“知识库列表 + 知识库详情文档管理页”体验:列表页提供知识库卡片、分组、上传进度和能力徽标;详情页提供面包屑、文档/ Wiki / 图谱页签、标签侧栏、文档筛选栏、网格/列表切换、上传入口、解析状态和批量管理。
MNote 应保留本地 source registry、allowed roots、workspace path、provider 映射和打开引用能力;WeKnora 页面结构只作为 UI / 交互参考,不直接接管 MNote 的事实源、鉴权、文件写入或索引生命周期。
## WeKnora 源文件
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/KnowledgeBaseList.vue`
- 知识库列表页主入口。
- 核心结构:`kb-list-container``ListSpaceSidebar`、标题区、未初始化提示、上传进度面板、知识库卡片网格、分组标题、收藏 / 置顶 / 设置 / 删除菜单、能力徽标。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/KnowledgeBase.vue`
- 单个知识库详情页主入口。
- 核心结构:`knowledge-layout`、面包屑 + `KBSwitcherDropdown``KBInfoPopover`、设置按钮、Documents / Wiki / Graph 页签、标签侧栏、文档筛选栏、文档卡片网格、列表视图、`DocContent` 抽屉。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/components/DocumentListView.vue`
- 文档列表视图。
- 核心结构:吸顶表头、复选框、多列 row、来源、大小、解析状态、更新时间、行菜单。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/components/KbUploadSourceDropdown.vue`
- 添加文档入口。
- 支持上传文件、上传文件夹、导入 URL,可选手动创建。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/components/DocumentBatchBar.vue`
- 批量选择后的浮动操作条。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/components/knowledge-processing-timeline.vue`
- 解析 / 后处理 trace 时间线。
- 显示 docreader、chunking、embedding、multimodal、postprocess 等阶段,以及 live polling、失败、取消和耗时。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/components/empty-knowledge.vue`
- 空知识库占位。
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/api/knowledge-base/index.ts`
- API 字段参考:`listKnowledgeBases``getKnowledgeBaseById``uploadKnowledgeFile``createKnowledgeFromURL``listKnowledgeFiles``reparseKnowledge``cancelKnowledgeParse``batchDeleteKnowledge``getKnowledgeSpans`
- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/i18n/locales/zh-CN.ts`
- 中文文案参考:`knowledgeBase``knowledgeList``knowledgeEditor`
## 可嫁接页面结构
### 1. 知识库列表
最小结构:
- 左侧范围筛选:全部 / 我的 / 收藏 / 最近 / 空间,MNote 首版可降级为全部 + 当前 workspace。
- 顶部标题:`知识库`,副标题采用 WeKnora 语义“管理和组织您的知识库,支持文档型和问答型知识库”。
- 新建按钮:图标按钮或小按钮,接 MNote 创建 KB / 绑定本地 folder 的流程。
- 全局状态提示:
- 未初始化 / provider 未连接提示。
- 上传或索引中的 progress panel。
- 卡片网格:
- 标题、描述。
- 文档数量 / FAQ 数量 / processing loading。
- 能力徽标:知识图谱、多模态、问题生成、共享。
- 卡片菜单:置顶、设置、删除。
- hover 显示边框与更多按钮。
MNote 首版不需要完整照搬 WeKnora 的组织共享分组,但应保留分组能力的视觉槽位,避免后续 workspace / shared KB 接入时重做页面。
### 2. 知识库详情
最小结构:
- 面包屑:`知识库 / 当前知识库 / 文档`
- 当前 KB 下拉切换:参考 `KBSwitcherDropdown`MNote 可映射为当前 workspace 下 KB registry。
- 右侧动作:信息 popover、设置按钮。
- 文档页签:
- `文档` 为默认主视图。
- 若 provider 暴露 wiki / graph 能力,再显示 `Wiki``图谱` 页签。
- 左侧标签侧栏:
- `文档分类` 标题、数量、搜索、标签列表、新建 / 重命名 / 删除。
- MNote 首版可先映射为 source kind / folder / tag,不能伪造 WeKnora 后端 tag 写入。
- 主内容筛选栏:
- 搜索文档。
- 文件类型筛选。
- 解析状态筛选。
- 来源筛选。
- 更新时间范围。
- 网格 / 列表切换。
- 添加文档入口。
- 文档区:
- skeleton loading。
- grid 卡片作为默认视图。
- list 作为密集视图。
- 空状态。
- 批量选择浮动条。
- 文档详情:
- 点击文档打开 `DocContent` 风格抽屉,展示全文、分块、原文件 / 引用。
### 3. 上传与导入
参考 `KbUploadSourceDropdown.vue` 的四类入口:
- 上传文档。
- 上传文件夹。
- 导入网页 URL。
- 手动创建。
MNote 映射时应改成:
- 上传文档 / 文件夹:落到 MNote local-first source registry,记录 local path / providerKnowledgeBaseId / providerKnowledgeId,再交 WeKnora provider ingest。
- 导入 URL:可作为 provider source,不对应本地文件。
- 手动创建:如果没有明确的 MNote 文档创建语义,首版不启用,避免产生第二套正文事实源。
### 4. 状态与交互
WeKnora 的文档状态结构可直接作为 MNote UI 状态模型参考:
- `pending` / `processing`:显示 loading、解析中,可打开 trace。
- `finalizing`:主解析完成但 summary / question / graph 等后处理仍在跑。
- `failed`:红色失败状态,可重建 / 查看 trace。
- `cancelled`:黄色取消状态。
- `draft`:草稿。
- `completed`:绿色完成。
- `completed + summary pending/processing`:显示“生成摘要中”。
重要交互:
- 卡片和列表都能打开文档详情。
- 更多菜单提供重建、取消解析、移动、批量管理、删除。
- 解析中 / 失败状态应能打开 trace。
- 网格 / 列表视图选择可持久化到 localStorage。
- 批量选择不能与普通打开文档冲突。
## MNote 字段映射
| WeKnora UI 字段 | MNote 映射建议 |
| --- | --- |
| `kb.id` | MNote `knowledge_base_id` / registry entry id |
| `kb.name` | 知识库显示名,默认可来自 folder name |
| `kb.description` | registry description / provider description |
| `kb.type` | MNote 首版固定 `document`FAQ 暂不作为主路径 |
| `kb.knowledge_count` | provider 文档数 + registry source count |
| `kb.chunk_count` | provider chunk count,可从 WeKnora status / search metadata 显示 |
| `kb.isProcessing` | registry processing count 或 provider parse in-flight count |
| `extract_config.enabled` | WeKnora graph / MNote graph capability |
| `vlm_config.enabled` | 多模态 / 图片解析能力 |
| `question_generation_config.enabled` | 问题生成能力 |
| `storage_provider_config.provider` | provider / vector store / file store 显示,不作为 MNote 文件事实源 |
| `knowledge.id` | `providerKnowledgeId` |
| `knowledge.file_name` | source display name / local file basename |
| `knowledge.file_type` | 文件扩展名 / source kind |
| `knowledge.file_size` | source file sizeURL/manual 可为空 |
| `knowledge.tag_id` | MNote tag / folder / source group 映射,首版可只读 |
| `knowledge.parse_status` | provider parse status |
| `knowledge.summary_status` | provider postprocess status |
| `knowledge.channel` | `web` / `api` / `url` / `manual` / local upload 映射 |
| `knowledge.source` | local path / URL / provider source |
| `knowledge.updated_at` | registry updated_at 或 provider updated_at |
## 不能直接接管的边界
- 不直接把 WeKnora Vue 组件复制进 MNote runtimeMNote 当前主壳是 Rust SSR / browser runtime,不是 WeKnora Vue 应用。
- 不让 WeKnora 成为 MNote 的文件事实源;本地 `.md`、source registry、workspace path、allowed roots 仍归 MNote。
- 不把 WeKnora 的 tenant / organization / share 语义直接套到 MNoteMNote 的 auth、membership、share grants 由 Rust SQLite control-plane 管。
- 不把 WeKnora 的 tag 删除语义直接映射到 MNote folder 删除;WeKnora tag delete 可能级联删除文档,MNote 需要独立确认。
- 不直接启用手动创建文档,除非 MNote 明确将其映射到本地文件或页面创建命令。
- 不直接复用 WeKnora 的轮询策略作为 MNote 主链;MNote 可展示 provider status,但刷新应优先走 MNote watcher / event / command result。
- 不直接暴露 WeKnora 的删除 KB / 删除文档为本地文件删除;首版删除应只删除 provider index 与 registry 映射,是否删除本地文件必须单独确认。
- 不把 WeKnora `Wiki` / `Graph` 页签默认显示为可用;只有 provider capability 明确可用时展示。
## MNote 最小呈现建议
首版替换原知识库设置面板时,建议至少实现以下可见结构:
1. 知识库列表页:标题、副标题、新建/绑定入口、KB 卡片网格、文档数、processing 状态、provider 能力徽标、设置入口。
2. 知识库详情页:面包屑、KB 切换、文档分类侧栏、搜索/筛选栏、添加文档下拉、网格/列表切换。
3. 文档项:文件名、描述/摘要、标签或来源、文件类型、更新时间、解析状态、打开引用。
4. 状态层:pending / processing / finalizing / completed / failed / cancelled / draft 至少要有不同视觉表达。
5. 安全边界:删除、移动、手动创建、共享、Wiki / Graph、tag 写入可以先隐藏或只读,避免 UI 暗示已具备不可逆能力。