diff --git a/ai.memory/project_review_agent_impl.md b/ai.memory/project_review_agent_impl.md index ed907f0..46dbda1 100644 --- a/ai.memory/project_review_agent_impl.md +++ b/ai.memory/project_review_agent_impl.md @@ -66,6 +66,25 @@ type: project 流程:关 CAD → `cmake --build --preset R230 --config Debug` → 重启 `acad.exe`(`acad.rx` 自动加载) → 轮询 `127.0.0.1:8080` 监听。编译阶段可先行(语法错误会被提前暴露,只有链接会因占用失败)。 +## 基座健壮性:工具执行的异常安全(2026-10-14) + +**不变量:`cad_mcp_frame` 的工具执行运行在 AutoCAD 主线程的窗口消息处理链上 +(`TcpServerLoop` → `PostMessage` → `WndProc` → `ExecuteTaskInMainThread` → `mcp_Tools::Call`), +任何异常都不得逃逸出 `WndProc`,且异常路径上不得遗留文档写锁。** + +**Why**:插件 `Execute` 在参数**类型**不符时会抛 `nlohmann::type_error`(如 +`args.value("bulge", 0.5)` 收到字符串、`parsePoint` 对 `["a","b"]` 取 `get()`—— +它只查 `is_array()/size()>=2`,不查元素类型)。而 `dwg_review` 由 LLM 驱动、 +**模型经常生成类型错误的参数** → 这是日常路径,不是"恶意客户端"场景。 +原实现全程无 try/catch:异常逃逸出 `WndProc` 属未定义行为(表现为 CAD 崩溃), +且 `mcp_Tool_DllProxy::Execute` 的 `unlockDocument` 在异常路径被跳过 → 写锁泄漏。 + +**How to apply**:基座已统一兜住(`mcp_Tools::Call` try/catch → `-32603` → `result.isError=true`; +`HandleToolsCall` 校验 `params`/`arguments` 必须为对象、`name` 必须为字符串,否则 `-32602`; +`ExecuteTaskInMainThread` 兜底 try/catch + `!req.is_object()` → `-32600`; +DLL 代理写锁改为 `ScopedDocumentWriteLock` RAII)。**插件作者无需自行 try/catch**, +但仍不要在异常路径上留下副作用。新增/改动工具后跑 `verify_mcp_fault_injection.py` 回归。 + ## 验证结论(2026-10-13 实测通过) - `verify_mcp_frame.py` 4/4 PASS(换行分帧 / >2KB 报文不截断 / string id 原样回显 / 通知不回包 / isError)。 @@ -74,3 +93,10 @@ type: project - **受控取证子循环实测生效**:LLM 自主调用了白名单内的 `get_drawing_info`/`get_layers`/ `find_zero_length`/`find_duplicate_entities`/`get_dimensions` 做二次确认。 - 批注回写实测通过;注意 `add_leader_annotation` 的 `points` **必须 ≥2 个点**(首个为箭头)。 + +## 验证结论(2026-10-14 补充) + +- `verify_mcp_fault_injection.py` **22/22 PASS**(畸形/类型错误请求:结构性错误回 + `-32602/-32600/-32700`;工具内部类型异常被兜为 `result.isError=true`;反复注入 10 次后 + CAD 存活、写锁未泄漏)。回归 `verify_mcp_frame` 4/4、`verify_stdio_bridge` 24/24、 + `verify_annotate` 9/9、`dwg_review --check` 均正常。