Files
mnote/design/07-ai/process/7-71-unified-ai-management-control-plane-and-pi-lab-integration-v1.md
T

563 lines
26 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-71 MNote 统一 AI 管理面板与 Pi Lab 功能接入设计 v1
状态:process
Owner07-ai / mnote-web / control-plane / Page AI
日期:2026-07-04
## 1. 结论
应该做 MNote 原生统一 AI 管理面板,但顺序不是先堆 UI,而是先把 control-plane 合同、持久化和 effective config API 做成唯一真相,再让 Pi Lab / OpenHub / LightRAG 消费同一套配置。
文件夹授权必须只有一套:当前 `/admin/access-policy``/user/access-policy` 管理的 `directory_grants` 同时就是 MNote 工作区访问授权和 AI `allowed roots` 的事实源。AI 管理面板可以把它纳入同一个信息架构,但不能再维护第二套 AI-only allowed roots。
OpenHub 的 admin 面板证明了正确产品结构:provider/model、skills、tools、MCP、目录权限、知识库、历史、用量、健康状态必须先有管理面,聊天 UI 只展示用户可用的有效配置。MNote 不能把 OpenHub admin 作为 MNote AI 的长期真相,因为 OpenHub 仍是独立默认 Page AI 主线,Pi Lab 是 MNote-native 新入口,二者后续可能任意退役。MNote AI 管理中心应落在 Turso/libSQL control-plane。
## 2. 调研依据
### 2.1 OpenHub
源码:
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/pages/AdminPage.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/UserSettingsDrawer.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/HistoryDrawer.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/services/api.js`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-backend/app/api/admin.py`
可复用的产品模型:
- 管理后台按区段管理:用户、模型配置、工具权限、技能、目录权限、知识库、用量、系统健康。
- 用户设置抽屉只显示管理员已允许的有效配置:模型、技能、MCP、目录、工具、用量。
- 权限是三层:admin global policy -> user override -> effective policy。
- 历史记录是左侧抽屉,支持新建、刷新、归档、分页/滚动。
不应直接搬的部分:
- opencode 服务管理、飞书渠道、WeKnora 配置导出。
- React/AntD 组件主链。
- OpenHub SQLite 表作为 MNote 真相层。
### 2.2 Pi / Pi Web UI
源码:
- `earendil-works-pi-web-ui-0.75.3.tgz`
- `/mnt/Data1T/tmp/pi-web-ui-shot/node_modules/@earendil-works/pi-web-ui/src/`
- `/mnt/Data1T/tmp/pi-web-ui-shot/node_modules/@earendil-works/pi-agent-core/dist/`
- `rust/crates/mnote-web/src/routes/page_ai_pi.rs`
- `rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js`
可复用的合同:
- `SessionData`: `id/title/model/thinkingLevel/messages/createdAt/lastModified`
- `SessionMetadata`: `id/title/lastModified/messageCount/usage/thinkingLevel/preview`
- `AgentEvent`: `agent_start/end``message_update/end``tool_execution_start/update/end`
- `ThinkingLevel`: `off/minimal/low/medium/high/xhigh`
- Provider/model 设置形态:SettingsDialog、ModelSelector、ProvidersModelsTab、CustomProvider。
不能照搬的部分:
- IndexedDB 是 Pi standalone browser app 的存储,不是 MNote 真相层。
- Pi 原始 `bash/read/write/edit` 仍必须禁用。
- Pi web-ui 是 Lit Web ComponentsMNote 当前 Pi Lab 保持原生 runtime,不 iframe、不让 Pi UI 接管 provider key 或工具权限。
### 2.3 MNote 当前底座
已有:
- `directory_grants`:当前文件夹授权事实。
- `ai_policies`:已有 `allowed_roots_json / model_policy_json / quota_json`,但无 HTTP 管理 API 和 UI。
- `ai_runtime_runs / ai_runtime_events`:已有跨 provider run/event journal。
- `ai_external_conversation_bindings`:已有 MNote session 与外部 provider conversation 绑定。
- `/admin/access-policy``/user/access-policy`:已有文件夹授权面板。
缺口:
- Pi Lab session 仍在内存 HashMap,重启丢失。
- Pi Lab receipt 仍在内存 + JSONL adapter,无法按用户/工作区查询。
- provider/model/skills/MCP/tools 的 MNote-native 管理 API 和 UI 不存在。
- Pi Lab UI 的历史、设置、模型选择还没有接入真实配置源。
## 3. 目标架构
```text
MNote AI 管理中心
/admin/ai admin global policy
/user/ai user effective settings
|
v
Turso/libSQL control-plane
directory_grants
ai_policies
ai_runtime_runs / ai_runtime_events
ai_external_conversation_bindings
ai_provider_configs / ai_model_catalog / ai_model_grants
ai_tool_policies / ai_skill_registry / ai_mcp_servers
ai_tool_events / ai_file_patches
|
v
MNote AI Facade APIs
effective config
allowed roots
model/provider catalog
skills/tools/MCP policy
LightRAG facade
session history / receipts
|
+--> OpenHub iframe/default Page AI
+--> Pi Lab native drawer
+--> future native agents
```
OpenHub 继续保持默认 Page AI 入口。MNote 管理中心不替换 OpenHub admin,而是成为 MNote 侧 AI 权限和配置的真相层;需要同步给 OpenHub 时,通过 provider identity sync / OpenHub admin API 做下游同步。
## 4. 管理面板信息架构
### 4.1 Admin`/admin/ai`
沿用现有 `/admin/access-policy` 的 SSR 页面方式,新增 AI 管理中心。左侧区段建议:
1. `Overview`
- AI runtime 状态、OpenHub 状态、Pi Lab 状态、LightRAG 状态、最近错误。
2. `Providers & Models`
- Omniroute/OpenAI/Anthropic/Google/local providers。
- API key/base URL/认证状态。
- 模型可见性、默认 Build/Plan/Task 模型、failover chain。
- 默认保留 `omniroute/freefirst` 作为 Pi Lab dev/default model。
3. `Access Scopes`
- 直接复用当前 `/admin/access-policy``/user/access-policy` 文件夹授权 UI 和既有授权 API,不新增 AI-only 授权目录。
- `directory_grants` 是 MNote 与 AI 共用的 allowed roots 事实源。
- `AiAccessScope.allowed_roots`、OpenHub allowedRoots、Pi Lab allowed roots 都从 `directory_grants` 派生。
- 支持 read/write、recursive、capabilities、workspace/user 维度。
- 该区段只提供授权摘要、effective scope 预览和进入现有授权页的入口;若后续内嵌编辑,也必须调用同一套 `directory_grants` command/API。
4. `Tools`
- MNote-owned tools 全局策略:allow/ask/deny。
- 默认 deny raw bash/read/write/edit。
- Pi 只显示并使用 MNote facade tools。
5. `Skills`
- MNote builtin skills、Codex/Acontext skills、OpenHub/opencode skills 的 registry。
- 全局启停,用户可在授权范围内开关。
6. `MCP`
- MCP server registry、启动命令、allowed roots、network policy、env/secret 引用。
- 默认不让 Pi 直接接管 raw MCPPi 通过 MNote facade 调用。
7. `Knowledge`
- LightRAG 是唯一默认 provider。
- 管理 source registry、index 状态、citation/open-reference 映射。
8. `Sessions & Receipts`
- Page AI / OpenHub / Pi Lab / native agent 历史。
- tool events、deny reason、diff summary、file version、citation count。
9. `Usage & Quota`
- 按用户、workspace、provider、model 统计。
- token、cost、request count、失败率。
### 4.2 User`/user/ai`
用户设置只显示 effective config
- 可用模型:来自 admin allowed models;用户只能隐藏或选择默认,不提升权限。
- 可用 skills/tools/MCP:只能在 admin 允许范围内开关或降级。
- 目录权限:查看由 `directory_grants` 派生的 allowed roots,并进入 `/user/access-policy` 请求或调整授权;不保存 AI 私有目录配置。
- 历史记录:自己的 Page AI / Pi Lab sessions。
- 用量:自己的 quota 与调用统计。
### 4.3 Pi Lab 抽屉接入
Pi Lab 顶部工具栏:
- 历史按钮:打开 Pi Lab history drawer,数据来自 MNote `/api/page-ai/pi/sessions` 或统一 `/api/page-ai/sessions?provider=pi`
- 设置按钮:打开 MNote-native user AI settings overlay;管理员可跳到 `/admin/ai`
- 模型选择:读取 effective model options,不再硬编码单个 select。
- Build/Plan:读取 agent mode policy,决定可用模型、thinkingLevel、tool policy。
- 工具/引用/diff:继续只显示 MNote facade 返回的 receipt 和 citation。
## 5. 数据合同
### 5.1 复用现有表
- `directory_grants`:MNote 与 AI 共用的唯一文件夹授权事实源,也是 allowed roots 派生源。
- `ai_policies`workspace/user 的模型、配额、工具、skills/MCP 策略汇总层;不得作为第二套 allowed roots 真相。历史字段 `allowed_roots_json` 只能视为缓存/兼容快照,写入时必须由 `directory_grants` 派生。
- `ai_runtime_runs`:统一 AI session/run 元数据。Pi Lab 不新建独立 session 真相表,先复用它。
- `ai_runtime_events`:统一 message/tool/runtime event journal。
- `ai_external_conversation_bindings`OpenHub remote conversation / future external provider binding。
- `user_ui_preferences`:前端折叠状态、默认 tab 等低风险 UI preference。
### 5.2 授权目录单一真相规则
- 不新增 `ai_allowed_roots``pi_allowed_roots``openhub_allowed_roots` 或任何同义表。
- 不允许 Pi Lab、OpenHub、AI 管理页或 session 设置单独写一份目录授权 JSON。
- 所有目录授权变更统一写入 `directory_grants`,沿用现有管理员/用户权限边界、审计和路径校验。
- AI effective config 按 user/workspace 读取 `directory_grants`,生成 `AiAccessScope.allowed_roots`
- Pi Lab 创建 session/run 时记录 effective scope 快照,但每次工具执行仍按当前 `directory_grants` 重新校验。
- OpenHub `allowedRoots` 是 MNote 下发的下游投影,不能反向成为授权真相。
- `ai_policies.allowed_roots_json` 只能作为不可独立编辑的兼容快照、迁移输入或缓存;满足迁移条件后应删除或改名,避免误用和双写。
### 5.3 新增或规范化表
第一阶段优先最小新增:
```sql
ai_tool_events(
id,
user_id,
workspace_id,
session_id,
run_id,
provider,
provider_session_id,
tool_name,
allowed,
deny_reason,
root_uri,
page_path,
normalized_file_path,
diff_summary,
citation_count,
before_file_version,
after_file_version,
payload_json,
created_at,
deleted_at
)
```
```sql
ai_file_patches(
id,
user_id,
workspace_id,
session_id,
run_id,
tool_event_id,
root_uri,
relative_path,
before_file_version,
after_file_version,
patch_summary_json,
created_at
)
```
第二阶段再规范化:
- `ai_provider_configs`
- `ai_model_catalog`
- `ai_model_grants`
- `ai_tool_policies`
- `ai_skill_registry`
- `ai_skill_grants`
- `ai_mcp_servers`
- `ai_mcp_grants`
- `ai_usage_logs`
- `ai_model_failover_chains`
说明:不要急着新增 `ai_provider_sessions`。MNote 已有 `ai_runtime_runs / ai_runtime_events`Pi Lab history 可以先落这里;只有当 provider 原生 thread 与 MNote run/event journal 差异太大时,再加 provider-specific session detail 表。
## 6. API 合同
### 6.1 管理 API
```text
GET /api/ai-admin/overview
GET /api/ai-admin/providers
PUT /api/ai-admin/providers/{provider_id}
GET /api/ai-admin/models
PUT /api/ai-admin/models/{provider_id}/{model_id}/policy
GET /api/ai-admin/access-scopes
GET /api/ai-admin/tools
PUT /api/ai-admin/tools/{tool_name}/policy
GET /api/ai-admin/skills
PUT /api/ai-admin/skills/{skill_id}/policy
GET /api/ai-admin/mcp
PUT /api/ai-admin/mcp/{server_id}/policy
GET /api/ai-admin/knowledge
GET /api/ai-admin/sessions
GET /api/ai-admin/receipts
GET /api/ai-admin/usage
```
### 6.2 用户有效配置 API
```text
GET /api/ai-settings/effective
GET /api/ai-settings/models
PUT /api/ai-settings/models/{provider_id}/{model_id}
GET /api/ai-settings/tools
PUT /api/ai-settings/tools/{tool_name}
GET /api/ai-settings/skills
PUT /api/ai-settings/skills/{skill_id}
GET /api/ai-settings/mcp
GET /api/ai-settings/access-scopes
GET /api/ai-settings/usage
```
`GET /api/ai-admin/access-scopes``GET /api/ai-settings/access-scopes` 只读返回由 `directory_grants` 计算的 effective scope,并返回现有授权管理页入口。授权写入继续使用现有 access-policy / `directory_grants` command/API;不在 AI admin route 中定义第二套 grants 写语义。
### 6.3 Pi Lab history API
可以做成 Pi-specific facade,但底层写 `ai_runtime_runs/events`
```text
GET /api/page-ai/pi/sessions
GET /api/page-ai/pi/sessions/{session_id}
PUT /api/page-ai/pi/sessions/{session_id}/title
DELETE /api/page-ai/pi/sessions/{session_id}
GET /api/page-ai/pi/sessions/{session_id}/events
```
响应参考 Pi `SessionMetadata`
```json
{
"id": "pi_lab_...",
"title": "当前页解释",
"model": "omniroute/freefirst",
"thinkingLevel": "medium",
"lastModified": "2026-07-04T14:20:00Z",
"messageCount": 8,
"usage": {"input": 1200, "output": 400, "totalTokens": 1600},
"preview": "帮我解释当前页面的逻辑..."
}
```
## 7. 实施顺序
### P0:合同与只读聚合
- 新增设计和 smoke baseline。
- 增加 `/api/ai-settings/effective` 只读 API。
- 聚合已有 `directory_grants``ai_policies`、agent descriptors、Pi defaults。
- 明确 `effective.allowedRoots` 只从 `directory_grants` 派生;若 `ai_policies.allowed_roots_json` 存在,只作为兼容只读快照或迁移源,不参与新增写入。
- Pi Lab model selector 改为消费 effective models,但仍保留 `omniroute/freefirst` fallback。
验收:
- OpenHub 默认入口不变。
- Pi Lab 打开后能显示来自 MNote effective config 的模型、allowed roots、tool policy。
### P1Pi Lab history 入库
- Pi Lab start/send/end 写 `ai_runtime_runs / ai_runtime_events`
- 历史 drawer 读 MNote DB,不读 IndexedDB、不读内存 HashMap。
- 现有 JSONL receipt 保留为 debug fallback。
验收:
- 重启 mnote-web 后 Pi Lab 历史仍可见。
- 同一 user/workspace 隔离生效。
### P2Receipt 与 patch 入库
- 新增 `ai_tool_events / ai_file_patches`
- `mnote.local_file.read/patch`、LightRAG query、reference.open、tool_receipt.write 全部写入。
- Pi Lab receipt rail 改读 DB event stream。
验收:
- allowed roots deny、diff summary、file version、citation count 可在管理面板查询。
- patch 后 watcher refresh 仍不新增轮询。
### P3MNote AI 管理中心首版
- `/admin/ai` 先做四个可用 tabOverview、Providers & Models、Access Scopes、Sessions & Receipts。
- `/user/ai` 做 Models、Access、History、Usage。
- 复用 `/admin/access-policy` / `/user/access-policy` 逻辑,不拆第二套授权事实;AI 面板里的 Access Scopes 本质上是该授权页面的 AI 视角入口。
验收:
- 管理员可设置 Pi Lab 默认模型和可见模型。
- 用户只能选择管理员允许的模型。
- 文件夹授权仍同步 OpenHub,不破坏 OpenHub。
### P4Skills / MCP / Tools / LightRAG 管理
- Skills registry 与 grants。
- MCP registry 与 sandbox policy。
- MNote-owned tools allow/ask/deny。
- LightRAG source/index/status 管理。
验收:
- Pi Lab settings 能显示 skills/MCP/tools effective 状态。
- Pi 仍不能直接启用 raw bash/read/write/edit。
- LightRAG 仍是唯一默认 knowledge provider。
## 8. 关键决策
1. **MNote 管理中心是必需的**,但第一步是 control-plane + effective API,不是先做完整 UI。
2. **OpenHub admin 只作为参考和下游同步对象**,不作为 MNote AI 真相层。
3. **文件夹授权只保留一套**`directory_grants` 同时服务 MNote 工作区访问、OpenHub `allowedRoots` 和 Pi Lab `AiAccessScope.allowed_roots`;AI 管理页只提供同源视图和现有授权入口。
4. **Pi IndexedDB schema 可参考,不能作为 MNote 持久化方案**
5. **Pi Lab history 先复用 `ai_runtime_runs/events`**,避免新增重复 session 表。
6. **receipt/patch 必须入 Turso/libSQL**JSONL 只保留 debug/crash recovery。
7. **skills/MCP/tools 先管权限,再接 UI**;否则会把未受控工具暴露给 Pi。
8. **secrets 不进前端、不硬编码**provider API key 应只存 secret reference/env key,后续再接正式 secret store。
## 9. 风险
- 直接把 OpenHub admin 嵌进 MNote 会形成第二个 iframe 管理系统,和 Pi Lab 原生化目标冲突。
- 先做 Pi UI settings 会把配置写进临时 DOM/内存,后续仍要重接 Turso。
- 如果不先统一 historyPi Lab 与 OpenHub 会话会分裂,用户无法判断哪个入口产生了修改。
- MCP 管理若过早放开,容易绕过 AiAccessScope;必须默认只注册、只显示,不默认启用。
- Model/provider secret 管理不能复用 Codex auth 作为产品实现;Codex auth 只可作为 dev smoke 输入。
## 10. 下一步最小可执行设计稿
建议下一张执行稿拆成:
`7-72-page-ai-pi-lab-history-and-effective-config-control-plane-v1`
范围只做:
- `/api/ai-settings/effective`
- Pi Lab model options 读取 effective config
- Pi Lab session 写入 `ai_runtime_runs/events`
- Pi Lab history drawer 读 DB
- receipt 仍保留 JSONL,但设计 migration 到 `ai_tool_events`
- smoke 覆盖重启后历史存在、用户隔离、OpenHub 默认入口不变
做完 7-72 后,再做 `/admin/ai` 首版 UI。这样不会在 UI 上先行制造第二套事实源。
---
## 11. 实施进度(2026-07-04
### P0: 合同与只读聚合 — 进度
| 项 | 状态 | 证据 |
|---|---|---|
| 新增设计和 smoke baseline | ✅ 已完成 | `design/07-ai/process/7-71-*` 设计文档、`scripts/task-ai-management-control-plane-static-smoke.js` 静态 smoke27/27 通过) |
| `/api/ai-settings/effective` 只读 API | ✅ 已实现 | `routes/ai_settings.rs::effective_settings` — GET-only,读 `directory_grants``ai_policies.model_policy_json/quota_json` |
| `/api/ai-settings/access-scopes` 用户只读 API | ✅ 已实现 | `routes/ai_settings.rs::user_access_scopes` — GET-only |
| `/api/ai-admin/access-scopes` 管理员只读 API | ✅ 已实现 | `routes/ai_settings.rs::admin_access_scopes` — GET-only,管理员鉴权 |
| 聚合 `directory_grants``ai_policies`、agent descriptors、Pi defaults | ✅ 已实现 | effective 响应聚合 providers、models、tool_catalog、lightrag_provider、access_policy_links |
| `effective.allowedRoots` 只从 `directory_grants` 派生 | ✅ 已验证 | `load_active_directory_grants` 读取 `list_directory_grants_for_actor``ai_policies.allowed_roots_json` 不被读取 |
| Pi Lab model selector 消费 effective models | ✅ 已实现 | `effective_settings` 响应含 models、default_modelPi Lab 前端消费 |
| 静态 smoke 覆盖:路由注册、source of truth、无写操作 | ✅ 27/27 通过 | `scripts/task-ai-management-control-plane-static-smoke.js` |
### P1: Pi Lab history 入库 — 进度
| 项 | 状态 | 证据 |
|---|---|---|
| Pi Lab start/send/end 写 `ai_runtime_runs / ai_runtime_events` | ✅ 已实现 | `page_ai_pi.rs::persist_upsert_run` / `persist_append_event` |
| 历史 drawer 读 MNote DB | ✅ 已实现 | `list_sessions``control_plane.list_ai_runtime_runs``get_session_history` / `get_session_events` 分别读 run 和 events |
| 现有 JSONL receipt 保留为 debug fallback | ✅ 已保留 | `PI_LAB_RECEIPT_STORE` JSONL 静态存储仍在 |
| Pi Lab session 路由注册 | ✅ 已验证 | `GET /api/page-ai/pi/sessions``GET .../{session_id}``GET .../{session_id}/events` |
| Pi Lab session 按 pi_lab/pi profile/acp_runtime 过滤 | ✅ 已验证 | `list_sessions` 过滤 `r.profile == PI_LAB_PROFILE && r.acp_runtime == PI_LAB_ACP_RUNTIME` |
### P2: Receipt 与 patch 入库 — 已完成
| 项 | 状态 | 证据 |
|---|---|---|
| `ai_tool_events / ai_file_patches` migration | ✅ | `control-plane/migrations/009-ai-tool-events-file-patches.sql` |
| SQLite 与 Turso/libSQL store | ✅ | `append/list_ai_tool_event``append/list_ai_file_patch` |
| Pi tool receipt 入 control-plane | ✅ | `page_ai_pi.rs::write_receipt` |
| patch 关联写入 file patch journal | ✅ | receipt id 作为 `tool_event_id` |
| JSONL 降级为 debug fallback | ✅ | 仅 control-plane 写失败后追加 |
| 管理 API 查询 receipts / patches | ✅ | `/api/ai-settings/receipts``/api/ai-admin/receipts` |
### P3: MNote AI 管理中心首版 — 管理员写管理完成
| 项 | 状态 | 证据 |
|---|---|---|
| `/admin/ai` 路由与入口 | ✅ 已实现 | `gateway.rs::admin_ai_entry`,鉴权后跳转,管理员模式 |
| `/user/ai` 路由与入口 | ✅ 已实现 | `gateway.rs::user_ai_entry`,鉴权后跳转,用户模式 |
| 账户菜单独立 AI 管理入口 | ✅ 已实现 | `sidebar-workspace-runtime.js` `mnote-account-ai-management`,按角色跳 `/admin/ai``/user/ai` |
| AI 管理页面 SSR | ✅ 已实现 | `ssr/pages/ai_admin.rs::AiManagementPage` — 对齐 OpenHub Admin 左侧分页、白色 Card 壳、表格、抽屉与分组权限面板;管理 Overview、模型、服务、用量、工具、Skills/MCP、目录、知识库、会话与监控 |
| Access Scopes 只读展示 | ✅ 已验证 | 页面脚本只 GET access-scopes 数据,无 POST/PUT/DELETE,无写表单 |
| Sessions & Receipts 区域 | ✅ 已接真实数据 | 展示 Pi session、tool receipt、deny reason、diff summary、patch 数量 |
| `/admin/ai` 管理写操作(provider/model 编辑等) | ✅ 已实现 | `GET/PUT /api/ai-admin/settings`;模型配置浏览器保存与刷新持久化通过 |
| `/user/ai` 用户设置 | ❌ 未实现 | 当前 `/user/ai` 复用 `AiManagementPage` 用户模式,仅有只读视图;逐用户模型/工具/Skill/MCP override 已在管理员用户抽屉完成 |
### P4: Skills / MCP / Tools / LightRAG 管理 — 部分完成
- Skills、MCP 与工具策略已通过原子管理 API 和 SSR 管理 UI 写入 `ai_policies.model_policy_json`
- MCP 强制 `facadeOnly=true``sandbox=true`secret 仅接受 `env://` / `secret://`
- MNote-owned tools 已支持 allow/ask/deny
- LightRAG source/index/status 管理 UI 未实现
- provider/skill/MCP 专用规范化表未实现;出现独立查询、授权或生命周期需求时再迁移
### OpenHub / Pi Lab 独立入口状态
| 项 | 状态 |
|---|---|
| OpenHub `/page-ai/openhub/ai` 未删除 | ✅ |
| OpenHub `/page-ai/openhub/admin` 未删除 | ✅ |
| OpenHub status API 未删除 | ✅ |
| Pi Lab shell `/page-ai/pi` 未删除 | ✅ |
| Pi Lab start/send/abort/events 路由未删除 | ✅ |
### 验证命令占位
```bash
# 静态 smoke(只读代码审计)
node scripts/task-ai-management-control-plane-static-smoke.js
# 运行时 smoke(需要 mnote-web 运行中)
# curl -s http://localhost:3000/api/ai-settings/effective | jq '.sourceOfTruth'
# curl -s http://localhost:3000/api/ai-settings/access-scopes | jq '.sourceOfTruth'
# curl -s http://localhost:3000/api/ai-admin/access-scopes | jq '.sourceOfTruth'
# curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/admin/ai
# curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/user/ai
# curl -s http://localhost:3000/api/page-ai/pi/sessions | jq '.schema'
# curl -s http://localhost:3000/api/ai-settings/receipts | jq '{receiptCount, patchCount}'
```
### 运行时与浏览器证据
- 隔离运行端口:`127.0.0.1:33171`
- `task-pi-lab-mock-api-smoke.js`:通过
- `task-pi-lab-browser-smoke.js`:通过
- Pi Lab 截图:`tmp/7-71-pi-lab-browser-final.png`
- AI 管理页截图:`tmp/7-71-ai-admin-final.png`
- 模型写管理截图:`tmp/7-71-ai-admin-openhub-like-ui-final/models.png`
- Skills/MCP 写管理截图:`tmp/7-71-ai-admin-openhub-like-ui-final/skills-mcp.png`
- OpenHub-like 逐用户配置抽屉截图:`tmp/7-71-ai-admin-openhub-like-ui-final/users.png`
- 管理页实测摘要:会话 1、回执 3、文件补丁 1
- OpenHub 仍为默认 Page AIPi Lab 继续使用独立按钮、独立抽屉与独立 session
- `mnote-e2e` 已通过 control-plane `role=admin` 验证管理员访问和写入;浏览器 smoke 使用 dev seed 仅为临时验收账号准备,不写第二套授权目录
### P4: Skills / MCP / Tools 管理 — 验收结果
| 项 | 状态 | 证据 |
|---|---|---|
| admin 原子策略 GET/PUT | ✅ 通过 | `/api/ai-admin/settings`,统一保存模型、工具、Skills、MCP |
| 浏览器保存与刷新持久化 | ✅ 通过 | `scripts/task-ai-management-browser-smoke.js` |
| secretRef-only 检查 | ✅ 通过 (3 项) | Section 9: 无硬编码 api_key/secret,仅 env 引用 |
| allowed roots 不可写检查 | ✅ 通过 (4 项) | Section 10: 无写路由、无写函数、无写表单 |
| MCP facade-only 检查 | ✅ 通过 (3 项) | Section 11: Pi 仅暴露 mnote. 工具,MCP 经 facade 管控 |
### P5: OpenHub 式逐用户 AI 配置 — 已完成
- OpenHub Admin 的真实用户管理入口为用户表格 + 模型/工具/Skill/目录权限操作;MCP 仅展示用户工作区 `.opencode/mcp` 文件,不存在 Admin per-user MCP 策略。
- MNote 新增用户列表与逐用户 AI 策略 API,用户 override 继续落 `ai_policies(user_id, workspace_id=NULL)`
- 当前 `ai_policies` 没有系统全局行,因此暂以 `MNOTE_ADMIN_USER_IDS` 首个用户作为全局策略 owner;迁移到规范化 policy 表时移除此兼容约定。
- 本轮已把 MNote `/admin/ai` 用户配置抽屉改成 OpenHub-like 体验:模型按 provider 分组、工具按 risk level 分组、Skill/MCP 使用启停开关、目录权限只读并跳转统一授权管理页。
- effective 合并规则:
- 模型只能从管理员 allowed models 中选择;
- 工具只能降权,不能从 deny/ask 提升到 allow
- Skill/MCP 只能关闭或启用全局已允许项;
- MCP 始终保持 facade-only + sandbox
- allowed roots 完全不参与该合并,继续只读 `directory_grants`
- 浏览器截图:`tmp/7-71-ai-admin-openhub-like-ui-final/users.png`
- 浏览器 smoke 同时以普通用户 `ai-user` 登录,确认 effective API 不返回被禁用模型和 Skill。
### 本轮 smoke 结果
静态 smoke:**47 通过,0 失败**。浏览器 smoke 验证模型、工具、Skills/MCP 保存、OpenHub Admin 独立入口保留、OpenHub-like 用户抽屉、逐用户 override、刷新持久化及 effective 投影全部通过。
```
$ node scripts/task-ai-management-control-plane-static-smoke.js
通过: 47 失败: 0 跳过: 0
$ MNOTE_AI_ADMIN_OUTPUT_DIR=tmp/7-71-ai-admin-openhub-like-ui-final \
node scripts/task-ai-management-browser-smoke.js
ok: true
```
### 后续范围
- `/user/ai` 用户模型/Skills/MCP 偏好
- LightRAG source/index/status 写管理
- provider/skill/MCP 专用规范化表迁移