94 lines
12 KiB
Markdown
94 lines
12 KiB
Markdown
# 7-70 Page AI Pi Lab 原生 OpenHub-like UI 验收记录 v1
|
||
|
||
状态:done(UI spike + P2 Pi tool bridge 回归)
|
||
Owner:07-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、默认折叠 diagnostics;2026-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 date;CodeGraph 提示索引由旧 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 落盘风险。
|