@wu529778790/open-im 1.6.2 → 1.6.3-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.
Files changed (40) hide show
  1. package/dist/adapters/registry.d.ts +1 -1
  2. package/dist/adapters/registry.js +23 -18
  3. package/dist/commands/handler.d.ts +1 -1
  4. package/dist/commands/handler.js +10 -8
  5. package/dist/config-web-page-i18n.d.ts +4 -0
  6. package/dist/config-web-page-i18n.js +4 -0
  7. package/dist/config-web-page-script.js +16 -5
  8. package/dist/config-web-page-template.js +5 -0
  9. package/dist/config-web.d.ts +7 -0
  10. package/dist/config-web.js +60 -2
  11. package/dist/config-web.test.js +9 -1
  12. package/dist/config.d.ts +14 -0
  13. package/dist/config.js +29 -1
  14. package/dist/dingtalk/event-handler.d.ts +1 -1
  15. package/dist/dingtalk/event-handler.js +7 -5
  16. package/dist/feishu/event-handler.d.ts +1 -1
  17. package/dist/feishu/event-handler.js +8 -6
  18. package/dist/hook/permission-server.d.ts +1 -0
  19. package/dist/hook/permission-server.js +13 -7
  20. package/dist/hook/permission-server.test.d.ts +1 -0
  21. package/dist/hook/permission-server.test.js +12 -0
  22. package/dist/index.js +7 -10
  23. package/dist/manager.js +2 -1
  24. package/dist/qq/event-handler.d.ts +1 -1
  25. package/dist/qq/event-handler.js +6 -4
  26. package/dist/qq/event-handler.test.js +7 -0
  27. package/dist/service-control.d.ts +1 -0
  28. package/dist/service-control.js +26 -7
  29. package/dist/service-control.test.d.ts +1 -0
  30. package/dist/service-control.test.js +36 -0
  31. package/dist/shared/ai-task.js +128 -104
  32. package/dist/shared/ai-task.test.d.ts +1 -0
  33. package/dist/shared/ai-task.test.js +69 -0
  34. package/dist/telegram/event-handler.d.ts +1 -1
  35. package/dist/telegram/event-handler.js +8 -6
  36. package/dist/wechat/event-handler.d.ts +1 -1
  37. package/dist/wechat/event-handler.js +8 -6
  38. package/dist/wework/event-handler.d.ts +1 -1
  39. package/dist/wework/event-handler.js +8 -6
  40. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import type { Config } from '../config.js';
1
+ import { type Config } from '../config.js';
2
2
  import type { ToolAdapter } from './tool-adapter.interface.js';
3
3
  export declare function initAdapters(config: Config): void;
4
4
  export declare function getAdapter(aiCommand: string): ToolAdapter | undefined;
@@ -1,3 +1,4 @@
1
+ import { getConfiguredAiCommands } from '../config.js';
1
2
  import { ClaudeAdapter } from './claude-adapter.js';
2
3
  import { ClaudeSDKAdapter } from './claude-sdk-adapter.js';
3
4
  import { CursorAdapter } from './cursor-adapter.js';
@@ -5,26 +6,30 @@ import { CodexAdapter } from './codex-adapter.js';
5
6
  const adapters = new Map();
6
7
  export function initAdapters(config) {
7
8
  adapters.clear();
8
- if (config.aiCommand === 'claude') {
9
- if (config.useSdkMode) {
10
- console.log('⚡ 启用 Claude Agent SDK 模式 - 进程内执行,响应更快');
11
- adapters.set('claude', new ClaudeSDKAdapter());
9
+ for (const aiCommand of getConfiguredAiCommands(config)) {
10
+ if (aiCommand === 'claude') {
11
+ if (config.useSdkMode) {
12
+ console.log(' 启用 Claude Agent SDK 模式 - 进程内执行,响应更快');
13
+ adapters.set('claude', new ClaudeSDKAdapter());
14
+ }
15
+ else {
16
+ console.log('🚀 使用标准 Claude 适配器');
17
+ adapters.set('claude', new ClaudeAdapter(config.claudeCliPath, {
18
+ useProcessPool: true,
19
+ idleTimeoutMs: 2 * 60 * 1000,
20
+ }));
21
+ }
22
+ continue;
12
23
  }
13
- else {
14
- console.log('🚀 使用标准 Claude 适配器');
15
- adapters.set('claude', new ClaudeAdapter(config.claudeCliPath, {
16
- useProcessPool: true,
17
- idleTimeoutMs: 2 * 60 * 1000,
18
- }));
24
+ if (aiCommand === 'cursor') {
25
+ console.log('🖱️ 使用 Cursor Agent CLI 适配器');
26
+ adapters.set('cursor', new CursorAdapter(config.cursorCliPath));
27
+ continue;
28
+ }
29
+ if (aiCommand === 'codex') {
30
+ console.log('📦 使用 Codex CLI 适配器');
31
+ adapters.set('codex', new CodexAdapter(config.codexCliPath));
19
32
  }
20
- }
21
- else if (config.aiCommand === 'cursor') {
22
- console.log('🖱️ 使用 Cursor Agent CLI 适配器');
23
- adapters.set('cursor', new CursorAdapter(config.cursorCliPath));
24
- }
25
- else if (config.aiCommand === 'codex') {
26
- console.log('📦 使用 Codex CLI 适配器');
27
- adapters.set('codex', new CodexAdapter(config.codexCliPath));
28
33
  }
29
34
  }
30
35
  export function getAdapter(aiCommand) {
@@ -1,4 +1,4 @@
1
- import type { Config } from '../config.js';
1
+ import { type Config } from '../config.js';
2
2
  import type { SessionManager } from '../session/session-manager.js';
3
3
  import type { RequestQueue } from '../queue/request-queue.js';
4
4
  import { type PermissionMode } from '../permission-mode/types.js';
@@ -1,3 +1,4 @@
1
+ import { resolvePlatformAiCommand } from '../config.js';
1
2
  import { resolveLatestPermission, getPendingCount } from '../hook/permission-server.js';
2
3
  import { escapePathForMarkdown } from '../shared/utils.js';
3
4
  import { getPermissionMode, setPermissionMode } from '../permission-mode/session-mode.js';
@@ -26,7 +27,7 @@ export class CommandHandler {
26
27
  if (t === '/pwd')
27
28
  return this.handlePwd(chatId, userId);
28
29
  if (t === '/status')
29
- return this.handleStatus(chatId, userId);
30
+ return this.handleStatus(chatId, userId, platform);
30
31
  if (t === '/allow' || t === '/y')
31
32
  return this.handleAllow(chatId);
32
33
  if (t === '/deny' || t === '/n')
@@ -109,15 +110,16 @@ export class CommandHandler {
109
110
  await this.deps.sender.sendTextReply(chatId, `当前工作目录: ${escapePathForMarkdown(workDir)}`);
110
111
  return true;
111
112
  }
112
- async handleStatus(chatId, userId) {
113
- const version = await this.getAiVersion();
113
+ async handleStatus(chatId, userId, platform) {
114
+ const aiCommand = resolvePlatformAiCommand(this.deps.config, platform);
115
+ const version = await this.getAiVersion(aiCommand);
114
116
  const workDir = this.deps.sessionManager.getWorkDir(userId);
115
117
  const convId = this.deps.sessionManager.getConvId(userId);
116
- const sessionId = this.deps.sessionManager.getSessionIdForConv(userId, convId, this.deps.config.aiCommand);
118
+ const sessionId = this.deps.sessionManager.getSessionIdForConv(userId, convId, aiCommand);
117
119
  const lines = [
118
120
  '📊 状态:',
119
121
  '',
120
- `AI 工具: ${this.deps.config.aiCommand}`,
122
+ `AI 工具: ${aiCommand}`,
121
123
  `版本: ${version}`,
122
124
  `工作目录: ${escapePathForMarkdown(workDir)}`,
123
125
  `会话: ${sessionId ?? '无'}`,
@@ -169,10 +171,10 @@ export class CommandHandler {
169
171
  }
170
172
  return true;
171
173
  }
172
- getAiVersion() {
173
- const cmd = this.deps.config.aiCommand === 'cursor'
174
+ getAiVersion(aiCommand) {
175
+ const cmd = aiCommand === 'cursor'
174
176
  ? this.deps.config.cursorCliPath
175
- : this.deps.config.aiCommand === 'codex'
177
+ : aiCommand === 'codex'
176
178
  ? this.deps.config.codexCliPath
177
179
  : this.deps.config.claudeCliPath;
178
180
  return new Promise((resolve) => {
@@ -41,6 +41,8 @@ export declare const PAGE_TEXTS: {
41
41
  readonly platformsTitle: "Platforms";
42
42
  readonly platformsHint: "Disabled platforms keep their saved values.";
43
43
  readonly enabled: "Enabled";
44
+ readonly platformAiTool: "AI tool override";
45
+ readonly inheritDefaultAi: "Use default AI tool";
44
46
  readonly botToken: "Bot token";
45
47
  readonly proxy: "Proxy";
46
48
  readonly allowedUserIds: "Allowed user IDs";
@@ -137,6 +139,8 @@ export declare const PAGE_TEXTS: {
137
139
  readonly platformsTitle: "平台配置";
138
140
  readonly platformsHint: "禁用的平台会保留已保存的值。";
139
141
  readonly enabled: "启用";
142
+ readonly platformAiTool: "平台 AI 工具覆盖";
143
+ readonly inheritDefaultAi: "使用默认 AI 工具";
140
144
  readonly botToken: "Bot Token";
141
145
  readonly proxy: "代理";
142
146
  readonly allowedUserIds: "允许的用户 ID";
@@ -41,6 +41,8 @@ export const PAGE_TEXTS = {
41
41
  platformsTitle: "Platforms",
42
42
  platformsHint: "Disabled platforms keep their saved values.",
43
43
  enabled: "Enabled",
44
+ platformAiTool: "AI tool override",
45
+ inheritDefaultAi: "Use default AI tool",
44
46
  botToken: "Bot token",
45
47
  proxy: "Proxy",
46
48
  allowedUserIds: "Allowed user IDs",
@@ -137,6 +139,8 @@ export const PAGE_TEXTS = {
137
139
  platformsTitle: "\u5e73\u53f0\u914d\u7f6e",
138
140
  platformsHint: "\u7981\u7528\u7684\u5e73\u53f0\u4f1a\u4fdd\u7559\u5df2\u4fdd\u5b58\u7684\u503c\u3002",
139
141
  enabled: "\u542f\u7528",
142
+ platformAiTool: "\u5e73\u53f0 AI \u5de5\u5177\u8986\u76d6",
143
+ inheritDefaultAi: "\u4f7f\u7528\u9ed8\u8ba4 AI \u5de5\u5177",
140
144
  botToken: "Bot Token",
141
145
  proxy: "\u4ee3\u7406",
142
146
  allowedUserIds: "\u5141\u8bb8\u7684\u7528\u6237 ID",
@@ -1,9 +1,9 @@
1
1
  export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
2
- { key: "telegram", label: "Telegram", fields: ["botToken", "proxy", "allowedUserIds"], testFields: ["botToken", "proxy"] },
3
- { key: "feishu", label: "Feishu", fields: ["appId", "appSecret", "allowedUserIds"], testFields: ["appId", "appSecret"] },
4
- { key: "qq", label: "QQ", fields: ["appId", "secret", "allowedUserIds"], testFields: ["appId", "secret"] },
5
- { key: "wework", label: "WeWork", fields: ["corpId", "secret", "allowedUserIds"], testFields: ["corpId", "secret"] },
6
- { key: "dingtalk", label: "DingTalk", fields: ["clientId", "clientSecret", "cardTemplateId", "allowedUserIds"], testFields: ["clientId", "clientSecret"] },
2
+ { key: "telegram", label: "Telegram", fields: ["aiCommand", "botToken", "proxy", "allowedUserIds"], testFields: ["botToken", "proxy"] },
3
+ { key: "feishu", label: "Feishu", fields: ["aiCommand", "appId", "appSecret", "allowedUserIds"], testFields: ["appId", "appSecret"] },
4
+ { key: "qq", label: "QQ", fields: ["aiCommand", "appId", "secret", "allowedUserIds"], testFields: ["appId", "secret"] },
5
+ { key: "wework", label: "WeWork", fields: ["aiCommand", "corpId", "secret", "allowedUserIds"], testFields: ["corpId", "secret"] },
6
+ { key: "dingtalk", label: "DingTalk", fields: ["aiCommand", "clientId", "clientSecret", "cardTemplateId", "allowedUserIds"], testFields: ["clientId", "clientSecret"] },
7
7
  ];
8
8
  const platformKeys = platformDefinitions.map((platform) => platform.key);
9
9
  const ids = platformDefinitions.flatMap((platform) => ["enabled", ...platform.fields].map((field) => platform.key + "-" + field)).concat(["ai-aiCommand","ai-claudeCliPath","ai-claudeWorkDir","ai-claudeSkipPermissions","ai-claudeTimeoutMs","ai-codexTimeoutMs","ai-claudeModel","ai-cursorCliPath","ai-codexCliPath","ai-codexProxy","ai-hookPort","ai-logLevel","ai-useSdkMode"]);
@@ -59,6 +59,11 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
59
59
  setText("qq-enabled-label", t("enabled"));
60
60
  setText("wework-enabled-label", t("enabled"));
61
61
  setText("dingtalk-enabled-label", t("enabled"));
62
+ setText("telegram-aiCommand-label", t("platformAiTool"));
63
+ setText("feishu-aiCommand-label", t("platformAiTool"));
64
+ setText("qq-aiCommand-label", t("platformAiTool"));
65
+ setText("wework-aiCommand-label", t("platformAiTool"));
66
+ setText("dingtalk-aiCommand-label", t("platformAiTool"));
62
67
  setText("telegram-botToken-label", t("botToken"));
63
68
  setText("telegram-proxy-label", t("proxy"));
64
69
  setText("telegram-allowedUserIds-label", t("allowedUserIds"));
@@ -86,6 +91,12 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
86
91
  el("wework-allowedUserIds").placeholder = t("commaSeparatedIds");
87
92
  el("dingtalk-allowedUserIds").placeholder = t("commaSeparatedIds");
88
93
  el("dingtalk-cardTemplateId").placeholder = t("optional");
94
+ ["telegram","feishu","qq","wework","dingtalk"].forEach((platform) => {
95
+ const select = el(platform + "-aiCommand");
96
+ if (select?.options?.length) {
97
+ select.options[0].text = t("inheritDefaultAi");
98
+ }
99
+ });
89
100
  setText("ai-aiCommand-label", t("aiTool"));
90
101
  setText("ai-claudeWorkDir-label", t("workDir"));
91
102
  setText("ai-claudeCliPath-label", t("claudeCli"));
@@ -159,6 +159,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
159
159
  <article class="panel" id="telegram-panel">
160
160
  <div class="panel-head"><h3>Telegram</h3><label class="toggle"><input id="telegram-enabled" type="checkbox" /> <span id="telegram-enabled-label">Enabled</span></label></div>
161
161
  <div class="summary" id="telegram-help" style="margin-bottom:12px;color:var(--muted);font-size:0.9em;">Get credentials: visit <a href="https://t.me/BotFather" target="_blank" style="color:var(--green);text-decoration:underline;">@BotFather</a>, send /newbot, then copy the Bot Token</div>
162
+ <label><span id="telegram-aiCommand-label">AI tool override</span><select id="telegram-aiCommand"><option value="">Use default AI tool</option><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option></select></label>
162
163
  <label><span id="telegram-botToken-label">Bot token</span><input id="telegram-botToken" type="password" autocomplete="off" placeholder="123456:ABC..." /></label>
163
164
  <label><span id="telegram-proxy-label">Proxy</span><input id="telegram-proxy" placeholder="http://127.0.0.1:7890" /></label>
164
165
  <label><span id="telegram-allowedUserIds-label">Allowed user IDs</span><textarea id="telegram-allowedUserIds" placeholder="Comma-separated IDs"></textarea></label>
@@ -167,6 +168,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
167
168
  <article class="panel" id="feishu-panel">
168
169
  <div class="panel-head"><h3>Feishu</h3><label class="toggle"><input id="feishu-enabled" type="checkbox" /> <span id="feishu-enabled-label">Enabled</span></label></div>
169
170
  <div class="summary" id="feishu-help" style="margin-bottom:12px;color:var(--muted);font-size:0.9em;">Get credentials: visit <a href="https://open.feishu.cn/" target="_blank" style="color:var(--green);text-decoration:underline;">Feishu Open Platform</a>, create an app, enable the bot, and copy the App ID / App Secret</div>
171
+ <label><span id="feishu-aiCommand-label">AI tool override</span><select id="feishu-aiCommand"><option value="">Use default AI tool</option><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option></select></label>
170
172
  <label><span id="feishu-appId-label">App ID</span><input id="feishu-appId" /></label>
171
173
  <label><span id="feishu-appSecret-label">App Secret</span><input id="feishu-appSecret" type="password" autocomplete="off" /></label>
172
174
  <label><span id="feishu-allowedUserIds-label">Allowed user IDs</span><textarea id="feishu-allowedUserIds" placeholder="Comma-separated IDs"></textarea></label>
@@ -175,6 +177,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
175
177
  <article class="panel" id="qq-panel">
176
178
  <div class="panel-head"><h3>QQ</h3><label class="toggle"><input id="qq-enabled" type="checkbox" /> <span id="qq-enabled-label">Enabled</span></label></div>
177
179
  <div class="summary" id="qq-help" style="margin-bottom:12px;color:var(--muted);font-size:0.9em;">Get credentials: visit <a href="https://bot.q.qq.com" target="_blank" style="color:var(--green);text-decoration:underline;">QQ Open Platform</a>, create a bot, and copy the App ID / App Secret</div>
180
+ <label><span id="qq-aiCommand-label">AI tool override</span><select id="qq-aiCommand"><option value="">Use default AI tool</option><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option></select></label>
178
181
  <label><span id="qq-appId-label">App ID</span><input id="qq-appId" /></label>
179
182
  <label><span id="qq-secret-label">App Secret</span><input id="qq-secret" type="password" autocomplete="off" /></label>
180
183
  <label><span id="qq-allowedUserIds-label">Allowed user IDs</span><textarea id="qq-allowedUserIds" placeholder="Comma-separated IDs"></textarea></label>
@@ -183,6 +186,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
183
186
  <article class="panel" id="wework-panel">
184
187
  <div class="panel-head"><h3>WeWork</h3><label class="toggle"><input id="wework-enabled" type="checkbox" /> <span id="wework-enabled-label">Enabled</span></label></div>
185
188
  <div class="summary" id="wework-help" style="margin-bottom:12px;color:var(--muted);font-size:0.9em;">Get credentials: visit <a href="https://work.weixin.qq.com/" target="_blank" style="color:var(--green);text-decoration:underline;">WeWork Admin Console</a>, create an app, and copy the Bot ID (Corp ID) / Secret</div>
189
+ <label><span id="wework-aiCommand-label">AI tool override</span><select id="wework-aiCommand"><option value="">Use default AI tool</option><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option></select></label>
186
190
  <label><span id="wework-corpId-label">Corp ID / Bot ID</span><input id="wework-corpId" /></label>
187
191
  <label><span id="wework-secret-label">Secret</span><input id="wework-secret" type="password" autocomplete="off" /></label>
188
192
  <label><span id="wework-allowedUserIds-label">Allowed user IDs</span><textarea id="wework-allowedUserIds" placeholder="Comma-separated IDs"></textarea></label>
@@ -191,6 +195,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
191
195
  <article class="panel" id="dingtalk-panel">
192
196
  <div class="panel-head"><h3>DingTalk</h3><label class="toggle"><input id="dingtalk-enabled" type="checkbox" /> <span id="dingtalk-enabled-label">Enabled</span></label></div>
193
197
  <div class="summary" id="dingtalk-help" style="margin-bottom:12px;color:var(--muted);font-size:0.9em;">Get credentials: create an enterprise internal app on DingTalk Open Platform, enable Stream Mode, and copy the Client ID / Client Secret</div>
198
+ <label><span id="dingtalk-aiCommand-label">AI tool override</span><select id="dingtalk-aiCommand"><option value="">Use default AI tool</option><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option></select></label>
194
199
  <label><span id="dingtalk-clientId-label">Client ID / AppKey</span><input id="dingtalk-clientId" /></label>
195
200
  <label><span id="dingtalk-clientSecret-label">Client Secret / AppSecret</span><input id="dingtalk-clientSecret" type="password" autocomplete="off" /></label>
196
201
  <label><span id="dingtalk-cardTemplateId-label">Card template ID</span><input id="dingtalk-cardTemplateId" placeholder="Optional" /></label>
@@ -1,3 +1,4 @@
1
+ import { type FileConfig } from "./config.js";
1
2
  type WebFlowMode = "init" | "start" | "dev";
2
3
  type WebFlowResult = "saved" | "cancel";
3
4
  export interface StartedWebConfigServer {
@@ -5,6 +6,12 @@ export interface StartedWebConfigServer {
5
6
  url: string;
6
7
  waitForResult: Promise<WebFlowResult>;
7
8
  }
9
+ export declare function getHealthPlatformSnapshot(file: FileConfig, env?: NodeJS.ProcessEnv): Record<string, {
10
+ configured: boolean;
11
+ enabled: boolean;
12
+ healthy: boolean;
13
+ message?: string;
14
+ }>;
8
15
  export declare function testPlatformConfig(platform: string, config: Record<string, unknown>): Promise<string>;
9
16
  export declare function getWebConfigPort(): number;
10
17
  export declare function getWebConfigUrl(): string;
@@ -8,6 +8,54 @@ import { PAGE_HTML } from "./config-web-page.js";
8
8
  import { getServiceStatus, startBackgroundService, stopBackgroundService } from "./service-control.js";
9
9
  import { initWeWork, stopWeWork } from "./wework/client.js";
10
10
  const TEST_TIMEOUT_MS = 10000;
11
+ export function getHealthPlatformSnapshot(file, env = process.env) {
12
+ const fileTelegram = file.platforms?.telegram;
13
+ const fileFeishu = file.platforms?.feishu;
14
+ const fileQQ = file.platforms?.qq;
15
+ const fileWework = file.platforms?.wework;
16
+ const fileDingtalk = file.platforms?.dingtalk;
17
+ const telegramBotToken = env.TELEGRAM_BOT_TOKEN ?? fileTelegram?.botToken ?? file.telegramBotToken;
18
+ const feishuAppId = env.FEISHU_APP_ID ?? fileFeishu?.appId ?? file.feishuAppId;
19
+ const feishuAppSecret = env.FEISHU_APP_SECRET ?? fileFeishu?.appSecret ?? file.feishuAppSecret;
20
+ const qqAppId = env.QQ_BOT_APPID ?? env.QQ_APP_ID ?? fileQQ?.appId;
21
+ const qqSecret = env.QQ_BOT_SECRET ?? env.QQ_SECRET ?? fileQQ?.secret;
22
+ const weworkCorpId = env.WEWORK_CORP_ID ?? fileWework?.corpId;
23
+ const weworkSecret = env.WEWORK_SECRET ?? fileWework?.secret;
24
+ const dingtalkClientId = env.DINGTALK_CLIENT_ID ?? fileDingtalk?.clientId;
25
+ const dingtalkClientSecret = env.DINGTALK_CLIENT_SECRET ?? fileDingtalk?.clientSecret;
26
+ return {
27
+ telegram: {
28
+ configured: !!telegramBotToken,
29
+ enabled: !!telegramBotToken && fileTelegram?.enabled !== false,
30
+ healthy: !!telegramBotToken,
31
+ message: telegramBotToken ? "Token configured" : "Token not configured",
32
+ },
33
+ feishu: {
34
+ configured: !!(feishuAppId && feishuAppSecret),
35
+ enabled: !!(feishuAppId && feishuAppSecret) && fileFeishu?.enabled !== false,
36
+ healthy: !!(feishuAppId && feishuAppSecret),
37
+ message: feishuAppId && feishuAppSecret ? "App ID and Secret configured" : "Missing credentials",
38
+ },
39
+ qq: {
40
+ configured: !!(qqAppId && qqSecret),
41
+ enabled: !!(qqAppId && qqSecret) && fileQQ?.enabled !== false,
42
+ healthy: !!(qqAppId && qqSecret),
43
+ message: qqAppId && qqSecret ? "App ID and Secret configured" : "Missing credentials",
44
+ },
45
+ wework: {
46
+ configured: !!(weworkCorpId && weworkSecret),
47
+ enabled: !!(weworkCorpId && weworkSecret) && fileWework?.enabled !== false,
48
+ healthy: !!(weworkCorpId && weworkSecret),
49
+ message: weworkCorpId && weworkSecret ? "Corp ID and Secret configured" : "Missing credentials",
50
+ },
51
+ dingtalk: {
52
+ configured: !!(dingtalkClientId && dingtalkClientSecret),
53
+ enabled: !!(dingtalkClientId && dingtalkClientSecret) && fileDingtalk?.enabled !== false,
54
+ healthy: !!(dingtalkClientId && dingtalkClientSecret),
55
+ message: dingtalkClientId && dingtalkClientSecret ? "Client ID and Secret configured" : "Missing credentials",
56
+ },
57
+ };
58
+ }
11
59
  function splitCsv(value) {
12
60
  return value.split(",").map((item) => item.trim()).filter(Boolean);
13
61
  }
@@ -40,30 +88,35 @@ function buildInitialPayload(file) {
40
88
  platforms: {
41
89
  telegram: {
42
90
  enabled: file.platforms?.telegram?.enabled ?? Boolean(file.platforms?.telegram?.botToken),
91
+ aiCommand: file.platforms?.telegram?.aiCommand ?? "",
43
92
  botToken: file.platforms?.telegram?.botToken ?? "",
44
93
  proxy: file.platforms?.telegram?.proxy ?? "",
45
94
  allowedUserIds: (file.platforms?.telegram?.allowedUserIds ?? []).join(", "),
46
95
  },
47
96
  feishu: {
48
97
  enabled: file.platforms?.feishu?.enabled ?? Boolean(file.platforms?.feishu?.appId && file.platforms?.feishu?.appSecret),
98
+ aiCommand: file.platforms?.feishu?.aiCommand ?? "",
49
99
  appId: file.platforms?.feishu?.appId ?? "",
50
100
  appSecret: file.platforms?.feishu?.appSecret ?? "",
51
101
  allowedUserIds: (file.platforms?.feishu?.allowedUserIds ?? []).join(", "),
52
102
  },
53
103
  qq: {
54
104
  enabled: file.platforms?.qq?.enabled ?? Boolean(file.platforms?.qq?.appId && file.platforms?.qq?.secret),
105
+ aiCommand: file.platforms?.qq?.aiCommand ?? "",
55
106
  appId: file.platforms?.qq?.appId ?? "",
56
107
  secret: file.platforms?.qq?.secret ?? "",
57
108
  allowedUserIds: (file.platforms?.qq?.allowedUserIds ?? []).join(", "),
58
109
  },
59
110
  wework: {
60
111
  enabled: file.platforms?.wework?.enabled ?? Boolean(file.platforms?.wework?.corpId && file.platforms?.wework?.secret),
112
+ aiCommand: file.platforms?.wework?.aiCommand ?? "",
61
113
  corpId: file.platforms?.wework?.corpId ?? "",
62
114
  secret: file.platforms?.wework?.secret ?? "",
63
115
  allowedUserIds: (file.platforms?.wework?.allowedUserIds ?? []).join(", "),
64
116
  },
65
117
  dingtalk: {
66
118
  enabled: file.platforms?.dingtalk?.enabled ?? Boolean(file.platforms?.dingtalk?.clientId && file.platforms?.dingtalk?.clientSecret),
119
+ aiCommand: file.platforms?.dingtalk?.aiCommand ?? "",
67
120
  clientId: file.platforms?.dingtalk?.clientId ?? "",
68
121
  clientSecret: file.platforms?.dingtalk?.clientSecret ?? "",
69
122
  cardTemplateId: file.platforms?.dingtalk?.cardTemplateId ?? "",
@@ -351,6 +404,7 @@ function toFileConfig(payload, existing) {
351
404
  telegram: {
352
405
  ...existing.platforms?.telegram,
353
406
  enabled: payload.platforms.telegram.enabled,
407
+ aiCommand: clean(payload.platforms.telegram.aiCommand),
354
408
  botToken: clean(payload.platforms.telegram.botToken),
355
409
  proxy: clean(payload.platforms.telegram.proxy),
356
410
  allowedUserIds: splitCsv(payload.platforms.telegram.allowedUserIds),
@@ -358,6 +412,7 @@ function toFileConfig(payload, existing) {
358
412
  feishu: {
359
413
  ...existing.platforms?.feishu,
360
414
  enabled: payload.platforms.feishu.enabled,
415
+ aiCommand: clean(payload.platforms.feishu.aiCommand),
361
416
  appId: clean(payload.platforms.feishu.appId),
362
417
  appSecret: clean(payload.platforms.feishu.appSecret),
363
418
  allowedUserIds: splitCsv(payload.platforms.feishu.allowedUserIds),
@@ -365,6 +420,7 @@ function toFileConfig(payload, existing) {
365
420
  qq: {
366
421
  ...existing.platforms?.qq,
367
422
  enabled: payload.platforms.qq.enabled,
423
+ aiCommand: clean(payload.platforms.qq.aiCommand),
368
424
  appId: clean(payload.platforms.qq.appId),
369
425
  secret: clean(payload.platforms.qq.secret),
370
426
  allowedUserIds: splitCsv(payload.platforms.qq.allowedUserIds),
@@ -372,6 +428,7 @@ function toFileConfig(payload, existing) {
372
428
  wework: {
373
429
  ...existing.platforms?.wework,
374
430
  enabled: payload.platforms.wework.enabled,
431
+ aiCommand: clean(payload.platforms.wework.aiCommand),
375
432
  corpId: clean(payload.platforms.wework.corpId),
376
433
  secret: clean(payload.platforms.wework.secret),
377
434
  allowedUserIds: splitCsv(payload.platforms.wework.allowedUserIds),
@@ -379,6 +436,7 @@ function toFileConfig(payload, existing) {
379
436
  dingtalk: {
380
437
  ...existing.platforms?.dingtalk,
381
438
  enabled: payload.platforms.dingtalk.enabled,
439
+ aiCommand: clean(payload.platforms.dingtalk.aiCommand),
382
440
  clientId: clean(payload.platforms.dingtalk.clientId),
383
441
  clientSecret: clean(payload.platforms.dingtalk.clientSecret),
384
442
  cardTemplateId: clean(payload.platforms.dingtalk.cardTemplateId),
@@ -492,8 +550,8 @@ export async function startWebConfigServer(options) {
492
550
  const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN ?? fileTelegram?.botToken ?? file.telegramBotToken;
493
551
  const feishuAppId = process.env.FEISHU_APP_ID ?? fileFeishu?.appId ?? file.feishuAppId;
494
552
  const feishuAppSecret = process.env.FEISHU_APP_SECRET ?? fileFeishu?.appSecret ?? file.feishuAppSecret;
495
- const qqAppId = process.env.QQ_APP_ID ?? fileQQ?.appId;
496
- const qqSecret = process.env.QQ_SECRET ?? fileQQ?.secret;
553
+ const qqAppId = process.env.QQ_BOT_APPID ?? process.env.QQ_APP_ID ?? fileQQ?.appId;
554
+ const qqSecret = process.env.QQ_BOT_SECRET ?? process.env.QQ_SECRET ?? fileQQ?.secret;
497
555
  const weworkCorpId = process.env.WEWORK_CORP_ID ?? fileWework?.corpId;
498
556
  const weworkSecret = process.env.WEWORK_SECRET ?? fileWework?.secret;
499
557
  const dingtalkClientId = process.env.DINGTALK_CLIENT_ID ?? fileDingtalk?.clientId;
@@ -15,7 +15,7 @@ vi.mock("./wework/client.js", () => ({
15
15
  initWeWork: initWeWorkMock,
16
16
  stopWeWork: stopWeWorkMock,
17
17
  }));
18
- import { testPlatformConfig } from "./config-web.js";
18
+ import { getHealthPlatformSnapshot, testPlatformConfig } from "./config-web.js";
19
19
  describe("testPlatformConfig", () => {
20
20
  beforeEach(() => {
21
21
  vi.clearAllMocks();
@@ -47,3 +47,11 @@ describe("testPlatformConfig", () => {
47
47
  expect(globalThis.fetch).not.toHaveBeenCalled();
48
48
  });
49
49
  });
50
+ describe("getHealthPlatformSnapshot", () => {
51
+ it("recognizes QQ credentials from runtime env names", () => {
52
+ const snapshot = getHealthPlatformSnapshot({ platforms: { qq: { enabled: true } } }, { QQ_BOT_APPID: "qq-app", QQ_BOT_SECRET: "qq-secret" });
53
+ expect(snapshot.qq.configured).toBe(true);
54
+ expect(snapshot.qq.enabled).toBe(true);
55
+ expect(snapshot.qq.message).toContain("configured");
56
+ });
57
+ });
package/dist/config.d.ts CHANGED
@@ -49,19 +49,23 @@ export interface Config {
49
49
  platforms: {
50
50
  telegram?: {
51
51
  enabled: boolean;
52
+ aiCommand?: AiCommand;
52
53
  proxy?: string;
53
54
  allowedUserIds: string[];
54
55
  };
55
56
  feishu?: {
56
57
  enabled: boolean;
58
+ aiCommand?: AiCommand;
57
59
  allowedUserIds: string[];
58
60
  };
59
61
  qq?: {
60
62
  enabled: boolean;
63
+ aiCommand?: AiCommand;
61
64
  allowedUserIds: string[];
62
65
  };
63
66
  wechat?: {
64
67
  enabled: boolean;
68
+ aiCommand?: AiCommand;
65
69
  wsUrl?: string;
66
70
  token?: string;
67
71
  jwtToken?: string;
@@ -72,10 +76,12 @@ export interface Config {
72
76
  };
73
77
  wework?: {
74
78
  enabled: boolean;
79
+ aiCommand?: AiCommand;
75
80
  allowedUserIds: string[];
76
81
  };
77
82
  dingtalk?: {
78
83
  enabled: boolean;
84
+ aiCommand?: AiCommand;
79
85
  allowedUserIds: string[];
80
86
  cardTemplateId?: string;
81
87
  };
@@ -84,6 +90,7 @@ export interface Config {
84
90
  export interface FilePlatformTelegram {
85
91
  enabled?: boolean;
86
92
  botToken?: string;
93
+ aiCommand?: AiCommand;
87
94
  allowedUserIds?: string[];
88
95
  proxy?: string;
89
96
  }
@@ -91,18 +98,21 @@ export interface FilePlatformFeishu {
91
98
  enabled?: boolean;
92
99
  appId?: string;
93
100
  appSecret?: string;
101
+ aiCommand?: AiCommand;
94
102
  allowedUserIds?: string[];
95
103
  }
96
104
  interface FilePlatformQQ {
97
105
  enabled?: boolean;
98
106
  appId?: string;
99
107
  secret?: string;
108
+ aiCommand?: AiCommand;
100
109
  allowedUserIds?: string[];
101
110
  }
102
111
  interface FilePlatformWechat {
103
112
  enabled?: boolean;
104
113
  appId?: string;
105
114
  appSecret?: string;
115
+ aiCommand?: AiCommand;
106
116
  token?: string;
107
117
  jwtToken?: string;
108
118
  loginKey?: string;
@@ -115,6 +125,7 @@ export interface FilePlatformWework {
115
125
  enabled?: boolean;
116
126
  corpId?: string;
117
127
  secret?: string;
128
+ aiCommand?: AiCommand;
118
129
  wsUrl?: string;
119
130
  allowedUserIds?: string[];
120
131
  }
@@ -122,6 +133,7 @@ export interface FilePlatformDingtalk {
122
133
  enabled?: boolean;
123
134
  clientId?: string;
124
135
  clientSecret?: string;
136
+ aiCommand?: AiCommand;
125
137
  allowedUserIds?: string[];
126
138
  cardTemplateId?: string;
127
139
  }
@@ -182,4 +194,6 @@ export declare function needsSetup(): boolean;
182
194
  export declare function loadConfig(): Config;
183
195
  /** 获取已配置凭证的平台列表(用于多通道启动时让用户选择),顺序:Telegram、飞书、企业微信、微信 */
184
196
  export declare function getPlatformsWithCredentials(config: Config): Platform[];
197
+ export declare function resolvePlatformAiCommand(config: Config, platform: Platform): AiCommand;
198
+ export declare function getConfiguredAiCommands(config: Config): AiCommand[];
185
199
  export {};
package/dist/config.js CHANGED
@@ -9,6 +9,7 @@ import { execFileSync } from 'node:child_process';
9
9
  import { join, dirname, isAbsolute } from 'node:path';
10
10
  import { homedir } from 'node:os';
11
11
  import { APP_HOME } from './constants.js';
12
+ const AI_COMMANDS = ['claude', 'codex', 'cursor'];
12
13
  export const CONFIG_PATH = join(APP_HOME, 'config.json');
13
14
  const CODEX_AUTH_PATHS = [
14
15
  join(homedir(), '.codex', 'auth.json'),
@@ -24,6 +25,11 @@ function hasOldConfigFormat(raw) {
24
25
  const hasNew = raw.tools && typeof raw.tools === 'object' && raw.tools.claude;
25
26
  return !!hasOld && !hasNew;
26
27
  }
28
+ function normalizeAiCommand(value, fallback) {
29
+ return typeof value === 'string' && AI_COMMANDS.includes(value)
30
+ ? value
31
+ : fallback;
32
+ }
27
33
  function hasCodexAuth() {
28
34
  if (process.env.OPENAI_API_KEY)
29
35
  return true;
@@ -323,7 +329,7 @@ export function loadConfig() {
323
329
  ? parseCommaSeparated(process.env.DINGTALK_ALLOWED_USER_IDS)
324
330
  : fileDingtalk?.allowedUserIds ?? allowedUserIds;
325
331
  // 5. AI / 工作目录 / 安全配置(从 tools 读取)
326
- const aiCommand = (process.env.AI_COMMAND ?? file.aiCommand ?? 'claude');
332
+ const aiCommand = normalizeAiCommand(process.env.AI_COMMAND ?? file.aiCommand, 'claude');
327
333
  const tc = file.tools?.claude ?? {};
328
334
  const tcur = file.tools?.cursor ?? {};
329
335
  const tcod = file.tools?.codex ?? {};
@@ -550,35 +556,42 @@ export function loadConfig() {
550
556
  telegram: telegramEnabled
551
557
  ? {
552
558
  enabled: true,
559
+ aiCommand: normalizeAiCommand(file.platforms?.telegram?.aiCommand, aiCommand),
553
560
  proxy: process.env.TELEGRAM_PROXY ?? file.platforms?.telegram?.proxy,
554
561
  allowedUserIds: telegramAllowedUserIds,
555
562
  }
556
563
  : {
557
564
  enabled: false,
565
+ aiCommand: normalizeAiCommand(file.platforms?.telegram?.aiCommand, aiCommand),
558
566
  proxy: process.env.TELEGRAM_PROXY ?? file.platforms?.telegram?.proxy,
559
567
  allowedUserIds: telegramAllowedUserIds,
560
568
  },
561
569
  feishu: feishuEnabled
562
570
  ? {
563
571
  enabled: true,
572
+ aiCommand: normalizeAiCommand(file.platforms?.feishu?.aiCommand, aiCommand),
564
573
  allowedUserIds: feishuAllowedUserIds,
565
574
  }
566
575
  : {
567
576
  enabled: false,
577
+ aiCommand: normalizeAiCommand(file.platforms?.feishu?.aiCommand, aiCommand),
568
578
  allowedUserIds: feishuAllowedUserIds,
569
579
  },
570
580
  qq: qqEnabled
571
581
  ? {
572
582
  enabled: true,
583
+ aiCommand: normalizeAiCommand(file.platforms?.qq?.aiCommand, aiCommand),
573
584
  allowedUserIds: qqAllowedUserIds,
574
585
  }
575
586
  : {
576
587
  enabled: false,
588
+ aiCommand: normalizeAiCommand(file.platforms?.qq?.aiCommand, aiCommand),
577
589
  allowedUserIds: qqAllowedUserIds,
578
590
  },
579
591
  wechat: wechatEnabled
580
592
  ? {
581
593
  enabled: true,
594
+ aiCommand: normalizeAiCommand(file.platforms?.wechat?.aiCommand, aiCommand),
582
595
  wsUrl: wechatWsUrl,
583
596
  token: wechatToken,
584
597
  jwtToken: wechatJwtToken,
@@ -589,6 +602,7 @@ export function loadConfig() {
589
602
  }
590
603
  : {
591
604
  enabled: false,
605
+ aiCommand: normalizeAiCommand(file.platforms?.wechat?.aiCommand, aiCommand),
592
606
  wsUrl: wechatWsUrl,
593
607
  token: wechatToken,
594
608
  jwtToken: wechatJwtToken,
@@ -600,20 +614,24 @@ export function loadConfig() {
600
614
  wework: weworkEnabled
601
615
  ? {
602
616
  enabled: true,
617
+ aiCommand: normalizeAiCommand(file.platforms?.wework?.aiCommand, aiCommand),
603
618
  allowedUserIds: weworkAllowedUserIds,
604
619
  }
605
620
  : {
606
621
  enabled: false,
622
+ aiCommand: normalizeAiCommand(file.platforms?.wework?.aiCommand, aiCommand),
607
623
  allowedUserIds: weworkAllowedUserIds,
608
624
  },
609
625
  dingtalk: dingtalkEnabled
610
626
  ? {
611
627
  enabled: true,
628
+ aiCommand: normalizeAiCommand(file.platforms?.dingtalk?.aiCommand, aiCommand),
612
629
  allowedUserIds: dingtalkAllowedUserIds,
613
630
  cardTemplateId: dingtalkCardTemplateId,
614
631
  }
615
632
  : {
616
633
  enabled: false,
634
+ aiCommand: normalizeAiCommand(file.platforms?.dingtalk?.aiCommand, aiCommand),
617
635
  allowedUserIds: dingtalkAllowedUserIds,
618
636
  cardTemplateId: dingtalkCardTemplateId,
619
637
  },
@@ -683,3 +701,13 @@ export function getPlatformsWithCredentials(config) {
683
701
  r.push('wechat');
684
702
  return r;
685
703
  }
704
+ export function resolvePlatformAiCommand(config, platform) {
705
+ return config.platforms[platform]?.aiCommand ?? config.aiCommand;
706
+ }
707
+ export function getConfiguredAiCommands(config) {
708
+ const commands = new Set([config.aiCommand]);
709
+ for (const platform of config.enabledPlatforms) {
710
+ commands.add(resolvePlatformAiCommand(config, platform));
711
+ }
712
+ return Array.from(commands);
713
+ }
@@ -1,5 +1,5 @@
1
1
  import type { DWClientDownStream } from 'dingtalk-stream';
2
- import type { Config } from '../config.js';
2
+ import { type Config } from '../config.js';
3
3
  import type { SessionManager } from '../session/session-manager.js';
4
4
  export interface DingTalkEventHandlerHandle {
5
5
  stop: () => void;