@xmanrui/dsh-im 4.22.0 → 4.24.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 +5 -4
- package/README.md +5 -4
- package/lib/client.js +2584 -1153
- package/lib/index.js +290 -298
- package/package.json +14 -2
- 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 +23 -7
- 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 +8 -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/telegram/index.js +3 -0
- package/plugin-src/client/channels/telegram/styles.js +12 -0
- package/plugin-src/client/channels/telegram/thinking-traces.js +25 -0
- 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 +69 -1
- package/plugin-src/client/index.js +40 -4
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +8 -2
- 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 +14 -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/shared/thinking-traces-rpc.mjs +11 -0
- 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/host/modern-harness-api.mjs +7 -2
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +13 -5
- 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 +699 -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 +604 -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 +39 -11
- package/src/channels/feishu/feishu-channel.mjs +35 -0
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/live-cot.mjs +260 -0
- 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/feishu/slash-command-registry.mjs +17 -0
- package/src/channels/feishu/step-push-mode.mjs +10 -4
- 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 +287 -57
- 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 +150 -25
- 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/config-store.mjs +4 -1
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-controller.mjs +13 -1
- package/src/channels/telegram/telegram-runtime.mjs +215 -14
- 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(),
|
|
@@ -50,12 +50,15 @@ export function normalizeTelegramAccessPolicy(value = {}) {
|
|
|
50
50
|
function normalizeTelegramBotExtension(value) {
|
|
51
51
|
const hasAccessMode = Object.hasOwn(value, 'accessMode');
|
|
52
52
|
const hasAllowedUsers = Object.hasOwn(value, 'allowedUsers');
|
|
53
|
-
|
|
53
|
+
const hasThinkingTraces = Object.hasOwn(value, 'thinkingTraces');
|
|
54
|
+
if (!hasAccessMode && !hasAllowedUsers && !hasThinkingTraces) return {};
|
|
54
55
|
try {
|
|
55
56
|
const policy = normalizeTelegramAccessPolicy(value);
|
|
56
57
|
return {
|
|
57
58
|
...(hasAccessMode ? { accessMode: policy.accessMode } : {}),
|
|
58
59
|
...(hasAllowedUsers || hasAccessMode ? { allowedUsers: policy.allowedUsers } : {}),
|
|
60
|
+
// Explicit field is kept; an absent field stays absent.
|
|
61
|
+
...(hasThinkingTraces ? { thinkingTraces: value.thinkingTraces === true } : {}),
|
|
59
62
|
};
|
|
60
63
|
} catch {
|
|
61
64
|
return null;
|
|
@@ -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';
|
|
@@ -28,7 +28,12 @@ export class TelegramController extends TokenBotController {
|
|
|
28
28
|
bots: snapshot.bots.map((bot) => {
|
|
29
29
|
const config = this.#configStore.get(bot.botId);
|
|
30
30
|
const accessPolicy = normalizeTelegramAccessPolicy(config ?? {});
|
|
31
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
...bot,
|
|
33
|
+
accessPolicy,
|
|
34
|
+
// Default ON; only an explicit false in the config opts out.
|
|
35
|
+
thinkingTraces: config?.thinkingTraces !== false,
|
|
36
|
+
};
|
|
32
37
|
}),
|
|
33
38
|
};
|
|
34
39
|
}
|
|
@@ -37,4 +42,11 @@ export class TelegramController extends TokenBotController {
|
|
|
37
42
|
const accessPolicy = normalizeTelegramAccessPolicy(value);
|
|
38
43
|
return this.updateBotConfig(botId, (config) => ({ ...config, ...accessPolicy }));
|
|
39
44
|
}
|
|
45
|
+
|
|
46
|
+
async setThinkingTraces(botId, value) {
|
|
47
|
+
return this.updateBotConfig(botId, (config) => ({
|
|
48
|
+
...config,
|
|
49
|
+
thinkingTraces: value === true,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
40
52
|
}
|