From e433c070614192f75b8b9c8c1bf474879f20d1f0 Mon Sep 17 00:00:00 2001 From: Agent Board Date: Fri, 26 Jun 2026 20:01:02 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=20OpenHub=20=E4=B8=8E=20WeKn?= =?UTF-8?q?ora=20Page=20AI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 + ...7-50-lightrag-knowledge-rag-provider-v1.md | 2 + .../7-51-lightrag-post-commit-hardening-v1.md | 2 + ...rag-image-ocr-search-chain-hardening-v1.md | 2 + ...htrag-docx-citation-rerank-alignment-v1.md | 2 + ...tive-docx-sidecar-addressable-blocks-v1.md | 2 + ...rence-ai-run-config-knowledge-facade-v1.md | 2 + .../7-65-opencode-webui-embed-page-ai-v1.md | 24 +- .../7-66-opencode-native-page-ai-ui-v1.md | 17 +- .../process/7-67-openhub-page-ai-fusion-v1.md | 32 +- ...-weknora-mnote-deep-fusion-checklist-v1.md | 278 ++ ...68-openhub-weknora-mnote-deep-fusion-v1.md | 198 +- .../7-68-weknora-kb-page-ui-reference-v1.md | 170 ++ rust/Cargo.lock | 17 + rust/crates/mnote-web/Cargo.toml | 2 +- .../browser/sidebar-page-ai-runtime.js | 366 ++- .../browser/sidebar-page-settings-runtime.js | 843 +++++- .../mnote-web/browser/sidebar-tree-runtime.js | 25 + .../src/hermes_tools/knowledge_rag.rs | 179 +- .../mnote-web/src/hermes_tools/manifest.rs | 124 +- .../mnote-web/src/hermes_tools/skill.rs | 7 +- rust/crates/mnote-web/src/lib.rs | 1 + .../mnote-web/src/provider_identity_sync.rs | 223 ++ rust/crates/mnote-web/src/routes/gateway.rs | 42 +- .../mnote-web/src/routes/hermes_tools.rs | 22 +- .../mnote-web/src/routes/knowledge_rag.rs | 2387 ++++++++++++++++- rust/crates/mnote-web/src/routes/mod.rs | 107 +- .../mnote-web/src/routes/page_ai_opencode.rs | 21 +- .../mnote-web/src/routes/page_ai_openhub.rs | 1224 +++++++++ .../src/ssr/styles/components/main.css | 554 +++- .../src/ssr/styles/components/page-ai.css | 61 + scripts/desktop-hot.js | 141 + scripts/desktop-hot.test.js | 42 + ...nowledge-rag-page-ai-final-answer-smoke.js | 2 +- ...g-search-grouping-and-short-query-smoke.js | 4 +- ...44-weknora-provider-bridge-static-smoke.js | 122 + ...sk768-page-ai-openhub-host-static-smoke.js | 46 + ...nora-knowledge-settings-ui-static-smoke.js | 117 + ...git-snapshot-restore-guard-static-smoke.js | 98 + ...ask771-openhub-mnote-scope-static-smoke.js | 127 + ...eknora-ingest-search-open-reference-e2e.js | 371 +++ .../task773-page-ai-openhub-browser-smoke.js | 553 ++++ ...74-openhub-mnote-send-and-file-edit-e2e.js | 294 ++ ...775-openhub-mnote-scope-isolation-smoke.js | 255 ++ ...k776-openhub-changed-files-bridge-smoke.js | 234 ++ .../task777-weknora-section-context-smoke.js | 152 ++ ...778-openhub-artifact-index-static-smoke.js | 92 + ...b-file-edit-document-pane-refresh-smoke.js | 220 ++ ...80-openhub-artifact-index-runtime-smoke.js | 219 ++ .../task781-knowledge-bases-registry-smoke.js | 256 ++ ...nhub-weknora-tool-citation-bridge-smoke.js | 187 ++ ...783-weknora-kb-settings-ui-static-smoke.js | 111 + ...sk784-weknora-kb-settings-browser-smoke.js | 118 + ...ra-create-kb-folder-ingest-search-smoke.js | 248 ++ ...6-openhub-history-refresh-browser-smoke.js | 555 ++++ ...7-weknora-default-lightrag-legacy-smoke.js | 272 ++ ...88-weknora-kb-folder-browser-flow-smoke.js | 382 +++ ...knora-kb-page-ui-reference-static-smoke.js | 58 + ...weknora-kb-page-experience-static-smoke.js | 88 + ...enhub-native-mnote-context-static-smoke.js | 101 + ...enhub-native-mnote-context-source-smoke.js | 169 ++ ...enhub-mnote-context-fastapi-chain-smoke.js | 225 ++ .../task79x-weknora-kb-page-browser-smoke.js | 212 ++ 63 files changed, 12678 insertions(+), 332 deletions(-) create mode 100644 design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md create mode 100644 design/07-ai/reference/7-68-weknora-kb-page-ui-reference-v1.md create mode 100644 rust/crates/mnote-web/src/provider_identity_sync.rs create mode 100644 rust/crates/mnote-web/src/routes/page_ai_openhub.rs create mode 100644 scripts/task544-weknora-provider-bridge-static-smoke.js create mode 100644 scripts/task768-page-ai-openhub-host-static-smoke.js create mode 100644 scripts/task769-weknora-knowledge-settings-ui-static-smoke.js create mode 100644 scripts/task770-openhub-git-snapshot-restore-guard-static-smoke.js create mode 100644 scripts/task771-openhub-mnote-scope-static-smoke.js create mode 100644 scripts/task772-weknora-ingest-search-open-reference-e2e.js create mode 100644 scripts/task773-page-ai-openhub-browser-smoke.js create mode 100644 scripts/task774-openhub-mnote-send-and-file-edit-e2e.js create mode 100644 scripts/task775-openhub-mnote-scope-isolation-smoke.js create mode 100644 scripts/task776-openhub-changed-files-bridge-smoke.js create mode 100644 scripts/task777-weknora-section-context-smoke.js create mode 100644 scripts/task778-openhub-artifact-index-static-smoke.js create mode 100644 scripts/task779-openhub-file-edit-document-pane-refresh-smoke.js create mode 100644 scripts/task780-openhub-artifact-index-runtime-smoke.js create mode 100644 scripts/task781-knowledge-bases-registry-smoke.js create mode 100644 scripts/task782-openhub-weknora-tool-citation-bridge-smoke.js create mode 100644 scripts/task783-weknora-kb-settings-ui-static-smoke.js create mode 100644 scripts/task784-weknora-kb-settings-browser-smoke.js create mode 100644 scripts/task785-weknora-create-kb-folder-ingest-search-smoke.js create mode 100644 scripts/task786-openhub-history-refresh-browser-smoke.js create mode 100644 scripts/task787-weknora-default-lightrag-legacy-smoke.js create mode 100644 scripts/task788-weknora-kb-folder-browser-flow-smoke.js create mode 100644 scripts/task789-weknora-kb-page-ui-reference-static-smoke.js create mode 100644 scripts/task790-weknora-kb-page-experience-static-smoke.js create mode 100644 scripts/task791-openhub-native-mnote-context-static-smoke.js create mode 100644 scripts/task792-openhub-native-mnote-context-source-smoke.js create mode 100644 scripts/task793-openhub-mnote-context-fastapi-chain-smoke.js create mode 100644 scripts/task79x-weknora-kb-page-browser-smoke.js diff --git a/AGENTS.md b/AGENTS.md index 7a757c92..5eb9861e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,9 @@ - 涉及 local-first AI 普通 Markdown 编辑时,优先沿“授权文件引用 + `AiAccessScope` + allowed roots + agent 原生 patch/diff + 文件版本冲突模型 + watcher 同步”推进;涉及 cloud / remote agent / 复杂结构辅助时,才沿 `mnote.doc.*` / `mnote.block.*` Hermes tools 与 Rust `EditorCommand` 推进。`mnote.page.save` 只作为页面级兜底写入工具。 - 涉及资源归属(哪个页面拥有哪个 mindmap/附件)、object identity 和资源生命周期时,优先沿 Resource Tree → File Tree projection → `tree.resource.*` 命令面推进;`tree.*` 是正式命令面,`documents.*` 只视为兼容层。 - 页面 AI 编辑当前 local-first 主路径为:页面定位到真实 `.md` 文件,MNote 计算 `AiAccessScope` / allowed roots / selection,Hermes 或 Reasonix 在白名单目录内用自身 patch/diff/文件编辑能力写入,MNote 通过 watcher / refresh 同步 tiptap。`mnote.doc.markdown_edit` 只作为 cloud / remote agent / compat fallback;`mnote.block.*` 保留为结构性辅助(拖拽排序等)。两层操作模型已获 CLI Main(Lark Doc)参考实现验证。流式 apply + suggest/review(参考 BlockNote AI)作为 Phase C 设计冻结,当前不实施。 +- Page AI / OpenHub / 知识库这类用户入口首屏必须优先保证真实任务区可用:对话、输入框、知识库列表/详情占主体空间;debug、scope、health、route guard 等诊断信息只能放在折叠诊断区、开发态或日志中,不得默认挤占用户输入和阅读区域。 +- MNote + OpenHub 深度融合的交互能力优先改 OpenHub 源码本体(React/FastAPI/服务边界),MNote 只提供 auth、scope、proxy、文件/引用回跳等宿主胶水;不得用 MNote 侧 DOM 注入、悬浮按钮或拦截脚本伪装成 OpenHub 原生能力。 +- OpenHub/opencode 等 legacy fallback 只能保留为显式 debug/internal 边界;普通用户 UI 不得出现切换到 fallback 的按钮、入口或引导文案。 - 涉及知识库资料索引 / OCR / RAG 时,默认沿 `mnote.knowledge_rag.*` / `/api/knowledge-rag/*` 和 LightRAG source registry 推进。图片源不能直接假设会被 LightRAG `/documents/scan` 识别,当前通过 MNote 生成 Markdown wrapper 后进入 LightRAG;LightRAG 后台完成状态通过 MNote 有界 status bridge/backoff 同步到 registry 与 FileTree 灯号,而不是新增无界轮询。 - 需要架构判断时,优先参考: - `/mnt/Data1T/mnote/ARCHITECTURE.md` diff --git a/design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md b/design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md index 0c0af1f3..dd10d0f1 100644 --- a/design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md +++ b/design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md @@ -4,6 +4,8 @@ > > 当前状态:`DONE` > +> 2026-06-25 历史口径:本文记录 LightRAG provider 当时已完成的历史事实,不再代表当前新主线。当前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 将默认知识库 provider 切到 WeKnora;LightRAG 仅保留为 `lightrag_legacy` / fallback / 迁移对照边界。 +> > Owner:07-ai / knowledge-rag / plugin-ui / external-provider > > 取代:`design/old/07-ai/process/7-49-local-understanding-graphrag-kernel-v1.md` diff --git a/design/07-ai/done/7-51-lightrag-post-commit-hardening-v1.md b/design/07-ai/done/7-51-lightrag-post-commit-hardening-v1.md index 38362b8c..5e0c247d 100644 --- a/design/07-ai/done/7-51-lightrag-post-commit-hardening-v1.md +++ b/design/07-ai/done/7-51-lightrag-post-commit-hardening-v1.md @@ -4,6 +4,8 @@ > > 状态:`done` > +> 2026-06-25 历史口径:本文记录 LightRAG hardening 当时已完成的历史事实,不再代表当前新主线。当前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 将默认知识库 provider 切到 WeKnora;本文只作为 `lightrag_legacy` / fallback / 迁移对照边界。 +> > Owner:07-ai / knowledge-rag / 03-rust-web / plugin-ui > > 来源:`design/10-review/done/20-post-lightrag-runtime-hardening-checklist-v1.md` P0 审计 diff --git a/design/07-ai/done/7-52-lightrag-image-ocr-search-chain-hardening-v1.md b/design/07-ai/done/7-52-lightrag-image-ocr-search-chain-hardening-v1.md index d57106f2..c20213e0 100644 --- a/design/07-ai/done/7-52-lightrag-image-ocr-search-chain-hardening-v1.md +++ b/design/07-ai/done/7-52-lightrag-image-ocr-search-chain-hardening-v1.md @@ -4,6 +4,8 @@ > > 当前状态:`DONE` > +> 2026-06-25 历史口径:本文记录 LightRAG 图片 OCR 与搜索召回链路当时已完成的历史事实,不再代表当前新主线。当前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 将默认知识库 provider 切到 WeKnora;本文只作为 `lightrag_legacy` / fallback / 迁移对照边界。 +> > Owner:07-ai / knowledge-rag / local-search > > 上位依据:`design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md` diff --git a/design/07-ai/done/7-55-lightrag-docx-citation-rerank-alignment-v1.md b/design/07-ai/done/7-55-lightrag-docx-citation-rerank-alignment-v1.md index d12e1903..e2783764 100644 --- a/design/07-ai/done/7-55-lightrag-docx-citation-rerank-alignment-v1.md +++ b/design/07-ai/done/7-55-lightrag-docx-citation-rerank-alignment-v1.md @@ -4,6 +4,8 @@ > > 当前状态:`done` > +> 2026-06-25 历史口径:本文记录 LightRAG DOCX 引用与定位合同当时已完成的历史事实,不再代表当前新主线。当前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 将默认知识库 provider 切到 WeKnora;本文只作为 `lightrag_legacy` / fallback / 迁移对照边界。 +> > Owner:07-ai / knowledge-rag / 03-rust-web / office-preview > > 关联: diff --git a/design/07-ai/done/7-56-lightrag-native-docx-sidecar-addressable-blocks-v1.md b/design/07-ai/done/7-56-lightrag-native-docx-sidecar-addressable-blocks-v1.md index 6399b316..884de717 100644 --- a/design/07-ai/done/7-56-lightrag-native-docx-sidecar-addressable-blocks-v1.md +++ b/design/07-ai/done/7-56-lightrag-native-docx-sidecar-addressable-blocks-v1.md @@ -4,6 +4,8 @@ > > 当前状态:`done` > +> 2026-06-25 历史口径:本文记录 LightRAG native DOCX sidecar 当时已完成的历史事实,不再代表当前新主线。当前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 将默认知识库 provider 切到 WeKnora;本文只作为 `lightrag_legacy` / fallback / 迁移对照边界。 +> > Owner:07-ai / knowledge-rag / LightRAG native parser / office-preview > > 前置完成: diff --git a/design/07-ai/done/7-57-yuxi-reference-ai-run-config-knowledge-facade-v1.md b/design/07-ai/done/7-57-yuxi-reference-ai-run-config-knowledge-facade-v1.md index f8e40de0..524e8308 100644 --- a/design/07-ai/done/7-57-yuxi-reference-ai-run-config-knowledge-facade-v1.md +++ b/design/07-ai/done/7-57-yuxi-reference-ai-run-config-knowledge-facade-v1.md @@ -4,6 +4,8 @@ > > 当前状态:`DONE` > +> 2026-06-25 历史口径:本文中 LightRAG 相关能力记录的是当时已完成的历史事实,不再代表当前新主线。当前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 将默认知识库 provider 切到 WeKnora;本文只作为 run/config/facade 参考与 `lightrag_legacy` 迁移对照边界。 +> > Owner:07-ai / Page AI runtime / Hermes client runs / Reasonix ACP / Knowledge RAG facade > > 参考代码: diff --git a/design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md b/design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md index f2bc1b2a..ff8646f6 100644 --- a/design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md +++ b/design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md @@ -1,10 +1,10 @@ -> 状态补充(2026-06-25):本稿降级为 opencode runtime / iframe fallback 参考;Page AI 产品主线由 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 接管。 +> 状态补充(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 > -> 当前状态:`PROCESS / opencode-chat style iframe MVP 已落地,bridge 与 event receipt 已补第一版` +> 当前状态:`FROZEN / 官方 opencode iframe fallback,OpenHub + WeKnora + MNote 深度融合为主线` > > Owner:07-ai / Page AI / opencode WebUI embed > @@ -21,14 +21,15 @@ ## 1. 核心结论 -废弃 MNote Page AI 自研 provider 接入与 Board-first 产品壳,改为 **只接入 opencode 官方 runtime + 官方 WebUI**。 +废弃 MNote Page AI 自研 provider 接入与 Board-first 产品壳后,本稿曾把 **opencode 官方 runtime + 官方 WebUI** 作为主线;截至 `7-68`,该方案降级为 fallback。 ```text -MNote Page AI = MNote 宿主壳 + opencode 官方 WebUI + MNote 打开/刷新/上下文集成 -opencode = 官方 WebUI + 官方 HTTP server/OpenAPI/SSE + 官方 SDK + opencodego provider +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 主路径。 +MNote 不再直接维护 Reasonix / ZCode / Hermes / Chat-only / Board worker 作为页面 AI provider。它们可以继续存在于历史、调试或外部工作流边界,但不进入 Page AI 主路径。官方 opencode WebUI iframe 也不再进入默认产品主路径,只作为 fallback。 选择 opencode 的原因: - `opencode web` 官方提供本地 WebUI,不需要 MNote 自研完整聊天前端。 @@ -52,7 +53,7 @@ MNote 不再直接维护 Reasonix / ZCode / Hermes / Chat-only / Board worker ### 2.2 仍可保留的边界 -- `mnote.doc.*`、`mnote.block.*`、LightRAG facade 等工具可继续作为 opencode 可调用的 MCP/工具能力。 +- `mnote.doc.*`、`mnote.block.*`、旧 LightRAG legacy facade 等工具可继续作为 fallback/compat 边界;默认知识库工具应改向 WeKnora。 - Agent Board 仍可作为外部 workflow/QA/review 系统,不再作为 Page AI 默认聊天后端。 - CodexMobile 可保留为备选 spike 或体验对照,不作为当前实现目标。 @@ -60,7 +61,7 @@ MNote 不再直接维护 Reasonix / ZCode / Hermes / Chat-only / Board worker ### 3.1 MNote 只做四件事 -1. **上下文**:当前页、选区、页面标题、真实 `.md` 路径、workspaceId、allowed roots、LightRAG 引用。 +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。 @@ -203,7 +204,7 @@ MNote open Page AI - 当前页真实 Markdown 路径。 - selection 文本。 - allowed roots 与读写权限。 -- LightRAG 引用摘要。 +- 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 边界。 @@ -483,8 +484,8 @@ $MNOTE_DATA/users//opencode//state/opencode/ 不建议直接搬入的部分: - OpenHub 自带登录、用户管理、admin 页,与 MNote control-plane auth 重叠;应替换成 MNote 登录态。 -- OpenHub FastAPI 后端与 MNote Rust SSR/control-plane 双后端并存会增加部署复杂度;长期应把必要 API/DB schema 移植到 Rust,而不是新增常驻 Python 服务。 -- OpenHub 自研知识库/记忆/任务会与 MNote 现有 LightRAG、workspace、tree/file resource、control-plane 产生事实源冲突;应按模块逐步映射。 +- 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 限制。 推荐融合路线: @@ -552,4 +553,3 @@ OpenHub 知识库真实实现: 3. MNote Rust adapter 提供 OpenHub-compatible `/api/knowledge/*`,内部走 WeKnora 或本地 fallback。 4. 保留 OpenHub 轻量知识库作为“未配置 WeKnora 时的 local fallback / 用户手工短知识”,但不能称为默认知识库主线。 5. 新设计稿应明确:`OpenHub UI` 负责交互,`MNote control-plane` 负责用户与权限,`WeKnora` 负责知识库索引与检索,`opencode` 负责 agent 执行。 - diff --git a/design/07-ai/process/7-66-opencode-native-page-ai-ui-v1.md b/design/07-ai/process/7-66-opencode-native-page-ai-ui-v1.md index 97ad3a92..f6887bb9 100644 --- a/design/07-ai/process/7-66-opencode-native-page-ai-ui-v1.md +++ b/design/07-ai/process/7-66-opencode-native-page-ai-ui-v1.md @@ -1,20 +1,23 @@ # 7-66 [process] Page AI opencode-native UI v1 +> 状态补充(2026-06-25):本稿冻结为 native UI fallback / debug receipt 参考;Page AI 产品主线由 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 接管。不得继续扩写 MNote 自研聊天框作为默认 Page AI UI。 + > 创建时间:2026-06-24 > -> 当前状态:`RECYCLED / 失败方向,仅保留为反例` +> 当前状态:`FROZEN / native UI fallback,仅保留为 debug receipt 与失败方向参考` > > Owner:07-ai / Page AI / opencode-native UI ## 1. 结论 -本稿提出的 MNote-native opencode UI 方向已废弃。用户确认目标应参考 `opencode-chat`:**MNote 薄宿主壳 + 官方 opencode WebUI iframe + 最小 host bridge**,而不是在 MNote 内复刻 opencode 消息流、tool UI、permission UI。 +本稿提出的 MNote-native opencode UI 方向已冻结为 fallback。当前主线不是在 MNote 内复刻 opencode 消息流、tool UI、permission UI,也不是回到官方 opencode WebUI iframe 默认路径,而是 `7-68` 定义的 **MNote 薄宿主壳 + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + WeKnora**。 -因此 `7-66` 只作为反例保留:native message timeline / composer / permission cards 可以临时作为 debug receipt,但不得成为 Page AI 主 UI,也不继续扩写。当前主线回到 `7-65`: +因此 `7-66` 只作为反例和 fallback 保留:native message timeline / composer / permission cards 可以临时作为 debug receipt、故障诊断或 OpenHub 不可用时的降级参考,但不得成为 Page AI 主 UI,也不继续扩写自研聊天框。 ```text -MNote Page AI = MNote host chrome + opencode 官方 WebUI iframe + MNote open/refresh/changed files receipt -opencode = 官方聊天 UI、session、tool、permission、diff、model/provider +主线 Page AI = MNote host chrome + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + WeKnora +native fallback = MNote debug receipt + opencode session/event/diff 摘要 +iframe fallback = 7-65 官方 opencode WebUI iframe ``` ## 2. 参考结论 @@ -35,7 +38,7 @@ opencode = 官方聊天 UI、session、tool、permission、diff、model/pro - sidebar chat + diff viewer + multi-session tabs;自动启动 `opencode serve`,通过 HTTP/SSE 通信。 - React webview 渲染 session、message parts、tool cards、permission、diff、provider/model。 -对 MNote 的启发:不要再塞 iframe;应该使用 opencode API/SSE 重渲染 MNote-native UI,并保留 MNote 打开文件/刷新编辑器。 +对 MNote 的历史启发:直接重渲染 opencode API/SSE 会把 MNote 拉回自研聊天框路线。`7-68` 已覆盖该判断:优先嵌入 OpenHub AI 面板并保留 OpenHub 后端栈,native UI 只做 fallback / debug receipt。 ## 3. 必须覆盖的 opencode 功能 @@ -75,7 +78,7 @@ opencode = 官方聊天 UI、session、tool、permission、diff、model/pro - changed file click:`window.__mnoteDocumentPaneRuntime.openResourceInActiveTab()`。 - 当前页被修改:`refreshPrimaryDocument({ reason: 'page-ai-opencode-event' })`。 - selection/current page/allowed roots 由现有 Page AI target runtime 计算。 -- iframe 反代只保留 `debug fallback`,默认 UI 不使用 iframe。 +- native UI 只保留 `debug fallback`;默认 UI 使用 `7-68` 的 OpenHub AI 面板嵌入路线。 ## 5. 第一版验收 diff --git a/design/07-ai/process/7-67-openhub-page-ai-fusion-v1.md b/design/07-ai/process/7-67-openhub-page-ai-fusion-v1.md index ddd620ac..9ebba9f8 100644 --- a/design/07-ai/process/7-67-openhub-page-ai-fusion-v1.md +++ b/design/07-ai/process/7-67-openhub-page-ai-fusion-v1.md @@ -1,4 +1,4 @@ -> 状态补充(2026-06-25):本稿为 OpenHub 初步融合稿;更完整的 OpenHub + WeKnora + MNote 取舍与实施主线由 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 接管。 +> 状态补充(2026-06-25):本稿为 OpenHub 初步融合稿,已被 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 覆盖。路线从“抽取/重写 OpenHub 组件 + MNote Rust adapter”改为“嵌入 OpenHub AI 面板 + 保留 OpenHub FastAPI/Redis/opencode client + WeKnora 工具/知识底座”。 # 7-67 OpenHub Page AI 深度融合设计 v1 @@ -10,23 +10,23 @@ Owner:07-ai / mnote-web / control-plane `7-65` 的 opencode 官方 WebUI iframe 路线验证了 opencode runtime、同源反代、session binding、文件打开/刷新等接缝,但 UI 深度融合受 iframe 与官方 WebUI 结构限制。用户目标已经调整为:尽量复用成熟社区项目 OpenHub 的前端、消息库、权限、知识库 UI 和 opencode 接入模式,把 MNote Page AI 做成类似 VSCode + Cline 的一体化侧边栏,而不是 MNote 自研一个简陋聊天框。 -当前 Page AI 新主路径: +当前 Page AI 主路径以 `7-68` 为准: ```text MNote Rust SSR / control-plane / local workspace - -> OpenHub Chat 子系统 UI(裁剪融合) - -> MNote Rust adapter(兼容 OpenHub API 形状) - -> opencode serve runtime - -> WeKnora knowledge provider(长期知识库底座) + -> OpenHub AI 面板(只暴露 AI 能力,裁剪非 AI 入口) + -> OpenHub FastAPI + Redis + OpenHub SQLite session/skill/tool permission;MCP 由 MNote/WeKnora/opencode 工具配置承接 + -> OpenHub opencode client / opencode serve runtime + -> WeKnora knowledge provider(唯一知识库底座) ``` -`7-65` 降级为 opencode runtime spike / fallback;不再把官方 opencode WebUI iframe 作为默认产品 UI。 +`7-65` 降级为 opencode runtime / 官方 iframe fallback;`7-66` 降级为 native UI fallback;本稿的 Rust adapter / 自建 message store 路线也冻结为参考,不再作为执行主线。 ## 2. 硬边界 - 不恢复 Reasonix / ZCode / Hermes / Board / CodexMobile 为 Page AI 默认后端。 - 不新增 Page AI Leptos island;Page AI 仍属于 `mnote-web` host runtime 与普通前端资源。 -- 不直接引入 OpenHub 登录、用户管理、admin 整站后端;用户、权限、workspace 真相归 MNote control-plane。 +- 不直接引入 OpenHub 登录、admin、FileManager、KnowledgeManager 等非 AI 入口;用户、权限、workspace 真相归 MNote control-plane。 - 不把 OpenHub 自带 SQLite 文本知识库当作 MNote 长期知识库底座;长期知识库 provider 是 WeKnora。 - 不使用 `--dangerously-skip-permissions` 作为默认路径。 - 不把 opencode 原生 session 列表裸露给前端;所有 session 必须绑定 MNote 用户、rootUri、page path。 @@ -53,7 +53,7 @@ MNote Rust SSR / control-plane / local workspace - SmartEntity / Team / Scheduler。 - GitTimeMachine 的 restore 写入能力;可先只显示 diff / changed files。 -### 3.2 后端模式 +### 3.2 后端模式(已由 7-68 覆盖) 可复用其 API contract 和 opencode 调用模式: @@ -64,28 +64,28 @@ MNote Rust SSR / control-plane / local workspace - `/api/files/*` - opencode `/session?directory=`、`/session/{id}/prompt_async?directory=`、`/global/event?directory=` -但实现落在 Rust `mnote-web` / control-plane,不新增常驻 FastAPI 后端。 +本稿原判断是“实现落在 Rust `mnote-web` / control-plane,不新增常驻 FastAPI 后端”。`7-68` 已更正:第一阶段保留 OpenHub FastAPI + Redis + opencode client 作为 AI 运行栈,MNote 只做登录态、scope 注入、入口裁剪、文件打开和 citation 回跳桥。 ## 4. MNote 目标架构 ### 4.1 UI 层 -`sidebar-page-ai-runtime.js` 不再维护自研聊天消息渲染主链,而是挂载 OpenHub-derived Page AI micro frontend: +`sidebar-page-ai-runtime.js` 不再维护自研聊天消息渲染主链,而是承载 `7-68` 的 OpenHub AI 面板嵌入: - MNote-native host chrome:当前页、selection、workspace、授权状态、runtime 状态、changed file chips。 -- OpenHub-derived chat area:消息、tool call、diff、history、input、附件。 +- OpenHub AI 面板:消息、tool call、diff、history、input、skill/permission、WeKnora CLI/MCP tool 状态、运行日志。 - Bridge:只处理 MNote 专属动作:`open-file`、`refresh-file`、`insert-context`、`session-ready`、`changed-files`。 -### 4.2 Rust adapter 层 +### 4.2 Rust adapter 层(冻结为旧设想) -新增或重构 Page AI API: +以下自建 session/message 表路线已被 `7-68` 覆盖,不作为当前执行主线: - `page_ai_sessions`:MNote 用户维度的跨浏览器 session binding。 - `page_ai_messages`:用户消息、assistant 消息、tool call、opencode ids、状态。 - `page_ai_context_snapshots`:当前页标题、真实 Markdown 路径、selection、rootUri、allowed roots、知识摘要。 - `page_ai_changed_files`:opencode event/diff 得到的 changed files 与 MNote resource mapping。 -所有 API 必须读取 MNote 登录态,禁止由前端自由传入 user id 或任意 directory。 +当前主线是:OpenHub session/message 是 Page AI 会话真相;MNote control-plane 只保存 binding、scope、artifact/open-reference 索引,禁止复制消息全文主存储。 ### 4.3 opencode runtime 层 @@ -167,4 +167,4 @@ MVP 完成条件: ## 7. 当前结论 -OpenHub 是目前最适合 MNote Page AI 深度融合的参考实现。它不解决 opencode 内核级多用户隔离,也不提供完整知识库底座,但它提供了 MNote 当前最缺的成熟 Chat/UI/message/session/diff/file/knowledge 管理壳。正确路线不是整站照搬 OpenHub,而是把 OpenHub Page AI 子系统移植为 MNote 原生 Page AI UI,后端由 MNote Rust adapter 接 opencode 与 WeKnora。 +OpenHub 仍是目前最适合 MNote Page AI 深度融合的参考实现,但本文已被 `7-68` 覆盖。正确路线不是把 OpenHub Page AI 子系统重写成 MNote 原生 UI,也不是由 MNote Rust adapter 重写 opencode client,而是在 MNote 侧嵌入 OpenHub AI 面板,保留 OpenHub FastAPI/Redis/opencode client,由 MNote 提供登录态、workspace scope、文件打开、citation 回跳和 WeKnora 工具/知识底座边界。 diff --git a/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md b/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md new file mode 100644 index 00000000..f4055c06 --- /dev/null +++ b/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md @@ -0,0 +1,278 @@ +# 7-68 OpenHub + WeKnora + MNote 深度融合执行 Checklist v1 + +状态:process +Owner:07-ai / mnote-web / control-plane / knowledge-provider +日期:2026-06-25 + +上位设计:`design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-v1.md` + +## 0. 执行原则 + +- 保留各系统已完成能力:OpenHub 保留 AI 面板、FastAPI、SQLite session/message、Redis 临时态、skill/tool permission、opencode client;WeKnora 保留 KB/API/CLI/MCP/search/index;MNote 保留 auth/workspace/resource tree/document pane/source registry。 +- 只在产品真相冲突处做胶水:登录入口、workspace/rootUri scope、知识库 source truth、文件打开、citation 回跳、OpenHub 非 AI 入口裁剪、Git snapshot 禁用。 +- 不把未确认能力写成已完成能力:当前 OpenHub 源码未确认独立 MCP 管理 UI/API,第一阶段通过 MNote 注册 WeKnora CLI/MCP/API tool 到 OpenHub/opencode。 +- 不把 Redis 当持久真相:OpenHub message 真相在 SQLite,MNote auth/source/file truth 在 control-plane/本地文件,WeKnora Redis 属于其内部队列/缓存。 + +## 1. P0 设计冻结与旧路径降级 + +- [x] 在 `design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md` 顶部加覆盖说明:官方 opencode WebUI iframe 仅保留 fallback,不是 Page AI 主线。 +- [x] 在 `design/07-ai/process/7-66-opencode-native-page-ai-ui-v1.md` 顶部加覆盖说明:自研 native UI 仅保留 fallback,不继续扩自研聊天框。 +- [x] 在 `design/07-ai/process/7-67-openhub-page-ai-fusion-v1.md` 顶部加覆盖说明:已被 7-68 覆盖,路线改为嵌入 OpenHub AI 面板并保留 FastAPI/opencode client。 +- [x] 搜索 `design/07-ai/`、`scripts/`、`rust/crates/mnote-web/browser/` 中仍把 LightRAG 描述为默认知识库 provider 的文案,列出需改文件,不在本任务中顺手改代码。 +- [x] 标记 `7-50` 到 `7-57` 中 LightRAG 相关 done 文档为历史事实:曾经完成,不再代表当前新主线。 + +P0 搜索记录(2026-06-25): + +- 已在 `design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md`、`7-51`、`7-52`、`7-55`、`7-56`、`7-57` 顶部标记历史口径:LightRAG 曾完成,但不再代表当前新主线;WeKnora 是 7-68 默认 provider。 +- 仍需后续单独迁移或归档的 active/process 文档:`design/07-ai/process/7-47-mnote-public-capability-plugin-registry-v1.md`、`design/07-ai/process/7-54-raganything-multimodal-retrieval-alignment-v1.md`、`design/07-ai/process/7-61-yuxi-reference-middleware-extensions-dashboard-v1.md`。这些文档仍含 LightRAG 默认主线语义,应按 7-68 后续批次改为 WeKnora / provider-neutral 或移入 `design/old/`。 +- 仍需后续代码迁移的 LightRAG 残留:`rust/crates/mnote-web/browser/document-resource-tab-runtime.js`、`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`、`scripts/reasonix-acp-wrapper.mjs`、`scripts/task538-knowledge-rag-source-scope-api-smoke.js`、`scripts/task542-knowledge-rag-search-grouping-and-short-query-smoke.js`、`scripts/TESTING_REFERENCE.md`、`scripts/task530-knowledge-rag-page-ai-final-answer-smoke.js`。本轮先完成 7-68 主链静态边界与 WeKnora facade,不批量重写所有 legacy smoke。 + +## 2. P0 OpenHub 源码事实冻结 + +- [x] 固定 OpenHub 源码路径:`/tmp/mnote-openhub-research/OpenHub`;若后续要深改,先建立可复核源码快照或 CodeGraph 索引。 +- [x] 记录 FastAPI 入口:`smart-query-backend/app/main.py`,router 包括 `auth/query/session/admin/files/internal/knowledge/admin_knowledge/channels`。 +- [x] 记录 opencode client:`app/services/opencode_client.py` 使用 `OPENCODE_BASE_URL`,并支持 `directory` 参数。 +- [x] 记录 opencode launcher:`app/services/opencode_launcher.py` 可启动 `opencode serve`;集成时 `workdir` 必须来自 MNote rootUri,不用 OpenHub 默认路径。 +- [x] 记录 session/message 真相:`init_db.py` 的 `conversation_sessions/conversation_messages` 与 `app/database.py`。 +- [x] 记录 Redis 用途:`app/core/auth.py`、`app/database.py` 中的 token/rate-limit/workspace cache/图片临时态;不得写成消息队列主链。 +- [x] 记录自动 Git snapshot 触发点:`app/services/stream.py`、`app/services/task_executor.py`、`app/api/session.py` restore API。 +- [x] 记录冲突入口:`App.jsx` 的 `/login`、`/admin`;`FileManager.jsx`;`KnowledgeManager.jsx`;`GitTimeMachine.jsx`。 + +P0 OpenHub 记录(2026-06-25): + +- 源码路径已固定为 `/tmp/mnote-openhub-research/OpenHub`。 +- FastAPI 入口 `/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/main.py` include routers:`auth/query/session/admin/files/internal/smart_entity/smart_entity_tasks/knowledge/admin_knowledge/channels`。 +- `app/config.py` 中 `OPENCODE_BASE_URL` 默认 `http://127.0.0.1:4096`;`app/services/opencode_client.py` 的 `get/post/put/patch` 支持把 `directory` 写入 query params。 +- `app/services/opencode_launcher.py` 可启动 `opencode serve`;`main.py` 中 `opencode_auto_start` 当前默认 workdir 仍有 OpenHub 自带 fallback,MNote 集成时必须由 rootUri 派生或禁用 auto-start。 +- Git snapshot 写链真实存在:`app/services/stream.py`、`app/services/task_executor.py` 会 init git / has_changes / create_snapshot;`app/api/session.py` 暴露 snapshot list/detail/diff/restore/restore-file 路由。 +- 冲突入口真实存在:前端 `src/App.jsx` 有 `/login` 与 `/admin`;OpenHub 还包含 FileManager / KnowledgeManager / GitTimeMachine 相关入口,MNote host 第一阶段必须 guard 或转接。 + +## 3. P0 WeKnora 源码事实冻结 + +- [x] 固定 WeKnora 源码路径:`/mnt/Data1T/Mnote_data/weknora/WeKnora`。 +- [x] 记录 KB UI 入口:`frontend/src/views/knowledge/KnowledgeBaseList.vue`、`KnowledgeBase.vue`、`KnowledgeBaseEditorModal.vue`、`frontend/src/components/knowledge-processing-timeline.vue`。 +- [x] 记录多 KB 检索:`docs/api/knowledge-search.md` 与 router `/api/v1/knowledge-search`,请求必须带 `knowledge_base_id(s)` 或 `knowledge_ids`。 +- [x] 记录单 KB hybrid-search:`client/knowledgebase.go` 的 `/api/v1/knowledge-bases/:id/hybrid-search`。 +- [x] 记录 `SearchResult` 全字段:`id/content/knowledge_id/knowledge_base_id/chunk_index/knowledge_title/start_at/end_at/seq/score/match_type/sub_chunk_id/metadata/chunk_type/parent_chunk_id/image_info/knowledge_filename/knowledge_source/knowledge_channel/chunk_metadata/matched_content/knowledge_description`。 +- [x] 记录 CLI MCP:`cli/internal/mcp/tools.go`、`cli/cmd/mcp/serve.go`;第一阶段默认采用只读 curated surface。 +- [x] 记录 Python `mcp-server/` 作为后续写能力参考,第一阶段不默认暴露给 OpenHub/opencode。 +- [x] 记录 WeKnora Redis/Asynq/Langfuse 属于 WeKnora stack,不和 OpenHub Redis keyspace 混用。 + +P0 WeKnora 记录(2026-06-25): + +- 源码路径已固定为 `/mnt/Data1T/Mnote_data/weknora/WeKnora`。 +- KB UI 入口已定位:`KnowledgeBaseList.vue`、`KnowledgeBase.vue`、`KnowledgeBaseEditorModal.vue`、`knowledge-processing-timeline.vue`。 +- 多 KB 搜索合同来自 `docs/api/knowledge-search.md`:`POST /api/v1/knowledge-search`,请求至少包含 `knowledge_base_id` 或 `knowledge_base_ids`;可用 `knowledge_ids` 限定文件。 +- `client/knowledgebase.go` 记录 `HybridSearch` 路径 `/api/v1/knowledge-bases/:id/hybrid-search`;`SearchResult` 包含 `id/content/knowledge_id/chunk_index/knowledge_title/start_at/end_at/seq/score/match_type/chunk_type/image_info/metadata/knowledge_filename/knowledge_source/knowledge_channel/matched_content`,服务端还有 parent/sub/chunk metadata 等扩展字段,本轮 MNote mapper 以原始 chunk 保留这些字段。 +- CLI MCP 已定位:`cli/internal/mcp/tools.go` 注册 curated tools;`cli/cmd/mcp/serve.go` 构建 `weknora mcp serve`,当前 stdio-only。第一阶段 MNote 暴露自己的只读 facade,不默认开放 Python `mcp-server` 写能力。 + +## 4. P1 OpenHub 服务栈接入 + +- [x] 为 MNote dev-hot 增加 OpenHub 开关:`ENABLE_OPENHUB=1`、`OPENHUB_BACKEND_CMD`、`OPENHUB_BACKEND_PORT`、`OPENHUB_REDIS_URL/DB`、`OPENHUB_OPENCODE_BASE_URL`。 +- [~] 启动顺序明确为 Redis -> opencode serve -> OpenHub FastAPI -> MNote host health proxy。 +- [x] health 输出区分 `openhub_fastapi`、`openhub_redis`、`opencode_serve`、`weknora_api`、`weknora_cli_mcp`、`mnote_binding`。 +- [x] 禁止 kill-port/destructive launcher 行为;端口被占用时只报告占用 PID 和进程名。 +- [~] OpenHub `opencode_auto_start` 若启用,`opencode_workdir` 必须由 MNote 当前 rootUri 派生;否则 MNote 自己启动 opencode serve 并让 OpenHub 复用。 +- [x] 加环境开关或最小 fork patch 禁用 OpenHub 自动 `git init/commit/restore`。 +- [~] smoke:OpenHub FastAPI 可 `/openapi.json` 或 health 探针;Redis 可认证连接;opencode serve 可访问;WeKnora CLI profile/doctor 可通过;WeKnora stdio MCP handshake 仍未作为最终通过项。 + +P1 OpenHub 服务栈记录: + +- `scripts/desktop-hot.js` 已增加 `ENABLE_OPENHUB`、`OPENHUB_CMD`、`OPENHUB_PORT`、`OPENHUB_BACKEND_DIR`、`OPENHUB_REDIS_HEALTH_URL`、`MNOTE_OPENHUB_BASE_URL`、`MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE`,并补 `scripts/desktop-hot.test.js`。 +- 当前实现的变量名是 `OPENHUB_CMD` / `OPENHUB_PORT`,不是 checklist 早期草案里的 `OPENHUB_BACKEND_CMD` / `OPENHUB_BACKEND_PORT`;后续若需要兼容别名可再补。 +- `desktop-hot` 不释放或 kill OpenHub 端口;OpenHub Redis 目前只有 health URL 检查,不负责启动 Redis。 +- `MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE=1` 已作为 MNote 启动环境默认值和 smoke 断言;OpenHub 研究源码 `/tmp/mnote-openhub-research/OpenHub/smart-query-backend` 已最小 patch:`git_snapshot.py` 中央拦截 `init/config/add/commit/checkout/restore/reset/revert`,`stream.py`、`task_executor.py` 跳过自动 snapshot,`session.py` 的 restore / restore-file 在禁用时返回 403。`scripts/task770-openhub-git-snapshot-restore-guard-static-smoke.js` 和 Python guard 探针已通过。 +- 当前真实探针:OpenHub FastAPI `127.0.0.1:18080`、opencode `127.0.0.1:4096`、MNote `3021` 均可达;WeKnora 在 MNote 进程携带 `MNOTE_WEKNORA_API_KEY` 后 `/api/page-ai/openhub/status` 返回 ready。Redis 目前只作为 OpenHub 内部可选依赖/降级链路,MNote health 仅支持外部 URL 探针,不负责启动 Redis;已用 WeKnora `.env` 的 `REDIS_PASSWORD` 真实 `AUTH + PING` 返回 `+OK/+PONG`。 +- 2026-06-26 WeKnora CLI 真实 profile smoke:临时 `XDG_CONFIG_HOME` 下执行 `weknora profile add mnote-local --host http://127.0.0.1:8080 --use`,再把 MNote 进程中的 `MNOTE_WEKNORA_API_KEY` pipe 给 `weknora auth login --with-token`,`weknora doctor --no-cache --format json` 返回 4/4 checks passed(base URL、auth、server version、credential storage)。`weknora mcp serve --help` 通过;stdio MCP handshake 探针未稳定完成,保留为 dev-hot/orchestration 后续项。 + +## 5. P1 登录与 OpenHub Scope + +- [x] 新增/扩展 MNote OpenHub bootstrap endpoint:输入当前 `mnote_session + workspace/rootUri + page_resource_id`,输出 OpenHub runtime scope。 +- [x] 定义 `openhub_user_key = stable_hash(mnote_user_id)`。 +- [x] 定义 `openhub_workspace_key = stable_hash(workspace_id, root_uri)`。 +- [x] 定义 `openhub_session_scope = hash(mnote_user_id, workspace_id, root_uri, page_resource_id)`。 +- [x] 定义 `weknora_tool_scope = allowed_kb_ids + allowed_source_ids + citation_policy`。 +- [x] 禁用 OpenHub 前端 `/login` 跳转和 localStorage token 作为 MNote 产品入口;由 MNote proxy/header/bootstrap 注入受控身份。 +- [x] 保留 OpenHub 后端 user/session owner 校验;映射到 MNote 派生 OpenHub user,不共享单一 OpenHub 用户。 +- [~] rootUri/workspace 切换时新建或切换 OpenHub session;旧 binding 标记 `stale/archived`。 +- [x] 测试:两个 MNote 用户同一页面不能读到同一个 OpenHub session/message/skill/tool permission scope。 + +P1 scope 记录: + +- 新增 `rust/crates/mnote-web/src/routes/page_ai_openhub.rs`,`/api/page-ai/openhub/bootstrap` 输出 camelCase 与 snake_case scope:`openhubUserKey/openhub_user_key`、`openhubWorkspaceKey/workspace_key`、`openhubSessionScope/session_scope`、`skillScope/skill_scope`、`toolPermissionScope/tool_permission_scope`、`weknoraToolScope/weknora_tool_scope`。 +- MNote 侧已拒绝 OpenHub JWT/localStorage 作为产品入口;`/page-ai/openhub/login` 等非 AI 路由由 MNote guard。 +- OpenHub 研究源码已消费 `X-MNote-*` headers 派生后端 user/scope,并保留 session owner guard。 +- 2026-06-26 `node --check scripts/task775-openhub-mnote-scope-isolation-smoke.js` 与 `node scripts/task775-openhub-mnote-scope-isolation-smoke.js`:通过。直连 OpenHub MNote header scope 创建 session `task775-openhub-scope-1782410072554-157c8c`,Owner A 可读取自己写入的 user message,Owner B 读取同一 session messages 返回 403 `无权访问该会话`;stream 阶段模型不可用不影响 owner guard 验收,因为消息持久化和隔离均已验证。 + +## 6. P1 Page AI 嵌入 OpenHub AI 面板 + +- [x] 在 `rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 设计嵌入方式:iframe 或 reverse proxy 二选一。 +- [x] 只暴露 OpenHub AI 页面;`/login`、`/admin`、FileManager、KnowledgeManager、GitTimeMachine、Team/Scheduler/SmartEntity 非 MVP 入口隐藏、404 或转 MNote。 +- [~] 保留 AI 主界面、history/session 抽屉、skill/agent 设置、tool/model permission、tool card、diff、changed files、运行日志。 +- [x] 如果 OpenHub 无 MCP 管理 UI,只显示 WeKnora MCP/CLI tool 连接状态和 MNote scope,不新建完整 MCP 管理器。 +- [x] MNote context 注入字段:user display、workspace/rootUri、page path/title/resource id、selection 摘要、allowed roots、session/skill/tool/weknora scope。 +- [x] changed file chip 点击走 MNote document pane bridge。 +- [x] OpenHub KnowledgeManager 入口若出现,跳转 MNote WeKnora 知识库页;不得打开 OpenHub knowledge tables。 +- [x] browser smoke:登录 MNote 后打开 Page AI,不出现 OpenHub/WeKnora 登录页。 +- [x] browser/API smoke:OpenHub session/message 已持久化并可读;刷新后 UI history 恢复已完成真实浏览器验收。 + +P1 Page AI host 记录: + +- `sidebar-page-ai-runtime.js` 默认打开 OpenHub host drawer,iframe 指向 `/page-ai/openhub/ai?scope=...`;`localStorage mnote.page_ai.openhub_host=0` 可回退 `/page-ai/opencode`。 +- `page_ai_openhub.rs` 与 `routes/mod.rs` 已挂 `/page-ai/openhub/ai` 静态 shell 和 `/page-ai/openhub/ai/{*path}` proxy;`login/admin/file/files/knowledge/git` 路由已 guard。 +- OpenHub React 前端已做 MNote embed 最小补丁:`mnoteEmbed.js` 识别 `/page-ai/openhub/ai` 嵌入态,`BrowserRouter` 使用 `/page-ai/openhub/ai` basename,API 默认走 `/page-ai/openhub/ai/api`,请求自动携带 `scope/mnoteScope`,无 token 不再发送 `Bearer null`,401 不跳 OpenHub `/login`,`/login/admin/file/files/knowledge/git` 前缀导航回 AI 根页;嵌入态隐藏 OpenHub FileManager、KnowledgeManager、GitTimeMachine、SmartEntity/Team 入口和对应 drawer。 +- MNote `ai_shell` 已从 OpenHub FastAPI 根 `/` 拉取 SPA index.html 并重写 `/assets/...` 到 `/page-ai/openhub/ai/assets/...`;OpenHub React dist 已复制到 `smart-query-backend/static/`。 +- 2026-06-26 浏览器 smoke `node scripts/task773-page-ai-openhub-browser-smoke.js`:通过。MNote 3000 可达,测试账号真实登录成功(`authMode=sqliteSession`),Page AI drawer、OpenHub host chrome 与 `/page-ai/openhub/ai?scope=...&mnoteScope=...` iframe 可见,未出现 OpenHub/WeKnora 登录页;iframe 内 OpenHub React UI 可见,文本 marker 包含 `OpenHub 平台/开始对话/历史记录/技能管理`,`openhubReactConnected=true`、`staticBoundaryOnly=false`,冲突入口 `文件管理/知识库/时光机/智能体/协作任务/团队状态` 不再出现在嵌入态正文。截图:`tmp/7-68-runtime/page-ai-openhub-browser.png`;结果 JSON:`tmp/7-68-runtime/page-ai-openhub-browser-result.json`。 +- 2026-06-26 send/history smoke `node scripts/task774-openhub-mnote-send-and-file-edit-e2e.js`:通过。MNote SQLite 测试账号登录后,经 MNote proxy 调 OpenHub `/api/models` 返回 66 个真实 opencode 模型,选用 `opencodego/deepseek-v4-flash` 发送真实 stream,OpenHub SQLite 可读取同一 session 的 user/assistant message。结果 JSON:`tmp/7-68-runtime/openhub-send-smoke-result.json`。 +- 2026-06-26 changed files bridge smoke `node scripts/task776-openhub-changed-files-bridge-smoke.js`:通过。OpenHub MNote host mode `/api/sessions/{session_id}/diff` 从 opencode tool event metadata 提取真实 changed file path,并返回 `diffAvailable=false` 与限制说明,不伪造 diff content;结果命中 `knowledge-rag-fixtures-7-68/task774-openhub-file-edit-1782409309451.md`。OpenHub 前端 `DiffViewer` / `SmartQueryPage` 通过 `postMNoteOpenFile` 发送 `mnote:open-file`,MNote host 监听后复用现有 document pane open-file bridge。 +- 2026-06-26 `BASE_URL=http://127.0.0.1:3021 node scripts/task786-openhub-history-refresh-browser-smoke.js`:通过。真实浏览器创建 OpenHub session message,刷新后 history drawer 可见 marker,点击 session 后当前 message area 可见同一 marker;OpenHub API message/history 同步验证通过。该轮 stream 返回“所有模型均不可用” error event,但 user message/session/history 持久化与 UI 恢复已验收;真实 AI reply 由 `task774` 覆盖。截图:`tmp/task786-openhub-history-refresh-browser-smoke/openhub-history-refresh.png`。 + +## 7. P1 OpenHub 原生 opencode 链路 + +- [x] 保留 OpenHub FastAPI prompt/event/diff client,不在 MNote 重写 event parser。 +- [x] 确认 OpenHub prompt 请求携带 `directory = MNote rootUri`。 +- [x] 确认 OpenHub 能监听 opencode `/global/event` 并渲染 assistant/tool part。 +- [x] 确认 OpenHub 能读取 changed files/diff,并将 path 暴露给 MNote bridge。 +- [x] MNote 只存 artifact index:`openhub_session_id + kind + provider_id + path/citation payload`,不复制消息全文。 +- [x] 当前打开文档被 opencode/OpenHub changed-file bridge 标记后,MNote 走 watcher 或 `refreshPrimaryDocument()` 刷新。 +- [~] smoke:让 OpenHub AI 修改 rootUri 内测试 Markdown,MNote document pane 可见变更,且没有 OpenHub Git snapshot commit。当前已覆盖真实 opencode 文件修改 + 模拟 OpenHub changed-file bridge 刷新;单条端到端 AI 修改“当前已打开文档”仍可后续补强。 + +P1 opencode 链路记录: + +- MNote 当前只做 host/status/bootstrap/proxy 边界,没有重写 OpenHub prompt/event parser。 +- OpenHub 后端 `query.py` 已在 MNote host mode 下使用 `resolve_user_workspace(current_user)`,创建 session 与 stream prompt 均把 `directory` 指向 MNote rootUri 解析出的 workspace path。 +- OpenHub 后端 `query.py` 已补 MNote host mode `/api/models`:不再返回空模型,而是调用真实 opencode `/config/providers`,展开 provider/model 并按 opencode `default` 输出 `default/defaultBuild/defaultPlan`;opencode 不可达时返回明确 empty + error,不伪造 mock model。 +- 2026-06-26 主控复核:`curl -H X-MNote-* http://127.0.0.1:18080/api/models` 返回 `success=true`、`modelCount=66`、`source=opencode_config_providers`、默认 `openai/gpt-5.3-chat-latest`;`python -m py_compile` 覆盖 `query.py/auth.py/mnote_scope.py` 通过。 +- 2026-06-26 `node scripts/task774-openhub-mnote-send-and-file-edit-e2e.js`:通过。经 MNote proxy 发送真实 OpenHub stream,事件包含 `session/step-start/text/reasoning/step-finish/message_complete/session_idle`,OpenHub SQLite `/api/sessions/{id}/messages` 可读 user/assistant 两条消息。 +- 2026-06-26 `MNOTE_OPENHUB_FILE_EDIT=1 node scripts/task774-openhub-mnote-send-and-file-edit-e2e.js`:通过。opencode 在 MNote rootUri 内把 `knowledge-rag-fixtures-7-68/task774-openhub-file-edit-1782409309451.md` 修改为 `status: MNOTE_OPENHUB_FILE_EDIT_OK`,stream 事件包含 `tool`,workspace `.git` 在前后均不存在,未创建 OpenHub Git snapshot。当前 document pane 刷新由后续 `task779` 覆盖。 +- 2026-06-26 `node scripts/task776-openhub-changed-files-bridge-smoke.js`:通过。MNote proxy fresh bootstrap 后访问 OpenHub `/api/sessions/ses_1002052f0ffeOpebUFbGq4EC76/diff` 返回 `source=opencode_tool_events`、`diffAvailable=false`,changed path 命中上面的真实 file-edit fixture;OpenHub Git snapshot 写链仍禁用,因此只暴露真实 changed path,不生成或伪造行级 diff。当前 document pane 刷新由后续 `task779` 覆盖。 +- 2026-06-26 `node scripts/task779-openhub-file-edit-document-pane-refresh-smoke.js`:通过。浏览器打开 local-first 测试 Markdown 后,脚本在 rootUri 内模拟 OpenHub/opencode 改写磁盘文件,再触发 MNote OpenHub changed-file bridge;MNote 侧复用 `mnote:open-file` / local-folder event bus synthetic watch batch / `refreshPrimaryDocument()` / `openPrimaryDocument()`,primary document pane 原地显示新内容,并写入 `data-mnote-page-ai-openhub-document-pane-refresh` 诊断 marker。该 smoke 不新增应用轮询。 +- 2026-06-26 MNote 侧新增 `/api/page-ai/openhub/artifact-index` GET/POST 最小边界:写入 `schema=mnote.page_ai_openhub_artifact_index_record.v1` 的轻量索引,只接受 `openhub_session_id/openhubSessionId + kind + provider_id/providerId + path + citation_payload/citationPayload` 等 locator 字段;默认持久化到 rootUri 下 `.mnote/page-ai-openhub-artifact-index.json`,也可由 `MNOTE_OPENHUB_ARTIFACT_INDEX_PATH` 指定。该实现明确 `messageFulltextCopied=false`,并拒绝 `message/messages/content/text/transcript/conversationMessages/assistantMessage/userMessage` 等 OpenHub user/assistant message 全文字段;不是复制 OpenHub SQLite message 表。 +- 2026-06-26 `node --check scripts/task778-openhub-artifact-index-static-smoke.js` 与 `node scripts/task778-openhub-artifact-index-static-smoke.js`:通过。静态 smoke 断言 artifact index route、轻量字段、全文字段拒绝、最小文件持久化和本 checklist 记录。 +- 2026-06-26 主控 runtime 复核:登录 MNote 测试账号后 POST `/api/page-ai/openhub/artifact-index` 写入 `kind=changed_file`、`providerId=opencode-tool-event-1`、`path=knowledge-rag-fixtures-7-68/task774-openhub-file-edit-1782409309451.md`,GET 同 session 返回 1 条记录,`storage.messageFulltextCopied=false`;携带 `message` 全文字段的 POST 返回 `page_ai_openhub_artifact_index_forbidden_fulltext_field`。 +- 2026-06-26 OpenHub 嵌入态已接 MNote artifact index:`mnoteEmbed.js` 从 `mnoteScope` 解出 `rootUri/workspaceId/pageResourceId`,`SmartQueryPage.jsx` 在 changed files diff metadata 加载后 POST `kind=changed_file` 轻量记录,并在 WeKnora citation/tool output 出现时 POST `kind=citation` 轻量记录;请求字段限定为 `openhubSessionId/kind/providerId/path/citationPayload/rootUri/workspaceId/pageResourceId`,citationPayload 递归去除 `message/messages/content/text/transcript/quote/displayQuote/matched_content` 等全文或证据正文键,不发送 OpenHub message/content/text 全文字段。 +- 2026-06-26 `node --check scripts/task780-openhub-artifact-index-runtime-smoke.js` 与 `node scripts/task780-openhub-artifact-index-runtime-smoke.js`:通过。smoke 验证 OpenHub 前端静态边界、runtime POST URL/request body 形状、`mnoteScope` 上下文字段、changed_file / citation 轻量 artifact index 记录,以及请求体不含全文字段。 + +## 8. P1 WeKnora 知识库页面替换 + +- [x] 建立 MNote `mnote_knowledge_bases` 模型:user/workspace/rootUri/name/provider/provider_kb_id/default_tool_enabled。 +- [x] 迁移/扩展 `mnote_knowledge_sources`:兼容旧 `lightRag*` 字段,新增 provider-neutral 字段。 +- [x] 知识库列表页复用 WeKnora KB list 体验:名称、描述、文档数、chunk 数、processing 状态、更新时间。 +- [x] 知识库详情页复用 WeKnora detail 体验:source 列表、状态、reparse/delete、搜索入口。 +- [x] source 添加入口使用 MNote FileTree/resource picker,不使用 WeKnora 独立文件真相。 +- [x] 入库前 MNote 校验 allowed roots;文件/文件夹/page resource 转成 WeKnora file/manual/url ingest。 +- [x] processing timeline 显示 WeKnora `pending/processing/completed/failed`,并同步到 registry 与 FileTree 灯号。 +- [x] 删除 source 只删除 provider index/registry 映射,不删除本地文件。 +- [x] browser smoke:创建 KB -> 添加本地文件夹 -> 看到 indexing -> 完成后可搜索。 + +P1 WeKnora 知识库模型记录: + +- 2026-06-26 新增 MNote 后端 `mnote_knowledge_bases` runtime registry:`schema=mnote.knowledge_bases.registry.v1`,持久化到 workspace root 下 `.mnote/index/mnote-knowledge-bases.json`,字段包含 `userId/workspaceId/rootUri/name/provider/providerKbId/defaultToolEnabled/sourceCount/chunkCount/status`;`status` 与 `ingest` 会在有 `rootUri` 时返回 `knowledgeBases`。 +- `mnote_knowledge_sources` 边界通过现有 `.mnote/index/lightrag-source-registry.json` 兼容落地:新增 provider-neutral 字段 `provider/providerStatus/providerSourceId/providerKnowledgeId/providerKnowledgeBaseId`,同时保留旧 `lightRagDocId/lightRagStatus/lightRagFilePath` 兼容字段;读写 registry 时会为旧记录补齐 provider-neutral 字段,不删除旧 registry。 +- WeKnora ingest 现在写入 source registry 的 provider-neutral 映射,并同步 `mnote_knowledge_bases.sourceCount`;search/open-reference 继续复用 provider ids 与旧 LightRAG/source registry 兼容映射,不破坏已完成 ingest/search/open-reference。 +- 2026-06-26 `node --check scripts/task781-knowledge-bases-registry-smoke.js`:通过。 +- 2026-06-26 `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3021 node scripts/task781-knowledge-bases-registry-smoke.js`:通过。验证 status 可读 `mnote_knowledge_bases`、ingest 写 provider-neutral source 字段、search 复用同一 WeKnora KB/source 映射;结果文件 `tmp/task781-knowledge-bases-registry-smoke/result.json`。同一脚本曾因 WeKnora 异步索引窗口在 240s 内未返回新 source 而失败一次,随后重跑通过,说明 search readiness 仍有 provider 异步延迟风险。 +- 2026-06-26 `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3021 node scripts/task785-weknora-create-kb-folder-ingest-search-smoke.js`:通过。真实 API 创建 WeKnora KB `df7bd813-fef2-40f4-b08d-b47e03a58cea`,ingest 本地文件夹下 2 个 Markdown source,scoped search 命中同一 KB/source 映射;结果文件 `tmp/task785-weknora-create-kb-folder-ingest-search-smoke/result.json`。 +- 2026-06-26 `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3022 node scripts/task788-weknora-kb-folder-browser-flow-smoke.js`:通过。真实浏览器 UI 创建 WeKnora KB `dc5e35f6-4d0f-4289-ae79-de8f4dac8441`,UI 添加本地文件夹 source,source registry 映射 2 个 source,UI 指定新 KB 检索并展示 source result,API search 复核命中同一 KB/source;`uiCreatesKb=true`、`uiAddsFolder=true`、`browserVerifiesSearch=true`、`apiVerifiesSearch=true`。截图:`tmp/task788-weknora-kb-folder-browser-flow-smoke/weknora-kb-folder-browser-flow.png`。 +- 2026-06-26 修复 WeKnora KB 创建 payload:新增 `embedding_model_id`,默认 `builtin-mnote-embedding`,可由 `MNOTE_WEKNORA_EMBEDDING_MODEL_ID` / `WEKNORA_EMBEDDING_MODEL_ID` 覆盖;否则新 KB 会长期 processing 且 search 无结果。 +- 2026-06-26 修复 scoped search:`sourcePaths` 从 registry 推导 `knowledge_ids`,并在 WeKnora 返回后按 source scope 二次过滤,避免同 KB 下其他旧文件污染指定 source 检索。 + +## 9. P1 WeKnora 检索替换 LightRAG + +- [~] 在 `rust/crates/mnote-web/src/routes/knowledge_rag.rs` 或拆分模块中抽出 `KnowledgeProvider` boundary。 +- [x] 新增 `weknora` provider,旧 LightRAG 标为 `lightrag_legacy` 并默认隐藏。 +- [x] `/api/knowledge-rag/status` 改为 WeKnora health + KB/source registry 状态。 +- [x] `/api/knowledge-rag/ingest` 改为 WeKnora ingest,写 provider KB / knowledge / source hash。 +- [x] `/api/knowledge-rag/search` 调 `/api/v1/knowledge-search`;单 KB 调试可走 hybrid-search。 +- [x] `/api/knowledge-rag/query` 第一阶段用 search results + citations 生成 answer envelope,不启用 WeKnora chat session。 +- [x] `/api/knowledge-rag/section-context` 改为 WeKnora chunk/list/by-id + MNote locator,不读 LightRAG sidecar。 +- [~] `/api/knowledge-rag/open-reference` 仅接受 provider ids / open-reference token,回查 registry 后生成 MNote locator。 +- [x] 映射保留 `content/matched_content/match_type/metadata/chunk_metadata/image_info/parent_chunk_id/sub_chunk_id`。 +- [x] UI 不把 score 显示为百分比相似度;只显示排序或弱化分值。 +- [x] 单测:SearchResult -> MNote result/citation/open-reference;缺 registry 映射时降级且不伪造路径。 +- [x] smoke:WeKnora ingest 本地 md -> MNote search 返回 provider=`weknora` -> open-reference 打开 MNote 文档。 + +P1 WeKnora 检索记录: + +- `knowledge_rag.rs` 默认 provider 为 `weknora`,`MNOTE_KNOWLEDGE_PROVIDER=lightrag|lightrag_legacy` 才进入 legacy。 +- `/api/knowledge-rag/status` 返回 `providerConfig.active/default/legacyFallback/weknora/lightrag`、WeKnora health、legacyHealth、registry。 +- `/api/knowledge-rag/ingest` 默认 WeKnora provider 时通过 `POST /api/v1/knowledge-bases/:id/knowledge/file` multipart 上传本地授权文件,写回 `providerKnowledgeBaseId/providerKnowledgeId/sourceHash/providerStatus`;缺 KB id 时只登记 `pending_provider_ingest_missing_kb`,不碰 LightRAG staging。 +- `/api/knowledge-rag/search` 与 `/api/knowledge-rag/query` 调 WeKnora `/knowledge-search`,请求包含 `query + knowledge_base_ids`,按需 `knowledge_ids/match_count`;API key 只在后端注入。 +- WeKnora chunk mapper 保留 provider ids、raw chunk、content/matched_content、metadata/chunk_metadata/image_info/parent/sub chunks;未命中 registry 时 `filePath=null`、`locatorDegraded=true`,不把 `knowledge_filename` 伪装成本地路径。 +- `sidebar-page-settings-runtime.js` 已收口 WeKnora 默认 provider、LightRAG legacy fallback、source registry、processing/citation/open-reference 文案;score 只显示 provider 排序分,不再表达为相似度百分比;删除 source 明确不删除本地文件。 +- `node scripts/task772-weknora-ingest-search-open-reference-e2e.js` 已真实通过:在 allowed root 下创建 `knowledge-rag-fixtures-7-68/task772-weknora-e2e-1782405331456.md`,MNote `/api/knowledge-rag/ingest` 返回 provider=`weknora`、`mappingStatus=provider_mapped`、`providerKnowledgeBaseId=90cc060c-3a16-4146-9d2b-fef8eb2c2d90`、`providerKnowledgeId=78b2c703-6d6a-4fa6-a881-f5ff6cfb769d`;`/api/knowledge-rag/search` 返回 4 条 weknora reference 并保留 provider ids;`/api/knowledge-rag/open-reference` 返回 `filePath=null`、`sourceRootRelativePath` 为 registry 映射路径、`openAction.params.path` 为本地 source path,未把 provider filename 伪造成本地路径。 +- `/api/knowledge-rag/section-context` 已改为 WeKnora provider chunk 优先:有 `providerChunkId` 时调 `/chunks/by-id/:id`,否则按 `providerKnowledgeId` 调 `/chunks/:knowledge_id`;返回 `providerChunkFetch.attempted/ok/source/fetchedChunks`、`providerChunk` metadata 与 MNote locator,且 `sidecarRead=false`。为适配 WeKnora 异步 parse 状态,section-context 对 WeKnora provider 已允许 `pending/submitted/processing/processed/completed/provider_mapped` 的 registry 映射进入 provider chunk fetch;LightRAG legacy sidecar 仍要求 `processed`。 +- 2026-06-26 `node scripts/task777-weknora-section-context-smoke.js`:通过。基于 task772 真实 ingest 结果,by-id 命中 `providerChunkId=78f026a5-7bb1-4b82-92f7-ee4f560f5640`,list 命中同一 `providerKnowledgeId=ec461ca5-37f7-471c-b4f7-4ced7be09a01`,两路均返回 `providerChunkFetch.ok=true`、`sidecarRead=false`、文本含 task772 marker。 +- 2026-06-26 `cargo test -p mnote-web knowledge_rag --lib -- --test-threads=1`:59 passed;`node scripts/task544-weknora-provider-bridge-static-smoke.js`:通过。 + +## 10. P1 WeKnora MCP/CLI Tool Bridge + +- [x] 定义 MNote 对 OpenHub/opencode 暴露的工具名:`mnote.weknora.search`、`mnote.weknora.open_reference`、`mnote.weknora.list_sources`、`mnote.weknora.get_source_status`。 +- [x] 底层第一阶段优先用 Go CLI `weknora mcp serve` 或 MNote HTTP facade 包装同等只读能力。 +- [x] tool 调用前注入 KB/source allowlist;禁止直接传未授权 `knowledge_base_id`。 +- [x] tool result 返回 provider ids、quote/matched_content、chunk metadata、MNote open-reference token。 +- [x] 不把 WeKnora API key 暴露给浏览器或 OpenHub 前端 localStorage。 +- [x] Python `mcp-server` 的 create/delete/chunk mutation 不默认暴露;如未来开启写能力,必须经过 MNote 权限审批。 +- [~] smoke:OpenHub AI 通过 WeKnora tool 查询 KB,回答中出现可点击 citation,点击后由 MNote 打开本地页面。当前完成 OpenHub host mode 自动 WeKnora search/tool event + clickable citation bridge;仍不是 opencode 原生 MCP 自动选 tool 的完整闭环。 + +P1 WeKnora tool 记录: + +- Hermes tool manifest 与 dispatch 已新增 `mnote.weknora.search/open_reference/list_sources/get_source_status`。 +- Tool wrapper 要求 `rootUri` 以及 `scope/allowlist/allowedRoots/aiAccessScope/sourcePaths` 之一,否则返回 `mnote_weknora_scope_required`。 +- 当前通过 MNote HTTP facade 调用 WeKnora;没有把 WeKnora API key 暴露到浏览器/localStorage,也没有默认开放 Python mcp-server 写能力。 +- 2026-06-26 task782 可写 worker:OpenHub backend 新增 MNote host mode `/api/mnote/tools/call` 只读 facade,通过 MNote 反代受控 headers 与 server-side `X-MNote-Session-Cookie` 调 `/api/hermes/tools/mnote/call`,支持 `mnote.weknora.search/open_reference/list_sources/get_source_status`;OpenHub 不接触 WeKnora API key,也不把 MNote cookie 暴露给前端 localStorage。`stream.py` 在 MNote host mode 下对知识库/资料库/WeKnora/citation/source 类问题先调用 MNote WeKnora search,把结构化 tool result 注入 OpenHub/opencode prompt,并向 OpenHub 前端推送 `mnote.weknora.search` tool event。 +- 2026-06-26 task782 可写 worker:OpenHub 前端 `AssistantMessage` 从 WeKnora tool output 的 `citations/uiCitations/references` 渲染 `data-mnote-openhub-citation` 可点击 citation,点击经 `postMNoteOpenReference` 发 `mnote:open-reference`;MNote Page AI host 已接受 `openhub-citation` 并复用 document pane open-file bridge 打开本地页面。新增 `scripts/task782-openhub-weknora-tool-citation-bridge-smoke.js` 覆盖后端 facade、scope/cookie/API key 边界、stream auto tool bridge、citation button 与 postMessage runtime。主控复核:OpenHub `/openapi.json` 已暴露 `/api/mnote/tools/call`,`node scripts/task782-openhub-weknora-tool-citation-bridge-smoke.js` 通过;边界仍标 partial,不宣称 opencode 原生 MCP 自动选 tool 完成。 + +## 11. P1 验收与证据 + +- [~] `npm run dev:hot` 或目标启动命令能启动/检查 MNote、OpenHub FastAPI、OpenHub Redis、opencode、WeKnora API、WeKnora CLI/MCP。OpenHub/MNote/opencode/WeKnora API、Redis auth、WeKnora CLI profile/doctor 与 MCP 命令面已探通;stdio MCP handshake 和单命令 dev-hot orchestration 仍作为后续收口。 +- [x] 用 `mnote.e2e@example.com` 登录 MNote;Page AI 不出现 OpenHub/WeKnora 登录。 +- [x] Page AI 可发送消息,OpenHub SQLite session/message/history 持久化,刷新恢复。 +- [x] AI 修改当前 rootUri 内 Markdown,MNote 当前页面刷新后可见。当前证据由 `task774` 真实 opencode 文件修改 + `task779` 浏览器模拟 OpenHub changed-file bridge 刷新组成。 +- [x] OpenHub Login/Admin/FileManager/KnowledgeManager/GitTimeMachine 非 AI 入口不接管 MNote。 +- [x] WeKnora 知识库页完成 KB 创建、source 添加、状态显示、检索、citation 回跳。 +- [x] 旧 LightRAG 默认入口不再出现在主 UI/smoke;legacy fallback 显式标识。 +- [x] 保存 smoke 输出、关键截图、失败分层日志。 + +P1 当前验证证据: + +- `cargo check -p mnote-web`:通过。 +- `cargo test -p mnote-web knowledge_rag --lib -- --test-threads=1`:59 passed。 +- `node scripts/task544-weknora-provider-bridge-static-smoke.js`:通过。 +- `node scripts/task768-page-ai-openhub-host-static-smoke.js`:通过。 +- `node scripts/task769-weknora-knowledge-settings-ui-static-smoke.js`:通过。 +- `node scripts/task770-openhub-git-snapshot-restore-guard-static-smoke.js`:通过。 +- `node --check scripts/task773-page-ai-openhub-browser-smoke.js`:通过。 +- 早期 `node scripts/task773-page-ai-openhub-browser-smoke.js` 只验证到静态 shell,已被后续 React bundle 接入结果覆盖;保留为阶段证据,不作为当前最终状态。 +- `npm run build`(`/tmp/mnote-openhub-research/OpenHub/smart-query-frontend`):通过;Vite 仍提示主 chunk 超过 500 kB,这是既有体积 warning。 +- OpenHub React dist 已复制到 `/tmp/mnote-openhub-research/OpenHub/smart-query-backend/static/`,MNote `/page-ai/openhub/ai` 可加载 OpenHub SPA index/assets。 +- `node scripts/task773-page-ai-openhub-browser-smoke.js`:通过;测试账号真实登录,Page AI OpenHub host drawer/iframe 可见,OpenHub React UI marker 出现,未出现 OpenHub/WeKnora 登录页;`openhubReactConnected=true`、`staticBoundaryOnly=false`,嵌入态未显示 FileManager/KnowledgeManager/GitTimeMachine 等冲突入口。 +- `curl -H X-MNote-* http://127.0.0.1:18080/api/models`:通过;MNote host mode 返回 66 个真实 opencode 模型,`source=opencode_config_providers`,未使用 mock。 +- `node scripts/task774-openhub-mnote-send-and-file-edit-e2e.js`:通过;MNote proxy -> OpenHub `/api/models` -> `/api/query/stream` -> OpenHub SQLite `/api/sessions/{id}/messages` 全链路通过,选用 `opencodego/deepseek-v4-flash`。 +- `MNOTE_OPENHUB_FILE_EDIT=1 node scripts/task774-openhub-mnote-send-and-file-edit-e2e.js`:通过;opencode 修改 MNote rootUri 内测试 Markdown,最终内容含 `status: MNOTE_OPENHUB_FILE_EDIT_OK`,`.git` 未被 OpenHub 创建。 +- `node --check scripts/task772-weknora-ingest-search-open-reference-e2e.js`:通过。 +- `node scripts/task772-weknora-ingest-search-open-reference-e2e.js`:通过;当前 shell 未直接暴露 WeKnora env,脚本先输出 `local_weknora_env_not_visible` warning,但继续以 MNote 3000 的 status 和真实 API 调用为准,最终 ingest/search/open-reference 全链路通过。最新主控重跑创建 `knowledge-rag-fixtures-7-68/task772-weknora-e2e-1782408870763.md`,映射到 WeKnora KB `90cc060c-3a16-4146-9d2b-fef8eb2c2d90` 与 knowledge `c6789541-7ad5-47a4-b621-5ef1e2762688`。 +- `python -m py_compile /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/api/query.py /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/api/session.py /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/core/auth.py /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/core/mnote_scope.py /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/git_snapshot.py /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/stream.py /tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/task_executor.py`:通过。 +- `MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE=1 PYTHONPATH=/tmp/mnote-openhub-research/OpenHub/smart-query-backend python ...`:通过,确认底层 git 写命令 guard 生效。 +- `node --test scripts/desktop-hot.test.js`:6 passed。 +- `git diff --check`:通过。 +- `codegraph sync .`:完成,报告 synced 38 changed files。 +- `node scripts/task776-openhub-changed-files-bridge-smoke.js`:通过;MNote proxy -> OpenHub changed files endpoint 返回真实 opencode tool-event path,且 OpenHub 前端到 MNote document pane open-file bridge 静态边界通过。 +- `node scripts/task779-openhub-file-edit-document-pane-refresh-smoke.js`:通过;打开测试 Markdown 后改写 rootUri 内文件并触发 OpenHub changed-file bridge,document pane 可见刷新到新正文,诊断显示 `eventBusSource=openhub_changed_file_bridge`。 +- `node --check scripts/task780-openhub-artifact-index-runtime-smoke.js`:通过。 +- `node scripts/task780-openhub-artifact-index-runtime-smoke.js`:通过;验证 OpenHub embed changed_file / citation 轻量 artifact index POST 形状,且不含 message/content/text 等全文字段。 +- 外部服务真实探针:`/api/page-ai/openhub/status` 在配置 WeKnora API key 后返回 `status=ready`,OpenHub FastAPI `127.0.0.1:18080` ready,opencode `127.0.0.1:4096` ready,WeKnora public `/health` ready,OpenHub `/openapi.json` 暴露 `/api/mnote/tools/call`,Redis `AUTH + PING` 返回 `+OK/+PONG`,WeKnora CLI `profile add + auth login --with-token + doctor --no-cache` 通过,`go run . mcp serve --help` 通过。stdio MCP handshake 未作为最终通过项,因此 dev-hot/CLI/MCP 全链路仍 partial。 +- 2026-06-26 task783/task784 UI 增量:`sidebar-page-settings-runtime.js` 在 WeKnora 设置面板增加 KB summary(KB/source/provider docs/processing)、本地文件/文件夹 source picker 边界标记、source row 与 search result 的 `open-source-reference` 控件;`sidebar-tree-runtime.js` 接线该控件到 MNote document pane `openPrimaryDocument`,仍复用 `/api/knowledge-rag/status/ingest/search/delete-source`,未改后端 `knowledge_rag.rs`。`node --check rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js`、`node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js`、`node --check scripts/task783-weknora-kb-settings-ui-static-smoke.js`、`node --check scripts/task784-weknora-kb-settings-browser-smoke.js`、`node scripts/task783-weknora-kb-settings-ui-static-smoke.js`、`node scripts/task769-weknora-knowledge-settings-ui-static-smoke.js` 均通过。真实 browser smoke 使用临时 3018 MNote 实例与已有 task772 WeKnora 入库结果:`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3018 node scripts/task784-weknora-kb-settings-browser-smoke.js` 通过,截图 `tmp/task784-weknora-kb-settings-browser-smoke/weknora-kb-settings.png`;验证 KB summary 包含 KB `90cc060c-3a16-4146-9d2b-fef8eb2c2d90`,source row/search result/open-reference/delete-preserve-local-file 控件可见。仍 partial:未创建新的 KB;未真实点击 open-reference 后断言文档 pane 跳转;未完成 checklist 第 8 节“创建 KB -> 添加本地文件夹 -> indexing -> 完成后搜索”的完整链路。 +- 2026-06-26 task788 UI 完整链路增量:`sidebar-page-settings-runtime.js` 新增 WeKnora KB select、创建 KB 控件,ingest/search 带当前选中 `providerKnowledgeBaseId`;`sidebar-tree-runtime.js` 接线 create-kb 与 KB select change。主控复核 `node --check rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js`、`node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js`、`node --check scripts/task788-weknora-kb-folder-browser-flow-smoke.js` 通过;临时当前构建 MNote `3022` 上 `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3022 node scripts/task788-weknora-kb-folder-browser-flow-smoke.js` 通过,UI 创建 KB `dc5e35f6-4d0f-4289-ae79-de8f4dac8441`、UI 添加文件夹 source、UI 指定新 KB 检索、API search 复核均通过。 +- 2026-06-26 用户纠正后知识库页面替换增量:原“资料库问答/资料源管理”小面板不符合 7-68,已改为 WeKnora 风格“知识库列表 + 知识库详情/文档管理”页面。`sidebar-page-settings-runtime.js` 补入 KB 面包屑、Documents/Wiki/Graph/Search 页签、source/tag 侧栏、文档搜索/筛选、添加文档/文件夹、列表/网格切换、processing/status 与 open-reference 控件;`main.css` 补齐对应布局;新增 `scripts/task790-weknora-kb-page-experience-static-smoke.js` 与 `scripts/task79x-weknora-kb-page-browser-smoke.js`。 +- 2026-06-26 纠正后复核:`node --check rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js`、`node --check scripts/task790-weknora-kb-page-experience-static-smoke.js`、`node --check scripts/task79x-weknora-kb-page-browser-smoke.js`、`node scripts/task783-weknora-kb-settings-ui-static-smoke.js`、`node scripts/task789-weknora-kb-page-ui-reference-static-smoke.js`、`node scripts/task790-weknora-kb-page-experience-static-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3022 node scripts/task79x-weknora-kb-page-browser-smoke.js` 均通过。浏览器结果确认 `openedReplacementPanel=true`、`notOldSimpleSettingsPanel=true`、`hasKnowledgeBaseListCards=true`、`hasDetailStructure=true`、`hasDocumentTabs=true`、`hasSearchAndFilter=true`;截图 `tmp/task79x-weknora-kb-page-browser-smoke/weknora-kb-page-browser-smoke.png`。 +- 2026-06-26 Page AI 截图复核:`MNOTE_UI_BASE_URL=http://127.0.0.1:3022 node scripts/task773-page-ai-openhub-browser-smoke.js` 通过,截图 `tmp/7-68-runtime/page-ai-openhub-browser.png`;OpenHub React UI marker 可见,`openhubReactConnected=true`、`staticBoundaryOnly=false`,未出现 OpenHub/WeKnora 登录页或 FileManager/KnowledgeManager/GitTimeMachine 冲突入口。 +- 2026-06-26 `BASE_URL=http://127.0.0.1:3021 node scripts/task786-openhub-history-refresh-browser-smoke.js`:通过;OpenHub session/message/history 刷新恢复已由浏览器和 API 双重验证,结果文件 `tmp/task786-openhub-history-refresh-browser-smoke/result.json`。 +- 2026-06-26 `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3021 node scripts/task787-weknora-default-lightrag-legacy-smoke.js`:通过;静态扫描 33 个主 UI/smoke 文件,`violations=[]`,status API 的默认/active provider 为 WeKnora,LightRAG 仅作为显式 legacy fallback。 + +## 12. P2 后续优化 + +- [ ] 若需要开放 OpenHub Admin/tool/model 管理,先做 MNote role -> OpenHub admin scope 映射设计。 +- [ ] 若需要完整 MCP 管理 UI,再单独设计 OpenHub/MNote MCP 管理面,不混入首版嵌入。 +- [ ] 若需要 WeKnora chat/agent-chat,作为 OpenHub tool 内部 provider call,不变更 Page AI session 真相。 +- [ ] 若需要版本恢复,走 MNote DocumentBuffer/watcher/显式用户确认策略,不恢复 OpenHub GitTimeMachine 写链。 diff --git a/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-v1.md b/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-v1.md index e85f8e44..04262154 100644 --- a/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-v1.md +++ b/design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-v1.md @@ -4,6 +4,8 @@ Owner:07-ai / mnote-web / control-plane / knowledge-provider 日期:2026-06-25 +执行说明:本文是 Page AI / OpenHub / WeKnora 深度融合的当前主设计,不表示代码已完成。具体落地按本文第 10 节与配套清单 `design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md` 执行;若实现中发现源码能力与本文假设不一致,优先遵循“保留原系统已完成能力,只在产品真相冲突处做最小胶水/嫁接/裁剪”的总原则。 + ## 0. 结论先行 MNote Page AI 不应再沿“官方 opencode WebUI iframe”或“自研简陋聊天框”继续堆功能。新的主路线是: @@ -11,22 +13,22 @@ MNote Page AI 不应再沿“官方 opencode WebUI iframe”或“自研简陋 ```text MNote 当前文档页 / workspace / auth / resource truth ├─ Page AI UI:嵌入 OpenHub AI 界面(只暴露 AI 面板能力) - ├─ OpenHub backend:运行 OpenHub FastAPI + Redis + OpenHub session/skill/MCP/permission - ├─ Agent runtime:OpenHub FastAPI 调用 opencode serve / opencodego provider - ├─ Knowledge provider:WeKnora,通过 OpenHub MCP/CLI/skill 工具调用 + ├─ OpenHub backend:运行 OpenHub FastAPI + Redis + OpenHub SQLite session/skill/tool permission + ├─ Agent runtime:OpenHub FastAPI 调用 opencode serve / opencode provider + ├─ Knowledge provider:WeKnora,通过 MNote 注册给 OpenHub/opencode 的 MCP/CLI/skill 工具调用 └─ MNote boundary:统一登录授权、workspace/rootUri scope、文件打开、citation 回跳 ``` 核心取舍: -总原则:**各部分尽量保持原有已完成能力,只有发生产品真相冲突时才做最小胶水/嫁接/裁剪**。OpenHub 保持 AI 面板、FastAPI、Redis、session、skill/MCP、opencode client;WeKnora 保持知识库底座、MCP/CLI/API;MNote 保持 workspace/auth/resource tree/document pane/source registry。MNote 不重写 OpenHub/WeKnora 已有能力,只在登录态、workspace scope、文件打开、citation 回跳、禁用无关入口这些冲突点上做最小移植。 +总原则:**各部分尽量保持原有已完成能力,只有发生产品真相冲突时才做最小胶水/嫁接/裁剪**。OpenHub 保持 AI 面板、FastAPI、Redis、SQLite session/message、skill/tool permission、opencode client;WeKnora 保持知识库底座、MCP/CLI/API;MNote 保持 workspace/auth/resource tree/document pane/source registry。MNote 不重写 OpenHub/WeKnora 已有能力,只在登录态、workspace scope、文件打开、citation 回跳、禁用无关入口这些冲突点上做最小移植。 -- **OpenHub 负责 Page AI 界面与多用户 AI 运行栈**:嵌入 OpenHub AI 面板,运行 FastAPI/Redis,使用 OpenHub 的用户隔离、session、skill、agent/MCP、tool permission、opencode 调用链;FileManager/KnowledgeManager/Admin/Login 等非 AI 入口掐断或转接。 +- **OpenHub 负责 Page AI 界面与多用户 AI 运行栈**:嵌入 OpenHub AI 面板,运行 FastAPI/Redis,使用 OpenHub 的用户隔离、session、skill、agent/tool permission、opencode 调用链;MCP 由 MNote/WeKnora/opencode 工具配置承接,不把“OpenHub 已有完整 MCP 管理面”当成已确认事实;FileManager/KnowledgeManager/Admin/Login 等非 AI 入口掐断或转接。 - **WeKnora 负责知识库底座、知识库页面参考实现和 MCP/CLI/API 工具能力**:解析、chunk、混合检索、RAG 引用、Wiki/图谱、知识库管理 API;知识库页面优先复用 WeKnora 的 KB list/detail/upload/status 体验,但不让 WeKnora 接管 MNote 文件真相。 - **MNote 负责宿主真相与冲突胶水**:登录、用户、workspace/rootUri、resource tree、页面打开、allowed roots、session binding、source registry、citation/open-reference;不重写 OpenHub/WeKnora 已有主功能。 - **opencode 负责 agent 执行**:文件编辑、diff、工具审批、模型调用。 -不得把 OpenHub 登录入口、OpenHub FileManager、OpenHub KnowledgeManager、WeKnora RBAC/前端变成 MNote 的用户/文件/知识库真相。OpenHub AI session/message/skill/MCP/permission/FastAPI/Redis 可以作为 Page AI 运行真相,但必须受 MNote 派生的用户与 workspace scope 隔离;WeKnora 保持知识库底座真相,MNote 只做展示、授权和回跳映射。 +不得把 OpenHub 登录入口、OpenHub FileManager、OpenHub KnowledgeManager、WeKnora RBAC/前端变成 MNote 的用户/文件/知识库真相。OpenHub AI session/message/skill/tool permission/FastAPI/Redis 可以作为 Page AI 运行真相,但必须受 MNote 派生的用户与 workspace scope 隔离;WeKnora 保持知识库底座真相,MNote 只做展示、授权和回跳映射。 ## 1. 背景与当前状态 @@ -44,7 +46,7 @@ MNote 当前文档页 / workspace / auth / resource truth 本设计替代 `7-65` 的官方 opencode iframe 产品主线,并收敛 `7-67` 的 OpenHub 初步融合设想。核心不是重写三套系统,而是保留各自已有能力,只对冲突点做最小胶水: 1. 登录/用户/权限冲突。 -2. WeKnora 知识库底座、MNote 展示层、OpenHub MCP/skill/tool 调用边界。 +2. WeKnora 知识库底座、MNote 展示层、MNote 注册给 OpenHub/opencode 的 CLI/MCP/skill/tool 调用边界。 3. 页面/文件打开、changed files、citation 回跳边界。 ## 2. 三方职责边界 @@ -52,16 +54,16 @@ MNote 当前文档页 / workspace / auth / resource truth | 能力 | MNote | OpenHub | WeKnora | 取舍 | |---|---|---|---|---| | 登录/用户 | MNote SQLite control-plane、`mnote_session` | OpenHub JWT/localStorage 禁用或后端注入;使用 MNote 派生 user/workspace | WeKnora tenant/RBAC/API key | MNote 是唯一登录入口;OpenHub 运行态按 MNote 用户/工作区派生 | -| workspace/rootUri | MNote local workspace、rootUri 授权 | OpenHub user workspace path / session scope / skill scope / MCP scope | WeKnora tenant/KB | rootUri 归 MNote;OpenHub 的 workspace directory 由 MNote 授权 rootUri 派生 | -| Page AI UI | MNote sidebar host / iframe/proxy shell | OpenHub AI 界面 | WeKnora Web UI 不嵌入 | 嵌入 OpenHub AI 面板;隐藏或掐断非 AI 页面入口 | -| session/message | MNote 只做 host binding / ownership index | OpenHub conversation/session tables + Redis/cache | WeKnora chat sessions 暂弃用 | OpenHub session/message 是 Page AI 真相;MNote 不复制消息全文 | -| agent runtime | MNote 启停/健康检查/反代边界 | OpenHub FastAPI + Redis + opencode client | WeKnora MCP/CLI/API,agent-chat 暂弃用 | 运行 OpenHub 后端;由 OpenHub 调 opencode;MNote 不重写 opencode client | -| 知识库 UI | MNote shell / FileTree 灯号 / citation 回跳 | OpenHub KnowledgeManager 掐断或跳转 MNote | 复用 WeKnora KB list/detail/upload/status 页面能力 | 用 WeKnora 知识库页面替换 MNote 简陋页,但本地文件和授权仍归 MNote | -| 检索/RAG | MNote source registry / citation 回跳 / scope 集合定义 | OpenHub AI 通过 MCP/skill/tool 调 WeKnora | WeKnora hybrid/vector/graph/chunk | 知识库是 MNote 授权文件/文件夹集合的索引视图;WeKnora 是唯一索引与查询 provider | +| workspace/rootUri | MNote local workspace、rootUri 授权 | OpenHub user workspace path / session scope / skill scope / tool scope | WeKnora tenant/KB | rootUri 归 MNote;OpenHub 的 workspace directory 由 MNote 授权 rootUri 派生 | +| Page AI UI | MNote sidebar host / iframe/proxy shell | OpenHub AI 界面 | WeKnora 不进入 Page AI 对话壳 | 嵌入 OpenHub AI 面板;隐藏或掐断非 AI 页面入口 | +| session/message | MNote 只做 host binding / ownership index | OpenHub SQLite conversation/session/message tables;Redis 只作缓存/临时态 | WeKnora chat sessions 暂弃用 | OpenHub session/message 是 Page AI 真相;MNote 不复制消息全文 | +| agent runtime | MNote 启停/健康检查/反代边界 | OpenHub FastAPI + Redis + opencode client | WeKnora CLI/MCP/API,agent-chat 暂弃用 | 运行 OpenHub 后端;由 OpenHub 调 opencode;MNote 不重写 opencode client | +| 知识库 UI | MNote shell / FileTree 灯号 / citation 回跳 | OpenHub KnowledgeManager 掐断或跳转 MNote | 复用 WeKnora KB list/detail/upload/status 页面体验 | 用 MNote shell + WeKnora KB adapter 替换 MNote 简陋页,但本地文件和授权仍归 MNote | +| 检索/RAG | MNote source registry / citation 回跳 / scope 集合定义 | OpenHub AI 通过 MNote 注册的 CLI/MCP/skill/tool 调 WeKnora | WeKnora hybrid/vector/graph/chunk | 知识库是 MNote 授权文件/文件夹集合的索引视图;WeKnora 是唯一索引与查询 provider | | 文件打开 | MNote document pane/resource tab/FileTree,相当于 FileManager | OpenHub FileManager 掐断或跳转 MNote | WeKnora 不负责页面打开 | OpenHub AI 回复中的 path/citation 点击回 MNote 打开页面 | -| 权限审批 | MNote allowed roots + workspace grants + scope 注入 | OpenHub tool/model/skill/MCP permissions | WeKnora RBAC | MNote 提供授权边界;OpenHub 按原生权限规范执行;WeKnora RBAC 作 provider 防线 | +| 权限审批 | MNote allowed roots + workspace grants + scope 注入 | OpenHub tool/model/skill permissions;MCP 走 opencode/WeKnora 工具配置 | WeKnora RBAC | MNote 提供授权边界;OpenHub 按原生权限规范执行;WeKnora RBAC 作 provider 防线 | | Git / snapshot / restore | MNote watcher、buffer、用户显式保存/版本策略 | OpenHub Git snapshot 默认关闭 | 无关 | 第一阶段禁用 Git snapshot/restore,避免污染 local-first workspace | -| Redis/cache/queue | MNote 不存 Page AI 消息真相 | OpenHub Redis/cache/queue 由 OpenHub stack 管理 | WeKnora 如有内部 Redis 由 WeKnora stack 管理 | Redis 归各自服务栈;不作为 MNote 权限/文件/知识库真相 | +| Redis/cache | MNote 不存 Page AI 消息真相 | OpenHub Redis 主要用于 token/rate-limit/cache/临时态;消息真相在 SQLite | WeKnora Redis/Asynq/Langfuse 由 WeKnora stack 管理 | Redis 归各自服务栈;不作为 MNote 权限/文件/知识库真相 | ## 3. 登录、OpenHub 隔离与 WeKnora 授权融合 @@ -71,12 +73,12 @@ MNote 当前文档页 / workspace / auth / resource truth ```text MNote 登录态 / user_id / workspace grants - ├─ OpenHub 派生隔离上下文:openhub_user_key + workspace/runtime/session/skill/MCP scope + ├─ OpenHub 派生隔离上下文:openhub_user_key + workspace/runtime/session/skill/tool scope └─ WeKnora 派生知识库上下文:已授权 workspace/rootUri -> provider tenant/profile/KB/source registry ``` - **MNote 负责入口认证与授权判断**:当前用户是谁、能访问哪些 workspace/rootUri、能读写哪些 source。 -- **OpenHub 需要 per-user / per-workspace 隔离**:session、message、skill、MCP、tool permission、opencode directory、changed files 都必须绑定 MNote 用户与 workspace;不能所有 MNote 用户共用一个 OpenHub runtime identity。 +- **OpenHub 需要 per-user / per-workspace 隔离**:session、message、skill、tool permission、opencode directory、changed files 都必须绑定 MNote 用户与 workspace;MCP/tool scope 由 MNote 注册的 WeKnora/opencode 工具配置体现;不能所有 MNote 用户共用一个 OpenHub runtime identity。 - **WeKnora 是唯一知识库底座,并通过 MCP / CLI / API 暴露给 OpenHub/opencode**:它只接收 MNote 已授权 workspace 的文件/文件夹集合 ingest/search/query 或工具调用;知识库可见性本身依赖 MNote 的 source registry 和 allowed roots,WeKnora 用户认证可以保持简单。 - **WeKnora RBAC/API key 是 provider 防线**:不承担 MNote 产品层用户隔离,不把 WeKnora tenant/user 反向暴露成 MNote 登录体系。 @@ -85,18 +87,18 @@ MNote 登录态 / user_id / workspace grants OpenHub 和 WeKnora 的用户模型对 MNote 的影响不同: - OpenHub 前端会使用 `auth_token` / JWT / localStorage,并在 401 后跳转到 `/login`。 -- OpenHub 后端还有会话、消息、skill、MCP、tool/model permission、workspace path 和 Git snapshot 等用户相关状态。 +- OpenHub 后端还有会话、消息、skill、tool/model permission、workspace path 和 Git snapshot 等用户相关状态;MCP 工具面由 MNote/WeKnora/opencode 配置承接。 - WeKnora 有 tenant RBAC、Owner/Admin/Contributor/Viewer、共享空间与 API Key,但 MNote 的知识库使用场景主要来自“用户已授权 workspace/rootUri”。 - MNote 已有 SQLite control-plane auth、`mnote_session` cookie、测试账号与 local workspace 授权。 -如果直接嵌入 OpenHub 或 WeKnora Web UI,会出现三套登录入口、三套用户 id、三套权限判断;但如果把 OpenHub 也降成“无用户共享 runtime”,又会让 session、skill、MCP、工具审批和文件变更串用户。 +如果直接嵌入 OpenHub 或 WeKnora Web UI,会出现三套登录入口、三套用户 id、三套权限判断;但如果把 OpenHub 也降成“无用户共享 runtime”,又会让 session、skill、工具审批、WeKnora tool scope 和文件变更串用户。 ### 3.3 决策 - MNote 是唯一**前端登录入口**和产品层授权入口。 - OpenHub 不保留自己的 Login 页面、JWT/localStorage 登录跳转,但 MNote boundary 必须为每个 MNote 用户派生 OpenHub runtime identity。 -- OpenHub 派生 identity 至少包含:`mnote_user_id`、`workspace_id`、`root_uri`、`openhub_user_key`、`opencode_session_scope`、`skill_scope`、`mcp_scope`、`tool_permission_scope`。 -- OpenHub session/message/skill/MCP/tool permission 不得跨 `mnote_user_id + workspace_id/root_uri` 共享。 +- OpenHub 派生 identity 至少包含:`mnote_user_id`、`workspace_id`、`root_uri`、`openhub_user_key`、`opencode_session_scope`、`skill_scope`、`tool_permission_scope`、`weknora_tool_scope`。 +- OpenHub session/message/skill/tool permission 不得跨 `mnote_user_id + workspace_id/root_uri` 共享;MCP/tool 配置不得绕过 MNote 注入的 KB/source allowlist。 - WeKnora 使用 MNote 后端服务 API key 或受控 profile 调用;前端不直接持有 WeKnora API key。 - WeKnora KB/source 由 MNote 的 workspace grants、source registry、allowed roots 决定;WeKnora tenant/RBAC 只作为 provider 内部防线。 @@ -110,8 +112,8 @@ MNote user_id + workspace_id/rootUri -> OpenHub scopes: session_scope = user_id + workspace_id + rootUri + page_resource_id skill_scope = user_id + workspace_id + rootUri - mcp_scope = user_id + workspace_id + allowed_roots tool_permission_scope = user_id + workspace_id + rootUri + weknora_tool_scope = user_id + workspace_id + allowed_kb_ids/source_ids -> WeKnora provider profile: mnote-local 服务 API key -> WeKnora KB: mnote-{workspace_id}-{purpose} -> MNote source registry 记录 provider KB / knowledge / chunk 映射 @@ -128,7 +130,7 @@ MNote user_id + workspace membership -> 所有 WeKnora 结果仍由 MNote source registry / allowed roots 二次过滤 ``` -不要在第一阶段为每个 MNote 用户强行同步 WeKnora RBAC;这会放大生命周期与权限同步复杂度。相反,第一阶段应优先保证 OpenHub 派生上下文隔离,因为 Page AI 的 session、skill、MCP、工具审批和文件变更都直接依赖 MNote 登录态。 +不要在第一阶段为每个 MNote 用户强行同步 WeKnora RBAC;这会放大生命周期与权限同步复杂度。相反,第一阶段应优先保证 OpenHub 派生上下文隔离,因为 Page AI 的 session、skill、tool permission、WeKnora tool scope 和文件变更都直接依赖 MNote 登录态。 ## 4. OpenHub Session / Message 真相 @@ -144,7 +146,7 @@ MNote Page AI 面板 -> WeKnora 不参与 Page AI 会话真相 ``` -- **OpenHub session 是 Page AI session 真相**:消息、turn、tool call、skill/MCP 状态、history、retry、visible/hidden 等语义以 OpenHub conversation/session 模型为准。 +- **OpenHub session 是 Page AI session 真相**:消息、turn、tool call、skill/tool 状态、WeKnora tool call 状态、history、retry、visible/hidden 等语义以 OpenHub conversation/session 模型为准。 - **MNote 不复制消息主存储**:MNote 只需要保存 `mnote_user/workspace/rootUri/page_resource_id -> openhub_session_id/opencode_session_id` 的绑定,以及 changed file / citation 回跳所需的轻量索引。 - **WeKnora 不产生会话冲突**:当前 WeKnora 主要作为 MCP/CLI/知识库工具 provider;`knowledge-chat` / `agent-chat` 暂时弃用,不纳入 Page AI 主链,因此不设计 `knowledge_session_id`。 @@ -202,7 +204,7 @@ openhub_workspace_key = stable_hash(workspace_id, root_uri) - 同一用户同一页面可恢复最近 active OpenHub session。 - 切换 rootUri 或 workspace 必须新开 OpenHub session;旧 session 标记 stale 或 archived。 -- 不同 MNote 用户不得共享同一个 OpenHub session、skill scope、MCP scope 或 tool permission scope。 +- 不同 MNote 用户不得共享同一个 OpenHub session、skill scope、tool permission scope 或 WeKnora tool scope。 - MNote boundary 对 OpenHub session 的读写必须先校验 `mnote_session` 与 binding ownership。 ### 4.5 WeKnora session policy @@ -234,7 +236,7 @@ WeKnora 应承担 MNote 知识库 provider: - 知识库类型:以 WeKnora `KnowledgeBase.Type` 和 FAQ 配置为准;Wiki/图谱属于 WeKnora Wiki mode / graph 能力,不能未经接口枚举直接当作 KB type 写死。 - 导入:文件、URL、Markdown/手工知识、外部数据源。 - 文档处理:chunk、OCR/VLM/ASR、图谱抽取、问题生成、reparse。 -- 检索:优先对接 `POST /api/v1/knowledge-search`,或按 KB 维度对接 hybrid-search;返回分数为融合排序分(如 RRF),不能当原始相似度解释。 +- 检索:多 KB / 指定 knowledge 检索优先对接 `POST /api/v1/knowledge-search`;单 KB 调试或 CLI/MCP 可走 `POST /api/v1/knowledge-bases/:id/hybrid-search`;返回分数按排序分处理,不能按百分比或原始相似度解释。 - 问答:`POST /api/v1/knowledge-chat/:session_id`、`POST /api/v1/agent-chat/:session_id` SSE 作为后续可选 provider 能力;第一阶段 Page AI 主链暂弃用,不产生主会话真相。 - 权限:tenant RBAC / shared organization 作内部防线。 @@ -279,7 +281,7 @@ trait KnowledgeProvider { - `/api/knowledge-rag/search`:调用 WeKnora `/api/v1/knowledge-search` 或 KB 级 hybrid-search,返回 MNote `search_results.v1` 兼容结构。 - `/api/knowledge-rag/query`:不再调用 LightRAG `/query/data`;第一阶段用 WeKnora search result 生成带 citations/references 的 query result。 - `/api/knowledge-rag/section-context`:不再读 LightRAG sidecar blocks;改为基于 WeKnora chunk / source registry / 本地文件 locator 构造上下文。 -- 排序与阈值:WeKnora score 是 RRF 融合分,不能沿用 LightRAG 相似度阈值和旧 rank 解释。 +- 排序与阈值:WeKnora score 是 provider 排序/融合分,不能沿用 LightRAG 相似度阈值、百分比相似度或旧 rank 解释。 - 引用映射:WeKnora `knowledge_id` / `chunk_id` / `knowledge_base_id` 只用于回查 registry,不能直接作为 MNote 文件路径。 WeKnora search result 到 MNote registry 的最低字段映射: @@ -288,17 +290,21 @@ WeKnora search result 到 MNote registry 的最低字段映射: |---|---|---| | `id` | `providerChunkId` | WeKnora chunk id | | `knowledge_id` | `providerKnowledgeId` | 回查 registry 的主键之一 | -| `knowledge_base_id` | `providerKnowledgeBaseId` | provider KB 映射 | +| `knowledge_base_id` | `providerKnowledgeBaseId` | provider KB 映射;`knowledge-search` 与部分 CLI 输出字段覆盖不完全时从请求 scope/registry 补齐 | +| `content` / `matched_content` | `quote` / `matchedText` | citation 文本来源;FAQ/相似问命中优先保留 matched_content | | `chunk_index` | `chunkIndex` | 可用于同一 knowledge 内排序/定位 | | `start_at` / `end_at` | `providerOffsets` | 只能作为 provider 内偏移,不能直接当 Markdown 行号 | | `knowledge_filename` | `providerDisplayName` | 只用于显示,不能当本地路径真相 | | `knowledge_source` / `knowledge_channel` | `providerSourceMeta` | 用于辅助映射和审计 | +| `match_type` | `providerMatchType` | 区分 vector/keyword/hybrid/enrichment/direct 等命中渠道 | +| `parent_chunk_id` / `sub_chunk_id` | `providerChunkHierarchy` | 父子 chunk 与上下文扩展 | +| `metadata` / `chunk_metadata` / `image_info` | `providerMetadata` | 图片/VLM、FAQ、自定义元数据与定位诊断 | `sourcePath`、`lineStart`、`lineEnd`、`mnoteResourceId`、`openReference` 必须由 MNote registry / locator 派生;WeKnora 未返回时不能伪造。 ### 5.4 知识库定义、页面选择与 WeKnora Tool Bridge -这里不应设计 `OpenHub-compatible /knowledge/*`,也不应把 OpenHub `KnowledgeManager.jsx` 作为第一阶段知识库页。知识库页面建议复用 WeKnora 的 `KnowledgeBaseList.vue` / `KnowledgeBase.vue` / 上传与 processing timeline 组件,作为 MNote 知识库展示层的实现。当前边界是: +这里不应设计 `OpenHub-compatible /knowledge/*`,也不应把 OpenHub `KnowledgeManager.jsx` 作为第一阶段知识库页。知识库页面建议复用 WeKnora 的 `KnowledgeBaseList.vue` / `KnowledgeBase.vue` / `KnowledgeBaseEditorModal.vue` / 上传与 processing timeline 组件体验,作为 MNote 知识库展示层的实现。当前边界是: ```text MNote Knowledge UI(复用 WeKnora KB list/detail/upload/status 体验) @@ -306,7 +312,7 @@ MNote Knowledge UI(复用 WeKnora KB list/detail/upload/status 体验) -> WeKnora ingest / search / status / open-reference MNote Page AI / OpenHub-style session - -> opencode 按 MCP/skill/tool 规范自行决定 tool call + -> opencode 按 skill/tool/MCP 规范自行决定 tool call;MNote 只提供受限工具配置和 open-reference bridge -> 已注册的 `mnote.weknora.*` MCP/CLI/API tool -> WeKnora search/query -> tool result 回 OpenHub session @@ -316,9 +322,10 @@ MNote Page AI / OpenHub-style session - **知识库不是新的文件真相**:真相永远是 MNote 授权 rootUri 下的本地文件/文件夹/page resource;知识库是这些 source 的命名集合、索引状态和检索配置。 - **唯一知识库底座是 WeKnora**:OpenHub 自带 knowledge tables、KnowledgeManager 页面和 prompt stuffing 知识库第一阶段全部不使用。 -- **知识库页面选择 WeKnora**:复用 WeKnora 的 KB list/detail/upload/status/reparse/processing timeline 体验来替换 MNote 简陋知识库页;MNote 外壳负责登录、workspace、source registry、FileTree 灯号和 citation 回跳。 +- **知识库页面选择 WeKnora 体验,但不直接接管 MNote shell**:第一阶段采用 `MNote shell + WeKnora KB adapter`。优先抽取或复刻 WeKnora `KnowledgeBaseList.vue`、`KnowledgeBase.vue`、`KnowledgeBaseEditorModal.vue`、`knowledge-processing-timeline.vue` 的交互和数据模型;不 iframe WeKnora 全量产品前端,不暴露 WeKnora 登录/租户切换/独立文件真相。 - **OpenHub 不管理知识库页面**:OpenHub 只在对话过程中通过 MCP/CLI/API tool 调用 WeKnora,tool result 进入 OpenHub session。 - **MNote tool facade 是注册与权限边界**:OpenHub/opencode 不能直接持有 WeKnora API key,也不能绕过 MNote allowed roots / source registry;是否调用工具、如何组织 tool call 由 OpenHub/opencode 自己判断。 +- **WeKnora MCP surface 第一阶段选择 Go CLI `weknora mcp serve` 或 MNote facade 包装后的等价只读工具面**:该 surface 当前是手工维护的只读工具集,更适合先挂给 OpenHub/opencode。Python `mcp-server` 暴露 create/delete/chunk mutation 等更宽能力,第一阶段只作为参考,不默认接入。 第一阶段需要的接口不是 OpenHub-compatible knowledge API,而是三类接口: @@ -345,9 +352,34 @@ OpenHub/opencode tool facade: WeKnora 页面 adapter 可以复用 WeKnora 前端组件/交互,但数据入口必须先经过 MNote source registry 与 allowed roots;`mnote.weknora.*` 可以底层走 WeKnora MCP、CLI 或 HTTP API,但对 OpenHub 暴露的合同必须是 MNote 权限过滤后的 tool contract。 +推荐页面复用方式: + +1. **首选:MNote adapter 页面复刻 WeKnora 知识库体验**。在 MNote 前端保留统一路由、auth、workspace 和 FileTree/resource picker;后端通过 MNote canonical API 转接 WeKnora KB/doc/status/search。成本高于 iframe,但冲突最少。 +2. **可选:抽取 WeKnora Vue 组件进入独立 adapter bundle**。仅抽知识库列表、详情、上传、状态时间线组件;替换其 auth/client/router/store,数据仍走 MNote 后端。 +3. **不选:iframe WeKnora 全量前端**。会带来 WeKnora 登录、租户切换、独立上传文件真相和打开页面冲突,只能作为调试入口,不作为产品主线。 + +知识库最小数据模型应分成 `mnote_knowledge_bases` 与 `mnote_knowledge_sources` 两层: + +```text +mnote_knowledge_bases + id + user_id + workspace_id + root_uri + name + description + provider = weknora + provider_kb_id + default_tool_enabled + metadata_json + created_at / updated_at / archived_at +``` + +`mnote_knowledge_bases` 是用户可见的知识库集合;`mnote_knowledge_sources` 是 source 到 provider knowledge/chunk 的映射。删除 source 只删除索引和映射,不删除本地原文件;删除 KB 默认只归档 MNote KB 与删除/禁用对应 WeKnora provider KB,不能批量删除 MNote 本地文件。 + ### 5.5 Source Registry provider-neutral 化 -旧 LightRAG 字段应迁移为: +旧 LightRAG 字段应迁移为 provider-neutral registry;当前 JSON 文件 `lightrag-source-registry.json` 中的 `lightRagDocId/lightRagStatus/lightRagFilePath/symlinkPath` 等字段必须兼容读取并迁移或双写一段时间。目标结构为: ```text mnote_knowledge_sources @@ -383,7 +415,7 @@ mnote_knowledge_sources - OpenHub 第一阶段只借用多用户 AI 能力和 AI 页面产品参考,不使用 OpenHub FileManager 或完整前端。 - WeKnora 只暴露 MCP/CLI/API 知识工具,返回 knowledge/chunk/reference,不参与页面打开。 -因此这里不需要做 OpenHub FileManager、WeKnora Web UI 或 provider path 的打开融合;只需要保证 MNote Page AI 回复中的 changed file、diff、citation、tool result 能映射回 MNote 页面。 +因此这里不需要做 OpenHub FileManager、WeKnora 全量 Web UI 或 provider path 的打开融合;只需要保证 MNote Page AI 回复中的 changed file、diff、citation、tool result 能映射回 MNote 页面。注意:这里的“不使用 WeKnora Web UI”只针对 Page AI 对话壳、引用打开链路和文件打开链路;知识库管理页面仍按 5.4 复用 WeKnora KB 页面体验,但必须运行在 MNote shell 与 MNote 权限/source registry 后面。 ### 6.2 决策 @@ -394,7 +426,7 @@ mnote_knowledge_sources - WeKnora citation / MCP/CLI tool result 点击:`/api/knowledge-rag/open-reference` 返回 MNote locator,再由 MNote 前端打开。 - OpenHub session 中出现的 path/diff/tool reference 只作为数据来源,渲染和点击由 MNote Page AI 处理。 - 不接 OpenHub FileManager,不使用 OpenHub `/api/files` 作为页面读写入口。 -- 不嵌 WeKnora Web UI,不让 WeKnora 决定打开哪个 MNote 页面。 +- 不在 Page AI / 引用打开链路嵌入 WeKnora 全量 Web UI,不让 WeKnora 决定打开哪个 MNote 页面。 ### 6.3 MNote Page AI open reference payload @@ -428,15 +460,16 @@ mnote_knowledge_sources ```text MNote Page AI host -> MNote 校验登录态、workspace、rootUri、allowed roots - -> 注入/映射 OpenHub user workspace + session/skill/MCP scope + -> 注入/映射 OpenHub user workspace + session/skill/tool scope -> OpenHub AI UI - -> OpenHub FastAPI + Redis/session/cache + -> OpenHub FastAPI + SQLite session/message + Redis cache -> OpenHub opencode client -> opencode serve /global/event /session/{id}/prompt_async /diff ``` -- **OpenHub FastAPI 继续运行**:负责 AI session/message、skill/MCP、tool permission、opencode client、event stream、diff/changed files 等 OpenHub 原生能力。 -- **Redis 跟随 OpenHub stack**:如果 OpenHub 用 Redis 做 cache/queue/session 辅助,就由 OpenHub stack 管理;MNote 不把 Redis 当自己的消息、权限、文件或知识库真相。 +- **OpenHub FastAPI 继续运行**:负责 AI session/message、skill、tool permission、opencode client、event stream、diff/changed files 等 OpenHub 原生能力。 +- **不要把待补能力写成已存在能力**:OpenHub 源码已确认有 session/message、skill、tool/model permission、SmartEntity、Git snapshot、FileManager、KnowledgeManager 和 opencode event/diff 链路;未确认独立 MCP 管理 API/UI。MCP 第一阶段应由 MNote 注册 WeKnora CLI/MCP/API tool 到 OpenHub/opencode,而不是假设 OpenHub 已有完整 MCP 管理面。 +- **Redis 跟随 OpenHub stack**:OpenHub Redis 主要用于 token/rate-limit/cache/临时态;OpenHub session/message 真相仍在 SQLite。MNote 不把 Redis 当自己的消息、权限、文件或知识库真相。 - **MNote 不重写 planner/client**:MNote 不替 OpenHub 判断何时调用 WeKnora,也不重写 opencode prompt/event/diff 流程。 - **MNote 只裁剪不用入口**:OpenHub Login、FileManager、KnowledgeManager、Admin 等页面不暴露;必要时在 proxy 层 404、隐藏菜单或跳转到 MNote 对应页面。 @@ -445,10 +478,10 @@ MNote Page AI host OpenHub FastAPI 在本设计中保留以下作用: - 管理 OpenHub conversation/session/message/history。 -- 管理 skill、agent/MCP、tool/model permission。 +- 管理 skill、agent/SmartEntity、tool/model permission。 - 调用 `opencode serve`,包括创建 session、发送 prompt、监听 `/global/event`、读取 diff。 - 向 OpenHub AI 前端提供消息流、tool card、diff、changed files、history 等 API。 -- 读取由 MNote 注入的 workspace directory、user scope、allowed roots、WeKnora MCP/CLI/skill 配置。 +- 读取由 MNote 注入的 workspace directory、user scope、allowed roots、WeKnora CLI/MCP/API tool 配置。 OpenHub FastAPI 不保留以下作用: @@ -460,10 +493,11 @@ OpenHub FastAPI 不保留以下作用: ### 7.3 Redis 安排 -- OpenHub 需要的 Redis/cache/queue 由 OpenHub deployment 管理,随 OpenHub FastAPI 启停和 health check。 -- WeKnora 如果有内部 Redis/队列依赖,由 WeKnora stack 管理,MNote 只检查 WeKnora health。 +- OpenHub 需要的 Redis/cache/临时态由 OpenHub deployment 管理,随 OpenHub FastAPI 启停和 health check;OpenHub `REDIS_HOST/REDIS_PORT/REDIS_DB` 应独立配置,避免误连 WeKnora Redis DB。 +- WeKnora 有自己的 Redis/Asynq/Langfuse 相关依赖,由 WeKnora stack 管理;MNote 只检查 WeKnora health、必要 API 与 `weknora mcp serve`/CLI profile 是否可用。 - MNote control-plane 不依赖 Redis 保存登录授权、workspace grants、source registry、open-reference 或文件真相。 - Redis 中只允许放可重建状态:缓存、队列、临时流状态;不能成为用户权限、文件内容、知识库 source registry 或 Page AI 消息的唯一持久真相。 +- 本机开发阶段允许共用一个 Redis 进程,但必须使用独立 DB/前缀:OpenHub、WeKnora、Langfuse/worker queue 不得混用 keyspace;`dev-hot` health 输出应显示各自 Redis 连接目标。 ### 7.4 Page AI context @@ -475,7 +509,7 @@ MNote 注入给 OpenHub / opencode 的 context: - 当前页面标题、真实 Markdown path、resource id - selection 摘要 - allowed roots / write constraints -- OpenHub session scope / skill scope / MCP scope / tool permission scope +- OpenHub session scope / skill scope / tool permission scope - WeKnora tool scope:允许查询的 kb ids/source ids/citation policy - MNote 文件打开/刷新 bridge usage ``` @@ -489,8 +523,8 @@ MNote 注入给 OpenHub / opencode 的 context: MNote sidebar host 的职责应尽量薄:承载 OpenHub AI 面板、注入 MNote context、处理 MNote 回跳,不重新设计一套顶部/侧边栏产品结构。 - 顶部:第一阶段可以不要 MNote 自定义顶部;如需状态,只做极简 host 状态条或错误提示,避免覆盖 OpenHub AI 面板原有布局。 -- 主体:OpenHub AI 面板,由 OpenHub 前端/后端处理消息、tool card、diff、history、agent/MCP/skill 状态。 -- 侧边:优先保留 OpenHub AI 相关侧边能力,包括历史 session、MCP、skill、agent、tool/model permission 等设置。 +- 主体:OpenHub AI 面板,由 OpenHub 前端/后端处理消息、tool card、diff、history、agent/skill/tool 状态。 +- 侧边:优先保留 OpenHub AI 相关侧边能力,包括历史 session、skill、agent/SmartEntity、tool/model permission 等设置;MCP 若当前 OpenHub 前端没有原生管理页,第一阶段显示 WeKnora MCP/CLI tool 连接状态与 MNote scope,而不是重写完整 MCP 管理器。 - MNote context:以 context pills / hidden bootstrap / postMessage / proxy header 方式注入当前 page path、rootUri、selection、allowed roots,不强行改 OpenHub UI 主结构。 - 回跳:changed file、citation、reference 点击时走 MNote bridge 打开页面。 @@ -502,8 +536,8 @@ MNote sidebar host 的职责应尽量薄:承载 OpenHub AI 面板、注入 MNo - AI chat 主界面。 - history / session 抽屉。 -- MCP 设置与连接状态。 - skill / agent 设置。 +- WeKnora CLI/MCP tool 的连接状态;若 OpenHub 无原生 MCP 设置 UI,则通过 MNote host 或 OpenHub 最小扩展显示,不阻塞 AI 主界面。 - tool/model permission UI。 - tool card、diff、changed files、运行日志等 AI 运行态 UI。 @@ -518,12 +552,12 @@ MNote sidebar host 的职责应尽量薄:承载 OpenHub AI 面板、注入 MNo ### 8.3 MNote 暴露给 OpenHub 的边界能力 -MNote 不替 OpenHub 判断何时调用知识库、何时用 skill/MCP、如何组织 tool call;这些交给 OpenHub/opencode 已有 MCP/skill/agent 规范处理。MNote 只提供最小边界能力: +MNote 不替 OpenHub 判断何时调用知识库、何时用 skill/tool/MCP 工具、如何组织 tool call;这些交给 OpenHub/opencode 已有 skill/tool/agent 规范处理,并由 MNote 注册的 WeKnora CLI/MCP/API tool 提供知识能力。MNote 只提供最小边界能力: ```text - 当前页面 context:page path / title / selection / rootUri / allowed roots -- OpenHub scope:user/workspace/rootUri 派生的 session/skill/MCP/tool permission scope -- WeKnora MCP/CLI/API 配置:以 skill/MCP/tool 形式注册给 OpenHub/opencode +- OpenHub scope:user/workspace/rootUri 派生的 session/skill/tool permission scope +- WeKnora CLI/MCP/API 配置:以 skill/tool/MCP 工具形式注册给 OpenHub/opencode - open-reference bridge:把 provider citation/chunk/source 映射成 MNote 页面打开动作 - changed-file bridge:把 OpenHub/opencode 返回的 path 映射成 MNote document pane 打开/刷新 ``` @@ -538,7 +572,7 @@ MNote 不替 OpenHub 判断何时调用知识库、何时用 skill/MCP、如何 用户输入 -> MNote Page AI host 中的 OpenHub AI 面板 -> OpenHub 前端调用 OpenHub FastAPI - -> OpenHub FastAPI 使用 OpenHub session/message/skill/MCP/tool permission + -> OpenHub FastAPI 使用 OpenHub session/message/skill/tool permission 与 MNote 注册的 WeKnora tool scope -> OpenHub FastAPI 调 opencode serve -> opencode 读写 MNote 授权 rootUri 内文件 -> OpenHub session/message/tool history 持久化 @@ -552,14 +586,14 @@ MNote 不在消息主链里重写 OpenHub planner,也不解析知识需求后 ```text OpenHub/opencode 判断需要知识 - -> 按 MCP/skill/tool 规范调用已注册的 WeKnora 工具 + -> 按 skill/tool/MCP 规范调用已注册的 WeKnora 工具 -> WeKnora MCP/CLI/API 返回 chunks/references -> OpenHub/opencode 把结果纳入当前 session/tool result -> 用户点击 citation/reference 时 -> MNote open-reference bridge 按 source registry / allowed roots 映射并打开对应页面 ``` -MNote 不负责替 OpenHub 判断 knowledge scope;scope 在注册 WeKnora MCP/skill/tool 时由 MNote 根据当前用户、workspace、allowed roots 预先约束。 +MNote 不负责替 OpenHub 判断 knowledge scope;scope 在注册 WeKnora CLI/MCP/skill/tool 时由 MNote 根据当前用户、workspace、allowed roots 预先约束。 ### 9.3 知识库生成与展示 @@ -570,27 +604,38 @@ MNote 知识库展示层上传/添加资料 -> WeKnora file/manual/url ingest -> 写 mnote_knowledge_sources registry -> FileTree/Knowledge UI 显示 indexing 状态 - -> 生成/更新可供 OpenHub/opencode 使用的 WeKnora MCP/skill/tool 配置 + -> 生成/更新可供 OpenHub/opencode 使用的 WeKnora CLI/MCP/skill/tool 配置 ``` OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边界配置好的 WeKnora 工具。知识库生成页面复用 WeKnora KB 页面体验,但 source 选择应以 MNote 本地文件/文件夹集合为入口。 ## 10. 可执行 Checklist +本节保留主线阶段清单;逐文件、逐接口、逐 smoke 的细化执行项见 `design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md`。实施时先完成配套清单 P0/P1,再回填本文状态;不要把 P2/P3 优化提前混入 MVP。 + + ### 10.1 设计与旧路径冻结 -- [ ] 在 `7-65` 标注官方 opencode iframe 只保留为 fallback,不再作为 Page AI 产品主线。 -- [ ] 在 `7-66` 标注自研 native UI 只保留为 fallback,不再继续扩自研聊天框。 -- [ ] 在 `7-67` 标注已被本文覆盖:OpenHub 路线从“参考/重写”改为“嵌入 AI 面板 + 保留 FastAPI/Redis/opencode client”。 -- [ ] 将本文保留在 `design/07-ai/process/`,作为当前 Page AI 嵌入式集成主设计。 -- [ ] 在相关 design / bugs / testing 文档中统一术语:WeKnora 是唯一知识库底座,MNote 是知识库展示和文件真相层,OpenHub KnowledgeManager 不作为知识库页。 -- [ ] 搜索并标记仍把 LightRAG 描述为默认知识库 provider 的文案,改成 legacy/fallback。 +- [x] 在 `7-65` 标注官方 opencode iframe 只保留为 fallback,不再作为 Page AI 产品主线。 +- [x] 在 `7-66` 标注自研 native UI 只保留为 fallback,不再继续扩自研聊天框。 +- [x] 在 `7-67` 标注已被本文覆盖:OpenHub 路线从“参考/重写”改为“嵌入 AI 面板 + 保留 FastAPI/Redis/opencode client”。 +- [x] 将本文保留在 `design/07-ai/process/`,作为当前 Page AI 嵌入式集成主设计。 +- [x] 在本轮授权写入范围内统一术语:WeKnora 是唯一知识库底座,MNote 是知识库展示和文件真相层,OpenHub KnowledgeManager 不作为知识库页;bugs/testing 文档未在本任务授权写入范围内修改。 +- [x] 在本轮授权写入范围内搜索并标记仍把 LightRAG 描述为默认知识库 provider 的文案,改成 legacy/fallback。 + +#### 10.1 证据 + +- `design/07-ai/process/7-65-opencode-webui-embed-page-ai-v1.md`:已冻结为官方 opencode WebUI iframe fallback;上下文口径从 LightRAG 引用改为 WeKnora 引用,旧 LightRAG 仅 legacy/fallback。 +- `design/07-ai/process/7-66-opencode-native-page-ai-ui-v1.md`:已冻结为 native UI fallback / debug receipt,不再扩写自研聊天框。 +- `design/07-ai/process/7-67-openhub-page-ai-fusion-v1.md`:已标注被本文覆盖,路线改为嵌入 OpenHub AI 面板并保留 OpenHub FastAPI/Redis/opencode client。 +- `design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-v1.md`:保留在 `process/`,作为当前 Page AI 嵌入式集成主设计;10.2-10.9 后续执行细化到 `design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md`。 ### 10.2 OpenHub 服务栈接入 - [ ] 确认 OpenHub 本机源码路径、启动命令、依赖文件和默认端口。 -- [ ] 确认 OpenHub FastAPI 是否真实依赖 Redis;记录 Redis host/port/env 和启动顺序。 +- [ ] 确认 OpenHub FastAPI Redis 的真实用途:token/rate-limit/cache/临时态;记录 Redis host/port/db/env 和启动顺序,避免写成消息真相或队列主链。 - [ ] 确认 OpenHub FastAPI 调 opencode 的配置项:opencode base URL、directory 参数、BasicAuth、模型/provider env。 +- [ ] 确认 OpenHub 自动 Git snapshot 的触发点,并通过配置/补丁关闭 `stream.py`、`task_executor.py` 中的自动 snapshot 写链。 - [ ] 在 `scripts/desktop-hot.js` 或等价 dev-hot 链路中增加 OpenHub FastAPI、Redis、opencode serve 的启动/跳过/health check。 - [ ] 增加 OpenHub health endpoint 探针;失败时错误信息区分 FastAPI、Redis、opencode。 - [ ] 禁用 OpenHub launcher 的 kill-port 或 destructive workspace 行为,避免影响 MNote dev 进程。 @@ -605,7 +650,7 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 - [ ] 在 MNote 后端实现或扩展 OpenHub host/proxy bootstrap endpoint,输出 user/workspace/session/tool scope。 - [ ] 禁止前端持有 OpenHub JWT/localStorage 登录真相;OpenHub 用户态由 MNote 后端注入或代理。 - [ ] rootUri / workspace 切换时新建或切换 OpenHub session,旧 session 标记 stale/archived。 -- [ ] 不同 MNote 用户访问同一页面时不得复用同一 OpenHub session、skill scope、MCP scope、tool permission scope。 +- [ ] 不同 MNote 用户访问同一页面时不得复用同一 OpenHub session、skill scope、tool permission scope 或 WeKnora tool scope。 - [ ] 写 control-plane 测试:binding 受 user/workspace/rootUri 隔离,跨用户查询失败。 ### 10.4 OpenHub AI 面板嵌入 @@ -613,7 +658,7 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 - [ ] 在 MNote Page AI sidebar host 中选择 iframe 或 reverse proxy 嵌入方式。 - [ ] 只暴露 OpenHub AI 页面路由;Login/Admin/Team/Scheduler/SmartEntity 默认不可达。 - [ ] OpenHub workspace selector 固定到 MNote 授权 rootUri 派生 workspace。 -- [ ] 保留 OpenHub AI 主界面、history/session、MCP 设置、skill/agent 设置、tool/model permission、tool card、diff、changed files、运行日志。 +- [ ] 保留 OpenHub AI 主界面、history/session、skill/agent 设置、tool/model permission、tool card、diff、changed files、运行日志;MCP 管理面如源码不存在,不把它作为已完成 OpenHub UI 依赖。 - [ ] FileManager 入口若出现在 AI 面板内,跳转 MNote document pane / FileTree 或禁用。 - [ ] KnowledgeManager 入口若出现在 AI 面板内,跳转 MNote WeKnora 知识库页或隐藏。 - [ ] MNote context 通过 postMessage、proxy header 或 bootstrap JSON 注入 page path、title、selection、rootUri、allowed roots。 @@ -634,7 +679,7 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 ### 10.6 WeKnora 知识库页面替换 - [ ] 盘点 WeKnora `KnowledgeBaseList.vue`、`KnowledgeBase.vue`、`KnowledgeBaseEditorModal.vue`、`knowledge-processing-timeline.vue` 的依赖。 -- [ ] 决定复用方式:嵌 WeKnora frontend route、抽组件、或做 MNote adapter 页面复刻 WeKnora 交互。 +- [ ] 决定复用方式:优先 MNote adapter 页面复刻 WeKnora 交互;可选抽组件;不采用 iframe WeKnora 全量 frontend route 作为产品主线。 - [ ] 知识库定义为 MNote 授权文件/文件夹/page resource 集合,不创建新的文件内容真相。 - [ ] 建立 source set 模型:kb id、workspace id、rootUri、source path/resource id、provider kb id、provider knowledge id、source hash。 - [ ] source 选择 UI 接 MNote FileTree / resource picker,而不是 WeKnora 自己的独立文件真相。 @@ -648,10 +693,10 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 - [ ] 抽出 `KnowledgeProvider` 或等价 provider boundary,新增 `weknora` 实现。 - [ ] `/api/knowledge-rag/status` 改为检查 WeKnora health、KB 映射、source registry 状态。 - [ ] `/api/knowledge-rag/ingest` 改为 WeKnora ingest,并写入 provider KB / knowledge / source hash。 -- [ ] `/api/knowledge-rag/search` 改为 WeKnora `/api/v1/knowledge-search` 或 KB 级 hybrid-search。 +- [ ] `/api/knowledge-rag/search` 改为 WeKnora `/api/v1/knowledge-search`;单 KB 调试/CLI/MCP 可走 `/api/v1/knowledge-bases/:id/hybrid-search`。 - [ ] `/api/knowledge-rag/query` 第一阶段由 WeKnora search results + citations 组成 query result,不启用 WeKnora chat session。 - [ ] `/api/knowledge-rag/section-context` 改为基于 WeKnora chunk + MNote 本地 locator,不读 LightRAG sidecar。 -- [ ] `open-reference` 从 WeKnora `knowledge_id/chunk_id/knowledge_base_id` 回查 MNote registry,再生成 MNote locator。 +- [ ] `open-reference` 从 WeKnora `knowledge_id/chunk_id/knowledge_base_id` 回查 MNote registry,再生成 MNote locator;保留 `content/matched_content/match_type/metadata/chunk_metadata/image_info/parent_chunk_id/sub_chunk_id` 供 citation 与诊断。 - [ ] WeKnora RRF score 不沿用 LightRAG 阈值;UI 只显示排序分或弱化分值解释。 - [ ] 保留旧 LightRAG provider 为 legacy fallback,但默认隐藏且不作为 smoke 基线。 - [ ] 更新现有 knowledge-rag smoke,把 provider 断言从 `lightrag` 改为 `weknora`。 @@ -659,7 +704,7 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 ### 10.8 WeKnora MCP/CLI Tool Bridge -- [ ] 选定第一阶段 MCP surface:Go CLI `weknora mcp serve` 或 Python `mcp-server`,记录读写能力差异。 +- [ ] 第一阶段 MCP surface 选定 Go CLI `weknora mcp serve` 或 MNote facade 包装后的等价只读工具面;Python `mcp-server` 只作为后续写能力参考,不默认暴露给 OpenHub/opencode。 - [ ] 默认暴露只读工具:`mnote.weknora.search`、`mnote.weknora.list_sources`、`mnote.weknora.get_source_status`、`mnote.weknora.open_reference`。 - [ ] 写工具 manifest / skill,使 OpenHub/opencode 能在当前 session scope 内调用 WeKnora。 - [ ] tool 调用前注入 KB/source allowlist,不让 OpenHub/opencode 查询未授权 source。 @@ -672,7 +717,7 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 - [ ] `npm run dev:hot` 启动或检查 MNote、OpenHub FastAPI、Redis、opencode、WeKnora。 - [ ] 登录 `mnote.e2e@example.com`,确认没有 OpenHub/WeKnora 登录跳转。 - [ ] 打开真实 Markdown 页面并打开 Page AI。 -- [ ] Page AI 嵌入 OpenHub AI 面板,history/session/MCP/skill/agent/tool permission 入口仍可用。 +- [ ] Page AI 嵌入 OpenHub AI 面板,history/session/skill/agent/tool permission 入口仍可用;WeKnora MCP/CLI tool 状态可见。 - [ ] OpenHub Login/Admin/FileManager/KnowledgeManager 非 AI 入口被隐藏、404 或跳转 MNote。 - [ ] 发送消息后 OpenHub session/message/history 持久化,刷新浏览器可恢复。 - [ ] 让 AI 修改当前 rootUri 内 Markdown,MNote document pane 可刷新并显示变更。 @@ -690,8 +735,8 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 | 知识库 UI | OpenHub KnowledgeManager 暂不使用 | WeKnora KB list/detail/upload/status 体验可复用 | MNote shell + WeKnora KB UI adapter | 选 WeKnora 知识库页面体验,嵌入 MNote 外壳 | | 知识库底座 | OpenHub 自带知识库暂不用 | 强 RAG/Wiki/Graph | MNote registry + WeKnora | 选 WeKnora | | 文件打开 | workspace path | knowledge filename | MNote resource/open-reference | 选 MNote | -| 权限 | 模型/工具/skill/MCP | tenant RBAC | MNote scope + OpenHub/opencode 权限规范 + provider 防线 | 组合,以 MNote scope 为边界 | -| Agent 编辑 | opencode | WeKnora agent-chat 暂弃用 | opencode 编辑 + WeKnora MCP/CLI/API 知识工具 | 组合 | +| 权限 | 模型/工具/skill;MCP 由工具配置承接 | tenant RBAC | MNote scope + OpenHub/opencode 权限规范 + provider 防线 | 组合,以 MNote scope 为边界 | +| Agent 编辑 | opencode | WeKnora agent-chat 暂弃用 | opencode 编辑 + WeKnora CLI/MCP/API 知识工具 | 组合 | | UI 成本 | 嵌入 AI 面板,裁剪非 AI 入口 | 不适合 Page AI 编辑侧栏 | MNote host + OpenHub AI 面板 | 选 OpenHub AI 面板嵌入 | | 运维 | OpenHub FastAPI + Redis + opencode 运行 | WeKnora 服务 | MNote 启动/检查 OpenHub/opencode/Redis/WeKnora,提供 binding/tool 配置 | 选 OpenHub 栈运行 + MNote 边界控制 | @@ -699,7 +744,7 @@ OpenHub 不管理知识库生成页面;它只消费已经按 MNote 授权边 ### 12.1 风险:三套权限漂移 -防线:MNote 是浏览器入口和授权边界;OpenHub/opencode 只能拿到 MNote 派生 scope、allowed roots、tool/MCP 配置,不直接获得未过滤 workspace/rootUri。 +防线:MNote 是浏览器入口和授权边界;OpenHub/opencode 只能拿到 MNote 派生 scope、allowed roots、tool/CLI/MCP 配置,不直接获得未过滤 workspace/rootUri。 补充:WeKnora RBAC 只能作为 provider 防线,不作为 MNote 授权来源;RBAC 关闭、API key 复用或共享空间变化时,MNote 过滤结果仍必须保持一致。 @@ -736,16 +781,27 @@ MVP 通过必须同时满足: - 同一 MNote 用户跨浏览器可恢复绑定到当前页面/rootUri 的 OpenHub session。 - opencode 在当前 rootUri 下能真实读取/修改 Markdown。 - changed file chips 与 DiffViewer path 能用 MNote 打开。 +- MNote 知识库页面已由 `MNote shell + WeKnora KB adapter` 替换简陋页:可创建/列出/查看 KB,可从 MNote FileTree/resource picker 添加本地文件或文件夹,可显示 WeKnora processing/indexed/failed 状态。 +- `/api/knowledge-rag/status/search/query/section-context/open-reference/delete-source` 默认 provider 为 `weknora`;旧 LightRAG 只作为 `lightrag_legacy` 隐藏 fallback,不再作为默认 smoke 基线。 - WeKnora search/MCP/CLI 工具返回真实引用,citation 经 registry 映射后可回跳 MNote 文件;失败时明确定位降级,且不能把 `knowledge_filename` 伪装成本地路径。 +- OpenHub/opencode 通过 MNote 注册的 WeKnora tool scope 调用知识库;MNote 不替 OpenHub 做 planner 判断,只限制 KB/source allowlist 与 open-reference。 - `npm run dev:hot` 能拉起并检查必要 runtime;失败时错误页给出 OpenHub FastAPI、Redis、opencode、WeKnora 或 OpenHub session binding 哪个不可达。 - MNote Page AI 路径不触发 OpenHub 登录跳转;OpenHub FileManager/KnowledgeManager/Admin 等非 AI 入口被隐藏、404 或跳转 MNote;消息真相保留在受 MNote scope 隔离的 OpenHub session 中,不触发 Git snapshot/restore 自动写链。 ## 14. 本轮源码依据 -- OpenHub 源码:`/tmp/mnote-openhub-research/OpenHub` +- OpenHub 源码:`/tmp/mnote-openhub-research/OpenHub`(当前无 `.codegraph/`,本轮以 targeted `find/rg/sed` 复核) - WeKnora 本机部署:`/mnt/Data1T/Mnote_data/weknora/WeKnora` - MNote Page AI runtime:`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` - MNote document pane bridge:`rust/crates/mnote-web/browser/document-editor-adapter-runtime.js` - MNote opencode route:`rust/crates/mnote-web/src/routes/page_ai_opencode.rs` - MNote knowledge route:`rust/crates/mnote-web/src/routes/knowledge_rag.rs` - MNote auth/session route:`rust/crates/mnote-web/src/routes/session.rs`、`rust/crates/mnote-web/src/routes/gateway.rs` + +- OpenHub FastAPI 入口:`/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/main.py` +- OpenHub opencode client / launcher:`/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/opencode_client.py`、`/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/opencode_launcher.py` +- OpenHub streaming / Git snapshot 触发:`/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/stream.py`、`/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/services/task_executor.py` +- OpenHub session/auth/files/knowledge/admin:`/tmp/mnote-openhub-research/OpenHub/smart-query-backend/app/api/session.py`、`auth.py`、`files.py`、`knowledge.py`、`admin.py` +- WeKnora SearchResult / hybrid-search client:`/mnt/Data1T/Mnote_data/weknora/WeKnora/client/knowledgebase.go` +- WeKnora MCP CLI:`/mnt/Data1T/Mnote_data/weknora/WeKnora/cli/internal/mcp/tools.go`、`/mnt/Data1T/Mnote_data/weknora/WeKnora/cli/cmd/mcp/serve.go` +- WeKnora KB UI:`/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/` diff --git a/design/07-ai/reference/7-68-weknora-kb-page-ui-reference-v1.md b/design/07-ai/reference/7-68-weknora-kb-page-ui-reference-v1.md new file mode 100644 index 00000000..f2eeeddf --- /dev/null +++ b/design/07-ai/reference/7-68-weknora-kb-page-ui-reference-v1.md @@ -0,0 +1,170 @@ +# 7-68 WeKnora 知识库页面 UI 参考 + +状态:reference +范围:只记录 WeKnora 知识库页面可嫁接的页面结构、状态与 MNote 字段映射;不修改 MNote runtime,不修改 WeKnora 源码。 + +## 结论 + +7-68 的知识库页不应继续保留 MNote 原来的简陋“知识库设置面板”形态。更合适的嫁接目标是复用 WeKnora 的“知识库列表 + 知识库详情文档管理页”体验:列表页提供知识库卡片、分组、上传进度和能力徽标;详情页提供面包屑、文档/ Wiki / 图谱页签、标签侧栏、文档筛选栏、网格/列表切换、上传入口、解析状态和批量管理。 + +MNote 应保留本地 source registry、allowed roots、workspace path、provider 映射和打开引用能力;WeKnora 页面结构只作为 UI / 交互参考,不直接接管 MNote 的事实源、鉴权、文件写入或索引生命周期。 + +## WeKnora 源文件 + +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/KnowledgeBaseList.vue` + - 知识库列表页主入口。 + - 核心结构:`kb-list-container`、`ListSpaceSidebar`、标题区、未初始化提示、上传进度面板、知识库卡片网格、分组标题、收藏 / 置顶 / 设置 / 删除菜单、能力徽标。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/KnowledgeBase.vue` + - 单个知识库详情页主入口。 + - 核心结构:`knowledge-layout`、面包屑 + `KBSwitcherDropdown`、`KBInfoPopover`、设置按钮、Documents / Wiki / Graph 页签、标签侧栏、文档筛选栏、文档卡片网格、列表视图、`DocContent` 抽屉。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/components/DocumentListView.vue` + - 文档列表视图。 + - 核心结构:吸顶表头、复选框、多列 row、来源、大小、解析状态、更新时间、行菜单。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/components/KbUploadSourceDropdown.vue` + - 添加文档入口。 + - 支持上传文件、上传文件夹、导入 URL,可选手动创建。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/views/knowledge/components/DocumentBatchBar.vue` + - 批量选择后的浮动操作条。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/components/knowledge-processing-timeline.vue` + - 解析 / 后处理 trace 时间线。 + - 显示 docreader、chunking、embedding、multimodal、postprocess 等阶段,以及 live polling、失败、取消和耗时。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/components/empty-knowledge.vue` + - 空知识库占位。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/api/knowledge-base/index.ts` + - API 字段参考:`listKnowledgeBases`、`getKnowledgeBaseById`、`uploadKnowledgeFile`、`createKnowledgeFromURL`、`listKnowledgeFiles`、`reparseKnowledge`、`cancelKnowledgeParse`、`batchDeleteKnowledge`、`getKnowledgeSpans`。 +- `/mnt/Data1T/Mnote_data/weknora/WeKnora/frontend/src/i18n/locales/zh-CN.ts` + - 中文文案参考:`knowledgeBase`、`knowledgeList`、`knowledgeEditor`。 + +## 可嫁接页面结构 + +### 1. 知识库列表 + +最小结构: + +- 左侧范围筛选:全部 / 我的 / 收藏 / 最近 / 空间,MNote 首版可降级为全部 + 当前 workspace。 +- 顶部标题:`知识库`,副标题采用 WeKnora 语义“管理和组织您的知识库,支持文档型和问答型知识库”。 +- 新建按钮:图标按钮或小按钮,接 MNote 创建 KB / 绑定本地 folder 的流程。 +- 全局状态提示: + - 未初始化 / provider 未连接提示。 + - 上传或索引中的 progress panel。 +- 卡片网格: + - 标题、描述。 + - 文档数量 / FAQ 数量 / processing loading。 + - 能力徽标:知识图谱、多模态、问题生成、共享。 + - 卡片菜单:置顶、设置、删除。 + - hover 显示边框与更多按钮。 + +MNote 首版不需要完整照搬 WeKnora 的组织共享分组,但应保留分组能力的视觉槽位,避免后续 workspace / shared KB 接入时重做页面。 + +### 2. 知识库详情 + +最小结构: + +- 面包屑:`知识库 / 当前知识库 / 文档`。 +- 当前 KB 下拉切换:参考 `KBSwitcherDropdown`,MNote 可映射为当前 workspace 下 KB registry。 +- 右侧动作:信息 popover、设置按钮。 +- 文档页签: + - `文档` 为默认主视图。 + - 若 provider 暴露 wiki / graph 能力,再显示 `Wiki`、`图谱` 页签。 +- 左侧标签侧栏: + - `文档分类` 标题、数量、搜索、标签列表、新建 / 重命名 / 删除。 + - MNote 首版可先映射为 source kind / folder / tag,不能伪造 WeKnora 后端 tag 写入。 +- 主内容筛选栏: + - 搜索文档。 + - 文件类型筛选。 + - 解析状态筛选。 + - 来源筛选。 + - 更新时间范围。 + - 网格 / 列表切换。 + - 添加文档入口。 +- 文档区: + - skeleton loading。 + - grid 卡片作为默认视图。 + - list 作为密集视图。 + - 空状态。 + - 批量选择浮动条。 +- 文档详情: + - 点击文档打开 `DocContent` 风格抽屉,展示全文、分块、原文件 / 引用。 + +### 3. 上传与导入 + +参考 `KbUploadSourceDropdown.vue` 的四类入口: + +- 上传文档。 +- 上传文件夹。 +- 导入网页 URL。 +- 手动创建。 + +MNote 映射时应改成: + +- 上传文档 / 文件夹:落到 MNote local-first source registry,记录 local path / providerKnowledgeBaseId / providerKnowledgeId,再交 WeKnora provider ingest。 +- 导入 URL:可作为 provider source,不对应本地文件。 +- 手动创建:如果没有明确的 MNote 文档创建语义,首版不启用,避免产生第二套正文事实源。 + +### 4. 状态与交互 + +WeKnora 的文档状态结构可直接作为 MNote UI 状态模型参考: + +- `pending` / `processing`:显示 loading、解析中,可打开 trace。 +- `finalizing`:主解析完成但 summary / question / graph 等后处理仍在跑。 +- `failed`:红色失败状态,可重建 / 查看 trace。 +- `cancelled`:黄色取消状态。 +- `draft`:草稿。 +- `completed`:绿色完成。 +- `completed + summary pending/processing`:显示“生成摘要中”。 + +重要交互: + +- 卡片和列表都能打开文档详情。 +- 更多菜单提供重建、取消解析、移动、批量管理、删除。 +- 解析中 / 失败状态应能打开 trace。 +- 网格 / 列表视图选择可持久化到 localStorage。 +- 批量选择不能与普通打开文档冲突。 + +## MNote 字段映射 + +| WeKnora UI 字段 | MNote 映射建议 | +| --- | --- | +| `kb.id` | MNote `knowledge_base_id` / registry entry id | +| `kb.name` | 知识库显示名,默认可来自 folder name | +| `kb.description` | registry description / provider description | +| `kb.type` | MNote 首版固定 `document`,FAQ 暂不作为主路径 | +| `kb.knowledge_count` | provider 文档数 + registry source count | +| `kb.chunk_count` | provider chunk count,可从 WeKnora status / search metadata 显示 | +| `kb.isProcessing` | registry processing count 或 provider parse in-flight count | +| `extract_config.enabled` | WeKnora graph / MNote graph capability | +| `vlm_config.enabled` | 多模态 / 图片解析能力 | +| `question_generation_config.enabled` | 问题生成能力 | +| `storage_provider_config.provider` | provider / vector store / file store 显示,不作为 MNote 文件事实源 | +| `knowledge.id` | `providerKnowledgeId` | +| `knowledge.file_name` | source display name / local file basename | +| `knowledge.file_type` | 文件扩展名 / source kind | +| `knowledge.file_size` | source file size,URL/manual 可为空 | +| `knowledge.tag_id` | MNote tag / folder / source group 映射,首版可只读 | +| `knowledge.parse_status` | provider parse status | +| `knowledge.summary_status` | provider postprocess status | +| `knowledge.channel` | `web` / `api` / `url` / `manual` / local upload 映射 | +| `knowledge.source` | local path / URL / provider source | +| `knowledge.updated_at` | registry updated_at 或 provider updated_at | + +## 不能直接接管的边界 + +- 不直接把 WeKnora Vue 组件复制进 MNote runtime;MNote 当前主壳是 Rust SSR / browser runtime,不是 WeKnora Vue 应用。 +- 不让 WeKnora 成为 MNote 的文件事实源;本地 `.md`、source registry、workspace path、allowed roots 仍归 MNote。 +- 不把 WeKnora 的 tenant / organization / share 语义直接套到 MNote;MNote 的 auth、membership、share grants 由 Rust SQLite control-plane 管。 +- 不把 WeKnora 的 tag 删除语义直接映射到 MNote folder 删除;WeKnora tag delete 可能级联删除文档,MNote 需要独立确认。 +- 不直接启用手动创建文档,除非 MNote 明确将其映射到本地文件或页面创建命令。 +- 不直接复用 WeKnora 的轮询策略作为 MNote 主链;MNote 可展示 provider status,但刷新应优先走 MNote watcher / event / command result。 +- 不直接暴露 WeKnora 的删除 KB / 删除文档为本地文件删除;首版删除应只删除 provider index 与 registry 映射,是否删除本地文件必须单独确认。 +- 不把 WeKnora `Wiki` / `Graph` 页签默认显示为可用;只有 provider capability 明确可用时展示。 + +## MNote 最小呈现建议 + +首版替换原知识库设置面板时,建议至少实现以下可见结构: + +1. 知识库列表页:标题、副标题、新建/绑定入口、KB 卡片网格、文档数、processing 状态、provider 能力徽标、设置入口。 +2. 知识库详情页:面包屑、KB 切换、文档分类侧栏、搜索/筛选栏、添加文档下拉、网格/列表切换。 +3. 文档项:文件名、描述/摘要、标签或来源、文件类型、更新时间、解析状态、打开引用。 +4. 状态层:pending / processing / finalizing / completed / failed / cancelled / draft 至少要有不同视觉表达。 +5. 安全边界:删除、移动、手动创建、共享、Wiki / Graph、tag 写入可以先隐藏或只读,避免 UI 暗示已具备不可逆能力。 + diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d17d354e..03f77e64 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1759,6 +1759,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2367,6 +2377,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime_guess", "percent-encoding", "pin-project-lite", "quinn", @@ -3255,6 +3266,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/rust/crates/mnote-web/Cargo.toml b/rust/crates/mnote-web/Cargo.toml index da4522fa..d6645717 100644 --- a/rust/crates/mnote-web/Cargo.toml +++ b/rust/crates/mnote-web/Cargo.toml @@ -16,7 +16,7 @@ hyper = "1" hyper-util = { version = "0.1", features = ["tokio"] } leptos = { version = "0.8.14", default-features = false, features = ["ssr"] } mnote-editor-core = { path = "../mnote-editor-core" } -reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls", "stream"] } +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls", "stream"] } rusqlite = { version = "0.34", features = ["bundled"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js index 99597ca9..9f37ab2f 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js @@ -199,6 +199,12 @@ export function createSidebarPageAiRuntime(context) { pageAiOpencodeEventSource: null, pageAiOpencodeEventSessionId: '', pageAiOpencodeEventTimer: 0, + pageAiOpenHubStatus: null, + pageAiOpenHubStatusError: '', + pageAiOpenHubBootstrap: null, + pageAiOpenHubIframeUrl: '', + pageAiOpenHubScopeSummary: '', + pageAiOpenHubFallbackReason: '', pageAiSessionSearchQuery: '', pageAiSessionSearchResults: [], pageAiSessionSearchTimer: 0, @@ -886,6 +892,10 @@ export function createSidebarPageAiRuntime(context) { } } + function pageAiOpenHubHostEnabled() { + return true; + } + function pageAiOpencodeStatusLabel(status) { var node = status && typeof status === 'object' ? status : {}; var state = String(node.status || node.state || node.runtimeStatus || '').trim(); @@ -949,6 +959,44 @@ export function createSidebarPageAiRuntime(context) { }); } + function pageAiNormalizeRootRelativePath(path) { + return String(path || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); + } + + function pageAiRootPathFromFileUri(rootUri) { + var root = String(rootUri || '').trim(); + if (!root.startsWith('file://')) return ''; + try { + return decodeURIComponent(new URL(root).pathname || '').replace(/\/+$/, ''); + } catch (_error) { + return ''; + } + } + + function pageAiRootRelativePathFromChangedPath(path, rootUri) { + var value = String(path || '').trim(); + if (!value) return ''; + try { + var parsed = new URL(value, window.location.origin); + if (parsed.pathname === '/api/local-folder/files/open') { + return pageAiNormalizeRootRelativePath(parsed.searchParams.get('path') || ''); + } + if (parsed.protocol === 'file:') value = decodeURIComponent(parsed.pathname || ''); + } catch (_error) {} + value = value.replace(/^file:\/\//, ''); + var rootPath = pageAiRootPathFromFileUri(rootUri || currentRootUri()); + if (rootPath && value.startsWith(rootPath + '/')) { + value = value.slice(rootPath.length + 1); + } + return pageAiNormalizeRootRelativePath(value); + } + + function pageAiDocumentIdFromRootRelativePath(relativePath) { + var normalized = pageAiNormalizeRootRelativePath(relativePath); + if (!normalized || !/\.(md|markdown)$/i.test(normalized)) return ''; + return localMarkdownDocumentIdFromPageAiRelativePath(normalized); + } + function pageAiOpencodeCurrentPageLabel() { var aggregate = currentPageAggregate(); var meta = aggregate && aggregate.meta && typeof aggregate.meta === 'object' ? aggregate.meta : {}; @@ -983,6 +1031,95 @@ export function createSidebarPageAiRuntime(context) { } } + function pageAiMNoteDocumentPathSegment(documentId) { + return encodeURIComponent(String(documentId || '').trim()).replace(/%3A/g, ':').replace(/%7E/g, '~'); + } + + function pageAiParentRelativePath(relativePath) { + var normalized = pageAiNormalizeRootRelativePath(relativePath); + if (!normalized || normalized.indexOf('/') === -1) return ''; + return normalized.split('/').slice(0, -1).join('/'); + } + + function pageAiFileUriForRootRelativePath(rootUri, relativePath) { + var root = String(rootUri || '').trim().replace(/\/+$/, ''); + var relative = pageAiNormalizeRootRelativePath(relativePath); + if (!root) return ''; + if (!relative) return root; + return root + '/' + relative.split('/').map(function(segment) { + return encodeURIComponent(segment); + }).join('/'); + } + + function pageAiCurrentActiveTabEditorTarget() { + var snapshot = currentPageAiOpenEditorsSnapshot(); + if (snapshot && snapshot.activeEditor) { + var activeTarget = pageAiTargetFromOpenEditor(snapshot.activeEditor, 'open_editors_active_tab'); + if (activeTarget) return activeTarget; + } + if (snapshot) { + var entries = pageAiNormalizeArray(snapshot.editors).concat(pageAiNormalizeArray(snapshot.resourceEditors)); + var activeEntry = entries.find(function(entry) { return entry && entry.active === true; }); + if (activeEntry) { + var target = pageAiTargetFromOpenEditor(activeEntry, 'open_editors_active_tab'); + if (target) return target; + } + } + return pageAiFallbackEditorTarget(); + } + + function pageAiCurrentActiveTabAddressPayload() { + var target = pageAiCurrentActiveTabEditorTarget() || {}; + var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; + var rootUri = String(workspacePath.rootUri || target.rootUri || currentRootUri() || '').trim(); + var sourceKind = String(workspacePath.sourceKind || target.sourceKind || currentSourceKind() || '').trim(); + var workspaceId = String(workspacePath.workspaceId || target.workspaceId || resolveWorkspaceId(document.body) || '').trim(); + var documentId = String(workspacePath.documentId || target.documentId || currentDocumentId() || '').trim(); + var relativePath = pageAiNormalizeRootRelativePath(workspacePath.relativePath || workspacePath.path || target.relativePath || target.path || ''); + if (!relativePath) relativePath = pageAiNormalizeRootRelativePath(localMarkdownRelativePathFromPageAiDocumentId(documentId)); + if (!documentId && relativePath && /\.(md|markdown)$/i.test(relativePath)) { + documentId = localMarkdownDocumentIdFromPageAiRelativePath(relativePath); + } + if (!documentId) documentId = String(currentDocumentId() || '').trim(); + var resourceKind = String(target.resourceKind || workspacePath.resourceKind || target.editorKind || '').trim().toLowerCase(); + var editorKind = String(target.editorKind || '').trim().toLowerCase(); + var objectIdentity = String(target.objectIdentity || target.targetId || workspacePath.objectIdentity || '').trim(); + var tabUrl = new URL('/documents/' + pageAiMNoteDocumentPathSegment(documentId), window.location.origin); + if (sourceKind) tabUrl.searchParams.set('sourceKind', sourceKind); + if (rootUri) tabUrl.searchParams.set('rootUri', rootUri); + if (workspaceId) tabUrl.searchParams.set('workspaceId', workspaceId); + var isResourceTab = Boolean(relativePath) + && resourceKind !== 'markdown_page' + && resourceKind !== 'page' + && editorKind !== 'page'; + if (isResourceTab) { + tabUrl.searchParams.set('resourcePath', relativePath); + if (objectIdentity) tabUrl.searchParams.set('resourceTab', objectIdentity); + } + var folderRelativePath = pageAiParentRelativePath(relativePath); + var folderUrl = pageAiFileUriForRootRelativePath(rootUri, folderRelativePath); + if (!folderUrl) { + var folderMNoteUrl = new URL('/documents/' + pageAiMNoteDocumentPathSegment(documentId), window.location.origin); + if (sourceKind) folderMNoteUrl.searchParams.set('sourceKind', sourceKind); + if (rootUri) folderMNoteUrl.searchParams.set('rootUri', rootUri); + if (workspaceId) folderMNoteUrl.searchParams.set('workspaceId', workspaceId); + if (folderRelativePath) folderMNoteUrl.searchParams.set('fileTreeScope', folderRelativePath); + folderUrl = folderMNoteUrl.toString(); + } + return { + schema: 'mnote.page_ai.active_tab_address.v1', + tabUrl: tabUrl.toString(), + folderUrl: folderUrl, + documentId: documentId, + rootUri: rootUri, + workspaceId: workspaceId, + relativePath: relativePath, + folderRelativePath: folderRelativePath, + targetId: String(target.targetId || objectIdentity || '').trim(), + resourceKind: resourceKind || 'markdown_page' + }; + } + function pageAiBuildOpencodeContextPayload() { var selectedText = currentPageAiSelectedText(); var target = currentPageAiEditorTarget() || {}; @@ -1014,6 +1151,116 @@ export function createSidebarPageAiRuntime(context) { return lines.join('\n'); } + function pageAiOpenHubScopeSummary(scope) { + var node = scope && typeof scope === 'object' ? scope : {}; + var workspace = node.workspaceScope && typeof node.workspaceScope === 'object' ? node.workspaceScope : {}; + var items = []; + if (node.openhubUserKey || node.openhub_user_key) items.push('openhub_user_key=' + String(node.openhubUserKey || node.openhub_user_key)); + if (node.openhubWorkspaceKey || node.workspace_key) items.push('workspace_key=' + String(node.openhubWorkspaceKey || node.workspace_key)); + if (workspace.workspaceId) items.push('workspace=' + String(workspace.workspaceId)); + if (workspace.rootUri) items.push('rootUri=' + String(workspace.rootUri)); + if (node.openhubSessionScope || node.session_scope) items.push('session_scope=' + String(node.openhubSessionScope || node.session_scope)); + if (node.skillScope || node.skill_scope) items.push('skill_scope=' + String(node.skillScope || node.skill_scope)); + if (node.mcpScope) items.push('mcp_scope=' + String(node.mcpScope)); + if (node.toolPermissionScope || node.tool_permission_scope) items.push('tool_permission_scope=' + String(node.toolPermissionScope || node.tool_permission_scope)); + if (node.weknoraToolScope || node.weknora_tool_scope) items.push('weknora_tool_scope=' + String(node.weknoraToolScope || node.weknora_tool_scope)); + return items.join('\n'); + } + + function pageAiOpenHubStatusText(status) { + var node = status && typeof status === 'object' ? status : {}; + if (node.ok === true || String(node.status || '').trim() === 'ready') return '已连接 OpenHub'; + if (String(node.status || '').trim() === 'degraded') return 'OpenHub 连接受限'; + return '正在连接 OpenHub'; + } + + function pageAiRenderOpenHubHostChrome() { + var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); + if (!(drawer instanceof HTMLElement) || drawer.getAttribute('data-page-ai-openhub-host') !== 'true') return; + var bootstrap = pageUiState.pageAiOpenHubBootstrap && typeof pageUiState.pageAiOpenHubBootstrap === 'object' ? pageUiState.pageAiOpenHubBootstrap : {}; + var scope = bootstrap.scope && typeof bootstrap.scope === 'object' ? bootstrap.scope : {}; + var status = pageUiState.pageAiOpenHubStatus && typeof pageUiState.pageAiOpenHubStatus === 'object' ? pageUiState.pageAiOpenHubStatus : {}; + var statusNode = drawer.querySelector('[data-page-ai-openhub-runtime-status]'); + if (statusNode instanceof HTMLElement) { + statusNode.textContent = pageUiState.pageAiOpenHubStatusError || pageAiOpenHubStatusText(status); + statusNode.setAttribute('data-state', pageUiState.pageAiOpenHubStatusError ? 'error' : (status.ok ? 'ok' : 'degraded')); + } + var userNode = drawer.querySelector('[data-page-ai-openhub-user-key]'); + if (userNode instanceof HTMLElement) userNode.textContent = String(scope.openhubUserKey || scope.openhub_user_key || '等待 bootstrap'); + var authNode = drawer.querySelector('[data-page-ai-openhub-auth-truth]'); + if (authNode instanceof HTMLElement) authNode.textContent = '登录真相:MNote mnote_session;拒绝 OpenHub JWT/localStorage'; + var workspaceNode = drawer.querySelector('[data-page-ai-openhub-workspace-scope]'); + if (workspaceNode instanceof HTMLElement) workspaceNode.textContent = pageAiOpenHubScopeSummary(scope) || '等待 workspace/rootUri/session/tool scope'; + var guardNode = drawer.querySelector('[data-page-ai-openhub-route-guard]'); + if (guardNode instanceof HTMLElement) guardNode.textContent = '非 AI 路由 guard:login/admin/file/knowledge 由 MNote 接管'; + var fallbackNode = drawer.querySelector('[data-page-ai-openhub-fallback]'); + if (fallbackNode instanceof HTMLElement) { + fallbackNode.textContent = pageUiState.pageAiOpenHubFallbackReason || 'OpenHub 不可用时提示用户刷新或检查服务,不再提供 opencode 页面回退'; + } + var iframe = drawer.querySelector('[data-page-ai-openhub-iframe]'); + if (iframe instanceof HTMLIFrameElement) { + var nextUrl = String(pageUiState.pageAiOpenHubIframeUrl || bootstrap.openhubIframeUrl || '/page-ai/openhub/ai').trim(); + if (nextUrl && iframe.getAttribute('src') !== nextUrl) iframe.setAttribute('src', nextUrl); + iframe.hidden = !nextUrl; + } + } + + function pageAiEnsureOpenHubHostDrawer() { + var drawer = ensurePageAiDrawer(); + pageAiInstallMNoteOpenFileBridge(); + if (drawer.getAttribute('data-page-ai-openhub-host') !== 'true') { + drawer.setAttribute('data-page-ai-openhub-host', 'true'); + drawer.removeAttribute('data-page-ai-opencode-host'); + drawer.setAttribute('data-mnote-acp-runtime', 'openhub-opencode'); + drawer.innerHTML = '' + + '' + + ''; + } + pageAiRenderOpenHubHostChrome(); + void pageAiBootstrapOpenHubHost(); + return drawer; + } + + async function pageAiBootstrapOpenHubHost() { + var context = pageAiBuildOpencodeContextPayload(); + try { + var statusResponse = await fetch('/api/page-ai/openhub/status', { headers: { accept: 'application/json' }, cache: 'no-store' }); + pageUiState.pageAiOpenHubStatus = await statusResponse.json().catch(function() { return null; }) || {}; + var response = await fetch('/api/page-ai/openhub/bootstrap', { + method: 'POST', + headers: { 'content-type': 'application/json', accept: 'application/json' }, + cache: 'no-store', + body: JSON.stringify(context) + }); + var payload = await response.json().catch(function() { return null; }); + if (!response.ok) throw new Error(payload && (payload.error || payload.message) ? String(payload.error || payload.message) : 'HTTP ' + response.status); + pageUiState.pageAiOpenHubBootstrap = payload || {}; + pageUiState.pageAiOpenHubStatusError = ''; + pageUiState.pageAiOpenHubIframeUrl = String(payload && payload.openhubIframeUrl || '/page-ai/openhub/ai').trim(); + var statusFallback = pageUiState.pageAiOpenHubStatus && pageUiState.pageAiOpenHubStatus.fallback ? pageUiState.pageAiOpenHubStatus.fallback : null; + pageUiState.pageAiOpenHubFallbackReason = payload && payload.fallback + ? String(payload.fallback.reason || '') + : (statusFallback ? String(statusFallback.reason || '') : ''); + pageUiState.pageAiOpenHubScopeSummary = pageAiOpenHubScopeSummary(payload && payload.scope); + pageAiRenderOpenHubHostChrome(); + } catch (error) { + pageUiState.pageAiOpenHubStatusError = error instanceof Error ? error.message : String(error); + pageUiState.pageAiOpenHubFallbackReason = 'OpenHub bootstrap 不可用,请刷新或检查 OpenHub 服务'; + pageAiRenderOpenHubHostChrome(); + } + } + function pageAiPostOpencodeBridgeMessage(type, payload) { var iframe = document.querySelector('[data-page-ai-opencode-iframe]'); if (!(iframe instanceof HTMLIFrameElement) || !iframe.contentWindow) return false; @@ -1021,6 +1268,41 @@ export function createSidebarPageAiRuntime(context) { return true; } + function pageAiInstallMNoteOpenFileBridge() { + if (pageUiState.pageAiMNoteOpenFileBridgeInstalled) return; + pageUiState.pageAiMNoteOpenFileBridgeInstalled = true; + window.addEventListener('message', function(event) { + if (event.origin !== window.location.origin) return; + var message = event.data && typeof event.data === 'object' ? event.data : null; + if (message && message.source === 'openhub-ai' && message.type === 'mnote:get-active-tab-address') { + var addressPayload = pageAiCurrentActiveTabAddressPayload(); + var requestPayload = message.payload && typeof message.payload === 'object' ? message.payload : {}; + var kind = String(requestPayload.kind || message.kind || 'tab').trim(); + var value = kind === 'folder' ? addressPayload.folderUrl : addressPayload.tabUrl; + if (event.source && typeof event.source.postMessage === 'function') { + event.source.postMessage({ + source: 'mnote-page-ai', + type: 'mnote:active-tab-address', + requestId: message.requestId || '', + payload: Object.assign({}, addressPayload, { + kind: kind === 'folder' ? 'folder' : 'tab', + value: value + }) + }, event.origin); + } + return; + } + if (!message || (message.type !== 'mnote:open-file' && message.type !== 'mnote:open-reference')) return; + if (message.source !== 'openhub-diff' && message.source !== 'openhub-changed-files' && message.source !== 'openhub-citation') return; + var payload = message.payload && typeof message.payload === 'object' ? message.payload : {}; + pageAiOpenOpencodeChangedFile(message.path || payload.path || payload.href || '', { + rootUri: String(message.rootUri || payload.rootUri || payload.root_uri || currentRootUri() || '').trim(), + workspaceId: String(message.workspaceId || payload.workspaceId || payload.workspace_id || resolveWorkspaceId(document.body) || '').trim(), + documentId: String(message.documentId || payload.documentId || payload.document_id || '').trim() + }); + }); + } + function pageAiInstallOpencodeBridge() { if (pageUiState.pageAiOpencodeBridgeInstalled) return; pageUiState.pageAiOpencodeBridgeInstalled = true; @@ -1254,8 +1536,10 @@ export function createSidebarPageAiRuntime(context) { function pageAiEnsureOpencodeHostDrawer() { var drawer = ensurePageAiDrawer(); + pageAiInstallMNoteOpenFileBridge(); if (drawer.getAttribute('data-page-ai-opencode-host') !== 'true') { drawer.setAttribute('data-page-ai-opencode-host', 'true'); + drawer.removeAttribute('data-page-ai-openhub-host'); drawer.setAttribute('data-mnote-acp-runtime', 'opencode'); drawer.innerHTML = '' + '' + @@ -1530,16 +1814,59 @@ export function createSidebarPageAiRuntime(context) { pageAiRenderOpencodeHostChrome(); } - function pageAiOpenOpencodeChangedFile(path) { - var targetPath = String(path || '').trim(); - if (!targetPath || !window.__mnoteDocumentPaneRuntime || typeof window.__mnoteDocumentPaneRuntime.openResourceInActiveTab !== 'function') return false; - void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ path: targetPath }); + function pageAiOpenOpencodeChangedFile(path, options) { + var rootUri = String(options && options.rootUri || currentRootUri() || '').trim(); + var targetPath = pageAiRootRelativePathFromChangedPath(path, rootUri); + if (!targetPath || !window.__mnoteDocumentPaneRuntime) return false; + var documentId = String(options && options.documentId || pageAiDocumentIdFromRootRelativePath(targetPath) || '').trim(); + var workspaceId = String(options && options.workspaceId || resolveWorkspaceId(document.body) || '').trim(); + var isMarkdown = Boolean(documentId); + if (isMarkdown) { + if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch === 'function') { + window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch({ + source: 'openhub_changed_file_bridge', + reason: 'openhub_changed_file', + rootUri: rootUri, + workspaceId: workspaceId, + payload: { + schema: 'mnote.local_folder.watch_batch.v1', + source: 'openhub_changed_file_bridge', + rootUri: rootUri, + changedPaths: [{ relativePath: targetPath, documentId: documentId, changeType: 'modified' }], + affectedParents: [{ relativePath: pageAiParentRelativePath(targetPath), reason: 'openhub_changed_file' }] + } + }); + } + if (typeof window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument === 'function') { + void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({ + documentId: documentId, + workspaceId: workspaceId, + source: 'openhub-changed-file-bridge' + }); + } + if (typeof window.__mnoteDocumentPaneRuntime.openPrimaryDocument === 'function') { + void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({ + documentId: documentId, + workspaceId: workspaceId, + sourceKind: currentSourceKind() || 'local_folder', + rootUri: rootUri + }); + document.documentElement.setAttribute('data-mnote-page-ai-openhub-document-pane-refresh', targetPath); + return true; + } + } + if (typeof window.__mnoteDocumentPaneRuntime.openResourceInActiveTab !== 'function') return false; + void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ path: targetPath, rootUri: rootUri }); return true; } function pageAiRefreshOpencodeCurrentPage(reason) { if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument === 'function') { - void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({ reason: reason || 'page-ai-opencode-host' }); + if (reason) { + void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({ reason: reason }); + } else { + void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({ reason: 'page-ai-opencode-host' }); + } return true; } return false; @@ -1739,7 +2066,7 @@ export function createSidebarPageAiRuntime(context) { 'mnote.selection.read', 'mnote.open_resources.snapshot', 'mnote.allowed_roots.describe', - 'mnote.lightrag.query', + 'mnote.weknora.search', 'mnote.reference.open', 'mnote.page_aggregate.snapshot', 'mnote.local_file.receipt' @@ -3540,6 +3867,20 @@ export function createSidebarPageAiRuntime(context) { function openPageAiDrawer() { + if (pageAiOpenHubHostEnabled()) { + pageAiEnsureContextRefState(); + void pageAiLoadAllowedRoots().then(function() { + pageAiRenderOpenHubHostChrome(); + }).catch(function() { + pageAiRenderOpenHubHostChrome(); + }); + var openhubDrawer = pageAiEnsureOpenHubHostDrawer(); + pageAiApplyDrawerWidth(openhubDrawer); + openhubDrawer.hidden = false; + pageUiState.pageAiOpen = true; + updatePageAiTriggerState(); + return; + } if (pageAiOpencodeHostEnabled()) { pageAiEnsureContextRefState(); void pageAiLoadAllowedRoots().then(function() { @@ -3748,7 +4089,7 @@ export function createSidebarPageAiRuntime(context) { return text.indexOf(word) >= 0; }); if (!asksCitation) return false; - return ['lightrag', '资料库', '知识库', 'rag', '文献', '论文', '保护基'].some(function(word) { + return ['weknora', '资料库', '知识库', 'rag', '文献', '论文', '保护基'].some(function(word) { return text.indexOf(word) >= 0; }); } @@ -3759,7 +4100,7 @@ export function createSidebarPageAiRuntime(context) { var afterColon = text.split(/[::]/).slice(1).join(':').trim(); var candidate = afterColon || text; candidate = candidate - .replace(/^(请|请用|帮我|帮忙|用)?(资料库|知识库|lightrag|rag)?(搜索|检索|查找|查询|回答|说明|解释|总结)?/i, '') + .replace(/^(请|请用|帮我|帮忙|用)?(资料库|知识库|weknora|rag)?(搜索|检索|查找|查询|回答|说明|解释|总结)?/i, '') .replace(/(请)?(给出|给我|附上|提供)?(链接|引用|来源|出处|证据|定位).*$/i, '') .trim(); var stopIndex = candidate.search(/(在|的|是|有哪些|有什么|如何|怎么|用于|用途|作用|资料|文献|论文)/); @@ -3885,7 +4226,7 @@ export function createSidebarPageAiRuntime(context) { pageAiPersistSessions(); renderPageAiConversation(); } catch (error) { - console.warn('MNote Page AI 自动追加 LightRAG 引用失败', error); + console.warn('MNote Page AI 自动追加知识库引用失败', error); } } @@ -4749,6 +5090,9 @@ export function createSidebarPageAiRuntime(context) { }); } } + if (action === 'openhub-refresh-bootstrap') { + void pageAiBootstrapOpenHubHost(); + } if (action === 'cancel-queued-run') { void pageAiCancelQueuedRun(pageAiAction.getAttribute('data-page-ai-queue-id')); } @@ -5159,7 +5503,7 @@ export function createSidebarPageAiRuntime(context) { } - return { + var api = { ensurePageAiStateFacade: (...args) => ensurePageAiStateFacade(...args), installPageAiDelegates: (...args) => installPageAiDelegates(...args), handlePageAiClick: (...args) => handlePageAiClick(...args), @@ -5217,4 +5561,6 @@ export function createSidebarPageAiRuntime(context) { pageAiRefreshOpencodeCurrentPage: (...args) => pageAiRefreshOpencodeCurrentPage(...args), updatePageAiTriggerState: (...args) => updatePageAiTriggerState(...args) }; + window.__mnoteSidebarPageAiRuntime = api; + return api; } diff --git a/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js index 724a082c..fee0a852 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js @@ -700,73 +700,130 @@ export function createSidebarPageSettingsRuntime(context) { function createKnowledgeRagSettingsPanelHtmlV2() { return '' + - '