@wu529778790/open-im 1.10.9-beta.2 → 1.10.9-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/README.md +43 -62
- package/README.zh-CN.md +43 -62
- package/dist/adapters/claude-sdk-adapter.d.ts +13 -0
- package/dist/adapters/claude-sdk-adapter.js +221 -23
- package/dist/adapters/registry.js +3 -0
- package/dist/channels/capabilities.js +5 -0
- package/dist/clawbot/client.d.ts +14 -0
- package/dist/clawbot/client.js +299 -0
- package/dist/clawbot/event-handler.d.ts +12 -0
- package/dist/clawbot/event-handler.js +85 -0
- package/dist/clawbot/message-sender.d.ts +18 -0
- package/dist/clawbot/message-sender.js +109 -0
- package/dist/clawbot/qr-login.d.ts +33 -0
- package/dist/clawbot/qr-login.js +120 -0
- package/dist/clawbot/types.d.ts +111 -0
- package/dist/clawbot/types.js +7 -0
- package/dist/codebuddy/cli-runner.js +31 -2
- package/dist/codex/cli-runner.js +28 -2
- package/dist/config/file-io.d.ts +6 -3
- package/dist/config/file-io.js +12 -7
- package/dist/config/types.d.ts +17 -1
- package/dist/config-web-page-i18n.d.ts +24 -2
- package/dist/config-web-page-i18n.js +24 -2
- package/dist/config-web.js +79 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +38 -2
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +6 -0
- package/dist/dingtalk/client.js +2 -1
- package/dist/dingtalk/event-handler.js +1 -1
- package/dist/index.js +50 -0
- package/dist/qq/client.js +7 -1
- package/dist/queue/request-queue.js +11 -10
- package/dist/setup.js +131 -3
- package/dist/shared/active-chats.d.ts +2 -2
- package/dist/shared/ai-task.d.ts +14 -0
- package/dist/shared/ai-task.js +57 -9
- package/dist/shared/process-kill.d.ts +24 -0
- package/dist/shared/process-kill.js +79 -0
- package/dist/shared/reconnect.d.ts +28 -0
- package/dist/shared/reconnect.js +56 -0
- package/dist/shared/task-cleanup.d.ts +16 -0
- package/dist/shared/task-cleanup.js +34 -1
- package/dist/telegram/client.js +7 -1
- package/dist/telemetry/telemetry-upload.js +1 -1
- package/dist/wework/client.js +17 -5
- package/dist/wework/event-handler.js +3 -0
- package/dist/workbuddy/centrifuge-client.d.ts +4 -0
- package/dist/workbuddy/centrifuge-client.js +76 -28
- package/dist/workbuddy/client.js +39 -2
- package/package.json +1 -1
- package/web/dist/assets/index-BaLTMeeF.js +57 -0
- package/web/dist/index.html +1 -1
- package/dist/config/credentials.d.ts +0 -19
- package/dist/config/credentials.js +0 -36
- package/web/dist/assets/index-B-oVSMUp.js +0 -57
package/dist/config.js
CHANGED
|
@@ -23,7 +23,9 @@ function resolveFilePlatformAi(file, platform) {
|
|
|
23
23
|
? pf?.wework?.aiCommand
|
|
24
24
|
: platform === 'dingtalk'
|
|
25
25
|
? pf?.dingtalk?.aiCommand
|
|
26
|
-
:
|
|
26
|
+
: platform === 'clawbot'
|
|
27
|
+
? pf?.clawbot?.aiCommand
|
|
28
|
+
: pf?.workbuddy?.aiCommand;
|
|
27
29
|
return normalizeAiCommand(raw ?? process.env.AI_COMMAND ?? file.aiCommand, 'claude');
|
|
28
30
|
}
|
|
29
31
|
// Re-export file I/O and credential helpers from sub-modules
|
|
@@ -53,6 +55,7 @@ export function needsSetup() {
|
|
|
53
55
|
const ww = file.platforms?.wework;
|
|
54
56
|
const dt = file.platforms?.dingtalk;
|
|
55
57
|
const wb = file.platforms?.workbuddy;
|
|
58
|
+
const cb = file.platforms?.clawbot;
|
|
56
59
|
// Also check legacy platforms.wechat for migration path
|
|
57
60
|
const legacyWc = file.platforms?.wechat;
|
|
58
61
|
const hasTelegram = !!tg?.botToken;
|
|
@@ -61,8 +64,9 @@ export function needsSetup() {
|
|
|
61
64
|
const hasWework = !!(ww?.corpId && ww?.secret);
|
|
62
65
|
const hasDingtalk = !!(dt?.clientId && dt?.clientSecret);
|
|
63
66
|
const hasWorkBuddy = !!(wb?.accessToken && wb?.refreshToken && wb?.userId);
|
|
67
|
+
const hasClawBot = !!(cb?.apiToken);
|
|
64
68
|
const hasLegacyWechat = !!(legacyWc?.workbuddyAccessToken && legacyWc?.workbuddyRefreshToken);
|
|
65
|
-
return !hasTelegram && !hasFeishu && !hasQQ && !hasWework && !hasDingtalk && !hasWorkBuddy && !hasLegacyWechat;
|
|
69
|
+
return !hasTelegram && !hasFeishu && !hasQQ && !hasWework && !hasDingtalk && !hasWorkBuddy && !hasClawBot && !hasLegacyWechat;
|
|
66
70
|
}
|
|
67
71
|
export function loadConfig() {
|
|
68
72
|
const file = loadFileConfig();
|
|
@@ -73,6 +77,7 @@ export function loadConfig() {
|
|
|
73
77
|
const fileQQ = file.platforms?.qq;
|
|
74
78
|
const fileWework = file.platforms?.wework;
|
|
75
79
|
const fileDingtalk = file.platforms?.dingtalk;
|
|
80
|
+
const fileClawbot = file.platforms?.clawbot;
|
|
76
81
|
// Auto-migrate legacy platforms.wechat WorkBuddy credentials → platforms.workbuddy
|
|
77
82
|
const legacyWechat = file.platforms?.wechat;
|
|
78
83
|
const fileWorkBuddy = file.platforms?.workbuddy ?? (legacyWechat?.workbuddyAccessToken && legacyWechat?.workbuddyRefreshToken
|
|
@@ -122,6 +127,11 @@ export function loadConfig() {
|
|
|
122
127
|
fileWorkBuddy?.guid;
|
|
123
128
|
const workbuddyWorkspacePath = process.env.WORKBUDDY_WORKSPACE_PATH ??
|
|
124
129
|
fileWorkBuddy?.workspacePath;
|
|
130
|
+
// ClawBot credentials
|
|
131
|
+
const clawbotApiUrl = process.env.CLAWBOT_API_URL ??
|
|
132
|
+
fileClawbot?.apiUrl;
|
|
133
|
+
const clawbotApiToken = process.env.CLAWBOT_API_TOKEN ??
|
|
134
|
+
fileClawbot?.apiToken;
|
|
125
135
|
// 2. 计算启用平台
|
|
126
136
|
const enabledPlatforms = [];
|
|
127
137
|
const telegramEnabledFlag = fileTelegram?.enabled;
|
|
@@ -130,12 +140,14 @@ export function loadConfig() {
|
|
|
130
140
|
const weworkEnabledFlag = fileWework?.enabled;
|
|
131
141
|
const dingtalkEnabledFlag = fileDingtalk?.enabled;
|
|
132
142
|
const workbuddyEnabledFlag = fileWorkBuddy?.enabled;
|
|
143
|
+
const clawbotEnabledFlag = fileClawbot?.enabled;
|
|
133
144
|
const telegramEnabled = !!telegramBotToken && (telegramEnabledFlag !== false);
|
|
134
145
|
const feishuEnabled = !!(feishuAppId && feishuAppSecret) && (feishuEnabledFlag !== false);
|
|
135
146
|
const qqEnabled = !!(qqAppId && qqSecret) && (qqEnabledFlag !== false);
|
|
136
147
|
const weworkEnabled = !!(weworkCorpId && weworkSecret) && (weworkEnabledFlag !== false);
|
|
137
148
|
const dingtalkEnabled = !!(dingtalkClientId && dingtalkClientSecret) && (dingtalkEnabledFlag !== false);
|
|
138
149
|
const workbuddyEnabled = !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId) && (workbuddyEnabledFlag !== false);
|
|
150
|
+
const clawbotEnabled = !!clawbotApiToken && (clawbotEnabledFlag !== false);
|
|
139
151
|
if (telegramEnabled)
|
|
140
152
|
enabledPlatforms.push('telegram');
|
|
141
153
|
if (feishuEnabled)
|
|
@@ -148,6 +160,8 @@ export function loadConfig() {
|
|
|
148
160
|
enabledPlatforms.push('dingtalk');
|
|
149
161
|
if (workbuddyEnabled)
|
|
150
162
|
enabledPlatforms.push('workbuddy');
|
|
163
|
+
if (clawbotEnabled)
|
|
164
|
+
enabledPlatforms.push('clawbot');
|
|
151
165
|
if (enabledPlatforms.length === 0) {
|
|
152
166
|
throw new Error('至少需要配置 Telegram、Feishu、WeChat、WeWork 或 DingTalk 其中一个平台(可以通过环境变量或 config.json)');
|
|
153
167
|
}
|
|
@@ -174,6 +188,9 @@ export function loadConfig() {
|
|
|
174
188
|
const workbuddyAllowedUserIds = process.env.WORKBUDDY_ALLOWED_USER_IDS !== undefined
|
|
175
189
|
? parseCommaSeparated(process.env.WORKBUDDY_ALLOWED_USER_IDS)
|
|
176
190
|
: fileWorkBuddy?.allowedUserIds ?? allowedUserIds;
|
|
191
|
+
const clawbotAllowedUserIds = process.env.CLAWBOT_ALLOWED_USER_IDS !== undefined
|
|
192
|
+
? parseCommaSeparated(process.env.CLAWBOT_ALLOWED_USER_IDS)
|
|
193
|
+
: fileClawbot?.allowedUserIds ?? allowedUserIds;
|
|
177
194
|
// 5. AI / 工作目录 / 安全配置(从 tools 读取)
|
|
178
195
|
const tc = file.tools?.claude ?? {};
|
|
179
196
|
const tcod = file.tools?.codex ?? {};
|
|
@@ -507,6 +524,21 @@ export function loadConfig() {
|
|
|
507
524
|
guid: workbuddyGuid,
|
|
508
525
|
workspacePath: workbuddyWorkspacePath,
|
|
509
526
|
},
|
|
527
|
+
clawbot: clawbotEnabled
|
|
528
|
+
? {
|
|
529
|
+
enabled: true,
|
|
530
|
+
aiCommand: resolveFilePlatformAi(file, 'clawbot'),
|
|
531
|
+
allowedUserIds: clawbotAllowedUserIds,
|
|
532
|
+
apiUrl: clawbotApiUrl,
|
|
533
|
+
apiToken: clawbotApiToken,
|
|
534
|
+
}
|
|
535
|
+
: {
|
|
536
|
+
enabled: false,
|
|
537
|
+
aiCommand: resolveFilePlatformAi(file, 'clawbot'),
|
|
538
|
+
allowedUserIds: clawbotAllowedUserIds,
|
|
539
|
+
apiUrl: clawbotApiUrl,
|
|
540
|
+
apiToken: clawbotApiToken,
|
|
541
|
+
},
|
|
510
542
|
};
|
|
511
543
|
return {
|
|
512
544
|
enabledPlatforms,
|
|
@@ -528,6 +560,7 @@ export function loadConfig() {
|
|
|
528
560
|
weworkAllowedUserIds,
|
|
529
561
|
dingtalkAllowedUserIds,
|
|
530
562
|
workbuddyAllowedUserIds,
|
|
563
|
+
clawbotAllowedUserIds,
|
|
531
564
|
codexCliPath,
|
|
532
565
|
codebuddyCliPath,
|
|
533
566
|
claudeProxy,
|
|
@@ -562,6 +595,9 @@ export function getPlatformsWithCredentials(config) {
|
|
|
562
595
|
const wb = config.platforms.workbuddy;
|
|
563
596
|
if (wb?.accessToken && wb?.refreshToken)
|
|
564
597
|
r.push('workbuddy');
|
|
598
|
+
const cb = config.platforms.clawbot;
|
|
599
|
+
if (cb?.apiToken)
|
|
600
|
+
r.push('clawbot');
|
|
565
601
|
return r;
|
|
566
602
|
}
|
|
567
603
|
export function resolvePlatformAiCommand(config, platform) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -38,3 +38,9 @@ export declare const MAX_WEWORK_MESSAGE_LENGTH = 2048;
|
|
|
38
38
|
export declare const MAX_DINGTALK_MESSAGE_LENGTH = 2048;
|
|
39
39
|
/** WeChat KF (微信客服) 单条消息最大字符数 */
|
|
40
40
|
export declare const MAX_WORKBUDDY_MESSAGE_LENGTH = 2000;
|
|
41
|
+
/** ClawBot 流式更新节流 */
|
|
42
|
+
export declare const CLAWBOT_THROTTLE_MS = 1000;
|
|
43
|
+
/** ClawBot 单条消息最大字符数 */
|
|
44
|
+
export declare const MAX_CLAWBOT_MESSAGE_LENGTH = 2000;
|
|
45
|
+
/** ClawBot 长轮询间隔 */
|
|
46
|
+
export declare const CLAWBOT_POLL_INTERVAL_MS = 3000;
|
package/dist/constants.js
CHANGED
|
@@ -68,3 +68,9 @@ export const MAX_WEWORK_MESSAGE_LENGTH = 2048;
|
|
|
68
68
|
export const MAX_DINGTALK_MESSAGE_LENGTH = 2048;
|
|
69
69
|
/** WeChat KF (微信客服) 单条消息最大字符数 */
|
|
70
70
|
export const MAX_WORKBUDDY_MESSAGE_LENGTH = 2000;
|
|
71
|
+
/** ClawBot 流式更新节流 */
|
|
72
|
+
export const CLAWBOT_THROTTLE_MS = 1000;
|
|
73
|
+
/** ClawBot 单条消息最大字符数 */
|
|
74
|
+
export const MAX_CLAWBOT_MESSAGE_LENGTH = 2000;
|
|
75
|
+
/** ClawBot 长轮询间隔 */
|
|
76
|
+
export const CLAWBOT_POLL_INTERVAL_MS = 3000;
|
package/dist/dingtalk/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DWClient, TOPIC_ROBOT } from 'dingtalk-stream';
|
|
2
2
|
import { createLogger } from '../logger.js';
|
|
3
|
+
import { jitteredDelay } from '../shared/reconnect.js';
|
|
3
4
|
import { registerSessionWebhook as registerWebhook, clearWebhooks, sendText as webhookSendText, sendMarkdown as webhookSendMarkdown, downloadRobotMessageFile as webhookDownloadFile, sendProactiveText as webhookSendProactive, } from './webhook.js';
|
|
4
5
|
import { prepareStreamingCard as cardPrepare, updateStreamingCard as cardUpdate, finishStreamingCard as cardFinish, createAndDeliverCard as cardCreateAndDeliver, updateCardInstance as cardUpdateInstance, sendRobotInteractiveCard as cardSendInteractive, updateRobotInteractiveCard as cardUpdateInteractive, clearUnionIdCache, } from './streaming-card.js';
|
|
5
6
|
const log = createLogger('DingTalk');
|
|
@@ -111,7 +112,7 @@ export async function initDingTalk(cfg, eventHandler) {
|
|
|
111
112
|
lastErr = err;
|
|
112
113
|
if (is429(err) && attempt < maxTries) {
|
|
113
114
|
log.warn(`DingTalk gateway 429 (attempt ${attempt}/${maxTries}), retrying in ${retryDelayMs / 1000}s...`);
|
|
114
|
-
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
115
|
+
await new Promise((r) => setTimeout(r, jitteredDelay(retryDelayMs)));
|
|
115
116
|
continue;
|
|
116
117
|
}
|
|
117
118
|
throw new Error(formatDingTalkInitError(err));
|
|
@@ -163,7 +163,7 @@ export function setupDingTalkHandlers(config, sessionManager) {
|
|
|
163
163
|
sessionManager,
|
|
164
164
|
sender: { sendTextReply, sendDirectorySelection },
|
|
165
165
|
});
|
|
166
|
-
const { accessControl, requestQueue, runningTasks
|
|
166
|
+
const { accessControl, requestQueue, runningTasks } = ctx;
|
|
167
167
|
// DingTalk-specific sender callbacks for the factory
|
|
168
168
|
const dingtalkSender = {
|
|
169
169
|
sendThinkingMessage: async (chatId, replyToMessageId, toolId) => {
|
package/dist/index.js
CHANGED
|
@@ -24,14 +24,20 @@ import { setupDingTalkHandlers } from "./dingtalk/event-handler.js";
|
|
|
24
24
|
import { initWorkBuddy, stopWorkBuddy } from "./workbuddy/client.js";
|
|
25
25
|
import { setupWorkBuddyHandlers } from "./workbuddy/event-handler.js";
|
|
26
26
|
import { sendTextReply as sendWorkBuddyTextReply } from "./workbuddy/message-sender.js";
|
|
27
|
+
import { initClawbot, stopClawbot } from "./clawbot/client.js";
|
|
28
|
+
import { setupClawbotHandlers } from "./clawbot/event-handler.js";
|
|
29
|
+
import { sendTextReply as sendClawbotTextReply } from "./clawbot/message-sender.js";
|
|
30
|
+
import { initClawBotSender } from "./clawbot/message-sender.js";
|
|
27
31
|
import { initAdapters, cleanupAdapters } from "./adapters/registry.js";
|
|
28
32
|
import { SessionManager } from "./session/session-manager.js";
|
|
29
33
|
import { loadActiveChats, getActiveChatId, flushActiveChats, } from "./shared/active-chats.js";
|
|
34
|
+
import { destroyAllLiveChildren } from "./shared/process-kill.js";
|
|
30
35
|
import { initLogger, createLogger, closeLogger, emitStructuredEvent, shutdownLoggerTelemetry, } from "./logger.js";
|
|
31
36
|
import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
|
|
32
37
|
import { createRequire } from "node:module";
|
|
33
38
|
import { escapePathForMarkdown } from "./shared/utils.js";
|
|
34
39
|
import { applyOpenImGitCoauthorToProcessEnv } from "./shared/git-coauthor.js";
|
|
40
|
+
import { emitInterruptedTerminals } from "./shared/task-cleanup.js";
|
|
35
41
|
const require = createRequire(import.meta.url);
|
|
36
42
|
const { version: APP_VERSION } = require("../package.json");
|
|
37
43
|
const log = createLogger("Main");
|
|
@@ -92,6 +98,19 @@ const PLATFORM_MODULES = {
|
|
|
92
98
|
stop: () => stopWorkBuddy(),
|
|
93
99
|
sendNotification: (chatId, msg) => sendWorkBuddyTextReply(null, chatId, msg, randomUUID()),
|
|
94
100
|
},
|
|
101
|
+
clawbot: {
|
|
102
|
+
init: async (config, sessionManager) => {
|
|
103
|
+
const pc = config.platforms.clawbot;
|
|
104
|
+
if (pc?.apiUrl && pc?.apiToken) {
|
|
105
|
+
initClawBotSender(pc.apiUrl, pc.apiToken);
|
|
106
|
+
}
|
|
107
|
+
const handle = setupClawbotHandlers(config, sessionManager);
|
|
108
|
+
await initClawbot(config, handle.handleEvent);
|
|
109
|
+
return handle;
|
|
110
|
+
},
|
|
111
|
+
stop: () => stopClawbot(),
|
|
112
|
+
sendNotification: (chatId, msg) => sendClawbotTextReply(chatId, msg),
|
|
113
|
+
},
|
|
95
114
|
};
|
|
96
115
|
async function sendLifecycleNotification(platform, message) {
|
|
97
116
|
const mod = PLATFORM_MODULES[platform];
|
|
@@ -262,6 +281,15 @@ export async function main() {
|
|
|
262
281
|
const uptimeSec = Math.floor((Date.now() - startedAt) / 1000);
|
|
263
282
|
const m = Math.floor(uptimeSec / 60);
|
|
264
283
|
const shutdownMsg = buildShutdownMessage(m);
|
|
284
|
+
// 在任何异步等待之前,先为仍在运行的任务补发终态遥测事件。
|
|
285
|
+
// 这样即使后续 await(通知发送、platform.stop)期间进程被第二次信号杀死,
|
|
286
|
+
// ai.task.start 也有对应的 interrupted 终态,避免遥测里的 miss。
|
|
287
|
+
for (const platform of successfulPlatforms) {
|
|
288
|
+
const handle = activeHandles.get(platform);
|
|
289
|
+
if (handle?.runningTasks && handle.runningTasks.size > 0) {
|
|
290
|
+
emitInterruptedTerminals(handle.runningTasks);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
265
293
|
// Send notification only to successfully initialized platforms
|
|
266
294
|
for (const platform of successfulPlatforms) {
|
|
267
295
|
await sendLifecycleNotification(platform, shutdownMsg).catch((err) => {
|
|
@@ -299,6 +327,16 @@ export async function main() {
|
|
|
299
327
|
};
|
|
300
328
|
process.on("SIGINT", () => shutdown().catch(() => process.exit(1)));
|
|
301
329
|
process.on("SIGTERM", () => shutdown().catch(() => process.exit(1)));
|
|
330
|
+
// 兜底:进程退出(含异常路径,如未捕获异常 / SIGKILL)时强制清理 CLI 子进程,
|
|
331
|
+
// 避免僵尸 / 孤儿。正常 shutdown 已在 cleanupAdapters() 里清理过。
|
|
332
|
+
process.on("exit", () => {
|
|
333
|
+
try {
|
|
334
|
+
destroyAllLiveChildren();
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
/* 退出期间 best effort */
|
|
338
|
+
}
|
|
339
|
+
});
|
|
302
340
|
// Global error handlers to prevent unhandled crashes
|
|
303
341
|
process.on("unhandledRejection", (reason) => {
|
|
304
342
|
log.error("Unhandled Promise rejection (this indicates a bug — the affected request may hang without a response):", reason);
|
|
@@ -312,6 +350,18 @@ export async function main() {
|
|
|
312
350
|
return;
|
|
313
351
|
}
|
|
314
352
|
log.error("Uncaught exception (process will exit):", err);
|
|
353
|
+
// 进程即将因未捕获异常退出:补发在途任务的终态,避免 miss
|
|
354
|
+
for (const platform of successfulPlatforms) {
|
|
355
|
+
const handle = activeHandles.get(platform);
|
|
356
|
+
if (handle?.runningTasks && handle.runningTasks.size > 0) {
|
|
357
|
+
try {
|
|
358
|
+
emitInterruptedTerminals(handle.runningTasks);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
/* best effort:不能因遥测失败再次抛出 */
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
315
365
|
void shutdownLoggerTelemetry()
|
|
316
366
|
.then(() => closeLogger())
|
|
317
367
|
.finally(() => process.exit(1));
|
package/dist/qq/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import { createLogger } from "../logger.js";
|
|
3
|
+
import { jitteredDelay, SLOW_PROBE_MS } from "../shared/reconnect.js";
|
|
3
4
|
const log = createLogger("QQ");
|
|
4
5
|
const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
5
6
|
const API_BASE = "https://api.sgroup.qq.com";
|
|
@@ -271,7 +272,12 @@ async function connectWebSocket(config, handler) {
|
|
|
271
272
|
sessionId = null;
|
|
272
273
|
seq = null;
|
|
273
274
|
}
|
|
274
|
-
const
|
|
275
|
+
const isFatalClose = code === 4004 || code === 4006 || code === 4007;
|
|
276
|
+
const baseDelay = RECONNECT_DELAYS_MS[Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)];
|
|
277
|
+
const delay = isFatalClose ? jitteredDelay(SLOW_PROBE_MS) : jitteredDelay(baseDelay);
|
|
278
|
+
if (isFatalClose) {
|
|
279
|
+
log.warn(`QQ 致命关闭码 ${code}(鉴权失败),转慢探测(每 ${Math.round(SLOW_PROBE_MS / 1000)}s 一次)`);
|
|
280
|
+
}
|
|
275
281
|
reconnectAttempt += 1;
|
|
276
282
|
reconnectTimer = setTimeout(() => {
|
|
277
283
|
if (currentConfig && currentHandler) {
|
|
@@ -80,17 +80,18 @@ export class RequestQueue {
|
|
|
80
80
|
finally {
|
|
81
81
|
this.runningControllers.delete(key);
|
|
82
82
|
const q = this.queues.get(key);
|
|
83
|
-
if (!q)
|
|
84
|
-
return;
|
|
85
|
-
const next = q.tasks.shift();
|
|
86
|
-
if (next) {
|
|
87
|
-
setImmediate(() => this.run(key, next.prompt, next.execute).catch((err) => {
|
|
88
|
-
log.error(`Unhandled error in next task execution for ${key}:`, err);
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
83
|
+
if (!q) { /* queue already cleared */ }
|
|
91
84
|
else {
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
const next = q.tasks.shift();
|
|
86
|
+
if (next) {
|
|
87
|
+
setImmediate(() => this.run(key, next.prompt, next.execute).catch((err) => {
|
|
88
|
+
log.error(`Unhandled error in next task execution for ${key}:`, err);
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
q.running = false;
|
|
93
|
+
this.queues.delete(key);
|
|
94
|
+
}
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
}
|
package/dist/setup.js
CHANGED
|
@@ -31,6 +31,7 @@ function getConfiguredPlatforms(existing) {
|
|
|
31
31
|
{ k: "wework", label: "企业微信" },
|
|
32
32
|
{ k: "dingtalk", label: "钉钉" },
|
|
33
33
|
{ k: "workbuddy", label: "WorkBuddy (微信)" },
|
|
34
|
+
{ k: "clawbot", label: "ClawBot (微信 iLink)" },
|
|
34
35
|
];
|
|
35
36
|
return names
|
|
36
37
|
.filter(({ k }) => {
|
|
@@ -49,6 +50,8 @@ function getConfiguredPlatforms(existing) {
|
|
|
49
50
|
return !!(p.corpId && p.secret);
|
|
50
51
|
if (k === "dingtalk")
|
|
51
52
|
return !!(p.clientId && p.clientSecret);
|
|
53
|
+
if (k === "clawbot")
|
|
54
|
+
return !!p.apiToken;
|
|
52
55
|
return false;
|
|
53
56
|
})
|
|
54
57
|
.map(({ label }) => label);
|
|
@@ -125,12 +128,19 @@ function printManualInstructions(configPath) {
|
|
|
125
128
|
"workbuddyRefreshToken": "",
|
|
126
129
|
"userId": "",
|
|
127
130
|
"allowedUserIds": ["允许访问的微信用户 ID(可选)"]
|
|
131
|
+
},
|
|
132
|
+
"clawbot": {
|
|
133
|
+
"enabled": false,
|
|
134
|
+
"aiCommand": "claude",
|
|
135
|
+
"apiUrl": "http://127.0.0.1:26322",
|
|
136
|
+
"apiToken": "你的 ClawBot Bearer Token(可选)",
|
|
137
|
+
"allowedUserIds": ["允许访问的微信用户 ID(可选)"]
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
140
|
}`);
|
|
131
141
|
console.log("");
|
|
132
|
-
console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeChat、WeWork 或
|
|
133
|
-
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_WORKBUDDY_ACCESS_TOKEN=xxx、WEWORK_CORP_ID=xxx 或
|
|
142
|
+
console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeChat、WeWork、DingTalk 或 ClawBot 其中一个平台");
|
|
143
|
+
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_WORKBUDDY_ACCESS_TOKEN=xxx、WEWORK_CORP_ID=xxx、DINGTALK_CLIENT_ID=xxx 或 CLAWBOT_API_TOKEN=xxx 后再运行");
|
|
134
144
|
console.log("");
|
|
135
145
|
}
|
|
136
146
|
const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
@@ -317,6 +327,11 @@ export async function runInteractiveSetup() {
|
|
|
317
327
|
(hasWc ? " ✓已配置" : ""),
|
|
318
328
|
value: "workbuddy",
|
|
319
329
|
},
|
|
330
|
+
{
|
|
331
|
+
title: "ClawBot 微信 iLink (需要 API Token)" +
|
|
332
|
+
(!!existing?.platforms?.clawbot?.apiToken ? " ✓已配置" : ""),
|
|
333
|
+
value: "clawbot",
|
|
334
|
+
},
|
|
320
335
|
{ title: "配置多个平台", value: "multi" },
|
|
321
336
|
],
|
|
322
337
|
initial: 0,
|
|
@@ -339,6 +354,7 @@ export async function runInteractiveSetup() {
|
|
|
339
354
|
{ title: "企业微信 (WeWork)" + (hasWw ? " ✓已配置" : ""), value: "wework", selected: hasWw },
|
|
340
355
|
{ title: "钉钉 (DingTalk)" + (hasDt ? " ✓已配置" : ""), value: "dingtalk", selected: hasDt },
|
|
341
356
|
{ title: "WorkBuddy 微信客服 (WeChat KF)" + (hasWc ? " ✓已配置" : ""), value: "workbuddy", selected: hasWc },
|
|
357
|
+
{ title: "ClawBot 微信 iLink" + (!!existing?.platforms?.clawbot?.apiToken ? " ✓已配置" : ""), value: "clawbot" },
|
|
342
358
|
],
|
|
343
359
|
}, { onCancel });
|
|
344
360
|
if (!multiResp.platforms || multiResp.platforms.length === 0) {
|
|
@@ -563,6 +579,84 @@ export async function runInteractiveSetup() {
|
|
|
563
579
|
return false;
|
|
564
580
|
}
|
|
565
581
|
}
|
|
582
|
+
if (selectedPlatforms.includes("clawbot")) {
|
|
583
|
+
const hasCbToken = !!existing?.platforms?.clawbot?.apiToken;
|
|
584
|
+
const cbModeResp = await prompts({
|
|
585
|
+
type: "select",
|
|
586
|
+
name: "mode",
|
|
587
|
+
message: "ClawBot 微信 iLink",
|
|
588
|
+
choices: [
|
|
589
|
+
{ title: "扫码登录(自动获取 Token)", value: "qr" },
|
|
590
|
+
{ title: "使用已有 Token" + (hasCbToken ? " ✓" : ""), value: "token", disabled: !hasCbToken },
|
|
591
|
+
],
|
|
592
|
+
initial: 0,
|
|
593
|
+
}, { onCancel });
|
|
594
|
+
let cbApiToken = "";
|
|
595
|
+
let cbApiUrl = existing?.platforms?.clawbot?.apiUrl ?? "http://127.0.0.1:26322";
|
|
596
|
+
if (cbModeResp.mode === "qr") {
|
|
597
|
+
console.log("\n正在获取二维码...\n");
|
|
598
|
+
try {
|
|
599
|
+
const { startQRLogin, waitForQRLogin } = await import("./clawbot/qr-login.js");
|
|
600
|
+
const session = await startQRLogin();
|
|
601
|
+
console.log("请用微信扫描以下链接中的二维码:");
|
|
602
|
+
console.log(session.qrcodeUrl);
|
|
603
|
+
console.log("\n等待扫码...(最长 5 分钟)\n");
|
|
604
|
+
const result = await waitForQRLogin(session, (status) => {
|
|
605
|
+
if (status === "scaned")
|
|
606
|
+
process.stdout.write("已扫码,验证中...\n");
|
|
607
|
+
});
|
|
608
|
+
if (result.connected && result.botToken) {
|
|
609
|
+
cbApiToken = result.botToken;
|
|
610
|
+
if (result.baseUrl)
|
|
611
|
+
cbApiUrl = result.baseUrl;
|
|
612
|
+
console.log("\n✅ 登录成功!");
|
|
613
|
+
if (result.userId) {
|
|
614
|
+
console.log(` 用户 ID: ${result.userId}`);
|
|
615
|
+
console.log(" 提示:可将此 ID 添加到白名单");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
console.log(`\n❌ 登录失败: ${result.message}`);
|
|
620
|
+
if (platform === "clawbot")
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch (err) {
|
|
625
|
+
console.error(`\n❌ 二维码获取失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
626
|
+
if (platform === "clawbot")
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
const cbResp = await prompts([
|
|
632
|
+
{
|
|
633
|
+
type: "text",
|
|
634
|
+
name: "apiUrl",
|
|
635
|
+
message: "ClawBot iLink API 地址(默认 http://127.0.0.1:26322)",
|
|
636
|
+
initial: cbApiUrl,
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
type: "text",
|
|
640
|
+
name: "apiToken",
|
|
641
|
+
message: "ClawBot Bearer Token",
|
|
642
|
+
initial: existing?.platforms?.clawbot?.apiToken ?? "",
|
|
643
|
+
validate: (v) => (v.trim() ? true : "Token 不能为空"),
|
|
644
|
+
},
|
|
645
|
+
], { onCancel });
|
|
646
|
+
cbApiUrl = cbResp.apiUrl?.trim() || cbApiUrl;
|
|
647
|
+
cbApiToken = cbResp.apiToken?.trim() || "";
|
|
648
|
+
}
|
|
649
|
+
if (cbApiToken) {
|
|
650
|
+
config.platforms.clawbot = {
|
|
651
|
+
enabled: true,
|
|
652
|
+
apiUrl: cbApiUrl,
|
|
653
|
+
apiToken: cbApiToken,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
else if (platform === "clawbot") {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
566
660
|
if (selectedPlatforms.includes("wework")) {
|
|
567
661
|
const weworkResp = await prompts([
|
|
568
662
|
{
|
|
@@ -687,6 +781,15 @@ export async function runInteractiveSetup() {
|
|
|
687
781
|
initial: dtIds,
|
|
688
782
|
});
|
|
689
783
|
}
|
|
784
|
+
if (selectedPlatforms.includes("clawbot")) {
|
|
785
|
+
const cbIds = existing?.platforms?.clawbot?.allowedUserIds?.join(", ") ?? "";
|
|
786
|
+
commonPrompts.push({
|
|
787
|
+
type: "text",
|
|
788
|
+
name: "clawbotAllowedUserIds",
|
|
789
|
+
message: "ClawBot 白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
790
|
+
initial: cbIds,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
690
793
|
commonPrompts.push({
|
|
691
794
|
type: "text",
|
|
692
795
|
name: "workDir",
|
|
@@ -796,6 +899,9 @@ export async function runInteractiveSetup() {
|
|
|
796
899
|
const dingtalkIds = selectedPlatforms.includes("dingtalk")
|
|
797
900
|
? parseIds(commonResp.dingtalkAllowedUserIds)
|
|
798
901
|
: parseIds(existing?.platforms?.dingtalk?.allowedUserIds?.join(", "));
|
|
902
|
+
const clawbotIds = selectedPlatforms.includes("clawbot")
|
|
903
|
+
? parseIds(commonResp.clawbotAllowedUserIds)
|
|
904
|
+
: parseIds(existing?.platforms?.clawbot?.allowedUserIds?.join(", "));
|
|
799
905
|
// 增量合并:以已有配置为底,只覆盖本次选中的平台(不写入根级旧字段 telegramBotToken 等)
|
|
800
906
|
const base = existing
|
|
801
907
|
? JSON.parse(JSON.stringify(existing))
|
|
@@ -985,6 +1091,27 @@ export async function runInteractiveSetup() {
|
|
|
985
1091
|
else {
|
|
986
1092
|
outPlatforms.dingtalk = { enabled: false, aiCommand: "claude", allowedUserIds: dingtalkIds };
|
|
987
1093
|
}
|
|
1094
|
+
if (selectedPlatforms.includes("clawbot")) {
|
|
1095
|
+
const cbConfig = config.platforms?.clawbot;
|
|
1096
|
+
const baseCb = base?.platforms?.clawbot;
|
|
1097
|
+
outPlatforms.clawbot = {
|
|
1098
|
+
enabled: true,
|
|
1099
|
+
aiCommand: defaultPlatformAi(baseCb?.aiCommand),
|
|
1100
|
+
apiUrl: String(cbConfig?.apiUrl ?? baseCb?.apiUrl ?? "http://127.0.0.1:26322"),
|
|
1101
|
+
apiToken: String(cbConfig?.apiToken ?? baseCb?.apiToken ?? ""),
|
|
1102
|
+
allowedUserIds: clawbotIds,
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
else if (basePlatforms?.clawbot) {
|
|
1106
|
+
outPlatforms.clawbot = {
|
|
1107
|
+
...basePlatforms.clawbot,
|
|
1108
|
+
aiCommand: defaultPlatformAi(basePlatforms.clawbot.aiCommand),
|
|
1109
|
+
allowedUserIds: clawbotIds.length > 0 ? clawbotIds : basePlatforms.clawbot.allowedUserIds ?? [],
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
outPlatforms.clawbot = { enabled: false, aiCommand: "claude", allowedUserIds: clawbotIds };
|
|
1114
|
+
}
|
|
988
1115
|
const dir = dirname(configPath);
|
|
989
1116
|
if (!existsSync(dir)) {
|
|
990
1117
|
mkdirSync(dir, { recursive: true });
|
|
@@ -1005,8 +1132,9 @@ const PLATFORM_LABELS = {
|
|
|
1005
1132
|
wework: "企业微信",
|
|
1006
1133
|
dingtalk: "钉钉",
|
|
1007
1134
|
workbuddy: "WorkBuddy 微信客服",
|
|
1135
|
+
clawbot: "ClawBot 微信 iLink",
|
|
1008
1136
|
};
|
|
1009
|
-
const ALL_PLATFORMS = ["telegram", "feishu", "qq", "wework", "dingtalk", "workbuddy"];
|
|
1137
|
+
const ALL_PLATFORMS = ["telegram", "feishu", "qq", "wework", "dingtalk", "workbuddy", "clawbot"];
|
|
1010
1138
|
/**
|
|
1011
1139
|
* 启动时让用户选择要启用的平台(无论单通道还是多通道)
|
|
1012
1140
|
* 显示全部 4 个平台,已配置的预选;若用户选择未配置的,引导运行 init
|
|
@@ -6,8 +6,8 @@ export interface DingTalkActiveTarget {
|
|
|
6
6
|
updatedAt: number;
|
|
7
7
|
}
|
|
8
8
|
export declare function loadActiveChats(): void;
|
|
9
|
-
export declare function getActiveChatId(platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy'): string | undefined;
|
|
10
|
-
export declare function setActiveChatId(platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy', chatId: string): void;
|
|
9
|
+
export declare function getActiveChatId(platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy' | 'clawbot'): string | undefined;
|
|
10
|
+
export declare function setActiveChatId(platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy' | 'clawbot', chatId: string): void;
|
|
11
11
|
export declare function getDingTalkActiveTarget(): DingTalkActiveTarget | undefined;
|
|
12
12
|
export declare function setDingTalkActiveTarget(target: Omit<DingTalkActiveTarget, 'updatedAt'>): void;
|
|
13
13
|
export declare function flushActiveChats(): void;
|
package/dist/shared/ai-task.d.ts
CHANGED
|
@@ -42,5 +42,19 @@ export interface TaskRunState {
|
|
|
42
42
|
startedAt: number;
|
|
43
43
|
/** AI 工具标识,用于动态显示工具名称。 */
|
|
44
44
|
toolId: string;
|
|
45
|
+
/**
|
|
46
|
+
* 进程退出(shutdown / 崩溃)时,用于为仍在运行的任务补发一条终态遥测事件,
|
|
47
|
+
* 避免 `ai.task.start` 没有对应的 complete/error(遥测里的 `miss`)。
|
|
48
|
+
* 已哈希(与 ai.task.* emit 处一致),不含原始 userId/msgId。
|
|
49
|
+
*/
|
|
50
|
+
taskKey: string;
|
|
51
|
+
platform: string;
|
|
52
|
+
/** 已哈希的 userId,与 ai.task.start/complete/error 中的 userKey 一致。 */
|
|
53
|
+
userKey: string;
|
|
45
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* 将 AI/CLI 错误文本归类为一个稳定的 errorType 字符串,用于遥测聚合。
|
|
57
|
+
* 分类基于线上真实错误签名(见 logs/r2-events),新增分支时同步更新测试。
|
|
58
|
+
*/
|
|
59
|
+
export declare function classifyErrorType(error: string): string;
|
|
46
60
|
export declare function runAITask(deps: TaskDeps, ctx: TaskContext, prompt: string, toolAdapter: ToolAdapter, platformAdapter: TaskAdapter): Promise<void>;
|