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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-dsh",
3
- "version": "0.3.1-b",
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-b",
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": "715c2d7bd935f3b4cc4ae0ffb10d3fab946bb62d93f592ccfe2a7446669ff76b"
10
+ "sha256": "a8fd11a5e6a2cb146deb599414434a299ebf78c53cfc1d3e5f42617ee11c1706"
11
11
  },
12
12
  "aarch64-unknown-linux-musl": {
13
13
  "file": "superd-run",
14
- "bytes": 397416,
15
- "sha256": "22ac1c7f913092d8ca8d312b93528dd2263200f9670dc183fd215bc89d2bac68"
14
+ "bytes": 397592,
15
+ "sha256": "3a8660fa099f1e71de6e08313ee2aaace00503f74365475ee46e2d2a89228397"
16
16
  },
17
17
  "x86_64-pc-windows-gnu": {
18
18
  "file": "superd-run.exe",
19
- "bytes": 336896,
20
- "sha256": "a638479e1ccb1c4e185085100450962e39164158a76097b5087d2a3bb8880ac0"
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": "79e42879509bb87d2fe9888d282151fd17113203d3172f59a16fcb5a180ba057"
25
+ "sha256": "05b428657942abf22a0ad609433d5cab6b6182fe389851345459a0274b5a6533"
26
26
  }
27
27
  },
28
28
  "sources": {
@@ -1 +1 @@
1
- 0.3.1-b
1
+ 0.3.1-d.b596
@@ -1 +1 @@
1
- 0.3.1-b
1
+ 0.3.1-d.b596
@@ -1 +1 @@
1
- 0.3.1-b
1
+ 0.3.1-d.b596
@@ -1 +1 @@
1
- 0.3.1-b
1
+ 0.3.1-d.b596
@@ -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
- verified: boolean;
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
- /** Native CLI success only — no independent digest was computed for this path. */
158
- verified: false;
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
- verified: false,
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.verified = true;
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
- verified: false,
1262
+ ok: false,
1263
+ reason,
1259
1264
  scratch,
1260
- error: error instanceof Error ? error.message : String(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 (port, key), placed BEFORE caller argv so
1571
- * an explicit caller -F/-i/-P/-J overrides the registered identity by scp's own
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
- const options = ["-o", "BatchMode=yes"];
1575
- if (connection.port !== void 0) options.push("-P", String(connection.port));
1576
- if (connection.key !== void 0) options.push("-i", connection.key);
1577
- if (connection.proxyJump !== void 0) options.push("-J", connection.proxyJump);
1578
- return options;
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
- verified: false,
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 -3 (data through origin)", [argv], [await runNative(wrap(argv), signal)]);
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 cp", [argv], [result]);
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 cp", [argv], [result]);
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 file push", [argv], [await runNative(wrap(argv), signal)]);
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 file pull", [argv], [await runNative(wrap(argv), signal)]);
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) return finish("two native commands with origin staging (push not started: pull failed)", [pullArgv], [first]);
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("two native commands with origin staging", [pullArgv, pushArgv], [first, second]);
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
- const args = [
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 legacy of [
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(args).map(rshQuote).join(" ")} "$@"`,
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
- const args = options.tty ? [
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 || request.mkdir !== void 0) throw new Error("copy: verify/mkdir apply to the Hub/local staged lane only; native CLI routes do not verify or create parents (mirror the CLI's own behavior with argv like -r)");
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, { wrapArgv: this.opts.nativeArgvFor });
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; success is NOT independently verified; verify/mkdir only on the Hub↔local lane. 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.
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, Hub↔local↔local staged lane only: independent SHA-256 verification (default true). Native CLI routes do not verify and report that fact."
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, Hub↔local↔local staged lane only: create missing destination parents (default true). Native CLI routes let the CLI decide."
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
- for (const legacy of [
4348
- "dryRun",
4349
- "delete",
4350
- "excludes",
4351
- "preserve"
4352
- ]) if (args[legacy] !== void 0) throw new Error(`${legacy} was removed: pass native options through argv instead (e.g. ${legacy === "dryRun" ? "\"--dry-run\"" : legacy === "delete" ? "\"--delete\"" : legacy === "excludes" ? "\"--exclude=<pattern>\"" : "\"-a\" / \"-r\""})`);
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: JSON.stringify(receipt$1),
4481
+ text: formatTransferLine("rsync", receipt$1, rsyncDuration),
4366
4482
  receipt: receipt$1,
4367
4483
  exit: receipt$1.exit,
4368
- durationMs: Date.now() - start
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 exits = receipt.exits ?? [];
4382
- if (exits.some((code) => code !== 0)) throw new Error(JSON.stringify(receipt));
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: JSON.stringify(receipt),
4503
+ text: formatTransferLine("copy", receipt, copyDuration),
4386
4504
  receipt,
4387
4505
  exit: exits.length > 0 ? 0 : null,
4388
- durationMs: Date.now() - copyStart
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 = [];
@@ -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": "bd6f290d3f8ec3e33f9980f160b26390a3be38ead891b81de7cf610940a7af87",
9
- "better-dsh/src/remote/driver.ts": "ba44b100228e54681a243d4b739aba14d2b5efe98c238220f1029cf9afba5e2b",
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": "0c2045330c1007e49251643971d5edb9ef887a4964493450fd4184319d347b6e",
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": "21f10f0a24fd5148e298c938aaabeff85c96becc76317a1d3cc5c2e8a517c7c0",
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": "3a30f65f1c548badd1e8a6018c4f7cacd8f75e2aec4d7b7181b03fc04f180f27",
28
- "better-dsh/src/remote/ssh-targets.ts": "d463357d03fec88f9cdc3c47bba85ae9213f2641b2c08689fe9a7af42fe8bcd4",
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": "997edf11d0512866682ee87ddd0a6b5e060db0c222d97d7544b2a52a5e8749bc",
32
- "better-dsh/src/remote/transports.ts": "258aa92224a310d632c53a326d2e3d3d2b0f0f1bf2f9ceeb554bded03581ae18",
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": "c8c13fffd331abbdcfb29783e2ebcbe1ba9737d56ed495df2d78788c6b6c7d94",
61
- "helper-bin/aarch64-apple-darwin/BUILT-VER": "0d13f4cceb4520b0970f93ee648432615239279700de836661c63f2c89ac41dd",
62
- "helper-bin/aarch64-apple-darwin/superd-run": "715c2d7bd935f3b4cc4ae0ffb10d3fab946bb62d93f592ccfe2a7446669ff76b",
63
- "helper-bin/aarch64-unknown-linux-musl/BUILT-VER": "0d13f4cceb4520b0970f93ee648432615239279700de836661c63f2c89ac41dd",
64
- "helper-bin/aarch64-unknown-linux-musl/superd-run": "22ac1c7f913092d8ca8d312b93528dd2263200f9670dc183fd215bc89d2bac68",
65
- "helper-bin/x86_64-pc-windows-gnu/BUILT-VER": "0d13f4cceb4520b0970f93ee648432615239279700de836661c63f2c89ac41dd",
66
- "helper-bin/x86_64-pc-windows-gnu/superd-run.exe": "a638479e1ccb1c4e185085100450962e39164158a76097b5087d2a3bb8880ac0",
67
- "helper-bin/x86_64-unknown-linux-musl/BUILT-VER": "0d13f4cceb4520b0970f93ee648432615239279700de836661c63f2c89ac41dd",
68
- "helper-bin/x86_64-unknown-linux-musl/superd-run": "79e42879509bb87d2fe9888d282151fd17113203d3172f59a16fcb5a180ba057",
69
- "plugin.d.ts": "b58e5a7148767a28ecc87b4fe1a39ff287ddba5cf6d1e538f6242dfb4d0485f8",
70
- "plugin.js": "d97c93d87242aaac376858206cd3407f4283dd9b0036a190329efb26e305a04a"
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: undefined, signal: AbortSignal, control: AbortController): Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>;
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, undefined, signal, new AbortController()),
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 local = getTarget(key)?.gateway;
106
- if (local === undefined)
105
+ const target = getTarget(key);
106
+ if (target?.gateway === undefined)
107
107
  throw new A2aAddressError('unreachable', addr);
108
- return bridgeTypertGateway(local);
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 three-method face of a world's typertGateway service instance. */
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 no peer (ctx0's fence already authenticated the client),
10
- * the mux controller's signal, and the controller itself, exactly as the
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: undefined, signal: AbortSignal, control: AbortController): Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>;
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, undefined, control.signal, control)), {
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). A receipt with attempted
7
- // but neither done nor error is an interrupted first use — e.g. killed by the restart
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 only to a CONFIRMED first use or a recorded
52
- // failure; an interrupted first use (no done, no error) re-arms.
53
- this.intent = saved.attempted && saved.done !== true && saved.error === undefined
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) => (federatedGatewayOf(label) ?? getTarget(label)?.gateway),
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): CarrierGatewayFace | undefined;
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 gateway = options.gatewayOf(agent);
134
- if (gateway === undefined) {
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 gateway = options.gatewayOf(agent);
159
- if (gateway === undefined) {
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;