> 状态补充(2026-07-03):本稿冻结为 opencode runtime / 官方 opencode WebUI iframe fallback 参考;Page AI 产品主线为 OpenHub / native agent + LightRAG + Turso/libSQL。官方 iframe 仅在 OpenHub AI 面板不可用、调试 opencode 原生行为或做回归对照时启用,不再作为默认产品路径。此前指向 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 的 WeKnora 默认 provider 口径已标记 stale。 # [recycle] 7-65 [process] Page AI opencode WebUI embed v1 > 创建时间:2026-06-23 > > 当前状态:`FROZEN / 官方 opencode iframe fallback,OpenHub + LightRAG + MNote 深度融合为主线` > > Owner:07-ai / Page AI / opencode WebUI embed > > 替代方案: > - `design/old/07-ai/process/7-62-recycle-page-ai-board-first-full-rewrite-v1.md` > - `design/old/07-ai/process/7-63-recycle-page-ai-board-first-productization-v1.md` > - `design/old/07-ai/process/7-64-recycle-codexmobile-embed-page-ai-v1.md` > > 上位依据: > - `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` > - `design/07-ai/done/7-38-page-ai-sidebar-runtime-owner-split-v1.md` > - `design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md` > - `design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md` ## 1. 核心结论 废弃 MNote Page AI 自研 provider 接入与 Board-first 产品壳后,本稿曾把 **opencode 官方 runtime + 官方 WebUI** 作为主线;当前该方案已降级为 fallback,默认产品主线为 OpenHub / native agent + LightRAG + Turso/libSQL。 ```text fallback MNote Page AI = MNote 宿主壳 + opencode 官方 WebUI iframe + MNote 打开/刷新/上下文集成 主线 Page AI = MNote 宿主壳 + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + LightRAG opencode = agent runtime / HTTP server / OpenAPI / SSE / SDK / opencodego provider ``` MNote 不再直接维护 Reasonix / ZCode / Hermes / Chat-only / Board worker 作为页面 AI provider。它们可以继续存在于历史、调试或外部工作流边界,但不进入 Page AI 主路径。官方 opencode WebUI iframe 也不再进入默认产品主路径,只作为 fallback。 选择 opencode 的原因: - `opencode web` 官方提供本地 WebUI,不需要 MNote 自研完整聊天前端。 - `opencode serve` 官方提供 headless HTTP server 和 OpenAPI,适合 MNote 做轻量 adapter。 - `@opencode-ai/sdk` 覆盖 session、message、diff、permission、event,足够承接上下文注入与回写 receipt。 - opencode 原生支持 SSE、权限审批、文件 diff、MCP、ACP、session export/import。 - 本机已有 `opencode` 和 opencodego 订阅链路,provider/runtime/UI 属于同一生态,少一层兼容债。 ## 2. 明确废弃 ### 2.1 Page AI 主路径不再接入 - Reasonix native session / Reasonix desktop bridge。 - ZCode worker / Board worker selector。 - Hermes profile / Hermes Web control surface。 - Chat-only remote conversation。 - Agent Board run/workflow 作为默认 Page AI 后端。 - CodexMobile iframe 作为默认 Page AI 后端。 这些能力不删除历史代码,不立刻清理工具层,只从 Page AI 新主路径退出。 ### 2.2 仍可保留的边界 - `mnote.doc.*`、`mnote.block.*` 等工具可继续作为 fallback/compat 边界;默认知识库工具走 LightRAG + provider-neutral `mnote.knowledge_rag.*` facade。 - Agent Board 仍可作为外部 workflow/QA/review 系统,不再作为 Page AI 默认聊天后端。 - CodexMobile 可保留为备选 spike 或体验对照,不作为当前实现目标。 ## 3. 新系统边界 ### 3.1 MNote 只做四件事 1. **上下文**:当前页、选区、页面标题、真实 `.md` 路径、workspaceId、allowed roots、LightRAG 引用;WeKnora 只作为历史设计、参考实现或备用 provider 边界。 2. **授权**:把 MNote local-first 文件权限转换为 opencode permission / external_directory / working directory。 3. **嵌入**:第一版通过 MNote 同源受登录态保护反代嵌入 opencode 官方 WebUI;`npm run dev:hot` 默认拉起 `opencode serve --hostname=127.0.0.1 --port 4096`。 4. **回执**:监听 opencode event/diff,触发 MNote watcher 刷新,记录 Page AI session binding。 ### 3.2 opencode 负责完整 agent runtime - 聊天 UI。 - 流式事件。 - session 管理与恢复。 - provider/model 调用。 - tool call 展示。 - permission ask/allow/deny。 - 文件读写、patch、diff。 - MCP / ACP / agent 配置。 ### 3.3 集成形态:不是新的 Leptos island 当前 MNote 文档编辑器已经是 `leptos_tiptap_island`,但 Page AI 不应该再做一个重前端 island。Page AI 更像 VSCode 里的 Cline: ```text VSCode workbench host + Cline webview/extension MNote web shell host + opencode WebUI iframe/bridge ``` 因此第一版形态是 **mnote-web sidebar host runtime**: - 继续使用 `rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 作为宿主入口,但把它瘦身成 opencode host。 - iframe 内尽量保留 opencode 官方 UI,包括消息、tool、permission、diff/session 页面。 - iframe 外只放 MNote 必要宿主控件:当前页 context bar、打开变更、刷新当前页、授权状态、运行状态。 - 不新建 Leptos island,不引入 React/Vue 到 MNote 主壳,不重写 opencode 消息 UI。 如果后续必须深度定制 opencode UI,也优先 fork opencode WebUI 的少量页面,而不是在 MNote 里复刻一套聊天前端。 ## 4. 集成拓扑 ```text MNote Rust SSR :3000 └─ Page AI sidebar ├─ MNote host chrome │ [当前页] [选区] [可写目录] [知识库引用] │ [打开变更] [刷新当前页] [在主编辑区打开] └─ iframe http://127.0.0.1:4096//session ↓ first MVP direct localhost iframe opencode web :4096 ├─ 官方 WebUI ├─ 官方 HTTP server / OpenAPI ├─ SSE /event ├─ session/message/diff/permission APIs └─ opencodego / configured providers ``` 第一版优先 iframe 官方 WebUI,不 fork、不精简、不重写样式。实测 opencode WebUI 使用根路径 `/assets`、`/session`、`/global/health` 等资源/API,子路径 `/page-ai/opencode/` iframe 会产生 root path 错位;因此 MVP 采用 MNote 同源根路径反代 `//session/`,让 iframe 内 `location.pathname` 与 opencode 官方 WebUI 预期保持一致,同时通过 MNote 登录态保护反代入口。`/api/page-ai/opencode/*` 负责 session binding、context 注入、status、diff/receipt adapter。只有 iframe/adapter 实测无法满足产品嵌入时,才考虑 fork WebUI。 ### 4.1 MNote host chrome Page AI 抽屉由 MNote 控制尺寸、开关、上下文和跨应用动作,opencode 只负责 AI 交互主体。 宿主控件最小集: - 当前页 pill:标题、相对路径、读写状态。 - 选区 pill:有选区才展示,点击可重新注入上下文。 - 变更 pill:来自 opencode diff/event,点击用 MNote 打开对应文件。 - 刷新按钮:调用 `window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument()`。 - 打开按钮:调用 `window.__mnoteDocumentPaneRuntime.openResourceInActiveTab()`。 这些控件由 MNote 渲染,避免修改 opencode 官方页面结构。 ### 4.2 MNote 打开 opencode 变更 opencode WebUI 里的 diff / changed file 默认按 opencode 自己的 UI 打开。MNote 需要额外提供宿主打开能力: ```text opencode diff/event → MNote adapter 归一化 changed files → Page AI host chrome 显示 changed file chips → 用户点击 chip → window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ path }) ``` 第一版不强行改 opencode diff 内部点击行为;先在 iframe 外给 MNote-native changed file chips。这样即使 opencode DOM 改版,MNote 打开变更仍可用。 ### 4.3 可选 bridge:只做宿主动作,不接管 UI 如果需要让 opencode 官方 UI 内部的文件链接也能用 MNote 打开,可在同源反代 HTML 中注入一个很小的 bridge 脚本: ```text opencode iframe click(file/diff link) → postMessage({ type: 'mnote:open-file', path }) → parent MNote host 调用 openResourceInActiveTab ``` bridge 只允许三类消息: - `mnote:open-file` - `mnote:refresh-file` - `mnote:session-ready` 不通过 bridge 解析模型事件、不重绘消息、不替换 permission UI。DOM 选择器脆弱时立即退回 host chrome chips。 ## 5. 最小实现 ### Phase A:runtime spike - [x] 运行 `opencode --version`、`opencode web --help`、`opencode serve --help`;当前版本已升级到 `1.17.9`。 - [x] 卸载 oh-my-openagent / oh-my-opencode 默认插件:`opencode.json` 中 `plugin` 已为空,`oh-my-openagent.jsonc` 已移除并备份。 - [x] 启动/复用 `opencode serve --hostname=127.0.0.1 --port=4096`,`/global/health` 返回 healthy。 - [x] 验证 WebUI 可打开、可进入 `/mnt/Data1T/mnote` project session、可真实回复。 - [x] 验证 opencodego/OmniRoute 模型可用:iframe 内真实回复 `OPENCODE_MNOTE_IFRAME_OK_*`。 - [x] 验证当前工作目录指向 MNote workspace:`/session` 返回 `directory=/mnt/Data1T/mnote`。 - [x] 验证编辑一个 `.md` 文件后,`/session/:id/diff` 能返回文件 diff:`opencode-smoke-test.md` 返回 `modified` diff。 - [ ] `/event` SSE 只做了接口可达性探索,尚未接入持续事件消费。 ### Phase B:MNote iframe embed - [x] Rust 新增 `/page-ai/opencode/{*path}` 反向代理到 `127.0.0.1:4096`,并新增 `/api/page-ai/opencode/status`、`/api/page-ai/opencode/diff`。 - [x] 只允许已登录 MNote session 访问反代/API;未登录请求返回 `401 page_ai_opencode_unauthorized`。 - [x] Page AI sidebar 精简为:MNote host chrome + iframe + basic status。 - [x] 尽量不改 opencode WebUI,保留官方页面布局、消息样式、tool/diff/permission UI。 - [x] iframe 容器与 host chrome 由 `sidebar-page-ai-runtime.js` + `page-ai.css` 承载,不新增 Page AI Leptos island。 - [x] WebUI 默认 iframe 使用 MNote 同源 `//session` 反代;避免局域网浏览器访问自身 `127.0.0.1:4096`,同时保留 opencode 官方 URL 形态。 ### Phase B2:MNote-native changed files - [x] 建立 opencode sessionId ↔ MNote host 状态的持久 binding:`/api/page-ai/opencode/session` 创建/复用 session,并落 SQLite/control-plane,按用户/session/workspace 区分。 - [x] 通过 `/api/page-ai/opencode/events` 代理 opencode `/event`,收到 session/message/diff/file 事件后触发有界 refresh。 - [x] 通过 `/session/:id/diff` 生成 changed file chips。 - [x] chip 点击走 `window.__mnoteDocumentPaneRuntime.openResourceInActiveTab()`。 - [x] 当前页刷新按钮已走 `refreshPrimaryDocument({ reason: 'page-ai-opencode-host' })`;changed files 命中当前页时由 event/diff refresh 链路自动触发刷新。 ### Phase C:context 注入 优先不修改 opencode WebUI,通过官方 API/SDK 注入上下文: ```text MNote open Page AI → create or resume opencode session → session.prompt(noReply=true, parts=[MNote context envelope]) → iframe 打开对应 session ``` 上下文 envelope 内容: - 当前页标题。 - 当前页真实 Markdown 路径。 - selection 文本。 - allowed roots 与读写权限。 - LightRAG 引用摘要;WeKnora 引用只作为备用 provider / 历史参考。 - 当前任务约束:优先编辑 primaryTarget,禁止越权修改。 当前状态:`done for MVP`。host chrome 已展示当前页、真实 Markdown path(可定位时)、selection、allowed roots/writable 状态;打开 Page AI 时会调用 `/api/page-ai/opencode/session`,用 `noReply=true` 向 opencode session 注入 MNote context envelope,并把 iframe 打到绑定 session URL。2026-06-24 已按 opencode-chat 参考改回官方 WebUI iframe 主路径,MNote-native timeline 仅保留为 debug/receipt 边界。 ### Phase D:writeback / receipt - [x] 监听 opencode `/event` 或 SDK `event.subscribe()`:当前通过 `/api/page-ai/opencode/events` 代理 `/event`,EventSource 收到相关事件后触发有界 refresh。 - [partial] prompt / event 后通过绑定 session 的 `/session/:id/diff` 刷新 changed files;仍需继续核对 opencode 各类 file/diff event payload。 - [x] changed files 命中当前页时调用 `refreshPrimaryDocument({ reason: 'page-ai-opencode-event' })`;手动刷新按钮保留。 - [x] 保存 MNote pageId ↔ opencode sessionId binding:当前已落 SQLite/control-plane,按用户/session/workspace 区分;浏览器刷新后可恢复同一 binding。 - [x] Page AI context bar 显示最近一次 changed files / runtime / error 摘要。 ### Phase E:可选 WebUI bridge - [ ] 只有 host chrome chips 体验不足时,才在反代层注入 `mnote-opencode-bridge.js`。 - [ ] bridge 只把 opencode UI 内部文件点击转成 `postMessage`。 - [ ] bridge 不解析/修改 opencode 消息流、tool UI、permission UI。 - [ ] selector 失效时不阻塞主流程,回退 host chrome chips。 ## 6. 安全与权限 - opencode 只监听 `127.0.0.1`;MVP iframe 直连本机地址,同源反代/API 仍必须受 MNote 登录态保护。 - 生产/长期运行必须设置 `OPENCODE_SERVER_PASSWORD`,或改为完整同源反代 + MNote 反代层隔离。 - opencode working directory 优先指向当前 workspace root。 - MNote allowed roots 映射到 opencode permission: - 当前 workspace root:允许读,写按用户授权。 - 当前页文件:允许读写。 - workspace 外路径:默认 deny,必要时显式 `external_directory`。 - 不使用 `--dangerously-skip-permissions` 作为默认路径。 ## 7. 验收标准 ### 7.1 UI - Page AI 面板内显示 opencode 官方 WebUI。 - 官方消息流、tool 卡片、permission 交互、diff/session UI 尽量原样保留。 - MNote 只在 iframe 外展示 host chrome,不重做 opencode UI。 - opencode 产生的 changed files 可以用 MNote 主编辑区或资源 tab 打开。 ### 7.2 Runtime - 可创建/恢复 opencode session。 - 可用 opencodego 模型完成真实回复。 - 流式回复浏览器可见。 - 权限审批走 opencode 原生机制。 - 文件修改后 MNote 当前页面能刷新。 ### 7.3 代码收敛 - `sidebar-page-ai-runtime.js` 不再承载 Reasonix/ZCode/Hermes/Board provider 状态机。 - 不新增 MNote 自研聊天 message store。 - 不 fork opencode WebUI,除非 spike 证明 iframe 方案不可用。 - 不新增 Page AI Leptos island;Page AI 是 mnote-web sidebar host runtime。 ## 8. 与旧方案对比 | 方案 | 优点 | 主要问题 | 当前结论 | |---|---|---|---| | Board-first | 可接多 worker/workflow | MNote 仍要维护产品壳和 Board adapter,聊天体验不成熟 | 废弃为主路径 | | CodexMobile embed | Codex 体验强,贴近现有 Codex 体系 | 需要 fork/精简/修 bug,维护派生产品 | 备胎/对照 | | opencode WebUI embed | 官方 runtime + 官方 WebUI + 官方 API,维护成本低 | opencode 能力可能不如 Codex 先进,嵌入细节需实测 | 当前主路径 | ## 9. 非目标 - 不重写 opencode WebUI。 - 不把 opencode WebUI 拆成 MNote 原生组件。 - 不同时接入 Reasonix/ZCode/Hermes/CodexMobile 多后端。 - 不把 Agent Board 控制台嵌入 Page AI。 - 不在第一版实现完整 MNote SSO 到 opencode;先由 MNote 反代保护。 ## 10. 退出条件 只有出现以下任一情况,才重新启用 CodexMobile 或自研 UI 方案: - opencode WebUI 无法稳定 iframe/反代嵌入。 - opencode session 无法通过 API 定位并打开指定 session。 - opencode 无法可靠编辑 MNote workspace 文件。 - opencode permission/diff/event 无法满足 MNote 最小安全闭环。 - opencodego/provider 链路在真实使用中明显不稳定且短期不可修。 ## 9. 2026-06-24 iframe 主路径验证记录 - [x] `opencode --version`:`1.17.9`。 - [x] `opencode serve --hostname=127.0.0.1 --port 4096 --print-logs`:真实可启动;官方 WebUI URL `http://127.0.0.1:4096/L21udC9EYXRhMVQvbW5vdGU/session` 可显示 `Build anything`。 - [x] `npm run dev:hot`:真实拉起 `mnote-web :3000` 和 `opencode :4096`。 - [x] 浏览器 smoke:登录 `mnote.e2e@example.com` 后打开 Page AI,iframe URL 为 `http://127.0.0.1:3000/L21udC9EYXRhMVQvbW5vdGU/session`,显示官方 opencode WebUI;截图 `/tmp/mnote-page-ai-opencode-iframe.png`。 - [partial] 发送真实消息:官方 WebUI 可输入并进入 `Thinking/Stop` 运行态;截图 `/tmp/mnote-page-ai-opencode-send.png`。本轮未等待到最终回复,不能声明 provider 回复完成。 - [x] binding 持久化 smoke:浏览器 reload 后仍恢复同一 session `ses_106555512ffe5wDz9eMlP4v2i2`。 - [x] 静态检查:`node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`。 - [x] Rust 检查:`cd rust && cargo check -p mnote-web`。 剩余 gap: - [x] 在同源反代层补最小 `postMessage` bridge:`open-file / refresh-file / insert-text`,不接管消息流。 - [partial] 更完整核对 opencode `/event` 的 file/diff payload:已递归解析常见 changed/diff/file payload 并由 event 触发有界 refresh;仍需真实编辑文件后补最终证据。 - [ ] 若要局域网访问,继续确认所有 opencode WebUI root-level API 路由都已被 MNote 登录态反代覆盖,避免直接暴露 4096。 ## 10. 2026-06-25 bridge / receipt 补充记录 - [x] HTML 反代注入极小 bridge:只处理 `insert-text / open-file / refresh-file / session-ready`,不接管 opencode 消息流、tool UI、permission UI。 - [x] MNote host chrome 新增“插入当前页上下文”按钮,通过 `postMessage` 把当前页标题、路径、选区、allowed roots 插入 opencode 官方输入框。 - [x] 浏览器 smoke:`/tmp/mnote-page-ai-opencode-bridge-final.png`,验证 iframe 内 `window.__mnoteOpencodeBridgeInstalled === true`,点击 host 按钮后官方输入框出现 `MNote 当前页上下文标题:主页`。 - [x] postMessage smoke:模拟 iframe 发 `open-file / refresh-file`,确认 MNote host 调用 `openResourceInActiveTab()` 与 `refreshPrimaryDocument()`;输出见 `tmp/mnote-opencode-postmessage-smoke.cjs` 运行结果。 - [x] event receipt 强化:`pageAiOpencodeNormalizeChangedFiles()` 改为递归收集 `changedFiles / files / diff / changes / edited / created / deleted / data / properties`,事件到达时先更新 chips,再做有界 projection refresh。 - [x] 静态检查:`node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`。 - [x] Rust 检查:`cd rust && cargo check -p mnote-web`。 剩余 gap: - [ ] 让 opencode 真实修改一个测试 Markdown 文件,等待 `/event` + `/session/:id/diff` 产出 changed file chips,再截图验证 chip 点击打开 MNote 主编辑区。 - [ ] 若 opencode 官方 WebUI 后续 CSP 变化,需要把 inline bridge 改成 nonce/hash 或外部小脚本。 ## 11. 2026-06-25 opencode 用户隔离与工作目录结论 ### 11.1 当前结论 - [x] Page AI 工作目录应简化为“当前用户打开的根文件夹”:MNote 当前只能打开一个大的本地根目录,`rootUri` 对应的真实目录就是 opencode `directory` / project directory。 - [x] 切换根文件夹应视为新的 opencode session 边界:同一用户同一根目录内可按页面恢复 binding;根目录变化时默认新建 session,不把旧 session 带到新根目录。 - [x] MNote 自身 binding 已按 `user_id + workspace_id + mnote_session_id + provider` 隔离;`mnote_session_id` 内包含 workspace/page/directory,因此同一用户跨根目录不会复用同一 binding。 - [partial] opencode 自身默认实例没有 MNote 用户概念;如果所有 MNote 用户共用一个 `opencode serve`,opencode 的 session、permission、credential、account、skill、MCP、snapshot、tool-output 会共用同一套本机状态。 ### 11.2 opencode 1.17.9 真实机制证据 本机 spike 使用临时环境启动: ```bash HOME=/tmp/.../home \ XDG_CONFIG_HOME=/tmp/.../config \ XDG_DATA_HOME=/tmp/.../data \ XDG_CACHE_HOME=/tmp/.../cache \ XDG_STATE_HOME=/tmp/.../state \ opencode serve --port 4197 --hostname 127.0.0.1 --print-logs ``` 观察结果: - 配置读取路径变为 `$XDG_CONFIG_HOME/opencode/{config.json,opencode.json,opencode.jsonc}`。 - 持久数据写入 `$XDG_DATA_HOME/opencode/opencode.db`。 - 日志写入 `$XDG_DATA_HOME/opencode/log/opencode.log`。 - 锁写入 `$XDG_STATE_HOME/opencode/locks/*`。 - 当前全局实例的默认持久库是 `/home/lix/.local/share/opencode/opencode.db`。 - `opencode.db` 内包含 `session`、`message`、`part`、`permission`、`credential`、`account`、`account_state`、`project`、`workspace`、`event` 等表;这些表没有 MNote 用户维度。 - `permission` 只按 `project_id + action + resource` 唯一;共用实例会导致不同 MNote 用户在同一 project/directory 下共享 opencode 权限记忆。 - `session` 表包含 `directory` / `project_id` / `workspace_id` / `metadata`,但不包含 MNote `user_id`;共用实例不能作为安全隔离边界。 - opencode 会从当前用户 HOME/配置路径加载 skill/MCP;未隔离时可看到 `/home/lix/.claude`、`/home/lix/.agents`、`/home/lix/.config/opencode/skill` 等重复 skill 警告。 ### 11.3 推荐隔离方案 第一版不要试图在单个 opencode server 内实现多用户隔离;改为 **MNote 用户/根目录维度的 opencode runtime profile**: ```text MNote user + workspace/rootUri -> runtime profile id -> dedicated XDG_CONFIG_HOME / XDG_DATA_HOME / XDG_CACHE_HOME / XDG_STATE_HOME -> dedicated opencode serve process on 127.0.0.1:dynamic_port -> MNote 登录态反代 /page-ai/opencode/... ``` 目录建议: ```text $MNOTE_DATA/users//opencode//config/opencode/opencode.json $MNOTE_DATA/users//opencode//data/opencode/opencode.db $MNOTE_DATA/users//opencode//cache/opencode/ $MNOTE_DATA/users//opencode//state/opencode/ ``` 配置策略: - provider/API key 可由 MNote 管理后写入每个 profile 的最小 `opencode.json`,或在本机单用户 dev 模式从 `/home/lix/.config/opencode/opencode.json` 复制 provider/model 白名单。 - skill/MCP 默认不继承宿主 HOME 的全部内容;只显式安装 MNote 允许的 skill/MCP,例如 `codegraph`、`mempalace`、后续 MNote MCP。 - opencode 原生 permission 继续保留,但存储在该 profile 的独立 `opencode.db` 中。 - MNote 的 allowed roots 仍作为上下文与反代边界;opencode 的工作目录固定为当前打开根目录。 ### 11.4 session 策略 - 同一 `user_id + workspace_id/rootUri + pageAbsolutePath`:优先恢复 MNote control-plane binding 指向的 opencode session。 - `rootUri` 变化:默认新建 session;旧 binding 保留但不跨根目录复用。 - `pageAbsolutePath` 变化:默认新建或按页面 binding 恢复,不把旧页面上下文继续注入到新页面。 - opencode 原生 session 恢复只作为 profile 内部能力;MNote 是否恢复以 control-plane binding 为准。 - 跨浏览器恢复依赖 SQLite control-plane binding + per-user opencode data profile,不依赖 `sessionStorage`。 ### 11.5 后续实现项 - [ ] 新增 opencode runtime profile manager:按 MNote user/rootUri 分配 XDG 目录、端口、启动/健康检查、生命周期。 - [ ] `dev:hot` 继续可一键启动,但默认只启动 dev 单用户 profile;多用户 profile 由后端按需拉起。 - [ ] 反代不再只读 `MNOTE_OPENCODE_BASE_URL` 全局值,需按当前登录用户和 rootUri 解析到对应 profile base URL。 - [ ] session binding metadata 写入 `runtimeProfileId`、`rootUri`、`projectDirectory`、`opencodeDataDir`,方便审计和恢复。 - [ ] 增加最小测试:两个不同 MNote 用户同一 Markdown 根目录下创建 session,不共享 opencode `opencode.db`、permission、session list。 ### 11.6 官方/社区多用户实现调研补充 本轮先核验豆包给出的项目名,再补充搜索到的真实社区线索。结论:**没有发现可直接嵌入 MNote 的官方/社区“单 opencode 进程多 MNote 用户强隔离”实现**;官方当前也把多用户 Web/serve 部署视为待增强能力。 豆包列表验真: | 项目 | 验真结果 | 对 MNote 的价值 | |---|---|---| | `anomalyco/opencode-orchestrator` | 未找到公开仓库;真实相近项目是 `agnusdei1207/opencode-orchestrator` | 后者是 opencode 多 agent 编排插件,不是多用户 runtime 隔离层 | | `pRizz/opencode-cloud` / `gitea.com/pRizz/opencode-cloud` | 真实存在 | Docker/container 级隔离参考;安全强但本地 MNote 开销偏大 | | `oc-ext/ocx` | 未找到公开仓库 | 暂不可作为依据 | | `daytonaio/daytona-opencode-plugin` | 未找到公开仓库 | 暂不可作为依据 | | `anomalyco/openwork` | 未找到公开仓库 | 暂不可作为依据 | | `lucentia/opencode-svip-proxy` | 未找到公开仓库 | 暂不可作为依据 | | `kwickramasekara/opencode-chat` | 真实存在 | VSCode WebView 嵌入参考:启动/复用 `opencode serve`、固定端口保留 localStorage、WebView proxy、剪贴板/键盘 bridge;不是多用户隔离实现 | 额外发现: | 项目/线索 | 结论 | |---|---| | 官方 `anomalyco/opencode` issue `#20067` | open:请求 `opencode web` 支持 multi-user auth 与 per-user provider credentials;issue 描述确认共享 Web 实例会共享身份、session、provider credentials | | 官方 `anomalyco/opencode` issue `#5784` | closed:请求多租户 `serve` 下 MCP auth/config;说明多租户 MCP/资源隔离是社区真实痛点,但不是已可用的完整 MNote 用户隔离方案 | | 官方 `SECURITY.md` | 明确 opencode 不提供安全 sandbox;server mode 只支持 `OPENCODE_SERVER_PASSWORD` Basic Auth;需要真隔离时建议 Docker/VM | | `millerjes37/opencode-multiplexer` | 真实存在,是 opencode fork 的 multi-client server 支持;文档承认“知道 sessionID 即可交互”的 session hijacking 风险,session ownership 仍是 future enhancement;不适合作 MNote 多用户隔离底座 | | `joeyism/opencode-multiplexer` | 真实存在,是多 session/多项目终端 dashboard,不是 Web 多用户隔离 | 对当前方案的修正: - 不应引入豆包描述的“单进程多租户 opencode”作为近期主路径;目前没有可靠公开实现,官方也还在 issue 层面。 - 也不应一开始上 Docker/container;它解决安全隔离但会显著增加本地笔记软件的启动、资源和运维成本。 - 近期最稳妥路线是 **单 opencode 进程 + 单 MNote 当前用户/当前根目录 profile**,先满足本机单用户和局域网同一登录用户;等 MNote 真正进入多用户同时在线场景,再升级为按需 profile manager。 - 若要减少端口/生命周期复杂度,可先采用 `opencode-chat` 的轻量做法:固定一个 dev/local 端口、优先复用已存活 server、MNote 反代统一入口;不要提前实现多实例调度。 - 多用户隔离仍必须作为设计约束保留:不能把共享 opencode `opencode.db` 声称为安全隔离,只能标为单用户/dev 模式。 更新后的分阶段建议: 1. **Phase MVP-local**:一个 MNote 登录用户 + 一个当前根目录 + 一个 opencode server;工作目录固定为 `rootUri`;MNote control-plane 做跨浏览器 session binding。 2. **Phase shared-device**:为每个 MNote 用户准备独立 XDG profile,但不常驻多进程;登录/打开 Page AI 时按需启动,空闲回收。 3. **Phase SaaS/团队**:再评估 `opencode-cloud`/Docker 或等待官方 multi-user auth/per-user credentials 落地;不要自己 fork 官方 opencode 做单进程多租户。 ### 11.7 OpenHub 对照结论 `xcl1989/OpenHub` 是目前找到的最接近“opencode 多用户平台”的社区实现,README 明确主张:一个 `opencode serve (:4096)`,后端按用户 workspace 通过 `?directory=` 路由到不同目录,并在应用 SQLite 中维护 users、sessions、messages、permissions、skills、tools 等业务层权限。 可复用点: - 单 opencode server + per-user workspace:后端调用 `/session`、`/session/{id}/prompt_async`、`/global/event` 时统一带 `directory=`。 - 应用层 session ownership:OpenHub 自己用 SQLite 记录 `conversation_sessions` / messages / user_id,不把 opencode 原生 session 列表直接暴露给所有用户。 - 应用层权限面板:模型权限、工具权限、skill 权限都在业务 DB 中维护,再同步/注入到用户 workspace。 - per-user `.opencode` 目录:README 架构图显示每个 workspace 下有独立 `.opencode/skills`、`.opencode/tools`。 - 单进程运维简单:固定 `OPENCODE_BASE_URL=http://127.0.0.1:4096`,Basic Auth 保护后端到 opencode 的内部访问。 关键风险: - OpenHub 不是 opencode 原生多租户;它仍依赖一个全局 opencode server 和全局 opencode 数据库/credential/account 状态。 - 隔离主要靠 `directory` 与 OpenHub 后端不暴露跨用户 session;如果绕过 OpenHub 直接访问 opencode,或知道别人的 session id,仍要依赖外层鉴权/反代拦截。 - provider credentials 是 opencode server 全局配置;OpenHub 的用户模型/工具权限是应用层控制,不等于 opencode 内核 per-user credentials。 - 它自研了聊天前端、消息库、知识/记忆/任务系统;这不符合 MNote 当前“尽量保留 opencode 官方 WebUI,不复刻消息 UI”的边界。 对 MNote 的启发: - OpenHub 证明“单 opencode serve + `?directory=` 按用户工作区隔离”在产品上可跑,比一开始做多进程 profile manager 更轻。 - MNote MVP 可以采用 OpenHub 的轻量隔离思路:固定一个本机 opencode server,所有请求由 MNote 登录态反代,MNote 后端只允许当前用户的 `rootUri` 作为 `directory`,并用 control-plane binding 限制 session ownership。 - 但必须把这种模式标为 **应用层隔离 / 单机可信 opencode 后端**,不能标为强安全多租户。强隔离仍需后续 XDG profile 或 Docker。 更新后的推荐: 1. **立即采用 OpenHub-lite**:单 `opencode serve`、固定端口、MNote 反代、`directory=rootUri`、control-plane session ownership。 2. **不复刻 OpenHub UI**:仍保留 opencode 官方 WebUI iframe;MNote 只做 host chrome、context、open/refresh、changed files。 3. **补安全闸**:所有 `/api/page-ai/opencode/*` 和 iframe 反代必须校验当前登录用户、rootUri、session binding;不允许前端任意传 directory 打开非当前 root。 4. **后续 shared-device 再升级**:当确实有多 MNote 用户同时使用同一机器时,再做 per-user XDG profile manager。 ### 11.8 OpenHub 融合可行性评估 用户新判断:OpenHub 的前端、消息库、权限、记忆、文件、任务等功能与 MNote Page AI 长期目标高度重合,应评估是否直接融合,减少 MNote 自研量。 结论:**可融合,但不建议整套 OpenHub 作为 MNote 新后端;推荐抽取 OpenHub Page-AI 子系统,形成 MNote 内的 `OpenHub-lite`。** 可最大化复用的部分: - **React/AntD 聊天前端**:`SmartQueryPage.jsx`、`ChatInput`、`AssistantMessage`、`ToolCall`、`QuestionForm`、`HistoryDrawer`、`DiffViewer`、`FileManager`、`GitTimeMachine` 等,可作为 Page AI 的 micro frontend,而不是继续维护当前简陋 host UI。 - **消息库模型**:`conversation_sessions`、`conversation_messages`、图片、turn、opencode message id、归档、retry、last-turn delete 等,适合迁移到 MNote control-plane,替代 `sessionStorage` 和当前临时 binding。 - **opencode 单进程接入模式**:后端按用户 workspace/rootUri 调 `/session`、`/session/{id}/prompt_async`、`/global/event` 并带 `directory=`,适合 MNote 当前“一个打开根目录”的简化模型。 - **应用层权限面板**:模型权限、工具权限、skill 权限、usage 统计可以映射到 MNote 用户体系;短期先只做 Page AI 所需的模型/工具/skill 白名单。 - **任务/团队/记忆模块**:Smart Entity、Team、Memory、Scheduler 与 MNote 长期 agent 目标相关,但第一阶段只作为后续模块,不应阻塞 Page AI MVP。 不建议直接搬入的部分: - OpenHub 自带登录、用户管理、admin 页,与 MNote control-plane auth 重叠;应替换成 MNote 登录态。 - OpenHub FastAPI 后端与 MNote Rust SSR/control-plane 双后端并存会增加部署复杂度;此判断已被 `7-68` 覆盖,当前第一阶段保留 OpenHub FastAPI/Redis/opencode client。 - OpenHub 自研知识库/记忆/任务会与 MNote WeKnora、workspace、tree/file resource、control-plane 产生事实源冲突;旧 LightRAG 仅作为 legacy/fallback 参考。 - OpenHub 不是 opencode 内核级强隔离,仍需 MNote 反代和 session ownership 限制。 推荐融合路线: 1. **Phase 1:iframe micro frontend spike** - 直接运行 OpenHub 前端的 Page-AI/Chat 子集,嵌入 MNote sidebar。 - 后端 API 不直接用 OpenHub FastAPI,而是由 MNote 提供兼容 `/api/query/stream`、`/api/sessions/*`、`/api/files/*` 的最小 Rust adapter。 - 目标是快速验证 UI/消息体验是否明显优于 opencode 官方 iframe。 2. **Phase 2:消息库迁移** - 在 MNote control-plane 增加 OpenHub-like `page_ai_sessions` / `page_ai_messages` / `page_ai_message_parts` / `page_ai_turns`。 - 将 opencode session id、message id、tool calls、diff、reasoning、attachments、rootUri、pageAbsolutePath 统一持久化。 - 替代当前临时 Page AI binding;支持跨浏览器、跨会话恢复。 3. **Phase 3:UI 组件裁剪融合** - 从 OpenHub 前端抽出 Chat shell、消息列表、工具调用、历史抽屉、Diff/File/GitTimeMachine 组件。 - 去掉 Login/Admin/Knowledge/SmartEntity/Team 等非 Page AI 首屏模块。 - 适配 MNote host chrome、当前页 context、changed file chip、MNote open/refresh。 4. **Phase 4:高级能力选择性引入** - FileManager 映射 MNote resource/file tree。 - GitTimeMachine 映射 MNote changed files / snapshot / restore 设计。 - Memory/Skill/Tool permission 映射 MNote 用户权限和未来 MNote MCP。 - Smart Entity/Team 作为 Page AI 后续 agent team,不进入当前 MVP。 技术判断: - 如果目标是“尽快有成熟 Page AI UI”,OpenHub 前端比 opencode 官方 iframe 更适合深度定制,因为它已经是普通 React/AntD 应用,消息、工具、历史、文件、diff 都在前端组件内。 - 如果目标是“最少维护债”,opencode 官方 iframe 仍最省事,但 MNote 与页面/文件/权限/历史的融合会受 iframe 限制。 - 当前更适合改为 **OpenHub UI + MNote Rust adapter + opencode runtime**:UI 和消息体验复用 OpenHub,用户/文件/权限/工作区真相仍归 MNote,agent runtime 仍归 opencode。 新的建议: - 把 `7-65` 当前 iframe 方案降级为 runtime spike 与 fallback。 - 新增或接续设计 `7-67-openhub-page-ai-fusion-v1`,目标是用 OpenHub Chat 子系统替代当前 Page AI UI。 - 第一阶段只做 Chat/Session/Message/Diff/File open 五件事,不引入 OpenHub 登录/admin/知识库/team。 ### 11.9 OpenHub 知识库实现与 WeKnora 对照 2026-07-03 口径回正:Page AI 要做深度融合;当前默认知识库主线为 LightRAG + OpenHub tool facade,WeKnora 仅保留为历史设计、参考实现或备用 provider。因此本节只作为当时 OpenHub 自带知识库能力对照记录。 源码核验结论:**OpenHub 自带知识库是轻量 SQLite 文本知识库,不是完整 RAG/知识库底座;适合复用 UI、API 形状和 prompt 注入链路,不建议替代 WeKnora。** OpenHub 知识库真实实现: - 数据表只有 `knowledge_bases` 与 `knowledge_sources`:字段包括 `scope=enterprise/user`、`owner_id`、`title`、`source_type`、`content`、`tags`、统计字段;没有 chunk 表、embedding 表、向量库、图谱或 citation 表。 - 上传解析支持 `.md/.txt/.pdf/.docx/.xlsx/.csv`:PDF 走 PyMuPDF 文本抽取,DOCX 走 python-docx 段落抽取,表格转文本行;没有 OCR、版面恢复、图片解析或复杂文档结构保真。 - `chunker.py` 存在 Markdown/文本/表格切块逻辑,但当前知识库主链没有把 chunk 持久化到 DB,检索与注入仍围绕整份 `knowledge_sources.content`。 - 检索分两层:DB 层用 `LIKE` 关键字筛出候选;服务层再对候选全文做 CJK/英文 token 的 BM25 + TF-IDF 重排;没有 embedding、semantic search、rerank model、hybrid vector search。 - 注入方式是 prompt stuffing:小型个人知识库全量或近似全量注入,大型个人知识库取 2 条结果,企业知识库最多取 1 条结果,每条截取相关片段,总上下文默认限制约 1200 字符。 - opencode 集成点是在发送用户问题前构造 `...`,并提示模型如果上下文不足就调用 `knowledge_knowledge_search` 工具继续查。 - 前端 `KnowledgeManager.jsx` 和 admin 企业知识库 UI 可直接参考:列表、搜索、上传、添加、编辑、删除、统计、企业只读提示这些产品能力与 MNote 需要高度重合。 与 WeKnora 的关系: - WeKnora 应继续作为 MNote 长期知识库底座候选:负责文档解析、索引、检索、召回、引用、权限过滤与跨文档问答。 - OpenHub 知识库不应替代 WeKnora;它更像“用户短记忆/轻量知识片段/企业公告文本”的 fallback。 - 当前融合方式是 **OpenHub AI 面板 + MNote Rust 知识库 adapter + LightRAG provider**:OpenHub 负责 Page AI 对话与工具事件承载,真正的 ingestion/search/citation 由 MNote 通过 provider-neutral facade 调 LightRAG。 - OpenHub 的 `knowledge_sources` schema 可以作为 MNote control-plane 的 source registry 参考,但需要增加 `workspace_id/root_uri/resource_id/source_uri/provider_doc_id/index_status/permission_scope/citation_locator` 等 MNote 字段。 - OpenHub 的 prompt 注入链路可以短期复用为 Page AI context block,但 WeKnora 命中结果必须带 citation/open-reference 映射,不能只塞纯文本。 对 7-67 深度融合设计的影响: 1. Page AI 主 UI 继续选 OpenHub Chat 子系统,而不是官方 opencode iframe。 2. Knowledge 模块第一阶段只迁移 UI 与 API contract,不迁移其 SQLite 文本检索为长期底座。 3. MNote Rust adapter 提供 OpenHub-compatible `/api/knowledge/*`,内部走 WeKnora 或本地 fallback。 4. 保留 OpenHub 轻量知识库作为“未配置 LightRAG 时的 local fallback / 用户手工短知识”,但不能称为默认知识库主线。 5. 新设计稿应明确:`OpenHub UI` 负责交互,`MNote control-plane` 负责用户与权限,`WeKnora` 负责知识库索引与检索,`opencode` 负责 agent 执行。