@xmanrui/dsh-im 0.14.0 → 0.16.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/lib/client.js +246 -46
- package/lib/index.js +137 -131
- package/package.json +1 -1
- package/plugin-src/client/channels/feishu/api.js +23 -1
- package/plugin-src/client/channels/feishu/index.js +235 -46
- package/plugin-src/client/channels/feishu/styles.js +2 -0
- package/plugin-src/client/i18n.js +39 -0
- package/plugin-src/client/index.js +2 -1
- package/plugin-src/client/styles.js +2 -1
- package/plugin-src/host/channels/feishu/production.mjs +3 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +146 -12
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +723 -1
- package/src/channels/feishu/feishu-cards.mjs +155 -0
- package/src/channels/feishu/feishu-runtime.mjs +198 -0
- package/src/channels/feishu/multi-bot-controller.mjs +357 -2
- package/src/channels/feishu/plugin-controller.mjs +1 -0
- package/src/channels/feishu/repair-manager.mjs +109 -0
- package/src/channels/shared/workspace-command.mjs +2 -2
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-controller.mjs +56 -12
- package/src/channels/weixin/weixin-runtime.mjs +12 -1
|
@@ -20,6 +20,14 @@ const ACTIVE_ATTEMPT_STATES = new Set([
|
|
|
20
20
|
]);
|
|
21
21
|
const TERMINAL_ATTEMPT_STATES = new Set(['connected', 'expired', 'failed', 'cancelled']);
|
|
22
22
|
const QR_TTL_MS = 5 * 60_000;
|
|
23
|
+
const ACTIVATION_ERROR_MESSAGES = Object.freeze({
|
|
24
|
+
'credential-read-failed': '微信已授权,但无法读取现有登录凭据。请检查 DSH 凭据存储。',
|
|
25
|
+
'credential-save-failed': '微信已授权,但登录凭据无法写入 DSH 凭据存储。请检查凭据存储是否可写。',
|
|
26
|
+
'account-config-save-failed': '微信已授权,但账号配置无法写入本机。请检查 DSH_HOME 目录权限。',
|
|
27
|
+
'runtime-prepare-failed': '微信已授权,但无法初始化账号状态或工作区。请检查 DSH_HOME 和工作区目录。',
|
|
28
|
+
'harness-unreachable': '微信已授权,但插件无法连接本机 Harness。请确认 dsh web 已正常启动。',
|
|
29
|
+
'connection-start-failed': '微信已授权,但消息连接初始化失败。请查看 dsh web 日志后重试。',
|
|
30
|
+
});
|
|
23
31
|
|
|
24
32
|
function cleanString(value) {
|
|
25
33
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
@@ -54,6 +62,26 @@ function safeAccountError(code, message) {
|
|
|
54
62
|
return Object.freeze({ code, message });
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
function activationStageError(code, cause) {
|
|
66
|
+
const error = new Error(`Weixin activation failed during ${code}`, { cause });
|
|
67
|
+
error.name = 'WeixinActivationStageError';
|
|
68
|
+
error.code = code;
|
|
69
|
+
return error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function publicProvisioningError(error) {
|
|
73
|
+
if (error instanceof WeixinApiError) return safeAccountError(error.code, error.message);
|
|
74
|
+
const message = ACTIVATION_ERROR_MESSAGES[error?.code];
|
|
75
|
+
return message
|
|
76
|
+
? safeAccountError(error.code, message)
|
|
77
|
+
: safeAccountError('activation-unknown-failed', '微信已授权,但激活过程中发生未知错误。请查看 dsh web 日志。');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function preserveActivationError(error, fallbackCode) {
|
|
81
|
+
if (error instanceof WeixinApiError || ACTIVATION_ERROR_MESSAGES[error?.code]) return error;
|
|
82
|
+
return activationStageError(fallbackCode, error);
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
export class WeixinController {
|
|
58
86
|
#api;
|
|
59
87
|
#credentials;
|
|
@@ -439,12 +467,7 @@ export class WeixinController {
|
|
|
439
467
|
record.error = safeAccountError('cancelled', '扫码绑定已取消。');
|
|
440
468
|
} else {
|
|
441
469
|
record.state = 'failed';
|
|
442
|
-
record.error =
|
|
443
|
-
error instanceof WeixinApiError ? error.code : 'activation-failed',
|
|
444
|
-
error instanceof WeixinApiError
|
|
445
|
-
? error.message
|
|
446
|
-
: '微信已授权,但无法保存凭据或启动消息连接。',
|
|
447
|
-
);
|
|
470
|
+
record.error = publicProvisioningError(error);
|
|
448
471
|
this.#logger.error?.('[dsh-weixin] provisioning failed:', error);
|
|
449
472
|
}
|
|
450
473
|
} finally {
|
|
@@ -469,13 +492,26 @@ export class WeixinController {
|
|
|
469
492
|
createdAt: previousConfig?.createdAt ?? new Date().toISOString(),
|
|
470
493
|
connectedAt: new Date().toISOString(),
|
|
471
494
|
};
|
|
472
|
-
|
|
495
|
+
let previousToken;
|
|
496
|
+
try {
|
|
497
|
+
previousToken = await this.#credentials.resolve(identity.tokenRef);
|
|
498
|
+
} catch (error) {
|
|
499
|
+
throw activationStageError('credential-read-failed', error);
|
|
500
|
+
}
|
|
473
501
|
|
|
474
502
|
return this.#withBotTransition(identity.botId, async () => {
|
|
475
|
-
await this.#credentials.set(identity.tokenRef, token);
|
|
476
503
|
try {
|
|
504
|
+
try {
|
|
505
|
+
await this.#credentials.set(identity.tokenRef, token);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
throw activationStageError('credential-save-failed', error);
|
|
508
|
+
}
|
|
477
509
|
this.#assertAttemptActive(record);
|
|
478
|
-
|
|
510
|
+
try {
|
|
511
|
+
await this.#configStore.save(config);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
throw activationStageError('account-config-save-failed', error);
|
|
514
|
+
}
|
|
479
515
|
this.#assertAttemptActive(record);
|
|
480
516
|
await this.#startRuntime(config, token);
|
|
481
517
|
this.#assertAttemptActive(record);
|
|
@@ -504,16 +540,24 @@ export class WeixinController {
|
|
|
504
540
|
|
|
505
541
|
async #startRuntime(config, token) {
|
|
506
542
|
await this.#stopRuntime(config.botId);
|
|
507
|
-
|
|
543
|
+
let runtime;
|
|
544
|
+
try {
|
|
545
|
+
runtime = await this.#createRuntime({ botId: config.botId, config, token });
|
|
546
|
+
} catch (error) {
|
|
547
|
+
throw preserveActivationError(error, 'runtime-prepare-failed');
|
|
548
|
+
}
|
|
508
549
|
if (!runtime || typeof runtime.start !== 'function' || typeof runtime.stop !== 'function') {
|
|
509
|
-
throw
|
|
550
|
+
throw activationStageError(
|
|
551
|
+
'runtime-prepare-failed',
|
|
552
|
+
new TypeError('createRuntime returned an invalid Weixin runtime'),
|
|
553
|
+
);
|
|
510
554
|
}
|
|
511
555
|
try {
|
|
512
556
|
await runtime.start();
|
|
513
557
|
this.#runtimes.set(config.botId, runtime);
|
|
514
558
|
} catch (error) {
|
|
515
559
|
await runtime.stop().catch(() => undefined);
|
|
516
|
-
throw error;
|
|
560
|
+
throw preserveActivationError(error, 'connection-start-failed');
|
|
517
561
|
}
|
|
518
562
|
}
|
|
519
563
|
|
|
@@ -25,6 +25,13 @@ function retryableStartError(error) {
|
|
|
25
25
|
&& (error.status === 408 || error.status === 425 || error.status === 429 || error.status >= 500);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function runtimeStartError(code, cause) {
|
|
29
|
+
const error = new Error(`Weixin runtime failed during ${code}`, { cause });
|
|
30
|
+
error.name = 'WeixinRuntimeStartError';
|
|
31
|
+
error.code = code;
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
function delay(ms, signal) {
|
|
29
36
|
return new Promise((resolve, reject) => {
|
|
30
37
|
if (signal?.aborted) {
|
|
@@ -117,7 +124,11 @@ export class WeixinRuntime {
|
|
|
117
124
|
this.#status.weixinConnectionState = 'connecting';
|
|
118
125
|
this.#status.lastError = null;
|
|
119
126
|
try {
|
|
120
|
-
|
|
127
|
+
try {
|
|
128
|
+
await this.#harness.ensureRunning();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw runtimeStartError('harness-unreachable', error);
|
|
131
|
+
}
|
|
121
132
|
this.#status.harnessReachable = true;
|
|
122
133
|
await this.#notifyStart();
|
|
123
134
|
this.#abortController = new AbortController();
|