Files
mnote/design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md
T

556 lines
38 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.
> 状态补充(2026-06-25):本稿冻结为 opencode runtime / 官方 opencode WebUI iframe fallback 参考;Page AI 产品主线由 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 接管。官方 iframe 仅在 OpenHub AI 面板不可用、调试 opencode 原生行为或做回归对照时启用,不再作为默认产品路径。
# 7-65 [process] Page AI opencode WebUI embed v1
> 创建时间:2026-06-23
>
> 当前状态:`FROZEN / 官方 opencode iframe fallbackOpenHub + WeKnora + MNote 深度融合为主线`
>
> Owner07-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** 作为主线;截至 `7-68`,该方案降级为 fallback。
```text
fallback MNote Page AI = MNote 宿主壳 + opencode 官方 WebUI iframe + MNote 打开/刷新/上下文集成
主线 Page AI = MNote 宿主壳 + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + WeKnora
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.*`、旧 LightRAG legacy facade 等工具可继续作为 fallback/compat 边界;默认知识库工具应改向 WeKnora。
- Agent Board 仍可作为外部 workflow/QA/review 系统,不再作为 Page AI 默认聊天后端。
- CodexMobile 可保留为备选 spike 或体验对照,不作为当前实现目标。
## 3. 新系统边界
### 3.1 MNote 只做四件事
1. **上下文**:当前页、选区、页面标题、真实 `.md` 路径、workspaceId、allowed roots、WeKnora 引用;旧 LightRAG 只作为 legacy/fallback 口径保留。
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/<project>/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 同源根路径反代 `/<base64-project>/session/<sessionId>`,让 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 Aruntime 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 BMNote 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 同源 `/<base64-project>/session` 反代;避免局域网浏览器访问自身 `127.0.0.1:4096`,同时保留 opencode 官方 URL 形态。
### Phase B2MNote-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 Ccontext 注入
优先不修改 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 与读写权限。
- WeKnora 引用摘要;旧 LightRAG 引用只作为 legacy/fallback。
- 当前任务约束:优先编辑 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 Dwriteback / 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 islandPage 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 AIiframe 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/<user-id>/opencode/<workspace-hash>/config/opencode/opencode.json
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/data/opencode/opencode.db
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/cache/opencode/
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/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 credentialsissue 描述确认共享 Web 实例会共享身份、session、provider credentials |
| 官方 `anomalyco/opencode` issue `#5784` | closed:请求多租户 `serve` 下 MCP auth/config;说明多租户 MCP/资源隔离是社区真实痛点,但不是已可用的完整 MNote 用户隔离方案 |
| 官方 `SECURITY.md` | 明确 opencode 不提供安全 sandboxserver 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=<user workspace>`
- 应用层 session ownershipOpenHub 自己用 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 iframeMNote 只做 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 1iframe 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 3UI 组件裁剪融合**
- 从 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,用户/文件/权限/工作区真相仍归 MNoteagent 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 对照
用户最新判断:Page AI 要做深度融合;MNote 当前已放弃 LightRAG,知识库方向原计划是 WeKnora。因此需要单独核验 OpenHub 自带知识库是否能替代 WeKnora。
源码核验结论:**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 集成点是在发送用户问题前构造 `<context>...</context>`,并提示模型如果上下文不足就调用 `knowledge_knowledge_search` 工具继续查。
- 前端 `KnowledgeManager.jsx` 和 admin 企业知识库 UI 可直接参考:列表、搜索、上传、添加、编辑、删除、统计、企业只读提示这些产品能力与 MNote 需要高度重合。
与 WeKnora 的关系:
- WeKnora 应继续作为 MNote 长期知识库底座候选:负责文档解析、索引、检索、召回、引用、权限过滤与跨文档问答。
- OpenHub 知识库不应替代 WeKnora;它更像“用户短记忆/轻量知识片段/企业公告文本”的 fallback。
- 最优融合方式是 **OpenHub Knowledge UI + MNote Rust 知识库 adapter + WeKnora provider**:前端交互复用 OpenHub,后端接口形状兼容 OpenHub,但真正的 ingestion/search/citation 由 MNote 调 WeKnora。
- 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 轻量知识库作为“未配置 WeKnora 时的 local fallback / 用户手工短知识”,但不能称为默认知识库主线。
5. 新设计稿应明确:`OpenHub UI` 负责交互,`MNote control-plane` 负责用户与权限,`WeKnora` 负责知识库索引与检索,`opencode` 负责 agent 执行。