dsh-zen-remote 2.2.2 → 2.2.4

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 (40) hide show
  1. package/README.md +7 -3
  2. package/README.zh-CN.md +7 -3
  3. package/docs/interface.md +1 -1
  4. package/docs/plugin-bridge.md +1 -1
  5. package/docs/remote-access.en.md +1 -1
  6. package/docs/remote-access.md +1 -1
  7. package/lib/adapters/dsh-better-sidebar/index.js +6 -2
  8. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
  9. package/lib/admin-routes.js +3 -2
  10. package/lib/admin-routes.js.map +1 -1
  11. package/lib/index.js +16 -1
  12. package/lib/index.js.map +1 -1
  13. package/lib/intercept-shape.js +10 -4
  14. package/lib/intercept-shape.js.map +1 -1
  15. package/lib/intercept.js +26 -11
  16. package/lib/intercept.js.map +1 -1
  17. package/lib/owner-peer.js +72 -0
  18. package/lib/owner-peer.js.map +1 -0
  19. package/lib/relay-server.js +10 -9
  20. package/lib/relay-server.js.map +1 -1
  21. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +2 -1
  22. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
  23. package/lib/types/admin-routes.d.ts +2 -0
  24. package/lib/types/admin-routes.d.ts.map +1 -1
  25. package/lib/types/index.d.ts.map +1 -1
  26. package/lib/types/intercept-shape.d.ts.map +1 -1
  27. package/lib/types/intercept.d.ts +9 -0
  28. package/lib/types/intercept.d.ts.map +1 -1
  29. package/lib/types/owner-peer.d.ts +57 -0
  30. package/lib/types/owner-peer.d.ts.map +1 -0
  31. package/lib/types/relay-server.d.ts +8 -0
  32. package/lib/types/relay-server.d.ts.map +1 -1
  33. package/package.json +36 -36
  34. package/src/adapters/dsh-better-sidebar/index.ts +6 -2
  35. package/src/admin-routes.ts +5 -2
  36. package/src/index.ts +16 -1
  37. package/src/intercept-shape.ts +10 -3
  38. package/src/intercept.ts +33 -7
  39. package/src/owner-peer.ts +81 -0
  40. package/src/relay-server.ts +18 -10
@@ -90,12 +90,14 @@ export interface RelayGateway {
90
90
  method: string;
91
91
  args: unknown;
92
92
  signal?: AbortSignal;
93
+ peer?: unknown;
93
94
  }): Promise<unknown>;
94
95
  stream(call: {
95
96
  namespace: string;
96
97
  method: string;
97
98
  args: unknown;
98
99
  signal?: AbortSignal;
100
+ peer?: unknown;
99
101
  }): Promise<AsyncIterable<unknown>>;
100
102
  /** The host's wire adapter, shared by the WebSocket mux and the local
101
103
  * transports. `open` is the async 0.2.0 method. */
@@ -143,6 +145,12 @@ export interface RelayHandlerOptions {
143
145
  * synthetic Request to the same exact-fetch route table the browser's
144
146
  * `/api` transport uses — no loopback HTTP, no login state. */
145
147
  getApiFetch?: () => ((request: Request) => Promise<Response>) | undefined;
148
+ /** The owner Peer every gateway call is made as (DSH 0.2.1-alpha.3+
149
+ * requires one; `$events` and `$events/result` need an owner Peer with
150
+ * `interactions: 'answer'`, the SAME one for both). Built by
151
+ * {@link createOwnerPeerSource}; absent or answering undefined keeps the
152
+ * pre-alpha.3 behavior (the host defaults the Peer). */
153
+ getPeer?: () => unknown;
146
154
  /** Stream heartbeat interval in ms (a `{"type":"ping"}` line that keeps
147
155
  * reverse proxies from timing the idle stream away); defaults to 15000.
148
156
  * Tests inject a small value. */
@@ -1 +1 @@
1
- {"version":3,"file":"relay-server.d.ts","sourceRoot":"","sources":["../../src/relay-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAWlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAE3E,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACpG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG1D,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,0BAA0B,CAAA;AAGjF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,2BAA2B,CAAA;AAQpD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,QAAoB,CAAA;AAEjD;0EAC0E;AAC1E,eAAO,MAAM,gBAAgB,kCAAkC,CAAA;AA+F/D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1G,MAAM,CAAC,IAAI,EAAE;QACX,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,OAAO,CAAA;QACb,MAAM,CAAC,EAAE,WAAW,CAAA;KACrB,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IACnC;uDACmD;IACnD,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAA;KACnH,CAAA;IACD,4EAA4E;IAC5E,WAAW,CAAC,CACV,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,OAAO,CAAC,CAAA;CACpB;AAED;;;;;;;;;;;uEAWuE;AACvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAC7E,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,KAAK,EAAE,UAAU,CAAA;IACjB,+DAA+D;IAC/D,OAAO,EAAE,YAAY,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,eAAe,CAAA;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC7C;;;;;;mEAM+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAA;IACzE;;qCAEiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;uDAGmD;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;0EAEsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;+EAE2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;kFAE8E;IAC9E,YAAY,CAAC,EAAE;QACb;;4BAEoB;QACpB,QAAQ,EAAE,cAAc,CAAA;QACxB;yDACiD;QACjD,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACzC,8DAA8D;QAC9D,QAAQ,EAAE,cAAc,CAAA;QACxB;;8DAEsD;QACtD,MAAM,CAAC,EAAE,kBAAkB,CAAA;QAC3B;;+DAEuD;QACvD,QAAQ,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAA;KACxC,CAAA;CACF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1D;+BAC2B;IAC3B,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;IACtC;;;;;;;yEAOqE;IACrE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AASD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBjD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAW1C;AAsWD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CA43D7E;AAaD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,wBAAwB,CAAA"}
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.2.2",
3
+ "version": "2.2.4",
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",
@@ -115,42 +115,42 @@
115
115
  }
116
116
  },
117
117
  "devDependencies": {
118
- "@deepseek-ai/cordis": "4.0.5-alpha.1",
118
+ "@deepseek-ai/cordis": "4.0.5-alpha.2",
119
119
  "@deepseek-ai/cosmokit": "1.8.5",
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",
120
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.4",
121
+ "@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.4",
122
+ "@deepseek-ai/dsh-api-session-controller": "0.2.1-alpha.4",
123
+ "@deepseek-ai/dsh-api-workspace-controller": "0.2.1-alpha.4",
124
+ "@deepseek-ai/dsh-brand": "0.2.1-alpha.4",
125
+ "@deepseek-ai/dsh-client-connection": "0.2.1-alpha.4",
126
+ "@deepseek-ai/dsh-client-locale": "0.2.1-alpha.4",
127
+ "@deepseek-ai/dsh-client-store": "0.2.1-alpha.4",
128
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-alpha.4",
129
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.4",
130
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.4",
131
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.4",
132
+ "@deepseek-ai/dsh-client-ui-session": "0.2.1-alpha.4",
133
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.4",
134
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.4",
135
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.4",
136
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.4",
137
+ "@deepseek-ai/dsh-ptc-runtime": "0.2.1-alpha.4",
138
+ "@deepseek-ai/dsh-sandbox": "0.2.1-alpha.4",
139
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.4",
140
+ "@deepseek-ai/dsh-scope": "0.2.1-alpha.4",
141
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.4",
142
+ "@deepseek-ai/dsh-session-log-export": "0.2.1-alpha.4",
143
+ "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.4",
144
+ "@deepseek-ai/dsh-session-query": "0.2.1-alpha.4",
145
+ "@deepseek-ai/dsh-session-stats": "0.2.1-alpha.4",
146
+ "@deepseek-ai/dsh-subagent": "0.2.1-alpha.4",
147
+ "@deepseek-ai/dsh-timeout": "0.2.1-alpha.4",
148
+ "@deepseek-ai/dsh-token-meter": "0.2.1-alpha.4",
149
+ "@deepseek-ai/dsh-tools": "0.2.1-alpha.4",
150
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.4",
151
+ "@deepseek-ai/dsh-util-crypto": "0.2.1-alpha.4",
152
+ "@deepseek-ai/dsh-util-values": "0.2.1-alpha.4",
153
+ "@deepseek-ai/dsh-workspace": "0.2.1-alpha.4",
154
154
  "@deepseek-ai/schemastery": "3.18.4",
155
155
  "@types/node": "^26.2.0",
156
156
  "@types/react": "~18.3.1",
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The dsh-better-sidebar adapter (PB-12), written against EXACTLY
3
- * versions 0.25.0 and 0.26.0 (PB-15 widened the list; 0.26.0 is a JSON-api
3
+ * versions 0.25.0, 0.26.0 and 0.26.1 (PB-15 widened the list; 0.26.1 is a
4
+ * perf-only patch with the same wire surface as 0.26.0; 0.26.0 is a JSON-api
4
5
  * superset of 0.25.0 — it grows fs.readPage and the sidechat dequeue/model/
5
6
  * models methods, and moves fs.write's content onto the new raw-text
6
7
  * /sidebar/write route). Everything the adapter knows about the plugin came
@@ -49,7 +50,10 @@ const apiMethodOf = (pathname: string): string => sidebarMethodNameOf(pathname)
49
50
 
50
51
  export const dshBetterSidebarAdapter: BridgeAdapter = {
51
52
  plugin: 'dsh-better-sidebar',
52
- versions: ['0.25.0', '0.26.0'],
53
+ // 0.26.1 (perf patch): JSON-api method set, routes and WebSocket paths
54
+ // identical to 0.26.0's wire surface (verified against the npm build;
55
+ // lib/index.js itself differs — a perf refactor, not byte-identical).
56
+ versions: ['0.25.0', '0.26.0', '0.26.1'],
53
57
  routes: [
54
58
  {
55
59
  // The JSON API: the method name rides the path tail, the session
@@ -37,6 +37,7 @@
37
37
  * drive it over a real socket with a mock gateway and a mock admit.
38
38
  */
39
39
 
40
+ import { withPeer } from './owner-peer.js'
40
41
  import { request as httpRequest } from 'node:http'
41
42
  import type { IncomingMessage, ServerResponse } from 'node:http'
42
43
  import type { ConfigSource, ZenRemoteConfig } from './config.js'
@@ -150,6 +151,8 @@ export interface AdminHandlerOptions {
150
151
  * share trusts the table alone. The lookup itself must never be allowed to
151
152
  * throw into the route, so callers wrap it. */
152
153
  typert?: () => RelayGateway | undefined
154
+ /** The owner Peer gateway calls are made as (DSH 0.2.1-alpha.3+); see owner-peer.ts. */
155
+ getPeer?: () => unknown
153
156
  /** Live agent roster for the busy restore a successful `share` performs.
154
157
  * Absent or throwing means "no information": nothing is marked busy. */
155
158
  listAgents?: () => readonly AgentStatusLike[]
@@ -358,12 +361,12 @@ async function projectionOf(options: AdminHandlerOptions, sessionId: string): Pr
358
361
  return { kind: 'no-gateway' }
359
362
  }
360
363
  if (gateway === undefined) return { kind: 'no-gateway' }
361
- const value = await gateway.invoke({
364
+ const value = await gateway.invoke(withPeer({
362
365
  namespace: 'session',
363
366
  method: 'projections',
364
367
  args: { request: { sessionId } },
365
368
  signal: AbortSignal.timeout(options.projectionTimeoutMs ?? PROJECTION_TIMEOUT_MS),
366
- })
369
+ }, options.getPeer))
367
370
  if (value === null || value === undefined) return { kind: 'no-session' }
368
371
  const values = (value as { values?: unknown }).values
369
372
  return {
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * the package.json dsh.client declaration.
16
16
  */
17
17
 
18
+ import { createOwnerPeerSource } from './owner-peer.js'
18
19
  import { lstat, mkdir, open, realpath, rm } from 'node:fs/promises'
19
20
  import type { FileHandle } from 'node:fs/promises'
20
21
  import type { IncomingMessage, ServerResponse } from 'node:http'
@@ -715,6 +716,7 @@ export function apply(ctx: Context, config: MobileNavConfig = {}): void {
715
716
  // which would turn every shares request into a 500.
716
717
  store,
717
718
  typert: () => webCtx.reflect.get('typertGateway') as RelayGateway | undefined,
719
+ getPeer: createOwnerPeerSource(() => webCtx.connection),
718
720
  listAgents: () => (webCtx.reflect.get('agents') as { list(): readonly AgentStatusLike[] } | undefined)?.list() ?? [],
719
721
  // The "who is looking" badge (T33a2): read through whichever relay
720
722
  // handler is live right now. sessionQuery likewise — the share
@@ -780,6 +782,7 @@ export function apply(ctx: Context, config: MobileNavConfig = {}): void {
780
782
  // registry and pin those throwaway values for the row's lifetime.
781
783
  // ~7 ms per computation against a full registry; a failed compute
782
784
  // lands as the empty map inside the relay handler's own guard.
785
+ const relayPeer = createOwnerPeerSource(() => relayCtx.reflect.get('connection'))
783
786
  relayCtx.effect(() => {
784
787
  // The plugin bridge's loopback facts (PB-3), both read PER REQUEST
785
788
  // through the reflection layer: the web server may have restarted on
@@ -812,6 +815,10 @@ export function apply(ctx: Context, config: MobileNavConfig = {}): void {
812
815
  store,
813
816
  gateway: gatewayService,
814
817
  getApiFetch,
818
+ // DSH 0.2.1-alpha.3: one cached owner/answer Peer for every relay
819
+ // gateway call — `$events/result` must come from the Peer that
820
+ // opened `$events`.
821
+ getPeer: relayPeer,
815
822
  // Subagent reachability (T22c-fix): isAccessible walks the same
816
823
  // child→parent chain the activity tracker keeps fresh.
817
824
  parentOf: parentIndex.parentOf,
@@ -1068,7 +1075,15 @@ export function apply(ctx: Context, config: MobileNavConfig = {}): void {
1068
1075
  // second failure uninstalls (a half-trusted interception is worse than
1069
1076
  // none). Failure handling lives in runSelfCheck, which records the
1070
1077
  // verdict via noteSelfCheck.
1071
- void runSelfCheck(raw, { handle, log: logWarn })
1078
+ // DSH 0.2.1-alpha.3: the probe needs an owner Peer of its own (the
1079
+ // gateway's operatorPeer fallback is gone) — read `connection` through
1080
+ // the reflection layer so a composition without it still installs.
1081
+ void runSelfCheck(raw, {
1082
+ handle,
1083
+ log: logWarn,
1084
+ info: (format, ...args) => { ctx.logger?.info(`dsh-zen-remote ${format}`, ...args) },
1085
+ getPeer: createOwnerPeerSource(() => gatewayCtx.reflect.get('connection')),
1086
+ })
1072
1087
  })
1073
1088
  // The exact-fetch-route interception (T51): the local backend's
1074
1089
  // `/api/session/uploadFileBinary` entry forwards REMOTE sessions'
@@ -23,6 +23,11 @@ export type GatewayShapeCheck = { ok: true; notes: string[] } | { ok: false; rea
23
23
  * §2.2/§4.1). If these strings vanish from the constructor source, own
24
24
  * properties would silently stop intercepting. */
25
25
  const DYNAMIC_OPEN_CALL = 'this.openWireStream(endpoint, payload, uplink, peer, control.signal, control)'
26
+ /** DSH 0.2.1-alpha.3 threads the mux's per-stream uplink window through as a
27
+ * seventh argument (`uplinkWindow`); the call site is otherwise unchanged. */
28
+ const DYNAMIC_OPEN_CALL_WINDOW = 'this.openWireStream(endpoint, payload, uplink, peer, control.signal, control, window)'
29
+ /** Accepted `openWireStream` arities: 6 through 0.2.1-alpha.2, 7 from alpha.3. */
30
+ const OPEN_ARITIES = [6, 7]
26
31
  const DYNAMIC_RPC_CALL = 'this.dispatchRpc(endpoint, payload, signal, peer)'
27
32
 
28
33
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -48,7 +53,9 @@ export function checkGatewayShape(raw: unknown): GatewayShapeCheck {
48
53
  // already installed.
49
54
  const open = proto === null ? undefined : proto.openWireStream
50
55
  if (typeof open !== 'function') reasons.push('prototype: openWireStream is not a function')
51
- else if (open.length !== 6) reasons.push(`prototype: openWireStream has ${open.length} parameters, expected 6`)
56
+ else if (!OPEN_ARITIES.includes(open.length)) {
57
+ reasons.push(`prototype: openWireStream has ${open.length} parameters, expected ${OPEN_ARITIES.join(' or ')}`)
58
+ }
52
59
  const dispatch = proto === null ? undefined : proto.dispatchRpc
53
60
  if (typeof dispatch !== 'function') reasons.push('prototype: dispatchRpc is not a function')
54
61
  else if (dispatch.length !== 4) reasons.push(`prototype: dispatchRpc has ${dispatch.length} parameters, expected 4`)
@@ -65,8 +72,8 @@ export function checkGatewayShape(raw: unknown): GatewayShapeCheck {
65
72
  if (constructorSource === '') {
66
73
  reasons.push('constructor: the prototype has no constructor to inspect')
67
74
  } else {
68
- if (!constructorSource.includes(DYNAMIC_OPEN_CALL)) {
69
- reasons.push(`constructor: source does not contain "${DYNAMIC_OPEN_CALL}"`)
75
+ if (!constructorSource.includes(DYNAMIC_OPEN_CALL) && !constructorSource.includes(DYNAMIC_OPEN_CALL_WINDOW)) {
76
+ reasons.push(`constructor: source does not contain "${DYNAMIC_OPEN_CALL}" or "${DYNAMIC_OPEN_CALL_WINDOW}"`)
70
77
  }
71
78
  if (!constructorSource.includes(DYNAMIC_RPC_CALL)) {
72
79
  reasons.push(`constructor: source does not contain "${DYNAMIC_RPC_CALL}"`)
package/src/intercept.ts CHANGED
@@ -3212,6 +3212,9 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3212
3212
  peer: unknown,
3213
3213
  signal: AbortSignal | undefined,
3214
3214
  control: unknown,
3215
+ // DSH 0.2.1-alpha.3: the mux's uplink window, passed through verbatim
3216
+ // (undefined on older hosts and on the 5-argument wireStream.open path).
3217
+ uplinkWindow?: unknown,
3215
3218
  ): unknown {
3216
3219
  if (!installed) {
3217
3220
  return (chainTarget.openWireStream as (...callArgs: unknown[]) => unknown).call(
@@ -3222,6 +3225,7 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3222
3225
  peer,
3223
3226
  signal,
3224
3227
  control,
3228
+ uplinkWindow,
3225
3229
  )
3226
3230
  }
3227
3231
  counters.openWireStream += 1
@@ -3235,7 +3239,7 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3235
3239
  if (endpoint === '$events') {
3236
3240
  const open = chainTarget.openWireStream as (this: unknown, ...callArgs: unknown[]) => unknown
3237
3241
  return (async (): Promise<unknown> => {
3238
- const local = await open.call(raw, endpoint, payload, uplink, peer, signal, control)
3242
+ const local = await open.call(raw, endpoint, payload, uplink, peer, signal, control, uplinkWindow)
3239
3243
  if (!isAsyncIterable(local)) return local
3240
3244
  return mergedEventsStream({ local, relay, signal, recordFailure, log, clock, summarySync })
3241
3245
  })()
@@ -3256,6 +3260,7 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3256
3260
  peer,
3257
3261
  signal,
3258
3262
  control,
3263
+ uplinkWindow,
3259
3264
  )
3260
3265
  }
3261
3266
  // The T23b-2 global merge. The 0.2.0 openWireStream is an ASYNC
@@ -3269,7 +3274,7 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3269
3274
  return (async (): Promise<unknown> => {
3270
3275
  // The uplink rides to the LOCAL method verbatim (it may consume
3271
3276
  // it); the remote leg carries no uplink.
3272
- const local = await open.call(raw, endpoint, payload, uplink, peer, signal, control)
3277
+ const local = await open.call(raw, endpoint, payload, uplink, peer, signal, control, uplinkWindow)
3273
3278
  if (!isAsyncIterable(local)) return local
3274
3279
  return mergedGlobalStream({
3275
3280
  endpoint,
@@ -3295,6 +3300,7 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3295
3300
  peer,
3296
3301
  signal,
3297
3302
  control,
3303
+ uplinkWindow,
3298
3304
  )
3299
3305
  }
3300
3306
  const verdict = validate(endpoint, virtuals)
@@ -3399,6 +3405,11 @@ export function installIntercept(options: InstallInterceptOptions): InterceptHan
3399
3405
  export interface SelfCheckOptions {
3400
3406
  /** How long the probe may wait for its first frame; default 5000 ms. */
3401
3407
  timeoutMs?: number
3408
+ /** The owner Peer the probe opens as. DSH 0.2.1-alpha.3 removed
3409
+ * `gateway.operatorPeer()` and requires a live Peer on every stream; see
3410
+ * owner-peer.ts. Undefined falls back to `operatorPeer()` where it still
3411
+ * exists (≤ alpha.2), else to no Peer. */
3412
+ getPeer?: () => unknown
3402
3413
  }
3403
3414
 
3404
3415
  /**
@@ -3430,7 +3441,15 @@ export async function behaviorSelfCheck(raw: object, options?: SelfCheckOptions)
3430
3441
  const wireStream = gateway.wireStream
3431
3442
  const open = isPlainObject(wireStream) ? wireStream.open : undefined
3432
3443
  if (typeof open !== 'function') return { ok: false, reason: 'wireStream.open disappeared before the self-check' }
3433
- const operatorPeer = typeof gateway.operatorPeer === 'function' ? gateway.operatorPeer : undefined
3444
+ // ≤ 0.2.1-alpha.2 only: alpha.3 deleted operatorPeer (and connection.operator).
3445
+ const legacyOperatorPeer = typeof gateway.operatorPeer === 'function' ? gateway.operatorPeer : undefined
3446
+ let peer: unknown
3447
+ try {
3448
+ peer = options?.getPeer?.()
3449
+ } catch (error) {
3450
+ return { ok: false, reason: `the self-check could not obtain an owner Peer: ${messageOf(error)}` }
3451
+ }
3452
+ if (peer === undefined && legacyOperatorPeer !== undefined) peer = legacyOperatorPeer.call(raw)
3434
3453
  const controller = new AbortController()
3435
3454
  // NOT unref'd: an unref'd timer lets the process exit (or a test runner
3436
3455
  // judge the loop empty) before the timeout ever fires when this probe is
@@ -3458,7 +3477,7 @@ export async function behaviorSelfCheck(raw: object, options?: SelfCheckOptions)
3458
3477
  'workspace/follow',
3459
3478
  payload,
3460
3479
  undefined,
3461
- typeof operatorPeer === 'function' ? operatorPeer.call(raw) : undefined,
3480
+ peer,
3462
3481
  controller.signal,
3463
3482
  ),
3464
3483
  ),
@@ -3502,6 +3521,10 @@ export interface RunSelfCheckOptions {
3502
3521
  retryDelayMs?: number
3503
3522
  /** Failure logging, wired to the context logger by index.ts. */
3504
3523
  log?: (format: string, ...args: unknown[]) => void
3524
+ /** Success logging (one line once the probe passes). */
3525
+ info?: (format: string, ...args: unknown[]) => void
3526
+ /** Forwarded to {@link behaviorSelfCheck}. */
3527
+ getPeer?: () => unknown
3505
3528
  }
3506
3529
 
3507
3530
  /**
@@ -3521,7 +3544,7 @@ export async function runSelfCheck(raw: object, options: RunSelfCheckOptions): P
3521
3544
  const { handle, log } = options
3522
3545
  const retryDelayMs = options.retryDelayMs ?? 3_000
3523
3546
  const stillInstalled = (): boolean => handle.diagnostics().installed
3524
- let result = await behaviorSelfCheck(raw)
3547
+ let result = await behaviorSelfCheck(raw, { getPeer: options.getPeer })
3525
3548
  if (!result.ok && stillInstalled()) {
3526
3549
  await new Promise<void>((resolve) => {
3527
3550
  const timer = setTimeout(resolve, retryDelayMs)
@@ -3529,11 +3552,14 @@ export async function runSelfCheck(raw: object, options: RunSelfCheckOptions): P
3529
3552
  // loop-empty judgement) open for the retry delay.
3530
3553
  if (typeof timer.unref === 'function') timer.unref()
3531
3554
  })
3532
- if (stillInstalled()) result = await behaviorSelfCheck(raw)
3555
+ if (stillInstalled()) result = await behaviorSelfCheck(raw, { getPeer: options.getPeer })
3533
3556
  }
3534
3557
  if (!stillInstalled()) return result
3535
3558
  handle.noteSelfCheck(result)
3536
- if (result.ok) return result
3559
+ if (result.ok) {
3560
+ options.info?.('client intercept self-check passed; interception active')
3561
+ return result
3562
+ }
3537
3563
  handle.uninstall()
3538
3564
  log?.('client intercept self-check failed (%s), interception removed', result.reason)
3539
3565
  return result
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The owner Peer this plugin speaks as when it calls the typert gateway on
3
+ * its own behalf (relay routes, admin projections, the intercept
4
+ * self-check, the `$events` relay subscription).
5
+ *
6
+ * DSH 0.2.1-alpha.3 removed the gateway's implicit operator fallback
7
+ * (`gateway.operatorPeer()`, `connection.operator` and the `OperatorPeer`
8
+ * export are gone): every gateway call — `invoke`, `stream`,
9
+ * `openWireStream`, `dispatchRpc` — now asserts a live Peer, and `$events` /
10
+ * `$events/result` additionally demand `principal.kind === 'owner'` and
11
+ * `interactions === 'answer'`, with the result answered by the SAME Peer that
12
+ * opened the stream. The sanctioned in-process door is
13
+ * `connection.createPeer({ principal: { kind: 'owner' }, interactions:
14
+ * 'answer' })` (docs/upgrade-guide/v0.2.1-alpha.2/operator-connect), whose
15
+ * Peer is disposed with the Context that read `connection`.
16
+ *
17
+ * Resolution order: a cached `createPeer` Peer (recreated once its scope is
18
+ * disposed), else `connection.browserPeer` (also an owner/answer Peer),
19
+ * else `undefined` — the pre-alpha.3 behavior, where the gateway itself
20
+ * defaulted a missing Peer to its operator.
21
+ */
22
+
23
+ /** Minimal structural view of a typert `PeerScope`. */
24
+ export interface PeerLike {
25
+ readonly signal?: AbortSignal
26
+ }
27
+
28
+ /** The connection-service members this module reads. */
29
+ export interface PeerConnectionLike {
30
+ createPeer?: (options: { principal: { kind: 'owner' }; interactions?: 'answer' | 'none' }) => unknown
31
+ browserPeer?: unknown
32
+ }
33
+
34
+ function isLive(peer: unknown): boolean {
35
+ if (peer === null || typeof peer !== 'object') return false
36
+ const signal = (peer as PeerLike).signal
37
+ return !(signal instanceof AbortSignal) || !signal.aborted
38
+ }
39
+
40
+ /**
41
+ * Build a lazy, caching owner-Peer source over a connection-service reader.
42
+ * The reader runs per call (the service may be absent or restarted). The
43
+ * cache is keyed on the Peer's LIVENESS, not on the connection object's
44
+ * identity: `ctx.reflect.get('connection')` hands out a fresh proxy per read,
45
+ * so an identity key would mint a new Peer per call — and the gateway
46
+ * refuses a `$events/result` from any Peer but the one that opened
47
+ * `$events` (found by the alpha.3 live smoke). A restarted connection
48
+ * service disposes its Peers, which is exactly when a new one is minted.
49
+ * @param getConnection - live reader of `ctx.connection` (may return undefined).
50
+ * @returns a function answering the Peer to pass, or undefined to let the
51
+ * host default it (pre-alpha.3 hosts).
52
+ */
53
+ export function createOwnerPeerSource(getConnection: () => unknown): () => unknown {
54
+ let cached: unknown
55
+ return () => {
56
+ let connection: PeerConnectionLike | undefined
57
+ try {
58
+ const value = getConnection()
59
+ connection = value !== null && typeof value === 'object' ? (value as PeerConnectionLike) : undefined
60
+ } catch {
61
+ connection = undefined
62
+ }
63
+ if (connection === undefined) return undefined
64
+ if (typeof connection.createPeer === 'function') {
65
+ if (isLive(cached)) return cached
66
+ cached = connection.createPeer({ principal: { kind: 'owner' }, interactions: 'answer' })
67
+ return cached
68
+ }
69
+ const browser = connection.browserPeer
70
+ return browser !== null && typeof browser === 'object' ? browser : undefined
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Attach `peer` to one gateway request object when a Peer is available;
76
+ * otherwise return the request unchanged (pre-alpha.3 shape).
77
+ */
78
+ export function withPeer<T extends object>(request: T, getPeer: (() => unknown) | undefined): T & { peer?: unknown } {
79
+ const peer = getPeer?.()
80
+ return peer === undefined ? request : { ...request, peer }
81
+ }
@@ -26,6 +26,7 @@
26
26
  * a 404.
27
27
  */
28
28
 
29
+ import { withPeer } from './owner-peer.js'
29
30
  import { randomBytes, timingSafeEqual } from 'node:crypto'
30
31
  import { Readable, Transform } from 'node:stream'
31
32
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
@@ -208,12 +209,13 @@ const JOB_CACHE_LIMIT = 1024
208
209
  * before its typert dispatch — and returns the `{ok,…}` envelope itself.
209
210
  */
210
211
  export interface RelayGateway {
211
- invoke(call: { namespace: string; method: string; args: unknown; signal?: AbortSignal }): Promise<unknown>
212
+ invoke(call: { namespace: string; method: string; args: unknown; signal?: AbortSignal; peer?: unknown }): Promise<unknown>
212
213
  stream(call: {
213
214
  namespace: string
214
215
  method: string
215
216
  args: unknown
216
217
  signal?: AbortSignal
218
+ peer?: unknown
217
219
  }): Promise<AsyncIterable<unknown>>
218
220
  /** The host's wire adapter, shared by the WebSocket mux and the local
219
221
  * transports. `open` is the async 0.2.0 method. */
@@ -268,6 +270,12 @@ export interface RelayHandlerOptions {
268
270
  * synthetic Request to the same exact-fetch route table the browser's
269
271
  * `/api` transport uses — no loopback HTTP, no login state. */
270
272
  getApiFetch?: () => ((request: Request) => Promise<Response>) | undefined
273
+ /** The owner Peer every gateway call is made as (DSH 0.2.1-alpha.3+
274
+ * requires one; `$events` and `$events/result` need an owner Peer with
275
+ * `interactions: 'answer'`, the SAME one for both). Built by
276
+ * {@link createOwnerPeerSource}; absent or answering undefined keeps the
277
+ * pre-alpha.3 behavior (the host defaults the Peer). */
278
+ getPeer?: () => unknown
271
279
  /** Stream heartbeat interval in ms (a `{"type":"ping"}` line that keeps
272
280
  * reverse proxies from timing the idle stream away); defaults to 15000.
273
281
  * Tests inject a small value. */
@@ -875,12 +883,12 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
875
883
  const cached = recentJobs.get(sessionId)
876
884
  if (cached !== undefined && cached.has(jobId)) return cached.get(jobId) === sessionId
877
885
  try {
878
- const iterable = await gateway.stream({
886
+ const iterable = await gateway.stream(withPeer({
879
887
  namespace: 'job',
880
888
  method: 'list',
881
889
  args: { request: { sessionId } },
882
890
  signal,
883
- })
891
+ }, options.getPeer))
884
892
  for await (const frame of iterable) {
885
893
  rememberJobs(sessionId, frame)
886
894
  const jobs = recentJobs.get(sessionId)
@@ -906,7 +914,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
906
914
  */
907
915
  const workspaceExists = async (workspaceId: string, signal: AbortSignal): Promise<boolean> => {
908
916
  try {
909
- const iterable = await gateway.stream({ namespace: 'workspace', method: 'follow', args: {}, signal })
917
+ const iterable = await gateway.stream(withPeer({ namespace: 'workspace', method: 'follow', args: {}, signal }, options.getPeer))
910
918
  for await (const frame of iterable) {
911
919
  if (!isPlainObject(frame) || frame.type !== 'baseline') return false
912
920
  const value = isPlainObject(frame.value) ? frame.value : undefined
@@ -939,7 +947,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
939
947
  '$events',
940
948
  { args: {} },
941
949
  undefined,
942
- undefined,
950
+ options.getPeer?.(),
943
951
  signal,
944
952
  )) as unknown
945
953
  if (
@@ -1217,7 +1225,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
1217
1225
  '$events/result',
1218
1226
  { args: { clientId: owner.clientId, eventId: originalEventId, outcome: parsed.result } },
1219
1227
  hangUp.signal,
1220
- undefined,
1228
+ options.getPeer?.(),
1221
1229
  )) as unknown
1222
1230
  responseJson(res, 200, isPlainObject(envelope) ? envelope : { ok: false, error: { code: 'internal' } })
1223
1231
  } catch (error) {
@@ -1341,7 +1349,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
1341
1349
  responseJson(res, 403, { ok: false, error: { code: 'not-shared' } })
1342
1350
  return
1343
1351
  }
1344
- const value = await gateway.invoke({ namespace, method, args, signal: hangUp.signal })
1352
+ const value = await gateway.invoke(withPeer({ namespace, method, args, signal: hangUp.signal }, options.getPeer))
1345
1353
  // T31: a session created or forked THROUGH the relay is shared
1346
1354
  // automatically — the client only ever sees shared sessions, so an
1347
1355
  // unshared result would be born unreachable. Re-sharing (a fork the
@@ -2471,12 +2479,12 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
2471
2479
  const syncProjections = async (sessionId: string): Promise<void> => {
2472
2480
  let result: unknown
2473
2481
  try {
2474
- result = await gateway.invoke({
2482
+ result = await gateway.invoke(withPeer({
2475
2483
  namespace: 'session',
2476
2484
  method: 'projections',
2477
2485
  args: { request: { sessionId } },
2478
2486
  signal: hangUp.signal,
2479
- })
2487
+ }, options.getPeer))
2480
2488
  } catch {
2481
2489
  return // projections unavailable — the next live projection frame will flow anyway
2482
2490
  }
@@ -2596,7 +2604,7 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
2596
2604
  const iterable =
2597
2605
  eventsSub !== undefined
2598
2606
  ? await openEventsStream(hangUp.signal)
2599
- : await gateway.stream({ namespace, method, args, signal: hangUp.signal })
2607
+ : await gateway.stream(withPeer({ namespace, method, args, signal: hangUp.signal }, options.getPeer))
2600
2608
  for await (const frame of iterable) {
2601
2609
  if (finished || clientGone) break
2602
2610
  if (namespace === 'job' && method === 'list') rememberJobs(ownerSessionIdOf(args), frame)