# 7-39 Page AI agent selector / context authorization settings v1

> 创建时间：2026-05-28
> 状态：`done`
> 来源：用户反馈：旧 Page AI UI 信息过载；“编辑当前文件 / 工作区任务”模式难以区分；应参考 Cline 在聊天输入区选择 agent，并在设置里按 agent 拆分配置；共性设置包含授权区域，且授权区域应按用户保存到 SQLite。

## 1. 背景

当前 Page AI sidebar 已能接 Hermes / Reasonix ACP、run target snapshot、changed files 审计和 SQLite directory grant。但 UI 仍把 agent、profile、runtime、skills、history、gateway、工具参数和错误解释混在一个抽屉中。

这导致两个问题：

- 用户真正要做的只是“选择 agent + 勾选要发给 AI 的上下文地址”，但现有 UI 暴露了太多 Hermes/ACP 运行细节。
- “编辑当前文件”和“工作区任务”作为主模式不稳定：本地 Markdown、打开资源、选区、文件夹和 changed files 都可能参与同一轮请求，按模式分流反而会误导。

因此后续 Page AI 主线应从“模式切换”改为“agent 选择 + contextRefs 勾选 + SQLite 授权区域”的结构。

## 2. 参考对照

### 2.1 Cline

可借鉴模型：

- 聊天输入区附近直接选择当前 agent / provider，让用户在提问前完成路由决策。
- 设置页按 agent/provider 拆分，只把模型、profile、API、能力开关等差异配置放在各自页面。
- 共性配置只放所有 agent 都需要理解的内容，例如上下文、授权、审计、历史保留。

只作参考实现：

- Cline 的 VSCode extension 状态和 provider 配置模型不能直接搬到 MNote；MNote 的授权区域必须继续走 SQLite control-plane，而不是浏览器 localStorage 或扩展全局状态。

不适合 MNote：

- 不把“工作区 root”当作天然可读写范围。MNote 必须基于 `directory_grants` / `AiAccessScope` / allowed roots 显式授权。

### 2.2 Sidex / VSCode workbench

可借鉴模型：

- 资源、编辑器、工作区和最近修改项应以可选引用表达，而不是把 UI 分成互斥大模式。
- Explorer / editor / panel 可以共享同一份打开资源快照，但不把 UI 层选择变成新的树或页面真相。

只作参考实现：

- VSCode / Sidex 的工作台布局、tab model、context key 可以作为交互参考；MNote 的真实对象边界仍由 Rust kernel projection、local-first source 和 control-plane 决定。

不适合 MNote：

- 不把 VSCode extension host 或工作台状态当成 MNote 的资源生命周期真相。

## 3. 产品决策

### 3.1 主聊天面

主聊天面只保留四类默认可见元素：

- 当前 agent 选择器：位于输入框下方或紧邻输入框，默认可快速切换 `Hermes`、`Reasonix`、`Chat-only`，未来可扩展其他 agent。
- ContextRefs 选择器：用勾选 chip / popover 选择要发送的地址，例如当前页、当前选区、当前打开资源、指定文件、指定文件夹、最近 changed files。
- 输入框和发送/停止按钮。
- 简短状态：只显示“可用 / 需要授权 / 正在运行 / 失败”，不默认显示 gateway、profile、tool trace、参数 JSON。

“编辑当前文件 / 工作区任务”不再作为主模式。它们退化为 contextRefs 的预设组合：

- 编辑当前文件：勾选当前页或当前文件 + 必要的写权限 allowed root。
- 工作区任务：勾选一个或多个文件夹 / changed files / 打开资源 + 必要的读写授权。

### 3.2 共性设置

共性设置只保留所有 agent 共享的配置：

- 授权区域：来自 SQLite `directory_grants`，按当前用户隔离；展示 root、权限、递归、来源、状态。
- 默认上下文勾选：保存到 SQLite `user_ui_preferences`，按用户、workspace、source 隔离。
- changed files 展示策略：默认收起，只显示摘要；需要时展开路径/hash/mtime/actor。
- 隐私与审计：是否记录 local agent audit、是否展示工具细节、是否允许发送选区/页面摘要。

共性设置不放 Hermes profile、Reasonix skill、provider API key、模型 preset 等 agent 差异项。

### 3.3 Agent 设置

设置页按 agent 拆分：

- Hermes：profile、Hermes 设置入口、Hermes/ACP runtime 兼容提示、Hermes 专属 tool 开关。
- Reasonix：ACP runtime、Reasonix skill 只读/可用状态、Reasonix 专属能力说明。
- Chat-only：模型或 provider 选择、是否允许读取 contextRefs；默认不请求文件写权限。
- 未来 agent：通过 registry 增加 `agentId`、显示名、能力、设置 schema、运行入口。

每个 agent 只能保存自身差异设置。默认 agent、默认 contextRefs 和授权区域仍属于共性设置。

## 4. 数据与合同

### 4.1 SQLite 使用边界

优先复用现有 control-plane：

- `directory_grants`：每用户授权区域真相。用于生成 allowed roots，不能由前端本地状态代替。
- `user_ui_preferences`：每用户 UI 偏好。用于默认 agent、contextRefs 默认勾选、授权区域排序/最近使用、UI 密度、changed files 默认折叠状态。

建议 preference scope / key：

- `scope = "ai.common"`，`key = "default_agent_id"`
- `scope = "ai.common"`，`key = "context_refs.default_selected"`
- `scope = "ai.common"`，`key = "allowed_roots.default_selected"`
- `scope = "ai.common"`，`key = "allowed_roots.order"`
- `scope = "ai.common"`，`key = "changed_files.visibility"`
- `scope = "ai.agent.hermes"`，`key = "profile_id"`
- `scope = "ai.agent.reasonix"`，`key = "skill_panel_filter"`
- `scope = "ai.agent.chat_only"`，`key = "model_id"`

除非现有表无法表达这些键值，否则第一阶段不新增 SQLite 表。

### 4.2 请求合同草案

```json
{
  "agentId": "reasonix",
  "message": "把选中的 Markdown 小节整理成 checklist",
  "contextRefs": [
    {
      "kind": "current_page",
      "documentId": "local:page:example",
      "rootUri": "file:///mnt/Data1T/mnote"
    },
    {
      "kind": "folder",
      "rootUri": "file:///mnt/Data1T/mnote",
      "relativePath": "design/07-ai"
    }
  ],
  "allowedRoots": [
    {
      "rootUri": "file:///mnt/Data1T/mnote",
      "permission": "write",
      "source": "sqlite_directory_grant"
    }
  ],
  "runTargetSnapshot": {
    "version": "mnote.page_ai_run_target_snapshot.v1",
    "source": "open_editors_snapshot"
  },
  "audit": true
}
```

合同要求：

- `agentId` 是主路由字段，不能再隐含在 Hermes profile 或 ACP runtime 里。
- `contextRefs` 是用户勾选的上下文地址，不等同于授权。
- `allowedRoots` 必须由 SQLite 授权区域和当前用户权限解析得到。
- `runTargetSnapshot` 继续从 `OpenEditorsSnapshot` 冻结，防止 run 过程中 UI 切换导致目标漂移。

## 5. 旧 UI 精简规则

默认聊天面应移除或收敛：

- 顶部同时展示 Hermes/profile/model/gateway 的多重状态。
- 空态长解释、示例段落和“后端 AI 会话”等实现细节文案。
- Runtime 页默认暴露 gateway health、queue、tools、trace、audit JSON。
- Skills 页默认展示大量参数、来源和技术标签。
- 工具调用卡默认展示 args/result/raw trace。
- 输入框 placeholder 固定写“问 Hermes...”。

替代方式：

- 默认只显示 agent 名称、上下文 chips、运行状态和最终回答。
- 工具调用、changed files、audit、gateway、queue 进入“高级 / 调试”折叠区。
- 错误文案先给用户动作，例如“需要授权该文件夹”，再提供可展开技术详情。
- Hermes / Reasonix 的差异通过 agent selector 和 agent 设置页表达，不在聊天主面堆说明。

## 6. Checklist

### Batch A - 现状冻结与视觉基线

- [x] 截图记录当前 Page AI sidebar 默认聊天面、Agent、Runtime、Skills、History 页。
- [x] 标注必须删除/折叠的冗余信息：profile/model/gateway、runtime queue、tool trace、长空态说明、Hermes 固定文案。
- [x] 对照 Cline agent selector 和 Sidex/VSCode context/resource 模型，形成最小信息架构图。
- [x] 验证：浏览器截图 + 本文档补充截图路径或 smoke 证据。

证据：旧 UI 基线截图在 `tmp/task502-page-ai-baseline/01-chat.png`、`02-agent.png`、`03-skills.png`、`04-runtime.png`、`05-history.png`；收敛后截图在 `tmp/task502-page-ai-agent-selector-context-smoke/01-agent-selector-context.png`。

### Batch B - Agent registry 与主选择器

- [x] 定义 `agentId` registry：`hermes`、`reasonix`、`chat_only`，包含显示名、能力、是否可写文件、设置页入口。
- [x] 在聊天输入区附近增加 agent selector，替代“Hermes 固定入口”。
- [x] 发送请求时 payload 显式包含 `agentId`，旧 Hermes/Reasonix ACP 路径保持兼容。
- [x] 验证：node smoke 断言切换 agent 后 payload 和 UI badge 正确。

证据：`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 的 `PAGE_AI_AGENT_REGISTRY`；`scripts/task502-page-ai-agent-selector-context-smoke.js` 断言 `hermes/reasonix/chat_only` 和 run payload `agentId=reasonix`。

### Batch C - ContextRefs 勾选器

- [x] 定义 `contextRefs` 类型：当前页、选区、当前打开资源、文件、文件夹、changed files。
- [x] ContextRefs 从 `OpenEditorsSnapshot`、当前 selection 和授权目录解析，不新增第二套页面/树真相。
- [x] “编辑当前文件 / 工作区任务”改为预设组合，不再作为主模式。
- [x] 验证：浏览器 smoke 勾选/取消 contextRefs 后 payload 稳定。

证据：`PAGE_AI_CONTEXT_REF_REGISTRY` 覆盖 `current_page/selection/active_editor/file/folder/changed_files`；`task502` 断言取消 `current_page` 后 payload 不含 `current_page/pageText/pageXml/contextBlocks`，并保留 `active_editor/folder/changed_files`。

### Batch D - SQLite per-user 授权区域

- [x] 共性设置页展示当前用户的 `directory_grants`，支持按 read/write、recursive、source、status 展示。
- [x] allowedRoots 只从当前用户 SQLite grant 解析；未授权目录必须提示授权，不允许前端绕过。
- [x] 修复/补齐 Page AI 与 ACP session create 的授权路径一致性，确保已授权本地文件夹不会误报无权访问。
- [x] 验证：Rust 定点测试覆盖不同用户 grant 隔离；浏览器 smoke 覆盖授权目录可用、未授权目录阻断。

证据：`hermes_client.rs::enforce_local_ai_run_access` 服务端按当前 actor 调 `resolve_access` 重算 `allowedRoots`；`sqlite.rs::resolve_access_does_not_match_sibling_uri_prefix` 修复 URI 前缀串扰；`local_ai_run_access_*` 覆盖 Alice/Bob 隔离与 `chat_only` read-only 降级；`task502` 验证授权 chip 来源为 `sqlite_directory_grant`。

### Batch E - UI preference per-user 持久化

- [x] 用 `user_ui_preferences` 保存默认 agent、默认 contextRefs、授权区域排序/默认勾选、changed files 折叠策略。
- [x] preference key 必须带 user/workspace/source/scope 边界。
- [x] 不用 localStorage 作为长期真相；最多作为临时 UI cache。
- [x] 验证：Rust/JS smoke 覆盖 Alice/Bob 设置隔离。

证据：`ui_preferences.rs` 支持 `ai.common.*` 与 `ai.agent.*`，并返回 `aiPreferences`；`ui_preferences_api_persists_ai_preferences_per_user_and_workspace` 验证 Alice/Bob 隔离；`task502` 捕获 PUT `ai.common.default_agent_id` 与 `ai.common.context_refs.default_selected`。

### Batch F - Agent 分页设置

- [x] 设置页拆为 Common / Hermes / Reasonix / Chat-only。
- [x] Common 只放授权、默认 contextRefs、隐私/审计、changed files 显示策略。
- [x] Hermes 只放 Hermes profile/upstream/tool 相关设置。
- [x] Reasonix 只放 Reasonix ACP/skill 相关设置。
- [x] Chat-only 默认不申请文件写权限。
- [x] 验证：浏览器 smoke 检查不同 agent 设置页不串项。

证据：`ensurePageAiDrawer` 拆出 Common/Hermes/Reasonix/Chat-only/高级面板；`task502` 用真实浏览器检查 Common 不含 `Hermes profile/ACP runtime`，Hermes 不混 Reasonix，Reasonix 不混 Hermes，Chat-only 不混 ACP runtime。

### Batch G - 旧 UI 冗余收敛

- [x] 默认聊天面隐藏 gateway/model/profile 技术细节，只保留 agent badge 和简短状态。
- [x] Runtime/Skills/History 收敛为高级/调试入口，默认不进入主聊天流。
- [x] 工具调用卡默认只展示摘要；args/result/raw trace 需展开。
- [x] changed files 默认摘要，展开后才显示路径/hash/mtime/actor。
- [x] 验证：浏览器截图对比，确保主面没有冗余解释和技术噪音。

证据：`sidebar-page-ai-runtime.js` 默认标题状态改为 agent/授权/AI session，旧 `Hermes session` 与固定 `问 Hermes` 文案退出默认聊天面；`task502` 断言默认聊天面不含 `问 Hermes/model.default/gateway:/Hermes profile`；changed files 仍由 `task453-local-folder-page-ai-changed-files-smoke.js` 验证摘要与 dirty 阻断。

### Batch H - 合同与回归

- [x] 补最小 JS/Rust 合同注释或文档，说明 `agentId + contextRefs + allowedRoots + runTargetSnapshot` 的边界。
- [x] 保持 Hermes / Reasonix 旧 payload 兼容，必要时在 route 层做窄 adapter。
- [x] 更新 Page AI smoke，覆盖 agent 切换、contextRefs、SQLite 授权、per-user preference、旧路径兼容。
- [x] 运行 `git diff --check`、相关 Rust 定点测试、node smoke；涉及代码图修改后运行 `codegraph sync .`。

证据：JS 合同注释位于 `PAGE_AI_AGENT_REGISTRY` 前；服务端兼容仍走 `/api/hermes/client/runs` 和 ACP `profile/acpRuntime` adapter；最终验证矩阵见本次执行汇报。

## 7. 非目标

- 不实现 Phase C 流式 apply / suggest review。
- 不把 local-first 普通 Markdown 编辑改回粗粒度 `mnote.page.save`。
- 不新增第二套树真相、页面真相、资源生命周期真相或授权真相。
- 不把 agent 设置写入浏览器 localStorage 作为长期存储。
- 不在本批次重构 Hermes / Reasonix agent 内部运行时。

## 8. 待确认产品点

- Agent selector 的精确位置：输入框下方一行，还是输入框左侧/上方 compact row。
- ContextRefs 默认勾选：是否默认包含当前页和当前选区，还是只默认当前页。
- Chat-only 是否允许读取文件夹摘要，或严格只允许聊天文本和显式勾选的页面摘要。
- 技术调试入口默认是否仅管理员可见，还是普通用户也可展开。
