@ai-hero/sandcastle 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +304 -37
- package/dist/AgentProvider.d.ts +0 -10
- package/dist/AgentProvider.d.ts.map +1 -1
- package/dist/AgentProvider.js +28 -45
- package/dist/AgentProvider.js.map +1 -1
- package/dist/DockerLifecycle.d.ts +5 -1
- package/dist/DockerLifecycle.d.ts.map +1 -1
- package/dist/DockerLifecycle.js +8 -1
- package/dist/DockerLifecycle.js.map +1 -1
- package/dist/InitService.d.ts.map +1 -1
- package/dist/InitService.js +2 -0
- package/dist/InitService.js.map +1 -1
- package/dist/Orchestrator.d.ts +0 -1
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +2 -13
- package/dist/Orchestrator.js.map +1 -1
- package/dist/SandboxFactory.d.ts +7 -7
- package/dist/SandboxFactory.d.ts.map +1 -1
- package/dist/SandboxFactory.js +39 -35
- package/dist/SandboxFactory.js.map +1 -1
- package/dist/SandboxProvider.d.ts +27 -0
- package/dist/SandboxProvider.d.ts.map +1 -1
- package/dist/SandboxProvider.js +2 -0
- package/dist/SandboxProvider.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/run.d.ts +0 -16
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +18 -20
- package/dist/run.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +3 -1
- package/dist/sandboxes/docker.d.ts.map +1 -1
- package/dist/sandboxes/docker.js +1 -0
- package/dist/sandboxes/docker.js.map +1 -1
- package/dist/sandboxes/test-isolated.d.ts +6 -2
- package/dist/sandboxes/test-isolated.d.ts.map +1 -1
- package/dist/sandboxes/test-isolated.js +2 -1
- package/dist/sandboxes/test-isolated.js.map +1 -1
- package/dist/syncIn.d.ts +4 -2
- package/dist/syncIn.d.ts.map +1 -1
- package/dist/syncIn.js +72 -22
- package/dist/syncIn.js.map +1 -1
- package/dist/syncOut.d.ts +3 -1
- package/dist/syncOut.d.ts.map +1 -1
- package/dist/syncOut.js +155 -76
- package/dist/syncOut.js.map +1 -1
- package/dist/templates/blank/.env.example +1 -0
- package/dist/templates/parallel-planner/.env.example +1 -0
- package/dist/templates/parallel-planner/main.mts +2 -3
- package/dist/templates/sequential-reviewer/.env.example +1 -0
- package/dist/templates/sequential-reviewer/main.mts +1 -2
- package/dist/templates/simple-loop/.env.example +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
A TypeScript library for orchestrating AI coding agents in isolated Docker containers:
|
|
12
12
|
|
|
13
13
|
1. You invoke agents with a single `sandcastle.run()`.
|
|
14
|
-
2. Sandcastle handles
|
|
14
|
+
2. Sandcastle handles sandboxing the agent with a configurable branch strategy.
|
|
15
15
|
3. The commits made on the branches get merged back.
|
|
16
16
|
|
|
17
17
|
Great for parallelizing multiple AFK agents, creating review pipelines, or even just orchestrating your own agents.
|
|
@@ -35,7 +35,7 @@ npm install @ai-hero/sandcastle
|
|
|
35
35
|
npx sandcastle init
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
3. Edit `.sandcastle/.env` and fill in your default values for `ANTHROPIC_API_KEY
|
|
38
|
+
3. Edit `.sandcastle/.env` and fill in your default values for `ANTHROPIC_API_KEY`. If you want to use your Claude subscription instead of an API key, see [#191](https://github.com/mattpocock/sandcastle/issues/191).
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
cp .sandcastle/.env.example .sandcastle/.env
|
|
@@ -90,8 +90,11 @@ const result = await run({
|
|
|
90
90
|
agent: claudeCode("claude-opus-4-6", { effort: "high" }),
|
|
91
91
|
|
|
92
92
|
// Sandbox provider — required. Import from "@ai-hero/sandcastle/sandboxes/docker".
|
|
93
|
-
// Provider-specific config (like imageName) lives inside the provider factory call.
|
|
94
|
-
sandbox: docker({
|
|
93
|
+
// Provider-specific config (like imageName and branchStrategy) lives inside the provider factory call.
|
|
94
|
+
sandbox: docker({
|
|
95
|
+
imageName: "sandcastle:local",
|
|
96
|
+
branchStrategy: { type: "branch", branch: "agent/fix-42" },
|
|
97
|
+
}),
|
|
95
98
|
|
|
96
99
|
// Prompt source — provide one of these, not both:
|
|
97
100
|
promptFile: ".sandcastle/prompt.md", // path to a prompt file
|
|
@@ -105,22 +108,17 @@ const result = await run({
|
|
|
105
108
|
// Maximum number of agent iterations to run before stopping. Default: 1
|
|
106
109
|
maxIterations: 5,
|
|
107
110
|
|
|
108
|
-
// Worktree mode for sandbox work. Defaults to { mode: 'temp-branch' }.
|
|
109
|
-
// { mode: 'none' } — bind-mount host working directory directly (no worktree).
|
|
110
|
-
// { mode: 'temp-branch' } — create a temp worktree, merge back.
|
|
111
|
-
// { mode: 'branch', branch } — create a worktree on an explicit branch.
|
|
112
|
-
worktree: { mode: "branch", branch: "agent/fix-42" },
|
|
113
|
-
|
|
114
111
|
// Display name for this run, shown as a prefix in log output.
|
|
115
112
|
name: "fix-issue-42",
|
|
116
113
|
|
|
117
114
|
// Lifecycle hooks — arrays of shell commands run sequentially inside the sandbox.
|
|
118
115
|
hooks: {
|
|
119
|
-
// Runs after the
|
|
116
|
+
// Runs after the sandbox is ready.
|
|
120
117
|
onSandboxReady: [{ command: "npm install" }],
|
|
121
118
|
},
|
|
122
119
|
|
|
123
|
-
// Host-relative file paths to copy into the
|
|
120
|
+
// Host-relative file paths to copy into the sandbox before the container starts.
|
|
121
|
+
// Not supported with branchStrategy: { type: "head" }.
|
|
124
122
|
copyToSandbox: [".env"],
|
|
125
123
|
|
|
126
124
|
// How to record progress. Default: write to a file under .sandcastle/logs/
|
|
@@ -143,7 +141,7 @@ console.log(result.branch); // target branch name
|
|
|
143
141
|
|
|
144
142
|
### `createSandbox()` — reusable sandbox
|
|
145
143
|
|
|
146
|
-
Use `createSandbox()` when you need to run multiple agents (or multiple rounds of the same agent) inside a single sandbox. It creates the
|
|
144
|
+
Use `createSandbox()` when you need to run multiple agents (or multiple rounds of the same agent) inside a single sandbox. It creates the sandbox once, and you call `sandbox.run()` as many times as you need. This avoids repeated container startup costs and keeps all runs on the same branch.
|
|
147
145
|
|
|
148
146
|
Use `run()` instead when you only need a single one-shot invocation — it handles sandbox lifecycle automatically.
|
|
149
147
|
|
|
@@ -196,7 +194,7 @@ Commits from all `run()` calls accumulate on the same branch. The sandbox contai
|
|
|
196
194
|
|
|
197
195
|
#### Automatic cleanup with `await using`
|
|
198
196
|
|
|
199
|
-
`await using` calls `sandbox.close()` automatically when the block exits. If the
|
|
197
|
+
`await using` calls `sandbox.close()` automatically when the block exits. If the sandbox has uncommitted changes, the worktree is preserved on disk; if clean, both container and worktree are removed.
|
|
200
198
|
|
|
201
199
|
#### Manual `close()` with `CloseResult`
|
|
202
200
|
|
|
@@ -214,21 +212,21 @@ if (closeResult.preservedWorktreePath) {
|
|
|
214
212
|
|
|
215
213
|
#### `CreateSandboxOptions`
|
|
216
214
|
|
|
217
|
-
| Option | Type | Default | Description
|
|
218
|
-
| --------------- | --------------- | ------- |
|
|
219
|
-
| `branch` | string | — | **Required.** Explicit branch for the
|
|
220
|
-
| `sandbox` | SandboxProvider | — | **Required.** Sandbox provider (e.g. `docker()`)
|
|
221
|
-
| `hooks` | object | — | Lifecycle hooks (`onSandboxReady`) — run once at creation time
|
|
222
|
-
| `copyToSandbox` | string[] | — | Host-relative file paths to copy into the
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
| --------------- | --------------- | ------- | ------------------------------------------------------------------ |
|
|
217
|
+
| `branch` | string | — | **Required.** Explicit branch for the sandbox |
|
|
218
|
+
| `sandbox` | SandboxProvider | — | **Required.** Sandbox provider (e.g. `docker()`) |
|
|
219
|
+
| `hooks` | object | — | Lifecycle hooks (`onSandboxReady`) — run once at creation time |
|
|
220
|
+
| `copyToSandbox` | string[] | — | Host-relative file paths to copy into the sandbox at creation time |
|
|
223
221
|
|
|
224
222
|
#### `Sandbox`
|
|
225
223
|
|
|
226
224
|
| Property / Method | Type | Description |
|
|
227
225
|
| ----------------------- | -------------------------------------------------- | ------------------------------------------- |
|
|
228
|
-
| `branch` | string | The branch the
|
|
226
|
+
| `branch` | string | The branch the sandbox is on |
|
|
229
227
|
| `worktreePath` | string | Host path to the worktree |
|
|
230
228
|
| `run(options)` | `(SandboxRunOptions) => Promise<SandboxRunResult>` | Invoke an agent inside the existing sandbox |
|
|
231
|
-
| `close()` | `() => Promise<CloseResult>` | Tear down the container and
|
|
229
|
+
| `close()` | `() => Promise<CloseResult>` | Tear down the container and sandbox |
|
|
232
230
|
| `[Symbol.asyncDispose]` | `() => Promise<void>` | Auto teardown via `await using` |
|
|
233
231
|
|
|
234
232
|
#### `SandboxRunOptions`
|
|
@@ -263,14 +261,15 @@ if (closeResult.preservedWorktreePath) {
|
|
|
263
261
|
|
|
264
262
|
## How it works
|
|
265
263
|
|
|
266
|
-
Sandcastle uses a
|
|
264
|
+
Sandcastle uses a **branch strategy** configured on the sandbox provider to control how the agent's changes relate to branches. There are three strategies:
|
|
265
|
+
|
|
266
|
+
- **Head** (`{ type: "head" }`) — The agent writes directly to the host working directory. No worktree, no branch indirection. This is the default for bind-mount providers like `docker()`.
|
|
267
|
+
- **Merge-to-head** (`{ type: "merge-to-head" }`) — Sandcastle creates a temporary branch in a git worktree. The agent works on the temp branch, and changes are merged back to HEAD when done. The temp branch is cleaned up after merge.
|
|
268
|
+
- **Branch** (`{ type: "branch", branch: "foo" }`) — Commits land on an explicitly named branch in a git worktree.
|
|
267
269
|
|
|
268
|
-
-
|
|
269
|
-
- **Bind-mount**: The worktree directory is bind-mounted into the sandbox container as the agent's working directory. The agent writes directly to the host filesystem through the mount.
|
|
270
|
-
- **No sync needed**: Because the agent writes directly to the host filesystem, there are no sync-in or sync-out operations. Commits made by the agent are immediately visible on the host.
|
|
271
|
-
- **Merge back**: After the run completes, the temp worktree branch is fast-forward merged back to the target branch, and the worktree is cleaned up.
|
|
270
|
+
For bind-mount providers (like Docker), the worktree directory is bind-mounted into the container — the agent writes directly to the host filesystem through the mount, so no sync is needed.
|
|
272
271
|
|
|
273
|
-
From your point of view, you just
|
|
272
|
+
From your point of view, you just configure `docker({ branchStrategy: { type: 'branch', branch: 'foo' } })`, and get a commit on branch `foo` once it's complete. All 100% local.
|
|
274
273
|
|
|
275
274
|
## Prompts
|
|
276
275
|
|
|
@@ -291,7 +290,7 @@ You must provide exactly one of:
|
|
|
291
290
|
|
|
292
291
|
Use `` !`command` `` expressions in your prompt to pull in dynamic context. Each expression is replaced with the command's stdout before the prompt is sent to the agent.
|
|
293
292
|
|
|
294
|
-
Commands run **inside the sandbox** after
|
|
293
|
+
Commands run **inside the sandbox** after `onSandboxReady` hooks complete, so they see the same repo state the agent sees (including installed dependencies).
|
|
295
294
|
|
|
296
295
|
```markdown
|
|
297
296
|
# Open issues
|
|
@@ -336,10 +335,10 @@ A `{{KEY}}` placeholder with no matching prompt argument is an error. Unused pro
|
|
|
336
335
|
|
|
337
336
|
Sandcastle automatically injects two built-in prompt arguments into every prompt:
|
|
338
337
|
|
|
339
|
-
| Placeholder | Value
|
|
340
|
-
| ------------------- |
|
|
341
|
-
| `{{SOURCE_BRANCH}}` | The branch the agent works on
|
|
342
|
-
| `{{TARGET_BRANCH}}` | The host's active branch at `run()` time
|
|
338
|
+
| Placeholder | Value |
|
|
339
|
+
| ------------------- | ----------------------------------------------------------------- |
|
|
340
|
+
| `{{SOURCE_BRANCH}}` | The branch the agent works on (determined by the branch strategy) |
|
|
341
|
+
| `{{TARGET_BRANCH}}` | The host's active branch at `run()` time |
|
|
343
342
|
|
|
344
343
|
Use them in your prompt without passing them via `promptArgs`:
|
|
345
344
|
|
|
@@ -405,7 +404,7 @@ Creates the following files:
|
|
|
405
404
|
├── Dockerfile # Sandbox environment (customize as needed)
|
|
406
405
|
├── prompt.md # Agent instructions
|
|
407
406
|
├── .env.example # Token placeholders
|
|
408
|
-
└── .gitignore # Ignores .env, logs
|
|
407
|
+
└── .gitignore # Ignores .env, logs/
|
|
409
408
|
```
|
|
410
409
|
|
|
411
410
|
Errors if `.sandcastle/` already exists to prevent overwriting customizations.
|
|
@@ -437,10 +436,9 @@ Removes the Docker image.
|
|
|
437
436
|
| `promptFile` | string | — | Path to prompt file (mutually exclusive with `prompt`) |
|
|
438
437
|
| `maxIterations` | number | `1` | Maximum iterations to run |
|
|
439
438
|
| `hooks` | object | — | Lifecycle hooks (`onSandboxReady`) |
|
|
440
|
-
| `worktree` | WorktreeMode | `{ mode: 'temp-branch' }` | Worktree mode: `{ mode: 'none' }`, `{ mode: 'temp-branch' }`, or `{ mode: 'branch', branch }` |
|
|
441
439
|
| `name` | string | — | Display name for the run, shown as a prefix in log output |
|
|
442
440
|
| `promptArgs` | PromptArgs | — | Key-value map for `{{KEY}}` placeholder substitution |
|
|
443
|
-
| `copyToSandbox` | string[] | — | Host-relative file paths to copy into the
|
|
441
|
+
| `copyToSandbox` | string[] | — | Host-relative file paths to copy into the sandbox before start (not supported with `branchStrategy: { type: 'head' }`) |
|
|
444
442
|
| `logging` | object | file (auto-generated) | `{ type: 'file', path }` or `{ type: 'stdout' }` |
|
|
445
443
|
| `completionSignal` | string \| string[] | `<promise>COMPLETE</promise>` | String or array of strings the agent emits to stop the iteration loop early |
|
|
446
444
|
| `idleTimeoutSeconds` | number | `600` | Idle timeout in seconds — resets on each agent output event |
|
|
@@ -470,6 +468,275 @@ agent: claudeCode("claude-opus-4-6", { effort: "high" });
|
|
|
470
468
|
|
|
471
469
|
Environment variables are resolved automatically from `.sandcastle/.env` and `process.env` — no need to pass them to the API. The required variables depend on the **agent provider** (see `sandcastle init` output for details).
|
|
472
470
|
|
|
471
|
+
## Custom Sandbox Providers
|
|
472
|
+
|
|
473
|
+
Sandcastle ships with a Docker provider, but you can create your own. A sandbox provider tells Sandcastle how to execute commands in an isolated environment. There are two kinds:
|
|
474
|
+
|
|
475
|
+
- **Bind-mount** — the sandbox can mount a host directory. Sandcastle creates a worktree on the host and the provider mounts it in. No file sync needed. Use this for Docker, Podman, or any local container runtime.
|
|
476
|
+
- **Isolated** — the sandbox has its own filesystem (e.g. a cloud VM). The provider handles syncing code in and out via `copyIn` and `copyOut`. Use this when the sandbox cannot access the host filesystem.
|
|
477
|
+
|
|
478
|
+
### The sandbox handle contract
|
|
479
|
+
|
|
480
|
+
Both provider types return a **sandbox handle** from their `create()` function. The handle exposes:
|
|
481
|
+
|
|
482
|
+
| Method | Required | Description |
|
|
483
|
+
| --------------- | -------- | ------------------------------------------------- |
|
|
484
|
+
| `exec` | Both | Run a command, return `ExecResult` when done |
|
|
485
|
+
| `execStreaming` | Both | Run a command, call `onLine` for each stdout line |
|
|
486
|
+
| `close` | Both | Tear down the sandbox |
|
|
487
|
+
| `copyIn` | Isolated | Copy a file from the host into the sandbox |
|
|
488
|
+
| `copyOut` | Isolated | Copy a file from the sandbox to the host |
|
|
489
|
+
| `workspacePath` | Both | Absolute path to the workspace inside the sandbox |
|
|
490
|
+
|
|
491
|
+
### `ExecResult`
|
|
492
|
+
|
|
493
|
+
Every `exec` and `execStreaming` call returns an `ExecResult`:
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
interface ExecResult {
|
|
497
|
+
readonly stdout: string;
|
|
498
|
+
readonly stderr: string;
|
|
499
|
+
readonly exitCode: number;
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Bind-mount provider example
|
|
504
|
+
|
|
505
|
+
A minimal bind-mount provider that shells out to local processes (no container):
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
import {
|
|
509
|
+
createBindMountSandboxProvider,
|
|
510
|
+
type BindMountCreateOptions,
|
|
511
|
+
type BindMountSandboxHandle,
|
|
512
|
+
type ExecResult,
|
|
513
|
+
} from "@ai-hero/sandcastle";
|
|
514
|
+
import { execFile, spawn } from "node:child_process";
|
|
515
|
+
import { createInterface } from "node:readline";
|
|
516
|
+
|
|
517
|
+
const localProcess = () =>
|
|
518
|
+
createBindMountSandboxProvider({
|
|
519
|
+
name: "local-process",
|
|
520
|
+
branchStrategy: { type: "merge-to-head" },
|
|
521
|
+
create: async (
|
|
522
|
+
options: BindMountCreateOptions,
|
|
523
|
+
): Promise<BindMountSandboxHandle> => {
|
|
524
|
+
const workspacePath = options.worktreePath;
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
workspacePath,
|
|
528
|
+
|
|
529
|
+
exec: (command: string, opts?: { cwd?: string }): Promise<ExecResult> =>
|
|
530
|
+
new Promise((resolve, reject) => {
|
|
531
|
+
execFile(
|
|
532
|
+
"sh",
|
|
533
|
+
["-c", command],
|
|
534
|
+
{ cwd: opts?.cwd ?? workspacePath, maxBuffer: 10 * 1024 * 1024 },
|
|
535
|
+
(error, stdout, stderr) => {
|
|
536
|
+
if (error && error.code === undefined) {
|
|
537
|
+
reject(new Error(`exec failed: ${error.message}`));
|
|
538
|
+
} else {
|
|
539
|
+
resolve({
|
|
540
|
+
stdout: stdout.toString(),
|
|
541
|
+
stderr: stderr.toString(),
|
|
542
|
+
exitCode: typeof error?.code === "number" ? error.code : 0,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
);
|
|
547
|
+
}),
|
|
548
|
+
|
|
549
|
+
execStreaming: (
|
|
550
|
+
command: string,
|
|
551
|
+
onLine: (line: string) => void,
|
|
552
|
+
opts?: { cwd?: string },
|
|
553
|
+
): Promise<ExecResult> =>
|
|
554
|
+
new Promise((resolve, reject) => {
|
|
555
|
+
const proc = spawn("sh", ["-c", command], {
|
|
556
|
+
cwd: opts?.cwd ?? workspacePath,
|
|
557
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const stdoutChunks: string[] = [];
|
|
561
|
+
const stderrChunks: string[] = [];
|
|
562
|
+
|
|
563
|
+
const rl = createInterface({ input: proc.stdout! });
|
|
564
|
+
rl.on("line", (line) => {
|
|
565
|
+
stdoutChunks.push(line);
|
|
566
|
+
onLine(line); // forward each line to Sandcastle
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
proc.stderr!.on("data", (chunk: Buffer) => {
|
|
570
|
+
stderrChunks.push(chunk.toString());
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
proc.on("error", (err) => reject(err));
|
|
574
|
+
proc.on("close", (code) => {
|
|
575
|
+
resolve({
|
|
576
|
+
stdout: stdoutChunks.join("\n"),
|
|
577
|
+
stderr: stderrChunks.join(""),
|
|
578
|
+
exitCode: code ?? 0,
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
}),
|
|
582
|
+
|
|
583
|
+
close: async () => {
|
|
584
|
+
// nothing to tear down for a local process
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Isolated provider example
|
|
592
|
+
|
|
593
|
+
A minimal isolated provider using a temp directory:
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
import {
|
|
597
|
+
createIsolatedSandboxProvider,
|
|
598
|
+
type IsolatedSandboxHandle,
|
|
599
|
+
type ExecResult,
|
|
600
|
+
} from "@ai-hero/sandcastle";
|
|
601
|
+
import { execFile, spawn } from "node:child_process";
|
|
602
|
+
import { copyFile, mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
603
|
+
import { tmpdir } from "node:os";
|
|
604
|
+
import { dirname, join } from "node:path";
|
|
605
|
+
import { createInterface } from "node:readline";
|
|
606
|
+
|
|
607
|
+
const tempDir = () =>
|
|
608
|
+
createIsolatedSandboxProvider({
|
|
609
|
+
name: "temp-dir",
|
|
610
|
+
branchStrategy: { type: "merge-to-head" },
|
|
611
|
+
create: async (): Promise<IsolatedSandboxHandle> => {
|
|
612
|
+
const root = await mkdtemp(join(tmpdir(), "sandbox-"));
|
|
613
|
+
const workspacePath = join(root, "workspace");
|
|
614
|
+
await mkdir(workspacePath, { recursive: true });
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
workspacePath,
|
|
618
|
+
|
|
619
|
+
exec: (command: string, opts?: { cwd?: string }): Promise<ExecResult> =>
|
|
620
|
+
new Promise((resolve, reject) => {
|
|
621
|
+
execFile(
|
|
622
|
+
"sh",
|
|
623
|
+
["-c", command],
|
|
624
|
+
{ cwd: opts?.cwd ?? workspacePath, maxBuffer: 10 * 1024 * 1024 },
|
|
625
|
+
(error, stdout, stderr) => {
|
|
626
|
+
if (error && error.code === undefined) {
|
|
627
|
+
reject(new Error(`exec failed: ${error.message}`));
|
|
628
|
+
} else {
|
|
629
|
+
resolve({
|
|
630
|
+
stdout: stdout.toString(),
|
|
631
|
+
stderr: stderr.toString(),
|
|
632
|
+
exitCode: typeof error?.code === "number" ? error.code : 0,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
);
|
|
637
|
+
}),
|
|
638
|
+
|
|
639
|
+
execStreaming: (
|
|
640
|
+
command: string,
|
|
641
|
+
onLine: (line: string) => void,
|
|
642
|
+
opts?: { cwd?: string },
|
|
643
|
+
): Promise<ExecResult> =>
|
|
644
|
+
new Promise((resolve, reject) => {
|
|
645
|
+
const proc = spawn("sh", ["-c", command], {
|
|
646
|
+
cwd: opts?.cwd ?? workspacePath,
|
|
647
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
const stdoutChunks: string[] = [];
|
|
651
|
+
const stderrChunks: string[] = [];
|
|
652
|
+
|
|
653
|
+
const rl = createInterface({ input: proc.stdout! });
|
|
654
|
+
rl.on("line", (line) => {
|
|
655
|
+
stdoutChunks.push(line);
|
|
656
|
+
onLine(line);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
proc.stderr!.on("data", (chunk: Buffer) => {
|
|
660
|
+
stderrChunks.push(chunk.toString());
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
proc.on("error", (err) => reject(err));
|
|
664
|
+
proc.on("close", (code) => {
|
|
665
|
+
resolve({
|
|
666
|
+
stdout: stdoutChunks.join("\n"),
|
|
667
|
+
stderr: stderrChunks.join(""),
|
|
668
|
+
exitCode: code ?? 0,
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
}),
|
|
672
|
+
|
|
673
|
+
copyIn: async (hostPath: string, sandboxPath: string) => {
|
|
674
|
+
await mkdir(dirname(sandboxPath), { recursive: true });
|
|
675
|
+
await copyFile(hostPath, sandboxPath);
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
copyOut: async (sandboxPath: string, hostPath: string) => {
|
|
679
|
+
await mkdir(dirname(hostPath), { recursive: true });
|
|
680
|
+
await copyFile(sandboxPath, hostPath);
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
close: async () => {
|
|
684
|
+
await rm(root, { recursive: true, force: true });
|
|
685
|
+
},
|
|
686
|
+
};
|
|
687
|
+
},
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Branch strategies
|
|
692
|
+
|
|
693
|
+
A branch strategy controls where the agent's commits land. Configure it when constructing the provider:
|
|
694
|
+
|
|
695
|
+
| Strategy | Behavior | Bind-mount | Isolated |
|
|
696
|
+
| --------------- | ------------------------------------------------------------------------ | ---------- | --------- |
|
|
697
|
+
| `head` | Agent writes directly to the host working directory. No worktree created | Default | N/A |
|
|
698
|
+
| `merge-to-head` | Sandcastle creates a temp branch, merges back to HEAD when done | Supported | Default |
|
|
699
|
+
| `branch` | Commits land on an explicit named branch you provide | Supported | Supported |
|
|
700
|
+
|
|
701
|
+
**When to use each:**
|
|
702
|
+
|
|
703
|
+
- **`head`** — fast iteration during development. No branch indirection, no merge step. Only works with bind-mount providers since the agent needs direct host filesystem access.
|
|
704
|
+
- **`merge-to-head`** — safe default for automation. The agent works on a throwaway branch; if something goes wrong, HEAD is untouched. Use this for CI or unattended runs.
|
|
705
|
+
- **`branch`** — when you want commits on a specific branch (e.g. for a PR). Pass `{ type: "branch", branch: "agent/fix-42" }`.
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
// head — direct write, bind-mount only
|
|
709
|
+
const provider = localProcess();
|
|
710
|
+
// merge-to-head — temp branch, merge back (default for isolated)
|
|
711
|
+
const provider = tempDir();
|
|
712
|
+
// branch — explicit named branch
|
|
713
|
+
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
714
|
+
const provider = docker({
|
|
715
|
+
branchStrategy: { type: "branch", branch: "agent/fix-42" },
|
|
716
|
+
});
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### Passing to `run()`
|
|
720
|
+
|
|
721
|
+
Pass your custom provider via the `sandbox` option — it works the same as the built-in `docker()` provider:
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
import { run, claudeCode } from "@ai-hero/sandcastle";
|
|
725
|
+
|
|
726
|
+
const result = await run({
|
|
727
|
+
agent: claudeCode("claude-opus-4-6"),
|
|
728
|
+
sandbox: localProcess(), // your custom provider
|
|
729
|
+
prompt: "Fix issue #42 in this repo.",
|
|
730
|
+
});
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Reference implementations
|
|
734
|
+
|
|
735
|
+
For real-world examples, see:
|
|
736
|
+
|
|
737
|
+
- [`src/sandboxes/docker.ts`](src/sandboxes/docker.ts) — bind-mount provider using Docker containers
|
|
738
|
+
- [`src/sandboxes/test-isolated.ts`](src/sandboxes/test-isolated.ts) — isolated provider using temp directories (used in tests)
|
|
739
|
+
|
|
473
740
|
## Configuration
|
|
474
741
|
|
|
475
742
|
### Config directory (`.sandcastle/`)
|
|
@@ -503,7 +770,7 @@ Hooks are arrays of `{ "command": "..." }` objects executed sequentially inside
|
|
|
503
770
|
| ---------------- | -------------------------- | ---------------------- |
|
|
504
771
|
| `onSandboxReady` | After the sandbox is ready | Sandbox repo directory |
|
|
505
772
|
|
|
506
|
-
**`onSandboxReady`** runs after the
|
|
773
|
+
**`onSandboxReady`** runs after the sandbox is ready. Use it for dependency installation or build steps (e.g., `npm install`).
|
|
507
774
|
|
|
508
775
|
Pass hooks programmatically via `run()`:
|
|
509
776
|
|
package/dist/AgentProvider.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
export interface TokenUsage {
|
|
2
|
-
readonly input_tokens: number;
|
|
3
|
-
readonly output_tokens: number;
|
|
4
|
-
readonly cache_read_input_tokens: number;
|
|
5
|
-
readonly cache_creation_input_tokens: number;
|
|
6
|
-
readonly total_cost_usd: number;
|
|
7
|
-
readonly num_turns: number;
|
|
8
|
-
readonly duration_ms: number;
|
|
9
|
-
}
|
|
10
1
|
export type ParsedStreamEvent = {
|
|
11
2
|
type: "text";
|
|
12
3
|
text: string;
|
|
13
4
|
} | {
|
|
14
5
|
type: "result";
|
|
15
6
|
result: string;
|
|
16
|
-
usage: TokenUsage | null;
|
|
17
7
|
} | {
|
|
18
8
|
type: "tool_call";
|
|
19
9
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentProvider.d.ts","sourceRoot":"","sources":["../src/AgentProvider.ts"],"names":[],"mappings":"AAAA,MAAM,
|
|
1
|
+
{"version":3,"file":"AgentProvider.d.ts","sourceRoot":"","sources":["../src/AgentProvider.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AA6DtD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/C,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAAC;CACpD;AAED,eAAO,MAAM,aAAa,oBAAoB,CAAC;AA2D/C,eAAO,MAAM,EAAE,kCAcb,CAAC;AAwCH,eAAO,MAAM,KAAK,kCAchB,CAAC;AAMH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;CACrD;AAED,eAAO,MAAM,UAAU,2EAoBrB,CAAC"}
|
package/dist/AgentProvider.js
CHANGED
|
@@ -1,25 +1,4 @@
|
|
|
1
1
|
const shellEscape = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
2
|
-
const extractUsage = (obj) => {
|
|
3
|
-
const usage = obj.usage;
|
|
4
|
-
if (!usage ||
|
|
5
|
-
typeof usage.input_tokens !== "number" ||
|
|
6
|
-
typeof usage.output_tokens !== "number") {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
return {
|
|
10
|
-
input_tokens: usage.input_tokens,
|
|
11
|
-
output_tokens: usage.output_tokens,
|
|
12
|
-
cache_read_input_tokens: typeof usage.cache_read_input_tokens === "number"
|
|
13
|
-
? usage.cache_read_input_tokens
|
|
14
|
-
: 0,
|
|
15
|
-
cache_creation_input_tokens: typeof usage.cache_creation_input_tokens === "number"
|
|
16
|
-
? usage.cache_creation_input_tokens
|
|
17
|
-
: 0,
|
|
18
|
-
total_cost_usd: typeof obj.total_cost_usd === "number" ? obj.total_cost_usd : 0,
|
|
19
|
-
num_turns: typeof obj.num_turns === "number" ? obj.num_turns : 0,
|
|
20
|
-
duration_ms: typeof obj.duration_ms === "number" ? obj.duration_ms : 0,
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
2
|
/** Maps allowlisted tool names to the input field containing the display arg */
|
|
24
3
|
const TOOL_ARG_FIELDS = {
|
|
25
4
|
Bash: "command",
|
|
@@ -65,7 +44,7 @@ const parseStreamJsonLine = (line) => {
|
|
|
65
44
|
return events;
|
|
66
45
|
}
|
|
67
46
|
if (obj.type === "result" && typeof obj.result === "string") {
|
|
68
|
-
return [{ type: "result", result: obj.result
|
|
47
|
+
return [{ type: "result", result: obj.result }];
|
|
69
48
|
}
|
|
70
49
|
}
|
|
71
50
|
catch {
|
|
@@ -82,42 +61,46 @@ const parsePiStreamLine = (line) => {
|
|
|
82
61
|
return [];
|
|
83
62
|
try {
|
|
84
63
|
const obj = JSON.parse(line);
|
|
85
|
-
if (obj.type === "message_update" &&
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
texts.push(block.text);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (texts.length > 0) {
|
|
93
|
-
return [{ type: "text", text: texts.join("") }];
|
|
64
|
+
if (obj.type === "message_update" && obj.assistantMessageEvent) {
|
|
65
|
+
const evt = obj.assistantMessageEvent;
|
|
66
|
+
if (evt.type === "text_delta" && typeof evt.delta === "string") {
|
|
67
|
+
return [{ type: "text", text: evt.delta }];
|
|
94
68
|
}
|
|
95
69
|
return [];
|
|
96
70
|
}
|
|
97
71
|
if (obj.type === "tool_execution_start") {
|
|
98
|
-
const toolName = obj.
|
|
72
|
+
const toolName = obj.toolName;
|
|
99
73
|
if (typeof toolName !== "string")
|
|
100
74
|
return [];
|
|
101
75
|
const argField = TOOL_ARG_FIELDS[toolName];
|
|
102
76
|
if (argField === undefined)
|
|
103
77
|
return [];
|
|
104
|
-
const
|
|
105
|
-
if (!
|
|
78
|
+
const args = obj.args;
|
|
79
|
+
if (!args)
|
|
106
80
|
return [];
|
|
107
|
-
const argValue =
|
|
81
|
+
const argValue = args[argField];
|
|
108
82
|
if (typeof argValue !== "string")
|
|
109
83
|
return [];
|
|
110
84
|
return [{ type: "tool_call", name: toolName, args: argValue }];
|
|
111
85
|
}
|
|
112
|
-
if (obj.type === "agent_end" &&
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
86
|
+
if (obj.type === "agent_end" && Array.isArray(obj.messages)) {
|
|
87
|
+
const messages = obj.messages;
|
|
88
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
89
|
+
const msg = messages[i];
|
|
90
|
+
if (msg?.role === "assistant") {
|
|
91
|
+
const texts = [];
|
|
92
|
+
for (const block of msg.content) {
|
|
93
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
94
|
+
texts.push(block.text);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (texts.length > 0) {
|
|
98
|
+
return [{ type: "result", result: texts.join("") }];
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
121
104
|
}
|
|
122
105
|
}
|
|
123
106
|
catch {
|
|
@@ -152,7 +135,7 @@ const parseCodexStreamLine = (line) => {
|
|
|
152
135
|
const text = obj.item.content;
|
|
153
136
|
return [
|
|
154
137
|
{ type: "text", text },
|
|
155
|
-
{ type: "result", result: text
|
|
138
|
+
{ type: "result", result: text },
|
|
156
139
|
];
|
|
157
140
|
}
|
|
158
141
|
// item.started with command_execution → tool call
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentProvider.js","sourceRoot":"","sources":["../src/AgentProvider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AgentProvider.js","sourceRoot":"","sources":["../src/AgentProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,9 +23,13 @@ export declare const startContainer: (containerName: string, imageName: string,
|
|
|
23
23
|
/**
|
|
24
24
|
* Fix ownership of a directory inside the container.
|
|
25
25
|
* Runs as root so the target owner can write to the path.
|
|
26
|
+
*
|
|
27
|
+
* Non-fatal: if chown fails (e.g. read-only .git/objects on macOS VirtioFS),
|
|
28
|
+
* a warning is logged but the error is not propagated.
|
|
29
|
+
*
|
|
26
30
|
* @param owner - chown-compatible owner spec, e.g. "1000:1000" or "agent"
|
|
27
31
|
*/
|
|
28
|
-
export declare const chownInContainer: (containerName: string, owner: string, path: string) => Effect.Effect<void,
|
|
32
|
+
export declare const chownInContainer: (containerName: string, owner: string, path: string) => Effect.Effect<void, never, never>;
|
|
29
33
|
/**
|
|
30
34
|
* Stop and remove a container without removing the image.
|
|
31
35
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DockerLifecycle.d.ts","sourceRoot":"","sources":["../src/DockerLifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAwB1C;;;;;;GAMG;AACH,eAAO,MAAM,UAAU;;yDAkBnB,CAAC;AAEL,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,iKAiDvB,CAAC;AAEL
|
|
1
|
+
{"version":3,"file":"DockerLifecycle.d.ts","sourceRoot":"","sources":["../src/DockerLifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAwB1C;;;;;;GAMG;AACH,eAAO,MAAM,UAAU;;yDAkBnB,CAAC;AAEL,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,iKAiDvB,CAAC;AAEL;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,2FAuB1B,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,eAAe,oEAQxB,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,WAAW,gEAKpB,CAAC"}
|