@xmanrui/dsh-im 1.0.0 → 1.0.2
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 +2 -0
- package/README.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/lib/client.js +8 -1
- package/lib/index.js +159 -159
- package/package.json +2 -1
- package/plugin-src/client/i18n.js +8 -1
- package/plugin-src/host/channels/feishu/production.mjs +26 -1
- package/scripts/verify-package.mjs +3 -2
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/feishu-app.mjs +18 -15
- package/src/channels/feishu/feishu-runtime.mjs +4 -0
- package/src/channels/shared/harness-client.mjs +69 -12
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-controller.mjs +8 -1
- package/src/channels/weixin/weixin-runtime.mjs +17 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"@larksuiteoapi/node-sdk": "1.73.0",
|
|
100
100
|
"@whiskeysockets/baileys": "7.0.0-rc14",
|
|
101
101
|
"esbuild": "0.25.9",
|
|
102
|
+
"https-proxy-agent": "5.0.1",
|
|
102
103
|
"react": "18.3.1",
|
|
103
104
|
"react-dom": "18.3.1",
|
|
104
105
|
"react-test-renderer": "18.3.1"
|
|
@@ -305,7 +305,14 @@ const EN = Object.freeze({
|
|
|
305
305
|
'微信已授权,但登录凭据无法写入 DSH 凭据存储。请检查凭据存储是否可写。': 'WeChat was authorized, but the login credential could not be written to the DSH credential store. Check that the store is writable.',
|
|
306
306
|
'微信已授权,但账号配置无法写入本机。请检查 DSH_HOME 目录权限。': 'WeChat was authorized, but the account configuration could not be saved locally. Check the DSH_HOME directory permissions.',
|
|
307
307
|
'微信已授权,但无法初始化账号状态或工作区。请检查 DSH_HOME 和工作区目录。': 'WeChat was authorized, but the account state or workspace could not be initialized. Check DSH_HOME and the workspace directory.',
|
|
308
|
-
'微信已授权,但插件无法连接本机 Harness
|
|
308
|
+
'微信已授权,但插件无法连接本机 Harness。请检查 dsh web 地址和端口。': 'WeChat was authorized, but the plugin could not connect to the local Harness. Check the dsh web address and port.',
|
|
309
|
+
'微信已授权,但 Harness 健康检查超时。请确认 dsh web 未阻塞。': 'WeChat was authorized, but the Harness health check timed out. Confirm that dsh web is not blocked.',
|
|
310
|
+
'微信已授权,但 Harness 拒绝了本机健康检查。请检查 Host 信任配置。': 'WeChat was authorized, but Harness denied the local health check. Check the Host trust configuration.',
|
|
311
|
+
'微信已授权,但找不到 Harness 健康检查接口。请确认 Harness 与插件版本兼容。': 'WeChat was authorized, but the Harness health endpoint was not found. Confirm that Harness and the plugin are compatible.',
|
|
312
|
+
'微信已授权,但 Harness 健康检查返回服务错误。请查看 dsh web 日志。': 'WeChat was authorized, but the Harness health check returned a service error. Check the dsh web logs.',
|
|
313
|
+
'微信已授权,但 Harness 返回了无法识别的响应。请确认 Harness 与插件版本兼容。': 'WeChat was authorized, but Harness returned an unrecognized response. Confirm that Harness and the plugin are compatible.',
|
|
314
|
+
'微信已授权,但 Harness 拒绝了健康检查请求。请查看 dsh web 日志。': 'WeChat was authorized, but Harness rejected the health-check request. Check the dsh web logs.',
|
|
315
|
+
'微信已授权,但 Harness 健康检查发生未知错误。请查看 dsh web 日志。': 'WeChat was authorized, but the Harness health check failed unexpectedly. Check the dsh web logs.',
|
|
309
316
|
'微信已授权,但消息连接初始化失败。请查看 dsh web 日志后重试。': 'WeChat was authorized, but the message connection could not be initialized. Check the dsh web logs and try again.',
|
|
310
317
|
'微信已授权,但激活过程中发生未知错误。请查看 dsh web 日志。': 'WeChat was authorized, but an unknown error occurred during activation. Check the dsh web logs.',
|
|
311
318
|
'微信已绑定,可以开始向已绑定的机器人发消息。': 'WeChat is connected and ready for messages.',
|
|
@@ -2,6 +2,7 @@ import { homedir } from 'node:os';
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { unlink } from 'node:fs/promises';
|
|
4
4
|
import * as Lark from '@larksuiteoapi/node-sdk';
|
|
5
|
+
import HttpsProxyAgent from 'https-proxy-agent';
|
|
5
6
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
6
7
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
7
8
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
@@ -22,6 +23,22 @@ import {
|
|
|
22
23
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
23
24
|
import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
|
|
24
25
|
|
|
26
|
+
function webSocketProxyUrl(env) {
|
|
27
|
+
for (const key of ['https_proxy', 'HTTPS_PROXY', 'http_proxy', 'HTTP_PROXY']) {
|
|
28
|
+
const value = env?.[key];
|
|
29
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createFeishuWebSocketAgent(
|
|
35
|
+
env,
|
|
36
|
+
createAgent = (url) => new HttpsProxyAgent(url),
|
|
37
|
+
) {
|
|
38
|
+
const proxyUrl = webSocketProxyUrl(env);
|
|
39
|
+
return proxyUrl ? createAgent(proxyUrl) : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
function harnessOrigin(webServer, configured) {
|
|
26
43
|
if (configured !== undefined) return new URL(configured);
|
|
27
44
|
const port = webServer?.port;
|
|
@@ -61,7 +78,11 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
61
78
|
const SessionStateStore = internals.StateStore ?? StateStore;
|
|
62
79
|
const Harness = internals.HarnessClient ?? HarnessClient;
|
|
63
80
|
const Runtime = internals.FeishuRuntime ?? FeishuRuntime;
|
|
64
|
-
const
|
|
81
|
+
const verify = internals.verifyFeishuApp ?? verifyFeishuApp;
|
|
82
|
+
const verifyApp = (options) => verify({
|
|
83
|
+
...options,
|
|
84
|
+
httpInstance: lark.defaultHttpInstance,
|
|
85
|
+
});
|
|
65
86
|
const createSupervisor = internals.createConnectionSupervisor ?? createConnectionSupervisor;
|
|
66
87
|
const logger = typeof ctx.logger === 'function'
|
|
67
88
|
? ctx.logger('dsh-feishu')
|
|
@@ -127,6 +148,8 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
127
148
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
128
149
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
129
150
|
});
|
|
151
|
+
const proxyEnv = internals.proxyEnv ?? process.env;
|
|
152
|
+
const wsAgent = createFeishuWebSocketAgent(proxyEnv, internals.createProxyAgent);
|
|
130
153
|
|
|
131
154
|
const coreController = new Controller({
|
|
132
155
|
registerApp: (options) => lark.registerApp(options),
|
|
@@ -153,6 +176,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
153
176
|
harness: workspaceScope.harness,
|
|
154
177
|
state: workspaceScope.state,
|
|
155
178
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
179
|
+
...(wsAgent ? { wsAgent } : {}),
|
|
156
180
|
logger: {
|
|
157
181
|
error: (...args) => logger.error?.(`[${botId ?? botConfig.id}]`, ...args),
|
|
158
182
|
warn: (...args) => logger.warn?.(`[${botId ?? botConfig.id}]`, ...args),
|
|
@@ -190,6 +214,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
190
214
|
await supervisor.close();
|
|
191
215
|
await controller.close();
|
|
192
216
|
harness.stopManagedProcess();
|
|
217
|
+
wsAgent?.destroy?.();
|
|
193
218
|
},
|
|
194
219
|
};
|
|
195
220
|
}
|
|
@@ -142,6 +142,7 @@ for (const [name, version] of Object.entries(directDependencies)) {
|
|
|
142
142
|
const bundledBuildDependencies = {
|
|
143
143
|
'@larksuiteoapi/node-sdk': '1.73.0',
|
|
144
144
|
'@whiskeysockets/baileys': '7.0.0-rc14',
|
|
145
|
+
'https-proxy-agent': '5.0.1',
|
|
145
146
|
};
|
|
146
147
|
for (const [name, version] of Object.entries(bundledBuildDependencies)) {
|
|
147
148
|
if (manifest.dependencies?.[name] !== undefined) {
|
|
@@ -157,8 +158,8 @@ if (lock.packages?.['node_modules/protobufjs']?.dev !== true) {
|
|
|
157
158
|
if (manifest.bin?.['dsh-im'] !== 'bin/dsh-im.mjs') {
|
|
158
159
|
throw new Error('package manifest must publish the dsh-im executable');
|
|
159
160
|
}
|
|
160
|
-
if (/(?:from\s*|import\s*\(|require\s*\()\s*["'](?:@larksuiteoapi\/node-sdk|@whiskeysockets\/baileys|protobufjs)(?:\/[^"']*)?["']/.test(host)) {
|
|
161
|
-
throw new Error('host bundle must not import a bundled SDK or protobufjs at runtime');
|
|
161
|
+
if (/(?:from\s*|import\s*\(|require\s*\()\s*["'](?:@larksuiteoapi\/node-sdk|@whiskeysockets\/baileys|https-proxy-agent|protobufjs)(?:\/[^"']*)?["']/.test(host)) {
|
|
162
|
+
throw new Error('host bundle must not import a bundled SDK, proxy agent, or protobufjs at runtime');
|
|
162
163
|
}
|
|
163
164
|
if ((executable.mode & 0o111) === 0) throw new Error('dsh-im CLI is not executable');
|
|
164
165
|
if (/private-bot-token|must-be-rolled-back|DEEPSEEK_API_KEY=/.test(client + host)) {
|
|
@@ -108,7 +108,7 @@ export class DiscordApi {
|
|
|
108
108
|
headers: {
|
|
109
109
|
authorization: `Bot ${this.#token}`,
|
|
110
110
|
'content-type': 'application/json',
|
|
111
|
-
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 1.0.
|
|
111
|
+
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 1.0.2)',
|
|
112
112
|
},
|
|
113
113
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
114
114
|
signal: requestSignal(signal, timeoutMs),
|
|
@@ -3,15 +3,12 @@ function endpointFor(domain, path) {
|
|
|
3
3
|
return new URL(path, origin);
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
body = await response.json();
|
|
10
|
-
} catch {
|
|
6
|
+
function jsonResponse(body, operation) {
|
|
7
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
11
8
|
throw new Error(`${operation} returned a non-JSON response`);
|
|
12
9
|
}
|
|
13
|
-
if (
|
|
14
|
-
throw new Error(`${operation} failed: ${body
|
|
10
|
+
if (body.code !== 0) {
|
|
11
|
+
throw new Error(`${operation} failed: ${body.msg || `code ${body.code}`}`);
|
|
15
12
|
}
|
|
16
13
|
return body;
|
|
17
14
|
}
|
|
@@ -21,26 +18,32 @@ export async function verifyFeishuApp({
|
|
|
21
18
|
appId,
|
|
22
19
|
appSecret,
|
|
23
20
|
domain = 'feishu',
|
|
24
|
-
|
|
21
|
+
httpInstance,
|
|
25
22
|
timeoutMs = 15000,
|
|
26
23
|
}) {
|
|
27
24
|
if (!appId || !appSecret) throw new Error('Feishu credentials are incomplete');
|
|
28
|
-
|
|
25
|
+
if (!httpInstance || typeof httpInstance.request !== 'function') {
|
|
26
|
+
throw new TypeError('Feishu verification requires an HTTP instance');
|
|
27
|
+
}
|
|
28
|
+
const tokenBody = jsonResponse(await httpInstance.request({
|
|
29
29
|
method: 'POST',
|
|
30
|
+
url: endpointFor(domain, '/open-apis/auth/v3/tenant_access_token/internal').href,
|
|
30
31
|
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
31
|
-
|
|
32
|
+
data: { app_id: appId, app_secret: appSecret },
|
|
32
33
|
signal: AbortSignal.timeout(timeoutMs),
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
timeout: timeoutMs,
|
|
35
|
+
}), 'Feishu authentication');
|
|
35
36
|
if (!tokenBody.tenant_access_token) {
|
|
36
37
|
throw new Error('Feishu authentication returned no tenant access token');
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
+
const botBody = jsonResponse(await httpInstance.request({
|
|
41
|
+
method: 'GET',
|
|
42
|
+
url: endpointFor(domain, '/open-apis/bot/v3/info/').href,
|
|
40
43
|
headers: { authorization: `Bearer ${tokenBody.tenant_access_token}` },
|
|
41
44
|
signal: AbortSignal.timeout(timeoutMs),
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
timeout: timeoutMs,
|
|
46
|
+
}), 'Feishu bot verification');
|
|
44
47
|
const bot = botBody.bot ?? {};
|
|
45
48
|
return Object.freeze({
|
|
46
49
|
appId,
|
|
@@ -96,6 +96,7 @@ export class FeishuRuntime {
|
|
|
96
96
|
#replyTimeoutMs;
|
|
97
97
|
#connectTimeoutMs;
|
|
98
98
|
#requestTimeoutMs;
|
|
99
|
+
#wsAgent;
|
|
99
100
|
#logger;
|
|
100
101
|
#repair;
|
|
101
102
|
#client = null;
|
|
@@ -122,6 +123,7 @@ export class FeishuRuntime {
|
|
|
122
123
|
replyTimeoutMs = 600000,
|
|
123
124
|
connectTimeoutMs = 15000,
|
|
124
125
|
requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
|
|
126
|
+
wsAgent,
|
|
125
127
|
logger = console,
|
|
126
128
|
}) {
|
|
127
129
|
if (!lark) throw new Error('FeishuRuntime requires the Feishu SDK');
|
|
@@ -152,6 +154,7 @@ export class FeishuRuntime {
|
|
|
152
154
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
153
155
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
154
156
|
this.#requestTimeoutMs = requestTimeoutMs;
|
|
157
|
+
this.#wsAgent = wsAgent;
|
|
155
158
|
this.#logger = logger;
|
|
156
159
|
this.#status = createBridgeStatus({ allowedSenderCount: normalizedOwners.length });
|
|
157
160
|
}
|
|
@@ -266,6 +269,7 @@ export class FeishuRuntime {
|
|
|
266
269
|
|
|
267
270
|
this.#wsClient = new this.#lark.WSClient({
|
|
268
271
|
...larkConfig,
|
|
272
|
+
...(this.#wsAgent ? { agent: this.#wsAgent } : {}),
|
|
269
273
|
loggerLevel: this.#lark.LoggerLevel.info,
|
|
270
274
|
handshakeTimeoutMs: 15000,
|
|
271
275
|
onReady: () => {
|
|
@@ -364,6 +364,26 @@ export class HarnessRpcError extends Error {
|
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
export class HarnessTransportError extends Error {
|
|
368
|
+
constructor(code, method, { cause, status } = {}) {
|
|
369
|
+
const statusDetail = Number.isInteger(status) ? `, HTTP ${status}` : '';
|
|
370
|
+
super(`Harness ${method} transport failed (${code}${statusDetail})`, { cause });
|
|
371
|
+
this.name = 'HarnessTransportError';
|
|
372
|
+
this.code = code;
|
|
373
|
+
this.method = method;
|
|
374
|
+
if (Number.isInteger(status)) this.status = status;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export class HarnessHealthError extends Error {
|
|
379
|
+
constructor(cause) {
|
|
380
|
+
super('Harness health RPC was rejected', { cause });
|
|
381
|
+
this.name = 'HarnessHealthError';
|
|
382
|
+
this.code = 'harness-rpc-rejected';
|
|
383
|
+
this.method = 'host.describe';
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
367
387
|
export class HarnessInteractionError extends Error {
|
|
368
388
|
constructor(code, message) {
|
|
369
389
|
super(message);
|
|
@@ -455,24 +475,60 @@ export class HarnessClient {
|
|
|
455
475
|
const signal = options.signal
|
|
456
476
|
? AbortSignal.any([options.signal, timeoutSignal])
|
|
457
477
|
: timeoutSignal;
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
478
|
+
let response;
|
|
479
|
+
try {
|
|
480
|
+
response = await this.#fetch(new URL(`/api/${method}`, this.#baseUrl), {
|
|
481
|
+
method: 'POST',
|
|
482
|
+
headers: { 'content-type': 'application/json' },
|
|
483
|
+
body: JSON.stringify({ type: 'client-request', rpcId, method, payload }),
|
|
484
|
+
signal,
|
|
485
|
+
});
|
|
486
|
+
} catch (error) {
|
|
487
|
+
// Preserve an explicit caller cancellation; it is control flow, not a
|
|
488
|
+
// Harness availability diagnosis.
|
|
489
|
+
if (options.signal?.aborted) throw error;
|
|
490
|
+
throw new HarnessTransportError(
|
|
491
|
+
timeoutSignal.aborted ? 'harness-timeout' : 'harness-connect-failed',
|
|
492
|
+
method,
|
|
493
|
+
{ cause: error },
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
if (!response.ok) {
|
|
497
|
+
const code = response.status === 401 || response.status === 403
|
|
498
|
+
? 'harness-access-denied'
|
|
499
|
+
: response.status === 404
|
|
500
|
+
? 'harness-api-not-found'
|
|
501
|
+
: 'harness-http-failed';
|
|
502
|
+
throw new HarnessTransportError(code, method, { status: response.status });
|
|
503
|
+
}
|
|
504
|
+
let body;
|
|
505
|
+
try {
|
|
506
|
+
body = await response.json();
|
|
507
|
+
} catch (error) {
|
|
508
|
+
throw new HarnessTransportError('harness-response-invalid', method, { cause: error });
|
|
509
|
+
}
|
|
466
510
|
if (body?.type !== 'server-response' || body?.rpcId !== rpcId) {
|
|
467
|
-
throw new
|
|
511
|
+
throw new HarnessTransportError('harness-response-invalid', method, {
|
|
512
|
+
cause: new Error(`Harness returned an invalid response for ${method}`),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (!body.result || typeof body.result !== 'object' || typeof body.result.ok !== 'boolean') {
|
|
516
|
+
throw new HarnessTransportError('harness-response-invalid', method, {
|
|
517
|
+
cause: new Error(`Harness returned an invalid result for ${method}`),
|
|
518
|
+
});
|
|
468
519
|
}
|
|
469
520
|
if (!body.result?.ok) throw new HarnessRpcError(method, body.result?.error);
|
|
470
521
|
return body.result.value;
|
|
471
522
|
}
|
|
472
523
|
|
|
473
524
|
async health(options = {}) {
|
|
474
|
-
|
|
475
|
-
|
|
525
|
+
try {
|
|
526
|
+
await this.rpc('host.describe', {}, 5_000, options);
|
|
527
|
+
return true;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (error instanceof HarnessRpcError) throw new HarnessHealthError(error);
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
476
532
|
}
|
|
477
533
|
|
|
478
534
|
async ensureRunning(options = {}) {
|
|
@@ -506,7 +562,8 @@ export class HarnessClient {
|
|
|
506
562
|
lastError = error;
|
|
507
563
|
}
|
|
508
564
|
}
|
|
509
|
-
|
|
565
|
+
if (lastError) throw lastError;
|
|
566
|
+
throw new HarnessTransportError('harness-timeout', 'host.describe');
|
|
510
567
|
}
|
|
511
568
|
|
|
512
569
|
async listWorkspaces(options = {}) {
|
|
@@ -25,7 +25,14 @@ const ACTIVATION_ERROR_MESSAGES = Object.freeze({
|
|
|
25
25
|
'credential-save-failed': '微信已授权,但登录凭据无法写入 DSH 凭据存储。请检查凭据存储是否可写。',
|
|
26
26
|
'account-config-save-failed': '微信已授权,但账号配置无法写入本机。请检查 DSH_HOME 目录权限。',
|
|
27
27
|
'runtime-prepare-failed': '微信已授权,但无法初始化账号状态或工作区。请检查 DSH_HOME 和工作区目录。',
|
|
28
|
-
'harness-
|
|
28
|
+
'harness-connect-failed': '微信已授权,但插件无法连接本机 Harness。请检查 dsh web 地址和端口。',
|
|
29
|
+
'harness-timeout': '微信已授权,但 Harness 健康检查超时。请确认 dsh web 未阻塞。',
|
|
30
|
+
'harness-access-denied': '微信已授权,但 Harness 拒绝了本机健康检查。请检查 Host 信任配置。',
|
|
31
|
+
'harness-api-not-found': '微信已授权,但找不到 Harness 健康检查接口。请确认 Harness 与插件版本兼容。',
|
|
32
|
+
'harness-http-failed': '微信已授权,但 Harness 健康检查返回服务错误。请查看 dsh web 日志。',
|
|
33
|
+
'harness-response-invalid': '微信已授权,但 Harness 返回了无法识别的响应。请确认 Harness 与插件版本兼容。',
|
|
34
|
+
'harness-rpc-rejected': '微信已授权,但 Harness 拒绝了健康检查请求。请查看 dsh web 日志。',
|
|
35
|
+
'harness-check-unknown-failed': '微信已授权,但 Harness 健康检查发生未知错误。请查看 dsh web 日志。',
|
|
29
36
|
'connection-start-failed': '微信已授权,但消息连接初始化失败。请查看 dsh web 日志后重试。',
|
|
30
37
|
});
|
|
31
38
|
|
|
@@ -6,6 +6,15 @@ import {
|
|
|
6
6
|
} from '../shared/connection-test.mjs';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_START_RETRY_DELAYS_MS = Object.freeze([250, 1_000, 3_000]);
|
|
9
|
+
const HARNESS_HEALTH_ERROR_CODES = new Set([
|
|
10
|
+
'harness-connect-failed',
|
|
11
|
+
'harness-timeout',
|
|
12
|
+
'harness-access-denied',
|
|
13
|
+
'harness-api-not-found',
|
|
14
|
+
'harness-http-failed',
|
|
15
|
+
'harness-response-invalid',
|
|
16
|
+
'harness-rpc-rejected',
|
|
17
|
+
]);
|
|
9
18
|
|
|
10
19
|
function startRetryDelays(value) {
|
|
11
20
|
if (value === undefined) return [...DEFAULT_START_RETRY_DELAYS_MS];
|
|
@@ -32,6 +41,13 @@ function runtimeStartError(code, cause) {
|
|
|
32
41
|
return error;
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
function harnessHealthError(cause) {
|
|
45
|
+
const code = HARNESS_HEALTH_ERROR_CODES.has(cause?.code)
|
|
46
|
+
? cause.code
|
|
47
|
+
: 'harness-check-unknown-failed';
|
|
48
|
+
return runtimeStartError(code, cause);
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
function delay(ms, signal) {
|
|
36
52
|
return new Promise((resolve, reject) => {
|
|
37
53
|
if (signal?.aborted) {
|
|
@@ -127,7 +143,7 @@ export class WeixinRuntime {
|
|
|
127
143
|
try {
|
|
128
144
|
await this.#harness.ensureRunning();
|
|
129
145
|
} catch (error) {
|
|
130
|
-
throw
|
|
146
|
+
throw harnessHealthError(error);
|
|
131
147
|
}
|
|
132
148
|
this.#status.harnessReachable = true;
|
|
133
149
|
await this.#notifyStart();
|