@penclipai/adapter-utils 2026.608.0 → 2026.702.0-canary.0
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/dist/command-managed-runtime.d.ts +10 -0
- package/dist/command-managed-runtime.d.ts.map +1 -1
- package/dist/command-managed-runtime.js +87 -12
- package/dist/command-managed-runtime.js.map +1 -1
- package/dist/command-managed-runtime.test.js +227 -134
- package/dist/command-managed-runtime.test.js.map +1 -1
- package/dist/exclude-patterns.d.ts +4 -0
- package/dist/exclude-patterns.d.ts.map +1 -0
- package/dist/exclude-patterns.js +26 -0
- package/dist/exclude-patterns.js.map +1 -0
- package/dist/execution-target.d.ts +6 -1
- package/dist/execution-target.d.ts.map +1 -1
- package/dist/execution-target.js +7 -0
- package/dist/execution-target.js.map +1 -1
- package/dist/git-workspace-sync.d.ts +52 -0
- package/dist/git-workspace-sync.d.ts.map +1 -0
- package/dist/git-workspace-sync.js +294 -0
- package/dist/git-workspace-sync.js.map +1 -0
- package/dist/git-workspace-sync.test.d.ts +2 -0
- package/dist/git-workspace-sync.test.d.ts.map +1 -0
- package/dist/git-workspace-sync.test.js +147 -0
- package/dist/git-workspace-sync.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/remote-managed-runtime.d.ts +3 -1
- package/dist/remote-managed-runtime.d.ts.map +1 -1
- package/dist/remote-managed-runtime.js +8 -2
- package/dist/remote-managed-runtime.js.map +1 -1
- package/dist/runtime-progress.d.ts +45 -0
- package/dist/runtime-progress.d.ts.map +1 -0
- package/dist/runtime-progress.js +95 -0
- package/dist/runtime-progress.js.map +1 -0
- package/dist/runtime-progress.test.d.ts +2 -0
- package/dist/runtime-progress.test.d.ts.map +1 -0
- package/dist/runtime-progress.test.js +240 -0
- package/dist/runtime-progress.test.js.map +1 -0
- package/dist/sandbox-callback-bridge.d.ts.map +1 -1
- package/dist/sandbox-callback-bridge.js +6 -2
- package/dist/sandbox-callback-bridge.js.map +1 -1
- package/dist/sandbox-managed-runtime.d.ts +15 -3
- package/dist/sandbox-managed-runtime.d.ts.map +1 -1
- package/dist/sandbox-managed-runtime.js +262 -74
- package/dist/sandbox-managed-runtime.js.map +1 -1
- package/dist/sandbox-managed-runtime.test.js +656 -1
- package/dist/sandbox-managed-runtime.test.js.map +1 -1
- package/dist/server-utils.d.ts +143 -3
- package/dist/server-utils.d.ts.map +1 -1
- package/dist/server-utils.js +586 -41
- package/dist/server-utils.js.map +1 -1
- package/dist/server-utils.test.js +502 -6
- package/dist/server-utils.test.js.map +1 -1
- package/dist/ssh-fixture.test.js +140 -1
- package/dist/ssh-fixture.test.js.map +1 -1
- package/dist/ssh.d.ts +7 -0
- package/dist/ssh.d.ts.map +1 -1
- package/dist/ssh.js +382 -128
- package/dist/ssh.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-restore-merge.d.ts +1 -0
- package/dist/workspace-restore-merge.d.ts.map +1 -1
- package/dist/workspace-restore-merge.js +3 -7
- package/dist/workspace-restore-merge.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { type PreparedSandboxManagedRuntime, type SandboxManagedRuntimeAsset, type SandboxManagedRuntimeClient } from "./sandbox-managed-runtime.js";
|
|
2
2
|
import type { RunProcessResult } from "./server-utils.js";
|
|
3
|
+
import type { RuntimeProgressSink, RuntimeStatusSink } from "./runtime-progress.js";
|
|
3
4
|
export interface CommandManagedRuntimeRunner {
|
|
5
|
+
/**
|
|
6
|
+
* True only when `execute({ stdin })` can surface useful in-flight progress
|
|
7
|
+
* for a single stdin-backed command. Provider-backed sandbox runners usually
|
|
8
|
+
* complete the entire RPC before returning, so they should leave this false
|
|
9
|
+
* and let the caller choose a chunked upload path when progress is requested.
|
|
10
|
+
*/
|
|
11
|
+
supportsSingleStreamStdinProgress?: boolean;
|
|
4
12
|
execute(input: {
|
|
5
13
|
command: string;
|
|
6
14
|
args?: string[];
|
|
@@ -41,5 +49,7 @@ export declare function prepareCommandManagedRuntime(input: {
|
|
|
41
49
|
installCommand?: string | null;
|
|
42
50
|
/** When provided alongside `installCommand`, skip the install if `command -v <detectCommand>` succeeds. */
|
|
43
51
|
detectCommand?: string | null;
|
|
52
|
+
onProgress?: RuntimeProgressSink;
|
|
53
|
+
onRuntimeProgress?: RuntimeStatusSink;
|
|
44
54
|
}): Promise<PreparedSandboxManagedRuntime>;
|
|
45
55
|
//# sourceMappingURL=command-managed-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-managed-runtime.d.ts","sourceRoot":"","sources":["../src/command-managed-runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAEjC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"command-managed-runtime.d.ts","sourceRoot":"","sources":["../src/command-managed-runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAEjC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEpF,MAAM,WAAW,2BAA2B;IAC1C;;;;;OAKG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAC5C,OAAO,CAAC,KAAK,EAAE;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACtE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACvE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,MAAM,0BAA0B,GAAG,0BAA0B,CAAC;AAsCpE,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACvD,MAAM,EAAE,2BAA2B,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CACrC,GAAG,2BAA2B,CAgJ9B;AAED,wBAAsB,4BAA4B,CAAC,KAAK,EAAE;IACxD,MAAM,EAAE,2BAA2B,CAAC;IACpC,IAAI,EAAE,yBAAyB,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACtC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2GAA2G;IAC3G,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAsFzC"}
|
|
@@ -7,7 +7,19 @@ function shellQuote(value) {
|
|
|
7
7
|
function mergeRuntimeExcludes(entries) {
|
|
8
8
|
return [...new Set([".paperclip-runtime", ...(entries ?? [])])];
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
// Largest base64 body we hand to the runner as a single stdin string. Normal
|
|
11
|
+
// multi-MB workspace/asset tarballs stay well under this and upload in one
|
|
12
|
+
// round-trip; anything larger uses the bounded chunked-append fallback so a
|
|
13
|
+
// runaway stdin string can't blow the runner/provider RPC limits.
|
|
14
|
+
const REMOTE_WRITE_SINGLE_STREAM_MAX_BASE64_BYTES = 96 * 1024 * 1024;
|
|
15
|
+
// Fallback chunk size (base64 bytes). Kept a multiple of 4 so each chunk is a
|
|
16
|
+
// self-contained base64 unit that decodes cleanly on its own.
|
|
17
|
+
const REMOTE_WRITE_FALLBACK_BASE64_CHUNK_SIZE = 4 * 1024 * 1024;
|
|
18
|
+
const REMOTE_WRITE_FALLBACK_DECODED_CHUNK_SIZE = (REMOTE_WRITE_FALLBACK_BASE64_CHUNK_SIZE / 4) * 3;
|
|
19
|
+
const REMOTE_READ_CHUNK_BYTES = REMOTE_WRITE_FALLBACK_DECODED_CHUNK_SIZE;
|
|
20
|
+
function base64EncodedLength(byteLength) {
|
|
21
|
+
return Math.ceil(byteLength / 3) * 4;
|
|
22
|
+
}
|
|
11
23
|
function toBuffer(bytes) {
|
|
12
24
|
if (Buffer.isBuffer(bytes))
|
|
13
25
|
return bytes;
|
|
@@ -31,6 +43,7 @@ export function createCommandManagedRuntimeClient(input) {
|
|
|
31
43
|
cwd: input.commandCwd,
|
|
32
44
|
stdin: opts.stdin,
|
|
33
45
|
timeoutMs: opts.timeoutMs ?? input.timeoutMs,
|
|
46
|
+
onLog: opts.onLog,
|
|
34
47
|
});
|
|
35
48
|
requireSuccessfulResult(result, script);
|
|
36
49
|
return result;
|
|
@@ -39,20 +52,78 @@ export function createCommandManagedRuntimeClient(input) {
|
|
|
39
52
|
makeDir: async (remotePath) => {
|
|
40
53
|
await runShell(`mkdir -p ${shellQuote(remotePath)}`);
|
|
41
54
|
},
|
|
42
|
-
writeFile: async (remotePath, bytes) => {
|
|
43
|
-
const
|
|
55
|
+
writeFile: async (remotePath, bytes, options) => {
|
|
56
|
+
const buffer = toBuffer(bytes);
|
|
57
|
+
const total = buffer.byteLength;
|
|
58
|
+
const encodedLength = base64EncodedLength(total);
|
|
44
59
|
const remoteDir = path.posix.dirname(remotePath);
|
|
45
|
-
const remoteTempPath = `${remotePath}.paperclip-upload
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
const remoteTempPath = `${remotePath}.paperclip-upload`;
|
|
61
|
+
const canUseSingleStreamProgressPath = input.runner.supportsSingleStreamStdinProgress === true;
|
|
62
|
+
// Primary path: a single round-trip. Stream the entire base64 body to one
|
|
63
|
+
// `base64 -d` process via stdin, decode straight into a temp file, then
|
|
64
|
+
// atomically rename into place. This replaces the previous loop that did
|
|
65
|
+
// one `printf >> tmpfile` shell round-trip per 32 KB — thousands of serial
|
|
66
|
+
// processes for a large workspace — with exactly one process.
|
|
67
|
+
if (encodedLength <= REMOTE_WRITE_SINGLE_STREAM_MAX_BASE64_BYTES &&
|
|
68
|
+
canUseSingleStreamProgressPath) {
|
|
69
|
+
const body = buffer.toString("base64");
|
|
70
|
+
await options?.onProgress?.(0, total);
|
|
71
|
+
await runShell(`mkdir -p ${shellQuote(remoteDir)} && ` +
|
|
72
|
+
`base64 -d > ${shellQuote(remoteTempPath)} && ` +
|
|
73
|
+
`mv -f ${shellQuote(remoteTempPath)} ${shellQuote(remotePath)}`, { stdin: body });
|
|
74
|
+
await options?.onProgress?.(total, total);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Bounded fallback for payloads too large to hand the runner as one stdin
|
|
78
|
+
// string: append the base64 body to a remote temp file in large chunks
|
|
79
|
+
// (orders of magnitude fewer round-trips than the old 32 KB loop), decoding
|
|
80
|
+
// each self-contained chunk on arrival and emitting progress per write,
|
|
81
|
+
// then atomically rename into place.
|
|
82
|
+
await runShell(`mkdir -p ${shellQuote(remoteDir)} && ` +
|
|
83
|
+
`rm -f ${shellQuote(remoteTempPath)} && : > ${shellQuote(remoteTempPath)}`);
|
|
84
|
+
for (let offset = 0; offset < total; offset += REMOTE_WRITE_FALLBACK_DECODED_CHUNK_SIZE) {
|
|
85
|
+
const end = Math.min(total, offset + REMOTE_WRITE_FALLBACK_DECODED_CHUNK_SIZE);
|
|
86
|
+
const chunk = buffer.subarray(offset, end).toString("base64");
|
|
87
|
+
await runShell(`base64 -d >> ${shellQuote(remoteTempPath)}`, { stdin: chunk });
|
|
88
|
+
await options?.onProgress?.(end, total);
|
|
50
89
|
}
|
|
51
|
-
await runShell(`
|
|
90
|
+
await runShell(`mv -f ${shellQuote(remoteTempPath)} ${shellQuote(remotePath)}`);
|
|
91
|
+
await options?.onProgress?.(total, total);
|
|
52
92
|
},
|
|
53
|
-
readFile: async (remotePath) => {
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
readFile: async (remotePath, options) => {
|
|
94
|
+
// Chunked reads intentionally query the remote size first, even without
|
|
95
|
+
// a progress sink, so each sandbox RPC stays bounded and truncation is
|
|
96
|
+
// detected without materializing the whole file as one stdout string.
|
|
97
|
+
const sizeResult = await runShell(`wc -c < ${shellQuote(remotePath)}`);
|
|
98
|
+
const totalBytes = Number.parseInt(sizeResult.stdout.trim(), 10);
|
|
99
|
+
if (!Number.isFinite(totalBytes) || totalBytes < 0) {
|
|
100
|
+
throw new Error(`Could not determine remote file size for ${remotePath}`);
|
|
101
|
+
}
|
|
102
|
+
// Read in bounded remote chunks so the runner never has to materialize a
|
|
103
|
+
// single base64 stdout string for the whole archive. The client API still
|
|
104
|
+
// returns the decoded file as a Buffer, but every command result stays
|
|
105
|
+
// small enough for provider-backed sandbox RPCs.
|
|
106
|
+
const decodedChunks = [];
|
|
107
|
+
let decodedSoFar = 0;
|
|
108
|
+
if (totalBytes === 0) {
|
|
109
|
+
await options?.onProgress?.(0, 0);
|
|
110
|
+
return Buffer.alloc(0);
|
|
111
|
+
}
|
|
112
|
+
for (let chunkIndex = 0; decodedSoFar < totalBytes; chunkIndex++) {
|
|
113
|
+
const result = await runShell(`dd if=${shellQuote(remotePath)} bs=${REMOTE_READ_CHUNK_BYTES} skip=${chunkIndex} count=1 2>/dev/null | base64`);
|
|
114
|
+
const chunk = Buffer.from(result.stdout.replace(/\s+/g, ""), "base64");
|
|
115
|
+
if (chunk.byteLength === 0)
|
|
116
|
+
break;
|
|
117
|
+
decodedChunks.push(chunk);
|
|
118
|
+
decodedSoFar += chunk.byteLength;
|
|
119
|
+
await options?.onProgress?.(Math.min(decodedSoFar, totalBytes), totalBytes);
|
|
120
|
+
}
|
|
121
|
+
const out = Buffer.concat(decodedChunks);
|
|
122
|
+
if (out.byteLength !== totalBytes) {
|
|
123
|
+
throw new Error(`Remote file read was truncated for ${remotePath}: ${out.byteLength}/${totalBytes} bytes`);
|
|
124
|
+
}
|
|
125
|
+
await options?.onProgress?.(out.byteLength, totalBytes);
|
|
126
|
+
return out;
|
|
56
127
|
},
|
|
57
128
|
listFiles: async (remotePath) => {
|
|
58
129
|
const result = await runShell(`if [ -d ${shellQuote(remotePath)} ]; then ` +
|
|
@@ -133,6 +204,8 @@ export async function prepareCommandManagedRuntime(input) {
|
|
|
133
204
|
workspaceExclude: mergeRuntimeExcludes(input.workspaceExclude),
|
|
134
205
|
preserveAbsentOnRestore: input.preserveAbsentOnRestore,
|
|
135
206
|
assets: input.assets,
|
|
207
|
+
onProgress: input.onProgress,
|
|
208
|
+
onRuntimeProgress: input.onRuntimeProgress,
|
|
136
209
|
});
|
|
137
210
|
}
|
|
138
211
|
}
|
|
@@ -163,6 +236,8 @@ export async function prepareCommandManagedRuntime(input) {
|
|
|
163
236
|
workspaceExclude: mergeRuntimeExcludes(input.workspaceExclude),
|
|
164
237
|
preserveAbsentOnRestore: input.preserveAbsentOnRestore,
|
|
165
238
|
assets: input.assets,
|
|
239
|
+
onProgress: input.onProgress,
|
|
240
|
+
onRuntimeProgress: input.onRuntimeProgress,
|
|
166
241
|
});
|
|
167
242
|
}
|
|
168
243
|
//# sourceMappingURL=command-managed-runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-managed-runtime.js","sourceRoot":"","sources":["../src/command-managed-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EACL,4BAA4B,GAK7B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"command-managed-runtime.js","sourceRoot":"","sources":["../src/command-managed-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,79 +5,15 @@ import path from "node:path";
|
|
|
5
5
|
import { execFile as execFileCallback, spawn } from "node:child_process";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { afterEach, describe, expect, it } from "vitest";
|
|
8
|
-
import { prepareCommandManagedRuntime } from "./command-managed-runtime.js";
|
|
8
|
+
import { createCommandManagedRuntimeClient, prepareCommandManagedRuntime, } from "./command-managed-runtime.js";
|
|
9
9
|
const execFile = promisify(execFileCallback);
|
|
10
|
-
function createChildProcessEnv(overrides) {
|
|
11
|
-
const env = {};
|
|
12
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
13
|
-
if (value !== undefined)
|
|
14
|
-
env[key] = value;
|
|
15
|
-
}
|
|
16
|
-
return { ...env, ...overrides };
|
|
17
|
-
}
|
|
18
|
-
function execFileWithInput(command, args, options) {
|
|
19
|
-
if (options.input == null) {
|
|
20
|
-
return execFile(command, args, {
|
|
21
|
-
cwd: options.cwd,
|
|
22
|
-
env: options.env,
|
|
23
|
-
maxBuffer: options.maxBuffer,
|
|
24
|
-
timeout: options.timeout,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
let stdout = "";
|
|
29
|
-
let stderr = "";
|
|
30
|
-
let timedOut = false;
|
|
31
|
-
const child = spawn(command, args, {
|
|
32
|
-
cwd: options.cwd,
|
|
33
|
-
env: options.env,
|
|
34
|
-
windowsHide: true,
|
|
35
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
36
|
-
});
|
|
37
|
-
const timer = options.timeout
|
|
38
|
-
? setTimeout(() => {
|
|
39
|
-
timedOut = true;
|
|
40
|
-
child.kill();
|
|
41
|
-
}, options.timeout)
|
|
42
|
-
: null;
|
|
43
|
-
child.stdout.setEncoding("utf8");
|
|
44
|
-
child.stderr.setEncoding("utf8");
|
|
45
|
-
child.stdout.on("data", (chunk) => {
|
|
46
|
-
stdout += chunk;
|
|
47
|
-
if (options.maxBuffer && stdout.length > options.maxBuffer)
|
|
48
|
-
child.kill();
|
|
49
|
-
});
|
|
50
|
-
child.stderr.on("data", (chunk) => {
|
|
51
|
-
stderr += chunk;
|
|
52
|
-
if (options.maxBuffer && stderr.length > options.maxBuffer)
|
|
53
|
-
child.kill();
|
|
54
|
-
});
|
|
55
|
-
child.on("error", reject);
|
|
56
|
-
child.on("close", (code, signal) => {
|
|
57
|
-
if (timer)
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
if (code === 0 && !timedOut) {
|
|
60
|
-
resolve({ stdout, stderr });
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const error = new Error(`${command} exited with code ${code ?? "null"}`);
|
|
64
|
-
error.stdout = stdout;
|
|
65
|
-
error.stderr = stderr;
|
|
66
|
-
error.code = code;
|
|
67
|
-
error.signal = signal;
|
|
68
|
-
error.killed = timedOut;
|
|
69
|
-
reject(error);
|
|
70
|
-
});
|
|
71
|
-
child.stdin.end(options.input);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
10
|
function resolveTestPosixShellCommand(command) {
|
|
75
11
|
if (process.platform !== "win32")
|
|
76
12
|
return command === "bash" ? "/bin/bash" : "/bin/sh";
|
|
77
13
|
const candidates = command === "bash"
|
|
78
14
|
? ["C:\\Program Files\\Git\\bin\\bash.exe", "C:\\Program Files\\Git\\usr\\bin\\bash.exe"]
|
|
79
15
|
: ["C:\\Program Files\\Git\\usr\\bin\\sh.exe", "C:\\Program Files\\Git\\bin\\bash.exe"];
|
|
80
|
-
return candidates.find((candidate) => existsSync(candidate)) ??
|
|
16
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? command;
|
|
81
17
|
}
|
|
82
18
|
function augmentTestPosixPath(env) {
|
|
83
19
|
if (process.platform !== "win32")
|
|
@@ -93,7 +29,109 @@ function augmentTestPosixPath(env) {
|
|
|
93
29
|
function rewriteWindowsPathsForGitShell(script) {
|
|
94
30
|
if (process.platform !== "win32")
|
|
95
31
|
return script;
|
|
96
|
-
|
|
32
|
+
const toGitShellPath = (drive, rest) => `/${drive.toLowerCase()}/${rest.replace(/\\/g, "/")}`;
|
|
33
|
+
const rewritten = script
|
|
34
|
+
.replace(/'([A-Za-z]):\\([^']*)'/g, (_match, drive, rest) => `'${toGitShellPath(drive, rest)}'`)
|
|
35
|
+
.replace(/"([A-Za-z]):\\([^"]*)"/g, (_match, drive, rest) => `"${toGitShellPath(drive, rest)}"`)
|
|
36
|
+
.replace(/([A-Za-z]):\\([^'"\s]*)/g, (_match, drive, rest) => toGitShellPath(drive, rest));
|
|
37
|
+
return rewritten.replace(/mkdir -p '\.' && (?=(?:base64 -d >|rm -f|: >)\s+'([^']+)')/g, (_match, targetPath) => `mkdir -p '${path.posix.dirname(targetPath)}' && `);
|
|
38
|
+
}
|
|
39
|
+
function resolveRunnerShell(input) {
|
|
40
|
+
const env = { ...process.env, ...input.env };
|
|
41
|
+
const command = input.command === "sh" || input.command === "bash"
|
|
42
|
+
? resolveTestPosixShellCommand(input.command)
|
|
43
|
+
: input.command;
|
|
44
|
+
const args = [...(input.args ?? [])];
|
|
45
|
+
if (input.command === "sh" || input.command === "bash") {
|
|
46
|
+
augmentTestPosixPath(env);
|
|
47
|
+
}
|
|
48
|
+
if ((input.command === "sh" || input.command === "bash") &&
|
|
49
|
+
(args[0] === "-c" || args[0] === "-lc") &&
|
|
50
|
+
typeof args[1] === "string") {
|
|
51
|
+
args[1] = rewriteWindowsPathsForGitShell(args[1]);
|
|
52
|
+
}
|
|
53
|
+
return { command, args, env };
|
|
54
|
+
}
|
|
55
|
+
// A runner that actually executes the shell scripts (piping stdin through a real
|
|
56
|
+
// pipe so multi-MB payloads work) and replays stdout through onLog in several
|
|
57
|
+
// chunks so the streaming readFile byte-counter is exercised.
|
|
58
|
+
function makeSpawnRunner(options = {}) {
|
|
59
|
+
const calls = [];
|
|
60
|
+
const runner = {
|
|
61
|
+
supportsSingleStreamStdinProgress: options.supportsSingleStreamStdinProgress,
|
|
62
|
+
execute: async (input) => await new Promise((resolve) => {
|
|
63
|
+
calls.push({ command: input.command, args: input.args, cwd: input.cwd, stdin: input.stdin });
|
|
64
|
+
const startedAt = new Date().toISOString();
|
|
65
|
+
const { command, args, env } = resolveRunnerShell(input);
|
|
66
|
+
const child = spawn(command, args, {
|
|
67
|
+
cwd: input.cwd,
|
|
68
|
+
env,
|
|
69
|
+
});
|
|
70
|
+
let stdout = "";
|
|
71
|
+
let stderr = "";
|
|
72
|
+
child.stdout.on("data", (chunk) => {
|
|
73
|
+
stdout += chunk.toString("utf8");
|
|
74
|
+
});
|
|
75
|
+
child.stderr.on("data", (chunk) => {
|
|
76
|
+
stderr += chunk.toString("utf8");
|
|
77
|
+
});
|
|
78
|
+
child.on("error", () => {
|
|
79
|
+
resolve({ exitCode: 127, signal: null, timedOut: false, stdout, stderr, pid: null, startedAt });
|
|
80
|
+
});
|
|
81
|
+
child.on("close", async (code) => {
|
|
82
|
+
if (options.maxStdoutBytes != null &&
|
|
83
|
+
Buffer.byteLength(stdout, "utf8") > options.maxStdoutBytes) {
|
|
84
|
+
resolve({
|
|
85
|
+
exitCode: 1,
|
|
86
|
+
signal: null,
|
|
87
|
+
timedOut: false,
|
|
88
|
+
stdout,
|
|
89
|
+
stderr: `stdout exceeded ${options.maxStdoutBytes} bytes`,
|
|
90
|
+
pid: child.pid ?? null,
|
|
91
|
+
startedAt,
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (input.onLog && stdout.length > 0) {
|
|
96
|
+
const chunkSize = Math.max(1, Math.ceil(stdout.length / 4));
|
|
97
|
+
for (let offset = 0; offset < stdout.length; offset += chunkSize) {
|
|
98
|
+
await input.onLog("stdout", stdout.slice(offset, offset + chunkSize));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
resolve({
|
|
102
|
+
exitCode: code ?? 0,
|
|
103
|
+
signal: null,
|
|
104
|
+
timedOut: false,
|
|
105
|
+
stdout,
|
|
106
|
+
stderr,
|
|
107
|
+
pid: child.pid ?? null,
|
|
108
|
+
startedAt,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
if (input.stdin != null)
|
|
112
|
+
child.stdin.write(input.stdin);
|
|
113
|
+
child.stdin.end();
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
return { runner, calls };
|
|
117
|
+
}
|
|
118
|
+
function toArrayBuffer(buffer) {
|
|
119
|
+
return Uint8Array.from(buffer).buffer;
|
|
120
|
+
}
|
|
121
|
+
async function withBase64StringByteLimit(limitBytes, fn) {
|
|
122
|
+
const originalToString = Buffer.prototype.toString;
|
|
123
|
+
Buffer.prototype.toString = function patchedToString(encoding, start, end) {
|
|
124
|
+
if (encoding === "base64" && this.byteLength > limitBytes) {
|
|
125
|
+
throw new Error(`test guard: attempted to base64-encode ${this.byteLength} bytes at once`);
|
|
126
|
+
}
|
|
127
|
+
return originalToString.call(this, encoding, start, end);
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
return await fn();
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
Buffer.prototype.toString = originalToString;
|
|
134
|
+
}
|
|
97
135
|
}
|
|
98
136
|
describe("command managed runtime", () => {
|
|
99
137
|
const cleanupDirs = [];
|
|
@@ -119,24 +157,18 @@ describe("command managed runtime", () => {
|
|
|
119
157
|
execute: async (input) => {
|
|
120
158
|
calls.push({ ...input });
|
|
121
159
|
const startedAt = new Date().toISOString();
|
|
122
|
-
const env =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
: input.command;
|
|
126
|
-
const args = [...(input.args ?? [])];
|
|
127
|
-
if (input.command === "sh" || input.command === "bash") {
|
|
128
|
-
augmentTestPosixPath(env);
|
|
129
|
-
}
|
|
130
|
-
if ((input.command === "sh" || input.command === "bash") &&
|
|
160
|
+
const { command, args, env } = resolveRunnerShell(input);
|
|
161
|
+
if (input.stdin != null &&
|
|
162
|
+
(input.command === "sh" || input.command === "bash") &&
|
|
131
163
|
(args[0] === "-c" || args[0] === "-lc") &&
|
|
132
164
|
typeof args[1] === "string") {
|
|
133
|
-
|
|
165
|
+
env.PAPERCLIP_TEST_STDIN = input.stdin;
|
|
166
|
+
args[1] = `printf '%s' \"$PAPERCLIP_TEST_STDIN\" | (${args[1]})`;
|
|
134
167
|
}
|
|
135
168
|
try {
|
|
136
|
-
const result = await
|
|
169
|
+
const result = await execFile(command, args, {
|
|
137
170
|
cwd: input.cwd,
|
|
138
171
|
env,
|
|
139
|
-
input: input.stdin,
|
|
140
172
|
maxBuffer: 32 * 1024 * 1024,
|
|
141
173
|
timeout: input.timeoutMs,
|
|
142
174
|
});
|
|
@@ -176,9 +208,9 @@ describe("command managed runtime", () => {
|
|
|
176
208
|
await expect(readFile(path.join(remoteWorkspaceDir, "README.md"), "utf8")).resolves.toBe("local workspace\n");
|
|
177
209
|
await expect(readFile(path.join(remoteWorkspaceDir, ".paperclip-runtime", "state.json"), "utf8")).rejects
|
|
178
210
|
.toMatchObject({ code: "ENOENT" });
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
expect(
|
|
211
|
+
// The single-stream upload pipes the tarball through exactly one stdin-backed
|
|
212
|
+
// process (the speed fix); nothing else streams stdin.
|
|
213
|
+
expect(calls.filter((call) => call.stdin != null).length).toBe(1);
|
|
182
214
|
await mkdir(path.join(remoteWorkspaceDir, ".paperclip-runtime"), { recursive: true });
|
|
183
215
|
await writeFile(path.join(remoteWorkspaceDir, "README.md"), "remote workspace\n", "utf8");
|
|
184
216
|
await writeFile(path.join(remoteWorkspaceDir, ".paperclip-runtime", "remote-state.json"), "{\"remote\":true}\n", "utf8");
|
|
@@ -188,7 +220,10 @@ describe("command managed runtime", () => {
|
|
|
188
220
|
.toBe("{\"keep\":true}\n");
|
|
189
221
|
await expect(readFile(path.join(localWorkspaceDir, ".paperclip-runtime", "remote-state.json"), "utf8")).rejects
|
|
190
222
|
.toMatchObject({ code: "ENOENT" });
|
|
191
|
-
|
|
223
|
+
// Restore streams the download through `base64`/onLog (no stdin), so the only
|
|
224
|
+
// stdin-backed call remains the single upload from prepare.
|
|
225
|
+
expect(calls.filter((call) => call.stdin != null).length).toBe(1);
|
|
226
|
+
});
|
|
192
227
|
it("runs setup commands from a stable root cwd when staging into a nested remote workspace dir", async () => {
|
|
193
228
|
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-runtime-nested-"));
|
|
194
229
|
cleanupDirs.push(rootDir);
|
|
@@ -198,56 +233,7 @@ describe("command managed runtime", () => {
|
|
|
198
233
|
await mkdir(localWorkspaceDir, { recursive: true });
|
|
199
234
|
await mkdir(remoteBaseDir, { recursive: true });
|
|
200
235
|
await writeFile(path.join(localWorkspaceDir, "README.md"), "local workspace\n", "utf8");
|
|
201
|
-
const calls =
|
|
202
|
-
const runner = {
|
|
203
|
-
execute: async (input) => {
|
|
204
|
-
calls.push({ ...input });
|
|
205
|
-
const startedAt = new Date().toISOString();
|
|
206
|
-
const env = createChildProcessEnv(input.env);
|
|
207
|
-
const command = input.command === "sh" || input.command === "bash"
|
|
208
|
-
? resolveTestPosixShellCommand(input.command)
|
|
209
|
-
: input.command;
|
|
210
|
-
const args = [...(input.args ?? [])];
|
|
211
|
-
if (input.command === "sh" || input.command === "bash") {
|
|
212
|
-
augmentTestPosixPath(env);
|
|
213
|
-
}
|
|
214
|
-
if ((input.command === "sh" || input.command === "bash") &&
|
|
215
|
-
(args[0] === "-c" || args[0] === "-lc") &&
|
|
216
|
-
typeof args[1] === "string") {
|
|
217
|
-
args[1] = rewriteWindowsPathsForGitShell(args[1]);
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
const result = await execFileWithInput(command, args, {
|
|
221
|
-
cwd: input.cwd,
|
|
222
|
-
env,
|
|
223
|
-
input: input.stdin,
|
|
224
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
225
|
-
timeout: input.timeoutMs,
|
|
226
|
-
});
|
|
227
|
-
return {
|
|
228
|
-
exitCode: 0,
|
|
229
|
-
signal: null,
|
|
230
|
-
timedOut: false,
|
|
231
|
-
stdout: result.stdout,
|
|
232
|
-
stderr: result.stderr,
|
|
233
|
-
pid: null,
|
|
234
|
-
startedAt,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
const err = error;
|
|
239
|
-
return {
|
|
240
|
-
exitCode: typeof err.code === "number" ? err.code : null,
|
|
241
|
-
signal: err.signal ?? null,
|
|
242
|
-
timedOut: Boolean(err.killed && input.timeoutMs),
|
|
243
|
-
stdout: err.stdout ?? "",
|
|
244
|
-
stderr: err.stderr ?? "",
|
|
245
|
-
pid: null,
|
|
246
|
-
startedAt,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
};
|
|
236
|
+
const { runner, calls } = makeSpawnRunner();
|
|
251
237
|
await prepareCommandManagedRuntime({
|
|
252
238
|
runner,
|
|
253
239
|
spec: {
|
|
@@ -261,6 +247,113 @@ describe("command managed runtime", () => {
|
|
|
261
247
|
expect(calls.length).toBeGreaterThan(0);
|
|
262
248
|
expect(calls.every((call) => call.cwd === "/")).toBe(true);
|
|
263
249
|
await expect(readFile(path.join(remoteWorkspaceDir, "README.md"), "utf8")).resolves.toBe("local workspace\n");
|
|
264
|
-
}
|
|
250
|
+
});
|
|
251
|
+
it("uploads a multi-MB payload in a single process and preserves exact bytes", async () => {
|
|
252
|
+
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-write-"));
|
|
253
|
+
cleanupDirs.push(rootDir);
|
|
254
|
+
const remotePath = path.join(rootDir, "nested", "payload.bin");
|
|
255
|
+
// ~3 MB of every byte value so the test catches any non-binary-safe handling.
|
|
256
|
+
const payload = Buffer.alloc(3 * 1024 * 1024);
|
|
257
|
+
for (let i = 0; i < payload.length; i++)
|
|
258
|
+
payload[i] = i % 256;
|
|
259
|
+
const { runner, calls } = makeSpawnRunner({ supportsSingleStreamStdinProgress: true });
|
|
260
|
+
const client = createCommandManagedRuntimeClient({ runner, commandCwd: "/", timeoutMs: 30_000 });
|
|
261
|
+
const progress = [];
|
|
262
|
+
await withBase64StringByteLimit(4 * 1024 * 1024, async () => {
|
|
263
|
+
await client.writeFile(remotePath, toArrayBuffer(payload), {
|
|
264
|
+
onProgress: (done, total) => {
|
|
265
|
+
progress.push({ done, total });
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
// Exactly one upload process: O(1) round-trips regardless of payload size.
|
|
270
|
+
expect(calls.length).toBe(1);
|
|
271
|
+
expect(calls[0].stdin).toBeTypeOf("string");
|
|
272
|
+
const written = await readFile(remotePath);
|
|
273
|
+
expect(written.equals(payload)).toBe(true);
|
|
274
|
+
// Progress is monotonically non-decreasing and reaches the total.
|
|
275
|
+
expect(progress.length).toBeGreaterThan(0);
|
|
276
|
+
for (let i = 1; i < progress.length; i++) {
|
|
277
|
+
expect(progress[i].done).toBeGreaterThanOrEqual(progress[i - 1].done);
|
|
278
|
+
}
|
|
279
|
+
expect(progress.at(-1)).toEqual({ done: payload.length, total: payload.length });
|
|
280
|
+
});
|
|
281
|
+
it("falls back to chunked upload progress when the runner cannot report mid-stream stdin progress", async () => {
|
|
282
|
+
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-write-fallback-"));
|
|
283
|
+
cleanupDirs.push(rootDir);
|
|
284
|
+
const remotePath = path.join(rootDir, "nested", "payload.bin");
|
|
285
|
+
const payload = Buffer.alloc(12 * 1024 * 1024);
|
|
286
|
+
for (let i = 0; i < payload.length; i++)
|
|
287
|
+
payload[i] = i % 256;
|
|
288
|
+
const { runner, calls } = makeSpawnRunner({ supportsSingleStreamStdinProgress: false });
|
|
289
|
+
const client = createCommandManagedRuntimeClient({ runner, commandCwd: "/", timeoutMs: 30_000 });
|
|
290
|
+
const progress = [];
|
|
291
|
+
await client.writeFile(remotePath, toArrayBuffer(payload), {
|
|
292
|
+
onProgress: (done, total) => {
|
|
293
|
+
progress.push({ done, total });
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
const written = await readFile(remotePath);
|
|
297
|
+
expect(written.equals(payload)).toBe(true);
|
|
298
|
+
// Provider-backed sandbox runners cannot surface mid-flight progress for a
|
|
299
|
+
// single stdin RPC, so we intentionally use several large append commands.
|
|
300
|
+
expect(calls.length).toBeGreaterThan(2);
|
|
301
|
+
const stdinCalls = calls.filter((call) => call.stdin != null);
|
|
302
|
+
expect(stdinCalls.length).toBeGreaterThan(2);
|
|
303
|
+
expect(stdinCalls.every((call) => Buffer.byteLength(call.stdin ?? "", "utf8") <= 4.1 * 1024 * 1024)).toBe(true);
|
|
304
|
+
expect(progress.length).toBeGreaterThan(2);
|
|
305
|
+
for (let i = 1; i < progress.length; i++) {
|
|
306
|
+
expect(progress[i].done).toBeGreaterThanOrEqual(progress[i - 1].done);
|
|
307
|
+
}
|
|
308
|
+
expect(progress.at(-1)).toEqual({ done: payload.length, total: payload.length });
|
|
309
|
+
});
|
|
310
|
+
it("falls back to bounded chunks when the runner does not explicitly opt in", async () => {
|
|
311
|
+
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-write-fallback-no-progress-"));
|
|
312
|
+
cleanupDirs.push(rootDir);
|
|
313
|
+
const remotePath = path.join(rootDir, "nested", "payload.bin");
|
|
314
|
+
const payload = Buffer.alloc(12 * 1024 * 1024);
|
|
315
|
+
for (let i = 0; i < payload.length; i++)
|
|
316
|
+
payload[i] = i % 256;
|
|
317
|
+
const { runner, calls } = makeSpawnRunner();
|
|
318
|
+
const client = createCommandManagedRuntimeClient({ runner, commandCwd: "/", timeoutMs: 30_000 });
|
|
319
|
+
await withBase64StringByteLimit(4 * 1024 * 1024, async () => {
|
|
320
|
+
await client.writeFile(remotePath, toArrayBuffer(payload));
|
|
321
|
+
});
|
|
322
|
+
const written = await readFile(remotePath);
|
|
323
|
+
expect(written.equals(payload)).toBe(true);
|
|
324
|
+
// A runner that doesn't mark single-stream stdin support must avoid passing
|
|
325
|
+
// the whole base64 archive as one string, so we expect multiple append calls.
|
|
326
|
+
const stdinCalls = calls.filter((call) => call.stdin != null);
|
|
327
|
+
expect(stdinCalls.length).toBeGreaterThan(1);
|
|
328
|
+
expect(stdinCalls.every((call) => Buffer.byteLength(call.stdin ?? "", "utf8") <= 4.1 * 1024 * 1024)).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
it("downloads in bounded stdout chunks and reports monotonic byte progress to the total", async () => {
|
|
331
|
+
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-read-"));
|
|
332
|
+
cleanupDirs.push(rootDir);
|
|
333
|
+
const remotePath = path.join(rootDir, "download.bin");
|
|
334
|
+
const payload = Buffer.alloc(7 * 1024 * 1024);
|
|
335
|
+
for (let i = 0; i < payload.length; i++)
|
|
336
|
+
payload[i] = (i * 7) % 256;
|
|
337
|
+
await writeFile(remotePath, payload);
|
|
338
|
+
const { runner, calls } = makeSpawnRunner({ maxStdoutBytes: 5 * 1024 * 1024 });
|
|
339
|
+
const client = createCommandManagedRuntimeClient({ runner, commandCwd: "/", timeoutMs: 30_000 });
|
|
340
|
+
const progress = [];
|
|
341
|
+
const bytes = await client.readFile(remotePath, {
|
|
342
|
+
onProgress: (done, total) => {
|
|
343
|
+
progress.push({ done, total });
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
expect(Buffer.from(bytes).equals(payload)).toBe(true);
|
|
347
|
+
// The old single `base64 < file` path would exceed the runner's stdout cap.
|
|
348
|
+
// The bounded path reads with several small `dd | base64` commands instead.
|
|
349
|
+
expect(calls.some((call) => call.args?.join(" ").includes("base64 <"))).toBe(false);
|
|
350
|
+
expect(calls.filter((call) => call.args?.join(" ").includes("dd if=")).length).toBeGreaterThan(1);
|
|
351
|
+
expect(progress.length).toBeGreaterThan(1);
|
|
352
|
+
for (let i = 1; i < progress.length; i++) {
|
|
353
|
+
expect(progress[i].done).toBeGreaterThanOrEqual(progress[i - 1].done);
|
|
354
|
+
}
|
|
355
|
+
expect(progress.every((entry) => entry.total === payload.length)).toBe(true);
|
|
356
|
+
expect(progress.at(-1)?.done).toBe(payload.length);
|
|
357
|
+
});
|
|
265
358
|
});
|
|
266
359
|
//# sourceMappingURL=command-managed-runtime.test.js.map
|