Files
mnote/design/07-ai/done/7-70-page-ai-pi-lab-native-openhub-like-ui-record-v1.md
T

94 lines
12 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-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 落盘风险。