# 7 [recycle] mnote Kernel Phase 7 CLI-First 外置 Agent 统一执行面方案 v4

> 更新时间：2026-05-09
>
> 回收说明（2026-05-13）：
> - 本稿的 `mnote-cli` 唯一长期 agent 执行面口径已被
>   `/mnt/Data1T/mnote/design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md`
>   覆盖。
> - 后续长期方向改为：页面 AI 面板只是 Hermes 的页面内客户端；
>   Hermes session/message/tool event/usage/model 才是会话真相；
>   mnote 通过 Hermes skill/plugin 暴露页面、树、artifact 与 edge 能力。
> - 本稿仅作为历史决策记录保留。`mnote-cli` 仍可作为 mnote plugin 内部适配器，
>   但不再是页面 AI 的唯一长期执行面。
>
> 上位依据：
> - `/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-tree-first-graph-kernel-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-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-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/done/7-1-phase7-document-ai-minimum-loop-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-phase7-ai-kernel-projection-plan-v2.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-phase7-ai-kernel-projection-plan-v3.md`
>
> 本稿与前稿的关系：
> - `v2` 的价值仍然成立：它记录了文档页 AI 最小闭环已经真实完成
> - `v3` 把 CLI 抬到了重要位置，但仍保留了“产品内 AI 主链”和“CLI 外置层”双中心
> - 本稿进一步收口，明确长期只保留一个执行方向：`CLI-first`
>
> 当前判断修正：
> - 文档页 AI 面板不再被视为独立 AI 编排主线
> - `openai-agents-python` 不再被视为长期默认主编排，而是一个可插拔外置 agent
> - Hermes、Codex、后续其他 agent，应统一通过 `mnote-cli` 接入
> - `3000` 上的页面内 AI host 已属于 Rust `mnote-web` 主壳；`/api/ai-agent/run` 只保留 CLI host / adapter 口径
> - 文档页 AI 上下文读取已优先消费 Rust `page aggregate` snapshot，旧 TS builder / 旧 sidecar 只保留 fallback / 对照语义

---

## 1. 文档目的

这份稿只回答一个问题：

> **`mnote` 的长期 AI 执行面，到底要不要收口成一个唯一方向。**

当前答案是：

> **要。长期只保留一个 AI 执行方向：`Rust kernel + mnote-cli`。**

这里的关键不是“要不要有 AI UI”，而是：

- UI 可以存在
- 外置 agent 可以很多
- 但执行面只能有一个

一句话收口：

> **`mnote` 的长期主线不是“内置 AI + CLI 两套长期并存”，而是“CLI 作为唯一 agent 执行面，Web 面板只是其中一个客户端”。**

---

## 2. 为什么要改成 CLI-first

当前如果继续保留两套长期方向：

1. 产品内一套 AI 编排
2. 外置 agent 一套 CLI / 脚本 / Hermes / Codex 编排

那么后面一定会出现这些重复维护成本：

- 两套工具注册表
- 两套上下文装配逻辑
- 两套 session / trace / audit 语义
- 两套能力开关和权限边界
- 两套“哪些命令是真执行、哪些只是兼容层”的判断

这不是“体验和平台分层”，而是“长期双轨维护”。

如果改成 CLI-first：

- Web AI 面板调用 CLI
- Hermes 调用 CLI
- Codex 调用 CLI
- 批处理脚本调用 CLI
- 定时任务调用 CLI

那么系统只维护：

1. 一个真执行方向：`mnote-cli`
2. 多个调用方：UI / agent / script / job

这才是真正减少长期维护成本。

---

## 3. 当前完成情况如何解释

### 3.1 `7-1` 已完成，但不代表它必须成为长期方向

当前文档页 AI 最小闭环已经完成，这个事实不变：

- Rust `mnote.page_aggregate.v1` snapshot 优先的页面上下文读取
- 标题改名与正文改写
- `openai-agents-python` sidecar
- `Hermes` fallback / 对照链

但这个完成状态只能说明：

> **我们已经验证过一条可工作的过渡主链。**

不能自动推出：

> **这条链就应该变成长期唯一主线。**

所以 `v2` 记录的工作不是白做，而是：

- 它证明了产品内写链需求真实存在
- 它帮助我们冻结了最小工具面和页面语义面
- 它可以作为 CLI-first 迁移时的行为基线

### 3.2 `mnote-cli` 已经足够接近长期入口

当前 `mnote-cli` 已经不是空壳，已有：

- `page`
- `block`
- `mindmap`
- `search`
- `sidebar`
- `editor`
- `tool`

同时还有这些关键参数：

- `--json`
- `--execute`
- `--session-id`
- `--reason`
- `--idempotency-key`
- `--validate-only`
- `--dry-run`

这说明真正应该继续投入的，不是再扩一条新的 Web 内 AI 主编排，而是把 CLI 补成稳定统一入口。

---

## 4. 新的长期架构边界

### 4.1 Rust kernel / Rust runtime

负责：

- 唯一事实源
- query / command / projection
- page / tree / graph / artifact 的正式语义
- 审计、回放、恢复、trace 基线

不负责：

- 直接承担 agent 编排
- 直接承担产品聊天壳

### 4.2 `mnote-cli`

负责：

- 唯一长期 agent 执行入口
- 人类、脚本、外置 agent、Web 面板的统一命令面
- 统一能力发现、统一参数面、统一输出结构

必须成为：

- CLI for humans
- CLI for agents
- CLI for jobs
- CLI for UI host

### 4.3 Web AI 面板

负责：

- 当前页面里的交互壳
- 流式展示
- 输入与审核
- 把页面上下文转成 CLI 可消费的标准输入
- 接收 CLI 事件并映射到 UI

不负责：

- 自己维护独立 agent 编排层
- 自己维护独立工具注册表
- 自己定义长期 AI 契约

### 4.4 外置 agent

包括但不限于：

- Hermes
- Codex
- `openai-agents-python`
- 后续任何别的 agent runtime

它们都负责：

- 决策
- 推理
- 工具调用策略
- 会话层 orchestration

但它们不应再直接拥有产品内部执行面。它们都应该统一调用 `mnote-cli`。

---

## 5. `openai-agents-python` 的新定位

### 5.1 不再是默认主线

从本稿开始，`openai-agents-python` 不再被定义为：

- `mnote` 的长期默认主编排
- 产品内文档页 AI 的唯一推荐底座

### 5.2 改为可插拔外置 agent

它的新定位是：

> **一个可插拔外置 agent runtime。**

也就是说，它和 Hermes、Codex 的区别不再是“谁是产品内主线”，而只是：

- 不同的 agent 实现
- 不同的模型接法
- 不同的 orchestration 风格

是否继续保留它，取决于后续真实效果，而不是当前历史惯性。

### 5.3 可接受的长期状态

未来允许的状态有三种：

1. `openai-agents-python` 继续保留，作为可选外置 agent
2. `openai-agents-python` 仅保留在测试 / 回归 / 对照场景
3. `openai-agents-python` 完全移除

这三种都不影响长期主架构，只要唯一执行面还是 `mnote-cli`。

---

## 6. CLI-first 下的产品口径

### 6.1 UI 仍然可以有 AI 面板

CLI-first 不等于没有产品内 AI UI。

允许继续有：

- 文档页 AI 面板
- mindmap AI 面板
- OnlyOffice AI 面板

但这些都只是：

> **CLI 的图形客户端。**

并且当前这些客户端默认都挂在 `3000` 的 Rust `mnote-web` 主壳里，而不是重新长出第二套产品内执行面。

### 6.2 页面语义仍然保留

虽然主执行面转到 CLI，但下面这些产品语义仍要保留：

- `model_key`
- `profile`
- `tool registry`
- `page aggregate`
- `page.head.updateTitle`
- `page.body.save`
- `tree.*`

区别只在于：

- 这些不再由 Web 内独立 AI service 主导
- 而是由 CLI 和 Rust runtime 共同定义

### 6.3 Web 不应再拥有第二套工具面

当前过渡工具面：

- `doc_get`
- `doc_find`
- `doc_insert_blocks`
- `doc_replace_range`
- `slash_run`

如果继续保留，后续也应通过 CLI 暴露，而不是继续作为 Web 内 service 的私有 tool surface。

---

## 7. CLI 必须补齐的能力

CLI-first 成立的前提，不是“已有命令很多”，而是这些能力必须补齐。

### 7.1 能力发现

至少要能稳定输出：

- 当前有哪些 command / query / job
- 每个能力属于哪个 scope
- 哪些可读，哪些可写
- 哪些是 plan-only
- 哪些是真执行

建议提供：

- `mnote-cli capabilities --json`

### 7.2 标准上下文输入

CLI 需要能接住标准化页面上下文，而不是让每个 agent 自己拼：

- `documentId`
- `blocks`
- `pageOptions`
- `editorRuntimePageOptions`
- `subtree`
- `outline`
- `evidence`

也就是说，页面上下文装配逻辑要从 Web 内 agent service 迁到 CLI 兼容输入协议。

### 7.3 标准事件输出

如果 Web 面板要流式显示，CLI 需要输出标准事件流，例如：

- `assistant_message`
- `tool_call`
- `tool_result`
- `completion`
- `error`

否则 UI 最后还是会倒逼出第二套 Web 内编排逻辑。

### 7.4 标准 session / trace / audit

CLI 必须把这些变成一等能力：

- `session_id`
- `trace_id`
- `request_id`
- `actor_id`
- `actor_type`
- `reason`
- `idempotency_key`

### 7.5 权限与 scope

CLI 要能明确限制：

- 当前只能改当前页
- 当前允许跨页还是不允许
- 当前允许树命令还是只允许页面命令
- 当前是只读、写入还是后台 job

---

## 8. 顺序执行 checklist

本节是后续实施时的主 checklist。执行时从上到下推进，完成一项后把对应 `- [ ]` 改成 `- [x]`，并在该项下补充实际命令输出摘要或截图路径。

### 8.1 冻结 CLI 基座

- [x] 确认 `mnote-cli` 顶层命令存在。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --help
    ```
  - 通过标准：输出包含 `page`、`block`、`mindmap`、`search`、`sidebar`、`editor`、`tool`。

- [x] 确认 agent 必需的全局参数存在。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --help
    ```
  - 通过标准：输出包含 `--json`、`--execute`、`--session-id`、`--reason`、`--idempotency-key`、`--validate-only`、`--dry-run`。

- [x] 确认 `mnote-cli` 当前单测全通过。
  - 命令：
    ```bash
    cargo test --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli
    ```
  - 通过标准：所有 `mnote-cli` 测试通过；当前基线应覆盖 CLI JSON contract、tool registry、page/save/sidebar 等最小面。

### 8.2 冻结页面命令面

- [x] 验证页面读取命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json page get --page-id page_demo --workspace-id ws_demo
    ```
  - 通过标准：输出包含 `domain: page`、`action: get`、`context.requestId`、`context.traceId`，并且 `operation.transport.functionName` 指向页面查询。

- [x] 验证页面标题更新命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json page title --page-id page_demo --workspace-id ws_demo --title "新标题"
    ```
  - 通过标准：输出包含 `domain: page`、`action: title`、`operation.kind: command`，并且写入仍通过 Rust runtime / transport 计划表达。

- [x] 验证页面正文保存命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json page save --page-id page_demo --workspace-id ws_demo --content-json '[{"id":"block_1"}]'
    ```
  - 通过标准：输出包含 `domain: page`、`action: save`、`operation.kind: command`，并且能看到正文写回 payload。

### 8.3 冻结块命令面

- [x] 验证块插入命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json block insert --page-id page_demo --workspace-id ws_demo --block-type paragraph --content "hello"
    ```
  - 通过标准：输出包含 `domain: block`、`action: insert`、`operation.kind: command`、`operation.name: insert_block`。

- [x] 验证块 patch 命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json block patch --page-id page_demo --workspace-id ws_demo --block-id block_demo --snapshot-json '{"id":"block_demo","type":"paragraph","text":"updated"}'
    ```
  - 通过标准：输出包含 `domain: block`、`action: patch`、`operation.kind: command`、`operation.name: blocks.patch`。

- [x] 第 1 步：固定块写链仍然存在的入口证据。
  - 结果：`/api/ai-agent/run` 已不再直接恢复 `slash_run` / `doc_insert_blocks` / `doc_replace_range`，`DocumentAiAgentPanel.runtime.tsx` 也已不再声明 Web 内独立 agent owner。
  - 验证命令：
    ```bash
    rg -n "doc_insert_blocks|doc_replace_range|slash_run|startHermesRun|startDocumentAiOrchestratorRun|runCodexBridge" \
      /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts \
      /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx
    ```

- [x] 第 2 步：把块写链收口为 `mnote-cli` host / adapter。
  - 结果：`/api/ai-agent/run` 已收口为薄 host route，块写链不再由 Web 私有 service 直接拥有。
  - 依赖检查：`DocumentAiAgentPanel.runtime.tsx` 与 `/api/ai-agent/run/route.ts` 的 contract 说明已同步改成 `mnote-cli`。

- [x] 第 3 步：补块写链回归测试。
  - 结果：`pnpm vitest run src/components/editor/DocumentAiAgentPanel.runtime.test.tsx` 与 `pnpm vitest run src/app/api/ai-agent/run/route.test.ts` 通过。
  - 验证命令：
    ```bash
    pnpm vitest run /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.test.tsx
    pnpm vitest run /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.test.ts
    ```
  - 通过标准：测试明确约束块写链不再暴露 Web 私有 tool surface，并且 route 只保留 CLI host / adapter 语义。

### 8.4 冻结搜索与侧栏投影

- [x] 验证文档搜索命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json search documents --workspace-id ws_demo --query "Hermes"
    ```
  - 通过标准：输出包含 `domain: search`、`action: documents`，并且查询计划可由 Web host 消费。

- [x] 验证块搜索命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json search blocks --page-id page_demo --query "Hermes"
    ```
  - 通过标准：输出包含 `domain: search`、`action: blocks`、`operation.name: search_blocks`。

- [x] 验证侧栏数据集命令。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json sidebar dataset --workspace-id ws_demo
    ```
  - 通过标准：输出包含 `domain: sidebar`、`action: dataset`、`operation.name: sidebar.dataset.list`。

### 8.5 冻结 tool runtime 与安全模式

- [x] 验证 `tool run` 的只读 explain-plan。
  - 命令：
    ```bash
    cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --json --validate-only --dry-run --session-id ai-checklist-smoke --reason phase7-cli-first --idempotency-key phase7-cli-first-001 tool run --tool-name doc_get --kind query --mode explain-plan --args-json '{"pageId":"page_demo"}'
    ```
  - 通过标准：输出包含 `domain: tool`、`action: run`、`context.sessionId: ai-checklist-smoke`、`context.idempotencyKey: phase7-cli-first-001`、`context.validateOnly: true`、`context.dryRun: true`。

- [x] 验证 tool registry 会拒绝未知工具。
  - 命令：
    ```bash
    cargo test --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli tool_run_rejects_unknown_tool
    ```
  - 通过标准：测试通过，未知 tool 不会绕过 registry 进入执行链。

- [x] 确认 `--reason` 进入后续 audit / trace 输出规划。
  - 当前状态：CLI 参数面已接住 `--reason`，并已在标准 JSON 输出的 `context.reason` 中展开。
  - 通过标准：`cargo test --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli output_context_keeps_reason_for_audit_trace` 通过，断言 `reason` 出现在标准 audit / trace 输出上下文里。

### 8.6 将 Web AI 面板降为 CLI host

- [x] 检查 Rust Web checklist 的 Phase 5 口径。
  - 文件：`/mnt/Data1T/mnote/design/03-rust-web/process/3-1-rust-web-long-term-checklist-v2.md`
  - 通过标准：AI 面板只能写成 `mnote-cli` host/client 收口，不得写成 Hermes 或 `openai-agents-python` 长期主编排。
  - 已核对：该文件明确写入“长期执行面应收口到 `mnote-cli`，Web 只作为 CLI host / client，`openai-agents-python` / Hermes / Codex 只作为可插拔外置 agent”。

- [x] 检查 legacy Next retirement gate 的 AI route 口径。
  - 文件：`/mnt/Data1T/mnote/design/03-rust-web/done/3-11-rust-web-legacy-next-retirement-gates-v1.md`
  - 通过标准：`/api/ai-agent/run` 只能写成 CLI host / adapter 兼容层，结构化写入必须经 Rust runtime。
  - 已核对：该文件明确写入 `/api/ai-agent/run` 长期应降为 `mnote-cli` host / adapter，结构化写入必须经 Rust runtime，外置 agent 不得拥有第二执行面。

- [x] 检查 Wolai-aline E27 的 AI 编辑口径。
  - 文件：`/mnt/Data1T/mnote/design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md`
  - 通过标准：E27 只能把 `openai-agents-python` sidecar / Hermes fallback 当作过渡基线，不得写成长期主线。
  - 已核对：E27 明确写入 `/api/ai-agent/run` 后续应收口为 `mnote-cli` 唯一长期 agent 执行面上的 host / adapter，`Hermes`、`Codex`、`openai-agents-python` 仅为可插拔外置 agent；当前 sidecar / fallback 只保留为过渡行为基线。

### 8.7 外置 agent 接入顺序

- [x] 第 1 步：冻结 Codex 接入方式。
  - 结果：`/api/ai-agent/run` 已不再保留 `provider=codex` 默认系统入口；请求会明确失败，默认主链只保留 `mnote-cli` host。
  - 验证命令：
    ```bash
    rg -n "provider === \"codex\"|runCodexBridge" /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts
    ```

- [x] 第 2 步：冻结 Hermes 接入方式。
  - 结果：`/api/ai-agent/run` 已不再直接调用 `startHermesRun` / `streamHermesRunEvents`，Hermes 不再是长期主链入口。
  - 验证命令：
    ```bash
    rg -n "startHermesRun|streamHermesRunEvents|/api/hermes/bridge" /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts
    ```

- [x] 第 3 步：冻结 `openai-agents-python` 接入方式。
  - 结果：`DocumentAiAgentPanel.runtime.tsx` 已不再声明 `bridgeOwner: "openai-agents-python"`，`provider=online` 也不再直连 orchestrator。
  - 验证命令：
    ```bash
    rg -n "bridgeOwner: \"openai-agents-python\"|startDocumentAiOrchestratorRun|/api/v1/ai-agent/document/run" /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts
    ```

- [x] 第 4 步：统一复核外置 agent 只剩 CLI host / adapter 口径。
  - 结果：已通过全局复核，`/api/ai-agent/run` 与面板 contract 都只保留 `mnote-cli` host / adapter 口径。
  - 验证命令：
    ```bash
    rg -n "provider === \"codex\"|runCodexBridge|startHermesRun|streamHermesRunEvents|bridgeOwner: \"openai-agents-python\"|startDocumentAiOrchestratorRun|/api/v1/ai-agent/document/run" \
      /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts \
      /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx
    ```

### 8.8 完成判定

- [x] `mnote-cli` 的最小命令面在仓库内可复跑。
  - 已验证：`cargo run --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli -- --help` 与 `cargo test --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli` 可复跑。
- [x] `--json`、`--execute`、`--validate-only`、`--dry-run` 的输出语义稳定。
  - 已验证：`--help` 暴露这些全局参数；`tool run --validate-only --dry-run --json` 输出包含 `context.validateOnly: true`、`context.dryRun: true`、`context.reason`、`context.sessionId` 与 `context.idempotencyKey`。
- [x] `page / block / search / sidebar / tool` 都能用 CLI 直接验证。
  - 已验证：8.2 到 8.5 已分别覆盖 `page get/title/save`、`block insert/patch`、`search documents/blocks`、`sidebar dataset`、`tool run`。
- [x] 第 1 步：确认 Web AI 面板的 contract 已收口为 `mnote-cli` host / client。
  - 结果：面板 contract 已改为 `bridgeOwner: "mnote-cli"`，`runtimeRole: "mnote_cli_host_client"`。
  - 验证命令：
    ```bash
    rg -n "bridgeOwner: \"openai-agents-python\"|startDocumentAiOrchestratorRun|startHermesRun|runCodexBridge" /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts
    ```

- [x] 第 2 步：确认三类外置 agent 都只作为可插拔 runtime。
  - 结果：`route.ts` 已收口为统一 host route，不再按 provider 分叉到三套执行实现。
  - 验证命令：
    ```bash
    rg -n "provider === \"codex\"|runCodexBridge|startHermesRun|startDocumentAiOrchestratorRun|bridgeOwner: \"openai-agents-python\"" /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.ts /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx
    ```

- [x] 第 3 步：补完成判定的最终回归。
  - 结果：`cargo test --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli`、`pnpm vitest run src/components/editor/DocumentAiAgentPanel.runtime.test.tsx`、`pnpm vitest run src/app/api/ai-agent/run/route.test.ts` 均通过。
  - 验证命令：
    ```bash
    cargo test --manifest-path /mnt/Data1T/mnote/rust/Cargo.toml -p mnote-cli
    pnpm vitest run /mnt/Data1T/mnote/wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.test.tsx
    pnpm vitest run /mnt/Data1T/mnote/wolai-frontend/src/app/api/ai-agent/run/route.test.ts
    ```
- [x] 结构化写入仍然只能回到 Rust runtime / Rust kernel。
  - 已核对：本稿 4.1、6.2、6.3 明确 Rust kernel / Rust runtime 负责事实源、query / command / projection 与结构化写入语义；`3-11` 与 Wolai-aline E27 均要求结构化写入经 Rust runtime 或 Rust tool / `page.body.save`。

### 8.9 当前测试账号写入验收

- [x] 确认当前测试账号默认允许 CLI 新建与编辑页面。
  - 命令：
    ```bash
    cd /mnt/Data1T/mnote/wolai-frontend && pnpm vitest run src/lib/server/mnote-cli-agent-host.test.ts
    ```
  - 通过标准：`mnote-cli` host 默认下发 `MNOTE_CLI_ALLOW_CREATE_PAGE=1` 与 `MNOTE_CLI_ALLOW_EDIT=1`，并把当前 Web 用户身份透传给 CLI 子进程。

- [x] 确认当前测试账号的真实 workspace。
  - 当前测试账号：
    - `userId`: `a4b72c17-49e3-46d3-8456-24a0a7044d64`
    - `email`: `dev@mnote.local`
    - `name`: `开发用户`
  - 当前真实 workspace：
    - `workspaceId`: `ws_req_1778035004501_15`
    - `workspaceName`: `开发用户 的空间`
    - 首个页面：`tree_1778036320856_1` / `新页面`
  - 通过标准：`/api/sidebar?workspaceId=ws_req_1778035004501_15` 返回的 `documents` 至少包含 `tree_1778036320856_1`。

- [x] 确认旧的 CLI 直连 Convex identity 不能作为长期用户写入面。
  - 事实：曾经写入 `tree_1777430834634_3` / `anonymous 的空间` 的页面不会出现在当前测试账号的 `ws_req_1778035004501_15`。
  - 结论：CLI 不能长期依赖伪造 `DEV_USER_ID` 直连 Convex 来代表用户写入；需要由 Web host / mnote-web 持有当前会话、当前 workspace 与 capability 后再执行写入。

- [x] 确认 CLI host 会把当前页面 workspaceId 传入 CLI 输入。
  - 命令：
    ```bash
    cd /mnt/Data1T/mnote/wolai-frontend && pnpm vitest run src/lib/server/mnote-cli-agent-host.test.ts
    ```
  - 通过标准：`--args-json` 包含 `workspaceId: "ws_req_1778035004501_15"`，避免 agent 在缺省 workspace 或错误 workspace 下创建页面。

- [x] 用当前浏览器会话创建当前账号可见页面。
  - 执行动作：在已登录测试账号的浏览器会话中调用 `/api/tree/commands` 创建：
    - `CLI 可见测试 A 1778057564`
    - `CLI 可见测试 B 1778057564`
  - 通过标准：创建响应的 `result.workspaceId` 必须是 `ws_req_1778035004501_15`。
  - 当前验证结果：
    - `tree_1778048047020_3` / `CLI 可见测试 A 1778057564`
    - `tree_1778048047047_4` / `CLI 可见测试 B 1778057564`

- [x] 用当前浏览器会话读取 sidebar 确认可见。
  - 命令等价动作：
    ```js
    await fetch('/api/sidebar?workspaceId=ws_req_1778035004501_15', { credentials: 'include' })
    ```
  - 通过标准：返回的 `documents`、`kernelSidebarProjection.items` 与 `kernelSidebarTree` 都包含 `CLI 可见测试 A 1778057564` 与 `CLI 可见测试 B 1778057564`。
  - 当前验证结果：已在浏览器会话 API 返回中看到上述两个页面；同一返回还显示早先创建的 `cli_visible_ws_req_a_17780471` 与 `cli_visible_ws_req_b_17780471`。

- [ ] 后续权限收口：补“禁止/允许 CLI 编辑、允许 CLI 新建页面”的 capability 开关。
  - 目标：默认测试账号允许 CLI 新建与编辑；后续真实用户可在设置中关闭关键页面的 CLI 编辑或新建权限。
  - 通过标准：CLI 写操作必须能读到当前用户、当前 workspace、当前页面 capability；未授权时返回明确拒绝，不得 fallback 到 anonymous / dev identity 写入。

- [x] 确认 Web host 会把当前会话用户透传给 `mnote-cli` 作为默认执行身份。
  - 命令：
    ```bash
    cd /mnt/Data1T/mnote/wolai-frontend && pnpm vitest run src/lib/server/mnote-cli-agent-host.test.ts src/app/api/ai-agent/run/route.test.ts
    ```
  - 通过标准：`spawn("cargo", ...)` 的环境变量包含 `DEV_USER_ID`、`DEV_USER_EMAIL`、`DEV_USER_NAME`，并默认下发 `MNOTE_CLI_ALLOW_CREATE_PAGE=1` 与 `MNOTE_CLI_ALLOW_EDIT=1`。

---

## 9. 迁移策略

### 阶段 0：口径冻结

先冻结新的长期判断：

- CLI 是唯一长期执行面
- Web 面板只是 CLI 客户端
- `openai-agents-python` 是可插拔外置 agent
- Hermes / Codex / 后续 agent 不再拥有默认产品内入口；是否保留仅通过显式接线决定

### 阶段 1：CLI 能力补齐

优先补：

- capability manifest
- 标准 JSON 输出
- 事件流输出
- 标准上下文输入
- 标准 trace / session 参数

### 阶段 2：Web 面板降为 CLI host

把当前文档页 AI 面板从：

- “调用内置 sidecar 主编排”

改成：

- “调用 CLI host / CLI adapter”

### 阶段 3：外置 agent 接入统一化

统一支持：

- Hermes -> 显式外置接线或停用
- Codex -> 显式外置接线或停用
- `openai-agents-python` -> `mnote-cli`

### 阶段 4：裁剪过渡层

当 CLI-first 成熟后，逐步评估：

- 是否保留 `openai-agents-python`
- 是否保留当前 Web 内 sidecar
- 是否完全移除旧过渡桥接

---

## 10. 非目标

本稿不做：

- 再造一套新的 agent 平台
- 让 Web 直接失去 AI UI
- 把所有 UX 都退回终端
- 让 Hermes 或 `openai-agents-python` 重新变成产品内部唯一中心

---

## 11. 完成判定

满足下面几项时，才算 CLI-first 真正成立：

- `mnote-cli` 是唯一长期 agent 执行入口
- Web AI 面板只是 CLI host，不再持有独立主编排
- Hermes / Codex / `openai-agents-python` 都通过 CLI 接入
- Rust kernel 仍然是唯一事实源
- 是否保留 `openai-agents-python` 不影响整体架构

一句话收口：

> **`mnote` 的长期 AI 方向应当收口成“Rust kernel 负责真相，`mnote-cli` 负责唯一执行面，外置 agent 全部可插拔，Web 只做交互壳”。**
