Merge remote-tracking branch 'origin/main' into feat/knowledge-retrieval-core

# Conflicts:
#	README.md
#	backend/app/routes.py
#	docs/architecture/AI笔记软件技术栈说明-团队版-v2.3.md
#	docs/development/Knowledge与Retrieval-Core开发说明.md
This commit is contained in:
yxx
2026-09-03 23:45:52 +08:00
76 changed files with 8530 additions and 245 deletions
+3
View File
@@ -33,6 +33,8 @@
- [Benchmark 开发说明](development/Benchmark开发说明.md)
- [模型提供商与模型发现开发说明](development/模型提供商与模型发现开发说明.md)
- [MCP Bridge 与 Plugin Host 开发说明](development/MCP-Bridge与Plugin-Host开发说明.md)
- [独立 MCP Server 配置中心开发说明](development/独立MCP-Server配置中心开发说明.md)
- [Plugin Command 与 Settings 开发说明](development/Plugin-Command与Settings开发说明.md)
- [前端壳子与接口层开发说明](development/前端壳子与接口层开发说明.md)
- [前端写作体验优化开发说明](development/前端写作体验优化开发说明.md)
- [前端视觉与轻量动效优化开发说明](development/前端视觉与轻量动效优化开发说明.md)
@@ -49,6 +51,7 @@
- [后端全面审阅问题与修复复盘](retrospectives/后端全面审阅问题与修复复盘.md)
- [Agent Core 第二阶段问题与修复复盘](retrospectives/Agent-Core第二阶段问题与修复复盘.md)
- [Knowledge 与 Retrieval Core 问题与修复复盘](retrospectives/Knowledge与Retrieval-Core问题与修复复盘.md)
- [Plugin Command 与 Settings 问题与修复复盘](retrospectives/Plugin-Command与Settings问题与修复复盘.md)
- [前端合并审阅问题与修复复盘](retrospectives/前端合并审阅问题与修复复盘.md)
## 推荐阅读顺序
@@ -5,7 +5,7 @@
> 适用范围:桌面客户端、本地知识库、RAG、Agent、Skill、多模型接入、多模态处理与可选云同步
> 目标读者:前端、Rust 桌面端、Python AI Core、算法、测试与后续接手项目的开发成员
> 实施状态更新:2026-09-03。本文同时包含目标架构、当前实现和第二阶段接口基线。第一阶段已完成 Vue Web 联调前端、FastAPI、Knowledge/Retrieval、Agent/Tool/Permission、Skill/Plugin 声明式运行时、Mock/OpenAI-Compatible/Ollama Provider、DeepSeek/OpenAI 预设、模型发现及开发阶段 Fernet 凭据存储。Web Workspace 已通过 FastAPI 接入后端配置的真实单 Vault;第二阶段 Agent Trace 持久化、分页快照、可恢复 SSE、stdio MCP Bridge隔离 Plugin Host 已完成;RAG Benchmark 检索评测(Dataset 加载、异步运行、SSE 进度、指标聚合与报告)已完成,Agent Benchmark 暂缓。后续继续接入真实音频处理、Plugin Command/Settings、Provider 协议增强、文档导出、主题包、Trace 可视化、Mermaid 和函数图像。Tauri/Rust Host、Stronghold、原生多 Vault 文件系统和 Sync Server 仍未实现。
> 实施状态更新:2026-09-03。本文同时包含目标架构、当前实现和第二阶段接口基线。第一阶段已完成 Vue Web 联调前端、FastAPI、Knowledge/Retrieval、Agent/Tool/Permission、Skill/Plugin 声明式运行时、Mock/OpenAI-Compatible/Ollama Provider、DeepSeek/OpenAI 预设、模型发现及开发阶段 Fernet 凭据存储。Web Workspace 已通过 FastAPI 接入后端配置的真实单 Vault;第二阶段 Agent Trace 持久化、分页快照、可恢复 SSE、stdio MCP Bridge隔离 Plugin Host、Plugin Command 与 Plugin Settings/Secret Contract 已完成;RAG Benchmark 检索评测(Dataset 加载、异步运行、SSE 进度、指标聚合与报告)已完成,Agent Benchmark 暂缓。后续继续接入真实音频处理、Provider 协议增强、文档导出、主题包、Trace 可视化、Mermaid 和函数图像。Tauri/Rust Host、Stronghold、原生多 Vault 文件系统和 Sync Server 仍未实现。
---
@@ -1050,7 +1050,7 @@ Python 包形式的 MCP Server 推荐使用固定版本的 `uvx --isolated --fro
MCP Bridge 用于接入具有 MCP Server 接口的插件或外部工具服务。
当前已实现本地 stdio 首版:Plugin Runtime 在授权后的启用阶段启动独立 Server 进程,完成 `initialize`、capability negotiation、分页 `tools/list`、`tools/call`、取消、超时、异常退出和 Host Restart。实现接受 `2025-11-25`、`2025-06-18`、`2025-03-26` 与 `2024-11-05` 协议版本;Streamable HTTP、Resource、Prompt、Sampling 与操作系统级沙箱仍属于后续范围。
当前 Plugin Runtime 已实现本地 stdio Host在授权后的启用阶段启动独立 Server 进程,完成 `initialize`、capability negotiation、分页 `tools/list`、`tools/call`、取消、超时、异常退出和 Host Restart。独立 MCP Server Registry 另行支持 stdio、Streamable HTTP 与旧 HTTP+SSE 兼容,包括 Session、协议 Header、认证 Header Secret、测试门禁和 Tool 动态映射。实现接受 `2025-11-25`、`2025-06-18`、`2025-03-26` 与 `2024-11-05` 协议版本;Plugin Manifest 的 Streamable HTTP、Resource、Prompt、Sampling 与操作系统级沙箱仍属于后续范围。
MCP Tool 进入系统后的调用路径为:
@@ -2333,7 +2333,7 @@ Markdown Workspace
第一阶段 Plugin Runtime 已完成安装、启用、停用、权限和声明式 Tool 注册,建立 Skill 调用 Plugin Tool 的基础链路。Command、Settings 和 MCP 执行不计入第一阶段完成项。
截至 2026-09-01,上述第一阶段后端链路和 Web 联调前端均已完成;第二阶段前置的 Workspace 去 Mock 联调、Agent Trace 持久化/恢复接口以及 stdio MCP Bridge / Plugin Host 也已完成。当前验证基线为后端 92 项测试、前端 27 项测试及生产构建通过。向量链路当前使用 `HashEmbeddingProvider` 验证工程正确性,真实 Embedding 召回质量不属于该测试结论。
截至 2026-09-03,上述第一阶段后端链路和 Web 联调前端均已完成;第二阶段的 Workspace 去 Mock 联调、Agent Trace 持久化/恢复接口stdio MCP Bridge / Plugin Host,以及 Plugin Command/Settings 前后端闭环也已完成。Plugin 详情页现已提供 Host 状态、重启、动态设置、Secret 管理和命令执行,全局命令面板可加载 Plugin Command。当前验证基线为后端 136 项测试、前端 32 项测试、TypeScript 类型检查及生产构建通过。向量链路当前使用 `HashEmbeddingProvider` 验证工程正确性,真实 Embedding 召回质量不属于该测试结论。
第二阶段在既有 Contract 上接入:
@@ -2365,7 +2365,7 @@ Frontend Extension
└── Plugin Settings UI
```
上述列表描述第二阶段技术范围,其中 stdio MCP Bridge 已实现,其余能力以各自开发说明的状态为准。每项功能必须继续经过现有 Service、Contract、Permission 和 Adapter 边界,不因 Demo 需要在 Vue 组件、Router 或 Agent Runtime 中直接绑定第三方协议。
上述列表描述第二阶段技术范围,其中 stdio MCP Bridge、Plugin Command Contribution 和 Plugin Settings Contribution 后端 Contract 已实现,其余能力以各自开发说明的状态为准。每项功能必须继续经过现有 Service、Contract、Permission 和 Adapter 边界,不因 Demo 需要在 Vue 组件、Router 或 Agent Runtime 中直接绑定第三方协议。
第三阶段处理:
@@ -2419,9 +2419,9 @@ Sync Server 按独立服务开发和部署,不进入桌面客户端核心启
目标桌面端采用 Tauri 2、Rust、Vue 3 和 TypeScript;当前可运行形态是 Vue/Vite Web 前端加 FastAPI。用户笔记以 Markdown 和 Assets 保存在本地 Vault,SQLite 已管理笔记元数据、全文索引、向量索引、任务及 Agent TraceProvider/Extension Registry 当前仍为内存实现。
Python AI Core 未来作为 Tauri Sidecar 运行,当前由开发命令独立启动,FastAPI 提供本地接口。Knowledge Core 管理笔记结构;Retrieval Core 当前通过 FTS5、`HashEmbeddingProvider`、sqlite-vec、RRF 和轻量 Reranker 跑通混合检索,真实 Embedding 与正式 Benchmark 第二阶段接入;Agent Runtime 使用 Tool Registry 操作知识库和任务,并将扩展 Agent Trace Contract 供可视化和 Benchmark 共用Skill Runtime 将提示词、工具、权限和检索参数组装为可复用 Agent 配置。
Python AI Core 未来作为 Tauri Sidecar 运行,当前由开发命令独立启动,FastAPI 提供本地接口。Knowledge Core 管理笔记结构;Retrieval Core 当前通过 FTS5、`HashEmbeddingProvider`、sqlite-vec、RRF 和轻量 Reranker 跑通混合检索,真实 Embedding 与正式 Benchmark 仍待第二阶段后续接入;Agent Runtime 使用 Tool Registry 操作知识库和任务,并已持久化可供前端可视化与 Benchmark 共用的 Agent Trace ContractSkill Runtime 将提示词、工具、权限和检索参数组装为可复用 Agent 配置。
当前 Plugin Runtime 支持 Manifest、生命周期声明式白名单 Tool Contribution,并已通过 stdio MCP Bridge 接入独立进程 Tool、Host 状态与重启接口;Command 与 Settings Contribution 尚待后续阶段实现。Provider Adapter 当前实现 Mock、OpenAI Chat/OpenAI-Compatible 与 Ollama第二阶段按统一行为测试完善 OpenAI Responses、Anthropic Messages 等协议。多模态目标方案使用 faster-whisper、pyannote.audio 和可选 emotion2vec;当前只读取 Host 预生成 transcript。
当前 Plugin Runtime 支持 Manifest、生命周期声明式白名单 Tool Contribution、Plugin Command 与 Plugin Settings/Secret,并已通过 stdio MCP Bridge 接入独立进程 Tool、专用 MCP Command Target、Host 状态与重启接口。Provider Adapter 当前实现 Mock、OpenAI Chat/OpenAI-Compatible 与 OllamaOpenAI Responses、Anthropic Messages 等协议仍待第二阶段后续完善。多模态目标方案使用 faster-whisper、pyannote.audio 和可选 emotion2vec;当前只读取 Host 预生成 transcript。
第二阶段内容输出以 Document AST、Exporter Adapter、Mermaid Renderer 和 Function Plot Renderer 为共同边界,支持 HTML、PDF、DOCX 与静态图导出。Theme Package 使用 Manifest、Design Token 和受限 CSS 实现本地导入;联网主题市场不属于本阶段核心依赖。API Key 在 Web 联调期由 Fernet 开发存储加密保存,桌面版迁移到 Tauri Stronghold。多设备同步的目标方案为独立、可自托管的 Sync Server,目前尚未实现;本地核心功能不依赖 Sync Server。
+1 -1
View File
@@ -1,6 +1,6 @@
# 第一阶段分工表
> 状态更新:2026-08-30。本文保留原始职责划分,同时记录当前交付状态。第一阶段后端目标已完成,前端 Web 联调页面已完成;尚未纳入本阶段完成项的是 Tauri Host、Stronghold、真实桌面文件系统、独立 MCP Plugin Host、真实音频模型和 Sync Server。
> 状态更新:2026-09-02。本文保留第一阶段原始职责划分和交付口径。第一阶段后端目标前端 Web 联调页面已完成;第二阶段此后又完成 Agent Trace 持久化与 SSE 恢复、独立 stdio MCP Plugin Host 和 Plugin Command/Settings 后端 Contract。Tauri Host、Stronghold、原生多 Vault 文件系统、真实音频模型和 Sync Server 仍未实现
## 当前交付状态
@@ -6,9 +6,9 @@
第二阶段继续保持第一阶段的模块 ownership:
- 范涵宇:Agent Core、Extension Core、Model Core、Multimodal、整体架构与代码审阅。
- 范涵宇:Agent Core、Extension Core、Model Core、Multimodal、Plugin Command / Settings UI、整体架构与代码审阅。
- 杨星萱:Knowledge Core、Retrieval Core、Benchmark、文档导出、函数图像绘制。
- 吉海燕:Frontend、Theme、Agent Trace、Plugin UI Contribution、Mermaid 渲染。
- 吉海燕:Frontend、Theme、Agent Trace、Mermaid 渲染。
---
@@ -16,9 +16,9 @@
| 成员 | 主要负责方向 | 第二阶段任务 | 配合事项 |
| --- | --- | --- | --- |
| 范涵宇 | Agent Core / Extension Core / Model Core / Multimodal / 总体架构 | faster-whisper、pyannote.audio、MCP Bridge、Plugin Command Contribution 后端、Plugin Settings Contribution 后端、更多 Provider、整体集成、代码审阅与统筹 | 与吉海燕联调 Plugin Command / Settings 前端;为杨星萱的 Agent Benchmark 提供 Agent Trace、Tool Call 等测试接口 |
| 范涵宇 | Agent Core / Extension Core / Model Core / Multimodal / 总体架构 | faster-whisper、pyannote.audio、MCP Bridge、Plugin Command / Settings 前后端闭环、更多 Provider、整体集成、代码审阅与统筹 | 为杨星萱的 Agent Benchmark 提供 Agent Trace、Tool Call 等测试接口 |
| 杨星萱 | Knowledge Core / Retrieval Core / Benchmark / Export / 数学内容渲染 | RAG Benchmark、Agent Benchmark 基础设施、Markdown → HTML / PDF / DOCX、函数图像绘制与渲染支持、Retrieval 调优 | 与范涵宇确认 Agent Benchmark 事件和测试数据结构;与吉海燕联调函数图像在编辑器和预览区中的展示 |
| 吉海燕 | Frontend / Theme / Visualization | Theme Import、Theme Manifest、社区主题格式、Agent Trace 可视化、Plugin Command / Settings 前端、Mermaid 渲染支持 | 与范涵宇联调 Plugin Contribution Contract 与 AgentEvent;与杨星萱联调函数图像及导出预览 |
| 吉海燕 | Frontend / Theme / Visualization | Theme Import、Theme Manifest、社区主题格式、Agent Trace 可视化、Mermaid 渲染支持 | 与范涵宇联调 AgentEvent;与杨星萱联调函数图像及导出预览 |
---
@@ -384,7 +384,7 @@ Metadata Filter
---
## 五、吉海燕
## 五、前端展示层(吉海燕;5.4—5.5 由范涵宇负责)
### 5.1 Theme Import
@@ -488,7 +488,7 @@ Error
- 用户取消;
- Citation 跳转。
### 5.4 Plugin Command 前端
### 5.4 Plugin Command 前端(范涵宇)
负责 Command Contribution 在前端呈现。
@@ -502,7 +502,7 @@ Toolbar Action
前端使用 Plugin Contribution Contract,不直接解析插件后端 Manifest。
### 5.5 Plugin Settings 前端
### 5.5 Plugin Settings 前端(范涵宇)
根据范涵宇提供的 Plugin Settings Schema 动态生成设置表单。
@@ -572,9 +572,9 @@ Mermaid 渲染需要与 Theme Design Token 联动。
| --- | --- | --- |
| MCP Bridge → Agent Tool | 范涵宇 | 杨星萱 |
| Plugin Command Runtime | 范涵宇 | 吉海燕 |
| Plugin Command UI | 吉海燕 | 范涵宇 |
| Plugin Command UI | 范涵宇 | 吉海燕 |
| Plugin Settings Runtime | 范涵宇 | 吉海燕 |
| Plugin Settings UI | 吉海燕 | 范涵宇 |
| Plugin Settings UI | 范涵宇 | 吉海燕 |
| Agent Benchmark Framework | 杨星萱 | 范涵宇 |
| Agent Trace Event Contract | 范涵宇 | 吉海燕、杨星萱 |
| Agent Trace Visualization | 吉海燕 | 范涵宇 |
@@ -748,8 +748,10 @@ Markdown
- [ ] 两者能组合生成带时间戳和 Speaker 的 Transcript
- [x] MCP Server 能通过 MCP Bridge 注册 Tool
- [x] Agent 能调用 MCP Tool
- [ ] Plugin Command Contribution 后端可注册;
- [ ] Plugin Settings Contribution 后端可解析;
- [x] Plugin Command Contribution 后端可注册;
- [x] Plugin Settings Contribution 后端可解析;
- [x] Plugin Command 可以显示并从前端执行;
- [x] Plugin Settings 可以动态生成设置项并独立提交 Secret;
- [ ] Provider Adapter 的 Streaming / Tool Calling / Error Mapping 稳定;
- [ ] 完成跨模块接口审阅和第二阶段集成。
@@ -773,8 +775,6 @@ Markdown
- [ ] Theme 可以启用、停用和卸载;
- [ ] Agent Trace 可以展示完整 Tool Call 顺序;
- [ ] Trace Node 可以查看参数、结果、耗时和错误;
- [ ] Plugin Command 可以显示在前端;
- [ ] Plugin Settings 可以动态生成设置项;
- [ ] Markdown Mermaid Code Block 可以渲染;
- [ ] Mermaid 支持主题切换;
- [ ] Mermaid 渲染错误可以明确展示;
@@ -791,6 +791,8 @@ Markdown
├── MCP Bridge
├── Plugin Command Runtime
├── Plugin Settings Runtime
├── Plugin Command UI
├── Plugin Settings UI
├── Provider Adapter
├── Code Review
└── Integration / Coordination
@@ -812,8 +814,6 @@ Markdown
├── Theme Import
├── Theme Community Format
├── Agent Trace Visualization
├── Plugin Command UI
├── Plugin Settings UI
└── Mermaid
├── Markdown Rendering
├── Theme Adaptation
@@ -1,6 +1,6 @@
# 后端接口契约(开发版)
> 更新日期:2026-09-01。本文档记录当前前后端联调使用的已实现接口;机器可读字段、校验规则和响应模型以 FastAPI 运行时生成的 OpenAPI 为准。第二阶段尚未实现的规划接口见 `第二阶段接口契约-开发版.md`,不要将规划路径视为当前服务能力。
> 更新日期:2026-09-02。本文档记录当前前后端联调使用的已实现接口;机器可读字段、校验规则和响应模型以 FastAPI 运行时生成的 OpenAPI 为准。第二阶段尚未实现的规划接口见 `第二阶段接口契约-开发版.md`,不要将规划路径视为当前服务能力。
## 契约入口
@@ -176,7 +176,7 @@ RunCancelled
## 当前实现状态
更新至 2026-09-01:后端 92 项回归测试通过。
更新至 2026-09-02:后端 136 项回归测试通过;第二阶段 Plugin Command 与 Plugin Settings/Secret 接口已实现,详细 DTO 和边界见《第二阶段接口契约-开发版》第 7 节
- Chat、Agent Run、Agent Events、Tool 列表、Provider 配置生命周期、模型列表和连接测试已经接入 AI Core。
- Agent Run/Event 已持久化到 SQLiteSSE 帧携带 sequence `id`,断线后可以回放缺失事件。Trace API 与 Benchmark 共用同一事件事实,并在入库前执行 Secret 脱敏和结果限长。
@@ -2,7 +2,7 @@
> 文档状态:接口冻结草案
>
> 更新日期:2026-09-01
> 更新日期:2026-09-03
>
> 依据:`../architecture/第二阶段团队分工表.md``../architecture/AI笔记软件技术栈说明-团队版-v2.3.md``后端接口契约-开发版.md`
@@ -27,7 +27,7 @@
| 标记 | 含义 |
| --- | --- |
| 已实现 | 第一阶段接口已经存在,第二阶段保持兼容 |
| 已实现 | 接口已经落地并由当前 OpenAPI 与自动化测试覆盖 |
| 扩展 | 路径已存在,第二阶段增加字段、事件或行为 |
| 计划新增 | 第二阶段需要新增实现 |
| 内部 Contract | 不直接暴露 HTTP,由两个模块共同遵守 |
@@ -47,11 +47,18 @@
| Agent Trace | GET | `/api/agent/runs/{run_id}/trace` | 已实现 | 分页读取可回放 Trace 快照 |
| Plugin Host | GET | `/api/plugins/{plugin_id}/host` | 已实现 | 获取 MCP Host 健康状态 |
| Plugin Host | POST | `/api/plugins/{plugin_id}/host/restart` | 已实现 | 重启异常 Host 并重新发现 Tool |
| Plugin Command | GET | `/api/plugin-contributions/commands` | 计划新增 | 获取前端可展示的 Command |
| Plugin Command | POST | `/api/plugin-contributions/commands/{command_id}/execute` | 计划新增 | 受控执行 Command |
| Plugin Settings | GET | `/api/plugins/{plugin_id}/settings` | 计划新增 | 获取 Schema 与非敏感配置 |
| Plugin Settings | PUT | `/api/plugins/{plugin_id}/settings` | 计划新增 | 更新非敏感配置 |
| Plugin Settings | PUT/DELETE | `/api/plugins/{plugin_id}/settings/{key}/secret` | 计划新增 | 写入或删除 Secret Reference |
| MCP Server | GET/POST | `/api/mcp/servers` | 已实现(C.1) | 列出、创建独立 MCP Server 配置 |
| MCP Server | GET/PUT/DELETE | `/api/mcp/servers/{server_id}` | 已实现(C.1) | 读取、版本化修改、删除独立配置 |
| MCP Server | GET | `/api/mcp/servers/{server_id}/tools` | 已实现(C.1) | 获取映射后的 Tool 摘要 |
| MCP Server | POST | `/api/mcp/servers/{server_id}/trust` | 已实现(C.1) | 确认当前连接配置摘要 |
| MCP Server | POST | `/api/mcp/servers/{server_id}/test` | 已实现(C.1) | 临时连接、握手、发现工具后关闭 |
| MCP Server | POST | `/api/mcp/servers/{server_id}/enable``disable` | 已实现(C.1) | 控制连接与动态 Tool 生命周期 |
| MCP Server | PUT/DELETE | `/api/mcp/servers/{server_id}/secrets/{key}` | 已实现(C.1 | 按 `kind` 写入或删除加密环境变量/Header |
| Plugin Command | GET | `/api/plugin-contributions/commands` | 已实现 | 获取前端可展示的 Command |
| Plugin Command | POST | `/api/plugin-contributions/commands/{command_id}/execute` | 已实现 | 受控执行 Command |
| Plugin Settings | GET | `/api/plugins/{plugin_id}/settings` | 已实现 | 获取 Schema 与非敏感配置 |
| Plugin Settings | PUT | `/api/plugins/{plugin_id}/settings` | 已实现 | 更新非敏感配置 |
| Plugin Settings | PUT/DELETE | `/api/plugins/{plugin_id}/settings/{key}/secret` | 已实现 | 写入或删除 Secret Reference |
| Provider | 现有路径 | `/api/providers/*``POST /api/chat` | 扩展 | 补齐协议能力和统一行为 |
| Retrieval | GET/POST | `/api/index/status``/api/index/rebuild` | 扩展 | 暴露 Embedding 兼容状态并安全重建向量 |
| Benchmark | GET | `/api/benchmarks/datasets` | 计划新增 | 枚举受控 Dataset |
@@ -540,7 +547,13 @@ error
}
```
允许的 effect 首批为 `none``notification``navigate``refresh``job`前端仅执行白名单 effect;未知类型显示结果但不执行。
允许的 effect 首批为 `none``notification``navigate``refresh``job`每类 payload 也是契约的一部分:`none` 必须为空;`notification` 只接受 `level``info/success/warning/error`)和非空 `message``navigate` 只接受宿主路由名 `route``refresh` 只接受 `workspace/commands/settings/plugins` 范围;`job` 只接受受限格式的 `job_id`。后端拒绝未知字段和不匹配的 payload,前端仍须按判别联合穷尽处理,不得把 effect 当作任意代码执行。
当前宿主只注册已启用且已满足权限授权的 Plugin Command。执行前按 JSON Schema 校验参数、按 `when` 校验上下文,再根据 Command 声明裁剪 Context;单次执行默认超时 30 秒,effect 的 JSON 编码结果不得超过 64 KiB。宿主保留最多 500 条轻量审计事件,仅记录 Command、Plugin、状态、耗时和错误码,不记录 arguments、Context、effect 或 Secret。
需要 Secret 的 Command 必须在 `commands.yaml` 的内部 `secrets` 数组中声明对应 Setting Key,并在 Plugin Manifest 声明 `secrets.use` 权限。安装时宿主校验该字段确实属于当前 Plugin Settings Schema 的 `secret` 类型;只有权限已授予并启用后,运行时才向受控 handler 或 MCP Command Target 提供声明过的 Secret。未声明字段返回 `PLUGIN_SECRET_ACCESS_DENIED`,必填 Secret 未配置返回 `PLUGIN_SECRET_REQUIRED``secrets` 不属于前端 `PluginCommand` DTOSecret 明文也不会并入普通 Settings 字典。
`commands.yaml` 中的执行目标必须在宿主白名单 `handler` 与当前 Plugin 命名空间的 `mcp_tool` 之间二选一。MCP Command Target 不注册为 Agent Tool;宿主用 `_notesagent` 保留包装传入 Command ID、参数、裁剪后的 Context、已校验的非敏感 Settings 和声明过的 Secret,并将 MCP structured result 再校验为白名单 effect。作为宿主协议标记,目标 MCP Tool 的 `inputSchema` 必须在顶层 `properties` 中直接声明 `_notesagent: { type: object }`;不得用顶层组合或引用替代该标记。`_notesagent` 对象内部仍可使用完整 Draft 2020-12 约束、文档内引用和组合 Schema。启用阶段只检查协议标记,不尝试求解 Schema 或伪造业务值;宿主会保留完整 Schema,并在每次调用前用官方 Validator 校验真实信封。Command 与 Tool Schema 仅允许 `#...` 文档内引用,任何通过 `$ref``$dynamicRef` 指向文件、HTTP 或其他外部资源的 Schema 都会在注册前被拒绝。文档内引用遵循 Draft 2020-12 的嵌套 `$id` 与 Anchor 资源作用域,不能解析的引用不得进入运行时。
### 7.5 Settings Schema
@@ -585,6 +598,8 @@ select
secret
```
Number 字段的 `minimum``maximum` 必须是有限数值;`NaN`、正无穷和负无穷均视为无效 Settings Schema。
### 7.6 更新 Settings 和 Secret
`PUT /api/plugins/{plugin_id}/settings`
@@ -596,7 +611,7 @@ secret
}
```
该接口拒绝 secret 字段。Schema 版本过期返回 `PLUGIN_SETTINGS_VERSION_CONFLICT` 并附当前版本。
该接口拒绝 secret 字段。Schema 版本过期返回 `PLUGIN_SETTINGS_VERSION_CONFLICT` 并附当前版本。没有默认值的必填普通字段必须先通过该接口配置;否则 Plugin Enable 和 Command Execute 返回 `PLUGIN_SETTINGS_REQUIRED`MCP 或内部 handler 不会收到残缺配置。
Secret 使用:
@@ -623,6 +638,10 @@ DELETE /api/plugins/{plugin_id}/settings/{key}/secret
Secret 明文不进入普通 Settings、日志、Trace、Benchmark Dataset 或前端持久化。
非敏感值按 `plugin_id` 写入 `APP_DATA_DIR/plugins/settings.json`。该文件只保存普通值、Schema 版本和确定性的定长 Secret Reference,格式为 `plugin.<sha256(plugin_id\\0setting_key)>`;Secret 本身由宿主凭据存储加密保存。卸载 Plugin 时同时清理它的 Settings 命名空间和 Secret Reference。当前开发阶段使用 Fernet 文件凭据存储,第三阶段接入桌面 Host 后应迁移到 Stronghold 或系统 Keychain。
`plugin.*` 为宿主保留凭据命名空间。`/api/credentials/{credential_id}`、Provider 持久配置、Provider 临时测试凭据和 Provider Resolver 均拒绝该前缀,防止通过 Provider 链路覆盖、删除或向外部 Base URL 发送 Plugin Secret。
### 7.7 Plugin/MCP 错误码
```text
@@ -635,13 +654,66 @@ MCP_TOOL_SCHEMA_INVALID
MCP_TOOL_CALL_FAILED
MCP_TOOL_RESULT_TOO_LARGE
MCP_TRUST_APPROVAL_REQUIRED
MCP_TRUST_DIGEST_STALE
MCP_SANDBOX_REQUIRED
MCP_TRANSPORT_UNSUPPORTED
MCP_SERVER_NOT_FOUND
MCP_SERVER_NAME_INVALID
MCP_SERVER_ALREADY_ENABLED
MCP_SERVER_VERSION_CONFLICT
MCP_SERVER_LIMIT_REACHED
MCP_REGISTRY_WRITE_FAILED
MCP_REGISTRY_INVALID
MCP_CONNECTION_TEST_REQUIRED
MCP_CONFIG_INVALID
MCP_COMMAND_INVALID
MCP_URL_INVALID
MCP_HEADER_INVALID
MCP_HTTP_REQUEST_FAILED
MCP_HTTP_RESPONSE_INVALID
MCP_SECRET_REQUIRED
MCP_SECRET_NOT_DECLARED
MCP_SECRET_KIND_INVALID
MCP_SECRET_STORE_ERROR
MCP_ENVIRONMENT_INVALID
MCP_PERMISSION_INVALID
PLUGIN_COMMAND_NOT_FOUND
PLUGIN_COMMAND_CONFLICT
PLUGIN_COMMAND_INVALID
PLUGIN_COMMAND_ARGUMENT_INVALID
PLUGIN_COMMAND_CONTEXT_INVALID
PLUGIN_COMMAND_TIMEOUT
PLUGIN_COMMAND_EXECUTION_FAILED
PLUGIN_COMMAND_RESULT_INVALID
PLUGIN_COMMAND_RESULT_TOO_LARGE
PLUGIN_COMMAND_TARGET_SCHEMA_MISMATCH
PLUGIN_SETTINGS_SCHEMA_INVALID
PLUGIN_SETTINGS_VERSION_CONFLICT
PLUGIN_SETTINGS_FIELD_INVALID
PLUGIN_SETTINGS_REQUIRED
PLUGIN_SECRET_FIELD_NOT_FOUND
PLUGIN_SECRET_ACCESS_DENIED
PLUGIN_SECRET_REQUIRED
PLUGIN_SECRET_VALUE_INVALID
PLUGIN_SECRET_STORE_ERROR
PLUGIN_STORAGE_ERROR
CREDENTIAL_NAMESPACE_RESERVED
```
### 7.8 独立 MCP Server RegistryC.1
独立 Server 不依附 Plugin Manifest,配置持久化于 `APP_DATA_DIR/mcp/servers.json``transport` 支持 `stdio``streamable_http` 和兼容旧服务的 `sse`。敏感环境变量与认证 Header 只以 `mcp.*` 引用进入加密凭据存储;读取响应以 `secret_environment``secret_headers` 的布尔值表示配置状态,不返回明文。动态工具使用 `mcp.{server_id}.{remote_tool}` 命名空间,来源标记为 `mcp_server`,仍通过统一 Tool Registry、Permission Manager 与 Agent Trace。
stdio 配置使用 `command``args``environment``secret_environment_keys`HTTP/SSE 配置使用 `url``headers``secret_header_keys`,两组 Transport 字段不可混用。更新请求必须携带当前 `version`,成功后版本递增;过期版本返回 `409 MCP_SERVER_VERSION_CONFLICT``GET /tools` 返回 `name``remote_name``description` 和可选 `permission`
创建或编辑配置后,调用方必须向 `/trust` 回传服务端计算的 `command_digest`。后端只接受与当前 Transport、连接参数、环境/Header 及权限完全一致的摘要;配置变化会撤销旧信任和测试结果。只有当前摘要通过 `/test`,才能调用 `/enable`。测试失败也会持久化时间和失败状态。
Secret 明文变化无法进入摘要,因此 Secret 写入和删除采用更严格规则:若 Server 已启用则先停用并注销 Tool,随后清除 `tested_digest` 和最近测试状态。调用方必须用新 Secret 再次执行 `/test`,不能沿用旧凭据的测试结果。
Streamable HTTP 支持 Session ID、`MCP-Protocol-Version`、JSON 或 SSE POST 响应、可选 GET 事件流及 `Last-Event-ID`;旧 SSE 按 endpoint 事件确定 POST 地址,并要求与初始 URL 同源。Secret 接口用 `?kind=environment``?kind=header` 区分类型。HTTP URL 不允许内嵌凭据或 Fragment,配置不得覆盖协议保留 Header。
stdio 命令始终以 executable 与 args 数组通过 `shell=False` 启动;普通环境变量和加密 Secret 显式注入,不继承 Provider Key、数据库或 Vault 路径。当前 Python Host 仅在 `APP_ENVIRONMENT=development` 时允许启动 stdio;其他环境返回 `403 MCP_SANDBOX_REQUIRED`。远程 HTTP Transport 不创建本机进程,但仍受摘要确认、成功测试、超时、消息限长与 Secret 隔离约束。
---
## 8. Provider Adapter 扩展
@@ -2,7 +2,7 @@
> 本文档用于团队开发和模块联调,记录当前已经落地的核心边界与使用方式。
> 更新日期:2026-09-01。第一阶段 AI Core、Agent Core、Extension Core 和 Model Core 主链路已经完成;第二阶段 Agent Trace 持久化、可恢复 SSE、stdio MCP Bridge隔离 Plugin Host 已落地,后端当前回归基线为 92 项测试通过。
> 更新日期:2026-09-02。第一阶段 AI Core、Agent Core、Extension Core 和 Model Core 主链路已经完成;第二阶段 Agent Trace 持久化、可恢复 SSE、stdio MCP Bridge隔离 Plugin Host 以及 Plugin Command/Settings 已落地,后端当前回归基线为 136 项测试通过。
## 当前实现
@@ -68,7 +68,7 @@ Router 只负责 HTTP/SSE 与错误转换,不实现 Agent、Tool 或 Provider
- Note、NoteBlock、Markdown Parser:由 Knowledge Core 提供;
- FTS5、Vector、RRF、Reranker、Citation:由 Retrieval Core 提供;
- 文件系统和 API Key 明文读取:由 Rust Host 提供;
- Frontend Extension Slot 与 Plugin Command/Settings:按第二阶段后续阶段实现
- Frontend Extension Slot 与 Plugin Command/Settings UI:后端 Contract 与前端 Service 已完成,页面由前端后续联调
## Provider
@@ -347,6 +347,6 @@ Skill Manifest
- Run/Trace 已通过 Repository 接入 SQLite;后续增加按保留策略归档和 Benchmark 引用保护。
- Permission 已有核心等待/恢复机制,前端确认 UI 已完成联调和中文展示。
- Task 已持久化到 SQLiteAttachment Tool 读取 Host 管理目录中的 UTF-8 文件。
- `audio.transcribe` 当前消费 Host 预生成的 transcriptfaster-whisper 与说话人分离仍按技术基线在第二阶段接入。
- `audio.transcribe` 当前消费 Host 预生成的 transcriptfaster-whisper 与说话人分离仍第二阶段后续接入。
- Extension 安装记录暂存内存;后续接入持久化 Registry 与版本升级流程。
- 当前 Plugin Host 支持内置声明式 handler本地 stdio MCP ServerStreamable HTTP、OS 级沙箱、Plugin Command/Settings 与 UI Contribution 留在后续阶段。
- 当前 Plugin Host 支持内置声明式 handler本地 stdio MCP Server 以及 Plugin Command/Settings;独立 MCP Server Registry 另行支持 stdio、Streamable HTTP 与旧 SSE 兼容。OS 级沙箱与 UI Contribution 留在后续阶段。
@@ -3,7 +3,7 @@
> 本文档用于团队开发和模块联调,记录 Knowledge Core / Retrieval Core 已经落地的
> 模块边界、数据模型、接口与使用方式,对应分工表中的杨星萱。
> 更新日期:2026-09-01。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 92 项测试通过。
> 更新日期:2026-09-02。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 136 项测试通过。
## 当前实现
@@ -198,7 +198,7 @@ cd backend
uv run pytest -q
```
当前后端完整测试共 120 个用例通过(单元 + 端到端)。测试通过 `tests/conftest.py` 的 autouse fixture 把
当前后端完整测试共 218 个用例通过(单元 + 端到端)。测试通过 `tests/conftest.py` 的 autouse fixture 把
数据目录/DB/Vault 重定向到临时目录,不读写真实 `backend/data`,任何本机状态下结果确定。
## 配置
@@ -1,6 +1,6 @@
# MCP Bridge 与 Plugin Host 开发说明
> 更新日期:2026-09-01。本文记录第二阶段阶段 C 已实现的本地 stdio MCP Bridge、隔离 Plugin Host、Tool Contract 转换和离线测试方式。Plugin Command 与 Settings 属于阶段 D,不在本文实现范围内
> 更新日期:2026-09-02。本文记录第二阶段阶段 C 已实现的本地 stdio MCP Bridge、隔离 Plugin Host、Tool Contract 转换和离线测试方式。阶段 D 的 Plugin Command 与 Settings 已在其独立开发说明中落地
## 1. 目标与实现状态
@@ -235,6 +235,7 @@ backend/extensions/fixtures/mcp-echo
- `mcp-fixture.large`:验证结果大小上限;
- `mcp-fixture.environment`:验证宿主 Secret/路径没有进入子进程;
- `mcp-fixture.exit`:验证异常退出、Tool 注销和 Restart。
- `mcp-fixture.command`:作为 Plugin Command 专用 MCP Target,验证 Context 裁剪、Secret 传递和与 Agent Tool 的隔离。
Fixture 的 `tools/list` 使用两页响应,用于覆盖分页发现。测试还会启动缺少 tools capability、返回无效 Schema/initialize result,以及输出超长无换行 stdout 的变体。
@@ -279,4 +280,4 @@ pnpm build
- 一键安装前的完整命令展示与确认 UI;
- Tool 列表热更新的无中断替换。
阶段 D 在当前 Plugin Runtime 上继续增加 Command、Settings、Secret Contract 和命名空间 Storage修改 Agent 使用内部 Tool Contract 的原则。
阶段 D 在当前 Plugin Runtime 上增加 Command、Settings、Secret Contract 和命名空间 Storage且未修改 Agent 使用内部 Tool Contract 的原则。实现细节见《Plugin Command 与 Settings 开发说明》。
@@ -0,0 +1,117 @@
# Plugin Command 与 Settings 开发说明
> 更新日期:2026-09-03。本文记录第二阶段阶段 D 已实现的 Plugin Command Contribution、Plugin Settings Contribution、Secret 边界及前端闭环。当前回归基线为后端 136 项测试、前端 32 项测试,TypeScript 类型检查和生产构建通过。
## 1. 阶段目标
阶段 D 在阶段 C 的 Plugin Runtime 与隔离 MCP Host 上补齐两类宿主贡献:
- Command:插件声明命令,宿主负责注册、展示、校验、执行和返回白名单 effect;
- Settings:插件声明设置 Schema,宿主负责动态表单 Contract、非敏感值持久化和 Secret 加密引用;
- FrontendPlugin 详情页提供 Host 状态、动态设置、Secret 管理与命令执行,全局命令面板加载 command_palette Contribution。
第三方代码不会导入 FastAPI 进程。操作系统级安全沙箱仍按规划在第三阶段桌面基础集成完成后、Tauri/Rust 沙箱正式构建前处理。
## 2. 包内声明
Plugin 在 `plugin.yaml``contributes.commands``contributes.settings_sections` 声明贡献标识,并分别提供 `commands.yaml``settings.yaml`。安装时宿主要求声明集合与文件内容完全一致,拒绝重复项、越过 Plugin 命名空间的 ID、未声明权限和无效 Schema。
`commands.yaml` 的首批字段包括:
- `command_id`、标题、描述、宿主图标;
- `locations``command_palette``context_menu``toolbar`
- `when` 与允许传入执行器的 Context 字段;
- 参数 JSON Schema、可选权限、执行目标和超时;
- 执行目标必须在宿主白名单 `handler` 与当前插件命名空间的 `mcp_tool` 之间二选一。
- 可选 `secrets` 字段:只声明当前 Command 允许按需读取的 Secret Setting Key,不暴露给前端 DTO。
`settings.yaml` 采用递增 `schema_version`,首批字段类型固定为 `string``number``boolean``select``secret`。宿主会校验默认值、必填项、有限数值边界、Select 选项,以及 Secret 不得携带默认明文;`NaN` 与正负无穷不能用作上下界。
仓库内 `text-tools` 是联调 Fixture,覆盖 Command 和五种 Settings 字段类型。
## 3. Command 运行链路
`CommandRegistry` 只发布处于启用状态的 Plugin Command。Plugin 禁用、Host 不可用或重启时,Command 与 Tool 使用同样的注销/重新注册生命周期,避免前端看到实际不可执行的命令。
执行顺序如下:
1. 查找已注册 Command
2. 使用 Draft 2020-12 JSON Schema 校验 arguments
3. 根据 `when` 检查必要上下文;
4. 仅向执行器传递声明过的 Context 字段;
5. 在超时范围内调用宿主受控 handler,或调用独立的 MCP Command Target
6. 按 effect 类型校验专属 payload、可序列化性和 64 KiB 大小上限;
7. 返回统一 `PluginCommandResult`
首批 effect 为 `none``notification``navigate``refresh``job`。后端分别限制通知级别与消息、宿主路由名、刷新范围和 Job IDPydantic 与 TypeScript 均使用同一判别语义,前端不得把 effect 当作任意代码执行。
Command 执行器通过受控 Resolver 按需读取 `commands.yaml` 已声明且确实属于当前 Plugin Schema 的 Secret;使用 Secret 的 Plugin 还必须声明并获授 `secrets.use` 权限。读取未声明字段返回 `PLUGIN_SECRET_ACCESS_DENIED`,必填 Secret 未配置则返回 `PLUGIN_SECRET_REQUIRED`。Secret 不会并入普通 Settings 字典。Command 审计使用 500 条有界内存队列,仅保留 `command_id``plugin_id`、成功/失败状态、耗时、错误码和时间。arguments、正文选区、文件路径、effect 与 Secret 均不进入审计事件。
MCP Command Target 是专用执行目标,不注册进 Agent `ToolRegistry`,因此模型无法绕过 Command 权限与 Context 裁剪直接调用。宿主通过 `_notesagent` 保留包装传入 `command_id`、已校验 arguments、已裁剪 Context、已校验的非敏感 Settings 和声明过的 Secret。启用阶段只检查目标 `inputSchema` 在顶层 `properties` 中直接声明 `_notesagent: { type: object }`,不自行求解 JSON Schema,也不用空对象伪造业务数据;引用和组合约束可以放在 `_notesagent` 对象内部。执行阶段再用保留的完整 Schema 和官方 Draft 2020-12 Validator 校验真实信封。MCP Server 必须返回结构化的白名单 effect。远程原始错误不直接透传给 HTTP 调用方。插件仍不能把模块路径或 Shell 字符串作为执行器。
Command 与 Tool 的 JSON Schema 只允许当前文档内的 Fragment 引用(`#...`);宿主在注册前递归拒绝 `$ref` / `$dynamicRef` 指向的文件、HTTP 或其他外部资源,避免 Schema 校验触发未授权 I/O。文档内引用使用 Draft 2020-12 Resource Resolver 预检,嵌套 `$id` 创建的新资源及其 Anchor 按各自作用域解析,无法解析的引用在注册阶段返回稳定错误。
## 4. Settings 与 Secret 边界
普通 Settings 以 Plugin 为命名空间持久化到:
```text
APP_DATA_DIR/plugins/settings.json
```
该文件只包含:
- 当前 Schema 版本;
- 非敏感字段值;
- Secret 的确定性定长引用,格式为 `plugin.<sha256(plugin_id\\0setting_key)>`
Secret 写入必须调用专用端点。后端通过 `SecretStr` 接收明文,再交给现有 `EncryptedCredentialStore`;普通 Settings API 只返回 `{ configured: true|false }`,不会返回 Secret 值。`plugin.*` 是保留命名空间,通用凭据 API、Provider 配置、Provider 临时测试凭据和 Provider Resolver 均不得访问,防止覆盖、删除或外发 Plugin Secret。卸载 Plugin 时同时删除普通设置命名空间和对应加密凭据。
没有默认值的 `required` 普通字段必须在启用 Plugin 前配置。Enable 和每次 Command Execute 都会重新检查有效设置;缺失时返回 `PLUGIN_SETTINGS_REQUIRED`,不启动 MCP Host,也不调用 Command handler。
读取持久化引用时,宿主会重新计算并核对 `plugin.<sha256(...)>`,引用不匹配即按损坏存储拒绝处理,不能借由篡改 `settings.json` 读取或删除 Provider 等其他命名空间的凭据。删除单个 Secret 或卸载 Plugin 时先原子更新 Settings 引用,再删除加密凭据;底层删除失败会恢复原引用。多 Secret 卸载使用一次凭据表原子替换,避免分批删除部分删除。
开发阶段凭据文件由本机 Fernet Key 加密。桌面端落地后,应由 Tauri Host 将同一引用语义迁移到 Stronghold 或系统 KeychainHTTP Contract 无需因此改变。
## 5. HTTP 与前端 Service
后端已实现:
```text
GET /api/plugin-contributions/commands?location=command_palette
POST /api/plugin-contributions/commands/{command_id}/execute
GET /api/plugins/{plugin_id}/settings
PUT /api/plugins/{plugin_id}/settings
PUT /api/plugins/{plugin_id}/settings/{key}/secret
DELETE /api/plugins/{plugin_id}/settings/{key}/secret
```
前端 `pluginService` 提供对应方法及 Wire DTOPlugin 详情页据此展示 MCP Host 状态和重启入口、动态生成五类设置字段、独立写入或删除 Secret,并执行带参数的 Plugin Command。全局命令面板打开时获取 `command_palette` 命令;需要必填参数的命令会引导用户进入详情页填写。调用方必须使用服务层,不自行拼接路径。
Secret 明文仅存在于当前密码输入框绑定的组件内存,提交后立即清空;不得写入 Pinia、LocalStorage、普通 Settings 请求或调试日志。前端不会读取 Secret 明文,只展示后端返回的 `configured` 状态。
## 6. 主要错误边界
- Command 未注册、冲突、参数或 Context 无效;
- 执行超时、执行器异常、effect 无效或过大;
- Settings Schema 无效、版本冲突、字段类型/边界错误或运行时必填值缺失;
- Secret 字段不存在、空 Secret、凭据存储异常;
- Settings JSON 根结构或 Plugin 命名空间损坏。
以上错误统一转换为 `ExtensionError` 和稳定业务错误码,HTTP 层不暴露内部堆栈、Secret 或插件返回的原始异常。
## 7. 验证
```powershell
cd backend
uv run pytest
cd ../frontend
pnpm test -- --run
pnpm type-check
pnpm build
```
阶段 D 测试覆盖注册/注销生命周期、位置过滤、参数与 Context 校验、上下文裁剪、设置影响命令执行、声明式 Secret Resolver 与越权拒绝、真实 MCP Command Target 与 Agent Tool 隔离、必填 Secret 传递、外部 Schema 引用拒绝、定长 Secret Reference、篡改引用的跨命名空间阻断、Secret 删除与卸载失败回滚、Provider/通用凭据命名空间隔离、五类设置字段、Schema 版本冲突、Secret 密文与清理、损坏存储、空 Command 列表等无效贡献文件、OpenAPI 路径、前端 Service 请求格式、Host 状态展示和动态 Secret 表单。
生产构建仍会报告现有大 Chunk 警告,不影响构建成功;该问题属于前端按路由和 Markdown 依赖拆包的后续性能任务。
@@ -1,6 +1,6 @@
# 前端写作体验优化开发说明
> 更新日期:2026-08-30。本文所述优化均已进入当前分支;前端完整回归基线为 14 项测试通过,TypeScript 检查和 Vite 生产构建通过。
> 更新日期:2026-09-02。本文所述优化均已进入 `main`当前前端完整回归基线为 29 项测试通过,TypeScript 检查和 Vite 生产构建通过。
## 1. 本次目标
@@ -103,7 +103,7 @@ pnpm build
pnpm test
```
验证结果:TypeScript 类型检查与 Vite 生产构建均通过。当前前端完整回归测试共 14 项;其中写作与文件切换相关回归覆盖:
验证结果:TypeScript 类型检查与 Vite 生产构建均通过。当前前端完整回归测试共 29 项;其中写作与文件切换相关回归覆盖:
- 顶部工具栏对选区应用加粗;
- 浮动工具栏对选区应用斜体;
@@ -1,6 +1,6 @@
# 前端壳子与接口层开发说明
> 更新日期:2026-08-30
> 更新日期:2026-09-02
> 适用范围:Vue 3 + TypeScript 页面、Workspace、公共 Service、FastAPI 接口适配和 SSE。
> 文档用途:帮助团队理解当前前端可用能力、模块边界、启动方式和后续页面开发入口。
@@ -186,13 +186,13 @@ pnpm build
```text
pnpm build passed
pnpm test 27 passed
uv run pytest 92 passed
pnpm test 29 passed
uv run pytest 136 passed
preview smoke HTTP 200
git diff --check passed
```
当前前端使用 Vitest 执行 Store、Workspace API Adapter、SSE 恢复游标、文件树、编辑器组件、智能体标签、轻量动效约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 92 项测试结果,也不涉及产品代码。
当前前端使用 Vitest 执行 Store、Workspace API Adapter、SSE 恢复游标、Plugin Command/Settings Service、文件树、编辑器组件、智能体标签、轻量动效约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 136 项测试结果,也不涉及产品代码。
Vite 当前会提示 Chat 与 Workspace 的部分异步 Chunk 超过 500 kB,这是 Milkdown、CodeMirror、KaTeX 和 Shiki 等编辑/渲染依赖带来的性能优化项,不影响构建成功或功能正确性;进入桌面打包前应通过手动分包或更细粒度动态加载继续优化。
@@ -1,6 +1,6 @@
# 模型提供商与模型发现开发说明
> 更新日期:2026-08-30。OpenAI、DeepSeek、Ollama 预设、模型自动发现、默认模型选择和开发阶段加密凭据存储均已实现并接入设置页。
> 更新日期:2026-09-02。OpenAI、DeepSeek、Ollama 预设、模型自动发现、默认模型选择和开发阶段加密凭据存储均已实现并接入设置页。
## 1. 本次目标
@@ -104,4 +104,4 @@ pnpm build
自动化验证覆盖 Provider 预设、OpenAI-Compatible `/models` 请求与鉴权头、模型映射、前端自动刷新、排序去重及按 Provider 隔离错误。生产构建同时执行 Vue 和 TypeScript 类型检查。
当前完整回归基线:后端 92 项测试、前端 27 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。
当前完整回归基线:后端 136 项测试、前端 29 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。`plugin.*` 为 Plugin Secret 保留命名空间,Provider 配置、临时测试凭据和通用凭据 API 均拒绝该前缀。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。
@@ -0,0 +1,141 @@
# 独立 MCP Server 配置中心开发说明
> 更新日期:2026-09-03。本文记录第二阶段 C.1 的完整实现;独立 MCP Server Registry 与 Plugin 自带 MCP Host 是两个并列入口。
## 1. 已实现范围
- 独立 Server 的创建、读取、版本化编辑、删除和 Tool 摘要查询;
- `stdio`、Streamable HTTP 和旧版 HTTP+SSE 三种 Transport
- stdio 可执行文件、参数、普通/加密环境变量,以及 HTTP URL、普通/加密 Header
- 配置摘要确认、连接测试、启停、异常状态与最近一次测试结果;
- MCP initialize、`tools/list``tools/call`、取消与动态 Tool 注册,名称为 `mcp.{server_id}.{tool}`
- Streamable HTTP Session、协议版本 Header、JSON/SSE POST 响应、可选 GET 事件流和 `Last-Event-ID` 重连;
- 旧 HTTP+SSE 的 endpoint 事件与消息 POST,并强制消息地址和配置地址同源;
- 前端表单/JSON 双模式、三种模板、高风险变更确认及请求期 Secret 输入。
Streamable HTTP 按 MCP 当前规范实现;SSE 仅用于兼容旧 Server,不应作为新部署首选。
## 2. 配置、版本与 Secret
普通配置原子写入 `APP_DATA_DIR/mcp/servers.json`。更新请求必须携带读取到的 `version`;版本过期返回 `409 MCP_SERVER_VERSION_CONFLICT`,避免多个页面互相覆盖。改变 Transport、命令、URL、Header、环境变量或权限后,旧授权和测试结果立即失效。
`backend/data/mcp/` 是本机运行数据,包含连接配置、授权状态和第三方进程工作目录,不属于团队共享配置。`.gitignore` 忽略整个目录以及 `server.json``servers.json` 文件名;不得强制添加到 Git。提交前检查暂存文件清单,不要将本地密钥、连接配置或运行数据推送到远程。
Secret 使用带类型和键名哈希的 `mcp.*` 内部 ID 写入 Fernet 凭据存储。查询 API 只返回环境变量或 Header 是否配置,不返回明文。删除 Server 或移除 Secret 键会同步清理密文。前端密码框提交后立即清空;用户主动粘贴到 JSON 的密钥仅在当前编辑会话中暂存,解析后从 JSON 中移除,不写入 localStorage、普通配置或日志。
环境变量的凭据 ID 使用区分大小写的 v2 名称规则,Header ID 保持大小写不敏感。更新配置按凭据 ID 的差集删除密文,因此 `Authorization` 改为 `authorization` 不会丢失认证信息。启动时对无歧义的旧环境变量凭据原子迁移密文,不覆盖新 ID 已有的值;若旧配置把 `TOKEN``token` 合并存到了同一个 ID,无法推断原来的两个值,会保留旧密文、停用连接并要求重新录入和测试。删除服务器时也会清理这些保留的旧密文。
### 2.1 JSON 导入与 API Key 填写
前端支持 NotesAgent 完整/精简配置、单个 `command / args / env` 配置,以及只含一个服务器的 `mcpServers` 包装。JSON 与表单之间切换会补齐数组、对象及超时默认值,并校验字段类型。批量导入暂不支持;后端配置接口仍只接收 NotesAgent DTO,兼容转换发生在前端。
可以先声明 `secret_environment_keys`,保存后在服务器卡片的密码框填写密钥;也可以把密钥放进 JSON 的 `environment` 或通用配置的 `env`。前端会将已声明的敏感变量,以及名称含 API Key、Token、Secret、Password、Authorization、Cookie、Credential 的常见字段拆出:普通配置请求只包含键名,密钥另经 Secret API 加密保存。其他敏感字段必须显式声明,不能只依赖名称识别;命令与参数中不要携带密钥。
例如 MiniMax 的输入结构如下,占位值需在自己的本地页面替换,不要把真实密钥贴进聊天或提交到 Git:
```json
{
"name": "MiniMax Coding Plan",
"command": "uvx",
"args": ["--index-url", "https://pypi.tuna.tsinghua.edu.cn/simple", "--with", "mcp<2", "minimax-coding-plan-mcp", "-y"],
"environment": {
"MINIMAX_API_HOST": "https://api.minimaxi.com",
"MINIMAX_API_KEY": "<在本地填入新密钥>"
},
"secret_environment_keys": ["MINIMAX_API_KEY"],
"startup_timeout_seconds": 120,
"tool_timeout_seconds": 300
}
```
旧版前端将 `environment.MINIMAX_API_KEY``secret_environment_keys` 原样一起发送,触发后端“普通与敏感变量不可同名”的校验。这是配置保存失败,不是模型服务返回的鉴权失败。现在在前端拆分两类请求,后端仍保留互斥校验。
导入兼容规则:`env` 转为 `environment``timeout` 作为启动超时;`sse_read_timeout` 作为工具等待上限,不保留其原客户端 SSE 读取超时语义。启动超时范围为 1–120 秒,工具超时为 1–300 秒。URL 必须是纯地址,不能使用 Markdown 链接,JSON 中不能包含 `\_` 这样的非法转义。
另一个已修复的失败原因是运行时适配层复用了 `PluginBackend` 的整数超时与 60 秒启动上限,导致合法的 120 秒或小数超时配置在保存返回、读取或测试时失败。独立 Server 现在使用专门的 Bridge 适配模型,保留自己的浮点超时范围,不改变 Plugin 清单原有约束。已有的 120 秒记录可直接读取,无需删库重建。
配置保存成功但后续 Secret 写入失败时,窗口保留服务器 ID、新版本和未写入的密钥。点击保存会更新同一服务器并重试,不重复创建记录;取消会清除未保存密钥,已经保存的服务器和凭据不会回滚。错误信息显示在配置窗口内。保存配置不会自动运行第三方进程,仍需确认、测试和启用。
暂存的 Header Secret 与已保存凭据使用一致的大小写规则:将 `Authorization` 改为 `authorization` 不会丢弃尚未保存的值,提交时采用当前声明名。重新输入同一 Header 的值会覆盖旧草稿;真正删除声明才清除草稿。环境变量仍区分大小写,不会把 `TOKEN` 的草稿转交给 `token`
跨 Registry 与凭据存储的删除以“失败后可重试”为顺序约束:先原子清理密文,再提交新版本或删除 Registry 记录。凭据存储失败时保留原版本和 Server 记录,避免出现返回 500 但配置已提交、版本无法重试或密文失去清理入口的状态。
写入或删除 Secret 会先停用正在运行的连接、注销动态 Tool,并撤销当前配置的测试通过状态;必须使用新凭据重新测试后才能启用。这样页面展示的凭据状态不会与运行中进程实际持有的旧凭据不一致。
## 3. 启用与运行时规则
一次连接按以下顺序执行:
1. 用户检查服务端生成的连接摘要并确认当前摘要;
2. 后端临时连接,完成 initialize 和 `tools/list` 后关闭连接;
3. 只有当前摘要测试成功,启用操作才会启动长期连接并注册 Tool;
4. 停用、删除、超时或异常退出会注销 Tool 并关闭连接。
运行期连接异常或 Tool 列表变化时,Registry 会在同一生命周期临界区内标记不可用、注销 Tool,并从 Bridge 移除 Host。每次启动分配独立的连接代次;失败回调取得锁后先核对代次,旧连接延迟到达的回调不能停用新连接。停用、测试结束及关闭服务时撤销对应代次。
所有独立 MCP API 都通过工作线程执行,包括新增、确认授权和读取接口。虽然部分操作不直接访问网络,但仍可能等待正在测试或启动的连接持有的锁,不能在 FastAPI 事件循环上同步等待。
HTTP Header 中 `Host``Content-Type``MCP-Session-Id` 等协议保留项不可由配置覆盖。URL 不允许内嵌凭据或 Fragment。旧 SSE 返回的 POST endpoint 必须与初始 URL 同源,防止认证 Header 被转发到其他站点。
启动与 Tool 请求超时会同时应用于业务等待和底层 HTTP 请求;旧 SSE 的 endpoint 等待也使用启动超时。非主动结束的旧 SSE 事件流视为 Host 不可用,宿主随后注销 Tool。注册表加载时逐条校验 Server ID、配置字段、Transport 组合和摘要格式,损坏记录统一返回 `MCP_REGISTRY_INVALID`
两种 HTTP Transport 共用有界 SSE 行解析器:按响应字节块检查未完成行及当前事件的累计大小,再扩展缓冲区,不依赖 `iter_lines()` 先缓存完整行。持续无换行输入也会及时触发上限;解析兼容跨块 UTF-8、首行 BOM、LF/CR/CRLF、多行 data 和事件间计数重置。`tests/test_mcp_sse_limits.py` 覆盖这些边界,防止仅在完整行生成后检查大小。
stdio 命令不经过 Shell,管道、重定向和命令拼接不会被解释。Windows 使用新进程组并通过 `taskkill /T` 回收子树;POSIX 使用独立 session/process group 并向进程组发信号。Python 阶段仍无法提供文件、网络、系统调用或操作系统版本差异下的绝对隔离保证。
`uvx` 模板使用 `--isolated`、明确的 `--from` 和固定包版本。它只能隔离依赖,不能替代安全沙箱。非开发环境仍拒绝启动 stdio Server,并返回 `403 MCP_SANDBOX_REQUIRED`;远程 HTTP Transport 不创建本机子进程,但仍要求摘要确认和成功测试。第三阶段前的 C.5 将冻结 Tauri/Rust 沙箱设计。
## 4. 接口
```text
GET /api/mcp/servers
POST /api/mcp/servers
GET /api/mcp/servers/{server_id}
PUT /api/mcp/servers/{server_id}
DELETE /api/mcp/servers/{server_id}
GET /api/mcp/servers/{server_id}/tools
POST /api/mcp/servers/{server_id}/trust
POST /api/mcp/servers/{server_id}/test
POST /api/mcp/servers/{server_id}/enable
POST /api/mcp/servers/{server_id}/disable
PUT /api/mcp/servers/{server_id}/secrets/{key}?kind=environment|header
DELETE /api/mcp/servers/{server_id}/secrets/{key}?kind=environment|header
```
完整字段、状态和错误码见《第二阶段接口契约-开发版》。协议实现参考 MCP 官方的 [Transports](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) 与 [Lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)。
## 5. 验证
```powershell
cd backend
uv run pytest -q tests/test_mcp_registry.py tests/test_extension_core.py
cd ../frontend
npm run type-check
npm test
npm run build
```
后端测试使用无需网络或真实密钥的 stdio Fixture,以及 `httpx.MockTransport` 驱动的确定性 HTTP/SSE Server Fixture。覆盖摘要授权、Secret 不回显、版本冲突、生产门禁、重启恢复、Streamable HTTP Session/Header/工具调用及旧 SSE 同源校验;新增覆盖旧回调隔离、路由线程卸载、凭据大小写差异与旧密文迁移。前端覆盖模板切换、JSON 默认值与格式兼容、明文拆分、模式切换、部分保存失败重试、取消清理、测试失败和删除确认。此处的 MiniMax 配置转换测试使用假密钥,不等同于真实 MiniMax 网络调用验证。
## 6. 后续边界
### 本轮 P1/P2 修复验收
| 审阅问题 | 修复方式 | 回归验证 |
| --- | --- | --- |
| P1:新增或授权等待生命周期锁时阻塞事件循环 | 独立 MCP 路由统一交给工作线程 | `test_mcp_lifecycle_lock_contention_keeps_event_loop_responsive`:分别阻塞 create/trust,在锁释放前仍能执行健康检查 |
| P2:旧失败回调误停新连接 | 回调在锁内核对连接代次 | `test_old_failure_callback_cannot_stop_replacement_host`:旧回调排队期间重启连接,释放锁后新连接仍可用,当前代次的失败仍正确停用 |
| P2:精简 JSON 切换模式或编辑保存报错 | 运行时校验、默认值补全、统一转换 | `configuration.spec.ts``McpServersView.spec.ts`:精简 JSON、编辑版本、模式切换和 Secret 保存重试 |
| P2:Header 大小写改名删除凭据 | 按规范化凭据 ID 而非原始键名计算差集 | `test_header_case_only_rename_preserves_secret` |
| P2:大小写不同的环境变量覆盖同一凭据 | 区分大小写的 v2 ID,带迁移标记的旧密文迁移 | `test_environment_secrets_are_case_sensitive_and_delete_independently` 及 legacy migration 测试,包含删除后不复活旧密钥 |
| P1:SSE 无换行输入在大小校验前无限缓冲 | 在行拼接前校验字节数与事件累计大小 | `test_mcp_sse_limits.py`,包括小块持续输入和跨块换行 |
| P2:Header 大小写改名丢失未保存密钥 | 草稿使用规范化名称匹配,并重新绑定当前声明名 | `configuration.spec.ts` 与页面保存回归测试 |
这些修复不放宽 stdio 的 JSON-RPC 校验。第三方程序向 stdout 打印普通日志造成的握手失败,应由服务端调整输出或使用不打印日志的启动入口处理。
### 后续工作
- 增加真实第三方 Server 的兼容矩阵;确定性 Fixture 只能证明宿主协议行为,不能代表所有实现兼容;
- C.5 在第二阶段开发与测试完成后、第三阶段桌面端实现前冻结沙箱 Contract;
- 第三阶段将 stdio 进程创建和 Secret 托管迁移至 Tauri/Rust Host 与 Stronghold/系统 Keychain。
@@ -1,9 +1,11 @@
# 第一阶段测试验证操作手册
> 适用基线:2026-08-30 `main`
> 适用对象:开发、自测、代码审阅、合并验收和 Demo 前检查
> 适用基线:2026-09-02 `main`
> 适用对象:开发、自测、代码审阅、合并验收和 Demo 前检查
> 验证范围:Vue Web 前端、FastAPI、Knowledge/Retrieval Core、AI/Agent Core、Extension Core、Provider 与开发阶段凭据链路
> 状态说明:本文保留第一阶段功能验收口径;当前全量自动化回归同时覆盖第二阶段已经合并的 Agent Trace 持久化与 SSE 恢复、stdio MCP Host、Plugin Command/Settings Contract 和前端 Service,但不反向修改第一阶段的交付定义。
## 1. 验证目标
本手册用于确认第一阶段已经形成可运行的本地知识工作流:
@@ -18,7 +20,7 @@
→ Skill / Plugin 完成生命周期与 Tool 注册
```
当前不作为第一阶段通过条件的内容:Tauri/Rust Host、Stronghold、真实桌面文件系统、独立 MCP Plugin Host、真实音频模型和 Sync Server
当前不作为第一阶段通过条件的内容:Tauri/Rust Host、Stronghold、原生多 Vault 文件系统、真实音频模型和 Sync Server。独立 stdio MCP Plugin Host 已在第二阶段完成,并进入当前全量回归测试
## 2. 环境准备
@@ -68,7 +70,7 @@ uv run pytest -q -p no:cacheprovider
当前基线:
```text
81 passed
136 passed
```
通过标准:退出码为 0、失败数为 0。用例数可以随功能增加,但不得低于当前基线。
@@ -83,11 +85,11 @@ pnpm test
当前基线:
```text
11 test files passed
27 tests passed
12 test files passed
29 tests passed
```
通过标准:退出码为 0、失败数为 0。测试覆盖 Provider Store、主题偏好、Workspace、文件树、文件切换、可视化编辑器、智能体中文标签、轻量动效性能约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题输出。
通过标准:退出码为 0、失败数为 0。测试覆盖 Provider Store、主题偏好、Workspace、文件树、文件切换、可视化编辑器、智能体中文标签、SSE 恢复游标、Plugin Command/Settings Service、轻量动效性能约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题输出。
### 3.3 类型检查与生产构建
@@ -330,7 +332,7 @@ Invoke-RestMethod -Method Delete -Uri "$apiBase/notes/$noteId"
- 没有真实密钥、生成目录或运行数据进入 Git;
- 已记录测试环境、提交、结果、警告和遗留问题。
外部 OpenAI/DeepSeek、Tauri、Stronghold、真实文件系统、真实音频模型与 Sync Server 失败或未测,不阻止当前第一阶段 Web 联调基线通过,但必须在验收记录中注明“未纳入本阶段”或“选测未执行”。
外部 OpenAI/DeepSeek、Tauri、Stronghold、原生多 Vault 文件系统、真实音频模型与 Sync Server 失败或未测,不阻止当前第一阶段 Web 联调基线通过,但必须在验收记录中注明“未纳入本阶段”或“选测未执行”。
## 9. 验收记录模板
@@ -3,7 +3,7 @@
> 本文记录 `feat/knowledge-retrieval-core` 合并前后的两轮代码审阅、问题复现、修复过程与工程经验。
> 它既是团队内部的问题档案,也可作为后续技术文档、课程报告和博客文章的素材底稿。
> 2026-08-30 状态补充:本文中的 43 项测试是当时该模块的历史基线,不应替换为当前全仓测试数。相关修复仍有效,当前完整后端回归为 71 项通过,Knowledge/Retrieval 已通过 `notes.*``rag.search` Tool 接入 Agent Runtime。
> 2026-09-02 状态补充:本文中的 43 项测试是当时该模块的历史基线,不应替换为当前全仓测试数。相关修复仍有效,当前完整后端回归为 136 项通过,Knowledge/Retrieval 已通过 `notes.*``rag.search` Tool 接入 Agent Runtime。
## 1. 背景
@@ -0,0 +1,464 @@
# Plugin Command 与 Settings 问题与修复复盘
> 审阅与修复日期:2026-09-02
> 涉及分支:`feat/plugin-command-settings`
> 初始功能提交:`6a08ad8 feat(extension): 实现插件命令与设置贡献`
> 最终修复提交:`d39ae72 fix(extension): 收紧插件命令运行时契约`
> 合并提交:`ff3da5d Merge pull request 'feat(extension): 实现 Plugin Command 与 Settings Contribution' (#10)`
> 文档用途:记录阶段 D 从首次提交、连续审阅到合并期间发现的问题,说明形成原因、实际后果、解决思路和最终方案,供后续开发、问题定位、比赛材料和技术博客写作使用。
## 1. 背景与结论
阶段 D 在既有 Extension Core、MCP Bridge 和 Plugin Host 上增加 Plugin Command 与 Settings Contribution。初始实现已经具备命令注册、参数校验、设置持久化、Secret 加密存储和前端 Service,但首次提交仍把若干“接口能够调用”误当成了“安全边界已经闭合”。
本次分支没有在初始功能提交后直接合并,而是围绕清单输入、凭据隔离、JSON Schema、MCP 协议、事务一致性和前后端 Contract 连续审阅。最终共形成 1 个功能提交和 6 个修复提交,处理 12 类问题:
| 编号 | 问题 | 级别 | 处理结果 |
| --- | --- | --- | --- |
| D-01 | Plugin Secret 可被通用凭据接口或 Provider 引用 | P0 | 建立 `plugin.*` 保留命名空间和双层访问门禁 |
| D-02 | Command 与 Settings 清单边界不完整 | P1 | 收紧空值、重复项、权限、类型和数值边界校验 |
| D-03 | MCP Command Target 只有声明,没有真实执行链路 | P0 | 增加专用 MCP 命令目标并与 Agent Tool 隔离 |
| D-04 | JSON Schema 外部引用可能触发越权 I/O | P0 | 只允许当前文档内 Fragment 引用 |
| D-05 | Secret 引用可被篡改并跨凭据命名空间 | P0 | 改用定长哈希引用并在读取、删除前重新核对 |
| D-06 | Secret 删除和插件卸载缺少事务一致性 | P0 | 原子替换凭据表,失败时恢复 Settings 引用 |
| D-07 | Schema 引用校验忽略嵌套 `$id` 资源作用域 | P1 | 使用 Draft 2020-12 Resource Resolver 按资源解析 |
| D-08 | MCP Command 未收到普通 Settings | P1 | 在固定信封中同时传递 Settings 与声明过的 Secret |
| D-09 | Command Effect 是任意字典,前后端边界过宽 | P0 | 改为五类判别联合并限制各自 Payload |
| D-10 | 必填普通设置没有阻止启用和执行 | P1 | 在 Enable 与 Execute 两个入口增加运行时门禁 |
| D-11 | MCP 工具 Schema 被发现后丢弃,真实信封未校验 | P0 | 保存目标 Schema,并在调用前校验完整信封 |
| D-12 | 启用探测和回归测试存在假阳性或假阴性 | P1 | 使用最小协议标记,移除自制求解器并补强测试 |
最终验证基线为后端 136 项测试、前端 29 项测试、TypeScript 类型检查、前端生产构建、`uv lock --check``git diff --check` 通过。PR #10 已合并到 `main`
## 2. 提交与审阅过程
| 顺序 | 提交 | 主要内容 |
| --- | --- | --- |
| 1 | `6a08ad8` | 首次实现 Plugin Command、Settings、Secret API 和前端 Service |
| 2 | `c3ef9df` | 收紧 Plugin Secret、Provider 凭据和 Command 清单边界 |
| 3 | `9e680a0` | 补齐真实 MCP Command Target,限制 JSON Schema 外部引用 |
| 4 | `022c322` | 改造 Secret 引用,修复删除和卸载事务 |
| 5 | `c06b962` | 按 Draft 2020-12 资源作用域修复 Schema 引用解析 |
| 6 | `eb3464b` | 补回 MCP Command 信封中的普通 Settings |
| 7 | `d39ae72` | 收紧 Effect、必填设置和 MCP 运行时 Contract,修复测试盲点 |
| 合并 | `ff3da5d` | PR #10 合并进入 `main` |
这段过程说明,Extension Core 的风险主要不在正常路径能否运行,而在不同入口是否共享同一套边界。HTTP、Provider Factory、Plugin Runtime、MCP Host、Settings Store 和测试 Fixture 只要有一个入口绕过限制,就可能形成跨命名空间访问、错误执行或假测试通过。
## 3. D-01Plugin Secret 可被通用凭据接口或 Provider 引用
### 原因
初始实现把 Plugin Secret 存入已有 `EncryptedCredentialStore`,但只在 Plugin Settings API 中隐藏明文,没有为凭据 ID 建立用途隔离。通用凭据 API 可以读写或删除同名 ID,Provider 配置也可以把 Plugin Secret 的凭据引用当作自己的 API Key 使用。
此外,Command 能否读取 Secret 只依赖 Plugin 拥有 `secrets.use` 权限,没有继续限制到当前 Command 在 `commands.yaml` 中明确声明的字段。
### 后果
- 前端或其他模块可以覆盖、删除 Plugin 私有 Secret
- Provider 可能把插件密钥发送给外部模型服务;
- 同一 Plugin 内权限较低的 Command 可以读取与自己无关的 Secret;
- API 没有回显明文并不代表 Secret 没有发生横向流动。
### 解决思路
Secret 隔离必须同时覆盖存储命名空间、公共 HTTP 入口、Provider 解析入口和 Command 字段级授权,不能只在返回 DTO 上隐藏值。
### 解决方案
- 将 `plugin.*` 设为 Plugin Settings 专用保留命名空间;
- 通用凭据查询、写入和删除接口拒绝访问该前缀;
- 在 `ProviderFactory` 外包一层 `ProviderCredentialResolver`,即使绕过 HTTP 配置校验也不能读取 Plugin Secret
- Command 通过 `secrets` 字段声明允许读取的 Setting Key
- Resolver 同时检查插件权限、字段是否存在、字段类型和 Command 声明;
- 未声明字段返回 `PLUGIN_SECRET_ACCESS_DENIED`,必填 Secret 未配置返回 `PLUGIN_SECRET_REQUIRED`
- 审计事件不记录 arguments、effect 或 Secret。
## 4. D-02Command 与 Settings 清单边界不完整
### 原因
YAML 解析成功只说明语法可读,并不代表清单结构满足宿主协议。初始校验对 `commands:` 空值、重复 Secret、未声明权限、无效执行目标、Settings 默认值及非有限数值边界等情况覆盖不足。
例如 YAML 中只有 `commands:` 时,解析结果是 `null` 而不是空数组;`NaN` 和正负无穷虽然属于 Python 浮点值,却不能成为可移植的表单边界。
### 后果
- 安装阶段可能放过无法执行的 Command;
- 空清单在后续遍历时变成内部异常,而不是稳定业务错误;
- 前后端对数值范围和默认值产生不一致理解;
- 重复声明或越过 Plugin 命名空间的 ID 会污染全局注册表;
- 错误只能到运行期暴露,定位成本更高。
### 解决思路
把 Plugin 包视为不可信输入,在安装阶段完成结构、语义和权限的完整验证,并将解析异常统一转换成稳定的 `ExtensionError`
### 解决方案
- 要求 `contributes` 声明与 `commands.yaml``settings.yaml` 内容完全一致;
- 拒绝 `null` Command 列表、重复 ID、重复 Secret 和越过 Plugin 命名空间的标识;
- 执行目标只能在受控 `handler` 与当前 Plugin 的 `mcp_tool` 中二选一;
- 校验 `when`、Context、Location 和权限白名单;
- Settings 类型限定为 `string``number``boolean``select``secret`
- 校验默认值、Select 选项、必填规则和最小/最大边界;
- 拒绝 `NaN`、正无穷和负无穷;
- 清单错误统一返回可定位的稳定错误码。
## 5. D-03MCP Command Target 只有声明,没有真实执行链路
### 原因
初始 Command 执行器只支持宿主内置 Handler。接口和规划中虽然存在 MCP Command 的概念,但 Runtime 没有把 Command 绑定到 MCP 工具,也没有定义 Command Context、Settings 和 Secret 如何进入 MCP 请求。
直接复用 Agent `ToolRegistry` 看似省事,却会把“用户主动执行的插件命令”和“模型可自主调用的 Agent Tool”混成同一种能力。
### 后果
- MCP 插件声明的 Command 实际无法执行;
- 如果简单注册为 Agent Tool,模型可能绕过命令位置、Context 裁剪和 Secret 声明直接调用;
- Command 超时、Effect 和错误边界无法统一;
- 前端看到命令已注册,点击后却只能得到运行时错误。
### 解决思路
为 MCP Command 建立独立执行通道。它可以复用 MCP 连接,但不能自动进入 Agent Tool Registry;宿主负责构造固定协议信封并验证返回的白名单 Effect。
### 解决方案
- `commands.yaml` 支持当前 Plugin 命名空间内的 `mcp_tool` 目标;
- Runtime 在 Plugin 启用后绑定目标,在禁用、异常退出和重启时同步注销;
- MCP Command 不注册到 Agent `ToolRegistry`
- 宿主只传递已校验 arguments、已裁剪 Context、有效 Settings 和当前 Command 声明的 Secret
- MCP 返回结果必须转换成受控 Effect;
- 远程原始异常、过大结果和超时统一映射为稳定宿主错误。
## 6. D-04JSON Schema 外部引用可能触发越权 I/O
### 原因
Command 和 MCP Tool 都接受插件提供的 JSON Schema。初始实现直接交给校验器处理 `$ref``$dynamicRef`,没有限制引用 URI。恶意或错误 Schema 可以引用本地文件、HTTP 地址或其他外部资源。
### 后果
- Schema 校验可能读取宿主文件或发起未授权网络请求;
- 安装一个插件就可能产生隐式 I/O;
- 离线环境中校验结果不稳定;
- 外部资源变化会让相同插件包得到不同验证结果;
- Command 和 Agent Tool 如果采用不同规则,会出现新的绕过路径。
### 解决思路
当前阶段不需要跨文件 Schema。宿主应只允许当前文档内部的 Fragment 引用,并在注册阶段递归检查所有 Schema 节点。
### 解决方案
- Command 与 Tool 共用 `schema_security` 校验边界;
- 递归扫描 `$ref``$dynamicRef`
- 只允许以 `#` 开头的当前文档 Fragment
- 拒绝文件、HTTP 和其他外部资源 URI;
- 无法解析的本地引用在安装或注册阶段直接失败;
- 运行期继续使用官方 Draft 2020-12 Validator 校验数据。
## 7. D-05Secret 引用可被篡改并跨凭据命名空间
### 原因
初始 Settings 文件保存的 Secret 引用由可读的 Plugin ID 和 Setting Key 拼接而成。长度随名称增长,而且 Runtime 读取引用时默认信任磁盘内容,没有重新确认该引用确实属于当前字段。
本地文件损坏或被篡改后,一个 Plugin Setting 可以被改为指向 Provider 凭据或另一个 Plugin 的 Secret。
### 后果
- 长 Plugin ID 和 Setting Key 可能超过凭据 ID 长度限制;
- Settings 文件泄露内部字段名称;
- 篡改引用可能造成跨命名空间读取或删除;
- 卸载一个插件时可能误删其他模块的凭据。
### 解决思路
Secret 引用应由宿主确定性生成,长度固定,并在每次敏感操作前由当前 `plugin_id + setting_key` 重新计算,而不是信任持久化文件。
### 解决方案
使用以下语义生成引用:
```text
plugin.<sha256(plugin_id + "\\0" + setting_key)>
```
- 引用长度固定且符合凭据 ID 规则;
- Settings Store 只保存引用和 `configured` 状态,不保存明文;
- 读取、覆盖、删除和卸载前重新计算期望引用;
- 引用不匹配时按损坏存储拒绝处理;
- 增加跨命名空间篡改回归测试。
## 8. D-06Secret 删除和插件卸载缺少事务一致性
### 原因
Secret 同时涉及 Settings 引用文件和加密凭据文件。初始删除流程按顺序修改两个存储,但任一步失败都没有完整回滚。插件卸载多个 Secret 时逐条删除,执行到一半失败会留下部分清理状态。
### 后果
- Settings 显示未配置,但密文仍残留;
- 凭据已删除,Settings 却仍显示已配置;
- 多 Secret 卸载可能只删除前几项;
- 重试操作无法判断上一次执行到哪里;
- 用户以为插件卸载已清除密钥,实际磁盘仍可能保留数据。
### 解决思路
把引用更新和凭据删除看作一个逻辑事务。底层单文件凭据表应一次构造新状态并原子替换;跨 Settings 与 Credential Store 的操作需要显式补偿回滚。
### 解决方案
- `EncryptedCredentialStore` 增加多凭据原子删除;
- 先验证全部目标引用,再生成新的凭据表;
- 通过临时文件和原子替换一次提交;
- 删除单个 Secret 时,凭据删除失败则恢复原 Settings 引用;
- 卸载 Plugin 时,批量删除失败则恢复完整 Settings 命名空间;
- 错误统一转换为稳定存储错误,避免部分成功被当作完整成功。
## 9. D-07Schema 引用校验忽略嵌套 `$id` 资源作用域
### 原因
第一版本地引用检查把整个 Schema 当成单一 Fragment 树,用根文档指针或 Anchor 查找所有引用。Draft 2020-12 允许嵌套 `$id` 创建新的 Schema Resource;资源内部的 `#anchor` 应相对于新的 Base URI 解析,根资源也不能反向使用嵌套资源的 Anchor。
### 后果
- 合法的嵌套资源引用被误拒绝;
- 根 Schema 可能错误引用只属于子资源的 Anchor;
- 宿主预检结果与官方运行时 Validator 不一致;
- 同一 Schema 在安装阶段通过,却可能在执行阶段失败,反之亦然。
### 解决思路
安全限制仍然是“禁止外部资源”,但本地资源内部的解析语义必须遵守 JSON Schema 标准,不能自己用字符串和全局 Anchor 集合近似实现。
### 解决方案
- 引入与 Draft 2020-12 Validator 配套的 Resource Registry
- 为根资源和嵌套 `$id` 建立正确作用域;
- 每个引用按其所在资源的 Base URI 解析;
- 保留外部资源拒绝策略;
- 增加“根资源不能使用子资源 Anchor”和“子资源可使用自身 Anchor”的成对测试。
## 10. D-08MCP Command 未收到普通 Settings
### 原因
真实 MCP Command 链路补齐后,固定信封传递了 Command ID、Arguments、Context 和 Secret,但遗漏了已经通过 Schema 校验的普通 Settings。声明式内置 Handler 能读取 SettingsMCP Handler 却不能,两个执行目标语义不一致。
### 后果
- 用户在插件设置页修改普通配置,对 MCP Command 不生效;
- 插件只能把非敏感设置错误地编码进 arguments 或 Secret
- 内置 Handler 测试通过会掩盖 MCP 路径的缺口;
- 插件从内置实现迁移到 MCP 后行为发生变化。
### 解决思路
内置 Handler 与 MCP Command 应消费同一份运行时配置。差别只在执行介质,不在 Command Contract。
### 解决方案
- MCP 固定信封增加 `settings`
- Settings 由 `PluginSettingsStore.runtime_values()` 产生;
- 只传递当前 Plugin Schema 中有效的非敏感字段;
- Secret 继续放在独立 `secrets` 命名空间;
- Fixture 回显非敏感配置用于断言,但不回显 Secret;
- 增加 Settings 实际到达 MCP Server 的集成测试。
## 11. D-09Command Effect 是任意字典,前后端边界过宽
### 原因
初始 `PluginCommandEffect` 只有 `type` 和任意 `payload`。宿主虽然限制 Effect 名称和总体大小,却没有限制 Payload 字段、路由名称、刷新范围或通知级别。前端 TypeScript 也只能把 Payload 当成普通对象处理。
### 后果
- 插件可以返回前端从未支持的字段和路由;
- 前端需要在运行时猜测 Payload 结构;
- `navigate``refresh` 可能越过宿主允许的目标;
- OpenAPI 无法表达不同 Effect 的必填字段;
- 无效 Effect 往往要到页面执行时才暴露。
### 解决思路
Effect 是宿主能力协议,不是插件任意消息。每种 Effect 都应是独立、封闭、可判别的 Contract,并在进入 HTTP 响应前完成验证。
### 解决方案
- 后端建立 `notification``navigate``refresh``job``none` 五类模型;
- 使用 `type` 作为 Pydantic 判别字段;
- 限制通知级别、消息长度、路由白名单、刷新 Scope 和 Job ID
- TypeScript 同步为精确的判别联合;
- OpenAPI `PluginCommandResult.effect` 生成 `oneOf` 和 discriminator
- 保留可序列化性和 64 KiB 总大小限制作为第二层保护。
## 12. D-10:必填普通设置没有阻止启用和执行
### 原因
初始 Runtime 会合并已保存值和默认值,却没有检查 `required` 且没有默认值的普通字段是否仍为空。Secret 已有独立缺失检查,因此测试容易只覆盖 Secret,忽略普通 Settings。
### 后果
- 配置不完整的 Plugin 仍可启动 MCP Host
- Command 到插件内部才因缺少字段失败;
- 用户只能看到模糊执行错误,不知道应先补配置;
- 插件启用后删除必填值,后续执行没有再次校验。
### 解决思路
必填设置既是启用前置条件,也是每次执行的运行时不变量。不能只在保存表单时校验,因为磁盘内容可能变化,启用后的配置也可能被更新。
### 解决方案
- `runtime_values()` 返回完整有效普通 Settings
- 缺少必填且无默认值的字段时抛出 `PLUGIN_SETTINGS_REQUIRED`
- Plugin Enable 前执行一次检查,不启动无效 Host;
- 每次 Command Execute 前重新检查;
- 返回 409 和缺失字段上下文,便于前端引导用户进入设置页;
- 增加“补齐设置后可启用”的完整回归测试。
## 13. D-11MCP 工具 Schema 被发现后丢弃,真实信封未校验
### 原因
MCP 初始化阶段能够取得工具名称和 `inputSchema`,但 Runtime 记录只保留了工具名。Command 执行时直接发送宿主信封,没有用目标工具的完整 Schema 校验实际数据。
这意味着插件只要暴露同名工具就可能通过启用检查,即使它根本不接受 NotesAgent Command 协议。
### 后果
- 不兼容目标在启用阶段被注册为可执行 Command;
- 错误推迟到远程 MCP Server,返回信息不稳定;
- Context、Settings 或 Secret 结构变化时无法在宿主边界发现漂移;
- 前端看到可用命令,执行后才得到 502;
- 禁用或重启后若 Schema 缓存不清理,还可能使用过期契约。
### 解决思路
发现阶段保留完整目标 Schema;启用阶段只检查最低协议标记;执行阶段再用真实数据验证全部约束。Schema 生命周期必须和 MCP 工具生命周期一致。
### 解决方案
- Plugin 运行记录增加 MCP Command Schema 映射;
- 禁用、回滚、重启和 Host 不可用时同步清理 Schema
- 启用时要求目标 Schema 顶层直接声明 `properties._notesagent`,且类型为 `object`
- 执行前构造完整固定信封;
- 使用官方 Draft 2020-12 Validator 校验真实信封;
- 不匹配时返回 `PLUGIN_COMMAND_TARGET_SCHEMA_MISMATCH`,不调用 MCP Server。
## 14. D-12:启用探测和回归测试存在假阳性或假阴性
### 原因
审阅期间先后暴露了三类测试方法问题:
1. 启用阶段曾用空 arguments、Context、Settings 和 Secret 伪造信封,以判断目标 Schema 是否兼容。合法 Schema 如果要求真实业务字段,会被错误拒绝;
2. 为避免空数据误判,曾尝试加入自定义 JSON Schema 可满足性求解,但该近似实现无法正确覆盖 `not``oneOf` 等完整 Draft 2020-12 语义;
3. MCP Secret Fixture 即使没有收到 `api_key` 也会返回成功,测试只验证了命令成功,没有证明 Secret 真正到达服务端。
最后还发现空 Echo 消息会构造 `notification`,但新的通知 Contract 要求消息非空,导致合法空输入被包装为 502。
### 后果
- 合法插件可能在启用阶段被拒绝,形成假阴性;
- 不兼容 Schema 可能被自定义求解器放过,形成假阳性;
- Secret 传递链路回归后测试仍显示通过;
- Contract 收紧后,旧 Fixture 的边界值会产生新的运行时错误;
- 测试数量增加,却没有真正覆盖需要证明的安全事实。
### 解决思路
启用阶段只做稳定且明确的协议结构检查,完整 Schema 语义交给官方 Validator 和真实执行数据。测试必须让目标事实缺失时明确失败,而不是通过返回值间接猜测。
### 解决方案
- 取消空业务数据 Probe
- 将启用门槛缩小为直接 `_notesagent: { type: object }` 协议标记;
- 允许 `$ref``$dynamicRef``allOf``anyOf``oneOf``not` 等约束出现在 `_notesagent` 内部;
- 完全移除自定义 Schema 可满足性求解器;
- 实际执行统一交给官方 Draft 2020-12 Validator
- MCP Fixture 未收到声明的 Secret 时主动返回 MCP 错误,但永不返回明文;
- 空 Echo 返回 `none` Effect,非空 Echo 返回 `notification`
- 为协议标记、真实信封、Secret 到达、空 Echo 和恶意 Effect 分别增加回归测试。
## 15. 验证方法与结果
本分支最终执行以下验证:
```powershell
cd backend
uv lock --check
uv run pytest
cd ../frontend
pnpm test -- --run
pnpm type-check
pnpm build
cd ..
git diff --check
```
结果:
```text
backend: 136 passed
frontend: 29 passed
TypeScript type-check passed
frontend production build passed
uv lock --check passed
git diff --check passed
```
后端测试在 Windows 下仍有既有 `.pytest_cache` 权限警告,不影响测试结果。前端生产构建仍有既有大 Chunk 提示,不影响本次功能正确性和合并结论。
新增或强化的关键测试包括:
- Plugin Secret 公共 API、Provider Resolver 和 Command Resolver 三层隔离;
- 清单空值、重复项、无效权限、非有限边界和非法 Schema;
- MCP Command 与 Agent Tool 隔离;
- 固定信封中的 Context、Settings 和声明式 Secret
- 外部 Schema 引用拒绝和嵌套 `$id` 资源作用域;
- Secret 定长引用、篡改阻断、删除回滚和卸载原子性;
- 五类 Effect 的合法 Payload 和恶意 Payload 拒绝;
- 必填普通设置对 Enable 和 Execute 的双重门禁;
- MCP 实际信封校验、Secret 到达证明和空 Echo 行为。
## 16. 预防措施
- Plugin 包、MCP Schema 和 MCP 返回值一律按不可信输入处理;
- Secret 安全检查必须覆盖写入、读取、删除、解析、传输和审计全链路;
- 保留命名空间需要在公共 API 和内部 Resolver 两侧同时阻断;
- 多存储更新必须设计原子提交或补偿回滚,并测试中途失败;
- 不自行实现通用 JSON Schema 求解器,标准语义交给官方 Validator;
- 启用阶段只检查静态协议能力,不使用伪业务数据推导可执行性;
- Agent Tool 和用户触发的 Plugin Command 必须保持独立注册和权限边界;
- Pydantic Contract、OpenAPI、TypeScript DTO 和测试 Fixture 在同一提交中同步;
- 回归测试应直接证明目标事实,例如“Secret 确实到达且未落盘”,不能只证明接口返回成功;
- 每次收紧 Contract 后,重新检查空值、默认值、最大值和旧 Fixture 等边界输入。
## 17. 当前边界与后续事项
本次合并完成的是阶段 D 的宿主协议和开发期运行边界,不等于第三方插件已经具备生产级操作系统隔离。
当前仍保留以下后续事项:
- 阶段 C.5 按规划放在第二阶段功能与测试完成后、第三阶段桌面集成正式构建 Tauri/Rust 沙箱之前;
- 生产环境继续通过配置门禁拒绝未沙箱化的 MCP Host;
- 桌面端将 Plugin Secret 从开发期 Fernet 文件迁移到 Stronghold 或系统 Keychain,保持现有引用和 HTTP Contract
- 前端已实现命令面板、Plugin 详情命令和动态 Settings / Secret 表单;上下文菜单与 Toolbar 挂载点仍复用现有 Service 继续扩展;
- Command 审计当前是 500 条有界内存队列,长期审计持久化需在后续阶段单独设计;
- 前端大 Chunk 应通过路由和 Markdown 依赖拆包处理,不与本次 Extension Contract 修改混合。
## 18. 复盘结论
本次最重要的经验是:插件系统的正确性不能只按“命令是否执行成功”判断。真正需要审阅的是数据从清单进入注册表、从 Settings 进入运行时、从 Secret Store 进入执行器、从宿主信封进入 MCP,以及从 MCP Effect 返回前端的每一道边界。
连续审阅避免了跨命名空间 Secret 访问、非事务删除、外部 Schema I/O、MCP 伪兼容和任意 Effect 等问题进入 `main`。最终实现把每个边界落到明确 Contract、稳定错误码和可失败的回归测试上,为下一阶段前端集成和桌面安全沙箱提供了可复用基础。
@@ -4,7 +4,7 @@
> 涉及提交:`f9efc4f`,合并提交 `c6c28e4`
> 文档用途:记录前端分支合并后暴露的问题域、形成原因、实际后果、修复思路和落地方案,供后续技术文档、比赛材料与博客写作使用。
> 2026-08-30 状态补充:在本文轮修复之后,项目又完成 Milkdown 写作工具栏、文件切换二次竞态修复、Shiki 只读高亮、Provider 预设/模型发现/加密 API Key 输入,以及智能体页面汉化。当前前端回归基线为 14 项测试和生产构建通过。
> 2026-09-02 状态补充:在本文轮修复之后,项目又完成 Milkdown 写作工具栏、文件切换二次竞态修复、Shiki 只读高亮、Provider 预设/模型发现/加密 API Key 输入智能体页面汉化,以及 Plugin Command/Settings Service。当前前端回归基线为 29 项测试TypeScript 类型检查和生产构建通过。
## 1. 结论
@@ -281,4 +281,4 @@ git diff --check passed
第三轮交互完善继续处理了文件切换、Markdown 选区格式、代码块默认状态、亮暗主题对比度和浮动工具栏失效问题。Provider 设置页增加 OpenAI、DeepSeek、Ollama 预设与模型自动发现,API Key 改为提交给后端加密保存,不进入 Pinia 或 Local Storage。智能体页面的运行状态、事件、工具、权限及导航文案已完成中文化,同时保留技术 ID 便于排障。
该轮新增 Store、Workspace、文件树、编辑器和中文标签回归测试;当前结果为前端 14 项、后端 71 项测试通过,生产构建通过。
该轮新增 Store、Workspace、文件树、编辑器和中文标签回归测试;该轮当时结果为前端 14 项、后端 71 项测试通过,生产构建通过。最新全仓基线见本文开头的状态补充。
@@ -4,7 +4,7 @@
> 审阅范围:FastAPI、Knowledge / Retrieval Core、Agent Core、Extension Core、Provider Adapter、公共接口和后端开发文档。
> 文档用途:记录问题形成原因、实际影响、修复判断和落地方案,供后续开发文档、比赛材料与技术博客使用。
> 2026-09-01 状态补充:本文记录的缺陷均保持修复。此后又加入 Provider 预设、模型发现、DeepSeek/OpenAI 凭据解析、Fernet 加密存储、Agent Trace 持久化stdio MCP Plugin Host,当前完整后端回归基线为 92 项测试通过。
> 2026-09-02 状态补充:本文记录的缺陷均保持修复。此后又加入 Provider 预设、模型发现、DeepSeek/OpenAI 凭据解析、Fernet 加密存储、Agent Trace 持久化stdio MCP Plugin Host 和 Plugin Command/Settings,当前完整后端回归基线为 136 项测试通过。
## 1. 审阅结论