@snappedly-tools/shipyard 0.4.4 → 0.5.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.
@@ -1,113 +0,0 @@
1
- import { Display } from './chunk-2GT3OPO5.js';
2
- import { Effect } from 'effect';
3
-
4
- var formatErrorMessage = (error) => {
5
- switch (error._tag) {
6
- case "ExecError":
7
- return `Command failed in sandbox (${error.command}): ${error.message}`;
8
- case "ExecHostError":
9
- return `Command failed on host (${error.command}): ${error.message}`;
10
- case "CopyError":
11
- return `File copy failed: ${error.message}`;
12
- case "DockerError":
13
- return `Docker operation failed: ${error.message}. Is Docker running?`;
14
- case "SyncError":
15
- return `Git sync failed: ${error.message}`;
16
- case "WorktreeError":
17
- return `Git worktree operation failed: ${error.message}`;
18
- case "PromptError":
19
- return `Failed to resolve prompt: ${error.message}`;
20
- case "AgentError":
21
- return `Agent invocation failed: ${error.message}`;
22
- case "ConfigDirError":
23
- return `${error.message}`;
24
- case "InitError":
25
- return `${error.message}`;
26
- case "AgentIdleTimeoutError":
27
- case "WorktreeTimeoutError":
28
- case "ContainerStartTimeoutError":
29
- case "CopyToWorktreeTimeoutError":
30
- case "CopyToWorktreeError":
31
- case "SyncInTimeoutError":
32
- case "HookTimeoutError":
33
- case "GitSetupTimeoutError":
34
- case "PromptExpansionTimeoutError":
35
- case "CommitCollectionTimeoutError":
36
- case "MergeToHostTimeoutError":
37
- case "SessionCaptureError":
38
- case "CwdError":
39
- return error.message;
40
- }
41
- };
42
- var showErrorAndExit = (error) => Effect.gen(function* () {
43
- const d = yield* Display;
44
- yield* d.status(formatErrorMessage(error), "error");
45
- return yield* Effect.sync(() => process.exit(1));
46
- });
47
- var withFriendlyErrors = (effect) => (
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
- Effect.catchTags(effect, {
50
- ExecError: showErrorAndExit,
51
- ExecHostError: showErrorAndExit,
52
- CopyError: showErrorAndExit,
53
- DockerError: showErrorAndExit,
54
- SyncError: showErrorAndExit,
55
- WorktreeError: showErrorAndExit,
56
- PromptError: showErrorAndExit,
57
- AgentError: showErrorAndExit,
58
- ConfigDirError: showErrorAndExit,
59
- InitError: showErrorAndExit,
60
- AgentIdleTimeoutError: showErrorAndExit,
61
- WorktreeTimeoutError: showErrorAndExit,
62
- ContainerStartTimeoutError: showErrorAndExit,
63
- CopyToWorktreeTimeoutError: showErrorAndExit,
64
- CopyToWorktreeError: showErrorAndExit,
65
- SyncInTimeoutError: showErrorAndExit,
66
- HookTimeoutError: showErrorAndExit,
67
- GitSetupTimeoutError: showErrorAndExit,
68
- PromptExpansionTimeoutError: showErrorAndExit,
69
- CommitCollectionTimeoutError: showErrorAndExit,
70
- MergeToHostTimeoutError: showErrorAndExit,
71
- SessionCaptureError: showErrorAndExit,
72
- CwdError: showErrorAndExit
73
- })
74
- );
75
-
76
- // src/modelConfig.ts
77
- var CODEX_REASONING_EFFORTS = [
78
- "low",
79
- "medium",
80
- "high",
81
- "xhigh",
82
- "max"
83
- ];
84
- var DEFAULT_CODEX_MODELS = {
85
- routine: { model: "gpt-5.6-luna", effort: "max" },
86
- strong: { model: "gpt-5.6-sol", effort: "max" }
87
- };
88
- var readModel = (role) => process.env[`SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`]?.trim() || DEFAULT_CODEX_MODELS[role].model;
89
- var readEffort = (role) => {
90
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
91
- const value = process.env[envName]?.trim();
92
- if (!value) return DEFAULT_CODEX_MODELS[role].effort;
93
- if (CODEX_REASONING_EFFORTS.includes(value)) {
94
- return value;
95
- }
96
- throw new Error(
97
- `${envName} must be one of ${CODEX_REASONING_EFFORTS.join(", ")}; received "${value}"`
98
- );
99
- };
100
- var CODEX_MODELS = {
101
- routine: {
102
- model: readModel("routine"),
103
- effort: readEffort("routine")
104
- },
105
- strong: {
106
- model: readModel("strong"),
107
- effort: readEffort("strong")
108
- }
109
- };
110
-
111
- export { CODEX_MODELS, CODEX_REASONING_EFFORTS, formatErrorMessage, withFriendlyErrors };
112
- //# sourceMappingURL=chunk-VDHZO5PP.js.map
113
- //# sourceMappingURL=chunk-VDHZO5PP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ErrorHandler.ts","../src/modelConfig.ts"],"names":[],"mappings":"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","file":"chunk-VDHZO5PP.js","sourcesContent":["import { Effect } from \"effect\";\nimport { Display } from \"./Display.js\";\nimport type { SandboxError } from \"./errors.js\";\n\n/**\n * Formats a tagged SandboxError into a user-friendly message with\n * context-specific hints about what went wrong and how to recover.\n */\nexport const formatErrorMessage = (error: SandboxError): string => {\n switch (error._tag) {\n case \"ExecError\":\n return `Command failed in sandbox (${error.command}): ${error.message}`;\n case \"ExecHostError\":\n return `Command failed on host (${error.command}): ${error.message}`;\n case \"CopyError\":\n return `File copy failed: ${error.message}`;\n case \"DockerError\":\n return `Docker operation failed: ${error.message}. Is Docker running?`;\n case \"SyncError\":\n return `Git sync failed: ${error.message}`;\n case \"WorktreeError\":\n return `Git worktree operation failed: ${error.message}`;\n case \"PromptError\":\n return `Failed to resolve prompt: ${error.message}`;\n case \"AgentError\":\n return `Agent invocation failed: ${error.message}`;\n case \"ConfigDirError\":\n return `${error.message}`;\n case \"InitError\":\n return `${error.message}`;\n case \"AgentIdleTimeoutError\":\n case \"WorktreeTimeoutError\":\n case \"ContainerStartTimeoutError\":\n case \"CopyToWorktreeTimeoutError\":\n case \"CopyToWorktreeError\":\n case \"SyncInTimeoutError\":\n case \"HookTimeoutError\":\n case \"GitSetupTimeoutError\":\n case \"PromptExpansionTimeoutError\":\n case \"CommitCollectionTimeoutError\":\n case \"MergeToHostTimeoutError\":\n case \"SessionCaptureError\":\n case \"CwdError\":\n return error.message;\n }\n};\n\nconst showErrorAndExit = (error: SandboxError) =>\n Effect.gen(function* () {\n const d = yield* Display;\n yield* d.status(formatErrorMessage(error), \"error\");\n return yield* Effect.sync(() => process.exit(1) as never);\n });\n\n/**\n * Wraps an effect so that any SandboxError is caught, displayed via the\n * Display service as an error-severity status message, then exits the process\n * with code 1. Non-SandboxError errors pass through unchanged.\n */\nexport const withFriendlyErrors = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, Exclude<E, SandboxError>, R | Display> =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Effect.catchTags(effect as Effect.Effect<A, SandboxError, R>, {\n ExecError: showErrorAndExit,\n ExecHostError: showErrorAndExit,\n CopyError: showErrorAndExit,\n DockerError: showErrorAndExit,\n SyncError: showErrorAndExit,\n WorktreeError: showErrorAndExit,\n PromptError: showErrorAndExit,\n AgentError: showErrorAndExit,\n ConfigDirError: showErrorAndExit,\n InitError: showErrorAndExit,\n AgentIdleTimeoutError: showErrorAndExit,\n WorktreeTimeoutError: showErrorAndExit,\n ContainerStartTimeoutError: showErrorAndExit,\n CopyToWorktreeTimeoutError: showErrorAndExit,\n CopyToWorktreeError: showErrorAndExit,\n SyncInTimeoutError: showErrorAndExit,\n HookTimeoutError: showErrorAndExit,\n GitSetupTimeoutError: showErrorAndExit,\n PromptExpansionTimeoutError: showErrorAndExit,\n CommitCollectionTimeoutError: showErrorAndExit,\n MergeToHostTimeoutError: showErrorAndExit,\n SessionCaptureError: showErrorAndExit,\n CwdError: showErrorAndExit,\n }) as Effect.Effect<A, Exclude<E, SandboxError>, R | Display>;\n","/** Reasoning efforts accepted by the Codex CLI. */\nexport const CODEX_REASONING_EFFORTS = [\n \"low\",\n \"medium\",\n \"high\",\n \"xhigh\",\n \"max\",\n] as const;\n\nexport type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORTS)[number];\n\n/** A model identifier plus the reasoning policy used for that model role. */\nexport interface CodexModelConfig {\n readonly model: string;\n readonly effort: CodexReasoningEffort;\n}\n\n/**\n * Default values for Shipyard's built-in Codex roles.\n *\n * Runtime environment variables override these defaults:\n * SHIPYARD_CODEX_ROUTINE_MODEL\n * SHIPYARD_CODEX_ROUTINE_REASONING_EFFORT\n * SHIPYARD_CODEX_STRONG_MODEL\n * SHIPYARD_CODEX_STRONG_REASONING_EFFORT\n *\n * Keep the defaults here so runtime providers, generated templates, and the\n * repository workflows all use the same role definitions.\n */\nconst DEFAULT_CODEX_MODELS = {\n routine: { model: \"gpt-5.6-luna\", effort: \"max\" },\n strong: { model: \"gpt-5.6-sol\", effort: \"max\" },\n} as const satisfies Record<string, CodexModelConfig>;\n\nconst readModel = (role: keyof typeof DEFAULT_CODEX_MODELS): string =>\n process.env[`SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`]?.trim() ||\n DEFAULT_CODEX_MODELS[role].model;\n\nconst readEffort = (\n role: keyof typeof DEFAULT_CODEX_MODELS,\n): CodexReasoningEffort => {\n const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;\n const value = process.env[envName]?.trim();\n if (!value) return DEFAULT_CODEX_MODELS[role].effort;\n if ((CODEX_REASONING_EFFORTS as readonly string[]).includes(value)) {\n return value as CodexReasoningEffort;\n }\n throw new Error(\n `${envName} must be one of ${CODEX_REASONING_EFFORTS.join(\", \")}; received \"${value}\"`,\n );\n};\n\nexport const CODEX_MODELS = {\n routine: {\n model: readModel(\"routine\"),\n effort: readEffort(\"routine\"),\n },\n strong: {\n model: readModel(\"strong\"),\n effort: readEffort(\"strong\"),\n },\n} as const satisfies Record<string, CodexModelConfig>;\n"]}
@@ -1,126 +0,0 @@
1
- import { MAX_TAIL_CHARS, OutputByteCounter, BoundedTail } from './chunk-WA7QPAXO.js';
2
- import { spawn } from 'child_process';
3
- import { createInterface } from 'readline';
4
-
5
- var noSandbox = (options) => ({
6
- tag: "none",
7
- name: "no-sandbox",
8
- env: options?.env ?? {},
9
- create: async (createOptions) => {
10
- const worktreePath = createOptions.worktreePath;
11
- const processEnv = { ...process.env, ...createOptions.env };
12
- const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;
13
- const handle = {
14
- worktreePath,
15
- exec: (command, opts) => {
16
- const cwd = opts?.cwd ?? worktreePath;
17
- const isWindows = process.platform === "win32";
18
- const shellCmd = isWindows ? "cmd.exe" : "sh";
19
- const shellArgs = isWindows ? ["/d", "/s", "/c", command] : ["-c", command];
20
- return new Promise((resolve, reject) => {
21
- const proc = spawn(shellCmd, shellArgs, {
22
- cwd,
23
- env: processEnv,
24
- signal: opts?.signal,
25
- stdio: [
26
- opts?.stdin !== void 0 ? "pipe" : "ignore",
27
- "pipe",
28
- "pipe"
29
- ],
30
- windowsVerbatimArguments: isWindows
31
- });
32
- if (opts?.stdin !== void 0) {
33
- proc.stdin.write(opts.stdin);
34
- proc.stdin.end();
35
- }
36
- proc.on("error", (error) => {
37
- reject(new Error(`exec failed: ${error.message}`));
38
- });
39
- if (opts?.onLine || opts?.maxOutputBytes !== void 0) {
40
- const onLine = opts?.onLine ?? (() => {
41
- });
42
- const outputLimit = opts?.maxOutputBytes === void 0 ? void 0 : new OutputByteCounter(opts.maxOutputBytes);
43
- const tailChars = opts?.maxOutputBytes ?? maxOutputTailChars;
44
- const stdoutTail = new BoundedTail(tailChars, "\n");
45
- const stderrTail = new BoundedTail(tailChars, "");
46
- let limitError;
47
- const checkOutputLimit = (chunk) => {
48
- if (outputLimit === void 0 || limitError !== void 0) {
49
- return;
50
- }
51
- outputLimit.add(chunk);
52
- if (outputLimit.exceeded) {
53
- limitError = new Error(
54
- `Sandbox command output exceeded ${opts.maxOutputBytes} bytes`
55
- );
56
- proc.kill("SIGKILL");
57
- }
58
- };
59
- proc.stdout.on("data", checkOutputLimit);
60
- proc.stderr.on("data", checkOutputLimit);
61
- const rl = createInterface({ input: proc.stdout });
62
- rl.on("line", (line) => {
63
- stdoutTail.push(line);
64
- onLine(line);
65
- });
66
- proc.stderr.on("data", (chunk) => {
67
- stderrTail.push(chunk.toString());
68
- });
69
- proc.on("close", (code) => {
70
- if (limitError !== void 0) {
71
- reject(limitError);
72
- return;
73
- }
74
- resolve({
75
- stdout: stdoutTail.toString(),
76
- stderr: stderrTail.toString(),
77
- exitCode: code ?? 0
78
- });
79
- });
80
- } else {
81
- const stdoutChunks = [];
82
- const stderrChunks = [];
83
- proc.stdout.on("data", (chunk) => {
84
- stdoutChunks.push(chunk.toString());
85
- });
86
- proc.stderr.on("data", (chunk) => {
87
- stderrChunks.push(chunk.toString());
88
- });
89
- proc.on("close", (code) => {
90
- resolve({
91
- stdout: stdoutChunks.join(""),
92
- stderr: stderrChunks.join(""),
93
- exitCode: code ?? 0
94
- });
95
- });
96
- }
97
- });
98
- },
99
- interactiveExec: (args, opts) => {
100
- return new Promise((resolve, reject) => {
101
- const [cmd, ...rest] = args;
102
- const proc = spawn(cmd, rest, {
103
- cwd: opts.cwd ?? worktreePath,
104
- env: processEnv,
105
- stdio: [opts.stdin, opts.stdout, opts.stderr],
106
- shell: process.platform === "win32",
107
- signal: opts.signal
108
- });
109
- proc.on("error", (error) => {
110
- reject(new Error(`exec failed: ${error.message}`));
111
- });
112
- proc.on("close", (code) => {
113
- resolve({ exitCode: code ?? 0 });
114
- });
115
- });
116
- },
117
- close: async () => {
118
- }
119
- };
120
- return handle;
121
- }
122
- });
123
-
124
- export { noSandbox };
125
- //# sourceMappingURL=chunk-VW4MI5GJ.js.map
126
- //# sourceMappingURL=chunk-VW4MI5GJ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/sandboxes/no-sandbox.ts"],"names":[],"mappings":"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","file":"chunk-VW4MI5GJ.js","sourcesContent":["/**\n * No-sandbox provider — runs the agent directly on the host with no container isolation.\n *\n * Usage:\n * import { noSandbox } from \"@snappedly-tools/shipyard/sandboxes/no-sandbox\";\n * await run({ agent: codex(CODEX_MODELS.routine), sandbox: noSandbox(), prompt: \"...\" });\n *\n * Accepted by `run()`, `interactive()`, and `createSandbox()`. It skips\n * container isolation entirely and does not pass\n * `--dangerously-skip-permissions` to the agent.\n */\n\nimport { spawn, type StdioOptions } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport type {\n NoSandboxProvider,\n NoSandboxHandle,\n ExecResult,\n InteractiveExecOptions,\n} from \"../SandboxProvider.js\";\nimport {\n BoundedTail,\n MAX_TAIL_CHARS,\n OutputByteCounter,\n} from \"../boundedTail.js\";\n\nexport interface NoSandboxOptions {\n /** Environment variables injected by this provider. Merged at launch time. */\n readonly env?: Record<string, string>;\n /**\n * Maximum number of characters of streamed `exec` output retained per stream\n * (stdout and stderr) when an `onLine` callback is supplied (default: 64KiB).\n *\n * Output is delivered live to `onLine` regardless; this only bounds the tail\n * returned in `ExecResult`, preventing a long-running agent's output from\n * overflowing V8's max string length and crashing the run.\n */\n readonly maxOutputTailChars?: number;\n}\n\n/**\n * Create a no-sandbox provider.\n *\n * The returned provider runs the agent directly on the host. All three\n * branch strategies are supported (head, merge-to-head, branch),\n * defaulting to head.\n */\nexport const noSandbox = (options?: NoSandboxOptions): NoSandboxProvider => ({\n tag: \"none\",\n name: \"no-sandbox\",\n env: options?.env ?? {},\n create: async (createOptions): Promise<NoSandboxHandle> => {\n const worktreePath = createOptions.worktreePath;\n const processEnv = { ...process.env, ...createOptions.env };\n const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;\n\n const handle: NoSandboxHandle = {\n worktreePath,\n\n exec: (\n command: string,\n opts?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n signal?: AbortSignal;\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult> => {\n // sudo is a no-op for no-sandbox — the user is already on the host\n const cwd = opts?.cwd ?? worktreePath;\n const isWindows = process.platform === \"win32\";\n // PowerShell and cmd.exe don't ship `sh`, so on Windows route the\n // command string through cmd.exe instead. `/d` skips AutoRun, `/s`\n // preserves the quoted command verbatim, `/c` runs it and exits.\n // `windowsVerbatimArguments` keeps Node from re-quoting our args.\n const shellCmd = isWindows ? \"cmd.exe\" : \"sh\";\n const shellArgs = isWindows\n ? [\"/d\", \"/s\", \"/c\", command]\n : [\"-c\", command];\n\n return new Promise((resolve, reject) => {\n const proc = spawn(shellCmd, shellArgs, {\n cwd,\n env: processEnv,\n signal: opts?.signal,\n stdio: [\n opts?.stdin !== undefined ? \"pipe\" : \"ignore\",\n \"pipe\",\n \"pipe\",\n ],\n windowsVerbatimArguments: isWindows,\n });\n\n if (opts?.stdin !== undefined) {\n proc.stdin!.write(opts.stdin);\n proc.stdin!.end();\n }\n\n proc.on(\"error\", (error) => {\n reject(new Error(`exec failed: ${error.message}`));\n });\n\n if (opts?.onLine || opts?.maxOutputBytes !== undefined) {\n const onLine = opts?.onLine ?? (() => {});\n const outputLimit =\n opts?.maxOutputBytes === undefined\n ? undefined\n : new OutputByteCounter(opts.maxOutputBytes);\n const tailChars = opts?.maxOutputBytes ?? maxOutputTailChars;\n const stdoutTail = new BoundedTail(tailChars, \"\\n\");\n const stderrTail = new BoundedTail(tailChars, \"\");\n let limitError: Error | undefined;\n const checkOutputLimit = (chunk: Buffer): void => {\n if (outputLimit === undefined || limitError !== undefined) {\n return;\n }\n outputLimit.add(chunk);\n if (outputLimit.exceeded) {\n limitError = new Error(\n `Sandbox command output exceeded ${opts.maxOutputBytes} bytes`,\n );\n proc.kill(\"SIGKILL\");\n }\n };\n proc.stdout!.on(\"data\", checkOutputLimit);\n proc.stderr!.on(\"data\", checkOutputLimit);\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n stdoutTail.push(line);\n onLine(line);\n });\n proc.stderr!.on(\"data\", (chunk: Buffer) => {\n stderrTail.push(chunk.toString());\n });\n proc.on(\"close\", (code) => {\n if (limitError !== undefined) {\n reject(limitError);\n return;\n }\n resolve({\n stdout: stdoutTail.toString(),\n stderr: stderrTail.toString(),\n exitCode: code ?? 0,\n });\n });\n } else {\n const stdoutChunks: string[] = [];\n const stderrChunks: string[] = [];\n proc.stdout!.on(\"data\", (chunk: Buffer) => {\n stdoutChunks.push(chunk.toString());\n });\n proc.stderr!.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk.toString());\n });\n proc.on(\"close\", (code) => {\n resolve({\n stdout: stdoutChunks.join(\"\"),\n stderr: stderrChunks.join(\"\"),\n exitCode: code ?? 0,\n });\n });\n }\n });\n },\n\n interactiveExec: (\n args: string[],\n opts: InteractiveExecOptions,\n ): Promise<{ exitCode: number }> => {\n return new Promise((resolve, reject) => {\n const [cmd, ...rest] = args;\n // Agent CLIs on Windows are typically installed as `.cmd`/`.ps1`\n // npm wrappers; bare `spawn(\"claude\", …)` only resolves `.exe`\n // without `shell: true`, so let cmd.exe handle PATHEXT lookup.\n const proc = spawn(cmd!, rest, {\n cwd: opts.cwd ?? worktreePath,\n env: processEnv,\n stdio: [opts.stdin, opts.stdout, opts.stderr] as StdioOptions,\n shell: process.platform === \"win32\",\n signal: opts.signal,\n });\n\n proc.on(\"error\", (error: Error) => {\n reject(new Error(`exec failed: ${error.message}`));\n });\n\n proc.on(\"close\", (code: number | null) => {\n resolve({ exitCode: code ?? 0 });\n });\n });\n },\n\n close: async (): Promise<void> => {\n // No-op — no container to tear down\n },\n };\n\n return handle;\n },\n});\n"]}
@@ -1,50 +0,0 @@
1
- // src/boundedTail.ts
2
- var MAX_TAIL_CHARS = 64 * 1024;
3
- var OutputByteCounter = class {
4
- constructor(maxBytes) {
5
- this.maxBytes = maxBytes;
6
- }
7
- totalBytes = 0;
8
- limitExceeded = false;
9
- add(chunk) {
10
- this.totalBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
11
- if (this.totalBytes > this.maxBytes) this.limitExceeded = true;
12
- }
13
- get exceeded() {
14
- return this.limitExceeded;
15
- }
16
- };
17
- var BoundedTail = class {
18
- items = [];
19
- totalChars = 0;
20
- maxChars;
21
- separator;
22
- /**
23
- * @param maxChars Maximum length of the joined tail. Defaults to {@link MAX_TAIL_CHARS}.
24
- * @param separator String placed between items by {@link toString}. Must match
25
- * how the caller would otherwise have joined the accumulated chunks (e.g.
26
- * `"\n"` for line streams, `""` for raw chunk streams).
27
- */
28
- constructor(maxChars = MAX_TAIL_CHARS, separator = "") {
29
- this.maxChars = maxChars;
30
- this.separator = separator;
31
- }
32
- /** Append one item to the tail, evicting oldest items to stay within budget. */
33
- push(item) {
34
- const bounded = item.length > this.maxChars ? item.slice(item.length - this.maxChars) : item;
35
- this.totalChars += bounded.length + (this.items.length > 0 ? this.separator.length : 0);
36
- this.items.push(bounded);
37
- while (this.totalChars > this.maxChars && this.items.length > 1) {
38
- const dropped = this.items.shift();
39
- this.totalChars -= dropped.length + this.separator.length;
40
- }
41
- }
42
- /** Join the retained tail into a single string (length ≤ `maxChars`). */
43
- toString() {
44
- return this.items.join(this.separator);
45
- }
46
- };
47
-
48
- export { BoundedTail, MAX_TAIL_CHARS, OutputByteCounter };
49
- //# sourceMappingURL=chunk-WA7QPAXO.js.map
50
- //# sourceMappingURL=chunk-WA7QPAXO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/boundedTail.ts"],"names":[],"mappings":";AAmBO,IAAM,iBAAiB,EAAA,GAAK;AAQ5B,IAAM,oBAAN,MAAwB;AAAA,EAI7B,YAA6B,QAAA,EAAkB;AAAlB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA,EAAmB;AAAA,EAHxC,UAAA,GAAa,CAAA;AAAA,EACb,aAAA,GAAgB,KAAA;AAAA,EAIxB,IAAI,KAAA,EAAkC;AACpC,IAAA,IAAA,CAAK,UAAA,IACH,OAAO,KAAA,KAAU,QAAA,GAAW,OAAO,UAAA,CAAW,KAAK,IAAI,KAAA,CAAM,UAAA;AAC/D,IAAA,IAAI,IAAA,CAAK,UAAA,GAAa,IAAA,CAAK,QAAA,OAAe,aAAA,GAAgB,IAAA;AAAA,EAC5D;AAAA,EAEA,IAAI,QAAA,GAAoB;AACtB,IAAA,OAAO,IAAA,CAAK,aAAA;AAAA,EACd;AACF;AAaO,IAAM,cAAN,MAAkB;AAAA,EACN,QAAkB,EAAC;AAAA,EAC5B,UAAA,GAAa,CAAA;AAAA,EACJ,QAAA;AAAA,EACA,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjB,WAAA,CAAY,QAAA,GAAmB,cAAA,EAAgB,SAAA,GAAY,EAAA,EAAI;AAC7D,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AAAA;AAAA,EAGA,KAAK,IAAA,EAAoB;AACvB,IAAA,MAAM,OAAA,GACJ,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,QAAA,GACf,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,QAAQ,CAAA,GACtC,IAAA;AACN,IAAA,IAAA,CAAK,UAAA,IACH,QAAQ,MAAA,IAAU,IAAA,CAAK,MAAM,MAAA,GAAS,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,MAAA,GAAS,CAAA,CAAA;AACpE,IAAA,IAAA,CAAK,KAAA,CAAM,KAAK,OAAO,CAAA;AACvB,IAAA,OAAO,KAAK,UAAA,GAAa,IAAA,CAAK,YAAY,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA,EAAG;AAC/D,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,KAAA,EAAM;AACjC,MAAA,IAAA,CAAK,UAAA,IAAc,OAAA,CAAQ,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,MAAA;AAAA,IACrD;AAAA,EACF;AAAA;AAAA,EAGA,QAAA,GAAmB;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA;AAAA,EACvC;AACF","file":"chunk-WA7QPAXO.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"]}