better-dsh 0.3.1-a → 0.3.1-b
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/lib/mobile/plugin.d.ts +1 -1
- package/lib/remote/helper-bin/MANIFEST.json +35 -0
- package/lib/remote/helper-bin/aarch64-apple-darwin/BUILT-VER +1 -0
- package/lib/remote/helper-bin/aarch64-apple-darwin/superd-run +0 -0
- package/lib/remote/helper-bin/aarch64-unknown-linux-musl/BUILT-VER +1 -0
- package/lib/remote/helper-bin/aarch64-unknown-linux-musl/superd-run +0 -0
- package/lib/remote/helper-bin/x86_64-pc-windows-gnu/BUILT-VER +1 -0
- package/lib/remote/helper-bin/x86_64-pc-windows-gnu/superd-run.exe +0 -0
- package/lib/remote/helper-bin/x86_64-unknown-linux-musl/BUILT-VER +1 -0
- package/lib/remote/helper-bin/x86_64-unknown-linux-musl/superd-run +0 -0
- package/lib/remote/plugin.d.ts +28 -0
- package/lib/remote/plugin.js +372 -25
- package/lib/url-schemes/index.d.ts +11 -11
- package/lib/web-password.d.ts +7 -7
- package/lib/{web-trust-C_Y6o8Z2.d.ts → web-trust-Dej_RI2B.d.ts} +11 -11
- package/lib/web-trust.d.ts +2 -1
- package/lib/welcome-notice.d.ts +3 -3
- package/package.json +2 -2
package/lib/mobile/plugin.d.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"name": "superd-run",
|
|
4
|
+
"ver": "0.3.1-b",
|
|
5
|
+
"protocol": 1,
|
|
6
|
+
"targets": {
|
|
7
|
+
"aarch64-apple-darwin": {
|
|
8
|
+
"file": "superd-run",
|
|
9
|
+
"bytes": 337808,
|
|
10
|
+
"sha256": "715c2d7bd935f3b4cc4ae0ffb10d3fab946bb62d93f592ccfe2a7446669ff76b"
|
|
11
|
+
},
|
|
12
|
+
"aarch64-unknown-linux-musl": {
|
|
13
|
+
"file": "superd-run",
|
|
14
|
+
"bytes": 397416,
|
|
15
|
+
"sha256": "22ac1c7f913092d8ca8d312b93528dd2263200f9670dc183fd215bc89d2bac68"
|
|
16
|
+
},
|
|
17
|
+
"x86_64-pc-windows-gnu": {
|
|
18
|
+
"file": "superd-run.exe",
|
|
19
|
+
"bytes": 336896,
|
|
20
|
+
"sha256": "a638479e1ccb1c4e185085100450962e39164158a76097b5087d2a3bb8880ac0"
|
|
21
|
+
},
|
|
22
|
+
"x86_64-unknown-linux-musl": {
|
|
23
|
+
"file": "superd-run",
|
|
24
|
+
"bytes": 447152,
|
|
25
|
+
"sha256": "79e42879509bb87d2fe9888d282151fd17113203d3172f59a16fcb5a180ba057"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"sources": {
|
|
29
|
+
"Cargo.toml": "129ab8360e9da872f9dfd6e713d81fe1389735a74d781f3d9c15e110f92d2757",
|
|
30
|
+
"build.rs": "c4fb356956b1ec700e99bdeca07d657a7d3b2a4026582db136411676da3d125e",
|
|
31
|
+
"src/main.rs": "47f1648ea1abc30583839edab219a9062c83d57be981c0505ab6bf79c6fbff54",
|
|
32
|
+
"src/unix.rs": "56c5f523b7b1a9efd50ef24ccdabc024bc065dd2f904fdbecdf0ca49fe7f3393",
|
|
33
|
+
"src/windows.rs": "6c1537424b1adc05ed17c7cf8bf376fb5711602b9da1697d596155bf21d541da"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-b
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-b
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-b
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-b
|
|
Binary file
|
package/lib/remote/plugin.d.ts
CHANGED
|
@@ -136,6 +136,23 @@ interface RsyncReceipt extends RunResult {
|
|
|
136
136
|
*/
|
|
137
137
|
declare function runRsync(request: RsyncRequest, source: RsyncEndpoint, destination: RsyncEndpoint, signal: AbortSignal, options?: RelayRsyncOptions): Promise<RsyncReceipt>;
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region src/remote/helper.d.ts
|
|
140
|
+
interface HelperManifest {
|
|
141
|
+
format: 1;
|
|
142
|
+
name: string;
|
|
143
|
+
ver: string;
|
|
144
|
+
protocol: number;
|
|
145
|
+
targets: Record<string, {
|
|
146
|
+
file: string;
|
|
147
|
+
bytes: number;
|
|
148
|
+
sha256: string;
|
|
149
|
+
}>;
|
|
150
|
+
}
|
|
151
|
+
interface HelperAssets {
|
|
152
|
+
dir: string;
|
|
153
|
+
manifest: HelperManifest;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
139
156
|
//#region src/remote/process.d.ts
|
|
140
157
|
declare const PROCESS_CLEANUP_BOUND_MS: number;
|
|
141
158
|
interface ProcessOptions {
|
|
@@ -210,6 +227,8 @@ interface RemoteCallResult {
|
|
|
210
227
|
session?: string;
|
|
211
228
|
/** 引导/教学行(同 label 重复给 spawn 时的「可省略 spawn」提示等);无则省略。 */
|
|
212
229
|
notice?: string;
|
|
230
|
+
/** oneshot 监督路径的如实标注:`superd-run <ver> (<triple>)` 或 `posix-shim`(spec remote-rescope §5)。 */
|
|
231
|
+
runner?: string;
|
|
213
232
|
}
|
|
214
233
|
interface RemoteAuditRecord {
|
|
215
234
|
target: string;
|
|
@@ -234,6 +253,8 @@ interface RemoteDriverOptions {
|
|
|
234
253
|
ptyArgvFor?: (plan: TargetPlan) => string[];
|
|
235
254
|
spawnArgvFor?: (spawnCommand: string) => string[];
|
|
236
255
|
probeRunner?: ProbeOptions['runner'];
|
|
256
|
+
/** 测试/验收缝:helper 资产来源(默认 lib 旁挂 helper-bin);显式 null 强制 shim 路径。 */
|
|
257
|
+
helperAssetsFor?: () => HelperAssets | null;
|
|
237
258
|
/** 测试缝:roster 扫描执行器(bare-name 解析复用)。 */
|
|
238
259
|
rosterRunner?: RosterOptions['runner'];
|
|
239
260
|
/** 测试缝:ssh config 读取(bare-name 解析复用)。 */
|
|
@@ -250,6 +271,9 @@ declare class RemoteDriver {
|
|
|
250
271
|
private readonly starting;
|
|
251
272
|
private readonly fileAbort;
|
|
252
273
|
private disposed;
|
|
274
|
+
/** undefined = 未尝试;null = 资产缺失(shim-only);registry = 每 target sticky 状态机。 */
|
|
275
|
+
private helpers;
|
|
276
|
+
private readonly runnerOf;
|
|
253
277
|
private disposal;
|
|
254
278
|
constructor(opts?: RemoteDriverOptions);
|
|
255
279
|
call(params: RemoteCallParams, callCtx?: {
|
|
@@ -273,6 +297,10 @@ declare class RemoteDriver {
|
|
|
273
297
|
start(input: RemoteInput, signal: AbortSignal): Promise<RemoteProcess>;
|
|
274
298
|
private startPrepared;
|
|
275
299
|
private nativeProcess;
|
|
300
|
+
/** Helper bootstrap is best-effort and sticky: any failure parks null for this driver
|
|
301
|
+
* lifetime and the shim stays the path. First call to a target pays probe (+ upload). */
|
|
302
|
+
private ensureHelper;
|
|
303
|
+
private helperExec;
|
|
276
304
|
private own;
|
|
277
305
|
/** Ruling 16/17:on-demand 探测 + 会话层,两行如实陈述。 */
|
|
278
306
|
/**
|
package/lib/remote/plugin.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { t as Schema } from "../lib-C8_jXvwA.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, 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";
|
|
@@ -739,6 +740,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
739
740
|
const argv = [
|
|
740
741
|
"scp",
|
|
741
742
|
...identityArgv(connection),
|
|
743
|
+
...await recursiveFlag(endpoints.src.path, oneShot, "-r"),
|
|
742
744
|
...oneShot,
|
|
743
745
|
endpoints.src.path,
|
|
744
746
|
scpSpec(connection, endpoints.dst.path)
|
|
@@ -771,7 +773,7 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
771
773
|
dstSpec = scpSpec(destination, endpoints.dst.path);
|
|
772
774
|
extra = identityArgv(source);
|
|
773
775
|
} else {
|
|
774
|
-
config = await tempSshConfig(source, destination);
|
|
776
|
+
config = await tempSshConfig(source, destination, runNative);
|
|
775
777
|
extra = ["-F", config.path];
|
|
776
778
|
srcSpec = `superd-scp3-source:${endpoints.src.path}`;
|
|
777
779
|
dstSpec = `superd-scp3-destination:${endpoints.dst.path}`;
|
|
@@ -803,7 +805,9 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
803
805
|
endpoints.src.path,
|
|
804
806
|
`${containerOf(endpoints.dst.plan)}:${endpoints.dst.path}`
|
|
805
807
|
];
|
|
806
|
-
|
|
808
|
+
const result = await runNative(wrap(argv), signal);
|
|
809
|
+
withSymlinkHint(result);
|
|
810
|
+
return finish("docker cp", [argv], [result]);
|
|
807
811
|
}
|
|
808
812
|
if (s === "docker" && t === "local") {
|
|
809
813
|
const argv = [
|
|
@@ -813,13 +817,16 @@ async function nativeCopy(request, endpoints, signal, options = {}) {
|
|
|
813
817
|
`${containerOf(endpoints.src.plan)}:${endpoints.src.path}`,
|
|
814
818
|
endpoints.dst.path
|
|
815
819
|
];
|
|
816
|
-
|
|
820
|
+
const result = await runNative(wrap(argv), signal);
|
|
821
|
+
withSymlinkHint(result);
|
|
822
|
+
return finish("docker cp", [argv], [result]);
|
|
817
823
|
}
|
|
818
824
|
if (s === "local" && t === "incus") {
|
|
819
825
|
const argv = [
|
|
820
826
|
"incus",
|
|
821
827
|
"file",
|
|
822
828
|
"push",
|
|
829
|
+
...await recursiveFlag(endpoints.src.path, oneShot, "-r"),
|
|
823
830
|
...oneShot,
|
|
824
831
|
endpoints.src.path,
|
|
825
832
|
`${containerOf(endpoints.dst.plan)}${endpoints.dst.path}`
|
|
@@ -913,30 +920,100 @@ function segmentArgv(plan, remote, staged, opts, leg) {
|
|
|
913
920
|
}
|
|
914
921
|
/** Distinguishing identities for scp -3: an origin-only temp config maps two
|
|
915
922
|
* synthetic aliases; keys are referenced in place, never copied to any remote. */
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
+
/** D8 (2026-10-14 user adoption, third acceptance round): a LOCAL directory source gets
|
|
924
|
+
* the route's recursive flag appended automatically — scp / incus file push refuse
|
|
925
|
+
* directories without it. The appended flag lands in the executed argv, which the
|
|
926
|
+
* receipt shows verbatim: disclosure by receipt, never silent. Remote sources stay
|
|
927
|
+
* caller-explicit (we do not dial just to stat); an explicit caller flag wins. */
|
|
928
|
+
async function recursiveFlag(localPath, callerArgv, flag) {
|
|
929
|
+
if (callerArgv.includes(flag) || callerArgv.includes("--recursive")) return [];
|
|
930
|
+
try {
|
|
931
|
+
if ((await stat(localPath)).isDirectory()) return [flag];
|
|
932
|
+
} catch {}
|
|
933
|
+
return [];
|
|
934
|
+
}
|
|
935
|
+
/** docker cp follows symlinks only with -L; its native error ("invalid symlink")
|
|
936
|
+
* names no remedy. Append the one-line hint on that failure path (F5, 2026-10-14). */
|
|
937
|
+
function withSymlinkHint(result) {
|
|
938
|
+
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)`;
|
|
939
|
+
}
|
|
940
|
+
async function tempSshConfig(source, destination, runNative$1) {
|
|
941
|
+
const resolvedSource = await resolveThroughOriginConfig(source, runNative$1);
|
|
942
|
+
const resolvedDestination = await resolveThroughOriginConfig(destination, runNative$1);
|
|
943
|
+
for (const { connection: c, identityFiles } of [resolvedSource, resolvedDestination]) {
|
|
944
|
+
for (const [field, value] of [
|
|
945
|
+
["host", c.host],
|
|
946
|
+
["user", c.user],
|
|
947
|
+
["key", c.key],
|
|
948
|
+
["proxyJump", c.proxyJump]
|
|
949
|
+
]) if (value !== void 0 && /[\r\n]/.test(value)) throw new Error(`remote: scp -3 temp config rejects newline in SSH ${field} (no config injection)`);
|
|
950
|
+
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)");
|
|
951
|
+
}
|
|
923
952
|
const dir = await mkdtemp(join(tmpdir(), "superd-scp3-"));
|
|
924
953
|
const path$1 = join(dir, "config");
|
|
925
|
-
const block = (name$1, c) => {
|
|
954
|
+
const block = (name$1, c, identityFiles) => {
|
|
926
955
|
const lines = [`Host ${name$1}`, ` HostName ${c.host.replace(/^.*@/, "")}`];
|
|
927
956
|
if (c.user !== void 0) lines.push(` User ${c.user}`);
|
|
928
957
|
if (c.port !== void 0) lines.push(` Port ${String(c.port)}`);
|
|
929
|
-
|
|
958
|
+
for (const idFile of [c.key !== void 0 ? [c.key] : [], identityFiles].flat()) lines.push(` IdentityFile ${idFile}`);
|
|
930
959
|
if (c.proxyJump !== void 0) lines.push(` ProxyJump ${c.proxyJump}`);
|
|
931
960
|
return lines.join("\n");
|
|
932
961
|
};
|
|
933
|
-
await writeFile(path$1, `${block("superd-scp3-source",
|
|
962
|
+
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
963
|
await chmod(path$1, 384);
|
|
935
964
|
return {
|
|
936
965
|
path: path$1,
|
|
937
966
|
dir
|
|
938
967
|
};
|
|
939
968
|
}
|
|
969
|
+
/** Effective fields for one connection per the origin's own ssh config (ssh -G,
|
|
970
|
+
* prints resolved settings without dialing). Explicit fields win; empty/none
|
|
971
|
+
* proxyjump is dropped. IdentityFile PATHS from the config are returned separately
|
|
972
|
+
* for inlining into the temp config — a path is not key material and stays on the
|
|
973
|
+
* origin (F1, 2026-10-14: dev4's only authorized key lived in config, not the agent;
|
|
974
|
+
* dropping it made scp -3 fail with a bare Permission denied). */
|
|
975
|
+
async function resolveThroughOriginConfig(c, runNative$1) {
|
|
976
|
+
let probe;
|
|
977
|
+
try {
|
|
978
|
+
probe = await runNative$1([
|
|
979
|
+
"ssh",
|
|
980
|
+
"-G",
|
|
981
|
+
c.host
|
|
982
|
+
], new AbortController().signal);
|
|
983
|
+
} catch {
|
|
984
|
+
probe = void 0;
|
|
985
|
+
}
|
|
986
|
+
if (probe === void 0 || probe.exit !== 0) return {
|
|
987
|
+
connection: c,
|
|
988
|
+
identityFiles: []
|
|
989
|
+
};
|
|
990
|
+
const lines = probe.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
991
|
+
const pick = (key) => lines.find((line) => line.startsWith(`${key} `))?.slice(key.length + 1).trim() || void 0;
|
|
992
|
+
const home = process.env.HOME ?? "";
|
|
993
|
+
const compiledDefault = (path$1) => path$1 === "none" || [
|
|
994
|
+
"id_rsa",
|
|
995
|
+
"id_ecdsa",
|
|
996
|
+
"id_ecdsa_sk",
|
|
997
|
+
"id_ed25519",
|
|
998
|
+
"id_ed25519_sk"
|
|
999
|
+
].some((name$1) => path$1 === `~/.ssh/${name$1}` || home !== "" && path$1 === `${home}/.ssh/${name$1}`);
|
|
1000
|
+
const identityFiles = [...new Set(lines.filter((line) => line.startsWith("identityfile ")).map((line) => line.slice(13).trim()).filter((path$1) => path$1 !== "" && !compiledDefault(path$1)))];
|
|
1001
|
+
const host = (pick("hostname") ?? c.host).replace(/^.*@/, "");
|
|
1002
|
+
const user = c.user ?? pick("user");
|
|
1003
|
+
const portText = pick("port");
|
|
1004
|
+
const port = c.port ?? (portText !== void 0 && /^\d+$/.test(portText) ? Number(portText) : void 0);
|
|
1005
|
+
const proxyJumpRaw = c.proxyJump ?? pick("proxyjump");
|
|
1006
|
+
return {
|
|
1007
|
+
connection: {
|
|
1008
|
+
host,
|
|
1009
|
+
...user !== void 0 && user !== "" ? { user } : {},
|
|
1010
|
+
...port !== void 0 && Number.isInteger(port) && port > 0 ? { port } : {},
|
|
1011
|
+
...c.key !== void 0 ? { key: c.key } : {},
|
|
1012
|
+
...proxyJumpRaw !== void 0 && proxyJumpRaw !== "" && proxyJumpRaw.toLowerCase() !== "none" ? { proxyJump: proxyJumpRaw } : {}
|
|
1013
|
+
},
|
|
1014
|
+
identityFiles
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
940
1017
|
|
|
941
1018
|
//#endregion
|
|
942
1019
|
//#region src/remote/ssh-rsync-native-relay.ts
|
|
@@ -1123,12 +1200,16 @@ async function forwardRoute(argv, source, destination, signal, options) {
|
|
|
1123
1200
|
return await removeRemoteTemp(destination.connection, temp, wrap) ? "released" : "unknown";
|
|
1124
1201
|
};
|
|
1125
1202
|
try {
|
|
1203
|
+
signal.throwIfAborted();
|
|
1204
|
+
const daemonState = await waitForDaemonReady(destinationChild, 5e3);
|
|
1205
|
+
if (daemonState === "exited") throw new Error(`dual-SSH rsync destination daemon exited before serving${relayDetail(destinationChild, sourceHold)}`);
|
|
1206
|
+
if (daemonState === "timeout") throw new Error(`dual-SSH rsync destination daemon never announced readiness${relayDetail(destinationChild, sourceHold)}`);
|
|
1126
1207
|
signal.throwIfAborted();
|
|
1127
1208
|
if (!await originPortOpen(originPort, 15e3)) throw new Error(`dual-SSH rsync destination daemon never became reachable${relayDetail(destinationChild, sourceHold)}`);
|
|
1128
1209
|
signal.throwIfAborted();
|
|
1129
1210
|
const runArgv = relaySsh(source.connection, [], rsyncClientCommand(argv, source.path, sourcePort));
|
|
1130
1211
|
const result = await runNative(wrap(runArgv), signal);
|
|
1131
|
-
if (result.exit
|
|
1212
|
+
if (result.exit !== 0) result.stderr = (result.stderr + relayDetail(destinationChild, sourceHold)).slice(-OUTPUT_LIMIT);
|
|
1132
1213
|
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
1214
|
return {
|
|
1134
1215
|
...result,
|
|
@@ -1153,6 +1234,19 @@ async function forwardRoute(argv, source, destination, signal, options) {
|
|
|
1153
1234
|
await Promise.allSettled([destinationChild.stop(), sourceHold.stop()]);
|
|
1154
1235
|
}
|
|
1155
1236
|
}
|
|
1237
|
+
/** Wait until the destination daemon script announces itself (stderr marker,
|
|
1238
|
+
* printed after its directory check and right before the daemon exec) or exits.
|
|
1239
|
+
* 'ready' only when the daemon is actually about to serve — closes the 0.3.1-e
|
|
1240
|
+
* race where -L opens at session start but the script dies ~150ms later. */
|
|
1241
|
+
async function waitForDaemonReady(held, budgetMs) {
|
|
1242
|
+
const deadline = Date.now() + budgetMs;
|
|
1243
|
+
for (;;) {
|
|
1244
|
+
if (/^SUPERD-RSYNC-TMP \S+$/m.test(held.stderrSink.text)) return "ready";
|
|
1245
|
+
if (held.child.exitCode !== null) return "exited";
|
|
1246
|
+
if (Date.now() >= deadline) return "timeout";
|
|
1247
|
+
await new Promise((resolve$1) => setTimeout(resolve$1, 50));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1156
1250
|
function relayDetail(...held) {
|
|
1157
1251
|
const detail = held.map((child) => child.stderrSink.text).filter(Boolean).join("\n");
|
|
1158
1252
|
return detail ? `\n[relay stderr]\n${detail}` : "";
|
|
@@ -1489,6 +1583,96 @@ async function runOneShot(argv, opts = {}) {
|
|
|
1489
1583
|
};
|
|
1490
1584
|
}
|
|
1491
1585
|
|
|
1586
|
+
//#endregion
|
|
1587
|
+
//#region src/remote/helper.ts
|
|
1588
|
+
/** Reads helper-bin/MANIFEST.json next to the compiled module (lib/remote in the npm
|
|
1589
|
+
* package, super-dsh/remote in the Super closure — same relative layout). Missing or
|
|
1590
|
+
* malformed assets ⇒ null ⇒ shim-only mode; never throws. */
|
|
1591
|
+
function helperAssets(baseDir) {
|
|
1592
|
+
const dir = baseDir ?? join(dirname(fileURLToPath(import.meta.url)), "helper-bin");
|
|
1593
|
+
try {
|
|
1594
|
+
const manifest = JSON.parse(readFileSync(join(dir, "MANIFEST.json"), "utf8"));
|
|
1595
|
+
if (manifest?.format !== 1 || typeof manifest.ver !== "string" || typeof manifest.protocol !== "number" || !manifest.targets || typeof manifest.targets !== "object") return null;
|
|
1596
|
+
return {
|
|
1597
|
+
dir,
|
|
1598
|
+
manifest
|
|
1599
|
+
};
|
|
1600
|
+
} catch {
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
/** `uname -s`/`uname -m` → shipped target triple. Unknown combos ⇒ null (shim). */
|
|
1605
|
+
function archTriple(sys, machine, shipped) {
|
|
1606
|
+
const s = sys.trim().toLowerCase(), m = machine.trim().toLowerCase();
|
|
1607
|
+
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;
|
|
1608
|
+
if (candidates === null) return null;
|
|
1609
|
+
return shipped.has(candidates) ? candidates : null;
|
|
1610
|
+
}
|
|
1611
|
+
function planKey(plan) {
|
|
1612
|
+
switch (plan.kind) {
|
|
1613
|
+
case "ssh": {
|
|
1614
|
+
const c = plan.connection;
|
|
1615
|
+
const host = c?.host ?? plan.host;
|
|
1616
|
+
return `ssh|${c?.user ? c.user + "@" : ""}${host}${c?.port ? ":" + c.port : ""}${c?.proxyJump ? "|" + c.proxyJump : ""}`;
|
|
1617
|
+
}
|
|
1618
|
+
case "docker": return `docker|${plan.container}`;
|
|
1619
|
+
case "incus": return `incus|${plan.container}`;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
const PROBE_RE = /^SUPERD-PROBE (\S+) (\S+)$/m;
|
|
1623
|
+
const READY_RE = /^SUPERD-READY (\S+)$/m;
|
|
1624
|
+
var HelperRegistry = class {
|
|
1625
|
+
states = /* @__PURE__ */ new Map();
|
|
1626
|
+
constructor(assets, exec, probeArgv, uploadArgv) {
|
|
1627
|
+
this.assets = assets;
|
|
1628
|
+
this.exec = exec;
|
|
1629
|
+
this.probeArgv = probeArgv;
|
|
1630
|
+
this.uploadArgv = uploadArgv;
|
|
1631
|
+
}
|
|
1632
|
+
/** Never throws: any bootstrap failure resolves null (sticky) and the caller stays on the shim. */
|
|
1633
|
+
ensure(plan) {
|
|
1634
|
+
if (plan.kind !== "ssh" && plan.kind !== "docker" && plan.kind !== "incus") return Promise.resolve(null);
|
|
1635
|
+
const key = planKey(plan);
|
|
1636
|
+
let entry = this.states.get(key);
|
|
1637
|
+
if (entry === void 0) {
|
|
1638
|
+
entry = this.bootstrap(plan).catch(() => null);
|
|
1639
|
+
entry.catch(() => {});
|
|
1640
|
+
this.states.set(key, entry);
|
|
1641
|
+
}
|
|
1642
|
+
return entry;
|
|
1643
|
+
}
|
|
1644
|
+
async bootstrap(plan) {
|
|
1645
|
+
const { manifest } = this.assets;
|
|
1646
|
+
const first = await this.exec(this.probeArgv(plan, this.assets), { timeoutSec: 15 });
|
|
1647
|
+
const ready = this.parseReady(first.stdout, manifest.ver, manifest.protocol);
|
|
1648
|
+
if (ready !== null) return ready;
|
|
1649
|
+
const probe = PROBE_RE.exec(first.stdout);
|
|
1650
|
+
if (probe === null || first.exit !== 90 && first.exit !== 0) return null;
|
|
1651
|
+
const triple = archTriple(probe[1], probe[2], new Set(Object.keys(manifest.targets)));
|
|
1652
|
+
if (triple === null) return null;
|
|
1653
|
+
const target = manifest.targets[triple];
|
|
1654
|
+
const binary = await readFile(join(this.assets.dir, triple, target.file));
|
|
1655
|
+
if ((await this.exec(this.uploadArgv(plan, triple, target.file, this.assets), {
|
|
1656
|
+
stdin: binary,
|
|
1657
|
+
timeoutSec: 60
|
|
1658
|
+
})).exit !== 0) return null;
|
|
1659
|
+
const second = await this.exec(this.probeArgv(plan, this.assets), { timeoutSec: 15 });
|
|
1660
|
+
return this.parseReady(second.stdout, manifest.ver, manifest.protocol);
|
|
1661
|
+
}
|
|
1662
|
+
parseReady(stdout, ver, protocol) {
|
|
1663
|
+
const ready = READY_RE.exec(stdout);
|
|
1664
|
+
const banner = /^SUPERD-RUN proto=(\d+) name=superd-run ver=(\S+) target=(\S+)/m.exec(stdout);
|
|
1665
|
+
if (ready === null || banner === null) return null;
|
|
1666
|
+
if (banner[1] !== String(protocol) || banner[2] !== ver) return null;
|
|
1667
|
+
return {
|
|
1668
|
+
binPath: ready[1],
|
|
1669
|
+
triple: banner[3],
|
|
1670
|
+
ver,
|
|
1671
|
+
protocol
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
|
|
1492
1676
|
//#endregion
|
|
1493
1677
|
//#region src/remote/transports.ts
|
|
1494
1678
|
/** POSIX 单引号机械转义(唯一一层转义,spec §三"一层机械转义")。 */
|
|
@@ -1568,6 +1752,104 @@ function buildSpawnArgv(spawnCommand) {
|
|
|
1568
1752
|
"/dev/null"
|
|
1569
1753
|
];
|
|
1570
1754
|
}
|
|
1755
|
+
/** superd-run helper argv (spec: docs/specs/remote-rescope §5). Probe and upload are single
|
|
1756
|
+
* `sh -c` hops (busybox/alpine/darwin-safe); run/killgroup over ssh go through exactly one
|
|
1757
|
+
* remote-shell parse, same discipline as buildOneShotArgv. */
|
|
1758
|
+
function buildHelperProbeArgv(plan, assets) {
|
|
1759
|
+
const { ver, targets } = assets.manifest;
|
|
1760
|
+
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`);
|
|
1761
|
+
}
|
|
1762
|
+
function buildHelperUploadArgv(plan, triple, file, assets) {
|
|
1763
|
+
const dir = `$HOME/.cache/superd/run/${triple}-${assets.manifest.ver}`;
|
|
1764
|
+
return helperShellArgv(plan, `mkdir -p "${dir}" && cat > "${dir}/${file}.tmp" && chmod 0755 "${dir}/${file}.tmp" && mv -f "${dir}/${file}.tmp" "${dir}/${file}"`);
|
|
1765
|
+
}
|
|
1766
|
+
function buildHelperOneShotArgv(plan, binPath, options) {
|
|
1767
|
+
const { tag, cmd } = options;
|
|
1768
|
+
switch (plan.kind) {
|
|
1769
|
+
case "ssh": return sshArgv(plan.connection ?? { host: plan.host }, { command: `exec ${shQuote(binPath)} run --tag ${shQuote(tag)} -- bash -lc ${shQuote(cmd)}` });
|
|
1770
|
+
case "docker": return [
|
|
1771
|
+
"docker",
|
|
1772
|
+
"exec",
|
|
1773
|
+
"-i",
|
|
1774
|
+
"--",
|
|
1775
|
+
plan.container,
|
|
1776
|
+
binPath,
|
|
1777
|
+
"run",
|
|
1778
|
+
"--tag",
|
|
1779
|
+
tag,
|
|
1780
|
+
"--",
|
|
1781
|
+
"bash",
|
|
1782
|
+
"-lc",
|
|
1783
|
+
cmd
|
|
1784
|
+
];
|
|
1785
|
+
case "incus": return [
|
|
1786
|
+
"incus",
|
|
1787
|
+
"exec",
|
|
1788
|
+
"--force-noninteractive",
|
|
1789
|
+
plan.container,
|
|
1790
|
+
"--",
|
|
1791
|
+
binPath,
|
|
1792
|
+
"run",
|
|
1793
|
+
"--tag",
|
|
1794
|
+
tag,
|
|
1795
|
+
"--",
|
|
1796
|
+
"bash",
|
|
1797
|
+
"-lc",
|
|
1798
|
+
cmd
|
|
1799
|
+
];
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function buildHelperKillgroupArgv(plan, binPath, tag) {
|
|
1803
|
+
switch (plan.kind) {
|
|
1804
|
+
case "ssh": return sshArgv(plan.connection ?? { host: plan.host }, { command: `${shQuote(binPath)} killgroup --tag ${shQuote(tag)}` });
|
|
1805
|
+
case "docker": return [
|
|
1806
|
+
"docker",
|
|
1807
|
+
"exec",
|
|
1808
|
+
"--",
|
|
1809
|
+
plan.container,
|
|
1810
|
+
binPath,
|
|
1811
|
+
"killgroup",
|
|
1812
|
+
"--tag",
|
|
1813
|
+
tag
|
|
1814
|
+
];
|
|
1815
|
+
case "incus": return [
|
|
1816
|
+
"incus",
|
|
1817
|
+
"exec",
|
|
1818
|
+
"--force-noninteractive",
|
|
1819
|
+
plan.container,
|
|
1820
|
+
"--",
|
|
1821
|
+
binPath,
|
|
1822
|
+
"killgroup",
|
|
1823
|
+
"--tag",
|
|
1824
|
+
tag
|
|
1825
|
+
];
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
function helperShellArgv(plan, script) {
|
|
1829
|
+
switch (plan.kind) {
|
|
1830
|
+
case "ssh": return sshArgv(plan.connection ?? { host: plan.host }, { command: script });
|
|
1831
|
+
case "docker": return [
|
|
1832
|
+
"docker",
|
|
1833
|
+
"exec",
|
|
1834
|
+
"-i",
|
|
1835
|
+
"--",
|
|
1836
|
+
plan.container,
|
|
1837
|
+
"sh",
|
|
1838
|
+
"-c",
|
|
1839
|
+
script
|
|
1840
|
+
];
|
|
1841
|
+
case "incus": return [
|
|
1842
|
+
"incus",
|
|
1843
|
+
"exec",
|
|
1844
|
+
"--force-noninteractive",
|
|
1845
|
+
plan.container,
|
|
1846
|
+
"--",
|
|
1847
|
+
"sh",
|
|
1848
|
+
"-c",
|
|
1849
|
+
script
|
|
1850
|
+
];
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1571
1853
|
|
|
1572
1854
|
//#endregion
|
|
1573
1855
|
//#region src/remote/nonce-framing.ts
|
|
@@ -2297,6 +2579,9 @@ var RemoteDriver = class {
|
|
|
2297
2579
|
starting = /* @__PURE__ */ new Set();
|
|
2298
2580
|
fileAbort = new AbortController();
|
|
2299
2581
|
disposed = false;
|
|
2582
|
+
/** undefined = 未尝试;null = 资产缺失(shim-only);registry = 每 target sticky 状态机。 */
|
|
2583
|
+
helpers;
|
|
2584
|
+
runnerOf = /* @__PURE__ */ new WeakMap();
|
|
2300
2585
|
disposal;
|
|
2301
2586
|
constructor(opts = {}) {
|
|
2302
2587
|
this.opts = opts;
|
|
@@ -2489,12 +2774,32 @@ var RemoteDriver = class {
|
|
|
2489
2774
|
if (signal.aborted || this.disposed) process$1.cancel("owner closed during start");
|
|
2490
2775
|
return process$1;
|
|
2491
2776
|
}
|
|
2492
|
-
nativeProcess(plan, params, timeoutSec, signal = this.fileAbort.signal) {
|
|
2777
|
+
async nativeProcess(plan, params, timeoutSec, signal = this.fileAbort.signal) {
|
|
2493
2778
|
const cleanupTag = randomUUID().slice(0, 8);
|
|
2779
|
+
const helper = await this.ensureHelper(plan);
|
|
2780
|
+
if (helper !== null && !this.disposed) {
|
|
2781
|
+
const process$2 = startOneShot(buildHelperOneShotArgv(plan, helper.binPath, {
|
|
2782
|
+
tag: cleanupTag,
|
|
2783
|
+
cmd: params.cmd
|
|
2784
|
+
}), {
|
|
2785
|
+
stdin: params.stdin,
|
|
2786
|
+
timeoutSec
|
|
2787
|
+
});
|
|
2788
|
+
this.runnerOf.set(process$2, `superd-run ${helper.ver} (${helper.triple})`);
|
|
2789
|
+
process$2.done.then((result) => {
|
|
2790
|
+
if (!result.cancelled && !result.timedOut) return;
|
|
2791
|
+
startOneShot(buildHelperKillgroupArgv(plan, helper.binPath, cleanupTag), { timeoutSec: 15 }).done.catch(() => {});
|
|
2792
|
+
}).catch(() => {});
|
|
2793
|
+
const tracked = Promise.resolve(process$2);
|
|
2794
|
+
this.starting.add(tracked);
|
|
2795
|
+
tracked.then(() => this.starting.delete(tracked), () => this.starting.delete(tracked));
|
|
2796
|
+
return tracked;
|
|
2797
|
+
}
|
|
2494
2798
|
const process$1 = startOneShot((this.opts.oneshotArgvFor ?? buildOneShotArgv)(plan, params.cmd, { cleanupTag }), {
|
|
2495
2799
|
stdin: params.stdin,
|
|
2496
2800
|
timeoutSec
|
|
2497
2801
|
});
|
|
2802
|
+
this.runnerOf.set(process$1, "posix-shim");
|
|
2498
2803
|
process$1.done.then((result) => {
|
|
2499
2804
|
if (!result.cancelled && !result.timedOut) return;
|
|
2500
2805
|
const pidfile = `"\${TMPDIR:-/tmp}/superd-oneshot-${cleanupTag}.pgid"`;
|
|
@@ -2506,6 +2811,38 @@ var RemoteDriver = class {
|
|
|
2506
2811
|
work.then(() => this.starting.delete(work), () => this.starting.delete(work));
|
|
2507
2812
|
return work;
|
|
2508
2813
|
}
|
|
2814
|
+
/** Helper bootstrap is best-effort and sticky: any failure parks null for this driver
|
|
2815
|
+
* lifetime and the shim stays the path. First call to a target pays probe (+ upload). */
|
|
2816
|
+
async ensureHelper(plan) {
|
|
2817
|
+
if (this.helpers === void 0) {
|
|
2818
|
+
const assets = this.opts.helperAssetsFor?.() ?? helperAssets();
|
|
2819
|
+
this.helpers = assets === null ? null : new HelperRegistry(assets, (argv, options) => this.helperExec(argv, options), buildHelperProbeArgv, buildHelperUploadArgv);
|
|
2820
|
+
}
|
|
2821
|
+
if (this.helpers === null || this.disposed) return null;
|
|
2822
|
+
try {
|
|
2823
|
+
return await this.helpers.ensure(plan);
|
|
2824
|
+
} catch {
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
async helperExec(argv, options) {
|
|
2829
|
+
this.assertOpen();
|
|
2830
|
+
const stdin = options?.stdin;
|
|
2831
|
+
if (stdin !== void 0) {
|
|
2832
|
+
const proc$1 = startBinaryProcess(argv, { timeoutSec: options?.timeoutSec });
|
|
2833
|
+
proc$1.stdin.on("error", () => {});
|
|
2834
|
+
proc$1.stdin.end(stdin);
|
|
2835
|
+
return {
|
|
2836
|
+
exit: (await proc$1.done).exit,
|
|
2837
|
+
stdout: ""
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
const proc = startOneShot(argv, { timeoutSec: options?.timeoutSec });
|
|
2841
|
+
return {
|
|
2842
|
+
exit: (await proc.done).exit,
|
|
2843
|
+
stdout: proc.read("stdout", 0).text
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2509
2846
|
own(process$1) {
|
|
2510
2847
|
this.processes.add(process$1);
|
|
2511
2848
|
process$1.done.then(() => this.processes.delete(process$1));
|
|
@@ -2783,7 +3120,8 @@ var RemoteDriver = class {
|
|
|
2783
3120
|
stderr: err?.stats ?? r$1.outputStats?.stderr
|
|
2784
3121
|
} } : {},
|
|
2785
3122
|
durationMs: r$1.durationMs,
|
|
2786
|
-
...out$1.truncated !== void 0 ? { truncated: out$1.truncated } : {}
|
|
3123
|
+
...out$1.truncated !== void 0 ? { truncated: out$1.truncated } : {},
|
|
3124
|
+
...this.runnerOf.get(process$1) === void 0 ? {} : { runner: this.runnerOf.get(process$1) }
|
|
2787
3125
|
};
|
|
2788
3126
|
}
|
|
2789
3127
|
const argv = (this.opts.ptyArgvFor ?? buildPtyArgv)(routing.plan);
|
|
@@ -2946,9 +3284,9 @@ const REMOTE_TOOL_DESCRIPTION = `Remote execution and file transfer over ssh / d
|
|
|
2946
3284
|
{target:"byop:lbl", spawn, cmd} → create the named shell once; reuse as {target:"byop:lbl", cmd} (no spawn)
|
|
2947
3285
|
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
3286
|
|
|
2949
|
-
oneshot: stateless bash -lc, stdout/stderr separate, exit code verbatim
|
|
3287
|
+
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
3288
|
|
|
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.
|
|
3289
|
+
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.
|
|
2952
3290
|
|
|
2953
3291
|
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
3292
|
function createRemoteTool(driver, opts = {}) {
|
|
@@ -2973,7 +3311,7 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
2973
3311
|
argv: {
|
|
2974
3312
|
type: "array",
|
|
2975
3313
|
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."
|
|
3314
|
+
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
3315
|
},
|
|
2978
3316
|
pullArgv: {
|
|
2979
3317
|
type: "array",
|
|
@@ -3088,7 +3426,11 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
3088
3426
|
outputStats: { type: "json" },
|
|
3089
3427
|
outputStatsUnavailable: { type: "boolean" },
|
|
3090
3428
|
session: { type: "string" },
|
|
3091
|
-
notice: { type: "string" }
|
|
3429
|
+
notice: { type: "string" },
|
|
3430
|
+
runner: {
|
|
3431
|
+
type: "string",
|
|
3432
|
+
description: "oneshot supervision path: superd-run <ver> (<triple>) or posix-shim"
|
|
3433
|
+
}
|
|
3092
3434
|
}
|
|
3093
3435
|
},
|
|
3094
3436
|
render: (_args, value) => [{
|
|
@@ -3134,6 +3476,7 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
3134
3476
|
durationMs: Date.now() - start
|
|
3135
3477
|
};
|
|
3136
3478
|
}
|
|
3479
|
+
const copyStart = Date.now();
|
|
3137
3480
|
const receipt = await driver.copy({
|
|
3138
3481
|
src: args.src,
|
|
3139
3482
|
dst: args.dst,
|
|
@@ -3143,12 +3486,14 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
3143
3486
|
...args.pullArgv === void 0 ? {} : { pullArgv: args.pullArgv },
|
|
3144
3487
|
...args.pushArgv === void 0 ? {} : { pushArgv: args.pushArgv }
|
|
3145
3488
|
}, exec.signal, { sessionKey: getSessionKey(exec) });
|
|
3489
|
+
const exits = receipt.exits ?? [];
|
|
3490
|
+
if (exits.some((code) => code !== 0)) throw new Error(JSON.stringify(receipt));
|
|
3146
3491
|
return {
|
|
3147
3492
|
kind: "copy",
|
|
3148
3493
|
text: JSON.stringify(receipt),
|
|
3149
3494
|
receipt,
|
|
3150
|
-
exit: null,
|
|
3151
|
-
durationMs:
|
|
3495
|
+
exit: exits.length > 0 ? 0 : null,
|
|
3496
|
+
durationMs: Date.now() - copyStart
|
|
3152
3497
|
};
|
|
3153
3498
|
}
|
|
3154
3499
|
if ([
|
|
@@ -3234,6 +3579,7 @@ function createRemoteTool(driver, opts = {}) {
|
|
|
3234
3579
|
if (r.outputStatsUnavailable) v.outputStatsUnavailable = true;
|
|
3235
3580
|
if (r.session !== void 0) v.session = r.session;
|
|
3236
3581
|
if (r.notice !== void 0) v.notice = r.notice;
|
|
3582
|
+
if (r.runner !== void 0) v.runner = r.runner;
|
|
3237
3583
|
return v;
|
|
3238
3584
|
}
|
|
3239
3585
|
});
|
|
@@ -3258,7 +3604,8 @@ function renderValue(v) {
|
|
|
3258
3604
|
const suffix = markers.length > 0 ? ` · ${markers.join(" · ")}` : "";
|
|
3259
3605
|
const cwdTag = typeof v.cwd === "string" && v.cwd.length > 0 ? ` · cwd ${v.cwd}` : "";
|
|
3260
3606
|
const sessionTag = typeof v.session === "string" && v.session.length > 0 ? ` · ${v.session}` : "";
|
|
3261
|
-
|
|
3607
|
+
const runnerTag = typeof v.runner === "string" && v.runner.length > 0 ? ` · runner ${v.runner}` : "";
|
|
3608
|
+
parts.push(`[exit ${v.exit} · ${secs}s${suffix}${cwdTag}${sessionTag}${runnerTag}]`);
|
|
3262
3609
|
if (v.stderr !== void 0 && v.stderr.length > 0) parts.push("[stderr]", v.stderr);
|
|
3263
3610
|
return parts.join("\n");
|
|
3264
3611
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { t as Schema } from "../index-DTDXUe6m.js";
|
|
1
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
3
|
import { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
3
|
-
import * as _deepseek_ai_schemastery11 from "@deepseek-ai/schemastery";
|
|
4
4
|
|
|
5
5
|
//#region src/url-schemes/tools/write.d.ts
|
|
6
6
|
|
|
@@ -50,12 +50,12 @@ declare const inject: string[];
|
|
|
50
50
|
* real patch-line config. Schema-level defaults (both ON — the service is
|
|
51
51
|
* opt-out) survive every overlay layer, per the v0.2.2a ruling.
|
|
52
52
|
*/
|
|
53
|
-
declare const Config:
|
|
54
|
-
urlSchemes:
|
|
55
|
-
hashline:
|
|
53
|
+
declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
54
|
+
urlSchemes: Schema<boolean, boolean, "defined">;
|
|
55
|
+
hashline: Schema<boolean, boolean, "defined">;
|
|
56
56
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
57
|
-
urlSchemes:
|
|
58
|
-
hashline:
|
|
57
|
+
urlSchemes: Schema<boolean, boolean, "defined">;
|
|
58
|
+
hashline: Schema<boolean, boolean, "defined">;
|
|
59
59
|
}>>, "plain">;
|
|
60
60
|
/** Register the four URL-aware tools on one agent's own scope layer. */
|
|
61
61
|
/**
|
|
@@ -75,12 +75,12 @@ declare function apply(ctx: Context, config: UrlSchemesConfig | undefined): void
|
|
|
75
75
|
declare const _default: {
|
|
76
76
|
name: string;
|
|
77
77
|
inject: string[];
|
|
78
|
-
Config:
|
|
79
|
-
urlSchemes:
|
|
80
|
-
hashline:
|
|
78
|
+
Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
79
|
+
urlSchemes: Schema<boolean, boolean, "defined">;
|
|
80
|
+
hashline: Schema<boolean, boolean, "defined">;
|
|
81
81
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
82
|
-
urlSchemes:
|
|
83
|
-
hashline:
|
|
82
|
+
urlSchemes: Schema<boolean, boolean, "defined">;
|
|
83
|
+
hashline: Schema<boolean, boolean, "defined">;
|
|
84
84
|
}>>, "plain">;
|
|
85
85
|
apply: typeof apply;
|
|
86
86
|
};
|
package/lib/web-password.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { t as Schema } from "./index-DTDXUe6m.js";
|
|
1
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
3
|
import { IncomingMessage } from "node:http";
|
|
3
4
|
import { CredentialRecord } from "@deepseek-ai/dsh-credentials";
|
|
4
|
-
import * as _deepseek_ai_schemastery21 from "@deepseek-ai/schemastery";
|
|
5
5
|
|
|
6
6
|
//#region src/web-password.d.ts
|
|
7
7
|
|
|
@@ -123,10 +123,10 @@ interface WebPasswordConfig {
|
|
|
123
123
|
/** Absolute browser-session lifetime in days. Default: 30 (the native default). */
|
|
124
124
|
cookieMaxAgeDays?: number;
|
|
125
125
|
}
|
|
126
|
-
declare const Config:
|
|
127
|
-
cookieMaxAgeDays:
|
|
126
|
+
declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
127
|
+
cookieMaxAgeDays: Schema<number, number, "defined">;
|
|
128
128
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
129
|
-
cookieMaxAgeDays:
|
|
129
|
+
cookieMaxAgeDays: Schema<number, number, "defined">;
|
|
130
130
|
}>>, "plain">;
|
|
131
131
|
/** Cordis plugin name; also the scope of this row's credential record. */
|
|
132
132
|
declare const name = "dashr-web-password";
|
|
@@ -145,10 +145,10 @@ declare function apply(ctx: Context, config?: WebPasswordConfig): void;
|
|
|
145
145
|
declare const _default: {
|
|
146
146
|
name: string;
|
|
147
147
|
inject: string[];
|
|
148
|
-
Config:
|
|
149
|
-
cookieMaxAgeDays:
|
|
148
|
+
Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
149
|
+
cookieMaxAgeDays: Schema<number, number, "defined">;
|
|
150
150
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
151
|
-
cookieMaxAgeDays:
|
|
151
|
+
cookieMaxAgeDays: Schema<number, number, "defined">;
|
|
152
152
|
}>>, "plain">;
|
|
153
153
|
apply: typeof apply;
|
|
154
154
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { t as Schema } from "./index-DTDXUe6m.js";
|
|
1
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
-
import * as _deepseek_ai_schemastery0 from "@deepseek-ai/schemastery";
|
|
3
3
|
|
|
4
4
|
//#region src/web-trust.d.ts
|
|
5
5
|
|
|
@@ -76,12 +76,12 @@ interface TrustConfig {
|
|
|
76
76
|
trustedPageAuthorities?: readonly string[];
|
|
77
77
|
}
|
|
78
78
|
/** Row config schema (schema-level default derives from `DSH_TRUSTED_HOSTS`). */
|
|
79
|
-
declare const Config:
|
|
80
|
-
trustAllHosts:
|
|
81
|
-
trustedPageAuthorities:
|
|
79
|
+
declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
80
|
+
trustAllHosts: Schema<boolean, boolean, "defined">;
|
|
81
|
+
trustedPageAuthorities: Schema<string[], string[], "defined">;
|
|
82
82
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
83
|
-
trustAllHosts:
|
|
84
|
-
trustedPageAuthorities:
|
|
83
|
+
trustAllHosts: Schema<boolean, boolean, "defined">;
|
|
84
|
+
trustedPageAuthorities: Schema<string[], string[], "defined">;
|
|
85
85
|
}>>, "plain">;
|
|
86
86
|
/** Cordis plugin name. */
|
|
87
87
|
declare const name = "dashr-web-trust";
|
|
@@ -100,12 +100,12 @@ declare function apply(ctx: Context, config: TrustConfig | undefined): void;
|
|
|
100
100
|
declare const _default: {
|
|
101
101
|
name: string;
|
|
102
102
|
inject: string[];
|
|
103
|
-
Config:
|
|
104
|
-
trustAllHosts:
|
|
105
|
-
trustedPageAuthorities:
|
|
103
|
+
Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
104
|
+
trustAllHosts: Schema<boolean, boolean, "defined">;
|
|
105
|
+
trustedPageAuthorities: Schema<string[], string[], "defined">;
|
|
106
106
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
107
|
-
trustAllHosts:
|
|
108
|
-
trustedPageAuthorities:
|
|
107
|
+
trustAllHosts: Schema<boolean, boolean, "defined">;
|
|
108
|
+
trustedPageAuthorities: Schema<string[], string[], "defined">;
|
|
109
109
|
}>>, "plain">;
|
|
110
110
|
apply: typeof apply;
|
|
111
111
|
};
|
package/lib/web-trust.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "./index-DTDXUe6m.js";
|
|
2
|
+
import { a as _default, c as buildTrustScript, d as name, i as TrustConfig, l as deriveDefaultPageAuthorities, n as Config, o as apply, r as MobileConfig, s as buildMobileScript, t as BootScriptRow, u as inject } from "./web-trust-Dej_RI2B.js";
|
|
2
3
|
export { BootScriptRow, Config, MobileConfig, TrustConfig, apply, buildMobileScript, buildTrustScript, _default as default, deriveDefaultPageAuthorities, inject, name };
|
package/lib/welcome-notice.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { t as Schema } from "./index-DTDXUe6m.js";
|
|
1
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
-
import * as _deepseek_ai_schemastery9 from "@deepseek-ai/schemastery";
|
|
3
3
|
|
|
4
4
|
//#region src/welcome-notice.d.ts
|
|
5
5
|
declare const name = "dashr-welcome-notice";
|
|
6
6
|
declare const inject: string[];
|
|
7
|
-
declare const Config:
|
|
7
|
+
declare const Config: Schema<NoInfer<Schemastery.ObjectS<{}>>, NoInfer<Schemastery.ObjectT<{}>>, "volatile">;
|
|
8
8
|
declare function apply(ctx: Context): void;
|
|
9
9
|
declare const _default: {
|
|
10
10
|
name: string;
|
|
11
11
|
inject: string[];
|
|
12
|
-
Config:
|
|
12
|
+
Config: Schema<NoInfer<Schemastery.ObjectS<{}>>, NoInfer<Schemastery.ObjectT<{}>>, "volatile">;
|
|
13
13
|
apply: typeof apply;
|
|
14
14
|
};
|
|
15
15
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-dsh",
|
|
3
|
-
"version": "0.3.1-
|
|
3
|
+
"version": "0.3.1-b",
|
|
4
4
|
"description": "Make your dsh ready for serious coding tasks. (Tools x Schemas)^REPL. `skill://`, `ctx://`, `agent://`, `dvc://`, `dsh://`, Python REPL, Context as Variables, cross compactions recallable, full context revive. `hash-edit`, `dvc://browser`, subagent as function, workflow as function.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
],
|
|
76
76
|
"scripts": {
|
|
77
77
|
"prebuild": "node scripts/copy-docs.mjs",
|
|
78
|
-
"build": "tsdown && npm run build-client && node scripts/copy-kernel-bridge.mjs && node scripts/copy-ast-assets.mjs",
|
|
78
|
+
"build": "tsdown && npm run build-client && node scripts/copy-kernel-bridge.mjs && node scripts/copy-ast-assets.mjs && node scripts/copy-helper-bins.mjs",
|
|
79
79
|
"typecheck": "npm run typecheck:host && npm run typecheck:client",
|
|
80
80
|
"typecheck:host": "tsc --noEmit",
|
|
81
81
|
"typecheck:client": "tsc -p tsconfig.client.json",
|