feat: connect local Obsidian vaults to conversations

Users can now read, search, and update existing local Obsidian notes through
built-in tools without configuring an external MCP server or Obsidian plugin.
Add vault discovery, an optional folder scope, and independent test, save,
enablement, and runtime assignment controls in MCP settings.

Bundle MCPVault 0.16.0 and its runtime dependencies, exposing 19 shared tools
to direct models and managed local OpenCode and Continue conversations.
Obsidian is disabled by default. Ask remains read-only; Execute permits
writes. Scope and assignment changes revoke existing Obsidian grants.
DeepSeek Harness and remote execution remain unsupported.

Validate the isolated staged snapshot with its own dependency installation:
typecheck, lint, production build, and 540 tests across 14 files passed.
No paid model calls were repeated. Physical-offline, installer, and other
platform acceptance remain pending as documented in the feature record.

Release note: Connect local Obsidian vaults in MCP settings to find notes and
write conversation results back to them. Test all registered vaults or a
chosen folder before enabling access; no Obsidian plugin is required.
This commit is contained in:
mesalogo
2026-09-27 00:37:46 +08:00
parent 5116613615
commit 55efca3ee1
47 changed files with 4060 additions and 76 deletions
+32
View File
@@ -0,0 +1,32 @@
# Obsidian 本机仓库接入
GoodBuddy 通过内置 MCPVault 访问本机 Obsidian 仓库,供会话读取、搜索和修改笔记。功能默认关闭;默认范围为本机已注册的全部仓库,也可指定一个文件夹。接入直接访问本地文件,不要求 Obsidian 正在运行或安装插件。
当前源码已实现主服务、设置 IPC、HTTP 网关和直连工具提供器。自动化覆盖 19 个共享工具;Windows 真实 Electron 设置操作、原生目录对话框取消、键盘开关、浅深主题窄窗口,以及 OpenCode / Continue 从聊天输入框发起的实际笔记读写均已通过。Windows x64 解包应用也通过设置到随包 MCPVault 的连接与保存验证,测试限制了子进程 Node 网络 API 和 Chromium 代理。物理断网、安装器及其他目标系统仍待验收;命令、模型调用次数和全量检查阻塞见[进度](./progress.md)。
## 文档职责
| 文档 | 唯一职责 |
| --- | --- |
| [产品需求](./prd.md) | 用户问题、范围、稳定需求 ID 和产品验收 |
| [用户故事](./user-stories.md) | 使用场景及 Given/When/Then 验收 |
| [功能逻辑](./logic-design.md) | 范围决策、状态转换、失败和取消规则 |
| [技术设计](./technical-design.md) | 模块、契约、打包、验证,以及独立章节中的 UI 设计 |
| [进度](./progress.md) | 已核对的实现事实、剩余工作和验证记录 |
界面仅在现有 MCP 设置中增加一个卡片,因此不另设 `ui-design.md`。技术设计的“UI 设计”章节承担信息结构、控件、反馈、无障碍和窄窗口适配职责;全局视觉规范仍由 [UI-DESIGN.md](../../../UI-DESIGN.md)定义。
## 术语与依赖
| 术语 | 含义 |
| --- | --- |
| 仓库(vault) | 一个本机笔记文件夹;显示名称不作为调用身份 |
| 注册仓库 | 本机 Obsidian 的 `obsidian.json` 中登记的仓库,包含当前未打开的仓库 |
| `vaultPath` | 保存的范围设置;空字符串代表注册仓库集合,非空代表指定文件夹 |
| `vaultId` | 一次工具调用选择的仓库标识,与文件相对路径分开传递 |
| MCPVault | npm 包 `@bitbonsai/mcpvault`,本功能固定使用 `0.16.0` |
| 草稿 | 设置页面尚未保存的范围,可单独测试 |
共享目录包含 `obsidian_list_vaults` 和 18 个加 `obsidian_` 前缀的上游工具。连接测试显示的上游工具数仍为 18。保存范围、切换开关或修改分配会撤销此前含 Obsidian 的请求授权,规则见[功能逻辑](./logic-design.md#授权撤销与清理)。
Node 启动环境复用[工具执行环境](../local-tool-environment/README.md),不建立第二套解释器安装或下载配置。接入目标为直连模型、GoodBuddy 管理的本机 OpenCode 和 Continue;DeepSeek Harness 与远程执行路径沿用现有不可用边界。本功能的仓库位于桌面所在设备。
+60
View File
@@ -0,0 +1,60 @@
# 功能逻辑
## 状态维度
启用状态、Runtime 分配、已保存范围和页面草稿分别管理。启用状态不表示服务已连接,测试成功也不表示会话可调用。会话使用已保存范围;测试使用本次提交的草稿。
测试状态为未测试、测试中、成功或失败。修改草稿使先前测试结果失效;测试结果只描述该次请求,不持久化为服务健康状态。
## 规则与追溯
| ID | 规则 | 需求 / 故事 |
| --- | --- | --- |
| LR-1 | 仅启用操作改变启用状态;仅保存操作改变持久化范围;测试不改变二者。 | FR-1、FR-7;US-1~US-3 |
| LR-2 | 非空指定路径优先,完全绕过注册信息;空路径读取全部注册条目,忽略条目的打开状态。 | FR-2、FR-6;US-1、US-2、US-6 |
| LR-3 | 先解析当前范围,再校验 `vaultId`,成功后才启动目标仓库的工具进程。 | FR-3;US-5 |
| LR-4 | 上游能力完整保留;启用、分配和应用现有工作模式由既有会话入口执行,不另加 Obsidian 权限层。 | FR-4、FR-9;US-4、US-9~US-11 |
| LR-5 | 范围解析失败使本次请求失败,不跳过失效注册条目,不选择替代仓库。 | FR-8;US-7 |
| LR-6 | 测试仅发现仓库并列举工具,逐仓库连接;工具数按上游名称去重,不按仓库数相乘。 | FR-7;US-1、US-2 |
| LR-7 | 操作结束或取消后关闭本次 MCP 连接和子进程;取消不承诺撤销已落盘写入。 | FR-8;US-8 |
| LR-8 | 保存范围、切换开关和修改分配在输入校验后、持久化前撤销含 Obsidian 的旧请求授权并取消在途调用;网关销毁等待活动调用清理。 | FR-10;US-12 |
## 仓库决策
| 条件 | 结果 |
| --- | --- |
| 指定路径非空 | 验证为可访问的绝对目录,得到单仓库范围 |
| 默认范围且注册信息有效 | 使用全部注册条目,保留各自注册 ID |
| 注册文件不可读、无条目、条目无路径或任一目录不可访问 | 返回对应错误;用户可修复注册信息或选择文件夹 |
| 范围只有一个仓库且请求省略 ID | 使用唯一仓库 |
| 范围超过一个仓库且请求省略 ID | 拒绝调用,要求明确 ID |
| 请求提供 ID 且属于当前范围 | 使用该 ID 对应仓库 |
| 请求 ID 不属于当前范围 | 拒绝调用,不回退到唯一仓库或首个仓库 |
工具清单描述与实际文件操作分开。服务可用首个仓库读取固定版本的工具 schema,这不赋予首个仓库任何默认写入地位。跨仓库搜索或修改由调用方对明确的仓库 ID 分别发起请求,不把一次请求广播到全部仓库。
## 设置转换
| 动作 | 草稿与结果 | 持久化影响 |
| --- | --- | --- |
| 选择文件夹 | 更新草稿,切换为指定文件夹,清除旧测试结果 | 无 |
| 取消文件夹选择 | 保留原草稿 | 无 |
| 切换范围或编辑路径 | 清除旧结果和关联错误;指定文件夹模式空路径时报错 | 无 |
| 测试 | 使用提交时的草稿;忙碌期间禁止重复提交,结束后显示结果或错误 | 无 |
| 保存成功 | 使用返回快照显示已保存范围,结束当前草稿编辑状态 | 仅范围 |
| 保存失败 | 保留草稿并显示错误,可修改后重试 | 已保存范围不变 |
| 启用、关闭或调整分配 | 更新对应状态,草稿不自动保存 | 仅对应状态 |
上表仅列持久化影响;涉及授权的副作用按 LR-8 执行。连接测试不提供独立取消按钮,其临时连接不持有会话授权。
## 授权撤销与清理
Main 对范围保存、开关和分配请求先验证来源及输入,再撤销现存的含 Obsidian 授权,随后保存设置。提交相同值也触发撤销。输入无效时在撤销前返回错误;持久化失败时原设置保留,但旧授权已经失效。草稿编辑、文件夹选择和连接测试均不触发此撤销。
撤销按整个请求授权执行。若同一个授权同时含 Obsidian 和知识库等能力,该授权整体失效;独立的不含 Obsidian 授权保留。后续请求按当前保存的范围、开关、分配和工作模式重新取得授权;关闭后不再签发 Obsidian 能力。
撤销会立即移除旧授权并发出取消信号,设置响应不等待子进程完全退出。工具调用负责在结束路径关闭 transport 和 client;网关销毁则等待活动调用完成清理,包含先前已撤销但仍在关闭的调用。取消过程中已落盘的内容保留。
## 完整性检查
规则已覆盖首次使用、草稿测试、保存、范围恢复、单仓库、多仓库、失效路径、旧配置、工作模式,以及设置变更对旧授权和在途调用的影响。当前没有待定的产品决策。实现及各层自动化、Electron 与安装包验证状态见[进度](./progress.md)。
+36
View File
@@ -0,0 +1,36 @@
# 产品需求
## 用户问题与目标
用户需要在会话中查找本机已有的 Obsidian 笔记,并将整理结果写回指定仓库。手动配置 Node、MCP 命令或 Obsidian 插件会增加首次使用步骤。接入应随应用提供依赖,支持先测试范围,再决定保存和启用。
## 功能需求
以下 ID 在后续实施中保持稳定。场景验收见[用户故事](./user-stories.md),行为决策见[功能逻辑](./logic-design.md)。
| ID | 产品要求 |
| --- | --- |
| FR-1 | 首次使用及旧设置缺少 Obsidian 字段时默认关闭。保存范围、测试连接和启用是独立操作,不互相隐式触发。 |
| FR-2 | 默认 `vaultPath` 为空,访问本机已注册的全部仓库,包含未打开的仓库。用户可改选一个本机文件夹,也可恢复默认范围。 |
| FR-3 | 工具调用使用仓库 ID 定位。可用仓库超过一个时必须明确指定 `vaultId`;缺失或无效时返回错误,不按顺序、名称或最近使用情况选择写入目标。 |
| FR-4 | 提供 MCPVault 0.16.0 的全部 18 个上游工具及其参数能力。不得增加 Obsidian 专用只读设置、工具过滤或额外审批;应用现有 Ask/Execute 工作模式照常生效。 |
| FR-5 | MCPVault 及完整运行依赖随安装包交付,使用现有托管 Node,首次调用无需联网安装 npm 包,也无需用户安装系统 Node。 |
| FR-6 | 不依赖正在运行的 Obsidian、社区插件、REST 服务或 API 密钥。指定文件夹可绕过注册信息,不要求该文件夹已在 Obsidian 注册。 |
| FR-7 | 功能关闭时也可测试未保存的草稿。测试返回仓库名称、路径和上游工具数,不修改笔记、不保存范围、不启用功能。 |
| FR-8 | 范围无效、仓库不可访问、内置依赖缺失或工具执行失败时说明原因并保留草稿。取消文件夹选择不清空输入;会话调用取消后释放对应子进程。 |
| FR-9 | 沿用内置 MCP 的启用与 Runtime 分配方式。设置范围属于桌面端全局配置,具体会话仍受既有能力分配和工作模式约束。 |
| FR-10 | 提交保存范围、切换 Obsidian 开关或修改分配时,撤销此前含 Obsidian 的请求授权并取消其在途调用,避免继续使用旧范围或旧分配。应用关闭须等待活动工具清理;不回滚已经落盘的修改。 |
“离线”指本地 MCP 服务的启动和文件操作不需要下载依赖;模型服务是否联网由模型配置决定。工具返回的笔记内容会进入当前会话处理流程,不能将本地接入描述为模型推理也完全离线。
## 范围与非目标
本期接入原有仓库文件,不复制为 GoodBuddy 知识库,不提供同步服务、仓库管理器或 Obsidian 编辑器。附件相关能力按上游工具提供的范围开放,不承诺未包含在上游中的附件编辑能力。文件删除等参数沿用上游语义;上游参数校验不构成新增产品审批。
注册仓库发现不扫描整块磁盘,不推断最近活动仓库。接入支持直连模型、GoodBuddy 管理的本机 OpenCode 和 Continue;DeepSeek Harness 和远程执行路径保持不可用。远程 Host 仓库发现、跨设备同步和仓库迁移不在本期范围。
## 产品验收
发布前须完成[技术设计中的验证矩阵](./technical-design.md#验证与实施顺序),并在[进度](./progress.md)记录真实结果。验收至少覆盖:关闭状态测试草稿、保存后重新进入设置、恢复全部仓库、多仓库明确选择、实际读取和写回、工作模式约束,以及安装包断网启动。
18 个上游工具按名称和参数契约逐项比对;加上仓库发现,共有 19 个共享工具。自动化验证分别覆盖注册 IPC、HTTP 网关和直连工具提供器,安装包及完整 Electron 界面仍单独验收。配置提交时还须验证旧授权立即失效、在途调用收到取消,以及网关销毁等待子进程清理完成。
+109
View File
@@ -0,0 +1,109 @@
# 实施进度
## 当前状态
2026-09-27:主服务、设置 UI、Main IPC、HTTP 网关和直连工具提供器已实现,共享目录共 19 个工具。Windows 真实 Electron 设置操作与 OpenCode / Continue 聊天输入框读写已通过;Windows x64 解包应用通过受限网络下的设置连接和保存验证。物理断网、安装器及其他系统验收仍待完成,全量检查存在下述无关失败;当前状态不表示已发布。
| 阶段 | 已核对事实 | 剩余工作 |
| --- | --- | --- |
| TS-1 配置与服务 | 默认关闭、范围独立保存、注册发现及 ID 路由已实现;复制后的真实 MCPVault 已用于文件操作和错误传播测试 | 跨平台安装包验证归入 TS-4 |
| TS-2 设置 UI | 真实 Electron 窗口中验证草稿测试不保存、不启用,保存和错误保留输入,Windows 原生目录对话框取消、键盘开关、19 工具展开及浅深主题窄窗口;修复表单横向撑宽 | 其他系统原生窗口验收 |
| TS-3 网关与 IPC | 19 工具、Ask/Execute、撤销、取消及清理自动化通过;OpenCode / Continue 真实会话及聊天输入框读写通过 | 安装后的聊天读写与其他系统验收 |
| TS-4 打包与交付 | 当前源码 `build:bundle` 和 Windows x64 `--dir` 打包通过;实际 `app.asar` UI 连接随包 MCPVault 并保存,子进程加载网络 API 限制,PATH 仅含 System32 | 物理断网或 OS 级隔离、安装器、macOS/Linux/其他架构原生资源验收 |
DeepSeek Harness 和远程执行保持既有不可用边界。本轮产品代码仅修改 Obsidian 表单布局,没有改变 Agent 或远程 Runtime 路径。
## 提交快照验证
提交前从 HEAD 与 Obsidian 暂存内容生成独立目录,排除并行开发的原生终端、DSH 升级、IPC 拆分与 Magic Notes 改动。锁文件从 HEAD 重新生成,仅添加固定版本 MCPVault 及其依赖,没有升级或删除既有依赖项;独立目录安装该锁文件后验证。
`npm run typecheck`、`npm run lint` 和 `npm run build` 均通过。聚焦服务、Electron、网关、直连工具、配置、Continue 适配器及共享 UI 的 11 个文件共 237 项通过;完整 IPC、设置面板与构建测试的 3 个文件共 303 项通过。合计 14 个文件、540 项测试通过。本次没有重跑全量套件、安装包或真实模型调用,新增模型调用为 0。下文全仓失败记录来自此前混合工作区,不代表此提交快照的类型检查或构建结果。
## 2026-09-27 验收补充
以下结果来自独立临时应用配置、项目和 vault。模型凭据通过临时复制的 `runtime-settings.json` 与配套 `Local State` 在 Electron Main 中解密;源配置和用户 vault 均未修改。测试结束删除临时配置、凭据副本和笔记;Windows 解包构建与检查日志保留在系统临时目录的 `opencode` 下。没有提交、发布、安装器或 portable 构建。
| 验证项 | 实际结果与边界 |
| --- | --- |
| 聚焦测试 | 8 文件、133 项通过,含 `obsidian-electron.test.ts` 和 Continue 适配器;IPC 定向 5 项通过、152 项跳过。布局修正后 MCP 设置、共享 UI 与 Electron 服务共 43 项通过 |
| `npm run build` / `npm run typecheck` | `build` 停在类型检查;末次类型检查仍为 SSH pool、SSH directory browser 和 SSH terminal 测试替身缺少 `forwardLoopback`。本轮脚本类型错误已修复;未修改并行开发的 SSH 测试 |
| `npm run build:bundle` | 当前源码通过,布局修改后再次通过;不能据此将 `npm run build` 记为通过 |
| `npm run lint` | 当前工作区通过 |
| 分项类型检查 | `npx tsc --noEmit -p tsconfig.web.json` 与 `npx tsc --noEmit -p tsconfig.agent.json` 均通过 |
| `npm test` | 首次 200 秒超时;随后后台运行完整结束,691.25 秒,444 文件通过、3 失败、11 跳过;5,274 测试通过、3 失败、83 跳过 |
| 全量失败定位 | `activity-history-io.test.ts`:迁移重复添加 `source_json`;`magic-notes-capture.electron.test.ts`:宽度 0、溢出 82 的布局断言;`build-release.test.ts`:fixture 缺少 `@deepseek-ai/dsh/package.json`。均属于并行改动范围,本轮保留原状 |
| 真实设置 UI | [Electron runner](../../../build/run-obsidian-electron-acceptance.cjs) 导入生产 Main、使用生产 Preload/Renderer 和原生鼠标键盘,覆盖测试 18 个上游工具、保存、错误、目录对话框取消、Space 启用和展开 19 个工具。未替换 IPC 或 ObsidianService |
| 布局缺陷与修复 | 原表单复用了横向 `.capability-diagnostic`;759 像素内容视口中卡片右边界达到 1116 像素。改为 Obsidian 纵向表单、按钮及路径换行后,普通状态右边界 691,含测试结果和工具展开时 732;浅深主题均无卡片内部横向溢出 |
| 真实 Runtime 会话 | [Runtime runner](../../../tests/obsidian-runtime-acceptance.ts) 使用真实 OpenCode / Continue、生产 HTTP 网关和 MCPVault,断言工具事件、模型返回和实际文件。两轮各 Runtime 各 3 次模型 HTTP 请求,共 12 次;第二轮使用 UI 保存并启用的能力快照 |
| 完整聊天输入框 | `--chat` 中 OpenCode 实际发送、读写及回复验证通过,计 4 次模型 HTTP 请求;该次随后在 Continue 菜单定位处失败。修正选择器后 `--chat --continue-only` 通过,同样 4 次请求。合计本轮 20 次真实模型 HTTP 请求,其他启动、菜单和凭据准备失败均为 0 次。聊天证据来自开发生产 bundle;没有将独立 Runtime harness 冒充聊天 UI |
| Windows x64 解包应用 | 正常 electron-builder 配置以 `--dir --publish never` 生成 `GoodBuddy.exe` 和 `app.asar`,含解包 MCPVault 及 `windows-trash.exe`。[打包 runner](../../../build/run-obsidian-packaged-acceptance.cjs) 通过 CDP 驱动真实页面控件,设置连接返回 18 工具,保存快照匹配临时 vault;托管 node shim 指向该打包 EXE |
| 网络限制 | 打包 EXE 的 PATH 仅含 System32,Chromium 指向不可用代理;Electron 启动会移除 `NODE_OPTIONS`,首次尝试未加载 guard。随后仅在测试 Main 的本机 inspector 中恢复该变量,审计确认真实 MCPVault 子进程加载 guard,入口为 `app.asar.unpacked/.../server.js`。Node guard 拒绝非 loopback socket 和 fetch;不覆盖 OS 全部联网方式,不算物理断网或防火墙隔离 |
复现命令如下。打包命令须使用真正的 Node 可执行文件;本机 PATH 中的 Electron Node shim 会使 electron-builder CLI 误读 argv。`--live` 和 `--chat` 会调用真实文本模型;默认读取当前用户 GoodBuddy 模型配置,也可用 `GOODBUDDY_ACCEPTANCE_SETTINGS` 指向含配套 `Local State` 的测试配置目录中的设置文件。
```powershell
npx vitest run src/shared/obsidian-tools.test.ts src/main/obsidian/obsidian-service.test.ts src/main/obsidian/obsidian-electron.test.ts src/main/agent/knowledge-mcp-gateway.obsidian.test.ts src/main/agent/model-tool-provider.obsidian.test.ts src/main/capabilities/capability-service.test.ts src/renderer/src/McpSettingsSection.test.tsx src/main/agent/continue-host-adapter.test.ts
npx vitest run src/main/ipc.test.ts -t Obsidian
npx vitest run src/renderer/src/McpSettingsSection.test.tsx src/renderer/src/WorkspacePrimitives.test.tsx src/main/obsidian/obsidian-electron.test.ts
npm run build:bundle
node build/run-obsidian-electron-acceptance.cjs
node build/run-obsidian-electron-acceptance.cjs --live
node build/run-obsidian-electron-acceptance.cjs --chat
node build/run-obsidian-electron-acceptance.cjs --chat --continue-only
# 以下占位路径替换为本机 Node、临时构建目录及打包 EXE。
& "<node.exe>" build/run-electron-builder.cjs --win --x64 --dir --publish never --config.directories.output="<temporary-output>"
node build/run-obsidian-packaged-acceptance.cjs "<temporary-output>/win-unpacked/GoodBuddy.exe"
```
本机打包输出为 `%TEMP%/opencode/obsidian-package-acceptance/win-unpacked`;全量日志为 `%TEMP%/opencode/obsidian-full-tests.log` 与 `.err`。以下保留 2026-09-26 的历史记录,不能用其中旧阻塞覆盖本节最新结果。
本轮文档 deai-writing 扫描 6 文件,0 个阻断项、12 个复核项;复核项均为测试条件、状态或能力范围限定,通读后保留。`git diff --check` 通过。
## 源码与测试证据
2026-09-26 核对以下源码与测试。本轮重新执行的结果见验证记录;此前构建结果保留原来源,不作为当前工作区构建通过的证明。
| 证据 | 可支持的结论 |
| --- | --- |
| [obsidian-service.ts](../../../src/main/obsidian/obsidian-service.ts) 与[服务测试](../../../src/main/obsidian/obsidian-service.test.ts) | 注册发现、真实复制包后的读写、许可证与资源、工具集合、多仓库 ID 以及取消后进程清理 |
| [capability-service.test.ts](../../../src/main/capabilities/capability-service.test.ts) | 范围持久化、旧字段缺省和拒绝 `readOnly` 参数 |
| [ipc.test.ts](../../../src/main/ipc.test.ts) | 设置组件调用正式注册的 IPC 处理器、真实配置服务及 MCPVault;Electron 传输和目录对话框由测试替代,不是安装后完整界面验证 |
| [共享工具测试](../../../src/shared/obsidian-tools.test.ts) | 19 工具集合和 18 个上游工具的字段、默认值、必填项与约束对应 |
| [HTTP 网关测试](../../../src/main/agent/knowledge-mcp-gateway.obsidian.test.ts) | 实际 HTTP MCP 发现 19 工具,真实多仓库读写、删除及错误返回;另覆盖撤销、取消和等待 transport 关闭 |
| [直连提供器测试](../../../src/main/agent/model-tool-provider.obsidian.test.ts) | 通过实际服务执行全部 19 个工具并核验文件,包含笔记移动、二进制附件移动和删除;覆盖 Ask、失效令牌、原生错误及关闭清理 |
| [index.ts](../../../src/main/index.ts) 与[ipc.ts](../../../src/main/ipc.ts) | 正式启动实例化、依赖注入和 IPC 注册;范围保存、开关及分配提交在持久化前撤销旧 Obsidian 请求授权 |
| [package-mcpvault.ts](../../../src/main/obsidian/package-mcpvault.ts) 与[构建配置](../../../electron.vite.config.ts) | 构建期递归复制运行依赖,并固定上游版本 |
## 验证记录
| 验证项 | 2026-09-26 结果及范围 |
| --- | --- |
| 聚焦生产组件自动化 | 本轮 7 个文件、132 项测试通过,包含 Continue 适配器;另运行 IPC 的 Obsidian 用例,5 项通过、151 项跳过 |
| `npm run build` | 此前主实施方反馈通过;本轮未重跑 |
| `npm run typecheck` | 首次运行报原生终端依赖未列入项目的 TS6307;并行改动后复跑,该错误已消失,当前阻塞为 `native-terminal-client.test.ts:33` 的 TS2352 类型转换错误。未修改这部分并行开发代码 |
| `npm run lint` | 本轮通过;最终测试文件改动另行定向检查 |
| `npm test` | 本轮在 200 秒超时;结束前出现数据库迁移、Magic Notes 等失败。全量套件没有通过结论;Continue 适配器定向复跑通过 |
| Electron 托管 Node 冒烟 | 将服务测试改为真实 Electron 可执行文件,以 `ELECTRON_RUN_AS_NODE=1` 启动复制后的包,10 项通过。Windows 子进程 PATH 仅保留测试 shim 与 System32;覆盖发现、读写、局部修改、属性、回收站和取消清理。没有隔离网络,物理断网安装包验收仍待完成 |
| 安装后完整 Electron UI 与跨平台验收 | 待完成;组件自动化使用替代 Electron 传输,不能覆盖原生窗口及安装环境 |
2026-09-26 本次文档修订新增 FR-10、LR-8、US-12,保留原有 ID。交付检查结果:
| 文档检查 | 结果 |
| --- | --- |
| 相对链接及锚点 | 6 份功能文档的 53 个链接、4 份跨功能文档新增的 5 个引用通过 |
| 稳定 ID 与工具清单 | FR-1~FR-10、US-1~US-12、LR-1~LR-8、TS-1~TS-4 唯一声明通过;18 个上游工具和 19 个共享工具映射一致 |
| deai-writing 审校 | 功能文档 0 个阻断项、11 个复核项,逐项核对后保留状态和能力边界限定;跨功能新增段落单独扫描,0 个阻断项、0 个复核项 |
| 差异与空白 | `git diff --check` 通过;临时脚本另检查未跟踪的功能文档,无行尾空白或旧网关接入状态 |
本轮修改服务测试和验证记录;测试使用独立临时仓库,未修改用户笔记或应用设置。
最终定向 ESLint 和 `git diff --check` 通过。README 与本进度文档经 deai-writing 扫描,0 个阻断项、3 个复核项;复核项均描述验收范围或草稿状态,保留事实限定。
聚焦测试复现入口如下;这些命令用于定位相关用例,不是全量通过证明:
```powershell
npx vitest run src/shared/obsidian-tools.test.ts src/main/obsidian/obsidian-service.test.ts src/main/agent/knowledge-mcp-gateway.obsidian.test.ts src/main/agent/model-tool-provider.obsidian.test.ts src/main/capabilities/capability-service.test.ts src/renderer/src/McpSettingsSection.test.tsx
npx vitest run src/main/ipc.test.ts -t Obsidian
```
剩余验收以本文顶部阶段表为准,按[验证矩阵](./technical-design.md#验证与实施顺序)补充物理断网、安装后及其他目标系统证据。配置变更触发取消与销毁等待的具体语义以[功能逻辑](./logic-design.md#授权撤销与清理)为准。
+103
View File
@@ -0,0 +1,103 @@
# 技术设计
本文负责实现架构及 UI 设计,落实 [FR-1~FR-10](./prd.md) 和 [LR-1~LR-8](./logic-design.md)。实现与验证状态统一在[进度](./progress.md)记录。
## 模块边界
| 层 | 职责与源码 |
| --- | --- |
| Renderer | [McpSettingsSection.tsx](../../../src/renderer/src/McpSettingsSection.tsx) 编辑草稿、显示已保存范围和测试结果;不直接访问文件系统或启动 Node |
| Shared / Preload | [capability-contracts.ts](../../../src/shared/capability-contracts.ts)、[contracts.ts](../../../src/shared/contracts.ts)、[ipc-channels.ts](../../../src/shared/ipc-channels.ts) 和 [preload/index.ts](../../../src/preload/index.ts) 定义窄接口 |
| 配置服务 | [capability-service.ts](../../../src/main/capabilities/capability-service.ts) 保存范围,沿用内置 MCP 的启用和分配状态 |
| Main 服务 | [obsidian-service.ts](../../../src/main/obsidian/obsidian-service.ts) 发现仓库、连接上游、列工具和调用工具 |
| 会话网关 | [knowledge-mcp-gateway.ts](../../../src/main/agent/knowledge-mcp-gateway.ts) 提供 HTTP MCP、仓库发现、目标路由、请求授权撤销及活动调用清理 |
| 共享目录与直连 | [obsidian-tools.ts](../../../src/shared/obsidian-tools.ts) 定义 19 个工具;[model-tool-provider.ts](../../../src/main/agent/model-tool-provider.ts) 发现并调用同一目录,保留上游错误详情 |
| 构建 | [package-mcpvault.ts](../../../src/main/obsidian/package-mcpvault.ts) 复制依赖树,[electron.vite.config.ts](../../../electron.vite.config.ts) 调用打包函数,[package.json](../../../package.json) 配置版本及解包资源 |
正式链路为设置页面经 Preload、受信任的 Main IPC 处理器到配置服务或 ObsidianService;会话经既有能力入口到 ObsidianService,再通过 stdio 到 MCPVault。[index.ts](../../../src/main/index.ts) 创建服务并注入工具启动环境、网关及 IPC;[ipc.ts](../../../src/main/ipc.ts) 已注册设置、测试和目录选择处理器,并在会话入口按保存设置及工作模式签发请求授权。
## 配置与 IPC
`obsidianSettingsSchema` 是严格对象,仅有 `vaultPath`:字符串,去除首尾空白,最大 4096 字符,默认 `''`。不接收 `readOnly`。持久化范围为桌面全局,沿用 CapabilityService 的存储,不增加数据库或配置副本。
| 接口 | 输入与结果 | 副作用 |
| --- | --- | --- |
| `updateObsidianSettings` | `{ vaultPath }` → `CapabilitySnapshot` | 先撤销旧 Obsidian 请求授权,再保存范围;启用与分配值不变 |
| `testObsidianConnection` | 草稿 `{ vaultPath }` → `{ vaults, toolCount }` | 临时连接,结束即清理,不保存设置 |
| `selectObsidianVault` | 无输入 → 目录字符串或 `null` | Main 打开系统目录选择器,取消返回 `null` |
`vaults` 的元素为 `{ id, name, path }`。Main 处理器验证调用来源和共享 schema。范围保存只验证设置形状;目录存在性由发现或测试校验,保存成功不能显示为连接成功。已有配置缺少新字段时补默认值,其他能力设置保持原值。
设置更新、内置 MCP 开关及分配处理器在参数校验后调用 `knowledgeGateway.revokeObsidianCapabilities()`;后两者只在 `serverId === 'obsidian'` 时调用。撤销发生在持久化前,之后通过既有 `refreshCapabilities` 刷新配置;失败语义由 [LR-8](./logic-design.md#授权撤销与清理)定义。
## 仓库发现与路由
注册文件位置如下,优先使用相应环境变量:
| 系统 | 注册文件 |
| --- | --- |
| Windows | `%APPDATA%/obsidian/obsidian.json`,缺少变量时使用用户目录下 `AppData/Roaming` |
| macOS | `~/Library/Application Support/obsidian/obsidian.json` |
| Linux | `$XDG_CONFIG_HOME/obsidian/obsidian.json`,缺少变量时使用 `~/.config` |
注册条目的键直接用作 ID。指定文件夹经绝对路径规范化后生成 SHA-256 ID,Windows 对哈希输入转小写;这是路径身份,不是文件完整性校验或跨机器永久身份。目录移动或注册 ID 变化后,调用方须重新发现仓库。
`callTool` 接收 `{ vaultId?, name, arguments }`,按 LR-3 验证目标后转发。共享工具 `obsidian_list_vaults` 返回仓库列表,其余工具名称为 `obsidian_` 加上游名称,输入额外包含 `vaultId`。网关校验共享 schema,提取 `vaultId`,移除名称前缀后调用上游;其余参数原样保留。输入默认值保持可选,frontmatter 键不受外层严格对象限制。[共享契约测试](../../../src/shared/obsidian-tools.test.ts)逐项比对上游字段、必填项、默认值及约束。
## 上游工具契约
名称集合依据本地安装的 `@bitbonsai/mcpvault@0.16.0` 中 `dist/src/createServer.js` 核对。HTTP 网关和直连工具提供器共享 19 个工具:`obsidian_list_vaults` 加下表 18 个工具的带前缀映射。设置连接测试返回的 `toolCount` 为上游名称去重数,正常为 18,不包含仓库发现工具。
| 上游工具 | 能力 |
| --- | --- |
| `read_note` | 读取笔记 |
| `write_note` | 写入笔记 |
| `patch_note` | 局部替换笔记内容 |
| `list_directory` | 列出目录 |
| `delete_note` | 按上游删除参数处理笔记 |
| `search_notes` | 搜索笔记 |
| `move_note` | 移动或重命名笔记 |
| `move_file` | 移动文件,包括上游支持的附件文件 |
| `read_multiple_notes` | 批量读取笔记 |
| `update_frontmatter` | 更新 frontmatter |
| `get_notes_info` | 获取笔记信息 |
| `get_frontmatter` | 获取 frontmatter |
| `manage_tags` | 管理标签 |
| `get_vault_stats` | 获取仓库统计 |
| `list_all_tags` | 列出标签 |
| `wiki_link` | 处理 wiki 链接 |
| `get_note_outline` | 获取笔记大纲 |
| `read_note_lines` | 按行读取笔记 |
服务分页读取上游工具,不设置功能白名单。读写属性供应用既有工作模式使用,不能据此增加 Obsidian 专用过滤。保留上游的错误结果与参数规则,例如删除参数 `confirmPath`;不把参数要求改造成额外确认弹窗。
## 离线打包与生命周期
构建固定 npm 版本为 `0.16.0`,复制包及递归运行依赖,保留许可证、模块相对资源和 `trash` 的原生辅助程序。资源位于 `out/main/obsidian-mcpvault/node_modules`,生产安装包将其放在 `app.asar.unpacked`。缺包或版本不符必须明确失败,不在运行时执行 `npx` 或回退到在线安装。
服务将 `app.asar` 路径映射到解包目录,检查 `@bitbonsai/mcpvault/dist/server.js` 存在,使用现有 `LaunchEnvironmentProvider` 注入命令环境后运行 `node <server.js> <vault.path>`,工作目录为目标仓库。托管 Node 的准备和用户自定义解释器规则由[工具执行环境技术设计](../local-tool-environment/technical-design.md)负责。Obsidian 不新增下载源;已有工具下载源不控制随包交付的 MCPVault。
每次列举或调用创建 stdio 连接,在 `finally` 中关闭 transport 和 client,初始化失败也清理。`callTool` 传递 `AbortSignal`,启动前已取消时不启动进程。连接测试逐仓库列工具,不调用写入工具;它没有独立取消 IPC。网关沿用现有 MCP 结果大小约束,保留上游原生内容和错误详情,超时沿用 SDK 与既有会话路径。
`callObsidianTool` 合并请求、授权撤销及单次调用的取消信号,并在结果返回前再次检查取消。`revokeObsidianCapabilities` 对含 Obsidian 的授权调用既有 `revoke`,移除令牌并中止其 controller;混合授权整体失效。网关以 `activeObsidianCalls` 跟踪调用 Promise,`dispose()` 先撤销授权,再等待这些 Promise 完成,随后关闭下游会话和 HTTP 服务。由于服务 Promise 包含 `finally` 中的 transport 关闭,等待范围包含真实子进程清理,也包含先前撤销后尚未关闭的调用。
## UI 设计
此章节独立承担功能 UI 设计职责,服从[统一界面设计系统](../../../UI-DESIGN.md)。入口为“能力与工具 → MCP → 内置 MCP”的 Obsidian 卡片,复用 `PageTabs`、卡片、表单、按钮、Switch 及 Runtime 分配 Checkbox。
卡片顺序为名称及状态、启用开关、能力说明、仓库范围、可选路径输入、已保存范围、选择/测试/保存操作、关联反馈、Runtime 分配和可展开工具列表。主按钮为“保存”,测试与选择文件夹为次按钮。范围选项为“所有本机仓库”和“指定文件夹”;指定模式才显示路径输入框。页面明确区分草稿与“已保存范围”。
关闭状态仍允许选择、测试和保存。快照尚未载入或操作忙碌时禁用相关提交,测试按钮显示进度。修改范围或路径清除旧结果;目录选择取消保留原值。连接结果在卡片内显示仓库名称、路径和工具数,作为可复查的诊断内容;字段错误及可立即重试的测试错误就近显示,保留输入。普通保存提示如需增加,使用应用通知,不重复显示同一事件。
启用控件使用 `role="switch"`;范围、路径和按钮具备可访问名称,路径错误使用 `aria-describedby`,错误用 `role="alert"`,测试结果用 `role="status"`。工具展开按钮提供 `aria-expanded` 和 `aria-controls`。沿用原生键盘行为和共享焦点样式。Obsidian 的多字段诊断表单采用纵向布局,操作按钮换行,结果中的长路径允许折行;浅色和深色主题均复用语义令牌。Windows 760 像素宽窗口的实际测量及原生输入结果见[进度](./progress.md)。
## 验证与实施顺序
| 阶段 | 验证内容 | 追溯 |
| --- | --- | --- |
| TS-1 配置与服务 | 缺省字段、持久化独立性、各平台注册路径、ID 路由、真实上游读写、取消清理 | LR-1~LR-3、LR-5~LR-7 |
| TS-2 设置 UI | 草稿测试不保存不启用、切换范围、取消选择、失败保留输入、键盘与窄窗口 | US-1~US-3、US-7、US-8 |
| TS-3 网关与 IPC | 正式 Main 处理器、19 个共享工具、分配、Ask/Execute、配置提交撤销旧授权、在途取消与销毁等待;核对 DeepSeek Harness 和远程执行不可用边界 | LR-3、LR-4、LR-7、LR-8;US-4、US-5、US-9~US-12 |
| TS-4 安装包与端到端 | 在实际支持系统上断网且无系统 Node 测试;Obsidian 关闭;许可证及原生资源完整;从设置到真实会话读写 | FR-5、FR-6;US-6 |
当前分配目标为 `model`、`opencode`、`continue`;[DeepSeek Harness](../../../src/main/agent/deepseek-harness-runtime.ts)和[远程 ACP Runtime](../../../src/main/agent/acp-remote-runtime.ts)的 `supportsScopedDataTools` 为 `false`,沿用既有不可用边界。已通过的生产组件自动化包含设置组件到注册 IPC、HTTP 网关,以及直连工具提供器全部 19 个工具的实际执行。真实聊天会话、Electron 托管 Node、Windows 解包应用及网络限制的不同验证范围见[进度](./progress.md)。
+20
View File
@@ -0,0 +1,20 @@
# 用户故事
本期角色为使用本机笔记的桌面用户,以及替用户调用工具的会话助手。需求定义见[PRD](./prd.md)。下表描述验收条件,不表示场景已经通过。
| ID | 场景与需求 | Given(前提) | When(动作) | Then(预期结果) |
| --- | --- | --- | --- | --- |
| US-1 | 首次检查,FR-1、FR-2、FR-7 | 本机有已注册仓库,功能尚未启用 | 用户进入 MCP 设置并测试默认范围 | 开关保持关闭;返回注册仓库列表和工具数;未保存配置,也未改动笔记 |
| US-2 | 指定草稿范围,FR-2、FR-6、FR-7 | 用户选择一个未注册文件夹,尚未保存 | 用户测试该草稿 | 仅探测此文件夹;已保存范围及启用状态不变 |
| US-3 | 保存与恢复默认,FR-1、FR-2 | 用户已测试指定文件夹 | 保存后重新进入设置,再切换为全部仓库并保存 | 保存结果可重新加载;恢复默认时保存空 `vaultPath`;两次保存均不自动启用 |
| US-4 | 单仓库日常操作,FR-4、FR-9 | 功能已启用并分配给当前会话,范围只有一个仓库 | 助手在允许写入的既有工作模式下读取、修改并再次读取笔记 | 写回当前唯一仓库,结果可从文件核验;无需额外 Obsidian 审批 |
| US-5 | 多仓库同名文件,FR-3 | 有两个仓库,都包含 `daily.md` | 助手先取得仓库 ID,再指定其中一个修改;另行尝试省略 ID 或传入无效 ID | 明确 ID 的请求仅作用于对应仓库;另两次调用返回错误,两个仓库均不因失败请求改变 |
| US-6 | 离线与关闭 Obsidian,FR-5、FR-6 | 安装包已有依赖,Obsidian 未运行,系统未安装 Node,网络断开 | 使用内置服务测试、读取和写入测试仓库 | 本地操作通过现有托管 Node 完成,不请求 npm,也不要求插件;模型联网另行验证 |
| US-7 | 范围失败恢复,FR-8 | 注册文件缺失或含失效路径,或者草稿路径不可访问 | 用户测试后改为有效文件夹并重试 | 显示具体原因,保留输入;重试使用新草稿,不偷偷跳过坏仓库或切换目标 |
| US-8 | 取消与失败保留,FR-8 | 用户正在编辑草稿或执行会话工具 | 取消文件夹选择、遇到保存失败,或取消会话工具 | 选择取消和保存失败均保留草稿;工具取消关闭对应进程,不报告成功,也不宣称回滚已发生的写入 |
| US-9 | 既有权限规则,FR-4、FR-9 | 功能启用且已分配,用户分别使用 Ask 和 Execute | 助手请求写入 | Ask 按应用现有规则保持只读;Execute 沿用正常执行规则,不增加 Obsidian 专用开关、过滤或审批 |
| US-10 | 旧配置与分配,FR-1、FR-9 | 旧配置没有 Obsidian 字段,已有其他 MCP 配置 | 加载设置并修改 Obsidian 分配 | Obsidian 默认关闭,其他配置保留;修改分配不改写仓库范围;未分配的会话不可调用 |
| US-11 | 上游能力完整,FR-4 | 在允许完整调用的工作模式下启用接入 | 获取工具集合并对照 MCPVault 0.16.0 | 18 个上游工具均有可调用映射,参数能力保留;仓库发现辅助工具不计入这 18 个 |
| US-12 | 设置变更与旧调用,FR-10 | 有含 Obsidian 的旧请求授权和正在执行的工具 | 提交保存范围、切换开关或修改分配,随后关闭网关 | 旧授权失效,在途调用收到取消;网关等待工具关闭。保存失败也不恢复旧授权;后续请求按实际保存状态重新取得授权 |
跨平台注册路径和安装包测试分别取证。通过模拟平台字符串测试注册路径,不等于在对应操作系统上完成安装包验收。