@rockclaver/sandcastle 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1355 -0
  3. package/dist/MountConfig-CmXclHA5.d.ts +26 -0
  4. package/dist/SandboxProvider-EkSMuBp8.d.ts +243 -0
  5. package/dist/chunk-72UVAC7B.js +99 -0
  6. package/dist/chunk-72UVAC7B.js.map +1 -0
  7. package/dist/chunk-BIWNFKGV.js +22 -0
  8. package/dist/chunk-BIWNFKGV.js.map +1 -0
  9. package/dist/chunk-FKX3DRTL.js +362 -0
  10. package/dist/chunk-FKX3DRTL.js.map +1 -0
  11. package/dist/chunk-NGBM7T3E.js +76 -0
  12. package/dist/chunk-NGBM7T3E.js.map +1 -0
  13. package/dist/chunk-QCLZLPJ7.js +26431 -0
  14. package/dist/chunk-QCLZLPJ7.js.map +1 -0
  15. package/dist/chunk-VAKEM3U2.js +26997 -0
  16. package/dist/chunk-VAKEM3U2.js.map +1 -0
  17. package/dist/index.d.ts +943 -0
  18. package/dist/index.js +2393 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/main.d.ts +1 -0
  21. package/dist/main.js +19268 -0
  22. package/dist/main.js.map +1 -0
  23. package/dist/mountUtils-CCA-bbpK.d.ts +25 -0
  24. package/dist/sandboxes/daytona.d.ts +60 -0
  25. package/dist/sandboxes/daytona.js +122 -0
  26. package/dist/sandboxes/daytona.js.map +1 -0
  27. package/dist/sandboxes/docker.d.ts +110 -0
  28. package/dist/sandboxes/docker.js +9 -0
  29. package/dist/sandboxes/docker.js.map +1 -0
  30. package/dist/sandboxes/no-sandbox.d.ts +38 -0
  31. package/dist/sandboxes/no-sandbox.js +7 -0
  32. package/dist/sandboxes/no-sandbox.js.map +1 -0
  33. package/dist/sandboxes/podman.d.ts +124 -0
  34. package/dist/sandboxes/podman.js +299 -0
  35. package/dist/sandboxes/podman.js.map +1 -0
  36. package/dist/sandboxes/vercel.d.ts +104 -0
  37. package/dist/sandboxes/vercel.js +148 -0
  38. package/dist/sandboxes/vercel.js.map +1 -0
  39. package/dist/templates/blank/main.mts +14 -0
  40. package/dist/templates/blank/prompt.md +12 -0
  41. package/dist/templates/blank/template.json +4 -0
  42. package/dist/templates/parallel-planner/implement-prompt.md +62 -0
  43. package/dist/templates/parallel-planner/main.mts +204 -0
  44. package/dist/templates/parallel-planner/merge-prompt.md +26 -0
  45. package/dist/templates/parallel-planner/plan-prompt.md +37 -0
  46. package/dist/templates/parallel-planner/template.json +4 -0
  47. package/dist/templates/parallel-planner-with-review/CODING_STANDARDS.md +27 -0
  48. package/dist/templates/parallel-planner-with-review/implement-prompt.md +62 -0
  49. package/dist/templates/parallel-planner-with-review/main.mts +226 -0
  50. package/dist/templates/parallel-planner-with-review/merge-prompt.md +26 -0
  51. package/dist/templates/parallel-planner-with-review/plan-prompt.md +37 -0
  52. package/dist/templates/parallel-planner-with-review/review-prompt.md +55 -0
  53. package/dist/templates/parallel-planner-with-review/template.json +4 -0
  54. package/dist/templates/sequential-reviewer/CODING_STANDARDS.md +27 -0
  55. package/dist/templates/sequential-reviewer/implement-prompt.md +53 -0
  56. package/dist/templates/sequential-reviewer/main.mts +119 -0
  57. package/dist/templates/sequential-reviewer/review-prompt.md +55 -0
  58. package/dist/templates/sequential-reviewer/template.json +4 -0
  59. package/dist/templates/simple-loop/main.mts +49 -0
  60. package/dist/templates/simple-loop/prompt.md +53 -0
  61. package/dist/templates/simple-loop/template.json +4 -0
  62. package/package.json +104 -0
@@ -0,0 +1,26 @@
1
+ /**
2
+ * User-facing mount configuration for bind-mount sandbox providers.
3
+ *
4
+ * Each entry describes a host directory to mount into the sandbox container.
5
+ */
6
+ /** A single bind-mount descriptor for docker()/podman() providers. */
7
+ interface MountConfig {
8
+ /**
9
+ * Path on the host. Supports:
10
+ * - Absolute paths (`/data/cache`)
11
+ * - Tilde-expanded paths (`~/data` → `<home>/data`)
12
+ * - Relative paths (`data` or `./data`) — resolved from `process.cwd()`
13
+ */
14
+ readonly hostPath: string;
15
+ /**
16
+ * Path inside the sandbox container. Supports:
17
+ * - Absolute paths (`/mnt/data`)
18
+ * - Tilde-expanded paths (`~/.npm` → `/home/agent/.npm`) — expanded using the provider's sandbox home directory
19
+ * - Relative paths (`data` or `./data`) — resolved from the worktree directory (`/home/agent/workspace`)
20
+ */
21
+ readonly sandboxPath: string;
22
+ /** Mount as read-only. Defaults to `false`. */
23
+ readonly readonly?: boolean;
24
+ }
25
+
26
+ export type { MountConfig as M };
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Sandbox provider types — the pluggable interface for sandbox runtimes.
3
+ *
4
+ * Provider authors implement a small Promise-based interface. Sandcastle
5
+ * handles worktree creation, git mount resolution, and commit extraction.
6
+ */
7
+ /** Result of executing a command inside a sandbox. */
8
+ interface ExecResult {
9
+ readonly stdout: string;
10
+ readonly stderr: string;
11
+ readonly exitCode: number;
12
+ }
13
+ /** Options for interactiveExec — the streams the provider should wire to the spawned process. */
14
+ interface InteractiveExecOptions {
15
+ readonly stdin: NodeJS.ReadableStream;
16
+ readonly stdout: NodeJS.WritableStream;
17
+ readonly stderr: NodeJS.WritableStream;
18
+ readonly cwd?: string;
19
+ }
20
+ /** Handle to a running bind-mount sandbox. */
21
+ interface BindMountSandboxHandle {
22
+ /** Absolute path to the worktree inside the sandbox. */
23
+ readonly worktreePath: string;
24
+ /**
25
+ * Execute a command in the sandbox.
26
+ *
27
+ * Implementations MUST support line-by-line streaming via `onLine`. This is
28
+ * how Sandcastle delivers live feedback to the user and enforces idle timeouts —
29
+ * without a streaming implementation, neither will work. A buffered/batch
30
+ * implementation that only calls `onLine` after the process exits does NOT
31
+ * satisfy this contract.
32
+ *
33
+ * When `stdin` is set, the implementation pipes the string to the child
34
+ * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.
35
+ */
36
+ exec(command: string, options?: {
37
+ onLine?: (line: string) => void;
38
+ cwd?: string;
39
+ sudo?: boolean;
40
+ stdin?: string;
41
+ }): Promise<ExecResult>;
42
+ /**
43
+ * Launch an interactive process inside the sandbox.
44
+ * Optional — providers that support interactive sessions implement this.
45
+ * The provider detects TTY mode from the streams (e.g. stdin.isTTY) and
46
+ * allocates a pseudo-terminal accordingly.
47
+ */
48
+ interactiveExec?(args: string[], options: InteractiveExecOptions): Promise<{
49
+ exitCode: number;
50
+ }>;
51
+ /** Copy a single file from the host into the sandbox. */
52
+ copyFileIn(hostPath: string, sandboxPath: string): Promise<void>;
53
+ /** Copy a single file from the sandbox to the host. */
54
+ copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;
55
+ /** Tear down the sandbox. */
56
+ close(): Promise<void>;
57
+ }
58
+ /** Options passed to a bind-mount provider's `create` function. */
59
+ interface BindMountCreateOptions {
60
+ /** Host-side path to the worktree directory. */
61
+ readonly worktreePath: string;
62
+ /** Host-side path to the original repo root. */
63
+ readonly hostRepoPath: string;
64
+ /** Volume mounts to apply (host:sandbox pairs). */
65
+ readonly mounts: Array<{
66
+ hostPath: string;
67
+ sandboxPath: string;
68
+ readonly?: boolean;
69
+ }>;
70
+ /** Environment variables to inject into the sandbox. */
71
+ readonly env: Record<string, string>;
72
+ }
73
+ /** Configuration for createBindMountSandboxProvider. */
74
+ interface BindMountSandboxProviderConfig {
75
+ /** Human-readable name for this provider (e.g. "docker", "podman"). */
76
+ readonly name: string;
77
+ /** Environment variables injected by this provider. Merged at launch time. */
78
+ readonly env?: Record<string, string>;
79
+ /**
80
+ * Absolute path to the home directory inside the sandbox (e.g. `"/home/agent"`).
81
+ * Used to expand `~` in user-provided `sandboxPath` mount configs.
82
+ * Set to `undefined` for providers that do not have a fixed home directory.
83
+ */
84
+ readonly sandboxHomedir?: string;
85
+ /** Create a sandbox handle from the given options. */
86
+ readonly create: (options: BindMountCreateOptions) => Promise<BindMountSandboxHandle>;
87
+ }
88
+ /** Handle to a running isolated sandbox (extends bind-mount with file transfer). */
89
+ interface IsolatedSandboxHandle {
90
+ /** Absolute path to the worktree inside the sandbox. */
91
+ readonly worktreePath: string;
92
+ /**
93
+ * Execute a command in the sandbox.
94
+ *
95
+ * Implementations MUST support line-by-line streaming via `onLine`. This is
96
+ * how Sandcastle delivers live feedback to the user and enforces idle timeouts —
97
+ * without a streaming implementation, neither will work. A buffered/batch
98
+ * implementation that only calls `onLine` after the process exits does NOT
99
+ * satisfy this contract.
100
+ *
101
+ * When `stdin` is set, the implementation pipes the string to the child
102
+ * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.
103
+ */
104
+ exec(command: string, options?: {
105
+ onLine?: (line: string) => void;
106
+ cwd?: string;
107
+ sudo?: boolean;
108
+ stdin?: string;
109
+ }): Promise<ExecResult>;
110
+ /**
111
+ * Launch an interactive process inside the sandbox.
112
+ * Optional — providers that support interactive sessions implement this.
113
+ * The provider detects TTY mode from the streams (e.g. stdin.isTTY) and
114
+ * allocates a pseudo-terminal accordingly.
115
+ */
116
+ interactiveExec?(args: string[], options: InteractiveExecOptions): Promise<{
117
+ exitCode: number;
118
+ }>;
119
+ /** Copy a file or directory from the host into the sandbox. */
120
+ copyIn(hostPath: string, sandboxPath: string): Promise<void>;
121
+ /** Copy a single file from the sandbox to the host. */
122
+ copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;
123
+ /** Tear down the sandbox. */
124
+ close(): Promise<void>;
125
+ }
126
+ /** Options passed to an isolated provider's `create` function. */
127
+ interface IsolatedCreateOptions {
128
+ /** Environment variables to inject into the sandbox. */
129
+ readonly env: Record<string, string>;
130
+ }
131
+ /** Configuration for createIsolatedSandboxProvider. */
132
+ interface IsolatedSandboxProviderConfig {
133
+ /** Human-readable name for this provider (e.g. "daytona", "e2b"). */
134
+ readonly name: string;
135
+ /** Environment variables injected by this provider. Merged at launch time. */
136
+ readonly env?: Record<string, string>;
137
+ /** Create an isolated sandbox handle from the given options. */
138
+ readonly create: (options: IsolatedCreateOptions) => Promise<IsolatedSandboxHandle>;
139
+ }
140
+ /** A bind-mount sandbox provider. */
141
+ interface BindMountSandboxProvider {
142
+ /** Human-readable provider name. */
143
+ readonly name: string;
144
+ /** Environment variables injected by this provider. */
145
+ readonly env: Record<string, string>;
146
+ /**
147
+ * Absolute path to the home directory inside the sandbox (e.g. `"/home/agent"`).
148
+ * `undefined` when the provider does not declare a sandbox home directory.
149
+ */
150
+ readonly sandboxHomedir: string | undefined;
151
+ }
152
+ /** An isolated sandbox provider. */
153
+ interface IsolatedSandboxProvider {
154
+ /** Human-readable provider name. */
155
+ readonly name: string;
156
+ /** Environment variables injected by this provider. */
157
+ readonly env: Record<string, string>;
158
+ }
159
+ /** Handle to a no-sandbox session — runs commands directly on the host. */
160
+ interface NoSandboxHandle {
161
+ /** Absolute path to the worktree on the host. */
162
+ readonly worktreePath: string;
163
+ /**
164
+ * Execute a command on the host.
165
+ *
166
+ * Implementations MUST support line-by-line streaming via `onLine`. This is
167
+ * how Sandcastle delivers live feedback to the user and enforces idle timeouts —
168
+ * without a streaming implementation, neither will work.
169
+ *
170
+ * When `stdin` is set, the implementation pipes the string to the child
171
+ * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.
172
+ */
173
+ exec(command: string, options?: {
174
+ onLine?: (line: string) => void;
175
+ cwd?: string;
176
+ sudo?: boolean;
177
+ stdin?: string;
178
+ }): Promise<ExecResult>;
179
+ /**
180
+ * Launch an interactive process on the host with inherited stdio.
181
+ */
182
+ interactiveExec(args: string[], options: InteractiveExecOptions): Promise<{
183
+ exitCode: number;
184
+ }>;
185
+ /** No-op — no container to tear down. */
186
+ close(): Promise<void>;
187
+ }
188
+ /** A no-sandbox provider — runs the agent directly on the host with no container isolation. */
189
+ interface NoSandboxProvider {
190
+ /** Human-readable provider name. */
191
+ readonly name: string;
192
+ /** Environment variables injected by this provider. */
193
+ readonly env: Record<string, string>;
194
+ }
195
+ /** Head strategy: agent writes directly to host working directory. Bind-mount only. */
196
+ interface HeadBranchStrategy {
197
+ readonly type: "head";
198
+ }
199
+ /** Merge-to-head strategy: temp branch, merge back to HEAD, delete temp branch. */
200
+ interface MergeToHeadBranchStrategy {
201
+ readonly type: "merge-to-head";
202
+ }
203
+ /** Branch strategy: commits land on an explicit named branch. */
204
+ interface NamedBranchStrategy {
205
+ readonly type: "branch";
206
+ readonly branch: string;
207
+ /**
208
+ * Git ref to use as the starting point when creating a new branch.
209
+ * Only used when the branch doesn't already exist — ignored otherwise.
210
+ * Callers are responsible for ensuring the ref is current (e.g. `git fetch`).
211
+ * Defaults to `HEAD` when omitted.
212
+ */
213
+ readonly baseBranch?: string;
214
+ }
215
+ /** Branch strategy for bind-mount providers (all three variants). */
216
+ type BindMountBranchStrategy = HeadBranchStrategy | MergeToHeadBranchStrategy | NamedBranchStrategy;
217
+ /** Branch strategy for isolated providers (no head — can't write to host). */
218
+ type IsolatedBranchStrategy = MergeToHeadBranchStrategy | NamedBranchStrategy;
219
+ /** Branch strategy for no-sandbox providers (all three — same as bind-mount). */
220
+ type NoSandboxBranchStrategy = HeadBranchStrategy | MergeToHeadBranchStrategy | NamedBranchStrategy;
221
+ /** Union of all branch strategy variants. */
222
+ type BranchStrategy = BindMountBranchStrategy | IsolatedBranchStrategy | NoSandboxBranchStrategy;
223
+ /**
224
+ * A sandbox provider — the pluggable unit that `run()`, `interactive()`, and
225
+ * `createSandbox()` accept. Tagged for internal dispatch: "bind-mount",
226
+ * "isolated", or "none". When `NoSandboxProvider` is used, the agent runs
227
+ * directly on the host with no container isolation — opt in at your own risk.
228
+ */
229
+ type SandboxProvider = BindMountSandboxProvider | IsolatedSandboxProvider | NoSandboxProvider;
230
+ /** @deprecated Use `SandboxProvider` — it now includes `NoSandboxProvider`. */
231
+ type AnySandboxProvider = SandboxProvider;
232
+ /**
233
+ * Create a bind-mount sandbox provider from a config object.
234
+ * The returned provider can be passed to `run()` or `createSandbox()`.
235
+ */
236
+ declare const createBindMountSandboxProvider: (config: BindMountSandboxProviderConfig) => BindMountSandboxProvider;
237
+ /**
238
+ * Create an isolated sandbox provider from a config object.
239
+ * The returned provider can be passed to `run()` or `createSandbox()`.
240
+ */
241
+ declare const createIsolatedSandboxProvider: (config: IsolatedSandboxProviderConfig) => IsolatedSandboxProvider;
242
+
243
+ export { type AnySandboxProvider as A, type BindMountSandboxHandle as B, type ExecResult as E, type HeadBranchStrategy as H, type IsolatedSandboxProvider as I, type MergeToHeadBranchStrategy as M, type NoSandboxProvider as N, type SandboxProvider as S, type BranchStrategy as a, type NamedBranchStrategy as b, type BindMountBranchStrategy as c, type BindMountCreateOptions as d, type BindMountSandboxProvider as e, type BindMountSandboxProviderConfig as f, type InteractiveExecOptions as g, type IsolatedBranchStrategy as h, type IsolatedCreateOptions as i, type IsolatedSandboxHandle as j, type IsolatedSandboxProviderConfig as k, type NoSandboxBranchStrategy as l, type NoSandboxHandle as m, createBindMountSandboxProvider as n, createIsolatedSandboxProvider as o };
@@ -0,0 +1,99 @@
1
+ import { createRequire } from 'node:module';
2
+ import { MAX_TAIL_CHARS, BoundedTail } from './chunk-NGBM7T3E.js';
3
+ import { spawn } from 'child_process';
4
+ import { createInterface } from 'readline';
5
+
6
+ createRequire(import.meta.url);
7
+ var noSandbox = (options) => ({
8
+ tag: "none",
9
+ name: "no-sandbox",
10
+ env: options?.env ?? {},
11
+ create: async (createOptions) => {
12
+ const worktreePath = createOptions.worktreePath;
13
+ const processEnv = { ...process.env, ...createOptions.env };
14
+ const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;
15
+ const handle = {
16
+ worktreePath,
17
+ exec: (command, opts) => {
18
+ const cwd = opts?.cwd ?? worktreePath;
19
+ return new Promise((resolve, reject) => {
20
+ const proc = spawn("sh", ["-c", command], {
21
+ cwd,
22
+ env: processEnv,
23
+ stdio: [
24
+ opts?.stdin !== void 0 ? "pipe" : "ignore",
25
+ "pipe",
26
+ "pipe"
27
+ ]
28
+ });
29
+ if (opts?.stdin !== void 0) {
30
+ proc.stdin.write(opts.stdin);
31
+ proc.stdin.end();
32
+ }
33
+ proc.on("error", (error) => {
34
+ reject(new Error(`exec failed: ${error.message}`));
35
+ });
36
+ if (opts?.onLine) {
37
+ const onLine = opts.onLine;
38
+ const stdoutTail = new BoundedTail(maxOutputTailChars, "\n");
39
+ const stderrTail = new BoundedTail(maxOutputTailChars, "");
40
+ const rl = createInterface({ input: proc.stdout });
41
+ rl.on("line", (line) => {
42
+ stdoutTail.push(line);
43
+ onLine(line);
44
+ });
45
+ proc.stderr.on("data", (chunk) => {
46
+ stderrTail.push(chunk.toString());
47
+ });
48
+ proc.on("close", (code) => {
49
+ resolve({
50
+ stdout: stdoutTail.toString(),
51
+ stderr: stderrTail.toString(),
52
+ exitCode: code ?? 0
53
+ });
54
+ });
55
+ } else {
56
+ const stdoutChunks = [];
57
+ const stderrChunks = [];
58
+ proc.stdout.on("data", (chunk) => {
59
+ stdoutChunks.push(chunk.toString());
60
+ });
61
+ proc.stderr.on("data", (chunk) => {
62
+ stderrChunks.push(chunk.toString());
63
+ });
64
+ proc.on("close", (code) => {
65
+ resolve({
66
+ stdout: stdoutChunks.join(""),
67
+ stderr: stderrChunks.join(""),
68
+ exitCode: code ?? 0
69
+ });
70
+ });
71
+ }
72
+ });
73
+ },
74
+ interactiveExec: (args, opts) => {
75
+ return new Promise((resolve, reject) => {
76
+ const [cmd, ...rest] = args;
77
+ const proc = spawn(cmd, rest, {
78
+ cwd: opts.cwd ?? worktreePath,
79
+ env: processEnv,
80
+ stdio: [opts.stdin, opts.stdout, opts.stderr]
81
+ });
82
+ proc.on("error", (error) => {
83
+ reject(new Error(`exec failed: ${error.message}`));
84
+ });
85
+ proc.on("close", (code) => {
86
+ resolve({ exitCode: code ?? 0 });
87
+ });
88
+ });
89
+ },
90
+ close: async () => {
91
+ }
92
+ };
93
+ return handle;
94
+ }
95
+ });
96
+
97
+ export { noSandbox };
98
+ //# sourceMappingURL=chunk-72UVAC7B.js.map
99
+ //# sourceMappingURL=chunk-72UVAC7B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sandboxes/no-sandbox.ts"],"names":[],"mappings":"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","file":"chunk-72UVAC7B.js","sourcesContent":["/**\n * No-sandbox provider — runs the agent directly on the host with no container isolation.\n *\n * Usage:\n * import { noSandbox } from \"sandcastle/sandboxes/no-sandbox\";\n * await interactive({ agent: claudeCode(\"claude-opus-4-7\"), sandbox: noSandbox() });\n *\n * Accepted by `run()`, `interactive()`, and `createSandbox()`. Skips\n * container isolation entirely — the agent executes on the host. Does not\n * pass `--dangerously-skip-permissions` to the agent — the user manages\n * permissions themselves.\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 { BoundedTail, MAX_TAIL_CHARS } 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 },\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\n return new Promise((resolve, reject) => {\n const proc = spawn(\"sh\", [\"-c\", command], {\n cwd,\n env: processEnv,\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(`exec failed: ${error.message}`));\n });\n\n if (opts?.onLine) {\n const onLine = opts.onLine;\n const stdoutTail = new BoundedTail(maxOutputTailChars, \"\\n\");\n const stderrTail = new BoundedTail(maxOutputTailChars, \"\");\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 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 const proc = spawn(cmd!, rest, {\n cwd: opts.cwd ?? worktreePath,\n env: processEnv,\n stdio: [opts.stdin, opts.stdout, opts.stderr] as StdioOptions,\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,22 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ createRequire(import.meta.url);
4
+
5
+ // src/SandboxProvider.ts
6
+ var createBindMountSandboxProvider = (config) => ({
7
+ tag: "bind-mount",
8
+ name: config.name,
9
+ env: config.env ?? {},
10
+ sandboxHomedir: config.sandboxHomedir,
11
+ create: config.create
12
+ });
13
+ var createIsolatedSandboxProvider = (config) => ({
14
+ tag: "isolated",
15
+ name: config.name,
16
+ env: config.env ?? {},
17
+ create: config.create
18
+ });
19
+
20
+ export { createBindMountSandboxProvider, createIsolatedSandboxProvider };
21
+ //# sourceMappingURL=chunk-BIWNFKGV.js.map
22
+ //# sourceMappingURL=chunk-BIWNFKGV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/SandboxProvider.ts"],"names":[],"mappings":";;;;;AAoTO,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","file":"chunk-BIWNFKGV.js","sourcesContent":["/**\n * Sandbox provider types — the pluggable interface for sandbox runtimes.\n *\n * Provider authors implement a small Promise-based interface. Sandcastle\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}\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 Sandcastle 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 },\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 */\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\", \"podman\"). */\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 Sandcastle 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 },\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 */\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 /** 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. \"daytona\", \"e2b\"). */\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 Sandcastle 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 },\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"]}