@snappedly-tools/shipyard 0.7.0-staging.36082887921 → 0.7.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/README.md +41 -34
- package/dist/MountConfig-bZoCs4Dd.d.ts +26 -0
- package/dist/SandboxProvider-XJQqEdSf.d.ts +261 -0
- package/dist/{chunk-JI3HDDMS.js → chunk-56TDSWFU.js} +3 -3
- package/dist/{chunk-JI3HDDMS.js.map → chunk-56TDSWFU.js.map} +1 -1
- package/dist/chunk-ACD46ZM4.js +136 -0
- package/dist/chunk-ACD46ZM4.js.map +1 -0
- package/dist/{chunk-L6PX5QTU.js → chunk-FDYOTN55.js} +887 -262
- package/dist/chunk-FDYOTN55.js.map +1 -0
- package/dist/chunk-KMGNFXKN.js +38 -0
- package/dist/chunk-KMGNFXKN.js.map +1 -0
- package/dist/chunk-SOJTAJTF.js +78 -0
- package/dist/chunk-SOJTAJTF.js.map +1 -0
- package/dist/{chunk-44I2BL6E.js → chunk-WAXEMZUV.js} +33 -225
- package/dist/chunk-WAXEMZUV.js.map +1 -0
- package/dist/index.d.ts +97 -117
- package/dist/index.js +344 -322
- package/dist/index.js.map +1 -1
- package/dist/main.js +139 -235
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +3 -1
- package/dist/sandboxes/docker.js +4 -2
- package/dist/sandboxes/no-sandbox.d.ts +37 -0
- package/dist/sandboxes/no-sandbox.js +4 -0
- package/dist/sandboxes/no-sandbox.js.map +1 -0
- package/dist/sandboxes/vercel.d.ts +104 -0
- package/dist/sandboxes/vercel.js +166 -0
- package/dist/sandboxes/vercel.js.map +1 -0
- package/dist/templates/blank/main.mts +13 -0
- package/dist/templates/blank/prompt.md +12 -0
- package/dist/templates/blank/template.json +4 -0
- package/dist/templates/parallel-planner/main.mts +9 -67
- package/dist/templates/parallel-planner-with-review/main.mts +12 -70
- package/dist/templates/sequential-reviewer/main.mts +3 -52
- package/dist/templates/simple-loop/main.mts +2 -51
- package/package.json +17 -1
- package/dist/MountConfig-BHnKnA4h.d.ts +0 -145
- package/dist/chunk-44I2BL6E.js.map +0 -1
- package/dist/chunk-L6PX5QTU.js.map +0 -1
- package/dist/templates/parallel-planner/planner-branch.mts +0 -151
- package/dist/templates/parallel-planner-with-review/planner-branch.mts +0 -151
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
|
|
3
|
+
// src/boundedTail.ts
|
|
4
|
+
var MAX_TAIL_CHARS = 64 * 1024;
|
|
5
|
+
var OutputByteCounter = class {
|
|
6
|
+
constructor(maxBytes) {
|
|
7
|
+
this.maxBytes = maxBytes;
|
|
8
|
+
}
|
|
9
|
+
totalBytes = 0;
|
|
10
|
+
limitExceeded = false;
|
|
11
|
+
add(chunk) {
|
|
12
|
+
this.totalBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
|
|
13
|
+
if (this.totalBytes > this.maxBytes) this.limitExceeded = true;
|
|
14
|
+
}
|
|
15
|
+
get exceeded() {
|
|
16
|
+
return this.limitExceeded;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var BoundedTail = class {
|
|
20
|
+
items = [];
|
|
21
|
+
totalChars = 0;
|
|
22
|
+
maxChars;
|
|
23
|
+
separator;
|
|
24
|
+
/**
|
|
25
|
+
* @param maxChars Maximum length of the joined tail. Defaults to {@link MAX_TAIL_CHARS}.
|
|
26
|
+
* @param separator String placed between items by {@link toString}. Must match
|
|
27
|
+
* how the caller would otherwise have joined the accumulated chunks (e.g.
|
|
28
|
+
* `"\n"` for line streams, `""` for raw chunk streams).
|
|
29
|
+
*/
|
|
30
|
+
constructor(maxChars = MAX_TAIL_CHARS, separator = "") {
|
|
31
|
+
this.maxChars = maxChars;
|
|
32
|
+
this.separator = separator;
|
|
33
|
+
}
|
|
34
|
+
/** Append one item to the tail, evicting oldest items to stay within budget. */
|
|
35
|
+
push(item) {
|
|
36
|
+
const bounded = item.length > this.maxChars ? item.slice(item.length - this.maxChars) : item;
|
|
37
|
+
this.totalChars += bounded.length + (this.items.length > 0 ? this.separator.length : 0);
|
|
38
|
+
this.items.push(bounded);
|
|
39
|
+
while (this.totalChars > this.maxChars && this.items.length > 1) {
|
|
40
|
+
const dropped = this.items.shift();
|
|
41
|
+
this.totalChars -= dropped.length + this.separator.length;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Join the retained tail into a single string (length ≤ `maxChars`). */
|
|
45
|
+
toString() {
|
|
46
|
+
return this.items.join(this.separator);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var StreamedProcessOutput = class {
|
|
50
|
+
maxOutputBytes;
|
|
51
|
+
byteCounter;
|
|
52
|
+
stdoutTail;
|
|
53
|
+
stderrTail;
|
|
54
|
+
onLine;
|
|
55
|
+
outputLimitError;
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.maxOutputBytes = options.maxOutputBytes;
|
|
58
|
+
this.byteCounter = options.maxOutputBytes === void 0 ? void 0 : new OutputByteCounter(options.maxOutputBytes);
|
|
59
|
+
const tailChars = options.maxOutputBytes ?? options.maxOutputTailChars ?? MAX_TAIL_CHARS;
|
|
60
|
+
this.stdoutTail = new BoundedTail(tailChars, "\n");
|
|
61
|
+
this.stderrTail = new BoundedTail(tailChars, "");
|
|
62
|
+
this.onLine = options.onLine ?? (() => {
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
checkLimit(chunk) {
|
|
66
|
+
if (this.byteCounter === void 0 || this.outputLimitError !== void 0) {
|
|
67
|
+
return this.outputLimitError;
|
|
68
|
+
}
|
|
69
|
+
this.byteCounter.add(chunk);
|
|
70
|
+
if (this.byteCounter.exceeded) {
|
|
71
|
+
this.outputLimitError = new Error(
|
|
72
|
+
`Sandbox command output exceeded ${this.maxOutputBytes} bytes`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return this.outputLimitError;
|
|
76
|
+
}
|
|
77
|
+
addStdoutLine(line) {
|
|
78
|
+
this.stdoutTail.push(line);
|
|
79
|
+
this.onLine(line);
|
|
80
|
+
}
|
|
81
|
+
addStderr(chunk) {
|
|
82
|
+
this.stderrTail.push(chunk);
|
|
83
|
+
}
|
|
84
|
+
get error() {
|
|
85
|
+
return this.outputLimitError;
|
|
86
|
+
}
|
|
87
|
+
result(exitCode) {
|
|
88
|
+
return {
|
|
89
|
+
stdout: this.stdoutTail.toString(),
|
|
90
|
+
stderr: this.stderrTail.toString(),
|
|
91
|
+
exitCode
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var collectProcessOutput = (process, options, resolve, reject) => {
|
|
96
|
+
if (options.onLine || options.maxOutputBytes !== void 0) {
|
|
97
|
+
const output = new StreamedProcessOutput(options);
|
|
98
|
+
const checkLimit = (chunk) => {
|
|
99
|
+
if (output.checkLimit(chunk) !== void 0) process.kill();
|
|
100
|
+
};
|
|
101
|
+
process.stdout.on("data", checkLimit);
|
|
102
|
+
process.stderr.on("data", checkLimit);
|
|
103
|
+
const lines = createInterface({ input: process.stdout });
|
|
104
|
+
lines.on("line", (line) => output.addStdoutLine(line));
|
|
105
|
+
process.stderr.on("data", (chunk) => {
|
|
106
|
+
output.addStderr(chunk.toString());
|
|
107
|
+
});
|
|
108
|
+
process.onClose((code) => {
|
|
109
|
+
if (output.error !== void 0) {
|
|
110
|
+
reject(output.error);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
resolve(output.result(code ?? 0));
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const stdoutChunks = [];
|
|
118
|
+
const stderrChunks = [];
|
|
119
|
+
process.stdout.on("data", (chunk) => {
|
|
120
|
+
stdoutChunks.push(chunk.toString());
|
|
121
|
+
});
|
|
122
|
+
process.stderr.on("data", (chunk) => {
|
|
123
|
+
stderrChunks.push(chunk.toString());
|
|
124
|
+
});
|
|
125
|
+
process.onClose((code) => {
|
|
126
|
+
resolve({
|
|
127
|
+
stdout: stdoutChunks.join(""),
|
|
128
|
+
stderr: stderrChunks.join(""),
|
|
129
|
+
exitCode: code ?? 0
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export { MAX_TAIL_CHARS, StreamedProcessOutput, collectProcessOutput };
|
|
135
|
+
//# sourceMappingURL=chunk-ACD46ZM4.js.map
|
|
136
|
+
//# sourceMappingURL=chunk-ACD46ZM4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/boundedTail.ts","../src/processOutput.ts"],"names":[],"mappings":"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","file":"chunk-ACD46ZM4.js","sourcesContent":["/**\n * A bounded, rolling tail of streamed output — a pure, provider-agnostic\n * utility shared by every sandbox provider that streams `exec` output.\n *\n * When a provider streams output line-by-line, it accumulates the stream only\n * to build the returned `ExecResult.stdout`/`stderr`. Consumers read just the\n * tail of that value (e.g. the last lines of an error, or a fallback for the\n * agent's final result), so retaining the whole stream is unnecessary — and,\n * once the accumulated string passes V8's ~512MB max string length, fatal: a\n * naive `chunks.join()` throws `RangeError: Invalid string length`, which on a\n * long agent run crashes the whole orchestration.\n */\n\n/**\n * Default maximum number of characters retained in a bounded output tail.\n *\n * 64KiB sits comfortably above any agent completion signal or structured-output\n * payload while staying far below V8's max string length.\n */\nexport const MAX_TAIL_CHARS = 64 * 1024;\n\n/**\n * Counts streamed output without retaining it. Providers use this when a\n * caller needs a hard byte ceiling for a one-shot command (for example prompt\n * expansion), so an untrusted command cannot exhaust memory before its output\n * is inspected.\n */\nexport class OutputByteCounter {\n private totalBytes = 0;\n private limitExceeded = false;\n\n constructor(private readonly maxBytes: number) {}\n\n add(chunk: string | Uint8Array): void {\n this.totalBytes +=\n typeof chunk === \"string\" ? Buffer.byteLength(chunk) : chunk.byteLength;\n if (this.totalBytes > this.maxBytes) this.limitExceeded = true;\n }\n\n get exceeded(): boolean {\n return this.limitExceeded;\n }\n}\n\n/**\n * A fixed-size rolling tail of strings, bounded by total character length.\n *\n * `push` appends to the tail; once the joined length would exceed `maxChars`,\n * the oldest items are dropped from the front. A single item longer than\n * `maxChars` is truncated to its own tail, so a newline-free blob can't\n * overflow on one push. `toString` joins the retained items, and its length is\n * always at most `maxChars`.\n *\n * The running length counter is encapsulated so callers can't desync it.\n */\nexport class BoundedTail {\n private readonly items: string[] = [];\n private totalChars = 0;\n private readonly maxChars: number;\n private readonly separator: string;\n\n /**\n * @param maxChars Maximum length of the joined tail. Defaults to {@link MAX_TAIL_CHARS}.\n * @param separator String placed between items by {@link toString}. Must match\n * how the caller would otherwise have joined the accumulated chunks (e.g.\n * `\"\\n\"` for line streams, `\"\"` for raw chunk streams).\n */\n constructor(maxChars: number = MAX_TAIL_CHARS, separator = \"\") {\n this.maxChars = maxChars;\n this.separator = separator;\n }\n\n /** Append one item to the tail, evicting oldest items to stay within budget. */\n push(item: string): void {\n const bounded =\n item.length > this.maxChars\n ? item.slice(item.length - this.maxChars)\n : item;\n this.totalChars +=\n bounded.length + (this.items.length > 0 ? this.separator.length : 0);\n this.items.push(bounded);\n while (this.totalChars > this.maxChars && this.items.length > 1) {\n const dropped = this.items.shift()!;\n this.totalChars -= dropped.length + this.separator.length;\n }\n }\n\n /** Join the retained tail into a single string (length ≤ `maxChars`). */\n toString(): string {\n return this.items.join(this.separator);\n }\n}\n","import type { Readable } from \"node:stream\";\nimport { createInterface } from \"node:readline\";\nimport {\n BoundedTail,\n MAX_TAIL_CHARS,\n OutputByteCounter,\n} from \"./boundedTail.js\";\n\nexport interface ProcessOutputResult {\n readonly stdout: string;\n readonly stderr: string;\n readonly exitCode: number;\n}\n\nexport interface ProcessOutputOptions {\n readonly onLine?: (line: string) => void;\n readonly maxOutputBytes?: number;\n readonly maxOutputTailChars?: number;\n}\n\nexport class StreamedProcessOutput {\n private readonly maxOutputBytes: number | undefined;\n private readonly byteCounter: OutputByteCounter | undefined;\n private readonly stdoutTail: BoundedTail;\n private readonly stderrTail: BoundedTail;\n private readonly onLine: (line: string) => void;\n private outputLimitError: Error | undefined;\n\n constructor(options: ProcessOutputOptions) {\n this.maxOutputBytes = options.maxOutputBytes;\n this.byteCounter =\n options.maxOutputBytes === undefined\n ? undefined\n : new OutputByteCounter(options.maxOutputBytes);\n const tailChars =\n options.maxOutputBytes ?? options.maxOutputTailChars ?? MAX_TAIL_CHARS;\n this.stdoutTail = new BoundedTail(tailChars, \"\\n\");\n this.stderrTail = new BoundedTail(tailChars, \"\");\n this.onLine = options.onLine ?? (() => {});\n }\n\n checkLimit(chunk: string | Uint8Array): Error | undefined {\n if (this.byteCounter === undefined || this.outputLimitError !== undefined) {\n return this.outputLimitError;\n }\n this.byteCounter.add(chunk);\n if (this.byteCounter.exceeded) {\n this.outputLimitError = new Error(\n `Sandbox command output exceeded ${this.maxOutputBytes} bytes`,\n );\n }\n return this.outputLimitError;\n }\n\n addStdoutLine(line: string): void {\n this.stdoutTail.push(line);\n this.onLine(line);\n }\n\n addStderr(chunk: string): void {\n this.stderrTail.push(chunk);\n }\n\n get error(): Error | undefined {\n return this.outputLimitError;\n }\n\n result(exitCode: number): ProcessOutputResult {\n return {\n stdout: this.stdoutTail.toString(),\n stderr: this.stderrTail.toString(),\n exitCode,\n };\n }\n}\n\nexport const collectProcessOutput = (\n process: {\n readonly stdout: Readable;\n readonly stderr: Readable;\n readonly kill: () => void;\n readonly onClose: (listener: (code: number | null) => void) => void;\n },\n options: ProcessOutputOptions,\n resolve: (result: ProcessOutputResult) => void,\n reject: (error: Error) => void,\n): void => {\n if (options.onLine || options.maxOutputBytes !== undefined) {\n const output = new StreamedProcessOutput(options);\n const checkLimit = (chunk: Buffer): void => {\n if (output.checkLimit(chunk) !== undefined) process.kill();\n };\n process.stdout.on(\"data\", checkLimit);\n process.stderr.on(\"data\", checkLimit);\n const lines = createInterface({ input: process.stdout });\n lines.on(\"line\", (line) => output.addStdoutLine(line));\n process.stderr.on(\"data\", (chunk: Buffer) => {\n output.addStderr(chunk.toString());\n });\n process.onClose((code) => {\n if (output.error !== undefined) {\n reject(output.error);\n return;\n }\n resolve(output.result(code ?? 0));\n });\n return;\n }\n\n const stdoutChunks: string[] = [];\n const stderrChunks: string[] = [];\n process.stdout.on(\"data\", (chunk: Buffer) => {\n stdoutChunks.push(chunk.toString());\n });\n process.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk.toString());\n });\n process.onClose((code) => {\n resolve({\n stdout: stdoutChunks.join(\"\"),\n stderr: stderrChunks.join(\"\"),\n exitCode: code ?? 0,\n });\n });\n};\n"]}
|