@xmanrui/dsh-im 4.22.0 → 4.23.0
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.en.md +3 -3
- package/README.md +3 -3
- package/lib/client.js +2505 -1152
- package/lib/index.js +290 -299
- package/package.json +8 -1
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +16 -3
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +5 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +61 -0
- package/plugin-src/client/index.js +30 -2
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +6 -0
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +3 -1
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +692 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +593 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +8 -7
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +63 -18
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +90 -11
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-runtime.mjs +17 -13
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractConnectionEvidence, atConnectionStage, createConnectionDiagnostics } from '../shared/connection-error.mjs';
|
|
1
2
|
import { connectionTestMessage } from '../shared/connection-test.mjs';
|
|
2
3
|
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
3
4
|
import { t } from '../shared/i18n.mjs';
|
|
@@ -20,6 +21,7 @@ export class SlackController {
|
|
|
20
21
|
#createRuntime;
|
|
21
22
|
#deleteState;
|
|
22
23
|
#logger;
|
|
24
|
+
#diagnostics;
|
|
23
25
|
#runtimes = new Map();
|
|
24
26
|
#errors = new Map();
|
|
25
27
|
#transitions = new Map();
|
|
@@ -51,6 +53,14 @@ export class SlackController {
|
|
|
51
53
|
this.#createRuntime = createRuntime;
|
|
52
54
|
this.#deleteState = deleteState;
|
|
53
55
|
this.#logger = logger;
|
|
56
|
+
this.#diagnostics = createConnectionDiagnostics({ channel: 'slack', logger });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get diagnostics() { return this.#diagnostics; }
|
|
60
|
+
|
|
61
|
+
#failure(error, code, message) {
|
|
62
|
+
const stage = code.startsWith('qr-') ? 'qr.begin' : code === 'activation-failed' ? 'activation' : 'connection.start';
|
|
63
|
+
return this.#diagnostics.report(error, { reuse: true, stage, publicError: { code, message } }).publicError;
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
async initialize() {
|
|
@@ -58,26 +68,23 @@ export class SlackController {
|
|
|
58
68
|
for (const config of this.#configStore.list()) {
|
|
59
69
|
await this.#withBotTransition(config.botId, async () => {
|
|
60
70
|
if (this.#closed || this.#runtimes.get(config.botId)?.status?.ready) return;
|
|
61
|
-
const resolved = await this.#resolveCredentials(config);
|
|
62
|
-
if (!resolved) {
|
|
63
|
-
this.#errors.set(config.botId, safeError(
|
|
64
|
-
'missing-token',
|
|
65
|
-
t('Slack机器人凭据缺失,请移除后重新接入。'),
|
|
66
|
-
));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
71
|
try {
|
|
72
|
+
const resolved = await this.#resolveCredentials(config);
|
|
73
|
+
if (!resolved) {
|
|
74
|
+
this.#errors.set(config.botId, safeError(
|
|
75
|
+
'missing-token',
|
|
76
|
+
t('Slack机器人凭据缺失,请移除后重新接入。'),
|
|
77
|
+
));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
70
80
|
await this.#startRuntime(config, resolved);
|
|
71
81
|
this.#errors.delete(config.botId);
|
|
72
82
|
} catch (error) {
|
|
73
|
-
this.#errors.set(config.botId,
|
|
83
|
+
this.#errors.set(config.botId, this.#failure(error,
|
|
74
84
|
'connection-failed',
|
|
75
85
|
t('Slack Socket Mode 连接未就绪,插件会自动重试。'),
|
|
76
86
|
));
|
|
77
|
-
|
|
78
|
-
`[dsh-im:slack] bot ${config.botId} failed to initialize:`,
|
|
79
|
-
error,
|
|
80
|
-
);
|
|
87
|
+
|
|
81
88
|
} finally {
|
|
82
89
|
this.#touch();
|
|
83
90
|
}
|
|
@@ -105,8 +112,8 @@ export class SlackController {
|
|
|
105
112
|
await this.#withBotTransition(identity.botId, async () => {
|
|
106
113
|
if (this.#closed) throw new Error('Slack controller is closed');
|
|
107
114
|
const previousConfig = this.#configStore.getByPlatformId(platformId);
|
|
108
|
-
const previousBotToken = await this.#credentials.resolve(identity.botTokenRef)
|
|
109
|
-
const previousAppToken = await this.#credentials.resolve(identity.appTokenRef)
|
|
115
|
+
const previousBotToken = await atConnectionStage('credential.read', () => this.#credentials.resolve(identity.botTokenRef), 'credential-store');
|
|
116
|
+
const previousAppToken = await atConnectionStage('credential.read', () => this.#credentials.resolve(identity.appTokenRef), 'credential-store');
|
|
110
117
|
const config = {
|
|
111
118
|
...identity,
|
|
112
119
|
platformId,
|
|
@@ -118,9 +125,9 @@ export class SlackController {
|
|
|
118
125
|
connectedAt: new Date().toISOString(),
|
|
119
126
|
};
|
|
120
127
|
try {
|
|
121
|
-
await this.#credentials.set(identity.botTokenRef, normalizedBotToken);
|
|
122
|
-
await this.#credentials.set(identity.appTokenRef, normalizedAppToken);
|
|
123
|
-
await this.#configStore.save(config);
|
|
128
|
+
await atConnectionStage('credential.save', () => this.#credentials.set(identity.botTokenRef, normalizedBotToken), 'credential-store');
|
|
129
|
+
await atConnectionStage('credential.save', () => this.#credentials.set(identity.appTokenRef, normalizedAppToken), 'credential-store');
|
|
130
|
+
await atConnectionStage('account.save', () => this.#configStore.save(config), 'account-config');
|
|
124
131
|
} catch (error) {
|
|
125
132
|
await Promise.all([
|
|
126
133
|
this.#restoreCredential(identity.botTokenRef, previousBotToken),
|
|
@@ -135,14 +142,11 @@ export class SlackController {
|
|
|
135
142
|
});
|
|
136
143
|
this.#errors.delete(identity.botId);
|
|
137
144
|
} catch (error) {
|
|
138
|
-
this.#errors.set(identity.botId,
|
|
145
|
+
this.#errors.set(identity.botId, this.#failure(error,
|
|
139
146
|
'connection-failed',
|
|
140
147
|
t('Slack机器人已接入,Socket Mode 连接暂未就绪。'),
|
|
141
148
|
));
|
|
142
|
-
|
|
143
|
-
`[dsh-im:slack] bot ${identity.botId} credential connection failed:`,
|
|
144
|
-
error,
|
|
145
|
-
);
|
|
149
|
+
|
|
146
150
|
}
|
|
147
151
|
this.#touch();
|
|
148
152
|
});
|
|
@@ -159,7 +163,7 @@ export class SlackController {
|
|
|
159
163
|
await this.#startRuntime(config, resolved);
|
|
160
164
|
this.#errors.delete(botId);
|
|
161
165
|
} catch (error) {
|
|
162
|
-
this.#errors.set(botId,
|
|
166
|
+
this.#errors.set(botId, this.#failure(error,
|
|
163
167
|
'connection-failed',
|
|
164
168
|
t('Slack Socket Mode 连接仍未就绪,请检查两个 Token。'),
|
|
165
169
|
));
|
|
@@ -204,36 +208,40 @@ export class SlackController {
|
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
async deleteBot(botId) {
|
|
211
|
+
const warnings = [];
|
|
207
212
|
const config = this.#configStore.get(botId);
|
|
208
213
|
if (!config) throw new Error('Unknown Slack bot');
|
|
209
214
|
await this.#withBotTransition(botId, async () => {
|
|
210
|
-
const previousBotToken = await this.#credentials.resolve(config.botTokenRef)
|
|
211
|
-
const previousAppToken = await this.#credentials.resolve(config.appTokenRef)
|
|
215
|
+
const previousBotToken = await atConnectionStage('credential.read', () => this.#credentials.resolve(config.botTokenRef), 'credential-store');
|
|
216
|
+
const previousAppToken = await atConnectionStage('credential.read', () => this.#credentials.resolve(config.appTokenRef), 'credential-store');
|
|
212
217
|
await this.#stopRuntime(botId);
|
|
213
218
|
try {
|
|
214
|
-
await this.#credentials.unset(config.botTokenRef);
|
|
215
|
-
await this.#credentials.unset(config.appTokenRef);
|
|
216
|
-
await this.#configStore.remove(botId);
|
|
219
|
+
await atConnectionStage('credential.remove', () => this.#credentials.unset(config.botTokenRef), 'credential-store');
|
|
220
|
+
await atConnectionStage('credential.remove', () => this.#credentials.unset(config.appTokenRef), 'credential-store');
|
|
221
|
+
await atConnectionStage('account.remove', () => this.#configStore.remove(botId), 'account-config');
|
|
217
222
|
} catch (error) {
|
|
218
|
-
|
|
219
|
-
this.#
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
if (!this.#configStore.get(botId)) {
|
|
224
|
+
warnings.push(this.#diagnostics.report(error, { operation: 'bot.delete', stage: 'workspace.cleanup', warning: true,
|
|
225
|
+
publicError: { code: 'workspace-cleanup-failed', message: '账号已移除,但本地状态清理失败。' } }).publicError);
|
|
226
|
+
} else {
|
|
227
|
+
await Promise.all([
|
|
228
|
+
this.#restoreCredential(config.botTokenRef, previousBotToken),
|
|
229
|
+
this.#restoreCredential(config.appTokenRef, previousAppToken),
|
|
230
|
+
]);
|
|
231
|
+
if (previousBotToken?.value && previousAppToken?.value) {
|
|
232
|
+
await this.#startRuntime(config, {
|
|
233
|
+
botToken: previousBotToken.value,
|
|
234
|
+
appToken: previousAppToken.value,
|
|
235
|
+
}).catch(() => undefined);
|
|
236
|
+
}
|
|
237
|
+
throw new Error('Unable to remove the Slack bot safely.', { cause: error });
|
|
227
238
|
}
|
|
228
|
-
throw new Error('Unable to remove the Slack bot safely.', { cause: error });
|
|
229
239
|
}
|
|
230
|
-
await this.#deleteState({ botId, config }).catch(
|
|
231
|
-
this.#logger.warn?.(`[dsh-im:slack] bot ${botId} state cleanup failed:`, error);
|
|
232
|
-
});
|
|
240
|
+
await this.#deleteState({ botId, config }).catch(error => { warnings.push(this.#diagnostics.report(error, { reuse: true, operation: 'bot.delete', stage: 'state.cleanup', resource: 'account-state', warning: true, publicError: { code: 'cleanup-failed', message: '账号已移除,但本地状态清理失败。' } }).publicError); });
|
|
233
241
|
this.#errors.delete(botId);
|
|
234
242
|
this.#touch();
|
|
235
243
|
});
|
|
236
|
-
return this.status();
|
|
244
|
+
return { ...this.status(), ...(warnings.length ? { warnings } : {}) };
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
status() {
|
|
@@ -270,7 +278,7 @@ export class SlackController {
|
|
|
270
278
|
messagesReplied: runtimeStatus?.messagesReplied ?? 0,
|
|
271
279
|
},
|
|
272
280
|
lastMessageError: publicMessageFailure(runtimeStatus?.lastMessageError),
|
|
273
|
-
error: structuredClone(this.#errors.get(config.botId) ?? null),
|
|
281
|
+
error: structuredClone(runtimeStatus?.error ?? this.#errors.get(config.botId) ?? null),
|
|
274
282
|
};
|
|
275
283
|
});
|
|
276
284
|
const connected = bots.filter((bot) => bot.connected).length;
|
|
@@ -296,12 +304,12 @@ export class SlackController {
|
|
|
296
304
|
if (this.#closed) throw new Error('Slack controller is closed');
|
|
297
305
|
await this.#stopRuntime(config.botId);
|
|
298
306
|
if (this.#closed) throw new Error('Slack controller is closed');
|
|
299
|
-
const runtime = await this.#createRuntime({
|
|
307
|
+
const runtime = await atConnectionStage('runtime.prepare', () => this.#createRuntime({
|
|
300
308
|
botId: config.botId,
|
|
301
309
|
config,
|
|
302
310
|
botToken,
|
|
303
311
|
appToken,
|
|
304
|
-
});
|
|
312
|
+
}));
|
|
305
313
|
if (!runtime || typeof runtime.start !== 'function' || typeof runtime.stop !== 'function') {
|
|
306
314
|
throw new TypeError('createRuntime returned an invalid Slack runtime');
|
|
307
315
|
}
|
|
@@ -319,14 +327,14 @@ export class SlackController {
|
|
|
319
327
|
const runtime = this.#runtimes.get(botId);
|
|
320
328
|
this.#runtimes.delete(botId);
|
|
321
329
|
await runtime?.stop().catch((error) => {
|
|
322
|
-
this.#logger.warn?.(`[dsh-im:slack] bot ${botId} failed to stop cleanly:`, error);
|
|
330
|
+
this.#logger.warn?.(`[dsh-im:slack] bot ${botId} failed to stop cleanly:`, extractConnectionEvidence(error).details);
|
|
323
331
|
});
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
async #resolveCredentials(config) {
|
|
327
335
|
const [bot, app] = await Promise.all([
|
|
328
|
-
this.#credentials.resolve(config.botTokenRef)
|
|
329
|
-
this.#credentials.resolve(config.appTokenRef)
|
|
336
|
+
atConnectionStage('credential.read', () => this.#credentials.resolve(config.botTokenRef), 'credential-store'),
|
|
337
|
+
atConnectionStage('credential.read', () => this.#credentials.resolve(config.appTokenRef), 'credential-store'),
|
|
330
338
|
]);
|
|
331
339
|
const botToken = cleanString(bot?.value);
|
|
332
340
|
const appToken = cleanString(app?.value);
|
|
@@ -334,8 +342,8 @@ export class SlackController {
|
|
|
334
342
|
}
|
|
335
343
|
|
|
336
344
|
async #restoreCredential(ref, previous) {
|
|
337
|
-
if (previous?.value) await this.#credentials.set(ref, previous.value).catch(() => undefined);
|
|
338
|
-
else await this.#credentials.unset(ref).catch(() => undefined);
|
|
345
|
+
if (previous?.value) await atConnectionStage('credential.save', () => this.#credentials.set(ref, previous.value), 'credential-store').catch(() => undefined);
|
|
346
|
+
else await atConnectionStage('credential.remove', () => this.#credentials.unset(ref), 'credential-store').catch(() => undefined);
|
|
339
347
|
}
|
|
340
348
|
|
|
341
349
|
#withBotTransition(botId, operation) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractConnectionEvidence, createConnectionDiagnostics, atConnectionStage } from '../shared/connection-error.mjs';
|
|
1
2
|
import { splitMessageText } from '../shared/editable-message-stream.mjs';
|
|
2
3
|
import { t } from '../shared/i18n.mjs';
|
|
3
4
|
import { SlackApi } from './slack-api.mjs';
|
|
@@ -410,6 +411,7 @@ export class SlackRuntime {
|
|
|
410
411
|
#contextEnhancement;
|
|
411
412
|
#accessPolicy;
|
|
412
413
|
#logger;
|
|
414
|
+
#diagnostics;
|
|
413
415
|
#replyTimeoutMs;
|
|
414
416
|
#connectTimeoutMs;
|
|
415
417
|
#createApi;
|
|
@@ -451,7 +453,7 @@ export class SlackRuntime {
|
|
|
451
453
|
this.#state = state;
|
|
452
454
|
this.#contextEnhancement = contextEnhancement;
|
|
453
455
|
this.#accessPolicy = accessPolicy;
|
|
454
|
-
this.#logger = logger;
|
|
456
|
+
this.#logger = logger; this.#diagnostics = createConnectionDiagnostics({ channel: 'slack', logger });
|
|
455
457
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
456
458
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
457
459
|
this.#createApi = createApi;
|
|
@@ -510,8 +512,8 @@ export class SlackRuntime {
|
|
|
510
512
|
this.#reconnectAttempt = 0;
|
|
511
513
|
this.#status.startedAt = new Date().toISOString();
|
|
512
514
|
this.#status.connectionState = 'connecting';
|
|
513
|
-
this.#status.lastError = null;
|
|
514
|
-
await this.#harness.ensureRunning();
|
|
515
|
+
this.#status.lastError = null; this.#status.error = null; this.#diagnostics.clear();
|
|
516
|
+
await atConnectionStage('harness.check', () => this.#harness.ensureRunning());
|
|
515
517
|
this.#status.harnessReachable = true;
|
|
516
518
|
const controller = new AbortController();
|
|
517
519
|
this.#abortController = controller;
|
|
@@ -540,7 +542,7 @@ export class SlackRuntime {
|
|
|
540
542
|
this.#connect(),
|
|
541
543
|
new Promise((_, reject) => {
|
|
542
544
|
timer = setTimeout(
|
|
543
|
-
() => reject(new Error('Slack Socket Mode did not become ready in time')),
|
|
545
|
+
() => reject(Object.assign(new Error('Slack Socket Mode did not become ready in time'), { code: 'ETIMEDOUT', timeoutMs: this.#connectTimeoutMs })),
|
|
544
546
|
this.#connectTimeoutMs,
|
|
545
547
|
);
|
|
546
548
|
timer?.unref?.();
|
|
@@ -553,7 +555,8 @@ export class SlackRuntime {
|
|
|
553
555
|
} catch (error) {
|
|
554
556
|
this.#status.ready = false;
|
|
555
557
|
this.#status.connectionState = 'failed';
|
|
556
|
-
this.#status.
|
|
558
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.restore', reuse: true, botId: this.#config?.botId, automatic: true }).publicError;
|
|
559
|
+
this.#status.lastError = this.#status.error.message;
|
|
557
560
|
await this.stop();
|
|
558
561
|
throw error;
|
|
559
562
|
}
|
|
@@ -582,7 +585,7 @@ export class SlackRuntime {
|
|
|
582
585
|
this.#status.connectionState = 'connected';
|
|
583
586
|
this.#status.lastCheckedAt = now;
|
|
584
587
|
this.#status.lastConnectedAt = now;
|
|
585
|
-
this.#status.lastError = null;
|
|
588
|
+
this.#status.lastError = null; this.#status.error = null; this.#diagnostics.clear();
|
|
586
589
|
resolve();
|
|
587
590
|
};
|
|
588
591
|
|
|
@@ -624,7 +627,7 @@ export class SlackRuntime {
|
|
|
624
627
|
if (generation !== this.#generation || this.#stopped) return;
|
|
625
628
|
this.#logger.error?.(
|
|
626
629
|
`[dsh-im:slack] bot ${this.#config.botId} message handling failed:`,
|
|
627
|
-
error,
|
|
630
|
+
extractConnectionEvidence(error).details,
|
|
628
631
|
);
|
|
629
632
|
});
|
|
630
633
|
}
|
|
@@ -641,7 +644,8 @@ export class SlackRuntime {
|
|
|
641
644
|
const error = new Error(`Slack Socket Mode closed (${code || 'unknown'})`);
|
|
642
645
|
this.#status.ready = false;
|
|
643
646
|
this.#status.connectionState = 'connecting';
|
|
644
|
-
this.#status.
|
|
647
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.monitor', botId: this.#config?.botId, automatic: true }).publicError;
|
|
648
|
+
this.#status.lastError = this.#status.error.message;
|
|
645
649
|
if (!settled) {
|
|
646
650
|
settled = true;
|
|
647
651
|
reject(error);
|
|
@@ -649,9 +653,11 @@ export class SlackRuntime {
|
|
|
649
653
|
this.#scheduleReconnect();
|
|
650
654
|
});
|
|
651
655
|
|
|
652
|
-
addSocketListener(socket, 'error', () => {
|
|
656
|
+
addSocketListener(socket, 'error', (event) => {
|
|
657
|
+
const error = event?.error ?? new Error('WebSocket error');
|
|
653
658
|
if (generation !== this.#generation || this.#stopped) return;
|
|
654
|
-
this.#status.
|
|
659
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.monitor', botId: this.#config?.botId, automatic: true }).publicError;
|
|
660
|
+
this.#status.lastError = this.#status.error.message;
|
|
655
661
|
});
|
|
656
662
|
});
|
|
657
663
|
}
|
|
@@ -664,7 +670,7 @@ export class SlackRuntime {
|
|
|
664
670
|
this.#reconnectTimer = null;
|
|
665
671
|
void this.#connect().catch((error) => {
|
|
666
672
|
if (this.#stopped) return;
|
|
667
|
-
this.#logger.warn?.('[dsh-im:slack] Socket Mode reconnect failed:', error);
|
|
673
|
+
this.#logger.warn?.('[dsh-im:slack] Socket Mode reconnect failed:', extractConnectionEvidence(error).details);
|
|
668
674
|
this.#scheduleReconnect();
|
|
669
675
|
});
|
|
670
676
|
}, delay);
|
|
@@ -687,7 +693,7 @@ export class SlackRuntime {
|
|
|
687
693
|
try {
|
|
688
694
|
if (socket && socket.readyState < 2) socket.close(1000, 'Plugin stopped');
|
|
689
695
|
} catch (error) {
|
|
690
|
-
this.#logger.warn?.(`[dsh-im:slack] bot ${this.#config.botId} failed to close Socket Mode:`, error);
|
|
696
|
+
this.#logger.warn?.(`[dsh-im:slack] bot ${this.#config.botId} failed to close Socket Mode:`, extractConnectionEvidence(error).details);
|
|
691
697
|
}
|
|
692
698
|
await Promise.race([
|
|
693
699
|
bridge?.waitForIdle() ?? Promise.resolve(),
|
|
@@ -450,8 +450,8 @@ export class TelegramApi {
|
|
|
450
450
|
let body;
|
|
451
451
|
try {
|
|
452
452
|
body = await response.json();
|
|
453
|
-
} catch {
|
|
454
|
-
const error = new Error(`Telegram ${method} returned invalid JSON
|
|
453
|
+
} catch (cause) {
|
|
454
|
+
const error = new Error(`Telegram ${method} returned invalid JSON`, { cause });
|
|
455
455
|
error.status = response?.status;
|
|
456
456
|
error.code = 'telegram-response-invalid';
|
|
457
457
|
error.deliveryOutcome = 'unknown';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractConnectionEvidence, createConnectionDiagnostics, atConnectionStage } from '../shared/connection-error.mjs';
|
|
1
2
|
import { randomInt } from 'node:crypto';
|
|
2
3
|
|
|
3
4
|
import { createEditableMessageStream, splitMessageText } from '../shared/editable-message-stream.mjs';
|
|
@@ -451,7 +452,7 @@ class TelegramDeliveryStream {
|
|
|
451
452
|
try {
|
|
452
453
|
return await this.#update(block);
|
|
453
454
|
} catch (error) {
|
|
454
|
-
this.#logger.warn?.('[dsh-im:telegram] rich stream update failed:', error);
|
|
455
|
+
this.#logger.warn?.('[dsh-im:telegram] rich stream update failed:', extractConnectionEvidence(error).details);
|
|
455
456
|
return undefined;
|
|
456
457
|
}
|
|
457
458
|
});
|
|
@@ -467,7 +468,7 @@ class TelegramDeliveryStream {
|
|
|
467
468
|
try {
|
|
468
469
|
return await this.#update(this.#lastBlock);
|
|
469
470
|
} catch (error) {
|
|
470
|
-
this.#logger.warn?.('[dsh-im:telegram] rich stream refresh failed:', error);
|
|
471
|
+
this.#logger.warn?.('[dsh-im:telegram] rich stream refresh failed:', extractConnectionEvidence(error).details);
|
|
471
472
|
return undefined;
|
|
472
473
|
}
|
|
473
474
|
});
|
|
@@ -926,6 +927,7 @@ export class TelegramRuntime {
|
|
|
926
927
|
#contextEnhancement;
|
|
927
928
|
#accessPolicy;
|
|
928
929
|
#logger;
|
|
930
|
+
#diagnostics;
|
|
929
931
|
#replyTimeoutMs;
|
|
930
932
|
#createApi;
|
|
931
933
|
#createHttpTransport;
|
|
@@ -968,7 +970,7 @@ export class TelegramRuntime {
|
|
|
968
970
|
this.#state = state;
|
|
969
971
|
this.#contextEnhancement = contextEnhancement;
|
|
970
972
|
this.#accessPolicy = accessPolicy;
|
|
971
|
-
this.#logger = logger;
|
|
973
|
+
this.#logger = logger; this.#diagnostics = createConnectionDiagnostics({ channel: 'telegram', logger });
|
|
972
974
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
973
975
|
this.#createApi = createApi;
|
|
974
976
|
this.#createHttpTransport = createHttpTransport;
|
|
@@ -1026,8 +1028,8 @@ export class TelegramRuntime {
|
|
|
1026
1028
|
this.#connecting = true;
|
|
1027
1029
|
this.#status.startedAt = new Date().toISOString();
|
|
1028
1030
|
this.#status.connectionState = 'connecting';
|
|
1029
|
-
this.#status.lastError = null;
|
|
1030
|
-
await this.#harness.ensureRunning();
|
|
1031
|
+
this.#status.lastError = null; this.#status.error = null; this.#diagnostics.clear();
|
|
1032
|
+
await atConnectionStage('harness.check', () => this.#harness.ensureRunning());
|
|
1031
1033
|
this.#status.harnessReachable = true;
|
|
1032
1034
|
|
|
1033
1035
|
const controller = new AbortController();
|
|
@@ -1057,7 +1059,7 @@ export class TelegramRuntime {
|
|
|
1057
1059
|
} catch (error) {
|
|
1058
1060
|
this.#logger.warn?.(
|
|
1059
1061
|
`[dsh-im:telegram] bot ${this.#config.botId} command menu setup failed:`,
|
|
1060
|
-
error,
|
|
1062
|
+
extractConnectionEvidence(error).details,
|
|
1061
1063
|
);
|
|
1062
1064
|
}
|
|
1063
1065
|
const client = new TelegramBotClient({
|
|
@@ -1099,7 +1101,7 @@ export class TelegramRuntime {
|
|
|
1099
1101
|
} catch (error) {
|
|
1100
1102
|
this.#logger.warn?.(
|
|
1101
1103
|
`[dsh-im:telegram] bot ${this.#config.botId} command menu catch-up failed:`,
|
|
1102
|
-
error,
|
|
1104
|
+
extractConnectionEvidence(error).details,
|
|
1103
1105
|
);
|
|
1104
1106
|
}
|
|
1105
1107
|
}
|
|
@@ -1108,14 +1110,16 @@ export class TelegramRuntime {
|
|
|
1108
1110
|
if (controller.signal.aborted) return;
|
|
1109
1111
|
this.#status.ready = false;
|
|
1110
1112
|
this.#status.connectionState = 'failed';
|
|
1111
|
-
this.#status.
|
|
1112
|
-
this.#
|
|
1113
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.monitor', botId: this.#config?.botId, automatic: true }).publicError;
|
|
1114
|
+
this.#status.lastError = this.#status.error.message;
|
|
1115
|
+
|
|
1113
1116
|
});
|
|
1114
1117
|
return this.status;
|
|
1115
1118
|
} catch (error) {
|
|
1116
1119
|
this.#status.ready = false;
|
|
1117
1120
|
this.#status.connectionState = 'failed';
|
|
1118
|
-
this.#status.
|
|
1121
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.restore', reuse: true, botId: this.#config?.botId, automatic: true }).publicError;
|
|
1122
|
+
this.#status.lastError = this.#status.error.message;
|
|
1119
1123
|
await this.stop();
|
|
1120
1124
|
throw error;
|
|
1121
1125
|
} finally {
|
|
@@ -1160,7 +1164,7 @@ export class TelegramRuntime {
|
|
|
1160
1164
|
} catch (error) {
|
|
1161
1165
|
this.#logger.warn?.(
|
|
1162
1166
|
`[dsh-im:telegram] bot ${this.#config.botId} command menu refresh failed:`,
|
|
1163
|
-
error,
|
|
1167
|
+
extractConnectionEvidence(error).details,
|
|
1164
1168
|
);
|
|
1165
1169
|
return false;
|
|
1166
1170
|
}
|
|
@@ -1210,7 +1214,7 @@ export class TelegramRuntime {
|
|
|
1210
1214
|
if (signal.aborted) return;
|
|
1211
1215
|
this.#logger.error?.(
|
|
1212
1216
|
`[dsh-im:telegram] bot ${this.#config.botId} callback handling failed:`,
|
|
1213
|
-
error,
|
|
1217
|
+
extractConnectionEvidence(error).details,
|
|
1214
1218
|
);
|
|
1215
1219
|
});
|
|
1216
1220
|
}
|
|
@@ -1230,7 +1234,7 @@ export class TelegramRuntime {
|
|
|
1230
1234
|
if (signal.aborted) return;
|
|
1231
1235
|
this.#logger.error?.(
|
|
1232
1236
|
`[dsh-im:telegram] bot ${this.#config.botId} message handling failed:`,
|
|
1233
|
-
error,
|
|
1237
|
+
extractConnectionEvidence(error).details,
|
|
1234
1238
|
);
|
|
1235
1239
|
});
|
|
1236
1240
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { imageInputLimits } from '../shared/image-input-policy.mjs';
|
|
2
|
+
import { imageDownloadLimitMessage } from '../shared/image-prompt.mjs';
|
|
1
3
|
import { createDeferredDeliveryCoordinator, deferredOutcomeText } from '../shared/deferred-delivery-coordinator.mjs';
|
|
2
4
|
import { generateReqId } from '@wecom/aibot-node-sdk';
|
|
3
5
|
import { randomUUID } from 'node:crypto';
|
|
@@ -216,7 +218,7 @@ function imageSource(client, image) {
|
|
|
216
218
|
throw new ImagePromptError(
|
|
217
219
|
'image-too-large',
|
|
218
220
|
`Enterprise WeChat image exceeds ${maxBytes} bytes`,
|
|
219
|
-
|
|
221
|
+
imageDownloadLimitMessage(maxBytes),
|
|
220
222
|
);
|
|
221
223
|
}
|
|
222
224
|
return { data, name: result?.filename };
|
|
@@ -280,12 +282,17 @@ function prefetchInboundFiles(message, signal) {
|
|
|
280
282
|
};
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
function prefetchInboundImages(message, signal) {
|
|
285
|
+
function prefetchInboundImages(message, signal, limitsPromise) {
|
|
284
286
|
if (!hasInboundImages(message)) return message;
|
|
285
287
|
return {
|
|
286
288
|
...message,
|
|
287
289
|
images: message.images.map((source) => {
|
|
288
|
-
const download =
|
|
290
|
+
const download = Promise.resolve(limitsPromise).then((limits) => {
|
|
291
|
+
if (message.images.length > limits.maxImages) throw new ImagePromptError(
|
|
292
|
+
'too-many-images', 'Too many inbound images',
|
|
293
|
+
t('一次最多只能处理 {maxImages} 张图片。', { maxImages: limits.maxImages }));
|
|
294
|
+
return source.load({ signal, maxBytes: limits.maxDownloadBytes });
|
|
295
|
+
});
|
|
289
296
|
// The conversation queue may not consume this promise immediately. Keep
|
|
290
297
|
// an attached rejection handler while preserving the original outcome.
|
|
291
298
|
download.catch(() => undefined);
|
|
@@ -301,7 +308,7 @@ function prefetchInboundImages(message, signal) {
|
|
|
301
308
|
throw new ImagePromptError(
|
|
302
309
|
'image-too-large',
|
|
303
310
|
`Enterprise WeChat image exceeds ${maxBytes} bytes`,
|
|
304
|
-
|
|
311
|
+
imageDownloadLimitMessage(maxBytes),
|
|
305
312
|
);
|
|
306
313
|
}
|
|
307
314
|
return result;
|
|
@@ -1060,7 +1067,8 @@ export class WecomHarnessBridge {
|
|
|
1060
1067
|
if (this.#prefetchedImageCount + imageCount <= MAX_PREFETCHED_IMAGES) {
|
|
1061
1068
|
reservedImages = imageCount;
|
|
1062
1069
|
this.#prefetchedImageCount += reservedImages;
|
|
1063
|
-
preparedMessage = prefetchInboundImages(inboundMessage, this.#signal
|
|
1070
|
+
preparedMessage = prefetchInboundImages(inboundMessage, this.#signal,
|
|
1071
|
+
this.#harness.getImageInputLimits?.() ?? imageInputLimits());
|
|
1064
1072
|
} else {
|
|
1065
1073
|
preparedMessage = imageQueueFullMessage(inboundMessage);
|
|
1066
1074
|
}
|
|
@@ -1356,7 +1364,7 @@ export class WecomHarnessBridge {
|
|
|
1356
1364
|
}
|
|
1357
1365
|
|
|
1358
1366
|
let content = hasImages || hasReply
|
|
1359
|
-
? await promptContentForInboundMessage(message, { signal: this.#signal })
|
|
1367
|
+
? await promptContentForInboundMessage(message, { signal: this.#signal, deferImages: true })
|
|
1360
1368
|
: undefined;
|
|
1361
1369
|
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
1362
1370
|
let contextEnhanced = false;
|
|
@@ -1408,6 +1416,7 @@ export class WecomHarnessBridge {
|
|
|
1408
1416
|
}),
|
|
1409
1417
|
onInteractionResolved: (resolution) => this.#handleInteractionResolved(resolution),
|
|
1410
1418
|
files: message.files,
|
|
1419
|
+
images: message.images,
|
|
1411
1420
|
},
|
|
1412
1421
|
});
|
|
1413
1422
|
if (batchSubmission) {
|