@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.
- package/dist/adapters/codebuddy-adapter.js +3 -17
- package/dist/adapters/codex-adapter.js +3 -16
- package/dist/adapters/opencode-adapter.d.ts +0 -5
- package/dist/adapters/opencode-adapter.js +10 -25
- package/dist/adapters/registry.js +10 -1
- package/dist/clawbot/client.js +72 -78
- package/dist/clawbot/qr-login.d.ts +1 -0
- package/dist/clawbot/qr-login.js +7 -0
- package/dist/codebuddy/cli-runner.d.ts +2 -22
- package/dist/codebuddy/cli-runner.js +9 -27
- package/dist/codex/cli-runner.d.ts +2 -22
- package/dist/codex/cli-runner.js +24 -75
- package/dist/commands/handler.d.ts +1 -0
- package/dist/commands/handler.js +34 -0
- package/dist/config/types.d.ts +23 -0
- package/dist/config-web-auth.d.ts +13 -0
- package/dist/config-web-auth.js +114 -0
- package/dist/config-web-browser.d.ts +4 -0
- package/dist/config-web-browser.js +48 -0
- package/dist/config-web-cors.d.ts +5 -0
- package/dist/config-web-cors.js +48 -0
- package/dist/config-web-health.d.ts +7 -0
- package/dist/config-web-health.js +65 -0
- package/dist/config-web-http.d.ts +3 -0
- package/dist/config-web-http.js +31 -0
- package/dist/config-web-page-i18n.d.ts +36 -2
- package/dist/config-web-page-i18n.js +36 -2
- package/dist/config-web-payload.d.ts +84 -0
- package/dist/config-web-payload.js +260 -0
- package/dist/config-web-probes.d.ts +2 -0
- package/dist/config-web-probes.js +271 -0
- package/dist/config-web.d.ts +3 -11
- package/dist/config-web.js +45 -815
- package/dist/config.js +22 -1
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +11 -0
- package/dist/opencode/cli-runner.d.ts +2 -22
- package/dist/opencode/cli-runner.js +47 -68
- package/dist/opencode/sdk-manager.d.ts +11 -0
- package/dist/opencode/sdk-manager.js +46 -0
- package/dist/opencode/sdk-runner.d.ts +7 -0
- package/dist/opencode/sdk-runner.js +225 -0
- package/dist/platform/handle-ai-request.js +18 -1
- package/dist/qq/client.js +45 -53
- package/dist/shared/ai-task.d.ts +15 -0
- package/dist/shared/ai-task.js +174 -2
- package/dist/shared/cli-runner-base.d.ts +39 -0
- package/dist/shared/cli-runner-base.js +108 -0
- package/dist/shared/connection-manager.d.ts +111 -0
- package/dist/shared/connection-manager.js +157 -0
- package/dist/shared/session-invalid-detector.d.ts +5 -0
- package/dist/shared/session-invalid-detector.js +20 -0
- package/dist/wework/client.js +79 -127
- package/dist/workbuddy/client.js +40 -74
- package/package.json +4 -1
- package/web/dist/assets/index-6e51Wtaa.css +1 -0
- package/web/dist/assets/index-Da9qKWA2.js +57 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-XKVTb-0p.css +0 -1
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|