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.
- package/agent-agy/bridge/agy_bridge.py +250 -0
- package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
- package/agent-agy/cordis.patch.yml +49 -0
- package/agent-agy/dist/adapter.d.ts +15 -0
- package/agent-agy/dist/adapter.js +42 -0
- package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
- package/agent-agy/dist/agent-preset-agy.js +169 -0
- package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
- package/agent-agy/dist/agent-preset-projection.js +33 -0
- package/agent-agy/dist/agent.d.ts +137 -0
- package/agent-agy/dist/agent.js +493 -0
- package/agent-agy/dist/agy-cli-client.d.ts +137 -0
- package/agent-agy/dist/agy-cli-client.js +301 -0
- package/agent-agy/dist/agy-client.d.ts +160 -0
- package/agent-agy/dist/agy-client.js +252 -0
- package/agent-agy/dist/agy-sessions.d.ts +24 -0
- package/agent-agy/dist/agy-sessions.js +116 -0
- package/agent-agy/dist/agy-store.d.ts +39 -0
- package/agent-agy/dist/agy-store.js +83 -0
- package/agent-agy/dist/inbox.d.ts +26 -0
- package/agent-agy/dist/inbox.js +53 -0
- package/agent-agy/dist/index.d.ts +80 -0
- package/agent-agy/dist/index.js +505 -0
- package/agent-agy/dist/models.d.ts +41 -0
- package/agent-agy/dist/models.js +74 -0
- package/agent-agy/dist/world-plugin.d.ts +9 -0
- package/agent-agy/dist/world-plugin.js +97 -0
- package/agent-agy/package.json +64 -0
- package/agent-claude/cordis.patch.yml +42 -0
- package/agent-claude/dist/adapter.js +62 -0
- package/agent-claude/dist/agent-preset-claude.js +169 -0
- package/agent-claude/dist/agent-preset-projection.js +30 -0
- package/agent-claude/dist/agent.js +1271 -0
- package/agent-claude/dist/claude-client.js +357 -0
- package/agent-claude/dist/claude-events.js +223 -0
- package/agent-claude/dist/claude-home.js +29 -0
- package/agent-claude/dist/commands.js +182 -0
- package/agent-claude/dist/content.js +134 -0
- package/agent-claude/dist/inbox.js +53 -0
- package/agent-claude/dist/index.js +555 -0
- package/agent-claude/dist/input-queue.js +64 -0
- package/agent-claude/dist/interaction.js +271 -0
- package/agent-claude/dist/models.js +158 -0
- package/agent-claude/dist/permission.js +120 -0
- package/agent-claude/dist/session-id.js +20 -0
- package/agent-claude/dist/session-map.js +165 -0
- package/agent-claude/dist/world-plugin.js +14 -0
- package/agent-claude/package.json +74 -0
- package/agent-codex/README.md +74 -0
- package/agent-codex/cordis.patch.yml +77 -0
- package/agent-codex/dist/adapter.js +55 -0
- package/agent-codex/dist/agent-preset-codex.js +171 -0
- package/agent-codex/dist/agent-preset-projection.js +37 -0
- package/agent-codex/dist/agent.js +1056 -0
- package/agent-codex/dist/app-home.js +65 -0
- package/agent-codex/dist/codex-client.js +469 -0
- package/agent-codex/dist/codex-events.js +297 -0
- package/agent-codex/dist/codex-store.js +190 -0
- package/agent-codex/dist/inbox.js +53 -0
- package/agent-codex/dist/index.js +605 -0
- package/agent-codex/dist/models.js +152 -0
- package/agent-codex/dist/permission.js +71 -0
- package/agent-codex/dist/session-map.js +170 -0
- package/agent-codex/dist/spike.js +46 -0
- package/agent-codex/dist/world-plugin.js +96 -0
- package/agent-codex/package.json +78 -0
- package/agent-hermes/README.md +144 -0
- package/agent-hermes/cordis.patch.yml +83 -0
- package/agent-hermes/dist/adapter.js +83 -0
- package/agent-hermes/dist/agent-preset-hermes.js +171 -0
- package/agent-hermes/dist/agent-preset-projection.js +34 -0
- package/agent-hermes/dist/agent.js +1068 -0
- package/agent-hermes/dist/hermes-client.js +953 -0
- package/agent-hermes/dist/hermes-events.js +289 -0
- package/agent-hermes/dist/hermes-store.js +158 -0
- package/agent-hermes/dist/inbox.js +53 -0
- package/agent-hermes/dist/index.js +541 -0
- package/agent-hermes/dist/models.js +324 -0
- package/agent-hermes/dist/permission.js +60 -0
- package/agent-hermes/dist/world-plugin.js +101 -0
- package/agent-hermes/package.json +76 -0
- package/agent-hub/cordis.patch.yml +12 -0
- package/agent-hub/dist/agent-roster.d.ts +34 -0
- package/agent-hub/dist/agent-roster.js +27 -0
- package/agent-hub/dist/carrier.d.ts +43 -0
- package/agent-hub/dist/carrier.js +241 -0
- package/agent-hub/dist/client-shim.d.ts +28 -0
- package/agent-hub/dist/client-shim.js +251 -0
- package/agent-hub/dist/envelope.d.ts +54 -0
- package/agent-hub/dist/envelope.js +92 -0
- package/agent-hub/dist/gateway.d.ts +28 -0
- package/agent-hub/dist/gateway.js +18 -0
- package/agent-hub/dist/index-pass.d.ts +22 -0
- package/agent-hub/dist/index-pass.js +47 -0
- package/agent-hub/dist/index.d.ts +29 -0
- package/agent-hub/dist/index.js +29 -0
- package/agent-hub/dist/labels.d.ts +45 -0
- package/agent-hub/dist/labels.js +83 -0
- package/agent-hub/dist/ownership.d.ts +37 -0
- package/agent-hub/dist/ownership.js +54 -0
- package/agent-hub/dist/roster.d.ts +28 -0
- package/agent-hub/dist/roster.js +38 -0
- package/agent-hub/dist/rpc.d.ts +38 -0
- package/agent-hub/dist/rpc.js +52 -0
- package/agent-hub/dist/spawn-world.d.ts +46 -0
- package/agent-hub/dist/spawn-world.js +72 -0
- package/agent-hub/dist/targets.d.ts +33 -0
- package/agent-hub/dist/targets.js +30 -0
- package/agent-hub/dist/world-entry.d.ts +31 -0
- package/agent-hub/dist/world-entry.js +51 -0
- package/agent-hub/dist/world-host.d.ts +69 -0
- package/agent-hub/dist/world-host.js +44 -0
- package/agent-hub/dist/world-join.d.ts +16 -0
- package/agent-hub/dist/world-join.js +122 -0
- package/agent-hub/dist/world-mount.d.ts +52 -0
- package/agent-hub/dist/world-mount.js +52 -0
- package/agent-hub/dist/world-mux.d.ts +72 -0
- package/agent-hub/dist/world-mux.js +224 -0
- package/agent-hub/dist/world-provision.d.ts +20 -0
- package/agent-hub/dist/world-provision.js +122 -0
- package/agent-hub/dist/world-web-server.d.ts +162 -0
- package/agent-hub/dist/world-web-server.js +248 -0
- package/agent-hub/lib/client/index.js +242 -0
- package/agent-hub/package.json +65 -0
- package/agent-omp/README.md +77 -0
- package/agent-omp/cordis.patch.yml +92 -0
- package/agent-omp/dist/adapter.js +36 -0
- package/agent-omp/dist/agent-preset-omp.js +171 -0
- package/agent-omp/dist/agent-preset-projection.js +36 -0
- package/agent-omp/dist/agent.js +1145 -0
- package/agent-omp/dist/app-home.js +86 -0
- package/agent-omp/dist/discovery.js +116 -0
- package/agent-omp/dist/inbox.js +53 -0
- package/agent-omp/dist/index.js +574 -0
- package/agent-omp/dist/knobs.js +50 -0
- package/agent-omp/dist/lazy-rpc.js +161 -0
- package/agent-omp/dist/mobile/gesture.js +148 -0
- package/agent-omp/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp/dist/mobile-boot.js +38 -0
- package/agent-omp/dist/models.js +299 -0
- package/agent-omp/dist/omp-cli.js +147 -0
- package/agent-omp/dist/omp-disk-discovery.js +178 -0
- package/agent-omp/dist/omp-store.js +518 -0
- package/agent-omp/dist/pairing.js +45 -0
- package/agent-omp/dist/permission.js +96 -0
- package/agent-omp/dist/protocol.js +13 -0
- package/agent-omp/dist/rpc-types.js +1 -0
- package/agent-omp/dist/sdk-client.js +378 -0
- package/agent-omp/dist/sidecar-client.js +195 -0
- package/agent-omp/dist/store/adapter.js +47 -0
- package/agent-omp/dist/store/db.js +210 -0
- package/agent-omp/dist/store/index.js +59 -0
- package/agent-omp/dist/store/reconcile.js +51 -0
- package/agent-omp/dist/store/schema.js +40 -0
- package/agent-omp/dist/world-plugin.js +96 -0
- package/agent-omp/package.json +97 -0
- package/agent-omp/sidecar/main.ts +481 -0
- package/agent-pi/README.md +67 -0
- package/agent-pi/cordis.patch.yml +80 -0
- package/agent-pi/dist/adapter.js +53 -0
- package/agent-pi/dist/agent-preset-pi.js +169 -0
- package/agent-pi/dist/agent-preset-projection.js +29 -0
- package/agent-pi/dist/agent.js +949 -0
- package/agent-pi/dist/inbox.js +53 -0
- package/agent-pi/dist/index.js +595 -0
- package/agent-pi/dist/knobs.js +24 -0
- package/agent-pi/dist/models.js +200 -0
- package/agent-pi/dist/permission.js +66 -0
- package/agent-pi/dist/pi-client.js +224 -0
- package/agent-pi/dist/pi-events.js +155 -0
- package/agent-pi/dist/pi-home.js +55 -0
- package/agent-pi/dist/safe-json.js +25 -0
- package/agent-pi/dist/session-map.js +119 -0
- package/agent-pi/dist/world-plugin.js +97 -0
- package/agent-pi/package.json +77 -0
- package/cordis.patch.yml +22 -19
- package/package.json +88 -25
- 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;
|