super-dsh 0.3.1-b → 0.3.1-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/package.json +1 -1
- package/remote/helper-bin/MANIFEST.json +7 -7
- package/remote/helper-bin/aarch64-apple-darwin/BUILT-VER +1 -1
- package/remote/helper-bin/aarch64-apple-darwin/superd-run +0 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/BUILT-VER +1 -1
- package/remote/helper-bin/aarch64-unknown-linux-musl/superd-run +0 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/BUILT-VER +1 -1
- 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 -1
- package/remote/helper-bin/x86_64-unknown-linux-musl/superd-run +0 -0
- package/remote/plugin.d.ts +23 -4
- package/remote/plugin.js +224 -86
- package/remote/provenance.json +20 -19
- 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/companions.js +8 -6
- 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/targets.d.ts +7 -0
- package/superd-hub/dist/world-lifecycle.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-dsh",
|
|
3
|
-
"version": "0.3.1-
|
|
3
|
+
"version": "0.3.1-e",
|
|
4
4
|
"description": "Super D for the DeepSeek Harness (DSH) — one dsh plugin that mounts the superd hub (runtime selector, roster, per-runtime agents) and joins local foreign-agent runtimes (OMP, Codex, Claude, Pi, Hermes, Antigravity) into the DSH Web UI. Each runtime keeps its native home and session store; the hub only bridges.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": 1,
|
|
3
3
|
"name": "superd-run",
|
|
4
|
-
"ver": "0.3.1-
|
|
4
|
+
"ver": "0.3.1-d.b596",
|
|
5
5
|
"protocol": 1,
|
|
6
6
|
"targets": {
|
|
7
7
|
"aarch64-apple-darwin": {
|
|
8
8
|
"file": "superd-run",
|
|
9
9
|
"bytes": 337808,
|
|
10
|
-
"sha256": "
|
|
10
|
+
"sha256": "a8fd11a5e6a2cb146deb599414434a299ebf78c53cfc1d3e5f42617ee11c1706"
|
|
11
11
|
},
|
|
12
12
|
"aarch64-unknown-linux-musl": {
|
|
13
13
|
"file": "superd-run",
|
|
14
|
-
"bytes":
|
|
15
|
-
"sha256": "
|
|
14
|
+
"bytes": 397592,
|
|
15
|
+
"sha256": "3a8660fa099f1e71de6e08313ee2aaace00503f74365475ee46e2d2a89228397"
|
|
16
16
|
},
|
|
17
17
|
"x86_64-pc-windows-gnu": {
|
|
18
18
|
"file": "superd-run.exe",
|
|
19
|
-
"bytes":
|
|
20
|
-
"sha256": "
|
|
19
|
+
"bytes": 337408,
|
|
20
|
+
"sha256": "1372450238c5bda702b4529f4148aa79e80a255c3c125d1a6229afd4693e9d90"
|
|
21
21
|
},
|
|
22
22
|
"x86_64-unknown-linux-musl": {
|
|
23
23
|
"file": "superd-run",
|
|
24
24
|
"bytes": 447152,
|
|
25
|
-
"sha256": "
|
|
25
|
+
"sha256": "05b428657942abf22a0ad609433d5cab6b6182fe389851345459a0274b5a6533"
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"sources": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.1-
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.1-
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.1-
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.1-
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
package/remote/plugin.d.ts
CHANGED
|
@@ -99,7 +99,9 @@ declare class SshTargets {
|
|
|
99
99
|
clear(): void;
|
|
100
100
|
}
|
|
101
101
|
/** Shared by execution, probes and file/relay consumers. extraArgs is trusted
|
|
102
|
-
* internal SSH argv (e.g. forwarding), never a model-provided shell string.
|
|
102
|
+
* internal SSH argv (e.g. forwarding), never a model-provided shell string.
|
|
103
|
+
* Identity assembly lives in ssh-argv.ts (the ONE policy — 2026-10-17 ruling);
|
|
104
|
+
* this wrapper only picks the tty/batch dialect base. */
|
|
103
105
|
declare function sshArgv(connection: SshConnection, options?: {
|
|
104
106
|
tty?: boolean;
|
|
105
107
|
extraArgs?: readonly string[];
|
|
@@ -117,7 +119,12 @@ interface TransferReceipt {
|
|
|
117
119
|
action: 'copy';
|
|
118
120
|
src: string;
|
|
119
121
|
dst: string;
|
|
120
|
-
|
|
122
|
+
route: 'hub-staged';
|
|
123
|
+
/** S4: tier actually performed — sha256 digests ran and matched, or explicitly skipped. */
|
|
124
|
+
verification: 'sha256' | 'not-applicable';
|
|
125
|
+
/** S3 unified failure envelope. */
|
|
126
|
+
ok: boolean;
|
|
127
|
+
reason?: 'digest-mismatch' | 'staging-error' | 'aborted';
|
|
121
128
|
sourceDigest?: string;
|
|
122
129
|
destinationDigest?: string;
|
|
123
130
|
bytes: number;
|
|
@@ -154,8 +161,16 @@ interface NativeCopyReceipt {
|
|
|
154
161
|
stdout: string[];
|
|
155
162
|
stderr: string[];
|
|
156
163
|
outputTruncated: boolean;
|
|
157
|
-
/**
|
|
158
|
-
|
|
164
|
+
/** S3/S4 (spec §3.3): verification names the tier actually performed — native routes are exit-code-only (D1=A). */
|
|
165
|
+
verification: 'native-exit-only';
|
|
166
|
+
/** S3 unified failure envelope: all native exits zero. */
|
|
167
|
+
ok: boolean;
|
|
168
|
+
/** Failure class when ok is false. */
|
|
169
|
+
reason?: 'non-zero-exit' | 'aborted';
|
|
170
|
+
/** Human context, e.g. two-segment 'push not started: pull failed' (prose moved out of route, S8). */
|
|
171
|
+
note?: string;
|
|
172
|
+
/** D3 mkdir pre-hop outcome: true = created this call, false = already existed, 'n-a' = skipped or unavailable (detail in note). */
|
|
173
|
+
created?: true | false | 'n-a';
|
|
159
174
|
/** Tool-owned staging/config release: released on normal/observed exit, unknown when a channel was lost. */
|
|
160
175
|
cleanup: 'released' | 'unknown' | 'none-required';
|
|
161
176
|
}
|
|
@@ -203,6 +218,10 @@ interface RsyncReceipt extends RunResult {
|
|
|
203
218
|
transferredBytes?: number;
|
|
204
219
|
/** Two-SSH route actually used, when applicable. */
|
|
205
220
|
route?: 'local-ssh' | 'ssh-ssh-forward' | 'ssh-ssh-stdio';
|
|
221
|
+
/** S3/S4: exit-code-only tier (D1=A) + unified failure envelope. */
|
|
222
|
+
verification: 'native-exit-only';
|
|
223
|
+
ok: boolean;
|
|
224
|
+
reason?: 'non-zero-exit' | 'aborted';
|
|
206
225
|
}
|
|
207
226
|
/**
|
|
208
227
|
* Native rsync with ordered caller options. Local↔SSH runs rsync directly with
|
package/remote/plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
import { chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { constants, createReadStream, createWriteStream, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
4
4
|
import { pipeline } from "node:stream/promises";
|
|
5
5
|
import { dirname, isAbsolute, join, posix, resolve } from "node:path";
|
|
6
6
|
import { Readable } from "node:stream";
|
|
@@ -1210,17 +1210,18 @@ async function copyTree(request, source, destination, signal, scratchRoot = join
|
|
|
1210
1210
|
signal.throwIfAborted();
|
|
1211
1211
|
if (request.argv !== void 0 || request.pullArgv !== void 0 || request.pushArgv !== void 0) throw new Error("copy: argv/pullArgv/pushArgv are native CLI options; the Hub/local staged lane does not accept CLI options");
|
|
1212
1212
|
await mkdir(scratchRoot, { recursive: true });
|
|
1213
|
-
signal.throwIfAborted();
|
|
1214
|
-
const scratch = await mkdtemp(join(scratchRoot, "remote-copy-")), payload = join(scratch, "payload.bin");
|
|
1215
1213
|
const receipt = {
|
|
1216
1214
|
action: "copy",
|
|
1215
|
+
route: "hub-staged",
|
|
1217
1216
|
src: request.src,
|
|
1218
1217
|
dst: request.dst,
|
|
1219
|
-
|
|
1218
|
+
ok: false,
|
|
1219
|
+
verification: "not-applicable",
|
|
1220
1220
|
bytes: 0,
|
|
1221
1221
|
entries: 0,
|
|
1222
1222
|
scope: "transferred entries"
|
|
1223
1223
|
};
|
|
1224
|
+
const scratch = await mkdtemp(join(scratchRoot, "remote-copy-")), payload = join(scratch, "payload.bin");
|
|
1224
1225
|
try {
|
|
1225
1226
|
await pipeline(await source.endpoint.exportTree(source.path, signal), createWriteStream(payload, {
|
|
1226
1227
|
flags: "wx",
|
|
@@ -1244,24 +1245,79 @@ async function copyTree(request, source, destination, signal, scratchRoot = join
|
|
|
1244
1245
|
const dest = await destination.endpoint.digest(destination.path, signal, selection);
|
|
1245
1246
|
receipt.destinationDigest = dest.sha256;
|
|
1246
1247
|
if (dest.sha256 !== receipt.sourceDigest) throw new Error("destination digest mismatch");
|
|
1247
|
-
receipt.
|
|
1248
|
+
receipt.verification = "sha256";
|
|
1248
1249
|
}
|
|
1249
1250
|
signal.throwIfAborted();
|
|
1250
1251
|
await rm(scratch, {
|
|
1251
1252
|
recursive: true,
|
|
1252
1253
|
force: true
|
|
1253
1254
|
});
|
|
1255
|
+
receipt.ok = true;
|
|
1254
1256
|
return receipt;
|
|
1255
1257
|
} catch (error) {
|
|
1258
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1259
|
+
const reason = signal.aborted ? "aborted" : /mismatch/i.test(message) ? "digest-mismatch" : "staging-error";
|
|
1256
1260
|
throw new CopyError({
|
|
1257
1261
|
...receipt,
|
|
1258
|
-
|
|
1262
|
+
ok: false,
|
|
1263
|
+
reason,
|
|
1259
1264
|
scratch,
|
|
1260
|
-
error:
|
|
1265
|
+
error: message
|
|
1261
1266
|
});
|
|
1262
1267
|
}
|
|
1263
1268
|
}
|
|
1264
1269
|
|
|
1270
|
+
//#endregion
|
|
1271
|
+
//#region src/remote/ssh-argv.ts
|
|
1272
|
+
/**
|
|
1273
|
+
* ONE identity-assembly policy for every SSH-speaking lane (user close-out
|
|
1274
|
+
* ruling 2026-10-17: "four builders necessarily mean four identity
|
|
1275
|
+
* semantics" — the structural root of F1).
|
|
1276
|
+
*
|
|
1277
|
+
* Assembly: explicit connection fields (byossh registrations; caller options
|
|
1278
|
+
* may override later by the CLI's own last-option-wins) in ONE order —
|
|
1279
|
+
* user, port, key, proxyJump — spoken in the target CLI's dialect.
|
|
1280
|
+
*
|
|
1281
|
+
* Resolution: config-side identity (aliases, config-only IdentityFile paths)
|
|
1282
|
+
* is read from the ORIGIN's own ssh config natively by every lane. The single
|
|
1283
|
+
* exception is the scp -3 temp config (ssh -F sees no user config), where
|
|
1284
|
+
* tempSshConfig inlines the `ssh -G`-resolved fields — paths only, never key
|
|
1285
|
+
* material (F1, 2026-10-14). That policy lives beside this builder in
|
|
1286
|
+
* native-copy.ts and must stay the only such inline site.
|
|
1287
|
+
*
|
|
1288
|
+
* Former duplicate builders now reduced here: sshArgv (execution/probes),
|
|
1289
|
+
* identityArgv (scp routes), rsyncRsh (RSYNC_RSH wiring), relaySsh
|
|
1290
|
+
* (dual-SSH relay). Behavior is byte-identical to each former site.
|
|
1291
|
+
*/
|
|
1292
|
+
/** Identity options ONLY for one connection, in the target CLI's dialect:
|
|
1293
|
+
* user, port, key, proxyJump — conditional, one order, NO base flags (each
|
|
1294
|
+
* lane's dialect base — BatchMode, -T, hardening — stays at its call site).
|
|
1295
|
+
* scp carries the user inside its user@host operand, so the scp dialect
|
|
1296
|
+
* never emits -l; its port flag is -P. */
|
|
1297
|
+
function sshIdentityArgv(connection, dialect) {
|
|
1298
|
+
const args = [];
|
|
1299
|
+
if (dialect === "ssh" && connection.user !== void 0) args.push("-l", connection.user);
|
|
1300
|
+
if (connection.port !== void 0) args.push(dialect === "scp" ? "-P" : "-p", String(connection.port));
|
|
1301
|
+
if (connection.key !== void 0) args.push("-i", connection.key);
|
|
1302
|
+
if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
|
|
1303
|
+
return args;
|
|
1304
|
+
}
|
|
1305
|
+
/** Full ssh invocation around the shared identity core. base and extra are
|
|
1306
|
+
* trusted internal argv (dialect flags incl. BatchMode, forwarding options)
|
|
1307
|
+
* — never a model-provided shell string. */
|
|
1308
|
+
function sshCommandArgv(connection, base, extra, command) {
|
|
1309
|
+
const args = [
|
|
1310
|
+
"ssh",
|
|
1311
|
+
...base,
|
|
1312
|
+
...sshIdentityArgv(connection, "ssh"),
|
|
1313
|
+
...extra,
|
|
1314
|
+
"--",
|
|
1315
|
+
connection.host
|
|
1316
|
+
];
|
|
1317
|
+
if (command !== void 0) args.push(command);
|
|
1318
|
+
return args;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1265
1321
|
//#endregion
|
|
1266
1322
|
//#region src/remote/output-ring.ts
|
|
1267
1323
|
const continuation = (byte) => (byte & 192) === 128;
|
|
@@ -1567,15 +1623,17 @@ function scpSpec(connection, path) {
|
|
|
1567
1623
|
const host = connection.host.includes(":") ? `[${connection.host}]` : connection.host;
|
|
1568
1624
|
return `${connection.user !== void 0 ? `${connection.user}@${host}` : host}:${path}`;
|
|
1569
1625
|
}
|
|
1570
|
-
/** Connection identity as scp options
|
|
1571
|
-
*
|
|
1572
|
-
* precedence (last option wins).
|
|
1626
|
+
/** Connection identity as scp options, placed BEFORE caller argv so an
|
|
1627
|
+
* explicit caller -F/-i/-P/-J overrides the registered identity by scp's own
|
|
1628
|
+
* precedence (last option wins). Assembly itself lives in ssh-argv.ts
|
|
1629
|
+
* (the ONE identity policy — 2026-10-17 ruling); only the scp dialect
|
|
1630
|
+
* placement rule is local. */
|
|
1573
1631
|
function identityArgv(connection) {
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1632
|
+
return [
|
|
1633
|
+
"-o",
|
|
1634
|
+
"BatchMode=yes",
|
|
1635
|
+
...sshIdentityArgv(connection, "scp")
|
|
1636
|
+
];
|
|
1579
1637
|
}
|
|
1580
1638
|
function sshEndpoint(plan) {
|
|
1581
1639
|
if (plan.kind !== "ssh") throw new Error("remote: native copy endpoint is not an SSH target");
|
|
@@ -1614,18 +1672,23 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1614
1672
|
stdout: [],
|
|
1615
1673
|
stderr: [],
|
|
1616
1674
|
outputTruncated: false,
|
|
1617
|
-
|
|
1675
|
+
verification: "native-exit-only",
|
|
1676
|
+
ok: true,
|
|
1618
1677
|
cleanup: "none-required"
|
|
1619
1678
|
};
|
|
1620
|
-
const finish = (route, argvs, results) => {
|
|
1679
|
+
const finish = (route, argvs, results, internal = []) => {
|
|
1621
1680
|
receipt.route = route;
|
|
1622
|
-
receipt.argv = argvs;
|
|
1681
|
+
receipt.argv = argvs.map((argv) => argv.map((token) => internal.find(([secret]) => secret === token)?.[1] ?? token));
|
|
1623
1682
|
receipt.exits = results.map((result) => result.exit);
|
|
1624
1683
|
receipt.stdout = results.map((result) => result.stdout);
|
|
1625
1684
|
receipt.stderr = results.map((result) => result.stderr);
|
|
1626
1685
|
receipt.outputTruncated = results.some((result) => result.outputTruncated);
|
|
1686
|
+
receipt.ok = !signal.aborted && receipt.exits.every((exit) => exit === 0);
|
|
1687
|
+
if (!receipt.ok) receipt.reason = signal.aborted ? "aborted" : "non-zero-exit";
|
|
1627
1688
|
return receipt;
|
|
1628
1689
|
};
|
|
1690
|
+
if (request.mkdir !== false) await ensureDestinationDir(endpoints.dst, signal, options.execArgvFor, wrap, receipt);
|
|
1691
|
+
else receipt.created = "n-a";
|
|
1629
1692
|
if (s === "local" && t === "ssh") {
|
|
1630
1693
|
rejectRemoteToRemoteScp(oneShot);
|
|
1631
1694
|
const connection = sshEndpoint(endpoints.dst.plan).connection;
|
|
@@ -1678,7 +1741,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1678
1741
|
srcSpec,
|
|
1679
1742
|
dstSpec
|
|
1680
1743
|
];
|
|
1681
|
-
return finish("scp
|
|
1744
|
+
return finish("scp-3", [argv], [await runNative(wrap(argv), signal)], config !== void 0 ? [[config.path, "<temp-ssh-config>"]] : []);
|
|
1682
1745
|
} catch (error) {
|
|
1683
1746
|
if (signal.aborted) unknown = true;
|
|
1684
1747
|
throw error;
|
|
@@ -1699,7 +1762,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1699
1762
|
];
|
|
1700
1763
|
const result = await runNative(wrap(argv), signal);
|
|
1701
1764
|
withSymlinkHint(result);
|
|
1702
|
-
return finish("docker
|
|
1765
|
+
return finish("docker-cp", [argv], [result]);
|
|
1703
1766
|
}
|
|
1704
1767
|
if (s === "docker" && t === "local") {
|
|
1705
1768
|
const argv = [
|
|
@@ -1711,7 +1774,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1711
1774
|
];
|
|
1712
1775
|
const result = await runNative(wrap(argv), signal);
|
|
1713
1776
|
withSymlinkHint(result);
|
|
1714
|
-
return finish("docker
|
|
1777
|
+
return finish("docker-cp", [argv], [result]);
|
|
1715
1778
|
}
|
|
1716
1779
|
if (s === "local" && t === "incus") {
|
|
1717
1780
|
const argv = [
|
|
@@ -1723,7 +1786,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1723
1786
|
endpoints.src.path,
|
|
1724
1787
|
`${containerOf(endpoints.dst.plan)}${endpoints.dst.path}`
|
|
1725
1788
|
];
|
|
1726
|
-
return finish("incus
|
|
1789
|
+
return finish("incus-file-push", [argv], [await runNative(wrap(argv), signal)]);
|
|
1727
1790
|
}
|
|
1728
1791
|
if (s === "incus" && t === "local") {
|
|
1729
1792
|
const argv = [
|
|
@@ -1734,7 +1797,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1734
1797
|
`${containerOf(endpoints.src.plan)}${endpoints.src.path}`,
|
|
1735
1798
|
endpoints.dst.path
|
|
1736
1799
|
];
|
|
1737
|
-
return finish("incus
|
|
1800
|
+
return finish("incus-file-pull", [argv], [await runNative(wrap(argv), signal)]);
|
|
1738
1801
|
}
|
|
1739
1802
|
if (s === "local" || t === "local") throw new Error("remote: this endpoint pair is a single native command route; pullArgv/pushArgv apply to cross-endpoint pairs only");
|
|
1740
1803
|
const pullOptions = request.pullArgv !== void 0 ? pull : oneShot;
|
|
@@ -1747,9 +1810,12 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
1747
1810
|
const pullArgv = segmentArgv(endpoints.src.plan, endpoints.src.path, staged, pullOptions, "pull");
|
|
1748
1811
|
const pushArgv = segmentArgv(endpoints.dst.plan, endpoints.dst.path, staged, pushOptions, "push");
|
|
1749
1812
|
const first = await runNative(wrap(pullArgv), signal);
|
|
1750
|
-
if (first.exit !== 0)
|
|
1813
|
+
if (first.exit !== 0) {
|
|
1814
|
+
receipt.note = "push not started: pull failed";
|
|
1815
|
+
return finish("staged-pull-push", [pullArgv], [first], [[staged, "<staging>"]]);
|
|
1816
|
+
}
|
|
1751
1817
|
const second = await runNative(wrap(pushArgv), signal);
|
|
1752
|
-
return finish("
|
|
1818
|
+
return finish("staged-pull-push", [pullArgv, pushArgv], [first, second], [[staged, "<staging>"]]);
|
|
1753
1819
|
} finally {
|
|
1754
1820
|
receipt.cleanup = await rm(scratch, {
|
|
1755
1821
|
recursive: true,
|
|
@@ -1810,6 +1876,58 @@ function segmentArgv(plan, remote, staged, opts, leg) {
|
|
|
1810
1876
|
spec
|
|
1811
1877
|
];
|
|
1812
1878
|
}
|
|
1879
|
+
/** D3 mkdir pre-hop: ensure the destination's PARENT directory exists before
|
|
1880
|
+
* the transfer (never the operand itself — see parent-only note below).
|
|
1881
|
+
* transfer. Local dst is node-side; remote dst runs ONE exec `mkdir -p` probe
|
|
1882
|
+
* whose stdout discloses created-vs-existed. Best-effort by contract: any
|
|
1883
|
+
* failure records created:'n-a' + note and lets the copy proceed so the native
|
|
1884
|
+
* CLI surfaces the real error (and stopped-container docker cp keeps working). */
|
|
1885
|
+
async function ensureDestinationDir(dst, signal, execArgvFor, wrap, receipt) {
|
|
1886
|
+
const dir = dirname(dst.path);
|
|
1887
|
+
if (dir === "/" || dir === ".") {
|
|
1888
|
+
receipt.created = false;
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (dst.plan.kind === "local") {
|
|
1892
|
+
try {
|
|
1893
|
+
if (existsSync(dir)) {
|
|
1894
|
+
receipt.created = false;
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
mkdirSync(dir, { recursive: true });
|
|
1898
|
+
receipt.created = true;
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
receipt.created = "n-a";
|
|
1901
|
+
receipt.note = `mkdir pre-hop failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1902
|
+
}
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (execArgvFor === void 0) {
|
|
1906
|
+
receipt.created = "n-a";
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
if (/[\r\n]/.test(dir)) {
|
|
1910
|
+
receipt.created = "n-a";
|
|
1911
|
+
receipt.note = "mkdir pre-hop skipped: newline-bearing destination path";
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
const quoted = `'${dir.replaceAll("'", "'\\\\''")}'`;
|
|
1915
|
+
const script = `if [ -d ${quoted} ]; then echo existed; else mkdir -p ${quoted} && echo created; fi`;
|
|
1916
|
+
try {
|
|
1917
|
+
const result = await runNative(wrap(execArgvFor(dst.plan, script)), signal);
|
|
1918
|
+
const word = result.stdout.trim().split("\n").pop();
|
|
1919
|
+
if (result.exit === 0 && word === "created") receipt.created = true;
|
|
1920
|
+
else if (result.exit === 0 && word === "existed") receipt.created = false;
|
|
1921
|
+
else {
|
|
1922
|
+
receipt.created = "n-a";
|
|
1923
|
+
const tail = result.stderr.trim().split("\n").filter((line) => line.length > 0).pop();
|
|
1924
|
+
receipt.note = `mkdir pre-hop unavailable (exit ${result.exit})${tail !== void 0 ? `: ${tail}` : ""}`;
|
|
1925
|
+
}
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
receipt.created = "n-a";
|
|
1928
|
+
receipt.note = `mkdir pre-hop failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1813
1931
|
/** Distinguishing identities for scp -3: an origin-only temp config maps two
|
|
1814
1932
|
* synthetic aliases; keys are referenced in place, never copied to any remote. */
|
|
1815
1933
|
/** D8 (2026-10-14 user adoption, third acceptance round): a LOCAL directory source gets
|
|
@@ -1910,10 +2028,10 @@ async function resolveThroughOriginConfig(c, runNative$1) {
|
|
|
1910
2028
|
//#endregion
|
|
1911
2029
|
//#region src/remote/ssh-rsync-native-relay.ts
|
|
1912
2030
|
/** ssh argv for the relay: batch, no agent, no multiplexing. `command` is the
|
|
1913
|
-
* remote command; forwarding options go before `--`, the host after it.
|
|
2031
|
+
* remote command; forwarding options go before `--`, the host after it.
|
|
2032
|
+
* Identity core comes from ssh-argv.ts (the ONE policy — 2026-10-17 ruling). */
|
|
1914
2033
|
function relaySsh(connection, extra, command) {
|
|
1915
|
-
|
|
1916
|
-
"ssh",
|
|
2034
|
+
return sshCommandArgv(connection, [
|
|
1917
2035
|
"-o",
|
|
1918
2036
|
"BatchMode=yes",
|
|
1919
2037
|
"-T",
|
|
@@ -1930,14 +2048,7 @@ function relaySsh(connection, extra, command) {
|
|
|
1930
2048
|
"ServerAliveInterval=5",
|
|
1931
2049
|
"-o",
|
|
1932
2050
|
"ServerAliveCountMax=2"
|
|
1933
|
-
];
|
|
1934
|
-
if (connection.user !== void 0) args.push("-l", connection.user);
|
|
1935
|
-
if (connection.port !== void 0) args.push("-p", String(connection.port));
|
|
1936
|
-
if (connection.key !== void 0) args.push("-i", connection.key);
|
|
1937
|
-
if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
|
|
1938
|
-
args.push(...extra, "--", connection.host);
|
|
1939
|
-
if (command !== void 0) args.push(command);
|
|
1940
|
-
return args;
|
|
2051
|
+
], extra, command);
|
|
1941
2052
|
}
|
|
1942
2053
|
async function freePorts(count) {
|
|
1943
2054
|
const servers = [];
|
|
@@ -2220,14 +2331,7 @@ async function runRsync(request, source, destination, signal, options = {}) {
|
|
|
2220
2331
|
checkAbsolute(destination.path);
|
|
2221
2332
|
const argv = checkArgv("argv", request.argv);
|
|
2222
2333
|
if ("pullArgv" in request || "pushArgv" in request) throw new Error("remote: rsync takes argv only; pullArgv/pushArgv are copy-only fields");
|
|
2223
|
-
for (const
|
|
2224
|
-
"dryRun",
|
|
2225
|
-
"delete",
|
|
2226
|
-
"excludes",
|
|
2227
|
-
"preserve",
|
|
2228
|
-
"verify",
|
|
2229
|
-
"mkdir"
|
|
2230
|
-
]) if (legacy in request) throw new Error(`remote: rsync field "${legacy}" was removed — pass native options through argv instead (e.g. "--dry-run", "--delete", "--exclude=<pattern>", "-a"/"-r"; verify/mkdir belong to the copy Hub/local lane)`);
|
|
2334
|
+
for (const key of Object.keys(request)) if (key !== "src" && key !== "dst" && key !== "argv") throw new Error(`remote: rsync rejects unknown field "${key}" — native options go through argv (verify is a copy Hub-lane option; mkdir is copy-only; for destination parents pass --mkpath yourself)`);
|
|
2231
2335
|
if (!source.connection && !destination.connection) throw new Error("rsync requires at least one SSH endpoint; local-only is unsupported");
|
|
2232
2336
|
let result;
|
|
2233
2337
|
let route;
|
|
@@ -2275,6 +2379,9 @@ async function runRsync(request, source, destination, signal, options = {}) {
|
|
|
2275
2379
|
dst: request.dst,
|
|
2276
2380
|
argv,
|
|
2277
2381
|
route,
|
|
2382
|
+
verification: "native-exit-only",
|
|
2383
|
+
ok: result.exit === 0,
|
|
2384
|
+
...result.exit === 0 ? {} : { reason: signal.aborted ? "aborted" : "non-zero-exit" },
|
|
2278
2385
|
command: `rsync ${argv.join(" ")}`,
|
|
2279
2386
|
...itemized.length > 0 || argv.includes("--itemize-changes") || argv.includes("-i") ? { itemizedChanges: itemized } : {},
|
|
2280
2387
|
...literalBytes === void 0 ? {} : { literalBytes },
|
|
@@ -2289,21 +2396,14 @@ async function runRsync(request, source, destination, signal, options = {}) {
|
|
|
2289
2396
|
* placeholder host rsync appends and execs the canonical SSH argv. All tokens
|
|
2290
2397
|
* are tool-built argv (identity options/aliases), never model text. */
|
|
2291
2398
|
function rsyncRsh(connection, wrap) {
|
|
2292
|
-
const args = [
|
|
2293
|
-
"ssh",
|
|
2294
|
-
"-o",
|
|
2295
|
-
"BatchMode=yes",
|
|
2296
|
-
"-T"
|
|
2297
|
-
];
|
|
2298
|
-
if (connection.user !== void 0) args.push("-l", connection.user);
|
|
2299
|
-
if (connection.port !== void 0) args.push("-p", String(connection.port));
|
|
2300
|
-
if (connection.key !== void 0) args.push("-i", connection.key);
|
|
2301
|
-
if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
|
|
2302
|
-
args.push("--", connection.host);
|
|
2303
2399
|
return [
|
|
2304
2400
|
"bash",
|
|
2305
2401
|
"-c",
|
|
2306
|
-
`shift; exec ${wrap(
|
|
2402
|
+
`shift; exec ${wrap(sshCommandArgv(connection, [
|
|
2403
|
+
"-o",
|
|
2404
|
+
"BatchMode=yes",
|
|
2405
|
+
"-T"
|
|
2406
|
+
], [])).map(rshQuote).join(" ")} "$@"`,
|
|
2307
2407
|
"rsync-rsh"
|
|
2308
2408
|
].map(rshQuote).join(" ");
|
|
2309
2409
|
}
|
|
@@ -2399,26 +2499,19 @@ var SshTargets = class {
|
|
|
2399
2499
|
}
|
|
2400
2500
|
};
|
|
2401
2501
|
/** Shared by execution, probes and file/relay consumers. extraArgs is trusted
|
|
2402
|
-
* internal SSH argv (e.g. forwarding), never a model-provided shell string.
|
|
2502
|
+
* internal SSH argv (e.g. forwarding), never a model-provided shell string.
|
|
2503
|
+
* Identity assembly lives in ssh-argv.ts (the ONE policy — 2026-10-17 ruling);
|
|
2504
|
+
* this wrapper only picks the tty/batch dialect base. */
|
|
2403
2505
|
function sshArgv(connection, options = {}) {
|
|
2404
|
-
|
|
2405
|
-
"ssh",
|
|
2506
|
+
return sshCommandArgv(connection, options.tty ? [
|
|
2406
2507
|
"-tt",
|
|
2407
2508
|
"-o",
|
|
2408
2509
|
"BatchMode=yes"
|
|
2409
2510
|
] : [
|
|
2410
|
-
"ssh",
|
|
2411
2511
|
"-o",
|
|
2412
2512
|
"BatchMode=yes",
|
|
2413
2513
|
"-T"
|
|
2414
|
-
];
|
|
2415
|
-
if (connection.user !== void 0) args.push("-l", connection.user);
|
|
2416
|
-
if (connection.port !== void 0) args.push("-p", String(connection.port));
|
|
2417
|
-
if (connection.key !== void 0) args.push("-i", connection.key);
|
|
2418
|
-
if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
|
|
2419
|
-
args.push(...options.extraArgs ?? [], "--", connection.host);
|
|
2420
|
-
if (options.command !== void 0) args.push(options.command);
|
|
2421
|
-
return args;
|
|
2514
|
+
], options.extraArgs ?? [], options.command);
|
|
2422
2515
|
}
|
|
2423
2516
|
|
|
2424
2517
|
//#endregion
|
|
@@ -3580,13 +3673,18 @@ var RemoteDriver = class {
|
|
|
3580
3673
|
path: dst.path,
|
|
3581
3674
|
endpoint: localFileEndpoint()
|
|
3582
3675
|
}, combined);
|
|
3583
|
-
if (request.verify !== void 0
|
|
3676
|
+
if (request.verify !== void 0) throw new Error("copy: verify is a Hub/local staged lane option (independent SHA-256); native routes report exit-code-only verification — self-verify via oneshot digest commands when needed");
|
|
3584
3677
|
const planOf = (end) => end.kind === "cli" ? end.plan : end.kind === "local" ? { kind: "local" } : void 0;
|
|
3585
3678
|
const srcPlan = planOf(src), dstPlan = planOf(dst);
|
|
3586
3679
|
if (srcPlan === void 0 || dstPlan === void 0) throw new Error("copy: unreachable route resolution");
|
|
3680
|
+
const execArgvFor = (plan, script) => {
|
|
3681
|
+
const argv = helperShellArgv(plan, script);
|
|
3682
|
+
return this.opts.nativeArgvFor ? this.opts.nativeArgvFor(argv) : argv;
|
|
3683
|
+
};
|
|
3587
3684
|
return nativeCopy({
|
|
3588
3685
|
src: request.src,
|
|
3589
3686
|
dst: request.dst,
|
|
3687
|
+
...request.mkdir === void 0 ? {} : { mkdir: request.mkdir },
|
|
3590
3688
|
...request.argv === void 0 ? {} : { argv: request.argv },
|
|
3591
3689
|
...request.pullArgv === void 0 ? {} : { pullArgv: request.pullArgv },
|
|
3592
3690
|
...request.pushArgv === void 0 ? {} : { pushArgv: request.pushArgv }
|
|
@@ -3599,7 +3697,10 @@ var RemoteDriver = class {
|
|
|
3599
3697
|
plan: dstPlan,
|
|
3600
3698
|
path: dst.path
|
|
3601
3699
|
}
|
|
3602
|
-
}, combined, {
|
|
3700
|
+
}, combined, {
|
|
3701
|
+
wrapArgv: this.opts.nativeArgvFor,
|
|
3702
|
+
execArgvFor
|
|
3703
|
+
});
|
|
3603
3704
|
})();
|
|
3604
3705
|
this.starting.add(work);
|
|
3605
3706
|
work.then(() => this.starting.delete(work), () => this.starting.delete(work));
|
|
@@ -4178,7 +4279,7 @@ Bare names resolve ssh config → docker → incus → live pty; unmatched goes
|
|
|
4178
4279
|
|
|
4179
4280
|
oneshot: stateless bash -lc, stdout/stderr separate, exit code verbatim. Supervision prefers a pinned superd-run helper cached on the endpoint (~/.cache/superd/run; first call uploads it over the same exec lane) and falls back to a POSIX wrapper everywhere else — the result field "runner" says which ran. Helper or wrapper, backgrounded children are reaped on completion and the whole group is killed on timeout/cancel; the endpoint needs bash only (no Python).
|
|
4180
4281
|
|
|
4181
|
-
Files — {action:"copy" | "rsync", src:"[target:]abs", dst:"[target:]abs", argv:["-a"]}: argv tokens pass verbatim before src/dst — no whitelist, no forced defaults (dry run = pass "--dry-run" yourself); the CLI's own exit code and stderr come back in the receipt. copy: local↔SSH scp; SSH↔SSH scp -3 through the origin (identities stay local); docker cp; incus file push/pull; any other pair = two commands with origin staging (per-half options via pullArgv/pushArgv, exclusive with argv); scp -R (uppercase — scp's remote-client mode) is refused; copy of a directory from a LOCAL source auto-appends the route's recursive flag (-r, shown in the receipt argv); from a REMOTE source pass -r yourself or use rsync;
|
|
4282
|
+
Files — {action:"copy" | "rsync", src:"[target:]abs", dst:"[target:]abs", argv:["-a"]}: argv tokens pass verbatim before src/dst — no whitelist, no forced defaults (dry run = pass "--dry-run" yourself); the CLI's own exit code and stderr come back in the receipt. copy: local↔SSH scp; SSH↔SSH scp -3 through the origin (identities stay local); docker cp; incus file push/pull; any other pair = two commands with origin staging (per-half options via pullArgv/pushArgv, exclusive with argv); scp -R (uppercase — scp's remote-client mode) is refused; copy of a directory from a LOCAL source auto-appends the route's recursive flag (-r, shown in the receipt argv); from a REMOTE source pass -r yourself or use rsync; mkdir defaults to ensuring the destination's PARENT directory first (one exec pre-hop, outcome in the receipt created field; the operand itself is never pre-created); receipts use stable route ids (scp / scp-3 / docker-cp / incus-file-push / incus-file-pull / staged-pull-push / hub-staged) with ok+reason, the verification tier actually performed (native-exit-only by default; sha256 on the Hub lane) and tool-owned temp paths in argv shown as placeholders. rsync: local↔SSH, or SSH↔SSH with one protocol conversation between the peers (real delta; forwarding denied → honest error, never full copy). rsync src trailing slash copies contents, no slash the directory itself.
|
|
4182
4283
|
|
|
4183
4284
|
Output: pty tails at ~30k chars ("[truncated: …]" header) — redirect bulk output to a file. Hard retention 4,000,000 UTF-16 units per stream (Hub 4MiB bytes); outputStats reports the rest; restart cannot recover discarded output. stdin feeds the command; timeout (seconds) interrupts then kills. run_in_background:true (oneshot) → jobId for job_output/job_kill. Native transport errors pass through verbatim — self-correct from them.`;
|
|
4184
4285
|
function createRemoteTool(driver, opts = {}) {
|
|
@@ -4217,11 +4318,11 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
4217
4318
|
},
|
|
4218
4319
|
verify: {
|
|
4219
4320
|
type: "boolean",
|
|
4220
|
-
description: "copy only
|
|
4321
|
+
description: "copy only. Receipts name the verification tier actually performed (D1=A): native CLI routes are \"native-exit-only\" (no independent check — self-verify via oneshot digest commands when needed); the Hub↔local staged lane computes independent SHA-256 digests by default and verify:false skips them (\"not-applicable\"). No hash tier exists on native routes."
|
|
4221
4322
|
},
|
|
4222
4323
|
mkdir: {
|
|
4223
4324
|
type: "boolean",
|
|
4224
|
-
description: "copy only,
|
|
4325
|
+
description: "copy only, all routes. Default true: before transfer, one best-effort exec pre-hop ensures the destination's PARENT directory exists (the operand itself is never pre-created — each CLI's own layout semantics stay byte-identical). Receipt created field: true (parent created this call) / false (already existed) / \"n-a\" (skipped or pre-hop unavailable, e.g. docker exec on a stopped container — the native CLI then reports the real error itself). false skips the pre-hop."
|
|
4225
4326
|
},
|
|
4226
4327
|
run_in_background: {
|
|
4227
4328
|
type: "boolean",
|
|
@@ -4344,12 +4445,26 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
4344
4445
|
args.timeout,
|
|
4345
4446
|
args.run_in_background
|
|
4346
4447
|
].some((value) => value !== void 0)) throw new Error(`${args.action} cannot be combined with execution/PTY/job arguments; register byossh separately first`);
|
|
4347
|
-
|
|
4348
|
-
"
|
|
4349
|
-
"
|
|
4350
|
-
"
|
|
4351
|
-
"
|
|
4352
|
-
|
|
4448
|
+
const KNOWN_PARAMS = new Set([
|
|
4449
|
+
"action",
|
|
4450
|
+
"src",
|
|
4451
|
+
"dst",
|
|
4452
|
+
"argv",
|
|
4453
|
+
"pullArgv",
|
|
4454
|
+
"pushArgv",
|
|
4455
|
+
"verify",
|
|
4456
|
+
"mkdir",
|
|
4457
|
+
"run_in_background",
|
|
4458
|
+
"target",
|
|
4459
|
+
"ssh",
|
|
4460
|
+
"spawn",
|
|
4461
|
+
"cmd",
|
|
4462
|
+
"script",
|
|
4463
|
+
"mode",
|
|
4464
|
+
"stdin",
|
|
4465
|
+
"timeout"
|
|
4466
|
+
]);
|
|
4467
|
+
for (const key of Object.keys(args)) if (!KNOWN_PARAMS.has(key)) throw new Error(`remote: unknown parameter "${key}" — this surface has no legacy aliases (dryRun/delete/excludes/preserve are gone); pass native CLI options through argv instead`);
|
|
4353
4468
|
if (args.action === "rsync") {
|
|
4354
4469
|
if (args.pullArgv !== void 0 || args.pushArgv !== void 0) throw new Error("rsync takes argv only; pullArgv/pushArgv are copy-only fields");
|
|
4355
4470
|
if ([args.verify, args.mkdir].some((value) => value !== void 0)) throw new Error("rsync cannot be combined with copy verify/mkdir options; the Hub/local staged lane is copy-only");
|
|
@@ -4359,13 +4474,14 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
4359
4474
|
dst: args.dst,
|
|
4360
4475
|
...args.argv === void 0 ? {} : { argv: args.argv }
|
|
4361
4476
|
}, exec.signal, { sessionKey: getSessionKey(exec) });
|
|
4362
|
-
if (receipt$1.exit !== 0) throw new Error(JSON.stringify(receipt$1));
|
|
4477
|
+
if (receipt$1.ok === false || receipt$1.exit !== 0) throw new Error(JSON.stringify(receipt$1));
|
|
4478
|
+
const rsyncDuration = Date.now() - start;
|
|
4363
4479
|
return {
|
|
4364
4480
|
kind: "rsync",
|
|
4365
|
-
text:
|
|
4481
|
+
text: formatTransferLine("rsync", receipt$1, rsyncDuration),
|
|
4366
4482
|
receipt: receipt$1,
|
|
4367
4483
|
exit: receipt$1.exit,
|
|
4368
|
-
durationMs:
|
|
4484
|
+
durationMs: rsyncDuration
|
|
4369
4485
|
};
|
|
4370
4486
|
}
|
|
4371
4487
|
const copyStart = Date.now();
|
|
@@ -4378,14 +4494,16 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
4378
4494
|
...args.pullArgv === void 0 ? {} : { pullArgv: args.pullArgv },
|
|
4379
4495
|
...args.pushArgv === void 0 ? {} : { pushArgv: args.pushArgv }
|
|
4380
4496
|
}, exec.signal, { sessionKey: getSessionKey(exec) });
|
|
4381
|
-
const
|
|
4382
|
-
|
|
4497
|
+
const asReceipt = receipt;
|
|
4498
|
+
const exits = asReceipt.exits ?? [];
|
|
4499
|
+
if (asReceipt.ok === false || exits.some((code) => code !== 0)) throw new Error(JSON.stringify(receipt));
|
|
4500
|
+
const copyDuration = Date.now() - copyStart;
|
|
4383
4501
|
return {
|
|
4384
4502
|
kind: "copy",
|
|
4385
|
-
text:
|
|
4503
|
+
text: formatTransferLine("copy", receipt, copyDuration),
|
|
4386
4504
|
receipt,
|
|
4387
4505
|
exit: exits.length > 0 ? 0 : null,
|
|
4388
|
-
durationMs:
|
|
4506
|
+
durationMs: copyDuration
|
|
4389
4507
|
};
|
|
4390
4508
|
}
|
|
4391
4509
|
if ([
|
|
@@ -4482,6 +4600,26 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
4482
4600
|
});
|
|
4483
4601
|
return tool;
|
|
4484
4602
|
}
|
|
4603
|
+
/** S8 (2026-10-17): copy/rsync render as one formatted line; the full receipt
|
|
4604
|
+
* stays in the receipt field — raw-JSON dumps are gone from text. */
|
|
4605
|
+
function formatTransferLine(action, receipt, durationMs) {
|
|
4606
|
+
const r = receipt;
|
|
4607
|
+
const parts = [];
|
|
4608
|
+
if (r.ok === false) parts.push(`✗ ${r.reason ?? "failed"}`);
|
|
4609
|
+
parts.push(`${action} ${r.src} → ${r.dst}`);
|
|
4610
|
+
if (r.route !== void 0 && r.route !== "") parts.push(`route ${r.route}`);
|
|
4611
|
+
if (r.exits !== void 0) parts.push(`exits [${r.exits.join(",")}]`);
|
|
4612
|
+
else if (r.exit !== void 0) parts.push(`exit ${r.exit}`);
|
|
4613
|
+
if (r.verification !== void 0) parts.push(`verification ${r.verification}`);
|
|
4614
|
+
if (r.created === true) parts.push("created yes");
|
|
4615
|
+
else if (r.created === false) parts.push("created pre-existing");
|
|
4616
|
+
else if (r.created === "n-a") parts.push("created n-a");
|
|
4617
|
+
if (r.cleanup !== void 0 && r.cleanup !== "none-required") parts.push(`cleanup ${r.cleanup}`);
|
|
4618
|
+
if (typeof r.bytes === "number") parts.push(`${r.bytes} bytes / ${r.entries} entries`);
|
|
4619
|
+
parts.push(`${(durationMs / 1e3).toFixed(1)}s`);
|
|
4620
|
+
if (r.note !== void 0 && r.note.length > 0) parts.push(`(${r.note})`);
|
|
4621
|
+
return parts.join(" · ");
|
|
4622
|
+
}
|
|
4485
4623
|
function renderValue(v) {
|
|
4486
4624
|
if (v.kind === "status" || v.kind === "roster" || v.kind === "job" || v.kind === "copy" || v.kind === "rsync") return v.text;
|
|
4487
4625
|
const parts = [];
|
package/remote/provenance.json
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
"sources": {
|
|
6
6
|
"better-dsh/src/remote/agent-registration.ts": "459301354ea1ab713984bb01bc491a080256d4e983e55ff074e2665c3213d355",
|
|
7
7
|
"better-dsh/src/remote/archive.ts": "fd32f96dc63421be7d56eb545badf355c6ee2498599605ffa48958b9efca9a63",
|
|
8
|
-
"better-dsh/src/remote/copy.ts": "
|
|
9
|
-
"better-dsh/src/remote/driver.ts": "
|
|
8
|
+
"better-dsh/src/remote/copy.ts": "ab2aec5ee8df814f4bc568370977d229c402489248c738eff147431a52650c52",
|
|
9
|
+
"better-dsh/src/remote/driver.ts": "74fe4e9bd161a1c3c43dd5143e63423a6802afe93b01177f5d7918c3b6880e36",
|
|
10
10
|
"better-dsh/src/remote/endpoints.ts": "315b6c37e6e5dbd51301aa971566b8345593560e1f2ba299e71143e147a8a17c",
|
|
11
11
|
"better-dsh/src/remote/file-worker.ts": "31ac019a944cf7fdcd0ff044f8b330d923bf4d384c77056f9b7e3032bc8c9598",
|
|
12
12
|
"better-dsh/src/remote/helper.ts": "789a802610fc480bb1c922dd284787ca22d7394fa906c729ee33d78ade79973c",
|
|
13
13
|
"better-dsh/src/remote/hub-contract.ts": "4bd333f3ef3e94305a64f04c03948a9dae4bbf5268b0d26d9de99770be2d8ac9",
|
|
14
14
|
"better-dsh/src/remote/input.ts": "7e543adb0973c25cbd24103c2d838db0fdacdd32525278d789004fc237dcd924",
|
|
15
15
|
"better-dsh/src/remote/jobs.ts": "9587f2694426fcf00ff33d9db164d5b15ea659aed7dbcb1df2640fa3f08b2149",
|
|
16
|
-
"better-dsh/src/remote/native-copy.ts": "
|
|
16
|
+
"better-dsh/src/remote/native-copy.ts": "3f8691abb14dfb7a94e956956fb4d182b7732ffbe70f1a8ff7dec5dd2b9afb7c",
|
|
17
17
|
"better-dsh/src/remote/nonce-framing.ts": "5592fec76cec0c1e1ac75f4806d0904cd111bf2061b1fada4f0dfa12014d1233",
|
|
18
18
|
"better-dsh/src/remote/oneshot.ts": "99f2c809ba1159c7865ec3cff2e6d7b1f165a7f03185a6fc9505c1a534ba57bc",
|
|
19
19
|
"better-dsh/src/remote/output-ring.ts": "a79104bd3dcddab06a3fe7e52d239a70becd4f2dcddd0c9b6513f5fe3c4ed0bc",
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
"better-dsh/src/remote/process.ts": "f4491c76457b662703e0b1e4a876299b9c8b7b3da5b304c6baee5576017de448",
|
|
22
22
|
"better-dsh/src/remote/pty-session.ts": "8ea63b3357c681c5dcacacbfb99da532cae8ed293482e7a54c0362b0f9f27a3d",
|
|
23
23
|
"better-dsh/src/remote/roster.ts": "753803521accb7a5eacc19a9bada648af8ee5fc67a152247915b92e9ddeb6887",
|
|
24
|
-
"better-dsh/src/remote/rsync.ts": "
|
|
24
|
+
"better-dsh/src/remote/rsync.ts": "f9c15279fdfa67b3265eeda216bcd0c46d8b4f05c6026cf2750a1c818ff333ba",
|
|
25
|
+
"better-dsh/src/remote/ssh-argv.ts": "36c916d21163b7db0c5a95bb196b0d5d0609fc812cfe425f4e74d91eb2fd10b4",
|
|
25
26
|
"better-dsh/src/remote/ssh-config.ts": "71ce833be5c934430e8a9ca982af055fccb71d6c3e64513ae83e7937b070e446",
|
|
26
27
|
"better-dsh/src/remote/ssh-process.ts": "9b4d754af5af0fe6a903e683642e94842f8662358b72467d41f2288e1e67fd79",
|
|
27
|
-
"better-dsh/src/remote/ssh-rsync-native-relay.ts": "
|
|
28
|
-
"better-dsh/src/remote/ssh-targets.ts": "
|
|
28
|
+
"better-dsh/src/remote/ssh-rsync-native-relay.ts": "5d9585ab30a11aeeba19ce7b43aa31f47e2ee0bde2e7c006ac3e811cf7940bdf",
|
|
29
|
+
"better-dsh/src/remote/ssh-targets.ts": "86dfdd248192ee38f8cfc5642906ec1615c54e5a3e14bd45e35880d636279a15",
|
|
29
30
|
"better-dsh/src/remote/status.ts": "54264917bd50401c5a2f2ff9df325af7a5e790597e6307fcdc46a8455d18b530",
|
|
30
31
|
"better-dsh/src/remote/target.ts": "965a04b3561aa56ee089876a869fd2116b1773ef34b053b3006905fcde063a43",
|
|
31
|
-
"better-dsh/src/remote/tool.ts": "
|
|
32
|
-
"better-dsh/src/remote/transports.ts": "
|
|
32
|
+
"better-dsh/src/remote/tool.ts": "3059d69033de389956446e01d250bdbf01e1c411422c8d4e1c20912747b0f50a",
|
|
33
|
+
"better-dsh/src/remote/transports.ts": "2983e707b287b5e2742a23de45ef0976a3ff264425b7d29f02399ee77e19742c",
|
|
33
34
|
"better-dsh/third_party/schemastery/lib/index.cjs": "aca1c1a47c7b6034feee01501aefca3e6dd97a0bac4fe334fd30647ae5a9e480",
|
|
34
35
|
"better-dsh/third_party/schemastery/lib/index.d.mts": "66847f605af58c42575333c825eefdec4ad38278ad6cb260e37aa0cc63803bbd",
|
|
35
36
|
"better-dsh/third_party/schemastery/lib/index.mjs": "cfcbe46c7416bdb35ce891c252152bbf519e08199bede5930dd7cb880c65f1ef",
|
|
@@ -57,16 +58,16 @@
|
|
|
57
58
|
},
|
|
58
59
|
"outputs": {
|
|
59
60
|
"file-worker.js": "f84f5b48de915949d85c3d0340a343cc5901218c3bc2dafdf5cecaa244f7b6e3",
|
|
60
|
-
"helper-bin/MANIFEST.json": "
|
|
61
|
-
"helper-bin/aarch64-apple-darwin/BUILT-VER": "
|
|
62
|
-
"helper-bin/aarch64-apple-darwin/superd-run": "
|
|
63
|
-
"helper-bin/aarch64-unknown-linux-musl/BUILT-VER": "
|
|
64
|
-
"helper-bin/aarch64-unknown-linux-musl/superd-run": "
|
|
65
|
-
"helper-bin/x86_64-pc-windows-gnu/BUILT-VER": "
|
|
66
|
-
"helper-bin/x86_64-pc-windows-gnu/superd-run.exe": "
|
|
67
|
-
"helper-bin/x86_64-unknown-linux-musl/BUILT-VER": "
|
|
68
|
-
"helper-bin/x86_64-unknown-linux-musl/superd-run": "
|
|
69
|
-
"plugin.d.ts": "
|
|
70
|
-
"plugin.js": "
|
|
61
|
+
"helper-bin/MANIFEST.json": "efce4956d5c23f7916f48218b81848717a425ccf78ebeb6fcc7c788015250884",
|
|
62
|
+
"helper-bin/aarch64-apple-darwin/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
63
|
+
"helper-bin/aarch64-apple-darwin/superd-run": "a8fd11a5e6a2cb146deb599414434a299ebf78c53cfc1d3e5f42617ee11c1706",
|
|
64
|
+
"helper-bin/aarch64-unknown-linux-musl/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
65
|
+
"helper-bin/aarch64-unknown-linux-musl/superd-run": "3a8660fa099f1e71de6e08313ee2aaace00503f74365475ee46e2d2a89228397",
|
|
66
|
+
"helper-bin/x86_64-pc-windows-gnu/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
67
|
+
"helper-bin/x86_64-pc-windows-gnu/superd-run.exe": "1372450238c5bda702b4529f4148aa79e80a255c3c125d1a6229afd4693e9d90",
|
|
68
|
+
"helper-bin/x86_64-unknown-linux-musl/BUILT-VER": "8601d3c2ff79795c9657b02080afffe91c42775534dd6687c6dbdc0f4924db70",
|
|
69
|
+
"helper-bin/x86_64-unknown-linux-musl/superd-run": "05b428657942abf22a0ad609433d5cab6b6182fe389851345459a0274b5a6533",
|
|
70
|
+
"plugin.d.ts": "d1cb7c08dee04fb3629727e8fa2394e2ece34d40f6a5a83adef13de1a22cd610",
|
|
71
|
+
"plugin.js": "c59076f72b0436636d9daa23dbc51e9139f8d4563a3d8de3d283e976a66fcaa4"
|
|
71
72
|
}
|
|
72
73
|
}
|
|
@@ -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
|
});
|
|
@@ -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
|
}
|
|
@@ -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.
|
|
@@ -130,15 +130,15 @@ export function registerHubRoute(options) {
|
|
|
130
130
|
});
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
133
|
+
const target = options.gatewayOf(agent);
|
|
134
|
+
if (target === undefined) {
|
|
135
135
|
answer404(res, { error: 'unknown-agent', label: agent });
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
// Consume the family plumbing: the kernel forwards a native-shaped URL
|
|
139
139
|
// (the kernel carries the same fence face as the carrier's own kernel).
|
|
140
140
|
req.url = worldUrlOf(req.url);
|
|
141
|
-
await forwardApiCall({ gateway, label: agent, mountPath: HUB_MOUNT_PATH, requestRejection: options.requestRejection }, req, res);
|
|
141
|
+
await forwardApiCall({ gateway: target.gateway, peer: target.peer, label: agent, mountPath: HUB_MOUNT_PATH, requestRejection: options.requestRejection }, req, res);
|
|
142
142
|
};
|
|
143
143
|
const handleUpgrade = (req, socket, head) => {
|
|
144
144
|
if (!gateUpgrade(options, req, socket))
|
|
@@ -155,8 +155,8 @@ export function registerHubRoute(options) {
|
|
|
155
155
|
rejectUpgrade(socket, 404, JSON.stringify({ error: 'foreign-only' }), 'application/json');
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
158
|
+
const target = options.gatewayOf(agent);
|
|
159
|
+
if (target === undefined) {
|
|
160
160
|
rejectUpgrade(socket, 404, JSON.stringify({ error: 'unknown-agent', label: agent }), 'application/json');
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
@@ -165,7 +165,7 @@ export function registerHubRoute(options) {
|
|
|
165
165
|
// a heartbeat interval and a per-connection WebSocketServer; on socket
|
|
166
166
|
// close it must be CLOSED (world-mux clears the heartbeat only in close),
|
|
167
167
|
// or every reconnect leaks the timer plus the mux graph forever.
|
|
168
|
-
const mux = worldMuxOf(gateway);
|
|
168
|
+
const mux = worldMuxOf(target.gateway, target.peer);
|
|
169
169
|
socket.once?.('close', () => {
|
|
170
170
|
void mux.close().catch(() => undefined);
|
|
171
171
|
});
|
|
@@ -22,6 +22,13 @@ export interface ForeignTarget {
|
|
|
22
22
|
key: string;
|
|
23
23
|
/** The context's typertGateway service instance (or a wire adapter with the same face). */
|
|
24
24
|
gateway: GatewayFace;
|
|
25
|
+
/**
|
|
26
|
+
* The world's owner browser Peer (connection.browserPeer) for in-process
|
|
27
|
+
* faces — DSH 0.2.1 gateways read peer.signal/principal on every forwarded
|
|
28
|
+
* invocation, so the hub must present the world's shared owner identity.
|
|
29
|
+
* Wire adapters (mesh federation) omit it: the far end supplies its own.
|
|
30
|
+
*/
|
|
31
|
+
peer?: unknown;
|
|
25
32
|
}
|
|
26
33
|
/** Register (or replace) the target for a runtime key. */
|
|
27
34
|
export declare function registerForeignTarget(target: ForeignTarget): void;
|
|
@@ -48,7 +48,7 @@ export function ownWorld(ctx, entry, spawn, onFailure) {
|
|
|
48
48
|
unregisterForeignTarget(key);
|
|
49
49
|
setReady(key, false);
|
|
50
50
|
}, `superd.world.${key}: availability`);
|
|
51
|
-
target = { key, gateway: world.get('typertGateway') };
|
|
51
|
+
target = { key, gateway: world.get('typertGateway'), peer: world.get('connection')?.browserPeer };
|
|
52
52
|
registerForeignTarget(target);
|
|
53
53
|
setReady(key, true);
|
|
54
54
|
return world;
|