From 1132a4cece83bbd5acb83d36d493ba9767ed6811 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Tue, 1 Sep 2026 12:11:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(extension):=20=E4=BF=AE=E5=A4=8D=20MCP=20Ho?= =?UTF-8?q?st=20=E8=B5=84=E6=BA=90=E4=B8=8E=E5=8D=8F=E8=AE=AE=E8=BE=B9?= =?UTF-8?q?=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- backend/README.md | 2 +- backend/app/extensions/mcp.py | 39 ++++++++++-- backend/app/extensions/runtime.py | 15 ++++- .../extensions/fixtures/mcp-echo/server.py | 9 +++ backend/tests/test_extension_core.py | 62 ++++++++++++++++++- .../AI笔记软件技术栈说明-团队版-v2.3.md | 2 +- docs/contracts/后端接口契约-开发版.md | 2 +- docs/contracts/第二阶段接口契约-开发版.md | 4 +- .../AI-Core与Agent-Core开发说明.md | 2 +- .../Knowledge与Retrieval-Core开发说明.md | 2 +- .../MCP-Bridge与Plugin-Host开发说明.md | 13 ++-- docs/development/前端壳子与接口层开发说明.md | 4 +- .../模型提供商与模型发现开发说明.md | 2 +- .../后端全面审阅问题与修复复盘.md | 2 +- 15 files changed, 138 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index bfc698e..ac7a7db 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ cd frontend pnpm test ``` -当前回归基线为后端 87 项测试、前端 27 项测试,且生产构建通过。测试数量会随功能增长,以本地实际输出和 CI 为准。 +当前回归基线为后端 91 项测试、前端 27 项测试,且生产构建通过。测试数量会随功能增长,以本地实际输出和 CI 为准。 构建产物位于 `frontend/dist`,该目录不提交到 Git。 diff --git a/backend/README.md b/backend/README.md index 3c8429e..6a0930e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -23,7 +23,7 @@ uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000 uv run pytest ``` -当前基线为 87 项测试通过。Provider API Key 可通过前端设置页写入,也可用 `OPENAI_API_KEY`、`DEEPSEEK_API_KEY` 或 `AINOTE_CREDENTIAL_` 注入;不要把真实密钥写入仓库。 +当前基线为 91 项测试通过。Provider API Key 可通过前端设置页写入,也可用 `OPENAI_API_KEY`、`DEEPSEEK_API_KEY` 或 `AINOTE_CREDENTIAL_` 注入;不要把真实密钥写入仓库。 团队接口清单见 `../docs/contracts/后端接口契约-开发版.md`,机器可读契约以运行时的 `/openapi.json` 为准。 diff --git a/backend/app/extensions/mcp.py b/backend/app/extensions/mcp.py index 2647c20..d5036a7 100644 --- a/backend/app/extensions/mcp.py +++ b/backend/app/extensions/mcp.py @@ -188,7 +188,7 @@ class McpStdioClient: result = response.get("result") if not isinstance(result, dict): raise McpBridgeError( - "MCP_TOOL_CALL_FAILED", "MCP response result must be an object." + response_error_code, "MCP response result must be an object." ) return result @@ -207,9 +207,18 @@ class McpStdioClient: except McpBridgeError: pass - def abandon(self, request_id: int) -> None: + def abandon( + self, request_id: int, wake_error: BaseException | None = None + ) -> None: with self._pending_lock: - self._pending.pop(request_id, None) + pending = self._pending.pop(request_id, None) + # asyncio.to_thread 被取消时不会停止底层线程;主动唤醒 Queue,避免线程 + # 一直占用默认线程池直至远端超时。 + if pending is not None and wake_error is not None: + try: + pending.response.put_nowait(wake_error) + except queue.Full: + pass def stop(self) -> None: process = self.process @@ -258,7 +267,15 @@ class McpStdioClient: assert process is not None and process.stdout is not None failure: str | None = None try: - for raw_line in process.stdout: + while True: + # readline(size) 在换行缺失时仍有硬上限,不能先把任意大的 + # 第三方 stdout 行完整读入宿主内存再检查。 + raw_line = process.stdout.readline(MAX_MCP_MESSAGE_BYTES + 1) + if raw_line == "": + break + if not raw_line.endswith("\n"): + failure = "MCP server emitted an oversized or unterminated message." + break if len(raw_line.encode("utf-8")) > MAX_MCP_MESSAGE_BYTES: failure = "MCP server emitted an oversized protocol message." break @@ -313,7 +330,12 @@ class McpStdioClient: process = self.process assert process is not None and process.stderr is not None try: - for line in process.stderr: + while True: + # stderr 不是协议通道,但同样按块读取,避免无换行日志造成 + # 宿主侧的无界字符串分配。 + line = process.stderr.readline(1025) + if line == "": + break self._stderr_tail.append(line.rstrip()[:1024]) except (OSError, ValueError): return @@ -489,7 +511,12 @@ class McpBridge: ) except asyncio.CancelledError: host.client.cancel(rpc_id) - host.client.abandon(rpc_id) + host.client.abandon( + rpc_id, + McpBridgeError( + "MCP_TOOL_CALL_FAILED", "MCP request was cancelled." + ), + ) raise except McpBridgeError as exc: raise ToolExecutionError(exc.code, exc.message) from exc diff --git a/backend/app/extensions/runtime.py b/backend/app/extensions/runtime.py index 33d0bbc..fa6f72f 100644 --- a/backend/app/extensions/runtime.py +++ b/backend/app/extensions/runtime.py @@ -479,6 +479,17 @@ class PluginRuntime: status_code=409, details={"plugin_id": plugin_id}, ) + if record.plugin.status in { + PluginStatus.installed, + PluginStatus.disabled, + PluginStatus.permission_required, + }: + raise ExtensionError( + "PLUGIN_HOST_UNAVAILABLE", + "Disabled or inactive MCP Plugins must be started with Enable.", + status_code=409, + details={"plugin_id": plugin_id, "status": record.plugin.status.value}, + ) for name in record.registered_tools: self.registry.unregister(name) record.registered_tools.clear() @@ -664,7 +675,9 @@ def _arguments_model_from_schema( annotation = types.get(field_schema.get("type"), Any) fields[name] = (annotation, ... if name in required else None) model_name = "PluginArgs_" + re.sub(r"\W+", "_", tool_name) - return create_model(model_name, __config__=ConfigDict(extra="forbid"), **fields) + # 完整 JSON Schema 已在 ToolRegistry 中先行校验。这里允许额外字段,避免 + # Pydantic 再次拒绝 additionalProperties/patternProperties 接受的合法参数。 + return create_model(model_name, __config__=ConfigDict(extra="allow"), **fields) def _validate_tool_schema(spec: DeclarativeToolSpec) -> None: diff --git a/backend/extensions/fixtures/mcp-echo/server.py b/backend/extensions/fixtures/mcp-echo/server.py index 6cf43e1..59f2be9 100644 --- a/backend/extensions/fixtures/mcp-echo/server.py +++ b/backend/extensions/fixtures/mcp-echo/server.py @@ -135,6 +135,15 @@ def main() -> None: request_id = message.get("id") params = message.get("params") or {} if method == "initialize" and isinstance(request_id, int): + if MODE == "invalid-result": + send({"jsonrpc": "2.0", "id": request_id, "result": None}) + continue + if MODE == "oversized-stdout": + # 不带换行,验证 Host 在读取完整内容前执行硬上限。 + sys.stdout.write("x" * (2 * 1024 * 1024 + 1)) + sys.stdout.flush() + time.sleep(10) + return respond( request_id, { diff --git a/backend/tests/test_extension_core.py b/backend/tests/test_extension_core.py index 1c3274a..4e0a9a8 100644 --- a/backend/tests/test_extension_core.py +++ b/backend/tests/test_extension_core.py @@ -1,5 +1,6 @@ import asyncio import shutil +import threading import time import pytest @@ -14,6 +15,8 @@ from app.contracts import ( ToolCall, ) from app.extensions import ExtensionError +from app.extensions.mcp import McpStdioClient +from app.extensions.runtime import _arguments_model_from_schema from app.services import note_service from app.config import BACKEND_DIR, get_settings @@ -362,6 +365,11 @@ def test_mcp_stdio_host_discovers_namespaced_tools_and_maps_results( assert disabled.status == "disabled" assert mcp_container.plugins.get_host_status("mcp-fixture").status == "stopped" assert not mcp_container.tools.contains("mcp-fixture.echo") + with pytest.raises(ExtensionError) as exc: + mcp_container.plugins.restart_host("mcp-fixture") + assert exc.value.code == "PLUGIN_HOST_UNAVAILABLE" + assert mcp_container.plugins.get("mcp-fixture").status == "disabled" + assert not mcp_container.tools.contains("mcp-fixture.echo") run(scenario()) @@ -438,6 +446,56 @@ def test_mcp_business_error_size_limit_and_timeout_are_structured(mcp_container) run(scenario()) +def test_mcp_cancel_releases_blocking_response_thread( + mcp_container, monkeypatch +) -> None: + async def scenario() -> None: + mcp_container.plugins.enable("mcp-fixture") + released = threading.Event() + original_wait = McpStdioClient.wait_response + + def tracked_wait(self, *args, **kwargs): + try: + return original_wait(self, *args, **kwargs) + finally: + released.set() + + monkeypatch.setattr(McpStdioClient, "wait_response", tracked_wait) + task = asyncio.create_task( + mcp_container.plugins.mcp.call_tool( + "mcp-fixture", + "sleep", + {"seconds": 5}, + request_id="call_cancel_release", + ) + ) + await asyncio.sleep(0.05) + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + + deadline = time.monotonic() + 0.5 + while not released.is_set() and time.monotonic() < deadline: + await asyncio.sleep(0.01) + assert released.is_set(), "cancelled MCP wait must not occupy a worker until timeout" + + run(scenario()) + + +def test_mcp_argument_model_preserves_json_schema_additional_properties() -> None: + arguments_model = _arguments_model_from_schema( + "mcp-fixture.dynamic", + { + "type": "object", + "additionalProperties": {"type": "string"}, + }, + ) + + arguments = arguments_model.model_validate({"dynamic_key": "value"}) + + assert arguments.model_dump() == {"dynamic_key": "value"} + + def test_mcp_abnormal_exit_unregisters_tools_and_restart_recovers(mcp_container) -> None: async def scenario() -> None: mcp_container.plugins.enable("mcp-fixture") @@ -471,9 +529,11 @@ def test_mcp_abnormal_exit_unregisters_tools_and_restart_recovers(mcp_container) [ ("no-tools", "[]", "MCP_CAPABILITY_UNSUPPORTED"), ("invalid-schema", "[mcp-invalid.broken]", "MCP_TOOL_SCHEMA_INVALID"), + ("invalid-result", "[]", "MCP_INITIALIZE_FAILED"), + ("oversized-stdout", "[]", "PLUGIN_HOST_UNAVAILABLE"), ], ) -def test_mcp_rejects_missing_capability_and_invalid_discovery( +def test_mcp_rejects_invalid_initialization_and_discovery( tmp_path, mode, contributions, expected_code ) -> None: package = tmp_path / f"mcp-{mode}" diff --git a/docs/architecture/AI笔记软件技术栈说明-团队版-v2.3.md b/docs/architecture/AI笔记软件技术栈说明-团队版-v2.3.md index 622abdc..6cdde3f 100644 --- a/docs/architecture/AI笔记软件技术栈说明-团队版-v2.3.md +++ b/docs/architecture/AI笔记软件技术栈说明-团队版-v2.3.md @@ -2323,7 +2323,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 也已完成。当前验证基线为后端 87 项测试、前端 27 项测试及生产构建通过。向量链路当前使用 `HashEmbeddingProvider` 验证工程正确性,真实 Embedding 召回质量不属于该测试结论。 +截至 2026-09-01,上述第一阶段后端链路和 Web 联调前端均已完成;第二阶段前置的 Workspace 去 Mock 联调、Agent Trace 持久化/恢复接口以及 stdio MCP Bridge / Plugin Host 也已完成。当前验证基线为后端 91 项测试、前端 27 项测试及生产构建通过。向量链路当前使用 `HashEmbeddingProvider` 验证工程正确性,真实 Embedding 召回质量不属于该测试结论。 第二阶段在既有 Contract 上接入: diff --git a/docs/contracts/后端接口契约-开发版.md b/docs/contracts/后端接口契约-开发版.md index e639636..ea080e8 100644 --- a/docs/contracts/后端接口契约-开发版.md +++ b/docs/contracts/后端接口契约-开发版.md @@ -176,7 +176,7 @@ RunCancelled ## 当前实现状态 -更新至 2026-09-01:后端 87 项回归测试通过。 +更新至 2026-09-01:后端 91 项回归测试通过。 - Chat、Agent Run、Agent Events、Tool 列表、Provider 配置生命周期、模型列表和连接测试已经接入 AI Core。 - Agent Run/Event 已持久化到 SQLite;SSE 帧携带 sequence `id`,断线后可以回放缺失事件。Trace API 与 Benchmark 共用同一事件事实,并在入库前执行 Secret 脱敏和结果限长。 diff --git a/docs/contracts/第二阶段接口契约-开发版.md b/docs/contracts/第二阶段接口契约-开发版.md index 7428e42..c2f4ecd 100644 --- a/docs/contracts/第二阶段接口契约-开发版.md +++ b/docs/contracts/第二阶段接口契约-开发版.md @@ -427,7 +427,7 @@ class McpBridge(Protocol): async def stop(self, plugin_id: str) -> None: ... ``` -首个实现已支持本地 `stdio`,按 MCP `2025-11-25` 发起 initialize,并兼容 `2025-06-18`、`2025-03-26` 和 `2024-11-05` 协商结果。Host 负责 capability negotiation、分页 `tools/list`、进程生命周期、超时取消、stderr 隔离和异常退出后的 Tool 注销。stdio 消息使用 UTF-8 单行 JSON-RPC;当前不实现 Streamable HTTP。 +首个实现已支持本地 `stdio`,按 MCP `2025-11-25` 发起 initialize,并兼容 `2025-06-18`、`2025-03-26` 和 `2024-11-05` 协商结果。Host 负责 capability negotiation、分页 `tools/list`、进程生命周期、超时取消、stderr 隔离和异常退出后的 Tool 注销。stdio 消息使用 UTF-8 单行 JSON-RPC,并在完整行进入内存前执行有界读取;当前不实现 Streamable HTTP。 MCP Plugin 的 `backend` 增加: @@ -482,7 +482,7 @@ error `POST /api/plugins/{plugin_id}/host/restart` 返回 `202 OperationResponse`。重启期间先注销旧 Tool,发现和校验全部成功后再一次性发布新 Tool 集合,避免半注册状态。 -当前实现还返回协商后的 `protocol_version`、`server_name` 和 `server_version`。单条协议消息上限为 2 MiB,单次 Tool Result 上限为 256 KiB;超限分别按 Host/Result 错误处理。Server 异常退出或发送无效 stdout 消息时,Host 进入 `unhealthy`,Plugin 进入 `error`,相关 Tool 立即注销。 +当前实现还返回协商后的 `protocol_version`、`server_name` 和 `server_version`。单条协议消息上限为 2 MiB,单次 Tool Result 上限为 256 KiB;超限分别按 Host/Result 错误处理。Server 异常退出或发送无效 stdout 消息时,Host 进入 `unhealthy`,Plugin 进入 `error`,相关 Tool 立即注销。取消会同时通知 Server、移除 pending request 并唤醒本地等待线程。Restart 不得把 `installed`、`disabled` 或 `permission_required` Plugin 隐式启用,这些状态必须走 Enable。 ### 7.3 Command Contribution 列表 diff --git a/docs/development/AI-Core与Agent-Core开发说明.md b/docs/development/AI-Core与Agent-Core开发说明.md index e3cf687..4bc716f 100644 --- a/docs/development/AI-Core与Agent-Core开发说明.md +++ b/docs/development/AI-Core与Agent-Core开发说明.md @@ -2,7 +2,7 @@ > 本文档用于团队开发和模块联调,记录当前已经落地的核心边界与使用方式。 -> 更新日期:2026-09-01。第一阶段 AI Core、Agent Core、Extension Core 和 Model Core 主链路已经完成;第二阶段 Agent Trace 持久化、可恢复 SSE、stdio MCP Bridge 与隔离 Plugin Host 已落地,后端当前回归基线为 87 项测试通过。 +> 更新日期:2026-09-01。第一阶段 AI Core、Agent Core、Extension Core 和 Model Core 主链路已经完成;第二阶段 Agent Trace 持久化、可恢复 SSE、stdio MCP Bridge 与隔离 Plugin Host 已落地,后端当前回归基线为 91 项测试通过。 ## 当前实现 diff --git a/docs/development/Knowledge与Retrieval-Core开发说明.md b/docs/development/Knowledge与Retrieval-Core开发说明.md index 2fcd38c..24f2e63 100644 --- a/docs/development/Knowledge与Retrieval-Core开发说明.md +++ b/docs/development/Knowledge与Retrieval-Core开发说明.md @@ -3,7 +3,7 @@ > 本文档用于团队开发和模块联调,记录 Knowledge Core / Retrieval Core 已经落地的 > 模块边界、数据模型、接口与使用方式,对应分工表中的杨星萱。 -> 更新日期:2026-09-01。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 87 项测试通过。 +> 更新日期:2026-09-01。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 91 项测试通过。 ## 当前实现 diff --git a/docs/development/MCP-Bridge与Plugin-Host开发说明.md b/docs/development/MCP-Bridge与Plugin-Host开发说明.md index 61bb74b..4828ade 100644 --- a/docs/development/MCP-Bridge与Plugin-Host开发说明.md +++ b/docs/development/MCP-Bridge与Plugin-Host开发说明.md @@ -146,6 +146,7 @@ MCP Tool: - 远端名称必须能转换为合法且稳定的项目 Tool ID; - `inputSchema` 必须是有效的 object JSON Schema; +- `additionalProperties`、`patternProperties` 等动态字段先由完整 JSON Schema 校验,Pydantic 参数载体不会再次误拒绝合法字段; - `_meta.notesagent/permission` 必须属于项目已知权限; - Tool 权限必须同时出现在 Plugin Manifest 中; - Agent 仍通过 Tool Registry 执行参数校验、Permission、超时和 Trace; @@ -165,9 +166,10 @@ MCP Tool: - 不把 Provider API Key、`APP_DB_PATH`、Vault 路径和其他宿主环境变量传入子进程; - stderr 与 JSON-RPC stdout 分离,stderr 不进入 API 和 Agent Trace; - stdout 只能发送合法 MCP JSON-RPC; -- 单条协议消息上限 2 MiB; +- stdout 在读取完整行前即应用有界读取,单条协议消息上限 2 MiB;stderr 也按固定大小分块读取; - 单次 Tool Result 上限 256 KiB; - MCP Tool 不绕过 Permission Manager 和 Agent Tool Timeout。 +- 调用被 Agent 取消时,同时通知 Server 并唤醒本地 pending Queue,阻塞线程不会继续占用线程池直至远端超时。 当前尚未提供容器、受限系统账户、seccomp、Windows AppContainer 或 macOS Sandbox,因此 Plugin 进程仍具有当前操作系统用户授予的一般文件访问能力。正式社区插件分发前必须继续增加包签名、来源验证和平台级沙箱;不得把当前进程隔离描述为完全安全执行任意不可信代码。 @@ -201,7 +203,7 @@ unhealthy error ``` -Restart 返回 `202 OperationResponse`。接口返回前已完成本地 Host 重启和 Tool 重新发现;`message` 中给出最终 Host 状态。 +Restart 返回 `202 OperationResponse`。接口返回前已完成本地 Host 重启和 Tool 重新发现;`message` 中给出最终 Host 状态。Restart 只用于运行中或异常 Host;用户主动停用、尚未启用或等待授权的 Plugin 返回 `409 PLUGIN_HOST_UNAVAILABLE`,必须通过 Enable 明确启动。 ## 8. 离线 Fixture @@ -220,7 +222,7 @@ backend/extensions/fixtures/mcp-echo - `mcp-fixture.environment`:验证宿主 Secret/路径没有进入子进程; - `mcp-fixture.exit`:验证异常退出、Tool 注销和 Restart。 -Fixture 的 `tools/list` 使用两页响应,用于覆盖分页发现。测试还会启动缺少 tools capability 和返回无效 Schema 的变体。 +Fixture 的 `tools/list` 使用两页响应,用于覆盖分页发现。测试还会启动缺少 tools capability、返回无效 Schema/initialize result,以及输出超长无换行 stdout 的变体。 ## 9. 验证 @@ -241,10 +243,13 @@ pnpm build - 分页 `tools/list` 与命名空间映射; - Permission、JSON Schema 与 Contribution 集合; - Tool 成功、业务错误、结果过大和超时; +- Agent 取消后 pending 等待线程及时释放; +- `additionalProperties` 动态参数保持 JSON Schema 语义; - Agent Runtime 调用 MCP Tool 并写入正式 Trace; - Secret/Vault 环境隔离; - Server 异常退出、Tool 注销和 Host Restart; -- 缺少 capability 与无效 MCP Schema; +- 缺少 capability、无效 initialize result、无效 MCP Schema 和超长无换行 stdout; +- disabled Plugin 不会被 Host Restart 隐式重新启用; - OpenAPI 发布 Host 状态和重启路径。 ## 10. 当前边界与后续阶段 diff --git a/docs/development/前端壳子与接口层开发说明.md b/docs/development/前端壳子与接口层开发说明.md index de7fc1a..b943dee 100644 --- a/docs/development/前端壳子与接口层开发说明.md +++ b/docs/development/前端壳子与接口层开发说明.md @@ -187,12 +187,12 @@ pnpm build ```text pnpm build passed pnpm test 27 passed -uv run pytest 87 passed +uv run pytest 91 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 权限警告,不影响 87 项测试结果,也不涉及产品代码。 +当前前端使用 Vitest 执行 Store、Workspace API Adapter、SSE 恢复游标、文件树、编辑器组件、智能体标签、轻量动效约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 91 项测试结果,也不涉及产品代码。 Vite 当前会提示 Chat 与 Workspace 的部分异步 Chunk 超过 500 kB,这是 Milkdown、CodeMirror、KaTeX 和 Shiki 等编辑/渲染依赖带来的性能优化项,不影响构建成功或功能正确性;进入桌面打包前应通过手动分包或更细粒度动态加载继续优化。 diff --git a/docs/development/模型提供商与模型发现开发说明.md b/docs/development/模型提供商与模型发现开发说明.md index 0d72d9a..54cfae0 100644 --- a/docs/development/模型提供商与模型发现开发说明.md +++ b/docs/development/模型提供商与模型发现开发说明.md @@ -104,4 +104,4 @@ pnpm build 自动化验证覆盖 Provider 预设、OpenAI-Compatible `/models` 请求与鉴权头、模型映射、前端自动刷新、排序去重及按 Provider 隔离错误。生产构建同时执行 Vue 和 TypeScript 类型检查。 -当前完整回归基线:后端 87 项测试、前端 27 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。 +当前完整回归基线:后端 91 项测试、前端 27 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。 diff --git a/docs/retrospectives/后端全面审阅问题与修复复盘.md b/docs/retrospectives/后端全面审阅问题与修复复盘.md index 401fa16..fe03208 100644 --- a/docs/retrospectives/后端全面审阅问题与修复复盘.md +++ b/docs/retrospectives/后端全面审阅问题与修复复盘.md @@ -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,当前完整后端回归基线为 87 项测试通过。 +> 2026-09-01 状态补充:本文记录的缺陷均保持修复。此后又加入 Provider 预设、模型发现、DeepSeek/OpenAI 凭据解析、Fernet 加密存储、Agent Trace 持久化和 stdio MCP Plugin Host,当前完整后端回归基线为 91 项测试通过。 ## 1. 审阅结论