diff --git a/docs/features/MODULE_AUDIT_SUMMARY.md b/docs/features/MODULE_AUDIT_SUMMARY.md new file mode 100644 index 0000000..56e9f5c --- /dev/null +++ b/docs/features/MODULE_AUDIT_SUMMARY.md @@ -0,0 +1,191 @@ +# ZCLAW V12 模块化端到端审计总报告 + +> **审计版本**: V12 (第 15 次) +> **审计日期**: 2026-04-04 +> **审计方法**: 混合矩阵式(10 个功能模块 + 五维检查清单) +> **审计范围**: 全量 9 大功能领域,11 个模块 + +--- + +## 1. 审计总览 + +### 各模块健康度评分 + +| 排名 | 模块 | 健康度 | P0 | P1 | P2 | P3 | 总问题数 | +|------|------|--------|----|----|----|----|---------| +| 1 | M1 智能对话 | **91** | 0 | 0 | 4 | 7 | 11 | +| 2 | M9 通信与安全 | **86** | 0 | 0 | 5 | 4 | 9 | +| 3 | M5 技能生态 | **85** | 0 | 1 | 2 | 3 | 6 | +| 4 | M7 SaaS Desktop | **85** | 0 | 2 | 2 | 2 | 6 | +| 5 | M8 Admin V2 | **82** | 0 | 0 | 3 | 5 | 8 | +| 6 | M6 Pipeline 工作流 | **72** | 0 | 2 | 5 | 1 | 8 | +| 7 | M11 Classroom | **70** | 0 | 3 | 2 | 3 | 8 | +| 8 | M4 智能层 | **61** | 2 | 2 | 5 | 4 | 13 | +| 9 | M2 Agent 分身 | **67** | 0 | 2 | 7 | 5 | 14 | +| 10 | M3 Hands 自主能力 | **58** | 0 | 5 | 5 | 3 | 13 | + +**项目整体健康度: 76/100** + +--- + +## 2. P0/P1 问题优先修复清单 + +### P0 — 必然崩溃或数据丢失(2 项,全部在 M4 智能层) + +| ID | 模块 | 问题 | 文件 | 修复方案 | +|----|------|------|------|---------| +| M4-01 | M4 | **双数据库**: PersistentMemoryStore vs SqliteStorage 数据不共享,用户手动保存的记忆不出现在对话注入中 | `memory/persistent.rs` vs `zclaw-growth/storage/sqlite.rs` | 合并为单一存储或让前端也查询 SqliteStorage | +| M4-02 | M4 | **反思引擎 LLM 未接入**: reflection_reflect 传 driver=None,永远只用规则分析 | `reflection.rs:759-766` | 传入实际 LLM 驱动 | + +### P1 — 功能失效/核心断链(15 项) + +| ID | 模块 | 问题 | 文件 | +|----|------|------|------| +| M2-01 | M2 | KernelClient createClone 只传 3 字段,丢失 7 个人格字段 | `kernel-agent.ts:74-97` | +| M2-02 | M2 | 两条通路创建逻辑严重不对等 | `gateway-api.ts` vs `kernel-agent.ts` | +| M3-01 | M3 | hand_execute 丢弃 run_id | `hand.rs:184` | +| M3-02 | M3 | Browser Hand 双路径断裂 | `browser.rs:191-343` | +| M3-03 | M3 | browserHandStore 绕过审批流程 | `browserHandStore.ts:222-339` | +| M3-04 | M3 | max_concurrent 未实现 | `kernel/hands.rs` | +| M3-05 | M3 | timeout_secs 未实现 | `hand.rs:47` | +| M4-03 | M4 | 心跳不自动启动 | `heartbeat.rs` | +| M4-04 | M4 | 自主授权后端无强制 | `autonomy-manager.ts` | +| M4-05 | M4 | 前端记忆搜索用 LIKE 非 FTS5 | `persistent.rs` | +| M5-01 | M5 | skill-discovery 将 tags 误映射为 triggers | `skill-discovery.ts:110-123` | +| M6-01 | M6 | route_intent Tauri 命令未注册 | `intent.rs` / `IntentInput.tsx` | +| M6-02 | M6 | pipeline_list 只用 v1 解析器,v2 被丢弃 | `discovery.rs:59` | +| M7-02 | M7 | ConfigMigrationWizard PUT 路径参数语义错误 | `ConfigMigrationWizard.tsx:118` | +| M7-04 | M7 | refreshToken 未传 body,Tauri 可能无 cookie | `saas-client.ts:66` | +| M11-01 | M11 | blocking_lock() 在 async 中可能死锁 | `generate.rs:141-144` | +| M11-02 | M11 | Stage 0/1 LLM 无 map_err | `generate.rs:158-175` | +| M11-03 | M11 | 课堂数据仅内存,重启丢失 | `generate.rs:190-213` | + +--- + +## 3. 断链模式分析 + +### 3.1 "写了没接"型断链(前端代码存在但后端未消费) + +| 断链 | 模块 | 影响 | +|------|------|------| +| hand-execution-complete 事件前端无人监听 | M3 | Hand 执行结果不自动更新 UI | +| classroom_list / classroom_generation_progress | M11 | 课堂历史列表和进度查询功能空置 | +| TOML permissions/rate_limit/max_concurrent 配置 | M3 | 9 个 TOML 中丰富的安全和限流配置形同虚设 | +| 反思引擎 LLM 路径 | M4 | Tauri 命令层永远传 None | + +### 3.2 "接了不对"型断链(两端参数/类型不匹配) + +| 断链 | 模块 | 影响 | +|------|------|------| +| hand_execute 返回值类型不匹配 | M3 | 前端拿不到 runId | +| skill-discovery tags→triggers 映射错误 | M5 | 所有技能的触发词数据错误 | +| pipeline inputs 映射为 steps | M6 | WorkflowBuilder 展示虚假步骤数据 | +| createClone 仅传 3 字段 | M2 | 人格/工作空间配置丢失 | + +### 3.3 "双实现未统一"型问题 + +| 问题 | 模块 | 影响 | +|------|------|------| +| PersistentMemoryStore vs SqliteStorage | M4 | 双数据库数据不共享 | +| hand_approve vs approval_respond | M3 | 重复审批逻辑 | +| BrowserHand Rust vs browserHandStore 前端 | M3 | 两条完全独立的浏览器操作路径 | +| Tauri 压缩 vs Runtime 压缩 | M4 | 两套 CompactionConfig 默认值不同 | +| WhiteboardCanvas vs SceneRenderer 内联 SVG | M11 | 两套白板渲染实现 | + +--- + +## 4. 按模块类型统计 + +### 安全相关(跨模块) + +| 问题 | 涉及模块 | 优先级 | +|------|---------|--------| +| Gemini API Key URL 泄漏 | M1 | P2 | +| master key 明文 localStorage | M9 | P2 | +| TOTP QR 通过外部服务生成 | M7 | P2 | +| ToolOutputGuard 只 warn 不 block | M1 | P2 | +| browserHandStore 绕过审批 | M3 | P1 | +| 自主授权后端无强制 | M4 | P1 | + +### 状态管理相关 + +| 问题 | 涉及模块 | +|------|---------| +| 共享 isLoading(非操作级) | M2 | +| Agent 切换不取消流 | M2 | +| hand-execution-complete 无人接收 | M3 | +| 心跳不自动启动 | M4 | +| 课堂数据无持久化 | M11 | + +### 参数/类型一致性相关 + +| 问题 | 涉及模块 | +|------|---------| +| 前端 password 6 vs 后端 8 | M7 | +| types 数组 vs 单值 | M4 | +| v1 `{{}}` vs `${}` 混用 | M6 | +| WorkflowDetail 映射语义错误 | M6 | + +--- + +## 5. 修复优先级路线图 + +### 第一阶段:P0 立即修复(预计 2-3 天) + +1. **[M4-01]** 统一记忆存储层 — 让前端 memory_search 也查询 SqliteStorage +2. **[M4-02]** 反思引擎接入 LLM — reflection_reflect 传入实际 driver + +### 第二阶段:P1 核心功能修复(预计 5-7 天) + +3. **[M2-01/02]** 扩展 kernel-agent.ts createClone 字段 + 统一双通路 +4. **[M3-01]** hand_execute 返回 run_id +5. **[M3-05]** 实现 timeout_secs 超时保护 +6. **[M3-04]** 实现 max_concurrent 并发限制 +7. **[M3-03]** browserHandStore 集成审批流程 +8. **[M5-01]** 修正 skill-discovery triggers 映射 +9. **[M6-01]** 注册 route_intent Tauri 命令 +10. **[M6-02]** pipeline_list 支持 v2 格式 +11. **[M4-03]** 自动启动心跳引擎 +12. **[M4-05]** 前端记忆搜索升级 FTS5 +13. **[M11-01]** 修复 blocking_lock 死锁风险 +14. **[M11-03]** 课堂数据持久化 + +### 第三阶段:P2 健壮性提升(预计 5-7 天) + +- M1: Gemini Key URL → header, ToolOutputGuard Block, Mutex unwrap +- M2: 参数验证、Agent 切换联动、SQLite 外键确认 +- M3: 注册全局事件监听、审批 TTL、统一审批路径 +- M4: interval 验证、identity 加密、自主映射修正 +- M7: 密码校验统一、master key 安全、TOTP 本地 QR +- M8: 权限动态获取、Relay retry、API Key admin 端点 +- M11: placeholder 标志、ID 冲突、导出丰富 + +--- + +## 6. 对 TRUTH.md 的更新建议 + +审计中发现的数字偏差: + +| 条目 | TRUTH.md 当前值 | 审计实际值 | 说明 | +|------|----------------|----------|------| +| Tauri 命令无前端调用 | 24 | 需重新统计 | classroom_list/generation_progress 等新增 @reserved | +| 75 个 SKILL.md | 75 | 75 ✅ | 全部可解析,但 tools 字段未消费 | +| 11 个 Hand | 9 启用 + 2 禁用 | 9 启用,TOML 中无 enabled=false | 2 个禁用 Hand 未找到 TOML 或 Rust 实现 | +| 5 个 Pipeline 模板 | 5 | 17 个 YAML 文件 | pipelines/ 目录有更多模板 | +| Desktop 前端测试 | 0 | 0 ✅ | 仍未解决 | + +--- + +## 7. 审计方法总结 + +本审计采用**混合矩阵式**方案: +- **主轴**: 11 个功能模块(M1-M11) +- **检查模板**: 五维检查(链路完整性/参数一致性/边界错误/状态管理/安全资源) +- **断链检测**: Tauri 命令调用矩阵 + 参数签名比对 + 事件链路追踪 + DB-API-UI 三端对齐 +- **验证深度**: 60% 静态分析 + 30% 代码推理 + 10% 实机验证(M1/M3 加强至 20-30%) + +相比前 14 轮审计的创新: +1. 首次以功能领域为主轴追踪完整调用链 +2. 首次系统验证参数/类型一致性(发现了 4 处前端参数不匹配) +3. 首次验证边界场景(错误传播、空状态、并发) +4. 建立了模块级健康度基线,未来审计可增量更新 diff --git a/docs/features/audit-v12/M1-intelligent-chat.md b/docs/features/audit-v12/M1-intelligent-chat.md new file mode 100644 index 0000000..26c78f0 --- /dev/null +++ b/docs/features/audit-v12/M1-intelligent-chat.md @@ -0,0 +1,186 @@ +# 模块 M1 智能对话 审计报告 + +> **审计版本**: V12 +> **审计日期**: 2026-04-04 +> **审计范围**: 用户输入 → ChatArea → chatStore → Kernel → LLM Driver → 流式回传 + +--- + +## 1. 模块概况 + +### 功能描述 +ZCLAW 智能对话模块是用户与 AI Agent 交互的核心入口,支持流式响应、8 个 LLM Provider、推理模式、工具调用、多轮对话。 + +### 涉及文件清单 + +**前端 (Desktop)** +- UI 组件: `desktop/src/components/ai/` (ChatMode, StreamingText, ReasoningBlock, ToolCallChain, TokenMeter, SuggestionChips, ArtifactPanel, ModelSelector) +- 入口组件: `desktop/src/components/ChatArea.tsx` +- Store 层: `desktop/src/store/chat/streamStore.ts`, `conversationStore.ts`, `messageStore.ts`, `artifactStore.ts` +- Client 层: `desktop/src/lib/kernel-chat.ts`, `gateway-client.ts`, `saas-relay-client.ts` +- 类型: `desktop/src/types/chat.ts`, `kernel-types.ts` + +**后端 (Rust)** +- Kernel: `crates/zclaw-kernel/src/kernel/messaging.rs` +- Agent Loop: `crates/zclaw-runtime/src/loop_runner.rs` +- Driver: `crates/zclaw-runtime/src/driver/` (anthropic.rs, openai.rs, gemini.rs, local.rs) +- 中间件: `crates/zclaw-runtime/src/middleware/` (11 个中间件) +- SaaS Relay: `crates/zclaw-saas/src/relay/` (handlers.rs, service.rs, key_pool.rs) +- 存储: `crates/zclaw-memory/src/store.rs` +- Tauri 命令: `desktop/src-tauri/src/kernel_commands/chat.rs` + +### 调用链路图 + +``` +用户输入 + → ChatArea.handleSend() + → streamStore.sendMessage() + → getClient().chatStream(content, callbacks, opts) + ├─ [Kernel 模式] kernel-chat.ts → invoke('agent_chat_stream', { request }) + │ → Rust: agent_chat_stream() → Kernel.send_message_stream_with_prompt() + │ → AgentLoop.run_streaming() + │ → create_middleware_chain() (11层 before_completion) + │ → driver.stream() (LLM调用) + │ → 工具执行循环 (max 10 iterations) + │ → app.emit("stream:chunk", payload) + │ ← listen("stream:chunk") → streamCallbacks.onDelta/onTool/onComplete + │ + ├─ [Gateway 模式] gateway-client.ts → WebSocket + │ → ZCLAW WebSocket 协议 + │ ← WS onmessage → streamCallbacks + │ + └─ [SaaS 模式] saas-relay-client.ts → HTTP SSE + → POST /api/v1/relay/chat/completions + ← SSE stream → streamCallbacks + ← streamStore 更新 isStreaming/activeRunId + ← UI 重渲染 +``` + +--- + +## 2. 五维检查结果 + +### 2.1 链路完整性 + +| 链路 | 起点 | 终点 | 状态 | 备注 | +|------|------|------|------|------| +| Kernel 流式聊天 | ChatArea.tsx | agent_chat_stream (Rust) | ✅ 连通 | invoke 参数一致 | +| Gateway 聊天 | ChatArea.tsx | WebSocket server | ✅ 连通 | 独立路径 | +| SaaS Relay 聊天 | ChatArea.tsx | /api/v1/relay/chat/completions | ✅ 连通 | SSE 流 | +| 流式事件 stream:chunk | Rust emit | kernel-chat.ts listen | ✅ 连通 | listen 在 line 91 | +| 取消流 cancel_stream | streamStore | Rust cancel_stream | ✅ 连通 | AtomicBool 标志 | +| hand-execution-complete | Rust emit | ChatArea.tsx + kernel-hands.ts listen | ✅ 连通 | 双重监听 | +| 消息持久化 | AgentLoop | SqliteStorage.append_message | ✅ 连通 | | +| Token 用量记录 | AgentLoop after_completion | usage 数据 | ✅ 连通 | | +| SaaS 计费 | relay service.rs | quota 递增 | ✅ 连通 | Semaphore(16) 限流 | +| 中间件链 | Kernel | 11 层中间件 | ✅ 连通 | 按 priority 排序 | + +### 2.2 参数/类型一致性 + +| 接口 | 前端类型 | 后端类型 | 一致性 | 备注 | +|------|---------|---------|--------|------| +| agent_chat_stream.request | `{ agentId, sessionId, message, thinkingEnabled?, reasoningEffort?, planMode? }` | `StreamChatRequest { agent_id, session_id, message, thinking_enabled?, reasoning_effort?, plan_mode? }` | ✅ 一致 | `#[serde(rename_all = "camelCase")]` 自动转换 | +| stream:chunk payload | `StreamChunkPayload` | `serde_json::json!({ sessionId, type, ... })` | ✅ 一致 | | +| cancel_stream | `{ sessionId }` | `session_id: String` | ✅ 一致 | camelCase 转换 | +| ChatStreamOptions | `thinking_enabled?, reasoning_effort?, plan_mode?` | `thinking_enabled, reasoning_effort, plan_mode` | ✅ 一致 | 全部 Optional | + +**参数一致性结论**: 无断链。Tauri 的 `rename_all = "camelCase"` 正确处理了命名风格差异。 + +### 2.3 边界与错误处理 + +| 场景 | 输入 | 预期行为 | 实际行为 | 级别 | +|------|------|---------|---------|------| +| 空消息发送 | `""` | 阻止发送 | ChatArea line 211 检查空输入并 return | ✅ 正确 | +| 流式中重复发送 | 连续点击发送 | 阻止重复 | streamStore line 193 `if (isStreaming) return` | ✅ 正确 | +| 同 session 并发流 | 相同 sessionId 两次 invoke | 拒绝第二个 | Rust AtomicBool CAS 保证 (chat.rs:131-133) | ✅ 正确 | +| 后端断开 | 流式中后端 crash | 超时或错误回调 | StreamBridge 90s 超时 (6个心跳) + channel close 检测 | ✅ 正确 | +| 流式中断(悬空工具) | 中途断网留下 ToolUse 无 ToolResult | 修补悬空 | DanglingToolMiddleware 自动插入占位 ToolResult | ✅ 正确 | +| 超长消息 | 100K 字符 | 拒绝或截断 | Rust 端 validate_string_length(100000) 限制 | ✅ 正确 | +| Payload 过大 | 大量工具结果 | 截断 | OpenAiDriver 1.8MB payload limit + 紧急截断 (保留 sys+最近4条) | ✅ 正确 | +| SaaS Key 全部冷却 | 无可用 API Key | 等待提示 | key_pool.rs 返回预计等待时间 | ✅ 正确 | +| LLM 请求超时 | Provider 不响应 | 超时终止 | 流式 chunk 超时 60s (loop_runner.rs:683) | ✅ 正确 | +| 模型只输出 reasoning 无 text | thinking 模式下 | 回退为 reasoning 内容 | loop_runner.rs:769-774 自动回退 | ✅ 正确 | +| 循环调用检测 | 重复工具调用 | warn/block/abort | LoopGuard SHA256 指纹 + 三级阈值 (3/5/30) | ✅ 正确 | +| SaaS 请求体过大 | > 1MB | 拒绝 | handlers.rs:41 大小限制 | ✅ 正确 | +| temperature 越界 | > 2.0 或 < 0 | 拒绝 | handlers.rs:91-103 范围验证 | ✅ 正确 | +| max_tokens 越界 | > 128000 | 拒绝 | handlers.rs:106-118 范围验证 | ✅ 正确 | +| 前端 cancel 时 Rust 端清理 | 取消后继续收到 chunk | 丢弃并清理 | cancel_flag 检测 + channel close + stream_guard 释放 | ✅ 正确 | + +### 2.4 状态管理 + +| Store/组件 | 状态机完整性 | 持久化 | 竞态风险 | +|-----------|------------|--------|---------| +| streamStore | ✅ isStreaming/activeRunId 完整 | ❌ 无持久化(纯运行时状态) | ⚠️ **低风险** — cancelStream 中使用 stale `activeRunId` | +| conversationStore | ✅ 会话列表/当前会话 | ✅ IndexedDB 持久化 | ✅ 低风险 | +| messageStore | ✅ 消息追加/更新 | ❌ 消息持久化在 SQLite 后端 | ✅ 低风险 | +| artifactStore | ✅ 文件列表/选中/开关 | ❌ 无持久化 | ✅ 无风险 | +| ChatArea 组件 | ✅ 输入/流式状态 | ❌ 无持久化 | ✅ 低风险 | + +**cancelStream 竞态风险详情**: +- `streamStore.ts:476`: `const sessionId = useConversationStore.getState().sessionKey || activeRunId || ''` +- 如果用户在 cancel 和新 stream 之间快速操作,`sessionKey` 可能已指向新 session,导致 cancel 错误的 stream +- **风险等级**: P3(极端操作顺序,且 cancel 错误 stream 只会导致空操作) + +### 2.5 安全与资源 + +| 检查项 | 状态 | 说明 | +|--------|------|------| +| API Key 安全存储 | ✅ | `SecretString` 包装,`expose_secret()` 仅在 HTTP header 中使用 | +| API Key 加密存储 (SaaS) | ✅ | AES-256-GCM + 随机 nonce | +| 对话内容不进入日志 | ✅ | 仅记录前 50 字符 (memory.rs:65) | +| SSRF 防护 | ✅ | 精确的 URL 验证 (service.rs:810-922) | +| SQL 注入防护 | ✅ | 全部参数化查询 | +| 请求体大小限制 | ✅ | SaaS 1MB, OpenAI driver 1.8MB | +| 流式 channel 泄漏 | ✅ | stream_guard 在所有路径(成功/错误/cancel)都清理 | +| 工具输出敏感信息检测 | ⚠️ **只 warn 不 block** | 检测到 API Key 等敏感信息时仅 warn,仍然传递给 LLM | +| **Gemini API Key URL 泄漏** | ⚠️ **P2 风险** | Key 通过 `?key=` query param 传递,出现在日志/代理/网络面板 | + +--- + +## 3. 问题清单 + +| ID | 描述 | 文件:行号 | 级别 | 修复建议 | 验证方法 | +|----|------|----------|------|---------|---------| +| M1-01 | GeminiDriver API Key 通过 URL query param 传递,出现在日志和代理 | `driver/gemini.rs:71-74` | **P2** | 改用 HTTP header 认证(Gemini 支持 `x-goog-api-key` header) | 检查 HTTP 日志是否包含 key | +| M1-02 | ToolOutputGuardMiddleware 检测到敏感信息只 warn 不 block | `middleware/tool_output_guard.rs:99-128` | **P2** | 对确认的 API Key/密码模式应 Block 或至少截断 | 构造含 `sk-xxx` 的工具输出测试 | +| M1-03 | MemoryMiddleware 中 `std::sync::Mutex::unwrap()` 在 async 上下文 | `middleware/memory.rs:46` | **P2** | 改用 `lock().unwrap_or_else(\|e\| e.into_inner())` 或 `tokio::sync::Mutex` | stress test 并发记忆提取 | +| M1-04 | LoopGuardMiddleware 中 `std::sync::Mutex::unwrap()` 在 async 上下文 | `middleware/loop_guard.rs:40` | **P2** | 同 M1-03 | stress test 循环检测 | +| M1-05 | Agent Loop 迭代上限硬编码为 10 | `loop_runner.rs:298` | **P3** | 提取为 KernelConfig 配置项 | 修改配置验证生效 | +| M1-06 | TitleMiddleware 是空占位符 | `middleware/title.rs` | **P3** | 完成实现或移除以减少 chain 开销 | 确认无功能依赖后移除 | +| M1-07 | OpenAiDriver `trace` 级别记录完整请求体 | `driver/openai.rs:127` | **P3** | 生产环境确保 trace 级别不启用,或移除内容日志 | 检查日志配置 | +| M1-08 | cancelStream 竞态:sessionKey 可能指向新 session | `streamStore.ts:476` | **P3** | cancel 时使用 `activeRunId` 而非 `sessionKey` | 快速 cancel + 新 stream 测试 | +| M1-09 | LoopGuard 不在 agent turn 间 reset | `middleware/loop_guard.rs` | **P3** | 考虑在 `after_completion` 中 reset | 多轮对话验证计数器不累积 | +| M1-10 | OpenAiDriver 将 SecretString 转为普通 String | `driver/openai.rs:130` | **P3** | String 在 async 闭包生命周期内可被内存转储获取;考虑使用零化 | 内存安全审计 | +| M1-11 | `loop_runner.rs:513/804` 使用 `unwrap_or_default()` 静默吞掉数据库错误 | `loop_runner.rs:513,804` | **P3** | 改为 `log::warn!` + 默认值 | 断开 DB 验证有 warn 日志 | + +--- + +## 4. 改进建议 + +### 短期修复项(按优先级排序) + +1. **[P2] Gemini API Key URL → Header**: 将 `?key=` 改为 `x-goog-api-key` header 传递 +2. **[P2] ToolOutputGuard Block**: 对检测到 `sk-`、`AKIA` 等确认敏感信息的工具输出应 Block +3. **[P2] Mutex unwrap → 安全处理**: MemoryMiddleware 和 LoopGuardMiddleware 的 `std::sync::Mutex` 改用安全解包方式 + +### 长期架构建议 + +- 将 Agent Loop 迭代上限和 LoopGuard 阈值提取为 KernelConfig 配置项 +- 为 TitleMiddleware 完成实现或清理移除 +- 考虑 Driver 层的 SecretString 生命周期管理改进(避免 `to_string()`) + +--- + +## 5. 健康度评分 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 链路完整性 | **95/100** | 三种连接模式全部连通,无断链 | +| 参数一致性 | **100/100** | 全部接口参数匹配,Tauri camelCase 自动转换正确 | +| 边界处理 | **90/100** | 空值/超长/并发/超时全覆盖 | +| 状态管理 | **85/100** | 基本完整,cancelStream 有低概率竞态 | +| 安全资源 | **85/100** | Gemini Key 泄漏和 ToolOutputGuard warn-only 需修复 | + +**综合健康度: 91/100** + +模块整体质量很高,11 层中间件设计参考了 DeerFlow 2.0 最佳实践,三层连接模式(Kernel/Gateway/SaaS)全部连通。发现的问题均为 P2/P3 级别,无 P0/P1 断链。 diff --git a/docs/features/audit-v12/M11-classroom.md b/docs/features/audit-v12/M11-classroom.md new file mode 100644 index 0000000..2b98abf --- /dev/null +++ b/docs/features/audit-v12/M11-classroom.md @@ -0,0 +1,36 @@ +# 模块 M11 Classroom 课堂 审计报告 + +> **审计版本**: V12 | **日期**: 2026-04-04 + +## 1. 链路完整性 + +| 链路 | 状态 | 备注 | +|------|------|------| +| 课堂生成 | ✅ | classroom_generate → 4 阶段 pipeline → classroom:progress 事件 | +| 课堂加载 | ✅ | classroom_get → ClassroomStore HashMap | +| 场景播放 | ✅ | SceneRenderer 按 actions 自动播放(speech/whiteboard/quiz) | +| 课堂聊天 | ✅ | classroom_chat → LLM 多 agent 响应 | +| 笔记展示 | ✅ | scenes 列表 → 点击切换 | +| 白板渲染 | ⚠️ | WhiteboardCanvas 和 SceneRenderer 内联 SVG 两套实现未统一 | +| TTS | ✅ | 浏览器 SpeechSynthesis zh-CN | +| 导出 | ✅ | JSON/HTML/Markdown → Blob download | +| 取消生成 | ✅ | classroom_cancel_generation → tasks 移除 | +| 课堂历史列表 | ❌ | classroom_list 后端已实现但前端未集成 | +| 生成进度查询 | ❌ | classroom_generation_progress 后端已实现但前端未集成 | + +**链路完整性评分: 82/100** + +## 2. 问题清单 + +| ID | 文件 | 级别 | 描述 | 修复建议 | +|----|------|------|------|---------| +| M11-01 | `generate.rs:141-144` | **P1** | `is_cancelled()` 用 `blocking_lock()` 在 async 函数中,tokio runtime 可能死锁 | 改为 `try_lock()` 或 `tokio::sync::watch` channel | +| M11-02 | `generate.rs:158-175` | **P1** | Stage 0/1 LLM 调用无 `map_err`,driver 未配置时可能 panic 或返回空结果无错误提示 | 增加 map_err 错误处理 | +| M11-03 | `generate.rs:190-213` | **P1** | 课堂数据仅存内存 HashMap,应用重启后全部丢失 | 增加 SQLite 或文件持久化 | +| M11-04 | `chat.rs:82-92` | P2 | LLM 失败静默 fallback 到 placeholder,用户无法区分真实和模拟回复 | 增加 isPlaceholder 标志 | +| M11-05 | `ClassroomPlayer.tsx:74-77` | P2 | 生成完成后强制打开 player,即使生成期间用户已关闭 | 增加手动关闭状态检查 | +| M11-06 | `classroomStore.ts:192` | P2 | chat message ID 用 `Date.now()` 可能冲突 | 改用 `crypto.randomUUID()` | +| M11-07 | `WhiteboardCanvas.tsx` | P3 | SceneRenderer 未引用 WhiteboardCanvas,白板有两套渲染实现 | 统一为一套 | +| M11-08 | `export.rs:92-103` | P3 | HTML 导出只渲染 title+duration,不包含 key_points 等内容 | 丰富导出内容 | + +**综合健康度: 70/100** — 功能链路基本贯通,但无持久化、async 锁风险、LLM 错误不透明是核心短板。 diff --git a/docs/features/audit-v12/M2-agent-clones.md b/docs/features/audit-v12/M2-agent-clones.md new file mode 100644 index 0000000..906a4fe --- /dev/null +++ b/docs/features/audit-v12/M2-agent-clones.md @@ -0,0 +1,149 @@ +# 模块 M2 Agent 分身 审计报告 + +> **审计版本**: V12 +> **审计日期**: 2026-04-04 +> **审计范围**: Agent 创建/切换/配置/删除 → agentStore → Kernel → SQLite + +--- + +## 1. 模块概况 + +### 功能描述 +多 Agent 创建/配置/管理,每个 Agent 拥有独立的身份、技能和配置,SQLite 持久化。 + +### 涉及文件清单 + +**前端** +- Store: `desktop/src/store/agentStore.ts`, `desktop/src/store/chat/conversationStore.ts` +- Client: `desktop/src/lib/kernel-agent.ts`, `desktop/src/lib/gateway-api.ts` +- 类型: `desktop/src/lib/kernel-types.ts` +- UI: `desktop/src/components/AgentSelector.tsx` + +**后端 (Rust)** +- Tauri 命令: `desktop/src-tauri/src/kernel_commands/agent.rs` +- Kernel 业务: `crates/zclaw-kernel/src/kernel/agents.rs` +- Registry: `crates/zclaw-kernel/src/registry.rs` +- SQLite 存储: `crates/zclaw-memory/src/store.rs` +- Schema: `crates/zclaw-memory/src/schema.rs` +- 类型: `crates/zclaw-types/src/agent.rs`, `crates/zclaw-types/src/config.rs` + +### 调用链路图 + +``` +用户操作 (创建/切换/配置/删除 Agent) + → agentStore.createClone(opts) / updateClone() / deleteClone() + → getClient().createClone() / updateClone() / deleteClone() + ├─ [Kernel 模式] kernel-agent.ts → invoke('agent_create/update/delete') + │ → Rust: agent_create/update/delete → kernel.spawn/kill/update_agent() + │ → agents.rs: 验证 → MemoryStore.save_agent() → Registry 注册/移除 + │ → store.rs: INSERT/UPDATE/DELETE agents 表 (SQLite) + │ + └─ [Gateway 模式] gateway-api.ts → REST POST/PUT/DELETE /api/agents +``` + +--- + +## 2. 五维检查结果 + +### 2.1 链路完整性 + +| 链路 | 起点 | 终点 | 状态 | 备注 | +|------|------|------|------|------| +| 创建 Agent (Kernel) | agentStore.createClone() | store.rs INSERT | ⚠️ **字段丢失** | kernel-agent.ts 只传 3 字段,丢 7 个人格字段 | +| 创建 Agent (Gateway) | agentStore.createClone() | gateway-api.ts POST | ✅ 连通 | 传完整 TOML manifest | +| 列举 Agent | agentStore.loadClones() | Registry → SQLite | ✅ 连通 | | +| 更新 Agent | agentStore.updateClone() | store.rs UPDATE | ⚠️ **字段丢失** | 同创建,仅 7 字段可更新 | +| 删除 Agent | agentStore.deleteClone() | store.rs DELETE | ✅ 连通 | SQLite 外键级联删除 sessions | +| 切换 Agent | conversationStore.setCurrentAgent() | conversationStore state | ⚠️ **不通知 Kernel** | defaultAgentId 不更新 | +| 从模板创建 | agentStore.createFromTemplate() | SaaS + Kernel | ⚠️ **metadata 更新可能静默失败** | | + +### 2.2 参数/类型一致性 + +| 接口 | 前端传递字段 | 后端期望字段 | 一致性 | 备注 | +|------|------------|------------|--------|------| +| agent_create request | name, description, model | agent_id, name, description, model, system_prompt, max_tokens, temperature, provider, ... | ❌ **不完整** | 前端丢 7+ 字段 | +| AgentInfo 返回 | id, name, description?, state, model?, provider? | id, name, description, model, provider, state, message_count, created_at, updated_at | ❌ **字段丢失** | 前端缺 message_count/created_at/updated_at | +| agent_update updates | name, description, systemPrompt, model, provider, maxTokens, temperature | 同左 (7 字段) | ✅ 一致 | 但缺 emoji/personality 等 | +| 默认 provider | kernel-agent.ts: `'openai'` | agent.rs: `'anthropic'` | ❌ **不一致** | JS 层覆盖所以不影响运行 | + +### 2.3 边界与错误处理 + +| 场景 | 输入 | 预期行为 | 实际行为 | 级别 | +|------|------|---------|---------|------| +| 创建同名 Agent | 相同 name | 警告或拒绝 | ✅ 允许创建,无唯一约束 | P3 | +| 删除正在使用的 Agent | 当前 active agent | 阻止或警告 | ❌ 直接删除,不检查活跃状态 | P2 | +| 空名称创建 | name="" | 拒绝 | ❌ 允许,无验证 | P2 | +| temperature 越界 | 5.0 | 拒绝或 clamp | ❌ 无范围验证 | P2 | +| 切换 Agent 时流式进行中 | 中途切换 | 取消流再切换 | ❌ 流继续,消息可能追到错误对话 | P2 | +| 删除后 selectedAgent 引用 | 删除当前选中 | 清空选择 | ❌ conversationStore.currentAgent 可能指向已删除 Agent | P2 | +| 模板创建 metadata 失败 | updateClone 抛错 | 回滚或提示 | ⚠️ 仅 warn 日志,Agent 已创建但配置不完整 | P2 | + +### 2.4 状态管理 + +| Store | 状态机完整性 | 持久化 | 竞态风险 | +|-------|------------|--------|---------| +| agentStore | ⚠️ 共享 isLoading,无操作级状态 | ❌ 无持久化 | ⚠️ 并发操作共享 isLoading | +| conversationStore (Agent 相关) | ✅ currentAgent/agentList | ✅ IndexedDB | ⚠️ 切换 Agent 不取消流 | + +### 2.5 安全与资源 + +| 检查项 | 状态 | 说明 | +|--------|------|------| +| agent_id 格式验证 | ✅ | validate_agent_id 使用 UUID 格式 + validate_identifier | +| agent_import 大小限制 | ✅ | validate_string_length 限制 1MB | +| API Key 不存储在 Agent 配置 | ✅ | AgentConfig 仅存 env var 名 | +| 删除级联 | ⚠️ | 依赖 SQLite PRAGMA foreign_keys 启用 | + +--- + +## 3. 问题清单 + +| ID | 描述 | 文件:行号 | 级别 | 修复建议 | 验证方法 | +|----|------|----------|------|---------|---------| +| M2-01 | **[P1] KernelClient createClone 字段丢失** — 仅传 name/description/model 3 字段,丢失 emoji/personality/communicationStyle/notes/nickname/scenarios/workspace 等 7+ 字段 | `kernel-agent.ts:74-97` | **P1** | 扩展 CreateAgentRequest 和 kernel-agent.ts 传递全部字段 | 通过 Kernel 创建 Agent 后检查 SQLite config 列 | +| M2-02 | **[P1] 两条通路创建逻辑严重不对等** — Gateway 构建完整 TOML manifest,Kernel 仅 3 字段 | `gateway-api.ts:60-106` vs `kernel-agent.ts:74-97` | **P1** | 统一两条通路的字段映射 | 分别用两种模式创建 Agent 对比结果 | +| M2-03 | AgentInfo 缺 message_count/created_at/updated_at | `kernel-types.ts:22-29` | P2 | 扩展前端 AgentInfo 类型 | 加载 Agent 列表检查字段 | +| M2-04 | updateClone 缺少人格/工作空间字段 | `kernel-agent.ts:109-134`, `agent.rs:50-61` | P2 | 扩展 AgentUpdateRequest | 更新 Agent 的 emoji/personality 验证生效 | +| M2-05 | 删除 Agent 不检查是否正在使用 | `agent.rs:143-160` | P2 | 检查活跃 session/流状态 | 删除正在聊天的 Agent | +| M2-06 | Agent 切换不通知 Kernel defaultAgentId | `conversationStore.ts:249-303` | P2 | 切换时调用 client.setDefaultAgentId() | 切换后发消息检查 agentId | +| M2-07 | 切换 Agent 不取消进行中的流 | `conversationStore.ts:249-303` | P2 | 切换前检查并 cancelStream | 流式中切换 Agent 验证 | +| M2-08 | 创建/更新无参数验证(空名/温度范围) | `agent.rs:70-104` | P2 | 添加 name 非空、temperature [0,2]、max_tokens >0 | 传入空名/异常温度 | +| M2-09 | 删除后 selectedAgent 可能指向已删除 Agent | `agentStore.ts` | P2 | 删除后自动切换到第一个可用 Agent | 删除当前选中 Agent | +| M2-10 | SQLite 外键级联依赖 PRAGMA foreign_keys | `schema.rs:22-24` | P2 | 确认初始化时执行 PRAGMA foreign_keys = ON | 检查 schema.rs 初始化代码 | +| M2-11 | 默认 provider/model 前后端不一致 | `kernel-agent.ts:39-43` vs `agent.rs:15-18` | P3 | 统一默认值为 config.rs 中的值 | 不传 provider 检查实际使用 | +| M2-12 | agentStore 共享 isLoading 无操作级状态 | `agentStore.ts` | P3 | 改为 per-operation loading | 同时触发 load + create | +| M2-13 | Agent 列表变更不自动同步 conversationStore | `agentStore.ts` | P3 | CRUD 后触发 conversationStore.syncAgents() | 创建 Agent 后检查选择器 | +| M2-14 | system_prompt 双重持久化可能不一致 | `agentStore.ts:218-235` | P3 | 明确单一数据源 | 分别更新两处检查同步 | + +--- + +## 4. 改进建议 + +### 短期修复(按优先级) + +1. **[P1]** 扩展 `kernel-agent.ts` 的 `createClone()` 传递全部字段(对齐 gateway-api.ts) +2. **[P1]** 扩展 `CreateAgentRequest` struct 支持人格字段 +3. **[P2]** 添加 Agent CRUD 参数验证 +4. **[P2]** Agent 切换时同步 defaultAgentId + 取消流 + +### 长期架构建议 + +- 统一 Kernel 和 Gateway 两条通路的 Agent 创建逻辑 +- 为 agentStore 添加 per-operation 状态机 +- 明确 system_prompt 的单一数据源(AgentConfig vs Identity 文件) + +--- + +## 5. 健康度评分 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 链路完整性 | **65/100** | Kernel 创建/更新通路字段丢失是核心断链 | +| 参数一致性 | **70/100** | 类型基本匹配但字段覆盖不完整 | +| 边界处理 | **55/100** | 缺少参数验证、删除不检查活跃状态、切换不取消流 | +| 状态管理 | **60/100** | 缺操作级状态、跨 Store 同步不完整 | +| 安全资源 | **85/100** | ID 验证完善,主要风险在级联删除 | + +**综合健康度: 67/100** + +两个 P1 级断链(Kernel 创建字段丢失 + 双通路不对等)是此模块的核心问题。修复后预计可提升至 85+。 diff --git a/docs/features/audit-v12/M3-hands-system.md b/docs/features/audit-v12/M3-hands-system.md new file mode 100644 index 0000000..329a26c --- /dev/null +++ b/docs/features/audit-v12/M3-hands-system.md @@ -0,0 +1,170 @@ +# 模块 M3 Hands 自主能力 审计报告 + +> **审计版本**: V12 +> **审计日期**: 2026-04-04 +> **审计范围**: 11 个自主能力包触发/审批/执行/回调完整流程 + +--- + +## 1. 模块概况 + +### 功能描述 +9 个已实现 Hand(Browser/Researcher/Collector/Clip/Twitter/Whiteboard/Slideshow/Speech/Quiz),含触发、审批、执行、事件回调全流程。 + +### 涉及文件清单 + +**前端** +- Store: `desktop/src/store/handStore.ts`, `desktop/src/store/browserHandStore.ts` +- Client: `desktop/src/lib/kernel-hands.ts`, `desktop/src/lib/browser-client.ts` +- 自主管理: `desktop/src/lib/autonomy-manager.ts` +- UI: `desktop/src/components/Automation/` (ApprovalQueue, AutomationPanel, ExecutionResult 等) + +**后端 (Rust)** +- Tauri 命令: `desktop/src-tauri/src/kernel_commands/hand.rs`, `approval.rs` +- Kernel: `crates/zclaw-kernel/src/kernel/hands.rs`, `crates/zclaw-kernel/src/kernel/approvals.rs` +- Registry: `crates/zclaw-hands/src/registry.rs` +- Hand 实现: `crates/zclaw-hands/src/hands/` (9 个 .rs 文件) +- 配置: `hands/*.HAND.toml` (9 个) + +### 调用链路图 + +``` +用户触发 Hand + → AutomationPanel.triggerHand(id, params) + → handStore.triggerHand() + → canAutoExecute('hand_trigger', 5) [前端自主性检查] + → client.triggerHand(name, params, autonomyLevel) + → KernelClient.triggerHand() → invoke('hand_execute', { id, input, autonomyLevel }) + → Rust hand_execute: + ├─ supervised 模式 → 创建 ApprovalEntry → 返回 pending_approval + ├─ needs_approval + 非 autonomous → 创建 ApprovalEntry + └─ autonomous 或无需审批 → kernel.execute_hand() + → HandRegistry.execute() → Hand::execute() + → 创建 HandRun (Pending→Running→Completed/Failed) + → emit("hand-execution-complete") + +审批流程: + ApprovalQueue → handStore.respondToApproval() + → invoke('approval_respond', { id, approved, reason }) + → kernel.respond_to_approval() + → tokio::spawn 异步执行 Hand + 轮询完成 + → emit("hand-execution-complete") + +前端事件接收: + kernel-hands.ts onHandExecutionComplete → listen("hand-execution-complete") + → [问题: 前端未注册全局监听] +``` + +--- + +## 2. 五维检查结果 + +### 2.1 链路完整性 + +| 链路 | 起点 | 终点 | 状态 | 备注 | +|------|------|------|------|------| +| Hand 列举 | AutomationPanel | Registry.list() | ✅ 连通 | | +| Hand 触发(直接执行) | handStore | Hand::execute() | ⚠️ **run_id 丢失** | hand.rs:184 丢弃 _run_id | +| Hand 触发(需审批) | handStore | ApprovalEntry | ✅ 连通 | | +| 审批确认 | ApprovalQueue | Hand::execute() | ✅ 连通 | 两条重复路径 | +| hand-execution-complete 事件 | Rust emit | 前端 listen | ⚠️ **无人接收** | onHandExecutionComplete 未被调用 | +| Browser Hand (Rust) | BrowserHand::execute() | 浏览器操作 | ❌ **断裂** | 只返回结构化指令不执行 | +| Browser Hand (前端) | browserHandStore | browser-client → Rust browser_* 命令 | ✅ 连通 | 但绕过审批流程 | +| Hand 取消 | handStore | cancel_flag AtomicBool | ✅ 连通 | | +| Hand 运行状态查询 | handStore | Rust hand_run_status | ✅ 连通 | | + +### 2.2 参数/类型一致性 + +| 接口 | 前端参数 | 后端参数 | 一致性 | 备注 | +|------|---------|---------|--------|------| +| hand_execute | `{ id, input, autonomyLevel }` | `id, input, autonomy_level` | ✅ | camelCase 正确转换 | +| hand_approve | `{ handName, runId, approved, reason }` | `hand_name, run_id, approved, reason` | ✅ | | +| approval_respond | `{ id, approved, reason }` | `id, approved, reason` | ✅ | | +| hand_execute 返回值 | 期望 `{ instance_id, status }` | 实际 `{ success, output, error, durationMs }` | ❌ **不匹配** | 前端拿不到 run_id | +| TOML permissions/rate_limit | 定义了但未使用 | HandConfig 只有基础字段 | ❌ **配置未生效** | | + +### 2.3 边界与错误处理 + +| 场景 | 预期行为 | 实际行为 | 级别 | +|------|---------|---------|------| +| 触发不存在的 Hand | 返回错误 | ✅ ZclawError::NotFound | P4 正确 | +| 并发触发同一 Hand | 限流/拒绝 | ❌ 无保护,可无限并发 | **P1** | +| Hand 执行超时 | 超时终止 | ❌ `timeout_secs` 定义但未使用 | **P1** | +| 审批不响应 | 超时清理 | ❌ 永不过期,pending 无限积累 | P2 | +| Hand 执行失败 | 错误传播到 UI | ✅ 错误传播链路完整 | P4 正确 | +| max_concurrent 限制 | 限制并发数 | ❌ TOML 定义但未实现 | P2 | +| Clip Hand 路径含单引号 | 正常执行 | ⚠️ FFmpeg concat 文件解析可能异常 | P3 | + +### 2.4 状态管理 + +| Store/组件 | 状态机完整性 | 持久化 | 竞态风险 | +|-----------|------------|--------|---------| +| handStore | ✅ idle/running/needs_approval/error/unavailable/setup_needed | ❌ 无持久化 | ⚠️ triggerHand 后依赖异步 loadHands 更新状态 | +| browserHandStore | ✅ sessions/execution/logs/templates | ❌ 无持久化 | ⚠️ 独立于 handStore,绕过审批 | +| autonomy-manager | ✅ 三级自主+三级风险 | ✅ localStorage | ❌ 仅前端,后端无强制 | + +### 2.5 安全与资源 + +| 检查项 | 状态 | 说明 | +|--------|------|------| +| 审批安全(跨 Hand 攻击防护) | ✅ | entry.hand_id == hand_name 校验 | +| Approval ID 不可预测 | ✅ | UUID v4 | +| Browser Hand 绕过审批 | ❌ **P1** | browserHandStore 完全绕过 autonomy-manager | +| TOML requires_approval 形同虚设 | ❌ **P1** | browserHandStore 不走审批流程 | +| 审批内存不释放 | ⚠️ P2 | Vec 无限增长 | +| Clip Hand 命令注入防护 | ✅ | 使用 Command::new + .args() 非 shell | + +--- + +## 3. 问题清单 + +| ID | 描述 | 文件:行号 | 级别 | 修复建议 | 验证方法 | +|----|------|----------|------|---------|---------| +| M3-01 | **hand_execute 丢弃 run_id** — 前端拿不到执行 ID,无法追踪运行状态 | `hand.rs:184` | **P1** | 将 HandRunId 包含在返回结果中 | triggerHand 后检查返回值含 runId | +| M3-02 | **Browser Hand 双路径断裂** — Rust execute() 只返回结构化指令不执行实际操作 | `browser.rs:191-343` | **P1** | 统一路径:要么 Rust 端执行操作,要么移除 Rust BrowserHand | 通过 handStore 触发 browser 验证 | +| M3-03 | **browserHandStore 绕过审批** — 无视 TOML requires_approval = true | `browserHandStore.ts:222-339` | **P1** | browserHandStore 操作前检查 autonomy-manager | 在 autonomous=false 下执行浏览器操作 | +| M3-04 | **max_concurrent 未实现** — TOML 定义但运行时无检查 | `kernel/hands.rs` | **P1** | 在 execute_hand 中添加并发计数检查 | 并发触发同一 Hand | +| M3-05 | **timeout_secs 未实现** — Hand 可无限挂起 | `hand.rs:47` | **P1** | 在 execute 中使用 tokio::time::timeout 包装 | 启动长时间 Hand 验证超时 | +| M3-06 | hand_execute 返回值类型不匹配 | `kernel-hands.ts:94` vs Rust `HandResult` | **P2** | 统一返回类型定义 | 检查 triggerHand 返回值 | +| M3-07 | hand-execution-complete 事件前端无人监听 | `kernel-hands.ts:195` | **P2** | 应用初始化时注册全局监听 | 审批执行完成后检查 UI 更新 | +| M3-08 | 审批条目永不过期,内存不释放 | `approvals.rs:16-19` | **P2** | 添加 expires_at + 定期清理 | 检查长期运行后的 pending_approvals 大小 | +| M3-09 | 重复审批确认路径(hand_approve vs approval_respond) | `hand.rs:196-295` vs `approval.rs:54-142` | **P2** | 统一为单一审批路径 | 检查两路径是否行为一致 | +| M3-10 | tool_count/metric_count 硬编码为 0 | `hand.rs:82-83` | **P2** | 从 Hand 实际能力中计算 | 检查 hand_list 返回值 | +| M3-11 | TOML permissions/rate_limit/audit 配置未被读取 | `hands/*.HAND.toml` vs `hand.rs:10-32` | **P3** | 扩展 HandConfig 或在运行时解析 | 修改 TOML 验证行为无变化 | +| M3-12 | hand_trigger 在 autonomy-manager 中永远不自动允许 | `autonomy-manager.ts:268` | **P3** | 修正映射逻辑 | 设为 autonomous 后触发 Hand | +| M3-13 | Clip Hand concat 文件路径单引号未转义 | `clip.rs:448` | **P3** | 转义路径中的单引号 | 使用含单引号的路径执行 concat | + +--- + +## 4. 改进建议 + +### 短期修复(按优先级) + +1. **[P1]** 修复 `hand_execute` 返回 `run_id`:在 HandResult 中添加 `run_id` 字段 +2. **[P1]** 统一 Browser Hand 路径:移除 Rust BrowserHand 的假执行,让 handStore 走 browserHandStore +3. **[P1]** browserHandStore 集成审批流程:在 executeTemplate 前检查 autonomy-manager +4. **[P1]** 实现 `max_concurrent` 并发限制 +5. **[P1]** 实现 `timeout_secs` 超时保护 + +### 长期架构建议 + +- 注册 `hand-execution-complete` 全局监听器到 handStore +- 统一审批路径(移除重复的 hand_approve / approval_respond) +- 添加审批 TTL 和自动清理 +- 扩展 HandConfig 解析 TOML 中的权限/限流/审计配置 + +--- + +## 5. 健康度评分 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 链路完整性 | **55/100** | Browser Hand 断裂、事件无人接收、run_id 丢失 | +| 参数一致性 | **70/100** | invoke 参数匹配但返回值不匹配、TOML 配置未生效 | +| 边界处理 | **50/100** | 无并发限制、无超时、审批不过期 | +| 状态管理 | **60/100** | 基本状态机存在但 browserHandStore 独立运作 | +| 安全资源 | **55/100** | Browser Hand 绕过审批是核心安全问题 | + +**综合健康度: 58/100** + +5 个 P1 级问题集中在此模块(run_id 丢失、Browser 断裂、绕过审批、无并发限制、无超时)。修复 P1 后预计可提升至 75+。 diff --git a/docs/features/audit-v12/M4-intelligence-layer.md b/docs/features/audit-v12/M4-intelligence-layer.md new file mode 100644 index 0000000..064d82e --- /dev/null +++ b/docs/features/audit-v12/M4-intelligence-layer.md @@ -0,0 +1,162 @@ +# 模块 M4 智能层 审计报告 + +> **审计版本**: V12 +> **审计日期**: 2026-04-04 +> **审计范围**: 记忆/身份演化/反思引擎/心跳巡检/自主授权/上下文压缩 6 个子模块 + +--- + +## 1. 模块概况 + +### 功能描述 +ZCLAW 智能层提供 Agent 的"认知能力":跨会话记忆、人格演化、行为反思、主动巡检、自主决策授权、上下文压缩。 + +### 涉及文件清单 + +**前端** +- 记忆: `store/memoryGraphStore.ts`, `lib/intelligence-client/memory.ts`, `components/MemoryPanel.tsx` +- 身份: `lib/intelligence-client/identity.ts`, `components/AgentOnboardingWizard.tsx` +- 反思: `lib/intelligence-client/reflection.ts`, `components/ReflectionLog.tsx` +- 心跳: `lib/intelligence-client/heartbeat.ts` +- 自主: `lib/autonomy-manager.ts`, `components/AutonomyConfig.tsx` +- 压缩: `lib/intelligence-client/fallback-compactor.ts` + +**后端 (Rust)** +- Intelligence 引擎: `desktop/src-tauri/src/intelligence/` (memory.rs, identity.rs, reflection.rs, heartbeat.rs, compactor.rs) +- Runtime 中间件: `crates/zclaw-runtime/src/middleware/` (memory.rs, compaction.rs) +- 存储: `crates/zclaw-growth/src/storage/sqlite.rs` +- Kernel 集成: `crates/zclaw-kernel/src/kernel/messaging.rs` + +--- + +## 2. 子模块逐个分析 + +### 2.1 Agent 记忆 + +| 检查维度 | 状态 | 详情 | +|---------|------|------| +| 链路完整性 | ⚠️ | **双数据库问题** — 前端 UI 用 `PersistentMemoryStore`(LIKE 搜索),Runtime 中间件用 `SqliteStorage`(FTS5 搜索),两套独立 SQLite | +| 参数一致性 | ⚠️ | 前端 `types?: MemoryType[]`(数组) vs 后端 `memory_type?: string`(单值) | +| 边界处理 | ✅ | 错误传播完整,embedding 失败优雅降级 | +| 状态管理 | ⚠️ | 前端手动刷新,无自动同步 | +| 安全 | ⚠️ | 无 content 长度限制,无 importance 范围验证 | + +**关键问题 [P0]**: 双数据库导致: +- 用户手动保存的记忆不出现在对话记忆注入中 +- Agent 自动提取的记忆不出现在前端记忆面板 +- 前端记忆搜索用的是 LIKE 模糊匹配,文档声称的 FTS5 仅在 Runtime 层生效 + +### 2.2 身份演化 + +| 检查维度 | 状态 | 详情 | +|---------|------|------| +| 链路完整性 | ✅ | identity_get/propose_change/approve_proposal 全部注册,UI 组件完整 | +| 参数一致性 | ✅ | Soul/Instructions 枚举匹配正确 | +| 边界处理 | ✅ | 快照上限 50,并发 Mutex 保护 | +| 状态管理 | ✅ | 文件系统持久化(store.json),重启恢复 | +| 安全 | ⚠️ | store.json 无加密,proposal_id 可预测 | + +**关键问题**: user_profile 注入被注释掉(防止对话泄漏),心跳人格改进不会自动创建 identity proposal。整体功能可用但需手动触发。 + +### 2.3 反思引擎 + +| 检查维度 | 状态 | 详情 | +|---------|------|------| +| 链路完整性 | ⚠️ | 命令全部注册,但 **LLM 驱动未接入** | +| 参数一致性 | ✅ | 类型转换正确 | +| 边界处理 | ✅ | LLM 失败回退到规则分析,空记忆列表处理正确 | +| 状态管理 | ✅ | 对话计数器 + 双重触发条件 | +| 安全 | ✅ | 持久化 fire-and-forget 不影响主流程 | + +**关键问题 [P0]**: `reflection_reflect` Tauri 命令传入 `driver=None`,即使配置 `use_llm=true` 也只用规则分析。LLM 分析路径从未在 Tauri 桌面端生效。 + +### 2.4 心跳巡检 + +| 检查维度 | 状态 | 详情 | +|---------|------|------| +| 链路完整性 | ✅ | 后端引擎完整(5 项内置检查) | +| 参数一致性 | ✅ | 安静时间解析正确 | +| 边界处理 | ⚠️ | interval_minutes 无下限验证 | +| 状态管理 | ⚠️ | **不自动启动**,需前端调用 heartbeat_init + heartbeat_start | +| 安全 | ⚠️ | 全局静态状态,所有 agent 共享 | + +**关键问题 [P1]**: 心跳引擎完全依赖前端主动初始化和数据同步。`MEMORY_STATS_CACHE`、`LAST_INTERACTION`、`CORRECTION_COUNTERS` 三个全局缓存需前端定期调用命令填充,否则心跳检查返回空结果。 + +### 2.5 自主授权 + +| 检查维度 | 状态 | 详情 | +|---------|------|------| +| 链路完整性 | ❌ | **纯前端实现**,无后端 Rust 集成 | +| 参数一致性 | ⚠️ | workflow_trigger 映射到 autoCompaction,语义不匹配 | +| 边界处理 | ✅ | 高风险操作强制审批,自主模式禁止自我修改 | +| 状态管理 | ✅ | localStorage 持久化,审计日志 100 条上限 | +| 安全 | ❌ | **后端无强制检查**,恶意用户可绕过前端直接调用 Tauri 命令 | + +**关键问题 [P1]**: 自主授权仅是前端"咨询层",后端 MemoryMiddleware 等不检查自主授权级别。TOML 的 `requires_approval` 仅在 hand_execute 路径生效,其他操作完全绕过。 + +### 2.6 上下文压缩 + +| 检查维度 | 状态 | 详情 | +|---------|------|------| +| 链路完整性 | ✅ | Runtime 中间件自动触发,Tauri 命令手动触发 | +| 参数一致性 | ⚠️ | 两套 CompactionConfig 默认值不同 | +| 边界处理 | ✅ | 空消息处理正确,LLM 失败回退 | +| 状态管理 | ✅ | 无状态设计,校准因子全局共享 | +| 安全 | ✅ | 校准使用 EMA + clamp,合理 | + +**关键问题**: Tauri 层和 Runtime 层各有独立的压缩实现,代码重复但配置不同。Runtime 层是实际生效路径。 + +--- + +## 3. 问题清单 + +| ID | 描述 | 文件:行号 | 级别 | +|----|------|----------|------| +| M4-01 | **[P0] 双数据库**: PersistentMemoryStore vs SqliteStorage 数据不共享 | `memory/persistent.rs` vs `zclaw-growth/src/storage/sqlite.rs` | **P0** | +| M4-02 | **[P0] 反思引擎 LLM 未接入**: reflection_reflect 传 driver=None | `reflection.rs:759-766` | **P0** | +| M4-03 | **[P1] 心跳不自动启动**: 需前端手动初始化 | `heartbeat.rs` | **P1** | +| M4-04 | **[P1] 自主授权后端无强制**: 仅前端咨询层 | `autonomy-manager.ts` | **P1** | +| M4-05 | 前端记忆搜索用 LIKE 非 FTS5 | `persistent.rs search()` | **P1** | +| M4-06 | 前端 types 数组 vs 后端单值 | `MemorySearchOptions` 类型定义 | **P2** | +| M4-07 | 记忆 content 无长度限制 | `memory_commands` | **P2** | +| M4-08 | tags 参数标注 dead_code 未使用 | `persistent.rs` | **P2** | +| M4-09 | heartbeat interval_minutes 无下限验证 | `heartbeat.rs` | **P2** | +| M4-10 | identity proposal_id 可预测 | `identity.rs rand_id()` | **P2** | +| M4-11 | store.json 无加密 | `identity.rs` | **P2** | +| M4-12 | 自主授权 workflow_trigger 映射错误 | `autonomy-manager.ts:271` | **P2** | +| M4-13 | 两套压缩实现代码重复 | `compactor.rs` vs `compaction.rs` | **P3** | +| M4-14 | heartbeat broadcast subscribe 未被使用 | `heartbeat.rs` | **P3** | +| M4-15 | heartbeat 历史截断 split_off 语义 | `heartbeat.rs` | **P3** | + +--- + +## 4. 改进建议 + +### 短期修复(按优先级) + +1. **[P0]** 统一存储层:合并 PersistentMemoryStore 和 SqliteStorage,或让前端搜索也查询 SqliteStorage +2. **[P0]** 在 reflection_reflect Tauri 命令中传入实际 LLM 驱动 +3. **[P1]** 自动启动心跳引擎(在 agent_chat_stream 或 Kernel 初始化中) +4. **[P1]** 将自主授权关键检查移到 Rust 后端 + +### 长期架构建议 + +- 前端记忆搜索升级为 FTS5 +- 移除 Tauri 层的重复压缩实现,统一使用 Runtime 中间件 +- identity 持久化改为 SQLite + +--- + +## 5. 健康度评分 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 链路完整性 | **50/100** | 双数据库是架构级问题,LLM/心跳未接入 | +| 参数一致性 | **65/100** | 基本匹配但有多处类型/映射不一致 | +| 边界处理 | **70/100** | 整体较好,缺少输入验证 | +| 状态管理 | **65/100** | 多数子模块缺少自动触发/同步 | +| 安全资源 | **55/100** | 自主授权不强制、store.json 不加密 | + +**综合健康度: 61/100** + +两个 P0 级问题(双数据库 + 反思 LLM 未接入)是此模块的核心架构缺陷。智能层"写了但没接通"的问题最集中。 diff --git a/docs/features/audit-v12/M5-skill-ecosystem.md b/docs/features/audit-v12/M5-skill-ecosystem.md new file mode 100644 index 0000000..15b29c1 --- /dev/null +++ b/docs/features/audit-v12/M5-skill-ecosystem.md @@ -0,0 +1,31 @@ +# 模块 M5 技能生态 审计报告 + +> **审计版本**: V12 | **日期**: 2026-04-04 + +## 1. 链路完整性 + +| 链路 | 状态 | 备注 | +|------|------|------| +| 技能发现(discover) | ✅ | loader.rs → skill_list Tauri 命令 | +| 技能加载(load) | ✅ | SKILL.md YAML frontmatter 解析 | +| 技能注册(register) | ✅ | Registry HashMap + Tauri skill_list | +| 技能索引(index) | ✅ | SkillIndexMiddleware 注入 system prompt | +| 语义路由(route) | ✅ | TF-IDF + 可选 embedding + LLM fallback | +| 技能执行(execute) | ✅ | PromptOnly/Python/Shell 三模式 | +| 技能 CRUD | ✅ | create/update/delete 写磁盘 SKILL.md | +| 技能编排(orchestration) | ✅ | DAG 并行执行+重试+取消 | + +**链路完整性评分: 92/100** + +## 2. 问题清单 + +| ID | 文件:行号 | 级别 | 描述 | 修复建议 | +|----|----------|------|------|---------| +| M5-01 | `skill-discovery.ts:110-123` | **P1** | convertFromBackend() 将 `tags` 误映射为 `triggers`,`tags[0]` 映射为 `category`,丢失真正的 triggers 数据 | 改为 `triggers: backend.triggers`, `category: backend.category` | +| M5-02 | `loader.rs:49-104` | P2 | SKILL.md frontmatter 不解析 `tools` 字段,75 个 SKILL.md 的 tools 定义被静默忽略 | 增加 tools 字段提取 | +| M5-03 | `runner.rs:83` | P2 | PythonSkill 硬编码 `python3`,Windows 上通常是 `python`,Python 模式技能在 Windows 失败 | 条件编译或运行时检测 | +| M5-04 | `loader.rs:59-64` | P3 | YAML triggers 解析引号剥离只处理双引号 | 统一处理单引号和无引号 | +| M5-05 | `runner.rs:130-170` | P3 | ShellSkill duration_ms 未设置,始终为 None | 移除 `_` 前缀赋值到 SkillResult | +| M5-06 | `SkillCard.tsx:35-45` | P3 | CATEGORY_CONFIG 只覆盖 9 种分类,未匹配分类显示灰色 | 扩展或动态生成 | + +**综合健康度: 85/100** — 核心链路完整,前端 triggers 映射错误是唯一 P1。 diff --git a/docs/features/audit-v12/M6-pipeline-workflow.md b/docs/features/audit-v12/M6-pipeline-workflow.md new file mode 100644 index 0000000..163f721 --- /dev/null +++ b/docs/features/audit-v12/M6-pipeline-workflow.md @@ -0,0 +1,32 @@ +# 模块 M6 Pipeline 工作流 审计报告 + +> **审计版本**: V12 | **日期**: 2026-04-04 + +## 1. 链路完整性 + +| 链路 | 状态 | 备注 | +|------|------|------| +| Pipeline 发现(list) | ⚠️ | 只用 v1 解析器,v2 格式被静默丢弃 | +| Pipeline 推荐(Intent) | ❌ | route_intent Tauri 命令未注册,IntentInput 组件无法工作 | +| Pipeline 解析 | ✅ | v1 + v2 解析器均存在 | +| Pipeline 执行 | ✅ | 9 种 Action + 并行 + 取消 + 重试 | +| Pipeline 监控 | ✅ | get_run/get_progress/list_runs + 取消 | +| Pipeline CRUD | ⚠️ | create 只映射 Hand 类型,丢失 LLM/Parallel/Condition | +| WorkflowBuilder 编辑 | ⚠️ | inputs 映射为 steps,语义错误 | + +**链路完整性评分: 78/100** + +## 2. 问题清单 + +| ID | 文件:行号 | 级别 | 描述 | 修复建议 | +|----|----------|------|------|---------| +| M6-01 | `intent.rs` / `IntentInput.tsx` | **P1** | route_intent Tauri 命令未注册,IntentInput 完全无法工作 | 在 pipeline_commands 中增加 route_intent 命令 | +| M6-02 | `discovery.rs:59` | **P1** | pipeline_list 只用 v1 解析器,v2 格式 Pipeline 被静默丢弃 | 先尝试 v2 再 fallback v1 | +| M6-03 | `crud.rs:78-92` | P2 | pipeline_create 将所有步骤映射为 Hand 类型 | 扩展支持完整 Action 类型 | +| M6-04 | `workflowStore.ts:386-389` | P2 | pipeline.inputs.length 映射为 steps 数量,语义错误 | 后端返回实际步骤数 | +| M6-05 | `workflowStore.ts:405-410` | P2 | getWorkflow 将 inputs 映射为 WorkflowDetail.steps | 需后端返回真实步骤结构 | +| M6-06 | `classroom.yaml:99,154` | P2 | 管道操作符 `\|` 在 context.resolve() 中不支持 | 实现 `\|` 支持或改用其他语法 | +| M6-07 | `campaign.yaml:87-90` | P2 | `{{topic}}` (Mustache) 和 `${inputs.topic}` (Zclaw) 混用 | 统一为 `${}` 格式 | +| M6-08 | `executor.rs:467-484` | P3 | get_progress() 百分比计算过于粗糙(0/50/100) | 基于完成步骤数计算真实进度 | + +**综合健康度: 72/100** — 执行引擎健壮但发现链路 v1/v2 分裂和前端映射错误是核心风险。 diff --git a/docs/features/audit-v12/M7-saas-desktop.md b/docs/features/audit-v12/M7-saas-desktop.md new file mode 100644 index 0000000..c5e67db --- /dev/null +++ b/docs/features/audit-v12/M7-saas-desktop.md @@ -0,0 +1,34 @@ +# 模块 M7 SaaS 平台 Desktop 端 审计报告 + +> **审计版本**: V12 | **日期**: 2026-04-04 + +## 1. 链路完整性 + +| 链路 | 状态 | 备注 | +|------|------|------| +| 登录(无/带 TOTP) | ✅ | POST /auth/login | +| JWT 获取/刷新 | ✅ | HttpOnly Cookie + refresh 端点 | +| Session 恢复 | ✅ | restoreSession + /auth/me | +| 模型列表 | ✅ | GET /relay/models | +| 聊天中继(流式/非流式) | ✅ | POST /relay/chat/completions + SSE | +| 中继任务列表/重试 | ✅ | GET/POST /relay/tasks | +| TOTP 设置/验证/禁用 | ✅ | 三个端点完整 | +| 计费(计划/订阅/支付) | ✅ | 全部端点对齐 | +| 配置同步(拉/推) | ✅ | config/pull + config/diff + config/sync | +| 设备注册/心跳 | ✅ | 5 分钟间隔心跳 | +| 配置迁移 | ⚠️ | PUT 路径参数语义错误 | + +**链路完整性评分: 90/100** + +## 2. 问题清单 + +| ID | 文件:行号 | 级别 | 描述 | 修复建议 | +|----|----------|------|------|---------| +| M7-01 | `SaaSLogin.tsx:59` | P2 | 前端密码最少 6 字符 vs 后端最少 8 字符 | 统一为 8 | +| M7-02 | `ConfigMigrationWizard.tsx:118` | **P1** | PUT 使用布尔值 exists 作为路径参数,应为 config item ID | 修正为实际 ID | +| M7-03 | `TOTPSettings.tsx:109` | P2 | QR Code 通过外部服务生成,secret 明文发送到 api.qrserver.com | 改用本地 QR 库 | +| M7-04 | `saas-client.ts:66` | **P1** | refreshToken() 未传 refresh_token body,Tauri 非浏览器可能无 cookie | 验证 cookie 自动附加或显式传 body | +| M7-05 | `saasStore.ts:212` | P3 | saveSaaSSession fire-and-forget,失败无感知 | 添加错误回退 | +| M7-06 | `saas-relay-client.ts:113` | P3 | chatStream 不传 sessionKey/agentId 等参数 | 确认是否需要透传 | + +**综合健康度: 85/100** — 核心链路完整,配置迁移路径参数和 token 刷新是主要风险。 diff --git a/docs/features/audit-v12/M8-admin-v2.md b/docs/features/audit-v12/M8-admin-v2.md new file mode 100644 index 0000000..6184459 --- /dev/null +++ b/docs/features/audit-v12/M8-admin-v2.md @@ -0,0 +1,41 @@ +# 模块 M8 Admin V2 管理后台 审计报告 + +> **审计版本**: V12 | **日期**: 2026-04-04 + +## 1. 链路完整性 + +14 个页面全部与后端 API 对齐。认证流程(HttpOnly Cookie + JWT)完整。权限检查一致。 + +| 页面 | CRUD 完整性 | 权限 | 状态 | +|------|------------|------|------| +| Login | R(认证) | 公开 | ✅ | +| Dashboard | R | admin | ✅ | +| Accounts | R + U | admin | ✅ | +| ModelServices | CRUD | provider/model:manage | ✅ | +| Relay | R only | relay:use/admin | ⚠️ 缺 retry | +| Billing | R + Payment | billing | ✅ | +| Logs | R | admin | ✅ | +| Prompts | CRUD + 版本 | prompt:manage | ✅ | +| Roles | CRUD | role:manage | ✅ | +| Knowledge | CRUD + 搜索 + 版本 | knowledge:manage | ✅ | +| ScheduledTasks | CRUD | scheduler:manage | ✅ | +| AgentTemplates | C + R + U + D | template:manage | ✅ | +| Usage | R | admin | ✅ | +| Config | R + U | admin | ✅ | + +**链路完整性评分: 88/100** + +## 2. 问题清单 + +| ID | 文件:行号 | 级别 | 描述 | 修复建议 | +|----|----------|------|------|---------| +| M8-01 | `api-keys.ts:6` | P2 | API Key 列表只返回当前用户的,Admin 需要全局视角 | 后端新增 admin 级 key 列表端点 | +| M8-02 | `Relay.tsx` | P2 | 缺少 retry 操作,后端有但前端未暴露 | 添加 retry 按钮 | +| M8-03 | `authStore.ts:13-25` | P2 | ROLE_PERMISSIONS 硬编码前端,与后端可能不同步 | 改为从 API 动态获取 | +| M8-04 | `config.ts:10` | P3 | config list 返回类型不一致(数组 vs 分页) | 统一返回类型 | +| M8-05 | `Accounts.tsx:122` | P3 | 引用 last_login_at 但后端 AccountPublic 无此字段 | 后端添加字段 | +| M8-06 | `Login.tsx:39` | P3 | TOTP 检测靠字符串匹配('TOTP'),不够健壮 | 改为结构化 error code | +| M8-07 | `agent-templates.ts:31` | P3 | update 用 POST 而非 PUT/PATCH,REST 语义不规范 | 前后端统一为 PUT | +| M8-08 | `billing.ts` | P3 | 类型定义在 service 内重复,未复用 @/types | 统一到 types 目录 | + +**综合健康度: 82/100** — 14 页面 CRUD 整体对齐良好,前端权限硬编码和缺少 retry 是主要扣分项。 diff --git a/docs/features/audit-v12/M9-communication-security.md b/docs/features/audit-v12/M9-communication-security.md new file mode 100644 index 0000000..9ee5460 --- /dev/null +++ b/docs/features/audit-v12/M9-communication-security.md @@ -0,0 +1,36 @@ +# 模块 M9 通信与安全 审计报告 + +> **审计版本**: V12 | **日期**: 2026-04-04 + +## 1. 链路完整性 + +| 链路 | 状态 | 备注 | +|------|------|------| +| Kernel 模式连接 | ✅ | invoke → kernel_init → Kernel 启动 | +| Gateway 模式连接 | ✅ | WebSocket + Ed25519 签名握手 | +| SaaS Relay 模式连接 | ✅ | HTTP Cookie + SSE 流 | +| 三模式自动切换 | ✅ | connectionStore 按 admin routing → SaaS session → isTauriRuntime 分支 | +| Ed25519 设备认证 | ✅ | nacl.sign → SHA-256 derive deviceId → signDeviceAuth | +| 安全存储 (Keyring) | ✅ | OS Keyring 优先 → AES-256-GCM localStorage 降级 | +| MCP 工具发现 | ✅ | startMcpService → initialize → list_tools → 注册 | +| MCP 工具执行 | ✅ | callMcpTool → JSON-RPC tools/call → 解析 content blocks | +| Gateway 心跳/重连 | ✅ | 30s ping → 10s timeout → 3次丢失 → 指数退避重连(1.5^x, max 30s, 10次) | +| SaaS 降级 | ✅ | 401 → logout / 其他 → saasReachable:false → fall through Kernel | + +**链路完整性评分: 90/100** + +## 2. 问题清单 + +| ID | 文件 | 级别 | 描述 | 修复建议 | +|----|------|------|------|---------| +| M9-01 | `connectionStore.ts:501` | P2 | SaaS Relay 分支用 `require()` (CommonJS),ESM+Vite 环境可能报错 | 改为 `await import()` | +| M9-02 | `connectionStore.ts:546` | P2 | 同 M9-01,浏览器模式同样使用 `require()` | 改为 `await import()` | +| M9-03 | `secure-storage.ts:186-188` | P2 | Keyring 不可用时 master key 明文存 localStorage,作为加密根密钥暴露 | 从用户密码或设备指纹派生 master key | +| M9-04 | `gateway-client.ts:143` | P3 | REST 路径跳过 WebSocket 安全检查,非 localhost 无 TLS 强制 | REST 模式也增加 HTTPS 检查 | +| M9-05 | `gateway-client.ts:486-506` | P2 | chatStream() agentId 为空时异步获取,runId callback 注册时序问题 | 改为 async/await 串行处理 | +| M9-06 | `securityStore.ts:260-270` | P2 | Kernel 模式下未调用 setSecurityStoreClient(),审计日志加载不出 | Kernel 分支也设置 securityStore client | +| M9-07 | `gateway-client.ts:280` | P3 | 端口检测 `includes(':4200')` 过于简单 | 改为 URL 解析后精确比对 port | +| M9-08 | `secure-storage.ts:255-259` | P3 | legacy 解密全失败时 fallback 返回明文原始值 | 仅在版本匹配时 fallback | +| M9-09 | `crypto-utils.ts:14` | P3 | LEGACY_SALT 硬编码,v1 格式安全性依赖源码不可见 | 增加 v1→v2 迁移提示 | + +**综合健康度: 86/100** — 三种连接模式+Ed25519+MCP 全链路完整。master key localStorage 和 require 混用是主要扣分项。