@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "4.36.0",
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()