@wu529778790/open-im 1.11.8-beta.2 → 1.11.8-beta.21

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 (60) hide show
  1. package/dist/adapters/codebuddy-adapter.js +3 -17
  2. package/dist/adapters/codex-adapter.js +3 -16
  3. package/dist/adapters/opencode-adapter.d.ts +0 -5
  4. package/dist/adapters/opencode-adapter.js +10 -25
  5. package/dist/adapters/registry.js +10 -1
  6. package/dist/clawbot/client.js +72 -78
  7. package/dist/clawbot/qr-login.d.ts +1 -0
  8. package/dist/clawbot/qr-login.js +7 -0
  9. package/dist/codebuddy/cli-runner.d.ts +2 -22
  10. package/dist/codebuddy/cli-runner.js +9 -27
  11. package/dist/codex/cli-runner.d.ts +2 -22
  12. package/dist/codex/cli-runner.js +24 -75
  13. package/dist/commands/handler.d.ts +1 -0
  14. package/dist/commands/handler.js +34 -0
  15. package/dist/config/types.d.ts +23 -0
  16. package/dist/config-web-auth.d.ts +13 -0
  17. package/dist/config-web-auth.js +114 -0
  18. package/dist/config-web-browser.d.ts +4 -0
  19. package/dist/config-web-browser.js +48 -0
  20. package/dist/config-web-cors.d.ts +5 -0
  21. package/dist/config-web-cors.js +48 -0
  22. package/dist/config-web-health.d.ts +7 -0
  23. package/dist/config-web-health.js +65 -0
  24. package/dist/config-web-http.d.ts +3 -0
  25. package/dist/config-web-http.js +31 -0
  26. package/dist/config-web-page-i18n.d.ts +36 -2
  27. package/dist/config-web-page-i18n.js +36 -2
  28. package/dist/config-web-payload.d.ts +84 -0
  29. package/dist/config-web-payload.js +260 -0
  30. package/dist/config-web-probes.d.ts +2 -0
  31. package/dist/config-web-probes.js +271 -0
  32. package/dist/config-web.d.ts +3 -11
  33. package/dist/config-web.js +45 -815
  34. package/dist/config.js +22 -1
  35. package/dist/constants.d.ts +10 -0
  36. package/dist/constants.js +11 -0
  37. package/dist/opencode/cli-runner.d.ts +2 -22
  38. package/dist/opencode/cli-runner.js +47 -68
  39. package/dist/opencode/sdk-manager.d.ts +11 -0
  40. package/dist/opencode/sdk-manager.js +46 -0
  41. package/dist/opencode/sdk-runner.d.ts +7 -0
  42. package/dist/opencode/sdk-runner.js +225 -0
  43. package/dist/platform/handle-ai-request.js +18 -1
  44. package/dist/qq/client.js +45 -53
  45. package/dist/shared/ai-task.d.ts +15 -0
  46. package/dist/shared/ai-task.js +174 -2
  47. package/dist/shared/cli-runner-base.d.ts +39 -0
  48. package/dist/shared/cli-runner-base.js +108 -0
  49. package/dist/shared/connection-manager.d.ts +111 -0
  50. package/dist/shared/connection-manager.js +157 -0
  51. package/dist/shared/session-invalid-detector.d.ts +5 -0
  52. package/dist/shared/session-invalid-detector.js +20 -0
  53. package/dist/wework/client.js +79 -127
  54. package/dist/workbuddy/client.js +40 -74
  55. package/package.json +4 -1
  56. package/web/dist/assets/index-6e51Wtaa.css +1 -0
  57. package/web/dist/assets/index-Da9qKWA2.js +57 -0
  58. package/web/dist/index.html +2 -2
  59. package/web/dist/assets/index-XKVTb-0p.css +0 -1
  60. package/web/dist/assets/index-yDVGC_84.js +0 -57
@@ -66,6 +66,19 @@ export declare const PAGE_TEXTS: {
66
66
  readonly qrLoginFailed: "Login failed";
67
67
  readonly qrLoginExpired: "QR code expired, please try again";
68
68
  readonly qrScanHint: "Use WeChat to scan the QR code";
69
+ readonly configure: "Configure";
70
+ readonly rebind: "Rebind";
71
+ readonly platformBound: "Bound";
72
+ readonly platformUnbound: "Not bound";
73
+ readonly qrModalTitle: "Scan to Bind";
74
+ readonly qrModalHint: "Scan the QR code below with WeChat to bind.";
75
+ readonly qrModalGenerating: "Generating QR code...";
76
+ readonly qrModalScanning: "Waiting for scan...";
77
+ readonly qrModalSuccess: "Bound successfully";
78
+ readonly qrModalExpired: "QR code expired";
79
+ readonly qrModalRetry: "Scan again";
80
+ readonly qrModalClose: "Close";
81
+ readonly qrModalError: "Binding failed";
69
82
  readonly platformCredentialsTitle: "Credentials";
70
83
  readonly platformAccessTitle: "Routing and access";
71
84
  readonly platformTestNote: "Checks required credentials against the platform.";
@@ -109,7 +122,7 @@ export declare const PAGE_TEXTS: {
109
122
  readonly claudeCli: "Claude CLI path";
110
123
  readonly codexCli: "Codex CLI path";
111
124
  readonly codebuddyCli: "CodeBuddy CLI path";
112
- readonly opencodeCli: "OpenCode CLI path";
125
+ readonly opencodeSdkInfo: "OpenCode uses the built-in SDK integration. No CLI path needed.";
113
126
  readonly codexProxy: "Codex proxy";
114
127
  readonly codexApiKey: "OPENAI_API_KEY";
115
128
  readonly codexApiKeyTip: "Set the OpenAI API key used by Codex. You can also edit the auth file below.";
@@ -210,6 +223,10 @@ export declare const PAGE_TEXTS: {
210
223
  readonly wizardAuthApiKey: "API Key";
211
224
  readonly wizardAuthToken: "Auth Token";
212
225
  readonly wizardLoading: "Loading...";
226
+ readonly aiCommandPickerTitle: "🤖 AI Tool";
227
+ readonly aiCommandPickerHint: "Messages from this channel will be replied by the AI selected above";
228
+ readonly platformStatusOn: "Connected";
229
+ readonly platformStatusOff: "Not connected";
213
230
  };
214
231
  readonly zh: {
215
232
  readonly pageTitle: "open-im 本地控制台";
@@ -278,6 +295,19 @@ export declare const PAGE_TEXTS: {
278
295
  readonly qrLoginFailed: "登录失败";
279
296
  readonly qrLoginExpired: "二维码已过期,请重试";
280
297
  readonly qrScanHint: "请使用微信扫描二维码";
298
+ readonly configure: "配置";
299
+ readonly rebind: "重新绑定";
300
+ readonly platformBound: "已绑定";
301
+ readonly platformUnbound: "未绑定";
302
+ readonly qrModalTitle: "扫码绑定";
303
+ readonly qrModalHint: "请用微信扫描下方二维码完成绑定";
304
+ readonly qrModalGenerating: "正在生成二维码...";
305
+ readonly qrModalScanning: "等待扫码...";
306
+ readonly qrModalSuccess: "绑定成功";
307
+ readonly qrModalExpired: "二维码已过期";
308
+ readonly qrModalRetry: "重新扫码";
309
+ readonly qrModalClose: "关闭";
310
+ readonly qrModalError: "绑定失败";
281
311
  readonly platformCredentialsTitle: "凭证";
282
312
  readonly platformAccessTitle: "路由与访问";
283
313
  readonly platformTestNote: "只会检查该平台的必填凭证是否可用。";
@@ -320,7 +350,7 @@ export declare const PAGE_TEXTS: {
320
350
  readonly claudeCli: "Claude CLI 路径";
321
351
  readonly codexCli: "Codex CLI 路径";
322
352
  readonly codebuddyCli: "CodeBuddy CLI 路径";
323
- readonly opencodeCli: "OpenCode CLI 路径";
353
+ readonly opencodeSdkInfo: "OpenCode 使用 SDK 集成,无需配置 CLI 路径。";
324
354
  readonly codexProxy: "Codex 代理";
325
355
  readonly codexApiKey: "OPENAI_API_KEY";
326
356
  readonly codexApiKeyTip: "设置 Codex 使用的 OpenAI API Key。也可以在下方编辑 auth 文件。";
@@ -419,5 +449,9 @@ export declare const PAGE_TEXTS: {
419
449
  readonly wizardAuthApiKey: "API Key";
420
450
  readonly wizardAuthToken: "Auth Token";
421
451
  readonly wizardLoading: "加载中...";
452
+ readonly aiCommandPickerTitle: "🤖 AI 工具";
453
+ readonly aiCommandPickerHint: "这个渠道收到的消息会用上面的 AI 回复";
454
+ readonly platformStatusOn: "已连接";
455
+ readonly platformStatusOff: "未连接";
422
456
  };
423
457
  };
@@ -66,6 +66,19 @@ export const PAGE_TEXTS = {
66
66
  qrLoginFailed: "Login failed",
67
67
  qrLoginExpired: "QR code expired, please try again",
68
68
  qrScanHint: "Use WeChat to scan the QR code",
69
+ configure: "Configure",
70
+ rebind: "Rebind",
71
+ platformBound: "Bound",
72
+ platformUnbound: "Not bound",
73
+ qrModalTitle: "Scan to Bind",
74
+ qrModalHint: "Scan the QR code below with WeChat to bind.",
75
+ qrModalGenerating: "Generating QR code...",
76
+ qrModalScanning: "Waiting for scan...",
77
+ qrModalSuccess: "Bound successfully",
78
+ qrModalExpired: "QR code expired",
79
+ qrModalRetry: "Scan again",
80
+ qrModalClose: "Close",
81
+ qrModalError: "Binding failed",
69
82
  platformCredentialsTitle: "Credentials",
70
83
  platformAccessTitle: "Routing and access",
71
84
  platformTestNote: "Checks required credentials against the platform.",
@@ -109,7 +122,7 @@ export const PAGE_TEXTS = {
109
122
  claudeCli: "Claude CLI path",
110
123
  codexCli: "Codex CLI path",
111
124
  codebuddyCli: "CodeBuddy CLI path",
112
- opencodeCli: "OpenCode CLI path",
125
+ opencodeSdkInfo: "OpenCode uses the built-in SDK integration. No CLI path needed.",
113
126
  codexProxy: "Codex proxy",
114
127
  codexApiKey: "OPENAI_API_KEY",
115
128
  codexApiKeyTip: "Set the OpenAI API key used by Codex. You can also edit the auth file below.",
@@ -211,6 +224,10 @@ export const PAGE_TEXTS = {
211
224
  wizardAuthApiKey: "API Key",
212
225
  wizardAuthToken: "Auth Token",
213
226
  wizardLoading: "Loading...",
227
+ aiCommandPickerTitle: "🤖 AI Tool",
228
+ aiCommandPickerHint: "Messages from this channel will be replied by the AI selected above",
229
+ platformStatusOn: "Connected",
230
+ platformStatusOff: "Not connected",
214
231
  },
215
232
  zh: {
216
233
  pageTitle: "open-im \u672c\u5730\u63a7\u5236\u53f0",
@@ -279,6 +296,19 @@ export const PAGE_TEXTS = {
279
296
  qrLoginFailed: "\u767b\u5f55\u5931\u8d25",
280
297
  qrLoginExpired: "\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f\uff0c\u8bf7\u91cd\u8bd5",
281
298
  qrScanHint: "\u8bf7\u4f7f\u7528\u5fae\u4fe1\u626b\u63cf\u4e8c\u7ef4\u7801",
299
+ configure: "\u914d\u7f6e",
300
+ rebind: "\u91cd\u65b0\u7ed1\u5b9a",
301
+ platformBound: "\u5df2\u7ed1\u5b9a",
302
+ platformUnbound: "\u672a\u7ed1\u5b9a",
303
+ qrModalTitle: "\u626b\u7801\u7ed1\u5b9a",
304
+ qrModalHint: "\u8bf7\u7528\u5fae\u4fe1\u626b\u63cf\u4e0b\u65b9\u4e8c\u7ef4\u7801\u5b8c\u6210\u7ed1\u5b9a",
305
+ qrModalGenerating: "\u6b63\u5728\u751f\u6210\u4e8c\u7ef4\u7801...",
306
+ qrModalScanning: "\u7b49\u5f85\u626b\u7801...",
307
+ qrModalSuccess: "\u7ed1\u5b9a\u6210\u529f",
308
+ qrModalExpired: "\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f",
309
+ qrModalRetry: "\u91cd\u65b0\u626b\u7801",
310
+ qrModalClose: "\u5173\u95ed",
311
+ qrModalError: "\u7ed1\u5b9a\u5931\u8d25",
282
312
  platformCredentialsTitle: "\u51ed\u8bc1",
283
313
  platformAccessTitle: "\u8def\u7531\u4e0e\u8bbf\u95ee",
284
314
  platformTestNote: "\u53ea\u4f1a\u68c0\u67e5\u8be5\u5e73\u53f0\u7684\u5fc5\u586b\u51ed\u8bc1\u662f\u5426\u53ef\u7528\u3002",
@@ -321,7 +351,7 @@ export const PAGE_TEXTS = {
321
351
  claudeCli: "Claude CLI \u8def\u5f84",
322
352
  codexCli: "Codex CLI \u8def\u5f84",
323
353
  codebuddyCli: "CodeBuddy CLI \u8def\u5f84",
324
- opencodeCli: "OpenCode CLI \u8def\u5f84",
354
+ opencodeSdkInfo: "OpenCode \u4f7f\u7528 SDK \u96c6\u6210\uff0c\u65e0\u9700\u914d\u7f6e CLI \u8def\u5f84\u3002",
325
355
  codexProxy: "Codex \u4ee3\u7406",
326
356
  codexApiKey: "OPENAI_API_KEY",
327
357
  codexApiKeyTip: "\u8bbe\u7f6e Codex \u4f7f\u7528\u7684 OpenAI API Key\u3002\u4e5f\u53ef\u4ee5\u5728\u4e0b\u65b9\u7f16\u8f91 auth \u6587\u4ef6\u3002",
@@ -421,5 +451,9 @@ export const PAGE_TEXTS = {
421
451
  wizardAuthApiKey: "API Key",
422
452
  wizardAuthToken: "Auth Token",
423
453
  wizardLoading: "\u52a0\u8f7d\u4e2d...",
454
+ aiCommandPickerTitle: "🤖 AI 工具",
455
+ aiCommandPickerHint: "这个渠道收到的消息会用上面的 AI 回复",
456
+ platformStatusOn: "已连接",
457
+ platformStatusOff: "未连接",
424
458
  }
425
459
  };
@@ -0,0 +1,84 @@
1
+ import { type FileConfig } from "./config.js";
2
+ import type { AiCommand } from "./adapters/tool-registry.js";
3
+ /** 前端 aiCommand 可为空字符串(未选择态);保存时经 persistedPlatformAi 规范化 */
4
+ export type WebAiCommand = AiCommand | "";
5
+ export interface WebConfigPayload {
6
+ platforms: {
7
+ telegram: {
8
+ enabled: boolean;
9
+ aiCommand: WebAiCommand;
10
+ botToken: string;
11
+ proxy: string;
12
+ allowedUserIds: string;
13
+ };
14
+ feishu: {
15
+ enabled: boolean;
16
+ aiCommand: WebAiCommand;
17
+ appId: string;
18
+ appSecret: string;
19
+ allowedUserIds: string;
20
+ };
21
+ qq: {
22
+ enabled: boolean;
23
+ aiCommand: WebAiCommand;
24
+ appId: string;
25
+ secret: string;
26
+ allowedUserIds: string;
27
+ };
28
+ wework: {
29
+ enabled: boolean;
30
+ aiCommand: WebAiCommand;
31
+ corpId: string;
32
+ secret: string;
33
+ allowedUserIds: string;
34
+ };
35
+ dingtalk: {
36
+ enabled: boolean;
37
+ aiCommand: WebAiCommand;
38
+ clientId: string;
39
+ clientSecret: string;
40
+ cardTemplateId: string;
41
+ allowedUserIds: string;
42
+ };
43
+ workbuddy: {
44
+ enabled: boolean;
45
+ aiCommand: WebAiCommand;
46
+ accessToken: string;
47
+ refreshToken: string;
48
+ userId: string;
49
+ baseUrl: string;
50
+ allowedUserIds: string;
51
+ };
52
+ clawbot: {
53
+ enabled: boolean;
54
+ aiCommand: WebAiCommand;
55
+ apiUrl: string;
56
+ apiToken: string;
57
+ allowedUserIds: string;
58
+ };
59
+ };
60
+ ai: {
61
+ claudeWorkDir: string;
62
+ claudeConfigPath: string;
63
+ claudeAuthToken: string;
64
+ claudeBaseUrl: string;
65
+ claudeModel: string;
66
+ claudeProxy: string;
67
+ codexCliPath: string;
68
+ codebuddyCliPath: string;
69
+ opencodeCliPath: string;
70
+ codexProxy: string;
71
+ codexApiKey?: string;
72
+ logDir?: string;
73
+ logLevel: "default" | "DEBUG" | "INFO" | "WARN" | "ERROR";
74
+ };
75
+ }
76
+ export declare function clean(value: string | undefined): string | undefined;
77
+ export declare function persistedPlatformAi(v: string | undefined): AiCommand;
78
+ export declare function isMasked(value: string | undefined): boolean;
79
+ /** 如果前端传回的是掩码值(包含 ****),保留 existing 中的真实密钥,避免覆盖 */
80
+ export declare function resolveSecret(incoming: string | undefined, existing: string | undefined): string | undefined;
81
+ export declare function maskSecret(value: string | undefined): string;
82
+ export declare function buildInitialPayload(file: FileConfig): WebConfigPayload;
83
+ export declare function validatePayload(payload: WebConfigPayload): string[];
84
+ export declare function toFileConfig(payload: WebConfigPayload, existing: FileConfig): FileConfig;
@@ -0,0 +1,260 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, normalizeAiCommand, CODEX_AUTH_PATHS, } from "./config.js";
3
+ import { splitCsv } from "./config-web-cors.js";
4
+ export function clean(value) {
5
+ if (!value)
6
+ return undefined;
7
+ const trimmed = value.trim();
8
+ return trimmed ? trimmed : undefined;
9
+ }
10
+ export function persistedPlatformAi(v) {
11
+ // 经 registry 规范化;此前硬编码只认 codex/codebuddy/claude,会把 opencode 降级为 claude。
12
+ return normalizeAiCommand(clean(v), "claude");
13
+ }
14
+ export function isMasked(value) {
15
+ return typeof value === "string" && value.includes("****");
16
+ }
17
+ /** 如果前端传回的是掩码值(包含 ****),保留 existing 中的真实密钥,避免覆盖 */
18
+ export function resolveSecret(incoming, existing) {
19
+ if (isMasked(incoming))
20
+ return existing;
21
+ return clean(incoming);
22
+ }
23
+ export function maskSecret(value) {
24
+ if (!value || value.length <= 4)
25
+ return value ? "****" : "";
26
+ return value.slice(0, 2) + "****" + value.slice(-2);
27
+ }
28
+ export function buildInitialPayload(file) {
29
+ // Load Claude settings from ~/.claude/settings.json
30
+ const claudeEnv = loadClaudeSettingsEnv();
31
+ return {
32
+ platforms: {
33
+ telegram: {
34
+ enabled: file.platforms?.telegram?.enabled ?? Boolean(file.platforms?.telegram?.botToken),
35
+ aiCommand: normalizeAiCommand(file.platforms?.telegram?.aiCommand, "claude"),
36
+ botToken: maskSecret(file.platforms?.telegram?.botToken),
37
+ proxy: file.platforms?.telegram?.proxy ?? "",
38
+ allowedUserIds: (file.platforms?.telegram?.allowedUserIds ?? []).join(", "),
39
+ },
40
+ feishu: {
41
+ enabled: file.platforms?.feishu?.enabled ?? Boolean(file.platforms?.feishu?.appId && file.platforms?.feishu?.appSecret),
42
+ aiCommand: normalizeAiCommand(file.platforms?.feishu?.aiCommand, "claude"),
43
+ appId: file.platforms?.feishu?.appId ?? "",
44
+ appSecret: maskSecret(file.platforms?.feishu?.appSecret),
45
+ allowedUserIds: (file.platforms?.feishu?.allowedUserIds ?? []).join(", "),
46
+ },
47
+ qq: {
48
+ enabled: file.platforms?.qq?.enabled ?? Boolean(file.platforms?.qq?.appId && file.platforms?.qq?.secret),
49
+ aiCommand: normalizeAiCommand(file.platforms?.qq?.aiCommand, "claude"),
50
+ appId: file.platforms?.qq?.appId ?? "",
51
+ secret: maskSecret(file.platforms?.qq?.secret),
52
+ allowedUserIds: (file.platforms?.qq?.allowedUserIds ?? []).join(", "),
53
+ },
54
+ wework: {
55
+ enabled: file.platforms?.wework?.enabled ?? Boolean(file.platforms?.wework?.corpId && file.platforms?.wework?.secret),
56
+ aiCommand: normalizeAiCommand(file.platforms?.wework?.aiCommand, "claude"),
57
+ corpId: file.platforms?.wework?.corpId ?? "",
58
+ secret: maskSecret(file.platforms?.wework?.secret),
59
+ allowedUserIds: (file.platforms?.wework?.allowedUserIds ?? []).join(", "),
60
+ },
61
+ dingtalk: {
62
+ enabled: file.platforms?.dingtalk?.enabled ?? Boolean(file.platforms?.dingtalk?.clientId && file.platforms?.dingtalk?.clientSecret),
63
+ aiCommand: normalizeAiCommand(file.platforms?.dingtalk?.aiCommand, "claude"),
64
+ clientId: file.platforms?.dingtalk?.clientId ?? "",
65
+ clientSecret: maskSecret(file.platforms?.dingtalk?.clientSecret),
66
+ cardTemplateId: file.platforms?.dingtalk?.cardTemplateId ?? "",
67
+ allowedUserIds: (file.platforms?.dingtalk?.allowedUserIds ?? []).join(", "),
68
+ },
69
+ workbuddy: {
70
+ enabled: file.platforms?.workbuddy?.enabled ?? Boolean(file.platforms?.workbuddy?.accessToken && file.platforms?.workbuddy?.refreshToken && file.platforms?.workbuddy?.userId),
71
+ aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, "claude"),
72
+ accessToken: maskSecret(file.platforms?.workbuddy?.accessToken),
73
+ refreshToken: maskSecret(file.platforms?.workbuddy?.refreshToken),
74
+ userId: file.platforms?.workbuddy?.userId ?? "",
75
+ baseUrl: file.platforms?.workbuddy?.baseUrl ?? "",
76
+ allowedUserIds: (file.platforms?.workbuddy?.allowedUserIds ?? []).join(", "),
77
+ },
78
+ clawbot: {
79
+ enabled: file.platforms?.clawbot?.enabled ?? Boolean(file.platforms?.clawbot?.apiToken),
80
+ aiCommand: normalizeAiCommand(file.platforms?.clawbot?.aiCommand, "claude"),
81
+ apiUrl: file.platforms?.clawbot?.apiUrl ?? "http://127.0.0.1:26322",
82
+ apiToken: maskSecret(file.platforms?.clawbot?.apiToken),
83
+ allowedUserIds: (file.platforms?.clawbot?.allowedUserIds ?? []).join(", "),
84
+ },
85
+ },
86
+ ai: {
87
+ claudeWorkDir: file.tools?.claude?.workDir ?? process.cwd(),
88
+ claudeConfigPath: process.platform === 'win32'
89
+ ? getClaudeConfigHome() + "\\.claude\\settings.json"
90
+ : getClaudeConfigHome() + "/.claude/settings.json",
91
+ claudeAuthToken: maskSecret(claudeEnv.ANTHROPIC_AUTH_TOKEN),
92
+ claudeBaseUrl: claudeEnv.ANTHROPIC_BASE_URL ?? "",
93
+ claudeModel: claudeEnv.ANTHROPIC_MODEL ?? "",
94
+ claudeProxy: file.tools?.claude?.proxy ?? "",
95
+ codexCliPath: file.tools?.codex?.cliPath ?? "codex",
96
+ codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
97
+ opencodeCliPath: file.tools?.opencode?.cliPath ?? "opencode",
98
+ codexProxy: file.tools?.codex?.proxy ?? "",
99
+ codexApiKey: (() => {
100
+ if (process.env.OPENAI_API_KEY)
101
+ return maskSecret(process.env.OPENAI_API_KEY);
102
+ for (const p of CODEX_AUTH_PATHS) {
103
+ try {
104
+ if (existsSync(p)) {
105
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
106
+ const key = raw?.openai_api_key ?? raw?.apiKey;
107
+ if (typeof key === "string" && key)
108
+ return maskSecret(key);
109
+ }
110
+ }
111
+ catch { /* ignore */ }
112
+ }
113
+ return "";
114
+ })(),
115
+ logDir: file.logDir ?? "",
116
+ logLevel: file.logLevel ?? "default",
117
+ },
118
+ };
119
+ }
120
+ export function validatePayload(payload) {
121
+ const errors = [];
122
+ const enabledCount = Object.values(payload.platforms).filter((item) => item.enabled).length;
123
+ if (enabledCount === 0)
124
+ errors.push("At least one platform must be enabled.");
125
+ if (payload.platforms.telegram.enabled && !clean(payload.platforms.telegram.botToken))
126
+ errors.push("Telegram bot token is required.");
127
+ if (payload.platforms.feishu.enabled && !clean(payload.platforms.feishu.appId))
128
+ errors.push("Feishu app ID is required.");
129
+ if (payload.platforms.feishu.enabled && !clean(payload.platforms.feishu.appSecret))
130
+ errors.push("Feishu app secret is required.");
131
+ if (payload.platforms.qq.enabled && !clean(payload.platforms.qq.appId))
132
+ errors.push("QQ app ID is required.");
133
+ if (payload.platforms.qq.enabled && !clean(payload.platforms.qq.secret))
134
+ errors.push("QQ app secret is required.");
135
+ if (payload.platforms.wework.enabled && !clean(payload.platforms.wework.corpId))
136
+ errors.push("WeWork corp ID is required.");
137
+ if (payload.platforms.wework.enabled && !clean(payload.platforms.wework.secret))
138
+ errors.push("WeWork secret is required.");
139
+ if (payload.platforms.dingtalk.enabled && !clean(payload.platforms.dingtalk.clientId))
140
+ errors.push("DingTalk client ID is required.");
141
+ if (payload.platforms.dingtalk.enabled && !clean(payload.platforms.dingtalk.clientSecret))
142
+ errors.push("DingTalk client secret is required.");
143
+ if (payload.platforms.workbuddy.enabled && !clean(payload.platforms.workbuddy.accessToken))
144
+ errors.push("WorkBuddy access token is required.");
145
+ if (payload.platforms.workbuddy.enabled && !clean(payload.platforms.workbuddy.refreshToken))
146
+ errors.push("WorkBuddy refresh token is required.");
147
+ if (payload.platforms.workbuddy.enabled && !clean(payload.platforms.workbuddy.userId))
148
+ errors.push("WorkBuddy user ID is required.");
149
+ if (payload.platforms.clawbot.enabled && !clean(payload.platforms.clawbot.apiToken))
150
+ errors.push("ClawBot API token is required.");
151
+ if (!clean(payload.ai.claudeWorkDir))
152
+ errors.push("Default work directory is required.");
153
+ return errors;
154
+ }
155
+ export function toFileConfig(payload, existing) {
156
+ // Save Claude environment variables to ~/.claude/settings.json
157
+ const claudeEnv = {};
158
+ const existingClaudeEnv = loadClaudeSettingsEnv();
159
+ const resolvedAuthToken = resolveSecret(payload.ai.claudeAuthToken, existingClaudeEnv.ANTHROPIC_AUTH_TOKEN);
160
+ if (resolvedAuthToken)
161
+ claudeEnv.ANTHROPIC_AUTH_TOKEN = resolvedAuthToken;
162
+ if (payload.ai.claudeBaseUrl)
163
+ claudeEnv.ANTHROPIC_BASE_URL = payload.ai.claudeBaseUrl;
164
+ if (payload.ai.claudeModel)
165
+ claudeEnv.ANTHROPIC_MODEL = payload.ai.claudeModel;
166
+ if (Object.keys(claudeEnv).length > 0) {
167
+ saveClaudeSettingsEnv(claudeEnv);
168
+ }
169
+ // claudeConfigPath is informational only, not saved
170
+ const { env: _discardLegacyRootEnv, aiCommand: _discardLegacyGlobalAi, ...existingWithoutRootEnv } = existing;
171
+ return {
172
+ ...existingWithoutRootEnv,
173
+ logDir: payload.ai.logDir === undefined ? existing.logDir : clean(payload.ai.logDir),
174
+ logLevel: payload.ai.logLevel === "default" ? undefined : payload.ai.logLevel,
175
+ tools: {
176
+ claude: {
177
+ ...existing.tools?.claude,
178
+ workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
179
+ proxy: clean(payload.ai.claudeProxy),
180
+ // model is now saved to ~/.claude/settings.json as env var
181
+ },
182
+ codex: {
183
+ ...existing.tools?.codex,
184
+ cliPath: clean(payload.ai.codexCliPath) ?? "codex",
185
+ workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
186
+ proxy: clean(payload.ai.codexProxy),
187
+ },
188
+ codebuddy: {
189
+ ...existing.tools?.codebuddy,
190
+ cliPath: clean(payload.ai.codebuddyCliPath) ?? "codebuddy",
191
+ },
192
+ opencode: {
193
+ ...existing.tools?.opencode,
194
+ cliPath: clean(payload.ai.opencodeCliPath) ?? "opencode",
195
+ },
196
+ },
197
+ platforms: {
198
+ ...existing.platforms,
199
+ telegram: {
200
+ ...existing.platforms?.telegram,
201
+ enabled: payload.platforms.telegram.enabled,
202
+ aiCommand: persistedPlatformAi(payload.platforms.telegram.aiCommand),
203
+ botToken: resolveSecret(payload.platforms.telegram.botToken, existing.platforms?.telegram?.botToken),
204
+ proxy: clean(payload.platforms.telegram.proxy),
205
+ allowedUserIds: splitCsv(payload.platforms.telegram.allowedUserIds),
206
+ },
207
+ feishu: {
208
+ ...existing.platforms?.feishu,
209
+ enabled: payload.platforms.feishu.enabled,
210
+ aiCommand: persistedPlatformAi(payload.platforms.feishu.aiCommand),
211
+ appId: clean(payload.platforms.feishu.appId),
212
+ appSecret: resolveSecret(payload.platforms.feishu.appSecret, existing.platforms?.feishu?.appSecret),
213
+ allowedUserIds: splitCsv(payload.platforms.feishu.allowedUserIds),
214
+ },
215
+ qq: {
216
+ ...existing.platforms?.qq,
217
+ enabled: payload.platforms.qq.enabled,
218
+ aiCommand: persistedPlatformAi(payload.platforms.qq.aiCommand),
219
+ appId: clean(payload.platforms.qq.appId),
220
+ secret: resolveSecret(payload.platforms.qq.secret, existing.platforms?.qq?.secret),
221
+ allowedUserIds: splitCsv(payload.platforms.qq.allowedUserIds),
222
+ },
223
+ wework: {
224
+ ...existing.platforms?.wework,
225
+ enabled: payload.platforms.wework.enabled,
226
+ aiCommand: persistedPlatformAi(payload.platforms.wework.aiCommand),
227
+ corpId: clean(payload.platforms.wework.corpId),
228
+ secret: resolveSecret(payload.platforms.wework.secret, existing.platforms?.wework?.secret),
229
+ allowedUserIds: splitCsv(payload.platforms.wework.allowedUserIds),
230
+ },
231
+ dingtalk: {
232
+ ...existing.platforms?.dingtalk,
233
+ enabled: payload.platforms.dingtalk.enabled,
234
+ aiCommand: persistedPlatformAi(payload.platforms.dingtalk.aiCommand),
235
+ clientId: clean(payload.platforms.dingtalk.clientId),
236
+ clientSecret: resolveSecret(payload.platforms.dingtalk.clientSecret, existing.platforms?.dingtalk?.clientSecret),
237
+ cardTemplateId: clean(payload.platforms.dingtalk.cardTemplateId),
238
+ allowedUserIds: splitCsv(payload.platforms.dingtalk.allowedUserIds),
239
+ },
240
+ workbuddy: {
241
+ ...existing.platforms?.workbuddy,
242
+ enabled: payload.platforms.workbuddy.enabled,
243
+ aiCommand: persistedPlatformAi(payload.platforms.workbuddy.aiCommand),
244
+ accessToken: resolveSecret(payload.platforms.workbuddy.accessToken, existing.platforms?.workbuddy?.accessToken),
245
+ refreshToken: resolveSecret(payload.platforms.workbuddy.refreshToken, existing.platforms?.workbuddy?.refreshToken),
246
+ userId: clean(payload.platforms.workbuddy.userId),
247
+ baseUrl: clean(payload.platforms.workbuddy.baseUrl),
248
+ allowedUserIds: splitCsv(payload.platforms.workbuddy.allowedUserIds),
249
+ },
250
+ clawbot: {
251
+ ...existing.platforms?.clawbot,
252
+ enabled: payload.platforms.clawbot.enabled,
253
+ aiCommand: persistedPlatformAi(payload.platforms.clawbot.aiCommand),
254
+ apiUrl: clean(payload.platforms.clawbot.apiUrl) ?? "http://127.0.0.1:26322",
255
+ apiToken: resolveSecret(payload.platforms.clawbot.apiToken, existing.platforms?.clawbot?.apiToken),
256
+ allowedUserIds: splitCsv(payload.platforms.clawbot.allowedUserIds),
257
+ },
258
+ },
259
+ };
260
+ }
@@ -0,0 +1,2 @@
1
+ export declare function toErrorMessage(error: unknown): string;
2
+ export declare function testPlatformConfig(platform: string, config: Record<string, unknown>): Promise<string>;