dsh-zen-remote 2.0.1 → 2.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"relay-server.d.ts","sourceRoot":"","sources":["../../src/relay-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-server.d.ts","sourceRoot":"","sources":["../../src/relay-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-zen-remote",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "一个插件、两种角色:主服务端把 DeepSeek Harness 变成手机能安全访问的 PWA(移动端界面、配对码网关、Web Push),并把选定会话共享给已配对的桌面端;子客户端在另一台 DSH 桌面端的侧边栏里远程使用这些会话。One DSH plugin, two roles: the host turns DeepSeek Harness into a phone-reachable PWA (mobile UI, pairing-code gateway behind your own reverse proxy, Web Push) and shares chosen sessions to paired desktops; the client uses those sessions remotely from another DSH desktop app's sidebar.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -82,18 +82,18 @@
82
82
  },
83
83
  "peerDependencies": {
84
84
  "@deepseek-ai/cordis": "*",
85
- "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2 || ^0.2.0-rc.1",
86
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.2 || ^0.2.0-rc.1",
87
- "@deepseek-ai/dsh-client-ui-layout": "^0.1.7-rc.2 || ^0.2.0-rc.1",
88
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2 || ^0.2.0-rc.1",
89
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.7-rc.2 || ^0.2.0-rc.1",
90
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2 || ^0.2.0-rc.1",
91
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1",
92
- "@deepseek-ai/dsh-session": "^0.1.7-rc.2 || ^0.2.0-rc.1",
93
- "@deepseek-ai/dsh-session-log-export": "^0.1.7-rc.2 || ^0.2.0-rc.1",
94
- "@deepseek-ai/dsh-session-query": "^0.1.7-rc.2 || ^0.2.0-rc.1",
95
- "@deepseek-ai/dsh-tools": "^0.1.7-rc.2 || ^0.2.0-rc.1",
96
- "@deepseek-ai/dsh-workspace": "^0.1.7-rc.2 || ^0.2.0-rc.1",
85
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
86
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
87
+ "@deepseek-ai/dsh-client-ui-layout": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
88
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
89
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
90
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
91
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
92
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
93
+ "@deepseek-ai/dsh-session-log-export": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
94
+ "@deepseek-ai/dsh-session-query": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
95
+ "@deepseek-ai/dsh-tools": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
96
+ "@deepseek-ai/dsh-workspace": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
97
97
  "@deepseek-ai/schemastery": "*",
98
98
  "react": "^18.2.0"
99
99
  },
@@ -115,43 +115,42 @@
115
115
  }
116
116
  },
117
117
  "devDependencies": {
118
- "@deepseek-ai/cordis": "4.0.4",
118
+ "@deepseek-ai/cordis": "4.0.5-alpha.1",
119
119
  "@deepseek-ai/cosmokit": "1.8.5",
120
- "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
121
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
122
- "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.2",
123
- "@deepseek-ai/dsh-api-workspace-controller": "0.1.7-rc.2",
124
- "@deepseek-ai/dsh-brand": "0.1.7-rc.2",
125
- "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2",
126
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
127
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
128
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
129
- "@deepseek-ai/dsh-client-ui-layout": "0.1.7-rc.2",
130
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
131
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
132
- "@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.2",
133
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-rc.2",
134
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
135
- "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
136
- "@deepseek-ai/dsh-invariants": "0.1.7-rc.2",
137
- "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
138
- "@deepseek-ai/dsh-ptc-runtime": "0.1.7-rc.2",
139
- "@deepseek-ai/dsh-sandbox": "0.1.7-rc.2",
140
- "@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.2",
141
- "@deepseek-ai/dsh-scope": "0.1.7-rc.2",
142
- "@deepseek-ai/dsh-session": "0.1.7-rc.2",
143
- "@deepseek-ai/dsh-session-log-export": "0.1.7-rc.2",
144
- "@deepseek-ai/dsh-session-projection": "0.1.7-rc.2",
145
- "@deepseek-ai/dsh-session-query": "0.1.7-rc.2",
146
- "@deepseek-ai/dsh-session-stats": "0.1.7-rc.2",
147
- "@deepseek-ai/dsh-subagent": "0.1.7-rc.2",
148
- "@deepseek-ai/dsh-timeout": "0.1.7-rc.2",
149
- "@deepseek-ai/dsh-token-meter": "0.1.7-rc.2",
150
- "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
151
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
152
- "@deepseek-ai/dsh-util-crypto": "0.1.7-rc.2",
153
- "@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
154
- "@deepseek-ai/dsh-workspace": "0.1.7-rc.2",
120
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
121
+ "@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.2",
122
+ "@deepseek-ai/dsh-api-session-controller": "0.2.1-alpha.2",
123
+ "@deepseek-ai/dsh-api-workspace-controller": "0.2.1-alpha.2",
124
+ "@deepseek-ai/dsh-brand": "0.2.1-alpha.2",
125
+ "@deepseek-ai/dsh-client-connection": "0.2.1-alpha.2",
126
+ "@deepseek-ai/dsh-client-locale": "0.2.1-alpha.2",
127
+ "@deepseek-ai/dsh-client-store": "0.2.1-alpha.2",
128
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-alpha.2",
129
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.2",
130
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.2",
131
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
132
+ "@deepseek-ai/dsh-client-ui-session": "0.2.1-alpha.2",
133
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.2",
134
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.2",
135
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.2",
136
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
137
+ "@deepseek-ai/dsh-ptc-runtime": "0.2.1-alpha.2",
138
+ "@deepseek-ai/dsh-sandbox": "0.2.1-alpha.2",
139
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.2",
140
+ "@deepseek-ai/dsh-scope": "0.2.1-alpha.2",
141
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.2",
142
+ "@deepseek-ai/dsh-session-log-export": "0.2.1-alpha.2",
143
+ "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.2",
144
+ "@deepseek-ai/dsh-session-query": "0.2.1-alpha.2",
145
+ "@deepseek-ai/dsh-session-stats": "0.2.1-alpha.2",
146
+ "@deepseek-ai/dsh-subagent": "0.2.1-alpha.2",
147
+ "@deepseek-ai/dsh-timeout": "0.2.1-alpha.2",
148
+ "@deepseek-ai/dsh-token-meter": "0.2.1-alpha.2",
149
+ "@deepseek-ai/dsh-tools": "0.2.1-alpha.2",
150
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.2",
151
+ "@deepseek-ai/dsh-util-crypto": "0.2.1-alpha.2",
152
+ "@deepseek-ai/dsh-util-values": "0.2.1-alpha.2",
153
+ "@deepseek-ai/dsh-workspace": "0.2.1-alpha.2",
155
154
  "@deepseek-ai/schemastery": "3.18.4",
156
155
  "@types/node": "^26.2.0",
157
156
  "@types/react": "~18.3.1",
@@ -440,14 +440,20 @@ export const COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility -----
440
440
  cache) is long. It is the single entry the official StatsLine puts in
441
441
  conversation.composer.dock, so the structural anchor below reaches it
442
442
  without any DOM marking (S3 deleted the text-matching effect that used
443
- to set [data-slot="conversation.composer.dock"] > [class$="_root"]). Layout: ONE fixed-height (28px) flex
443
+ to set the dock-entry marker). Layout: ONE fixed-height (28px) flex
444
444
  strip that scrolls horizontally — the full metrics stream stays
445
445
  reachable by swiping, the row never grows vertically, no ellipsis or
446
446
  fade, 12px gaps between metric groups, a 2px scrollbar as the swipe
447
447
  affordance. The phone breakpoint hides the strip outright instead
448
448
  (styles/composer.css.ts — its data moves into the session info card). */
449
449
 
450
- [data-slot="conversation.composer.dock"] > [class$="_root"] {
450
+ /* Marker set is deliberately dual: 0.2.0-rc.2 renders the strip root with
451
+ data-composer-stats (plural, dsh-client-ui-chat rc.2 lib), 0.2.1-alpha.2
452
+ dropped the strip wrapper and registers activity/usage as separate dock
453
+ entries whose pill spans carry data-composer-stat (singular). Keeping
454
+ both lets this plugin style the strip on a host that has NOT upgraded to
455
+ alpha.2 — verified against both npm tarballs 2026-10-09. */
456
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats]) {
451
457
  display: flex !important;
452
458
  flex-flow: row nowrap !important;
453
459
  align-items: center !important;
@@ -469,17 +475,17 @@ export const COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility -----
469
475
  line-height: 20px !important;
470
476
  font-size: 12px !important;
471
477
  }
472
- [data-slot="conversation.composer.dock"] > [class$="_root"]::-webkit-scrollbar {
478
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats])::-webkit-scrollbar {
473
479
  height: 2px !important;
474
480
  }
475
- [data-slot="conversation.composer.dock"] > [class$="_root"]::-webkit-scrollbar-thumb {
481
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats])::-webkit-scrollbar-thumb {
476
482
  background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;
477
483
  border-radius: 2px !important;
478
484
  }
479
- [data-slot="conversation.composer.dock"] > [class$="_root"]::-webkit-scrollbar-track {
485
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats])::-webkit-scrollbar-track {
480
486
  background: transparent !important;
481
487
  }
482
- [data-slot="conversation.composer.dock"] > [class$="_root"] > * {
488
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats]) > * {
483
489
  display: flex !important;
484
490
  flex: 0 0 auto !important;
485
491
  flex-flow: row nowrap !important;
@@ -491,10 +497,10 @@ export const COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility -----
491
497
  margin-right: 12px !important;
492
498
  padding: 0 !important;
493
499
  }
494
- [data-slot="conversation.composer.dock"] > [class$="_root"] > *:last-child {
500
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats]) > *:last-child {
495
501
  margin-right: 0 !important;
496
502
  }
497
- [data-slot="conversation.composer.dock"] > [class$="_root"] * {
503
+ [data-slot="conversation.composer.dock"] > :is([data-composer-stat], [data-composer-stats]) * {
498
504
  white-space: nowrap !important;
499
505
  }
500
506
 
@@ -803,7 +803,8 @@ export const COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- *
803
803
  /* --- 8. the official stats strip leaves the composer ---
804
804
  Its data moves into the session info card (S4). The row is the composer
805
805
  dock's own \`_root\` entry; the slot itself stays live for later entries. */
806
- [data-slot="conversation.composer.dock"] > [class$="_root"] {
806
+ [data-slot="conversation.composer.dock"] > [class$="_root"],
807
+ [data-slot="conversation.composer.dock"] > [data-composer-stat] {
807
808
  display: none !important;
808
809
  }
809
810
 
package/src/intercept.ts CHANGED
@@ -1100,9 +1100,16 @@ export function rewriteResult(endpoint: string, value: unknown, serverId: string
1100
1100
  // mergeSessionList rewrite instead, merge-streams.ts.)
1101
1101
  if (!isPlainObject(value) || !isPlainObject(value.values)) return value
1102
1102
  const values: Record<string, unknown> = { ...value.values }
1103
- if (!Object.hasOwn(values, 'modelSelection')) return value
1103
+ // alpha.2 clients validate list/control projections with kind:'cached'|'sequenced'.
1104
+ // Older hosts (rc.2) omit it on the session/projections control-key result — synthesize.
1105
+ const withKind = typeof value.kind === 'string'
1106
+ ? value
1107
+ : (typeof value.asOfSeq === 'number' ? { ...value, kind: 'sequenced' as const } : value)
1108
+ if (!Object.hasOwn(values, 'modelSelection')) {
1109
+ return withKind === value ? value : { ...withKind, values }
1110
+ }
1104
1111
  values.modelSelection = virtualizeModelSelectionValue(values.modelSelection, serverId)
1105
- return { ...value, values }
1112
+ return { ...withKind, values }
1106
1113
  }
1107
1114
  if (endpoint === 'session/selectModel') {
1108
1115
  // T52: the result echoes the selection it accepted (`{selected:
@@ -2637,7 +2644,10 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
2637
2644
  return { ok: true, value: undefined }
2638
2645
  }
2639
2646
  recordFailure(endpoint, code)
2640
- return { ok: false, error: { code, message: messageOf(error), details: {} } }
2647
+ // The host's sanitized `details` rides along when present — its
2648
+ // `reason` codes (MODEL_DOES_NOT_SUPPORT_IMAGES, …) are what the UI
2649
+ // maps onto a user-facing line; without them it prints `undefined`.
2650
+ return { ok: false, error: { code, message: messageOf(error), details: error instanceof RelayError && isPlainObject(error.details) ? error.details : {} } }
2641
2651
  }
2642
2652
  })()
2643
2653
  }
@@ -2696,7 +2706,7 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
2696
2706
  // A call on a closed session reads like the banner, not like the
2697
2707
  // relay's bare code (T73).
2698
2708
  const message = code === 'not-shared' ? CLOSED_MESSAGE : messageOf(error)
2699
- return { ok: false, error: { code, message, details: {} } }
2709
+ return { ok: false, error: { code, message, details: error instanceof RelayError && isPlainObject(error.details) ? error.details : {} } }
2700
2710
  }
2701
2711
  }
2702
2712
 
@@ -197,16 +197,33 @@ export class RelayError extends Error {
197
197
  code: string
198
198
  status?: number
199
199
  reason?: string
200
+ /** The host error's sanitized `details` (invoke path): the UI maps
201
+ * `details.reason` codes like `MODEL_DOES_NOT_SUPPORT_IMAGES` onto a
202
+ * user-facing line — absent details stay undefined. */
203
+ details?: Record<string, unknown>
200
204
 
201
- constructor(code: string, message?: string, httpStatus?: number, reason?: string) {
205
+ constructor(code: string, message?: string, httpStatus?: number, reason?: string, details?: Record<string, unknown>) {
202
206
  super(message === undefined ? code : message)
203
207
  this.name = 'RelayError'
204
208
  this.code = code
205
209
  if (httpStatus !== undefined) this.status = httpStatus
206
210
  if (reason !== undefined) this.reason = reason
211
+ if (details !== undefined) this.details = details
207
212
  }
208
213
  }
209
214
 
215
+ /** The error body's structured `details`, when it is a record of scalars —
216
+ * the shape the server's own sanitizer emits. */
217
+ function detailsOf(error: Record<string, unknown>): Record<string, unknown> | undefined {
218
+ const raw = error.details
219
+ if (!isRecord(raw)) return undefined
220
+ const out: Record<string, unknown> = {}
221
+ for (const [key, value] of Object.entries(raw)) {
222
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) out[key] = value
223
+ }
224
+ return Object.keys(out).length > 0 ? out : undefined
225
+ }
226
+
210
227
  export interface CreateRelayClientOptions {
211
228
  /** The normalized server address (no trailing slash), read live per
212
229
  * request; `undefined` counts as unpaired. */
@@ -948,7 +965,7 @@ export function createRelayClient(options: CreateRelayClientOptions): RelayClien
948
965
  // 200 {ok:false} envelopes, 400/404/429 answers and anything else
949
966
  // unheard-of: the body's error code is the answer, the state is not
950
967
  // touched.
951
- if (code !== undefined) return new RelayError(code, message, status)
968
+ if (code !== undefined) return new RelayError(code, message, status, undefined, detailsOf(error))
952
969
  // The bad-gateway band carries no relay body of its own — it is what a
953
970
  // reverse proxy answers when the gateway behind it is down (nginx: an
954
971
  // HTML 502). That IS the offline case, and the e2e contract expects it.
@@ -611,6 +611,16 @@ function drainUpload(req: IncomingMessage, fromBytes: number, deadlineMs: number
611
611
  })
612
612
  }
613
613
 
614
+
615
+ /** The 200 error envelope for a failed gateway call, `details` included when
616
+ * the host error carried a sanitized one (see errorOf). */
617
+ function errorEnvelopeOf(error: unknown): { ok: false; error: Record<string, unknown> } {
618
+ const { code, message, details } = errorOf(error)
619
+ const inner: Record<string, unknown> = message === undefined ? { code } : { code, message }
620
+ if (details !== undefined) inner.details = details
621
+ return { ok: false, error: inner }
622
+ }
623
+
614
624
  /**
615
625
  * What may travel to the client about a failed gateway call. DSH's own errors
616
626
  * always carry a `namespace/name`-shaped string `code` — those pass through
@@ -618,7 +628,7 @@ function drainUpload(req: IncomingMessage, fromBytes: number, deadlineMs: number
618
628
  * error whose `code` (`ENOENT`, …) and message (`… '/Users/x/…'`) quote the
619
629
  * server's filesystem — reports `internal` and NO message at all (T22a-fix).
620
630
  */
621
- function errorOf(error: unknown): { code: string; message?: string } {
631
+ function errorOf(error: unknown): { code: string; message?: string; details?: Record<string, unknown> } {
622
632
  const code =
623
633
  error !== null && typeof error === 'object' && typeof (error as { code?: unknown }).code === 'string'
624
634
  ? (error as { code: string }).code
@@ -629,7 +639,23 @@ function errorOf(error: unknown): { code: string; message?: string } {
629
639
  // error would, so only the code travels.
630
640
  if (code.endsWith('/internal')) return { code }
631
641
  const text = error instanceof Error ? error.message : String(error)
632
- return { code, message: text.slice(0, MAX_MESSAGE_CHARS) }
642
+ // The structured `details` beside code/message (2026-10-10): the host
643
+ // rejects an image-bearing prompt with `session/attachment-invalid` whose
644
+ // `details.reason` (`MODEL_DOES_NOT_SUPPORT_IMAGES`, …) is what the client
645
+ // UI maps onto a user-facing line — dropped here, the UI prints
646
+ // `undefined`. Scalars and clipped strings only, bounded key count;
647
+ // nested objects/arrays stay out so server internals cannot ride this.
648
+ const raw = (error as { details?: unknown }).details
649
+ const details: Record<string, unknown> = {}
650
+ if (isPlainObject(raw)) {
651
+ for (const [key, value] of Object.entries(raw).slice(0, 16)) {
652
+ if (typeof value === 'string') details[key] = value.slice(0, MAX_MESSAGE_CHARS)
653
+ else if (typeof value === 'number' || typeof value === 'boolean' || value === null) details[key] = value
654
+ }
655
+ }
656
+ const out: { code: string; message?: string; details?: Record<string, unknown> } = { code, message: text.slice(0, MAX_MESSAGE_CHARS) }
657
+ if (Object.keys(details).length > 0) out.details = details
658
+ return out
633
659
  }
634
660
 
635
661
  /**
@@ -1117,8 +1143,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
1117
1143
  )) as unknown
1118
1144
  responseJson(res, 200, isPlainObject(envelope) ? envelope : { ok: false, error: { code: 'internal' } })
1119
1145
  } catch (error) {
1120
- const { code, message } = errorOf(error)
1121
- responseJson(res, 200, message === undefined ? { ok: false, error: { code } } : { ok: false, error: { code, message } })
1146
+ responseJson(res, 200, errorEnvelopeOf(error))
1122
1147
  } finally {
1123
1148
  res.off('close', onClientGone)
1124
1149
  }
@@ -1266,8 +1291,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
1266
1291
  // The gateway's own failures (unknown namespace, absent service) are
1267
1292
  // business answers, not transport errors: they ride a 200 envelope
1268
1293
  // like every other result, with the gateway's string code preserved.
1269
- const { code, message } = errorOf(error)
1270
- responseJson(res, 200, message === undefined ? { ok: false, error: { code } } : { ok: false, error: { code, message } })
1294
+ responseJson(res, 200, errorEnvelopeOf(error))
1271
1295
  } finally {
1272
1296
  res.off('close', onClientGone)
1273
1297
  // The invoke is over — answered, refused mid-flight, or its client
@@ -1927,8 +1951,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
1927
1951
  // DOCUMENTED normal end, swallowed whole.
1928
1952
  closeWith(null)
1929
1953
  } else {
1930
- const { code, message } = errorOf(error)
1931
- closeWith(message === undefined ? { type: 'error', error: { code } } : { type: 'error', error: { code, message } })
1954
+ closeWith({ type: 'error', error: errorEnvelopeOf(error).error })
1932
1955
  }
1933
1956
  } finally {
1934
1957
  openStreamKills.delete(killThisStream)