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:
@@ -23,6 +23,8 @@ import { app } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import log from 'electron-log';
|
||||
import { CONFIG_DEFAULTS } from './database.service';
|
||||
import { decryptConfigValue, encryptConfigValue, isSensitiveConfigKey } from '../utils/secure-config';
|
||||
|
||||
/** 全局配置文件路径(userData 下,与工作空间无关) */
|
||||
const GLOBAL_CONFIG_FILE = join(app.getPath('userData'), 'global-config.json');
|
||||
@@ -38,6 +40,8 @@ const GLOBAL_KEY_PREFIXES = [
|
||||
'deepseek.',
|
||||
'agnes.',
|
||||
'mimo.',
|
||||
'openai.',
|
||||
'anthropic.',
|
||||
'onboarding.',
|
||||
];
|
||||
|
||||
@@ -48,39 +52,13 @@ export function isGlobalKey(key: string): boolean {
|
||||
|
||||
/**
|
||||
* seedDefaults 灌入的默认值映射表(用于判断"DB 值等于默认值时回退全局层")
|
||||
* 必须与 database.service.ts 的 seedDefaults 保持同步
|
||||
*
|
||||
* P1-13: 由 database.service.ts 的 CONFIG_DEFAULTS 单一来源派生,
|
||||
* 不再手工维护副本(原双源曾出现漂移风险)。
|
||||
*/
|
||||
export const SEED_DEFAULTS: Record<string, unknown> = {
|
||||
'llm.provider': '',
|
||||
'llm.model': '',
|
||||
'llm.apiKey': '',
|
||||
'llm.baseURL': '',
|
||||
'llm.temperature': 0,
|
||||
'llm.maxTokens': 63488,
|
||||
'llm.fallbackProvider': '',
|
||||
'llm.fallbackModel': '',
|
||||
'agent.maxIterations': 20,
|
||||
'agent.totalTimeoutMs': 600000,
|
||||
'agent.enableThinking': true,
|
||||
'agent.thinkingEffort': 'high',
|
||||
'agent.enableReflection': false,
|
||||
'agent.confirmationTimeoutMs': 120000,
|
||||
'agent.toolExecutionTimeoutMs': 120000,
|
||||
'security.requireWriteConfirmation': true,
|
||||
'security.maxFileWriteSizeKB': 1024,
|
||||
'security.promptInjectionDefense': true,
|
||||
'ui.theme': 'auto',
|
||||
'ui.animationMode': 'auto',
|
||||
'ui.fontSize': 'medium',
|
||||
'logging.level': 'info',
|
||||
'logging.auditEnabled': true,
|
||||
'logging.traceEnabled': true,
|
||||
'ollama.numCtx': null,
|
||||
'deepseek.contextWindow': 1000000,
|
||||
'agnes.contextWindow': 1000000,
|
||||
'mimo.contextWindow': 1000000,
|
||||
'onboarding.completed': false,
|
||||
};
|
||||
export const SEED_DEFAULTS: Record<string, unknown> = Object.fromEntries(
|
||||
CONFIG_DEFAULTS.map((d) => [d.key, d.value]),
|
||||
);
|
||||
|
||||
/**
|
||||
* 判断全局 key 在工作空间 DB 中的值是否为"未配置"(应回退到全局层)
|
||||
@@ -146,7 +124,7 @@ export class GlobalConfigService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取全局配置
|
||||
* 读取全局配置(P0-1: 敏感 key 自动解密)
|
||||
*/
|
||||
get<T = unknown>(key: string): T | null {
|
||||
if (!this.initialized) {
|
||||
@@ -154,26 +132,27 @@ export class GlobalConfigService {
|
||||
return null;
|
||||
}
|
||||
if (key in this.data) {
|
||||
return this.data[key] as T;
|
||||
const raw = this.data[key];
|
||||
return (isSensitiveConfigKey(key) ? decryptConfigValue(raw) : raw) as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入全局配置(内存 + 立即落盘)
|
||||
* 写入全局配置(内存 + 立即落盘;P0-1: 敏感 key 加密后存储)
|
||||
*/
|
||||
set(key: string, value: unknown): void {
|
||||
if (!this.initialized) {
|
||||
log.warn('[GlobalConfig] set() called before initialize()');
|
||||
return;
|
||||
}
|
||||
this.data[key] = value;
|
||||
this.data[key] = isSensitiveConfigKey(key) ? encryptConfigValue(value) : value;
|
||||
this.flush();
|
||||
log.debug(`[GlobalConfig] set: ${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量写入(仅落盘一次,避免多次 IO)
|
||||
* 批量写入(仅落盘一次,避免多次 IO;敏感 key 加密后存储)
|
||||
*/
|
||||
setBatch(entries: Array<{ key: string; value: unknown }>): void {
|
||||
if (!this.initialized) {
|
||||
@@ -181,17 +160,21 @@ export class GlobalConfigService {
|
||||
return;
|
||||
}
|
||||
for (const { key, value } of entries) {
|
||||
this.data[key] = value;
|
||||
this.data[key] = isSensitiveConfigKey(key) ? encryptConfigValue(value) : value;
|
||||
}
|
||||
this.flush();
|
||||
log.debug(`[GlobalConfig] setBatch: ${entries.length} keys`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有全局配置(用于迁移和调试)
|
||||
* 获取所有全局配置(用于迁移和调试;敏感 key 解密后返回)
|
||||
*/
|
||||
getAll(): GlobalConfigData {
|
||||
return { ...this.data };
|
||||
const out: GlobalConfigData = {};
|
||||
for (const [key, value] of Object.entries(this.data)) {
|
||||
out[key] = isSensitiveConfigKey(key) ? decryptConfigValue(value) : value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,7 +205,7 @@ export class GlobalConfigService {
|
||||
// 注意:isUnconfiguredGlobalKey 已包含"值等于默认值"判断,此处复用
|
||||
if (isUnconfiguredGlobalKey(key, value)) continue;
|
||||
|
||||
this.data[key] = value;
|
||||
this.data[key] = isSensitiveConfigKey(key) ? encryptConfigValue(value) : value;
|
||||
migrated++;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user