dsh-zen-remote 2.3.11 → 2.4.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.
Files changed (249) hide show
  1. package/CHANGELOG.md +45 -78
  2. package/README.md +94 -84
  3. package/README.zh-CN.md +90 -81
  4. package/bin/zen-remote.mjs +5 -0
  5. package/docs/interface.md +6 -6
  6. package/docs/remote-access.en.md +6 -4
  7. package/docs/remote-access.md +6 -4
  8. package/dsh-push.mjs +96 -10
  9. package/lib/adapters/dsh-better-sidebar/index.js +7 -3
  10. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
  11. package/lib/admin-routes.js +10 -2
  12. package/lib/admin-routes.js.map +1 -1
  13. package/lib/cli/args.js +251 -0
  14. package/lib/cli/args.js.map +1 -0
  15. package/lib/cli/bootstrap.js +449 -0
  16. package/lib/cli/bootstrap.js.map +1 -0
  17. package/lib/cli/command-setup.js +484 -0
  18. package/lib/cli/command-setup.js.map +1 -0
  19. package/lib/cli/commands-client.js +359 -0
  20. package/lib/cli/commands-client.js.map +1 -0
  21. package/lib/cli/commands-host.js +1108 -0
  22. package/lib/cli/commands-host.js.map +1 -0
  23. package/lib/cli/commands-status.js +279 -0
  24. package/lib/cli/commands-status.js.map +1 -0
  25. package/lib/cli/config-writer.js +334 -0
  26. package/lib/cli/config-writer.js.map +1 -0
  27. package/lib/cli/context.js +10 -0
  28. package/lib/cli/context.js.map +1 -0
  29. package/lib/cli/i18n.js +620 -0
  30. package/lib/cli/i18n.js.map +1 -0
  31. package/lib/cli/ipc-client.js +160 -0
  32. package/lib/cli/ipc-client.js.map +1 -0
  33. package/lib/cli/ipc-server.js +464 -0
  34. package/lib/cli/ipc-server.js.map +1 -0
  35. package/lib/cli/main.js +245 -0
  36. package/lib/cli/main.js.map +1 -0
  37. package/lib/cli/qr-terminal.js +48 -0
  38. package/lib/cli/qr-terminal.js.map +1 -0
  39. package/lib/client-data/qr.js +286 -0
  40. package/lib/client-data/qr.js.map +1 -0
  41. package/lib/client-routes.js +308 -25
  42. package/lib/client-routes.js.map +1 -1
  43. package/lib/client.js +1013 -229
  44. package/lib/config.js +50 -12
  45. package/lib/config.js.map +1 -1
  46. package/lib/debug-report.js +28 -1
  47. package/lib/debug-report.js.map +1 -1
  48. package/lib/fetch-route-intercept.js +334 -26
  49. package/lib/fetch-route-intercept.js.map +1 -1
  50. package/lib/index.js +608 -375
  51. package/lib/index.js.map +1 -1
  52. package/lib/intercept.js +1833 -280
  53. package/lib/intercept.js.map +1 -1
  54. package/lib/lan-gate-server.cjs +36 -10
  55. package/lib/merge-streams.js +103 -11
  56. package/lib/merge-streams.js.map +1 -1
  57. package/lib/plugin-route-intercept.js +93 -63
  58. package/lib/plugin-route-intercept.js.map +1 -1
  59. package/lib/relay-access.js +8 -1
  60. package/lib/relay-access.js.map +1 -1
  61. package/lib/relay-client.js +133 -4
  62. package/lib/relay-client.js.map +1 -1
  63. package/lib/relay-pool.js +275 -0
  64. package/lib/relay-pool.js.map +1 -0
  65. package/lib/relay-server.js +269 -17
  66. package/lib/relay-server.js.map +1 -1
  67. package/lib/restart-fields.js +6 -4
  68. package/lib/restart-fields.js.map +1 -1
  69. package/lib/share-ops.js +68 -0
  70. package/lib/share-ops.js.map +1 -1
  71. package/lib/share-store.js +65 -27
  72. package/lib/share-store.js.map +1 -1
  73. package/lib/state-store.js +400 -0
  74. package/lib/state-store.js.map +1 -0
  75. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +3 -2
  76. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
  77. package/lib/types/admin-routes.d.ts.map +1 -1
  78. package/lib/types/cli/args.d.ts +78 -0
  79. package/lib/types/cli/args.d.ts.map +1 -0
  80. package/lib/types/cli/bootstrap.d.ts +170 -0
  81. package/lib/types/cli/bootstrap.d.ts.map +1 -0
  82. package/lib/types/cli/command-setup.d.ts +72 -0
  83. package/lib/types/cli/command-setup.d.ts.map +1 -0
  84. package/lib/types/cli/commands-client.d.ts +36 -0
  85. package/lib/types/cli/commands-client.d.ts.map +1 -0
  86. package/lib/types/cli/commands-host.d.ts +82 -0
  87. package/lib/types/cli/commands-host.d.ts.map +1 -0
  88. package/lib/types/cli/commands-status.d.ts +46 -0
  89. package/lib/types/cli/commands-status.d.ts.map +1 -0
  90. package/lib/types/cli/config-writer.d.ts +92 -0
  91. package/lib/types/cli/config-writer.d.ts.map +1 -0
  92. package/lib/types/cli/context.d.ts +39 -0
  93. package/lib/types/cli/context.d.ts.map +1 -0
  94. package/lib/types/cli/i18n.d.ts +32 -0
  95. package/lib/types/cli/i18n.d.ts.map +1 -0
  96. package/lib/types/cli/ipc-client.d.ts +83 -0
  97. package/lib/types/cli/ipc-client.d.ts.map +1 -0
  98. package/lib/types/cli/ipc-server.d.ts +113 -0
  99. package/lib/types/cli/ipc-server.d.ts.map +1 -0
  100. package/lib/types/cli/main.d.ts +57 -0
  101. package/lib/types/cli/main.d.ts.map +1 -0
  102. package/lib/types/cli/qr-terminal.d.ts +17 -0
  103. package/lib/types/cli/qr-terminal.d.ts.map +1 -0
  104. package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -1
  105. package/lib/types/client/current-session-report.d.ts +4 -0
  106. package/lib/types/client/current-session-report.d.ts.map +1 -1
  107. package/lib/types/client/effects/model-group-side.d.ts.map +1 -1
  108. package/lib/types/client/effects/remote-session.d.ts.map +1 -1
  109. package/lib/types/client/locales.d.ts +47 -3
  110. package/lib/types/client/locales.d.ts.map +1 -1
  111. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -1
  112. package/lib/types/client/settings/settings-css.d.ts +1 -1
  113. package/lib/types/client/settings/settings-css.d.ts.map +1 -1
  114. package/lib/types/client/styles/remote-session.css.d.ts +1 -1
  115. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -1
  116. package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -1
  117. package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -1
  118. package/lib/types/client/wizard/wizard-css.d.ts +1 -1
  119. package/lib/types/client/wizard/wizard-css.d.ts.map +1 -1
  120. package/lib/types/client-data/model-group-side.d.ts +23 -2
  121. package/lib/types/client-data/model-group-side.d.ts.map +1 -1
  122. package/lib/types/client-data/remote-status.d.ts +47 -1
  123. package/lib/types/client-data/remote-status.d.ts.map +1 -1
  124. package/lib/types/client-data/settings-form.d.ts +85 -1
  125. package/lib/types/client-data/settings-form.d.ts.map +1 -1
  126. package/lib/types/client-data/wizard.d.ts +56 -1
  127. package/lib/types/client-data/wizard.d.ts.map +1 -1
  128. package/lib/types/client-routes.d.ts +25 -1
  129. package/lib/types/client-routes.d.ts.map +1 -1
  130. package/lib/types/config.d.ts +40 -14
  131. package/lib/types/config.d.ts.map +1 -1
  132. package/lib/types/debug-report.d.ts.map +1 -1
  133. package/lib/types/fetch-route-intercept.d.ts +66 -8
  134. package/lib/types/fetch-route-intercept.d.ts.map +1 -1
  135. package/lib/types/index.d.ts +9 -2
  136. package/lib/types/index.d.ts.map +1 -1
  137. package/lib/types/intercept.d.ts +72 -10
  138. package/lib/types/intercept.d.ts.map +1 -1
  139. package/lib/types/merge-streams.d.ts +79 -1
  140. package/lib/types/merge-streams.d.ts.map +1 -1
  141. package/lib/types/plugin-route-intercept.d.ts +25 -9
  142. package/lib/types/plugin-route-intercept.d.ts.map +1 -1
  143. package/lib/types/relay-access.d.ts +3 -1
  144. package/lib/types/relay-access.d.ts.map +1 -1
  145. package/lib/types/relay-client.d.ts +23 -2
  146. package/lib/types/relay-client.d.ts.map +1 -1
  147. package/lib/types/relay-pool.d.ts +148 -0
  148. package/lib/types/relay-pool.d.ts.map +1 -0
  149. package/lib/types/relay-server.d.ts +30 -3
  150. package/lib/types/relay-server.d.ts.map +1 -1
  151. package/lib/types/restart-fields.d.ts +5 -5
  152. package/lib/types/restart-fields.d.ts.map +1 -1
  153. package/lib/types/share-ops.d.ts +47 -1
  154. package/lib/types/share-ops.d.ts.map +1 -1
  155. package/lib/types/share-store.d.ts.map +1 -1
  156. package/lib/types/state-store.d.ts +179 -0
  157. package/lib/types/state-store.d.ts.map +1 -0
  158. package/lib/types/upload-limits.d.ts +15 -0
  159. package/lib/types/upload-limits.d.ts.map +1 -1
  160. package/lib/types/virtual-id.d.ts +21 -0
  161. package/lib/types/virtual-id.d.ts.map +1 -1
  162. package/lib/types/wizard/probe.d.ts.map +1 -1
  163. package/lib/types/wizard/proxy-snippets.d.ts +3 -0
  164. package/lib/types/wizard/proxy-snippets.d.ts.map +1 -1
  165. package/lib/types/wizard/routes.d.ts +1 -0
  166. package/lib/types/wizard/routes.d.ts.map +1 -1
  167. package/lib/types/wizard/status.d.ts +9 -0
  168. package/lib/types/wizard/status.d.ts.map +1 -1
  169. package/lib/types/wizard/verify.d.ts +3 -0
  170. package/lib/types/wizard/verify.d.ts.map +1 -1
  171. package/lib/upload-limits.js +15 -0
  172. package/lib/upload-limits.js.map +1 -1
  173. package/lib/virtual-id.js +60 -0
  174. package/lib/virtual-id.js.map +1 -1
  175. package/lib/wizard/probe.js +46 -1
  176. package/lib/wizard/probe.js.map +1 -1
  177. package/lib/wizard/proxy-snippets.js +14 -1
  178. package/lib/wizard/proxy-snippets.js.map +1 -1
  179. package/lib/wizard/routes.js +23 -4
  180. package/lib/wizard/routes.js.map +1 -1
  181. package/lib/wizard/status.js +21 -0
  182. package/lib/wizard/status.js.map +1 -1
  183. package/lib/wizard/verify.js +31 -8
  184. package/lib/wizard/verify.js.map +1 -1
  185. package/package.json +8 -4
  186. package/pwa/icons/badge-96.png +0 -0
  187. package/pwa/icons/badge.svg +1 -0
  188. package/pwa/manifest.json +2 -1
  189. package/pwa/sw.js +3 -1
  190. package/scripts/check-debug-report.mjs +41 -1
  191. package/scripts/check-settings-form.mjs +14 -3
  192. package/scripts/check-settings-view.mjs +247 -0
  193. package/scripts/check-wizard-ui.mjs +37 -0
  194. package/src/adapters/dsh-better-sidebar/index.ts +7 -3
  195. package/src/admin-routes.ts +10 -2
  196. package/src/cli/args.ts +289 -0
  197. package/src/cli/bootstrap.ts +569 -0
  198. package/src/cli/command-setup.ts +540 -0
  199. package/src/cli/commands-client.ts +369 -0
  200. package/src/cli/commands-host.ts +1127 -0
  201. package/src/cli/commands-status.ts +296 -0
  202. package/src/cli/config-writer.ts +354 -0
  203. package/src/cli/context.ts +38 -0
  204. package/src/cli/i18n.ts +645 -0
  205. package/src/cli/ipc-client.ts +216 -0
  206. package/src/cli/ipc-server.ts +531 -0
  207. package/src/cli/main.ts +285 -0
  208. package/src/cli/qr-terminal.ts +49 -0
  209. package/src/client/RemoteStatusIcon.tsx +4 -1
  210. package/src/client/current-session-report.ts +28 -4
  211. package/src/client/effects/model-group-side.ts +19 -5
  212. package/src/client/effects/remote-session.ts +26 -6
  213. package/src/client/locales.ts +96 -6
  214. package/src/client/settings/SettingsSection.tsx +287 -92
  215. package/src/client/settings/settings-css.ts +18 -0
  216. package/src/client/styles/remote-session.css.ts +15 -1
  217. package/src/client/wizard/GatewayWizard.tsx +470 -109
  218. package/src/client/wizard/cards/ProxyCard.tsx +2 -1
  219. package/src/client/wizard/wizard-css.ts +23 -0
  220. package/src/client-data/model-group-side.ts +37 -10
  221. package/src/client-data/remote-status.ts +112 -17
  222. package/src/client-data/settings-form.ts +164 -8
  223. package/src/client-data/wizard.ts +112 -2
  224. package/src/client-routes.ts +334 -26
  225. package/src/config.ts +62 -16
  226. package/src/debug-report.ts +27 -1
  227. package/src/fetch-route-intercept.ts +381 -32
  228. package/src/index.ts +608 -380
  229. package/src/intercept.ts +1866 -286
  230. package/src/merge-streams.ts +155 -10
  231. package/src/plugin-route-intercept.ts +120 -71
  232. package/src/relay-access.ts +14 -3
  233. package/src/relay-client.ts +163 -5
  234. package/src/relay-pool.ts +387 -0
  235. package/src/relay-server.ts +312 -16
  236. package/src/restart-fields.ts +6 -4
  237. package/src/share-ops.ts +82 -1
  238. package/src/share-store.ts +59 -26
  239. package/src/state-store.ts +428 -0
  240. package/src/upload-limits.ts +16 -0
  241. package/src/virtual-id.ts +57 -0
  242. package/src/wizard/probe.ts +54 -2
  243. package/src/wizard/proxy-snippets.ts +17 -2
  244. package/src/wizard/routes.ts +24 -5
  245. package/src/wizard/status.ts +28 -0
  246. package/src/wizard/verify.ts +33 -11
  247. package/AGENTS.md +0 -185
  248. package/docs/adr/0001-plugin-bridge-forwards-plugin-routes.md +0 -15
  249. package/docs/adr/0002-relay-emulates-websocket.md +0 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.js","sourceRoot":"","sources":["../../src/client-data/qr.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,7 @@ import { assembleDebugReport } from './debug-report.js';
30
30
  import { responseJson, sameOriginPost } from './http.js';
31
31
  import { RELAY_HTTP_ROUTES } from './relay-access.js';
32
32
  import { relayCredentialsDigest, RelayError } from './relay-client.js';
33
+ import { MAX_PAIRED_SERVERS } from './relay-pool.js';
33
34
  import { resolveDshVersion } from './relay-server.js';
34
35
  import { fromVirtual } from './virtual-id.js';
35
36
  /** Prefix all client routes live under (one webServer prefix registration). */
@@ -85,6 +86,12 @@ export const CLIENT_UNSHARE_ROUTE = `${CLIENT_ROUTE_PREFIX}/unshare`;
85
86
  * means a readable signal says nothing is open; the sink stores the value
86
87
  * and re-judges the mergers immediately. */
87
88
  export const CLIENT_CURRENT_SESSION_ROUTE = `${CLIENT_ROUTE_PREFIX}/current-session`;
89
+ /** POST `{entryId?: string, all?: boolean}` (MS-5): remove one paired server
90
+ * by its entry id, or every paired server at once (`all: true`). The paired
91
+ * list lives in `zen-remote-state.json`; this route goes through the pool so
92
+ * the live clients stop with their records. Pool-mode only — a mount without
93
+ * a pool has no paired list to edit and answers the unknown-path 404. */
94
+ export const CLIENT_UNPAIR_ROUTE = `${CLIENT_ROUTE_PREFIX}/unpair`;
88
95
  /** Longest `sessionId` the current-session route accepts (T62): real ids —
89
96
  * a local `session-<uuid>` or a `zr~<serverId>~<id>` virtual id — are far
90
97
  * shorter; the cap bounds garbage, not reality. */
@@ -321,28 +328,20 @@ function remoteStatusStateOf(relay) {
321
328
  }
322
329
  }
323
330
  /**
324
- * The status route's body, computed exactly as the CLIENT_STATUS_ROUTE
325
- * branch always has — extracted (CU-3) so the debug-report route can embed
326
- * the same diagnostics aggregation without a second implementation. All
327
- * reads are live per call: volatile row fields, the interception getters,
328
- * the relay client's current state. May await one bounded probe or connect
329
- * (never longer than the probe timeout).
331
+ * The diagnostics decorator shared by the single-client and pool status
332
+ * bodies (CU-3's extraction, extended by MS-5): every interception getter is
333
+ * read live per call, and the compat verdict rides whenever there is
334
+ * something to report — from `compatRelay`'s last handshake (the single
335
+ * client, or in pool mode the FIRST entry's client) or from the interceptor's
336
+ * incompatible-call ring. `pluginBridgeOverride` replaces the single-client
337
+ * verdict source in pool mode (the first entry's per-server verdicts, keeping
338
+ * the top-level field's shape).
330
339
  */
331
- async function statusBodyOf(options) {
332
- const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
333
- // The typert interception's view (T23b-1) rides on EVERY status
334
- // answer, whatever the connection state — the settings surface needs
335
- // it to explain a refused install even while unpaired. The interface
336
- // compat verdict (T42) appears once there is something to report: a
337
- // stored comparison from the relay client's last handshake, or any
338
- // incompatible call the interceptor recorded. Absent otherwise —
339
- // nothing to show, and the settings block presence-gates on exactly
340
- // that.
340
+ function diagnosticsDecorator(options, compatRelay, pluginBridgeOverride) {
341
341
  const intercept = options.getIntercept?.();
342
342
  const fetchRouteIntercept = options.getFetchRouteIntercept?.();
343
343
  const serviceProbe = options.getServiceProbe?.();
344
344
  const pluginRouteIntercept = options.getPluginRouteIntercept?.();
345
- const compatRelay = options.getRelayClient?.();
346
345
  const compat = compatRelay?.compat !== undefined || (intercept?.incompatibleCalls.length ?? 0) > 0
347
346
  ? {
348
347
  identical: compatRelay?.compat?.identical ?? [],
@@ -351,11 +350,11 @@ async function statusBodyOf(options) {
351
350
  incompatibleCalls: intercept?.incompatibleCalls ?? [],
352
351
  }
353
352
  : undefined;
354
- const withDiagnostics = (body) => {
353
+ return (body) => {
355
354
  // PB-2: the per-plugin bridge verdict rides EVERY status answer —
356
355
  // the settings surface explains a not-bridged plugin whatever the
357
356
  // connection state (unpaired reads all-offline, the honest verdict).
358
- let next = { ...body, pluginBridge: options.getPluginBridgeStatus?.() ?? [] };
357
+ let next = { ...body, pluginBridge: pluginBridgeOverride ?? options.getPluginBridgeStatus?.() ?? [] };
359
358
  if (intercept !== undefined)
360
359
  next = { ...next, intercept };
361
360
  if (fetchRouteIntercept !== undefined)
@@ -366,6 +365,81 @@ async function statusBodyOf(options) {
366
365
  next = { ...next, pluginRouteIntercept };
367
366
  return compat === undefined ? next : { ...next, compat };
368
367
  };
368
+ }
369
+ /**
370
+ * One pool entry's per-server status row (MS-5): the live client facts and
371
+ * the entry's own identity — NEVER the token, which the route never even
372
+ * sees (it lives in the state file, behind the pool). `compat` appears only
373
+ * once that client has a handshake verdict to report; `pluginBridge` comes
374
+ * from the per-server verdict getter ([] unwired).
375
+ */
376
+ function poolServerStatus(entry, options) {
377
+ const client = entry.client;
378
+ const body = {
379
+ entryId: entry.entryId,
380
+ serverId: entry.serverId,
381
+ serverUrl: entry.serverUrl,
382
+ serverName: entry.serverName ?? '',
383
+ displayName: entry.displayName,
384
+ state: client.state,
385
+ nextRetryAt: client.nextRetryAt ?? null,
386
+ ...(client.lastError !== undefined ? { lastError: client.lastError } : {}),
387
+ ...(client.compat !== undefined
388
+ ? { compat: { identical: client.compat.identical, different: client.compat.different, unavailable: client.compat.unavailable } }
389
+ : {}),
390
+ pluginBridge: options.getPluginBridgeStatusForServer?.(entry.serverId, client) ?? [],
391
+ };
392
+ return body;
393
+ }
394
+ /**
395
+ * The POOL status body (MS-5): the paired list is the truth — the row's
396
+ * serverUrl/deviceToken are never consulted. The top level keeps the FIRST
397
+ * entry's compatibility fields (the old single-server page keeps rendering)
398
+ * and every entry reports as one `servers` row; `maxServers` names the cap.
399
+ * An empty pool answers `unpaired` — the honest "nothing paired".
400
+ */
401
+ async function poolStatusBodyOf(options) {
402
+ const pool = options.pool;
403
+ const entries = pool.entries();
404
+ const first = entries[0];
405
+ const bridgeOverride = first !== undefined
406
+ ? options.getPluginBridgeStatusForServer?.(first.serverId, first.client) ?? []
407
+ : [];
408
+ const withDiagnostics = diagnosticsDecorator(options, first?.client, bridgeOverride);
409
+ const servers = entries.map((entry) => poolServerStatus(entry, options));
410
+ if (first === undefined) {
411
+ return withDiagnostics({ state: 'unpaired', maxServers: MAX_PAIRED_SERVERS, servers });
412
+ }
413
+ const body = deviceStatusBody(first.client.state, { serverName: first.serverName ?? '', deviceName: first.client.handshakeInfo?.deviceName }, first.serverUrl, first.client.deviceNameSyncing);
414
+ if (first.client.nextRetryAt !== null && first.client.nextRetryAt !== undefined)
415
+ body.nextRetryAt = first.client.nextRetryAt;
416
+ if (first.client.lastError !== undefined)
417
+ body.lastError = first.client.lastError;
418
+ return withDiagnostics({ ...body, maxServers: MAX_PAIRED_SERVERS, servers });
419
+ }
420
+ /**
421
+ * The status route's body, computed exactly as the CLIENT_STATUS_ROUTE
422
+ * branch always has — extracted (CU-3) so the debug-report route can embed
423
+ * the same diagnostics aggregation without a second implementation. All
424
+ * reads are live per call: volatile row fields, the interception getters,
425
+ * the relay client's current state. May await one bounded probe or connect
426
+ * (never longer than the probe timeout). In pool mode the paired list is
427
+ * the truth instead ({@link poolStatusBodyOf}); the row is not consulted.
428
+ */
429
+ async function statusBodyOf(options) {
430
+ const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
431
+ if (options.pool !== undefined)
432
+ return poolStatusBodyOf(options);
433
+ // The typert interception's view (T23b-1) rides on EVERY status
434
+ // answer, whatever the connection state — the settings surface needs
435
+ // it to explain a refused install even while unpaired. The interface
436
+ // compat verdict (T42) appears once there is something to report: a
437
+ // stored comparison from the relay client's last handshake, or any
438
+ // incompatible call the interceptor recorded. Absent otherwise —
439
+ // nothing to show, and the settings block presence-gates on exactly
440
+ // that.
441
+ const compatRelay = options.getRelayClient?.();
442
+ const withDiagnostics = diagnosticsDecorator(options, compatRelay);
369
443
  // The ROW decides its own two shapes first, per request (both fields
370
444
  // are volatile { get() } wrappers — only the row object apply()
371
445
  // received is a live source): without both credentials nothing is
@@ -427,6 +501,11 @@ async function statusBodyOf(options) {
427
501
  */
428
502
  export function createClientHandler(options) {
429
503
  const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
504
+ const resolveLang = () => {
505
+ const value = typeof options.lang === 'function' ? options.lang() : options.lang;
506
+ return value === 'en' ? 'en' : 'zh';
507
+ };
508
+ const mismatchMessage = () => resolveLang() === 'en' ? 'This remote session belongs to another server' : '此远程会话属于其他主服务端';
430
509
  return async (req, res) => {
431
510
  // Wall 1: DSH's own admission, relaying its refusal verbatim.
432
511
  const admission = options.admit(req);
@@ -449,7 +528,10 @@ export function createClientHandler(options) {
449
528
  if (route === CLIENT_DEBUG_REPORT_ROUTE && req.headers['x-zen-remote-via'] !== undefined) {
450
529
  responseJson(res, 403, {
451
530
  ok: false,
452
- error: { code: 'via-gateway', message: '诊断报告只能在本机导出' },
531
+ error: {
532
+ code: 'via-gateway',
533
+ message: resolveLang() === 'en' ? 'Diagnostics report can only be exported on this machine' : '诊断报告只能在本机导出',
534
+ },
453
535
  });
454
536
  return;
455
537
  }
@@ -480,6 +562,23 @@ export function createClientHandler(options) {
480
562
  responseJson(res, 400, { ok: false, code: normalized.reason });
481
563
  return;
482
564
  }
565
+ // MS-5 pool mode: the 8-server cap is judged BEFORE the pairing
566
+ // round-trip — a refusal the user can fix locally (remove a server,
567
+ // or re-enter an already-paired address) must not spend a claim code
568
+ // on the wire. An address that is ALREADY paired is always allowed
569
+ // through: re-pairing it just refreshes the token in place, at 8/8
570
+ // too. The pool stays the single writer afterwards: the gateway's
571
+ // token goes straight into `zen-remote-state.json` and NEVER reaches
572
+ // the browser — this response carries only the entry id and the
573
+ // address echo.
574
+ const pool = options.pool;
575
+ if (pool !== undefined) {
576
+ const known = pool.entries().some((entry) => entry.serverUrl === normalized.url);
577
+ if (!known && pool.entries().length >= MAX_PAIRED_SERVERS) {
578
+ responseJson(res, 409, { ok: false, code: 'limit-reached' });
579
+ return;
580
+ }
581
+ }
483
582
  const payload = JSON.stringify({
484
583
  code: stringOrEmpty(body.code),
485
584
  name: stringOrEmpty(body.name),
@@ -516,7 +615,68 @@ export function createClientHandler(options) {
516
615
  responseJson(res, 502, { ok: false, code: 'unreachable' });
517
616
  return;
518
617
  }
519
- respondClaim(res, classifyClaimResponse(status, gatewayBody), normalized.url);
618
+ const outcome = classifyClaimResponse(status, gatewayBody);
619
+ if (pool === undefined || !outcome.ok) {
620
+ respondClaim(res, outcome, normalized.url);
621
+ return;
622
+ }
623
+ // Pool mode, redemption succeeded: land the pairing in the state
624
+ // file through the pool (which also starts/refreshes the live
625
+ // client) and answer without the token. The pair() can only refuse
626
+ // past the pre-check on a concurrent writer — the same envelopes.
627
+ const paired = await pool.pair({
628
+ serverUrl: normalized.url,
629
+ token: outcome.token,
630
+ deviceId: outcome.deviceId,
631
+ deviceName: outcome.deviceName,
632
+ });
633
+ if (!paired.ok) {
634
+ responseJson(res, paired.code === 'limit-reached' ? 409 : 400, { ok: false, code: paired.code });
635
+ return;
636
+ }
637
+ responseJson(res, 200, {
638
+ ok: true,
639
+ entryId: paired.entry.entryId,
640
+ deviceId: outcome.deviceId,
641
+ deviceName: outcome.deviceName,
642
+ serverUrl: normalized.url,
643
+ });
644
+ return;
645
+ }
646
+ if (route === CLIENT_UNPAIR_ROUTE) {
647
+ // MS-5: remove one paired server or all of them. Pool-mode only —
648
+ // a mount without a pool has no paired list to edit (the old
649
+ // single-server pairing lived in the ROW, which the browser writes
650
+ // directly), and answers the same 404 an unknown path would.
651
+ const pool = options.pool;
652
+ if (pool === undefined) {
653
+ responseJson(res, 404, { ok: false, error: { code: 'not-found', message: 'Unknown client route' } });
654
+ return;
655
+ }
656
+ if (method !== 'POST') {
657
+ res.setHeader('Allow', 'POST');
658
+ responseJson(res, 405, { ok: false, error: { code: 'method-not-allowed', message: 'Use POST' } });
659
+ return;
660
+ }
661
+ if (!sameOriginPost(req)) {
662
+ responseJson(res, 403, {
663
+ ok: false,
664
+ error: { code: 'origin-rejected', message: 'The request must originate from this DSH Web application' },
665
+ });
666
+ return;
667
+ }
668
+ const body = await readJsonBody(req);
669
+ if (body.all === true) {
670
+ const removed = pool.unpairAll();
671
+ responseJson(res, 200, { ok: true, removed });
672
+ return;
673
+ }
674
+ if (typeof body.entryId === 'string' && body.entryId !== '') {
675
+ const removed = pool.unpair(body.entryId);
676
+ responseJson(res, 200, { ok: true, removed: removed ? 1 : 0 });
677
+ return;
678
+ }
679
+ responseJson(res, 400, { ok: false, error: { code: 'bad-request', message: 'entryId (non-empty string) or all (true) is required' } });
520
680
  return;
521
681
  }
522
682
  if (route === CLIENT_STATUS_ROUTE) {
@@ -574,6 +734,8 @@ export function createClientHandler(options) {
574
734
  pushSummary: values.pushSummary,
575
735
  pushTurnEnd: values.pushTurnEnd,
576
736
  pushTool: values.pushTool,
737
+ pushEnabled: values.pushEnabled,
738
+ pushSessionName: values.pushSessionName,
577
739
  serverUrl: stringOrEmpty(unwrapVolatile(record.serverUrl)).trim(),
578
740
  },
579
741
  statusBody: await statusBodyOf(options),
@@ -598,6 +760,31 @@ export function createClientHandler(options) {
598
760
  return;
599
761
  }
600
762
  const relay = options.getRelayClient?.();
763
+ // MS-5 pool mode: an optional { entryId } names ONE server — the
764
+ // same offline-only, 409-on-conflict contract the single client's
765
+ // button has, per entry; without it every OFFLINE server gets one
766
+ // attempt (an online server is not "reconnected", it is fine).
767
+ const pool = options.pool;
768
+ if (pool !== undefined) {
769
+ const body = await readJsonBody(req);
770
+ const entryId = typeof body.entryId === 'string' && body.entryId !== '' ? body.entryId : undefined;
771
+ if (entryId === undefined) {
772
+ const started = await pool.reconnect();
773
+ responseJson(res, 200, { ok: true, started });
774
+ return;
775
+ }
776
+ const entry = pool.getByEntryId(entryId);
777
+ if (entry === undefined) {
778
+ responseJson(res, 404, { ok: false, error: { code: 'not-found', message: 'No such paired server' } });
779
+ return;
780
+ }
781
+ if (entry.client.state !== 'offline' || !entry.client.reconnect()) {
782
+ responseJson(res, 409, { ok: false, error: { code: 'not-offline', message: 'The client is not offline' } });
783
+ return;
784
+ }
785
+ responseJson(res, 200, { ok: true });
786
+ return;
787
+ }
601
788
  // No relay client (a composition that never built one): there is
602
789
  // nothing to reconnect, and the probe-shaped status route is the
603
790
  // only connection surface this deployment has.
@@ -631,6 +818,16 @@ export function createClientHandler(options) {
631
818
  return;
632
819
  }
633
820
  const relay = options.getRelayClient?.();
821
+ // MS-5 pool mode: the same QUESTION per server — one authenticated
822
+ // ping sweep over the offline clients, the reachable ones get the
823
+ // controlled `networkRestored()` (probe-first, no storm, offline
824
+ // only — the pool method enforces the same rules per client).
825
+ const pool = options.pool;
826
+ if (pool !== undefined) {
827
+ const outcome = await pool.probeAndRestoreAll();
828
+ responseJson(res, 200, { ok: true, reachable: outcome.reachable, reconnected: outcome.reconnected });
829
+ return;
830
+ }
634
831
  if (relay === undefined) {
635
832
  responseJson(res, 503, { ok: false, error: { code: 'unavailable', message: 'No relay client is running' } });
636
833
  return;
@@ -664,8 +861,41 @@ export function createClientHandler(options) {
664
861
  // sort is pinned here so the field's shape never depends on the
665
862
  // registry's internal order). The host mount — no verdict source —
666
863
  // answers [].
667
- const relay = options.getRelayClient?.();
864
+ // MS-5 pool mode: the SAME shape per server, under `servers` keyed
865
+ // by the handshake serverId (an entry that never handshook owns no
866
+ // virtual ids and is not addressable — skipped). The top level
867
+ // mirrors the FIRST entry so the old page keeps rendering.
868
+ const pool = options.pool;
668
869
  const intercept = options.getIntercept?.();
870
+ const bridgedOf = (serverId, client) => (options.getPluginBridgeStatusForServer?.(serverId, client) ?? [])
871
+ .filter((status) => status.bridged)
872
+ .map((status) => status.plugin)
873
+ .sort();
874
+ if (pool !== undefined) {
875
+ const entries = pool.entries();
876
+ const first = entries[0];
877
+ const servers = {};
878
+ for (const entry of entries) {
879
+ if (entry.serverId === undefined)
880
+ continue;
881
+ servers[entry.serverId] = {
882
+ state: remoteStatusStateOf(entry.client),
883
+ versionMismatch: (entry.client.compat?.different?.length ?? 0) > 0,
884
+ serverName: entry.serverName ?? '',
885
+ bridgedPlugins: bridgedOf(entry.serverId, entry.client),
886
+ };
887
+ }
888
+ responseJson(res, 200, {
889
+ state: remoteStatusStateOf(first?.client),
890
+ versionMismatch: (first?.client.compat?.different?.length ?? 0) > 0,
891
+ serverName: first?.serverName ?? '',
892
+ closed: Object.fromEntries((intercept?.closedSessions ?? []).map((record) => [record.sessionId, record.reason])),
893
+ bridgedPlugins: first !== undefined ? bridgedOf(first.serverId, first.client) : [],
894
+ servers,
895
+ });
896
+ return;
897
+ }
898
+ const relay = options.getRelayClient?.();
669
899
  responseJson(res, 200, {
670
900
  state: remoteStatusStateOf(relay),
671
901
  versionMismatch: (relay?.compat?.different?.length ?? 0) > 0,
@@ -709,7 +939,8 @@ export function createClientHandler(options) {
709
939
  return;
710
940
  }
711
941
  const read = raw === null ? { kind: 'none' } : { kind: 'open', sessionId: raw };
712
- sink(read);
942
+ const reportedLang = body.lang === 'en' ? 'en' : body.lang === 'zh' ? 'zh' : undefined;
943
+ sink(read, reportedLang);
713
944
  responseJson(res, 200, { ok: true });
714
945
  return;
715
946
  }
@@ -734,6 +965,26 @@ export function createClientHandler(options) {
734
965
  responseJson(res, 400, { ok: false, error: { code: 'not-virtual', message: 'sessionId must be a remote (virtual) session id' } });
735
966
  return;
736
967
  }
968
+ // MS-5 pool mode: the id's serverId half names the server — its own
969
+ // client closes the session with the ORIGINAL id. A server the pool
970
+ // does not hold is the same remote-mismatch the interceptor answers.
971
+ const pool = options.pool;
972
+ if (pool !== undefined) {
973
+ const entry = pool.getByServerId(parts.serverId);
974
+ if (entry === undefined) {
975
+ responseJson(res, 200, { ok: false, error: { code: 'remote-mismatch', message: mismatchMessage() } });
976
+ return;
977
+ }
978
+ try {
979
+ await entry.client.unshare(parts.id);
980
+ responseJson(res, 200, { ok: true });
981
+ }
982
+ catch (error) {
983
+ const code = error instanceof RelayError ? error.code : 'internal';
984
+ responseJson(res, 200, { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } });
985
+ }
986
+ return;
987
+ }
737
988
  const relay = options.getRelayClient?.();
738
989
  if (relay === undefined) {
739
990
  responseJson(res, 503, { ok: false, error: { code: 'unavailable', message: 'No relay client is running' } });
@@ -743,7 +994,7 @@ export function createClientHandler(options) {
743
994
  // just answer not-shared out there — the mismatch is answerable
744
995
  // locally, with the code the interceptor uses for the same fact.
745
996
  if (relay.handshakeInfo === undefined || relay.handshakeInfo.serverId !== parts.serverId) {
746
- responseJson(res, 200, { ok: false, error: { code: 'remote-mismatch', message: '此远程会话属于其他主服务端' } });
997
+ responseJson(res, 200, { ok: false, error: { code: 'remote-mismatch', message: mismatchMessage() } });
747
998
  return;
748
999
  }
749
1000
  try {
@@ -791,13 +1042,45 @@ export function createClientHandler(options) {
791
1042
  responseJson(res, 400, { ok: false, error: { code: 'not-virtual', message: 'sessionId is not a remote session id' } });
792
1043
  return;
793
1044
  }
1045
+ // MS-5 pool mode: route by the id's serverId half. A server the pool
1046
+ // does not hold refuses like a foreign one; its client must be the
1047
+ // ONLINE one before the query travels with the original id restored.
1048
+ const pool = options.pool;
1049
+ if (pool !== undefined) {
1050
+ const entry = pool.getByServerId(parts.serverId);
1051
+ if (entry === undefined) {
1052
+ responseJson(res, 400, { ok: false, error: { code: 'remote-mismatch', message: mismatchMessage() } });
1053
+ return;
1054
+ }
1055
+ if (entry.client.state !== 'online') {
1056
+ responseJson(res, 503, { ok: false, error: { code: 'remote-offline' } });
1057
+ return;
1058
+ }
1059
+ url.searchParams.set('sessionId', parts.id);
1060
+ // A browser walk-off cancels the round-trip mid-flight; the write
1061
+ // guard in the catch keeps a settled answer from racing the close.
1062
+ const hangUp = new AbortController();
1063
+ res.once('close', () => {
1064
+ if (!res.writableEnded)
1065
+ hangUp.abort();
1066
+ });
1067
+ try {
1068
+ const result = await entry.client.http(httpRoute, url.searchParams.toString(), hangUp.signal);
1069
+ respondUpstream(res, result);
1070
+ }
1071
+ catch (error) {
1072
+ if (!res.writableEnded && !res.destroyed)
1073
+ respondRelayFailure(res, error);
1074
+ }
1075
+ return;
1076
+ }
794
1077
  const relay = options.getRelayClient?.();
795
1078
  if (relay === undefined || relay.state !== 'online') {
796
1079
  responseJson(res, 503, { ok: false, error: { code: 'remote-offline' } });
797
1080
  return;
798
1081
  }
799
1082
  if (relay.handshakeInfo?.serverId !== parts.serverId) {
800
- responseJson(res, 400, { ok: false, error: { code: 'remote-mismatch', message: '此远程会话属于其他主服务端' } });
1083
+ responseJson(res, 400, { ok: false, error: { code: 'remote-mismatch', message: mismatchMessage() } });
801
1084
  return;
802
1085
  }
803
1086
  url.searchParams.set('sessionId', parts.id);