@snappedly-tools/shipyard 0.2.0 → 0.3.1
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 +14 -11
- package/dist/{SandboxProvider-KeFp_nju.d.ts → SandboxProvider-XJQqEdSf.d.ts} +4 -0
- package/dist/chunk-2GT3OPO5.js +2620 -0
- package/dist/chunk-2GT3OPO5.js.map +1 -0
- package/dist/{chunk-CP6YEQKU.js → chunk-KMGNFXKN.js} +2 -6
- package/dist/chunk-KMGNFXKN.js.map +1 -0
- package/dist/{chunk-OAHPDSOJ.js → chunk-OCJKONZ7.js} +90 -22
- package/dist/chunk-OCJKONZ7.js.map +1 -0
- package/dist/chunk-VDHZO5PP.js +113 -0
- package/dist/chunk-VDHZO5PP.js.map +1 -0
- package/dist/{chunk-YVJHSJUW.js → chunk-VW4MI5GJ.js} +5 -6
- package/dist/chunk-VW4MI5GJ.js.map +1 -0
- package/dist/chunk-WA7QPAXO.js +50 -0
- package/dist/chunk-WA7QPAXO.js.map +1 -0
- package/dist/index.d.ts +28 -5
- package/dist/index.js +471 -297
- package/dist/index.js.map +1 -1
- package/dist/main.js +242 -19347
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +1 -1
- package/dist/sandboxes/docker.js +4 -7
- package/dist/sandboxes/no-sandbox.d.ts +1 -1
- package/dist/sandboxes/no-sandbox.js +2 -5
- package/dist/sandboxes/vercel.d.ts +1 -1
- package/dist/sandboxes/vercel.js +2 -4
- package/dist/sandboxes/vercel.js.map +1 -1
- package/package.json +15 -12
- package/dist/chunk-4VDXZY7T.js +0 -27624
- package/dist/chunk-4VDXZY7T.js.map +0 -1
- package/dist/chunk-BU6XJFJ2.js +0 -90
- package/dist/chunk-BU6XJFJ2.js.map +0 -1
- package/dist/chunk-CP6YEQKU.js.map +0 -1
- package/dist/chunk-OAHPDSOJ.js.map +0 -1
- package/dist/chunk-SRUH232X.js +0 -26712
- package/dist/chunk-SRUH232X.js.map +0 -1
- package/dist/chunk-YVJHSJUW.js.map +0 -1
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
|
-
|
|
3
|
-
createRequire(import.meta.url);
|
|
4
|
-
|
|
5
1
|
// src/SandboxProvider.ts
|
|
6
2
|
var createBindMountSandboxProvider = (config) => ({
|
|
7
3
|
tag: "bind-mount",
|
|
@@ -38,5 +34,5 @@ var shellQuote = (value) => {
|
|
|
38
34
|
};
|
|
39
35
|
|
|
40
36
|
export { createBindMountSandboxProvider, createIsolatedSandboxProvider, shellQuote, toSessionTransferHandle };
|
|
41
|
-
//# sourceMappingURL=chunk-
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
37
|
+
//# sourceMappingURL=chunk-KMGNFXKN.js.map
|
|
38
|
+
//# sourceMappingURL=chunk-KMGNFXKN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/SandboxProvider.ts","../src/shellQuote.ts"],"names":[],"mappings":";AAsUO,IAAM,8BAAA,GAAiC,CAC5C,MAAA,MAC8B;AAAA,EAC9B,GAAA,EAAK,YAAA;AAAA,EACL,MAAM,MAAA,CAAO,IAAA;AAAA,EACb,GAAA,EAAK,MAAA,CAAO,GAAA,IAAO,EAAC;AAAA,EACpB,gBAAgB,MAAA,CAAO,cAAA;AAAA,EACvB,QAAQ,MAAA,CAAO;AACjB,CAAA;AAMO,IAAM,6BAAA,GAAgC,CAC3C,MAAA,MAC6B;AAAA,EAC7B,GAAA,EAAK,UAAA;AAAA,EACL,MAAM,MAAA,CAAO,IAAA;AAAA,EACb,GAAA,EAAK,MAAA,CAAO,GAAA,IAAO,EAAC;AAAA,EACpB,QAAQ,MAAA,CAAO;AACjB,CAAA;AAGO,IAAM,uBAAA,GAA0B,CACrC,MAAA,KAKuC;AACvC,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,EAAE,aAAA,IAAiB,SAAS,OAAO,MAAA;AAC/D,EAAA,IAAI,YAAA,IAAgB,QAAQ,OAAO,MAAA;AACnC,EAAA,OAAO;AAAA,IACL,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,MAAM,CAAC,OAAA,EAAS,YAAY,MAAA,CAAO,IAAA,CAAK,SAAS,OAAO,CAAA;AAAA,IACxD,eAAA,EAAiB,MAAA,CAAO,eAAA,EAAiB,IAAA,CAAK,MAAM,CAAA;AAAA,IACpD,YAAY,CAAC,MAAA,EAAQ,gBAAgB,MAAA,CAAO,MAAA,CAAO,QAAQ,WAAW,CAAA;AAAA,IACtE,aAAa,CAAC,MAAA,EAAQ,gBACpB,MAAA,CAAO,WAAA,CAAY,QAAQ,WAAW,CAAA;AAAA,IACxC,KAAA,EAAO,MAAM,MAAA,CAAO,KAAA;AAAM,GAC5B;AACF;;;AC/WO,IAAM,UAAA,GAAa,CAAC,KAAA,KAA0B;AACnD,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,EAAG;AACxB,IAAA,MAAM,IAAI,MAAM,6CAA6C,CAAA;AAAA,EAC/D;AACA,EAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,UAAA,CAAW,GAAA,EAAK,OAAO,CAAC,CAAA,CAAA,CAAA;AAC3C","file":"chunk-KMGNFXKN.js","sourcesContent":["/**\n * Sandbox provider types — the pluggable interface for sandbox runtimes.\n *\n * Provider authors implement a small Promise-based interface. Shipyard\n * handles worktree creation, git mount resolution, and commit extraction.\n */\n\n/** Result of executing a command inside a sandbox. */\nexport interface ExecResult {\n readonly stdout: string;\n readonly stderr: string;\n readonly exitCode: number;\n}\n\n/** Options for interactiveExec — the streams the provider should wire to the spawned process. */\nexport interface InteractiveExecOptions {\n readonly stdin: NodeJS.ReadableStream;\n readonly stdout: NodeJS.WritableStream;\n readonly stderr: NodeJS.WritableStream;\n readonly cwd?: string;\n /** Terminate the interactive process when the caller cancels the session. */\n readonly signal?: AbortSignal;\n}\n\n/** Handle to a running bind-mount sandbox. */\nexport interface BindMountSandboxHandle {\n /** Absolute path to the worktree inside the sandbox. */\n readonly worktreePath: string;\n /**\n * Execute a command in the sandbox.\n *\n * Implementations MUST support line-by-line streaming via `onLine`. This is\n * how Shipyard delivers live feedback to the user and enforces idle timeouts —\n * without a streaming implementation, neither will work. A buffered/batch\n * implementation that only calls `onLine` after the process exits does NOT\n * satisfy this contract.\n *\n * When `stdin` is set, the implementation pipes the string to the child\n * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.\n */\n exec(\n command: string,\n options?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n /** Abort the command when the caller's operation is cancelled. */\n signal?: AbortSignal;\n /** Reject/terminate the command after this many combined output bytes. */\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult>;\n /**\n * Launch an interactive process inside the sandbox.\n * Optional — providers that support interactive sessions implement this.\n * The provider detects TTY mode from the streams (e.g. stdin.isTTY) and\n * allocates a pseudo-terminal accordingly.\n * Implementations MUST terminate the underlying process when `signal` aborts.\n */\n interactiveExec?(\n args: string[],\n options: InteractiveExecOptions,\n ): Promise<{ exitCode: number }>;\n /** Copy a single file from the host into the sandbox. */\n copyFileIn(hostPath: string, sandboxPath: string): Promise<void>;\n /** Copy a single file from the sandbox to the host. */\n copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;\n /** Tear down the sandbox. */\n close(): Promise<void>;\n}\n\n/** Options passed to a bind-mount provider's `create` function. */\nexport interface BindMountCreateOptions {\n /** Host-side path to the worktree directory. */\n readonly worktreePath: string;\n /** Host-side path to the original repo root. */\n readonly hostRepoPath: string;\n /** Volume mounts to apply (host:sandbox pairs). */\n readonly mounts: Array<{\n hostPath: string;\n sandboxPath: string;\n readonly?: boolean;\n }>;\n /** Environment variables to inject into the sandbox. */\n readonly env: Record<string, string>;\n}\n\n/** Configuration for createBindMountSandboxProvider. */\nexport interface BindMountSandboxProviderConfig {\n /** Human-readable name for this provider (e.g. \"docker\"). */\n readonly name: string;\n /** Environment variables injected by this provider. Merged at launch time. */\n readonly env?: Record<string, string>;\n /**\n * Absolute path to the home directory inside the sandbox (e.g. `\"/home/agent\"`).\n * Used to expand `~` in user-provided `sandboxPath` mount configs.\n * Set to `undefined` for providers that do not have a fixed home directory.\n */\n readonly sandboxHomedir?: string;\n /** Create a sandbox handle from the given options. */\n readonly create: (\n options: BindMountCreateOptions,\n ) => Promise<BindMountSandboxHandle>;\n}\n\n/** Handle to a running isolated sandbox (extends bind-mount with file transfer). */\nexport interface IsolatedSandboxHandle {\n /** Absolute path to the worktree inside the sandbox. */\n readonly worktreePath: string;\n /**\n * Execute a command in the sandbox.\n *\n * Implementations MUST support line-by-line streaming via `onLine`. This is\n * how Shipyard delivers live feedback to the user and enforces idle timeouts —\n * without a streaming implementation, neither will work. A buffered/batch\n * implementation that only calls `onLine` after the process exits does NOT\n * satisfy this contract.\n *\n * When `stdin` is set, the implementation pipes the string to the child\n * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.\n */\n exec(\n command: string,\n options?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n /** Abort the command when the caller's operation is cancelled. */\n signal?: AbortSignal;\n /** Reject/terminate the command after this many combined output bytes. */\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult>;\n /**\n * Launch an interactive process inside the sandbox.\n * Optional — providers that support interactive sessions implement this.\n * The provider detects TTY mode from the streams (e.g. stdin.isTTY) and\n * allocates a pseudo-terminal accordingly.\n * Implementations MUST terminate the underlying process when `signal` aborts.\n */\n interactiveExec?(\n args: string[],\n options: InteractiveExecOptions,\n ): Promise<{ exitCode: number }>;\n /** Copy a file or directory from the host into the sandbox. */\n copyIn(hostPath: string, sandboxPath: string): Promise<void>;\n /** Copy a single file from the sandbox to the host. */\n copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;\n /** Tear down the sandbox. */\n close(): Promise<void>;\n}\n\n/** Options passed to an isolated provider's `create` function. */\nexport interface IsolatedCreateOptions {\n /** Original host repository path, used only for image naming; never mounted. */\n readonly hostRepoPath?: string;\n /** Environment variables to inject into the sandbox. */\n readonly env: Record<string, string>;\n}\n\n/** Configuration for createIsolatedSandboxProvider. */\nexport interface IsolatedSandboxProviderConfig {\n /** Human-readable name for this provider (e.g. \"vercel\"). */\n readonly name: string;\n /** Environment variables injected by this provider. Merged at launch time. */\n readonly env?: Record<string, string>;\n /** Create an isolated sandbox handle from the given options. */\n readonly create: (\n options: IsolatedCreateOptions,\n ) => Promise<IsolatedSandboxHandle>;\n}\n\n/** A bind-mount sandbox provider. */\nexport interface BindMountSandboxProvider {\n /** @internal Discriminator for internal dispatch. */\n readonly tag: \"bind-mount\";\n /** Human-readable provider name. */\n readonly name: string;\n /** Environment variables injected by this provider. */\n readonly env: Record<string, string>;\n /**\n * Absolute path to the home directory inside the sandbox (e.g. `\"/home/agent\"`).\n * `undefined` when the provider does not declare a sandbox home directory.\n */\n readonly sandboxHomedir: string | undefined;\n /** @internal Create a sandbox handle. */\n readonly create: (\n options: BindMountCreateOptions,\n ) => Promise<BindMountSandboxHandle>;\n}\n\n/** An isolated sandbox provider. */\nexport interface IsolatedSandboxProvider {\n /** @internal Discriminator for internal dispatch. */\n readonly tag: \"isolated\";\n /** Human-readable provider name. */\n readonly name: string;\n /** Environment variables injected by this provider. */\n readonly env: Record<string, string>;\n /** @internal Create an isolated sandbox handle. */\n readonly create: (\n options: IsolatedCreateOptions,\n ) => Promise<IsolatedSandboxHandle>;\n}\n\n/** Handle to a no-sandbox session — runs commands directly on the host. */\nexport interface NoSandboxHandle {\n /** Absolute path to the worktree on the host. */\n readonly worktreePath: string;\n /**\n * Execute a command on the host.\n *\n * Implementations MUST support line-by-line streaming via `onLine`. This is\n * how Shipyard delivers live feedback to the user and enforces idle timeouts —\n * without a streaming implementation, neither will work.\n *\n * When `stdin` is set, the implementation pipes the string to the child\n * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.\n */\n exec(\n command: string,\n options?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n /** Abort the command when the caller's operation is cancelled. */\n signal?: AbortSignal;\n /** Reject/terminate the command after this many combined output bytes. */\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult>;\n /**\n * Launch an interactive process on the host with inherited stdio.\n */\n interactiveExec(\n args: string[],\n options: InteractiveExecOptions,\n ): Promise<{ exitCode: number }>;\n /** No-op — no container to tear down. */\n close(): Promise<void>;\n}\n\n/** A no-sandbox provider — runs the agent directly on the host with no container isolation. */\nexport interface NoSandboxProvider {\n /** @internal Discriminator for internal dispatch. */\n readonly tag: \"none\";\n /** Human-readable provider name. */\n readonly name: string;\n /** Environment variables injected by this provider. */\n readonly env: Record<string, string>;\n /** @internal Create a no-sandbox handle. */\n readonly create: (options: {\n readonly worktreePath: string;\n readonly env: Record<string, string>;\n }) => Promise<NoSandboxHandle>;\n}\n\n// ---------- Branch strategy types ----------\n\n/** Head strategy: agent writes directly to host working directory. Bind-mount only. */\nexport interface HeadBranchStrategy {\n readonly type: \"head\";\n}\n\n/** Merge-to-head strategy: temp branch, merge back to HEAD, delete temp branch. */\nexport interface MergeToHeadBranchStrategy {\n readonly type: \"merge-to-head\";\n}\n\n/** Branch strategy: commits land on an explicit named branch. */\nexport interface NamedBranchStrategy {\n readonly type: \"branch\";\n readonly branch: string;\n /**\n * Git ref to use as the starting point when creating a new branch.\n * Only used when the branch doesn't already exist — ignored otherwise.\n * Callers are responsible for ensuring the ref is current (e.g. `git fetch`).\n * Defaults to `HEAD` when omitted.\n */\n readonly baseBranch?: string;\n}\n\n/** Branch strategy for bind-mount providers (all three variants). */\nexport type BindMountBranchStrategy =\n | HeadBranchStrategy\n | MergeToHeadBranchStrategy\n | NamedBranchStrategy;\n\n/** Branch strategy for isolated providers (no head — can't write to host). */\nexport type IsolatedBranchStrategy =\n | MergeToHeadBranchStrategy\n | NamedBranchStrategy;\n\n/** Branch strategy for no-sandbox providers (all three — same as bind-mount). */\nexport type NoSandboxBranchStrategy =\n | HeadBranchStrategy\n | MergeToHeadBranchStrategy\n | NamedBranchStrategy;\n\n/** Union of all branch strategy variants. */\nexport type BranchStrategy =\n | BindMountBranchStrategy\n | IsolatedBranchStrategy\n | NoSandboxBranchStrategy;\n\n/**\n * A sandbox provider — the pluggable unit that `run()`, `interactive()`, and\n * `createSandbox()` accept. Tagged for internal dispatch: \"bind-mount\",\n * \"isolated\", or \"none\". When `NoSandboxProvider` is used, the agent runs\n * directly on the host with no container isolation — opt in at your own risk.\n */\nexport type SandboxProvider =\n | BindMountSandboxProvider\n | IsolatedSandboxProvider\n | NoSandboxProvider;\n\n/** @deprecated Use `SandboxProvider` — it now includes `NoSandboxProvider`. */\nexport type AnySandboxProvider = SandboxProvider;\n\n/**\n * Create a bind-mount sandbox provider from a config object.\n * The returned provider can be passed to `run()` or `createSandbox()`.\n */\nexport const createBindMountSandboxProvider = (\n config: BindMountSandboxProviderConfig,\n): BindMountSandboxProvider => ({\n tag: \"bind-mount\",\n name: config.name,\n env: config.env ?? {},\n sandboxHomedir: config.sandboxHomedir,\n create: config.create,\n});\n\n/**\n * Create an isolated sandbox provider from a config object.\n * The returned provider can be passed to `run()` or `createSandbox()`.\n */\nexport const createIsolatedSandboxProvider = (\n config: IsolatedSandboxProviderConfig,\n): IsolatedSandboxProvider => ({\n tag: \"isolated\",\n name: config.name,\n env: config.env ?? {},\n create: config.create,\n});\n\n/** @internal Adapt isolated file transfer to the existing agent-session boundary. */\nexport const toSessionTransferHandle = (\n handle:\n | BindMountSandboxHandle\n | IsolatedSandboxHandle\n | NoSandboxHandle\n | undefined,\n): BindMountSandboxHandle | undefined => {\n if (handle === undefined || !(\"copyFileOut\" in handle)) return undefined;\n if (\"copyFileIn\" in handle) return handle;\n return {\n worktreePath: handle.worktreePath,\n exec: (command, options) => handle.exec(command, options),\n interactiveExec: handle.interactiveExec?.bind(handle),\n copyFileIn: (source, destination) => handle.copyIn(source, destination),\n copyFileOut: (source, destination) =>\n handle.copyFileOut(source, destination),\n close: () => handle.close(),\n };\n};\n","/** Quote a value for use as one POSIX-shell argument. */\nexport const shellQuote = (value: string): string => {\n if (value.includes(\"\\0\")) {\n throw new Error(\"Cannot quote a string containing a NUL byte\");\n }\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\n};\n"]}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { MAX_TAIL_CHARS, OutputByteCounter, BoundedTail } from './chunk-BU6XJFJ2.js';
|
|
1
|
+
import { resolveUserMounts, assertIsolatedWorkspaceMounts, processFileMountParents, defaultImageName, registerShutdown, DockerError, formatVolumeMount } from './chunk-2GT3OPO5.js';
|
|
2
|
+
import { createIsolatedSandboxProvider } from './chunk-KMGNFXKN.js';
|
|
3
|
+
import { MAX_TAIL_CHARS, OutputByteCounter, BoundedTail } from './chunk-WA7QPAXO.js';
|
|
5
4
|
import { execFile, execFileSync, spawn } from 'child_process';
|
|
6
5
|
import { resolve, posix } from 'path';
|
|
7
6
|
import { promisify } from 'util';
|
|
8
7
|
import { randomUUID } from 'crypto';
|
|
9
8
|
import { createInterface } from 'readline';
|
|
9
|
+
import { Effect } from 'effect';
|
|
10
10
|
|
|
11
|
-
createRequire(import.meta.url);
|
|
12
11
|
var exec = promisify(execFile);
|
|
13
12
|
var copyIntoContainer = async (engine, container, owner, hostPath, sandboxPath) => {
|
|
14
13
|
try {
|
|
@@ -38,7 +37,7 @@ var copyIntoContainer = async (engine, container, owner, hostPath, sandboxPath)
|
|
|
38
37
|
);
|
|
39
38
|
}
|
|
40
39
|
};
|
|
41
|
-
var dockerExec = (args) =>
|
|
40
|
+
var dockerExec = (args) => Effect.async((resume) => {
|
|
42
41
|
execFile(
|
|
43
42
|
"docker",
|
|
44
43
|
args,
|
|
@@ -46,19 +45,19 @@ var dockerExec = (args) => Effect_exports.async((resume) => {
|
|
|
46
45
|
(error, stdout, stderr) => {
|
|
47
46
|
if (error) {
|
|
48
47
|
resume(
|
|
49
|
-
|
|
48
|
+
Effect.fail(
|
|
50
49
|
new DockerError({
|
|
51
50
|
message: `docker ${args[0]} failed: ${stderr?.toString() || error.message}`
|
|
52
51
|
})
|
|
53
52
|
)
|
|
54
53
|
);
|
|
55
54
|
} else {
|
|
56
|
-
resume(
|
|
55
|
+
resume(Effect.succeed(stdout.toString()));
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
58
|
);
|
|
60
59
|
});
|
|
61
|
-
var buildImage = (imageName, dockerfileDir, options) =>
|
|
60
|
+
var buildImage = (imageName, dockerfileDir, options) => Effect.gen(function* () {
|
|
62
61
|
const buildArgFlags = Object.entries(options?.buildArgs ?? {}).flatMap(
|
|
63
62
|
([k, v]) => ["--build-arg", `${k}=${v}`]
|
|
64
63
|
);
|
|
@@ -82,7 +81,7 @@ var buildImage = (imageName, dockerfileDir, options) => Effect_exports.gen(funct
|
|
|
82
81
|
]);
|
|
83
82
|
}
|
|
84
83
|
});
|
|
85
|
-
var startContainer = (containerName, imageName, env, options) =>
|
|
84
|
+
var startContainer = (containerName, imageName, env, options) => Effect.gen(function* () {
|
|
86
85
|
const existing = yield* dockerExec([
|
|
87
86
|
"ps",
|
|
88
87
|
"-a",
|
|
@@ -92,7 +91,7 @@ var startContainer = (containerName, imageName, env, options) => Effect_exports.
|
|
|
92
91
|
"{{.Names}}"
|
|
93
92
|
]);
|
|
94
93
|
if (existing.trim() === containerName) {
|
|
95
|
-
yield*
|
|
94
|
+
yield* Effect.fail(
|
|
96
95
|
new DockerError({
|
|
97
96
|
message: `Container '${containerName}' already exists. Run cleanup first.`
|
|
98
97
|
})
|
|
@@ -123,6 +122,7 @@ var startContainer = (containerName, imageName, env, options) => Effect_exports.
|
|
|
123
122
|
yield* dockerExec([
|
|
124
123
|
"run",
|
|
125
124
|
"-d",
|
|
125
|
+
"--init",
|
|
126
126
|
"--name",
|
|
127
127
|
containerName,
|
|
128
128
|
...envFlags,
|
|
@@ -136,11 +136,11 @@ var startContainer = (containerName, imageName, env, options) => Effect_exports.
|
|
|
136
136
|
imageName
|
|
137
137
|
]);
|
|
138
138
|
});
|
|
139
|
-
var removeContainer = (containerName) =>
|
|
140
|
-
yield*
|
|
141
|
-
yield*
|
|
139
|
+
var removeContainer = (containerName) => Effect.gen(function* () {
|
|
140
|
+
yield* Effect.ignore(dockerExec(["stop", containerName]));
|
|
141
|
+
yield* Effect.ignore(dockerExec(["rm", containerName]));
|
|
142
142
|
});
|
|
143
|
-
var removeImage = (imageName) =>
|
|
143
|
+
var removeImage = (imageName) => Effect.gen(function* () {
|
|
144
144
|
yield* dockerExec(["rmi", imageName]);
|
|
145
145
|
});
|
|
146
146
|
|
|
@@ -172,7 +172,7 @@ var docker = (options) => {
|
|
|
172
172
|
const containerUid = options?.containerUid ?? process.getuid?.() ?? 1e3;
|
|
173
173
|
const containerGid = options?.containerGid ?? process.getgid?.() ?? 1e3;
|
|
174
174
|
await checkImageUid(imageName, containerUid);
|
|
175
|
-
await
|
|
175
|
+
await Effect.runPromise(
|
|
176
176
|
startContainer(
|
|
177
177
|
containerName,
|
|
178
178
|
imageName,
|
|
@@ -317,6 +317,10 @@ var docker = (options) => {
|
|
|
317
317
|
},
|
|
318
318
|
interactiveExec: (args, opts) => {
|
|
319
319
|
return new Promise((resolve2, reject) => {
|
|
320
|
+
opts.signal?.throwIfAborted();
|
|
321
|
+
const executionId = randomUUID();
|
|
322
|
+
const pidFile = `/tmp/shipyard-interactive-${executionId}.pid`;
|
|
323
|
+
const cancelFile = `/tmp/shipyard-interactive-${executionId}.cancel`;
|
|
320
324
|
const dockerArgs = ["exec"];
|
|
321
325
|
if ("isTTY" in opts.stdin && opts.stdin.isTTY) {
|
|
322
326
|
dockerArgs.push("-it");
|
|
@@ -324,15 +328,79 @@ var docker = (options) => {
|
|
|
324
328
|
dockerArgs.push("-i");
|
|
325
329
|
}
|
|
326
330
|
if (opts.cwd) dockerArgs.push("-w", opts.cwd);
|
|
327
|
-
dockerArgs.push(
|
|
331
|
+
dockerArgs.push(
|
|
332
|
+
containerName,
|
|
333
|
+
"sh",
|
|
334
|
+
"-c",
|
|
335
|
+
'pid_file=$1; cancel_file=$2; shift 2; command -v setsid >/dev/null 2>&1 && command -v ps >/dev/null 2>&1 && command -v awk >/dev/null 2>&1 || { printf "%s\\n" "setsid, ps, and awk are required for cancellable interactive execution" >&2; exit 127; }; if [ -e "$cancel_file" ]; then rm -f "$pid_file" "$cancel_file"; exit 130; fi; setsid "$@" & child=$!; printf "%s\\n" "$child" > "$pid_file"; wait "$child"; status=$?; rm -f "$pid_file" "$cancel_file"; exit "$status"',
|
|
336
|
+
"shipyard-interactive",
|
|
337
|
+
pidFile,
|
|
338
|
+
cancelFile,
|
|
339
|
+
...args
|
|
340
|
+
);
|
|
328
341
|
const proc = spawn("docker", dockerArgs, {
|
|
329
342
|
stdio: [opts.stdin, opts.stdout, opts.stderr]
|
|
330
343
|
});
|
|
344
|
+
let settled = false;
|
|
345
|
+
let cancelling = false;
|
|
346
|
+
const finish = (result) => {
|
|
347
|
+
if (settled) return;
|
|
348
|
+
settled = true;
|
|
349
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
350
|
+
if ("error" in result) reject(result.error);
|
|
351
|
+
else resolve2(result);
|
|
352
|
+
};
|
|
353
|
+
const onAbort = () => {
|
|
354
|
+
if (cancelling || settled) return;
|
|
355
|
+
cancelling = true;
|
|
356
|
+
const cancellationScript = `touch "$1"; attempts=0; while [ ! -s "$2" ] && [ "$attempts" -lt 50 ]; do sleep 0.1; attempts=$((attempts + 1)); done; if [ -s "$2" ]; then pid=$(cat "$2"); group_running() { ps -eo pgid=,stat= | awk -v pg="$pid" '$1 == pg && $2 !~ /^Z/ { found=1 } END { exit found ? 0 : 1 }'; }; kill -TERM "-$pid" 2>/dev/null || true; attempts=0; while group_running && [ "$attempts" -lt 20 ]; do sleep 0.1; attempts=$((attempts + 1)); done; kill -KILL "-$pid" 2>/dev/null || true; attempts=0; while group_running && [ "$attempts" -lt 20 ]; do sleep 0.1; attempts=$((attempts + 1)); done; if group_running; then exit 1; fi; rm -f "$1" "$2"; fi`;
|
|
357
|
+
execFile(
|
|
358
|
+
"docker",
|
|
359
|
+
[
|
|
360
|
+
"exec",
|
|
361
|
+
containerName,
|
|
362
|
+
"sh",
|
|
363
|
+
"-c",
|
|
364
|
+
cancellationScript,
|
|
365
|
+
"shipyard-cancel",
|
|
366
|
+
cancelFile,
|
|
367
|
+
pidFile
|
|
368
|
+
],
|
|
369
|
+
{ timeout: 7e3 },
|
|
370
|
+
(error) => {
|
|
371
|
+
proc.kill("SIGKILL");
|
|
372
|
+
if (error) {
|
|
373
|
+
finish({
|
|
374
|
+
error: new Error(
|
|
375
|
+
`Failed to terminate interactive process in Docker sandbox: ${error.message}`
|
|
376
|
+
)
|
|
377
|
+
});
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
finish({
|
|
381
|
+
error: opts.signal?.reason ?? new Error("Interactive execution aborted")
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
};
|
|
386
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
387
|
+
if (opts.signal?.aborted) onAbort();
|
|
331
388
|
proc.on("error", (error) => {
|
|
332
|
-
|
|
389
|
+
if (!cancelling) {
|
|
390
|
+
finish({
|
|
391
|
+
error: new Error(`docker exec failed: ${error.message}`)
|
|
392
|
+
});
|
|
393
|
+
}
|
|
333
394
|
});
|
|
334
395
|
proc.on("close", (code) => {
|
|
335
|
-
|
|
396
|
+
if (cancelling) return;
|
|
397
|
+
execFile(
|
|
398
|
+
"docker",
|
|
399
|
+
["exec", containerName, "rm", "-f", pidFile, cancelFile],
|
|
400
|
+
() => {
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
finish({ exitCode: code ?? 0 });
|
|
336
404
|
});
|
|
337
405
|
});
|
|
338
406
|
},
|
|
@@ -358,7 +426,7 @@ var docker = (options) => {
|
|
|
358
426
|
}),
|
|
359
427
|
close: async () => {
|
|
360
428
|
unregisterShutdown();
|
|
361
|
-
await
|
|
429
|
+
await Effect.runPromise(removeContainer(containerName));
|
|
362
430
|
}
|
|
363
431
|
};
|
|
364
432
|
return handle;
|
|
@@ -403,5 +471,5 @@ var checkImageUid = (imageName, expectedUid) => new Promise((resolve2, reject) =
|
|
|
403
471
|
});
|
|
404
472
|
|
|
405
473
|
export { buildImage, docker, removeImage };
|
|
406
|
-
//# sourceMappingURL=chunk-
|
|
407
|
-
//# sourceMappingURL=chunk-
|
|
474
|
+
//# sourceMappingURL=chunk-OCJKONZ7.js.map
|
|
475
|
+
//# sourceMappingURL=chunk-OCJKONZ7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sandboxes/copyIntoContainer.ts","../src/DockerLifecycle.ts","../src/sandboxes/docker.ts"],"names":["execFile","Effect","resolve"],"mappings":"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","file":"chunk-OCJKONZ7.js","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { posix } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\n/** Container cp creates root-owned files; session files may have mode 0600. */\nexport const copyIntoContainer = async (\n engine: \"docker\",\n container: string,\n owner: string,\n hostPath: string,\n sandboxPath: string,\n): Promise<void> => {\n try {\n await exec(engine, [\n \"exec\",\n container,\n \"mkdir\",\n \"-p\",\n \"--\",\n posix.dirname(sandboxPath),\n ]);\n await exec(engine, [\"cp\", hostPath, `${container}:${sandboxPath}`]);\n await exec(engine, [\n \"exec\",\n \"--user\",\n \"0:0\",\n container,\n \"chown\",\n \"-hR\",\n \"--\",\n owner,\n sandboxPath,\n ]);\n } catch (error) {\n throw new Error(\n `${engine} cp (in) failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n};\n","import { Effect } from \"effect\";\nimport { execFile } from \"node:child_process\";\nimport { resolve } from \"node:path\";\nimport { DockerError } from \"./errors.js\";\nimport { formatVolumeMount, type SelinuxLabel } from \"./mountUtils.js\";\n\nconst dockerExec = (args: string[]): Effect.Effect<string, DockerError> =>\n Effect.async((resume) => {\n execFile(\n \"docker\",\n args,\n { maxBuffer: 10 * 1024 * 1024 },\n (error, stdout, stderr) => {\n if (error) {\n resume(\n Effect.fail(\n new DockerError({\n message: `docker ${args[0]} failed: ${stderr?.toString() || error.message}`,\n }),\n ),\n );\n } else {\n resume(Effect.succeed(stdout.toString()));\n }\n },\n );\n });\n\n/**\n * Build the shipyard Docker image.\n *\n * When `dockerfile` is provided, uses `docker build -f <dockerfile> <cwd>`\n * so COPY instructions resolve relative to the current working directory.\n * Otherwise, uses `docker build <dockerfileDir>` (the default .shipyard/ directory).\n */\nexport const buildImage = (\n imageName: string,\n dockerfileDir: string,\n options?: {\n readonly dockerfile?: string;\n readonly buildArgs?: Record<string, string>;\n },\n): Effect.Effect<void, DockerError> =>\n Effect.gen(function* () {\n const buildArgFlags = Object.entries(options?.buildArgs ?? {}).flatMap(\n ([k, v]) => [\"--build-arg\", `${k}=${v}`],\n );\n if (options?.dockerfile) {\n yield* dockerExec([\n \"build\",\n \"-t\",\n imageName,\n ...buildArgFlags,\n \"-f\",\n resolve(options.dockerfile),\n process.cwd(),\n ]);\n } else {\n yield* dockerExec([\n \"build\",\n \"-t\",\n imageName,\n ...buildArgFlags,\n resolve(dockerfileDir),\n ]);\n }\n });\n\nexport interface VolumeMount {\n readonly hostPath: string;\n readonly sandboxPath: string;\n readonly readonly?: boolean;\n}\n\nexport interface StartContainerOptions {\n readonly volumeMounts?: readonly VolumeMount[];\n readonly workdir?: string;\n /** Run the container as this uid:gid instead of the Dockerfile's USER. */\n readonly user?: string;\n /** Docker network(s) to attach the container to. Passed as `--network` flags. */\n readonly network?: string | readonly string[];\n /** Supplementary groups to add the container user to. Passed as `--group-add` flags. */\n readonly groups?: readonly (string | number)[];\n /** Host devices to expose to the container. Passed as `--device` flags. */\n readonly devices?: readonly string[];\n /** Limit CPU resources via `--cpus` (e.g. `1.5`). Fractional values allowed. */\n readonly cpus?: number;\n /**\n * SELinux volume label suffix applied to bind mounts (default `\"z\"`).\n *\n * - `\"z\"` — shared label. No-op on non-SELinux systems.\n * - `\"Z\"` — private label; only this container can access the mount.\n * - `false` — disable labeling entirely.\n */\n readonly selinuxLabel?: SelinuxLabel;\n}\n\n/**\n * Start a new container with environment variables injected.\n */\nexport const startContainer = (\n containerName: string,\n imageName: string,\n env: Record<string, string>,\n options?: StartContainerOptions,\n): Effect.Effect<void, DockerError> =>\n Effect.gen(function* () {\n // Check if container already exists\n const existing = yield* dockerExec([\n \"ps\",\n \"-a\",\n \"--filter\",\n `name=^${containerName}$`,\n \"--format\",\n \"{{.Names}}\",\n ]);\n\n if (existing.trim() === containerName) {\n yield* Effect.fail(\n new DockerError({\n message: `Container '${containerName}' already exists. Run cleanup first.`,\n }),\n );\n }\n\n const envFlags = Object.entries(env).flatMap(([k, v]) => [\n \"-e\",\n `${k}=${v}`,\n ]);\n\n const selinuxLabel = options?.selinuxLabel ?? \"z\";\n const volumeFlags = (options?.volumeMounts ?? []).flatMap((mount) => [\n \"-v\",\n formatVolumeMount(mount, selinuxLabel),\n ]);\n\n const workdirFlags = options?.workdir ? [\"-w\", options.workdir] : [];\n const userFlags = options?.user ? [\"--user\", options.user] : [];\n const networks = options?.network\n ? Array.isArray(options.network)\n ? options.network\n : [options.network]\n : [];\n const networkFlags = networks.flatMap((n) => [\"--network\", n]);\n const groupAddFlags = (options?.groups ?? []).flatMap((g) => [\n \"--group-add\",\n String(g),\n ]);\n const deviceFlags = (options?.devices ?? []).flatMap((d) => [\n \"--device\",\n d,\n ]);\n const cpusFlags =\n options?.cpus !== undefined ? [\"--cpus\", String(options.cpus)] : [];\n\n yield* dockerExec([\n \"run\",\n \"-d\",\n \"--init\",\n \"--name\",\n containerName,\n ...envFlags,\n ...volumeFlags,\n ...workdirFlags,\n ...userFlags,\n ...networkFlags,\n ...groupAddFlags,\n ...deviceFlags,\n ...cpusFlags,\n imageName,\n ]);\n });\n\n/**\n * Stop and remove a container without removing the image.\n */\nexport const removeContainer = (\n containerName: string,\n): Effect.Effect<void, DockerError> =>\n Effect.gen(function* () {\n // Stop container (ignore errors if already stopped)\n yield* Effect.ignore(dockerExec([\"stop\", containerName]));\n // Remove container (ignore errors if not found)\n yield* Effect.ignore(dockerExec([\"rm\", containerName]));\n });\n\n/**\n * Remove a Docker image.\n */\nexport const removeImage = (\n imageName: string,\n): Effect.Effect<void, DockerError> =>\n Effect.gen(function* () {\n yield* dockerExec([\"rmi\", imageName]);\n });\n","import { copyIntoContainer } from \"./copyIntoContainer.js\";\n/**\n * Docker sandbox provider — wraps DockerLifecycle into a SandboxProvider.\n *\n * Usage:\n * import { docker } from \"@snappedly-tools/shipyard/sandboxes/docker\";\n * await run({ agent: codex(CODEX_MODELS.routine), sandbox: docker() });\n */\n\nimport {\n execFile,\n execFileSync,\n spawn,\n type StdioOptions,\n} from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { createInterface } from \"node:readline\";\nimport { Effect } from \"effect\";\nimport { startContainer, removeContainer } from \"../DockerLifecycle.js\";\nimport {\n createIsolatedSandboxProvider,\n type IsolatedSandboxProvider,\n type IsolatedCreateOptions,\n type IsolatedSandboxHandle,\n type ExecResult,\n type InteractiveExecOptions,\n} from \"../SandboxProvider.js\";\nimport type { MountConfig } from \"../MountConfig.js\";\nimport type { SelinuxLabel } from \"../mountUtils.js\";\nimport {\n defaultImageName,\n resolveUserMounts,\n assertIsolatedWorkspaceMounts,\n processFileMountParents,\n} from \"../mountUtils.js\";\nimport {\n BoundedTail,\n MAX_TAIL_CHARS,\n OutputByteCounter,\n} from \"../boundedTail.js\";\nimport { registerShutdown } from \"../shutdownRegistry.js\";\n\nexport interface DockerOptions {\n /** Docker image name (default: derived from repo directory name). */\n readonly imageName?: string;\n /**\n * The UID of the `agent` user inside the container image (default: host UID via `process.getuid()`, or 1000).\n *\n * Must match the UID baked into the image at build time. Used as the `--user` flag value\n * and checked against the image's configured UID in the pre-flight diagnostic.\n */\n readonly containerUid?: number;\n /**\n * The GID of the `agent` user inside the container image (default: host GID via `process.getgid()`, or 1000).\n *\n * Must match the GID baked into the image at build time. Used as the `--user` flag value.\n */\n readonly containerGid?: number;\n /**\n * SELinux volume label suffix applied to bind mounts.\n *\n * - `\"z\"` — shared label (default). No-op on non-SELinux systems.\n * - `\"Z\"` — private label; only this container can access the mount.\n * - `false` — disable labeling entirely.\n */\n readonly selinuxLabel?: SelinuxLabel;\n /**\n * Additional host directories to bind-mount into the sandbox.\n *\n * Each entry specifies a `hostPath` (tilde-expanded) and `sandboxPath`.\n * If `hostPath` does not exist, sandbox creation fails with a clear error.\n */\n readonly mounts?: readonly MountConfig[];\n /** Environment variables injected by this provider. Merged at launch time with env resolver and agent provider env. */\n readonly env?: Record<string, string>;\n /**\n * Docker network(s) to attach the container to.\n *\n * - `\"my-network\"` → `--network my-network`\n * - `[\"net1\", \"net2\"]` → `--network net1 --network net2`\n *\n * When omitted, Docker's default bridge network is used.\n */\n readonly network?: string | readonly string[];\n /**\n * Supplementary groups to add the container user to, via `--group-add`.\n *\n * Accepts group names or numeric GIDs:\n *\n * - `[\"docker\"]` → `--group-add docker`\n * - `[999]` → `--group-add 999`\n * - `[\"docker\", 999]` → `--group-add docker --group-add 999`\n *\n * Useful for granting access to a bind-mounted Docker socket (Docker-outside-of-Docker).\n * When omitted, no `--group-add` flags are added.\n */\n readonly groups?: readonly (string | number)[];\n /**\n * Host devices to expose to the container, via `--device`.\n *\n * Each entry is a full device spec in `host[:container[:permissions]]` form:\n *\n * - `[\"/dev/kvm\"]` → `--device /dev/kvm`\n * - `[\"/dev/sda:/dev/xvda:rwm\"]` → `--device /dev/sda:/dev/xvda:rwm`\n * - `[\"/dev/kvm\", \"/dev/fuse\"]` → `--device /dev/kvm --device /dev/fuse`\n *\n * When omitted, no `--device` flags are added.\n */\n readonly devices?: readonly 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 * Limit the CPU resources available to the container, via `--cpus`.\n *\n * Maps directly to `docker run --cpus`. Accepts fractional values:\n *\n * - `2` → `--cpus 2` (at most 2 CPUs)\n * - `1.5` → `--cpus 1.5` (at most 1.5 CPUs)\n *\n * When omitted, no `--cpus` flag is added and the container is unconstrained.\n */\n readonly cpus?: number;\n}\n\n/**\n * Create a Docker sandbox provider.\n *\n * The returned provider creates Docker containers with isolated repository and Git storage.\n * Changes are transferred through git bundle and validated sync-out.\n */\nexport const docker = (options?: DockerOptions): IsolatedSandboxProvider => {\n const configuredImageName = options?.imageName;\n const selinuxLabel = options?.selinuxLabel ?? \"z\";\n const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;\n const sandboxHomedir = \"/home/agent\";\n const userMounts = options?.mounts\n ? resolveUserMounts(options.mounts, sandboxHomedir)\n : [];\n assertIsolatedWorkspaceMounts(userMounts);\n // Validate file mounts and collect parent dirs to create at container start.\n // Throws at construction time if any file mount parent is outside sandboxHomedir.\n const parentDirsToCreate = processFileMountParents(\n userMounts,\n sandboxHomedir,\n );\n\n return createIsolatedSandboxProvider({\n name: \"docker\",\n env: options?.env,\n create: async (\n createOptions: IsolatedCreateOptions,\n ): Promise<IsolatedSandboxHandle> => {\n const containerName = `shipyard-${randomUUID()}`;\n\n const worktreePath = \"/home/agent/workspace\";\n\n // Repository and Git storage are container-owned. Only explicitly\n // configured mounts cross the host boundary.\n const allMounts = userMounts;\n const volumeMounts = allMounts.map((m) => ({\n hostPath: m.hostPath,\n sandboxPath: m.sandboxPath,\n readonly: m.readonly,\n }));\n\n // Resolve image name\n const imageName =\n configuredImageName ??\n defaultImageName(createOptions.hostRepoPath ?? process.cwd());\n\n const containerUid = options?.containerUid ?? process.getuid?.() ?? 1000;\n const containerGid = options?.containerGid ?? process.getgid?.() ?? 1000;\n\n // Pre-flight: verify image exists and UID matches\n await checkImageUid(imageName, containerUid);\n\n // Start container\n await Effect.runPromise(\n startContainer(\n containerName,\n imageName,\n {\n ...createOptions.env,\n HOME: \"/home/agent\",\n },\n {\n volumeMounts,\n workdir: worktreePath,\n user: `${containerUid}:${containerGid}`,\n network: options?.network,\n groups: options?.groups,\n devices: options?.devices,\n cpus: options?.cpus,\n selinuxLabel,\n },\n ),\n );\n\n // Create parent directories for file mounts and chown to the container user\n for (const dir of parentDirsToCreate) {\n await new Promise<void>((resolve, reject) => {\n execFile(\n \"docker\",\n [\n \"exec\",\n \"--user\",\n \"0:0\",\n containerName,\n \"sh\",\n \"-c\",\n `mkdir -p \"$1\" && chown \"$2\" \"$1\"`,\n \"sh\",\n dir,\n `${containerUid}:${containerGid}`,\n ],\n (error) => {\n if (error) {\n reject(\n new Error(\n `Failed to create parent directory '${dir}' in container: ${error.message}`,\n ),\n );\n } else {\n resolve();\n }\n },\n );\n });\n }\n\n // Register synchronous container cleanup via the shared shutdown registry\n // so concurrent sandboxes share a single exit/SIGINT/SIGTERM listener\n // instead of tripping Node's MaxListenersExceededWarning.\n const removeContainerSync = () => {\n try {\n execFileSync(\"docker\", [\"rm\", \"-f\", containerName], {\n stdio: \"ignore\",\n });\n } catch {\n /* best-effort */\n }\n };\n const unregisterShutdown = registerShutdown(removeContainerSync);\n\n const handle: IsolatedSandboxHandle = {\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 const effectiveCommand = opts?.sudo ? `sudo ${command}` : command;\n const args = [\"exec\"];\n if (opts?.stdin !== undefined) args.push(\"-i\");\n if (opts?.cwd) args.push(\"-w\", opts.cwd);\n args.push(containerName, \"sh\", \"-c\", effectiveCommand);\n\n return new Promise((resolve, reject) => {\n const proc = spawn(\"docker\", args, {\n signal: opts?.signal,\n stdio: [\n opts?.stdin !== undefined ? \"pipe\" : \"ignore\",\n \"pipe\",\n \"pipe\",\n ],\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(`docker 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 opts.signal?.throwIfAborted();\n const executionId = randomUUID();\n const pidFile = `/tmp/shipyard-interactive-${executionId}.pid`;\n const cancelFile = `/tmp/shipyard-interactive-${executionId}.cancel`;\n const dockerArgs = [\"exec\"];\n // Allocate a pseudo-terminal when stdin looks like a TTY\n if (\n \"isTTY\" in opts.stdin &&\n (opts.stdin as { isTTY?: boolean }).isTTY\n ) {\n dockerArgs.push(\"-it\");\n } else {\n dockerArgs.push(\"-i\");\n }\n if (opts.cwd) dockerArgs.push(\"-w\", opts.cwd);\n dockerArgs.push(\n containerName,\n \"sh\",\n \"-c\",\n 'pid_file=$1; cancel_file=$2; shift 2; command -v setsid >/dev/null 2>&1 && command -v ps >/dev/null 2>&1 && command -v awk >/dev/null 2>&1 || { printf \"%s\\\\n\" \"setsid, ps, and awk are required for cancellable interactive execution\" >&2; exit 127; }; if [ -e \"$cancel_file\" ]; then rm -f \"$pid_file\" \"$cancel_file\"; exit 130; fi; setsid \"$@\" & child=$!; printf \"%s\\\\n\" \"$child\" > \"$pid_file\"; wait \"$child\"; status=$?; rm -f \"$pid_file\" \"$cancel_file\"; exit \"$status\"',\n \"shipyard-interactive\",\n pidFile,\n cancelFile,\n ...args,\n );\n\n const proc = spawn(\"docker\", dockerArgs, {\n stdio: [opts.stdin, opts.stdout, opts.stderr] as StdioOptions,\n });\n\n let settled = false;\n let cancelling = false;\n const finish = (\n result:\n | { readonly exitCode: number }\n | { readonly error: unknown },\n ): void => {\n if (settled) return;\n settled = true;\n opts.signal?.removeEventListener(\"abort\", onAbort);\n if (\"error\" in result) reject(result.error);\n else resolve(result);\n };\n\n const onAbort = (): void => {\n if (cancelling || settled) return;\n cancelling = true;\n const cancellationScript =\n 'touch \"$1\"; attempts=0; while [ ! -s \"$2\" ] && [ \"$attempts\" -lt 50 ]; do sleep 0.1; attempts=$((attempts + 1)); done; if [ -s \"$2\" ]; then pid=$(cat \"$2\"); group_running() { ps -eo pgid=,stat= | awk -v pg=\"$pid\" \\'$1 == pg && $2 !~ /^Z/ { found=1 } END { exit found ? 0 : 1 }\\'; }; kill -TERM \"-$pid\" 2>/dev/null || true; attempts=0; while group_running && [ \"$attempts\" -lt 20 ]; do sleep 0.1; attempts=$((attempts + 1)); done; kill -KILL \"-$pid\" 2>/dev/null || true; attempts=0; while group_running && [ \"$attempts\" -lt 20 ]; do sleep 0.1; attempts=$((attempts + 1)); done; if group_running; then exit 1; fi; rm -f \"$1\" \"$2\"; fi';\n execFile(\n \"docker\",\n [\n \"exec\",\n containerName,\n \"sh\",\n \"-c\",\n cancellationScript,\n \"shipyard-cancel\",\n cancelFile,\n pidFile,\n ],\n { timeout: 7_000 },\n (error) => {\n proc.kill(\"SIGKILL\");\n if (error) {\n finish({\n error: new Error(\n `Failed to terminate interactive process in Docker sandbox: ${error.message}`,\n ),\n });\n return;\n }\n finish({\n error:\n opts.signal?.reason ??\n new Error(\"Interactive execution aborted\"),\n });\n },\n );\n };\n\n opts.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (opts.signal?.aborted) onAbort();\n\n proc.on(\"error\", (error: Error) => {\n if (!cancelling) {\n finish({\n error: new Error(`docker exec failed: ${error.message}`),\n });\n }\n });\n\n proc.on(\"close\", (code: number | null) => {\n if (cancelling) return;\n execFile(\n \"docker\",\n [\"exec\", containerName, \"rm\", \"-f\", pidFile, cancelFile],\n () => {},\n );\n finish({ exitCode: code ?? 0 });\n });\n });\n },\n\n copyIn: (hostPath, sandboxPath) =>\n copyIntoContainer(\n \"docker\",\n containerName,\n `${containerUid}:${containerGid}`,\n hostPath,\n sandboxPath,\n ),\n\n copyFileOut: (sandboxPath: string, hostPath: string): Promise<void> =>\n new Promise((resolve, reject) => {\n execFile(\n \"docker\",\n [\"cp\", `${containerName}:${sandboxPath}`, hostPath],\n (error) => {\n if (error) {\n reject(new Error(`docker cp (out) failed: ${error.message}`));\n } else {\n resolve();\n }\n },\n );\n }),\n\n close: async (): Promise<void> => {\n unregisterShutdown();\n await Effect.runPromise(removeContainer(containerName));\n },\n };\n\n return handle;\n },\n });\n};\n\n// Re-export for backwards compatibility\nexport { defaultImageName };\n\nconst checkImageUid = (imageName: string, expectedUid: number): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n execFile(\n \"docker\",\n [\"image\", \"inspect\", imageName, \"--format\", \"{{.Config.User}}\"],\n (error, stdout) => {\n if (error) {\n reject(\n new Error(\n `Image '${imageName}' not found locally. Build it first with 'shipyard docker build-image'.`,\n ),\n );\n return;\n }\n const imageUser = (stdout ?? \"\").toString().trim();\n if (!imageUser) {\n // No USER directive in image — skip check\n resolve();\n return;\n }\n const uidPart = imageUser.split(\":\")[0]!;\n const imageUid = parseInt(uidPart, 10);\n if (isNaN(imageUid)) {\n // Non-numeric user (e.g. \"agent\") — can't compare, skip check\n resolve();\n return;\n }\n if (imageUid !== expectedUid) {\n reject(\n new Error(\n `UID mismatch: image '${imageName}' was built with UID ${imageUid}, ` +\n `but the expected UID is ${expectedUid}. ` +\n `Rebuild the image with 'shipyard docker build-image', ` +\n `or pass containerUid: ${imageUid} to docker() to match the image.`,\n ),\n );\n } else {\n resolve();\n }\n },\n );\n });\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { MAX_TAIL_CHARS, OutputByteCounter, BoundedTail } from './chunk-BU6XJFJ2.js';
|
|
1
|
+
import { MAX_TAIL_CHARS, OutputByteCounter, BoundedTail } from './chunk-WA7QPAXO.js';
|
|
3
2
|
import { spawn } from 'child_process';
|
|
4
3
|
import { createInterface } from 'readline';
|
|
5
4
|
|
|
6
|
-
createRequire(import.meta.url);
|
|
7
5
|
var noSandbox = (options) => ({
|
|
8
6
|
tag: "none",
|
|
9
7
|
name: "no-sandbox",
|
|
@@ -105,7 +103,8 @@ var noSandbox = (options) => ({
|
|
|
105
103
|
cwd: opts.cwd ?? worktreePath,
|
|
106
104
|
env: processEnv,
|
|
107
105
|
stdio: [opts.stdin, opts.stdout, opts.stderr],
|
|
108
|
-
shell: process.platform === "win32"
|
|
106
|
+
shell: process.platform === "win32",
|
|
107
|
+
signal: opts.signal
|
|
109
108
|
});
|
|
110
109
|
proc.on("error", (error) => {
|
|
111
110
|
reject(new Error(`exec failed: ${error.message}`));
|
|
@@ -123,5 +122,5 @@ var noSandbox = (options) => ({
|
|
|
123
122
|
});
|
|
124
123
|
|
|
125
124
|
export { noSandbox };
|
|
126
|
-
//# sourceMappingURL=chunk-
|
|
127
|
-
//# sourceMappingURL=chunk-
|
|
125
|
+
//# sourceMappingURL=chunk-VW4MI5GJ.js.map
|
|
126
|
+
//# sourceMappingURL=chunk-VW4MI5GJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|