feat: v0.4.0 四阶段迭代 — 安全加固 + 工程基线 + 架构重构 + 双 Provider 扩展

P0 安全修复:
- API Key 加密存储(safeStorage 密钥链,版本化前缀,历史明文平滑兼容)
- 间接提示注入防护(SecurityScanHook 工具结果深扫描,网络工具脱敏/本地工具警示分级)
- error:report IPC 断链修复(渲染进程错误上报落 electron-log + 审计)
- abort 信号贯通工具层(run_command/dev-tools 子进程随会话中断终止)
- run_command 沙箱加固(cd 系统目录/敏感文件读取拦截 + chcp 前缀剥离防解析退化)
- .env 真实生效(dotenv 回退加载,应用内配置优先)

P1 工程基础:
- ESLint 9 flat config + 全部 34 条存量 warnings 清零(零容忍基线)
- 测试基线 118 用例 11 文件(token/文件防护/权限/沙箱/注入/命令/引擎/注册表/审计链/摘要分层)
- test:electron 双模式(ELECTRON_RUN_AS_NODE 跑 Electron ABI,SQLite 套件全执行)
- SessionRecorder 多会话隔离 + 9 种 TRACE 事件补全(含最终轮 iteration_end)
- Provider 故障转移(重试耗尽/不可重试一次性切换 fallback + 前端通知)
- MCP 真就绪(等待全部连接完成再广播 tools:ready)
- SLO/HealthChecker 真实接入(60s 巡检 + 托盘状态)
- CONFIG_DEFAULTS 单一来源(消除 SEED 双源漂移)

P2 架构升级:
- handlers.ts 1940 行拆分为 13 个 IPC 域模块(防重入注册 + 多窗口广播)
- AgentEngineManager 每会话独立引擎(LRU 30 + adapter 工厂隔离 abort 信号)
- TaskOrchestrator EngineProvider 改造 + abortByParent 联动中断 SubAgent
- 会话摘要分层上下文(session_summaries 滚动摘要 + 截断游标清理防因果污染)
- 消息编辑重发/重新生成(truncateAfter IPC + store 动作 + UI)
- Markdown 导出 / WebSearch 并行抓取(并发 3)/ 记忆 TF 缓存 / 版本构建期注入

P3 能力扩展:
- OpenAI Adapter(o 系列推理模型 reasoning_effort/max_completion_tokens)
- Anthropic Adapter(原生 Messages API:tool_use 块/角色合并/thinking budget/图片 base64/SSE 事件机)
- 设置页/Onboarding 六 Provider 全链路接入
This commit is contained in:
2026-08-20 23:17:02 +08:00
parent b9f7ec5118
commit 2230bcec3f
90 changed files with 6581 additions and 2771 deletions
+108
View File
@@ -0,0 +1,108 @@
/**
* IPC Config Handlers — 配置管理域(P2-9 从 handlers.ts 拆分)
*
* config:get / config:set / config:setBatch。
* P2-9: set 与 setBatch 共享 applyConfigSideEffects(消除原 300 行重复逻辑)。
*/
import { ipcMain } from 'electron';
import type { IPCContext } from './context';
import { LLM_CONFIG_KEYS, applyConfigSideEffects, clearApiKeyOnProviderChange, maskSensitive } from './shared';
import log from 'electron-log';
export function registerConfigHandlers(ctx: IPCContext): void {
const { configService, auditService } = ctx;
ipcMain.handle('config:get', async (_event, key) => {
return configService.get(key);
});
ipcMain.handle('config:set', async (_event, key: unknown, value: unknown) => {
// M-42 修复: 参数校验
if (typeof key !== 'string' || !key.trim()) {
return { success: false, error: 'Invalid config key' };
}
// value 必须是可序列化的基本类型(string|number|boolean|null
if (value !== null && typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
return { success: false, error: 'Invalid config value: must be string, number, boolean, or null' };
}
// C-1 修复: Provider 切换时清空 API keyP2-9: 统一走共享函数)
clearApiKeyOnProviderChange(ctx, [{ key, value }]);
configService.set(key, value);
// M-42 修复: 敏感配置项脱敏后再写入审计日志
auditService.log({
sessionId: '',
eventType: 'config_change',
actor: 'user',
target: key,
details: { value: maskSensitive(key, value) },
outcome: 'success',
});
// P2-9: 统一副作用(reloadAdapter + Engine 同步 + 广播 + 工作空间路径)
const sideEffectError = await applyConfigSideEffects(ctx, [{ key, value }]);
if (sideEffectError) {
return { success: false, error: sideEffectError };
}
if (LLM_CONFIG_KEYS.includes(key)) {
log.info(`[CONFIG] LLM config changed (${key}), adapter reloaded`);
}
return { success: true };
});
// ===== v0.3.9: 批量保存配置(P2-9: 复用共享副作用逻辑) =====
// 设计原因:前端设置页一次保存多个字段,串行 config:set 会在中间态(如 provider
// 已改但 apiKey 还没保存)触发 reloadAdapter 失败。批量保存:先写入所有字段,
// 最后统一触发一次 reloadAdapter 和 Engine/Orchestrator 同步。
ipcMain.handle('config:setBatch', async (_event, entries: unknown) => {
// 参数校验:必须是 {key, value}[] 非空数组
if (!Array.isArray(entries) || entries.length === 0) {
return { success: false, error: 'Invalid entries: must be non-empty array of {key, value}' };
}
// 逐条校验每个元素的结构和类型
for (const entry of entries) {
if (!entry || typeof entry !== 'object') {
return { success: false, error: 'Invalid entry: must be {key, value} object' };
}
const e = entry as { key?: unknown; value?: unknown };
if (typeof e.key !== 'string' || !e.key.trim()) {
return { success: false, error: 'Invalid config key in batch' };
}
const v = e.value;
if (v !== null && typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {
return { success: false, error: `Invalid config value for key "${e.key}": must be string, number, boolean, or null` };
}
}
// v0.3.10 防御性修复: Provider 切换清空 apiKey 必须在循环前执行(共享函数内含说明)
clearApiKeyOnProviderChange(ctx, entries as Array<{ key: string; value: unknown }>);
// 第一步:逐条写入 configService + 审计日志(脱敏)
for (const entry of entries) {
const { key, value } = entry as { key: string; value: unknown };
configService.set(key, value);
auditService.log({
sessionId: '',
eventType: 'config_change',
actor: 'user',
target: key,
details: { value: maskSensitive(key, value) },
outcome: 'success',
});
}
// 第二步:统一副作用(reloadAdapter + Engine 同步 + 日志级别 + 广播 + 工作空间路径)
const sideEffectError = await applyConfigSideEffects(ctx, entries as Array<{ key: string; value: unknown }>);
if (sideEffectError) {
log.warn('[CONFIG] Config batch save side effects failed');
return { success: false, error: sideEffectError };
}
log.info(`[CONFIG] Config batch saved (${entries.length} keys)`);
return { success: true };
});
}