@wu529778790/open-im 1.8.1-beta.2 → 1.8.1-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/access/access-control.js +1 -1
- package/dist/adapters/claude-sdk-adapter.js +94 -36
- package/dist/channels/capabilities.js +5 -0
- package/dist/cli.js +5 -2
- package/dist/commands/handler.d.ts +1 -2
- package/dist/commands/handler.js +6 -18
- package/dist/config-web-page-i18n.d.ts +12 -0
- package/dist/config-web-page-i18n.js +12 -0
- package/dist/config-web-page-script.js +1 -0
- package/dist/config-web-page-template.js +48 -1
- package/dist/config-web.js +110 -7
- package/dist/config.d.ts +25 -1
- package/dist/config.js +46 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/dingtalk/client.js +11 -3
- package/dist/dingtalk/event-handler.js +18 -3
- package/dist/dingtalk/message-sender.js +13 -0
- package/dist/feishu/event-handler.js +144 -10
- package/dist/index.js +26 -2
- package/dist/manager-control.js +7 -0
- package/dist/qq/client.js +111 -88
- package/dist/qq/event-handler.js +16 -2
- package/dist/qq/message-sender.js +11 -0
- package/dist/service-control.js +4 -0
- package/dist/session/session-manager.js +11 -1
- package/dist/setup.js +2 -1
- package/dist/shared/active-chats.d.ts +2 -2
- package/dist/shared/ai-task.js +13 -1
- package/dist/shared/chat-user-map.js +11 -0
- package/dist/shared/media-storage.js +27 -0
- package/dist/telegram/client.js +25 -3
- package/dist/telegram/event-handler.js +44 -8
- package/dist/telegram/message-sender.js +13 -0
- package/dist/wechat/auth/qclaw-api.js +1 -1
- package/dist/wechat/client.js +81 -4
- package/dist/wechat/event-handler.js +10 -3
- package/dist/wework/client.js +36 -14
- package/dist/wework/event-handler.js +39 -4
- package/dist/wework/message-sender.js +53 -21
- package/dist/workbuddy/centrifuge-client.d.ts +74 -0
- package/dist/workbuddy/centrifuge-client.js +272 -0
- package/dist/workbuddy/client.d.ts +27 -0
- package/dist/workbuddy/client.js +162 -0
- package/dist/workbuddy/event-handler.d.ts +11 -0
- package/dist/workbuddy/event-handler.js +118 -0
- package/dist/workbuddy/index.d.ts +8 -0
- package/dist/workbuddy/index.js +8 -0
- package/dist/workbuddy/message-sender.d.ts +16 -0
- package/dist/workbuddy/message-sender.js +51 -0
- package/dist/workbuddy/oauth.d.ts +114 -0
- package/dist/workbuddy/oauth.js +310 -0
- package/dist/workbuddy/types.d.ts +86 -0
- package/dist/workbuddy/types.js +4 -0
- package/package.json +4 -2
package/dist/config-web.js
CHANGED
|
@@ -117,6 +117,7 @@ export function getHealthPlatformSnapshot(file, env = process.env) {
|
|
|
117
117
|
const fileQQ = file.platforms?.qq;
|
|
118
118
|
const fileWework = file.platforms?.wework;
|
|
119
119
|
const fileDingtalk = file.platforms?.dingtalk;
|
|
120
|
+
const fileWorkbuddy = file.platforms?.workbuddy;
|
|
120
121
|
const telegramBotToken = env.TELEGRAM_BOT_TOKEN ?? fileTelegram?.botToken ?? file.telegramBotToken;
|
|
121
122
|
const feishuAppId = env.FEISHU_APP_ID ?? fileFeishu?.appId ?? file.feishuAppId;
|
|
122
123
|
const feishuAppSecret = env.FEISHU_APP_SECRET ?? fileFeishu?.appSecret ?? file.feishuAppSecret;
|
|
@@ -126,6 +127,9 @@ export function getHealthPlatformSnapshot(file, env = process.env) {
|
|
|
126
127
|
const weworkSecret = env.WEWORK_SECRET ?? fileWework?.secret;
|
|
127
128
|
const dingtalkClientId = env.DINGTALK_CLIENT_ID ?? fileDingtalk?.clientId;
|
|
128
129
|
const dingtalkClientSecret = env.DINGTALK_CLIENT_SECRET ?? fileDingtalk?.clientSecret;
|
|
130
|
+
const workbuddyAccessToken = fileWorkbuddy?.accessToken;
|
|
131
|
+
const workbuddyRefreshToken = fileWorkbuddy?.refreshToken;
|
|
132
|
+
const workbuddyUserId = fileWorkbuddy?.userId;
|
|
129
133
|
return {
|
|
130
134
|
telegram: {
|
|
131
135
|
configured: !!telegramBotToken,
|
|
@@ -157,6 +161,12 @@ export function getHealthPlatformSnapshot(file, env = process.env) {
|
|
|
157
161
|
healthy: !!(dingtalkClientId && dingtalkClientSecret),
|
|
158
162
|
message: dingtalkClientId && dingtalkClientSecret ? "Client ID and Secret configured" : "Missing credentials",
|
|
159
163
|
},
|
|
164
|
+
workbuddy: {
|
|
165
|
+
configured: !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId),
|
|
166
|
+
enabled: !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId) && fileWorkbuddy?.enabled !== false,
|
|
167
|
+
healthy: !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId),
|
|
168
|
+
message: workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId ? "OAuth credentials configured" : "Missing credentials",
|
|
169
|
+
},
|
|
160
170
|
};
|
|
161
171
|
}
|
|
162
172
|
function splitCsv(value) {
|
|
@@ -170,10 +180,20 @@ function clean(value) {
|
|
|
170
180
|
const trimmed = value.trim();
|
|
171
181
|
return trimmed ? trimmed : undefined;
|
|
172
182
|
}
|
|
183
|
+
const MAX_REQUEST_BODY_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
173
184
|
function readJson(request) {
|
|
174
185
|
return new Promise((resolve, reject) => {
|
|
175
186
|
const chunks = [];
|
|
176
|
-
|
|
187
|
+
let totalBytes = 0;
|
|
188
|
+
request.on("data", (chunk) => {
|
|
189
|
+
totalBytes += chunk.length;
|
|
190
|
+
if (totalBytes > MAX_REQUEST_BODY_BYTES) {
|
|
191
|
+
reject(new Error("Request body too large (max 1 MB)"));
|
|
192
|
+
request.destroy();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
chunks.push(Buffer.from(chunk));
|
|
196
|
+
});
|
|
177
197
|
request.on("end", () => {
|
|
178
198
|
try {
|
|
179
199
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
@@ -190,6 +210,11 @@ function json(response, statusCode, body) {
|
|
|
190
210
|
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
|
|
191
211
|
response.end(JSON.stringify(body));
|
|
192
212
|
}
|
|
213
|
+
function maskSecret(value) {
|
|
214
|
+
if (!value || value.length <= 4)
|
|
215
|
+
return value ? "****" : "";
|
|
216
|
+
return value.slice(0, 2) + "****" + value.slice(-2);
|
|
217
|
+
}
|
|
193
218
|
function buildInitialPayload(file) {
|
|
194
219
|
// Load Claude settings from ~/.claude/settings.json
|
|
195
220
|
const claudeEnv = loadClaudeSettingsEnv();
|
|
@@ -198,7 +223,7 @@ function buildInitialPayload(file) {
|
|
|
198
223
|
telegram: {
|
|
199
224
|
enabled: file.platforms?.telegram?.enabled ?? Boolean(file.platforms?.telegram?.botToken),
|
|
200
225
|
aiCommand: file.platforms?.telegram?.aiCommand ?? "",
|
|
201
|
-
botToken: file.platforms?.telegram?.botToken
|
|
226
|
+
botToken: maskSecret(file.platforms?.telegram?.botToken),
|
|
202
227
|
proxy: file.platforms?.telegram?.proxy ?? "",
|
|
203
228
|
allowedUserIds: (file.platforms?.telegram?.allowedUserIds ?? []).join(", "),
|
|
204
229
|
},
|
|
@@ -206,31 +231,40 @@ function buildInitialPayload(file) {
|
|
|
206
231
|
enabled: file.platforms?.feishu?.enabled ?? Boolean(file.platforms?.feishu?.appId && file.platforms?.feishu?.appSecret),
|
|
207
232
|
aiCommand: file.platforms?.feishu?.aiCommand ?? "",
|
|
208
233
|
appId: file.platforms?.feishu?.appId ?? "",
|
|
209
|
-
appSecret: file.platforms?.feishu?.appSecret
|
|
234
|
+
appSecret: maskSecret(file.platforms?.feishu?.appSecret),
|
|
210
235
|
allowedUserIds: (file.platforms?.feishu?.allowedUserIds ?? []).join(", "),
|
|
211
236
|
},
|
|
212
237
|
qq: {
|
|
213
238
|
enabled: file.platforms?.qq?.enabled ?? Boolean(file.platforms?.qq?.appId && file.platforms?.qq?.secret),
|
|
214
239
|
aiCommand: file.platforms?.qq?.aiCommand ?? "",
|
|
215
240
|
appId: file.platforms?.qq?.appId ?? "",
|
|
216
|
-
secret: file.platforms?.qq?.secret
|
|
241
|
+
secret: maskSecret(file.platforms?.qq?.secret),
|
|
217
242
|
allowedUserIds: (file.platforms?.qq?.allowedUserIds ?? []).join(", "),
|
|
218
243
|
},
|
|
219
244
|
wework: {
|
|
220
245
|
enabled: file.platforms?.wework?.enabled ?? Boolean(file.platforms?.wework?.corpId && file.platforms?.wework?.secret),
|
|
221
246
|
aiCommand: file.platforms?.wework?.aiCommand ?? "",
|
|
222
247
|
corpId: file.platforms?.wework?.corpId ?? "",
|
|
223
|
-
secret: file.platforms?.wework?.secret
|
|
248
|
+
secret: maskSecret(file.platforms?.wework?.secret),
|
|
224
249
|
allowedUserIds: (file.platforms?.wework?.allowedUserIds ?? []).join(", "),
|
|
225
250
|
},
|
|
226
251
|
dingtalk: {
|
|
227
252
|
enabled: file.platforms?.dingtalk?.enabled ?? Boolean(file.platforms?.dingtalk?.clientId && file.platforms?.dingtalk?.clientSecret),
|
|
228
253
|
aiCommand: file.platforms?.dingtalk?.aiCommand ?? "",
|
|
229
254
|
clientId: file.platforms?.dingtalk?.clientId ?? "",
|
|
230
|
-
clientSecret: file.platforms?.dingtalk?.clientSecret
|
|
255
|
+
clientSecret: maskSecret(file.platforms?.dingtalk?.clientSecret),
|
|
231
256
|
cardTemplateId: file.platforms?.dingtalk?.cardTemplateId ?? "",
|
|
232
257
|
allowedUserIds: (file.platforms?.dingtalk?.allowedUserIds ?? []).join(", "),
|
|
233
258
|
},
|
|
259
|
+
workbuddy: {
|
|
260
|
+
enabled: file.platforms?.workbuddy?.enabled ?? Boolean(file.platforms?.workbuddy?.accessToken && file.platforms?.workbuddy?.refreshToken && file.platforms?.workbuddy?.userId),
|
|
261
|
+
aiCommand: file.platforms?.workbuddy?.aiCommand ?? "",
|
|
262
|
+
accessToken: maskSecret(file.platforms?.workbuddy?.accessToken),
|
|
263
|
+
refreshToken: maskSecret(file.platforms?.workbuddy?.refreshToken),
|
|
264
|
+
userId: file.platforms?.workbuddy?.userId ?? "",
|
|
265
|
+
baseUrl: file.platforms?.workbuddy?.baseUrl ?? "",
|
|
266
|
+
allowedUserIds: (file.platforms?.workbuddy?.allowedUserIds ?? []).join(", "),
|
|
267
|
+
},
|
|
234
268
|
},
|
|
235
269
|
ai: {
|
|
236
270
|
aiCommand: file.aiCommand ?? "claude",
|
|
@@ -239,7 +273,7 @@ function buildInitialPayload(file) {
|
|
|
239
273
|
claudeConfigPath: process.platform === 'win32'
|
|
240
274
|
? getClaudeConfigHome() + "\\.claude\\settings.json"
|
|
241
275
|
: getClaudeConfigHome() + "/.claude/settings.json",
|
|
242
|
-
claudeAuthToken: claudeEnv.ANTHROPIC_AUTH_TOKEN
|
|
276
|
+
claudeAuthToken: maskSecret(claudeEnv.ANTHROPIC_AUTH_TOKEN),
|
|
243
277
|
claudeBaseUrl: claudeEnv.ANTHROPIC_BASE_URL ?? "",
|
|
244
278
|
claudeModel: claudeEnv.ANTHROPIC_MODEL ?? "",
|
|
245
279
|
claudeProxy: file.tools?.claude?.proxy ?? "",
|
|
@@ -276,6 +310,12 @@ function validatePayload(payload) {
|
|
|
276
310
|
errors.push("DingTalk client ID is required.");
|
|
277
311
|
if (payload.platforms.dingtalk.enabled && !clean(payload.platforms.dingtalk.clientSecret))
|
|
278
312
|
errors.push("DingTalk client secret is required.");
|
|
313
|
+
if (payload.platforms.workbuddy.enabled && !clean(payload.platforms.workbuddy.accessToken))
|
|
314
|
+
errors.push("WorkBuddy access token is required.");
|
|
315
|
+
if (payload.platforms.workbuddy.enabled && !clean(payload.platforms.workbuddy.refreshToken))
|
|
316
|
+
errors.push("WorkBuddy refresh token is required.");
|
|
317
|
+
if (payload.platforms.workbuddy.enabled && !clean(payload.platforms.workbuddy.userId))
|
|
318
|
+
errors.push("WorkBuddy user ID is required.");
|
|
279
319
|
if (!clean(payload.ai.claudeWorkDir))
|
|
280
320
|
errors.push("Default work directory is required.");
|
|
281
321
|
if (!Number.isFinite(payload.ai.claudeTimeoutMs) || payload.ai.claudeTimeoutMs <= 0)
|
|
@@ -330,6 +370,17 @@ function validateConfigForPlatform(platform, config) {
|
|
|
330
370
|
errors.push("DingTalk client secret is required and must be a non-empty string.");
|
|
331
371
|
}
|
|
332
372
|
break;
|
|
373
|
+
case "workbuddy":
|
|
374
|
+
if (!c.accessToken || typeof c.accessToken !== "string" || !clean(c.accessToken)) {
|
|
375
|
+
errors.push("WorkBuddy access token is required and must be a non-empty string.");
|
|
376
|
+
}
|
|
377
|
+
if (!c.refreshToken || typeof c.refreshToken !== "string" || !clean(c.refreshToken)) {
|
|
378
|
+
errors.push("WorkBuddy refresh token is required and must be a non-empty string.");
|
|
379
|
+
}
|
|
380
|
+
if (!c.userId || typeof c.userId !== "string" || !clean(c.userId)) {
|
|
381
|
+
errors.push("WorkBuddy user ID is required and must be a non-empty string.");
|
|
382
|
+
}
|
|
383
|
+
break;
|
|
333
384
|
default:
|
|
334
385
|
errors.push(`Unknown platform: ${platform}`);
|
|
335
386
|
}
|
|
@@ -359,6 +410,7 @@ function createProbeConfig(values) {
|
|
|
359
410
|
wechatAllowedUserIds: [],
|
|
360
411
|
weworkAllowedUserIds: [],
|
|
361
412
|
dingtalkAllowedUserIds: [],
|
|
413
|
+
workbuddyAllowedUserIds: [],
|
|
362
414
|
aiCommand: "claude",
|
|
363
415
|
codexCliPath: "codex",
|
|
364
416
|
claudeWorkDir: process.cwd(),
|
|
@@ -454,6 +506,34 @@ async function probeDingTalk(config) {
|
|
|
454
506
|
}
|
|
455
507
|
return "DingTalk credentials are valid.";
|
|
456
508
|
}
|
|
509
|
+
async function probeWorkBuddy(config) {
|
|
510
|
+
const accessToken = clean(String(config.accessToken ?? ""));
|
|
511
|
+
const refreshToken = clean(String(config.refreshToken ?? ""));
|
|
512
|
+
const userId = clean(String(config.userId ?? ""));
|
|
513
|
+
if (!accessToken || !refreshToken || !userId)
|
|
514
|
+
throw new Error("WorkBuddy access token, refresh token, and user ID are required.");
|
|
515
|
+
const baseUrl = clean(String(config.baseUrl ?? "")) || "https://copilot.tencent.com";
|
|
516
|
+
// Validate credentials by attempting to register workspace
|
|
517
|
+
const response = await fetch(`${baseUrl}/api/copilot/workspace/register`, {
|
|
518
|
+
method: "POST",
|
|
519
|
+
headers: {
|
|
520
|
+
"content-type": "application/json",
|
|
521
|
+
"authorization": `Bearer ${accessToken}`,
|
|
522
|
+
},
|
|
523
|
+
body: JSON.stringify({
|
|
524
|
+
userId,
|
|
525
|
+
hostId: "open-im-test",
|
|
526
|
+
workspaceId: "open-im-test-workspace",
|
|
527
|
+
workspaceName: "OpenIM Test Workspace",
|
|
528
|
+
}),
|
|
529
|
+
signal: AbortSignal.timeout(TEST_TIMEOUT_MS),
|
|
530
|
+
});
|
|
531
|
+
if (!response.ok) {
|
|
532
|
+
const body = await response.text();
|
|
533
|
+
throw new Error(`WorkBuddy authentication failed: ${body.slice(0, 200) || `HTTP ${response.status}`}`);
|
|
534
|
+
}
|
|
535
|
+
return "WorkBuddy credentials are valid.";
|
|
536
|
+
}
|
|
457
537
|
export async function testPlatformConfig(platform, config) {
|
|
458
538
|
const errors = validateConfigForPlatform(platform, config);
|
|
459
539
|
if (errors.length > 0) {
|
|
@@ -470,6 +550,8 @@ export async function testPlatformConfig(platform, config) {
|
|
|
470
550
|
return probeWeWork(config);
|
|
471
551
|
case "dingtalk":
|
|
472
552
|
return probeDingTalk(config);
|
|
553
|
+
case "workbuddy":
|
|
554
|
+
return probeWorkBuddy(config);
|
|
473
555
|
default:
|
|
474
556
|
throw new Error(`Unknown platform: ${platform}`);
|
|
475
557
|
}
|
|
@@ -556,6 +638,16 @@ function toFileConfig(payload, existing) {
|
|
|
556
638
|
cardTemplateId: clean(payload.platforms.dingtalk.cardTemplateId),
|
|
557
639
|
allowedUserIds: splitCsv(payload.platforms.dingtalk.allowedUserIds),
|
|
558
640
|
},
|
|
641
|
+
workbuddy: {
|
|
642
|
+
...existing.platforms?.workbuddy,
|
|
643
|
+
enabled: payload.platforms.workbuddy.enabled,
|
|
644
|
+
aiCommand: clean(payload.platforms.workbuddy.aiCommand),
|
|
645
|
+
accessToken: clean(payload.platforms.workbuddy.accessToken),
|
|
646
|
+
refreshToken: clean(payload.platforms.workbuddy.refreshToken),
|
|
647
|
+
userId: clean(payload.platforms.workbuddy.userId),
|
|
648
|
+
baseUrl: clean(payload.platforms.workbuddy.baseUrl),
|
|
649
|
+
allowedUserIds: splitCsv(payload.platforms.workbuddy.allowedUserIds),
|
|
650
|
+
},
|
|
559
651
|
},
|
|
560
652
|
};
|
|
561
653
|
}
|
|
@@ -811,6 +903,7 @@ export async function startWebConfigServer(options) {
|
|
|
811
903
|
const fileQQ = file.platforms?.qq;
|
|
812
904
|
const fileWework = file.platforms?.wework;
|
|
813
905
|
const fileDingtalk = file.platforms?.dingtalk;
|
|
906
|
+
const fileWorkbuddy = file.platforms?.workbuddy;
|
|
814
907
|
const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN ?? fileTelegram?.botToken ?? file.telegramBotToken;
|
|
815
908
|
const feishuAppId = process.env.FEISHU_APP_ID ?? fileFeishu?.appId ?? file.feishuAppId;
|
|
816
909
|
const feishuAppSecret = process.env.FEISHU_APP_SECRET ?? fileFeishu?.appSecret ?? file.feishuAppSecret;
|
|
@@ -820,6 +913,9 @@ export async function startWebConfigServer(options) {
|
|
|
820
913
|
const weworkSecret = process.env.WEWORK_SECRET ?? fileWework?.secret;
|
|
821
914
|
const dingtalkClientId = process.env.DINGTALK_CLIENT_ID ?? fileDingtalk?.clientId;
|
|
822
915
|
const dingtalkClientSecret = process.env.DINGTALK_CLIENT_SECRET ?? fileDingtalk?.clientSecret;
|
|
916
|
+
const workbuddyAccessToken = fileWorkbuddy?.accessToken;
|
|
917
|
+
const workbuddyRefreshToken = fileWorkbuddy?.refreshToken;
|
|
918
|
+
const workbuddyUserId = fileWorkbuddy?.userId;
|
|
823
919
|
const platforms = {};
|
|
824
920
|
// 检查 Telegram
|
|
825
921
|
platforms.telegram = {
|
|
@@ -856,6 +952,13 @@ export async function startWebConfigServer(options) {
|
|
|
856
952
|
healthy: !!(dingtalkClientId && dingtalkClientSecret),
|
|
857
953
|
message: (dingtalkClientId && dingtalkClientSecret) ? "Client ID and Secret configured" : "Missing credentials"
|
|
858
954
|
};
|
|
955
|
+
// 检查 WorkBuddy
|
|
956
|
+
platforms.workbuddy = {
|
|
957
|
+
configured: !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId),
|
|
958
|
+
enabled: !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId) && fileWorkbuddy?.enabled !== false,
|
|
959
|
+
healthy: !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId),
|
|
960
|
+
message: (workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId) ? "OAuth credentials configured" : "Missing credentials"
|
|
961
|
+
};
|
|
859
962
|
json(response, 200, { platforms, serviceStatus: getServiceStatus() });
|
|
860
963
|
return;
|
|
861
964
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type LogLevel } from './logger.js';
|
|
2
|
-
export type Platform = 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework';
|
|
2
|
+
export type Platform = 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy';
|
|
3
3
|
export type AiCommand = 'claude' | 'codex' | 'codebuddy';
|
|
4
4
|
export interface Config {
|
|
5
5
|
enabledPlatforms: Platform[];
|
|
@@ -29,6 +29,7 @@ export interface Config {
|
|
|
29
29
|
wechatAllowedUserIds: string[];
|
|
30
30
|
weworkAllowedUserIds: string[];
|
|
31
31
|
dingtalkAllowedUserIds: string[];
|
|
32
|
+
workbuddyAllowedUserIds: string[];
|
|
32
33
|
aiCommand: AiCommand;
|
|
33
34
|
codexCliPath: string;
|
|
34
35
|
codebuddyCliPath: string;
|
|
@@ -82,6 +83,17 @@ export interface Config {
|
|
|
82
83
|
allowedUserIds: string[];
|
|
83
84
|
cardTemplateId?: string;
|
|
84
85
|
};
|
|
86
|
+
workbuddy?: {
|
|
87
|
+
enabled: boolean;
|
|
88
|
+
aiCommand?: AiCommand;
|
|
89
|
+
allowedUserIds: string[];
|
|
90
|
+
accessToken?: string;
|
|
91
|
+
refreshToken?: string;
|
|
92
|
+
userId?: string;
|
|
93
|
+
baseUrl?: string;
|
|
94
|
+
guid?: string;
|
|
95
|
+
workspacePath?: string;
|
|
96
|
+
};
|
|
85
97
|
};
|
|
86
98
|
}
|
|
87
99
|
export interface FilePlatformTelegram {
|
|
@@ -134,6 +146,17 @@ export interface FilePlatformDingtalk {
|
|
|
134
146
|
allowedUserIds?: string[];
|
|
135
147
|
cardTemplateId?: string;
|
|
136
148
|
}
|
|
149
|
+
interface FilePlatformWorkBuddy {
|
|
150
|
+
enabled?: boolean;
|
|
151
|
+
aiCommand?: AiCommand;
|
|
152
|
+
allowedUserIds?: string[];
|
|
153
|
+
accessToken?: string;
|
|
154
|
+
refreshToken?: string;
|
|
155
|
+
userId?: string;
|
|
156
|
+
baseUrl?: string;
|
|
157
|
+
guid?: string;
|
|
158
|
+
workspacePath?: string;
|
|
159
|
+
}
|
|
137
160
|
export interface FileToolClaude {
|
|
138
161
|
cliPath?: string;
|
|
139
162
|
workDir?: string;
|
|
@@ -167,6 +190,7 @@ export interface FileConfig {
|
|
|
167
190
|
wechat?: FilePlatformWechat;
|
|
168
191
|
wework?: FilePlatformWework;
|
|
169
192
|
dingtalk?: FilePlatformDingtalk;
|
|
193
|
+
workbuddy?: FilePlatformWorkBuddy;
|
|
170
194
|
};
|
|
171
195
|
env?: Record<string, string>;
|
|
172
196
|
aiCommand?: string;
|
package/dist/config.js
CHANGED
|
@@ -233,6 +233,7 @@ export function loadConfig() {
|
|
|
233
233
|
const fileWechat = file.platforms?.wechat;
|
|
234
234
|
const fileWework = file.platforms?.wework;
|
|
235
235
|
const fileDingtalk = file.platforms?.dingtalk;
|
|
236
|
+
const fileWorkBuddy = file.platforms?.workbuddy;
|
|
236
237
|
// 1. 加载各平台凭证(env 优先,其次新结构,最后旧字段)
|
|
237
238
|
const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN ??
|
|
238
239
|
fileTelegram?.botToken ??
|
|
@@ -276,6 +277,19 @@ export function loadConfig() {
|
|
|
276
277
|
fileDingtalk?.clientSecret;
|
|
277
278
|
const dingtalkCardTemplateId = process.env.DINGTALK_CARD_TEMPLATE_ID ??
|
|
278
279
|
fileDingtalk?.cardTemplateId;
|
|
280
|
+
// WorkBuddy credentials
|
|
281
|
+
const workbuddyAccessToken = process.env.WORKBUDDY_ACCESS_TOKEN ??
|
|
282
|
+
fileWorkBuddy?.accessToken;
|
|
283
|
+
const workbuddyRefreshToken = process.env.WORKBUDDY_REFRESH_TOKEN ??
|
|
284
|
+
fileWorkBuddy?.refreshToken;
|
|
285
|
+
const workbuddyUserId = process.env.WORKBUDDY_USER_ID ??
|
|
286
|
+
fileWorkBuddy?.userId;
|
|
287
|
+
const workbuddyBaseUrl = process.env.WORKBUDDY_BASE_URL ??
|
|
288
|
+
fileWorkBuddy?.baseUrl;
|
|
289
|
+
const workbuddyGuid = process.env.WORKBUDDY_GUID ??
|
|
290
|
+
fileWorkBuddy?.guid;
|
|
291
|
+
const workbuddyWorkspacePath = process.env.WORKBUDDY_WORKSPACE_PATH ??
|
|
292
|
+
fileWorkBuddy?.workspacePath;
|
|
279
293
|
// 2. 计算启用平台
|
|
280
294
|
const enabledPlatforms = [];
|
|
281
295
|
const telegramEnabledFlag = fileTelegram?.enabled;
|
|
@@ -284,6 +298,7 @@ export function loadConfig() {
|
|
|
284
298
|
const wechatEnabledFlag = fileWechat?.enabled;
|
|
285
299
|
const weworkEnabledFlag = fileWework?.enabled;
|
|
286
300
|
const dingtalkEnabledFlag = fileDingtalk?.enabled;
|
|
301
|
+
const workbuddyEnabledFlag = fileWorkBuddy?.enabled;
|
|
287
302
|
const telegramEnabled = !!telegramBotToken && (telegramEnabledFlag !== false);
|
|
288
303
|
const feishuEnabled = !!(feishuAppId && feishuAppSecret) && (feishuEnabledFlag !== false);
|
|
289
304
|
const qqEnabled = !!(qqAppId && qqSecret) && (qqEnabledFlag !== false);
|
|
@@ -294,6 +309,8 @@ export function loadConfig() {
|
|
|
294
309
|
// 企业微信只需要 corpId (botId) 和 secret
|
|
295
310
|
const weworkEnabled = !!(weworkCorpId && weworkSecret) && (weworkEnabledFlag !== false);
|
|
296
311
|
const dingtalkEnabled = !!(dingtalkClientId && dingtalkClientSecret) && (dingtalkEnabledFlag !== false);
|
|
312
|
+
// WorkBuddy 需要 OAuth 凭证
|
|
313
|
+
const workbuddyEnabled = !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId) && (workbuddyEnabledFlag !== false);
|
|
297
314
|
if (telegramEnabled)
|
|
298
315
|
enabledPlatforms.push('telegram');
|
|
299
316
|
if (feishuEnabled)
|
|
@@ -306,6 +323,8 @@ export function loadConfig() {
|
|
|
306
323
|
enabledPlatforms.push('wework');
|
|
307
324
|
if (dingtalkEnabled)
|
|
308
325
|
enabledPlatforms.push('dingtalk');
|
|
326
|
+
if (workbuddyEnabled)
|
|
327
|
+
enabledPlatforms.push('workbuddy');
|
|
309
328
|
if (enabledPlatforms.length === 0) {
|
|
310
329
|
throw new Error('至少需要配置 Telegram、Feishu、WeChat、WeWork 或 DingTalk 其中一个平台(可以通过环境变量或 config.json)');
|
|
311
330
|
}
|
|
@@ -332,6 +351,9 @@ export function loadConfig() {
|
|
|
332
351
|
const dingtalkAllowedUserIds = process.env.DINGTALK_ALLOWED_USER_IDS !== undefined
|
|
333
352
|
? parseCommaSeparated(process.env.DINGTALK_ALLOWED_USER_IDS)
|
|
334
353
|
: fileDingtalk?.allowedUserIds ?? allowedUserIds;
|
|
354
|
+
const workbuddyAllowedUserIds = process.env.WORKBUDDY_ALLOWED_USER_IDS !== undefined
|
|
355
|
+
? parseCommaSeparated(process.env.WORKBUDDY_ALLOWED_USER_IDS)
|
|
356
|
+
: fileWorkBuddy?.allowedUserIds ?? allowedUserIds;
|
|
335
357
|
// 5. AI / 工作目录 / 安全配置(从 tools 读取)
|
|
336
358
|
const aiCommand = normalizeAiCommand(process.env.AI_COMMAND ?? file.aiCommand, 'claude');
|
|
337
359
|
const tc = file.tools?.claude ?? {};
|
|
@@ -579,6 +601,29 @@ export function loadConfig() {
|
|
|
579
601
|
allowedUserIds: dingtalkAllowedUserIds,
|
|
580
602
|
cardTemplateId: dingtalkCardTemplateId,
|
|
581
603
|
},
|
|
604
|
+
workbuddy: workbuddyEnabled
|
|
605
|
+
? {
|
|
606
|
+
enabled: true,
|
|
607
|
+
aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, aiCommand),
|
|
608
|
+
allowedUserIds: workbuddyAllowedUserIds,
|
|
609
|
+
accessToken: workbuddyAccessToken,
|
|
610
|
+
refreshToken: workbuddyRefreshToken,
|
|
611
|
+
userId: workbuddyUserId,
|
|
612
|
+
baseUrl: workbuddyBaseUrl,
|
|
613
|
+
guid: workbuddyGuid,
|
|
614
|
+
workspacePath: workbuddyWorkspacePath,
|
|
615
|
+
}
|
|
616
|
+
: {
|
|
617
|
+
enabled: false,
|
|
618
|
+
aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, aiCommand),
|
|
619
|
+
allowedUserIds: workbuddyAllowedUserIds,
|
|
620
|
+
accessToken: workbuddyAccessToken,
|
|
621
|
+
refreshToken: workbuddyRefreshToken,
|
|
622
|
+
userId: workbuddyUserId,
|
|
623
|
+
baseUrl: workbuddyBaseUrl,
|
|
624
|
+
guid: workbuddyGuid,
|
|
625
|
+
workspacePath: workbuddyWorkspacePath,
|
|
626
|
+
},
|
|
582
627
|
};
|
|
583
628
|
return {
|
|
584
629
|
enabledPlatforms,
|
|
@@ -608,6 +653,7 @@ export function loadConfig() {
|
|
|
608
653
|
wechatAllowedUserIds,
|
|
609
654
|
weworkAllowedUserIds,
|
|
610
655
|
dingtalkAllowedUserIds,
|
|
656
|
+
workbuddyAllowedUserIds,
|
|
611
657
|
aiCommand,
|
|
612
658
|
codexCliPath,
|
|
613
659
|
codebuddyCliPath,
|
package/dist/constants.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare const CARDKIT_THROTTLE_MS = 80;
|
|
|
11
11
|
export declare const TELEGRAM_THROTTLE_MS = 200;
|
|
12
12
|
/** WeChat 流式更新节流:1000ms(AGP 协议建议值) */
|
|
13
13
|
export declare const WECHAT_THROTTLE_MS = 1000;
|
|
14
|
+
/** WorkBuddy 流式更新节流:1000ms(Centrifuge 协议建议值) */
|
|
15
|
+
export declare const WORKBUDDY_THROTTLE_MS = 1000;
|
|
14
16
|
export declare const WEWORK_THROTTLE_MS = 500;
|
|
15
17
|
export declare const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
|
|
16
18
|
export declare const MAX_FEISHU_MESSAGE_LENGTH = 4000;
|
package/dist/constants.js
CHANGED
|
@@ -32,6 +32,8 @@ export const CARDKIT_THROTTLE_MS = 80;
|
|
|
32
32
|
export const TELEGRAM_THROTTLE_MS = 200;
|
|
33
33
|
/** WeChat 流式更新节流:1000ms(AGP 协议建议值) */
|
|
34
34
|
export const WECHAT_THROTTLE_MS = 1000;
|
|
35
|
+
/** WorkBuddy 流式更新节流:1000ms(Centrifuge 协议建议值) */
|
|
36
|
+
export const WORKBUDDY_THROTTLE_MS = 1000;
|
|
35
37
|
export const WEWORK_THROTTLE_MS = 500;
|
|
36
38
|
export const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
|
|
37
39
|
export const MAX_FEISHU_MESSAGE_LENGTH = 4000;
|
package/dist/dingtalk/client.js
CHANGED
|
@@ -7,7 +7,9 @@ const TEXT_MSG_KEY = 'sampleText';
|
|
|
7
7
|
const DINGTALK_STREAM_HOST = 'wss-open-connection.dingtalk.com';
|
|
8
8
|
let client = null;
|
|
9
9
|
let messageHandler = null;
|
|
10
|
+
// sessionWebhook 有过期时间(约 2 小时),需要记录时间戳
|
|
10
11
|
const sessionWebhookByChat = new Map();
|
|
12
|
+
const WEBHOOK_TTL_MS = 90 * 60 * 1000; // 90 分钟后视为过期
|
|
11
13
|
const unionIdByUserId = new Map();
|
|
12
14
|
let dingtalkWarnFilterInstalled = false;
|
|
13
15
|
export function shouldSuppressDingTalkSocketWarn(args) {
|
|
@@ -44,13 +46,19 @@ function getClient() {
|
|
|
44
46
|
export function registerSessionWebhook(chatId, sessionWebhook) {
|
|
45
47
|
if (!chatId || !sessionWebhook)
|
|
46
48
|
return;
|
|
47
|
-
sessionWebhookByChat.set(chatId, sessionWebhook);
|
|
49
|
+
sessionWebhookByChat.set(chatId, { webhook: sessionWebhook, registeredAt: Date.now() });
|
|
48
50
|
}
|
|
49
51
|
async function sendByWebhook(chatId, body) {
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
52
|
+
const entry = sessionWebhookByChat.get(chatId);
|
|
53
|
+
if (!entry) {
|
|
52
54
|
throw new Error(`DingTalk sessionWebhook unavailable for chat ${chatId}`);
|
|
53
55
|
}
|
|
56
|
+
// 检查 webhook 是否过期
|
|
57
|
+
if (Date.now() - entry.registeredAt > WEBHOOK_TTL_MS) {
|
|
58
|
+
sessionWebhookByChat.delete(chatId);
|
|
59
|
+
throw new Error(`DingTalk sessionWebhook expired for chat ${chatId}`);
|
|
60
|
+
}
|
|
61
|
+
const sessionWebhook = entry.webhook;
|
|
54
62
|
const accessToken = await getClient().getAccessToken();
|
|
55
63
|
const res = await fetch(sessionWebhook, {
|
|
56
64
|
method: 'POST',
|
|
@@ -99,7 +99,8 @@ async function buildMediaPrompt(message, kind, robotCodeFallback) {
|
|
|
99
99
|
fallbackExtension: kind === 'image' ? 'jpg' : 'bin',
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
-
catch {
|
|
102
|
+
catch (err) {
|
|
103
|
+
log.warn('Failed to download DingTalk media from URL:', err);
|
|
103
104
|
localPath = undefined;
|
|
104
105
|
}
|
|
105
106
|
}
|
|
@@ -118,7 +119,8 @@ async function buildMediaPrompt(message, kind, robotCodeFallback) {
|
|
|
118
119
|
localPath = await saveBufferMedia(downloaded.buffer, extension, basenameHint);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
|
-
catch {
|
|
122
|
+
catch (err) {
|
|
123
|
+
log.warn('Failed to download DingTalk media via robotCode:', err);
|
|
122
124
|
localPath = undefined;
|
|
123
125
|
}
|
|
124
126
|
}
|
|
@@ -185,7 +187,20 @@ export function setupDingTalkHandlers(config, sessionManager) {
|
|
|
185
187
|
: undefined;
|
|
186
188
|
log.info(`[AI_REQUEST] Running ${aiCommand} for user ${userId}, sessionId=${sessionId ?? 'new'}`);
|
|
187
189
|
const toolId = aiCommand;
|
|
188
|
-
|
|
190
|
+
let msgId;
|
|
191
|
+
try {
|
|
192
|
+
msgId = await sendThinkingMessage(chatId, replyToMessageId, toolId, dingtalkTarget);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
log.error('Failed to send thinking message:', err);
|
|
196
|
+
try {
|
|
197
|
+
await sendTextReply(chatId, '启动 AI 处理失败,请重试。');
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
log.warn('Failed to send startup error reply:', err);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
189
204
|
const stopTyping = startTypingLoop(chatId);
|
|
190
205
|
const taskKey = `${userId}:${msgId}`;
|
|
191
206
|
await runAITask({ config, sessionManager }, { userId, chatId, workDir, sessionId, convId, platform: 'dingtalk', taskKey }, prompt, toolAdapter, {
|
|
@@ -24,6 +24,19 @@ const FLOW_STATUS = {
|
|
|
24
24
|
};
|
|
25
25
|
let senderSettings = {};
|
|
26
26
|
const streamStates = new Map();
|
|
27
|
+
// Periodic cleanup of orphaned stream states (max 30 minutes)
|
|
28
|
+
const STREAM_MAX_AGE_MS = 30 * 60 * 1000;
|
|
29
|
+
setInterval(() => {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
for (const [id, state] of streamStates) {
|
|
32
|
+
// streamStates in DingTalk don't have createdAt, clean up by size
|
|
33
|
+
if (streamStates.size > 50) {
|
|
34
|
+
streamStates.delete(id);
|
|
35
|
+
log.info(`Cleaned up old DingTalk stream state: ${id}`);
|
|
36
|
+
break; // Clean one at a time to avoid blocking
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, STREAM_MAX_AGE_MS);
|
|
27
40
|
function generateMessageId() {
|
|
28
41
|
return `${Date.now()}-${randomBytes(6).toString('hex')}`;
|
|
29
42
|
}
|