feat: add pi lab ai management integration

This commit is contained in:
Agent Board
2026-07-04 21:47:42 +08:00
parent 36d027a4a1
commit c8472bb898
62 changed files with 15077 additions and 443 deletions
@@ -0,0 +1,90 @@
# 7-69 Page AI Pi-first Lab Spike Checklist v1
状态:done(最小可验证 spike)
Owner07-ai / mnote-web / control-plane
日期:2026-07-04
## 1. 结论
7-69 的最小可验证 spike 已完成:OpenHub 继续作为默认 Page AI 主线,Pi Lab 仅在 `MNOTE_PAGE_AI_PI_LAB=1` 下启用,运行时默认禁用 Pi 原始 `bash/read/write/edit`,文件读写只走 MNote-owned tool facade 与 allowed roots。
Pi 可以进入下一阶段受控验证,但不能进入生产默认路径。下一阶段的门槛是 Turso/libSQL 正式 receipt 表、登录态 watcher -> tiptap 端到端验证、selection snapshot 注入和 UI 产品化。
## 2. Phase Checklist
### Phase A:源码与包可运行性
- [x] 重新拉取并核验当前 Pi 源码:`tmp/pi-research-20260703/pi-mono`commit `23d14626 fix(ai): rotate stale Codex websocket sessions`
- [x] 核验当前 npm 包:`@earendil-works/pi-coding-agent@0.80.3`,并通过 `MNOTE_PAGE_AI_PI_BIN` wrapper 跑通真实 RPC。
- [x] 跑通 `pi --mode rpc` 最小托管路径。
- [x] 记录 RPC event schema 到 MNote SSE `pi_rpc_event` 映射。
- [x] 验证 `abort`MNote 可向 Pi RPC 发送 abort 并清理子进程。
- [ ] `steer` / `follow_up` 行为未纳入本轮 spike,留到下一阶段真实 agent turn 验收。
### Phase BMNote scoped Pi runtime
- [x] 新增内部 runtime manager,不替换 OpenHub 默认入口。
- [x] `sessionDir` 派生到 MNote 管控目录:`<workspace>/.mnote/ai/pi-sessions/<actor>/<session>`
- [x] 禁止全局 `~/.pi/agent/sessions` 作为默认 MNote session 落点。
- [x] 只开放 MNote-owned tools,默认不开放 Pi builtin `bash/read/write/edit`
- [x] 路径越界读写被拒绝,并写入 provider-neutral receipt。
### Phase CLightRAG tool
- [x] 实现 `mnote.knowledge_rag.query` facade。
- [x] 通过现有 `knowledge_rag::query_rag` 调用 LightRAG provider facade,不引入第二套 RAG。
- [x] tool envelope 保留 citation/open-reference 所需字段。
- [x] UI 支持 citation 展示。
- [x] 实现 `mnote.reference.open` facade,支持 citation 回跳入口。
### Phase D:文件 patch 闭环
- [x] 当前页面可通过 `rootUri + pagePath` 解析到真实 `.md`
- [x] `mnote.local_file.patch` 支持 content 或 text operations patch 当前文件。
- [x] patch 写入前后记录 file version 与 diff summary。
- [x] patch 响应声明 watcher/document-session refresh,并显式 `polling=false`
- [ ] 登录态浏览器中验证 watcher 实际刷新 tiptap 当前页仍未完成,是下一阶段阻塞项。
- [ ] changed file chip 打开对应资源未纳入本轮 spike,是下一阶段 UI 验收项。
### Phase EUI 对照
- [x] 新增 MNote-native Pi Lab internal UI,支持 start/send/events/abort,并集成到当前 MNote Page AI 抽屉。
- [x] Pi RPC stream 的 `thinking_*` 事件不进入可见 assistant bubble;截图验证 Omniroute/freefirst 最终可见回复为 `OK`
- [x] UI 支持 SSE stream、tool card、citation、diff summary、receipt 诊断。
- [x] 重新拉取并评估 `@earendil-works/pi-web-ui@0.75.3`;确认其 UI 成熟,但默认 browser Agent/IndexedDB/API key/tools/artifacts 边界不适合直接承接 MNote 托管 Pi RPC/SSE。
- [x] 采用参考官方 `ChatPanel/AgentInterface` 形态的 MNote-native adapter,并保留后续 remote-session adapter 接官方组件的迁移条件。
- [ ] 与 OpenHub 当前面板并排截图对照未纳入本轮 spike。
- [ ] 移动端 / 窄 sidebar 布局未纳入本轮 spike。
- [ ] retry / session resume 仍是下一阶段 UI 产品化项。
## 3. 验收结果
- [x] `pi --mode rpc` 能由 MNote 托管启动,并返回 `runtimePid`
- [x] 当前页读取、LightRAG facade、文件 patch、receipt adapter、SSE UI 已形成最小闭环。
- [x] 所有文件读写受 allowed roots 控制。
- [x] 所有 tool call 均写 provider-neutral JSONL receipt。
- [x] Pi 原始 `bash/read/write/edit` 默认不可用。
- [x] citation/open-reference facade 已接入 MNote knowledge_rag 路径。
- [x] Pi Lab 与 OpenHub 并存,OpenHub 仍为默认 Page AI。
- [ ] Turso/libSQL 正式 receipt 表未完成。
- [ ] watcher -> tiptap 真实端到端刷新未完成。
- [ ] UI 尚未达到可替代 OpenHub 的生产级体验。
## 4. 验证命令
- `node scripts/task-pi-lab-static-smoke.js`:通过,78 checks。
- `node scripts/task-pi-lab-api-endpoint-smoke.js`:通过,10/10。
- `node scripts/task-pi-lab-mock-api-smoke.js`:通过。
- `node scripts/task-pi-lab-browser-smoke.js`:通过。
- `node scripts/task-pi-lab-rpc-api-smoke.js`:新增固化 RPC smoke;需以 `MNOTE_PAGE_AI_PI_LAB_RUNTIME=rpc``MNOTE_PAGE_AI_PI_BIN` 启动服务后运行。
- `cd rust && cargo check -p mnote-web`:通过。
## 5. 下一阶段条件
进入下一阶段前必须补齐:
- Turso/libSQL control-plane 表:`ai_provider_sessions``ai_tool_events``ai_file_patches`
- 登录态浏览器 smokePi patch 当前 `.md` 后,watcher 驱动 tiptap 当前页刷新,不允许高频轮询。
- sidebar host 注入真实 selection snapshot。
- Pi RPC send 的真实 agent turn 自动化验证,固定兼容版本范围或安装方式。
- UI 与 OpenHub baseline 的截图/交互对照。
@@ -0,0 +1,94 @@
# 7-69 Page AI Pi-first Lab Spike 验收记录 v1
状态:done(最小可验证 spike)
Owner07-ai / mnote-web / control-plane
日期:2026-07-03
## 1. 本轮结论
7-69 已完成 MNote 侧最小可验证 Pi-first Page AI Lab spikeOpenHub 仍是默认 Page AI 主线,Pi Lab 仅在 `MNOTE_PAGE_AI_PI_LAB=1` 下作为 dev-only/internal 入口启用。
当前结论不是“替换 OpenHub”,而是:Pi 值得进入下一阶段受控验证;扩大前必须补真实 Pi CLI/RPC、Turso/libSQL receipt 表、auth 下浏览器 smoke 和 watcher 端到端证据。
## 2. 已实现内容
- 新增 Pi Lab 后端路由:`/api/page-ai/pi/status``bootstrap``start``send``abort``events``tool-call``/page-ai/pi`
- 新增 MNote 托管 runtime`pi --mode rpc` subprocess 或 `MNOTE_PAGE_AI_PI_LAB_RUNTIME=mock`sessionDir 派生到 MNote 管控目录。
- 默认禁用 Pi builtin tools:启动参数包含 `--no-builtin-tools`,状态响应显式标注 `bash/read/write/edit` 不开放。
- 实现 MNote-owned tool facade`mnote.current_page.read``mnote.selection.read``mnote.allowed_roots.describe``mnote.local_file.read``mnote.local_file.patch``mnote.knowledge_rag.query``mnote.reference.open``mnote.tool_receipt.write`
- 文件读写受 allowed roots 限制,越界读写返回 deny reason 且写 receipt。
- LightRAG 保持唯一默认知识 provider;Pi 只通过 `knowledge_rag` facade 查询和 open-reference。
- receipt 先落 `provider_neutral_jsonl_adapter_v1`,字段含 session binding、tool event、deny reason、diff summary、file version、allowed roots snapshot,并写明迁移到 Turso/libSQL `ai_provider_sessions` / `ai_tool_events` / `ai_file_patches` 的条件。
- 文件 patch 响应声明 `mnote local-folder watcher / document-session external refresh`,并显式 `polling=false`,不新增高频轮询。
- 新增 Pi Lab runtime UI:状态机、SSE/EventSource、stream、tool card、citation、diff、abort、receipt 诊断。
- Pi Lab 已从简陋独立 debug 页改为当前 MNote Page AI 抽屉内的 dev-only provider 面板,并保留悬浮 `π` 入口;切回 OpenHub 时复用原抽屉与 iframeOpenHub 仍是默认入口。
- Pi RPC stream 中 `thinking_*` 事件默认不进入可见 assistant bubble;可见回复只渲染 `text_*` / assistant final text,避免 Omniroute/freefirst 的 reasoning_content 泄漏到页面。
- gateway 仅在 `enable_page_ai_pi_lab` 开启时注入 Pi Lab runtime,默认 body 标记为 hidden,不改变 OpenHub 默认入口。
- 新增 smoke:静态结构、禁用态 API endpoint、mock-mode API 验证脚本、浏览器 smoke 脚本。
- 新增 spike checklist`design/07-ai/done/7-69-page-ai-pi-first-lab-checklist-v1.md`,逐项标注 Phase A-E 已完成项与下一阶段 gap。
## 3. 未完成内容
- 系统 PATH 仍未安装全局 `pi`,但已通过 `npx -y @earendil-works/pi-coding-agent@0.80.3` 包装器完成真实 `pi --mode rpc` 端到端 smoke;后续需决定是否固定安装方式或配置 `MNOTE_PAGE_AI_PI_BIN`
- receipt 尚未写入 Turso/libSQL 正式表,仍为 provider-neutral JSONL adapter。
- `mnote.tool_receipt.write` 当前仍是最小 tool envelope 路径,未作为独立持久化入口深化。
- watcher 刷新已按 MNote 文件写入链路声明和返回 metadata,但还需登录态浏览器端 smoke 证明 tiptap 当前页被 watcher 刷新。
- 已基于最新 `@earendil-works/pi-web-ui@0.75.3` 源码重新评估,而不是使用 recycle 旧源码。其 `ChatPanel` / `AgentInterface` / `MessageEditor` / message-tool renderer 是成熟 UI 参考,但直接接入会同时引入浏览器端 `pi-agent-core`、IndexedDB session/provider key、API key prompt、proxy、attachments、artifacts 和 `Agent.state.tools` 事实源,与 MNote 托管 Pi RPC/SSE、AiAccessScope 和 MNote-owned tools 边界冲突。
- 本轮采用 MNote-native adapter:视觉与交互参考官方 `ChatPanel/AgentInterface` 的消息流、composer、tool timeline、model/runtime controls 和 artifacts-style railsession、API key、模型、工具权限、receipt 仍由 MNote 后端托管。若下一阶段要直接复用官方组件,迁移条件是先提供不依赖浏览器 API key/IndexedDB/Agent tools 的 remote-session adapter。
- API endpoint smoke 当前覆盖禁用态 3000;完整 mock-mode smoke 需要用 `MNOTE_PAGE_AI_PI_LAB=1 MNOTE_PAGE_AI_PI_LAB_RUNTIME=mock` 启动独立服务后运行。
## 4. Pi 是否进入下一阶段
建议进入下一阶段,但仍保持 internal/lab
- 通过:MNote 侧最小 runtime manager、tool facade、allowed roots、LightRAG facade、file patch、receipt adapter、SSE UI 已形成闭环。
- 未通过生产门槛:Turso receipt 表、正式登录态 browser smoke、watcher tiptap 刷新截图/断言仍未完整。
下一阶段应聚焦真实 runtime 和验收,不扩大默认入口。
## 5. OpenHub 状态
OpenHub 继续保持默认 Page AI 主线。Pi Lab 未替换、未破坏 OpenHub / LightRAG / Turso 路径;相关路由和 UI 均受 `MNOTE_PAGE_AI_PI_LAB=1` / `enable_page_ai_pi_lab` 控制。
## 6. 验证记录
- `node scripts/task-pi-lab-static-smoke.js`:通过,78 项。
- `node scripts/task-pi-lab-api-endpoint-smoke.js`:通过,10/10;当前 3000 服务未启用 Pi Lab,验证禁用态和 asset。
- `node scripts/task-pi-lab-mock-api-smoke.js`:通过;覆盖 mock runtime start、SSE events、allowed roots、当前页读取、越界拒绝、当前 `.md` patch、watcher refresh metadata、LightRAG facade、citation/open-reference facade、send、abort。
- `node scripts/task-pi-lab-browser-smoke.js`:通过;覆盖当前 MNote Page AI 抽屉、dev-only 悬浮 `π` 入口、Pi/OpenHub 切换、`omniroute/freefirst`、MNote tool rail、composer、status API 和截图;不再把独立 `/page-ai/pi` 当作主要 UI 验收面。
- Pi + Omniroute/freefirst 浏览器实测截图:`tmp/page-ai-pi-omniroute-freefirst-final-ok-20260704-v3.png`;证据 JSON`tmp/page-ai-pi-omniroute-freefirst-final-ok-20260704-v3.json`。最终 assistant 可见文本为 `OK``assistantStreaming=false``inDrawer=true`,模型为 `omniroute/freefirst`runtime 为 `rpc`
- `node scripts/task-pi-lab-mock-api-smoke.js`:通过;使用 mock mode 独立 3332 服务与 `MNOTE_PI_LAB_SMOKE_ROOT=/tmp/mnote-pi-lab-smoke`,覆盖 start/send/abort/events、allowed roots、越界拒绝、当前页 read、文件 patch、receipt。
- `node scripts/task-pi-lab-api-endpoint-smoke.js`:通过,10/10;支持启用态 + Bearer smoke auth,验证 status schema、disabled builtins、runtime asset 和端点存在。
- `codegraph sync . && codegraph status .`:通过;索引 up to date。
- 真实 Pi RPC smoke:通过;使用 `/tmp/mnote-pi-cli-wrapper.sh` 调用 `npx -y @earendil-works/pi-coding-agent@0.80.3`MNote 以 `MNOTE_PAGE_AI_PI_LAB_RUNTIME=rpc` / `MNOTE_PAGE_AI_PI_BIN=/tmp/mnote-pi-cli-wrapper.sh` 启动,验证 `start` 返回 `runtimePid`、当前页读取、越界拒绝、当前 `.md` patch、`send` accepted、`abort` 成功。
- `node scripts/task-pi-lab-rpc-api-smoke.js`:新增 RPC API smoke 固化脚本(2026-07-04 7-69 收尾),覆盖 runtimeMode=rpc status、runtimePid 真实子进程验证、start/send/abort 端点、tool-call MNote facadesallowed roots、越界拒绝、.md patch 含 watcher refresh/version change、LightRAG、citation)、SSE 事件流(runtime_started 含 pid/mode/disabledBuiltinTools)、多会话生命周期、Pi 进程清理。需要真实 Pi CLI + API key 完整测试 send 实际 AI 调用。
- `cd rust && cargo check -p mnote-web`:通过。
- `which pi`:无输出;本轮通过 `MNOTE_PAGE_AI_PI_BIN` 指向 wrapper 完成真实 RPC smoke。
## RPC 验证命令
```bash
# 启动 mnote-web(真实 Pi RPC 模式)
MNOTE_PAGE_AI_PI_LAB=1 \
MNOTE_PAGE_AI_PI_LAB_RUNTIME=rpc \
MNOTE_PAGE_AI_PI_BIN=/tmp/mnote-pi-cli-wrapper.sh \
MNOTE_PI_LAB_ALLOWED_ROOTS=/tmp/mnote-pi-rpc-smoke \
npm run desktop:hot
# 另一个终端运行 smoke
MNOTE_PI_LAB_BASE=http://127.0.0.1:3000 \
MNOTE_PI_LAB_AUTH="Bearer pi-lab-smoke" \
MNOTE_PAGE_AI_PI_BIN=/tmp/mnote-pi-cli-wrapper.sh \
MNOTE_PI_LAB_ALLOWED_ROOTS=/tmp/mnote-pi-rpc-smoke \
node scripts/task-pi-lab-rpc-api-smoke.js
```
mock 验证命令:
```bash
MNOTE_PAGE_AI_PI_LAB=1 \
MNOTE_PAGE_AI_PI_LAB_RUNTIME=mock \
MNOTE_PI_LAB_ALLOWED_ROOTS=/tmp/mnote-pi-lab-smoke \
MNOTE_PI_LAB_BASE=http://127.0.0.1:3000 \
node scripts/task-pi-lab-mock-api-smoke.js
```
@@ -0,0 +1,51 @@
# 7-70 Page AI Pi Lab 原生 OpenHub-like UI Checklist v1
状态:doneUI spike + P2 Pi tool bridge 回归)
Owner07-ai / mnote-web / page-ai-runtime
日期:2026-07-04
## 1. 验收项
- [x] OpenHub 继续保持默认 Page AI 主线。
- [x] Pi Lab 默认显示独立 `π` 悬浮按钮,不以 `MNOTE_PAGE_AI_PI_LAB=1` 作为显示前置条件。
- [x] `MNOTE_PAGE_AI_PI_LAB` 保留为强制关闭开关,默认值为开启。
- [x] Pi Lab 点击后打开独立 MNote-native drawer。
- [x] Pi Lab 不挂载到 `[data-testid="wolai-page-ai-drawer"]`
- [x] Pi Lab 不 iframe 第二个完整应用。
- [x] Pi Lab 不复用 OpenHub provider tab、按钮或 iframe 页面。
- [x] topbar 显示 Pi Lab、session/runtime status、model、close/minimize。
- [x] context strip 显示当前页、选区、allowed roots、LightRAG、changed files。
- [x] message timeline 显示 user / assistant / system、streaming、aborted。
- [x] provider thinking 不进入 visible final answerreasoning 仍默认隐藏。
- [x] tool/receipt rail 显示 tool status、deny reason、receipt、file version、diff summary、citation count。
- [x] composer 支持多行输入、当前页/选区/LightRAG 快捷按钮、send、abort。
- [x] diagnostics 默认折叠,不挤占首屏。
- [x] MNote-owned tools 保留,不开放 Pi 原始 `bash/read/write/edit`
- [x] allowed roots 越界读写在 UI 中显示 denied 和 deny reason,并写 receipt。
- [x] LightRAG 仍是唯一默认 knowledge providerPi 只通过 MNote facade 查询。
- [x] patch 响应保持 watcher refresh / `polling=false` contract。
- [x] Pi 默认模型继续为 Omniroute `freefirst`
- [x] static smoke119 checks)、api endpoint smoke、mock/API smoke、RPC API smoke、browser smoke、cargo check、CodeGraph sync/status 已运行。
- [x] 真实 Pi RPC + Omniroute/freefirst UI 截图回归已补:`tmp/page-ai-pi-lab-rpc-browser-real-7-71.png`
- [x] 真实 `/documents/local-md:*` 文档页默认注入独立 Pi Lab `π` 入口:`tmp/page-ai-pi-lab-rpc-browser-p1-7-71c.png`
- [x] Pi Lab start session 绑定当前 root/page/title。
- [x] “当前页”快捷按钮调用 `mnote.current_page.read` 并读取当前 Markdown。
- [x] “选区”快捷按钮读取 tiptap live selection snapshot 并注入 composer。
- [x] `.md` patch 后通过 local-folder event bus / document refresh 更新当前 `.ProseMirror`,未新增高频轮询。
- [x] 修复 P1 审查发现的任意 Cookie 认证绕过、跨用户 session 查询、selection tool host snapshot 标识、`tool_receipt.write` 空操作和 pagePath smoke 误报。
- [x] Pi RPC subprocess 通过 per-session extension 注册 MNote-owned custom tools。
- [x] 真实 Pi 主动调用 `mnote_current_page_read`,并经 `/api/page-ai/pi/tool-call-bridge` 回到 MNote `mnote.current_page.read` 的 API smoke 已通过。
- [x] bridge token 使用 OS 随机源生成,不序列化到 session JSON,不写入 extension 文件。
- [x] bridge endpoint 拒绝非 running sessionaborted/error session 不能继续通过 bridge 调 tool。
- [x] session TTL、最大 session 数、start/send/tool rate limit 和过期 session 目录清理已补。
- [x] P2 真实浏览器截图已补:`tmp/page-ai-pi-lab-rpc-browser-p2-visible-reply-7-70.png`,并断言 assistant bubble 本身包含真实 Pi 回复。
- [x] Pi Lab UI 已按 OpenHub-like 方向收敛:设置、上下文、tools、receipts 默认折叠,主对话区优先,模型选择入口保留。
- [x] OpenHub-like 折叠 UI 截图已补:`tmp/page-ai-pi-lab-openhub-like-ui-collapsed-v2-7-70.png`
- [x] Pi Lab UI 已按 OpenHub 平台式首屏二次收敛:大标题、水平工具栏、白色对话卡片、居中空状态示例、底部大输入框与 Build/Plan/model/budget 控制条;截图:`tmp/page-ai-pi-lab-openhub-platform-empty-v4-7-70.png`
## 2. 下一阶段
- [ ] Turso/libSQL 正式 receipt 表。
- [ ] in-process `AgentSession` / Node sidecar SDK 方案评估,用于替代当前 RPC subprocess + extension bridge spike。
- [ ] production Pi binary 显式配置和版本锁定。
- [ ] session resume / retry / history drawer 产品化。
@@ -0,0 +1,93 @@
# 7-70 Page AI Pi Lab 原生 OpenHub-like UI 验收记录 v1
状态:doneUI spike + P2 Pi tool bridge 回归)
Owner07-ai / mnote-web / page-ai-runtime
日期:2026-07-04
## 1. 结论
7-70 已完成 Pi Lab 从 7-69 的“OpenHub 抽屉内 provider 面板”到“默认显示的独立 `π` 悬浮入口 + MNote-native 抽屉”的切换。OpenHub 继续作为默认 Page AI 主线,Pi Lab 不复用 OpenHub 按钮、iframe 页面、provider tab 或抽屉容器。
Pi Lab 可以进入下一阶段受控验证,但仍不替换 OpenHub。2026-07-04 已补跑真实 Pi RPC + Omniroute/freefirst 的浏览器回归,证明真实 Pi text stream 能进入 MNote-native timeline 且 provider thinking 不进入 visible final answer。随后补齐了文档页 `/documents/local-md:*` 默认注入独立 Pi Lab、当前页读取、tiptap live selection snapshot、allowed roots deny、`.md` patch 后 watcher/refresh 刷新当前 tiptap 页的浏览器回归。
P2 已补齐真实 Pi 主动调用 MNote-owned tools 的最小链路:由于 Pi RPC 协议没有 host 回写 `tool_result` 的 stdin command,当前 spike 采用 per-session Pi extension bridge 注册 `mnote_*` custom tools。Pi 子进程仍使用 `--no-builtin-tools` 禁用原始 `bash/read/write/edit`,只允许 `mnote_current_page_read` 等 MNote 工具名;extension execute 通过内部 `/api/page-ai/pi/tool-call-bridge` 回到 MNote `execute_tool`,由 MNote 执行 allowed roots / LightRAG facade / receipt 写入。真实 Omniroute/freefirst API smoke 已观察到 Pi 发出 `tool_execution_start: mnote_current_page_read`MNote bridge 执行 `mnote.current_page.read`,并返回最终 marker。
## 2. 已实现内容
- `sidebar-page-ai-pi-lab-runtime.js` 改为独立 drawer`[data-page-ai-pi-lab-launcher]` 默认显示,点击打开 `[data-page-ai-pi-lab="drawer"]`,不再调用 `attachPanelToDrawer` / `setOpenHubVisible`,不再出现 `data-page-ai-pi-lab-openhub-tab`
- UI 补齐 topbar、session/runtime/model/status、context strip、message timeline、composer、right rail receipts/tools、默认折叠 diagnostics2026-07-04 二次收敛为 OpenHub 平台式首屏:大标题、水平工具栏、白色对话卡片、居中空状态示例、底部大输入框与 Build/Plan/model/budget 控制条。
- `MNOTE_PAGE_AI_PI_LAB` 改为默认开启,保留为 `0/false` 强制关闭开关;后端 `status` 返回 `uiMode=independent_mnote_native_drawer`
- 保留 MNote-owned tool set 和 Pi builtin 禁用策略:`bash/read/write/edit` 仍默认不可用。
- tool event UI 补充 deny reason、diff summary、file version、citation count、changed files chip。
- mock runtime send 事件补充 LightRAG mock citation 与 diff event,用于浏览器截图验证 stream/citation/diff 的 UI 形态。
- OpenHub 回归通过浏览器 smoke 验证:OpenHub drawer API 仍存在,OpenHub iframe host 仍独立打开,Pi drawer 不在 OpenHub drawer 内。
- 文档页 shell 现在在 `enable_page_ai_pi_lab` 开启时注入 Pi Lab runtime,因此真实 `/documents/local-md:*` 页面默认也显示独立 `π` 入口。
- Pi Lab start session 会绑定当前 `rootUri / workspaceId / pagePath / pageTitle`
- “当前页”快捷按钮实际调用 `mnote.current_page.read`,并把当前 Markdown 内容注入 composer / system timeline。
- “选区”快捷按钮消费 `window.getSelection()``mnote:leptos-tiptap-spike:selection` 快照,已在真实 tiptap 页面验证选中文本注入 composer。
- `mnote.local_file.patch` tool event 后会走 local-folder event bus + document pane refresh,不新增轮询;真实浏览器截图已验证当前 `.md` patch 后 `.ProseMirror` 显示新内容。
- 每个 Pi session 生成独立 tool bridge extension,注册 `mnote_current_page_read / mnote_selection_read / mnote_allowed_roots_describe / mnote_local_file_read / mnote_local_file_patch / mnote_knowledge_rag_query / mnote_reference_open / mnote_tool_receipt_write`
- bridge token 使用 `/dev/urandom` 生成,不随 session JSON 序列化,不写入 extension 文件;token 通过子进程 env 注入。
- `/api/page-ai/pi/tool-call-bridge` 只接受匹配 bridge token 且仍处于 `RuntimeRunning/TurnRunning` 的 session。
- session TTL、最大 session 数、start/send/tool 简单 rate limit 已补;过期 session 目录和空 rate bucket 会清理。
## 3. 第三方 UI 复用决策
- `@earendil-works/pi-web-ui` 继续作为 ChatPanel / AgentInterface / ThinkingBlock / tool renderer / artifacts rail 的交互参考,不直接引入 MNote 主链。
- `@assistant-ui/react-pi` 继续作为 HTTP/SSE thread contract 参考,不让其接管 MNote provider key、IndexedDB session、Pi browser Agent 或 tool 权限。
- `ai-elements` 继续作为 message / reasoning / tool / source / prompt input 形态参考,不引入 React/Tailwind/shadcn 依赖。
## 4. 验证记录
- `node scripts/task-pi-lab-static-smoke.js`:通过,119 checks。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33170 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' node scripts/task-pi-lab-api-endpoint-smoke.js`:通过,10/10。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33170 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' MNOTE_PI_LAB_SMOKE_ROOT=/tmp/mnote-pi-lab-smoke node scripts/task-pi-lab-mock-api-smoke.js`:通过。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33170 MNOTE_PI_LAB_AUTH_HEADER='Bearer pi-lab-smoke' MNOTE_PI_LAB_BROWSER_ROOT=/tmp/mnote-pi-lab-browser-smoke MNOTE_PI_LAB_SCREENSHOT=/mnt/Data1T/mnote/tmp/page-ai-pi-lab-native-drawer-7-70.png node scripts/task-pi-lab-browser-smoke.js`:通过。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33172 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' MNOTE_PI_LAB_BROWSER_ROOT=/tmp/mnote-pi-real-rpc-smoke-1783130268 MNOTE_PI_LAB_SCREENSHOT=/mnt/Data1T/mnote/tmp/page-ai-pi-lab-rpc-browser-real-7-71.png node scripts/task-pi-lab-rpc-browser-smoke.js`:通过;使用 `MNOTE_PAGE_AI_PI_LAB_RUNTIME=rpc``MNOTE_PAGE_AI_PI_BIN=/tmp/mnote-pi-cli-wrapper.sh` 和 Omniroute `freefirst`
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33172 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' MNOTE_PI_LAB_BROWSER_ROOT=/tmp/mnote-pi-p1-rpc-smoke-1783133999 MNOTE_PI_LAB_SCREENSHOT=/mnt/Data1T/mnote/tmp/page-ai-pi-lab-rpc-browser-p1-7-71c.png node scripts/task-pi-lab-rpc-browser-smoke.js`:通过;覆盖真实文档页 Pi Lab 默认入口、当前页读取、tiptap selection、allowed roots deny、`.md` patch、watcher/refresh 到 `.ProseMirror`、真实 Pi stream、abort、非 iframe;同时收紧 pagePath 绑定断言。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33172 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' MNOTE_PI_LAB_SMOKE_ROOT=/tmp/mnote-pi-real-rpc-smoke-1783130268 MNOTE_PAGE_AI_PI_BIN=/tmp/mnote-pi-cli-wrapper.sh node scripts/task-pi-lab-rpc-api-smoke.js`:通过;覆盖真实 Pi RPC subprocess、send、SSE、abort、allowed roots deny、`.md` patch、receipt。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33172 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' OPENAI_API_KEY=<codex auth> node scripts/task-pi-lab-rpc-api-smoke.js`:通过;覆盖真实 Pi custom tool 主动调用 `mnote_current_page_read`、MNote bridge 执行 `mnote.current_page.read`、marker 返回、send、SSE、abort、allowed roots deny、`.md` patch、receipt。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33172 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' MNOTE_PI_LAB_SCREENSHOT=/mnt/Data1T/mnote/tmp/page-ai-pi-lab-rpc-browser-p2-visible-reply-7-70.png node scripts/task-pi-lab-rpc-browser-smoke.js`:通过;覆盖真实浏览器 Pi Lab 默认入口、原生 drawer、当前页/选区、allowed roots deny、`.md` patch watcher refresh、真实 Pi stream、assistant bubble 可见回复、abort、非 iframe。该 smoke 已修正为断言 assistant bubble 本身包含 marker,而不是误用 drawer 全文里的用户 prompt。
- `MNOTE_PI_LAB_BASE=http://127.0.0.1:33172 MNOTE_PI_LAB_AUTH='Bearer pi-lab-smoke' MNOTE_PI_LAB_SCREENSHOT=/mnt/Data1T/mnote/tmp/page-ai-pi-lab-openhub-like-ui-collapsed-v2-7-70.png node scripts/task-pi-lab-rpc-browser-smoke.js`:通过;覆盖 OpenHub-like 折叠 UI,设置/上下文/右侧 tools/receipts 默认收起,主对话区成为视觉重点,同时保留模型选择入口和真实 Pi 可见回复。
- `curl -H 'Cookie: foo=bar' http://127.0.0.1:33172/api/page-ai/pi/status`:返回 401 `page_ai_pi_lab_invalid_session_cookie`,验证任意 Cookie 不能绕过 Pi Lab API 认证。
- `cd rust && cargo check -p mnote-web`:通过。
- `codegraph sync . && codegraph status .`:通过,index up to dateCodeGraph 提示索引由旧 engine 构建,可后续择机 full re-index。
## 5. 截图证据
- Pi Lab 原生抽屉:`tmp/page-ai-pi-lab-native-drawer-7-70.png`
- 显示独立 `π` 悬浮按钮。
- 显示独立 Pi Lab drawer,不是 iframe。
- 显示 `omniroute/freefirst`、runtime `mock`、aborted、context strip、tool rail、receipt rail。
- 显示 prompt、mock stream、abort、LightRAG mock citation、patch/diff、changed files、allowed roots deny receipt。
- OpenHub 默认入口回归:`tmp/page-ai-pi-lab-openhub-default-7-70.png`
- 显示 OpenHub drawer 仍可独立打开。
- 同屏保留独立 `π` 入口,说明 Pi Lab 未复用 OpenHub 抽屉容器。
- 真实 Pi RPC 浏览器回归:`tmp/page-ai-pi-lab-rpc-browser-real-7-71.png`
- 显示 runtime `rpc`、模型 `omniroute/freefirst`
- 显示真实 Pi RPC text stream 返回 `REAL_PI_BROWSER_OK_*` 标记。
- provider thinking 事件仅进入 diagnostics,不进入 visible final answer。
- 同时验证 allowed roots deny、markdown patch receipt、abort UI 状态和非 iframe 独立 drawer。
- P1 文档页功能回归:`tmp/page-ai-pi-lab-rpc-browser-p1-7-71c.png`
- 显示真实 `/documents/local-md:*` 页面默认存在独立 `π` 入口。
- 显示 Pi Lab drawer 内绑定当前页、选区、runtime `rpc`、模型 `omniroute/freefirst`
- 显示当前页已由 patch 从 `Browser RPC Original` 刷新为 `Browser RPC Patched`
- 显示 allowed roots deny receipt、patch diff receipt、changed files chip。
- P2 真实 Pi RPC 浏览器回归:`tmp/page-ai-pi-lab-rpc-browser-p2-visible-reply-7-70.png`
- 显示真实 MNote 文档页默认独立 `π` 入口与独立 Pi Lab drawer。
- 显示 runtime `rpc`、模型 `omniroute/freefirst`、Pi builtin `bash/read/write/edit` disabled。
- 显示当前页读取、选区、allowed roots deny receipt、patch diff receipt、changed files chip、assistant 可见回复。
- OpenHub-like 折叠 UI 回归:`tmp/page-ai-pi-lab-openhub-like-ui-collapsed-v2-7-70.png`
- 显示设置、上下文、MNote tools、Receipts 默认折叠。
- 显示模型选择入口 `omniroute/freefirst`,主 timeline 和 composer 保持首屏重点。
- OpenHub 平台式空状态回归:`tmp/page-ai-pi-lab-openhub-platform-empty-v4-7-70.png`
- 显示独立 Pi Lab 抽屉使用 OpenHub 平台式标题、顶部工具栏、白色对话卡片、居中问号空状态和五个示例。
- 设置、上下文、诊断默认不占首屏;通过齿轮展开。
- 底部保留大输入框、发送按钮、Build/Plan、`omniroute/freefirst` 模型选择、预算和当前页/选区快捷入口。
## 6. 未完成内容与风险
- receipt 仍落 `provider_neutral_jsonl_adapter_v1`,尚未迁移到 Turso/libSQL 正式 `ai_provider_sessions` / `ai_tool_events` / `ai_file_patches` 表。
- 真实 Pi RPC + Omniroute/freefirst 已补浏览器回归;真实 Pi 主动调用 MNote tools 的最小 bridge 链路已在 API smoke 中通过。长期更正统方向仍是 in-process `AgentSession` / Node sidecar SDK,避免依赖 RPC subprocess + extension bridge。
- watcher refresh 已通过真实文档页 `.md` patch 后 `.ProseMirror` 更新验证;仍需在后续产品化中补更细的 file version 冲突与多 pane 场景。
- P1/P2 审查后已修复任意 Cookie 认证绕过、跨用户 session 查询、selection tool 缺少 host snapshot 标识、`tool_receipt.write` 空操作、browser smoke pagePath 误报、bridge token 可预测、aborted session bridge 调用、rate bucket 增长和 token 落盘风险。
@@ -0,0 +1,499 @@
# 7-70 Page AI Pi Lab 原生 OpenHub-like UI 设计 v1
状态:done
Owner07-ai / mnote-web / page-ai-runtime
日期:2026-07-04
## 1. 结论
Pi Lab 下一阶段不做第二个 iframe,也不把 `@earendil-works/pi-web-ui` 或 LibreChat / Open WebUI 这类完整应用直接嵌入 MNote。正确方向是:
```text
MNote Page AI 独立 Pi Lab 浮动入口 + 原生抽屉 UI
-> 体验参考当前 OpenHub AI 面板,但入口和容器不依赖 OpenHub
-> 运行协议参考 @assistant-ui/react-pi 的 PiClient / HTTP SSE thread contract
-> 消息、composer、tool、artifact 形态参考 Pi 官方 web-ui 与 ai-elements
-> 权限、知识库、receipt、watcher refresh 继续由 MNote 托管
```
OpenHub 继续作为默认 Page AI 主线。Pi Lab 使用独立浮动按钮与独立抽屉容器,默认在 MNote 页面中显示;目标是把 7-69 的 debug spike UI 升级成可验证的 MNote-native Page AI 体验,而不是替代或复用 OpenHub 的入口。若后续确认不需要 Pi Lab,直接退役这一独立入口和 runtime 即可,不需要先用 feature flag 隔离。
## 2. 原则
- **独立入口默认显示**:Pi Lab 新增自己的悬浮按钮和抽屉容器,默认显示,不复用当前 OpenHub 的按钮、iframe 页面或 provider tab 容器;以后退役 OpenHub 或 Pi Lab 任一侧,都不影响另一侧。
- **原生承载**Pi Lab UI 渲染在 MNote 自有 DOM 中,不新增独立产品页作为主验收面,不 iframe 第二个完整 AI 应用。
- **优先参考和复用**:能复用当前 OpenHub 已证明的交互、Pi 官方 UI 的事件语义、assistant-ui 的 runtime contract、ai-elements 的组件形态,就不要自造一套孤立协议。
- **MNote 拥有真相**session binding、allowed roots、LightRAG facade、tool receipt、file patch、citation/open-reference、watcher refresh 仍归 MNote;第三方 UI 不能接管这些事实源。
- **Debug 后置**Page AI 首屏必须是对话、输入、上下文和变更结果;runtime health、scope JSON、receipt 原文只进折叠诊断区或开发态右栏。
- **不破坏 OpenHub**OpenHub 仍是默认入口和默认 Page AI 主线;Pi Lab 作为独立入口与 OpenHub 并列但互不依赖,不出现普通用户可见的 fallback/迁移暗示。
## 3. 参考资源取证
### 3.1 当前 OpenHub UI
本地 OpenHub 是当前最接近目标体验的产品基线:
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/pages/SmartQueryPage.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/ChatInput.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/AssistantMessage.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/ToolCall.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/HistoryDrawer.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/DiffViewer.jsx`
- `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/KnowledgeManager.jsx`
可参考能力:
- 顶部/底部紧凑控制区:agent mode、model select、当前页面/当前文件夹按钮。
- 主消息流:user / assistant bubble、streaming、retry / undo、idle state。
- assistant messagereasoning 折叠、tool summary、tool cards、citation chips。
- session/history:历史抽屉、恢复会话、归档。
- changed files / diff:隐藏索引桥接 MNote,用户侧展示 changed files / diff 入口。
- knowledge:知识库入口和 citation 回跳模式。
不直接复用方式:
- 不把 OpenHub React/AntD 组件直接搬进 `mnote-web` 主运行时。
- 不复制 OpenHub session/message 全文存储作为 Pi Lab 真相。
- 不复用 OpenHub iframe shell 实现 Pi Lab,因为用户目标是 MNote-native。
### 3.2 Pi 官方 web-ui
`@earendil-works/pi-web-ui@0.75.3` 已核验为 MIT 包,描述为 “Reusable web UI components for AI chat interfaces powered by @earendil-works/pi-ai”。包内包含:
- `ChatPanel`
- `AgentInterface`
- `MessageList` / `Messages`
- `MessageEditor`
- `StreamingMessageContainer`
- `ThinkingBlock`
- tool renderers
- artifacts runtime / sandbox
- `ModelSelector`
- IndexedDB storage stores
可参考能力:
- Pi 消息流与工具流的视觉分层。
- thinking / tool / artifact 的独立组件边界。
- composer 与 model/thinking selector 的组合方式。
- artifacts-style 侧栏。
不直接整包接入原因:
- 默认假设 browser 侧持有 `Agent`、provider keys、IndexedDB session/settings。
- 默认可暴露 Pi 自带 tools / attachments / artifacts 事实源。
- 与 MNote 后端托管 Pi RPC/SSE、AiAccessScope、MNote-owned tools 的边界冲突。
迁移条件:
- 先提供 remote-session adapter,使 `ChatPanel` / `AgentInterface` 不直接管理 provider key、IndexedDB 和 `Agent.state.tools`
- 所有 tool call、file read/write、knowledge query、receipt 仍必须走 MNote facade。
### 3.3 assistant-ui / react-pi
`@assistant-ui/react-pi@0.0.5` 已核验为 MIT,说明其 browser entry 是 RPC-isomorphic `PiClient`,通过 HTTP/SSE 驱动 Pi-backed threads。其 contract 包含:
```text
GET /threads
POST /threads
GET /threads/:id
PATCH /threads/:id
POST /threads/:id/messages
POST /threads/:id/cancel
GET /models
POST /threads/:id/model
POST /threads/:id/thinking
GET /threads/:id/events
```
可参考能力:
- thread / message / composer / queue / cancel 的协议边界。
- snapshot-first SSE reconnect 语义。
- model / thinking controls。
- host UI approval requests。
- tool-associated approval / interrupt 的 UI 映射。
当前不直接引 React UI 的原因:
- `mnote-web` 当前主路径是 Rust SSR + browser JS,不是 React app。
- 直接引入 React island、Tailwind/shadcn 体系会扩大构建和运行时边界。
- 当前优先目标是把 MNote-owned Pi adapter 做完整,而不是重开前端框架主线。
设计决策:**先对齐 contract,不先引 UI 框架**。后续如果 MNote 需要 React island,再以该 contract 为迁移桥。
### 3.4 ai-elements
`ai-elements@1.9.0` 已核验为 Apache-2.0,定位是基于 shadcn/ui 的 AI-native component registry。
可参考能力:
- message / conversation / prompt input / reasoning / tool / source / response card 的组件形态。
- 更现代的 AI chat 视觉密度和状态表达。
当前不直接接入原因:
- 依赖 React、Tailwind、shadcn registry 和 CSS variables。
- 对当前 MNote Rust SSR + browser JS 主路径引入成本偏高。
设计决策:作为视觉和组件拆分参考,不作为 7-70 第一阶段依赖。
### 3.5 完整应用类参考
LibreChat、Open WebUI、AnythingLLM、Continue、Cline、Roo Code 只作为交互模式参考:
- LibreChat / Open WebUI / AnythingLLM:参考历史会话、多模型、RAG、文件区、设置入口。
- Continue / Cline / Roo Code:参考 agent tool timeline、审批、diff、changed files、任务状态。
不作为直接依赖或嵌入目标。
## 4. UI 目标结构
### 4.1 独立入口与抽屉整体
```text
MNote page
Pi Lab floating buttondefault visible
-> Pi Lab native drawerindependent from OpenHub iframe/page
topbar
Pi Lab title
session title
runtime status
close / minimize
context strip
current page chip
selection chip
allowed roots chip
LightRAG chip
model / thinking selector
body
message timeline
optional right rail
composer
prompt input
context buttons
send / abort / queue state
folded diagnostics
```
桌面端允许右侧 rail;移动端不显示右 rail,改为底部 sheet 或折叠 section。
入口规则:
- Pi Lab 使用独立悬浮按钮,例如右下角 `π`,默认渲染。
- Pi Lab 点击后打开独立抽屉,不借用 OpenHub 当前按钮、OpenHub iframe 页或 OpenHub provider tab。
- Pi Lab 抽屉可以视觉参考 OpenHub,但 DOM、状态、session、事件订阅、关闭/最小化都独立。
- OpenHub 的现有按钮和页面保持不变;Pi Lab 不在 OpenHub 面板内新增 tab 作为主入口。
- 若未来退役 OpenHub,Pi Lab 浮动入口仍可保留;若退役 Pi Lab,OpenHub 入口不受影响。
- `MNOTE_PAGE_AI_PI_LAB` 可保留为临时开发/强制关闭开关,但不是产品入口显示的前置条件。
### 4.2 Topbar
目标是接近 OpenHub 的 AI 面板,而不是 debug 工具页。
必须包含:
- Pi Lab 标题与实验标记;不放 OpenHub/Pi provider tabs 作为主导航。
- session label`Pi Lab · 当前页面标题`,恢复会话后显示短 session id。
- runtime statusidle / starting / streaming / error。
- model label:默认 `omniroute/freefirst`,可后续扩展 selector。
- close/minimize。
不要在 topbar 放大段 `scope``disabled builtins`、raw env 等诊断文案。
### 4.3 Context Strip
替代当前偏 debug 的工具列表,参考 OpenHub `ChatInput` 中当前页面/文件夹按钮:
- 当前页:真实 `.md` 路径或页面标题,点击在 MNote 中定位。
- 选区:无选区时 disabled,有选区时显示字符数。
- allowed roots:显示 root 名称和数量,点击展开只读详情。
- LightRAG:显示 provider 状态;query citation 走 `mnote.reference.open`
- 文件变更:当前 session 有 patch 后显示 changed files chip。
Context strip 是用户可理解的任务上下文,不展示 JSON。
### 4.4 Message Timeline
消息模型要吸收 OpenHub `AssistantMessage` 与 Pi/assistant-ui 的 part 语义:
```text
MNotePiUiMessage
id
role = user | assistant | system
status = queued | streaming | done | error | aborted
createdAt
model
parts[]
MNotePiUiPart
text
reasoning
toolCall
toolResult
citation
diff
fileChange
approval
error
```
展示规则:
- user message 右侧气泡。
- assistant message 左侧主体块,正文优先。
- reasoning 默认折叠,严禁把 provider `reasoning_content` 混入 final answer。
- tool timeline 默认折叠成 summary,运行中自动展开当前 tool。
- tool 输入/输出长 JSON 默认收起,显示摘要和 deny reason。
- citation 显示为来源 chip,点击走 MNote open-reference。
- diff / fileChange 显示为 changed file chips + diff summary,点击走 MNote document pane / diff view。
- error 明确显示 provider/runtime/tool deny,不伪装成功。
### 4.5 Tool Timeline
参考 OpenHub `ToolCall.jsx`、Pi official tool renderers、Cline/Roo 的工具审批流。
第一阶段必须覆盖:
- `mnote.current_page.read`
- `mnote.selection.read`
- `mnote.allowed_roots.describe`
- `mnote.local_file.read`
- `mnote.local_file.patch`
- `mnote.knowledge_rag.query`
- `mnote.reference.open`
- `mnote.tool_receipt.write`
每个 tool card 至少显示:
- tool name
- statepending / running / allowed / denied / error / done
- compact params summary
- deny reason
- receipt id
- file version before / after
- diff summary
- citation count
默认禁用 Pi 原始 `bash/read/write/edit` 的状态可以放在折叠详情里,不占据首屏。
### 4.6 Composer
参考 OpenHub `ChatInput`、Pi `MessageEditor`、assistant-ui composer queue 语义。
第一阶段:
- 多行输入。
- icon-only 当前页、选区、文件夹/allowed roots、LightRAG 按钮,带 tooltip 和 aria-label。
- send 按钮。
- streaming 时显示 abort;若后端支持 queue,再允许 follow-up queue。
- 发送前自动附带 MNote context envelope,不把上下文 URL 或 JSON 写入 textarea。
- 禁用态要有明确原因:未启动 runtime、missing model、missing auth、scope denied。
后续阶段:
- model selector。
- thinking level selector。
- thread queue / steer。
- prompt template / skill picker。
### 4.7 Right Rail / Drawer
桌面端右侧 rail 承接“有用但不该挤占主对话”的信息:
- session/history:当前只显示当前 session 与最近 session,完整历史后续做抽屉。
- changed files:变更文件列表、patch count、打开按钮。
- citations:本轮 citation 列表。
- receipts:最近 tool receipts,默认摘要。
- runtimePi PID / provider session id / runtime mode,只在 dev mode 显示。
移动端右 rail 不常驻,改为按钮展开底部 sheet。
### 4.8 Diagnostics
折叠 diagnostics 只面向开发:
- raw SSE events tail。
- allowed roots raw snapshot。
- receipt raw JSON。
- runtime command / pid / session dir。
- disabled builtin tools。
默认折叠,不参与普通用户验收截图。
## 5. 后端与协议设计
### 5.1 保留现有 7-69 endpoint
当前 7-69 已有:
- `/api/page-ai/pi/status`
- `/api/page-ai/pi/bootstrap`
- `/api/page-ai/pi/start`
- `/api/page-ai/pi/send`
- `/api/page-ai/pi/abort`
- `/api/page-ai/pi/events`
- `/api/page-ai/pi/tool-call`
- `/page-ai/pi`
7-70 不要求立即破坏这些 endpoint。
但前端入口要调整为默认显示的独立浮动按钮与独立 drawer host`/page-ai/pi` 可保留为 debug/internal 直达页,不作为产品主验收入口。
### 5.2 新增 PiClient-aligned adapter 层
为了后续能接 `@assistant-ui/react-pi` 或复用其 reducer 语义,新增 provider-neutral adapter contract
```text
GET /api/page-ai/pi/threads
POST /api/page-ai/pi/threads
GET /api/page-ai/pi/threads/:id
POST /api/page-ai/pi/threads/:id/messages
POST /api/page-ai/pi/threads/:id/cancel
GET /api/page-ai/pi/threads/:id/events
GET /api/page-ai/pi/models
POST /api/page-ai/pi/threads/:id/model
POST /api/page-ai/pi/threads/:id/thinking
```
兼容策略:
- 第一阶段可在前端 adapter 中把旧 endpoint 映射为 thread contract。
- 后端可逐步增加新 endpoint;旧 smoke 不被破坏。
- SSE 必须 snapshot-first:重连先发当前 thread snapshot,再发 live events。
- browser disconnect 不等于 abort;只有 cancel/abort 明确停止 runtime。
### 5.3 UI event normalization
Pi RPC / mock / future SDK event 统一归一为:
```text
thread_snapshot
message_created
message_delta
message_completed
reasoning_delta
tool_call_started
tool_call_updated
tool_call_completed
tool_call_denied
citation_added
file_patch_applied
receipt_written
runtime_status_changed
thread_error
```
所有 UI 只消费 normalized events,不直接依赖 Pi JSONL 原始字段。
## 6. 数据与权限边界
- `AiAccessScope` / allowed roots 是所有 file tool 的唯一授权来源。
- Pi Lab UI 只显示 allowed roots 摘要,不允许用户从 UI 临时扩大 root。
- `mnote.local_file.read` / `patch` 必须返回 normalized path、root id、deny reason、file version。
- LightRAG 继续是唯一默认 knowledge providerPi 只能通过 `mnote.knowledge_rag.query` 查询。
- citation 点击必须走 `mnote.reference.open` / MNote open-reference,不直接让 Pi 打开文件。
- receipt 当前仍可落 provider-neutral adapter,但 UI v2 必须按 Turso/libSQL 迁移目标设计字段。
- 文件 patch 后必须通过 MNote watcher / document refresh 链路更新 tiptap;禁止新增轮询刷新。
## 7. 实施阶段
### Phase ANative UI v2 骨架
修改范围:
- `rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js`
- `scripts/task-pi-lab-static-smoke.js`
- `scripts/task-pi-lab-browser-smoke.js`
- 必要时补 `rust/crates/mnote-web/src/ssr/styles/components/page-ai.css`
目标:
- 当前 Pi Lab 从 debug shell 升级为 OpenHub-like 抽屉。
- Pi Lab 使用独立悬浮按钮打开独立抽屉,不复用 OpenHub 按钮、OpenHub iframe 页面或 provider tab。
- 首屏只保留对话、context strip、composer。
- tool receipts / diagnostics 默认后置。
- 截图必须显示:在 MNote 当前页面中通过独立 `π` 浮动按钮打开 Pi Lab 原生抽屉,非 OpenHub iframe、非独立页、非 OpenHub provider tab。
### Phase BEvent / message model 收口
修改范围:
- `sidebar-page-ai-pi-lab-runtime.js`
- `rust/crates/mnote-web/src/routes/page_ai_pi.rs`
- smoke 脚本
目标:
- 新增 normalized event reducer。
- reasoning、tool、citation、diff、receipt 不再混杂在一条 debug 文本里。
- SSE reconnect snapshot-first。
- abort/cancel 明确区分 browser disconnect。
### Phase CPiClient-aligned adapter
目标:
- 增加或前端模拟 `/threads` contract。
- `send` / `cancel` / `events` 对齐 `@assistant-ui/react-pi` 的语义。
- 保持旧 endpoint 兼容,直到 smoke 全部迁移。
### Phase DOpenHub-like 功能补齐
目标:
- session history 抽屉。
- changed files rail。
- citation list。
- diff preview。
- model / thinking selector。
- tool approval / host UI requests。
### Phase E:第三方组件复用决策
进入条件:
- Phase A-D 浏览器截图和 smoke 通过。
- 当前 MNote native UI 已形成稳定 message/event model。
决策:
- 若 MNote 仍保持非 React 主路径:继续原生实现,只参考 Pi web-ui / ai-elements。
- 若引入 React island:优先评估 `assistant-ui + @assistant-ui/react-pi`,并复用 Phase C contract。
- 若官方 Pi web-ui 提供 remote-session adapter:评估局部复用 `AgentInterface` / `Messages` / `MessageEditor`,不得引入 browser provider key 和 IndexedDB 作为 MNote 真相。
## 8. 验收标准
- OpenHub 默认入口不变。
- MNote 默认显示独立 Pi Lab 浮动按钮;点击后打开独立 Pi Lab 原生抽屉。
- Pi Lab 不复用 OpenHub 按钮、OpenHub 页面或 OpenHub iframe 容器。
- UI 首屏接近 OpenHub:消息流、上下文、输入框、模型/状态、tool/citation/diff 摘要齐全。
- 不出现第二个 iframe AI 应用。
- 可发送 prompt、接收 stream、abort。
- reasoning 折叠且不泄漏到 final answer。
- tool call 显示状态、deny reason、receipt id。
- allowed roots 越界读写在 UI 中显示 denied。
- LightRAG citation 可点击回跳。
- 当前 `.md` patch 后显示 changed file,并通过 watcher refresh 当前 tiptap 页。
- diagnostics 默认折叠。
- browser smoke 输出截图。
## 9. 明确非目标
- 不替换 OpenHub。
- 不把 Pi Lab 做成普通用户默认入口。
- 不 iframe LibreChat / Open WebUI / AnythingLLM / Pi web-ui demo app。
- 不把 Pi Lab 做成 OpenHub 面板内部的 provider tab 或复用 OpenHub 页面容器。
- 不把 React / shadcn / Tailwind 作为 7-70 Phase A 必需依赖。
- 不开放 Pi 原始 bash、无约束 read/write/edit。
- 不新增第二套 RAG。
- 不把 OpenHub 或 Pi 的 session JSONL 作为 MNote control-plane 长期真相。
## 10. 当前后续任务
- [ ] Phase A:重做 `sidebar-page-ai-pi-lab-runtime.js` 信息架构、独立浮动入口与视觉层级。
- [ ] Phase A:更新 browser smoke,截图验证独立 `π` 浮动按钮打开 OpenHub-like Pi Lab 抽屉。
- [ ] Phase B:抽出 normalized event reducer。
- [ ] Phase C:补 PiClient-aligned thread adapter。
- [ ] Phase D:补 changed files / diff / citation rail。
- [ ] Phase E:重新评估是否引入 `assistant-ui` 或 Pi official remote-session adapter。
@@ -0,0 +1,141 @@
# 7-71 统一 AI 管理面板与 Pi Lab 功能接入 — 实施检查清单
状态:partial — P0/P1/P2 完成;P3/P4/P5 管理员侧 OpenHub-like AI 管理与逐用户配置完成,用户自助偏好与 LightRAG 写管理待后续
日期:2026-07-04
> 本清单与 `design/07-ai/process/7-71-unified-ai-management-control-plane-and-pi-lab-integration-v1.md` 的 §11 进度表保持同步。
> process 文档保留架构结论;本文件记录已完成的检查项供快速验证。
## 已通过验收项
### P0 — 合同与只读聚合 ✅
| 检查项 | 通过 |
|---|---|
| `/api/ai-settings/effective` GET 端点已注册 | ✅ |
| `/api/ai-settings/access-scopes` GET 端点已注册 | ✅ |
| `/api/ai-admin/access-scopes` GET 端点已注册 | ✅ |
| 所有 AI settings/scopes 端点无 POST/PUT/DELETE | ✅ |
| `effective_settings` 使用 `load_active_directory_grants` 生成 allowed roots | ✅ |
| `load_model_policy_and_quota` 不读取 `allowed_roots_json` | ✅ |
| `source_of_truth` 声明为 `"directory_grants"` | ✅ |
| 静态 smoke 27/27 通过 | ✅ |
### P1 — Pi Lab history 入库 ✅
| 检查项 | 通过 |
|---|---|
| `persist_upsert_run` 写入 `ai_runtime_runs` | ✅ |
| `persist_append_event` 写入 `ai_runtime_events` | ✅ |
| `GET /api/page-ai/pi/sessions` 路由注册 | ✅ |
| `GET /api/page-ai/pi/sessions/{id}` 路由注册 | ✅ |
| `GET /api/page-ai/pi/sessions/{id}/events` 路由注册 | ✅ |
| list_sessions 按 pi_lab profile + pi acp_runtime 过滤 | ✅ |
### P2 — Receipt 与 patch 入库 ✅
| 检查项 | 通过 |
|---|---|
| migration v9 新增 `ai_tool_events / ai_file_patches` | ✅ |
| SQLite 与 Turso/libSQL store 均实现 append/list | ✅ |
| Pi tool receipt 默认写 control-plane | ✅ |
| 成功 patch 关联写入 `ai_file_patches` | ✅ |
| JSONL 仅在 control-plane 写失败时作为 debug fallback | ✅ |
| deny reason、diff summary、file version、citation count 可查询 | ✅ |
| receipt 与 patch 按当前用户隔离 | ✅ |
### P3 — AI 管理中心与管理员模型写管理 ✅
| 检查项 | 通过 |
|---|---|
| `/admin/ai` 路由与管理员鉴权 | ✅ |
| `/user/ai` 路由与用户鉴权 | ✅ |
| 账户菜单有独立 AI 管理入口 → `/admin/ai``/user/ai` | ✅ |
| AI 管理页面 SSR 渲染 OpenHub-like 左侧分页与多 section | ✅ |
| Access Scopes 只读展示(无 POST/PUT/DELETE、无写表单) | ✅ |
| Sessions & Receipts 显示真实 Pi session、receipt、patch 数量 | ✅ |
| Providers & Models 管理写操作 | ✅ |
| `/user/ai` 用户可操作设置 | ❌ |
### 独立入口保留检查
| 检查项 | 通过 |
|---|---|
| OpenHub `/page-ai/openhub/ai` | ✅ |
| OpenHub admin guard 路由 | ✅ |
| OpenHub status API | ✅ |
| Pi Lab shell `/page-ai/pi` | ✅ |
| Pi Lab event stream `GET /api/page-ai/pi/events` | ✅ |
| Pi Lab start/send/abort 路由 | ✅ |
### P4 — 工具、Skills/MCP 管理 ✅
| 检查项 | 通过 |
|---|---|---|
| `GET/PUT /api/ai-admin/settings` 原子策略端点 | ✅ |
| 模型 provider、allowed models、角色模型与 failover 写入 | ✅ |
| MNote-owned tools allow/ask/deny 写入 | ✅ |
| Skill 添加、描述、启停、删除 | ✅ |
| MCP transport/command/url/network/secret refs 写入 | ✅ |
| MCP 强制 facade-only 与 sandbox | ✅ |
| 刷新后配置持久存在 | ✅ |
| effective API 投影模型、Skills、MCP | ✅ |
### P5 — 逐用户 AI 配置 ✅
| 检查项 | 通过 |
|---|---|
| OpenHub `UserManagement`、模型/工具/Skill 权限实现已核验 | ✅ |
| `GET /api/ai-admin/users` 用户列表 | ✅ |
| `GET/PUT /api/ai-admin/users/{user_id}/settings` | ✅ |
| 用户可独立选择管理员允许的模型和默认模型 | ✅ |
| 用户工具策略只能相对全局降权 | ✅ |
| 用户 Skill/MCP 只能在全局启用目录中开关 | ✅ |
| 普通用户登录后的 effective API 应用 override | ✅ |
| allowed roots 仍只来自 `directory_grants` | ✅ |
| 用户抽屉采用 OpenHub-like 分组:模型按 provider、工具按 risk、Skill/MCP 开关、目录只读跳统一授权页 | ✅ |
## 后续项
| 项 | 状态 | 说明 |
|---|---|---|
| `/user/ai` 用户可操作偏好 | 未实现 | 当前只读 effective config,不能提升管理员权限 |
| LightRAG source/index/status 写管理 | 未实现 | 继续使用唯一 knowledge provider 与现有 facade |
| provider/skill/MCP 专用规范化表 | 未实现 | 当前原子策略存入 `ai_policies.model_policy_json`;出现独立查询、授权或生命周期需求时迁移 |
## 浏览器证据
- 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`
- 逐用户 AI 配置:`tmp/7-71-ai-admin-openhub-like-ui-final/users.png`
- 管理页运行时摘要:会话 1、回执 3、文件补丁 1
- Pi Lab 截图可见真实 mock 回复、citation、diff、abort,且 OpenHub 默认入口仍独立存在
- `mnote-e2e` 通过 control-plane `role=admin` 进入管理员模式;浏览器验收用 dev seed 临时确保测试账号为 admin
- OpenHub 的 MCP 实际为用户工作区 `.opencode/mcp` 文件展示,并非 Admin per-user overrideMNote 采用更严格的管理员注册 + 用户启停 + facade/sandbox 模型
## 验证命令
```bash
# 静态 smoke(只读代码审计,无需启动服务器)
node scripts/task-ai-management-control-plane-static-smoke.js
MNOTE_AI_ADMIN_OUTPUT_DIR=tmp/7-71-ai-admin-openhub-like-ui-final \
node scripts/task-ai-management-browser-smoke.js
node scripts/task-pi-lab-static-smoke.js
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo check -p mnote-web
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo test -p control-plane ai_tool_events
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo test -p control-plane ai_file_patches
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo test -p control-plane migrations
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo test -p mnote-web ai_settings
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo test -p mnote-web page_ai_pi
CARGO_TARGET_DIR=/tmp/mnote-7-71-target cargo test -p mnote-web ai_admin
# 运行时验证(需要 mnote-web 运行于 localhost:3000
# curl -s http://localhost:3000/api/ai-settings/effective | jq '{source: .sourceOfTruth, models: [.models[].id], rootsCount: (.allowedRoots | length)}'
# 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}\n' http://localhost:3000/admin/ai
# curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/user/ai
```
@@ -449,8 +449,8 @@ assistant-ui / CopilotKit / AG-UI 进入 UI 备选评估
本稿通过后新增:
- `7-69-page-ai-pi-first-lab-checklist-v1.md`
- `scripts/task*-page-ai-pi-rpc-smoke.js`
- `design/07-ai/done/7-69-page-ai-pi-first-lab-checklist-v1.md`
- `scripts/task-pi-lab-rpc-api-smoke.js`
- `scripts/task*-page-ai-pi-lightrag-smoke.js`
- `scripts/task*-page-ai-pi-file-patch-smoke.js`
@@ -472,3 +472,38 @@ assistant-ui / CopilotKit / AG-UI 进入 UI 备选评估
Pi 不应被过早否定;它已经有足够多的 runtime 与生态基础,值得作为首选实验底座。
Pi 也不应被过早宣布替换 OpenHub;MNote 需要自己补齐多用户控制面、Turso 审计、LightRAG tool、allowed roots 和 UI 产品化边界。
## 13. Spike 执行记录(2026-07-03
本稿对应的最小可验证 spike 已落地到代码,验收记录见:
- `design/07-ai/done/7-69-page-ai-pi-first-lab-spike-record-v1.md`
- `design/07-ai/done/7-69-page-ai-pi-first-lab-checklist-v1.md`
当前完成范围:
- `/api/page-ai/pi/*``/page-ai/pi` dev-only 入口。
- Pi RPC/mock runtime manager 与 MNote 管控 sessionDir。
- 8 个 MNote-owned tool facade。
- allowed roots 越界拒绝、file version、diff summary、deny reason、receipt JSONL adapter。
- LightRAG 仍通过 `knowledge_rag` facade 暴露给 Pi,不引入第二套 RAG。
- 文件 patch 返回 watcher refresh metadata,且 `polling=false`
- MNote-native Pi Lab UI 支持 start/send/events/abort、SSE stream、tool/citation/diff/receipt 显示。
- OpenHub 仍保持默认 Page AI 主线。
当前未完成范围:
- 全局 `pi` CLI 仍未进入 PATH;但已通过 `MNOTE_PAGE_AI_PI_BIN=/tmp/mnote-pi-cli-wrapper.sh` 包装 `npx -y @earendil-works/pi-coding-agent@0.80.3` 跑通真实 `pi --mode rpc` 端到端 smoke。
- Turso/libSQL 正式 receipt 表迁移;当前仍为 `provider_neutral_jsonl_adapter_v1`
- 登录态浏览器中验证 watcher 刷新 tiptap 当前页。
- `@earendil-works/pi-web-ui` 未成为长期 UI 基座,本轮由 MNote-native debug UI 承接。
验证结果:
- `node scripts/task-pi-lab-static-smoke.js`:通过。
- `node scripts/task-pi-lab-api-endpoint-smoke.js`:通过。
- `node scripts/task-pi-lab-mock-api-smoke.js`:通过,覆盖 mock runtime、SSE、allowed roots、越界拒绝、当前页读取、当前 `.md` patch、watcher refresh metadata、LightRAG facade、citation/open-reference facade、send、abort。
- `node scripts/task-pi-lab-browser-smoke.js`:通过,覆盖 `/page-ai/pi` 独立页、Pi Lab 面板、输入框、启动/发送/清空按钮、runtime asset、status API。
- 真实 Pi RPC smoke:通过,覆盖 MNote 托管启动 `pi --mode rpc``runtimePid`、当前页读取、越界拒绝、当前 `.md` patch、send accepted、abort。
- `node scripts/task-pi-lab-rpc-api-smoke.js`:新增,覆盖 runtimeMode=rpc 下 status、runtimePid 真实子进程、start/send/abort 端点、tool-call MNote facadesallowed roots、越界拒绝、.md patch、watcher refresh、LightRAG、citation)、SSE 事件流、多会话生命周期、Pi 进程清理。需要真实 Pi CLIMNOTE_PAGE_AI_PI_BIN)与可选 API key 验证 send。
- `cd rust && cargo check -p mnote-web`:通过。
@@ -0,0 +1,562 @@
# 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 专用规范化表迁移