super-dsh 0.3.1 → 0.3.2-d
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/client/index.js +135 -14
- package/package.json +1 -1
- package/remote/helper-bin/MANIFEST.json +35 -0
- package/remote/helper-bin/aarch64-apple-darwin/BUILT-VER +1 -0
- package/remote/helper-bin/aarch64-apple-darwin/superd-run +0 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/BUILT-VER +1 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/superd-run +0 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/BUILT-VER +1 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/superd-run.exe +0 -0
- package/remote/helper-bin/x86_64-unknown-linux-musl/BUILT-VER +1 -0
- package/remote/helper-bin/x86_64-unknown-linux-musl/superd-run +0 -0
- package/remote/hub-pty-pump.mjs +97 -0
- package/remote/plugin.d.ts +519 -197
- package/remote/plugin.js +2516 -754
- package/remote/provenance.json +36 -21
- package/superd-hub/dist/a2a-host.d.ts +11 -3
- package/superd-hub/dist/a2a-host.js +7 -7
- package/superd-hub/dist/a2a-row.js +7 -1
- package/superd-hub/dist/carrier.d.ts +9 -7
- package/superd-hub/dist/carrier.js +3 -3
- package/superd-hub/dist/client/pill-geometry.d.ts +70 -0
- package/superd-hub/dist/client/pill-geometry.js +49 -0
- package/superd-hub/dist/client/switch.d.ts +15 -0
- package/superd-hub/dist/client/switch.js +54 -1
- package/superd-hub/dist/companions.js +8 -6
- package/superd-hub/dist/fusion-transport.d.ts +18 -1
- package/superd-hub/dist/fusion-transport.js +44 -5
- package/superd-hub/dist/gateway.js +8 -1
- package/superd-hub/dist/hub-route.d.ts +5 -2
- package/superd-hub/dist/hub-route.js +6 -6
- package/superd-hub/dist/hub-tool.d.ts +1 -1
- package/superd-hub/dist/hub-tool.js +1 -1
- package/superd-hub/dist/remote-provider.js +70 -36
- package/superd-hub/dist/rpc.d.ts +5 -0
- package/superd-hub/dist/rpc.js +20 -1
- package/superd-hub/dist/switch-core.d.ts +41 -0
- package/superd-hub/dist/switch-core.js +40 -0
- package/superd-hub/dist/targets.d.ts +7 -0
- package/superd-hub/dist/world-lifecycle.js +1 -1
- package/superd-hub/lib/client/index.js +135 -14
- package/superd-hub/dist/exec-pty-channel.d.ts +0 -58
- package/superd-hub/dist/exec-pty-channel.js +0 -162
- package/superd-hub/dist/exec-pty-framing.d.ts +0 -34
- package/superd-hub/dist/exec-pty-framing.js +0 -89
- package/superd-hub/dist/exec-pty-session.d.ts +0 -78
- package/superd-hub/dist/exec-pty-session.js +0 -228
package/remote/provenance.json
CHANGED
|
@@ -3,32 +3,37 @@
|
|
|
3
3
|
"entry": "better-dsh/src/remote/plugin.ts",
|
|
4
4
|
"workerEntry": "better-dsh/src/remote/file-worker.ts",
|
|
5
5
|
"sources": {
|
|
6
|
-
"better-dsh/src/remote/agent-registration.ts": "
|
|
6
|
+
"better-dsh/src/remote/agent-registration.ts": "cb08eb9f5d592201054410da69b3bf383e2604899d9494a8cbd8ba2f86b3f7ad",
|
|
7
7
|
"better-dsh/src/remote/archive.ts": "fd32f96dc63421be7d56eb545badf355c6ee2498599605ffa48958b9efca9a63",
|
|
8
|
-
"better-dsh/src/remote/copy.ts": "
|
|
9
|
-
"better-dsh/src/remote/driver.ts": "
|
|
10
|
-
"better-dsh/src/remote/endpoints.ts": "
|
|
8
|
+
"better-dsh/src/remote/copy.ts": "ab2aec5ee8df814f4bc568370977d229c402489248c738eff147431a52650c52",
|
|
9
|
+
"better-dsh/src/remote/driver.ts": "89a690159bc64056c6b740ae76722b13a922f36cb2fc908adbbe2ccce1b9ebb3",
|
|
10
|
+
"better-dsh/src/remote/endpoints.ts": "e4ab0fa1afcee815ad78976eba4036e2ed2b21eaea11f7451874b56229f12eac",
|
|
11
11
|
"better-dsh/src/remote/file-worker.ts": "31ac019a944cf7fdcd0ff044f8b330d923bf4d384c77056f9b7e3032bc8c9598",
|
|
12
|
-
"better-dsh/src/remote/
|
|
13
|
-
"better-dsh/src/remote/
|
|
14
|
-
"better-dsh/src/remote/
|
|
15
|
-
"better-dsh/src/remote/
|
|
16
|
-
"better-dsh/src/remote/
|
|
12
|
+
"better-dsh/src/remote/helper.ts": "789a802610fc480bb1c922dd284787ca22d7394fa906c729ee33d78ade79973c",
|
|
13
|
+
"better-dsh/src/remote/hub-contract.ts": "a7ae5501c4ffe63ced1526c2d64d7d83798fc2025f9b4aea998e033a124fe598",
|
|
14
|
+
"better-dsh/src/remote/hub-pty-pump.mjs": "dfe0cb331cc585eb4d858fbb6d8f3ff22414f3ee55109c933df0fc14502aee5c",
|
|
15
|
+
"better-dsh/src/remote/input.ts": "dfe087d34e9e9aefc0d8ec2e33371dc86981b05723d436419ad1afb6ac8426b4",
|
|
16
|
+
"better-dsh/src/remote/jobs.ts": "4556bd9f30580a8bf90800c7a945282e276abb548df6db7fe95e7091bb1543c6",
|
|
17
|
+
"better-dsh/src/remote/labels.ts": "d9dcfdc1c6bb292b37d3250e7897b239c416b55b3102943bcc4f1d0ce7d2e709",
|
|
18
|
+
"better-dsh/src/remote/native-copy.ts": "3f8691abb14dfb7a94e956956fb4d182b7732ffbe70f1a8ff7dec5dd2b9afb7c",
|
|
19
|
+
"better-dsh/src/remote/nonce-framing.ts": "e2590293fe44c2997f6d65e0d6828aa67e51e0fc6d12cb71726063eec2f922b2",
|
|
17
20
|
"better-dsh/src/remote/oneshot.ts": "99f2c809ba1159c7865ec3cff2e6d7b1f165a7f03185a6fc9505c1a534ba57bc",
|
|
18
21
|
"better-dsh/src/remote/output-ring.ts": "a79104bd3dcddab06a3fe7e52d239a70becd4f2dcddd0c9b6513f5fe3c4ed0bc",
|
|
19
|
-
"better-dsh/src/remote/plugin.ts": "
|
|
22
|
+
"better-dsh/src/remote/plugin.ts": "058c35172c32b347b14d5ea6db89234ccdde6f13d94eea550c5f2b8f31726288",
|
|
20
23
|
"better-dsh/src/remote/process.ts": "f4491c76457b662703e0b1e4a876299b9c8b7b3da5b304c6baee5576017de448",
|
|
21
|
-
"better-dsh/src/remote/pty-session.ts": "
|
|
22
|
-
"better-dsh/src/remote/roster.ts": "
|
|
23
|
-
"better-dsh/src/remote/rsync.ts": "
|
|
24
|
+
"better-dsh/src/remote/pty-session.ts": "3cb07345b90f0e54cbcea00204ae0b5346d9bb6edf3a784f009fc1b07357f799",
|
|
25
|
+
"better-dsh/src/remote/roster.ts": "b84f75edc87550bb96803042487cfd7c39135b1710ff9715dd5ca402df0aa378",
|
|
26
|
+
"better-dsh/src/remote/rsync.ts": "f9c15279fdfa67b3265eeda216bcd0c46d8b4f05c6026cf2750a1c818ff333ba",
|
|
27
|
+
"better-dsh/src/remote/ssh-argv.ts": "e7f46ef3e53eaba6cfe92033afad046435db18b0b2f4b86832353d609c535662",
|
|
24
28
|
"better-dsh/src/remote/ssh-config.ts": "71ce833be5c934430e8a9ca982af055fccb71d6c3e64513ae83e7937b070e446",
|
|
25
29
|
"better-dsh/src/remote/ssh-process.ts": "9b4d754af5af0fe6a903e683642e94842f8662358b72467d41f2288e1e67fd79",
|
|
26
|
-
"better-dsh/src/remote/ssh-rsync-native-relay.ts": "
|
|
27
|
-
"better-dsh/src/remote/ssh-targets.ts": "
|
|
28
|
-
"better-dsh/src/remote/status.ts": "
|
|
29
|
-
"better-dsh/src/remote/target.ts": "
|
|
30
|
-
"better-dsh/src/remote/tool.ts": "
|
|
31
|
-
"better-dsh/src/remote/
|
|
30
|
+
"better-dsh/src/remote/ssh-rsync-native-relay.ts": "5d9585ab30a11aeeba19ce7b43aa31f47e2ee0bde2e7c006ac3e811cf7940bdf",
|
|
31
|
+
"better-dsh/src/remote/ssh-targets.ts": "2a7e754fc26cf34e05ffb5d7f7406f35c09fd0770684db904dbf4c56d2c00beb",
|
|
32
|
+
"better-dsh/src/remote/status.ts": "e144c1cf98388dbaca8dab5622e6e37ceac85d898119e37d07f88e129113a82e",
|
|
33
|
+
"better-dsh/src/remote/target.ts": "bce47c6c61975a52e5cb77a6b727f68cbe78ecdcb3866c237fb932444ecc954e",
|
|
34
|
+
"better-dsh/src/remote/tool.ts": "e2dda347880a8787196720e71f32da473f9135ba6ab9a21cb22a7241628c0744",
|
|
35
|
+
"better-dsh/src/remote/transfer-tool.ts": "d02b216a31314e98fdf6933230b57adce58b9c8aebe9798aae7be808105b41e2",
|
|
36
|
+
"better-dsh/src/remote/transports.ts": "8a7190d6013f12901d9007e6b30708645baa4aa0770fcb1087ae6fcde656a8d0",
|
|
32
37
|
"better-dsh/third_party/schemastery/lib/index.cjs": "aca1c1a47c7b6034feee01501aefca3e6dd97a0bac4fe334fd30647ae5a9e480",
|
|
33
38
|
"better-dsh/third_party/schemastery/lib/index.d.mts": "66847f605af58c42575333c825eefdec4ad38278ad6cb260e37aa0cc63803bbd",
|
|
34
39
|
"better-dsh/third_party/schemastery/lib/index.mjs": "cfcbe46c7416bdb35ce891c252152bbf519e08199bede5930dd7cb880c65f1ef",
|
|
@@ -56,7 +61,17 @@
|
|
|
56
61
|
},
|
|
57
62
|
"outputs": {
|
|
58
63
|
"file-worker.js": "f84f5b48de915949d85c3d0340a343cc5901218c3bc2dafdf5cecaa244f7b6e3",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
64
|
+
"helper-bin/MANIFEST.json": "efce4956d5c23f7916f48218b81848717a425ccf78ebeb6fcc7c788015250884",
|
|
65
|
+
"helper-bin/aarch64-apple-darwin/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
66
|
+
"helper-bin/aarch64-apple-darwin/superd-run": "a8fd11a5e6a2cb146deb599414434a299ebf78c53cfc1d3e5f42617ee11c1706",
|
|
67
|
+
"helper-bin/aarch64-unknown-linux-musl/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
68
|
+
"helper-bin/aarch64-unknown-linux-musl/superd-run": "3a8660fa099f1e71de6e08313ee2aaace00503f74365475ee46e2d2a89228397",
|
|
69
|
+
"helper-bin/x86_64-pc-windows-gnu/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
70
|
+
"helper-bin/x86_64-pc-windows-gnu/superd-run.exe": "1372450238c5bda702b4529f4148aa79e80a255c3c125d1a6229afd4693e9d90",
|
|
71
|
+
"helper-bin/x86_64-unknown-linux-musl/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
72
|
+
"helper-bin/x86_64-unknown-linux-musl/superd-run": "05b428657942abf22a0ad609433d5cab6b6182fe389851345459a0274b5a6533",
|
|
73
|
+
"hub-pty-pump.mjs": "dfe0cb331cc585eb4d858fbb6d8f3ff22414f3ee55109c933df0fc14502aee5c",
|
|
74
|
+
"plugin.d.ts": "92ea2007e5eb9907031de1cf0c53b30758804def9ebb957900cd3fb58d92be90",
|
|
75
|
+
"plugin.js": "c98040cb2f1e9d4e15f1c0aa61d3b0213e9281aa254baff92ab40549e0e22d48"
|
|
61
76
|
}
|
|
62
77
|
}
|
|
@@ -10,6 +10,14 @@ export interface A2aHostOptions {
|
|
|
10
10
|
* ({@link bridgeTypertGateway}).
|
|
11
11
|
*/
|
|
12
12
|
nativeGateway?: RawTypertGatewayFace;
|
|
13
|
+
/**
|
|
14
|
+
* LAZY provider of ctx0's owner browser Peer (connection.browserPeer) —
|
|
15
|
+
* 0.2.1 gateways read peer.signal/principal on every invocation. A
|
|
16
|
+
* provider, not a value: typertGateway mounts before connection, so a
|
|
17
|
+
* wiring-time read would capture undefined forever; resolution happens
|
|
18
|
+
* per call, when connection is long live.
|
|
19
|
+
*/
|
|
20
|
+
nativePeer?: () => unknown;
|
|
13
21
|
}
|
|
14
22
|
/**
|
|
15
23
|
* The RAW typertGateway service face — upstream canonical stream order
|
|
@@ -20,8 +28,8 @@ export interface A2aHostOptions {
|
|
|
20
28
|
* mesh-gateway wire adapters (dual-face, 3rd-argument dispatch).
|
|
21
29
|
*/
|
|
22
30
|
export interface RawTypertGatewayFace {
|
|
23
|
-
dispatchRpc(endpoint: string, payload: unknown, signal: AbortSignal): Promise<unknown>;
|
|
24
|
-
openWireStream(endpoint: string, payload: unknown, uplink: undefined, peer:
|
|
31
|
+
dispatchRpc(endpoint: string, payload: unknown, signal: AbortSignal, peer: unknown): Promise<unknown>;
|
|
32
|
+
openWireStream(endpoint: string, payload: unknown, uplink: undefined, peer: unknown, signal: AbortSignal, control: AbortController): Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>;
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Bridge one RAW typertGateway instance to Task 2's 3-arg `A2aGateway` face
|
|
@@ -38,7 +46,7 @@ export interface RawTypertGatewayFace {
|
|
|
38
46
|
* @param real - a raw in-process typertGateway instance (ctx0's own or a local world's).
|
|
39
47
|
* @returns the gateway Task 2's service consumes.
|
|
40
48
|
*/
|
|
41
|
-
export declare function bridgeTypertGateway(real: RawTypertGatewayFace): A2aGateway;
|
|
49
|
+
export declare function bridgeTypertGateway(real: RawTypertGatewayFace, peer?: unknown): A2aGateway;
|
|
42
50
|
/**
|
|
43
51
|
* Build the gatewayFor resolver Task 2's service consumes: federated table
|
|
44
52
|
* first, shared target registry second, local id-map inversion third,
|
|
@@ -59,10 +59,10 @@ function localKeyOfA6(home, a6) {
|
|
|
59
59
|
* @param real - a raw in-process typertGateway instance (ctx0's own or a local world's).
|
|
60
60
|
* @returns the gateway Task 2's service consumes.
|
|
61
61
|
*/
|
|
62
|
-
export function bridgeTypertGateway(real) {
|
|
62
|
+
export function bridgeTypertGateway(real, peer) {
|
|
63
63
|
return {
|
|
64
|
-
dispatchRpc: (endpoint, payload, signal) => real.dispatchRpc(endpoint, payload, signal),
|
|
65
|
-
openWireStream: (endpoint, payload, signal) => real.openWireStream(endpoint, payload, undefined,
|
|
64
|
+
dispatchRpc: (endpoint, payload, signal) => real.dispatchRpc(endpoint, payload, signal, peer),
|
|
65
|
+
openWireStream: (endpoint, payload, signal) => real.openWireStream(endpoint, payload, undefined, peer, signal, new AbortController()),
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
@@ -95,17 +95,17 @@ export function resolveA2aGatewayFor(options) {
|
|
|
95
95
|
if (key === 'native') {
|
|
96
96
|
if (options.nativeGateway === undefined)
|
|
97
97
|
throw new A2aAddressError('unreachable', addr);
|
|
98
|
-
return bridgeTypertGateway(options.nativeGateway);
|
|
98
|
+
return bridgeTypertGateway(options.nativeGateway, options.nativePeer?.());
|
|
99
99
|
}
|
|
100
100
|
// The registry's declared GatewayFace (targets.ts) types the stream
|
|
101
101
|
// face as the 3-arg subset, but a LOCAL row's runtime object is the RAW
|
|
102
102
|
// typertGateway instance (world-join.ts registers it verbatim) — re-cast
|
|
103
103
|
// to RawTypertGatewayFace so the bridge above binds the canonical 6-arg
|
|
104
104
|
// positional order instead of the declared subset.
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
105
|
+
const target = getTarget(key);
|
|
106
|
+
if (target?.gateway === undefined)
|
|
107
107
|
throw new A2aAddressError('unreachable', addr);
|
|
108
|
-
return bridgeTypertGateway(
|
|
108
|
+
return bridgeTypertGateway(target.gateway, target.peer);
|
|
109
109
|
}
|
|
110
110
|
// The a6 resolves nowhere — classify the miss (D7): a roster row exists →
|
|
111
111
|
// its plane is down; else the machine decides agent-vs-machine blame.
|
|
@@ -56,12 +56,18 @@ export function wireHubToolRow(ctx, opts = {}) {
|
|
|
56
56
|
// RAW instance in, bridged at the resolver (fix round 1: one shared
|
|
57
57
|
// bridge in a2a-host.ts covers the native seam AND all local world rows).
|
|
58
58
|
const raw = t.get('typertGateway');
|
|
59
|
+
// 0.2.1: every invocation reads the caller Peer — ctx0's owner browser
|
|
60
|
+
// Peer rides the native seam as a LAZY provider: typertGateway mounts
|
|
61
|
+
// BEFORE connection (the service registers in its constructor), so a
|
|
62
|
+
// wiring-time value would capture undefined forever; resolution happens
|
|
63
|
+
// per call, when connection is long live.
|
|
64
|
+
const nativePeer = () => ctx.get('connection')?.browserPeer;
|
|
59
65
|
// postoffice R4: ONE resolver, ONE ingest, TWO consumers — the initiator
|
|
60
66
|
// service's self-routing leg calls the very same ingest handler the HTTP
|
|
61
67
|
// endpoint calls (protocol-identical by construction). The service takes
|
|
62
68
|
// a LAZY provider: the ingest's delivery face buffers through the
|
|
63
69
|
// service's own buffer leg, so it can only be built after the service.
|
|
64
|
-
const gatewayFor = resolveA2aGatewayFor({ home, nativeGateway: raw });
|
|
70
|
+
const gatewayFor = resolveA2aGatewayFor({ home, nativeGateway: raw, nativePeer });
|
|
65
71
|
// Operational journal: the pure modules only format lines; this is
|
|
66
72
|
// where they meet the process console and land in the unit log (4999 rig
|
|
67
73
|
// / dev3 dsh.service journal). NOT ctx.logger: the pack composes no
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { type WorldRequest, type WorldResponse } from './world-web-server.js';
|
|
2
2
|
import { WorldMuxServer, type MuxFailure } from './world-mux.js';
|
|
3
|
-
/** The
|
|
3
|
+
/** The face of a world's typertGateway service instance the hub forwards through. */
|
|
4
4
|
export interface CarrierGatewayFace {
|
|
5
|
-
dispatchRpc(endpoint: string, payload: unknown, signal: AbortSignal): Promise<unknown>;
|
|
5
|
+
dispatchRpc(endpoint: string, payload: unknown, signal: AbortSignal, peer: unknown): Promise<unknown>;
|
|
6
6
|
/**
|
|
7
7
|
* Streams return an async iterable (upstream face is synchronous-returning).
|
|
8
8
|
* Canonical order: (endpoint, payload, uplink, peer, signal, control) — the
|
|
9
|
-
* carrier passes
|
|
10
|
-
* the mux controller's signal, and the controller
|
|
11
|
-
* native gateway's mux wires its own streams.
|
|
9
|
+
* carrier passes the world's owner browser Peer (0.2.1 gateways read it on
|
|
10
|
+
* every invocation), the mux controller's signal, and the controller
|
|
11
|
+
* itself, exactly as the native gateway's mux wires its own streams.
|
|
12
12
|
*/
|
|
13
|
-
openWireStream(endpoint: string, payload: unknown, uplink: AsyncIterable<unknown>, peer:
|
|
13
|
+
openWireStream(endpoint: string, payload: unknown, uplink: AsyncIterable<unknown>, peer: unknown, signal: AbortSignal, control: AbortController): Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>;
|
|
14
14
|
}
|
|
15
15
|
export declare function writeJson(res: WorldResponse, status: number, body: string): void;
|
|
16
16
|
/** Deps the forwarding kernel needs to answer for one world's traffic. */
|
|
17
17
|
export interface ForwardOptions {
|
|
18
18
|
/** The addressed world's gateway instance. */
|
|
19
19
|
gateway: CarrierGatewayFace;
|
|
20
|
+
/** The addressed world's owner browser Peer (0.2.1 invocation identity). */
|
|
21
|
+
peer?: unknown;
|
|
20
22
|
/** World key addressing the world's published `/api` fetch handler. */
|
|
21
23
|
label: string;
|
|
22
24
|
/** URL prefix this carrier strips before the world sees the request (`/omp`, `/hub`). */
|
|
@@ -47,7 +49,7 @@ export declare function forwardWorldFetch(options: {
|
|
|
47
49
|
*/
|
|
48
50
|
export declare function forwardApiCall(options: ForwardOptions, req: WorldRequest, res: WorldResponse): Promise<void>;
|
|
49
51
|
/** One WorldMuxServer bound to `gateway` — the canonical openWireStream wiring in one place. */
|
|
50
|
-
export declare function worldMuxOf(gateway: CarrierGatewayFace, options?: {
|
|
52
|
+
export declare function worldMuxOf(gateway: CarrierGatewayFace, peer?: unknown, options?: {
|
|
51
53
|
heartbeatIntervalMs?: number;
|
|
52
54
|
failure?: (error: unknown) => MuxFailure;
|
|
53
55
|
}): WorldMuxServer;
|
|
@@ -177,7 +177,7 @@ export async function forwardApiCall(options, req, res) {
|
|
|
177
177
|
}
|
|
178
178
|
rpcId = envelope.rpcId;
|
|
179
179
|
const controller = new AbortController();
|
|
180
|
-
const result = await options.gateway.dispatchRpc(envelope.method, envelope.payload, controller.signal);
|
|
180
|
+
const result = await options.gateway.dispatchRpc(envelope.method, envelope.payload, controller.signal, options.peer);
|
|
181
181
|
// The gateway already returns a Result (`{ok:true,value}` | `{ok:false,error}`),
|
|
182
182
|
// which is exactly the server-response's `result` field.
|
|
183
183
|
writeJson(res, 200, serverResult(rpcId, result));
|
|
@@ -194,8 +194,8 @@ export async function forwardApiCall(options, req, res) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
/** One WorldMuxServer bound to `gateway` — the canonical openWireStream wiring in one place. */
|
|
197
|
-
export function worldMuxOf(gateway, options) {
|
|
198
|
-
return new WorldMuxServer((endpoint, payload, uplink, control) => Promise.resolve(gateway.openWireStream(endpoint, payload, uplink,
|
|
197
|
+
export function worldMuxOf(gateway, peer, options) {
|
|
198
|
+
return new WorldMuxServer((endpoint, payload, uplink, control) => Promise.resolve(gateway.openWireStream(endpoint, payload, uplink, peer, control.signal, control)), {
|
|
199
199
|
...(options?.heartbeatIntervalMs === undefined ? {} : { heartbeatIntervalMs: options.heartbeatIntervalMs }),
|
|
200
200
|
...(options?.failure === undefined ? {} : { failure: options.failure }),
|
|
201
201
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pill geometry for the Hub sidebar row's agent selector (2026-10-18): the
|
|
3
|
+
* pure math behind anchoring the selector to its row. Split out of
|
|
4
|
+
* HubPanelIcon so node tests can pin it from `dist/client/` — the
|
|
5
|
+
* mesh-groups precedent (pure logic beside the components, no DOM, no
|
|
6
|
+
* React, no @deepseek-ai imports).
|
|
7
|
+
*
|
|
8
|
+
* Why absolute-in-flow and not `position: fixed` + re-measure: the native
|
|
9
|
+
* sidebar scrolls (many workspaces; iOS Safari drags the whole page), and a
|
|
10
|
+
* fixed pill tracks the VIEWPORT, not the content — without a scroll
|
|
11
|
+
* listener it stayed at its mount-time viewport spot while the row moved
|
|
12
|
+
* (the reported mis-alignment). Anchoring the pill to its positioned
|
|
13
|
+
* ancestor (the `Menu` wrapper span — upstream `.root` is
|
|
14
|
+
* `position: relative`, in-flow inside the row) makes the composite ride ANY
|
|
15
|
+
* scroll natively: zero JS per scrolled frame, exact by construction, and
|
|
16
|
+
* the open menu keeps following through its own capture-phase scroll
|
|
17
|
+
* placement (`getAnchorRect` reads the pill live).
|
|
18
|
+
*
|
|
19
|
+
* @module superd-hub/client/pill-geometry
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The measured boxes one anchoring reads. All viewport px, straight from
|
|
23
|
+
* `getBoundingClientRect()` — same-frame reads, so any scroll moves both
|
|
24
|
+
* boxes equally and the derived offsets are scroll-invariant by
|
|
25
|
+
* construction.
|
|
26
|
+
*/
|
|
27
|
+
export interface PillMeasureInput {
|
|
28
|
+
/** The row button's box (the `.panelRow` the glyph sits in). */
|
|
29
|
+
row: {
|
|
30
|
+
readonly top: number;
|
|
31
|
+
readonly bottom: number;
|
|
32
|
+
readonly right: number;
|
|
33
|
+
};
|
|
34
|
+
/** The pill's positioned ancestor box (the `Menu` wrapper span). */
|
|
35
|
+
wrap: {
|
|
36
|
+
readonly left: number;
|
|
37
|
+
readonly top: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Wrapper-relative offsets the pill is placed from. */
|
|
41
|
+
export interface PillBox {
|
|
42
|
+
/** Wrapper left edge → row right edge (px; both ride the same flow). */
|
|
43
|
+
readonly dx: number;
|
|
44
|
+
/** Wrapper top edge → row vertical center (px). */
|
|
45
|
+
readonly cy: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Derive the scroll-invariant offsets from one same-frame measurement.
|
|
49
|
+
* Measuring the DELTAS (not absolute coordinates) is the whole point: they
|
|
50
|
+
* survive any scroll of any ancestor, so nothing needs to re-run while the
|
|
51
|
+
* sidebar scrolls.
|
|
52
|
+
*/
|
|
53
|
+
export declare function measurePillBox(input: PillMeasureInput): PillBox;
|
|
54
|
+
/** The row's own horizontal padding (native `.panelRow`): the selector's inset. */
|
|
55
|
+
export declare const PILL_ROW_INSET = 8;
|
|
56
|
+
/** Structural CSS style the render side spreads into the pill span. */
|
|
57
|
+
export interface PillStyle {
|
|
58
|
+
readonly position: 'absolute';
|
|
59
|
+
readonly left: number;
|
|
60
|
+
readonly top: number;
|
|
61
|
+
readonly transform: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The pill's placement, wrapper-relative: its right edge sits `inset` px
|
|
65
|
+
* inside the row's right edge, vertically centered on the row
|
|
66
|
+
* (`translateY(-50%)`). `selectorWidth` is the render-time pill width (the
|
|
67
|
+
* mark is present only while an agent is active), so it stays a render
|
|
68
|
+
* concern; the measured box is not.
|
|
69
|
+
*/
|
|
70
|
+
export declare function pillStyle(box: PillBox, selectorWidth: number, inset?: number): PillStyle;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pill geometry for the Hub sidebar row's agent selector (2026-10-18): the
|
|
3
|
+
* pure math behind anchoring the selector to its row. Split out of
|
|
4
|
+
* HubPanelIcon so node tests can pin it from `dist/client/` — the
|
|
5
|
+
* mesh-groups precedent (pure logic beside the components, no DOM, no
|
|
6
|
+
* React, no @deepseek-ai imports).
|
|
7
|
+
*
|
|
8
|
+
* Why absolute-in-flow and not `position: fixed` + re-measure: the native
|
|
9
|
+
* sidebar scrolls (many workspaces; iOS Safari drags the whole page), and a
|
|
10
|
+
* fixed pill tracks the VIEWPORT, not the content — without a scroll
|
|
11
|
+
* listener it stayed at its mount-time viewport spot while the row moved
|
|
12
|
+
* (the reported mis-alignment). Anchoring the pill to its positioned
|
|
13
|
+
* ancestor (the `Menu` wrapper span — upstream `.root` is
|
|
14
|
+
* `position: relative`, in-flow inside the row) makes the composite ride ANY
|
|
15
|
+
* scroll natively: zero JS per scrolled frame, exact by construction, and
|
|
16
|
+
* the open menu keeps following through its own capture-phase scroll
|
|
17
|
+
* placement (`getAnchorRect` reads the pill live).
|
|
18
|
+
*
|
|
19
|
+
* @module superd-hub/client/pill-geometry
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Derive the scroll-invariant offsets from one same-frame measurement.
|
|
23
|
+
* Measuring the DELTAS (not absolute coordinates) is the whole point: they
|
|
24
|
+
* survive any scroll of any ancestor, so nothing needs to re-run while the
|
|
25
|
+
* sidebar scrolls.
|
|
26
|
+
*/
|
|
27
|
+
export function measurePillBox(input) {
|
|
28
|
+
return {
|
|
29
|
+
dx: input.row.right - input.wrap.left,
|
|
30
|
+
cy: (input.row.top + input.row.bottom) / 2 - input.wrap.top,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The row's own horizontal padding (native `.panelRow`): the selector's inset. */
|
|
34
|
+
export const PILL_ROW_INSET = 8;
|
|
35
|
+
/**
|
|
36
|
+
* The pill's placement, wrapper-relative: its right edge sits `inset` px
|
|
37
|
+
* inside the row's right edge, vertically centered on the row
|
|
38
|
+
* (`translateY(-50%)`). `selectorWidth` is the render-time pill width (the
|
|
39
|
+
* mark is present only while an agent is active), so it stays a render
|
|
40
|
+
* concern; the measured box is not.
|
|
41
|
+
*/
|
|
42
|
+
export function pillStyle(box, selectorWidth, inset = PILL_ROW_INSET) {
|
|
43
|
+
return {
|
|
44
|
+
position: 'absolute',
|
|
45
|
+
left: box.dx - inset - selectorWidth,
|
|
46
|
+
top: box.cy,
|
|
47
|
+
transform: 'translateY(-50%)',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -107,6 +107,21 @@ export interface SwitchSessions {
|
|
|
107
107
|
}
|
|
108
108
|
/** Whether this page is a fusion page: the baked flag global is present. */
|
|
109
109
|
export declare function fusionPage(): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Live-install staleness probe (2026-10-18): the ONE call the client entry
|
|
112
|
+
* makes when the page does not carry the fusion flag. A flag-absent page is
|
|
113
|
+
* either a pre-install page (the client half hot-loaded into HTML served
|
|
114
|
+
* before the row mounted — the fresh-install case where the Hub row renders
|
|
115
|
+
* but every switch affordance stays hidden until a manual restart) or a
|
|
116
|
+
* deliberate flag-off deployment (T-E). The roster's additive `hub.fusion`
|
|
117
|
+
* answers which; on an armed host the page re-boots ONCE — the marker keeps
|
|
118
|
+
* a misbehaving host from ever looping the tab. Every failure mode (fetch
|
|
119
|
+
* error, non-OK, old host without the field) degrades to no action: the
|
|
120
|
+
* flag-off shapes below are the honest rendering of an unanswered probe.
|
|
121
|
+
*
|
|
122
|
+
* Fire-and-forget by contract — never throws, never blocks the apply.
|
|
123
|
+
*/
|
|
124
|
+
export declare function probeStaleFusionPage(): void;
|
|
110
125
|
/** The page atom, when the page carries one (it always does on a fusion page). */
|
|
111
126
|
export declare function uiState(): SuperdUiState | undefined;
|
|
112
127
|
/**
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
*
|
|
38
38
|
* @module superd-hub/client/switch
|
|
39
39
|
*/
|
|
40
|
-
import { createReadinessWaiter, createSwitchController, decidePreFlight, observeCachesRefreshed, runMachineDownFeedback, SESSIONS_CURRENT_KEY, } from '../switch-core.js';
|
|
40
|
+
import { createReadinessWaiter, createSwitchController, decidePreFlight, observeCachesRefreshed, runMachineDownFeedback, SESSIONS_CURRENT_KEY, FUSION_RELOAD_MARKER, fusionReloadDecision, } from '../switch-core.js';
|
|
41
41
|
import { showSuperdToast } from '../toast-core.js';
|
|
42
42
|
import { fetchMachines, reprobeMachine } from './mesh-api.js';
|
|
43
43
|
import { fetchRoster } from './roster.js';
|
|
@@ -45,6 +45,59 @@ import { fetchRoster } from './roster.js';
|
|
|
45
45
|
export function fusionPage() {
|
|
46
46
|
return globalThis.__SUPERD_UI_FUSION__ === true;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The sessionStorage marker read (undefined-safe: a storage failure is
|
|
50
|
+
* `false` — the probe then reloads without the loop guard, which only
|
|
51
|
+
* matters for a host that reports fusion but never bakes the flag).
|
|
52
|
+
*/
|
|
53
|
+
function staleReloadMarker() {
|
|
54
|
+
try {
|
|
55
|
+
const storage = globalThis.sessionStorage;
|
|
56
|
+
return storage?.getItem(FUSION_RELOAD_MARKER) !== null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Live-install staleness probe (2026-10-18): the ONE call the client entry
|
|
64
|
+
* makes when the page does not carry the fusion flag. A flag-absent page is
|
|
65
|
+
* either a pre-install page (the client half hot-loaded into HTML served
|
|
66
|
+
* before the row mounted — the fresh-install case where the Hub row renders
|
|
67
|
+
* but every switch affordance stays hidden until a manual restart) or a
|
|
68
|
+
* deliberate flag-off deployment (T-E). The roster's additive `hub.fusion`
|
|
69
|
+
* answers which; on an armed host the page re-boots ONCE — the marker keeps
|
|
70
|
+
* a misbehaving host from ever looping the tab. Every failure mode (fetch
|
|
71
|
+
* error, non-OK, old host without the field) degrades to no action: the
|
|
72
|
+
* flag-off shapes below are the honest rendering of an unanswered probe.
|
|
73
|
+
*
|
|
74
|
+
* Fire-and-forget by contract — never throws, never blocks the apply.
|
|
75
|
+
*/
|
|
76
|
+
export function probeStaleFusionPage() {
|
|
77
|
+
void (async () => {
|
|
78
|
+
let hubFusion;
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch('/api/agent-runtime', { credentials: 'same-origin' });
|
|
81
|
+
if (!response.ok)
|
|
82
|
+
return;
|
|
83
|
+
hubFusion = (await response.json()).hub?.fusion;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (fusionReloadDecision({ flagPresent: fusionPage(), markerPresent: staleReloadMarker(), hubFusion }) !== 'reload')
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
;
|
|
92
|
+
globalThis.sessionStorage?.setItem(FUSION_RELOAD_MARKER, '1');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* private-mode storage: reload without the marker (a truthful host re-arms the page, ending the flow) */
|
|
96
|
+
}
|
|
97
|
+
;
|
|
98
|
+
globalThis.location?.reload();
|
|
99
|
+
})();
|
|
100
|
+
}
|
|
48
101
|
/** The page atom, when the page carries one (it always does on a fusion page). */
|
|
49
102
|
export function uiState() {
|
|
50
103
|
const state = globalThis.__SUPERD_UI_STATE__;
|
|
@@ -3,8 +3,9 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { readFile, mkdir, unlink, rmdir } from 'node:fs/promises';
|
|
4
4
|
import { join, dirname } from 'node:path';
|
|
5
5
|
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
6
|
-
// done marks a CONFIRMED first use (Better observed installed)
|
|
7
|
-
//
|
|
6
|
+
// done marks a CONFIRMED first use (Better observed installed); removed marks a
|
|
7
|
+
// migrated legacy removal record. A receipt with attempted but neither done,
|
|
8
|
+
// error, nor removed is an interrupted first use — e.g. killed by the restart
|
|
8
9
|
// that installing Super itself triggers — and must re-arm, not suppress, the retry.
|
|
9
10
|
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
10
11
|
async function optionalJson(path) {
|
|
@@ -48,9 +49,10 @@ export class BetterDashCompanion {
|
|
|
48
49
|
return this.initialized ??= (async () => {
|
|
49
50
|
const saved = await optionalJson(this.receipt);
|
|
50
51
|
if (saved !== undefined) {
|
|
51
|
-
// Never-resurrect applies
|
|
52
|
-
//
|
|
53
|
-
|
|
52
|
+
// Never-resurrect applies to a CONFIRMED first use, a recorded failure,
|
|
53
|
+
// or a migrated removal record; an interrupted first use (no done, no
|
|
54
|
+
// error, no removal marker) re-arms.
|
|
55
|
+
this.intent = saved.attempted && saved.done !== true && saved.error === undefined && saved.removed !== true
|
|
54
56
|
? { attempted: false } : saved;
|
|
55
57
|
await this.cleanLegacy();
|
|
56
58
|
return;
|
|
@@ -59,7 +61,7 @@ export class BetterDashCompanion {
|
|
|
59
61
|
// Any old registration was first use. An absent Better now means removal,
|
|
60
62
|
// including when Super has since changed version; never resurrect it.
|
|
61
63
|
if (old?.['super-dsh:better-dsh'] !== undefined) {
|
|
62
|
-
this.intent = { attempted: true };
|
|
64
|
+
this.intent = { attempted: true, removed: true };
|
|
63
65
|
await this.save();
|
|
64
66
|
await this.cleanLegacy();
|
|
65
67
|
}
|
|
@@ -117,6 +117,13 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
117
117
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
118
118
|
enabled: z<boolean, boolean, "defined">;
|
|
119
119
|
}>>, "plain">;
|
|
120
|
+
/**
|
|
121
|
+
* Whether the fusion row currently injects the transport script + flag
|
|
122
|
+
* global into served indexes. `false` before the first mount and after
|
|
123
|
+
* disposal — the roster GET answers with exactly this, and a stale page's
|
|
124
|
+
* client half reloads on `true` only (switch-core's `fusionReloadDecision`).
|
|
125
|
+
*/
|
|
126
|
+
export declare function fusionTransportArmed(): boolean;
|
|
120
127
|
/** Cordis plugin name. */
|
|
121
128
|
export declare const name = "superd-fusion-transport";
|
|
122
129
|
/**
|
|
@@ -132,8 +139,18 @@ export declare const inject: string[];
|
|
|
132
139
|
* the {@link FUSION_FLAG_GLOBAL} baked global row (Task 4): one gate, both
|
|
133
140
|
* halves.
|
|
134
141
|
*
|
|
142
|
+
* Armed state: while the listener is registered the row publishes
|
|
143
|
+
* {@link fusionTransportArmed} = true (counter-owned, reset on effect
|
|
144
|
+
* disposal) — the one fact an already-open page needs to know that its
|
|
145
|
+
* stale, pre-install HTML cannot tell it. Consumed additively as
|
|
146
|
+
* `hub.fusion` on the roster GET (rpc.ts).
|
|
135
147
|
* Off → the component registers nothing: no listener, no row, no script,
|
|
136
|
-
* no flag global.
|
|
148
|
+
* no flag global. When ON, the row also publishes its armed state through
|
|
149
|
+
* {@link fusionTransportArmed}, which the roster RPC's additive `hub.fusion`
|
|
150
|
+
* field carries to already-open pages: their client half probes it and
|
|
151
|
+
* re-boots the page once (see switch-core's live-install staleness block —
|
|
152
|
+
* the served index predates the install, so only a re-serve can bake the
|
|
153
|
+
* flag + transport script).
|
|
137
154
|
* @param ctx - host plugin context.
|
|
138
155
|
* @param config - the row config (`enabled`, default on).
|
|
139
156
|
*/
|
|
@@ -356,6 +356,25 @@ export const FUSION_FLAG_GLOBAL = '__SUPERD_UI_FUSION__';
|
|
|
356
356
|
export const Config = z.object({
|
|
357
357
|
enabled: z.boolean().default(true),
|
|
358
358
|
});
|
|
359
|
+
/**
|
|
360
|
+
* How many live index-inject listeners this row has registered (one per
|
|
361
|
+
* mounted component fiber). Module-level because the consumer (the roster
|
|
362
|
+
* RPC) is a DIFFERENT row in the same bundle: a cross-row import of live
|
|
363
|
+
* composition state, the same single-instance guarantee that lets the
|
|
364
|
+
* client half share the shell's module table. Reset by the listener
|
|
365
|
+
* effect's disposal, so a runtime row disable stops the advertisement
|
|
366
|
+
* immediately.
|
|
367
|
+
*/
|
|
368
|
+
let armedRows = 0;
|
|
369
|
+
/**
|
|
370
|
+
* Whether the fusion row currently injects the transport script + flag
|
|
371
|
+
* global into served indexes. `false` before the first mount and after
|
|
372
|
+
* disposal — the roster GET answers with exactly this, and a stale page's
|
|
373
|
+
* client half reloads on `true` only (switch-core's `fusionReloadDecision`).
|
|
374
|
+
*/
|
|
375
|
+
export function fusionTransportArmed() {
|
|
376
|
+
return armedRows > 0;
|
|
377
|
+
}
|
|
359
378
|
/** Cordis plugin name. */
|
|
360
379
|
export const name = 'superd-fusion-transport';
|
|
361
380
|
/**
|
|
@@ -371,8 +390,18 @@ export const inject = [];
|
|
|
371
390
|
* the {@link FUSION_FLAG_GLOBAL} baked global row (Task 4): one gate, both
|
|
372
391
|
* halves.
|
|
373
392
|
*
|
|
393
|
+
* Armed state: while the listener is registered the row publishes
|
|
394
|
+
* {@link fusionTransportArmed} = true (counter-owned, reset on effect
|
|
395
|
+
* disposal) — the one fact an already-open page needs to know that its
|
|
396
|
+
* stale, pre-install HTML cannot tell it. Consumed additively as
|
|
397
|
+
* `hub.fusion` on the roster GET (rpc.ts).
|
|
374
398
|
* Off → the component registers nothing: no listener, no row, no script,
|
|
375
|
-
* no flag global.
|
|
399
|
+
* no flag global. When ON, the row also publishes its armed state through
|
|
400
|
+
* {@link fusionTransportArmed}, which the roster RPC's additive `hub.fusion`
|
|
401
|
+
* field carries to already-open pages: their client half probes it and
|
|
402
|
+
* re-boots the page once (see switch-core's live-install staleness block —
|
|
403
|
+
* the served index predates the install, so only a re-serve can bake the
|
|
404
|
+
* flag + transport script).
|
|
376
405
|
* @param ctx - host plugin context.
|
|
377
406
|
* @param config - the row config (`enabled`, default on).
|
|
378
407
|
*/
|
|
@@ -382,10 +411,20 @@ export function apply(ctx, config) {
|
|
|
382
411
|
if (config?.enabled === false)
|
|
383
412
|
return;
|
|
384
413
|
ctx.inject(['webServer'], () => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
414
|
+
// The armed state rides the LISTENER's lifetime (an effect, not a bare
|
|
415
|
+
// mount): a live row disable/unload resets it, so the roster answer can
|
|
416
|
+
// never advertise fusion for a row that no longer injects anything.
|
|
417
|
+
ctx.effect(() => {
|
|
418
|
+
armedRows += 1;
|
|
419
|
+
const offInject = ctx.on('webserver/index-inject', (table) => {
|
|
420
|
+
table.push({ kind: 'script', placement: 'head', text: renderFusionTransport() });
|
|
421
|
+
table.push({ kind: 'global', name: FUSION_FLAG_GLOBAL, value: true });
|
|
422
|
+
});
|
|
423
|
+
return () => {
|
|
424
|
+
armedRows -= 1;
|
|
425
|
+
offInject();
|
|
426
|
+
};
|
|
427
|
+
}, 'superd-fusion-transport: index injection (armed state)');
|
|
389
428
|
});
|
|
390
429
|
}
|
|
391
430
|
export default { name, inject, Config, apply };
|
|
@@ -90,7 +90,14 @@ export function apply(ctx, config) {
|
|
|
90
90
|
real: real,
|
|
91
91
|
authorize: (req, res) => connection.authorizeIndex(req, res),
|
|
92
92
|
requestRejection: (req) => connection.requestRejection(req),
|
|
93
|
-
gatewayOf: (label) =>
|
|
93
|
+
gatewayOf: (label) => {
|
|
94
|
+
const federated = federatedGatewayOf(label);
|
|
95
|
+
if (federated !== undefined)
|
|
96
|
+
return { key: label, gateway: federated };
|
|
97
|
+
// The stored face IS the service instance (canonical methods); targets.ts
|
|
98
|
+
// keeps a looser structural view for the mesh wire adapters.
|
|
99
|
+
return getTarget(label);
|
|
100
|
+
},
|
|
94
101
|
}), 'superd-hub: /hub route family');
|
|
95
102
|
// P4 §8.2: the PTY executor's upgrade route — same gate as the /hub family
|
|
96
103
|
// (authorizeIndex cookie auth first, then the browser-trust fence). The
|
|
@@ -63,8 +63,11 @@ export interface HubRouteOptions {
|
|
|
63
63
|
* answer, so even the demux 404s never speak to untrusted traffic.
|
|
64
64
|
*/
|
|
65
65
|
requestRejection?(req: unknown): number | undefined;
|
|
66
|
-
/** The live gateway of a registered label, or undefined (unknown / not yet spawned). */
|
|
67
|
-
gatewayOf(label: string):
|
|
66
|
+
/** The live target (gateway + owner peer) of a registered label, or undefined (unknown / not yet spawned). */
|
|
67
|
+
gatewayOf(label: string): {
|
|
68
|
+
gateway: CarrierGatewayFace;
|
|
69
|
+
peer?: unknown;
|
|
70
|
+
} | undefined;
|
|
68
71
|
}
|
|
69
72
|
/**
|
|
70
73
|
* Register the family on ctx0's webServer.
|