super-dsh 0.1.6 → 0.1.10-e
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-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +154 -17
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- 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 +1883 -287
- package/cordis.patch.yml +80 -0
- package/package.json +23 -9
- 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 +210 -0
- package/superd-hub/dist/client/agent-settings.js +300 -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 +1883 -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,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mesh link (mesh P1, spec §4/§6.3): remote connection management —
|
|
3
|
+
* token-URL parsing, cookie mint, roster probe, and the pairing /
|
|
4
|
+
* reprobe orchestration that adopts the remote machine id (D4) into
|
|
5
|
+
* the machine registry from `./machines.js`.
|
|
6
|
+
*
|
|
7
|
+
* Pure module — no cordis dependencies. Outbound calls use the global
|
|
8
|
+
* fetch (injectable via `fetchImpl` / `deps.fetchImpl` for tests) with
|
|
9
|
+
* a 10s AbortSignal timeout. The minted cookie is persisted on the machine
|
|
10
|
+
* row (`MachineRow.cookie`, D1 sensitivity, §3.3/§4) so hub restarts keep the
|
|
11
|
+
* remote's 30-day session; an in-process jar fronts the row as a write-through
|
|
12
|
+
* cache of the same value. Probe outcomes land
|
|
13
|
+
* in the module-level status table exported read-only via
|
|
14
|
+
* `machineStatus()` (mesh API consumes; `resetMachineStatus()` is the
|
|
15
|
+
* test hook). P1 was probe-on-demand only; the mesh P3 Task 5a user
|
|
16
|
+
* mandate ("状态更新走单向 API + 5 分钟周期轮询") adds the PERIODIC sweep
|
|
17
|
+
* (`startReprobeSweep`) — a timer-discipline layer over the same
|
|
18
|
+
* `reprobeMachine`, never a second probing path. I2 also lands here: every
|
|
19
|
+
* successful probe compares the peer's `hub.version` against ours and
|
|
20
|
+
* records a `versionWarning` on the status entry (diff major/minor or
|
|
21
|
+
* unparsable → warn; patch-only drift → clean).
|
|
22
|
+
*/
|
|
23
|
+
import { loadMachines, saveMachines, upsertRemote, listRemotes } from './machines.js';
|
|
24
|
+
import { PACK_VERSION } from './pack-version.js';
|
|
25
|
+
/**
|
|
26
|
+
* The `ours` side of the I2 comparison — the super-dsh PACK version (D6: the
|
|
27
|
+
* hub subpackage's constant 0.1.0 made the comparator dead; the pack number
|
|
28
|
+
* is what `dsh plugin add` versions and the only one that moves). Exported so
|
|
29
|
+
* tests can pin the comparator's production input to the real pack manifest.
|
|
30
|
+
*/
|
|
31
|
+
export const OWN_VERSION = PACK_VERSION;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a version's major.minor core (I2): tolerant of a leading `v`, a
|
|
34
|
+
* missing patch segment, and a prerelease/build suffix — the mismatch rule
|
|
35
|
+
* ignores everything past `major.minor` by design (patch drift between
|
|
36
|
+
* paired hubs is routine and harmless).
|
|
37
|
+
*/
|
|
38
|
+
function parseVersionCore(version) {
|
|
39
|
+
const m = /^\s*v?(\d+)\.(\d+)/.exec(version);
|
|
40
|
+
if (m === null)
|
|
41
|
+
return undefined;
|
|
42
|
+
return { major: Number(m[1]), minor: Number(m[2]) };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The I2 comparator: when to warn about a peer's superd version.
|
|
46
|
+
* - different major OR minor → a warning naming both versions;
|
|
47
|
+
* - same major+minor (patch-only drift) → `undefined` (clean);
|
|
48
|
+
* - unparsable or missing on EITHER side → a warning carrying 'unknown'
|
|
49
|
+
* (an unreadable peer version is itself a compatibility fact).
|
|
50
|
+
* Pure and exported for direct unit testing; the pair/reprobe paths feed it
|
|
51
|
+
* `probe.version` and the module's {@link OWN_VERSION}.
|
|
52
|
+
*/
|
|
53
|
+
export function versionWarningFor(peer, ours) {
|
|
54
|
+
const peerCore = peer !== undefined ? parseVersionCore(peer) : undefined;
|
|
55
|
+
const ourCore = parseVersionCore(ours);
|
|
56
|
+
if (peerCore === undefined || ourCore === undefined) {
|
|
57
|
+
if (peer === undefined)
|
|
58
|
+
return 'version unknown (peer carries none)';
|
|
59
|
+
if (peerCore === undefined)
|
|
60
|
+
return `version unknown ("${peer}")`;
|
|
61
|
+
return `version unknown (ours "${ours}")`;
|
|
62
|
+
}
|
|
63
|
+
if (peerCore.major !== ourCore.major || peerCore.minor !== ourCore.minor) {
|
|
64
|
+
return `version ${peer} ≠ ours ${ours}`;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
69
|
+
/** <machineId, cookie> — in-process jar, a front for the persisted `MachineRow.cookie` (same value; write-through on every mint). */
|
|
70
|
+
const cookieJar = new Map();
|
|
71
|
+
const machineStatusTable = new Map();
|
|
72
|
+
function errMsg(err) {
|
|
73
|
+
return err instanceof Error ? err.message : String(err);
|
|
74
|
+
}
|
|
75
|
+
/** Validate a pasted token URL: absolute http(s) origin + non-empty `?token=`.
|
|
76
|
+
* Error copy never echoes the raw input — a typo'd paste may still carry a live
|
|
77
|
+
* token, so messages carry at most the (userinfo-free) origin. */
|
|
78
|
+
export function parseTokenUrl(tokenUrl) {
|
|
79
|
+
let url;
|
|
80
|
+
try {
|
|
81
|
+
url = new URL(tokenUrl);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw new Error('mesh-link: invalid token url — not a parseable absolute URL');
|
|
85
|
+
}
|
|
86
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
87
|
+
throw new Error(`mesh-link: invalid token url — origin must be http(s), got "${url.origin}"`);
|
|
88
|
+
}
|
|
89
|
+
const token = url.searchParams.get('token') ?? '';
|
|
90
|
+
if (token === '') {
|
|
91
|
+
throw new Error(`mesh-link: invalid token url at "${url.origin}" — missing ?token= query parameter`);
|
|
92
|
+
}
|
|
93
|
+
return { origin: url.origin, token };
|
|
94
|
+
}
|
|
95
|
+
/** Fallback Set-Cookie splitter for header environments without `getSetCookie()`. */
|
|
96
|
+
function splitSetCookieHeader(joined) {
|
|
97
|
+
if (!joined)
|
|
98
|
+
return [];
|
|
99
|
+
return joined.split(/,(?=\s*[^;=\s]+\s*=)/);
|
|
100
|
+
}
|
|
101
|
+
/** First `name=value` pair of a Set-Cookie value (attributes stripped). */
|
|
102
|
+
function firstCookiePair(raw) {
|
|
103
|
+
const m = /^\s*([^=;\s]+=[^;]*)/.exec(raw);
|
|
104
|
+
return m ? m[1].trim() : null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Mint a request cookie from a launch token: `GET <origin>/?token=<t>` with
|
|
108
|
+
* `redirect: 'manual'`, taking the first `dsh-auth-*` name=value pair from
|
|
109
|
+
* Set-Cookie. Non-303/2xx, a missing cookie, or a network failure throws
|
|
110
|
+
* with the status/cause attached.
|
|
111
|
+
*/
|
|
112
|
+
export async function mintCookie(origin, token, fetchImpl = globalThis.fetch) {
|
|
113
|
+
let res;
|
|
114
|
+
try {
|
|
115
|
+
res = await fetchImpl(`${origin}/?token=${encodeURIComponent(token)}`, {
|
|
116
|
+
redirect: 'manual',
|
|
117
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
throw new Error(`mesh-link: token mint to ${origin} failed — ${errMsg(err)}`);
|
|
122
|
+
}
|
|
123
|
+
if (res.status !== 303 && !(res.status >= 200 && res.status < 300)) {
|
|
124
|
+
throw new Error(`mesh-link: token mint failed — unexpected status ${res.status} from ${origin}`);
|
|
125
|
+
}
|
|
126
|
+
const rawCookies = typeof res.headers.getSetCookie === 'function'
|
|
127
|
+
? res.headers.getSetCookie()
|
|
128
|
+
: splitSetCookieHeader(res.headers.get('set-cookie'));
|
|
129
|
+
for (const raw of rawCookies) {
|
|
130
|
+
const pair = firstCookiePair(raw);
|
|
131
|
+
if (pair !== null && pair.startsWith('dsh-auth-'))
|
|
132
|
+
return pair;
|
|
133
|
+
}
|
|
134
|
+
throw new Error(`mesh-link: token mint failed — no dsh-auth-* Set-Cookie in response (status ${res.status}) from ${origin}`);
|
|
135
|
+
}
|
|
136
|
+
function coerceAgent(v) {
|
|
137
|
+
const a = (typeof v === 'object' && v !== null ? v : {});
|
|
138
|
+
return {
|
|
139
|
+
id: typeof a.id === 'string' ? a.id : '',
|
|
140
|
+
key: typeof a.key === 'string' ? a.key : '',
|
|
141
|
+
label: typeof a.label === 'string' ? a.label : '',
|
|
142
|
+
ready: a.ready === true,
|
|
143
|
+
failed: a.failed === true,
|
|
144
|
+
machine: typeof a.machine === 'string' && a.machine !== '' ? a.machine : 'local',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Probe a remote hub's roster: `GET <origin>/api/agent-runtime` with the
|
|
149
|
+
* cookie riding along. 401 → `auth-expired`; network error → `unreachable`;
|
|
150
|
+
* 200 without a `hub.machineId` block is a non-superdsh 0.1.6+ peer →
|
|
151
|
+
* `unreachable` with `peer roster has no hub block`.
|
|
152
|
+
*/
|
|
153
|
+
export async function probeMachine(origin, cookie, fetchImpl = globalThis.fetch) {
|
|
154
|
+
let res;
|
|
155
|
+
try {
|
|
156
|
+
// `?mesh=1` asks the peer for ITS OWN rows only (self-description
|
|
157
|
+
// discipline): a hub never re-broadcasts other remotes' agents. The
|
|
158
|
+
// adoption filter below is the belt-and-braces leg for older peers.
|
|
159
|
+
res = await fetchImpl(`${origin}/api/agent-runtime?mesh=1`, {
|
|
160
|
+
headers: cookie === '' ? {} : { cookie },
|
|
161
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
return { ok: false, status: 'unreachable', error: `probe fetch to ${origin} failed — ${errMsg(err)}` };
|
|
166
|
+
}
|
|
167
|
+
if (res.status === 401) {
|
|
168
|
+
return { ok: false, status: 'auth-expired', error: `probe got 401 from ${origin} — cookie rejected` };
|
|
169
|
+
}
|
|
170
|
+
if (res.status !== 200) {
|
|
171
|
+
return { ok: false, status: 'unreachable', error: `probe got status ${res.status} from ${origin}` };
|
|
172
|
+
}
|
|
173
|
+
let body;
|
|
174
|
+
try {
|
|
175
|
+
body = await res.json();
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
return { ok: false, status: 'unreachable', error: `probe roster from ${origin} is not JSON — ${errMsg(err)}` };
|
|
179
|
+
}
|
|
180
|
+
const o = (typeof body === 'object' && body !== null ? body : {});
|
|
181
|
+
const hub = (typeof o.hub === 'object' && o.hub !== null ? o.hub : {});
|
|
182
|
+
if (typeof hub.machineId !== 'string' || hub.machineId === '') {
|
|
183
|
+
return { ok: false, status: 'unreachable', error: 'peer roster has no hub block' };
|
|
184
|
+
}
|
|
185
|
+
// P2P (2026-09-28 user ruling): adopt ONLY the responder's own rows
|
|
186
|
+
// (machine === 'local'). Whatever else its roster carries — mirrors of US,
|
|
187
|
+
// third-party remotes it federates — is its own business; the requester
|
|
188
|
+
// merges its N paired machines itself, one probe each.
|
|
189
|
+
const agents = (Array.isArray(o.agents) ? o.agents.map(coerceAgent) : [])
|
|
190
|
+
.filter((a) => a.machine === 'local');
|
|
191
|
+
return {
|
|
192
|
+
ok: true,
|
|
193
|
+
machineId: hub.machineId,
|
|
194
|
+
version: typeof hub.version === 'string' ? hub.version : undefined,
|
|
195
|
+
agents,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** Read-only snapshot copy of the status table — callers can hold/mutate it freely. */
|
|
199
|
+
export function machineStatus() {
|
|
200
|
+
return new Map(Array.from(machineStatusTable, ([id, e]) => [id, { ...e }]));
|
|
201
|
+
}
|
|
202
|
+
/** Test hook: drop all status entries. */
|
|
203
|
+
export function resetMachineStatus() {
|
|
204
|
+
machineStatusTable.clear();
|
|
205
|
+
}
|
|
206
|
+
const federationListeners = new Set();
|
|
207
|
+
/** Subscribe to federation lifecycle events; returns an unsubscribe function. */
|
|
208
|
+
export function onMeshFederationEvent(fn) {
|
|
209
|
+
federationListeners.add(fn);
|
|
210
|
+
return () => {
|
|
211
|
+
federationListeners.delete(fn);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function emitFederation(event) {
|
|
215
|
+
for (const fn of federationListeners)
|
|
216
|
+
fn(event);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Announce a machine removal on the federation bus (`{type: 'defederated'}`).
|
|
220
|
+
* Called by mesh-rpc's DELETE path AFTER a successful `removeRemote` — the
|
|
221
|
+
* route module stays free of federation logic (one-way imports: mesh-rpc →
|
|
222
|
+
* mesh-link, the registrar subscribes at wiring time).
|
|
223
|
+
*/
|
|
224
|
+
export function emitMachineRemoved(machineId) {
|
|
225
|
+
emitFederation({ type: 'defederated', machineId });
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The machine's CURRENT pairing cookie: the in-process jar first (warm after
|
|
229
|
+
* every mint this process — pair and reprobe both write it before any
|
|
230
|
+
* federation event fires), then the persisted `MachineRow.cookie` (only when
|
|
231
|
+
* `home` is given; the defensive cold path for gateways built without a probe
|
|
232
|
+
* in this process), else `''` (wire calls then simply omit the Cookie header).
|
|
233
|
+
*/
|
|
234
|
+
export function cookieOf(machineId, home) {
|
|
235
|
+
const jarred = cookieJar.get(machineId);
|
|
236
|
+
if (jarred !== undefined)
|
|
237
|
+
return jarred;
|
|
238
|
+
if (home === undefined)
|
|
239
|
+
return '';
|
|
240
|
+
return listRemotes(home).find((r) => r.id === machineId)?.cookie ?? '';
|
|
241
|
+
}
|
|
242
|
+
/** Record one probe outcome; the I2 versionWarning (4th arg) lands only on
|
|
243
|
+
* reachable probes — every entry is written FRESH, so a re-probe against a
|
|
244
|
+
* converged peer replaces a stale warning with a clean entry. */
|
|
245
|
+
function recordStatus(id, status, error, versionWarning) {
|
|
246
|
+
const entry = error === undefined
|
|
247
|
+
? { status, lastProbeAt: Date.now() }
|
|
248
|
+
: { status, lastProbeAt: Date.now(), error };
|
|
249
|
+
if (versionWarning !== undefined)
|
|
250
|
+
entry.versionWarning = versionWarning;
|
|
251
|
+
machineStatusTable.set(id, entry);
|
|
252
|
+
}
|
|
253
|
+
/** Persist one remote row; uses `upsertRemote` unless a load/save seam is injected (test isolation). */
|
|
254
|
+
function persistRow(home, row, deps) {
|
|
255
|
+
if (!deps?.load && !deps?.save) {
|
|
256
|
+
upsertRemote(home, row);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const file = (deps.load ?? loadMachines)(home);
|
|
260
|
+
const idx = file.remotes.findIndex((r) => r.id === row.id);
|
|
261
|
+
if (idx === -1)
|
|
262
|
+
file.remotes.push({ ...row });
|
|
263
|
+
else
|
|
264
|
+
file.remotes[idx] = { ...row };
|
|
265
|
+
(deps.save ?? saveMachines)(home, file);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Full pairing: parse the pasted token URL → mint a cookie → probe the roster
|
|
269
|
+
* → adopt the remote machine id (fail-loud on collision with self or an
|
|
270
|
+
* existing remote) → persist the row with the retained token and minted
|
|
271
|
+
* cookie → jar the cookie and mark the machine reachable. A failed mint/probe throws before
|
|
272
|
+
* anything is persisted.
|
|
273
|
+
*/
|
|
274
|
+
export async function pairMachine(home, input, deps) {
|
|
275
|
+
const fetchImpl = deps?.fetchImpl ?? globalThis.fetch;
|
|
276
|
+
const load = deps?.load ?? loadMachines;
|
|
277
|
+
const { origin, token } = parseTokenUrl(input.tokenUrl);
|
|
278
|
+
const cookie = await mintCookie(origin, token, fetchImpl);
|
|
279
|
+
const probe = await probeMachine(origin, cookie, fetchImpl);
|
|
280
|
+
if (!probe.ok) {
|
|
281
|
+
throw new Error(`mesh-link: pairing ${origin} failed — ${probe.status}: ${probe.error}`);
|
|
282
|
+
}
|
|
283
|
+
const file = load(home);
|
|
284
|
+
if (probe.machineId === file.self.id || file.remotes.some((r) => r.id === probe.machineId)) {
|
|
285
|
+
throw new Error(`mesh-link: duplicate-machine-id ${probe.machineId} — collides with self or an existing remote row; remove the stale row before re-pairing`);
|
|
286
|
+
}
|
|
287
|
+
const row = {
|
|
288
|
+
id: probe.machineId,
|
|
289
|
+
name: input.name,
|
|
290
|
+
label: input.label ?? input.name,
|
|
291
|
+
origin,
|
|
292
|
+
token,
|
|
293
|
+
cookie,
|
|
294
|
+
enabled: true,
|
|
295
|
+
addedAt: Date.now(),
|
|
296
|
+
};
|
|
297
|
+
persistRow(home, row, deps);
|
|
298
|
+
cookieJar.set(row.id, cookie);
|
|
299
|
+
recordStatus(row.id, 'reachable', undefined, versionWarningFor(probe.version, OWN_VERSION));
|
|
300
|
+
emitFederation({ type: 'federated', machineId: row.id, machineName: row.name, origin, agents: probe.agents });
|
|
301
|
+
return { machineId: row.id, row, probe };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Re-probe a paired machine: the jar's cookie, falling back to the cookie
|
|
305
|
+
* persisted on the row (cold jar after a hub restart), rides the probe. On
|
|
306
|
+
* `auth-expired`, if the row retains a token, re-mint once and probe again; a
|
|
307
|
+
* successful re-mint writes the new cookie back into the jar AND the row. Every
|
|
308
|
+
* outcome lands in the status table
|
|
309
|
+
* (`reachable` / `auth-expired` / `unreachable`, the latter two with the error).
|
|
310
|
+
*/
|
|
311
|
+
export async function reprobeMachine(home, id, deps) {
|
|
312
|
+
const fetchImpl = deps?.fetchImpl ?? globalThis.fetch;
|
|
313
|
+
const remotes = deps?.load ? deps.load(home).remotes : listRemotes(home);
|
|
314
|
+
const row = remotes.find((r) => r.id === id);
|
|
315
|
+
if (!row) {
|
|
316
|
+
throw new Error(`mesh-link: unknown machine id "${id}" — no remote row in the registry`);
|
|
317
|
+
}
|
|
318
|
+
const origin = row.origin;
|
|
319
|
+
let cookie = cookieJar.get(id) ?? row.cookie ?? '';
|
|
320
|
+
let probe = await probeMachine(origin, cookie, fetchImpl);
|
|
321
|
+
if (!probe.ok && probe.status === 'auth-expired' && row.token) {
|
|
322
|
+
try {
|
|
323
|
+
cookie = await mintCookie(origin, row.token, fetchImpl);
|
|
324
|
+
cookieJar.set(id, cookie);
|
|
325
|
+
persistRow(home, { ...row, cookie }, deps);
|
|
326
|
+
probe = await probeMachine(origin, cookie, fetchImpl);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
probe = { ok: false, status: 'auth-expired', error: `re-mint failed — ${errMsg(err)}` };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (probe.ok) {
|
|
333
|
+
cookieJar.set(id, cookie);
|
|
334
|
+
recordStatus(id, 'reachable', undefined, versionWarningFor(probe.version, OWN_VERSION));
|
|
335
|
+
emitFederation({ type: 'federated', machineId: id, machineName: row.name, origin, agents: probe.agents });
|
|
336
|
+
}
|
|
337
|
+
else if (probe.status === 'auth-expired') {
|
|
338
|
+
recordStatus(id, 'auth-expired', probe.error);
|
|
339
|
+
emitFederation({ type: 'degraded', machineId: id, reason: 'auth-expired', error: probe.error });
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
recordStatus(id, 'unreachable', probe.error);
|
|
343
|
+
emitFederation({ type: 'degraded', machineId: id, reason: probe.status, error: probe.error });
|
|
344
|
+
}
|
|
345
|
+
return probe;
|
|
346
|
+
}
|
|
347
|
+
// ---- I1(a): the periodic reprobe sweep (mesh P3 Task 5a) ----------------------
|
|
348
|
+
/** Default sweep period — the user mandate's 5 minutes (5a I1-a). */
|
|
349
|
+
export const REPROBE_SWEEP_DEFAULT_SEC = 300;
|
|
350
|
+
/** Floor for any sweep period: below this a reprobe loop is a self-inflicted storm. */
|
|
351
|
+
export const REPROBE_SWEEP_MIN_SEC = 60;
|
|
352
|
+
let registeredCollectors = {};
|
|
353
|
+
/** Register/unregister the process-level collectors (the a2a row's assembly; set-once semantics, ctx0 only). */
|
|
354
|
+
export function setSweepCollectors(collectors) {
|
|
355
|
+
registeredCollectors = collectors ?? {};
|
|
356
|
+
}
|
|
357
|
+
/** One full sweep — the timer's entire payload. Reads the registry FRESH at
|
|
358
|
+
* start (rows paired/removed since the last tick are honored), probes every
|
|
359
|
+
* ENABLED remote through the real {@link reprobeMachine} (success converges
|
|
360
|
+
* federation via the existing event hook; failure degrades — no second
|
|
361
|
+
* probing path), and swallows every per-machine rejection so one dead peer
|
|
362
|
+
* (or a row deleted mid-sweep → unknown-id throw) never rejects the sweep.
|
|
363
|
+
* Since R5 the collectors run inside the same swallow discipline.
|
|
364
|
+
* `localCollect: false` (O1b, 0.1.8-e) skips ONLY the local leg: the boot tick
|
|
365
|
+
* uses it because the local session plane is not mounted yet the moment the
|
|
366
|
+
* hub's rows come up — sampling there yields an all-error collection that
|
|
367
|
+
* would then read as a fresh, empty window (see `startReprobeSweep`). */
|
|
368
|
+
export async function runReprobeSweep(deps) {
|
|
369
|
+
const collectors = deps.collectors ?? registeredCollectors;
|
|
370
|
+
if (deps.localCollect !== false && collectors.onLocalCollect !== undefined) {
|
|
371
|
+
try {
|
|
372
|
+
await collectors.onLocalCollect();
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// the local leg degrades — remotes still get their probe
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
for (const row of listRemotes(deps.home)) {
|
|
379
|
+
if (!row.enabled)
|
|
380
|
+
continue;
|
|
381
|
+
try {
|
|
382
|
+
const probe = await reprobeMachine(deps.home, row.id, { fetchImpl: deps.fetchImpl });
|
|
383
|
+
if (probe.ok && collectors.onRemoteProbed !== undefined) {
|
|
384
|
+
try {
|
|
385
|
+
await collectors.onRemoteProbed({ remote: row, probe });
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// a leg failure never breaks probe accounting or the sweep loop
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// Per-machine errors end here — the sweep itself always settles clean.
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Start the periodic reprobe sweep (I1-a). Discipline, all of it load-bearing:
|
|
399
|
+
* - the interval handle is `unref()`d — a freshness sweep never holds the
|
|
400
|
+
* hub process (or a test runner) open;
|
|
401
|
+
* - a tick while the previous sweep is still in flight is SKIPPED (no
|
|
402
|
+
* overlap: a slow peer slows the cadence, it never stacks sweeps);
|
|
403
|
+
* - the returned disposer clears the timer AND kills any late tick — the
|
|
404
|
+
* hub host wiring registers it as a ctx effect, so the sweep dies with
|
|
405
|
+
* the row (dispose-clean); the disposer is idempotent.
|
|
406
|
+
* The first sweep runs after one full period, except when `immediate` asks
|
|
407
|
+
* for one at boot (the mesh UI's own reprobe stays available too).
|
|
408
|
+
*/
|
|
409
|
+
export function startReprobeSweep(deps) {
|
|
410
|
+
const seconds = Math.max(REPROBE_SWEEP_MIN_SEC, deps.intervalSec ?? REPROBE_SWEEP_DEFAULT_SEC);
|
|
411
|
+
const setInt = deps.setIntervalFn ?? ((fn, ms) => globalThis.setInterval(fn, ms));
|
|
412
|
+
const clearInt = deps.clearIntervalFn ?? ((handle) => globalThis.clearInterval(handle));
|
|
413
|
+
let inFlight = false;
|
|
414
|
+
let disposed = false;
|
|
415
|
+
/** One tick. The boot tick passes `localCollect=false` (O1b, 0.1.8-e). */
|
|
416
|
+
const tick = (localCollect) => {
|
|
417
|
+
if (disposed || inFlight)
|
|
418
|
+
return;
|
|
419
|
+
inFlight = true;
|
|
420
|
+
void runReprobeSweep({
|
|
421
|
+
home: deps.home,
|
|
422
|
+
...(deps.fetchImpl === undefined ? {} : { fetchImpl: deps.fetchImpl }),
|
|
423
|
+
...(deps.collectors === undefined ? {} : { collectors: deps.collectors }),
|
|
424
|
+
...(localCollect ? {} : { localCollect: false }),
|
|
425
|
+
}).finally(() => { inFlight = false; });
|
|
426
|
+
};
|
|
427
|
+
const handle = setInt(() => tick(true), seconds * 1_000);
|
|
428
|
+
handle.unref?.();
|
|
429
|
+
// O1 (0.1.8-c): fill the roster at BOOT, not one full period later.
|
|
430
|
+
// O1b (0.1.8-e): the local leg is deferred to the first regular tick — the
|
|
431
|
+
// local session plane is still mounting and an early sample would be cached
|
|
432
|
+
// as an empty window for a full interval.
|
|
433
|
+
if (deps.immediate === true)
|
|
434
|
+
tick(false);
|
|
435
|
+
return () => {
|
|
436
|
+
if (disposed)
|
|
437
|
+
return;
|
|
438
|
+
disposed = true;
|
|
439
|
+
clearInt(handle);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-mesh management API (mesh P1, spec §6.3): the add-machine face
|
|
3
|
+
* mounted behind `ctx.connection.fetch` — the client-connection service owns
|
|
4
|
+
* the single `/api` prefix webServer route and runs the Host/Origin
|
|
5
|
+
* browser-trust fence + persistent browser auth before dispatching (same
|
|
6
|
+
* auth conclusion as rpc.ts; registering our own webServer route would
|
|
7
|
+
* bypass that fence).
|
|
8
|
+
*
|
|
9
|
+
* Route + handler ONLY — pairing/probing business logic lives in
|
|
10
|
+
* `./mesh-link.js`, persistence in `./machines.js`. This module never writes
|
|
11
|
+
* the mesh-link status table: `machineStatus()` is imported READ-ONLY and
|
|
12
|
+
* joined into GET rows by id (absent → `'pending'`).
|
|
13
|
+
*
|
|
14
|
+
* One exact route, body/method dispatched:
|
|
15
|
+
* GET /api/superd/machines → `{self, machines}` (public row
|
|
16
|
+
* projection — NEVER token/cookie,
|
|
17
|
+
* D1 + Task 2 review's deferred
|
|
18
|
+
* finding: the row carries two
|
|
19
|
+
* secrets and the wire carries
|
|
20
|
+
* none; since mesh P3 5a (I2) a
|
|
21
|
+
* row may also carry
|
|
22
|
+
* `versionWarning` — additive,
|
|
23
|
+
* present only when the peer's
|
|
24
|
+
* superd version differs from
|
|
25
|
+
* ours by more than patch)
|
|
26
|
+
* POST /api/superd/machines → `{op?: 'reprobe', ...}`:
|
|
27
|
+
* op:'reprobe' + {id} → `{probe: ProbeResult}` (the
|
|
28
|
+
* brief's `/machines/reprobe` path
|
|
29
|
+
* variant is FOLDED here because
|
|
30
|
+
* connection.fetch matches by exact
|
|
31
|
+
* path — one registration, body
|
|
32
|
+
* dispatch; documented per Task 3
|
|
33
|
+
* controller amendment)
|
|
34
|
+
* otherwise `{name, label?, tokenUrl}` → pairMachine → 201 `{machine}`;
|
|
35
|
+
* any failure → 400 `{error}` (the
|
|
36
|
+
* message is mesh-link's own,
|
|
37
|
+
* already token-redacted)
|
|
38
|
+
* DELETE /api/superd/machines?id=<m4> → `{removed: true|false}`
|
|
39
|
+
* DELETE /api/superd/machines?id=<m4> → `{removed: true|false}`; a successful
|
|
40
|
+
* removal ALSO announces `emitMachineRemoved`
|
|
41
|
+
* on mesh-link's federation bus (mesh P2
|
|
42
|
+
* Task 3) — the registrar defederates the
|
|
43
|
+
* machine's rows; this module stays
|
|
44
|
+
* route+handler only (no mesh-federate import)
|
|
45
|
+
* `DELETE` note: upstream's declared `ConnectionFetchMethod` union is
|
|
46
|
+
* `GET | HEAD | POST`, but the runtime route table dispatches through an
|
|
47
|
+
* untyped `methods.has(method)` set and `assertFetchRoute` never inspects
|
|
48
|
+
* method names (packages/client/connection/src/rpc-host.ts, verified against
|
|
49
|
+
* dsh-v0.1.7-rc.1) — DELETE rides the same exact-path dispatch. If a future
|
|
50
|
+
* upstream tightens that, remove folds into a POST op like reprobe.
|
|
51
|
+
*/
|
|
52
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
53
|
+
import { type A2aIngest } from './a2a-ingest.js';
|
|
54
|
+
import type { A2aLogFn } from './a2a-log.js';
|
|
55
|
+
/** The shared-channel Fetch route path this API owns. */
|
|
56
|
+
export declare const MESH_API_BASE = "/api/superd/machines";
|
|
57
|
+
/** Injection seam: the hub home (registry + pairing live under it) and an
|
|
58
|
+
* optional fetch override for tests. `home` is resolved by the caller
|
|
59
|
+
* (gateway.ts passes `resolveDshHome()` at registration time); this module
|
|
60
|
+
* stays free of home-resolution logic. */
|
|
61
|
+
export interface MeshDeps {
|
|
62
|
+
home: string;
|
|
63
|
+
fetchImpl?: typeof fetch;
|
|
64
|
+
}
|
|
65
|
+
/** The GET/POST row shape as it crosses the wire — the persisted MachineRow
|
|
66
|
+
* minus its two secrets (`token`, `cookie`), plus the joined status (and,
|
|
67
|
+
* additively since I2/mesh P3 5a, the version-mismatch warning when the
|
|
68
|
+
* mesh-link status entry carries one). */
|
|
69
|
+
export interface MachinePublicRow {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
label: string;
|
|
73
|
+
origin: string;
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
addedAt: number;
|
|
76
|
+
status: string;
|
|
77
|
+
lastProbeAt?: number;
|
|
78
|
+
error?: string;
|
|
79
|
+
versionWarning?: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The mesh-machines handler: GET list / POST pair-or-reprobe / DELETE remove.
|
|
83
|
+
* @param request - the incoming `/api/superd/machines` request.
|
|
84
|
+
* @param deps - home + optional fetch override (tests inject a fixture).
|
|
85
|
+
* @returns the JSON response.
|
|
86
|
+
*/
|
|
87
|
+
export declare function handleMeshMachines(request: Request, deps: MeshDeps): Promise<Response>;
|
|
88
|
+
/**
|
|
89
|
+
* Register the mesh-machines API on the shared `/api` connection channel
|
|
90
|
+
* (behind the standard browser-trust + auth fence). No-op when the
|
|
91
|
+
* `connection` service is absent (e.g. fake-ctx unit tests).
|
|
92
|
+
*/
|
|
93
|
+
export declare function registerMeshRpc(ctx: Context, deps: MeshDeps): void;
|
|
94
|
+
/**
|
|
95
|
+
* POST /api/superd/a2a — body = one v1 wire frame `{v: 1, envelope}` → the
|
|
96
|
+
* envelope's receipt (new/message: send receipt; stop: stop receipt;
|
|
97
|
+
* reply/notice: the local delivery outcome). Malformed frames are 400s with
|
|
98
|
+
* the decoder's named reason.
|
|
99
|
+
*/
|
|
100
|
+
export declare function handleA2aIngestPost(request: Request, ingest: A2aIngest): Promise<Response>;
|
|
101
|
+
/**
|
|
102
|
+
* GET /api/superd/a2a/outbox?machine=<m4> — claim & clear the buffered
|
|
103
|
+
* envelopes addressed to the claiming machine (declarative claim, D1).
|
|
104
|
+
*/
|
|
105
|
+
export declare function handleA2aOutboxGet(request: Request, ingest: A2aIngest): Response;
|
|
106
|
+
/**
|
|
107
|
+
* Register the a2a endpoints (POST /api/superd/a2a + GET .../outbox) on the
|
|
108
|
+
* shared `/api` connection channel — same fence as the machines route. The
|
|
109
|
+
* ingest instance is SHARED (R4): the initiator service's self-routing leg
|
|
110
|
+
* calls the very same handler these routes call. No-op when `connection` is
|
|
111
|
+
* absent.
|
|
112
|
+
*/
|
|
113
|
+
export declare function registerA2aRpc(ctx: Context, ingest: A2aIngest): void;
|
|
114
|
+
/**
|
|
115
|
+
* POST /api/superd/exec — one-shot `bash -lc <cmd>` on THIS machine (spec
|
|
116
|
+
* §8.1): tail-windowed stdout/stderr, two-stage group kill on timeout. The
|
|
117
|
+
* auth trust boundary is the shared `/api` fence (same as machines/a2a);
|
|
118
|
+
* the initiator-side per-machine knob (`execEnabled`) lives on the CALLER's
|
|
119
|
+
* registry row, not here.
|
|
120
|
+
*/
|
|
121
|
+
export declare function handleExecPost(request: Request, log?: A2aLogFn): Promise<Response>;
|
|
122
|
+
/** Register the exec endpoint on the shared `/api` connection channel. */
|
|
123
|
+
export declare function registerExecRpc(ctx: Context, log?: A2aLogFn): void;
|