better-dsh 0.3.1-a → 0.3.1-d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/BrowserWebSocketTransport.js +4 -0
  2. package/lib/{BrowserWebSocketTransport-wm99h94r.js → BrowserWebSocketTransport2.js} +1 -1
  3. package/lib/LaunchOptions.js +4 -0
  4. package/lib/{LaunchOptions-iE9M-STS.js → LaunchOptions2.js} +1 -1
  5. package/lib/{NodeWebSocketTransport-D9ysYGLM.js → NodeWebSocketTransport.js} +2 -2
  6. package/lib/NodeWebSocketTransport2.js +4 -0
  7. package/lib/{bidi-CaQeBZHY.js → bidi.js} +4 -4
  8. package/lib/compaction/index.d.ts +1 -1
  9. package/lib/compaction/index.js +1 -1
  10. package/lib/failover/index.d.ts +1 -1
  11. package/lib/failover/index.js +1 -1
  12. package/lib/fs-aware/sandbox-plugin.js +1 -1
  13. package/lib/{index-browser-CJzxT9Vi.js → index-browser.js} +6 -6
  14. package/lib/index.d.ts +1 -1
  15. package/lib/index.js +4 -4
  16. package/lib/kernel-env.js +1 -1
  17. package/lib/{lsp-server-registry-BcdkHROD.js → lsp-server-registry2.js} +1 -1
  18. package/lib/{main-taSMz1KX.js → main2.js} +1 -1
  19. package/lib/mobile/plugin.d.ts +2 -2
  20. package/lib/mobile/plugin.js +2 -2
  21. package/lib/{puppeteer-core-C-4NyOpY.js → puppeteer-core.js} +8 -8
  22. package/lib/py-sdk.js +1 -1
  23. package/lib/remote/helper-bin/MANIFEST.json +35 -0
  24. package/lib/remote/helper-bin/aarch64-apple-darwin/BUILT-VER +1 -0
  25. package/lib/remote/helper-bin/aarch64-apple-darwin/superd-run +0 -0
  26. package/lib/remote/helper-bin/aarch64-unknown-linux-musl/BUILT-VER +1 -0
  27. package/lib/remote/helper-bin/aarch64-unknown-linux-musl/superd-run +0 -0
  28. package/lib/remote/helper-bin/x86_64-pc-windows-gnu/BUILT-VER +1 -0
  29. package/lib/remote/helper-bin/x86_64-pc-windows-gnu/superd-run.exe +0 -0
  30. package/lib/remote/helper-bin/x86_64-unknown-linux-musl/BUILT-VER +1 -0
  31. package/lib/remote/helper-bin/x86_64-unknown-linux-musl/superd-run +0 -0
  32. package/lib/remote/plugin.d.ts +51 -4
  33. package/lib/remote/plugin.js +590 -105
  34. package/lib/url-schemes/index.d.ts +11 -11
  35. package/lib/url-schemes/index.js +11 -11
  36. package/lib/{wasm_bg-CNXhHjVg.js → wasm_bg.js} +1 -1
  37. package/lib/web-password.d.ts +7 -7
  38. package/lib/web-password.js +1 -1
  39. package/lib/web-trust.d.ts +1 -1
  40. package/lib/web-trust.js +2 -2
  41. package/lib/{web-trust-C_Y6o8Z2.d.ts → web-trust2.d.ts} +11 -11
  42. package/lib/{web-trust-D6gJpUMf.js → web-trust2.js} +1 -1
  43. package/lib/welcome-notice.d.ts +3 -3
  44. package/lib/welcome-notice.js +1 -1
  45. package/package.json +2 -2
  46. package/lib/BrowserWebSocketTransport-D1aFReu_.js +0 -4
  47. package/lib/LaunchOptions-0Mr6meJc.js +0 -4
  48. package/lib/NodeWebSocketTransport-BRLjmu2R.js +0 -4
  49. /package/lib/{chunk-DQxbwPpZ.js → chunk.js} +0 -0
  50. /package/lib/{index-DTDXUe6m.d.ts → index2.d.ts} +0 -0
  51. /package/lib/{kernel-env-x_WkG6Fr.js → kernel-env2.js} +0 -0
  52. /package/lib/{lib-C8_jXvwA.js → lib.js} +0 -0
  53. /package/lib/{lsp-server-registry-D4VLs1Sm.js → lsp-server-registry.js} +0 -0
  54. /package/lib/{main-uqGqYYnA.js → main.js} +0 -0
  55. /package/lib/{native-capture-BEByDXYM.js → native-capture.js} +0 -0
  56. /package/lib/{py-sdk-D7klhpWb.js → py-sdk2.js} +0 -0
  57. /package/lib/{util-BHL_dfV_.js → util.js} +0 -0
  58. /package/lib/{web-tree-sitter-CPrPD4J2.js → web-tree-sitter.js} +0 -0
  59. /package/lib/{wrap-0VA0uFx8.js → wrap.js} +0 -0
@@ -1,12 +1,13 @@
1
- import { t as Schema } from "../lib-C8_jXvwA.js";
1
+ import { t as Schema } from "../lib.js";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  import { createHash, randomBytes, randomUUID } from "node:crypto";
4
- import { constants, createReadStream, createWriteStream, mkdirSync, statSync } from "node:fs";
4
+ import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
5
5
  import { dirname, isAbsolute, join, posix, resolve } from "node:path";
6
6
  import { spawn } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
7
8
  import { homedir, tmpdir } from "node:os";
8
9
  import { defineTool } from "@deepseek-ai/dsh-tools";
9
- import { chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, rename, rm, symlink, writeFile } from "node:fs/promises";
10
+ import { chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
10
11
  import { pipeline } from "node:stream/promises";
11
12
  import { Readable } from "node:stream";
12
13
  import { createConnection, createServer } from "node:net";
@@ -317,17 +318,18 @@ async function copyTree(request, source, destination, signal, scratchRoot = join
317
318
  signal.throwIfAborted();
318
319
  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");
319
320
  await mkdir(scratchRoot, { recursive: true });
320
- signal.throwIfAborted();
321
- const scratch = await mkdtemp(join(scratchRoot, "remote-copy-")), payload = join(scratch, "payload.bin");
322
321
  const receipt = {
323
322
  action: "copy",
323
+ route: "hub-staged",
324
324
  src: request.src,
325
325
  dst: request.dst,
326
- verified: false,
326
+ ok: false,
327
+ verification: "not-applicable",
327
328
  bytes: 0,
328
329
  entries: 0,
329
330
  scope: "transferred entries"
330
331
  };
332
+ const scratch = await mkdtemp(join(scratchRoot, "remote-copy-")), payload = join(scratch, "payload.bin");
331
333
  try {
332
334
  await pipeline(await source.endpoint.exportTree(source.path, signal), createWriteStream(payload, {
333
335
  flags: "wx",
@@ -351,24 +353,79 @@ async function copyTree(request, source, destination, signal, scratchRoot = join
351
353
  const dest = await destination.endpoint.digest(destination.path, signal, selection);
352
354
  receipt.destinationDigest = dest.sha256;
353
355
  if (dest.sha256 !== receipt.sourceDigest) throw new Error("destination digest mismatch");
354
- receipt.verified = true;
356
+ receipt.verification = "sha256";
355
357
  }
356
358
  signal.throwIfAborted();
357
359
  await rm(scratch, {
358
360
  recursive: true,
359
361
  force: true
360
362
  });
363
+ receipt.ok = true;
361
364
  return receipt;
362
365
  } catch (error) {
366
+ const message = error instanceof Error ? error.message : String(error);
367
+ const reason = signal.aborted ? "aborted" : /mismatch/i.test(message) ? "digest-mismatch" : "staging-error";
363
368
  throw new CopyError({
364
369
  ...receipt,
365
- verified: false,
370
+ ok: false,
371
+ reason,
366
372
  scratch,
367
- error: error instanceof Error ? error.message : String(error)
373
+ error: message
368
374
  });
369
375
  }
370
376
  }
371
377
 
378
+ //#endregion
379
+ //#region src/remote/ssh-argv.ts
380
+ /**
381
+ * ONE identity-assembly policy for every SSH-speaking lane (user close-out
382
+ * ruling 2026-10-17: "four builders necessarily mean four identity
383
+ * semantics" — the structural root of F1).
384
+ *
385
+ * Assembly: explicit connection fields (byossh registrations; caller options
386
+ * may override later by the CLI's own last-option-wins) in ONE order —
387
+ * user, port, key, proxyJump — spoken in the target CLI's dialect.
388
+ *
389
+ * Resolution: config-side identity (aliases, config-only IdentityFile paths)
390
+ * is read from the ORIGIN's own ssh config natively by every lane. The single
391
+ * exception is the scp -3 temp config (ssh -F sees no user config), where
392
+ * tempSshConfig inlines the `ssh -G`-resolved fields — paths only, never key
393
+ * material (F1, 2026-10-14). That policy lives beside this builder in
394
+ * native-copy.ts and must stay the only such inline site.
395
+ *
396
+ * Former duplicate builders now reduced here: sshArgv (execution/probes),
397
+ * identityArgv (scp routes), rsyncRsh (RSYNC_RSH wiring), relaySsh
398
+ * (dual-SSH relay). Behavior is byte-identical to each former site.
399
+ */
400
+ /** Identity options ONLY for one connection, in the target CLI's dialect:
401
+ * user, port, key, proxyJump — conditional, one order, NO base flags (each
402
+ * lane's dialect base — BatchMode, -T, hardening — stays at its call site).
403
+ * scp carries the user inside its user@host operand, so the scp dialect
404
+ * never emits -l; its port flag is -P. */
405
+ function sshIdentityArgv(connection, dialect) {
406
+ const args = [];
407
+ if (dialect === "ssh" && connection.user !== void 0) args.push("-l", connection.user);
408
+ if (connection.port !== void 0) args.push(dialect === "scp" ? "-P" : "-p", String(connection.port));
409
+ if (connection.key !== void 0) args.push("-i", connection.key);
410
+ if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
411
+ return args;
412
+ }
413
+ /** Full ssh invocation around the shared identity core. base and extra are
414
+ * trusted internal argv (dialect flags incl. BatchMode, forwarding options)
415
+ * — never a model-provided shell string. */
416
+ function sshCommandArgv(connection, base, extra, command) {
417
+ const args = [
418
+ "ssh",
419
+ ...base,
420
+ ...sshIdentityArgv(connection, "ssh"),
421
+ ...extra,
422
+ "--",
423
+ connection.host
424
+ ];
425
+ if (command !== void 0) args.push(command);
426
+ return args;
427
+ }
428
+
372
429
  //#endregion
373
430
  //#region src/remote/output-ring.ts
374
431
  const continuation = (byte) => (byte & 192) === 128;
@@ -674,15 +731,17 @@ function scpSpec(connection, path$1) {
674
731
  const host = connection.host.includes(":") ? `[${connection.host}]` : connection.host;
675
732
  return `${connection.user !== void 0 ? `${connection.user}@${host}` : host}:${path$1}`;
676
733
  }
677
- /** Connection identity as scp options (port, key), placed BEFORE caller argv so
678
- * an explicit caller -F/-i/-P/-J overrides the registered identity by scp's own
679
- * precedence (last option wins). */
734
+ /** Connection identity as scp options, placed BEFORE caller argv so an
735
+ * explicit caller -F/-i/-P/-J overrides the registered identity by scp's own
736
+ * precedence (last option wins). Assembly itself lives in ssh-argv.ts
737
+ * (the ONE identity policy — 2026-10-17 ruling); only the scp dialect
738
+ * placement rule is local. */
680
739
  function identityArgv(connection) {
681
- const options = ["-o", "BatchMode=yes"];
682
- if (connection.port !== void 0) options.push("-P", String(connection.port));
683
- if (connection.key !== void 0) options.push("-i", connection.key);
684
- if (connection.proxyJump !== void 0) options.push("-J", connection.proxyJump);
685
- return options;
740
+ return [
741
+ "-o",
742
+ "BatchMode=yes",
743
+ ...sshIdentityArgv(connection, "scp")
744
+ ];
686
745
  }
687
746
  function sshEndpoint(plan) {
688
747
  if (plan.kind !== "ssh") throw new Error("remote: native copy endpoint is not an SSH target");
@@ -721,24 +780,30 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
721
780
  stdout: [],
722
781
  stderr: [],
723
782
  outputTruncated: false,
724
- verified: false,
783
+ verification: "native-exit-only",
784
+ ok: true,
725
785
  cleanup: "none-required"
726
786
  };
727
- const finish = (route, argvs, results) => {
787
+ const finish = (route, argvs, results, internal = []) => {
728
788
  receipt.route = route;
729
- receipt.argv = argvs;
789
+ receipt.argv = argvs.map((argv) => argv.map((token) => internal.find(([secret]) => secret === token)?.[1] ?? token));
730
790
  receipt.exits = results.map((result) => result.exit);
731
791
  receipt.stdout = results.map((result) => result.stdout);
732
792
  receipt.stderr = results.map((result) => result.stderr);
733
793
  receipt.outputTruncated = results.some((result) => result.outputTruncated);
794
+ receipt.ok = !signal.aborted && receipt.exits.every((exit) => exit === 0);
795
+ if (!receipt.ok) receipt.reason = signal.aborted ? "aborted" : "non-zero-exit";
734
796
  return receipt;
735
797
  };
798
+ if (request.mkdir !== false) await ensureDestinationDir(endpoints.dst, signal, options.execArgvFor, wrap, receipt);
799
+ else receipt.created = "n-a";
736
800
  if (s === "local" && t === "ssh") {
737
801
  rejectRemoteToRemoteScp(oneShot);
738
802
  const connection = sshEndpoint(endpoints.dst.plan).connection;
739
803
  const argv = [
740
804
  "scp",
741
805
  ...identityArgv(connection),
806
+ ...await recursiveFlag(endpoints.src.path, oneShot, "-r"),
742
807
  ...oneShot,
743
808
  endpoints.src.path,
744
809
  scpSpec(connection, endpoints.dst.path)
@@ -771,7 +836,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
771
836
  dstSpec = scpSpec(destination, endpoints.dst.path);
772
837
  extra = identityArgv(source);
773
838
  } else {
774
- config = await tempSshConfig(source, destination);
839
+ config = await tempSshConfig(source, destination, runNative);
775
840
  extra = ["-F", config.path];
776
841
  srcSpec = `superd-scp3-source:${endpoints.src.path}`;
777
842
  dstSpec = `superd-scp3-destination:${endpoints.dst.path}`;
@@ -784,7 +849,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
784
849
  srcSpec,
785
850
  dstSpec
786
851
  ];
787
- return finish("scp -3 (data through origin)", [argv], [await runNative(wrap(argv), signal)]);
852
+ return finish("scp-3", [argv], [await runNative(wrap(argv), signal)], config !== void 0 ? [[config.path, "<temp-ssh-config>"]] : []);
788
853
  } catch (error) {
789
854
  if (signal.aborted) unknown = true;
790
855
  throw error;
@@ -803,7 +868,9 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
803
868
  endpoints.src.path,
804
869
  `${containerOf(endpoints.dst.plan)}:${endpoints.dst.path}`
805
870
  ];
806
- return finish("docker cp", [argv], [await runNative(wrap(argv), signal)]);
871
+ const result = await runNative(wrap(argv), signal);
872
+ withSymlinkHint(result);
873
+ return finish("docker-cp", [argv], [result]);
807
874
  }
808
875
  if (s === "docker" && t === "local") {
809
876
  const argv = [
@@ -813,18 +880,21 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
813
880
  `${containerOf(endpoints.src.plan)}:${endpoints.src.path}`,
814
881
  endpoints.dst.path
815
882
  ];
816
- return finish("docker cp", [argv], [await runNative(wrap(argv), signal)]);
883
+ const result = await runNative(wrap(argv), signal);
884
+ withSymlinkHint(result);
885
+ return finish("docker-cp", [argv], [result]);
817
886
  }
818
887
  if (s === "local" && t === "incus") {
819
888
  const argv = [
820
889
  "incus",
821
890
  "file",
822
891
  "push",
892
+ ...await recursiveFlag(endpoints.src.path, oneShot, "-r"),
823
893
  ...oneShot,
824
894
  endpoints.src.path,
825
895
  `${containerOf(endpoints.dst.plan)}${endpoints.dst.path}`
826
896
  ];
827
- return finish("incus file push", [argv], [await runNative(wrap(argv), signal)]);
897
+ return finish("incus-file-push", [argv], [await runNative(wrap(argv), signal)]);
828
898
  }
829
899
  if (s === "incus" && t === "local") {
830
900
  const argv = [
@@ -835,7 +905,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
835
905
  `${containerOf(endpoints.src.plan)}${endpoints.src.path}`,
836
906
  endpoints.dst.path
837
907
  ];
838
- return finish("incus file pull", [argv], [await runNative(wrap(argv), signal)]);
908
+ return finish("incus-file-pull", [argv], [await runNative(wrap(argv), signal)]);
839
909
  }
840
910
  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");
841
911
  const pullOptions = request.pullArgv !== void 0 ? pull : oneShot;
@@ -848,9 +918,12 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
848
918
  const pullArgv = segmentArgv(endpoints.src.plan, endpoints.src.path, staged, pullOptions, "pull");
849
919
  const pushArgv = segmentArgv(endpoints.dst.plan, endpoints.dst.path, staged, pushOptions, "push");
850
920
  const first = await runNative(wrap(pullArgv), signal);
851
- if (first.exit !== 0) return finish("two native commands with origin staging (push not started: pull failed)", [pullArgv], [first]);
921
+ if (first.exit !== 0) {
922
+ receipt.note = "push not started: pull failed";
923
+ return finish("staged-pull-push", [pullArgv], [first], [[staged, "<staging>"]]);
924
+ }
852
925
  const second = await runNative(wrap(pushArgv), signal);
853
- return finish("two native commands with origin staging", [pullArgv, pushArgv], [first, second]);
926
+ return finish("staged-pull-push", [pullArgv, pushArgv], [first, second], [[staged, "<staging>"]]);
854
927
  } finally {
855
928
  receipt.cleanup = await rm(scratch, {
856
929
  recursive: true,
@@ -911,40 +984,162 @@ function segmentArgv(plan, remote, staged, opts, leg) {
911
984
  spec
912
985
  ];
913
986
  }
987
+ /** D3 mkdir pre-hop: ensure the destination's PARENT directory exists before
988
+ * the transfer (never the operand itself — see parent-only note below).
989
+ * transfer. Local dst is node-side; remote dst runs ONE exec `mkdir -p` probe
990
+ * whose stdout discloses created-vs-existed. Best-effort by contract: any
991
+ * failure records created:'n-a' + note and lets the copy proceed so the native
992
+ * CLI surfaces the real error (and stopped-container docker cp keeps working). */
993
+ async function ensureDestinationDir(dst, signal, execArgvFor, wrap, receipt) {
994
+ const dir = dirname(dst.path);
995
+ if (dir === "/" || dir === ".") {
996
+ receipt.created = false;
997
+ return;
998
+ }
999
+ if (dst.plan.kind === "local") {
1000
+ try {
1001
+ if (existsSync(dir)) {
1002
+ receipt.created = false;
1003
+ return;
1004
+ }
1005
+ mkdirSync(dir, { recursive: true });
1006
+ receipt.created = true;
1007
+ } catch (error) {
1008
+ receipt.created = "n-a";
1009
+ receipt.note = `mkdir pre-hop failed: ${error instanceof Error ? error.message : String(error)}`;
1010
+ }
1011
+ return;
1012
+ }
1013
+ if (execArgvFor === void 0) {
1014
+ receipt.created = "n-a";
1015
+ return;
1016
+ }
1017
+ if (/[\r\n]/.test(dir)) {
1018
+ receipt.created = "n-a";
1019
+ receipt.note = "mkdir pre-hop skipped: newline-bearing destination path";
1020
+ return;
1021
+ }
1022
+ const quoted = `'${dir.replaceAll("'", "'\\\\''")}'`;
1023
+ const script = `if [ -d ${quoted} ]; then echo existed; else mkdir -p ${quoted} && echo created; fi`;
1024
+ try {
1025
+ const result = await runNative(wrap(execArgvFor(dst.plan, script)), signal);
1026
+ const word = result.stdout.trim().split("\n").pop();
1027
+ if (result.exit === 0 && word === "created") receipt.created = true;
1028
+ else if (result.exit === 0 && word === "existed") receipt.created = false;
1029
+ else {
1030
+ receipt.created = "n-a";
1031
+ const tail = result.stderr.trim().split("\n").filter((line) => line.length > 0).pop();
1032
+ receipt.note = `mkdir pre-hop unavailable (exit ${result.exit})${tail !== void 0 ? `: ${tail}` : ""}`;
1033
+ }
1034
+ } catch (error) {
1035
+ receipt.created = "n-a";
1036
+ receipt.note = `mkdir pre-hop failed: ${error instanceof Error ? error.message : String(error)}`;
1037
+ }
1038
+ }
914
1039
  /** Distinguishing identities for scp -3: an origin-only temp config maps two
915
1040
  * synthetic aliases; keys are referenced in place, never copied to any remote. */
916
- async function tempSshConfig(source, destination) {
917
- for (const c of [source, destination]) for (const [field, value] of [
918
- ["host", c.host],
919
- ["user", c.user],
920
- ["key", c.key],
921
- ["proxyJump", c.proxyJump]
922
- ]) if (value !== void 0 && /[\r\n]/.test(value)) throw new Error(`remote: scp -3 temp config rejects newline in SSH ${field} (no config injection)`);
1041
+ /** D8 (2026-10-14 user adoption, third acceptance round): a LOCAL directory source gets
1042
+ * the route's recursive flag appended automatically — scp / incus file push refuse
1043
+ * directories without it. The appended flag lands in the executed argv, which the
1044
+ * receipt shows verbatim: disclosure by receipt, never silent. Remote sources stay
1045
+ * caller-explicit (we do not dial just to stat); an explicit caller flag wins. */
1046
+ async function recursiveFlag(localPath, callerArgv, flag) {
1047
+ if (callerArgv.includes(flag) || callerArgv.includes("--recursive")) return [];
1048
+ try {
1049
+ if ((await stat(localPath)).isDirectory()) return [flag];
1050
+ } catch {}
1051
+ return [];
1052
+ }
1053
+ /** docker cp follows symlinks only with -L; its native error ("invalid symlink")
1054
+ * names no remedy. Append the one-line hint on that failure path (F5, 2026-10-14). */
1055
+ function withSymlinkHint(result) {
1056
+ if (result.exit !== 0 && /invalid symlink/i.test(result.stderr)) result.stderr = `${result.stderr}\nhint: docker cp follows symlinks only with -L — pass argv:["-L"] (e.g. /etc/os-release is a symlink)`;
1057
+ }
1058
+ async function tempSshConfig(source, destination, runNative$1) {
1059
+ const resolvedSource = await resolveThroughOriginConfig(source, runNative$1);
1060
+ const resolvedDestination = await resolveThroughOriginConfig(destination, runNative$1);
1061
+ for (const { connection: c, identityFiles } of [resolvedSource, resolvedDestination]) {
1062
+ for (const [field, value] of [
1063
+ ["host", c.host],
1064
+ ["user", c.user],
1065
+ ["key", c.key],
1066
+ ["proxyJump", c.proxyJump]
1067
+ ]) if (value !== void 0 && /[\r\n]/.test(value)) throw new Error(`remote: scp -3 temp config rejects newline in SSH ${field} (no config injection)`);
1068
+ for (const idFile of identityFiles) if (/[\r\n]/.test(idFile)) throw new Error("remote: scp -3 temp config rejects newline in resolved IdentityFile (no config injection)");
1069
+ }
923
1070
  const dir = await mkdtemp(join(tmpdir(), "superd-scp3-"));
924
1071
  const path$1 = join(dir, "config");
925
- const block = (name$1, c) => {
1072
+ const block = (name$1, c, identityFiles) => {
926
1073
  const lines = [`Host ${name$1}`, ` HostName ${c.host.replace(/^.*@/, "")}`];
927
1074
  if (c.user !== void 0) lines.push(` User ${c.user}`);
928
1075
  if (c.port !== void 0) lines.push(` Port ${String(c.port)}`);
929
- if (c.key !== void 0) lines.push(` IdentityFile ${c.key}`);
1076
+ for (const idFile of [c.key !== void 0 ? [c.key] : [], identityFiles].flat()) lines.push(` IdentityFile ${idFile}`);
930
1077
  if (c.proxyJump !== void 0) lines.push(` ProxyJump ${c.proxyJump}`);
931
1078
  return lines.join("\n");
932
1079
  };
933
- await writeFile(path$1, `${block("superd-scp3-source", source)}\n${block("superd-scp3-destination", destination)}\n`, { mode: 384 });
1080
+ await writeFile(path$1, `${block("superd-scp3-source", resolvedSource.connection, resolvedSource.identityFiles)}\n${block("superd-scp3-destination", resolvedDestination.connection, resolvedDestination.identityFiles)}\n`, { mode: 384 });
934
1081
  await chmod(path$1, 384);
935
1082
  return {
936
1083
  path: path$1,
937
1084
  dir
938
1085
  };
939
1086
  }
1087
+ /** Effective fields for one connection per the origin's own ssh config (ssh -G,
1088
+ * prints resolved settings without dialing). Explicit fields win; empty/none
1089
+ * proxyjump is dropped. IdentityFile PATHS from the config are returned separately
1090
+ * for inlining into the temp config — a path is not key material and stays on the
1091
+ * origin (F1, 2026-10-14: dev4's only authorized key lived in config, not the agent;
1092
+ * dropping it made scp -3 fail with a bare Permission denied). */
1093
+ async function resolveThroughOriginConfig(c, runNative$1) {
1094
+ let probe;
1095
+ try {
1096
+ probe = await runNative$1([
1097
+ "ssh",
1098
+ "-G",
1099
+ c.host
1100
+ ], new AbortController().signal);
1101
+ } catch {
1102
+ probe = void 0;
1103
+ }
1104
+ if (probe === void 0 || probe.exit !== 0) return {
1105
+ connection: c,
1106
+ identityFiles: []
1107
+ };
1108
+ const lines = probe.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1109
+ const pick = (key) => lines.find((line) => line.startsWith(`${key} `))?.slice(key.length + 1).trim() || void 0;
1110
+ const home = process.env.HOME ?? "";
1111
+ const compiledDefault = (path$1) => path$1 === "none" || [
1112
+ "id_rsa",
1113
+ "id_ecdsa",
1114
+ "id_ecdsa_sk",
1115
+ "id_ed25519",
1116
+ "id_ed25519_sk"
1117
+ ].some((name$1) => path$1 === `~/.ssh/${name$1}` || home !== "" && path$1 === `${home}/.ssh/${name$1}`);
1118
+ const identityFiles = [...new Set(lines.filter((line) => line.startsWith("identityfile ")).map((line) => line.slice(13).trim()).filter((path$1) => path$1 !== "" && !compiledDefault(path$1)))];
1119
+ const host = (pick("hostname") ?? c.host).replace(/^.*@/, "");
1120
+ const user = c.user ?? pick("user");
1121
+ const portText = pick("port");
1122
+ const port = c.port ?? (portText !== void 0 && /^\d+$/.test(portText) ? Number(portText) : void 0);
1123
+ const proxyJumpRaw = c.proxyJump ?? pick("proxyjump");
1124
+ return {
1125
+ connection: {
1126
+ host,
1127
+ ...user !== void 0 && user !== "" ? { user } : {},
1128
+ ...port !== void 0 && Number.isInteger(port) && port > 0 ? { port } : {},
1129
+ ...c.key !== void 0 ? { key: c.key } : {},
1130
+ ...proxyJumpRaw !== void 0 && proxyJumpRaw !== "" && proxyJumpRaw.toLowerCase() !== "none" ? { proxyJump: proxyJumpRaw } : {}
1131
+ },
1132
+ identityFiles
1133
+ };
1134
+ }
940
1135
 
941
1136
  //#endregion
942
1137
  //#region src/remote/ssh-rsync-native-relay.ts
943
1138
  /** ssh argv for the relay: batch, no agent, no multiplexing. `command` is the
944
- * remote command; forwarding options go before `--`, the host after it. */
1139
+ * remote command; forwarding options go before `--`, the host after it.
1140
+ * Identity core comes from ssh-argv.ts (the ONE policy — 2026-10-17 ruling). */
945
1141
  function relaySsh(connection, extra, command) {
946
- const args = [
947
- "ssh",
1142
+ return sshCommandArgv(connection, [
948
1143
  "-o",
949
1144
  "BatchMode=yes",
950
1145
  "-T",
@@ -961,14 +1156,7 @@ function relaySsh(connection, extra, command) {
961
1156
  "ServerAliveInterval=5",
962
1157
  "-o",
963
1158
  "ServerAliveCountMax=2"
964
- ];
965
- if (connection.user !== void 0) args.push("-l", connection.user);
966
- if (connection.port !== void 0) args.push("-p", String(connection.port));
967
- if (connection.key !== void 0) args.push("-i", connection.key);
968
- if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
969
- args.push(...extra, "--", connection.host);
970
- if (command !== void 0) args.push(command);
971
- return args;
1159
+ ], extra, command);
972
1160
  }
973
1161
  async function freePorts(count) {
974
1162
  const servers = [];
@@ -1123,12 +1311,16 @@ async function forwardRoute(argv, source, destination, signal, options) {
1123
1311
  return await removeRemoteTemp(destination.connection, temp, wrap) ? "released" : "unknown";
1124
1312
  };
1125
1313
  try {
1314
+ signal.throwIfAborted();
1315
+ const daemonState = await waitForDaemonReady(destinationChild, 5e3);
1316
+ if (daemonState === "exited") throw new Error(`dual-SSH rsync destination daemon exited before serving${relayDetail(destinationChild, sourceHold)}`);
1317
+ if (daemonState === "timeout") throw new Error(`dual-SSH rsync destination daemon never announced readiness${relayDetail(destinationChild, sourceHold)}`);
1126
1318
  signal.throwIfAborted();
1127
1319
  if (!await originPortOpen(originPort, 15e3)) throw new Error(`dual-SSH rsync destination daemon never became reachable${relayDetail(destinationChild, sourceHold)}`);
1128
1320
  signal.throwIfAborted();
1129
1321
  const runArgv = relaySsh(source.connection, [], rsyncClientCommand(argv, source.path, sourcePort));
1130
1322
  const result = await runNative(wrap(runArgv), signal);
1131
- if (result.exit === 255) result.stderr = (result.stderr + relayDetail(destinationChild, sourceHold)).slice(-OUTPUT_LIMIT);
1323
+ if (result.exit !== 0) result.stderr = (result.stderr + relayDetail(destinationChild, sourceHold)).slice(-OUTPUT_LIMIT);
1132
1324
  if (result.exit !== 0 && /remote port forwarding failed|administratively prohibited|could not request (local|remote) forwarding/.test(relayDetail(destinationChild, sourceHold))) throw new Error(`dual-SSH rsync forwarding route refused by an endpoint: ${relayDetail(destinationChild, sourceHold)}`);
1133
1325
  return {
1134
1326
  ...result,
@@ -1153,6 +1345,19 @@ async function forwardRoute(argv, source, destination, signal, options) {
1153
1345
  await Promise.allSettled([destinationChild.stop(), sourceHold.stop()]);
1154
1346
  }
1155
1347
  }
1348
+ /** Wait until the destination daemon script announces itself (stderr marker,
1349
+ * printed after its directory check and right before the daemon exec) or exits.
1350
+ * 'ready' only when the daemon is actually about to serve — closes the 0.3.1-e
1351
+ * race where -L opens at session start but the script dies ~150ms later. */
1352
+ async function waitForDaemonReady(held, budgetMs) {
1353
+ const deadline = Date.now() + budgetMs;
1354
+ for (;;) {
1355
+ if (/^SUPERD-RSYNC-TMP \S+$/m.test(held.stderrSink.text)) return "ready";
1356
+ if (held.child.exitCode !== null) return "exited";
1357
+ if (Date.now() >= deadline) return "timeout";
1358
+ await new Promise((resolve$1) => setTimeout(resolve$1, 50));
1359
+ }
1360
+ }
1156
1361
  function relayDetail(...held) {
1157
1362
  const detail = held.map((child) => child.stderrSink.text).filter(Boolean).join("\n");
1158
1363
  return detail ? `\n[relay stderr]\n${detail}` : "";
@@ -1234,14 +1439,7 @@ async function runRsync(request, source, destination, signal, options = {}) {
1234
1439
  checkAbsolute(destination.path);
1235
1440
  const argv = checkArgv("argv", request.argv);
1236
1441
  if ("pullArgv" in request || "pushArgv" in request) throw new Error("remote: rsync takes argv only; pullArgv/pushArgv are copy-only fields");
1237
- for (const legacy of [
1238
- "dryRun",
1239
- "delete",
1240
- "excludes",
1241
- "preserve",
1242
- "verify",
1243
- "mkdir"
1244
- ]) 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)`);
1442
+ 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)`);
1245
1443
  if (!source.connection && !destination.connection) throw new Error("rsync requires at least one SSH endpoint; local-only is unsupported");
1246
1444
  let result;
1247
1445
  let route;
@@ -1289,6 +1487,9 @@ async function runRsync(request, source, destination, signal, options = {}) {
1289
1487
  dst: request.dst,
1290
1488
  argv,
1291
1489
  route,
1490
+ verification: "native-exit-only",
1491
+ ok: result.exit === 0,
1492
+ ...result.exit === 0 ? {} : { reason: signal.aborted ? "aborted" : "non-zero-exit" },
1292
1493
  command: `rsync ${argv.join(" ")}`,
1293
1494
  ...itemized.length > 0 || argv.includes("--itemize-changes") || argv.includes("-i") ? { itemizedChanges: itemized } : {},
1294
1495
  ...literalBytes === void 0 ? {} : { literalBytes },
@@ -1303,21 +1504,14 @@ async function runRsync(request, source, destination, signal, options = {}) {
1303
1504
  * placeholder host rsync appends and execs the canonical SSH argv. All tokens
1304
1505
  * are tool-built argv (identity options/aliases), never model text. */
1305
1506
  function rsyncRsh(connection, wrap) {
1306
- const args = [
1307
- "ssh",
1308
- "-o",
1309
- "BatchMode=yes",
1310
- "-T"
1311
- ];
1312
- if (connection.user !== void 0) args.push("-l", connection.user);
1313
- if (connection.port !== void 0) args.push("-p", String(connection.port));
1314
- if (connection.key !== void 0) args.push("-i", connection.key);
1315
- if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
1316
- args.push("--", connection.host);
1317
1507
  return [
1318
1508
  "bash",
1319
1509
  "-c",
1320
- `shift; exec ${wrap(args).map(rshQuote).join(" ")} "$@"`,
1510
+ `shift; exec ${wrap(sshCommandArgv(connection, [
1511
+ "-o",
1512
+ "BatchMode=yes",
1513
+ "-T"
1514
+ ], [])).map(rshQuote).join(" ")} "$@"`,
1321
1515
  "rsync-rsh"
1322
1516
  ].map(rshQuote).join(" ");
1323
1517
  }
@@ -1413,26 +1607,19 @@ var SshTargets = class {
1413
1607
  }
1414
1608
  };
1415
1609
  /** Shared by execution, probes and file/relay consumers. extraArgs is trusted
1416
- * internal SSH argv (e.g. forwarding), never a model-provided shell string. */
1610
+ * internal SSH argv (e.g. forwarding), never a model-provided shell string.
1611
+ * Identity assembly lives in ssh-argv.ts (the ONE policy — 2026-10-17 ruling);
1612
+ * this wrapper only picks the tty/batch dialect base. */
1417
1613
  function sshArgv(connection, options = {}) {
1418
- const args = options.tty ? [
1419
- "ssh",
1614
+ return sshCommandArgv(connection, options.tty ? [
1420
1615
  "-tt",
1421
1616
  "-o",
1422
1617
  "BatchMode=yes"
1423
1618
  ] : [
1424
- "ssh",
1425
1619
  "-o",
1426
1620
  "BatchMode=yes",
1427
1621
  "-T"
1428
- ];
1429
- if (connection.user !== void 0) args.push("-l", connection.user);
1430
- if (connection.port !== void 0) args.push("-p", String(connection.port));
1431
- if (connection.key !== void 0) args.push("-i", connection.key);
1432
- if (connection.proxyJump !== void 0) args.push("-J", connection.proxyJump);
1433
- args.push(...options.extraArgs ?? [], "--", connection.host);
1434
- if (options.command !== void 0) args.push(options.command);
1435
- return args;
1622
+ ], options.extraArgs ?? [], options.command);
1436
1623
  }
1437
1624
 
1438
1625
  //#endregion
@@ -1489,6 +1676,96 @@ async function runOneShot(argv, opts = {}) {
1489
1676
  };
1490
1677
  }
1491
1678
 
1679
+ //#endregion
1680
+ //#region src/remote/helper.ts
1681
+ /** Reads helper-bin/MANIFEST.json next to the compiled module (lib/remote in the npm
1682
+ * package, super-dsh/remote in the Super closure — same relative layout). Missing or
1683
+ * malformed assets ⇒ null ⇒ shim-only mode; never throws. */
1684
+ function helperAssets(baseDir) {
1685
+ const dir = baseDir ?? join(dirname(fileURLToPath(import.meta.url)), "helper-bin");
1686
+ try {
1687
+ const manifest = JSON.parse(readFileSync(join(dir, "MANIFEST.json"), "utf8"));
1688
+ if (manifest?.format !== 1 || typeof manifest.ver !== "string" || typeof manifest.protocol !== "number" || !manifest.targets || typeof manifest.targets !== "object") return null;
1689
+ return {
1690
+ dir,
1691
+ manifest
1692
+ };
1693
+ } catch {
1694
+ return null;
1695
+ }
1696
+ }
1697
+ /** `uname -s`/`uname -m` → shipped target triple. Unknown combos ⇒ null (shim). */
1698
+ function archTriple(sys, machine, shipped) {
1699
+ const s = sys.trim().toLowerCase(), m = machine.trim().toLowerCase();
1700
+ const candidates = s === "linux" ? m === "x86_64" || m === "amd64" ? "x86_64-unknown-linux-musl" : m === "aarch64" || m === "arm64" ? "aarch64-unknown-linux-musl" : null : s === "darwin" ? m === "arm64" || m === "aarch64" ? "aarch64-apple-darwin" : null : null;
1701
+ if (candidates === null) return null;
1702
+ return shipped.has(candidates) ? candidates : null;
1703
+ }
1704
+ function planKey(plan) {
1705
+ switch (plan.kind) {
1706
+ case "ssh": {
1707
+ const c = plan.connection;
1708
+ const host = c?.host ?? plan.host;
1709
+ return `ssh|${c?.user ? c.user + "@" : ""}${host}${c?.port ? ":" + c.port : ""}${c?.proxyJump ? "|" + c.proxyJump : ""}`;
1710
+ }
1711
+ case "docker": return `docker|${plan.container}`;
1712
+ case "incus": return `incus|${plan.container}`;
1713
+ }
1714
+ }
1715
+ const PROBE_RE = /^SUPERD-PROBE (\S+) (\S+)$/m;
1716
+ const READY_RE = /^SUPERD-READY (\S+)$/m;
1717
+ var HelperRegistry = class {
1718
+ states = /* @__PURE__ */ new Map();
1719
+ constructor(assets, exec, probeArgv, uploadArgv) {
1720
+ this.assets = assets;
1721
+ this.exec = exec;
1722
+ this.probeArgv = probeArgv;
1723
+ this.uploadArgv = uploadArgv;
1724
+ }
1725
+ /** Never throws: any bootstrap failure resolves null (sticky) and the caller stays on the shim. */
1726
+ ensure(plan) {
1727
+ if (plan.kind !== "ssh" && plan.kind !== "docker" && plan.kind !== "incus") return Promise.resolve(null);
1728
+ const key = planKey(plan);
1729
+ let entry = this.states.get(key);
1730
+ if (entry === void 0) {
1731
+ entry = this.bootstrap(plan).catch(() => null);
1732
+ entry.catch(() => {});
1733
+ this.states.set(key, entry);
1734
+ }
1735
+ return entry;
1736
+ }
1737
+ async bootstrap(plan) {
1738
+ const { manifest } = this.assets;
1739
+ const first = await this.exec(this.probeArgv(plan, this.assets), { timeoutSec: 15 });
1740
+ const ready = this.parseReady(first.stdout, manifest.ver, manifest.protocol);
1741
+ if (ready !== null) return ready;
1742
+ const probe = PROBE_RE.exec(first.stdout);
1743
+ if (probe === null || first.exit !== 90 && first.exit !== 0) return null;
1744
+ const triple = archTriple(probe[1], probe[2], new Set(Object.keys(manifest.targets)));
1745
+ if (triple === null) return null;
1746
+ const target = manifest.targets[triple];
1747
+ const binary = await readFile(join(this.assets.dir, triple, target.file));
1748
+ if ((await this.exec(this.uploadArgv(plan, triple, target.file, this.assets), {
1749
+ stdin: binary,
1750
+ timeoutSec: 60
1751
+ })).exit !== 0) return null;
1752
+ const second = await this.exec(this.probeArgv(plan, this.assets), { timeoutSec: 15 });
1753
+ return this.parseReady(second.stdout, manifest.ver, manifest.protocol);
1754
+ }
1755
+ parseReady(stdout, ver, protocol) {
1756
+ const ready = READY_RE.exec(stdout);
1757
+ const banner = /^SUPERD-RUN proto=(\d+) name=superd-run ver=(\S+) target=(\S+)/m.exec(stdout);
1758
+ if (ready === null || banner === null) return null;
1759
+ if (banner[1] !== String(protocol) || banner[2] !== ver) return null;
1760
+ return {
1761
+ binPath: ready[1],
1762
+ triple: banner[3],
1763
+ ver,
1764
+ protocol
1765
+ };
1766
+ }
1767
+ };
1768
+
1492
1769
  //#endregion
1493
1770
  //#region src/remote/transports.ts
1494
1771
  /** POSIX 单引号机械转义(唯一一层转义,spec §三"一层机械转义")。 */
@@ -1568,6 +1845,104 @@ function buildSpawnArgv(spawnCommand) {
1568
1845
  "/dev/null"
1569
1846
  ];
1570
1847
  }
1848
+ /** superd-run helper argv (spec: docs/specs/remote-rescope §5). Probe and upload are single
1849
+ * `sh -c` hops (busybox/alpine/darwin-safe); run/killgroup over ssh go through exactly one
1850
+ * remote-shell parse, same discipline as buildOneShotArgv. */
1851
+ function buildHelperProbeArgv(plan, assets) {
1852
+ const { ver, targets } = assets.manifest;
1853
+ return helperShellArgv(plan, `${Object.entries(targets).map(([triple, t]) => `d="$HOME/.cache/superd/run/${triple}-${ver}"; [ -x "$d/${t.file}" ] && { echo "SUPERD-READY $d/${t.file}"; "$d/${t.file}" banner; exit 0; };`).join(" ")} echo "SUPERD-PROBE $(uname -s 2>/dev/null || echo unknown) $(uname -m 2>/dev/null || echo unknown)"; exit 90`);
1854
+ }
1855
+ function buildHelperUploadArgv(plan, triple, file, assets) {
1856
+ const dir = `$HOME/.cache/superd/run/${triple}-${assets.manifest.ver}`;
1857
+ return helperShellArgv(plan, `mkdir -p "${dir}" && cat > "${dir}/${file}.tmp" && chmod 0755 "${dir}/${file}.tmp" && mv -f "${dir}/${file}.tmp" "${dir}/${file}"`);
1858
+ }
1859
+ function buildHelperOneShotArgv(plan, binPath, options) {
1860
+ const { tag, cmd } = options;
1861
+ switch (plan.kind) {
1862
+ case "ssh": return sshArgv(plan.connection ?? { host: plan.host }, { command: `exec ${shQuote(binPath)} run --tag ${shQuote(tag)} -- bash -lc ${shQuote(cmd)}` });
1863
+ case "docker": return [
1864
+ "docker",
1865
+ "exec",
1866
+ "-i",
1867
+ "--",
1868
+ plan.container,
1869
+ binPath,
1870
+ "run",
1871
+ "--tag",
1872
+ tag,
1873
+ "--",
1874
+ "bash",
1875
+ "-lc",
1876
+ cmd
1877
+ ];
1878
+ case "incus": return [
1879
+ "incus",
1880
+ "exec",
1881
+ "--force-noninteractive",
1882
+ plan.container,
1883
+ "--",
1884
+ binPath,
1885
+ "run",
1886
+ "--tag",
1887
+ tag,
1888
+ "--",
1889
+ "bash",
1890
+ "-lc",
1891
+ cmd
1892
+ ];
1893
+ }
1894
+ }
1895
+ function buildHelperKillgroupArgv(plan, binPath, tag) {
1896
+ switch (plan.kind) {
1897
+ case "ssh": return sshArgv(plan.connection ?? { host: plan.host }, { command: `${shQuote(binPath)} killgroup --tag ${shQuote(tag)}` });
1898
+ case "docker": return [
1899
+ "docker",
1900
+ "exec",
1901
+ "--",
1902
+ plan.container,
1903
+ binPath,
1904
+ "killgroup",
1905
+ "--tag",
1906
+ tag
1907
+ ];
1908
+ case "incus": return [
1909
+ "incus",
1910
+ "exec",
1911
+ "--force-noninteractive",
1912
+ plan.container,
1913
+ "--",
1914
+ binPath,
1915
+ "killgroup",
1916
+ "--tag",
1917
+ tag
1918
+ ];
1919
+ }
1920
+ }
1921
+ function helperShellArgv(plan, script) {
1922
+ switch (plan.kind) {
1923
+ case "ssh": return sshArgv(plan.connection ?? { host: plan.host }, { command: script });
1924
+ case "docker": return [
1925
+ "docker",
1926
+ "exec",
1927
+ "-i",
1928
+ "--",
1929
+ plan.container,
1930
+ "sh",
1931
+ "-c",
1932
+ script
1933
+ ];
1934
+ case "incus": return [
1935
+ "incus",
1936
+ "exec",
1937
+ "--force-noninteractive",
1938
+ plan.container,
1939
+ "--",
1940
+ "sh",
1941
+ "-c",
1942
+ script
1943
+ ];
1944
+ }
1945
+ }
1571
1946
 
1572
1947
  //#endregion
1573
1948
  //#region src/remote/nonce-framing.ts
@@ -2297,6 +2672,9 @@ var RemoteDriver = class {
2297
2672
  starting = /* @__PURE__ */ new Set();
2298
2673
  fileAbort = new AbortController();
2299
2674
  disposed = false;
2675
+ /** undefined = 未尝试;null = 资产缺失(shim-only);registry = 每 target sticky 状态机。 */
2676
+ helpers;
2677
+ runnerOf = /* @__PURE__ */ new WeakMap();
2300
2678
  disposal;
2301
2679
  constructor(opts = {}) {
2302
2680
  this.opts = opts;
@@ -2403,13 +2781,18 @@ var RemoteDriver = class {
2403
2781
  path: dst.path,
2404
2782
  endpoint: localFileEndpoint()
2405
2783
  }, combined);
2406
- 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)");
2784
+ 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");
2407
2785
  const planOf = (end) => end.kind === "cli" ? end.plan : end.kind === "local" ? { kind: "local" } : void 0;
2408
2786
  const srcPlan = planOf(src), dstPlan = planOf(dst);
2409
2787
  if (srcPlan === void 0 || dstPlan === void 0) throw new Error("copy: unreachable route resolution");
2788
+ const execArgvFor = (plan, script) => {
2789
+ const argv = helperShellArgv(plan, script);
2790
+ return this.opts.nativeArgvFor ? this.opts.nativeArgvFor(argv) : argv;
2791
+ };
2410
2792
  return nativeCopy({
2411
2793
  src: request.src,
2412
2794
  dst: request.dst,
2795
+ ...request.mkdir === void 0 ? {} : { mkdir: request.mkdir },
2413
2796
  ...request.argv === void 0 ? {} : { argv: request.argv },
2414
2797
  ...request.pullArgv === void 0 ? {} : { pullArgv: request.pullArgv },
2415
2798
  ...request.pushArgv === void 0 ? {} : { pushArgv: request.pushArgv }
@@ -2422,7 +2805,10 @@ var RemoteDriver = class {
2422
2805
  plan: dstPlan,
2423
2806
  path: dst.path
2424
2807
  }
2425
- }, combined, { wrapArgv: this.opts.nativeArgvFor });
2808
+ }, combined, {
2809
+ wrapArgv: this.opts.nativeArgvFor,
2810
+ execArgvFor
2811
+ });
2426
2812
  })();
2427
2813
  this.starting.add(work);
2428
2814
  work.then(() => this.starting.delete(work), () => this.starting.delete(work));
@@ -2489,12 +2875,32 @@ var RemoteDriver = class {
2489
2875
  if (signal.aborted || this.disposed) process$1.cancel("owner closed during start");
2490
2876
  return process$1;
2491
2877
  }
2492
- nativeProcess(plan, params, timeoutSec, signal = this.fileAbort.signal) {
2878
+ async nativeProcess(plan, params, timeoutSec, signal = this.fileAbort.signal) {
2493
2879
  const cleanupTag = randomUUID().slice(0, 8);
2880
+ const helper = await this.ensureHelper(plan);
2881
+ if (helper !== null && !this.disposed) {
2882
+ const process$2 = startOneShot(buildHelperOneShotArgv(plan, helper.binPath, {
2883
+ tag: cleanupTag,
2884
+ cmd: params.cmd
2885
+ }), {
2886
+ stdin: params.stdin,
2887
+ timeoutSec
2888
+ });
2889
+ this.runnerOf.set(process$2, `superd-run ${helper.ver} (${helper.triple})`);
2890
+ process$2.done.then((result) => {
2891
+ if (!result.cancelled && !result.timedOut) return;
2892
+ startOneShot(buildHelperKillgroupArgv(plan, helper.binPath, cleanupTag), { timeoutSec: 15 }).done.catch(() => {});
2893
+ }).catch(() => {});
2894
+ const tracked = Promise.resolve(process$2);
2895
+ this.starting.add(tracked);
2896
+ tracked.then(() => this.starting.delete(tracked), () => this.starting.delete(tracked));
2897
+ return tracked;
2898
+ }
2494
2899
  const process$1 = startOneShot((this.opts.oneshotArgvFor ?? buildOneShotArgv)(plan, params.cmd, { cleanupTag }), {
2495
2900
  stdin: params.stdin,
2496
2901
  timeoutSec
2497
2902
  });
2903
+ this.runnerOf.set(process$1, "posix-shim");
2498
2904
  process$1.done.then((result) => {
2499
2905
  if (!result.cancelled && !result.timedOut) return;
2500
2906
  const pidfile = `"\${TMPDIR:-/tmp}/superd-oneshot-${cleanupTag}.pgid"`;
@@ -2506,6 +2912,38 @@ var RemoteDriver = class {
2506
2912
  work.then(() => this.starting.delete(work), () => this.starting.delete(work));
2507
2913
  return work;
2508
2914
  }
2915
+ /** Helper bootstrap is best-effort and sticky: any failure parks null for this driver
2916
+ * lifetime and the shim stays the path. First call to a target pays probe (+ upload). */
2917
+ async ensureHelper(plan) {
2918
+ if (this.helpers === void 0) {
2919
+ const assets = this.opts.helperAssetsFor?.() ?? helperAssets();
2920
+ this.helpers = assets === null ? null : new HelperRegistry(assets, (argv, options) => this.helperExec(argv, options), buildHelperProbeArgv, buildHelperUploadArgv);
2921
+ }
2922
+ if (this.helpers === null || this.disposed) return null;
2923
+ try {
2924
+ return await this.helpers.ensure(plan);
2925
+ } catch {
2926
+ return null;
2927
+ }
2928
+ }
2929
+ async helperExec(argv, options) {
2930
+ this.assertOpen();
2931
+ const stdin = options?.stdin;
2932
+ if (stdin !== void 0) {
2933
+ const proc$1 = startBinaryProcess(argv, { timeoutSec: options?.timeoutSec });
2934
+ proc$1.stdin.on("error", () => {});
2935
+ proc$1.stdin.end(stdin);
2936
+ return {
2937
+ exit: (await proc$1.done).exit,
2938
+ stdout: ""
2939
+ };
2940
+ }
2941
+ const proc = startOneShot(argv, { timeoutSec: options?.timeoutSec });
2942
+ return {
2943
+ exit: (await proc.done).exit,
2944
+ stdout: proc.read("stdout", 0).text
2945
+ };
2946
+ }
2509
2947
  own(process$1) {
2510
2948
  this.processes.add(process$1);
2511
2949
  process$1.done.then(() => this.processes.delete(process$1));
@@ -2783,7 +3221,8 @@ var RemoteDriver = class {
2783
3221
  stderr: err?.stats ?? r$1.outputStats?.stderr
2784
3222
  } } : {},
2785
3223
  durationMs: r$1.durationMs,
2786
- ...out$1.truncated !== void 0 ? { truncated: out$1.truncated } : {}
3224
+ ...out$1.truncated !== void 0 ? { truncated: out$1.truncated } : {},
3225
+ ...this.runnerOf.get(process$1) === void 0 ? {} : { runner: this.runnerOf.get(process$1) }
2787
3226
  };
2788
3227
  }
2789
3228
  const argv = (this.opts.ptyArgvFor ?? buildPtyArgv)(routing.plan);
@@ -2946,9 +3385,9 @@ const REMOTE_TOOL_DESCRIPTION = `Remote execution and file transfer over ssh / d
2946
3385
  {target:"byop:lbl", spawn, cmd} → create the named shell once; reuse as {target:"byop:lbl", cmd} (no spawn)
2947
3386
  Bare names resolve ssh config → docker → incus → live pty; unmatched goes to ssh and its native error surfaces. Prefixes ssh: / docker: / incus: / byop: force a channel. byop: same spawn repeated is reused; a different spawn is refused ([E_LABEL_CONFLICT], nothing runs); unknown or idle-expired → [E_NO_SESSION] (spawn is not retained — pass it again); a failed spawn frees the label.
2948
3387
 
2949
- oneshot: stateless bash -lc, stdout/stderr separate, exit code verbatim; the endpoint needs bash only (no Python). A POSIX wrapper reaps backgrounded children on completion and kills the whole group on timeout/cancel — they never outlive the call or hold the channel. pty: one shell per (agent, target); cwd/env survive, streams merge, Ctrl-C interrupts without killing, idle reap after 600s.
3388
+ 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).
2950
3389
 
2951
- 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 refused; 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.
3390
+ 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.
2952
3391
 
2953
3392
  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.`;
2954
3393
  function createRemoteTool(driver, opts = {}) {
@@ -2973,7 +3412,7 @@ function createRemoteTool(driver, opts = {}) {
2973
3412
  argv: {
2974
3413
  type: "array",
2975
3414
  items: { type: "string" },
2976
- description: "Ordered native CLI options and values for one-command routes (e.g. rsync [\"-a\",\"--partial\",\"--dry-run\"], scp [\"-p\"], docker cp [\"-a\"], incus file push/pull). One element = one argv token, appended verbatim before src/dst; no shell parsing, no whitelist, no forced defaults — unknown options fail in the native CLI itself. rsync only (SSH endpoints): explicit -e/--rsh overrides the tool's SSH wiring. Two-segment cross-endpoint copy uses pullArgv/pushArgv instead."
3415
+ description: "Ordered native CLI options and values for one-command routes (e.g. rsync [\"-a\",\"--partial\",\"--dry-run\"], scp [\"-p\",\"-r\"], docker cp [\"-a\"], incus file push/pull). One element = one argv token, appended verbatim before src/dst; no shell parsing, no whitelist, no forced defaults — unknown options fail in the native CLI itself. rsync only (SSH endpoints): explicit -e/--rsh overrides the tool's SSH wiring. docker cp: pass -L to follow symlinks (a symlink source like /etc/os-release fails without it). Two-segment cross-endpoint copy uses pullArgv/pushArgv instead."
2977
3416
  },
2978
3417
  pullArgv: {
2979
3418
  type: "array",
@@ -2987,11 +3426,11 @@ function createRemoteTool(driver, opts = {}) {
2987
3426
  },
2988
3427
  verify: {
2989
3428
  type: "boolean",
2990
- 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."
3429
+ 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."
2991
3430
  },
2992
3431
  mkdir: {
2993
3432
  type: "boolean",
2994
- description: "copy only, Hub↔local↔local staged lane only: create missing destination parents (default true). Native CLI routes let the CLI decide."
3433
+ 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."
2995
3434
  },
2996
3435
  run_in_background: {
2997
3436
  type: "boolean",
@@ -3088,7 +3527,11 @@ function createRemoteTool(driver, opts = {}) {
3088
3527
  outputStats: { type: "json" },
3089
3528
  outputStatsUnavailable: { type: "boolean" },
3090
3529
  session: { type: "string" },
3091
- notice: { type: "string" }
3530
+ notice: { type: "string" },
3531
+ runner: {
3532
+ type: "string",
3533
+ description: "oneshot supervision path: superd-run <ver> (<triple>) or posix-shim"
3534
+ }
3092
3535
  }
3093
3536
  },
3094
3537
  render: (_args, value) => [{
@@ -3110,12 +3553,26 @@ function createRemoteTool(driver, opts = {}) {
3110
3553
  args.timeout,
3111
3554
  args.run_in_background
3112
3555
  ].some((value) => value !== void 0)) throw new Error(`${args.action} cannot be combined with execution/PTY/job arguments; register byossh separately first`);
3113
- for (const legacy of [
3114
- "dryRun",
3115
- "delete",
3116
- "excludes",
3117
- "preserve"
3118
- ]) 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\""})`);
3556
+ const KNOWN_PARAMS = new Set([
3557
+ "action",
3558
+ "src",
3559
+ "dst",
3560
+ "argv",
3561
+ "pullArgv",
3562
+ "pushArgv",
3563
+ "verify",
3564
+ "mkdir",
3565
+ "run_in_background",
3566
+ "target",
3567
+ "ssh",
3568
+ "spawn",
3569
+ "cmd",
3570
+ "script",
3571
+ "mode",
3572
+ "stdin",
3573
+ "timeout"
3574
+ ]);
3575
+ 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`);
3119
3576
  if (args.action === "rsync") {
3120
3577
  if (args.pullArgv !== void 0 || args.pushArgv !== void 0) throw new Error("rsync takes argv only; pullArgv/pushArgv are copy-only fields");
3121
3578
  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");
@@ -3125,15 +3582,17 @@ function createRemoteTool(driver, opts = {}) {
3125
3582
  dst: args.dst,
3126
3583
  ...args.argv === void 0 ? {} : { argv: args.argv }
3127
3584
  }, exec.signal, { sessionKey: getSessionKey(exec) });
3128
- if (receipt$1.exit !== 0) throw new Error(JSON.stringify(receipt$1));
3585
+ if (receipt$1.ok === false || receipt$1.exit !== 0) throw new Error(JSON.stringify(receipt$1));
3586
+ const rsyncDuration = Date.now() - start;
3129
3587
  return {
3130
3588
  kind: "rsync",
3131
- text: JSON.stringify(receipt$1),
3589
+ text: formatTransferLine("rsync", receipt$1, rsyncDuration),
3132
3590
  receipt: receipt$1,
3133
3591
  exit: receipt$1.exit,
3134
- durationMs: Date.now() - start
3592
+ durationMs: rsyncDuration
3135
3593
  };
3136
3594
  }
3595
+ const copyStart = Date.now();
3137
3596
  const receipt = await driver.copy({
3138
3597
  src: args.src,
3139
3598
  dst: args.dst,
@@ -3143,12 +3602,16 @@ function createRemoteTool(driver, opts = {}) {
3143
3602
  ...args.pullArgv === void 0 ? {} : { pullArgv: args.pullArgv },
3144
3603
  ...args.pushArgv === void 0 ? {} : { pushArgv: args.pushArgv }
3145
3604
  }, exec.signal, { sessionKey: getSessionKey(exec) });
3605
+ const asReceipt = receipt;
3606
+ const exits = asReceipt.exits ?? [];
3607
+ if (asReceipt.ok === false || exits.some((code) => code !== 0)) throw new Error(JSON.stringify(receipt));
3608
+ const copyDuration = Date.now() - copyStart;
3146
3609
  return {
3147
3610
  kind: "copy",
3148
- text: JSON.stringify(receipt),
3611
+ text: formatTransferLine("copy", receipt, copyDuration),
3149
3612
  receipt,
3150
- exit: null,
3151
- durationMs: 0
3613
+ exit: exits.length > 0 ? 0 : null,
3614
+ durationMs: copyDuration
3152
3615
  };
3153
3616
  }
3154
3617
  if ([
@@ -3234,6 +3697,7 @@ function createRemoteTool(driver, opts = {}) {
3234
3697
  if (r.outputStatsUnavailable) v.outputStatsUnavailable = true;
3235
3698
  if (r.session !== void 0) v.session = r.session;
3236
3699
  if (r.notice !== void 0) v.notice = r.notice;
3700
+ if (r.runner !== void 0) v.runner = r.runner;
3237
3701
  return v;
3238
3702
  }
3239
3703
  });
@@ -3244,6 +3708,26 @@ function createRemoteTool(driver, opts = {}) {
3244
3708
  });
3245
3709
  return tool;
3246
3710
  }
3711
+ /** S8 (2026-10-17): copy/rsync render as one formatted line; the full receipt
3712
+ * stays in the receipt field — raw-JSON dumps are gone from text. */
3713
+ function formatTransferLine(action, receipt, durationMs) {
3714
+ const r = receipt;
3715
+ const parts = [];
3716
+ if (r.ok === false) parts.push(`✗ ${r.reason ?? "failed"}`);
3717
+ parts.push(`${action} ${r.src} → ${r.dst}`);
3718
+ if (r.route !== void 0 && r.route !== "") parts.push(`route ${r.route}`);
3719
+ if (r.exits !== void 0) parts.push(`exits [${r.exits.join(",")}]`);
3720
+ else if (r.exit !== void 0) parts.push(`exit ${r.exit}`);
3721
+ if (r.verification !== void 0) parts.push(`verification ${r.verification}`);
3722
+ if (r.created === true) parts.push("created yes");
3723
+ else if (r.created === false) parts.push("created pre-existing");
3724
+ else if (r.created === "n-a") parts.push("created n-a");
3725
+ if (r.cleanup !== void 0 && r.cleanup !== "none-required") parts.push(`cleanup ${r.cleanup}`);
3726
+ if (typeof r.bytes === "number") parts.push(`${r.bytes} bytes / ${r.entries} entries`);
3727
+ parts.push(`${(durationMs / 1e3).toFixed(1)}s`);
3728
+ if (r.note !== void 0 && r.note.length > 0) parts.push(`(${r.note})`);
3729
+ return parts.join(" · ");
3730
+ }
3247
3731
  function renderValue(v) {
3248
3732
  if (v.kind === "status" || v.kind === "roster" || v.kind === "job" || v.kind === "copy" || v.kind === "rsync") return v.text;
3249
3733
  const parts = [];
@@ -3258,7 +3742,8 @@ function renderValue(v) {
3258
3742
  const suffix = markers.length > 0 ? ` · ${markers.join(" · ")}` : "";
3259
3743
  const cwdTag = typeof v.cwd === "string" && v.cwd.length > 0 ? ` · cwd ${v.cwd}` : "";
3260
3744
  const sessionTag = typeof v.session === "string" && v.session.length > 0 ? ` · ${v.session}` : "";
3261
- parts.push(`[exit ${v.exit} · ${secs}s${suffix}${cwdTag}${sessionTag}]`);
3745
+ const runnerTag = typeof v.runner === "string" && v.runner.length > 0 ? ` · runner ${v.runner}` : "";
3746
+ parts.push(`[exit ${v.exit} · ${secs}s${suffix}${cwdTag}${sessionTag}${runnerTag}]`);
3262
3747
  if (v.stderr !== void 0 && v.stderr.length > 0) parts.push("[stderr]", v.stderr);
3263
3748
  return parts.join("\n");
3264
3749
  }