super-dsh 0.1.3 → 0.1.5

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 (142) hide show
  1. package/README.md +3 -3
  2. package/agent-agy/cordis.patch.yml +8 -4
  3. package/agent-agy/dist/agent-preset-agy.d.ts +23 -43
  4. package/agent-agy/dist/agent-preset-agy.js +29 -166
  5. package/agent-agy/dist/agent.d.ts +2 -1
  6. package/agent-agy/dist/agent.js +3 -2
  7. package/agent-agy/dist/agy-sessions.d.ts +2 -1
  8. package/agent-agy/dist/agy-sessions.js +2 -11
  9. package/agent-agy/dist/index.js +13 -12
  10. package/agent-agy/dist/world-plugin.d.ts +2 -1
  11. package/agent-agy/dist/world-plugin.js +31 -51
  12. package/agent-agy/package.json +6 -6
  13. package/agent-claude/cordis.patch.yml +8 -4
  14. package/agent-claude/dist/agent-preset-claude.js +29 -166
  15. package/agent-claude/dist/agent.js +10 -10
  16. package/agent-claude/dist/claude-client.js +22 -3
  17. package/agent-claude/dist/claude-home.js +0 -8
  18. package/agent-claude/dist/index.js +12 -15
  19. package/agent-claude/dist/session-map.js +2 -5
  20. package/agent-claude/dist/world-plugin.js +3 -3
  21. package/agent-claude/package.json +9 -9
  22. package/agent-codex/README.md +8 -8
  23. package/agent-codex/cordis.patch.yml +9 -5
  24. package/agent-codex/dist/agent-preset-codex.js +29 -168
  25. package/agent-codex/dist/agent.js +17 -12
  26. package/agent-codex/dist/codex-store.js +0 -9
  27. package/agent-codex/dist/index.js +13 -16
  28. package/agent-codex/dist/session-map.js +2 -5
  29. package/agent-codex/dist/world-plugin.js +31 -51
  30. package/agent-codex/package.json +14 -11
  31. package/agent-hermes/README.md +9 -8
  32. package/agent-hermes/cordis.patch.yml +8 -4
  33. package/agent-hermes/dist/agent-preset-hermes.js +29 -168
  34. package/agent-hermes/dist/agent.js +33 -19
  35. package/agent-hermes/dist/hermes-client.js +5 -5
  36. package/agent-hermes/dist/hermes-store.js +2 -11
  37. package/agent-hermes/dist/index.js +13 -12
  38. package/agent-hermes/dist/world-plugin.js +31 -52
  39. package/agent-hermes/package.json +14 -11
  40. package/agent-omp/README.md +2 -2
  41. package/agent-omp/cordis.patch.yml +12 -7
  42. package/agent-omp/dist/agent-preset-omp.js +30 -169
  43. package/agent-omp/dist/agent.js +13 -5
  44. package/agent-omp/dist/index.js +12 -22
  45. package/agent-omp/dist/mobile/gesture.js +2 -2
  46. package/agent-omp/dist/mobile/zoom-guard.js +4 -4
  47. package/agent-omp/dist/mobile-boot.js +2 -2
  48. package/agent-omp/dist/omp-cli.js +1 -1
  49. package/agent-omp/dist/omp-disk-discovery.js +1 -1
  50. package/agent-omp/dist/protocol.js +1 -1
  51. package/agent-omp/dist/sidecar-client.js +1 -1
  52. package/agent-omp/dist/world-plugin.js +37 -50
  53. package/agent-omp/package.json +18 -14
  54. package/agent-omp/sidecar/main.ts +2 -2
  55. package/agent-pi/README.md +5 -5
  56. package/agent-pi/cordis.patch.yml +8 -4
  57. package/agent-pi/dist/agent-preset-pi.js +29 -166
  58. package/agent-pi/dist/agent.js +18 -12
  59. package/agent-pi/dist/index.js +14 -16
  60. package/agent-pi/dist/pi-home.js +2 -12
  61. package/agent-pi/dist/world-plugin.js +31 -51
  62. package/agent-pi/package.json +14 -11
  63. package/client/index.js +1994 -150
  64. package/cordis.patch.yml +38 -15
  65. package/package.json +32 -30
  66. package/{agent-hub → superd-hub}/cordis.patch.yml +3 -3
  67. package/{agent-hub → superd-hub}/dist/agent-roster.d.ts +3 -1
  68. package/{agent-hub → superd-hub}/dist/agent-roster.js +4 -2
  69. package/superd-hub/dist/carrier.d.ts +62 -0
  70. package/superd-hub/dist/carrier.js +213 -0
  71. package/superd-hub/dist/fusion-transport.d.ts +148 -0
  72. package/superd-hub/dist/fusion-transport.js +397 -0
  73. package/{agent-hub → superd-hub}/dist/gateway.d.ts +9 -4
  74. package/superd-hub/dist/gateway.js +44 -0
  75. package/superd-hub/dist/hub-route.d.ts +74 -0
  76. package/superd-hub/dist/hub-route.js +188 -0
  77. package/{agent-hub → superd-hub}/dist/index.d.ts +14 -6
  78. package/{agent-hub → superd-hub}/dist/index.js +14 -6
  79. package/{agent-hub → superd-hub}/dist/roster.d.ts +2 -2
  80. package/{agent-hub → superd-hub}/dist/rpc.js +1 -1
  81. package/{agent-hub → superd-hub}/dist/spawn-world.d.ts +27 -6
  82. package/superd-hub/dist/spawn-world.js +279 -0
  83. package/superd-hub/dist/switch-core.d.ts +630 -0
  84. package/superd-hub/dist/switch-core.js +734 -0
  85. package/superd-hub/dist/system-head.d.ts +69 -0
  86. package/superd-hub/dist/system-head.js +194 -0
  87. package/superd-hub/dist/web-trust.d.ts +112 -0
  88. package/superd-hub/dist/web-trust.js +126 -0
  89. package/superd-hub/dist/world-entry.d.ts +38 -0
  90. package/superd-hub/dist/world-entry.js +47 -0
  91. package/{agent-hub → superd-hub}/dist/world-host.d.ts +15 -20
  92. package/superd-hub/dist/world-host.js +65 -0
  93. package/{agent-hub → superd-hub}/dist/world-join.d.ts +2 -1
  94. package/{agent-hub → superd-hub}/dist/world-join.js +30 -50
  95. package/superd-hub/dist/world-mount.d.ts +82 -0
  96. package/superd-hub/dist/world-mount.js +105 -0
  97. package/{agent-hub → superd-hub}/dist/world-mux.d.ts +21 -6
  98. package/superd-hub/dist/world-mux.js +392 -0
  99. package/{agent-hub → superd-hub}/dist/world-provision.d.ts +20 -4
  100. package/superd-hub/dist/world-provision.js +203 -0
  101. package/superd-hub/dist/world-web-server.d.ts +113 -0
  102. package/superd-hub/dist/world-web-server.js +99 -0
  103. package/superd-hub/lib/client/index.js +2086 -0
  104. package/{agent-hub → superd-hub}/package.json +12 -7
  105. package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
  106. package/agent-agy/dist/agent-preset-projection.js +0 -33
  107. package/agent-claude/dist/agent-preset-projection.js +0 -30
  108. package/agent-codex/dist/agent-preset-projection.js +0 -37
  109. package/agent-codex/dist/app-home.js +0 -65
  110. package/agent-codex/dist/spike.js +0 -46
  111. package/agent-hermes/dist/agent-preset-projection.js +0 -34
  112. package/agent-hub/dist/carrier.d.ts +0 -43
  113. package/agent-hub/dist/carrier.js +0 -241
  114. package/agent-hub/dist/client-shim.d.ts +0 -28
  115. package/agent-hub/dist/client-shim.js +0 -251
  116. package/agent-hub/dist/gateway.js +0 -18
  117. package/agent-hub/dist/index-pass.d.ts +0 -22
  118. package/agent-hub/dist/index-pass.js +0 -47
  119. package/agent-hub/dist/labels.d.ts +0 -45
  120. package/agent-hub/dist/labels.js +0 -83
  121. package/agent-hub/dist/spawn-world.js +0 -72
  122. package/agent-hub/dist/world-entry.d.ts +0 -31
  123. package/agent-hub/dist/world-entry.js +0 -51
  124. package/agent-hub/dist/world-host.js +0 -44
  125. package/agent-hub/dist/world-mount.d.ts +0 -52
  126. package/agent-hub/dist/world-mount.js +0 -52
  127. package/agent-hub/dist/world-mux.js +0 -224
  128. package/agent-hub/dist/world-provision.js +0 -122
  129. package/agent-hub/dist/world-web-server.d.ts +0 -162
  130. package/agent-hub/dist/world-web-server.js +0 -248
  131. package/agent-hub/lib/client/index.js +0 -242
  132. package/agent-omp/dist/agent-preset-projection.js +0 -36
  133. package/agent-omp/dist/app-home.js +0 -86
  134. package/agent-pi/dist/agent-preset-projection.js +0 -29
  135. /package/{agent-hub → superd-hub}/dist/envelope.d.ts +0 -0
  136. /package/{agent-hub → superd-hub}/dist/envelope.js +0 -0
  137. /package/{agent-hub → superd-hub}/dist/ownership.d.ts +0 -0
  138. /package/{agent-hub → superd-hub}/dist/ownership.js +0 -0
  139. /package/{agent-hub → superd-hub}/dist/roster.js +0 -0
  140. /package/{agent-hub → superd-hub}/dist/rpc.d.ts +0 -0
  141. /package/{agent-hub → superd-hub}/dist/targets.d.ts +0 -0
  142. /package/{agent-hub → superd-hub}/dist/targets.js +0 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Per-world Remote stream mux server (AW-B DL6).
3
+ *
4
+ * Upstream's `RemoteStreamMuxServer` owns `/api/remote.mux` for the native
5
+ * gateway and is NOT reachable from outside its package (root exports omit it;
6
+ * `./src/*` cannot be imported at runtime). So this is our own wire-compatible
7
+ * implementation of the same protocol (dsh 0.1.7 `stream-protocol.ts`), bound
8
+ * to the ADDRESSED world's `openWireStream`:
9
+ *
10
+ * browser → server { type:'open', streamId, endpoint, payload }
11
+ * { type:'item', streamId, value? } (uplink)
12
+ * { type:'end', streamId } (uplink half-close)
13
+ * { type:'cancel', streamId }
14
+ * server → browser { type:'item', streamId, value }
15
+ * { type:'end', streamId }
16
+ * { type:'error', streamId, error:{code,message,details} }
17
+ *
18
+ * Dispatch semantics mirror upstream `RemoteStreamMuxConnection`: `item`,
19
+ * `end`, and `cancel` for a stream this connection no longer owns are dropped;
20
+ * a duplicate `open` is the one violation that closes the socket. Uplink items
21
+ * wait in a bounded inbox created before the opener resolves, so items sent
22
+ * right after `open` are never lost. Heartbeat: ping every interval, terminate
23
+ * after two missed pongs.
24
+ */
25
+ /** Specifier is widened on purpose: `ws` is a runtime peer, not a typed dep. */
26
+ const WS_MODULE = 'ws';
27
+ const MAX_MISSED_HEARTBEATS = 2;
28
+ const DEFAULT_HEARTBEAT_MS = 2_000;
29
+ const DEFAULT_STREAM_INBOX_BYTES = 262_144;
30
+ const UPLINK_DONE = { value: undefined, done: true };
31
+ function isRecord(value) {
32
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
33
+ }
34
+ function exactKeys(value, keys) {
35
+ const own = Object.keys(value);
36
+ return own.length === keys.length && keys.every(key => Object.hasOwn(value, key));
37
+ }
38
+ function validId(value) {
39
+ return typeof value === 'string' && value.length > 0;
40
+ }
41
+ /** An abort reason carrying wire failure fields (upstream `remoteErrorOf`). */
42
+ function isRemoteFailure(reason) {
43
+ return isRecord(reason) && typeof reason['code'] === 'string';
44
+ }
45
+ /**
46
+ * Parse one browser text frame.
47
+ * @param text - complete WebSocket text message.
48
+ * @returns the validated request.
49
+ * @throws {Error} on a malformed frame.
50
+ */
51
+ export function parseClientMessage(text) {
52
+ let raw;
53
+ try {
54
+ raw = JSON.parse(text);
55
+ }
56
+ catch {
57
+ throw new Error('world-mux: invalid client message');
58
+ }
59
+ if (!isRecord(raw))
60
+ throw new Error('world-mux: invalid client message');
61
+ if ((raw['type'] === 'cancel' || raw['type'] === 'end')
62
+ && exactKeys(raw, ['type', 'streamId']) && validId(raw['streamId'])) {
63
+ return { type: raw['type'], streamId: raw['streamId'] };
64
+ }
65
+ if (raw['type'] === 'item'
66
+ && (exactKeys(raw, ['type', 'streamId']) || exactKeys(raw, ['type', 'streamId', 'value']))
67
+ && validId(raw['streamId'])) {
68
+ return Object.hasOwn(raw, 'value')
69
+ ? { type: 'item', streamId: raw['streamId'], value: raw['value'] }
70
+ : { type: 'item', streamId: raw['streamId'] };
71
+ }
72
+ if (raw['type'] === 'open'
73
+ && exactKeys(raw, ['type', 'streamId', 'endpoint', 'payload'])
74
+ && validId(raw['streamId'])
75
+ && typeof raw['endpoint'] === 'string'
76
+ && raw['endpoint'].length > 0) {
77
+ return { type: 'open', streamId: raw['streamId'], endpoint: raw['endpoint'], payload: raw['payload'] };
78
+ }
79
+ throw new Error('world-mux: invalid client message');
80
+ }
81
+ /** Default failure mapper: keep the error code when it looks like one. */
82
+ export function defaultFailure(error) {
83
+ if (isRecord(error)) {
84
+ const code = typeof error['code'] === 'string' ? error['code'] : undefined;
85
+ const message = typeof error['message'] === 'string' ? error['message'] : undefined;
86
+ if (code !== undefined) {
87
+ const details = isRecord(error['details']) ? error['details'] : {};
88
+ return { code, message: message ?? code, details };
89
+ }
90
+ }
91
+ return { code: 'gateway/stream-error', message: error instanceof Error ? error.message : String(error), details: {} };
92
+ }
93
+ /** One uplink violation: an abort reason that must reach the client as a failure frame. */
94
+ function uplinkViolation(code, message, endpoint) {
95
+ return Object.assign(new Error(message), { code, details: { endpoint } });
96
+ }
97
+ /**
98
+ * Bounded single-consumer uplink queue of one logical stream, the source the
99
+ * world method reads as its uplink iterable. Mirrors upstream `UplinkInbox`:
100
+ * overflow, and an item after the client's `end`, fail the queue and abort the
101
+ * logical stream with a wire failure.
102
+ */
103
+ class UplinkInbox {
104
+ maxBytes;
105
+ endpoint;
106
+ onViolation;
107
+ queue = [];
108
+ bytes = 0;
109
+ ended = false;
110
+ closed = false;
111
+ taken = false;
112
+ failure;
113
+ wake;
114
+ constructor(maxBytes, endpoint, onViolation) {
115
+ this.maxBytes = maxBytes;
116
+ this.endpoint = endpoint;
117
+ this.onViolation = onViolation;
118
+ }
119
+ push(value, frameBytes) {
120
+ if (this.failure !== undefined || this.closed)
121
+ return;
122
+ if (this.ended) {
123
+ this.violate(uplinkViolation('gateway/protocol', 'world-mux: Remote stream uplink item after end', this.endpoint));
124
+ return;
125
+ }
126
+ if (this.bytes + frameBytes > this.maxBytes) {
127
+ this.violate(uplinkViolation('gateway/uplink-overflow', `world-mux: Remote stream uplink exceeded ${String(this.maxBytes)} buffered bytes`, this.endpoint));
128
+ return;
129
+ }
130
+ this.queue.push({ value, bytes: frameBytes });
131
+ this.bytes += frameBytes;
132
+ this.signal();
133
+ }
134
+ /** Client half-close; idempotent. */
135
+ end() {
136
+ if (this.ended)
137
+ return;
138
+ this.ended = true;
139
+ this.signal();
140
+ }
141
+ /** End the consumer's next read with `error`; idempotent, drops buffered items. */
142
+ fail(error) {
143
+ if (this.failure !== undefined)
144
+ return;
145
+ this.failure = error;
146
+ this.queue.length = 0;
147
+ this.bytes = 0;
148
+ this.signal();
149
+ }
150
+ [Symbol.asyncIterator]() {
151
+ if (this.taken)
152
+ throw new Error('world-mux: Remote stream uplink inbox already has a consumer');
153
+ this.taken = true;
154
+ return this;
155
+ }
156
+ async next() {
157
+ while (true) {
158
+ if (this.closed)
159
+ return UPLINK_DONE;
160
+ const entry = this.queue.shift();
161
+ if (entry !== undefined) {
162
+ this.bytes -= entry.bytes;
163
+ return { value: entry.value, done: false };
164
+ }
165
+ if (this.failure !== undefined)
166
+ throw this.failure;
167
+ if (this.ended)
168
+ return UPLINK_DONE;
169
+ if (this.wake !== undefined)
170
+ throw new Error('world-mux: Remote stream uplink inbox has one pending read');
171
+ await new Promise((resolve) => { this.wake = resolve; });
172
+ }
173
+ }
174
+ /** Consumer stopped reading: later items are dropped, a pending read ends. */
175
+ return() {
176
+ this.closed = true;
177
+ this.queue.length = 0;
178
+ this.bytes = 0;
179
+ this.signal();
180
+ return Promise.resolve(UPLINK_DONE);
181
+ }
182
+ violate(error) {
183
+ this.fail(error);
184
+ this.onViolation(error);
185
+ }
186
+ signal() {
187
+ const wake = this.wake;
188
+ this.wake = undefined;
189
+ wake?.();
190
+ }
191
+ }
192
+ /** One world's mux endpoint. */
193
+ export class WorldMuxServer {
194
+ open;
195
+ options;
196
+ server;
197
+ ws;
198
+ heartbeat;
199
+ connections = new Set();
200
+ missed = new WeakMap();
201
+ closed = false;
202
+ constructor(open, options = {}) {
203
+ this.open = open;
204
+ this.options = options;
205
+ }
206
+ /** Take over one accepted upgrade request. */
207
+ async handleUpgrade(req, socket, head) {
208
+ const { server, ws } = await this.load();
209
+ server.handleUpgrade(req, socket, head, (client) => {
210
+ this.missed.set(client, 0);
211
+ client.on('pong', () => { this.missed.set(client, 0); });
212
+ this.startHeartbeat();
213
+ const done = this.runConnection(client, ws);
214
+ this.connections.add(done);
215
+ void done.then(() => { this.connections.delete(done); });
216
+ });
217
+ }
218
+ /** Terminate every socket and wait for the active streams to unwind. */
219
+ async close() {
220
+ this.closed = true;
221
+ clearInterval(this.heartbeat);
222
+ this.heartbeat = undefined;
223
+ const server = this.server;
224
+ if (server !== undefined) {
225
+ for (const client of server.clients)
226
+ client.terminate();
227
+ await new Promise((resolve) => { server.close(() => resolve()); });
228
+ }
229
+ await Promise.all([...this.connections]);
230
+ }
231
+ async load() {
232
+ if (this.server !== undefined && this.ws !== undefined)
233
+ return { server: this.server, ws: this.ws };
234
+ const mod = (await import(WS_MODULE));
235
+ const ws = typeof mod.WebSocketServer === 'function' ? mod : mod.default;
236
+ const server = new ws.WebSocketServer({ noServer: true });
237
+ this.server = server;
238
+ this.ws = ws;
239
+ return { server, ws };
240
+ }
241
+ async runConnection(client, ws) {
242
+ const streams = new Map();
243
+ let writes = Promise.resolve();
244
+ const send = (message) => {
245
+ let text;
246
+ try {
247
+ text = JSON.stringify(message);
248
+ }
249
+ catch (cause) {
250
+ return Promise.reject(new Error('world-mux: item is not JSON serializable', { cause }));
251
+ }
252
+ const delivery = writes.then(() => new Promise((resolve, reject) => {
253
+ if (client.readyState !== ws.WebSocket.OPEN) {
254
+ reject(new Error('world-mux: socket is closed'));
255
+ return;
256
+ }
257
+ client.send(text, (error) => { if (error)
258
+ reject(error);
259
+ else
260
+ resolve(); });
261
+ }));
262
+ writes = delivery.catch(() => undefined);
263
+ return delivery;
264
+ };
265
+ const failureOf = this.options.failure ?? defaultFailure;
266
+ const sendFailure = async (streamId, error) => {
267
+ if (client.readyState !== ws.WebSocket.OPEN)
268
+ return;
269
+ try {
270
+ await send({ type: 'error', streamId, error: failureOf(error) });
271
+ }
272
+ catch {
273
+ client.close(1011, 'Remote stream failure could not be delivered');
274
+ }
275
+ };
276
+ const pump = async (streamId, endpoint, payload, active) => {
277
+ let failed;
278
+ try {
279
+ const source = await this.open(endpoint, payload, active.inbox, active.abort);
280
+ for await (const value of source) {
281
+ await send({ type: 'item', streamId, value });
282
+ }
283
+ }
284
+ catch (error) {
285
+ failed = { error };
286
+ }
287
+ // The downlink has settled; later uplink frames cannot change the outcome.
288
+ active.inbox.fail(new Error('world-mux: Remote stream ended'));
289
+ if (active.abort.signal.aborted) {
290
+ // A wire failure as the abort reason is this mux failing the stream (a
291
+ // rejected or overflowing uplink item, or an item after end); the client
292
+ // is still waiting for that terminal frame. Any other abort is a
293
+ // cancellation and stays silent.
294
+ const reason = active.abort.signal.reason;
295
+ if (isRemoteFailure(reason))
296
+ await sendFailure(streamId, reason);
297
+ return;
298
+ }
299
+ if (failed !== undefined) {
300
+ await sendFailure(streamId, failed.error);
301
+ return;
302
+ }
303
+ try {
304
+ await send({ type: 'end', streamId });
305
+ }
306
+ catch (error) {
307
+ await sendFailure(streamId, error);
308
+ }
309
+ };
310
+ await new Promise((resolve) => {
311
+ client.once('close', () => { resolve(); });
312
+ client.on('error', () => { client.terminate(); });
313
+ client.on('message', (data, isBinary) => {
314
+ if (isBinary === true) {
315
+ client.close(1003, 'text messages required');
316
+ return;
317
+ }
318
+ const text = String(data);
319
+ let message;
320
+ try {
321
+ message = parseClientMessage(text);
322
+ }
323
+ catch {
324
+ client.close(1008, 'invalid Remote stream request');
325
+ return;
326
+ }
327
+ if (message.type === 'open') {
328
+ if (streams.has(message.streamId)) {
329
+ client.close(1008, 'duplicate Remote stream id');
330
+ return;
331
+ }
332
+ const abort = new AbortController();
333
+ // Created before the opener resolves so items the client sends right
334
+ // after `open` wait in the inbox instead of being lost.
335
+ const inbox = new UplinkInbox(this.options.streamInboxBytes ?? DEFAULT_STREAM_INBOX_BYTES, message.endpoint, (error) => { abort.abort(error); });
336
+ const active = {
337
+ abort,
338
+ inbox,
339
+ stop: (reason) => {
340
+ abort.abort(reason);
341
+ inbox.fail(reason);
342
+ },
343
+ done: Promise.resolve(),
344
+ };
345
+ streams.set(message.streamId, active);
346
+ const done = pump(message.streamId, message.endpoint, message.payload, active);
347
+ active.done = done;
348
+ void done.then(() => streams.delete(message.streamId), () => streams.delete(message.streamId));
349
+ return;
350
+ }
351
+ // `item`, `end`, and `cancel` for a stream this connection no longer
352
+ // owns are dropped: a finished stream leaves the table while the
353
+ // client's in-flight frames are still arriving.
354
+ if (message.type === 'item') {
355
+ streams.get(message.streamId)?.inbox.push(message.value, Buffer.byteLength(text, 'utf8'));
356
+ return;
357
+ }
358
+ if (message.type === 'end') {
359
+ streams.get(message.streamId)?.inbox.end();
360
+ return;
361
+ }
362
+ streams.get(message.streamId)?.stop(new Error('world-mux: Remote stream cancelled'));
363
+ });
364
+ });
365
+ const active = [...streams.values()];
366
+ for (const stream of active)
367
+ stream.stop(new Error('world-mux: Remote stream socket closed'));
368
+ await Promise.all(active.map(stream => stream.done));
369
+ }
370
+ startHeartbeat() {
371
+ if (this.heartbeat !== undefined || this.closed)
372
+ return;
373
+ const interval = this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
374
+ this.heartbeat = setInterval(() => {
375
+ const server = this.server;
376
+ if (server === undefined)
377
+ return;
378
+ for (const client of server.clients) {
379
+ if (client.readyState !== (this.ws?.WebSocket.OPEN ?? 1))
380
+ continue;
381
+ const missed = this.missed.get(client) ?? 0;
382
+ if (missed >= MAX_MISSED_HEARTBEATS) {
383
+ client.terminate();
384
+ continue;
385
+ }
386
+ this.missed.set(client, missed + 1);
387
+ client.ping();
388
+ }
389
+ }, interval);
390
+ this.heartbeat.unref();
391
+ }
392
+ }
@@ -1,3 +1,19 @@
1
+ /**
2
+ * Resolve the running dsh installation's package.json — the anchor worlds
3
+ * resolve `@deepseek-ai` bundles and the fallback closure from.
4
+ *
5
+ * Precedence: `$SUPERD_DSH_ANCHOR` (the repo dev lines' contract — the
6
+ * launcher pins the checkout build), then a walk UP from the
7
+ * `@deepseek-ai/dsh-app-boot` this module resolved (the host always provides
8
+ * it) to the nearest ancestor package named `@deepseek-ai/dsh` — the
9
+ * packaged-install layout (`<install>/@deepseek-ai/dsh/node_modules/
10
+ * @deepseek-ai/dsh-app-boot`) lands on the install root in two hops. No
11
+ * machine-specific path ships in code.
12
+ * @throws when neither yields an installation (mis-launched composition).
13
+ */
14
+ export declare function resolveInstallAnchor(host?: {
15
+ get(name: string): unknown;
16
+ }): string;
1
17
  export interface ProvisionWorldOptions {
2
18
  /** Runtime key — also the mount label and the `agents/<key>` home segment. */
3
19
  key: string;
@@ -10,11 +26,11 @@ export interface ProvisionWorldOptions {
10
26
  }
11
27
  /**
12
28
  * Provision one world's nested profile (idempotent: rewrites profile files,
13
- * re-links packages — lstat-safe against dangling links). Returns the
14
- * `bareModuleBaseUrl` for {@link spawnWorld}, or null when unresolvable.
29
+ * re-links packages — lstat-safe against dangling links). Returns whether
30
+ * the layout landed.
15
31
  *
16
- * Never throws: any provisioning failure is logged and returns null so the
32
+ * Never throws: any provisioning failure is logged and returns false so the
17
33
  * calling world plugin degrades to a not-ready roster row instead of failing
18
34
  * the loader entry — a failed apply would take down the whole host boot.
19
35
  */
20
- export declare function provisionWorldProfile(opts: ProvisionWorldOptions): string | null;
36
+ export declare function provisionWorldProfile(opts: ProvisionWorldOptions): boolean;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Nested-world profile provisioning — the publish story.
3
+ *
4
+ * The dev line (test/smoke.mjs) hand-provisions every world's nested profile
5
+ * (`<home>/agents/<label>/profiles/web`) and the `@pgmi-builds/*` links its
6
+ * resolution needs. A foreign install has NO line bootstrap:
7
+ *
8
+ * dsh plugin --profile <name> add super-dsh
9
+ *
10
+ * mounts the hub + adapter world plugins into the consumer's profile, and the
11
+ * spawn call sites (adapter world-plugins, hub join) must provision the same
12
+ * layout THEMSELVES or every world spawn fails on a missing profile.
13
+ *
14
+ * This module is the library form of that bootstrap, called by all six spawn
15
+ * sites. Layout invariants (super-dsh/AGENTS.md S7):
16
+ *
17
+ * - `<worldHome>/profiles/web/package.json` — bundles `[dsh-base, dsh-web-app,
18
+ * adapterPkg]`; the world tree is a full sibling root, nothing shared.
19
+ * - `<worldHome>/profiles/web/cordis.patch.yml` — per-world home pinning:
20
+ * every home-resolving DSH plugin reads THIS tree's nested root.
21
+ * - `<worldHome>/profiles/web/node_modules/@pgmi-builds/{adapter,hub}` —
22
+ * the plugin-add shape: with the host table widened (spawnWorld, spec
23
+ * §2.1), these profile-local links serve the local-physical leg for the
24
+ * world's own packages. No `@deepseek-ai` scope anywhere — those names
25
+ * ride the host's installation entries.
26
+ *
27
+ * Link identity rule: both links point INSIDE the running line tree — the hub
28
+ * at this module's own package root, the adapter at its sibling
29
+ * `agent-<key>` directory — identical in the dev repo layout and inside the
30
+ * published single `super-dsh` package, so ctx0 and every world root share
31
+ * one hub/adapter instance with zero registry involvement.
32
+ *
33
+ * Returns whether the layout was provisioned; false (logged) when the
34
+ * adapter sibling does not exist next to the hub — a foreign composition
35
+ * this module does not own.
36
+ */
37
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
38
+ import { dirname, join } from 'node:path';
39
+ import { fileURLToPath } from 'node:url';
40
+ import { createRequire } from 'node:module';
41
+ const HUB_SCOPE = '@pgmi-builds';
42
+ /** The profile bundles every world tree composes besides its adapter. */
43
+ const WORLD_BASE_BUNDLES = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'];
44
+ /**
45
+ * Resolve the running dsh installation's package.json — the anchor worlds
46
+ * resolve `@deepseek-ai` bundles and the fallback closure from.
47
+ *
48
+ * Precedence: `$SUPERD_DSH_ANCHOR` (the repo dev lines' contract — the
49
+ * launcher pins the checkout build), then a walk UP from the
50
+ * `@deepseek-ai/dsh-app-boot` this module resolved (the host always provides
51
+ * it) to the nearest ancestor package named `@deepseek-ai/dsh` — the
52
+ * packaged-install layout (`<install>/@deepseek-ai/dsh/node_modules/
53
+ * @deepseek-ai/dsh-app-boot`) lands on the install root in two hops. No
54
+ * machine-specific path ships in code.
55
+ * @throws when neither yields an installation (mis-launched composition).
56
+ */
57
+ export function resolveInstallAnchor(host) {
58
+ // Spec §2.3: the host's launcher facts win — a world plugin runs in ctx0,
59
+ // where `profileContext` carries the exact anchor the live table was
60
+ // computed from. Env and the realpath walk remain for table-less harnesses.
61
+ const launcher = host?.get('profileContext');
62
+ if (launcher?.installAnchor !== undefined)
63
+ return launcher.installAnchor;
64
+ const fromEnv = process.env.SUPERD_DSH_ANCHOR;
65
+ if (fromEnv !== undefined && fromEnv !== '')
66
+ return fromEnv;
67
+ let dir = dirname(resolveHostPackageDir('@deepseek-ai/dsh-app-boot'));
68
+ while (true) {
69
+ const manifest = join(dir, 'package.json');
70
+ if (existsSync(manifest)) {
71
+ try {
72
+ const name = JSON.parse(readFileSync(manifest, 'utf8')).name;
73
+ if (name === '@deepseek-ai/dsh')
74
+ return manifest;
75
+ }
76
+ catch { /* malformed intermediate manifest — keep walking */ }
77
+ }
78
+ const parent = dirname(dir);
79
+ if (parent === dir) {
80
+ throw new Error('super-dsh: cannot derive the dsh installation anchor (set SUPERD_DSH_ANCHOR)');
81
+ }
82
+ dir = parent;
83
+ }
84
+ }
85
+ /** First `node_modules` search-path candidate that exists for a specifier,
86
+ * resolved to its REALPATH — farm and pnpm links must not leak into the
87
+ * ancestor walk (the install tree only exists behind the symlink). */
88
+ function resolveHostPackageDir(spec) {
89
+ const require = createRequire(import.meta.url);
90
+ // Interception first: under the launcher's resolution table the profile
91
+ // tree holds NO physical @deepseek-ai, but require.resolve goes through
92
+ // the patched Module._resolveFilename and lands on the installation's
93
+ // copy directly. resolve.paths() cannot see the table — it only lists
94
+ // physical walk-up candidates (the retired profiles-level farm is what
95
+ // used to plug this gap on consumer machines; 0.1.3-r prod evidence).
96
+ try {
97
+ return realpathSync(dirname(require.resolve(`${spec}/package.json`)));
98
+ }
99
+ catch { /* not intercepted or ./package.json not exported — walk */ }
100
+ for (const searchPath of require.resolve.paths(`${spec}/x`) ?? []) {
101
+ const candidate = join(searchPath, spec);
102
+ if (existsSync(join(candidate, 'package.json')))
103
+ return realpathSync(candidate);
104
+ }
105
+ throw new Error(`super-dsh: cannot resolve ${spec} from the running host (set SUPERD_DSH_ANCHOR)`);
106
+ }
107
+ /**
108
+ * Provision one world's nested profile (idempotent: rewrites profile files,
109
+ * re-links packages — lstat-safe against dangling links). Returns whether
110
+ * the layout landed.
111
+ *
112
+ * Never throws: any provisioning failure is logged and returns false so the
113
+ * calling world plugin degrades to a not-ready roster row instead of failing
114
+ * the loader entry — a failed apply would take down the whole host boot.
115
+ */
116
+ export function provisionWorldProfile(opts) {
117
+ try {
118
+ return provisionWorldProfileUnchecked(opts);
119
+ }
120
+ catch (cause) {
121
+ console.error(`[super-dsh] provisioning world '${opts.key}' failed:`, cause);
122
+ return false;
123
+ }
124
+ }
125
+ function provisionWorldProfileUnchecked(opts) {
126
+ // Single-pack topology: this module lives at `<line>/superd-hub/dist/`, and
127
+ // every adapter is a SIBLING directory (`<line>/agent-<key>`) — the same
128
+ // layout in the repo (dev link: lines) and inside the published `super-dsh`
129
+ // package. The adapter link therefore points at the sibling; no registry
130
+ // resolution is involved anywhere.
131
+ const hubRoot = dirname(dirname(fileURLToPath(import.meta.url))); // <line>/superd-hub
132
+ const lineRoot = dirname(hubRoot); // monolith root / repo super-dsh/
133
+ const adapterDir = join(lineRoot, `agent-${opts.key}`);
134
+ if (!existsSync(join(adapterDir, 'package.json')))
135
+ return false;
136
+ const profileName = opts.profileName ?? 'web';
137
+ const profDir = join(opts.worldHome, 'profiles', profileName);
138
+ mkdirSync(profDir, { recursive: true });
139
+ writeFileSync(join(profDir, 'package.json'), JSON.stringify({
140
+ name: `superd-${opts.key}-world-profile`,
141
+ private: true,
142
+ dsh: { profile: { bundles: [...WORLD_BASE_BUNDLES, opts.adapterPkg] } },
143
+ }, null, 2) + '\n');
144
+ // Per-world home pinning (smoke.mjs parity): settings, credentials,
145
+ // attachments, shell env and skills all land inside `agents/<key>/`.
146
+ // (`agent-instructions` keeps its base `maxBytes` home — instruction
147
+ // discovery root, not agent data.)
148
+ //
149
+ // 0.1.7: the profile hmr row arms itself wherever a profileContext exists,
150
+ // and its config reload recomposes the tree from THE PROFILE LAYERS ALONE —
151
+ // spawn-time extraPatches do not survive it. The world posture is therefore
152
+ // PERSISTED in this layer (webserver disabled + ephemeral port, hmr itself
153
+ // off, and — T-D — the webui rows `web-runtime`/`client-hmr` off: a world's
154
+ // composition is a spawn-time fact and must not be re-derived), exactly
155
+ // mirroring worldMountPatches.
156
+ writeFileSync(join(profDir, 'cordis.patch.yml'), [
157
+ '# Per-world home pinning (dshHomePath is this tree\'s nested home).',
158
+ ...['settings', 'credentials', 'attachment-local', 'shell-env', 'skill-filesystem'].flatMap((id) => [`- id: ${id}`, ' config:', ' dshHome: !!js dshHomePath()']),
159
+ '# Zero-listener posture (0.1.7): persisted so hmr/launcher recomposition',
160
+ '# cannot re-enable the world\'s own listener (default port 3080).',
161
+ '- id: webserver',
162
+ ' disabled: true',
163
+ ' config:',
164
+ ' host: 127.0.0.1',
165
+ ' port: 0',
166
+ '- id: hmr',
167
+ ' disabled: true',
168
+ '# Webui-less posture (T-D): the world page rows stay off across any',
169
+ '# recomposition — worlds are pure data planes, `/hub` is the sole entry.',
170
+ '- id: web-runtime',
171
+ ' disabled: true',
172
+ '- id: client-hmr',
173
+ ' disabled: true',
174
+ '',
175
+ ].join('\n'));
176
+ // @pgmi-builds links, plugin-add shape: adapter + hub into the world
177
+ // profile's OWN node_modules (resolveBundleDir consults the profile
178
+ // directory; the local-physical leg of the widened host table consumes
179
+ // the same links for row imports — spec §2.1). Dangling links first
180
+ // (lstat: existsSync is false for them, which skipped rmSync and crashed
181
+ // symlinkSync with EEXIST after repo moves).
182
+ const links = [
183
+ [opts.adapterPkg.slice(HUB_SCOPE.length + 1), adapterDir],
184
+ ['superd-hub', hubRoot],
185
+ ];
186
+ const linkRoot = join(profDir, 'node_modules');
187
+ mkdirSync(join(linkRoot, HUB_SCOPE), { recursive: true });
188
+ for (const [name, target] of links) {
189
+ const dst = join(linkRoot, HUB_SCOPE, name);
190
+ const st = lstatSync(dst, { throwIfNoEntry: false });
191
+ if (st)
192
+ rmSync(dst, { recursive: true, force: true });
193
+ symlinkSync(target, dst);
194
+ }
195
+ // Retired machinery (spec §3): scrub the profiles-level farm this function
196
+ // wrote before 2026-09-25. Stale links there sit inside the widened
197
+ // local-physical walk and would shadow the host table's installation
198
+ // entries with frozen-generation targets.
199
+ const staleFarm = join(opts.worldHome, 'profiles', 'node_modules');
200
+ rmSync(join(staleFarm, '@deepseek-ai'), { recursive: true, force: true });
201
+ rmSync(join(staleFarm, HUB_SCOPE), { recursive: true, force: true });
202
+ return true;
203
+ }