@wu529778790/open-im 1.10.6-beta.0 → 1.10.6-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,13 +62,12 @@ Session state is stored in **`~/.open-im/data/sessions.json`** (per user, not IM
62
62
 
63
63
  ### Per-platform AI
64
64
 
65
- Default: root **`aiCommand`**. Override with **`platforms.<name>.aiCommand`**:
65
+ Set **`platforms.<name>.aiCommand`** per channel (`claude` / `codex` / `codebuddy`). If unset, the process **`AI_COMMAND`** env var is used when present; otherwise it defaults to **`claude`**.
66
66
 
67
67
  ```json
68
68
  {
69
- "aiCommand": "claude",
70
69
  "platforms": {
71
- "telegram": { "enabled": true, "aiCommand": "codex" }
70
+ "telegram": { "enabled": true, "aiCommand": "codex", "botToken": "..." }
72
71
  }
73
72
  }
74
73
  ```
@@ -105,12 +104,11 @@ codebuddy login
105
104
 
106
105
  ```json
107
106
  {
108
- "aiCommand": "claude",
109
107
  "tools": {
110
108
  "claude": { "workDir": "/path/to/project", "skipPermissions": true, "timeoutMs": 600000 }
111
109
  },
112
110
  "platforms": {
113
- "telegram": { "enabled": true, "botToken": "YOUR_TELEGRAM_BOT_TOKEN" }
111
+ "telegram": { "enabled": true, "aiCommand": "claude", "botToken": "YOUR_TELEGRAM_BOT_TOKEN" }
114
112
  }
115
113
  }
116
114
  ```
package/README.zh-CN.md CHANGED
@@ -62,13 +62,12 @@ npx @wu529778790/open-im start
62
62
 
63
63
  ### 按平台指定 AI
64
64
 
65
- 根级 **`aiCommand`** 为默认;用 **`platforms.<name>.aiCommand`** 覆盖:
65
+ 在每个已启用渠道上设置 **`platforms.<name>.aiCommand`**(`claude` / `codex` / `codebuddy`)。若未设置,会尝试进程环境变量 **`AI_COMMAND`**;再否则默认为 **`claude`**。
66
66
 
67
67
  ```json
68
68
  {
69
- "aiCommand": "claude",
70
69
  "platforms": {
71
- "telegram": { "enabled": true, "aiCommand": "codex" }
70
+ "telegram": { "enabled": true, "aiCommand": "codex", "botToken": "..." }
72
71
  }
73
72
  }
74
73
  ```
@@ -105,12 +104,11 @@ codebuddy login
105
104
 
106
105
  ```json
107
106
  {
108
- "aiCommand": "claude",
109
107
  "tools": {
110
108
  "claude": { "workDir": "/path/to/project", "skipPermissions": true, "timeoutMs": 600000 }
111
109
  },
112
110
  "platforms": {
113
- "telegram": { "enabled": true, "botToken": "YOUR_TELEGRAM_BOT_TOKEN" }
111
+ "telegram": { "enabled": true, "aiCommand": "claude", "botToken": "YOUR_TELEGRAM_BOT_TOKEN" }
114
112
  }
115
113
  }
116
114
  ```
@@ -21,7 +21,6 @@ export interface Config {
21
21
  weworkAllowedUserIds: string[];
22
22
  dingtalkAllowedUserIds: string[];
23
23
  workbuddyAllowedUserIds: string[];
24
- aiCommand: AiCommand;
25
24
  codexCliPath: string;
26
25
  codebuddyCliPath: string;
27
26
  /** Claude 访问 API 的代理(如 http://127.0.0.1:7890) */
@@ -182,6 +181,7 @@ export interface FileConfig {
182
181
  dingtalk?: FilePlatformDingtalk;
183
182
  workbuddy?: FilePlatformWorkBuddy;
184
183
  };
184
+ /** @deprecated 仅旧配置兼容;运行时以各 platforms.*.aiCommand 为准 */
185
185
  aiCommand?: string;
186
186
  tools?: {
187
187
  claude?: FileToolClaude;
@@ -86,7 +86,8 @@ export declare const PAGE_TEXTS: {
86
86
  readonly optional: "Optional";
87
87
  readonly commaSeparatedIds: "Comma-separated IDs";
88
88
  readonly aiTitle: "AI Tooling";
89
- readonly aiHint: "Pick the default tool first. Claude SDK keys: sidebar Config files → ~/.claude/settings.json. Codex / CodeBuddy need a CLI path here.";
89
+ readonly aiHint: "Claude SDK keys: sidebar Config files → ~/.claude/settings.json. Set which AI each platform uses under Platforms above. Codex / CodeBuddy need a CLI path in the panels below when any platform uses them.";
90
+ readonly aiPerPlatformHint: "Which AI to use is configured per platform in the Platforms section (default: claude). This section sets shared paths, proxy, and logs.";
90
91
  readonly claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.";
91
92
  readonly aiCommonTitle: "Shared defaults";
92
93
  readonly aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.";
@@ -238,7 +239,8 @@ export declare const PAGE_TEXTS: {
238
239
  readonly optional: "可选";
239
240
  readonly commaSeparatedIds: "多个 ID 用逗号分隔";
240
241
  readonly aiTitle: "AI 工具配置";
241
- readonly aiHint: "先选默认 AI。Claude SDK:左侧栏「配置文件」→ ~/.claude/settings.jsonCodex / CodeBuddy 在此填 CLI 路径。";
242
+ readonly aiHint: "Claude SDK:左侧栏「配置文件」→ ~/.claude/settings.json。各渠道用哪个 AI 在上方「平台」里分别设置。若任一平台选 Codex/CodeBuddy,再在下方填对应 CLI 路径。";
243
+ readonly aiPerPlatformHint: "每个平台的 AI 工具在上方「平台」中配置(默认 claude)。本区域设置共用路径、代理与日志。";
242
244
  readonly claudeNote: "Claude 凭证仍然从环境变量或 ~/.claude/settings.json 读取。这个页面只管理本地桥接配置,不负责 Claude 账号登录。";
243
245
  readonly aiCommonTitle: "公共默认配置";
244
246
  readonly aiCommonHint: "先选择默认 AI 工具,下方再显示对应的工具专属配置。";
@@ -86,7 +86,8 @@ export const PAGE_TEXTS = {
86
86
  optional: "Optional",
87
87
  commaSeparatedIds: "Comma-separated IDs",
88
88
  aiTitle: "AI Tooling",
89
- aiHint: "Pick the default tool first. Claude SDK keys: sidebar Config files → ~/.claude/settings.json. Codex / CodeBuddy need a CLI path here.",
89
+ aiHint: "Claude SDK keys: sidebar Config files → ~/.claude/settings.json. Set which AI each platform uses under Platforms above. Codex / CodeBuddy need a CLI path in the panels below when any platform uses them.",
90
+ aiPerPlatformHint: "Which AI to use is configured per platform in the Platforms section (default: claude). This section sets shared paths, proxy, and logs.",
90
91
  claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.",
91
92
  aiCommonTitle: "Shared defaults",
92
93
  aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.",
@@ -238,7 +239,8 @@ export const PAGE_TEXTS = {
238
239
  optional: "\u53ef\u9009",
239
240
  commaSeparatedIds: "\u591a\u4e2a ID \u7528\u9017\u53f7\u5206\u9694",
240
241
  aiTitle: "AI \u5de5\u5177\u914d\u7f6e",
241
- aiHint: "\u5148\u9009\u9ed8\u8ba4 AI\u3002Claude SDK\uff1a\u5de6\u4fa7\u680f\u300c\u914d\u7f6e\u6587\u4ef6\u300d\u2192 ~/.claude/settings.json\u3002Codex / CodeBuddy \u5728\u6b64\u586b CLI \u8def\u5f84\u3002",
242
+ aiHint: "Claude SDK\uff1a\u5de6\u4fa7\u680f\u300c\u914d\u7f6e\u6587\u4ef6\u300d\u2192 ~/.claude/settings.json\u3002\u5404\u6e20\u9053\u7528\u54ea\u4e2a AI \u5728\u4e0a\u65b9\u300c\u5e73\u53f0\u300d\u91cc\u5206\u522b\u8bbe\u7f6e\u3002\u82e5\u4efb\u4e00\u5e73\u53f0\u9009 Codex/CodeBuddy\uff0c\u518d\u5728\u4e0b\u65b9\u586b\u5bf9\u5e94 CLI \u8def\u5f84\u3002",
243
+ aiPerPlatformHint: "\u6bcf\u4e2a\u5e73\u53f0\u7684 AI \u5de5\u5177\u5728\u4e0a\u65b9\u300c\u5e73\u53f0\u300d\u4e2d\u914d\u7f6e\uff08\u9ed8\u8ba4 claude\uff09\u3002\u672c\u533a\u57df\u8bbe\u7f6e\u5171\u7528\u8def\u5f84\u3001\u4ee3\u7406\u4e0e\u65e5\u5fd7\u3002",
242
244
  claudeNote: "Claude \u51ed\u8bc1\u4ecd\u7136\u4ece\u73af\u5883\u53d8\u91cf\u6216 ~/.claude/settings.json \u8bfb\u53d6\u3002\u8fd9\u4e2a\u9875\u9762\u53ea\u7ba1\u7406\u672c\u5730\u6865\u63a5\u914d\u7f6e\uff0c\u4e0d\u8d1f\u8d23 Claude \u8d26\u53f7\u767b\u5f55\u3002",
243
245
  aiCommonTitle: "\u516c\u5171\u9ed8\u8ba4\u914d\u7f6e",
244
246
  aiCommonHint: "\u5148\u9009\u62e9\u9ed8\u8ba4 AI \u5de5\u5177\uff0c\u4e0b\u65b9\u518d\u663e\u793a\u5bf9\u5e94\u7684\u5de5\u5177\u4e13\u5c5e\u914d\u7f6e\u3002",
@@ -6,7 +6,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
6
6
  import { join, dirname } from "node:path";
7
7
  import { DWClient } from "dingtalk-stream";
8
8
  import { WEB_CONFIG_PORT, getPublicWebDashboardUrl } from "./constants.js";
9
- import { CONFIG_PATH, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, loadConfig, loadFileConfig, saveFileConfig, CODEX_AUTH_PATHS } from "./config.js";
9
+ import { CONFIG_PATH, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, loadConfig, loadFileConfig, saveFileConfig, normalizeAiCommand, CODEX_AUTH_PATHS, } from "./config.js";
10
10
  import { getWebDistDir, tryServeDashboardStatic } from "./config-web-static.js";
11
11
  import { getServiceStatus, startBackgroundService, stopBackgroundService } from "./service-control.js";
12
12
  import { initWeWork, stopWeWork } from "./wework/client.js";
@@ -225,6 +225,12 @@ function clean(value) {
225
225
  const trimmed = value.trim();
226
226
  return trimmed ? trimmed : undefined;
227
227
  }
228
+ function persistedPlatformAi(v) {
229
+ const c = clean(v);
230
+ if (c === "codex" || c === "codebuddy" || c === "claude")
231
+ return c;
232
+ return "claude";
233
+ }
228
234
  function isMasked(value) {
229
235
  return typeof value === "string" && value.includes("****");
230
236
  }
@@ -276,35 +282,35 @@ function buildInitialPayload(file) {
276
282
  platforms: {
277
283
  telegram: {
278
284
  enabled: file.platforms?.telegram?.enabled ?? Boolean(file.platforms?.telegram?.botToken),
279
- aiCommand: file.platforms?.telegram?.aiCommand ?? "",
285
+ aiCommand: normalizeAiCommand(file.platforms?.telegram?.aiCommand, "claude"),
280
286
  botToken: maskSecret(file.platforms?.telegram?.botToken),
281
287
  proxy: file.platforms?.telegram?.proxy ?? "",
282
288
  allowedUserIds: (file.platforms?.telegram?.allowedUserIds ?? []).join(", "),
283
289
  },
284
290
  feishu: {
285
291
  enabled: file.platforms?.feishu?.enabled ?? Boolean(file.platforms?.feishu?.appId && file.platforms?.feishu?.appSecret),
286
- aiCommand: file.platforms?.feishu?.aiCommand ?? "",
292
+ aiCommand: normalizeAiCommand(file.platforms?.feishu?.aiCommand, "claude"),
287
293
  appId: file.platforms?.feishu?.appId ?? "",
288
294
  appSecret: maskSecret(file.platforms?.feishu?.appSecret),
289
295
  allowedUserIds: (file.platforms?.feishu?.allowedUserIds ?? []).join(", "),
290
296
  },
291
297
  qq: {
292
298
  enabled: file.platforms?.qq?.enabled ?? Boolean(file.platforms?.qq?.appId && file.platforms?.qq?.secret),
293
- aiCommand: file.platforms?.qq?.aiCommand ?? "",
299
+ aiCommand: normalizeAiCommand(file.platforms?.qq?.aiCommand, "claude"),
294
300
  appId: file.platforms?.qq?.appId ?? "",
295
301
  secret: maskSecret(file.platforms?.qq?.secret),
296
302
  allowedUserIds: (file.platforms?.qq?.allowedUserIds ?? []).join(", "),
297
303
  },
298
304
  wework: {
299
305
  enabled: file.platforms?.wework?.enabled ?? Boolean(file.platforms?.wework?.corpId && file.platforms?.wework?.secret),
300
- aiCommand: file.platforms?.wework?.aiCommand ?? "",
306
+ aiCommand: normalizeAiCommand(file.platforms?.wework?.aiCommand, "claude"),
301
307
  corpId: file.platforms?.wework?.corpId ?? "",
302
308
  secret: maskSecret(file.platforms?.wework?.secret),
303
309
  allowedUserIds: (file.platforms?.wework?.allowedUserIds ?? []).join(", "),
304
310
  },
305
311
  dingtalk: {
306
312
  enabled: file.platforms?.dingtalk?.enabled ?? Boolean(file.platforms?.dingtalk?.clientId && file.platforms?.dingtalk?.clientSecret),
307
- aiCommand: file.platforms?.dingtalk?.aiCommand ?? "",
313
+ aiCommand: normalizeAiCommand(file.platforms?.dingtalk?.aiCommand, "claude"),
308
314
  clientId: file.platforms?.dingtalk?.clientId ?? "",
309
315
  clientSecret: maskSecret(file.platforms?.dingtalk?.clientSecret),
310
316
  cardTemplateId: file.platforms?.dingtalk?.cardTemplateId ?? "",
@@ -312,7 +318,7 @@ function buildInitialPayload(file) {
312
318
  },
313
319
  workbuddy: {
314
320
  enabled: file.platforms?.workbuddy?.enabled ?? Boolean(file.platforms?.workbuddy?.accessToken && file.platforms?.workbuddy?.refreshToken && file.platforms?.workbuddy?.userId),
315
- aiCommand: file.platforms?.workbuddy?.aiCommand ?? "",
321
+ aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, "claude"),
316
322
  accessToken: maskSecret(file.platforms?.workbuddy?.accessToken),
317
323
  refreshToken: maskSecret(file.platforms?.workbuddy?.refreshToken),
318
324
  userId: file.platforms?.workbuddy?.userId ?? "",
@@ -321,7 +327,6 @@ function buildInitialPayload(file) {
321
327
  },
322
328
  },
323
329
  ai: {
324
- aiCommand: file.aiCommand ?? "claude",
325
330
  claudeWorkDir: file.tools?.claude?.workDir ?? process.cwd(),
326
331
  claudeConfigPath: process.platform === 'win32'
327
332
  ? getClaudeConfigHome() + "\\.claude\\settings.json"
@@ -471,7 +476,6 @@ function createProbeConfig(values) {
471
476
  weworkAllowedUserIds: [],
472
477
  dingtalkAllowedUserIds: [],
473
478
  workbuddyAllowedUserIds: [],
474
- aiCommand: "claude",
475
479
  codexCliPath: "codex",
476
480
  claudeWorkDir: process.cwd(),
477
481
  claudeSessionIdleTtlMinutes: 30,
@@ -631,10 +635,9 @@ function toFileConfig(payload, existing) {
631
635
  saveClaudeSettingsEnv(claudeEnv);
632
636
  }
633
637
  // claudeConfigPath is informational only, not saved
634
- const { env: _discardLegacyRootEnv, ...existingWithoutRootEnv } = existing;
638
+ const { env: _discardLegacyRootEnv, aiCommand: _discardLegacyGlobalAi, ...existingWithoutRootEnv } = existing;
635
639
  return {
636
640
  ...existingWithoutRootEnv,
637
- aiCommand: payload.ai.aiCommand,
638
641
  logDir: payload.ai.logDir === undefined ? existing.logDir : clean(payload.ai.logDir),
639
642
  logLevel: payload.ai.logLevel === "default" ? undefined : payload.ai.logLevel,
640
643
  tools: {
@@ -660,7 +663,7 @@ function toFileConfig(payload, existing) {
660
663
  telegram: {
661
664
  ...existing.platforms?.telegram,
662
665
  enabled: payload.platforms.telegram.enabled,
663
- aiCommand: clean(payload.platforms.telegram.aiCommand),
666
+ aiCommand: persistedPlatformAi(payload.platforms.telegram.aiCommand),
664
667
  botToken: resolveSecret(payload.platforms.telegram.botToken, existing.platforms?.telegram?.botToken),
665
668
  proxy: clean(payload.platforms.telegram.proxy),
666
669
  allowedUserIds: splitCsv(payload.platforms.telegram.allowedUserIds),
@@ -668,7 +671,7 @@ function toFileConfig(payload, existing) {
668
671
  feishu: {
669
672
  ...existing.platforms?.feishu,
670
673
  enabled: payload.platforms.feishu.enabled,
671
- aiCommand: clean(payload.platforms.feishu.aiCommand),
674
+ aiCommand: persistedPlatformAi(payload.platforms.feishu.aiCommand),
672
675
  appId: clean(payload.platforms.feishu.appId),
673
676
  appSecret: resolveSecret(payload.platforms.feishu.appSecret, existing.platforms?.feishu?.appSecret),
674
677
  allowedUserIds: splitCsv(payload.platforms.feishu.allowedUserIds),
@@ -676,7 +679,7 @@ function toFileConfig(payload, existing) {
676
679
  qq: {
677
680
  ...existing.platforms?.qq,
678
681
  enabled: payload.platforms.qq.enabled,
679
- aiCommand: clean(payload.platforms.qq.aiCommand),
682
+ aiCommand: persistedPlatformAi(payload.platforms.qq.aiCommand),
680
683
  appId: clean(payload.platforms.qq.appId),
681
684
  secret: resolveSecret(payload.platforms.qq.secret, existing.platforms?.qq?.secret),
682
685
  allowedUserIds: splitCsv(payload.platforms.qq.allowedUserIds),
@@ -684,7 +687,7 @@ function toFileConfig(payload, existing) {
684
687
  wework: {
685
688
  ...existing.platforms?.wework,
686
689
  enabled: payload.platforms.wework.enabled,
687
- aiCommand: clean(payload.platforms.wework.aiCommand),
690
+ aiCommand: persistedPlatformAi(payload.platforms.wework.aiCommand),
688
691
  corpId: clean(payload.platforms.wework.corpId),
689
692
  secret: resolveSecret(payload.platforms.wework.secret, existing.platforms?.wework?.secret),
690
693
  allowedUserIds: splitCsv(payload.platforms.wework.allowedUserIds),
@@ -692,7 +695,7 @@ function toFileConfig(payload, existing) {
692
695
  dingtalk: {
693
696
  ...existing.platforms?.dingtalk,
694
697
  enabled: payload.platforms.dingtalk.enabled,
695
- aiCommand: clean(payload.platforms.dingtalk.aiCommand),
698
+ aiCommand: persistedPlatformAi(payload.platforms.dingtalk.aiCommand),
696
699
  clientId: clean(payload.platforms.dingtalk.clientId),
697
700
  clientSecret: resolveSecret(payload.platforms.dingtalk.clientSecret, existing.platforms?.dingtalk?.clientSecret),
698
701
  cardTemplateId: clean(payload.platforms.dingtalk.cardTemplateId),
@@ -701,7 +704,7 @@ function toFileConfig(payload, existing) {
701
704
  workbuddy: {
702
705
  ...existing.platforms?.workbuddy,
703
706
  enabled: payload.platforms.workbuddy.enabled,
704
- aiCommand: clean(payload.platforms.workbuddy.aiCommand),
707
+ aiCommand: persistedPlatformAi(payload.platforms.workbuddy.aiCommand),
705
708
  accessToken: resolveSecret(payload.platforms.workbuddy.accessToken, existing.platforms?.workbuddy?.accessToken),
706
709
  refreshToken: resolveSecret(payload.platforms.workbuddy.refreshToken, existing.platforms?.workbuddy?.refreshToken),
707
710
  userId: clean(payload.platforms.workbuddy.userId),
package/dist/config.js CHANGED
@@ -10,6 +10,22 @@ import { join, isAbsolute } from 'node:path';
10
10
  import { createLogger } from './logger.js';
11
11
  import { APP_HOME, DEFAULT_TELEMETRY_INGEST_URL, DEFAULT_TELEMETRY_INGEST_TOKEN, } from './constants.js';
12
12
  const log = createLogger('config');
13
+ /** 分渠道 AI 工具:未配置时使用 AI_COMMAND / 旧版根级 aiCommand / 默认 claude */
14
+ function resolveFilePlatformAi(file, platform) {
15
+ const pf = file.platforms;
16
+ const raw = platform === 'telegram'
17
+ ? pf?.telegram?.aiCommand
18
+ : platform === 'feishu'
19
+ ? pf?.feishu?.aiCommand
20
+ : platform === 'qq'
21
+ ? pf?.qq?.aiCommand
22
+ : platform === 'wework'
23
+ ? pf?.wework?.aiCommand
24
+ : platform === 'dingtalk'
25
+ ? pf?.dingtalk?.aiCommand
26
+ : pf?.workbuddy?.aiCommand;
27
+ return normalizeAiCommand(raw ?? process.env.AI_COMMAND ?? file.aiCommand, 'claude');
28
+ }
13
29
  // Re-export file I/O and credential helpers from sub-modules
14
30
  export { CONFIG_PATH, loadFileConfig, saveFileConfig, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, CLAUDE_AUTH_ENV_KEYS, refreshClaudeEnvToProcess, processEnvForNonClaudeCliChild, CODEX_AUTH_PATHS, } from './config/file-io.js';
15
31
  import { loadFileConfig, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, loadClaudeSettingsEnv, } from './config/file-io.js';
@@ -159,7 +175,6 @@ export function loadConfig() {
159
175
  ? parseCommaSeparated(process.env.WORKBUDDY_ALLOWED_USER_IDS)
160
176
  : fileWorkBuddy?.allowedUserIds ?? allowedUserIds;
161
177
  // 5. AI / 工作目录 / 安全配置(从 tools 读取)
162
- const aiCommand = normalizeAiCommand(process.env.AI_COMMAND ?? file.aiCommand, 'claude');
163
178
  const tc = file.tools?.claude ?? {};
164
179
  const tcod = file.tools?.codex ?? {};
165
180
  const tcb = file.tools?.codebuddy ?? {};
@@ -216,9 +231,12 @@ export function loadConfig() {
216
231
  else {
217
232
  claudeSessionIdleTtlMinutes = 30;
218
233
  }
219
- // 6. 校验 Claude API 凭证(SDK 模式需要)
220
- // 支持:官方 API Key、Auth Token、或自定义 API(第三方模型等,BASE_URL + token)
221
- if (aiCommand === 'claude') {
234
+ // 6. 校验 Claude API 凭证(任一已启用渠道使用 claude 时需配置)
235
+ const toolsNeeded = new Set();
236
+ for (const p of enabledPlatforms) {
237
+ toolsNeeded.add(resolveFilePlatformAi(file, p));
238
+ }
239
+ if (toolsNeeded.has('claude')) {
222
240
  // 校验凭证时直接从各来源读取,不修改 process.env
223
241
  const claudeToolEnvForCheck = file.tools?.claude?.env;
224
242
  const claudeSettingsForCheck = loadClaudeSettingsEnv();
@@ -258,8 +276,8 @@ export function loadConfig() {
258
276
  throw new Error(errorMsg);
259
277
  }
260
278
  }
261
- // 7. 校验 Codex CLI(使用 codex 时)
262
- if (aiCommand === 'codex') {
279
+ // 7. 校验 Codex CLI(任一已启用渠道使用 codex 时)
280
+ if (toolsNeeded.has('codex')) {
263
281
  if (isAbsolute(codexCliPath) || codexCliPath.includes('/') || codexCliPath.includes('\\')) {
264
282
  try {
265
283
  accessSync(codexCliPath, constants.F_OK);
@@ -300,8 +318,8 @@ export function loadConfig() {
300
318
  '或在 shell 中 export OPENAI_API_KEY。');
301
319
  }
302
320
  }
303
- // 8. 校验 CodeBuddy CLI(使用 codebuddy 时)
304
- if (aiCommand === 'codebuddy') {
321
+ // 8. 校验 CodeBuddy CLI(任一已启用渠道使用 codebuddy 时)
322
+ if (toolsNeeded.has('codebuddy')) {
305
323
  if (isAbsolute(codebuddyCliPath) || codebuddyCliPath.includes('/') || codebuddyCliPath.includes('\\')) {
306
324
  try {
307
325
  accessSync(codebuddyCliPath, constants.F_OK);
@@ -402,66 +420,66 @@ export function loadConfig() {
402
420
  telegram: telegramEnabled
403
421
  ? {
404
422
  enabled: true,
405
- aiCommand: normalizeAiCommand(file.platforms?.telegram?.aiCommand, aiCommand),
423
+ aiCommand: resolveFilePlatformAi(file, 'telegram'),
406
424
  proxy: process.env.TELEGRAM_PROXY ?? file.platforms?.telegram?.proxy,
407
425
  allowedUserIds: telegramAllowedUserIds,
408
426
  }
409
427
  : {
410
428
  enabled: false,
411
- aiCommand: normalizeAiCommand(file.platforms?.telegram?.aiCommand, aiCommand),
429
+ aiCommand: resolveFilePlatformAi(file, 'telegram'),
412
430
  proxy: process.env.TELEGRAM_PROXY ?? file.platforms?.telegram?.proxy,
413
431
  allowedUserIds: telegramAllowedUserIds,
414
432
  },
415
433
  feishu: feishuEnabled
416
434
  ? {
417
435
  enabled: true,
418
- aiCommand: normalizeAiCommand(file.platforms?.feishu?.aiCommand, aiCommand),
436
+ aiCommand: resolveFilePlatformAi(file, 'feishu'),
419
437
  allowedUserIds: feishuAllowedUserIds,
420
438
  }
421
439
  : {
422
440
  enabled: false,
423
- aiCommand: normalizeAiCommand(file.platforms?.feishu?.aiCommand, aiCommand),
441
+ aiCommand: resolveFilePlatformAi(file, 'feishu'),
424
442
  allowedUserIds: feishuAllowedUserIds,
425
443
  },
426
444
  qq: qqEnabled
427
445
  ? {
428
446
  enabled: true,
429
- aiCommand: normalizeAiCommand(file.platforms?.qq?.aiCommand, aiCommand),
447
+ aiCommand: resolveFilePlatformAi(file, 'qq'),
430
448
  allowedUserIds: qqAllowedUserIds,
431
449
  }
432
450
  : {
433
451
  enabled: false,
434
- aiCommand: normalizeAiCommand(file.platforms?.qq?.aiCommand, aiCommand),
452
+ aiCommand: resolveFilePlatformAi(file, 'qq'),
435
453
  allowedUserIds: qqAllowedUserIds,
436
454
  },
437
455
  wework: weworkEnabled
438
456
  ? {
439
457
  enabled: true,
440
- aiCommand: normalizeAiCommand(file.platforms?.wework?.aiCommand, aiCommand),
458
+ aiCommand: resolveFilePlatformAi(file, 'wework'),
441
459
  allowedUserIds: weworkAllowedUserIds,
442
460
  }
443
461
  : {
444
462
  enabled: false,
445
- aiCommand: normalizeAiCommand(file.platforms?.wework?.aiCommand, aiCommand),
463
+ aiCommand: resolveFilePlatformAi(file, 'wework'),
446
464
  allowedUserIds: weworkAllowedUserIds,
447
465
  },
448
466
  dingtalk: dingtalkEnabled
449
467
  ? {
450
468
  enabled: true,
451
- aiCommand: normalizeAiCommand(file.platforms?.dingtalk?.aiCommand, aiCommand),
469
+ aiCommand: resolveFilePlatformAi(file, 'dingtalk'),
452
470
  allowedUserIds: dingtalkAllowedUserIds,
453
471
  cardTemplateId: dingtalkCardTemplateId,
454
472
  }
455
473
  : {
456
474
  enabled: false,
457
- aiCommand: normalizeAiCommand(file.platforms?.dingtalk?.aiCommand, aiCommand),
475
+ aiCommand: resolveFilePlatformAi(file, 'dingtalk'),
458
476
  allowedUserIds: dingtalkAllowedUserIds,
459
477
  cardTemplateId: dingtalkCardTemplateId,
460
478
  },
461
479
  workbuddy: workbuddyEnabled
462
480
  ? {
463
481
  enabled: true,
464
- aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, aiCommand),
482
+ aiCommand: resolveFilePlatformAi(file, 'workbuddy'),
465
483
  allowedUserIds: workbuddyAllowedUserIds,
466
484
  accessToken: workbuddyAccessToken,
467
485
  refreshToken: workbuddyRefreshToken,
@@ -472,7 +490,7 @@ export function loadConfig() {
472
490
  }
473
491
  : {
474
492
  enabled: false,
475
- aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, aiCommand),
493
+ aiCommand: resolveFilePlatformAi(file, 'workbuddy'),
476
494
  allowedUserIds: workbuddyAllowedUserIds,
477
495
  accessToken: workbuddyAccessToken,
478
496
  refreshToken: workbuddyRefreshToken,
@@ -502,7 +520,6 @@ export function loadConfig() {
502
520
  weworkAllowedUserIds,
503
521
  dingtalkAllowedUserIds,
504
522
  workbuddyAllowedUserIds,
505
- aiCommand,
506
523
  codexCliPath,
507
524
  codebuddyCliPath,
508
525
  claudeProxy,
@@ -540,10 +557,11 @@ export function getPlatformsWithCredentials(config) {
540
557
  return r;
541
558
  }
542
559
  export function resolvePlatformAiCommand(config, platform) {
543
- return config.platforms[platform]?.aiCommand ?? config.aiCommand;
560
+ const v = config.platforms[platform]?.aiCommand;
561
+ return v === 'claude' || v === 'codex' || v === 'codebuddy' ? v : 'claude';
544
562
  }
545
563
  export function getConfiguredAiCommands(config) {
546
- const commands = new Set([config.aiCommand]);
564
+ const commands = new Set();
547
565
  for (const platform of config.enabledPlatforms) {
548
566
  commands.add(resolvePlatformAiCommand(config, platform));
549
567
  }