super-dsh 0.1.0 → 0.1.2

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 (178) hide show
  1. package/agent-agy/bridge/agy_bridge.py +250 -0
  2. package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
  3. package/agent-agy/cordis.patch.yml +49 -0
  4. package/agent-agy/dist/adapter.d.ts +15 -0
  5. package/agent-agy/dist/adapter.js +42 -0
  6. package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
  7. package/agent-agy/dist/agent-preset-agy.js +169 -0
  8. package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
  9. package/agent-agy/dist/agent-preset-projection.js +33 -0
  10. package/agent-agy/dist/agent.d.ts +137 -0
  11. package/agent-agy/dist/agent.js +493 -0
  12. package/agent-agy/dist/agy-cli-client.d.ts +137 -0
  13. package/agent-agy/dist/agy-cli-client.js +301 -0
  14. package/agent-agy/dist/agy-client.d.ts +160 -0
  15. package/agent-agy/dist/agy-client.js +252 -0
  16. package/agent-agy/dist/agy-sessions.d.ts +24 -0
  17. package/agent-agy/dist/agy-sessions.js +116 -0
  18. package/agent-agy/dist/agy-store.d.ts +39 -0
  19. package/agent-agy/dist/agy-store.js +83 -0
  20. package/agent-agy/dist/inbox.d.ts +26 -0
  21. package/agent-agy/dist/inbox.js +53 -0
  22. package/agent-agy/dist/index.d.ts +80 -0
  23. package/agent-agy/dist/index.js +505 -0
  24. package/agent-agy/dist/models.d.ts +41 -0
  25. package/agent-agy/dist/models.js +74 -0
  26. package/agent-agy/dist/world-plugin.d.ts +9 -0
  27. package/agent-agy/dist/world-plugin.js +97 -0
  28. package/agent-agy/package.json +64 -0
  29. package/agent-claude/cordis.patch.yml +42 -0
  30. package/agent-claude/dist/adapter.js +62 -0
  31. package/agent-claude/dist/agent-preset-claude.js +169 -0
  32. package/agent-claude/dist/agent-preset-projection.js +30 -0
  33. package/agent-claude/dist/agent.js +1271 -0
  34. package/agent-claude/dist/claude-client.js +357 -0
  35. package/agent-claude/dist/claude-events.js +223 -0
  36. package/agent-claude/dist/claude-home.js +29 -0
  37. package/agent-claude/dist/commands.js +182 -0
  38. package/agent-claude/dist/content.js +134 -0
  39. package/agent-claude/dist/inbox.js +53 -0
  40. package/agent-claude/dist/index.js +555 -0
  41. package/agent-claude/dist/input-queue.js +64 -0
  42. package/agent-claude/dist/interaction.js +271 -0
  43. package/agent-claude/dist/models.js +158 -0
  44. package/agent-claude/dist/permission.js +120 -0
  45. package/agent-claude/dist/session-id.js +20 -0
  46. package/agent-claude/dist/session-map.js +165 -0
  47. package/agent-claude/dist/world-plugin.js +14 -0
  48. package/agent-claude/package.json +74 -0
  49. package/agent-codex/README.md +74 -0
  50. package/agent-codex/cordis.patch.yml +77 -0
  51. package/agent-codex/dist/adapter.js +55 -0
  52. package/agent-codex/dist/agent-preset-codex.js +171 -0
  53. package/agent-codex/dist/agent-preset-projection.js +37 -0
  54. package/agent-codex/dist/agent.js +1056 -0
  55. package/agent-codex/dist/app-home.js +65 -0
  56. package/agent-codex/dist/codex-client.js +469 -0
  57. package/agent-codex/dist/codex-events.js +297 -0
  58. package/agent-codex/dist/codex-store.js +190 -0
  59. package/agent-codex/dist/inbox.js +53 -0
  60. package/agent-codex/dist/index.js +605 -0
  61. package/agent-codex/dist/models.js +152 -0
  62. package/agent-codex/dist/permission.js +71 -0
  63. package/agent-codex/dist/session-map.js +170 -0
  64. package/agent-codex/dist/spike.js +46 -0
  65. package/agent-codex/dist/world-plugin.js +96 -0
  66. package/agent-codex/package.json +78 -0
  67. package/agent-hermes/README.md +144 -0
  68. package/agent-hermes/cordis.patch.yml +83 -0
  69. package/agent-hermes/dist/adapter.js +83 -0
  70. package/agent-hermes/dist/agent-preset-hermes.js +171 -0
  71. package/agent-hermes/dist/agent-preset-projection.js +34 -0
  72. package/agent-hermes/dist/agent.js +1068 -0
  73. package/agent-hermes/dist/hermes-client.js +953 -0
  74. package/agent-hermes/dist/hermes-events.js +289 -0
  75. package/agent-hermes/dist/hermes-store.js +158 -0
  76. package/agent-hermes/dist/inbox.js +53 -0
  77. package/agent-hermes/dist/index.js +541 -0
  78. package/agent-hermes/dist/models.js +324 -0
  79. package/agent-hermes/dist/permission.js +60 -0
  80. package/agent-hermes/dist/world-plugin.js +101 -0
  81. package/agent-hermes/package.json +76 -0
  82. package/agent-hub/cordis.patch.yml +12 -0
  83. package/agent-hub/dist/agent-roster.d.ts +34 -0
  84. package/agent-hub/dist/agent-roster.js +27 -0
  85. package/agent-hub/dist/carrier.d.ts +43 -0
  86. package/agent-hub/dist/carrier.js +241 -0
  87. package/agent-hub/dist/client-shim.d.ts +28 -0
  88. package/agent-hub/dist/client-shim.js +251 -0
  89. package/agent-hub/dist/envelope.d.ts +54 -0
  90. package/agent-hub/dist/envelope.js +92 -0
  91. package/agent-hub/dist/gateway.d.ts +28 -0
  92. package/agent-hub/dist/gateway.js +18 -0
  93. package/agent-hub/dist/index-pass.d.ts +22 -0
  94. package/agent-hub/dist/index-pass.js +47 -0
  95. package/agent-hub/dist/index.d.ts +29 -0
  96. package/agent-hub/dist/index.js +29 -0
  97. package/agent-hub/dist/labels.d.ts +45 -0
  98. package/agent-hub/dist/labels.js +83 -0
  99. package/agent-hub/dist/ownership.d.ts +37 -0
  100. package/agent-hub/dist/ownership.js +54 -0
  101. package/agent-hub/dist/roster.d.ts +28 -0
  102. package/agent-hub/dist/roster.js +38 -0
  103. package/agent-hub/dist/rpc.d.ts +38 -0
  104. package/agent-hub/dist/rpc.js +52 -0
  105. package/agent-hub/dist/spawn-world.d.ts +46 -0
  106. package/agent-hub/dist/spawn-world.js +72 -0
  107. package/agent-hub/dist/targets.d.ts +33 -0
  108. package/agent-hub/dist/targets.js +30 -0
  109. package/agent-hub/dist/world-entry.d.ts +31 -0
  110. package/agent-hub/dist/world-entry.js +51 -0
  111. package/agent-hub/dist/world-host.d.ts +69 -0
  112. package/agent-hub/dist/world-host.js +44 -0
  113. package/agent-hub/dist/world-join.d.ts +16 -0
  114. package/agent-hub/dist/world-join.js +122 -0
  115. package/agent-hub/dist/world-mount.d.ts +52 -0
  116. package/agent-hub/dist/world-mount.js +52 -0
  117. package/agent-hub/dist/world-mux.d.ts +72 -0
  118. package/agent-hub/dist/world-mux.js +224 -0
  119. package/agent-hub/dist/world-provision.d.ts +20 -0
  120. package/agent-hub/dist/world-provision.js +122 -0
  121. package/agent-hub/dist/world-web-server.d.ts +162 -0
  122. package/agent-hub/dist/world-web-server.js +248 -0
  123. package/agent-hub/lib/client/index.js +242 -0
  124. package/agent-hub/package.json +65 -0
  125. package/agent-omp/README.md +77 -0
  126. package/agent-omp/cordis.patch.yml +92 -0
  127. package/agent-omp/dist/adapter.js +36 -0
  128. package/agent-omp/dist/agent-preset-omp.js +171 -0
  129. package/agent-omp/dist/agent-preset-projection.js +36 -0
  130. package/agent-omp/dist/agent.js +1145 -0
  131. package/agent-omp/dist/app-home.js +86 -0
  132. package/agent-omp/dist/discovery.js +116 -0
  133. package/agent-omp/dist/inbox.js +53 -0
  134. package/agent-omp/dist/index.js +574 -0
  135. package/agent-omp/dist/knobs.js +50 -0
  136. package/agent-omp/dist/lazy-rpc.js +161 -0
  137. package/agent-omp/dist/mobile/gesture.js +148 -0
  138. package/agent-omp/dist/mobile/zoom-guard.js +294 -0
  139. package/agent-omp/dist/mobile-boot.js +38 -0
  140. package/agent-omp/dist/models.js +299 -0
  141. package/agent-omp/dist/omp-cli.js +147 -0
  142. package/agent-omp/dist/omp-disk-discovery.js +178 -0
  143. package/agent-omp/dist/omp-store.js +518 -0
  144. package/agent-omp/dist/pairing.js +45 -0
  145. package/agent-omp/dist/permission.js +96 -0
  146. package/agent-omp/dist/protocol.js +13 -0
  147. package/agent-omp/dist/rpc-types.js +1 -0
  148. package/agent-omp/dist/sdk-client.js +378 -0
  149. package/agent-omp/dist/sidecar-client.js +195 -0
  150. package/agent-omp/dist/store/adapter.js +47 -0
  151. package/agent-omp/dist/store/db.js +210 -0
  152. package/agent-omp/dist/store/index.js +59 -0
  153. package/agent-omp/dist/store/reconcile.js +51 -0
  154. package/agent-omp/dist/store/schema.js +40 -0
  155. package/agent-omp/dist/world-plugin.js +96 -0
  156. package/agent-omp/package.json +97 -0
  157. package/agent-omp/sidecar/main.ts +481 -0
  158. package/agent-pi/README.md +67 -0
  159. package/agent-pi/cordis.patch.yml +80 -0
  160. package/agent-pi/dist/adapter.js +53 -0
  161. package/agent-pi/dist/agent-preset-pi.js +169 -0
  162. package/agent-pi/dist/agent-preset-projection.js +29 -0
  163. package/agent-pi/dist/agent.js +949 -0
  164. package/agent-pi/dist/inbox.js +53 -0
  165. package/agent-pi/dist/index.js +595 -0
  166. package/agent-pi/dist/knobs.js +24 -0
  167. package/agent-pi/dist/models.js +200 -0
  168. package/agent-pi/dist/permission.js +66 -0
  169. package/agent-pi/dist/pi-client.js +224 -0
  170. package/agent-pi/dist/pi-events.js +155 -0
  171. package/agent-pi/dist/pi-home.js +55 -0
  172. package/agent-pi/dist/safe-json.js +25 -0
  173. package/agent-pi/dist/session-map.js +119 -0
  174. package/agent-pi/dist/world-plugin.js +97 -0
  175. package/agent-pi/package.json +77 -0
  176. package/cordis.patch.yml +22 -19
  177. package/package.json +88 -25
  178. package/client.js +0 -4
@@ -0,0 +1,43 @@
1
+ import { type RealWebServerFace } from './world-web-server.js';
2
+ import { type MuxFailure } from './world-mux.js';
3
+ /** The two-method face of a world's typertGateway service instance. */
4
+ export interface CarrierGatewayFace {
5
+ dispatchRpc(endpoint: string, payload: unknown, signal: AbortSignal): Promise<unknown>;
6
+ /** Streams return an async iterable (upstream face is synchronous-returning). */
7
+ openWireStream(endpoint: string, payload: unknown, signal: AbortSignal): Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>;
8
+ }
9
+ /** Options for mounting one world. */
10
+ export interface MountWorldOptions {
11
+ /** The ctx0 webServer the translated routes register on. */
12
+ real: RealWebServerFace;
13
+ /** Runtime key: the mount label (`omp` → `/omp`). */
14
+ label: string;
15
+ /** The addressed world's gateway instance. */
16
+ gateway: CarrierGatewayFace;
17
+ /** ctx0's browser-trust fence: returns an HTTP status to reject with. */
18
+ requestRejection?(req: unknown): number | undefined;
19
+ /** Max request body bytes (mirrors the connection default). */
20
+ maxBodyBytes?: number;
21
+ /** Mux ping interval (upstream default 30s). */
22
+ heartbeatIntervalMs?: number;
23
+ /** Error → wire failure mapper for the world's streams. */
24
+ failure?(error: unknown): MuxFailure;
25
+ }
26
+ /** The live mount handle. */
27
+ export interface MountedWorld {
28
+ /** Mount root (`/omp`). */
29
+ readonly labelPath: string;
30
+ /** Unary channel root (`/omp/api`). */
31
+ readonly apiPath: string;
32
+ /** Mux websocket path (`/omp/api/remote.mux`). */
33
+ readonly muxPath: string;
34
+ /** Unregister everything this mount installed. */
35
+ dispose(): void;
36
+ }
37
+ /**
38
+ * Mount one world on the shared ctx0 webServer.
39
+ * @param options - see {@link MountWorldOptions}.
40
+ * @returns the live mount handle.
41
+ */
42
+ export declare function mountWorld(options: MountWorldOptions): MountedWorld;
43
+ export { rewriteIndexHtml } from './index-pass.js';
@@ -0,0 +1,241 @@
1
+ /**
2
+ * ctx0 WebCarrier (AW-B DL1/DL5): mounts one world's traffic under `/<label>`.
3
+ *
4
+ * Responsibilities (grow per AW-B task):
5
+ * - Task 4: unary RPC — `/<label>/api/<endpoint>` decodes the connection
6
+ * envelope and dispatches into the ADDRESSED world's gateway instance.
7
+ * - Task 6: mux — `/<label>/api/remote.mux` upgrade bridged to the same
8
+ * instance (no shared selection anywhere).
9
+ * - 2026-09-17 (H1): non-envelope GET/HEAD on `/<label>/api` forwards into
10
+ * the owning world's registered exact Fetch routes — the world connection's
11
+ * shared `/api` handler, published from `world-entry` via `world-host` —
12
+ * still behind ctx0's fence, so `HEAD /<label>/api/session.export?...`
13
+ * reaches the world's session-log-download route.
14
+ *
15
+ * The addressed world is the request's path. Nothing here reads a global.
16
+ */
17
+ import { EnvelopeError, parseClientRequest, serverError, serverResult, wireErrorOf, } from './envelope.js';
18
+ import { stripLabelFromUrl } from './world-web-server.js';
19
+ import { worldFetchHandlerOf } from './world-host.js';
20
+ import { WorldMuxServer } from './world-mux.js';
21
+ /** Default body cap, aligned with the connection service. */
22
+ const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
23
+ async function readBody(req, limit) {
24
+ const carrier = req;
25
+ const chunks = [];
26
+ let size = 0;
27
+ if (typeof carrier[Symbol.asyncIterator] === 'function') {
28
+ for await (const chunk of carrier) {
29
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
30
+ size += buf.byteLength;
31
+ if (size > limit)
32
+ throw new EnvelopeError({ code: 'gateway/bad-request', message: 'request body too large' });
33
+ chunks.push(buf);
34
+ }
35
+ return Buffer.concat(chunks).toString('utf8');
36
+ }
37
+ if (typeof carrier.on !== 'function')
38
+ return '';
39
+ return await new Promise((resolve, reject) => {
40
+ carrier.on?.('data', (chunk) => {
41
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk ?? ''));
42
+ size += buf.byteLength;
43
+ if (size > limit) {
44
+ reject(new EnvelopeError({ code: 'gateway/bad-request', message: 'request body too large' }));
45
+ return;
46
+ }
47
+ chunks.push(buf);
48
+ });
49
+ carrier.on?.('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
50
+ carrier.on?.('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
51
+ });
52
+ }
53
+ /** Reject an upgrade on the raw socket (no ws ownership transfer). */
54
+ function rejectUpgrade(socket, status) {
55
+ const reason = status === 401 ? 'Unauthorized' : 'Forbidden';
56
+ const body = reason.toLowerCase();
57
+ const writable = socket;
58
+ writable.write?.(`HTTP/1.1 ${status} ${reason}\r\n`
59
+ + 'Connection: close\r\n'
60
+ + 'Content-Type: text/plain; charset=utf-8\r\n'
61
+ + `Content-Length: ${String(Buffer.byteLength(body, 'utf8'))}\r\n\r\n`
62
+ + body);
63
+ writable.destroy?.();
64
+ }
65
+ function writeJson(res, status, body) {
66
+ const carrier = res;
67
+ carrier.writeHead?.(status, { 'content-type': 'application/json' });
68
+ res.end(body);
69
+ }
70
+ /** Write a plain-text answer (the shape upstream's own 404/413 answers use). */
71
+ function writeText(res, status, body) {
72
+ const carrier = res;
73
+ carrier.writeHead?.(status, { 'content-type': 'text/plain; charset=utf-8' });
74
+ res.end(body);
75
+ }
76
+ /**
77
+ * Mount one world on the shared ctx0 webServer.
78
+ * @param options - see {@link MountWorldOptions}.
79
+ * @returns the live mount handle.
80
+ */
81
+ export function mountWorld(options) {
82
+ const labelPath = `/${options.label}`;
83
+ const apiPath = `${labelPath}/api`;
84
+ const muxPath = `${apiPath}/remote.mux`;
85
+ const maxBody = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
86
+ const disposers = [];
87
+ const forwardsFetchMethod = (req) => {
88
+ const method = req.method;
89
+ return method === 'GET' || method === 'HEAD';
90
+ };
91
+ /**
92
+ * Forward one non-envelope GET/HEAD request into the owning world's shared
93
+ * `/api` fetch handler (exact Fetch routes first, then the gateway's RPC
94
+ * interceptor, then 404 — the upstream composition). The ctx0 fence has
95
+ * already answered by the time we get here; the world's own browser-auth
96
+ * gate is never consulted (a different tree, a different secret).
97
+ */
98
+ const bridgeWorldFetch = async (req, res) => {
99
+ const worldFetch = worldFetchHandlerOf(options.label);
100
+ if (worldFetch === undefined) {
101
+ writeText(res, 404, 'not found');
102
+ return;
103
+ }
104
+ const method = (req.method ?? 'GET');
105
+ const resCarrier = res;
106
+ // Client-disconnect detection rides the response (same reason as the
107
+ // upstream node:http bridge: the request 'close' fires as soon as a
108
+ // bodyless GET is consumed).
109
+ const abort = new AbortController();
110
+ const onClose = () => {
111
+ if (resCarrier.writableEnded !== true)
112
+ abort.abort();
113
+ };
114
+ resCarrier.once?.('close', onClose);
115
+ let headersSent = false;
116
+ try {
117
+ const headers = {};
118
+ for (const [name, value] of Object.entries(req.headers ?? {})) {
119
+ if (typeof value === 'string')
120
+ headers[name.toLowerCase()] = value;
121
+ }
122
+ // The world route owns the world-relative URL: strip the mount label,
123
+ // keep the query (the session-export route reads it from searchParams).
124
+ const request = new Request(new URL(stripLabelFromUrl(req.url, labelPath), 'http://dsh.internal'), { method, headers, signal: abort.signal });
125
+ const response = await worldFetch.fetch(request);
126
+ res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
127
+ headersSent = true;
128
+ if (response.body === null) {
129
+ res.end();
130
+ return;
131
+ }
132
+ for await (const chunk of response.body) {
133
+ // Backpressure: wait for drain instead of buffering unboundedly; a
134
+ // socket close resolves the wait and the aborted signal ends the stream.
135
+ if (resCarrier.write?.(chunk) === false && typeof resCarrier.once === 'function') {
136
+ await new Promise((resolve) => {
137
+ const done = () => {
138
+ resCarrier.off?.('drain', done);
139
+ resCarrier.off?.('close', done);
140
+ resolve();
141
+ };
142
+ resCarrier.once?.('drain', done);
143
+ resCarrier.once?.('close', done);
144
+ });
145
+ }
146
+ }
147
+ res.end();
148
+ }
149
+ catch {
150
+ // The world route owns its statuses; only carrier-level faults land here.
151
+ if (!headersSent)
152
+ writeText(res, 500, 'carrier fetch bridge failure');
153
+ else
154
+ resCarrier.destroy?.();
155
+ }
156
+ finally {
157
+ resCarrier.off?.('close', onClose);
158
+ }
159
+ };
160
+ const handleUnary = async (req, res) => {
161
+ const rejection = options.requestRejection?.(req);
162
+ if (rejection !== undefined) {
163
+ writeJson(res, rejection, JSON.stringify({ error: rejection === 401 ? 'unauthorized' : 'forbidden' }));
164
+ return;
165
+ }
166
+ let rpcId = 'invalid-request';
167
+ try {
168
+ const text = await readBody(req, maxBody);
169
+ let envelope;
170
+ try {
171
+ envelope = parseClientRequest(text);
172
+ }
173
+ catch (error) {
174
+ // Non-envelope GET/HEAD traffic on the shared mount prefix belongs to
175
+ // the world's own registered fetch routes; envelopes (and every other
176
+ // method) keep the transport-level 400 below.
177
+ if (error instanceof EnvelopeError && forwardsFetchMethod(req)) {
178
+ await bridgeWorldFetch(req, res);
179
+ return;
180
+ }
181
+ throw error;
182
+ }
183
+ rpcId = envelope.rpcId;
184
+ const controller = new AbortController();
185
+ const result = await options.gateway.dispatchRpc(envelope.method, envelope.payload, controller.signal);
186
+ // The gateway already returns a Result (`{ok:true,value}` | `{ok:false,error}`),
187
+ // which is exactly the server-response's `result` field.
188
+ writeJson(res, 200, serverResult(rpcId, result));
189
+ }
190
+ catch (error) {
191
+ const wire = wireErrorOf(error);
192
+ // Envelope-level failures are transport-level 400s; dispatch failures are
193
+ // business results carried inside a 200 server-response (upstream shape).
194
+ if (error instanceof EnvelopeError) {
195
+ writeJson(res, 400, serverError(rpcId, wire));
196
+ return;
197
+ }
198
+ writeJson(res, 200, serverError(rpcId, wire));
199
+ }
200
+ };
201
+ disposers.push(options.real.register({
202
+ kind: 'prefix',
203
+ path: apiPath,
204
+ handler: handleUnary,
205
+ }));
206
+ // Mux: the world's own stream face behind the same mount prefix.
207
+ const mux = new WorldMuxServer((endpoint, payload, signal) => Promise.resolve(options.gateway.openWireStream(endpoint, payload, signal)), {
208
+ ...(options.heartbeatIntervalMs === undefined ? {} : { heartbeatIntervalMs: options.heartbeatIntervalMs }),
209
+ ...(options.failure === undefined ? {} : { failure: options.failure }),
210
+ });
211
+ disposers.push(options.real.registerUpgrade({
212
+ path: muxPath,
213
+ handler: ((req, socket, head) => {
214
+ const rejection = options.requestRejection?.(req);
215
+ if (rejection !== undefined) {
216
+ rejectUpgrade(socket, rejection === 401 ? 401 : 403);
217
+ return;
218
+ }
219
+ return mux.handleUpgrade(req, socket, head);
220
+ }),
221
+ }));
222
+ disposers.push(() => { void mux.close().catch(() => undefined); });
223
+ return {
224
+ labelPath,
225
+ apiPath,
226
+ muxPath,
227
+ dispose() {
228
+ for (const dispose of disposers.splice(0).reverse()) {
229
+ try {
230
+ dispose();
231
+ }
232
+ catch {
233
+ // teardown must not throw
234
+ }
235
+ }
236
+ },
237
+ };
238
+ }
239
+ // The mount HTML pass lives in its own module; re-exported here so the carrier
240
+ // remains the single import surface for mount behaviour.
241
+ export { rewriteIndexHtml } from './index-pass.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Client mount shim (AW-B DL8).
3
+ *
4
+ * ONE head script installs every client-side break point:
5
+ * - `__DSH_TRANSPORT__.fetch` → the Connection RPC fetch (URL object input)
6
+ * - `WebSocket` / `EventSource` / `XMLHttpRequest` constructors → path rewrite
7
+ * - `__DSH_FILE_UPLOAD__` → uploads bypass the blob worker + its XHR
8
+ * - Storage.prototype get/set/remove → per-world key namespace
9
+ * - `globalThis.fetch` → global-fetch calls asking for ctx0-root `/api`
10
+ * URLs (2026-09-17 H1: upstream session-log-export resolves the GLOBAL
11
+ * fetch at call time; narrow `/api`-only rule, query + hash preserved)
12
+ * - `HTMLAnchorElement.prototype.click` → download-anchor clicks re-rooted
13
+ * (2026-09-18 H1 r2: upstream hands the UNREWRITTEN absolute URL to a
14
+ * DETACHED `<a download>` and clicks it — detached clicks never reach
15
+ * document, so a document-level listener cannot see them, and a blanket
16
+ * rewrite breaks the runtime selector's root-absolute anchors
17
+ * (live-found 2026-09-18). Narrow rule: ONLY anchors carrying a
18
+ * `download` attribute AND a same-authority root-absolute `/api` href.)
19
+ * The rule is namespace-agnostic: a same-origin root-absolute URL is re-rooted
20
+ * under the mount (`/omp`), nothing else is touched. That covers every prefix
21
+ * a plugin may declare later — no list to keep in sync.
22
+ */
23
+ /**
24
+ * Render the shim as a classic script (parser-blocking head injection).
25
+ * @param labelPath - mount root, e.g. `/omp`.
26
+ * @returns JavaScript source.
27
+ */
28
+ export declare function renderClientShim(labelPath: string): string;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Client mount shim (AW-B DL8).
3
+ *
4
+ * ONE head script installs every client-side break point:
5
+ * - `__DSH_TRANSPORT__.fetch` → the Connection RPC fetch (URL object input)
6
+ * - `WebSocket` / `EventSource` / `XMLHttpRequest` constructors → path rewrite
7
+ * - `__DSH_FILE_UPLOAD__` → uploads bypass the blob worker + its XHR
8
+ * - Storage.prototype get/set/remove → per-world key namespace
9
+ * - `globalThis.fetch` → global-fetch calls asking for ctx0-root `/api`
10
+ * URLs (2026-09-17 H1: upstream session-log-export resolves the GLOBAL
11
+ * fetch at call time; narrow `/api`-only rule, query + hash preserved)
12
+ * - `HTMLAnchorElement.prototype.click` → download-anchor clicks re-rooted
13
+ * (2026-09-18 H1 r2: upstream hands the UNREWRITTEN absolute URL to a
14
+ * DETACHED `<a download>` and clicks it — detached clicks never reach
15
+ * document, so a document-level listener cannot see them, and a blanket
16
+ * rewrite breaks the runtime selector's root-absolute anchors
17
+ * (live-found 2026-09-18). Narrow rule: ONLY anchors carrying a
18
+ * `download` attribute AND a same-authority root-absolute `/api` href.)
19
+ * The rule is namespace-agnostic: a same-origin root-absolute URL is re-rooted
20
+ * under the mount (`/omp`), nothing else is touched. That covers every prefix
21
+ * a plugin may declare later — no list to keep in sync.
22
+ */
23
+ /** Escape a label for embedding in generated JavaScript. */
24
+ function js(value) {
25
+ return JSON.stringify(value);
26
+ }
27
+ /**
28
+ * Render the shim as a classic script (parser-blocking head injection).
29
+ * @param labelPath - mount root, e.g. `/omp`.
30
+ * @returns JavaScript source.
31
+ */
32
+ export function renderClientShim(labelPath) {
33
+ if (typeof labelPath !== 'string' || !labelPath.startsWith('/') || labelPath === '/' || labelPath.endsWith('/')) {
34
+ throw new Error(`client-shim: labelPath must start with "/" and carry no trailing slash, got ${JSON.stringify(labelPath)}`);
35
+ }
36
+ const prefix = `${labelPath}/`;
37
+ return `(() => {
38
+ const LABEL = ${js(labelPath)}
39
+ const PREFIX = ${js(prefix)}
40
+
41
+ /** Re-root one pathname when it is same-origin root-absolute and not mounted yet. */
42
+ const rewritePath = (path) => {
43
+ if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//')) return path
44
+ if (path === LABEL || path.startsWith(PREFIX)) return path
45
+ return PREFIX + path.slice(1)
46
+ }
47
+
48
+ const origin = () => {
49
+ const loc = globalThis.location
50
+ if (loc !== undefined && typeof loc.origin === 'string' && loc.origin !== 'null') return loc.origin
51
+ return 'http://dsh.internal'
52
+ }
53
+
54
+ /** Same authority as the page (scheme-agnostic: ws/wss/http/https share host). */
55
+ const sameAuthority = (url) => {
56
+ try {
57
+ return url.host === new URL(origin()).host
58
+ } catch {
59
+ return false
60
+ }
61
+ }
62
+
63
+ /** Rewrite a full URL (absolute or relative); other authorities are untouched. */
64
+ const rewriteUrl = (input) => {
65
+ if (typeof input === 'string') {
66
+ if (input.startsWith('/')) return rewritePath(input)
67
+ try {
68
+ const url = new URL(input, origin())
69
+ if (!sameAuthority(url)) return input
70
+ const next = rewritePath(url.pathname)
71
+ if (next === url.pathname) return input
72
+ url.pathname = next
73
+ return url.href
74
+ } catch {
75
+ return input
76
+ }
77
+ }
78
+ if (input !== null && typeof input === 'object' && typeof input.href === 'string') {
79
+ try {
80
+ const url = new URL(input.href)
81
+ if (!sameAuthority(url)) return input
82
+ const next = rewritePath(url.pathname)
83
+ if (next === url.pathname) return input
84
+ url.pathname = next
85
+ return url
86
+ } catch {
87
+ return input
88
+ }
89
+ }
90
+ return input
91
+ }
92
+
93
+ const nativeFetch = globalThis.fetch
94
+ const shimFetch = (input, init) => {
95
+ if (typeof nativeFetch !== 'function') throw new Error('client-shim: fetch is unavailable')
96
+ if (typeof Request === 'function' && input instanceof Request) {
97
+ const url = rewriteUrl(input.url)
98
+ return nativeFetch(url === input.url ? input : new Request(url, input), init)
99
+ }
100
+ return nativeFetch(rewriteUrl(input), init)
101
+ }
102
+
103
+ const transport = (globalThis.__DSH_TRANSPORT__ = globalThis.__DSH_TRANSPORT__ || {})
104
+ transport.rewrite = rewritePath
105
+ transport.fetch = shimFetch
106
+
107
+ // Construct through a Proxy, never a hand-rolled wrapper function: the
108
+ // upstream client compares socket.readyState against WebSocket.OPEN, so the
109
+ // native statics (CONNECTING/OPEN/CLOSING/CLOSED), prototype and instanceof
110
+ // must survive the interception.
111
+ const NativeWebSocket = globalThis.WebSocket
112
+ if (typeof NativeWebSocket === 'function') {
113
+ globalThis.WebSocket = new Proxy(NativeWebSocket, {
114
+ construct: (Target, args) => new Target(rewriteUrl(args[0]), ...args.slice(1)),
115
+ })
116
+ }
117
+
118
+ const NativeEventSource = globalThis.EventSource
119
+
120
+ if (typeof NativeEventSource === 'function') {
121
+ globalThis.EventSource = new Proxy(NativeEventSource, {
122
+ construct: (Target, args) => new Target(rewriteUrl(args[0]), ...args.slice(1)),
123
+ })
124
+ }
125
+
126
+ const NativeXhr = globalThis.XMLHttpRequest
127
+ if (typeof NativeXhr === 'function' && NativeXhr.prototype !== undefined && typeof NativeXhr.prototype.open === 'function') {
128
+ const nativeOpen = NativeXhr.prototype.open
129
+ NativeXhr.prototype.open = function (method, url, ...rest) {
130
+ return nativeOpen.call(this, method, rewriteUrl(url), ...rest)
131
+ }
132
+ }
133
+
134
+ // Read by the file-upload service at construction time: present ⇒ the page
135
+ // fetch carries uploads (no blob worker, and therefore no worker-scope XHR).
136
+ globalThis.__DSH_FILE_UPLOAD__ = { fetch: shimFetch }
137
+
138
+ // Narrow '/api' re-root helpers (2026-09-17 session-log 404, H1): upstream
139
+ // client code (session-log-export) resolves the GLOBAL fetch at call time
140
+ // and hands absolute ctx0-root URLs both to fetch and to a DETACHED
141
+ // download anchor — neither is visible to the transport faces above.
142
+ // Narrow rule: only root-absolute '/api' paths move under the mount;
143
+ // relative, same-mount, and foreign URLs pass through untouched. Query and
144
+ // hash survive because only the pathname is rewritten. The mount root comes
145
+ // from this shim's argument, never from location.pathname. NOTE: the '/api'
146
+ // literals below stay SINGLE-quoted on purpose — index-pass.ts rewrites
147
+ // every DOUBLE-quoted root-absolute string in the served HTML, and a
148
+ // doubled literal would silently disable these rewrites (regression tests
149
+ // survive the mount HTML pass).
150
+ const rewriteApiPath = (path) => {
151
+ if (path === '/api' || path.startsWith('/api/')) return PREFIX + path.slice(1)
152
+ return path
153
+ }
154
+ const rewriteApiUrl = (input) => {
155
+ if (typeof input === 'string') {
156
+ if (input.startsWith('/') && !input.startsWith('//')) return rewriteApiPath(input)
157
+ try {
158
+ const url = new URL(input, origin())
159
+ if (!sameAuthority(url)) return input
160
+ const next = rewriteApiPath(url.pathname)
161
+ if (next === url.pathname) return input
162
+ url.pathname = next
163
+ return url.href
164
+ } catch {
165
+ return input
166
+ }
167
+ }
168
+ if (input !== null && typeof input === 'object' && typeof input.href === 'string') {
169
+ try {
170
+ const url = new URL(input.href)
171
+ if (!sameAuthority(url)) return input
172
+ const next = rewriteApiPath(url.pathname)
173
+ if (next === url.pathname) return input
174
+ url.pathname = next
175
+ return url
176
+ } catch {
177
+ return input
178
+ }
179
+ }
180
+ return input
181
+ }
182
+ if (typeof nativeFetch === 'function' && globalThis.__DSH_FETCH_REWRITE__ !== true) {
183
+ globalThis.__DSH_FETCH_REWRITE__ = true
184
+ globalThis.fetch = (input, init) => nativeFetch(rewriteApiUrl(input), init)
185
+ }
186
+
187
+ // Download anchors (2026-09-18 H1 r2): upstream session-log-export hands
188
+ // the UNREWRITTEN absolute URL to a DETACHED '<a download>' element and
189
+ // calls .click() — detached clicks never propagate to document, so the r1
190
+ // document-level capture listener could not see them, while its BLANKET
191
+ // rewrite re-rooted the runtime selector's root-absolute anchors and broke
192
+ // cross-world navigation (live-found 2026-09-18). Narrow seam: wrap
193
+ // HTMLAnchorElement.prototype.click itself and rewrite ONLY anchors that
194
+ // carry a 'download' attribute AND a same-authority root-absolute '/api'
195
+ // href. Everything else — the runtime selector's links included — passes
196
+ // through untouched.
197
+ if (globalThis.HTMLAnchorElement !== undefined
198
+ && globalThis.HTMLAnchorElement.prototype !== undefined
199
+ && typeof globalThis.HTMLAnchorElement.prototype.click === 'function'
200
+ && globalThis.__DSH_ANCHOR_CLICK_REWRITE__ !== true) {
201
+ globalThis.__DSH_ANCHOR_CLICK_REWRITE__ = true
202
+ const nativeAnchorClick = globalThis.HTMLAnchorElement.prototype.click
203
+ globalThis.HTMLAnchorElement.prototype.click = function (...args) {
204
+ try {
205
+ if (this.hasAttribute('download')) {
206
+ const current = this.getAttribute('href')
207
+ if (typeof current === 'string' && current !== '') {
208
+ const next = rewriteApiUrl(current)
209
+ if (typeof next === 'string' && next !== current) this.setAttribute('href', next)
210
+ }
211
+ }
212
+ } catch {
213
+ /* best-effort: never break the click itself */
214
+ }
215
+ return nativeAnchorClick.apply(this, args)
216
+ }
217
+ }
218
+
219
+ // Storage namespace (2026-09-16 storage findings P1): all worlds share one
220
+
221
+ // origin, so bare localStorage keys would overwrite each other across
222
+ // mounts. Prefix every key with the label; root '/' never injects this
223
+ // shim and keeps bare keys. Never throws: attachPersistence only
224
+ // console-errors on failure, so a throwing shim would silently disable
225
+ // persistence.
226
+ try {
227
+ const StorageProto = globalThis.Storage && globalThis.Storage.prototype
228
+ if (globalThis.localStorage !== undefined && StorageProto !== undefined
229
+ && !globalThis.__DSH_STORAGE_NS__
230
+ && typeof StorageProto.getItem === 'function'
231
+ && typeof StorageProto.setItem === 'function'
232
+ && typeof StorageProto.removeItem === 'function') {
233
+ // NS derives from PREFIX, never LABEL: the mount HTML pass (index-pass)
234
+ // rewrites every quoted root-absolute string EXCEPT "<label>/"-prefixed
235
+ // ones, so the shim's LABEL="/omp" literal doubles to "/omp/omp" while
236
+ // PREFIX="/omp/" is immune (live-found 2026-09-17, acceptance A2/A5).
237
+ // The NS-from-PREFIX coupling is DELIBERATE: index-pass.ts rewrites double-quoted root-absolute strings except "<label>/"-prefixed ones, so LABEL="/omp" doubles while PREFIX="/omp/" survives; do NOT derive NS from LABEL.
238
+ const NS = PREFIX.slice(1, -1) + ':'
239
+ globalThis.__DSH_STORAGE_NS__ = NS
240
+ const nativeGetItem = StorageProto.getItem
241
+ const nativeSetItem = StorageProto.setItem
242
+ const nativeRemoveItem = StorageProto.removeItem
243
+ StorageProto.getItem = function (name) { return nativeGetItem.call(this, NS + name) }
244
+ StorageProto.setItem = function (name, value) { return nativeSetItem.call(this, NS + name, value) }
245
+ StorageProto.removeItem = function (name) { return nativeRemoveItem.call(this, NS + name) }
246
+ }
247
+ } catch {
248
+ /* storage namespace is best-effort: never break the shim */
249
+ }
250
+ })()`;
251
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Connection unary envelope (AW-B DL5).
3
+ *
4
+ * Our carrier owns `/<label>/api/*` and therefore must speak the wire shape
5
+ * itself. The shape is fixed by upstream (`client/connection/src/rpc-host.ts`):
6
+ *
7
+ * request { type: 'client-request', rpcId, method, payload }
8
+ * response { type: 'server-response', rpcId, result: { ok: true, value } }
9
+ * { type: 'server-response', rpcId, result: { ok: false, error } }
10
+ *
11
+ * Endpoint segments follow upstream's pattern so a crafted path cannot smuggle
12
+ * anything the native route would have rejected.
13
+ */
14
+ /** One decoded client request. */
15
+ export interface ClientRequestEnvelope {
16
+ /** Correlates the response; echoed verbatim. */
17
+ rpcId: string;
18
+ /** Typert endpoint (the gateway's dispatch key). */
19
+ method: string;
20
+ /** Invocation payload. */
21
+ payload: unknown;
22
+ }
23
+ /** One wire error body. */
24
+ export interface WireError {
25
+ code: string;
26
+ message: string;
27
+ details?: unknown;
28
+ }
29
+ /** Upstream's endpoint segment pattern (`rpc-host.ts`). */
30
+ export declare const ENDPOINT_SEGMENT_PATTERN: RegExp;
31
+ /** Envelope-level failure carrying the wire error to answer with. */
32
+ export declare class EnvelopeError extends Error {
33
+ readonly wire: WireError;
34
+ constructor(wire: WireError);
35
+ }
36
+ /**
37
+ * Decode one request body.
38
+ * @param text - raw body text.
39
+ * @returns the decoded envelope.
40
+ * @throws {EnvelopeError} when the body is not a valid client-request.
41
+ */
42
+ export declare function parseClientRequest(text: string): ClientRequestEnvelope;
43
+ /** Serialize a successful server response from a raw value. */
44
+ export declare function serverResponse(rpcId: string, value: unknown): string;
45
+ /**
46
+ * Serialize a server response whose `result` is already a gateway Result
47
+ * (`{ ok: true, value }` | `{ ok: false, error }`). The typert gateway's
48
+ * `dispatchRpc` returns exactly that shape, so it is copied verbatim.
49
+ */
50
+ export declare function serverResult(rpcId: string, result: unknown): string;
51
+ /** Serialize a failed server response. */
52
+ export declare function serverError(rpcId: string, error: WireError): string;
53
+ /** Normalize an unknown thrown value into a wire error. */
54
+ export declare function wireErrorOf(error: unknown): WireError;