super-dsh 0.1.6 → 0.1.10
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/README.md +25 -3
- package/agent-agy/cordis.patch.yml +7 -4
- package/agent-agy/dist/agent.d.ts +12 -11
- package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
- package/agent-agy/dist/agy-auth-gateway.js +233 -0
- package/agent-agy/dist/agy-auth.d.ts +153 -0
- package/agent-agy/dist/agy-auth.js +179 -0
- package/agent-agy/dist/agy-cli-client.d.ts +39 -9
- package/agent-agy/dist/agy-cli-client.js +82 -18
- package/agent-agy/dist/agy-store.d.ts +8 -0
- package/agent-agy/dist/index.d.ts +46 -16
- package/agent-agy/dist/index.js +82 -56
- package/agent-agy/dist/models.d.ts +70 -0
- package/agent-agy/dist/models.js +102 -3
- package/agent-agy/package.json +7 -5
- package/agent-agy-sdk/cordis.patch.yml +58 -0
- package/agent-agy-sdk/dist/adapter.d.ts +15 -0
- package/agent-agy-sdk/dist/adapter.js +42 -0
- package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
- package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
- package/agent-agy-sdk/dist/agent.d.ts +138 -0
- package/agent-agy-sdk/dist/agent.js +494 -0
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
- package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
- package/agent-agy-sdk/dist/agy-sessions.js +107 -0
- package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
- package/agent-agy-sdk/dist/agy-store.js +83 -0
- package/agent-agy-sdk/dist/inbox.d.ts +26 -0
- package/agent-agy-sdk/dist/inbox.js +53 -0
- package/agent-agy-sdk/dist/index.d.ts +93 -0
- package/agent-agy-sdk/dist/index.js +476 -0
- package/agent-agy-sdk/dist/models.d.ts +41 -0
- package/agent-agy-sdk/dist/models.js +74 -0
- package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
- package/agent-agy-sdk/dist/sdk-venv.js +173 -0
- package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-agy-sdk/dist/world-plugin.js +82 -0
- package/agent-agy-sdk/package.json +64 -0
- package/agent-codex/README.md +141 -36
- package/agent-codex/cordis.patch.yml +14 -10
- package/agent-codex/dist/adapter.js +64 -28
- package/agent-codex/dist/agent.js +255 -81
- package/agent-codex/dist/app-server-approvals.js +165 -0
- package/agent-codex/dist/app-server-client.js +723 -0
- package/agent-codex/dist/app-server-events.js +432 -0
- package/agent-codex/dist/app-server-protocol.js +95 -0
- package/agent-codex/dist/app-server-wire.js +299 -0
- package/agent-codex/dist/codex-launch.js +78 -0
- package/agent-codex/dist/codex-native-config.js +392 -0
- package/agent-codex/dist/codex-provider-gateway.js +402 -0
- package/agent-codex/dist/codex-providers.js +376 -0
- package/agent-codex/dist/codex-route.js +86 -0
- package/agent-codex/dist/index.js +256 -115
- package/agent-codex/dist/permission.js +75 -1
- package/agent-codex/package.json +1 -2
- package/agent-codex-sdk/README.md +79 -0
- package/agent-codex-sdk/cordis.patch.yml +81 -0
- package/agent-codex-sdk/dist/adapter.js +91 -0
- package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
- package/agent-codex-sdk/dist/agent.js +1085 -0
- package/agent-codex-sdk/dist/codex-native-config.js +392 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
- package/agent-codex-sdk/dist/codex-providers.js +376 -0
- package/agent-codex-sdk/dist/codex-route.js +86 -0
- package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
- package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
- package/agent-codex-sdk/dist/codex-store.js +180 -0
- package/agent-codex-sdk/dist/inbox.js +53 -0
- package/agent-codex-sdk/dist/index.js +739 -0
- package/agent-codex-sdk/dist/permission.js +71 -0
- package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
- package/agent-codex-sdk/dist/session-map.js +167 -0
- package/agent-codex-sdk/dist/world-plugin.js +93 -0
- package/agent-codex-sdk/package.json +79 -0
- package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
- package/agent-omp/README.md +72 -55
- package/agent-omp/dist/agent.js +36 -11
- package/agent-omp/dist/index.js +92 -52
- package/agent-omp/dist/lazy-rpc.js +11 -4
- package/agent-omp/dist/omp-cli.js +114 -97
- package/agent-omp/dist/omp-store.js +12 -4
- package/agent-omp/dist/permission.js +70 -5
- package/agent-omp/dist/rpc-client.js +409 -0
- package/agent-omp/dist/world-plugin.js +36 -3
- package/agent-omp/package.json +2 -6
- package/agent-omp-sdk/README.md +103 -0
- package/agent-omp-sdk/cordis.patch.yml +108 -0
- package/agent-omp-sdk/dist/adapter.d.ts +21 -0
- package/agent-omp-sdk/dist/adapter.js +36 -0
- package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
- package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
- package/agent-omp-sdk/dist/agent.d.ts +64 -0
- package/agent-omp-sdk/dist/agent.js +1178 -0
- package/agent-omp-sdk/dist/discovery.d.ts +57 -0
- package/agent-omp-sdk/dist/discovery.js +116 -0
- package/agent-omp-sdk/dist/inbox.d.ts +28 -0
- package/agent-omp-sdk/dist/inbox.js +53 -0
- package/agent-omp-sdk/dist/index.d.ts +60 -0
- package/agent-omp-sdk/dist/index.js +632 -0
- package/agent-omp-sdk/dist/knobs.d.ts +34 -0
- package/agent-omp-sdk/dist/knobs.js +50 -0
- package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
- package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
- package/agent-omp-sdk/dist/mobile-boot.js +38 -0
- package/agent-omp-sdk/dist/models.d.ts +39 -0
- package/agent-omp-sdk/dist/models.js +299 -0
- package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
- package/agent-omp-sdk/dist/omp-cli.js +164 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
- package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
- package/agent-omp-sdk/dist/omp-executable.js +30 -0
- package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
- package/agent-omp-sdk/dist/omp-store.js +526 -0
- package/agent-omp-sdk/dist/pairing.d.ts +7 -0
- package/agent-omp-sdk/dist/pairing.js +45 -0
- package/agent-omp-sdk/dist/permission.d.ts +138 -0
- package/agent-omp-sdk/dist/permission.js +161 -0
- package/agent-omp-sdk/dist/protocol.d.ts +189 -0
- package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
- package/agent-omp-sdk/dist/rpc-types.js +1 -0
- package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
- package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
- package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
- package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
- package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
- package/agent-omp-sdk/dist/sidecar-client.js +197 -0
- package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
- package/agent-omp-sdk/dist/store/adapter.js +47 -0
- package/agent-omp-sdk/dist/store/db.d.ts +70 -0
- package/agent-omp-sdk/dist/store/db.js +210 -0
- package/agent-omp-sdk/dist/store/index.d.ts +23 -0
- package/agent-omp-sdk/dist/store/index.js +59 -0
- package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
- package/agent-omp-sdk/dist/store/reconcile.js +51 -0
- package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
- package/agent-omp-sdk/dist/store/schema.js +40 -0
- package/agent-omp-sdk/dist/transport.d.ts +49 -0
- package/agent-omp-sdk/dist/transport.js +35 -0
- package/agent-omp-sdk/dist/world-home.d.ts +4 -0
- package/agent-omp-sdk/dist/world-home.js +23 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-omp-sdk/dist/world-plugin.js +137 -0
- package/agent-omp-sdk/package.json +87 -0
- package/client/index.js +1794 -287
- package/cordis.patch.yml +80 -0
- package/package.json +23 -8
- package/superd-hub/dist/a2a-delivery.d.ts +189 -0
- package/superd-hub/dist/a2a-delivery.js +240 -0
- package/superd-hub/dist/a2a-host.d.ts +76 -0
- package/superd-hub/dist/a2a-host.js +145 -0
- package/superd-hub/dist/a2a-ingest.d.ts +31 -0
- package/superd-hub/dist/a2a-ingest.js +636 -0
- package/superd-hub/dist/a2a-log.d.ts +17 -0
- package/superd-hub/dist/a2a-log.js +42 -0
- package/superd-hub/dist/a2a-row.d.ts +65 -0
- package/superd-hub/dist/a2a-row.js +162 -0
- package/superd-hub/dist/a2a-service.d.ts +357 -0
- package/superd-hub/dist/a2a-service.js +980 -0
- package/superd-hub/dist/a2a-wire.d.ts +220 -0
- package/superd-hub/dist/a2a-wire.js +305 -0
- package/superd-hub/dist/agent-ids.d.ts +10 -0
- package/superd-hub/dist/agent-ids.js +68 -0
- package/superd-hub/dist/agent-roster.d.ts +29 -18
- package/superd-hub/dist/agent-roster.js +54 -15
- package/superd-hub/dist/agent-status.d.ts +119 -0
- package/superd-hub/dist/agent-status.js +386 -0
- package/superd-hub/dist/client/agent-settings.d.ts +156 -0
- package/superd-hub/dist/client/agent-settings.js +229 -0
- package/superd-hub/dist/client/mesh-api.d.ts +134 -0
- package/superd-hub/dist/client/mesh-api.js +219 -0
- package/superd-hub/dist/client/mesh-groups.d.ts +63 -0
- package/superd-hub/dist/client/mesh-groups.js +66 -0
- package/superd-hub/dist/client/roster.d.ts +48 -0
- package/superd-hub/dist/client/roster.js +102 -0
- package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
- package/superd-hub/dist/client/runtime-settings-api.js +137 -0
- package/superd-hub/dist/client/switch.d.ts +165 -0
- package/superd-hub/dist/client/switch.js +295 -0
- package/superd-hub/dist/exec-pty-channel.d.ts +58 -0
- package/superd-hub/dist/exec-pty-channel.js +162 -0
- package/superd-hub/dist/exec-pty-framing.d.ts +32 -0
- package/superd-hub/dist/exec-pty-framing.js +81 -0
- package/superd-hub/dist/exec-pty-rpc.d.ts +21 -0
- package/superd-hub/dist/exec-pty-rpc.js +128 -0
- package/superd-hub/dist/exec-pty-session.d.ts +76 -0
- package/superd-hub/dist/exec-pty-session.js +234 -0
- package/superd-hub/dist/exec.d.ts +56 -0
- package/superd-hub/dist/exec.js +225 -0
- package/superd-hub/dist/gateway.d.ts +39 -2
- package/superd-hub/dist/gateway.js +62 -3
- package/superd-hub/dist/hub-tool.d.ts +174 -0
- package/superd-hub/dist/hub-tool.js +464 -0
- package/superd-hub/dist/index.d.ts +15 -1
- package/superd-hub/dist/index.js +16 -0
- package/superd-hub/dist/machines.d.ts +45 -0
- package/superd-hub/dist/machines.js +125 -0
- package/superd-hub/dist/mesh-federate.d.ts +138 -0
- package/superd-hub/dist/mesh-federate.js +223 -0
- package/superd-hub/dist/mesh-gateway.d.ts +67 -0
- package/superd-hub/dist/mesh-gateway.js +408 -0
- package/superd-hub/dist/mesh-link.d.ts +274 -0
- package/superd-hub/dist/mesh-link.js +441 -0
- package/superd-hub/dist/mesh-rpc.d.ts +123 -0
- package/superd-hub/dist/mesh-rpc.js +250 -0
- package/superd-hub/dist/mux-client.d.ts +71 -0
- package/superd-hub/dist/mux-client.js +281 -0
- package/superd-hub/dist/pack-version.d.ts +2 -0
- package/superd-hub/dist/pack-version.js +30 -0
- package/superd-hub/dist/roster.d.ts +6 -0
- package/superd-hub/dist/rpc.d.ts +18 -2
- package/superd-hub/dist/rpc.js +34 -4
- package/superd-hub/dist/spawn-world.js +3 -1
- package/superd-hub/dist/switch-core.d.ts +62 -25
- package/superd-hub/dist/switch-core.js +53 -30
- package/superd-hub/dist/system-head.d.ts +22 -12
- package/superd-hub/dist/system-head.js +100 -84
- package/superd-hub/dist/wire-endpoints.d.ts +264 -0
- package/superd-hub/dist/wire-endpoints.js +180 -0
- package/superd-hub/lib/client/index.js +1794 -287
- package/superd-hub/package.json +11 -2
- package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
- package/agent-agy/dist/agent-preset-projection.js +0 -33
- package/agent-claude/dist/agent-preset-projection.js +0 -30
- package/agent-codex/dist/agent-preset-projection.js +0 -37
- package/agent-codex/dist/app-home.js +0 -65
- package/agent-codex/dist/models.js +0 -152
- package/agent-codex/dist/spike.js +0 -46
- package/agent-hermes/dist/agent-preset-projection.js +0 -34
- package/agent-omp/dist/agent-preset-projection.js +0 -36
- package/agent-omp/dist/app-home.js +0 -86
- package/agent-omp/dist/sidecar-client.js +0 -195
- package/agent-pi/dist/agent-preset-projection.js +0 -29
- package/superd-hub/dist/client-shim.d.ts +0 -28
- package/superd-hub/dist/client-shim.js +0 -251
- package/superd-hub/dist/index-pass.d.ts +0 -22
- package/superd-hub/dist/index-pass.js +0 -47
- package/superd-hub/dist/labels.d.ts +0 -45
- package/superd-hub/dist/labels.js +0 -83
- /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
- /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
- /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
- /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
- /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { MuxClientSocket } from './mux-client.js';
|
|
2
|
+
import type { GatewayFace } from './targets.js';
|
|
3
|
+
export type { MuxClientSocket } from './mux-client.js';
|
|
4
|
+
/**
|
|
5
|
+
* The default ws seam: connect one `ws` WebSocket to `url` with `headers`
|
|
6
|
+
* riding the upgrade request (the pairing Cookie), presented as T1's
|
|
7
|
+
* {@link MuxClientSocket}.
|
|
8
|
+
*
|
|
9
|
+
* The adapter is crash-proof by construction (fix round 1):
|
|
10
|
+
* - an `'error'` listener is ALWAYS registered: an unreachable peer
|
|
11
|
+
* (ECONNREFUSED) or a rejected upgrade (non-101, e.g. a dead cookie's
|
|
12
|
+
* HTTP 401) emits `'error'` with no listener of its own — an uncaught
|
|
13
|
+
* exception that kills the hub process. The listener is a no-op: ws
|
|
14
|
+
* guarantees `'close'` follows `'error'`, and the close path already
|
|
15
|
+
* classifies (in-flight streams fail `gateway/socket-closed`) and retires
|
|
16
|
+
* the connection for redial. WS-lane auth failure therefore classifies
|
|
17
|
+
* as a socket cut post-fix — acceptable: the next POST-lane call is the
|
|
18
|
+
* one that reports `gateway/auth-expired`.
|
|
19
|
+
* - `ws` THROWS on sends made while CONNECTING, and its post-close send
|
|
20
|
+
* bookkeeping can itself throw in teardown windows, so every send is
|
|
21
|
+
* guarded: pre-open sends queue (flushed in order on 'open'), post-close
|
|
22
|
+
* sends are swallowed — the stream is dead and the close path owns the
|
|
23
|
+
* teardown. WS-level pings stay at the WS layer: `on('message')` fires
|
|
24
|
+
* for text data frames only, so protocol-level ping/pong NEVER surfaces
|
|
25
|
+
* as text.
|
|
26
|
+
*/
|
|
27
|
+
export declare function defaultWsFactory(url: string, headers: Record<string, string>): MuxClientSocket;
|
|
28
|
+
/** Options for {@link createRemoteGateway}. */
|
|
29
|
+
export interface RemoteGatewayOptions {
|
|
30
|
+
/** The peer hub origin (`http(s)://host[:port]`; the mux lane maps it to ws(s)). */
|
|
31
|
+
origin: string;
|
|
32
|
+
/** The remote agent's demux key (`?agent=` on both lanes; ignored by the native face). */
|
|
33
|
+
agentKey: string;
|
|
34
|
+
/** The machine's pairing cookie, read per call (the jar fronting `MachineRow.cookie`). */
|
|
35
|
+
cookie: () => string;
|
|
36
|
+
/** Fetch override (tests); defaults to `globalThis.fetch`. */
|
|
37
|
+
fetchImpl?: typeof fetch;
|
|
38
|
+
/** Socket factory override (tests); defaults to {@link defaultWsFactory}. */
|
|
39
|
+
wsFactory?: (url: string, headers: Record<string, string>) => MuxClientSocket;
|
|
40
|
+
/** Which peer surface to address (see the header's face table): 'foreign' =
|
|
41
|
+
* the `/hub` family with the `?agent=` demux (default); 'native' = ctx0's
|
|
42
|
+
* own `/api` surface, no demux key — how a browser ON the peer consumes its
|
|
43
|
+
* native DSH. */
|
|
44
|
+
face?: 'foreign' | 'native';
|
|
45
|
+
/** Idle ms with zero live streams before the ws closes (default 30 s; test hook for short real timers). */
|
|
46
|
+
idleCloseMs?: number;
|
|
47
|
+
}
|
|
48
|
+
/** A wire-form gateway: a {@link GatewayFace} plus the carrier call shape and a hard close. */
|
|
49
|
+
export interface RemoteGateway extends GatewayFace {
|
|
50
|
+
/**
|
|
51
|
+
* CarrierGatewayFace shape — the canonical order `worldMuxOf` calls with
|
|
52
|
+
* (`(endpoint, payload, uplink, peer, signal, control)`); the uplink is
|
|
53
|
+
* bridged into the wire upstream lane.
|
|
54
|
+
*/
|
|
55
|
+
openWireStream(endpoint: string, payload: unknown, uplink: AsyncIterable<unknown> | Iterable<unknown>, peer: undefined, signal: AbortSignal, control: AbortController): AsyncIterable<unknown>;
|
|
56
|
+
/** GatewayFace shape — no uplink lane; the stream's uplink half-closes immediately. */
|
|
57
|
+
openWireStream(endpoint: string, payload: unknown, signal: AbortSignal): AsyncIterable<unknown>;
|
|
58
|
+
/** Hard-close the shared connection now (machine unpair); in-flight streams fail `gateway/disposed`. */
|
|
59
|
+
dispose(): void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build one remote agent's wire gateway toward a paired hub's `/hub` family.
|
|
63
|
+
* @param options - see {@link RemoteGatewayOptions}.
|
|
64
|
+
* @returns the {@link RemoteGateway} (a GatewayFace; register it with
|
|
65
|
+
* `registerForeignTarget` — callers cannot tell it from a local world gateway).
|
|
66
|
+
*/
|
|
67
|
+
export declare function createRemoteGateway(options: RemoteGatewayOptions): RemoteGateway;
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemoteGatewayFace (machine-mesh P2 Task 2) — the wire form of a
|
|
3
|
+
* {@link GatewayFace}: one paired remote agent's dispatch/stream surface,
|
|
4
|
+
* reached over the PEER hub exactly as that hub would serve it locally —
|
|
5
|
+
* on ONE of two surfaces, selected by {@link RemoteGatewayOptions.face}:
|
|
6
|
+
*
|
|
7
|
+
* foreign (default) — the `/hub` family (hub-route is the server half):
|
|
8
|
+
* dispatchRpc POST <origin>/hub/api?agent=<agentKey>
|
|
9
|
+
* openWireStream WS <origin ws(s)>/hub/api/remote.mux?agent=<agentKey>
|
|
10
|
+
* native — ctx0's OWN envelope+mux surface, exactly how a browser ON the
|
|
11
|
+
* peer consumes its native DSH (NO demux key anywhere — the peer's
|
|
12
|
+
* native agent row federates as `key:'native'` with a minted a6, and the
|
|
13
|
+
* `/hub` family would 404 it as foreign-only):
|
|
14
|
+
* dispatchRpc POST <origin>/api/<endpoint> (endpoint in the PATH)
|
|
15
|
+
* openWireStream WS <origin ws(s)>/api/remote.mux
|
|
16
|
+
*
|
|
17
|
+
* The native unary URL mirrors upstream byte-exactly: the client builds
|
|
18
|
+
* `` `${channel}/${endpoint}`.slice(1) `` and the server decodes with
|
|
19
|
+
* `endpointFromPath` (a raw pathname slice, NO decodeURIComponent) — so the
|
|
20
|
+
* endpoint rides the path RAW, never percent-encoded (an encoded path would
|
|
21
|
+
* fail the server's ENDPOINT_SEGMENT_PATTERN; segments are already
|
|
22
|
+
* restricted to `[A-Za-z0-9_$.-]`, so nothing ever needs encoding). A bare
|
|
23
|
+
* `/api` carries no endpoint → 404.
|
|
24
|
+
*
|
|
25
|
+
* Everything else (envelope shape, cookie, Result verbatim, error taxonomy,
|
|
26
|
+
* connection lifecycle) is face-independent.
|
|
27
|
+
* Every call rides the machine's pairing cookie ({@link RemoteGatewayOptions.cookie},
|
|
28
|
+
* the per-machine jar fronting the persisted `MachineRow.cookie`).
|
|
29
|
+
*
|
|
30
|
+
* ## Mux connection lifecycle — ONE per gateway
|
|
31
|
+
* Lazy (dialed by the first stream), SHARED by every concurrent stream
|
|
32
|
+
* (per-call logical streams multiplex over it, T1 counter ids), IDLE-CLOSED
|
|
33
|
+
* {@link DEFAULT_IDLE_CLOSE_MS} after the last stream retires (the timer is
|
|
34
|
+
* `unref`d), and REDIALED transparently after any transport cut: on socket
|
|
35
|
+
* close the MuxClient fails every in-flight stream with
|
|
36
|
+
* `gateway/socket-closed`, the dead connection is dropped, and the next call
|
|
37
|
+
* dials fresh. The wire stream itself opens lazily on the returned iterable's
|
|
38
|
+
* first read — an iterable never iterated holds no connection and no stream.
|
|
39
|
+
*
|
|
40
|
+
* ## Heartbeat ruling (carried from the T1 review, controller ruling)
|
|
41
|
+
* The remote's heartbeat is WS-LEVEL ping/pong (WorldMuxServer pings its
|
|
42
|
+
* clients and terminates after two missed pongs); `ws` auto-answers those
|
|
43
|
+
* pings at the protocol layer and they NEVER surface as text (`on('message')`
|
|
44
|
+
* fires for data frames only — surfacing them would 1008-close the socket,
|
|
45
|
+
* since the server's parser has no ping case). Accordingly the MuxClient is
|
|
46
|
+
* configured with {@link HEARTBEAT_OFF_MS} (6 h), putting its JSON-text
|
|
47
|
+
* silence watchdog effectively OFF — an idle-but-alive connection is never
|
|
48
|
+
* torn down by the client — and liveness rides WS events alone: a dead peer
|
|
49
|
+
* surfaces as ws `close` → in-flight streams fail, the next call reconnects.
|
|
50
|
+
*
|
|
51
|
+
* ## Call shapes (the carrier lesson: canonical parameter order)
|
|
52
|
+
* `openWireStream` accepts BOTH faces with one runtime dispatch (an uplink
|
|
53
|
+
* iterable is recognizably not an AbortSignal):
|
|
54
|
+
* - GatewayFace: `(endpoint, payload, signal)` — the face `targets.ts`
|
|
55
|
+
* freezes. It carries NO uplink lane, so the stream's uplink is
|
|
56
|
+
* half-closed immediately (`end` right after `open`): a server handler
|
|
57
|
+
* reading uplink sees EOF instead of hanging.
|
|
58
|
+
* - CarrierGatewayFace: `(endpoint, payload, uplink, peer, signal, control)`
|
|
59
|
+
* — the exact order `worldMuxOf` calls with. The uplink is bridged into
|
|
60
|
+
* the wire upstream lane with the T1 channel idiom
|
|
61
|
+
* `for await (const v of uplink) await upstream.next(v); handle.end()`
|
|
62
|
+
* (read-one-send-one, zero buffering; a `done` read — dead stream — stops
|
|
63
|
+
* the pump quietly). `control` is accepted for shape
|
|
64
|
+
* parity only — cancellation rides the mux `cancel` frame, which is what
|
|
65
|
+
* actually stops the server-side stream.
|
|
66
|
+
*
|
|
67
|
+
* ## Error taxonomy (every throw is a plain `{code,message}` body — the
|
|
68
|
+
* brief-pinned shape; `wireErrorOf` normalizes it onto the wire downstream)
|
|
69
|
+
* - `gateway/unreachable` — network failure, or a non-200 answer that is
|
|
70
|
+
* not 401 (the status rides the message).
|
|
71
|
+
* - `gateway/auth-expired` — 401 anywhere: the MACHINE-LEVEL classification
|
|
72
|
+
* (the pairing cookie is dead) the roster downgrade consumes; never a
|
|
73
|
+
* per-request retry.
|
|
74
|
+
* - `gateway/bad-response` — 200 but the body is not a `server-response`
|
|
75
|
+
* envelope for the rpc we sent.
|
|
76
|
+
* - `gateway/aborted` — the caller's AbortSignal fired.
|
|
77
|
+
* - stream cuts — T1 `MuxClientError` codes pass through verbatim:
|
|
78
|
+
* `gateway/socket-closed` (peer cut / transport died),
|
|
79
|
+
* `gateway/disposed`, `gateway/protocol`; `gateway/heartbeat-silence` is
|
|
80
|
+
* dormant by the heartbeat ruling above. A REMOTE error frame keeps the
|
|
81
|
+
* remote's own code/message/details.
|
|
82
|
+
*
|
|
83
|
+
* ## Payload rule (T1 carry)
|
|
84
|
+
* `undefined` payloads are coerced to `null` on both lanes before the wire
|
|
85
|
+
* (T1's serializer drops the key for `undefined`, which the server's parser
|
|
86
|
+
* rejects); wire streamIds are T1's own stringified counters.
|
|
87
|
+
*/
|
|
88
|
+
import { createRequire } from 'node:module';
|
|
89
|
+
import { MuxClient } from './mux-client.js';
|
|
90
|
+
/** Text-watchdog-off horizon (see the heartbeat ruling in the header). */
|
|
91
|
+
const HEARTBEAT_OFF_MS = 6 * 60 * 60 * 1000;
|
|
92
|
+
/** Idle window before the shared ws closes with zero live streams. */
|
|
93
|
+
const DEFAULT_IDLE_CLOSE_MS = 30_000;
|
|
94
|
+
/** Specifier widened on purpose: `ws` is a runtime peer, not a typed dep. */
|
|
95
|
+
const WS_MODULE = 'ws';
|
|
96
|
+
let wsConstructor;
|
|
97
|
+
/** Synchronously load the ws constructor once (createRequire — rpc.ts precedent). */
|
|
98
|
+
function loadWs() {
|
|
99
|
+
if (wsConstructor !== undefined)
|
|
100
|
+
return wsConstructor;
|
|
101
|
+
const require = createRequire(import.meta.url);
|
|
102
|
+
const mod = require(WS_MODULE);
|
|
103
|
+
const boxed = mod;
|
|
104
|
+
wsConstructor = (typeof mod === 'function' ? mod : boxed.default);
|
|
105
|
+
return wsConstructor;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The default ws seam: connect one `ws` WebSocket to `url` with `headers`
|
|
109
|
+
* riding the upgrade request (the pairing Cookie), presented as T1's
|
|
110
|
+
* {@link MuxClientSocket}.
|
|
111
|
+
*
|
|
112
|
+
* The adapter is crash-proof by construction (fix round 1):
|
|
113
|
+
* - an `'error'` listener is ALWAYS registered: an unreachable peer
|
|
114
|
+
* (ECONNREFUSED) or a rejected upgrade (non-101, e.g. a dead cookie's
|
|
115
|
+
* HTTP 401) emits `'error'` with no listener of its own — an uncaught
|
|
116
|
+
* exception that kills the hub process. The listener is a no-op: ws
|
|
117
|
+
* guarantees `'close'` follows `'error'`, and the close path already
|
|
118
|
+
* classifies (in-flight streams fail `gateway/socket-closed`) and retires
|
|
119
|
+
* the connection for redial. WS-lane auth failure therefore classifies
|
|
120
|
+
* as a socket cut post-fix — acceptable: the next POST-lane call is the
|
|
121
|
+
* one that reports `gateway/auth-expired`.
|
|
122
|
+
* - `ws` THROWS on sends made while CONNECTING, and its post-close send
|
|
123
|
+
* bookkeeping can itself throw in teardown windows, so every send is
|
|
124
|
+
* guarded: pre-open sends queue (flushed in order on 'open'), post-close
|
|
125
|
+
* sends are swallowed — the stream is dead and the close path owns the
|
|
126
|
+
* teardown. WS-level pings stay at the WS layer: `on('message')` fires
|
|
127
|
+
* for text data frames only, so protocol-level ping/pong NEVER surfaces
|
|
128
|
+
* as text.
|
|
129
|
+
*/
|
|
130
|
+
export function defaultWsFactory(url, headers) {
|
|
131
|
+
const WebSocketCtor = loadWs();
|
|
132
|
+
const socket = new WebSocketCtor(url, { headers });
|
|
133
|
+
socket.on('error', () => { }); // crash guard: 'close' (and its classification) always follows
|
|
134
|
+
let open = false;
|
|
135
|
+
let closed = false;
|
|
136
|
+
const pending = [];
|
|
137
|
+
socket.on('open', () => {
|
|
138
|
+
open = true;
|
|
139
|
+
for (const data of pending.splice(0))
|
|
140
|
+
socket.send(data);
|
|
141
|
+
});
|
|
142
|
+
socket.on('close', () => {
|
|
143
|
+
closed = true;
|
|
144
|
+
open = false;
|
|
145
|
+
pending.length = 0;
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
send: (data) => {
|
|
149
|
+
if (closed)
|
|
150
|
+
return;
|
|
151
|
+
if (!open) {
|
|
152
|
+
pending.push(data);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
socket.send(data);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// CONNECTING/CLOSING race or sendAfterClose bookkeeping — the close
|
|
160
|
+
// path owns teardown; a lost frame on a dying socket changes nothing.
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
onMessage: (cb) => {
|
|
164
|
+
socket.on('message', (data) => { cb(String(data)); });
|
|
165
|
+
},
|
|
166
|
+
onClose: (cb) => { socket.on('close', () => { cb(); }); },
|
|
167
|
+
close: () => { socket.close(); },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** The ws(s) URL of the peer's mux lane (foreign: the `/hub` family with
|
|
171
|
+
* the demux key; native: ctx0's own surface, no query). */
|
|
172
|
+
function muxUrlOf(origin, agentKey, face) {
|
|
173
|
+
const url = new URL(origin);
|
|
174
|
+
url.protocol = url.protocol === 'https:' || url.protocol === 'wss:' ? 'wss:' : 'ws:';
|
|
175
|
+
url.pathname = `${url.pathname.replace(/\/+$/, '')}${face === 'native' ? '/api/remote.mux' : '/hub/api/remote.mux'}`;
|
|
176
|
+
if (face === 'foreign')
|
|
177
|
+
url.search = `agent=${encodeURIComponent(agentKey)}`;
|
|
178
|
+
return url.toString();
|
|
179
|
+
}
|
|
180
|
+
function isRecord(value) {
|
|
181
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
182
|
+
}
|
|
183
|
+
/** An uplink lane is recognizable at the seam: iterables (sync or async —
|
|
184
|
+
* `for await` bridges sync ones) are never AbortSignals. */
|
|
185
|
+
function isUplinkLane(value) {
|
|
186
|
+
return typeof value === 'object' && value !== null
|
|
187
|
+
&& (Symbol.asyncIterator in value || Symbol.iterator in value);
|
|
188
|
+
}
|
|
189
|
+
/** One gateway/* failure body — the brief-pinned thrown shape. */
|
|
190
|
+
function wireFail(code, message) {
|
|
191
|
+
return { code, message };
|
|
192
|
+
}
|
|
193
|
+
function messageOf(error) {
|
|
194
|
+
return error instanceof Error ? error.message : String(error);
|
|
195
|
+
}
|
|
196
|
+
class RemoteGatewayImpl {
|
|
197
|
+
origin;
|
|
198
|
+
agentKey;
|
|
199
|
+
cookieFn;
|
|
200
|
+
fetchImpl;
|
|
201
|
+
wsFactory;
|
|
202
|
+
idleCloseMs;
|
|
203
|
+
face;
|
|
204
|
+
muxUrl;
|
|
205
|
+
nextRpcId = 1;
|
|
206
|
+
connection;
|
|
207
|
+
idleTimer;
|
|
208
|
+
constructor(options) {
|
|
209
|
+
this.origin = options.origin;
|
|
210
|
+
this.agentKey = options.agentKey;
|
|
211
|
+
this.cookieFn = options.cookie;
|
|
212
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
213
|
+
this.wsFactory = options.wsFactory ?? defaultWsFactory;
|
|
214
|
+
this.idleCloseMs = options.idleCloseMs ?? DEFAULT_IDLE_CLOSE_MS;
|
|
215
|
+
this.face = options.face ?? 'foreign';
|
|
216
|
+
this.muxUrl = muxUrlOf(options.origin, options.agentKey, this.face);
|
|
217
|
+
}
|
|
218
|
+
async dispatchRpc(endpoint, payload, signal) {
|
|
219
|
+
// Foreign: the /hub family with the demux key. Native: ctx0's own
|
|
220
|
+
// surface with the endpoint in the PATH, raw (see the header's URL-truth
|
|
221
|
+
// note) — exactly the URL a browser on the peer posts.
|
|
222
|
+
const url = this.face === 'native'
|
|
223
|
+
? `${this.origin}/api/${endpoint}`
|
|
224
|
+
: `${this.origin}/hub/api?agent=${encodeURIComponent(this.agentKey)}`;
|
|
225
|
+
const rpcId = String(this.nextRpcId);
|
|
226
|
+
this.nextRpcId += 1;
|
|
227
|
+
const body = JSON.stringify({
|
|
228
|
+
type: 'client-request',
|
|
229
|
+
rpcId,
|
|
230
|
+
method: endpoint,
|
|
231
|
+
payload: payload === undefined ? null : payload, // the payload rule: never undefined
|
|
232
|
+
});
|
|
233
|
+
const cookie = this.cookieFn();
|
|
234
|
+
let response;
|
|
235
|
+
try {
|
|
236
|
+
response = await this.fetchImpl(url, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: {
|
|
239
|
+
'content-type': 'application/json',
|
|
240
|
+
...(cookie === '' ? {} : { cookie }),
|
|
241
|
+
},
|
|
242
|
+
body,
|
|
243
|
+
signal,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (signal?.aborted)
|
|
248
|
+
throw wireFail('gateway/aborted', `mesh-gateway: ${endpoint} aborted`);
|
|
249
|
+
throw wireFail('gateway/unreachable', `mesh-gateway: POST ${url} failed — ${messageOf(error)}`);
|
|
250
|
+
}
|
|
251
|
+
if (response.status === 401) {
|
|
252
|
+
throw wireFail('gateway/auth-expired', `mesh-gateway: ${endpoint} answered 401 — the pairing cookie is expired (machine-level downgrade material)`);
|
|
253
|
+
}
|
|
254
|
+
if (response.status !== 200) {
|
|
255
|
+
throw wireFail('gateway/unreachable', `mesh-gateway: ${endpoint} answered unexpected status ${response.status} from ${this.origin}`);
|
|
256
|
+
}
|
|
257
|
+
let text;
|
|
258
|
+
try {
|
|
259
|
+
text = await response.text();
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (signal?.aborted)
|
|
263
|
+
throw wireFail('gateway/aborted', `mesh-gateway: ${endpoint} aborted mid-body`);
|
|
264
|
+
throw wireFail('gateway/unreachable', `mesh-gateway: reading ${endpoint} response failed — ${messageOf(error)}`);
|
|
265
|
+
}
|
|
266
|
+
let parsed;
|
|
267
|
+
try {
|
|
268
|
+
parsed = JSON.parse(text);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
throw wireFail('gateway/bad-response', `mesh-gateway: ${endpoint} response is not JSON`);
|
|
272
|
+
}
|
|
273
|
+
if (!isRecord(parsed) || parsed['type'] !== 'server-response' || parsed['rpcId'] !== rpcId || !('result' in parsed)) {
|
|
274
|
+
throw wireFail('gateway/bad-response', `mesh-gateway: ${endpoint} response is not a server-response envelope for rpc ${rpcId}`);
|
|
275
|
+
}
|
|
276
|
+
// The Result ({ok,value} | {ok:false,error}) is DATA: returned verbatim,
|
|
277
|
+
// never unwrapped or thrown — the caller classifies.
|
|
278
|
+
return parsed['result'];
|
|
279
|
+
}
|
|
280
|
+
openWireStream(endpoint, payload, uplinkOrSignal, _peer, signal, _control) {
|
|
281
|
+
const carrierShape = isUplinkLane(uplinkOrSignal);
|
|
282
|
+
const uplink = carrierShape ? uplinkOrSignal : undefined;
|
|
283
|
+
const abortSignal = carrierShape ? signal : uplinkOrSignal;
|
|
284
|
+
const gateway = this;
|
|
285
|
+
// The wire stream opens on the first read — an iterable never iterated
|
|
286
|
+
// dials nothing and leaks nothing.
|
|
287
|
+
async function* downlink() {
|
|
288
|
+
if (abortSignal?.aborted)
|
|
289
|
+
return;
|
|
290
|
+
const conn = gateway.acquireConnection();
|
|
291
|
+
let handle;
|
|
292
|
+
try {
|
|
293
|
+
handle = conn.client.openStream(endpoint, payload === undefined ? null : payload);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
gateway.releaseConnection(conn);
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
if (uplink === undefined) {
|
|
300
|
+
// The GatewayFace shape carries no uplink lane: half-close at once so
|
|
301
|
+
// a server handler reading uplink sees EOF instead of hanging.
|
|
302
|
+
handle.end();
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// T1 ruling idiom: one read of the channel iterator sends one item
|
|
306
|
+
// frame; empty/errored uplink settles to end/cancel. A `done` read
|
|
307
|
+
// means the stream died (socket cut / cancel) — stop pumping quietly
|
|
308
|
+
// instead of draining the source against a dead wire.
|
|
309
|
+
const push = handle.upstream[Symbol.asyncIterator]();
|
|
310
|
+
void (async () => {
|
|
311
|
+
try {
|
|
312
|
+
for await (const value of uplink) {
|
|
313
|
+
if ((await push.next(value)).done)
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
handle.cancel();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
handle.end();
|
|
322
|
+
})();
|
|
323
|
+
}
|
|
324
|
+
const onAbort = () => { handle.cancel(); };
|
|
325
|
+
abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
326
|
+
try {
|
|
327
|
+
yield* handle.items;
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
331
|
+
handle.cancel(); // no-op when the stream settled; cancels the wire on early exit
|
|
332
|
+
gateway.releaseConnection(conn);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return downlink();
|
|
336
|
+
}
|
|
337
|
+
dispose() {
|
|
338
|
+
this.clearIdleTimer();
|
|
339
|
+
const conn = this.connection;
|
|
340
|
+
this.connection = undefined;
|
|
341
|
+
conn?.client.dispose();
|
|
342
|
+
}
|
|
343
|
+
/** Take a live-stream slot on the shared connection, dialing it if needed. */
|
|
344
|
+
acquireConnection() {
|
|
345
|
+
this.clearIdleTimer();
|
|
346
|
+
let conn = this.connection;
|
|
347
|
+
if (conn === undefined) {
|
|
348
|
+
conn = this.connect();
|
|
349
|
+
this.connection = conn;
|
|
350
|
+
}
|
|
351
|
+
conn.live += 1;
|
|
352
|
+
return conn;
|
|
353
|
+
}
|
|
354
|
+
/** Drop a live-stream slot; the last one out arms the idle-close timer. */
|
|
355
|
+
releaseConnection(conn) {
|
|
356
|
+
conn.live = Math.max(0, conn.live - 1);
|
|
357
|
+
if (conn.live > 0 || this.connection !== conn || this.idleTimer !== undefined)
|
|
358
|
+
return;
|
|
359
|
+
const timer = setTimeout(() => {
|
|
360
|
+
this.idleTimer = undefined;
|
|
361
|
+
if (this.connection === conn && conn.live === 0) {
|
|
362
|
+
this.connection = undefined;
|
|
363
|
+
conn.client.dispose();
|
|
364
|
+
}
|
|
365
|
+
}, this.idleCloseMs);
|
|
366
|
+
timer.unref();
|
|
367
|
+
this.idleTimer = timer;
|
|
368
|
+
}
|
|
369
|
+
/** Dial the mux ws and wrap it so a transport cut retires the connection. */
|
|
370
|
+
connect() {
|
|
371
|
+
const cookie = this.cookieFn();
|
|
372
|
+
const headers = cookie === '' ? {} : { cookie };
|
|
373
|
+
const socket = this.wsFactory(this.muxUrl, headers);
|
|
374
|
+
let conn;
|
|
375
|
+
const wrapped = {
|
|
376
|
+
send: (data) => { socket.send(data); },
|
|
377
|
+
onMessage: (cb) => { socket.onMessage(cb); },
|
|
378
|
+
onClose: (cb) => {
|
|
379
|
+
socket.onClose(() => {
|
|
380
|
+
cb(); // the MuxClient tears its streams down first (T1 contract) …
|
|
381
|
+
if (this.connection === conn) {
|
|
382
|
+
this.connection = undefined; // … then the dead connection is dropped: the next call redials
|
|
383
|
+
this.clearIdleTimer();
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
},
|
|
387
|
+
close: () => { socket.close(); },
|
|
388
|
+
};
|
|
389
|
+
const client = new MuxClient(wrapped, { heartbeatIntervalMs: HEARTBEAT_OFF_MS });
|
|
390
|
+
conn = { client, live: 0 };
|
|
391
|
+
return conn;
|
|
392
|
+
}
|
|
393
|
+
clearIdleTimer() {
|
|
394
|
+
if (this.idleTimer === undefined)
|
|
395
|
+
return;
|
|
396
|
+
clearTimeout(this.idleTimer);
|
|
397
|
+
this.idleTimer = undefined;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Build one remote agent's wire gateway toward a paired hub's `/hub` family.
|
|
402
|
+
* @param options - see {@link RemoteGatewayOptions}.
|
|
403
|
+
* @returns the {@link RemoteGateway} (a GatewayFace; register it with
|
|
404
|
+
* `registerForeignTarget` — callers cannot tell it from a local world gateway).
|
|
405
|
+
*/
|
|
406
|
+
export function createRemoteGateway(options) {
|
|
407
|
+
return new RemoteGatewayImpl(options);
|
|
408
|
+
}
|