@xmanrui/dsh-im 4.36.0 → 4.36.1
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 +27 -6
- package/lib/index.js +247 -247
- package/package.json +1 -1
- package/plugin-src/client/connection-error.js +2 -1
- package/src/channels/shared/connection-error.mjs +1 -0
- package/src/channels/shared/diagnostic-details.mjs +7 -1
- package/src/channels/shared/harness-client.mjs +15 -8
- package/src/channels/shared/i18n-en/diagnostics.mjs +3 -0
- package/src/channels/shared/message-failure.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "4.36.
|
|
3
|
+
"version": "4.36.1",
|
|
4
4
|
"icon": "./assets/logo-plugin-message-link-gradient.webp",
|
|
5
5
|
"description": "把微信、飞书、企微、钉钉、QQ、Telegram、WhatsApp、Slack、Discord、Matrix接入DSH。 Connect Telegram, WhatsApp, Slack, Discord, Matrix, WeChat, Feishu, WeCom, DingTalk, and QQ to DSH.",
|
|
6
6
|
"keywords": [
|
|
@@ -30,7 +30,7 @@ export function formatConnectionDiagnostic(value) {
|
|
|
30
30
|
return [
|
|
31
31
|
localizeText(error.message), details.hint ? localizeText(details.hint) : null,
|
|
32
32
|
localizeText('错误码') + ': ' + error.code,
|
|
33
|
-
...['channel', 'operation', 'stage', 'reason', 'reasons', 'host', 'durationMs', 'timeoutMs', 'nodeVersion', 'dshVersion', 'platform', 'dependencies', 'proxyConfigured', 'truncated', 'httpStatus', 'providerCode', 'resource', 'file', 'field', 'issue', 'referenceId', 'occurredAt', 'rollback', 'pluginVersion']
|
|
33
|
+
...['channel', 'operation', 'stage', 'method', 'transport', 'reason', 'reasons', 'host', 'durationMs', 'timeoutMs', 'nodeVersion', 'dshVersion', 'platform', 'dependencies', 'proxyConfigured', 'truncated', 'httpStatus', 'providerCode', 'resource', 'file', 'field', 'issue', 'referenceId', 'occurredAt', 'rollback', 'pluginVersion']
|
|
34
34
|
.filter(field => details[field] !== undefined)
|
|
35
35
|
.map(field => `${field}: ${typeof details[field] === 'object' ? JSON.stringify(details[field]) : details[field]}`),
|
|
36
36
|
].filter(Boolean).join('\n');
|
|
@@ -51,6 +51,7 @@ export function ConnectionError({ error: value, warning = false, showMessage = t
|
|
|
51
51
|
};
|
|
52
52
|
const fields = [
|
|
53
53
|
['错误码', error.code], ['失败阶段', localizeText(STAGE_LABELS[details.stage] ?? details.stage ?? '')],
|
|
54
|
+
['RPC 方法', details.method], ['调用方式', details.transport === 'host-api' ? localizeText('宿主 API(进程内)') : details.transport === 'http' ? 'HTTP' : undefined],
|
|
54
55
|
['底层原因', details.reason === 'unknown' ? localizeText('暂未识别') : details.reason], ['多个原因', details.reasons?.join(', ')], ['服务域名', details.host], ['请求耗时(毫秒)', details.durationMs], ['超时阈值(毫秒)', details.timeoutMs], ['Node 版本', details.nodeVersion], ['DSH 版本', details.dshVersion], ['依赖版本', details.dependencies ? Object.entries(details.dependencies).map(([name, version]) => `${name}: ${version}`).join(', ') : undefined], ['代理已配置', details.proxyConfigured], ['原因链已截断', details.truncated], ['运行平台', details.platform], ['HTTP 状态', details.httpStatus], ['服务返回码', details.providerCode],
|
|
55
56
|
['配置文件', details.file], ['配置字段', details.field],
|
|
56
57
|
['校验原因', details.issue ? localizeText(CONFIG_ISSUE_LABELS[details.issue]) : undefined],
|
|
@@ -62,6 +62,7 @@ export function extractConnectionEvidence(error) {
|
|
|
62
62
|
...(reasons.length > 1 ? { reasons } : {}),
|
|
63
63
|
httpStatus: errors.find(e => e.httpStatus !== undefined)?.httpStatus,
|
|
64
64
|
providerCode: errors.find(e => e.providerCode !== undefined)?.providerCode,
|
|
65
|
+
method: first('method'), transport: first('transport'),
|
|
65
66
|
durationMs: first('durationMs'), timeoutMs: first('timeoutMs'), host: first('host'), truncated,
|
|
66
67
|
}),
|
|
67
68
|
};
|
|
@@ -20,6 +20,12 @@ const REASONS = new Set([
|
|
|
20
20
|
'unknown', 'multiple-causes', 'timeout', 'http-error', 'provider-rejected', 'invalid-response', 'ERR_PROXY_CONNECTION_FAILED', 'ERR_TLS_HANDSHAKE_TIMEOUT', 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', 'ERR_NETWORK_ACCESS_DENIED', 'AUTHENTICATIONFAILED', 'EAUTH', 'invalid-json', 'invalid-config', 'read-only',
|
|
21
21
|
]);
|
|
22
22
|
const RESOURCES = new Set(['credential-store', 'account-config', 'account-state', 'workspace-config', 'workspace-directory']);
|
|
23
|
+
const RPC_METHODS = new Set([
|
|
24
|
+
'host.describe', 'workspace.list', 'workspace.create', 'session.list', 'session.create',
|
|
25
|
+
'session.history', 'session.prompt', 'session.rename', 'session.cancel', 'session.models',
|
|
26
|
+
'session.permissions', 'session.selectModel', 'llm.models',
|
|
27
|
+
]);
|
|
28
|
+
const TRANSPORTS = new Set(['host-api', 'http']);
|
|
23
29
|
const CONFIG_FILES = new Set(['config.json', 'workspaces.json']);
|
|
24
30
|
export const CONFIG_ISSUE_LABELS = Object.freeze({
|
|
25
31
|
'expected-object': '应为 JSON 对象。',
|
|
@@ -44,7 +50,7 @@ const CONFIG_FIELD = /^(?:\$|version|accounts(?:\[\d{1,10}\](?:\.(?:accountId|ow
|
|
|
44
50
|
export function normalizeDiagnosticDetails(value) {
|
|
45
51
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
46
52
|
const result = {};
|
|
47
|
-
for (const [field, allowed] of [['operation', OPERATIONS], ['stage', STAGES], ['reason', REASONS], ['resource', RESOURCES]]) {
|
|
53
|
+
for (const [field, allowed] of [['operation', OPERATIONS], ['stage', STAGES], ['reason', REASONS], ['resource', RESOURCES], ['method', RPC_METHODS], ['transport', TRANSPORTS]]) {
|
|
48
54
|
if (allowed.has(value[field])) result[field] = value[field];
|
|
49
55
|
}
|
|
50
56
|
if (DIAGNOSTIC_CHANNELS.includes(value.channel)) result.channel = value.channel;
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { isAbsolute } from 'node:path';
|
|
5
|
+
import { performance } from 'node:perf_hooks';
|
|
5
6
|
|
|
6
7
|
import { adoptRegisteredWorkspaceSession } from './harness-session-binding.mjs';
|
|
7
8
|
import { sameWorkspacePath } from './default-workspace.mjs';
|
|
@@ -1013,6 +1014,12 @@ export class HarnessClient {
|
|
|
1013
1014
|
}
|
|
1014
1015
|
|
|
1015
1016
|
async rpc(method, payload = {}, timeoutMs = 30_000, options = {}) {
|
|
1017
|
+
const startedAt = performance.now();
|
|
1018
|
+
const withDiagnostics = error => Object.assign(error, {
|
|
1019
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1020
|
+
timeoutMs,
|
|
1021
|
+
transport: this.#apiProxy ? 'host-api' : 'http',
|
|
1022
|
+
});
|
|
1016
1023
|
const rpcId = options.rpcId ?? `${this.#rpcIdPrefix}-${randomUUID()}`;
|
|
1017
1024
|
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
1018
1025
|
const signal = options.signal
|
|
@@ -1051,24 +1058,24 @@ export class HarnessClient {
|
|
|
1051
1058
|
// Preserve an explicit caller cancellation; it is control flow, not a
|
|
1052
1059
|
// Harness availability diagnosis.
|
|
1053
1060
|
if (options.signal?.aborted) throw error;
|
|
1054
|
-
if (error instanceof HarnessTransportError) throw error;
|
|
1055
|
-
throw new HarnessTransportError(
|
|
1061
|
+
if (error instanceof HarnessTransportError) throw withDiagnostics(error);
|
|
1062
|
+
throw withDiagnostics(new HarnessTransportError(
|
|
1056
1063
|
timeoutSignal.aborted ? 'harness-timeout' : 'harness-connect-failed',
|
|
1057
1064
|
method,
|
|
1058
1065
|
{ cause: error },
|
|
1059
|
-
);
|
|
1066
|
+
));
|
|
1060
1067
|
}
|
|
1061
1068
|
if (body?.type !== 'server-response' || body?.rpcId !== rpcId) {
|
|
1062
|
-
throw new HarnessTransportError('harness-response-invalid', method, {
|
|
1069
|
+
throw withDiagnostics(new HarnessTransportError('harness-response-invalid', method, {
|
|
1063
1070
|
cause: new Error(`Harness returned an invalid response for ${method}`),
|
|
1064
|
-
});
|
|
1071
|
+
}));
|
|
1065
1072
|
}
|
|
1066
1073
|
if (!body.result || typeof body.result !== 'object' || typeof body.result.ok !== 'boolean') {
|
|
1067
|
-
throw new HarnessTransportError('harness-response-invalid', method, {
|
|
1074
|
+
throw withDiagnostics(new HarnessTransportError('harness-response-invalid', method, {
|
|
1068
1075
|
cause: new Error(`Harness returned an invalid result for ${method}`),
|
|
1069
|
-
});
|
|
1076
|
+
}));
|
|
1070
1077
|
}
|
|
1071
|
-
if (!body.result?.ok) throw new HarnessRpcError(method, body.result?.error);
|
|
1078
|
+
if (!body.result?.ok) throw withDiagnostics(new HarnessRpcError(method, body.result?.error));
|
|
1072
1079
|
return body.result.value;
|
|
1073
1080
|
}
|
|
1074
1081
|
|
|
@@ -47,6 +47,9 @@ export default Object.freeze({
|
|
|
47
47
|
"暂未识别具体原因,请复制诊断信息,并查看相同参考号的 DSH 日志。": "The underlying cause is unknown. Copy the diagnostics and find the matching reference in the DSH logs.",
|
|
48
48
|
"多个原因": "Multiple causes",
|
|
49
49
|
"服务域名": "Service host",
|
|
50
|
+
"RPC 方法": "RPC method",
|
|
51
|
+
"调用方式": "Transport",
|
|
52
|
+
"宿主 API(进程内)": "Host API (in-process)",
|
|
50
53
|
"请求耗时(毫秒)": "Request duration (ms)",
|
|
51
54
|
"超时阈值(毫秒)": "Timeout (ms)",
|
|
52
55
|
"Node 版本": "Node version",
|
|
@@ -218,7 +218,7 @@ export function classifyMessageFailure(error, {
|
|
|
218
218
|
: businessCode;
|
|
219
219
|
if (code === 'REQUEST_TIMEOUT' || code === 'NETWORK_ERROR') details.hint = connectionHint(details);
|
|
220
220
|
return Object.freeze({
|
|
221
|
-
...(code === 'INTERNAL_UNKNOWN' || details.reason !== 'unknown' || details.httpStatus || details.providerCode ? { details } : {}),
|
|
221
|
+
...(code === 'INTERNAL_UNKNOWN' || details.reason !== 'unknown' || details.httpStatus || details.providerCode || details.method ? { details } : {}),
|
|
222
222
|
code,
|
|
223
223
|
reason: safeReason ?? safeFailureReason(error?.code) ?? code,
|
|
224
224
|
message: typeof userMessage === 'string' && userMessage.trim()
|