chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
# 7-18 [process] local-first agent 文件编辑控制面 v1
|
||||
|
||||
> 2026-07-03 口径更新:本文中的 Hermes / Reasonix 主路径口径已退为历史参考。当前 Page AI / agent runtime 主线以 OpenHub / native agent + LightRAG + Turso/libSQL 为准;`7-68-openhub-weknora-mnote-deep-fusion-v1.md` 与 `7-69-weknora-native-embed-mnote-openhub-governance-v1.md` 的 WeKnora 默认 provider 口径已标记 stale。本文仅保留 local-first 文件授权、watcher 同步和冲突模型的参考价值。
|
||||
> 2026-07-18 口径更新:当前 Page AI / agent runtime 主线是 Pi Rust Page AI + LightRAG + Turso/libSQL。本文保留并推进 local-first 文件授权、watcher/BufferStore 同步、冲突模型与审计回收;Hermes / Reasonix / OpenHub 仅作为兼容与历史证据,不作为实现入口。
|
||||
|
||||
> 创建时间:2026-05-22
|
||||
>
|
||||
> 当前状态:`PROCESS`
|
||||
>
|
||||
> 上位依据:
|
||||
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
|
||||
> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md`
|
||||
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
|
||||
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`(`CURRENT_ARCHITECTURE.md` 为兼容指针)
|
||||
> - `/mnt/Data1T/mnote/design/10-review/process/21-mvp-post-architecture-closure-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/reference/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
|
||||
>
|
||||
> 覆盖旧稿:`/mnt/Data1T/mnote/design/old/07-ai/process/7-14-local-first-ai-markdown-editing-convergence-v1.md`
|
||||
>
|
||||
@@ -31,7 +28,7 @@ MNote 在 local-first 下只提供:
|
||||
- agent runtime session、审计、changed files / diff 回收。
|
||||
- watcher、BufferStore、Page Aggregate、ProseMirror 前台同步。
|
||||
|
||||
Hermes / Reasonix 是已知 agent,使用自身文件 read / write / patch / diff 能力在授权目录内完成普通 `.md` 编辑。
|
||||
Pi Rust Page AI 使用自身文件 read / write / patch / diff 能力在授权目录内完成普通 `.md` 编辑。
|
||||
|
||||
## 2. 明确退役项
|
||||
|
||||
@@ -67,7 +64,7 @@ Page / active editor
|
||||
-> resolve canonical .md file / resource file
|
||||
-> build AiAccessScope
|
||||
-> freeze allowed roots / allowed files / readonly / dirty / selection
|
||||
-> start Hermes / Reasonix run with file refs and context
|
||||
-> start Pi Rust Page AI run with file refs and context
|
||||
-> agent native patch / diff / write inside allowed roots
|
||||
-> MNote collects changed files / diff / audit
|
||||
-> watcher observes file changes
|
||||
@@ -287,7 +284,7 @@ Rust SQLite control-plane 是默认控制面,负责:
|
||||
|
||||
### Phase A:运行时输入与权限边界
|
||||
|
||||
- [ ] 从文档页发起 Hermes / Reasonix run 时,request body 包含 `currentFile`、`allowedRoots`、`allowedFiles`、selection 和 dirty / readonly context。
|
||||
- [ ] 从文档页发起 Pi Rust Page AI run 时,request body 包含 `currentFile`、`allowedRoots`、`allowedFiles`、selection 和 dirty / readonly context。
|
||||
- [ ] run body 包含 `targetPackage`,且 `allowedRoots` / `allowedFiles` 从 targetPackage 推导。
|
||||
- [x] local-first 普通 Markdown 编辑 run 不包含 `mnote.doc.markdown_edit` 推荐工具。
|
||||
- [ ] allowed roots 外文件写入被 agent runtime 或 MNote 审计层拒绝。
|
||||
|
||||
@@ -1,421 +0,0 @@
|
||||
# 7-47 MNote 公共 capability/plugin 注册表设计 v1
|
||||
|
||||
> 2026-06-28 覆盖说明:本文的 Hermes / Reasonix / LightRAG 能力注册口径已退为 legacy 参考。当前能力模型应围绕 OpenHub / opencode / WeKnora,旧 `mnote-knowledge-rag` 与 `mnote_lightrag_bridge` 仅保留兼容映射和迁移对照。
|
||||
|
||||
> 状态:process
|
||||
>
|
||||
> 目标:把当前分裂的 MNote builtin skill、mnote tool manifest、Hermes plugin、Reasonix wrapper 和 Page AI UI 开关收口为同一个“AI 能力”模型。对用户来说 skill / plugin / tool 都是“授予 AI 的能力”,UI 不应暴露实现层分类;实现层再把一个能力映射到说明书、工具、runtime adapter 和权限策略。本轮只整理 MNote 公共能力;Reasonix / Hermes 自带的 skills/plugins 维持现状,不纳入统一注册表迁移范围。
|
||||
>
|
||||
> 2026-06-07 口径更新:7-50 后资料库问答主线已切到 LightRAG,第一批能力包试点从旧 `mnote-local-index` 改为 `mnote-knowledge-rag`。`mnote-document-evidence` / `mnote-local-index` 只作为兼容 alias 映射到 `mnote-knowledge-rag`,不再恢复 `mnote.evidence.*` / `mnote.index.*` 作为 active tool。
|
||||
|
||||
## 当前实施状态
|
||||
|
||||
- Phase A 已落地:`rust/crates/mnote-web/src/hermes_tools/skill.rs` 已有 `MnoteCapabilityPack` / `CAPABILITY_PACKS`,`manifest.rs` 已输出 `capabilities[]` 并给 tools 标注 `capabilityId` / `capabilityIds`。
|
||||
- Phase B 已落地:`/api/hermes/client/capabilities` 已存在,Page AI `runtime=mnote` 技能目录优先请求 capabilities,能力 payload 内含 tools、readOnly、contextRefs、enabled/status。
|
||||
- Phase C 已落地:`/api/hermes/client/capabilities/toggle` 会同步 capability skill preference 和 profile tool disabled policy;直接调用被关闭 tool 会走 `mnote_tool_disabled` 硬拒绝。
|
||||
- Phase D 已完成 Reasonix 侧最小收口:`scripts/reasonix-acp-wrapper.mjs` 启动时优先读取 `/api/hermes/tools/mnote/manifest` 动态注册 MNote tool specs,mnote-web 不可用时才回落到静态 fallback;Hermes Python plugin 生成仍作为后续独立收口。
|
||||
|
||||
## 0. 用户口径
|
||||
|
||||
用户不需要理解 skill、plugin、tool 的区别。Page AI 设置中统一展示为“AI 能力”:
|
||||
|
||||
- `当前页读取`
|
||||
- `资料库问答`
|
||||
- `本地文件编辑`
|
||||
- `思维导图`
|
||||
- `ONLYOFFICE 实时编辑`
|
||||
- `纯聊天`
|
||||
|
||||
每个能力只有一个主开关。展开后可以显示该能力包含的工具、上下文要求和读写权限,但这些是高级详情,不是主概念。
|
||||
|
||||
实现层映射:
|
||||
|
||||
- `skill`:能力说明书,告诉 agent 什么时候用、怎么用。
|
||||
- `tool`:能力的可执行函数,真正落到 Rust runtime / kernel。
|
||||
- `plugin / adapter`:把同一套工具暴露给 Hermes ACP、Reasonix ACP 或其它 agent runtime。
|
||||
- `policy`:决定该能力是否可见、是否启用、是否允许写入。
|
||||
|
||||
后续 UI 文案统一使用“能力”,不再把 MNote builtin skill、Hermes skill、Reasonix skill、mnote tool 作为并列用户入口。
|
||||
|
||||
## 1. 当前盘点
|
||||
|
||||
### 1.1 MNote builtin skills
|
||||
|
||||
当前入口:`rust/crates/mnote-web/src/hermes_tools/skill.rs`
|
||||
|
||||
现有内置 skill:
|
||||
|
||||
- `mnote-current-page`
|
||||
- `mnote-knowledge-rag`
|
||||
- `mnote-local-file`
|
||||
- `mnote-onlyoffice-live`
|
||||
- `mnote-mindmap`
|
||||
- `mnote-chat-only`
|
||||
|
||||
特点:
|
||||
|
||||
- skill 是 Rust 静态注册,正文来自 `skills/*/SKILL.md`。
|
||||
- `/api/hermes/client/skills?runtime=mnote&agentId=...` 通过 `mnote_builtin_skills_payload()` 输出到 UI。
|
||||
- 每个 skill 现在已经带 `toolNames` 和 `requiresContextRefs`,但这些只是弱引用,不是一个正式 capability/plugin 合同。
|
||||
- `mnote.skill.read` 能懒加载正文;旧 `mnote-document-evidence` / `mnote-local-index` 只作为兼容别名映射到 `mnote-knowledge-rag`,不恢复旧 evidence / index 工具。
|
||||
|
||||
### 1.2 MNote tools
|
||||
|
||||
当前入口:`rust/crates/mnote-web/src/hermes_tools/manifest.rs`
|
||||
|
||||
现有工具大类:
|
||||
|
||||
- skill/context:`mnote.skill.read`、`mnote.context.*`
|
||||
- knowledge-rag:`mnote.knowledge_rag.status`、`mnote.knowledge_rag.query`、`mnote.knowledge_rag.open_reference`
|
||||
- retired evidence/index:`mnote.evidence.*`、`mnote.index.*` 只保留历史对照,不作为 active manifest / capability 示例
|
||||
- doc/block/page/artifact:`mnote.doc.*`、`mnote.block.*`、`mnote.page.*`、`mnote.artifact.*`
|
||||
- mindmap:`mnote.mindmap.*`
|
||||
- office/onlyoffice:`mnote.office.*`、`mnote.onlyoffice.*`
|
||||
|
||||
执行入口:
|
||||
|
||||
- 当前中性入口:`/api/mnote/tools/manifest`
|
||||
- 当前中性入口:`/api/mnote/tools/call`
|
||||
- legacy alias:`/api/hermes/tools/mnote/manifest`
|
||||
- legacy alias:`/api/hermes/tools/mnote/call`
|
||||
- `execute_mnote_tool_call()` 统一做认证、profile 禁用检查、workspace 校验、capabilityScope 校验、写入 guard、audit、idempotency。
|
||||
|
||||
问题:
|
||||
|
||||
- manifest 只有 plugin 总描述和扁平 tools,没有“哪个 tool 属于哪个公共能力包”的结构。
|
||||
- `/api/hermes/client/tools?scope=mnote&profile=...` 只展示扁平工具列表。
|
||||
- tool 开关写入 Hermes profile 的 `mnote.tools.disabled`,skill 开关写入 MNote SQLite user preference,两个开关域不同步。
|
||||
|
||||
### 1.3 Hermes plugin
|
||||
|
||||
当前本机存在 `/home/lix/.hermes/plugins/mnote/`,包括:
|
||||
|
||||
- `plugin.yaml`
|
||||
- `__init__.py`
|
||||
|
||||
现状:
|
||||
|
||||
- 这是 Hermes 用户插件,不在 mnote repo 内。
|
||||
- `plugin.yaml` 只列出旧批次工具:`mnote_page_get/save/update_title/update_options`、`mnote_doc_*`、`mnote_block_*`、artifact 等。
|
||||
- 没有 `mnote_evidence_*`、`mnote_index_*`、mindmap、onlyoffice live 等新工具。
|
||||
- `__init__.py` 手写 Python wrapper,把 Hermes tool call 转发到 legacy `/api/hermes/tools/mnote/call`;OpenHub/WeKnora 主线应使用 `/api/mnote/tools/call`。
|
||||
|
||||
问题:
|
||||
|
||||
- Hermes 用户插件和 Rust manifest 已经漂移。
|
||||
- 新增 Rust tool 后不会自动进入 Hermes plugin。
|
||||
- 如果继续维护该插件,必须由 Rust manifest 生成 plugin.yaml / Python adapter,不能手写。
|
||||
|
||||
### 1.4 Reasonix wrapper
|
||||
|
||||
当前入口:`scripts/reasonix-acp-wrapper.mjs`
|
||||
|
||||
现状:
|
||||
|
||||
- 手写 Reasonix ACP 可见工具名,例如 `mnote_evidence_search`、`mnote_index_status`。
|
||||
- 手写 `REASONIX_TOOL_TO_MNOTE_TOOL` 映射到 Rust `mnote.*` 工具。
|
||||
- 手写每个工具的 parameters。
|
||||
|
||||
问题:
|
||||
|
||||
- 与 Rust manifest 重复。
|
||||
- 与 Hermes plugin 重复。
|
||||
- 新增/改名工具必须同步改 wrapper,否则 agent 看得到 skill 也无法调用工具。
|
||||
|
||||
### 1.5 Page AI UI
|
||||
|
||||
当前入口:
|
||||
|
||||
- skills panel:`rust/crates/mnote-web/browser/sidebar-page-ai-skill-runtime.js`
|
||||
- runtime/tools panel:`rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js`
|
||||
- data load/toggle:`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`
|
||||
|
||||
现状:
|
||||
|
||||
- Skills 页能展示 MNote builtin skills、Reasonix skills、Hermes profile skills。
|
||||
- Runtime/高级页展示扁平 `mnote tools` 列表。
|
||||
- MNote skill 开关调用 `/api/hermes/client/skills/toggle`,写 SQLite user preference。
|
||||
- Tool 开关调用 `/api/hermes/client/tools/toggle`,写 profile YAML `mnote.tools.disabled`。
|
||||
|
||||
问题:
|
||||
|
||||
- 用户想管理的是“索引能力包”,不是单独 skill 或一堆扁平工具。
|
||||
- 现在 UI 上 skill 和 tool 分两个地方,不能表达“启用索引 skill,同时启用对应工具”。
|
||||
- 无法像公共 skill 一样展示 plugin/capability 包的工具内容、权限、启停状态。
|
||||
|
||||
## 2. 设计判断
|
||||
|
||||
当前应该引入 `MnoteAiCapability` / `MnoteCapabilityPack`,而不是继续把 skill、tool、plugin 分开维护。代码中可叫 capability pack;UI 中只叫“AI 能力”。
|
||||
|
||||
定义:
|
||||
|
||||
```rust
|
||||
pub struct MnoteCapabilityPack {
|
||||
pub id: &'static str,
|
||||
pub title: &'static str,
|
||||
pub description: &'static str,
|
||||
pub agent_ids: &'static [&'static str],
|
||||
pub read_only: bool,
|
||||
pub requires_context_refs: &'static [&'static str],
|
||||
pub skill_id: &'static str,
|
||||
pub skill_content: &'static str,
|
||||
pub tool_names: &'static [&'static str],
|
||||
pub ui_kind: &'static str, // capability | chat | compat
|
||||
pub category: &'static str, // mnote, office, resource, chat
|
||||
pub public: bool,
|
||||
}
|
||||
```
|
||||
|
||||
原则:
|
||||
|
||||
- 一个公共能力包可以包含一个 skill 和多个 tools。
|
||||
- UI 主要展示 capability pack,不再让用户先理解 skill/tool/plugin 三套概念。
|
||||
- agent prompt 仍注入短 skill/capability 摘要,正文仍走 `mnote.skill.read` 懒加载。
|
||||
- tool manifest 仍是执行真相,但每个 tool 必须能反查所属 capability pack。
|
||||
- Reasonix wrapper / Hermes plugin 都从同一份 capability/tool manifest 生成或动态注册。
|
||||
|
||||
## 3. 目标 API
|
||||
|
||||
### 3.1 新增 capability 列表
|
||||
|
||||
`GET /api/hermes/client/capabilities?runtime=mnote&agentId=reasonix&profile=...`
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"runtime": "mnote",
|
||||
"categories": [
|
||||
{
|
||||
"name": "mnote",
|
||||
"title": "MNote",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "mnote-knowledge-rag",
|
||||
"title": "资料库问答",
|
||||
"description": "Ask the LightRAG-backed knowledge library and open returned MNote source references.",
|
||||
"enabled": true,
|
||||
"toggleable": true,
|
||||
"readOnly": false,
|
||||
"skillId": "mnote-knowledge-rag",
|
||||
"toolNames": [
|
||||
"mnote.knowledge_rag.status",
|
||||
"mnote.knowledge_rag.query",
|
||||
"mnote.knowledge_rag.open_reference"
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "mnote.knowledge_rag.query",
|
||||
"kind": "read",
|
||||
"status": "available",
|
||||
"enabled": true,
|
||||
"requiresWritePermission": true
|
||||
}
|
||||
],
|
||||
"requiresContextRefs": ["folder"],
|
||||
"configScope": "user_sqlite+profile_tool_policy"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 新增 capability toggle
|
||||
|
||||
`PUT /api/hermes/client/capabilities/toggle`
|
||||
|
||||
入参:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": "mnote",
|
||||
"profile": "reasonix",
|
||||
"id": "mnote-knowledge-rag",
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
- 写入 MNote SQLite user preference:`ai.agent.mnote_builtin.skill.{id}.enabled`
|
||||
- 对包内 tools 批量更新 profile `mnote.tools.disabled`
|
||||
- 保留单 tool 开关作为高级功能,但 UI 默认展示包开关。
|
||||
|
||||
### 3.3 manifest 扩展
|
||||
|
||||
`/api/hermes/tools/mnote/manifest` 增加:
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "mnote-knowledge-rag",
|
||||
"skillId": "mnote-knowledge-rag",
|
||||
"toolNames": ["mnote.knowledge_rag.status", "mnote.knowledge_rag.query", "mnote.knowledge_rag.open_reference"]
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "mnote.knowledge_rag.query",
|
||||
"capabilityId": "mnote-knowledge-rag",
|
||||
"capabilityScope": ["knowledge_rag.read", "evidence.read"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 4. UI 设计
|
||||
|
||||
### 4.1 Skills 页改为 AI 能力页
|
||||
|
||||
在 Page AI 的 Skills 页中,用户看到的是统一“AI 能力”列表,不再分 skill / plugin / tool:
|
||||
|
||||
- 行标题:`资料库问答`
|
||||
- 副标题:`知识库 / LightRAG · 3 tools · 需要 folder`
|
||||
- 状态 chip:`只读` / `可写` / `部分工具关闭` / `只读上下文不可写`
|
||||
- 主开关:启用/关闭整个能力包
|
||||
- 展开项:列出 tools,显示 read/write、enabled、status
|
||||
|
||||
不新增单独 landing/settings 页;沿用当前 Skills 页即可,避免再散一处入口。
|
||||
|
||||
页面标题建议从 `Skills` 改为 `能力`;内部 source filter 可以保留 `MNote / Hermes / Reasonix`,但展示为能力来源,不作为用户要理解的能力类型。
|
||||
|
||||
### 4.2 Runtime 高级页保留扁平工具
|
||||
|
||||
Runtime 页继续保留 `mnote tools`,但作为高级调试面:
|
||||
|
||||
- 默认按 capability 分组,而不是纯扁平列表。
|
||||
- 单 tool 开关仍保留,用于排查或临时禁用某个写工具。
|
||||
- 若 capability 关闭,包内工具显示 `disabled_by_capability`。
|
||||
|
||||
### 4.3 索引面板与能力包关系
|
||||
|
||||
Sidebar 的“资料库 / 知识库设置”仍是用户直接管理 LightRAG source、索引范围和服务状态的产品 UI;Page AI 的 `mnote-knowledge-rag` capability 是 agent 能力开关。
|
||||
|
||||
两者职责不同:
|
||||
|
||||
- 知识库设置面板:用户手动新增/删除/刷新 source,查看 LightRAG 服务和 source registry 状态。
|
||||
- MNote knowledge-rag capability:允许 agent 使用工具查询资料库、查看状态和打开返回来源;sourcePaths 当前只过滤返回 references,不声称 provider 层预过滤 raw chunks。
|
||||
|
||||
## 5. Runtime 适配
|
||||
|
||||
### 5.1 Reasonix
|
||||
|
||||
短期:
|
||||
|
||||
- 保留 `scripts/reasonix-acp-wrapper.mjs`。
|
||||
- 但 wrapper 启动时请求 `/api/hermes/tools/mnote/manifest`,按 manifest 自动注册工具。
|
||||
- 保留手写 fallback,避免 mnote-web 未启动时 wrapper 不能初始化。
|
||||
|
||||
中期:
|
||||
|
||||
- 删除 `REASONIX_TOOL_TO_MNOTE_TOOL` 手写表。
|
||||
- 工具名转换统一由函数生成:
|
||||
- `mnote.knowledge_rag.status` -> `mnote_knowledge_rag_status`
|
||||
- `mnote.knowledge_rag.open_reference` -> `mnote_knowledge_rag_open_reference`
|
||||
|
||||
### 5.2 Hermes ACP
|
||||
|
||||
短期:
|
||||
|
||||
- MNote 继续通过 `AcpMnoteToolContext` 把 `availableSkills` 和 capability policy 交给 Hermes ACP。
|
||||
- 如果 Hermes ACP 原生不能消费 Rust manifest 注册工具,则仍依赖 `/home/lix/.hermes/plugins/mnote`。
|
||||
|
||||
中期:
|
||||
|
||||
- 用 Rust manifest 生成 `/home/lix/.hermes/plugins/mnote/plugin.yaml` 和 Python adapter。
|
||||
- 生成内容覆盖当前手写旧插件,确保 Hermes plugin tools 与 Rust manifest 一致。
|
||||
|
||||
长期:
|
||||
|
||||
- Hermes ACP 若支持从 host 动态接收 tools,则不再需要本机 Python plugin,只保留 Rust manifest。
|
||||
|
||||
## 6. 迁移方案
|
||||
|
||||
### Phase A:只加 registry,不改 UI 行为
|
||||
|
||||
- 新增 `hermes_tools/capability.rs`。
|
||||
- 将当前 `SKILLS` 迁到 `CAPABILITY_PACKS`,`skill_summaries_for_agent()` 从 pack 派生。
|
||||
- `manifest()` 给每个 tool 增加 `capabilityId`,并输出 `capabilities`。
|
||||
- 保持 `/client/skills`、`/client/tools` 响应兼容。
|
||||
|
||||
验收:
|
||||
|
||||
- `mnote-knowledge-rag` 在 `/client/skills?runtime=mnote` 与 `/client/capabilities?runtime=mnote` 可见,`mnote-local-index` 不再作为 active capability 暴露。
|
||||
- `/api/hermes/tools/mnote/manifest` 可看到 `capabilities[]`。
|
||||
- 旧 `mnote.skill.read` 不破。
|
||||
|
||||
### Phase B:UI 展示 capability pack
|
||||
|
||||
- 新增 `/api/hermes/client/capabilities`。
|
||||
- Skills 页对 `runtime=mnote` 优先消费 capabilities。
|
||||
- 能力包行展示 tools 数、read/write、contextRefs。
|
||||
- Runtime 页 tools 按 capability 分组。
|
||||
|
||||
验收:
|
||||
|
||||
- Page AI Skills / 能力页中 `资料库问答` 像公共 skill 一样可见。
|
||||
- 展开能看到 `mnote.knowledge_rag.*`,看不到 `mnote.evidence.*` / `mnote.index.*` active tools。
|
||||
- 开关 capability 后,下一次 run 的 `skillPreferences.mnote` 同步变化。
|
||||
|
||||
### Phase C:能力包开关驱动工具开关
|
||||
|
||||
- 新增 `/api/hermes/client/capabilities/toggle`。
|
||||
- 开关包时同步 skill preference 和 profile tool disabled 列表。
|
||||
- `page_ai_capability_policy()` 过滤 disabled capability,且 `execute_mnote_tool_call()` 对 disabled tool 继续硬拒绝。
|
||||
|
||||
验收:
|
||||
|
||||
- 关闭 `mnote-knowledge-rag` 后 agent 不再看到该 skill 摘要。
|
||||
- 关闭后直接调用 `mnote.knowledge_rag.query` 返回 `mnote_tool_disabled` 或 capability disabled。
|
||||
- 再打开后恢复。
|
||||
|
||||
### Phase D:生成 Reasonix/Hermes adapters
|
||||
|
||||
- Reasonix wrapper 从 manifest 自动注册工具。
|
||||
- Hermes plugin 从 manifest 生成或在启动时同步。
|
||||
- 删除手写工具表漂移。
|
||||
|
||||
验收:
|
||||
|
||||
- 新增 Rust tool 后,只改 Rust manifest/capability pack,Reasonix/Hermes UI 与 runtime 均自动出现。
|
||||
- `node scripts/reasonix-acp-wrapper.mjs` selftest 覆盖 manifest 动态注册。
|
||||
- `hermes plugins list` / Hermes tool list 显示与 Rust manifest 一致。
|
||||
|
||||
## 7. 对 mnote-knowledge-rag 的落地形态
|
||||
|
||||
`mnote-knowledge-rag` 是第一批公共能力包试点:
|
||||
|
||||
- skill:`skills/mnote-knowledge-rag/SKILL.md`
|
||||
- tools:
|
||||
- `mnote.knowledge_rag.status`
|
||||
- `mnote.knowledge_rag.query`
|
||||
- `mnote.knowledge_rag.open_reference`
|
||||
- requiresContextRefs:`folder`
|
||||
- readOnly:`true`
|
||||
- write guard:本能力包不写用户 source;source 管理 UI / API 另归知识库设置面,不通过 agent capability 暴露写入。
|
||||
- UI 文案:`知识库 / LightRAG · 可回跳来源`
|
||||
|
||||
## 8. 非目标
|
||||
|
||||
- 不把索引设置面板挪进 Page AI。
|
||||
- 不让 agent 直接读写 `.mnote/index` 文件。
|
||||
- 不让 Hermes plugin 直接写 Convex 或绕过 Rust runtime。
|
||||
- 不在前端手写工具 schema。
|
||||
- 不把 Reasonix wrapper 作为长期 tool registry 真相。
|
||||
|
||||
## 9. 风险
|
||||
|
||||
- Hermes Python plugin 当前在 `/home/lix/.hermes/plugins/mnote`,不在 repo,自动生成前仍会漂移。
|
||||
- tool 开关现在是 profile 级,skill 开关是用户级;能力包 toggle 需要明确优先级。
|
||||
- `mnote-local-file` 是 agent 原生文件能力指导,当前没有对应 MNote 写工具,放入 capability pack 时需要标记为 `native_agent_tools`,避免误以为 MNote 提供文件 patch tool。
|
||||
- `mnote-chat-only` 不是业务能力包,应保留为 agent mode skill,不参与 tools。
|
||||
|
||||
## 10. 建议执行顺序
|
||||
|
||||
1. 先做 Phase A:Rust `MnoteCapabilityPack` registry,完全兼容现有接口。
|
||||
2. 再做 Phase B:UI 展示 capability pack,保留旧 skills/tools fallback。
|
||||
3. 再做 Phase C:能力包 toggle 批量控制 skill + tools。
|
||||
4. 最后做 Phase D:Reasonix/Hermes adapter 从 manifest 自动生成。
|
||||
@@ -1,507 +0,0 @@
|
||||
# 7-54 RAG-Anything 对照后的多模态检索设计 v1
|
||||
|
||||
> 2026-06-28 覆盖说明:本文围绕 LightRAG 增强撰写,已被 WeKnora / OpenHub 知识库主线覆盖。保留为多模态检索参考材料,不作为当前实施依据。
|
||||
|
||||
> 创建时间:2026-06-08
|
||||
>
|
||||
> 当前状态:`process`
|
||||
>
|
||||
> Owner:07-ai / 03-rust-web / knowledge-rag / search
|
||||
>
|
||||
> 取代:`7-53` 已放弃,不再作为搜索方案依据。
|
||||
>
|
||||
> 参考代码:
|
||||
> - `reference-code/RAG-Anything`
|
||||
> - repo:`https://github.com/HKUDS/RAG-Anything`
|
||||
> - commit:`1bbca28`
|
||||
> - CodeGraph:`codegraph init && codegraph index . --force --quiet`,49 files / 922 nodes / 2308 edges
|
||||
> - 关键文件:`raganything/query.py`、`raganything/processor.py`、`raganything/utils.py`、`raganything/config.py`、`README.md`、`docs/multimodal_rag_failure_modes.md`
|
||||
|
||||
## 1. 新结论
|
||||
|
||||
7-53 的问题是仍在围绕“普通搜索是否混入 OCR”打转,设计重心不对。
|
||||
|
||||
RAG-Anything 的检索设计说明:成熟的 PDF / DOCX / Office / 图片检索,不应该依赖 MNote 自己扫 OCR sidecar 做字符串匹配,而应该把多模态内容作为一等内容写进 RAG 索引:
|
||||
|
||||
```text
|
||||
PDF / DOCX / Office / Image
|
||||
-> parser content_list
|
||||
-> text content -> LightRAG text insert
|
||||
-> image/table/equation -> multimodal processor
|
||||
-> templated multimodal chunks
|
||||
-> LightRAG text_chunks + chunks_vdb + entities_vdb + graph
|
||||
-> query uses vector + graph retrieval
|
||||
```
|
||||
|
||||
所以 MNote 的目标应改为:
|
||||
|
||||
1. 默认普通搜索仍可保持轻量本地搜索。
|
||||
2. 必须有明确的“资料库检索 / 全盘资料库检索”入口,覆盖用户显式纳入资料库索引范围的 PDF、DOCX、Office、图片 OCR。
|
||||
3. 全盘资料库检索必须走 LightRAG 的 chunk/reference 检索,不再走 MNote 侧 OCR sidecar 字符串补结果。
|
||||
4. 多模态资料的 ingest 要尽量变成 RAG-Anything 式的结构化 chunk / entity / relation,而不是只生成 wrapper Markdown。
|
||||
5. 搜索结果必须是可打开的来源命中;问答答案是另一层能力。
|
||||
6. “全盘”只表示在已配置资料库索引目录内全盘检索,不表示自动索引整个 local folder。
|
||||
|
||||
## 2. RAG-Anything 是怎么做检索的
|
||||
|
||||
### 2.1 查询入口分三种
|
||||
|
||||
`raganything/query.py` 提供三类 query。
|
||||
|
||||
第一类是纯文本查询:
|
||||
|
||||
```python
|
||||
query_param = QueryParam(mode=mode, **kwargs)
|
||||
result = await self.lightrag.aquery(query, param=query_param, system_prompt=system_prompt)
|
||||
```
|
||||
|
||||
它直接复用 LightRAG 的 `local/global/hybrid/naive/mix/bypass` 模式。README 推荐示例里人工检索常用 `mode="hybrid"`。
|
||||
|
||||
第二类是 VLM enhanced query:
|
||||
|
||||
```python
|
||||
query_param = QueryParam(mode=mode, only_need_prompt=True, **kwargs)
|
||||
raw_prompt = await self.lightrag.aquery(query, param=query_param)
|
||||
```
|
||||
|
||||
它先只拿 LightRAG 检索出来的 prompt/context,不直接生成最终答案;然后从检索上下文中提取 `Image Path:`,做安全目录校验,编码图片为 base64,再把文本 context + 图片一起交给 VLM 回答。
|
||||
|
||||
第三类是显式 multimodal query:
|
||||
|
||||
```python
|
||||
enhanced_query = await self._process_multimodal_query_content(query, multimodal_content)
|
||||
result = await self.aquery(enhanced_query, mode=mode, system_prompt=system_prompt, **kwargs)
|
||||
```
|
||||
|
||||
用户把表格、公式、图片等内容作为 query 附件传入时,它先用对应 processor 生成描述,再把描述拼回查询文本,最后仍走 LightRAG 检索。
|
||||
|
||||
### 2.2 ingest 不是 OCR sidecar,而是一等 chunk
|
||||
|
||||
`raganything/utils.py` 先把 parser 的 `content_list` 分成:
|
||||
|
||||
```text
|
||||
text_content
|
||||
multimodal_items
|
||||
```
|
||||
|
||||
文本走 LightRAG 原生 `ainsert(...)`,并传 `file_paths` 用于引用。
|
||||
|
||||
多模态内容走 `raganything/processor.py`:
|
||||
|
||||
1. 图片、表格、公式先用对应 processor 生成增强描述。
|
||||
2. `_apply_chunk_template(...)` 把原始路径、caption、footnote、邻近文本、章节路径、表格 body、公式文本、增强描述拼成可检索 chunk。
|
||||
3. `_convert_to_lightrag_chunks_type_aware(...)` 生成 LightRAG 标准 chunk:
|
||||
|
||||
```python
|
||||
{
|
||||
"content": formatted_chunk_content,
|
||||
"tokens": tokens,
|
||||
"full_doc_id": doc_id,
|
||||
"chunk_order_index": chunk_order_index,
|
||||
"file_path": file_ref,
|
||||
"is_multimodal": True,
|
||||
"modal_entity_name": entity_info["entity_name"],
|
||||
"original_type": data["content_type"],
|
||||
"page_idx": data["item_info"].get("page_idx", 0),
|
||||
}
|
||||
```
|
||||
|
||||
4. chunk 同时写入:
|
||||
|
||||
```text
|
||||
lightrag.text_chunks
|
||||
lightrag.chunks_vdb
|
||||
```
|
||||
|
||||
5. 多模态主实体写入:
|
||||
|
||||
```text
|
||||
knowledge graph
|
||||
entities_vdb
|
||||
full_entities
|
||||
```
|
||||
|
||||
6. 对多模态 chunk 调 LightRAG `extract_entities(...)`,再追加 `belongs_to` 关系,最后调用 `merge_nodes_and_edges(...)` 合并回 graph。
|
||||
7. `doc_status.chunks_list/chunks_count` 追加多模态 chunk id。
|
||||
|
||||
这才是“全盘资料库可检索”的关键:图片、表格、公式不是旁路 OCR 文件,而是进入 LightRAG 的 chunk 向量库和图谱。
|
||||
|
||||
### 2.3 检索失败排查也围绕多模态索引
|
||||
|
||||
RAG-Anything 的 failure checklist 不是让 UI 再补一层字符串搜索,而是检查:
|
||||
|
||||
- parser Markdown / `content_list.json` 是否可信。
|
||||
- table block 的 `table_body` 是否保留结构。
|
||||
- image path / caption / page index 是否对齐。
|
||||
- multimodal processing flags 是否启用。
|
||||
- embedding path 是否看到 enriched text,而不是只看到 raw OCR。
|
||||
|
||||
这对 MNote 很重要:搜索不准时,第一诊断点应该是“资料有没有以正确 chunk 进入 LightRAG”,不是前端结果列表怎么融合。
|
||||
|
||||
## 3. MNote 当前差距
|
||||
|
||||
当前 MNote 已经有:
|
||||
|
||||
- `/api/knowledge-rag/ingest`
|
||||
- `/api/knowledge-rag/query`
|
||||
- source registry
|
||||
- LightRAG `/documents/scan`
|
||||
- `/query/data` + references 映射
|
||||
- citationUrl / citationMarkdown / locator enrich
|
||||
|
||||
但普通搜索仍有一条不成熟路径:
|
||||
|
||||
```text
|
||||
/api/search/documents
|
||||
-> local_search_index
|
||||
-> includeOcr 时追加 query_knowledge_rag_ocr_sidecar_results
|
||||
```
|
||||
|
||||
这条 `knowledge_rag_ocr_sidecar_results` 是 MNote 遍历 registry sidecar 后做文本匹配,不是 LightRAG vector/graph retrieval。它会让搜索看起来“用了 OCR”,但排序、召回、语义、图谱关系和多模态 chunk 都不是一套成熟系统。
|
||||
|
||||
另一个差距是 ingest。MNote 当前对 LightRAG scan 支持文件直接 symlink;对非 scan 支持文件生成 Markdown wrapper:
|
||||
|
||||
```text
|
||||
# file
|
||||

|
||||
```
|
||||
|
||||
这对图片入口有帮助,但不等于 RAG-Anything 的 multimodal chunk / entity / relation 管线。后者会把图片、表格、公式的描述、上下文、页码、路径都写进 LightRAG chunk 和 graph。
|
||||
|
||||
另一个必须保留的边界是索引范围。MNote 是 local-folder 产品,不能默认把整个本地目录都纳入 LightRAG。用户需要人工控制哪些目录参与资料库检索,否则会带来:
|
||||
|
||||
- 大目录首次索引成本不可控。
|
||||
- 隐私和权限边界不清。
|
||||
- 搜索噪音扩大,资料库结果质量下降。
|
||||
- 删除/移动/重建的 watcher 压力过高。
|
||||
|
||||
因此资料库检索依赖显式索引目录,而不是自动覆盖整个 workspace。
|
||||
|
||||
## 4. 新搜索边界
|
||||
|
||||
### 4.1 默认搜索
|
||||
|
||||
默认搜索只做快速定位:
|
||||
|
||||
- Markdown 页面标题
|
||||
- Markdown 正文
|
||||
- 路径
|
||||
- 最近打开 / 最近修改
|
||||
- 本地轻索引资源元信息
|
||||
|
||||
默认不混入 OCR sidecar,也不混入 LightRAG 语义结果。
|
||||
|
||||
### 4.2 全盘资料库检索
|
||||
|
||||
搜索弹窗必须有明确入口:
|
||||
|
||||
```text
|
||||
范围:当前页 / 工作区 / 全盘资料库
|
||||
```
|
||||
|
||||
选择 `全盘资料库` 后:
|
||||
|
||||
- 调 LightRAG retrieval,而不是 MNote sidecar grep。
|
||||
- 覆盖已纳入资料库索引目录的 PDF、DOCX、Office、图片 OCR、表格、公式、Markdown。
|
||||
- 返回 references/chunks 作为搜索结果。
|
||||
- 不默认生成 answer。
|
||||
- 每条结果有来源、snippet、类型、页码 / bbox / block / resource fallback。
|
||||
- 结果能直接打开对应 resource tab。
|
||||
- 对未入库目录明确显示“未纳入资料库索引”,而不是让用户误以为 LightRAG 漏检。
|
||||
|
||||
建议协议:
|
||||
|
||||
```text
|
||||
POST /api/knowledge-rag/search
|
||||
```
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"workspaceId": "...",
|
||||
"rootUri": "file:///...",
|
||||
"query": "压缩率 Canterbury corpus",
|
||||
"mode": "hybrid",
|
||||
"topK": 12,
|
||||
"chunkTopK": 24,
|
||||
"includeChunkContent": true,
|
||||
"includeReferences": true,
|
||||
"answer": false
|
||||
}
|
||||
```
|
||||
|
||||
实现上可以先复用 `/api/knowledge-rag/query` 的 `/query/data` 调用,只是响应包装成 search results,不展示 answer。
|
||||
|
||||
### 4.2.1 资料库索引范围
|
||||
|
||||
MNote 需要把“全盘资料库检索”建立在显式索引范围上:
|
||||
|
||||
```text
|
||||
Knowledge Search Scope
|
||||
includeRoots:
|
||||
- docs/research
|
||||
- resources/papers
|
||||
excludePatterns:
|
||||
- "**/draft-private/**"
|
||||
- "**/.git/**"
|
||||
- "**/node_modules/**"
|
||||
runOnChange: true|false
|
||||
```
|
||||
|
||||
产品语义:
|
||||
|
||||
- `includeRoots` 是用户主动加入资料库索引的目录或文件。
|
||||
- `excludePatterns` 后期必须支持,用于排除私密目录、临时目录、构建产物、大型无关资产。
|
||||
- LightRAG 只保证检索这些已入库范围。
|
||||
- 普通本地搜索仍可搜索 Markdown / 本地轻索引,不要求 LightRAG 入库。
|
||||
- 如果用户在全盘资料库模式下搜不到某文件,UI 要能提示它是否未入库、入库中、失败、已排除或已删除。
|
||||
|
||||
实现上,source registry 需要从“单个 source 列表”升级为“索引范围 + source 状态”:
|
||||
|
||||
```text
|
||||
.mnote/index/lightrag-source-registry.json
|
||||
indexedRoots[]
|
||||
rootRelativePath
|
||||
recursive
|
||||
excludes[]
|
||||
runOnChange
|
||||
updatedAt
|
||||
entries[]
|
||||
sourceRootRelativePath
|
||||
sourceHash
|
||||
lightRagDocId
|
||||
chunkIds
|
||||
status
|
||||
excludedBy
|
||||
```
|
||||
|
||||
这保留了当前“人工控制哪些需要全盘搜索”的价值,同时给后续自动 watcher / 增量重建留出边界。
|
||||
|
||||
### 4.3 Knowledge Ask
|
||||
|
||||
Knowledge Ask / Page AI 才生成答案:
|
||||
|
||||
- 用同一套 LightRAG references。
|
||||
- 展示 `Context used`。
|
||||
- 引用使用 `citationMarkdown` / `citationUrl`。
|
||||
- 可选 VLM enhanced:先拿 retrieved context,再把命中的图片资源交给 VLM 参与回答。
|
||||
|
||||
### 4.4 当前页搜索
|
||||
|
||||
当前页内查找仍不走 LightRAG:
|
||||
|
||||
- Markdown:editor / PageAggregate 内查找。
|
||||
- PDF:viewer 文本层 / sidecar locator。
|
||||
- Office:预览/解析层查找。
|
||||
|
||||
这是局部查找,不是全盘检索。
|
||||
|
||||
## 5. MNote 应参考 RAG-Anything 的实施方案
|
||||
|
||||
### Phase A:放弃 7-53,移除 sidecar 补结果主路径
|
||||
|
||||
目标:先停止把 sidecar grep 当成熟 OCR 搜索。
|
||||
|
||||
改动:
|
||||
|
||||
1. 默认搜索关闭 `includeOcr`。
|
||||
2. `includeOcr` 不再调用 `query_knowledge_rag_ocr_sidecar_results` 作为普通结果补充。
|
||||
3. UI 增加 `全盘资料库` scope,先复用 `/api/knowledge-rag/query` 的 references 返回。
|
||||
4. 结果标注 `provider=lightrag`、`matchSource=lightrag_reference`。
|
||||
|
||||
验收:
|
||||
|
||||
- 默认搜索短词不出现 PDF/OCR 假阳性。
|
||||
- 选择全盘资料库后可以搜到 PDF/DOCX/OCR 资料。
|
||||
- 搜索结果能打开对应 resource tab。
|
||||
|
||||
### Phase B:新增 references-only retrieval facade
|
||||
|
||||
目标:把问答和人工检索分开。
|
||||
|
||||
新增:
|
||||
|
||||
```text
|
||||
POST /api/knowledge-rag/search
|
||||
```
|
||||
|
||||
内部:
|
||||
|
||||
- 调 LightRAG `/query/data`。
|
||||
- `include_references=true`。
|
||||
- `include_chunk_content=true`。
|
||||
- `mode=hybrid` 或 `mix`,后续用 benchmark 决定默认值。
|
||||
- 不把 answer 放入搜索结果。
|
||||
- 对 references 做 registry mapping、locator enrich、ranking。
|
||||
|
||||
验收:
|
||||
|
||||
- API 返回 `mnote.knowledge_rag.search_results.v1`。
|
||||
- 每条 result 有 `citationUrl` / `citationMarkdown` / `locatorPrecision`。
|
||||
- 对无 locator 的命中明确降级到文件级打开。
|
||||
|
||||
### Phase C:RAG-Anything 式多模态 chunk 入库
|
||||
|
||||
目标:让图片、表格、公式、Office/PDF 结构进入 LightRAG retrieval,而不是只存在 sidecar。
|
||||
|
||||
设计:
|
||||
|
||||
1. MNote parser 输出统一 `content_list`:
|
||||
- `text`
|
||||
- `image`
|
||||
- `table`
|
||||
- `equation`
|
||||
- `generic`
|
||||
2. text 走 LightRAG 文本 insert / scan。
|
||||
3. multimodal items 生成 templated chunk:
|
||||
- source path
|
||||
- page index
|
||||
- block id / bbox
|
||||
- caption / footnote
|
||||
- table body
|
||||
- equation text / latex
|
||||
- neighbor text / section path
|
||||
- enhanced description
|
||||
4. 写入 LightRAG chunk/vector/graph,或通过扩展 LightRAG/RAG-Anything adapter 实现。
|
||||
5. registry 保存 `sourceHash -> lightRagDocId -> chunkIds -> locator map`。
|
||||
|
||||
验收:
|
||||
|
||||
- 查询“图 3 展示了什么”能召回图片 chunk,而不是只召回邻近正文。
|
||||
- 查询表格中的指标能召回 table chunk,snippet 包含表格 body。
|
||||
- query result 能通过 chunkId 映射回 MNote resource tab 的 page/bbox/block。
|
||||
|
||||
### Phase D:VLM enhanced Ask
|
||||
|
||||
目标:回答中真正看图,而不是只读图片 caption。
|
||||
|
||||
参考 RAG-Anything:
|
||||
|
||||
1. 先让 LightRAG 返回 retrieval prompt/context。
|
||||
2. 从 context 中提取命中图片路径或 MNote resource id。
|
||||
3. 做 allowed roots / resource permission 校验。
|
||||
4. 把图片编码或转为可访问 asset URL。
|
||||
5. 文本 context + image 一起交给 VLM。
|
||||
|
||||
这一步只用于 Ask,不用于普通搜索结果。
|
||||
|
||||
## 6. 关键约束
|
||||
|
||||
- 全盘资料库检索是必需能力,不能因为默认关闭 OCR 而消失。
|
||||
- 默认搜索和全盘资料库检索必须是两个明确模式,不能暗中混合。
|
||||
- 不再把 sidecar grep 当成熟资料库搜索。
|
||||
- locator 不足时只能声明降级,不能伪造页码/bbox。
|
||||
- `file_path` 不能作为唯一真相;MNote registry 必须维护绝对路径、root-relative path、source hash、LightRAG doc id、chunk id、locator map。
|
||||
- 多模态 chunk 的 source path / image path 必须做权限和安全目录校验。
|
||||
|
||||
## 7. 立即建议
|
||||
|
||||
下一步不要继续修 7-53。
|
||||
|
||||
应直接做:
|
||||
|
||||
1. 删除/停用普通搜索里的 OCR sidecar 补结果。
|
||||
2. 新增 `资料库索引范围` 设置,至少支持 include roots;excludes 先设计协议,后续实现。
|
||||
3. 新增 `全盘资料库` 搜索 scope,只检索已入库范围,调用 LightRAG references。
|
||||
4. 写 `7-54 Phase A/B` 的 smoke:默认搜索不混 OCR;未入库 PDF/DOCX 在资料库检索中明确提示未入库;加入索引目录后可搜到并打开。
|
||||
5. 后续再做 RAG-Anything 式多模态 chunk 入库。
|
||||
|
||||
这样既保留 MNote 的核心价值,也把检索系统拉回成熟 RAG 项目的设计方向。
|
||||
|
||||
## 8. Phase A/B 实施记录
|
||||
|
||||
时间:2026-06-08
|
||||
|
||||
已完成:
|
||||
|
||||
1. 普通搜索不再把 `includeOcr=true` 转成 `query_knowledge_rag_ocr_sidecar_results` 补结果;`/api/search/documents` 的边界固定回 `ordinary_local_search`,`knowledgeRag=false`,`ocrSidecarFallback=false`。
|
||||
2. 新增 `POST /api/knowledge-rag/search`,作为 references-only retrieval facade:
|
||||
- 调 LightRAG `/query/data`
|
||||
- `include_references=true`
|
||||
- 默认保留 `include_chunk_content=true`
|
||||
- 复用 registry mapping / locator enrich / citationUrl / citationMarkdown
|
||||
- 返回 `mnote.knowledge_rag.search_results.v1`
|
||||
- 搜索结果标记 `provider=lightrag`、`matchSource=lightrag_reference`
|
||||
3. source registry 增加兼容字段 `indexedRoots[]`,ingest 显式 source 时记录 include root:
|
||||
- `rootRelativePath`
|
||||
- `recursive`
|
||||
- `excludePatterns`
|
||||
- `runOnChange`
|
||||
- `updatedAtMs`
|
||||
旧 registry JSON 缺少 `indexedRoots` 时默认空数组。
|
||||
4. 搜索弹窗新增 `全盘资料库`开关;默认工作区搜索发送 `includeOcr=false`,打开开关后调用 `/api/knowledge-rag/search`。
|
||||
5. `scripts/task538-knowledge-rag-source-scope-api-smoke.js` 增加 `/api/knowledge-rag/search` 验证,确认 sourcePaths post-filter 后只返回目标 source,且结果来自 LightRAG references 并可打开来源。
|
||||
|
||||
验证:
|
||||
|
||||
```text
|
||||
node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js
|
||||
node --check scripts/task538-knowledge-rag-source-scope-api-smoke.js
|
||||
cargo check --manifest-path rust/Cargo.toml -p mnote-web
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_documents_route_is_owned_by_mnote_web -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_documents_local_folder_does_not_promote_evidence_sqlite_body_hits -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web indexed_root -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web references_only_search_result -- --nocapture
|
||||
node scripts/task125-rust-web-search-server-first-smoke.js
|
||||
node scripts/task538-knowledge-rag-source-scope-api-smoke.js
|
||||
Playwright 登录态验证:默认搜索 includeOcr=false;全盘资料库开关调用 /api/knowledge-rag/search
|
||||
```
|
||||
|
||||
`scripts/task128-rust-web-wolai-search-modal-smoke.js` 未作为本次验收依据:当前默认登录态要求下它直接访问 `/` 等待 topbar,曾因未建立登录态而超时。已用登录态 Playwright 脚本覆盖本次新增搜索 scope 行为。
|
||||
|
||||
未完成:
|
||||
|
||||
- `excludePatterns` 仅进入 registry 协议,尚未实现 UI 编辑和 glob 过滤。
|
||||
- RAG-Anything 式 multimodal chunks / entities / relations 入库仍是 Phase C,未标完成。
|
||||
|
||||
## 9. Phase B.1:chunk 上下文段落级定位
|
||||
|
||||
时间:2026-06-08
|
||||
|
||||
新增结论:
|
||||
|
||||
当前用户需要的是“搜索结果点击后落到对应段落”,不是必须文字级 bbox。对 DOCX / Office 这类当前缺少 page/bbox 的资料,短期不应强制改成 DOCX -> PDF viewer 管线;更低风险的路径是复用 LightRAG 已返回的 chunk 上下文做段落级定位。
|
||||
|
||||
参考 `rag-knowledge-system` 的可采用点:
|
||||
|
||||
- 检索结果必须保留稳定 chunk metadata。
|
||||
- 点击定位不能只用 query 短词,应使用 chunk/snippet 上下文作为 anchor。
|
||||
- 目录/文件夹 scope 是 retrieval 层的过滤条件,不是 UI 结果列表后处理。
|
||||
|
||||
不直接照搬的点:
|
||||
|
||||
- `rag-knowledge-system` 的 PDF highlight 先有 `page_num`,再在页内 `search_for(anchor)`;MNote 当前 DOCX sidecar 没有页码,所以只能先做到文档内段落级定位。
|
||||
- 对 DOCX 缺 page/bbox 时,不能伪造页码或 bbox,应返回 `locatorPrecision=paragraph` / `locatorDegraded=true`。
|
||||
|
||||
设计:
|
||||
|
||||
```text
|
||||
LightRAG /query/search
|
||||
-> chunk content / occurrence
|
||||
-> MNote registry mapping
|
||||
-> sidecar block match
|
||||
-> locator:
|
||||
blockId
|
||||
sourceMapPath
|
||||
evidenceText = query-centered block/chunk context
|
||||
locatorPrecision = bbox | paragraph | file
|
||||
-> office-preview:
|
||||
clean evidenceText
|
||||
generate multiple anchors
|
||||
score rendered paragraphs/tables by context overlap
|
||||
scroll/highlight best paragraph
|
||||
fallback to old short text candidates only after paragraph scoring fails
|
||||
```
|
||||
|
||||
验收样例:
|
||||
|
||||
- `吡咯烷`:不应因为短词优先命中 `N-甲基吡咯烷酮`,应优先选择与 returned chunk 上下文整体重合最高的段落。
|
||||
- `三乙基硅` / `三甲基硅`:存在 bbox 时仍使用 bbox;没有 bbox 时段落级定位,不伪造页码。
|
||||
- 同一已打开 DOCX 点击不同搜索结果时,resource tab 不重新加载,只通过 `postMessage` 更新 locator 并重新定位。
|
||||
|
||||
降级语义:
|
||||
|
||||
- `locatorPrecision=bbox`:有 page + bbox,可页内精确框选。
|
||||
- `locatorPrecision=paragraph`:有 block/context,但缺 page/bbox,只保证段落级定位。
|
||||
- `locatorPrecision=file`:只能打开文件,定位信息不足。
|
||||
@@ -1,494 +0,0 @@
|
||||
# 7-61 [process] Yuxi reference: middleware tool composition, extensions entry, knowledge settings UI, and local dashboard v1
|
||||
|
||||
> 2026-06-28 覆盖说明:本文中 Hermes / LightRAG 相关实现口径已部分退为 legacy。仍可参考 Yuxi 的 middleware、extensions 入口和 dashboard 信息架构,但当前 MNote 知识库 / agent 主线以 OpenHub / opencode / WeKnora 为准。
|
||||
|
||||
> 创建时间:2026-06-13
|
||||
>
|
||||
> 当前状态:`PROCESS`
|
||||
>
|
||||
> Owner:07-ai / 03-rust-web / Hermes tools / Page AI settings / Knowledge RAG UI / Dashboard
|
||||
>
|
||||
> 上位设计:`design/07-ai/done/7-57-yuxi-reference-ai-run-config-knowledge-facade-v1.md`
|
||||
>
|
||||
> 本稿是 7-57 的补充,覆盖 7-57 未涉及的 Yuxi 参考领域。
|
||||
>
|
||||
> 参考代码:
|
||||
> - `reference-code/Yuxi/backend/package/yuxi/agents/middlewares/`(中间件分层组合)
|
||||
> - `reference-code/Yuxi/backend/package/yuxi/agents/base.py`(Agent 基类 + checkpointer)
|
||||
> - `reference-code/Yuxi/web/src/views/ExtensionsView.vue`(扩展统一入口)
|
||||
> - `reference-code/Yuxi/web/src/views/DataBaseInfoView.vue`(知识库详情页)
|
||||
> - `reference-code/Yuxi/web/src/views/DashboardView.vue`(Dashboard 可观测性)
|
||||
> - `reference-code/Yuxi/web/src/composables/useAgentStreamHandler.js`(流式事件状态机)
|
||||
> - `reference-code/Yuxi/backend/package/yuxi/agents/backends/sandbox/paths.py`(虚拟路径安全)
|
||||
>
|
||||
> 上位依据:
|
||||
> - `ARCHITECTURE.md`
|
||||
> - `CURRENT_ARCHITECTURE.md`
|
||||
> - `design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md`
|
||||
> - `design/07-ai/done/7-57-yuxi-reference-ai-run-config-knowledge-facade-v1.md`
|
||||
> - `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md`
|
||||
> - `design/03-rust-web/reference/3-23-sidebar-local-folder-resource-runtime-followup-v1.md`
|
||||
> - `design/10-review/done/16-mnote-web-runtime-module-maintainability-checklist-v1.md`
|
||||
|
||||
## 1. 第一结论
|
||||
|
||||
Yuxi 有六个工程模式值得 MNote 借鉴,但不适合整体搬移:
|
||||
|
||||
1. **中间件分层工具组合**:Hermes tool manifest 当前是扁平 Vec<Value>,每次新增能力(knowledge base、skills、MCP、filesystem、subagent、summary)都在同一个 manifest 函数中线性追加。Yuxi 的 middleware 模式把每种能力作为独立层,按需组合。
|
||||
2. **扩展管理统一入口**:当前 MNote 的 Skills/MCP/Knowledge Base/Tools 入口分散在 settings、Page AI settings、侧边栏等不同位置。Yuxi 的 `ExtensionsView` 用 tab 统一管理所有扩展。
|
||||
3. **知识库详情设置页**:当前 LightRAG settings 较薄,只有 source directory 和 basic status。Yuxi 的 `DataBaseInfoView` 提供了文件管理、检索配置、图谱配置、评估等完整详情页。
|
||||
4. **流式事件状态机规范化**:当前 Hermes SSE/WS 事件处理分散在多个 consumer(sidebar runtime、document adapter、Page AI sidebar)。Yuxi 的 `handleStreamChunk` 是单一状态机,覆盖 init→loading→stream_event→finished/interrupted/error 全生命周期。
|
||||
5. **本地 Dashboard**:当前 MNote 没有本地可观测性面板。Yuxi 的 Dashboard 提供了 agent run 调用量、token 消耗、知识库检索次数、工具调用频率等可视化。
|
||||
6. **虚拟路径安全**:当前 `WorkspacePath` 校验较松散。Yuxi 的 sandbox paths 有 `validate_thread_id` + `relative_to` 防穿越、虚拟路径前缀隔离、按 thread/user 分层。
|
||||
|
||||
这些都属于 MNote 现有主线的 enhancer,不改变底层(Hermes ACP / Reasonix / LightRAG / Rust SSR),不引入新存储或新 agent 框架。
|
||||
|
||||
## 2. 非目标
|
||||
|
||||
- 不引入 LangGraph / DeepAgents 作为 agent runtime。
|
||||
- 不复制 Yuxi 的 Milvus / Neo4j / Postgres 存储体系。
|
||||
- 不把 Vue/Ant Design 前端模式迁入 Rust SSR / browser runtime。
|
||||
- 不改变 LightRAG 作为唯一知识库 provider 的定位。
|
||||
- 不新增定时轮询刷新。
|
||||
- 不改变 local-first 普通 Markdown 编辑主路径。
|
||||
- 不改变 7-57 已完成的 run journal / agent descriptor / knowledge facade 成果。
|
||||
|
||||
## 3. 六个借鉴领域详析
|
||||
|
||||
### 3.1 中间件分层工具组合
|
||||
|
||||
**Yuxi 模式**:
|
||||
|
||||
```python
|
||||
class ChatbotAgent(BaseAgent):
|
||||
async def get_graph(self, context=None, **kwargs):
|
||||
middlewares = [
|
||||
create_agent_filesystem_middleware(...), # 文件系统
|
||||
save_attachments_to_fs, # 附件落地
|
||||
KnowledgeBaseMiddleware(), # 知识库检索
|
||||
SkillsMiddleware(), # Skills 注入
|
||||
create_subagent_task_middleware(context), # 子智能体
|
||||
summary_middleware, # 上下文压缩
|
||||
TodoListMiddleware(...), # 任务列表
|
||||
PatchToolCallsMiddleware(), # 工具调用修复
|
||||
ModelRetryMiddleware(max_retries=2), # 模型重试
|
||||
]
|
||||
return create_agent(model=..., tools=..., middleware=middlewares, ...)
|
||||
```
|
||||
|
||||
每个中间件是独立类/函数,按声明顺序构成 agent 的能力栈。新增能力只需新增一个 middleware,不改动 agent 核心流程。
|
||||
|
||||
**MNote 当前状态**:
|
||||
|
||||
`manifest.rs` 中 Hermes tool manifest 是扁平 `Vec<Value>`,约 30+ 个工具在一个函数中线性追加。新增 knowledge_rag、skills、context、block、onlyoffice 等工具时,每次都在同一个 manifest 函数末尾追加,没有按能力域分组。
|
||||
|
||||
```rust
|
||||
pub fn manifest() -> Value {
|
||||
let tools = annotate_tools_with_capabilities(vec![
|
||||
skill_read_tool(),
|
||||
context_snapshot_tool(),
|
||||
// ... 30+ tools
|
||||
available_tool("mnote.artifact.create_summary", ...),
|
||||
]);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**借鉴方向**:
|
||||
|
||||
不是引入 Python 式 class middleware,而是把 Rust manifest 按能力域拆成独立 registry 模块,每个模块自声明工具和能力:
|
||||
|
||||
```text
|
||||
hermes_tools/
|
||||
manifest.rs → 顶层装配,组合各 registry
|
||||
registry/
|
||||
context_registry.rs → context.* 工具组
|
||||
doc_registry.rs → mnote.doc.* 工具组
|
||||
block_registry.rs → mnote.block.* 工具组
|
||||
page_registry.rs → mnote.page.* 工具组
|
||||
knowledge_rag_registry.rs → mnote.knowledge_rag.* 工具组
|
||||
skill_registry.rs → mnote.skill.* 工具组
|
||||
mindmap_registry.rs → mindmap.* 工具组
|
||||
onlyoffice_registry.rs → onlyoffice.* 工具组
|
||||
artifact_registry.rs → mnote.artifact.* 工具组
|
||||
```
|
||||
|
||||
每个 registry 暴露 `pub fn tools() -> Vec<Value>` 和 `pub fn capabilities() -> Vec<&'static str>`。manifest 只负责装配和 capability annotation。Agent descriptor 可以按 capability 选择性包含工具组。
|
||||
|
||||
### 3.2 扩展管理统一入口
|
||||
|
||||
**Yuxi 模式**:
|
||||
|
||||
`ExtensionsView.vue` 用一个 tab 组件统一管理四种扩展:
|
||||
|
||||
```
|
||||
智能体扩展
|
||||
├── 知识库(管理员可见)
|
||||
├── 工具(管理员可见)
|
||||
├── MCP(管理员可见)
|
||||
└── Skills(所有用户可见)
|
||||
```
|
||||
|
||||
每种扩展是独立子组件,通过 tab 切换懒加载。URL query 参数持久化当前 tab,刷新后不丢失。
|
||||
|
||||
**MNote 当前状态**:
|
||||
|
||||
- Skills 入口:Page AI settings → Skills 子面板
|
||||
- Knowledge base 入口:settings → 资料库 settings
|
||||
- MCP:Hermes MCP 配置在 Hermes workspace settings
|
||||
- Tools:无独立管理入口,Hermes tool manifest 是编译期静态
|
||||
|
||||
这四个入口分散在不同 settings surface,用户需要记住去哪找。
|
||||
|
||||
**借鉴方向**:
|
||||
|
||||
在 settings 页面增加"扩展"tab(或在现有 settings IA 中统一),按权限分组:
|
||||
|
||||
```text
|
||||
设置
|
||||
├── 通用
|
||||
├── 外观
|
||||
├── 编辑器
|
||||
├── 扩展 ← 新增
|
||||
│ ├── 知识库(LightRAG 资料库管理)
|
||||
│ ├── Skills(已安装 + 可用)
|
||||
│ ├── MCP 服务器
|
||||
│ └── Agent 工具状态(只读能力清单)
|
||||
├── AI(Hermes / Reasonix 配置)
|
||||
└── 关于
|
||||
```
|
||||
|
||||
第一阶段不要求完整 CRUD,先做只读能力清单 + 已安装 Skills/MCP 状态展示 + 知识库详情入口。URL hash 持久化当前 tab。
|
||||
|
||||
### 3.3 知识库详情设置页
|
||||
|
||||
**Yuxi 模式**:
|
||||
|
||||
`DataBaseInfoView.vue` 是知识库详情页,子 tab 包括:
|
||||
|
||||
- **文件管理**:文件夹树、上传、解析状态跟踪(pending/processing/done/error)、批量删除、文件预览
|
||||
- **检索测试**:输入 query → 展示检索结果 + 相似度分数 + 来源文件
|
||||
- **检索配置**:向量/BM25/混合检索模式选择、TopK、相似度阈值、BM25 权重
|
||||
- **图谱配置**:实体抽取 schema、关系类型约束
|
||||
- **评估**:检索质量评估、问答对测试
|
||||
- **设置**:名称、描述、嵌入模型、分块策略、共享配置
|
||||
|
||||
**MNote 当前状态**:
|
||||
|
||||
LightRAG settings 较薄,只有:
|
||||
- source directory 配置
|
||||
- ingest 触发按钮
|
||||
- 基本状态灯(indexed / pending)
|
||||
- OCR 开关
|
||||
|
||||
**借鉴方向**:
|
||||
|
||||
在不改变 LightRAG provider 的前提下,增强 knowledge base settings UI:
|
||||
|
||||
1. **文件管理面板**:展示已 ingest 文件列表、解析状态(parsing/chunking/indexing/done/error)、文件类型图标、最后更新时间
|
||||
2. **检索测试面板**:输入 query → 直接调 `/api/knowledge-rag/search` → 展示 top-K 结果 + citation preview + 来源文件 + 相关度
|
||||
3. **检索参数配置**:search mode(local/global/hybrid/naive/mix)、top_k、相似度阈值(expose LightRAG `QueryParam` 可配置项)
|
||||
4. **Ingest 状态跟踪**:LightRAG 后台完成状态通过现有 status bridge/backoff 同步到 UI,展示每个文件的 stage 和 error
|
||||
|
||||
不新增:
|
||||
- 分块策略 UI(由 LightRAG 管)
|
||||
- 图谱 schema 编辑(由 LightRAG 管)
|
||||
- 嵌入模型切换(由 LightRAG 管)
|
||||
|
||||
### 3.4 流式事件状态机规范化
|
||||
|
||||
**Yuxi 模式**:
|
||||
|
||||
`useAgentStreamHandler.js` 的 `handleStreamChunk` 是一个规范的状态机:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ stream start │
|
||||
└─────────────────┬───────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ init │ ← 建立 request_id 绑定
|
||||
│ → replyLoadingVisible │
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ loading │ ← message_delta / tool_call_delta
|
||||
│ → push msgChunks │
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ stream_event │ ← tool-started / tool-finished
|
||||
│ → 关联 tool_call_id │ agent_state (todos/uploads)
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ finished / interrupted │ ← terminal
|
||||
│ / error / approval │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
关键设计点:
|
||||
- 每种 status 有明确的过渡规则
|
||||
- `stream_event` 中的 `tool-finished` 通过 `tool_call_id` 关联到对应 AI 消息
|
||||
- `agent_state` 事件同步 todos/uploads 等运行时状态
|
||||
- 中断(approval/interrupted)有独立的恢复路径
|
||||
|
||||
**MNote 当前状态**:
|
||||
|
||||
Hermes SSE/WS 事件处理分散在多个 consumer:
|
||||
- `sidebar-page-ai-runtime.js` 处理 Page AI 面板的流式回复
|
||||
- `document-editor-adapter-runtime.js` 处理编辑器内的 agent 交互
|
||||
- 每个 consumer 有自己的事件解析和状态管理
|
||||
|
||||
**借鉴方向**:
|
||||
|
||||
不是引入 Vue composable,而是在 browser runtime 中抽出一个共享的 `AgentStreamEventRouter`:
|
||||
|
||||
```javascript
|
||||
// rust/crates/mnote-web/browser/agent-stream-event-router.js
|
||||
|
||||
const EVENT_STATES = {
|
||||
INIT: 'init',
|
||||
LOADING: 'loading',
|
||||
STREAM_EVENT: 'stream_event',
|
||||
FINISHED: 'finished',
|
||||
INTERRUPTED: 'interrupted',
|
||||
ERROR: 'error',
|
||||
APPROVAL_REQUIRED: 'approval_required',
|
||||
};
|
||||
|
||||
function createAgentStreamRouter({ onDelta, onToolCall, onToolResult, onAgentState, onTerminal }) {
|
||||
return function routeEvent(event) {
|
||||
switch (event.status) {
|
||||
case EVENT_STATES.INIT:
|
||||
// 建立 request_id ↔ message 绑定
|
||||
break;
|
||||
case EVENT_STATES.LOADING:
|
||||
// 判断 message_delta / tool_call_delta → onDelta / onToolCall
|
||||
break;
|
||||
case EVENT_STATES.STREAM_EVENT:
|
||||
// tool-started / tool-finished → onToolResult
|
||||
// agent_state → onAgentState
|
||||
break;
|
||||
case EVENT_STATES.FINISHED:
|
||||
case EVENT_STATES.INTERRUPTED:
|
||||
case EVENT_STATES.ERROR:
|
||||
onTerminal(event);
|
||||
break;
|
||||
case EVENT_STATES.APPROVAL_REQUIRED:
|
||||
// 中断等待用户确认
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Page AI sidebar runtime 和 document editor adapter 都消费同一个 router,各自提供自己的 `onDelta`/`onToolCall`/`onTerminal` 回调。
|
||||
|
||||
### 3.5 本地 Dashboard
|
||||
|
||||
**Yuxi 模式**:
|
||||
|
||||
Dashboard 包含:
|
||||
- **调用统计**:按时间线的 agent run 次数、成功/失败率
|
||||
- **用户活跃度**:DAU/MAU、会话时长
|
||||
- **AI 智能体分析**:各 agent 调用量排行
|
||||
- **工具调用监控**:各 tool 被调用次数、耗时
|
||||
- **知识库使用情况**:检索次数、文件数、chunk 数
|
||||
|
||||
**MNote 当前状态**:无 Dashboard。
|
||||
|
||||
**借鉴方向**:
|
||||
|
||||
为 local-first 场景做最小 Dashboard:
|
||||
|
||||
1. **Agent Run 统计**(基于 7-57 run journal):最近 N 天 run 次数、平均耗时、成功/失败/中断分布
|
||||
2. **Token 消耗**(基于 run journal 中的 token usage):按 agent、按日期维度
|
||||
3. **知识库状态**(基于 LightRAG status bridge):已索引文件数、总 chunk 数、最近 ingest 时间
|
||||
4. **Skills/MCP 健康**:已安装 skill 数、MCP 连接状态
|
||||
|
||||
数据源优先级:
|
||||
- Agent run 统计:SQLite control-plane `agent_runs` 表(7-57 已建)
|
||||
- Token 消耗:从 run journal 中提取
|
||||
- 知识库状态:LightRAG `/status` API + MNote status bridge
|
||||
- Skills/MCP:Hermes workspace 配置 + Rust manifest
|
||||
|
||||
不新增:
|
||||
- 多用户统计(local-first 不需要)
|
||||
- 实时推送更新(10 分钟 TTL + 手动刷新即可)
|
||||
- 独立的时序数据库
|
||||
|
||||
### 3.6 虚拟路径安全加固
|
||||
|
||||
**Yuxi 模式**:
|
||||
|
||||
沙盒路径的关键安全设计:
|
||||
|
||||
```python
|
||||
# 1. ID 安全校验
|
||||
_SAFE_ID_RE = re.compile(r"^[A-Za-z0-9_-]+$")
|
||||
|
||||
def validate_thread_id(thread_id: str) -> str:
|
||||
if not _SAFE_ID_RE.match(value):
|
||||
raise ValueError("thread_id contains invalid characters")
|
||||
|
||||
# 2. 虚拟路径前缀隔离
|
||||
def get_virtual_path_prefix() -> str:
|
||||
return "/" + VIRTUAL_PATH_PREFIX.strip("/") # /sandbox-v1
|
||||
|
||||
# 3. 路径穿越防护
|
||||
def resolve_virtual_path(thread_id, virtual_path, *, uid):
|
||||
# 必须从虚拟前缀开始
|
||||
# 用 relative_to 校验不穿越 base_dir
|
||||
|
||||
# 4. 按 thread/user 分层
|
||||
# workspace/ → 用户级共享
|
||||
# uploads/ → 线程级
|
||||
# outputs/ → 线程级
|
||||
```
|
||||
|
||||
**MNote 当前状态**:
|
||||
|
||||
`WorkspacePath` 主要通过 `allowed_roots` 做目录白名单校验,但没有:
|
||||
- 线程级隔离(同一用户的多个 agent run 共享同一文件系统视图)
|
||||
- 虚拟路径前缀(agent 直接看到宿主机真实路径)
|
||||
- 正则 ID 校验
|
||||
|
||||
**借鉴方向**:
|
||||
|
||||
不是引入 Yuxi 的 Python sandbox,而是在 Rust `WorkspacePath` / `AiAccessScope` 中加固:
|
||||
|
||||
1. **Agent 可见路径使用虚拟前缀**:agent 通过 `mnote://workspace/`、`mnote://uploads/` 等虚拟路径引用文件,MNote 在 tool 边界做映射
|
||||
2. **Run 级临时目录隔离**:每个 agent run 有独立 `mnote://runs/{run_id}/outputs/`,不污染 workspace
|
||||
3. **路径穿越防护**:所有 agent 发来的路径必须在 allowed roots 内,且经过 `canonicalize` + `starts_with` 双重校验
|
||||
4. **Attachments 目录**:agent 上传/生成的附件落 `mnote://attachments/{run_id}/`,与 workspace 文件明确分开
|
||||
|
||||
第一阶段只加固现有 `AiAccessScope` + `allowed_roots`,不改变 agent 现有文件操作语义。
|
||||
|
||||
## 4. 执行优先级
|
||||
|
||||
| 批次 | 内容 | 优先级 | 预计影响面 |
|
||||
|------|------|--------|-----------|
|
||||
| **Batch A** | Hermes tool registry 模块化拆分 | 中 | manifest.rs 重构,不改变 API |
|
||||
| **Batch B** | 扩展管理 settings 入口 + 能力清单 | 高 | settings UI,不改变后端 |
|
||||
| **Batch C** | LightRAG 知识库详情页(文件管理 + 检索测试 + 检索参数) | 高 | knowledge-rag settings UI + 少量 API |
|
||||
| **Batch D** | AgentStreamEventRouter 抽共享 | 中 | browser runtime 重构,不改变 API |
|
||||
| **Batch E** | 本地 Dashboard 最小版 | 低 | 新页面 + SQLite 查询 |
|
||||
| **Batch F** | WorkspacePath 虚拟路径安全加固 | 低 | AiAccessScope 增强,agent tool 适配 |
|
||||
|
||||
## 5. Checklist
|
||||
|
||||
### Batch A:Hermes tool registry 模块化拆分
|
||||
|
||||
- [ ] A1. 在 `hermes_tools/` 下创建 `registry/` 子目录
|
||||
- [ ] A2. 把 `manifest.rs` 中工具按能力域拆到独立 registry 模块
|
||||
- [ ] A2a. `context_registry.rs`:`context_snapshot`、`context_read_current_page`、`context_resolve_target`
|
||||
- [ ] A2b. `doc_registry.rs`:`doc_fetch`、`doc_find`、`doc_markdown_edit`、`doc_apply_block_ops`、`doc_plan_update`
|
||||
- [ ] A2c. `block_registry.rs`:`block_fetch`、`block_replace`、`block_insert_after`、`block_delete`、`block_move_after`
|
||||
- [ ] A2d. `page_registry.rs`:`page_get`、`page_save`、`page_update_title`、`page_update_options`
|
||||
- [ ] A2e. `knowledge_rag_registry.rs`:`knowledge_rag_status`、`knowledge_rag_query`、`knowledge_rag_section_context`、`knowledge_rag_open_reference`
|
||||
- [ ] A2f. `skill_registry.rs`:`skill_read`、`skill_*`
|
||||
- [ ] A2g. `mindmap_registry.rs`:`mindmap_fetch`、`mindmap_apply_ops`、`mindmap_create_from_outline`
|
||||
- [ ] A2h. `onlyoffice_registry.rs`:所有 `onlyoffice_*` 工具
|
||||
- [ ] A2i. `artifact_registry.rs`:`artifact_*` 工具
|
||||
- [ ] A3. 每个 registry 暴露 `pub fn tools() -> Vec<Value>` + `pub fn capability_names() -> &[&str]`
|
||||
- [ ] A4. `manifest.rs` 改为装配层:收集各 registry 的 tools + 统一 capability annotation
|
||||
- [ ] A5. `cargo test -p mnote-web hermes_tool_manifest --lib` 通过
|
||||
- [ ] A6. Agent descriptor smoke 验证 manifest 返回的工具列表与拆分前一致(`scripts/task557-page-ai-agent-descriptor-smoke.js`)
|
||||
|
||||
### Batch B:扩展管理 settings 入口
|
||||
|
||||
- [ ] B1. 在 settings 页面增加"扩展"tab,路由 `/settings?tab=extensions`
|
||||
- [ ] B2. 扩展 tab 包含子面板:知识库、Skills、MCP、Agent 工具
|
||||
- [ ] B3. 知识库子面板:展示 LightRAG 当前状态 + 已索引文件数 + "管理"按钮 → 跳转知识库详情
|
||||
- [ ] B4. Skills 子面板:展示已安装 skill 列表(名称、版本、状态、来源)
|
||||
- [ ] B5. MCP 子面板:展示已配置 MCP 服务器列表(名称、URL、连接状态)
|
||||
- [ ] B6. Agent 工具子面板:从 `/api/page-ai/agent-descriptors` 读取能力清单,只读展示
|
||||
- [ ] B7. URL query 参数持久化当前子 tab(`&ext=knowledge|skills|mcp|tools`)
|
||||
- [x] B8. 登录态浏览器验证:settings → 扩展 tab,切换子面板,刷新后 tab 不丢失
|
||||
|
||||
### Batch C:LightRAG 知识库详情页
|
||||
|
||||
- [ ] C1. 新增知识库详情页路由 `/knowledge-base`
|
||||
- [ ] C2. 文件管理面板
|
||||
- [ ] C2a. 展示已 ingest 文件列表(文件名、类型图标、大小、状态灯、最后更新时间)
|
||||
- [ ] C2b. 状态灯映射:pending/parsing/chunking/indexing/done/error,每 30s 有界刷新(来自 status bridge)
|
||||
- [ ] C2c. 文件删除按钮(调 `mnote.knowledge_rag.delete` 或 LightRAG API)
|
||||
- [ ] C2d. 手动 re-index 按钮(单文件)
|
||||
- [ ] C3. 检索测试面板
|
||||
- [ ] C3a. 输入框 + 搜索按钮 → 调 `/api/knowledge-rag/search`
|
||||
- [ ] C3b. 结果列表:citation preview + 来源文件 + 相关度分数
|
||||
- [ ] C3c. 点击结果 → `open_reference` 打开对应资源
|
||||
- [ ] C4. 检索参数配置面板
|
||||
- [ ] C4a. search mode 下拉:local/global/hybrid/naive/mix
|
||||
- [ ] C4b. top_k 数字输入(1-100)
|
||||
- [ ] C4c. 相似度阈值滑块(0.0-1.0)
|
||||
- [ ] C4d. 保存到 `user_ui_preferences`(SQLite control-plane)
|
||||
- [ ] C5. Ingest 源目录管理:展示当前 source roots + 添加/移除(只改 MNote source registry,不动 LightRAG 配置)
|
||||
- [x] C6. 登录态浏览器验证:知识库详情页三个面板切换、检索测试真实返回结果、参数保存后刷新不丢失
|
||||
|
||||
### Batch D:AgentStreamEventRouter 抽共享
|
||||
|
||||
- [ ] D1. 在 `rust/crates/mnote-web/browser/` 创建 `agent-stream-event-router.js`
|
||||
- [ ] D2. 定义标准事件类型 enum:init/loading/stream_event/finished/interrupted/error/approval_required
|
||||
- [ ] D3. 实现 `createAgentStreamRouter({ onDelta, onToolCall, onToolResult, onAgentState, onTerminal })` 工厂函数
|
||||
- [ ] D4. `sidebar-page-ai-runtime.js` 改为消费 AgentStreamEventRouter
|
||||
- [ ] D5. `document-editor-adapter-runtime.js` 改为消费 AgentStreamEventRouter(如涉及编辑器内 agent 交互)
|
||||
- [ ] D6. 浏览器 smoke 回归:Page AI 对话、tool call 展示、中断恢复、finished 状态均正常
|
||||
- [ ] D7. `scripts/task*-page-ai-*.js` 相关 smoke 通过
|
||||
|
||||
### Batch E:本地 Dashboard 最小版
|
||||
|
||||
- [ ] E1. 新增 `/dashboard` 路由 + 页面壳
|
||||
- [ ] E2. Agent Run 统计卡片
|
||||
- [ ] E2a. 后端:`GET /api/dashboard/agent-run-stats?days=7` → 返回 run 总数、成功/失败/中断分布、按 agent 分组
|
||||
- [ ] E2b. 前端:卡片 + 简易柱状图(按天分布)
|
||||
- [ ] E3. Token 消耗卡片
|
||||
- [ ] E3a. 后端:从 `agent_runs` 表提取 token usage 汇总(总 token、按 agent 分组)
|
||||
- [ ] E3b. 前端:数值卡片(本周消耗 / 总计)
|
||||
- [ ] E4. 知识库状态卡片
|
||||
- [ ] E4a. 后端:代理 LightRAG `/status` + MNote status bridge,返回文件数、chunk 数、最近 ingest 时间
|
||||
- [ ] E4b. 前端:数值卡片 + 状态灯
|
||||
- [ ] E5. Skills/MCP 健康卡片
|
||||
- [ ] E5a. 后端:从 Hermes workspace 配置 + Rust manifest 汇总
|
||||
- [ ] E5b. 前端:列表 + 状态灯
|
||||
- [ ] E6. 所有卡片使用 10 分钟 TTL + 手动刷新按钮,不做实时推送
|
||||
- [x] E7. 登录态浏览器验证:Dashboard 页面展示各卡片数据
|
||||
|
||||
### Batch F:WorkspacePath 虚拟路径安全加固
|
||||
|
||||
- [ ] F1. 在 `AiAccessScope` 中增加 `run_id` 字段
|
||||
- [ ] F2. 实现 `AgentVirtualPath` 类型:`mnote://workspace/`、`mnote://outputs/{run_id}/`、`mnote://uploads/{run_id}/`
|
||||
- [ ] F3. 在 Hermes tool 边界做虚拟路径 ↔ 真实路径映射(`resolve_agent_path` / `to_agent_virtual_path`)
|
||||
- [ ] F4. 路径穿越防护:`canonicalize` + `starts_with(allowed_root)` 双重校验
|
||||
- [ ] F5. Run 级临时目录:每个 agent run 开始时创建 `mnote://outputs/{run_id}/`,run 结束时清理
|
||||
- [ ] F6. Agent tool smoke 验证:agent 只能访问 allowed roots 内文件,路径穿越被拒绝
|
||||
|
||||
## 6. 与现有设计的关系
|
||||
|
||||
| 现有设计 | 本稿关系 |
|
||||
|----------|---------|
|
||||
| 7-57 Batch A (run journal) | 本稿 Batch E (Dashboard) 消费 run journal 做统计 |
|
||||
| 7-57 Batch B (agent descriptor) | 本稿 Batch B 的工具清单面板消费 descriptor |
|
||||
| 7-50 (LightRAG provider) | 本稿 Batch C 是其 UI 增强 |
|
||||
| 7-18 (agent file editing) | 本稿 Batch F 加固其 AiAccessScope |
|
||||
| 3-23 (sidebar local folder runtime) | 本稿 Batch B/C 在 settings 中增加入口 |
|
||||
| 7-38 (Page AI sidebar owner split) | 本稿 Batch D 是 sidebar runtime 的事件路由重构 |
|
||||
|
||||
## 7. 风险
|
||||
|
||||
- **Batch A 重构 manifest**:如果工具名或 capability annotation 在拆分中漂移,会导致 agent descriptor 不一致。必须用 smoke 对比拆分前后的 manifest JSON。
|
||||
- **Batch C 检索参数**:LightRAG `QueryParam` 的字段可能随版本变化。MNote 透传用户配置时应做字段白名单,未知字段忽略。
|
||||
- **Batch D 事件路由重构**:如果 browser runtime 中的事件格式在不同 consumer 间有细微差异(例如 Page AI 用 `message_id`,editor 用 `msg_id`),统一 router 时需要兼容映射。
|
||||
- **Batch E Dashboard**:SQLite `agent_runs` 表可能膨胀。后续需要 TTL 清理策略,当前阶段保留最近 30 天。
|
||||
- **Batch F 虚拟路径**:agent 端(Hermes / Reasonix)需要能处理 `mnote://` 前缀,或 MNote 在 tool 边界双向映射。第一阶段先只映射,不要求 agent 原生理解虚拟路径。
|
||||
|
||||
## 8. 验证基线
|
||||
|
||||
- `cargo test -p mnote-web --lib` 全绿
|
||||
- `scripts/task557-page-ai-agent-descriptor-smoke.js` 通过
|
||||
- `scripts/task529-knowledge-rag-citation-resource-tab-smoke.js` 通过
|
||||
- `scripts/task530-knowledge-rag-page-ai-final-answer-smoke.js` 通过
|
||||
- 新增 smoke:`scripts/task561-extensions-settings-smoke.js`(Batch B+C)
|
||||
- 新增 smoke:`scripts/task561-dashboard-smoke.js`(Batch E)
|
||||
- 登录态浏览器验证:settings 扩展 tab、知识库详情页、Dashboard 页面均可正常访问
|
||||
@@ -1,555 +0,0 @@
|
||||
> 状态补充(2026-07-03):本稿冻结为 opencode runtime / 官方 opencode WebUI iframe fallback 参考;Page AI 产品主线为 OpenHub / native agent + LightRAG + Turso/libSQL。官方 iframe 仅在 OpenHub AI 面板不可用、调试 opencode 原生行为或做回归对照时启用,不再作为默认产品路径。此前指向 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 的 WeKnora 默认 provider 口径已标记 stale。
|
||||
|
||||
# 7-65 [process] Page AI opencode WebUI embed v1
|
||||
|
||||
> 创建时间:2026-06-23
|
||||
>
|
||||
> 当前状态:`FROZEN / 官方 opencode iframe fallback,OpenHub + LightRAG + MNote 深度融合为主线`
|
||||
>
|
||||
> Owner:07-ai / Page AI / opencode WebUI embed
|
||||
>
|
||||
> 替代方案:
|
||||
> - `design/old/07-ai/process/7-62-recycle-page-ai-board-first-full-rewrite-v1.md`
|
||||
> - `design/old/07-ai/process/7-63-recycle-page-ai-board-first-productization-v1.md`
|
||||
> - `design/old/07-ai/process/7-64-recycle-codexmobile-embed-page-ai-v1.md`
|
||||
>
|
||||
> 上位依据:
|
||||
> - `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md`
|
||||
> - `design/07-ai/done/7-38-page-ai-sidebar-runtime-owner-split-v1.md`
|
||||
> - `design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md`
|
||||
> - `design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md`
|
||||
|
||||
## 1. 核心结论
|
||||
|
||||
废弃 MNote Page AI 自研 provider 接入与 Board-first 产品壳后,本稿曾把 **opencode 官方 runtime + 官方 WebUI** 作为主线;当前该方案已降级为 fallback,默认产品主线为 OpenHub / native agent + LightRAG + Turso/libSQL。
|
||||
|
||||
```text
|
||||
fallback MNote Page AI = MNote 宿主壳 + opencode 官方 WebUI iframe + MNote 打开/刷新/上下文集成
|
||||
主线 Page AI = MNote 宿主壳 + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + LightRAG
|
||||
opencode = agent runtime / HTTP server / OpenAPI / SSE / SDK / opencodego provider
|
||||
```
|
||||
|
||||
MNote 不再直接维护 Reasonix / ZCode / Hermes / Chat-only / Board worker 作为页面 AI provider。它们可以继续存在于历史、调试或外部工作流边界,但不进入 Page AI 主路径。官方 opencode WebUI iframe 也不再进入默认产品主路径,只作为 fallback。
|
||||
|
||||
选择 opencode 的原因:
|
||||
- `opencode web` 官方提供本地 WebUI,不需要 MNote 自研完整聊天前端。
|
||||
- `opencode serve` 官方提供 headless HTTP server 和 OpenAPI,适合 MNote 做轻量 adapter。
|
||||
- `@opencode-ai/sdk` 覆盖 session、message、diff、permission、event,足够承接上下文注入与回写 receipt。
|
||||
- opencode 原生支持 SSE、权限审批、文件 diff、MCP、ACP、session export/import。
|
||||
- 本机已有 `opencode` 和 opencodego 订阅链路,provider/runtime/UI 属于同一生态,少一层兼容债。
|
||||
|
||||
## 2. 明确废弃
|
||||
|
||||
### 2.1 Page AI 主路径不再接入
|
||||
|
||||
- Reasonix native session / Reasonix desktop bridge。
|
||||
- ZCode worker / Board worker selector。
|
||||
- Hermes profile / Hermes Web control surface。
|
||||
- Chat-only remote conversation。
|
||||
- Agent Board run/workflow 作为默认 Page AI 后端。
|
||||
- CodexMobile iframe 作为默认 Page AI 后端。
|
||||
|
||||
这些能力不删除历史代码,不立刻清理工具层,只从 Page AI 新主路径退出。
|
||||
|
||||
### 2.2 仍可保留的边界
|
||||
|
||||
- `mnote.doc.*`、`mnote.block.*` 等工具可继续作为 fallback/compat 边界;默认知识库工具走 LightRAG + provider-neutral `mnote.knowledge_rag.*` facade。
|
||||
- Agent Board 仍可作为外部 workflow/QA/review 系统,不再作为 Page AI 默认聊天后端。
|
||||
- CodexMobile 可保留为备选 spike 或体验对照,不作为当前实现目标。
|
||||
|
||||
## 3. 新系统边界
|
||||
|
||||
### 3.1 MNote 只做四件事
|
||||
|
||||
1. **上下文**:当前页、选区、页面标题、真实 `.md` 路径、workspaceId、allowed roots、LightRAG 引用;WeKnora 只作为历史设计、参考实现或备用 provider 边界。
|
||||
2. **授权**:把 MNote local-first 文件权限转换为 opencode permission / external_directory / working directory。
|
||||
3. **嵌入**:第一版通过 MNote 同源受登录态保护反代嵌入 opencode 官方 WebUI;`npm run dev:hot` 默认拉起 `opencode serve --hostname=127.0.0.1 --port 4096`。
|
||||
4. **回执**:监听 opencode event/diff,触发 MNote watcher 刷新,记录 Page AI session binding。
|
||||
|
||||
### 3.2 opencode 负责完整 agent runtime
|
||||
|
||||
- 聊天 UI。
|
||||
- 流式事件。
|
||||
- session 管理与恢复。
|
||||
- provider/model 调用。
|
||||
- tool call 展示。
|
||||
- permission ask/allow/deny。
|
||||
- 文件读写、patch、diff。
|
||||
- MCP / ACP / agent 配置。
|
||||
|
||||
### 3.3 集成形态:不是新的 Leptos island
|
||||
|
||||
当前 MNote 文档编辑器已经是 `leptos_tiptap_island`,但 Page AI 不应该再做一个重前端 island。Page AI 更像 VSCode 里的 Cline:
|
||||
|
||||
```text
|
||||
VSCode workbench host + Cline webview/extension
|
||||
MNote web shell host + opencode WebUI iframe/bridge
|
||||
```
|
||||
|
||||
因此第一版形态是 **mnote-web sidebar host runtime**:
|
||||
- 继续使用 `rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 作为宿主入口,但把它瘦身成 opencode host。
|
||||
- iframe 内尽量保留 opencode 官方 UI,包括消息、tool、permission、diff/session 页面。
|
||||
- iframe 外只放 MNote 必要宿主控件:当前页 context bar、打开变更、刷新当前页、授权状态、运行状态。
|
||||
- 不新建 Leptos island,不引入 React/Vue 到 MNote 主壳,不重写 opencode 消息 UI。
|
||||
|
||||
如果后续必须深度定制 opencode UI,也优先 fork opencode WebUI 的少量页面,而不是在 MNote 里复刻一套聊天前端。
|
||||
|
||||
## 4. 集成拓扑
|
||||
|
||||
```text
|
||||
MNote Rust SSR :3000
|
||||
└─ Page AI sidebar
|
||||
├─ MNote host chrome
|
||||
│ [当前页] [选区] [可写目录] [知识库引用]
|
||||
│ [打开变更] [刷新当前页] [在主编辑区打开]
|
||||
└─ iframe http://127.0.0.1:4096/<project>/session
|
||||
↓ first MVP direct localhost iframe
|
||||
opencode web :4096
|
||||
├─ 官方 WebUI
|
||||
├─ 官方 HTTP server / OpenAPI
|
||||
├─ SSE /event
|
||||
├─ session/message/diff/permission APIs
|
||||
└─ opencodego / configured providers
|
||||
```
|
||||
|
||||
第一版优先 iframe 官方 WebUI,不 fork、不精简、不重写样式。实测 opencode WebUI 使用根路径 `/assets`、`/session`、`/global/health` 等资源/API,子路径 `/page-ai/opencode/` iframe 会产生 root path 错位;因此 MVP 采用 MNote 同源根路径反代 `/<base64-project>/session/<sessionId>`,让 iframe 内 `location.pathname` 与 opencode 官方 WebUI 预期保持一致,同时通过 MNote 登录态保护反代入口。`/api/page-ai/opencode/*` 负责 session binding、context 注入、status、diff/receipt adapter。只有 iframe/adapter 实测无法满足产品嵌入时,才考虑 fork WebUI。
|
||||
|
||||
### 4.1 MNote host chrome
|
||||
|
||||
Page AI 抽屉由 MNote 控制尺寸、开关、上下文和跨应用动作,opencode 只负责 AI 交互主体。
|
||||
|
||||
宿主控件最小集:
|
||||
- 当前页 pill:标题、相对路径、读写状态。
|
||||
- 选区 pill:有选区才展示,点击可重新注入上下文。
|
||||
- 变更 pill:来自 opencode diff/event,点击用 MNote 打开对应文件。
|
||||
- 刷新按钮:调用 `window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument()`。
|
||||
- 打开按钮:调用 `window.__mnoteDocumentPaneRuntime.openResourceInActiveTab()`。
|
||||
|
||||
这些控件由 MNote 渲染,避免修改 opencode 官方页面结构。
|
||||
|
||||
### 4.2 MNote 打开 opencode 变更
|
||||
|
||||
opencode WebUI 里的 diff / changed file 默认按 opencode 自己的 UI 打开。MNote 需要额外提供宿主打开能力:
|
||||
|
||||
```text
|
||||
opencode diff/event
|
||||
→ MNote adapter 归一化 changed files
|
||||
→ Page AI host chrome 显示 changed file chips
|
||||
→ 用户点击 chip
|
||||
→ window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ path })
|
||||
```
|
||||
|
||||
第一版不强行改 opencode diff 内部点击行为;先在 iframe 外给 MNote-native changed file chips。这样即使 opencode DOM 改版,MNote 打开变更仍可用。
|
||||
|
||||
### 4.3 可选 bridge:只做宿主动作,不接管 UI
|
||||
|
||||
如果需要让 opencode 官方 UI 内部的文件链接也能用 MNote 打开,可在同源反代 HTML 中注入一个很小的 bridge 脚本:
|
||||
|
||||
```text
|
||||
opencode iframe click(file/diff link)
|
||||
→ postMessage({ type: 'mnote:open-file', path })
|
||||
→ parent MNote host 调用 openResourceInActiveTab
|
||||
```
|
||||
|
||||
bridge 只允许三类消息:
|
||||
- `mnote:open-file`
|
||||
- `mnote:refresh-file`
|
||||
- `mnote:session-ready`
|
||||
|
||||
不通过 bridge 解析模型事件、不重绘消息、不替换 permission UI。DOM 选择器脆弱时立即退回 host chrome chips。
|
||||
|
||||
## 5. 最小实现
|
||||
|
||||
### Phase A:runtime spike
|
||||
|
||||
- [x] 运行 `opencode --version`、`opencode web --help`、`opencode serve --help`;当前版本已升级到 `1.17.9`。
|
||||
- [x] 卸载 oh-my-openagent / oh-my-opencode 默认插件:`opencode.json` 中 `plugin` 已为空,`oh-my-openagent.jsonc` 已移除并备份。
|
||||
- [x] 启动/复用 `opencode serve --hostname=127.0.0.1 --port=4096`,`/global/health` 返回 healthy。
|
||||
- [x] 验证 WebUI 可打开、可进入 `/mnt/Data1T/mnote` project session、可真实回复。
|
||||
- [x] 验证 opencodego/OmniRoute 模型可用:iframe 内真实回复 `OPENCODE_MNOTE_IFRAME_OK_*`。
|
||||
- [x] 验证当前工作目录指向 MNote workspace:`/session` 返回 `directory=/mnt/Data1T/mnote`。
|
||||
- [x] 验证编辑一个 `.md` 文件后,`/session/:id/diff` 能返回文件 diff:`opencode-smoke-test.md` 返回 `modified` diff。
|
||||
- [ ] `/event` SSE 只做了接口可达性探索,尚未接入持续事件消费。
|
||||
|
||||
### Phase B:MNote iframe embed
|
||||
|
||||
- [x] Rust 新增 `/page-ai/opencode/{*path}` 反向代理到 `127.0.0.1:4096`,并新增 `/api/page-ai/opencode/status`、`/api/page-ai/opencode/diff`。
|
||||
- [x] 只允许已登录 MNote session 访问反代/API;未登录请求返回 `401 page_ai_opencode_unauthorized`。
|
||||
- [x] Page AI sidebar 精简为:MNote host chrome + iframe + basic status。
|
||||
- [x] 尽量不改 opencode WebUI,保留官方页面布局、消息样式、tool/diff/permission UI。
|
||||
- [x] iframe 容器与 host chrome 由 `sidebar-page-ai-runtime.js` + `page-ai.css` 承载,不新增 Page AI Leptos island。
|
||||
- [x] WebUI 默认 iframe 使用 MNote 同源 `/<base64-project>/session` 反代;避免局域网浏览器访问自身 `127.0.0.1:4096`,同时保留 opencode 官方 URL 形态。
|
||||
|
||||
### Phase B2:MNote-native changed files
|
||||
|
||||
- [x] 建立 opencode sessionId ↔ MNote host 状态的持久 binding:`/api/page-ai/opencode/session` 创建/复用 session,并落 SQLite/control-plane,按用户/session/workspace 区分。
|
||||
- [x] 通过 `/api/page-ai/opencode/events` 代理 opencode `/event`,收到 session/message/diff/file 事件后触发有界 refresh。
|
||||
- [x] 通过 `/session/:id/diff` 生成 changed file chips。
|
||||
- [x] chip 点击走 `window.__mnoteDocumentPaneRuntime.openResourceInActiveTab()`。
|
||||
- [x] 当前页刷新按钮已走 `refreshPrimaryDocument({ reason: 'page-ai-opencode-host' })`;changed files 命中当前页时由 event/diff refresh 链路自动触发刷新。
|
||||
|
||||
### Phase C:context 注入
|
||||
|
||||
优先不修改 opencode WebUI,通过官方 API/SDK 注入上下文:
|
||||
|
||||
```text
|
||||
MNote open Page AI
|
||||
→ create or resume opencode session
|
||||
→ session.prompt(noReply=true, parts=[MNote context envelope])
|
||||
→ iframe 打开对应 session
|
||||
```
|
||||
|
||||
上下文 envelope 内容:
|
||||
- 当前页标题。
|
||||
- 当前页真实 Markdown 路径。
|
||||
- selection 文本。
|
||||
- allowed roots 与读写权限。
|
||||
- LightRAG 引用摘要;WeKnora 引用只作为备用 provider / 历史参考。
|
||||
- 当前任务约束:优先编辑 primaryTarget,禁止越权修改。
|
||||
|
||||
当前状态:`done for MVP`。host chrome 已展示当前页、真实 Markdown path(可定位时)、selection、allowed roots/writable 状态;打开 Page AI 时会调用 `/api/page-ai/opencode/session`,用 `noReply=true` 向 opencode session 注入 MNote context envelope,并把 iframe 打到绑定 session URL。2026-06-24 已按 opencode-chat 参考改回官方 WebUI iframe 主路径,MNote-native timeline 仅保留为 debug/receipt 边界。
|
||||
|
||||
### Phase D:writeback / receipt
|
||||
|
||||
- [x] 监听 opencode `/event` 或 SDK `event.subscribe()`:当前通过 `/api/page-ai/opencode/events` 代理 `/event`,EventSource 收到相关事件后触发有界 refresh。
|
||||
- [partial] prompt / event 后通过绑定 session 的 `/session/:id/diff` 刷新 changed files;仍需继续核对 opencode 各类 file/diff event payload。
|
||||
- [x] changed files 命中当前页时调用 `refreshPrimaryDocument({ reason: 'page-ai-opencode-event' })`;手动刷新按钮保留。
|
||||
- [x] 保存 MNote pageId ↔ opencode sessionId binding:当前已落 SQLite/control-plane,按用户/session/workspace 区分;浏览器刷新后可恢复同一 binding。
|
||||
- [x] Page AI context bar 显示最近一次 changed files / runtime / error 摘要。
|
||||
|
||||
### Phase E:可选 WebUI bridge
|
||||
|
||||
- [ ] 只有 host chrome chips 体验不足时,才在反代层注入 `mnote-opencode-bridge.js`。
|
||||
- [ ] bridge 只把 opencode UI 内部文件点击转成 `postMessage`。
|
||||
- [ ] bridge 不解析/修改 opencode 消息流、tool UI、permission UI。
|
||||
- [ ] selector 失效时不阻塞主流程,回退 host chrome chips。
|
||||
|
||||
## 6. 安全与权限
|
||||
|
||||
- opencode 只监听 `127.0.0.1`;MVP iframe 直连本机地址,同源反代/API 仍必须受 MNote 登录态保护。
|
||||
- 生产/长期运行必须设置 `OPENCODE_SERVER_PASSWORD`,或改为完整同源反代 + MNote 反代层隔离。
|
||||
- opencode working directory 优先指向当前 workspace root。
|
||||
- MNote allowed roots 映射到 opencode permission:
|
||||
- 当前 workspace root:允许读,写按用户授权。
|
||||
- 当前页文件:允许读写。
|
||||
- workspace 外路径:默认 deny,必要时显式 `external_directory`。
|
||||
- 不使用 `--dangerously-skip-permissions` 作为默认路径。
|
||||
|
||||
## 7. 验收标准
|
||||
|
||||
### 7.1 UI
|
||||
|
||||
- Page AI 面板内显示 opencode 官方 WebUI。
|
||||
- 官方消息流、tool 卡片、permission 交互、diff/session UI 尽量原样保留。
|
||||
- MNote 只在 iframe 外展示 host chrome,不重做 opencode UI。
|
||||
- opencode 产生的 changed files 可以用 MNote 主编辑区或资源 tab 打开。
|
||||
|
||||
### 7.2 Runtime
|
||||
|
||||
- 可创建/恢复 opencode session。
|
||||
- 可用 opencodego 模型完成真实回复。
|
||||
- 流式回复浏览器可见。
|
||||
- 权限审批走 opencode 原生机制。
|
||||
- 文件修改后 MNote 当前页面能刷新。
|
||||
|
||||
### 7.3 代码收敛
|
||||
|
||||
- `sidebar-page-ai-runtime.js` 不再承载 Reasonix/ZCode/Hermes/Board provider 状态机。
|
||||
- 不新增 MNote 自研聊天 message store。
|
||||
- 不 fork opencode WebUI,除非 spike 证明 iframe 方案不可用。
|
||||
- 不新增 Page AI Leptos island;Page AI 是 mnote-web sidebar host runtime。
|
||||
|
||||
## 8. 与旧方案对比
|
||||
|
||||
| 方案 | 优点 | 主要问题 | 当前结论 |
|
||||
|---|---|---|---|
|
||||
| Board-first | 可接多 worker/workflow | MNote 仍要维护产品壳和 Board adapter,聊天体验不成熟 | 废弃为主路径 |
|
||||
| CodexMobile embed | Codex 体验强,贴近现有 Codex 体系 | 需要 fork/精简/修 bug,维护派生产品 | 备胎/对照 |
|
||||
| opencode WebUI embed | 官方 runtime + 官方 WebUI + 官方 API,维护成本低 | opencode 能力可能不如 Codex 先进,嵌入细节需实测 | 当前主路径 |
|
||||
|
||||
## 9. 非目标
|
||||
|
||||
- 不重写 opencode WebUI。
|
||||
- 不把 opencode WebUI 拆成 MNote 原生组件。
|
||||
- 不同时接入 Reasonix/ZCode/Hermes/CodexMobile 多后端。
|
||||
- 不把 Agent Board 控制台嵌入 Page AI。
|
||||
- 不在第一版实现完整 MNote SSO 到 opencode;先由 MNote 反代保护。
|
||||
|
||||
## 10. 退出条件
|
||||
|
||||
只有出现以下任一情况,才重新启用 CodexMobile 或自研 UI 方案:
|
||||
|
||||
- opencode WebUI 无法稳定 iframe/反代嵌入。
|
||||
- opencode session 无法通过 API 定位并打开指定 session。
|
||||
- opencode 无法可靠编辑 MNote workspace 文件。
|
||||
- opencode permission/diff/event 无法满足 MNote 最小安全闭环。
|
||||
- opencodego/provider 链路在真实使用中明显不稳定且短期不可修。
|
||||
|
||||
|
||||
## 9. 2026-06-24 iframe 主路径验证记录
|
||||
|
||||
- [x] `opencode --version`:`1.17.9`。
|
||||
- [x] `opencode serve --hostname=127.0.0.1 --port 4096 --print-logs`:真实可启动;官方 WebUI URL `http://127.0.0.1:4096/L21udC9EYXRhMVQvbW5vdGU/session` 可显示 `Build anything`。
|
||||
- [x] `npm run dev:hot`:真实拉起 `mnote-web :3000` 和 `opencode :4096`。
|
||||
- [x] 浏览器 smoke:登录 `mnote.e2e@example.com` 后打开 Page AI,iframe URL 为 `http://127.0.0.1:3000/L21udC9EYXRhMVQvbW5vdGU/session`,显示官方 opencode WebUI;截图 `/tmp/mnote-page-ai-opencode-iframe.png`。
|
||||
- [partial] 发送真实消息:官方 WebUI 可输入并进入 `Thinking/Stop` 运行态;截图 `/tmp/mnote-page-ai-opencode-send.png`。本轮未等待到最终回复,不能声明 provider 回复完成。
|
||||
- [x] binding 持久化 smoke:浏览器 reload 后仍恢复同一 session `ses_106555512ffe5wDz9eMlP4v2i2`。
|
||||
- [x] 静态检查:`node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`。
|
||||
- [x] Rust 检查:`cd rust && cargo check -p mnote-web`。
|
||||
|
||||
剩余 gap:
|
||||
- [x] 在同源反代层补最小 `postMessage` bridge:`open-file / refresh-file / insert-text`,不接管消息流。
|
||||
- [partial] 更完整核对 opencode `/event` 的 file/diff payload:已递归解析常见 changed/diff/file payload 并由 event 触发有界 refresh;仍需真实编辑文件后补最终证据。
|
||||
- [ ] 若要局域网访问,继续确认所有 opencode WebUI root-level API 路由都已被 MNote 登录态反代覆盖,避免直接暴露 4096。
|
||||
|
||||
|
||||
## 10. 2026-06-25 bridge / receipt 补充记录
|
||||
|
||||
- [x] HTML 反代注入极小 bridge:只处理 `insert-text / open-file / refresh-file / session-ready`,不接管 opencode 消息流、tool UI、permission UI。
|
||||
- [x] MNote host chrome 新增“插入当前页上下文”按钮,通过 `postMessage` 把当前页标题、路径、选区、allowed roots 插入 opencode 官方输入框。
|
||||
- [x] 浏览器 smoke:`/tmp/mnote-page-ai-opencode-bridge-final.png`,验证 iframe 内 `window.__mnoteOpencodeBridgeInstalled === true`,点击 host 按钮后官方输入框出现 `MNote 当前页上下文标题:主页`。
|
||||
- [x] postMessage smoke:模拟 iframe 发 `open-file / refresh-file`,确认 MNote host 调用 `openResourceInActiveTab()` 与 `refreshPrimaryDocument()`;输出见 `tmp/mnote-opencode-postmessage-smoke.cjs` 运行结果。
|
||||
- [x] event receipt 强化:`pageAiOpencodeNormalizeChangedFiles()` 改为递归收集 `changedFiles / files / diff / changes / edited / created / deleted / data / properties`,事件到达时先更新 chips,再做有界 projection refresh。
|
||||
- [x] 静态检查:`node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`。
|
||||
- [x] Rust 检查:`cd rust && cargo check -p mnote-web`。
|
||||
|
||||
剩余 gap:
|
||||
- [ ] 让 opencode 真实修改一个测试 Markdown 文件,等待 `/event` + `/session/:id/diff` 产出 changed file chips,再截图验证 chip 点击打开 MNote 主编辑区。
|
||||
- [ ] 若 opencode 官方 WebUI 后续 CSP 变化,需要把 inline bridge 改成 nonce/hash 或外部小脚本。
|
||||
|
||||
## 11. 2026-06-25 opencode 用户隔离与工作目录结论
|
||||
|
||||
### 11.1 当前结论
|
||||
|
||||
- [x] Page AI 工作目录应简化为“当前用户打开的根文件夹”:MNote 当前只能打开一个大的本地根目录,`rootUri` 对应的真实目录就是 opencode `directory` / project directory。
|
||||
- [x] 切换根文件夹应视为新的 opencode session 边界:同一用户同一根目录内可按页面恢复 binding;根目录变化时默认新建 session,不把旧 session 带到新根目录。
|
||||
- [x] MNote 自身 binding 已按 `user_id + workspace_id + mnote_session_id + provider` 隔离;`mnote_session_id` 内包含 workspace/page/directory,因此同一用户跨根目录不会复用同一 binding。
|
||||
- [partial] opencode 自身默认实例没有 MNote 用户概念;如果所有 MNote 用户共用一个 `opencode serve`,opencode 的 session、permission、credential、account、skill、MCP、snapshot、tool-output 会共用同一套本机状态。
|
||||
|
||||
### 11.2 opencode 1.17.9 真实机制证据
|
||||
|
||||
本机 spike 使用临时环境启动:
|
||||
|
||||
```bash
|
||||
HOME=/tmp/.../home \
|
||||
XDG_CONFIG_HOME=/tmp/.../config \
|
||||
XDG_DATA_HOME=/tmp/.../data \
|
||||
XDG_CACHE_HOME=/tmp/.../cache \
|
||||
XDG_STATE_HOME=/tmp/.../state \
|
||||
opencode serve --port 4197 --hostname 127.0.0.1 --print-logs
|
||||
```
|
||||
|
||||
观察结果:
|
||||
|
||||
- 配置读取路径变为 `$XDG_CONFIG_HOME/opencode/{config.json,opencode.json,opencode.jsonc}`。
|
||||
- 持久数据写入 `$XDG_DATA_HOME/opencode/opencode.db`。
|
||||
- 日志写入 `$XDG_DATA_HOME/opencode/log/opencode.log`。
|
||||
- 锁写入 `$XDG_STATE_HOME/opencode/locks/*`。
|
||||
- 当前全局实例的默认持久库是 `/home/lix/.local/share/opencode/opencode.db`。
|
||||
- `opencode.db` 内包含 `session`、`message`、`part`、`permission`、`credential`、`account`、`account_state`、`project`、`workspace`、`event` 等表;这些表没有 MNote 用户维度。
|
||||
- `permission` 只按 `project_id + action + resource` 唯一;共用实例会导致不同 MNote 用户在同一 project/directory 下共享 opencode 权限记忆。
|
||||
- `session` 表包含 `directory` / `project_id` / `workspace_id` / `metadata`,但不包含 MNote `user_id`;共用实例不能作为安全隔离边界。
|
||||
- opencode 会从当前用户 HOME/配置路径加载 skill/MCP;未隔离时可看到 `/home/lix/.claude`、`/home/lix/.agents`、`/home/lix/.config/opencode/skill` 等重复 skill 警告。
|
||||
|
||||
### 11.3 推荐隔离方案
|
||||
|
||||
第一版不要试图在单个 opencode server 内实现多用户隔离;改为 **MNote 用户/根目录维度的 opencode runtime profile**:
|
||||
|
||||
```text
|
||||
MNote user + workspace/rootUri
|
||||
-> runtime profile id
|
||||
-> dedicated XDG_CONFIG_HOME / XDG_DATA_HOME / XDG_CACHE_HOME / XDG_STATE_HOME
|
||||
-> dedicated opencode serve process on 127.0.0.1:dynamic_port
|
||||
-> MNote 登录态反代 /page-ai/opencode/...
|
||||
```
|
||||
|
||||
目录建议:
|
||||
|
||||
```text
|
||||
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/config/opencode/opencode.json
|
||||
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/data/opencode/opencode.db
|
||||
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/cache/opencode/
|
||||
$MNOTE_DATA/users/<user-id>/opencode/<workspace-hash>/state/opencode/
|
||||
```
|
||||
|
||||
配置策略:
|
||||
|
||||
- provider/API key 可由 MNote 管理后写入每个 profile 的最小 `opencode.json`,或在本机单用户 dev 模式从 `/home/lix/.config/opencode/opencode.json` 复制 provider/model 白名单。
|
||||
- skill/MCP 默认不继承宿主 HOME 的全部内容;只显式安装 MNote 允许的 skill/MCP,例如 `codegraph`、`mempalace`、后续 MNote MCP。
|
||||
- opencode 原生 permission 继续保留,但存储在该 profile 的独立 `opencode.db` 中。
|
||||
- MNote 的 allowed roots 仍作为上下文与反代边界;opencode 的工作目录固定为当前打开根目录。
|
||||
|
||||
### 11.4 session 策略
|
||||
|
||||
- 同一 `user_id + workspace_id/rootUri + pageAbsolutePath`:优先恢复 MNote control-plane binding 指向的 opencode session。
|
||||
- `rootUri` 变化:默认新建 session;旧 binding 保留但不跨根目录复用。
|
||||
- `pageAbsolutePath` 变化:默认新建或按页面 binding 恢复,不把旧页面上下文继续注入到新页面。
|
||||
- opencode 原生 session 恢复只作为 profile 内部能力;MNote 是否恢复以 control-plane binding 为准。
|
||||
- 跨浏览器恢复依赖 SQLite control-plane binding + per-user opencode data profile,不依赖 `sessionStorage`。
|
||||
|
||||
### 11.5 后续实现项
|
||||
|
||||
- [ ] 新增 opencode runtime profile manager:按 MNote user/rootUri 分配 XDG 目录、端口、启动/健康检查、生命周期。
|
||||
- [ ] `dev:hot` 继续可一键启动,但默认只启动 dev 单用户 profile;多用户 profile 由后端按需拉起。
|
||||
- [ ] 反代不再只读 `MNOTE_OPENCODE_BASE_URL` 全局值,需按当前登录用户和 rootUri 解析到对应 profile base URL。
|
||||
- [ ] session binding metadata 写入 `runtimeProfileId`、`rootUri`、`projectDirectory`、`opencodeDataDir`,方便审计和恢复。
|
||||
- [ ] 增加最小测试:两个不同 MNote 用户同一 Markdown 根目录下创建 session,不共享 opencode `opencode.db`、permission、session list。
|
||||
|
||||
### 11.6 官方/社区多用户实现调研补充
|
||||
|
||||
本轮先核验豆包给出的项目名,再补充搜索到的真实社区线索。结论:**没有发现可直接嵌入 MNote 的官方/社区“单 opencode 进程多 MNote 用户强隔离”实现**;官方当前也把多用户 Web/serve 部署视为待增强能力。
|
||||
|
||||
豆包列表验真:
|
||||
|
||||
| 项目 | 验真结果 | 对 MNote 的价值 |
|
||||
|---|---|---|
|
||||
| `anomalyco/opencode-orchestrator` | 未找到公开仓库;真实相近项目是 `agnusdei1207/opencode-orchestrator` | 后者是 opencode 多 agent 编排插件,不是多用户 runtime 隔离层 |
|
||||
| `pRizz/opencode-cloud` / `gitea.com/pRizz/opencode-cloud` | 真实存在 | Docker/container 级隔离参考;安全强但本地 MNote 开销偏大 |
|
||||
| `oc-ext/ocx` | 未找到公开仓库 | 暂不可作为依据 |
|
||||
| `daytonaio/daytona-opencode-plugin` | 未找到公开仓库 | 暂不可作为依据 |
|
||||
| `anomalyco/openwork` | 未找到公开仓库 | 暂不可作为依据 |
|
||||
| `lucentia/opencode-svip-proxy` | 未找到公开仓库 | 暂不可作为依据 |
|
||||
| `kwickramasekara/opencode-chat` | 真实存在 | VSCode WebView 嵌入参考:启动/复用 `opencode serve`、固定端口保留 localStorage、WebView proxy、剪贴板/键盘 bridge;不是多用户隔离实现 |
|
||||
|
||||
额外发现:
|
||||
|
||||
| 项目/线索 | 结论 |
|
||||
|---|---|
|
||||
| 官方 `anomalyco/opencode` issue `#20067` | open:请求 `opencode web` 支持 multi-user auth 与 per-user provider credentials;issue 描述确认共享 Web 实例会共享身份、session、provider credentials |
|
||||
| 官方 `anomalyco/opencode` issue `#5784` | closed:请求多租户 `serve` 下 MCP auth/config;说明多租户 MCP/资源隔离是社区真实痛点,但不是已可用的完整 MNote 用户隔离方案 |
|
||||
| 官方 `SECURITY.md` | 明确 opencode 不提供安全 sandbox;server mode 只支持 `OPENCODE_SERVER_PASSWORD` Basic Auth;需要真隔离时建议 Docker/VM |
|
||||
| `millerjes37/opencode-multiplexer` | 真实存在,是 opencode fork 的 multi-client server 支持;文档承认“知道 sessionID 即可交互”的 session hijacking 风险,session ownership 仍是 future enhancement;不适合作 MNote 多用户隔离底座 |
|
||||
| `joeyism/opencode-multiplexer` | 真实存在,是多 session/多项目终端 dashboard,不是 Web 多用户隔离 |
|
||||
|
||||
对当前方案的修正:
|
||||
|
||||
- 不应引入豆包描述的“单进程多租户 opencode”作为近期主路径;目前没有可靠公开实现,官方也还在 issue 层面。
|
||||
- 也不应一开始上 Docker/container;它解决安全隔离但会显著增加本地笔记软件的启动、资源和运维成本。
|
||||
- 近期最稳妥路线是 **单 opencode 进程 + 单 MNote 当前用户/当前根目录 profile**,先满足本机单用户和局域网同一登录用户;等 MNote 真正进入多用户同时在线场景,再升级为按需 profile manager。
|
||||
- 若要减少端口/生命周期复杂度,可先采用 `opencode-chat` 的轻量做法:固定一个 dev/local 端口、优先复用已存活 server、MNote 反代统一入口;不要提前实现多实例调度。
|
||||
- 多用户隔离仍必须作为设计约束保留:不能把共享 opencode `opencode.db` 声称为安全隔离,只能标为单用户/dev 模式。
|
||||
|
||||
更新后的分阶段建议:
|
||||
|
||||
1. **Phase MVP-local**:一个 MNote 登录用户 + 一个当前根目录 + 一个 opencode server;工作目录固定为 `rootUri`;MNote control-plane 做跨浏览器 session binding。
|
||||
2. **Phase shared-device**:为每个 MNote 用户准备独立 XDG profile,但不常驻多进程;登录/打开 Page AI 时按需启动,空闲回收。
|
||||
3. **Phase SaaS/团队**:再评估 `opencode-cloud`/Docker 或等待官方 multi-user auth/per-user credentials 落地;不要自己 fork 官方 opencode 做单进程多租户。
|
||||
|
||||
### 11.7 OpenHub 对照结论
|
||||
|
||||
`xcl1989/OpenHub` 是目前找到的最接近“opencode 多用户平台”的社区实现,README 明确主张:一个 `opencode serve (:4096)`,后端按用户 workspace 通过 `?directory=` 路由到不同目录,并在应用 SQLite 中维护 users、sessions、messages、permissions、skills、tools 等业务层权限。
|
||||
|
||||
可复用点:
|
||||
|
||||
- 单 opencode server + per-user workspace:后端调用 `/session`、`/session/{id}/prompt_async`、`/global/event` 时统一带 `directory=<user workspace>`。
|
||||
- 应用层 session ownership:OpenHub 自己用 SQLite 记录 `conversation_sessions` / messages / user_id,不把 opencode 原生 session 列表直接暴露给所有用户。
|
||||
- 应用层权限面板:模型权限、工具权限、skill 权限都在业务 DB 中维护,再同步/注入到用户 workspace。
|
||||
- per-user `.opencode` 目录:README 架构图显示每个 workspace 下有独立 `.opencode/skills`、`.opencode/tools`。
|
||||
- 单进程运维简单:固定 `OPENCODE_BASE_URL=http://127.0.0.1:4096`,Basic Auth 保护后端到 opencode 的内部访问。
|
||||
|
||||
关键风险:
|
||||
|
||||
- OpenHub 不是 opencode 原生多租户;它仍依赖一个全局 opencode server 和全局 opencode 数据库/credential/account 状态。
|
||||
- 隔离主要靠 `directory` 与 OpenHub 后端不暴露跨用户 session;如果绕过 OpenHub 直接访问 opencode,或知道别人的 session id,仍要依赖外层鉴权/反代拦截。
|
||||
- provider credentials 是 opencode server 全局配置;OpenHub 的用户模型/工具权限是应用层控制,不等于 opencode 内核 per-user credentials。
|
||||
- 它自研了聊天前端、消息库、知识/记忆/任务系统;这不符合 MNote 当前“尽量保留 opencode 官方 WebUI,不复刻消息 UI”的边界。
|
||||
|
||||
对 MNote 的启发:
|
||||
|
||||
- OpenHub 证明“单 opencode serve + `?directory=` 按用户工作区隔离”在产品上可跑,比一开始做多进程 profile manager 更轻。
|
||||
- MNote MVP 可以采用 OpenHub 的轻量隔离思路:固定一个本机 opencode server,所有请求由 MNote 登录态反代,MNote 后端只允许当前用户的 `rootUri` 作为 `directory`,并用 control-plane binding 限制 session ownership。
|
||||
- 但必须把这种模式标为 **应用层隔离 / 单机可信 opencode 后端**,不能标为强安全多租户。强隔离仍需后续 XDG profile 或 Docker。
|
||||
|
||||
更新后的推荐:
|
||||
|
||||
1. **立即采用 OpenHub-lite**:单 `opencode serve`、固定端口、MNote 反代、`directory=rootUri`、control-plane session ownership。
|
||||
2. **不复刻 OpenHub UI**:仍保留 opencode 官方 WebUI iframe;MNote 只做 host chrome、context、open/refresh、changed files。
|
||||
3. **补安全闸**:所有 `/api/page-ai/opencode/*` 和 iframe 反代必须校验当前登录用户、rootUri、session binding;不允许前端任意传 directory 打开非当前 root。
|
||||
4. **后续 shared-device 再升级**:当确实有多 MNote 用户同时使用同一机器时,再做 per-user XDG profile manager。
|
||||
|
||||
### 11.8 OpenHub 融合可行性评估
|
||||
|
||||
用户新判断:OpenHub 的前端、消息库、权限、记忆、文件、任务等功能与 MNote Page AI 长期目标高度重合,应评估是否直接融合,减少 MNote 自研量。
|
||||
|
||||
结论:**可融合,但不建议整套 OpenHub 作为 MNote 新后端;推荐抽取 OpenHub Page-AI 子系统,形成 MNote 内的 `OpenHub-lite`。**
|
||||
|
||||
可最大化复用的部分:
|
||||
|
||||
- **React/AntD 聊天前端**:`SmartQueryPage.jsx`、`ChatInput`、`AssistantMessage`、`ToolCall`、`QuestionForm`、`HistoryDrawer`、`DiffViewer`、`FileManager`、`GitTimeMachine` 等,可作为 Page AI 的 micro frontend,而不是继续维护当前简陋 host UI。
|
||||
- **消息库模型**:`conversation_sessions`、`conversation_messages`、图片、turn、opencode message id、归档、retry、last-turn delete 等,适合迁移到 MNote control-plane,替代 `sessionStorage` 和当前临时 binding。
|
||||
- **opencode 单进程接入模式**:后端按用户 workspace/rootUri 调 `/session`、`/session/{id}/prompt_async`、`/global/event` 并带 `directory=`,适合 MNote 当前“一个打开根目录”的简化模型。
|
||||
- **应用层权限面板**:模型权限、工具权限、skill 权限、usage 统计可以映射到 MNote 用户体系;短期先只做 Page AI 所需的模型/工具/skill 白名单。
|
||||
- **任务/团队/记忆模块**:Smart Entity、Team、Memory、Scheduler 与 MNote 长期 agent 目标相关,但第一阶段只作为后续模块,不应阻塞 Page AI MVP。
|
||||
|
||||
不建议直接搬入的部分:
|
||||
|
||||
- OpenHub 自带登录、用户管理、admin 页,与 MNote control-plane auth 重叠;应替换成 MNote 登录态。
|
||||
- OpenHub FastAPI 后端与 MNote Rust SSR/control-plane 双后端并存会增加部署复杂度;此判断已被 `7-68` 覆盖,当前第一阶段保留 OpenHub FastAPI/Redis/opencode client。
|
||||
- OpenHub 自研知识库/记忆/任务会与 MNote WeKnora、workspace、tree/file resource、control-plane 产生事实源冲突;旧 LightRAG 仅作为 legacy/fallback 参考。
|
||||
- OpenHub 不是 opencode 内核级强隔离,仍需 MNote 反代和 session ownership 限制。
|
||||
|
||||
推荐融合路线:
|
||||
|
||||
1. **Phase 1:iframe micro frontend spike**
|
||||
- 直接运行 OpenHub 前端的 Page-AI/Chat 子集,嵌入 MNote sidebar。
|
||||
- 后端 API 不直接用 OpenHub FastAPI,而是由 MNote 提供兼容 `/api/query/stream`、`/api/sessions/*`、`/api/files/*` 的最小 Rust adapter。
|
||||
- 目标是快速验证 UI/消息体验是否明显优于 opencode 官方 iframe。
|
||||
|
||||
2. **Phase 2:消息库迁移**
|
||||
- 在 MNote control-plane 增加 OpenHub-like `page_ai_sessions` / `page_ai_messages` / `page_ai_message_parts` / `page_ai_turns`。
|
||||
- 将 opencode session id、message id、tool calls、diff、reasoning、attachments、rootUri、pageAbsolutePath 统一持久化。
|
||||
- 替代当前临时 Page AI binding;支持跨浏览器、跨会话恢复。
|
||||
|
||||
3. **Phase 3:UI 组件裁剪融合**
|
||||
- 从 OpenHub 前端抽出 Chat shell、消息列表、工具调用、历史抽屉、Diff/File/GitTimeMachine 组件。
|
||||
- 去掉 Login/Admin/Knowledge/SmartEntity/Team 等非 Page AI 首屏模块。
|
||||
- 适配 MNote host chrome、当前页 context、changed file chip、MNote open/refresh。
|
||||
|
||||
4. **Phase 4:高级能力选择性引入**
|
||||
- FileManager 映射 MNote resource/file tree。
|
||||
- GitTimeMachine 映射 MNote changed files / snapshot / restore 设计。
|
||||
- Memory/Skill/Tool permission 映射 MNote 用户权限和未来 MNote MCP。
|
||||
- Smart Entity/Team 作为 Page AI 后续 agent team,不进入当前 MVP。
|
||||
|
||||
技术判断:
|
||||
|
||||
- 如果目标是“尽快有成熟 Page AI UI”,OpenHub 前端比 opencode 官方 iframe 更适合深度定制,因为它已经是普通 React/AntD 应用,消息、工具、历史、文件、diff 都在前端组件内。
|
||||
- 如果目标是“最少维护债”,opencode 官方 iframe 仍最省事,但 MNote 与页面/文件/权限/历史的融合会受 iframe 限制。
|
||||
- 当前更适合改为 **OpenHub UI + MNote Rust adapter + opencode runtime**:UI 和消息体验复用 OpenHub,用户/文件/权限/工作区真相仍归 MNote,agent runtime 仍归 opencode。
|
||||
|
||||
新的建议:
|
||||
|
||||
- 把 `7-65` 当前 iframe 方案降级为 runtime spike 与 fallback。
|
||||
- 新增或接续设计 `7-67-openhub-page-ai-fusion-v1`,目标是用 OpenHub Chat 子系统替代当前 Page AI UI。
|
||||
- 第一阶段只做 Chat/Session/Message/Diff/File open 五件事,不引入 OpenHub 登录/admin/知识库/team。
|
||||
### 11.9 OpenHub 知识库实现与 WeKnora 对照
|
||||
|
||||
2026-07-03 口径回正:Page AI 要做深度融合;当前默认知识库主线为 LightRAG + OpenHub tool facade,WeKnora 仅保留为历史设计、参考实现或备用 provider。因此本节只作为当时 OpenHub 自带知识库能力对照记录。
|
||||
|
||||
源码核验结论:**OpenHub 自带知识库是轻量 SQLite 文本知识库,不是完整 RAG/知识库底座;适合复用 UI、API 形状和 prompt 注入链路,不建议替代 WeKnora。**
|
||||
|
||||
OpenHub 知识库真实实现:
|
||||
|
||||
- 数据表只有 `knowledge_bases` 与 `knowledge_sources`:字段包括 `scope=enterprise/user`、`owner_id`、`title`、`source_type`、`content`、`tags`、统计字段;没有 chunk 表、embedding 表、向量库、图谱或 citation 表。
|
||||
- 上传解析支持 `.md/.txt/.pdf/.docx/.xlsx/.csv`:PDF 走 PyMuPDF 文本抽取,DOCX 走 python-docx 段落抽取,表格转文本行;没有 OCR、版面恢复、图片解析或复杂文档结构保真。
|
||||
- `chunker.py` 存在 Markdown/文本/表格切块逻辑,但当前知识库主链没有把 chunk 持久化到 DB,检索与注入仍围绕整份 `knowledge_sources.content`。
|
||||
- 检索分两层:DB 层用 `LIKE` 关键字筛出候选;服务层再对候选全文做 CJK/英文 token 的 BM25 + TF-IDF 重排;没有 embedding、semantic search、rerank model、hybrid vector search。
|
||||
- 注入方式是 prompt stuffing:小型个人知识库全量或近似全量注入,大型个人知识库取 2 条结果,企业知识库最多取 1 条结果,每条截取相关片段,总上下文默认限制约 1200 字符。
|
||||
- opencode 集成点是在发送用户问题前构造 `<context>...</context>`,并提示模型如果上下文不足就调用 `knowledge_knowledge_search` 工具继续查。
|
||||
- 前端 `KnowledgeManager.jsx` 和 admin 企业知识库 UI 可直接参考:列表、搜索、上传、添加、编辑、删除、统计、企业只读提示这些产品能力与 MNote 需要高度重合。
|
||||
|
||||
与 WeKnora 的关系:
|
||||
|
||||
- WeKnora 应继续作为 MNote 长期知识库底座候选:负责文档解析、索引、检索、召回、引用、权限过滤与跨文档问答。
|
||||
- OpenHub 知识库不应替代 WeKnora;它更像“用户短记忆/轻量知识片段/企业公告文本”的 fallback。
|
||||
- 当前融合方式是 **OpenHub AI 面板 + MNote Rust 知识库 adapter + LightRAG provider**:OpenHub 负责 Page AI 对话与工具事件承载,真正的 ingestion/search/citation 由 MNote 通过 provider-neutral facade 调 LightRAG。
|
||||
- OpenHub 的 `knowledge_sources` schema 可以作为 MNote control-plane 的 source registry 参考,但需要增加 `workspace_id/root_uri/resource_id/source_uri/provider_doc_id/index_status/permission_scope/citation_locator` 等 MNote 字段。
|
||||
- OpenHub 的 prompt 注入链路可以短期复用为 Page AI context block,但 WeKnora 命中结果必须带 citation/open-reference 映射,不能只塞纯文本。
|
||||
|
||||
对 7-67 深度融合设计的影响:
|
||||
|
||||
1. Page AI 主 UI 继续选 OpenHub Chat 子系统,而不是官方 opencode iframe。
|
||||
2. Knowledge 模块第一阶段只迁移 UI 与 API contract,不迁移其 SQLite 文本检索为长期底座。
|
||||
3. MNote Rust adapter 提供 OpenHub-compatible `/api/knowledge/*`,内部走 WeKnora 或本地 fallback。
|
||||
4. 保留 OpenHub 轻量知识库作为“未配置 LightRAG 时的 local fallback / 用户手工短知识”,但不能称为默认知识库主线。
|
||||
5. 新设计稿应明确:`OpenHub UI` 负责交互,`MNote control-plane` 负责用户与权限,`WeKnora` 负责知识库索引与检索,`opencode` 负责 agent 执行。
|
||||
@@ -1,90 +0,0 @@
|
||||
# 7-66 [process] Page AI opencode-native UI v1
|
||||
|
||||
> 状态补充(2026-07-03):本稿冻结为 native UI fallback / debug receipt 参考;Page AI 产品主线为 OpenHub / native agent + LightRAG + Turso/libSQL。不得继续扩写 MNote 自研聊天框作为默认 Page AI UI。此前指向 `7-68-openhub-weknora-mnote-deep-fusion-v1.md` 的 WeKnora 默认 provider 口径已标记 stale。
|
||||
|
||||
> 创建时间:2026-06-24
|
||||
>
|
||||
> 当前状态:`FROZEN / native UI fallback,仅保留为 debug receipt 与失败方向参考`
|
||||
>
|
||||
> Owner:07-ai / Page AI / opencode-native UI
|
||||
|
||||
## 1. 结论
|
||||
|
||||
本稿提出的 MNote-native opencode UI 方向已冻结为 fallback。当前主线不是在 MNote 内复刻 opencode 消息流、tool UI、permission UI,也不是回到官方 opencode WebUI iframe 默认路径,而是 **MNote 薄宿主壳 + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + LightRAG**。
|
||||
|
||||
因此 `7-66` 只作为反例和 fallback 保留:native message timeline / composer / permission cards 可以临时作为 debug receipt、故障诊断或 OpenHub 不可用时的降级参考,但不得成为 Page AI 主 UI,也不继续扩写自研聊天框。
|
||||
|
||||
```text
|
||||
主线 Page AI = MNote host chrome + OpenHub AI 面板 + OpenHub FastAPI/Redis/opencode client + LightRAG
|
||||
native fallback = MNote debug receipt + opencode session/event/diff 摘要
|
||||
iframe fallback = 7-65 官方 opencode WebUI iframe
|
||||
```
|
||||
|
||||
## 2. 参考结论
|
||||
|
||||
### 2.1 opencode 官方 VSCode 插件
|
||||
|
||||
官方 VSCode 插件 `sst-dev.opencode` 的公开说明更偏 IDE 集成而不是完整 WebUI 复刻:
|
||||
- 快捷键启动/聚焦 opencode terminal session。
|
||||
- 新建 opencode terminal session。
|
||||
- 自动共享当前 selection/tab。
|
||||
- 支持 `@File#L37-42` 文件引用。
|
||||
|
||||
这说明官方 IDE 路线不是“把 WebUI 完整 iframe 到 IDE”,而是让宿主 IDE 负责上下文、入口和文件引用,opencode runtime 负责 agent。
|
||||
|
||||
### 2.2 社区 VSCode WebUI 插件
|
||||
|
||||
社区方案集中在两类:
|
||||
- sidebar chat + diff viewer + multi-session tabs;自动启动 `opencode serve`,通过 HTTP/SSE 通信。
|
||||
- React webview 渲染 session、message parts、tool cards、permission、diff、provider/model。
|
||||
|
||||
对 MNote 的历史启发:直接重渲染 opencode API/SSE 会把 MNote 拉回自研聊天框路线。`7-68` 已覆盖该判断:优先嵌入 OpenHub AI 面板并保留 OpenHub 后端栈,native UI 只做 fallback / debug receipt。
|
||||
|
||||
## 3. 必须覆盖的 opencode 功能
|
||||
|
||||
### Phase A:可用会话壳
|
||||
|
||||
- [ ] session list / create / resume / current binding。
|
||||
- [ ] 当前 session 状态:agent、model/provider、token/cost、idle/running/error。
|
||||
- [ ] composer:发送 prompt、排队/运行状态、abort。
|
||||
- [ ] context bar:当前页、真实 Markdown 路径、selection、allowed roots。
|
||||
|
||||
### Phase B:消息与 parts
|
||||
|
||||
- [ ] user / assistant / system / synthetic 区分展示。
|
||||
- [ ] 隐藏 MNote context envelope 噪声,但保留“上下文已注入”状态。
|
||||
- [ ] text part 正文渲染。
|
||||
- [ ] reasoning part 折叠展示。
|
||||
- [ ] tool part 卡片:tool 名、状态、输入/输出摘要。
|
||||
- [ ] patch part / file part / shell part / agent/subtask part 以卡片展示。
|
||||
- [ ] message error、finish、tokens/cost 展示。
|
||||
|
||||
### Phase C:权限、问题、diff
|
||||
|
||||
- [ ] permission.asked 列表:allow once / always / reject。
|
||||
- [ ] question request:文本输入回复 / reject。
|
||||
- [ ] changed files chips:点击用 MNote 打开。
|
||||
- [ ] diff summary:新增/修改/删除数量;当前页命中后刷新编辑器。
|
||||
- [ ] abort / revert / unrevert 留出按钮,但 MVP 可先只接 abort。
|
||||
|
||||
### Phase D:事件驱动刷新
|
||||
|
||||
- [ ] 订阅 `/api/page-ai/opencode/events`。
|
||||
- [ ] 处理 `message.updated`、`message.part.updated`、`message.part.delta`、`session.next.*`、`permission.v2.asked/replied`、`session.diff`、`session.error`。
|
||||
- [ ] 不高频轮询;事件只触发有界 refresh:messages / permissions / diff。
|
||||
|
||||
## 4. MNote 特有集成
|
||||
|
||||
- changed file click:`window.__mnoteDocumentPaneRuntime.openResourceInActiveTab()`。
|
||||
- 当前页被修改:`refreshPrimaryDocument({ reason: 'page-ai-opencode-event' })`。
|
||||
- selection/current page/allowed roots 由现有 Page AI target runtime 计算。
|
||||
- native UI 只保留 `debug fallback`;默认 UI 使用 `7-68` 的 OpenHub AI 面板嵌入路线。
|
||||
|
||||
## 5. 第一版验收
|
||||
|
||||
- 打开 Page AI 看到 MNote-native opencode 面板,不出现 iframe 空壳。
|
||||
- 能创建/恢复 session。
|
||||
- 能发送真实消息,看到 user message 和 assistant/text/tool/reasoning/patch 卡片之一。
|
||||
- 能看到 session list、model/provider、changed files、permission 区域。
|
||||
- 如 opencode/provider 出错,UI 显示真实错误,不伪装成功。
|
||||
- 浏览器截图自检通过后再汇报。
|
||||
@@ -4,9 +4,19 @@
|
||||
Owner:07-ai / mnote-web / control-plane
|
||||
日期:2026-07-03
|
||||
|
||||
## 0. 口径修订(2026-07-18)
|
||||
|
||||
> **覆盖本稿原「保留 OpenHub 生产基线 / Pi 不作为默认入口」结论。**
|
||||
|
||||
- 代码已默认 `enable_page_ai_pi_lab=true`(`MNOTE_PAGE_AI_PI_LAB`)。
|
||||
- OpenHub / Pi TS 已退役到 recycle 边界,不再是生产基线。
|
||||
- 当前 Page AI 默认主链为 **Pi Rust + LightRAG + Turso/libSQL control-plane**。
|
||||
- 本稿后续只承接 Pi Lab **去 spike / 产品化**(持久化、模块拆分、命名),不再要求「保留 OpenHub 主线」。
|
||||
- 上位收口:`design/10-review/process/21-mvp-post-architecture-closure-checklist-v1.md`。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
本稿确认一个新方向:**保留 OpenHub 作为当前生产基线,同时新开 Pi-first Page AI Lab,验证 Pi 是否能成为 MNote-native Page AI runtime**。
|
||||
本稿原确认方向为:**保留 OpenHub 作为生产基线,同时新开 Pi-first Page AI Lab**。该「双主线」结论已被 2026-07-18 口径修订覆盖;以下历史正文仅作背景,实施以 §0 与 checklist 21 为准。
|
||||
|
||||
这里的 Pi 指当前官方仓库 `earendil-works/pi` / 旧 `badlogic/pi-mono`,当前 npm 主包已迁到 `@earendil-works/*`。本轮重新拉取的源码证据位于:
|
||||
|
||||
@@ -16,11 +26,11 @@ Owner:07-ai / mnote-web / control-plane
|
||||
|
||||
Pi 不是 OpenHub 的直接替代品,但它是当前最值得优先 spike 的底层候选。原因是:Pi 已经具备 coding agent、RPC、session tree、tool event、extension / skill / package、provider auth、多模型和可扩展工具体系;这些比通用 UI 框架更贴近 MNote 的 local-first Page AI 目标。
|
||||
|
||||
硬边界:
|
||||
硬边界(2026-07-18 修订):
|
||||
|
||||
- 不替换当前 OpenHub 主线。
|
||||
- 不把 Pi Lab 作为默认 Page AI 入口。
|
||||
- 不把 Pi JSONL session 直接当作 MNote 长期会话真相。
|
||||
- ~~不替换当前 OpenHub 主线。~~ → OpenHub 已退役;Pi Rust 为默认 Page AI。
|
||||
- ~~不把 Pi Lab 作为默认 Page AI 入口。~~ → 默认开启;「Lab」仅为入口名。
|
||||
- 不把 Pi JSONL session 直接当作 MNote 长期会话真相(元数据走 control-plane)。
|
||||
- 不绕过 MNote `AiAccessScope` / allowed roots / Turso control-plane。
|
||||
- 不把第三方 Pi package 当成已验收能力;每个第三方包必须通过本地 smoke 后才能纳入。
|
||||
|
||||
|
||||
+2
-3
@@ -6,11 +6,11 @@ Owner:07-ai / mnote-web / control-plane / Page AI
|
||||
|
||||
## 1. 结论
|
||||
|
||||
应该做 MNote 原生统一 AI 管理面板,但顺序不是先堆 UI,而是先把 control-plane 合同、持久化和 effective config API 做成唯一真相,再让 Pi Lab / OpenHub / LightRAG 消费同一套配置。
|
||||
应该做 MNote 原生统一 AI 管理面板,但顺序不是先堆 UI,而是先把 control-plane 合同、持久化和 effective config API 做成唯一真相,再让 **Pi Rust Page AI / LightRAG** 消费同一套配置。
|
||||
|
||||
文件夹授权必须只有一套:当前 `/admin/access-policy` 与 `/user/access-policy` 管理的 `directory_grants` 同时就是 MNote 工作区访问授权和 AI `allowed roots` 的事实源。AI 管理面板可以把它纳入同一个信息架构,但不能再维护第二套 AI-only allowed roots。
|
||||
|
||||
OpenHub 的 admin 面板证明了正确产品结构:provider/model、skills、tools、MCP、目录权限、知识库、历史、用量、健康状态必须先有管理面,聊天 UI 只展示用户可用的有效配置。MNote 不能把 OpenHub admin 作为 MNote AI 的长期真相,因为 OpenHub 仍是独立默认 Page AI 主线,Pi Lab 是 MNote-native 新入口,二者后续可能任意退役。MNote AI 管理中心应落在 Turso/libSQL control-plane。
|
||||
OpenHub 的 admin 面板(历史)证明了正确产品结构:provider/model、skills、tools、MCP、目录权限、知识库、历史、用量、健康状态必须先有管理面,聊天 UI 只展示用户可用的有效配置。MNote 不能把 OpenHub admin 作为 MNote AI 的长期真相。**2026-07-18:OpenHub 已不再是默认 Page AI 主线;Pi Rust 为默认入口。** MNote AI 管理中心应落在 Turso/libSQL control-plane。上位收口见 checklist 21。
|
||||
|
||||
## 2. 调研依据
|
||||
|
||||
@@ -41,7 +41,6 @@ OpenHub 的 admin 面板证明了正确产品结构:provider/model、skills、
|
||||
|
||||
源码:
|
||||
|
||||
- `earendil-works-pi-web-ui-0.75.3.tgz`
|
||||
- `/mnt/Data1T/tmp/pi-web-ui-shot/node_modules/@earendil-works/pi-web-ui/src/`
|
||||
- `/mnt/Data1T/tmp/pi-web-ui-shot/node_modules/@earendil-works/pi-agent-core/dist/`
|
||||
- `rust/crates/mnote-web/src/routes/page_ai_pi.rs`
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
> 范围:3000 主壳中的知识库 / RAG / OCR 状态面板、FileTree 知识库状态灯、当前页面附件索引入口、Page AI 对知识库状态的可见性。
|
||||
>
|
||||
> 上位依据:
|
||||
> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md`
|
||||
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`(`CURRENT_ARCHITECTURE.md` 为兼容指针)
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-51-lightrag-post-commit-hardening-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/done/7-52-lightrag-image-ocr-search-chain-hardening-v1.md`
|
||||
|
||||
Reference in New Issue
Block a user