一、设计原则(三个统一契约)
WeSight 在凭据与配置管理上对外做以下三条承诺。所有新代码与重构都应满足:
P1 — 已配置就不动
若用户的本地 CLI 配置文件(~/.claude/settings.json / ~/.codex/config.toml / ~/.codex/auth.json / ~/.openclaw/openclaw.json / ~/.cc-switch/cc-switch.db / ~/.config/opencode/opencode.json 等)已经存在或含用户字段,WeSight 不得以任何形式(覆写、整块替换、整体重建)修改用户非 WeSight 字段。
P2 — 未配置才 bootstrap
若用户的本地配置文件不存在或对应 block 为空,WeSight 可以写入最小可用配置以让 CLI 跑起来;写入后必须在该 block 上加 marker 标记,标记 WeSight 的所有权。
P3 — WeSight 凭据不落盘到非 WeSight 文件
WeSight 自己的 provider API key 只能存在于 WeSight sqlite(app_config.providers.<n>.apiKey),注入到子进程的方式只能是 spawn env 变量;任何写入用户文件的形式都必须用 ${WESIGHT_APIKEY_*} / ${LOBSTER_APIKEY_*} 占位符,永不写明文。
说明:以下 path:line 形式的引用在 GitHub 中会自动渲染为可点链接。
二、当前架构问题(量化)
| 维度 |
违规点 |
文件:行 |
严重度 |
| P1 |
Codex config.toml 全文覆写 |
src/main/libs/externalAgentConfigSync.ts:672 |
HIGH |
| P1 |
Claude env 整块替换 |
src/main/libs/externalAgentConfigSync.ts:319-331 |
HIGH |
| P1 |
OpenClaw models.providers 整体重建 |
src/main/libs/openclawConfigSync.ts:941-1000 |
HIGH |
| P1 |
applyProviderToLive 整文件覆写 |
src/main/libs/externalAgentProviderStore.ts:1272, 1405-1406 |
HIGH |
| P1 |
exec-approvals.json 强制升 security=full |
src/main/libs/openclawConfigSync.ts:1758-1768 |
MEDIUM |
| P2 |
Codex config.toml 不查空就写 |
src/main/libs/externalAgentConfigSync.ts:672 |
HIGH |
| P2 |
WesightModel 模式无 marker |
多处 |
MEDIUM |
| P3 |
cc-switch DB 明文 WeSight key |
src/main/libs/externalAgentConfigSync.ts:540-588 |
HIGH |
| P3 |
Claude env.ANTHROPIC_API_KEY 明文 |
src/main/libs/externalAgentConfigSync.ts:319-331 |
HIGH |
| P3 |
Codex auth.json.OPENAI_API_KEY 明文 |
src/main/libs/externalAgentConfigSync.ts:670-676 |
HIGH |
| P3 |
OpenClaw gateway env 含所有 enabled provider key |
src/main/libs/openclawConfigSync.ts:1618-1633 |
LOW |
三、提议的新架构
3.1 三层分层
┌──────────────────────────────────────────────┐
│ Layer 1: WeSight 私有 Store │
│ - wesight.sqlite → app_config.providers[N] │
│ - 唯一可信源 (single source of truth) │
└──────────────────────────────────────────────┘
│ env injection (per-spawn)
▼
┌──────────────────────────────────────────────┐
│ Layer 2: Child Process Env │
│ - spawn({ env: {...process.env, │
│ WESIGHT_APIKEY_*, ANTHROPIC_*, ...} }) │
│ - 不落盘 │
└──────────────────────────────────────────────┘
│ 占位符 + marker block
▼
┌──────────────────────────────────────────────┐
│ Layer 3: 用户本地 CLI 文件 │
│ - 仅 WeSight marker 块可写 │
│ - key 用 ${WESIGHT_APIKEY_*} 占位符 │
│ - user 字段永久保留 │
└──────────────────────────────────────────────┘
3.2 关键原语(团队已具备,需推广)
| 原语 |
位置 |
用途 |
getEnhancedEnvWithTmpdir |
src/main/libs/coworkUtil.ts:1423-1457 |
每次 spawn 构造新 env,不持久化。应当是所有 key 注入的唯一入口。 |
shouldInjectCoworkModelConfig |
src/main/libs/agentEngine/externalCliRuntimeAdapter.ts:580-585 |
gate 开关,决定 WeSight env 是否注入。应当推广为统一的 LocalCli 行为。 |
buildEnvForConfig |
src/main/libs/claudeSettings.ts:536-544 |
已能把 WeSight key 转成 ANTHROPIC_* env。应当替代"写文件"那一步。 |
mergeHermesManagedDotenvBlock |
src/main/libs/hermesConfig.ts:166-213 |
marker 模式参考实现。应当复制到所有引擎的对应 block。 |
writeIfChanged / syncFileIfChanged |
src/main/libs/hermesConfigSync.ts:58-62 / src/main/libs/openclawConfigSync.ts:2169-2184 |
内容不变不写。应当推广到所有 sync 函数。 |
ensureGatewayToken / writeMinimalConfig 的「不降级」守卫 |
src/main/libs/hermesEngineManager.ts:528-575 / src/main/libs/openclawConfigSync.ts:2224-2237 |
bootstrap-when-empty。应当作为所有 sync 函数的默认前置。 |
__wesightProviderMeta 内部命名空间 |
src/main/libs/externalAgentLocalEnv.ts:38 |
内部 key 命名空间。应当推广为 env.__wesight_managed 等 marker 字段。 |
LocalCli 读路径 |
src/main/libs/externalAgentLocalEnv.ts:97-144 |
{ readonly: true, fileMustExist: true } + 只 SELECT。应当作为所有 LocalCli 路径的样板。 |
atomicWrite / atomicWriteFile / writeJsonObject |
多处 |
原子写。已统一。 |
3.3 模块重构计划
Stage 1 — 抽离一个统一的 mergeWithManagedBlock helper
新文件 src/main/libs/managedConfigBlock.ts:
// 签名(伪代码)
function mergeWithManagedBlock<T>(opts: {
targetPath: string;
parser: (text: string) => T;
serializer: (config: T) => string;
managedKey: string; // e.g. "env.__wesight_managed"
managedValue: object; // e.g. { model: true, auth: true }
userValue: T | null; // null = bootstrap-when-empty
preserveUserFields: (keyof T)[];
}): { written: boolean; mergedConfig: T };
行为:
- 读
targetPath;不存在 → 走 userValue bootstrap 路径,写入后该 block 标 marker。
- 存在 → 解析;分离 WeSight marker block 与 user block;用
preserveUserFields 保留 user 字段;merge 之后 writeIfChanged 短路。
Stage 2 — 改造所有 sync 路径走 helper
替换:
mergeClaudeSettingsWithProvider(src/main/libs/externalAgentConfigSync.ts:333-360)
buildCodexConfig(src/main/libs/externalAgentConfigSync.ts:220-235)
mergeOpenCodeConfigForWesightModel(src/main/libs/externalAgentConfigSync.ts:683-688)
mergeQwenCodeConfigForWesightModel(src/main/libs/externalAgentConfigSync.ts:693-699)
mergeDeepSeekTuiConfigForWesightModel(src/main/libs/externalAgentConfigSync.ts:708-713)
mergeHermesConfigForWesightModel(src/main/libs/hermesConfig.ts:348-401)
syncOpenClawConfig(src/main/libs/openclawConfigSync.ts:1543-1566)中的 models.providers 子段
Stage 3 — 改 key 注入为 spawn env 唯一
把 buildEnvForConfig(src/main/libs/claudeSettings.ts:536-544)从「也写到文件」改为「只返回 env 对象」;下游 syncClaudeCodeFromWesightModel / syncCodexFromWesightModel 不再写 key 到文件,只写占位符。
Stage 4 — cc-switch 占位符化
仿 OpenClaw 的 WESIGHT_APIKEY_* 模式(src/main/libs/openclawConfigSync.ts:1606-1633):
- cc-switch 启动时由 WeSight 注入 env;
settings_config 内的 ANTHROPIC_API_KEY 改为 ${WESIGHT_APIKEY_ANTHROPIC};
- cc-switch 端需要确认支持
${ENV} 占位符语法。
四、详细需求(按 P1 / P2 / P3 分解)
4.1 P1 — 已配置就不动
- 任何
sync*FromWesightModel 函数必须先 readFile,再 merge,再 writeIfChanged。
- merge 顺序统一为
{...managed, ...user, ...managedExtras}(user 字段优先级高)。
applyProviderToLive 不再走 writeJsonFile,改为 merge 路径。
4.2 P2 — 未配置才 bootstrap
- sync 函数前置
if (!fs.existsSync(targetPath)) 短路。
- 已有路径但对应 block 为空 → 仍走 bootstrap(最小可用配置 + marker)。
- 已有路径且对应 block 非空 → 走 P1 merge。
4.3 P3 — 凭据隔离
- 移除所有「把 WeSight key 写到用户文件」的实现。
~/.claude/settings.json 中 ANTHROPIC_API_KEY 改为 ${WESIGHT_APIKEY_ANTHROPIC} 占位符;spawn 时注入。
- cc-switch DB 同理。
- Codex
~/.codex/auth.json 的 OPENAI_API_KEY 改为 ${WESIGHT_APIKEY_OPENAI}。
- 增加单元测试:
buildEnvForConfig 的输出 + getEnhancedEnvWithTmpdir 的 env 包含真实 key;反之,文件内容永远不含真实 key。
五、复现 / 用例
- 用户已有
~/.codex/config.toml,含自定义 [features] 与注释 → WeSight 保存 model 后这些字段原样保留。
- 用户在 WeSight 配置 Anthropic provider →
~/.claude/settings.json 中只有 marker block(env.__wesight_managed 标记),user 原 env.ANTHROPIC_API_KEY / FOO_TOKEN 全部保留。
- 用户用 cc-switch → cc-switch DB 中无明文 WeSight key,gateway 启动时由 WeSight 注入。
- 用户停用 WeSight(卸载或清配置)→ 所有 WeSight marker block 被识别为孤立可清理,不影响 user 字段。
- 用户在两台机器上登录不同账号 → WeSight key 各自隔离,互不污染。
六、实施后可改进
| 维度 |
改进 |
| 安全 |
WeSight key 不再落盘到 cc-switch DB / Claude env / Codex auth;降低 key 泄露面 |
| 数据完整性 |
用户本地配置不再被静默改写;删除 WeSight 时其他工具不受影响 |
| 可维护性 |
mergeWithManagedBlock helper 统一所有 sync 逻辑;新增引擎时只需提供 parser/serializer/managedKey |
| 可观察性 |
所有 WeSight 写入都有 marker;用户和 reviewer 一眼可分辨 WeSight 段 |
| 可测试性 |
当前 externalAgentConfigSync / openclawConfigSync 几乎无单测;helper 化后易于 mock + 断言 marker 行为 |
| 跨工具兼容 |
Claude Code / Codex / OpenClaw / cc-switch 都能与 WeSight 和平共存 |
| 符合"产品级桌面应用"定位 |
桌面应用对外契约必须尊重用户既有环境;这是从"内部工具"到"产品"的标志性改动 |
七、风险与回滚
- 风险:cc-switch 占位符语法如果不被 cc-switch 端支持,Stage 4 需要 fallback(保留明文但用 0o600 权限文件 + 警告用户)。
- 回滚:每个 Stage 独立可发版;Stage 1–3 都不涉及跨进程协议变更,回滚成本低。
- 灰度:建议 Stage 1 + 2 先在 Claude/Codex 灰度一两个 release,再推广 OpenClaw / OpenCode / Qwen / DeepSeek。
八、验收清单
九、补充:相关 issue
- 姊妹 bug issue:列出 6 步可立即修复的具体破坏行为;本 design proposal 的 Stage 1 + 2 一旦合并,bug issue 即可关闭。
- 姊妹 feature request:依赖本 design proposal 的 helper 完成「LocalCli 统一 + 认证状态探测」。
一、设计原则(三个统一契约)
WeSight 在凭据与配置管理上对外做以下三条承诺。所有新代码与重构都应满足:
P1 — 已配置就不动
若用户的本地 CLI 配置文件(
~/.claude/settings.json/~/.codex/config.toml/~/.codex/auth.json/~/.openclaw/openclaw.json/~/.cc-switch/cc-switch.db/~/.config/opencode/opencode.json等)已经存在或含用户字段,WeSight 不得以任何形式(覆写、整块替换、整体重建)修改用户非 WeSight 字段。P2 — 未配置才 bootstrap
若用户的本地配置文件不存在或对应 block 为空,WeSight 可以写入最小可用配置以让 CLI 跑起来;写入后必须在该 block 上加 marker 标记,标记 WeSight 的所有权。
P3 — WeSight 凭据不落盘到非 WeSight 文件
WeSight 自己的 provider API key 只能存在于 WeSight sqlite(
app_config.providers.<n>.apiKey),注入到子进程的方式只能是 spawn env 变量;任何写入用户文件的形式都必须用${WESIGHT_APIKEY_*}/${LOBSTER_APIKEY_*}占位符,永不写明文。二、当前架构问题(量化)
config.toml全文覆写src/main/libs/externalAgentConfigSync.ts:672env整块替换src/main/libs/externalAgentConfigSync.ts:319-331models.providers整体重建src/main/libs/openclawConfigSync.ts:941-1000applyProviderToLive整文件覆写src/main/libs/externalAgentProviderStore.ts:1272, 1405-1406exec-approvals.json强制升security=fullsrc/main/libs/openclawConfigSync.ts:1758-1768config.toml不查空就写src/main/libs/externalAgentConfigSync.ts:672WesightModel模式无 markersrc/main/libs/externalAgentConfigSync.ts:540-588env.ANTHROPIC_API_KEY明文src/main/libs/externalAgentConfigSync.ts:319-331auth.json.OPENAI_API_KEY明文src/main/libs/externalAgentConfigSync.ts:670-676src/main/libs/openclawConfigSync.ts:1618-1633三、提议的新架构
3.1 三层分层
3.2 关键原语(团队已具备,需推广)
getEnhancedEnvWithTmpdirsrc/main/libs/coworkUtil.ts:1423-1457shouldInjectCoworkModelConfigsrc/main/libs/agentEngine/externalCliRuntimeAdapter.ts:580-585buildEnvForConfigsrc/main/libs/claudeSettings.ts:536-544ANTHROPIC_*env。应当替代"写文件"那一步。mergeHermesManagedDotenvBlocksrc/main/libs/hermesConfig.ts:166-213writeIfChanged/syncFileIfChangedsrc/main/libs/hermesConfigSync.ts:58-62/src/main/libs/openclawConfigSync.ts:2169-2184ensureGatewayToken/writeMinimalConfig的「不降级」守卫src/main/libs/hermesEngineManager.ts:528-575/src/main/libs/openclawConfigSync.ts:2224-2237__wesightProviderMeta内部命名空间src/main/libs/externalAgentLocalEnv.ts:38env.__wesight_managed等 marker 字段。LocalCli读路径src/main/libs/externalAgentLocalEnv.ts:97-144{ readonly: true, fileMustExist: true }+ 只 SELECT。应当作为所有 LocalCli 路径的样板。atomicWrite/atomicWriteFile/writeJsonObject3.3 模块重构计划
Stage 1 — 抽离一个统一的
mergeWithManagedBlockhelper新文件
src/main/libs/managedConfigBlock.ts:行为:
targetPath;不存在 → 走userValuebootstrap 路径,写入后该 block 标 marker。preserveUserFields保留 user 字段;merge 之后writeIfChanged短路。Stage 2 — 改造所有 sync 路径走 helper
替换:
mergeClaudeSettingsWithProvider(src/main/libs/externalAgentConfigSync.ts:333-360)buildCodexConfig(src/main/libs/externalAgentConfigSync.ts:220-235)mergeOpenCodeConfigForWesightModel(src/main/libs/externalAgentConfigSync.ts:683-688)mergeQwenCodeConfigForWesightModel(src/main/libs/externalAgentConfigSync.ts:693-699)mergeDeepSeekTuiConfigForWesightModel(src/main/libs/externalAgentConfigSync.ts:708-713)mergeHermesConfigForWesightModel(src/main/libs/hermesConfig.ts:348-401)syncOpenClawConfig(src/main/libs/openclawConfigSync.ts:1543-1566)中的models.providers子段Stage 3 — 改 key 注入为 spawn env 唯一
把
buildEnvForConfig(src/main/libs/claudeSettings.ts:536-544)从「也写到文件」改为「只返回 env 对象」;下游syncClaudeCodeFromWesightModel/syncCodexFromWesightModel不再写 key 到文件,只写占位符。Stage 4 — cc-switch 占位符化
仿 OpenClaw 的
WESIGHT_APIKEY_*模式(src/main/libs/openclawConfigSync.ts:1606-1633):settings_config内的ANTHROPIC_API_KEY改为${WESIGHT_APIKEY_ANTHROPIC};${ENV}占位符语法。四、详细需求(按 P1 / P2 / P3 分解)
4.1 P1 — 已配置就不动
sync*FromWesightModel函数必须先readFile,再 merge,再writeIfChanged。{...managed, ...user, ...managedExtras}(user 字段优先级高)。applyProviderToLive不再走writeJsonFile,改为 merge 路径。4.2 P2 — 未配置才 bootstrap
if (!fs.existsSync(targetPath))短路。4.3 P3 — 凭据隔离
~/.claude/settings.json中ANTHROPIC_API_KEY改为${WESIGHT_APIKEY_ANTHROPIC}占位符;spawn 时注入。~/.codex/auth.json的OPENAI_API_KEY改为${WESIGHT_APIKEY_OPENAI}。buildEnvForConfig的输出 +getEnhancedEnvWithTmpdir的 env 包含真实 key;反之,文件内容永远不含真实 key。五、复现 / 用例
~/.codex/config.toml,含自定义[features]与注释 → WeSight 保存 model 后这些字段原样保留。~/.claude/settings.json中只有 marker block(env.__wesight_managed标记),user 原env.ANTHROPIC_API_KEY/FOO_TOKEN全部保留。六、实施后可改进
mergeWithManagedBlockhelper 统一所有 sync 逻辑;新增引擎时只需提供 parser/serializer/managedKeyexternalAgentConfigSync/openclawConfigSync几乎无单测;helper 化后易于 mock + 断言 marker 行为七、风险与回滚
八、验收清单
mergeWithManagedBlockhelper 并被 ≥4 个 sync 函数复用writeIfChanged/syncFileIfChanged覆盖率 ≥90%managedConfigBlock.test.ts覆盖 P1 / P2 / P3 三种场景九、补充:相关 issue