@ai-hero/sandcastle 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +258 -69
- package/dist/AgentProvider.d.ts +9 -0
- package/dist/AgentProvider.d.ts.map +1 -1
- package/dist/AgentProvider.js +14 -2
- package/dist/AgentProvider.js.map +1 -1
- package/dist/CopyToWorktree.d.ts +9 -0
- package/dist/CopyToWorktree.d.ts.map +1 -0
- package/dist/{CopyToWorkspace.js → CopyToWorktree.js} +9 -3
- package/dist/CopyToWorktree.js.map +1 -0
- package/dist/DockerLifecycle.d.ts +2 -0
- package/dist/DockerLifecycle.d.ts.map +1 -1
- package/dist/DockerLifecycle.js +7 -0
- package/dist/DockerLifecycle.js.map +1 -1
- package/dist/ErrorHandler.d.ts.map +1 -1
- package/dist/ErrorHandler.js +22 -2
- package/dist/ErrorHandler.js.map +1 -1
- package/dist/InitService.d.ts +6 -1
- package/dist/InitService.d.ts.map +1 -1
- package/dist/InitService.js +90 -56
- package/dist/InitService.js.map +1 -1
- package/dist/MountConfig.d.ts +13 -2
- package/dist/MountConfig.d.ts.map +1 -1
- package/dist/Orchestrator.d.ts +14 -2
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +56 -12
- package/dist/Orchestrator.js.map +1 -1
- package/dist/PodmanLifecycle.d.ts +10 -0
- package/dist/PodmanLifecycle.d.ts.map +1 -1
- package/dist/PodmanLifecycle.js +22 -0
- package/dist/PodmanLifecycle.js.map +1 -1
- package/dist/PromptPreprocessor.d.ts +2 -2
- package/dist/PromptPreprocessor.d.ts.map +1 -1
- package/dist/PromptPreprocessor.js +7 -2
- package/dist/PromptPreprocessor.js.map +1 -1
- package/dist/SandboxFactory.d.ts +13 -11
- package/dist/SandboxFactory.d.ts.map +1 -1
- package/dist/SandboxFactory.js +59 -41
- package/dist/SandboxFactory.js.map +1 -1
- package/dist/SandboxLifecycle.d.ts +23 -5
- package/dist/SandboxLifecycle.d.ts.map +1 -1
- package/dist/SandboxLifecycle.js +88 -18
- package/dist/SandboxLifecycle.js.map +1 -1
- package/dist/SandboxProvider.d.ts +11 -7
- package/dist/SandboxProvider.d.ts.map +1 -1
- package/dist/SandboxProvider.js.map +1 -1
- package/dist/SessionStore.d.ts +50 -0
- package/dist/SessionStore.d.ts.map +1 -0
- package/dist/SessionStore.js +120 -0
- package/dist/SessionStore.js.map +1 -0
- package/dist/WorktreeManager.d.ts +3 -3
- package/dist/WorktreeManager.d.ts.map +1 -1
- package/dist/WorktreeManager.js +14 -3
- package/dist/WorktreeManager.js.map +1 -1
- package/dist/createSandbox.d.ts +25 -10
- package/dist/createSandbox.d.ts.map +1 -1
- package/dist/createSandbox.js +253 -133
- package/dist/createSandbox.js.map +1 -1
- package/dist/createWorktree.d.ts +121 -0
- package/dist/createWorktree.d.ts.map +1 -0
- package/dist/createWorktree.js +318 -0
- package/dist/createWorktree.js.map +1 -0
- package/dist/errors.d.ts +110 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +41 -3
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/interactive.d.ts +1 -1
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +63 -46
- package/dist/interactive.js.map +1 -1
- package/dist/run.d.ts +10 -10
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +23 -6
- package/dist/run.js.map +1 -1
- package/dist/sandboxes/daytona.js +4 -4
- package/dist/sandboxes/daytona.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +9 -0
- package/dist/sandboxes/docker.d.ts.map +1 -1
- package/dist/sandboxes/docker.js +35 -5
- package/dist/sandboxes/docker.js.map +1 -1
- package/dist/sandboxes/no-sandbox.js +4 -4
- package/dist/sandboxes/no-sandbox.js.map +1 -1
- package/dist/sandboxes/podman.d.ts +9 -0
- package/dist/sandboxes/podman.d.ts.map +1 -1
- package/dist/sandboxes/podman.js +47 -5
- package/dist/sandboxes/podman.js.map +1 -1
- package/dist/sandboxes/test-bind-mount.d.ts +17 -0
- package/dist/sandboxes/test-bind-mount.d.ts.map +1 -0
- package/dist/sandboxes/test-bind-mount.js +91 -0
- package/dist/sandboxes/test-bind-mount.js.map +1 -0
- package/dist/sandboxes/test-isolated.js +5 -5
- package/dist/sandboxes/test-isolated.js.map +1 -1
- package/dist/sandboxes/vercel.js +7 -7
- package/dist/sandboxes/vercel.js.map +1 -1
- package/dist/startSandbox.d.ts +7 -6
- package/dist/startSandbox.d.ts.map +1 -1
- package/dist/startSandbox.js +38 -19
- package/dist/startSandbox.js.map +1 -1
- package/dist/syncIn.js +7 -7
- package/dist/syncIn.js.map +1 -1
- package/dist/syncOut.js +6 -6
- package/dist/syncOut.js.map +1 -1
- package/dist/templates/parallel-planner/implement-prompt.md +2 -2
- package/dist/templates/parallel-planner/main.mts +3 -7
- package/dist/templates/parallel-planner/merge-prompt.md +5 -1
- package/dist/templates/parallel-planner-with-review/implement-prompt.md +2 -2
- package/dist/templates/parallel-planner-with-review/main.mts +29 -63
- package/dist/templates/parallel-planner-with-review/merge-prompt.md +5 -1
- package/dist/templates/sequential-reviewer/main.mts +4 -4
- package/dist/templates/simple-loop/main.mts +9 -7
- package/package.json +1 -1
- package/dist/CopyToWorkspace.d.ts +0 -8
- package/dist/CopyToWorkspace.d.ts.map +0 -1
- package/dist/CopyToWorkspace.js.map +0 -1
- package/dist/templates/blank/.env.example +0 -5
- package/dist/templates/parallel-planner/.env.example +0 -5
- package/dist/templates/parallel-planner-with-review/.env.example +0 -5
- package/dist/templates/sequential-reviewer/.env.example +0 -5
- package/dist/templates/simple-loop/.env.example +0 -5
package/README.md
CHANGED
|
@@ -65,14 +65,16 @@ await run({
|
|
|
65
65
|
|
|
66
66
|
## Sandbox Providers
|
|
67
67
|
|
|
68
|
-
Sandcastle uses a `SandboxProvider` to create isolated environments. The `sandbox` option on `run()` and `createSandbox()` accepts any provider. A no-sandbox option is also available for `interactive()`
|
|
68
|
+
Sandcastle uses a `SandboxProvider` to create isolated environments. The `sandbox` option on `run()` and `createSandbox()` accepts any provider. A no-sandbox option is also available for `interactive()` and `wt.interactive()`. Built-in providers:
|
|
69
69
|
|
|
70
|
-
| Provider | Import path | Type | Accepted by
|
|
71
|
-
| ---------- | ------------------------------------------ | ---------- |
|
|
72
|
-
| Docker | `@ai-hero/sandcastle/sandboxes/docker` | Bind-mount | `run()`, `createSandbox()`, `interactive()`
|
|
73
|
-
| Podman | `@ai-hero/sandcastle/sandboxes/podman` | Bind-mount | `run()`, `createSandbox()`, `interactive()`
|
|
74
|
-
| Vercel | `@ai-hero/sandcastle/sandboxes/vercel` | Isolated | `run()`, `createSandbox()`, `interactive()`
|
|
75
|
-
| No-sandbox | `@ai-hero/sandcastle/sandboxes/no-sandbox` | None | `interactive()`
|
|
70
|
+
| Provider | Import path | Type | Accepted by |
|
|
71
|
+
| ---------- | ------------------------------------------ | ---------- | --------------------------------------------- |
|
|
72
|
+
| Docker | `@ai-hero/sandcastle/sandboxes/docker` | Bind-mount | `run()`, `createSandbox()`, `interactive()` |
|
|
73
|
+
| Podman | `@ai-hero/sandcastle/sandboxes/podman` | Bind-mount | `run()`, `createSandbox()`, `interactive()` |
|
|
74
|
+
| Vercel | `@ai-hero/sandcastle/sandboxes/vercel` | Isolated | `run()`, `createSandbox()`, `interactive()` |
|
|
75
|
+
| No-sandbox | `@ai-hero/sandcastle/sandboxes/no-sandbox` | None | `interactive()`, `wt.interactive()` (default) |
|
|
76
|
+
|
|
77
|
+
Worktree methods (`wt.run()`, `wt.interactive()`, `wt.createSandbox()`) accept the same providers as their top-level counterparts. `wt.interactive()` defaults to `noSandbox()` when no sandbox is specified.
|
|
76
78
|
|
|
77
79
|
```typescript
|
|
78
80
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -91,7 +93,7 @@ await run({
|
|
|
91
93
|
await interactive({
|
|
92
94
|
agent: claudeCode("claude-opus-4-6"),
|
|
93
95
|
sandbox: noSandbox(),
|
|
94
|
-
prompt: "...",
|
|
96
|
+
prompt: "...", // optional — omit to launch the TUI with no initial prompt
|
|
95
97
|
});
|
|
96
98
|
```
|
|
97
99
|
|
|
@@ -111,7 +113,8 @@ const result = await run({
|
|
|
111
113
|
promptFile: ".sandcastle/prompt.md",
|
|
112
114
|
});
|
|
113
115
|
|
|
114
|
-
console.log(result.
|
|
116
|
+
console.log(result.iterations.length); // number of iterations executed
|
|
117
|
+
console.log(result.iterations); // per-iteration results with optional sessionId
|
|
115
118
|
console.log(result.commits); // array of { sha } for commits created
|
|
116
119
|
console.log(result.branch); // target branch name
|
|
117
120
|
```
|
|
@@ -132,11 +135,16 @@ const result = await run({
|
|
|
132
135
|
sandbox: docker({
|
|
133
136
|
imageName: "sandcastle:local",
|
|
134
137
|
// Optional: mount host directories into the sandbox (e.g. package manager caches)
|
|
138
|
+
// hostPath supports absolute, tilde-expanded (~), and relative paths (resolved from cwd).
|
|
139
|
+
// sandboxPath supports absolute and relative paths (resolved from the sandbox repo directory).
|
|
135
140
|
mounts: [
|
|
136
141
|
{ hostPath: "~/.npm", sandboxPath: "/home/agent/.npm", readonly: true },
|
|
142
|
+
{ hostPath: "data", sandboxPath: "data" }, // mounts <cwd>/data → <sandbox-repo>/data
|
|
137
143
|
],
|
|
138
144
|
// Optional: provider-level env vars merged at launch time
|
|
139
145
|
env: { DOCKER_SPECIFIC: "value" },
|
|
146
|
+
// Optional: attach container to Docker network(s) — string or string[]
|
|
147
|
+
network: "my-network",
|
|
140
148
|
}),
|
|
141
149
|
|
|
142
150
|
// Branch strategy — controls how the agent's changes relate to branches.
|
|
@@ -158,15 +166,20 @@ const result = await run({
|
|
|
158
166
|
// Display name for this run, shown as a prefix in log output.
|
|
159
167
|
name: "fix-issue-42",
|
|
160
168
|
|
|
161
|
-
// Lifecycle hooks
|
|
169
|
+
// Lifecycle hooks grouped by where they run: host or sandbox.
|
|
162
170
|
hooks: {
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
host: {
|
|
172
|
+
onWorktreeReady: [{ command: "cp .env.example .env" }],
|
|
173
|
+
onSandboxReady: [{ command: "echo setup done" }],
|
|
174
|
+
},
|
|
175
|
+
sandbox: {
|
|
176
|
+
onSandboxReady: [{ command: "npm install" }],
|
|
177
|
+
},
|
|
165
178
|
},
|
|
166
179
|
|
|
167
180
|
// Host-relative file paths to copy into the sandbox before the container starts.
|
|
168
181
|
// Not supported with branchStrategy: { type: "head" }.
|
|
169
|
-
|
|
182
|
+
copyToWorktree: [".env"],
|
|
170
183
|
|
|
171
184
|
// How to record progress. Default: write to a file under .sandcastle/logs/
|
|
172
185
|
logging: { type: "file", path: ".sandcastle/logs/my-run.log" },
|
|
@@ -180,7 +193,7 @@ const result = await run({
|
|
|
180
193
|
idleTimeoutSeconds: 600,
|
|
181
194
|
});
|
|
182
195
|
|
|
183
|
-
console.log(result.
|
|
196
|
+
console.log(result.iterations.length); // number of iterations executed
|
|
184
197
|
console.log(result.completionSignal); // matched signal string, or undefined if none fired
|
|
185
198
|
console.log(result.commits); // array of { sha } for commits created
|
|
186
199
|
console.log(result.branch); // target branch name
|
|
@@ -220,7 +233,7 @@ import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
|
220
233
|
await using sandbox = await createSandbox({
|
|
221
234
|
branch: "agent/fix-42",
|
|
222
235
|
sandbox: docker(),
|
|
223
|
-
hooks: { onSandboxReady: [{ command: "npm install" }] },
|
|
236
|
+
hooks: { sandbox: { onSandboxReady: [{ command: "npm install" }] } },
|
|
224
237
|
});
|
|
225
238
|
|
|
226
239
|
// Step 1: implement
|
|
@@ -263,8 +276,8 @@ if (closeResult.preservedWorktreePath) {
|
|
|
263
276
|
| -------------------------- | --------------- | ------- | ------------------------------------------------------------------------ |
|
|
264
277
|
| `branch` | string | — | **Required.** Explicit branch for the sandbox |
|
|
265
278
|
| `sandbox` | SandboxProvider | — | **Required.** Sandbox provider (e.g. `docker()`, `podman()`) |
|
|
266
|
-
| `hooks` |
|
|
267
|
-
| `
|
|
279
|
+
| `hooks` | SandboxHooks | — | Lifecycle hooks (`host.*`, `sandbox.*`) — run once at creation time |
|
|
280
|
+
| `copyToWorktree` | string[] | — | Host-relative file paths to copy into the sandbox at creation time |
|
|
268
281
|
| `throwOnDuplicateWorktree` | boolean | `true` | When `false`, reuse an existing worktree instead of failing on collision |
|
|
269
282
|
|
|
270
283
|
#### `Sandbox`
|
|
@@ -293,13 +306,13 @@ if (closeResult.preservedWorktreePath) {
|
|
|
293
306
|
|
|
294
307
|
#### `SandboxRunResult`
|
|
295
308
|
|
|
296
|
-
| Field | Type
|
|
297
|
-
| ------------------ |
|
|
298
|
-
| `
|
|
299
|
-
| `completionSignal` | string?
|
|
300
|
-
| `stdout` | string
|
|
301
|
-
| `commits` | `{ sha }[]`
|
|
302
|
-
| `logFilePath` | string?
|
|
309
|
+
| Field | Type | Description |
|
|
310
|
+
| ------------------ | ------------------- | ------------------------------------------------------------------ |
|
|
311
|
+
| `iterations` | `IterationResult[]` | Per-iteration results (use `.length` for the count) |
|
|
312
|
+
| `completionSignal` | string? | The matched completion signal string, or `undefined` if none fired |
|
|
313
|
+
| `stdout` | string | Combined agent output from all iterations |
|
|
314
|
+
| `commits` | `{ sha }[]` | Commits created during the run |
|
|
315
|
+
| `logFilePath` | string? | Path to the log file (only when logging to a file) |
|
|
303
316
|
|
|
304
317
|
#### `CloseResult`
|
|
305
318
|
|
|
@@ -307,6 +320,124 @@ if (closeResult.preservedWorktreePath) {
|
|
|
307
320
|
| ----------------------- | ------- | ------------------------------------------------------------------------ |
|
|
308
321
|
| `preservedWorktreePath` | string? | Host path to the preserved worktree, set when it had uncommitted changes |
|
|
309
322
|
|
|
323
|
+
### `createWorktree()` — independent worktree lifecycle
|
|
324
|
+
|
|
325
|
+
Use `createWorktree()` when you need a worktree (git worktree) as an independent, first-class concept — separate from any sandbox. This is useful when you want to run an interactive session first and then hand the same worktree to a sandboxed AFK agent.
|
|
326
|
+
|
|
327
|
+
Only `branch` and `merge-to-head` strategies are accepted; `head` is a compile-time type error since it means no worktree.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { createWorktree } from "@ai-hero/sandcastle";
|
|
331
|
+
|
|
332
|
+
await using wt = await createWorktree({
|
|
333
|
+
branchStrategy: { type: "branch", branch: "agent/fix-42" },
|
|
334
|
+
copyToWorktree: ["node_modules"],
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
console.log(wt.worktreePath); // host path to the worktree
|
|
338
|
+
console.log(wt.branch); // "agent/fix-42"
|
|
339
|
+
|
|
340
|
+
// Run an interactive session in the worktree (defaults to noSandbox)
|
|
341
|
+
await wt.interactive({
|
|
342
|
+
agent: claudeCode("claude-opus-4-6"),
|
|
343
|
+
prompt: "Explore the codebase and understand the bug.",
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Run an AFK agent in the worktree (sandbox is required)
|
|
347
|
+
const result = await wt.run({
|
|
348
|
+
agent: claudeCode("claude-opus-4-6"),
|
|
349
|
+
sandbox: docker({ imageName: "sandcastle:myrepo" }),
|
|
350
|
+
prompt: "Fix issue #42.",
|
|
351
|
+
maxIterations: 3,
|
|
352
|
+
});
|
|
353
|
+
console.log(result.commits); // commits made during the run
|
|
354
|
+
|
|
355
|
+
// Create a long-lived sandbox from the worktree
|
|
356
|
+
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
357
|
+
|
|
358
|
+
await using sandbox = await wt.createSandbox({
|
|
359
|
+
sandbox: docker(),
|
|
360
|
+
hooks: { sandbox: { onSandboxReady: [{ command: "npm install" }] } },
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// sandbox.close() tears down the container only — the worktree stays
|
|
364
|
+
await sandbox.close();
|
|
365
|
+
|
|
366
|
+
// wt.close() cleans up the worktree
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`wt.close()` checks for uncommitted changes: if the worktree is dirty, it's preserved on disk; if clean, it's removed. `await using` calls `close()` automatically. The worktree persists after `run()`, `interactive()`, and `createSandbox()` complete, so you can hand it to another agent or inspect it.
|
|
370
|
+
|
|
371
|
+
**Split ownership**: When a sandbox is created via `wt.createSandbox()`, `sandbox.close()` tears down the container only — the worktree remains. `wt.close()` is responsible for worktree cleanup. This differs from the top-level `createSandbox()`, where `sandbox.close()` owns both container and worktree.
|
|
372
|
+
|
|
373
|
+
#### `CreateWorktreeOptions`
|
|
374
|
+
|
|
375
|
+
| Option | Type | Default | Description |
|
|
376
|
+
| ---------------- | ---------------------- | ------- | ------------------------------------------------------------------------- |
|
|
377
|
+
| `branchStrategy` | WorktreeBranchStrategy | — | **Required.** `{ type: "branch", branch }` or `{ type: "merge-to-head" }` |
|
|
378
|
+
| `copyToWorktree` | string[] | — | Host-relative file paths to copy into the worktree at creation time |
|
|
379
|
+
|
|
380
|
+
#### `Worktree`
|
|
381
|
+
|
|
382
|
+
| Property / Method | Type | Description |
|
|
383
|
+
| ------------------------ | --------------------------------------------------------------------- | --------------------------------------------------- |
|
|
384
|
+
| `branch` | string | The branch the worktree is on |
|
|
385
|
+
| `worktreePath` | string | Host path to the worktree |
|
|
386
|
+
| `run(options)` | `(options: WorktreeRunOptions) => Promise<WorktreeRunResult>` | Run an AFK agent in the worktree (sandbox required) |
|
|
387
|
+
| `interactive(options)` | `(options: WorktreeInteractiveOptions) => Promise<InteractiveResult>` | Run an interactive agent session in the worktree |
|
|
388
|
+
| `createSandbox(options)` | `(options: WorktreeCreateSandboxOptions) => Promise<Sandbox>` | Create a long-lived sandbox backed by this worktree |
|
|
389
|
+
| `close()` | `() => Promise<CloseResult>` | Clean up the worktree (preserves if dirty) |
|
|
390
|
+
| `[Symbol.asyncDispose]` | `() => Promise<void>` | Auto cleanup via `await using` |
|
|
391
|
+
|
|
392
|
+
#### `WorktreeInteractiveOptions`
|
|
393
|
+
|
|
394
|
+
| Option | Type | Default | Description |
|
|
395
|
+
| ------------ | ---------------------- | ------------- | ---------------------------------------------------- |
|
|
396
|
+
| `agent` | AgentProvider | — | **Required.** Agent provider |
|
|
397
|
+
| `sandbox` | AnySandboxProvider | `noSandbox()` | Sandbox provider (defaults to no sandbox) |
|
|
398
|
+
| `prompt` | string | — | Inline prompt (mutually exclusive with `promptFile`) |
|
|
399
|
+
| `promptFile` | string | — | Path to prompt file |
|
|
400
|
+
| `name` | string | — | Optional session name |
|
|
401
|
+
| `hooks` | SandboxHooks | — | Lifecycle hooks (`host.*`, `sandbox.*`) |
|
|
402
|
+
| `promptArgs` | PromptArgs | — | Key-value map for `{{KEY}}` placeholder substitution |
|
|
403
|
+
| `env` | Record<string, string> | — | Environment variables to inject into the sandbox |
|
|
404
|
+
|
|
405
|
+
#### `WorktreeRunOptions`
|
|
406
|
+
|
|
407
|
+
| Option | Type | Default | Description |
|
|
408
|
+
| -------------------- | ---------------------- | ------- | ------------------------------------------------------------- |
|
|
409
|
+
| `agent` | AgentProvider | — | **Required.** Agent provider |
|
|
410
|
+
| `sandbox` | SandboxProvider | — | **Required.** Sandbox provider (AFK agents must be sandboxed) |
|
|
411
|
+
| `prompt` | string | — | Inline prompt (mutually exclusive with `promptFile`) |
|
|
412
|
+
| `promptFile` | string | — | Path to prompt file |
|
|
413
|
+
| `maxIterations` | number | 1 | Maximum iterations to run |
|
|
414
|
+
| `completionSignal` | string \| string[] | — | Substring(s) to stop the iteration loop early |
|
|
415
|
+
| `idleTimeoutSeconds` | number | 600 | Idle timeout in seconds |
|
|
416
|
+
| `name` | string | — | Optional run name |
|
|
417
|
+
| `logging` | LoggingOption | file | Logging mode |
|
|
418
|
+
| `hooks` | SandboxHooks | — | Lifecycle hooks (`host.*`, `sandbox.*`) |
|
|
419
|
+
| `promptArgs` | PromptArgs | — | Key-value map for `{{KEY}}` placeholder substitution |
|
|
420
|
+
| `env` | Record<string, string> | — | Environment variables to inject into the sandbox |
|
|
421
|
+
|
|
422
|
+
#### `WorktreeRunResult`
|
|
423
|
+
|
|
424
|
+
| Property | Type | Description |
|
|
425
|
+
| ------------------ | ------------------- | ------------------------------------------------------ |
|
|
426
|
+
| `iterations` | `IterationResult[]` | Per-iteration results (use `.length` for the count) |
|
|
427
|
+
| `completionSignal` | string | The matched completion signal, or undefined |
|
|
428
|
+
| `stdout` | string | Combined stdout output from all agent iterations |
|
|
429
|
+
| `commits` | { sha: string }[] | List of commits made by the agent during the run |
|
|
430
|
+
| `branch` | string | The branch name the agent worked on |
|
|
431
|
+
| `logFilePath` | string | Path to the log file, if logging was drained to a file |
|
|
432
|
+
|
|
433
|
+
#### `WorktreeCreateSandboxOptions`
|
|
434
|
+
|
|
435
|
+
| Option | Type | Default | Description |
|
|
436
|
+
| ---------------- | --------------- | ------- | ------------------------------------------------------------------- |
|
|
437
|
+
| `sandbox` | SandboxProvider | — | **Required.** Sandbox provider (e.g. `docker()`) |
|
|
438
|
+
| `hooks` | SandboxHooks | — | Lifecycle hooks (`host.*`, `sandbox.*`) |
|
|
439
|
+
| `copyToWorktree` | string[] | — | Host-relative file paths to copy into the worktree at creation time |
|
|
440
|
+
|
|
310
441
|
## How it works
|
|
311
442
|
|
|
312
443
|
Sandcastle uses a **branch strategy** configured on the sandbox provider to control how the agent's changes relate to branches. There are three strategies:
|
|
@@ -338,7 +469,7 @@ You must provide exactly one of:
|
|
|
338
469
|
|
|
339
470
|
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. All expressions in a prompt run **in parallel** for faster expansion.
|
|
340
471
|
|
|
341
|
-
Commands run **inside the sandbox** after `onSandboxReady` hooks complete, so they see the same repo state the agent sees (including installed dependencies).
|
|
472
|
+
Commands run **inside the sandbox** after `sandbox.onSandboxReady` hooks complete, so they see the same repo state the agent sees (including installed dependencies).
|
|
342
473
|
|
|
343
474
|
```markdown
|
|
344
475
|
# Open issues
|
|
@@ -501,26 +632,62 @@ Removes the Podman image.
|
|
|
501
632
|
| `prompt` | string | — | Inline prompt (mutually exclusive with `promptFile`) |
|
|
502
633
|
| `promptFile` | string | — | Path to prompt file (mutually exclusive with `prompt`) |
|
|
503
634
|
| `maxIterations` | number | `1` | Maximum iterations to run |
|
|
504
|
-
| `hooks` |
|
|
635
|
+
| `hooks` | SandboxHooks | — | Lifecycle hooks (`host.*`, `sandbox.*`) |
|
|
505
636
|
| `name` | string | — | Display name for the run, shown as a prefix in log output |
|
|
506
637
|
| `promptArgs` | PromptArgs | — | Key-value map for `{{KEY}}` placeholder substitution |
|
|
507
638
|
| `branchStrategy` | BranchStrategy | per-provider default | Branch strategy: `{ type: 'head' }`, `{ type: 'merge-to-head' }`, or `{ type: 'branch', branch: '…' }` |
|
|
508
|
-
| `
|
|
639
|
+
| `copyToWorktree` | string[] | — | Host-relative file paths to copy into the sandbox before start (not supported with `branchStrategy: { type: 'head' }`) |
|
|
509
640
|
| `logging` | object | file (auto-generated) | `{ type: 'file', path }` or `{ type: 'stdout' }` |
|
|
510
641
|
| `completionSignal` | string \| string[] | `<promise>COMPLETE</promise>` | String or array of strings the agent emits to stop the iteration loop early |
|
|
511
642
|
| `idleTimeoutSeconds` | number | `600` | Idle timeout in seconds — resets on each agent output event |
|
|
512
643
|
| `throwOnDuplicateWorktree` | boolean | `true` | When `false`, reuse an existing worktree for the target branch instead of failing on collision |
|
|
644
|
+
| `resumeSession` | string | — | Resume a prior Claude Code session by ID. Incompatible with `maxIterations > 1`. Session file must exist on host. |
|
|
513
645
|
|
|
514
646
|
### `RunResult`
|
|
515
647
|
|
|
516
|
-
| Field | Type
|
|
517
|
-
| ------------------ |
|
|
518
|
-
| `
|
|
519
|
-
| `completionSignal` | string?
|
|
520
|
-
| `stdout` | string
|
|
521
|
-
| `commits` | `{ sha }[]`
|
|
522
|
-
| `branch` | string
|
|
523
|
-
| `logFilePath` | string?
|
|
648
|
+
| Field | Type | Description |
|
|
649
|
+
| ------------------ | ------------------- | ------------------------------------------------------------------ |
|
|
650
|
+
| `iterations` | `IterationResult[]` | Per-iteration results (use `.length` for the count) |
|
|
651
|
+
| `completionSignal` | string? | The matched completion signal string, or `undefined` if none fired |
|
|
652
|
+
| `stdout` | string | Agent output |
|
|
653
|
+
| `commits` | `{ sha }[]` | Commits created during the run |
|
|
654
|
+
| `branch` | string | Target branch name |
|
|
655
|
+
| `logFilePath` | string? | Path to the log file (only when logging to a file) |
|
|
656
|
+
|
|
657
|
+
### `IterationResult`
|
|
658
|
+
|
|
659
|
+
| Field | Type | Description |
|
|
660
|
+
| ----------------- | ------- | ------------------------------------------------------------------------------------ |
|
|
661
|
+
| `sessionId` | string? | Claude Code session ID from the init line, or `undefined` for non-Claude agents |
|
|
662
|
+
| `sessionFilePath` | string? | Absolute host path to the captured session JSONL, or `undefined` when capture is off |
|
|
663
|
+
|
|
664
|
+
### Session capture
|
|
665
|
+
|
|
666
|
+
After each Claude Code iteration, Sandcastle automatically captures the agent's session JSONL from the sandbox to the host at `~/.claude/projects/<encoded-path>/sessions/<session-id>.jsonl`. The `cwd` fields inside each JSONL entry are rewritten to match the host repo root, so `claude --resume` works natively.
|
|
667
|
+
|
|
668
|
+
Session capture is enabled by default for `claudeCode()` and can be opted out via `captureSessions: false`. Non-Claude agent providers never attempt capture. Capture failure fails the run.
|
|
669
|
+
|
|
670
|
+
### Session resume
|
|
671
|
+
|
|
672
|
+
Pass `resumeSession` to `run()` to continue a prior Claude Code conversation inside a new sandbox:
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
const result = await run({
|
|
676
|
+
agent: claudeCode("claude-opus-4-6"),
|
|
677
|
+
sandbox: docker(),
|
|
678
|
+
prompt: "Continue where you left off",
|
|
679
|
+
resumeSession: "abc-123-def",
|
|
680
|
+
});
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
Before the sandbox starts, Sandcastle validates that the session file exists on the host and transfers it into the sandbox with `cwd` fields rewritten to match the sandbox-side path. The Claude Code agent receives `--resume <id>` on its print command for iteration 1.
|
|
684
|
+
|
|
685
|
+
Constraints:
|
|
686
|
+
|
|
687
|
+
- `resumeSession` is incompatible with `maxIterations > 1` (throws before sandbox creation).
|
|
688
|
+
- The session file must exist at `~/.claude/projects/<encoded-path>/sessions/<id>.jsonl` (throws before sandbox creation).
|
|
689
|
+
- Only iteration 1 receives the resume flag; subsequent iterations (if any) start fresh.
|
|
690
|
+
- Non-Claude agent providers ignore `resumeSession`.
|
|
524
691
|
|
|
525
692
|
### `ClaudeCodeOptions`
|
|
526
693
|
|
|
@@ -530,10 +697,11 @@ The `claudeCode()` factory accepts an optional second argument for provider-spec
|
|
|
530
697
|
agent: claudeCode("claude-opus-4-6", { effort: "high" });
|
|
531
698
|
```
|
|
532
699
|
|
|
533
|
-
| Option
|
|
534
|
-
|
|
|
535
|
-
| `effort`
|
|
536
|
-
| `env`
|
|
700
|
+
| Option | Type | Default | Description |
|
|
701
|
+
| ----------------- | -------------------------------------------- | ------- | --------------------------------------------------------- |
|
|
702
|
+
| `effort` | `"low"` \| `"medium"` \| `"high"` \| `"max"` | — | Claude Code reasoning effort level (`max` is Opus only) |
|
|
703
|
+
| `env` | `Record<string, string>` | `{}` | Environment variables injected by this agent provider |
|
|
704
|
+
| `captureSessions` | `boolean` | `true` | Capture agent session JSONL to host for `claude --resume` |
|
|
537
705
|
|
|
538
706
|
### `CodexOptions`
|
|
539
707
|
|
|
@@ -583,13 +751,14 @@ Sandcastle ships with built-in providers for Docker, Podman, and Vercel, but you
|
|
|
583
751
|
|
|
584
752
|
Both provider types return a **sandbox handle** from their `create()` function. The handle exposes:
|
|
585
753
|
|
|
586
|
-
| Method
|
|
587
|
-
|
|
|
588
|
-
| `exec`
|
|
589
|
-
| `close`
|
|
590
|
-
| `
|
|
591
|
-
| `
|
|
592
|
-
| `
|
|
754
|
+
| Method | Required | Description |
|
|
755
|
+
| -------------- | ---------- | ---------------------------------------------------------------------------- |
|
|
756
|
+
| `exec` | Both | Run a command, optionally streaming stdout line-by-line via `options.onLine` |
|
|
757
|
+
| `close` | Both | Tear down the sandbox |
|
|
758
|
+
| `copyFileIn` | Bind-mount | Copy a single file from the host into the sandbox |
|
|
759
|
+
| `copyFileOut` | Both | Copy a single file from the sandbox to the host |
|
|
760
|
+
| `copyIn` | Isolated | Copy a file or directory from the host into the sandbox |
|
|
761
|
+
| `worktreePath` | Both | Absolute path to the repo directory inside the sandbox |
|
|
593
762
|
|
|
594
763
|
### `ExecResult`
|
|
595
764
|
|
|
@@ -615,6 +784,8 @@ import {
|
|
|
615
784
|
type ExecResult,
|
|
616
785
|
} from "@ai-hero/sandcastle";
|
|
617
786
|
import { execFile, spawn } from "node:child_process";
|
|
787
|
+
import { copyFile as fsCopyFile, mkdir as fsMkdir } from "node:fs/promises";
|
|
788
|
+
import { dirname } from "node:path";
|
|
618
789
|
import { createInterface } from "node:readline";
|
|
619
790
|
|
|
620
791
|
const localProcess = () =>
|
|
@@ -623,10 +794,10 @@ const localProcess = () =>
|
|
|
623
794
|
create: async (
|
|
624
795
|
options: BindMountCreateOptions,
|
|
625
796
|
): Promise<BindMountSandboxHandle> => {
|
|
626
|
-
const
|
|
797
|
+
const worktreePath = options.worktreePath;
|
|
627
798
|
|
|
628
799
|
return {
|
|
629
|
-
|
|
800
|
+
worktreePath,
|
|
630
801
|
|
|
631
802
|
exec: (
|
|
632
803
|
command: string,
|
|
@@ -636,7 +807,7 @@ const localProcess = () =>
|
|
|
636
807
|
const onLine = opts.onLine;
|
|
637
808
|
return new Promise((resolve, reject) => {
|
|
638
809
|
const proc = spawn("sh", ["-c", command], {
|
|
639
|
-
cwd: opts?.cwd ??
|
|
810
|
+
cwd: opts?.cwd ?? worktreePath,
|
|
640
811
|
stdio: ["ignore", "pipe", "pipe"],
|
|
641
812
|
});
|
|
642
813
|
|
|
@@ -668,7 +839,7 @@ const localProcess = () =>
|
|
|
668
839
|
execFile(
|
|
669
840
|
"sh",
|
|
670
841
|
["-c", command],
|
|
671
|
-
{ cwd: opts?.cwd ??
|
|
842
|
+
{ cwd: opts?.cwd ?? worktreePath, maxBuffer: 10 * 1024 * 1024 },
|
|
672
843
|
(error, stdout, stderr) => {
|
|
673
844
|
if (error && error.code === undefined) {
|
|
674
845
|
reject(new Error(`exec failed: ${error.message}`));
|
|
@@ -684,6 +855,16 @@ const localProcess = () =>
|
|
|
684
855
|
});
|
|
685
856
|
},
|
|
686
857
|
|
|
858
|
+
copyFileIn: async (hostPath: string, sandboxPath: string) => {
|
|
859
|
+
await fsMkdir(dirname(sandboxPath), { recursive: true });
|
|
860
|
+
await fsCopyFile(hostPath, sandboxPath);
|
|
861
|
+
},
|
|
862
|
+
|
|
863
|
+
copyFileOut: async (sandboxPath: string, hostPath: string) => {
|
|
864
|
+
await fsMkdir(dirname(hostPath), { recursive: true });
|
|
865
|
+
await fsCopyFile(sandboxPath, hostPath);
|
|
866
|
+
},
|
|
867
|
+
|
|
687
868
|
close: async () => {
|
|
688
869
|
// nothing to tear down for a local process
|
|
689
870
|
},
|
|
@@ -713,11 +894,11 @@ const tempDir = () =>
|
|
|
713
894
|
name: "temp-dir",
|
|
714
895
|
create: async (): Promise<IsolatedSandboxHandle> => {
|
|
715
896
|
const root = await mkdtemp(join(tmpdir(), "sandbox-"));
|
|
716
|
-
const
|
|
717
|
-
await mkdir(
|
|
897
|
+
const worktreePath = join(root, "workspace");
|
|
898
|
+
await mkdir(worktreePath, { recursive: true });
|
|
718
899
|
|
|
719
900
|
return {
|
|
720
|
-
|
|
901
|
+
worktreePath,
|
|
721
902
|
|
|
722
903
|
exec: (
|
|
723
904
|
command: string,
|
|
@@ -727,7 +908,7 @@ const tempDir = () =>
|
|
|
727
908
|
const onLine = opts.onLine;
|
|
728
909
|
return new Promise((resolve, reject) => {
|
|
729
910
|
const proc = spawn("sh", ["-c", command], {
|
|
730
|
-
cwd: opts?.cwd ??
|
|
911
|
+
cwd: opts?.cwd ?? worktreePath,
|
|
731
912
|
stdio: ["ignore", "pipe", "pipe"],
|
|
732
913
|
});
|
|
733
914
|
|
|
@@ -759,7 +940,7 @@ const tempDir = () =>
|
|
|
759
940
|
execFile(
|
|
760
941
|
"sh",
|
|
761
942
|
["-c", command],
|
|
762
|
-
{ cwd: opts?.cwd ??
|
|
943
|
+
{ cwd: opts?.cwd ?? worktreePath, maxBuffer: 10 * 1024 * 1024 },
|
|
763
944
|
(error, stdout, stderr) => {
|
|
764
945
|
if (error && error.code === undefined) {
|
|
765
946
|
reject(new Error(`exec failed: ${error.message}`));
|
|
@@ -891,28 +1072,36 @@ Add your project-specific dependencies (e.g., language runtimes, build tools) to
|
|
|
891
1072
|
|
|
892
1073
|
### Hooks
|
|
893
1074
|
|
|
894
|
-
Hooks are
|
|
895
|
-
|
|
896
|
-
| Hook | When it runs | Working directory |
|
|
897
|
-
| ---------------- | -------------------------- | ---------------------- |
|
|
898
|
-
| `onSandboxReady` | After the sandbox is ready | Sandbox repo directory |
|
|
899
|
-
|
|
900
|
-
**`onSandboxReady`** runs after the sandbox is ready. Use it for dependency installation or build steps (e.g., `npm install`).
|
|
901
|
-
|
|
902
|
-
Set `sudo: true` to run a command with elevated privileges inside the sandbox:
|
|
1075
|
+
Hooks are grouped by **where** they run — `host` (on the developer's machine) or `sandbox` (inside the container):
|
|
903
1076
|
|
|
904
1077
|
```ts
|
|
905
|
-
|
|
906
|
-
|
|
1078
|
+
hooks: {
|
|
1079
|
+
host: {
|
|
1080
|
+
onWorktreeReady: [{ command: "cp .env.example .env" }],
|
|
1081
|
+
onSandboxReady: [{ command: "echo sandbox is up" }],
|
|
1082
|
+
},
|
|
1083
|
+
sandbox: {
|
|
907
1084
|
onSandboxReady: [
|
|
908
|
-
{ command: "apt-get install -y ffmpeg", sudo: true },
|
|
909
1085
|
{ command: "npm install" },
|
|
1086
|
+
{ command: "apt-get install -y ffmpeg", sudo: true },
|
|
910
1087
|
],
|
|
911
1088
|
},
|
|
912
|
-
|
|
913
|
-
});
|
|
1089
|
+
}
|
|
914
1090
|
```
|
|
915
1091
|
|
|
1092
|
+
| Hook | Runs on | When | Working directory |
|
|
1093
|
+
| ------------------------ | ------- | -------------------------------------------- | ------------------------------------------- |
|
|
1094
|
+
| `host.onWorktreeReady` | Host | After `copyToWorktree`, before sandbox start | Worktree path (host repo root under `head`) |
|
|
1095
|
+
| `host.onSandboxReady` | Host | After sandbox is up | Worktree path (host repo root under `head`) |
|
|
1096
|
+
| `sandbox.onSandboxReady` | Sandbox | After sandbox is up | Sandbox repo directory |
|
|
1097
|
+
|
|
1098
|
+
**Ordering:** `copyToWorktree` -> `host.onWorktreeReady` (sequential) -> sandbox created -> `host.onSandboxReady` + `sandbox.onSandboxReady` (parallel).
|
|
1099
|
+
|
|
1100
|
+
- **Host hooks** accept `{ command: string }` — no `sudo`, no `cwd`. Use `cd` or inline env in the command string.
|
|
1101
|
+
- **Sandbox hooks** accept `{ command: string; sudo?: boolean }` — set `sudo: true` for elevated privileges.
|
|
1102
|
+
- Within each hook point, sandbox hooks run in parallel; host hooks within `onSandboxReady` also run in parallel with sandbox hooks. `host.onWorktreeReady` hooks run sequentially in declared order.
|
|
1103
|
+
- If any hook exits non-zero, setup fails fast.
|
|
1104
|
+
|
|
916
1105
|
## Development
|
|
917
1106
|
|
|
918
1107
|
```bash
|
package/dist/AgentProvider.d.ts
CHANGED
|
@@ -8,16 +8,23 @@ export type ParsedStreamEvent = {
|
|
|
8
8
|
type: "tool_call";
|
|
9
9
|
name: string;
|
|
10
10
|
args: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: "session_id";
|
|
13
|
+
sessionId: string;
|
|
11
14
|
};
|
|
12
15
|
/** Options passed to buildPrintCommand and buildInteractiveArgs. */
|
|
13
16
|
export interface AgentCommandOptions {
|
|
14
17
|
readonly prompt: string;
|
|
15
18
|
readonly dangerouslySkipPermissions: boolean;
|
|
19
|
+
/** When set, the agent should resume the given session ID instead of starting fresh. */
|
|
20
|
+
readonly resumeSession?: string;
|
|
16
21
|
}
|
|
17
22
|
export interface AgentProvider {
|
|
18
23
|
readonly name: string;
|
|
19
24
|
/** Environment variables injected by this agent provider. Merged at launch time with env resolver and sandbox provider env. */
|
|
20
25
|
readonly env: Record<string, string>;
|
|
26
|
+
/** When true, session capture is enabled for this provider. Default: true for Claude Code, false for others. */
|
|
27
|
+
readonly captureSessions: boolean;
|
|
21
28
|
buildPrintCommand(options: AgentCommandOptions): string;
|
|
22
29
|
buildInteractiveArgs?(options: AgentCommandOptions): string[];
|
|
23
30
|
parseStreamLine(line: string): ParsedStreamEvent[];
|
|
@@ -46,6 +53,8 @@ export interface ClaudeCodeOptions {
|
|
|
46
53
|
readonly effort?: "low" | "medium" | "high" | "max";
|
|
47
54
|
/** Environment variables injected by this agent provider. */
|
|
48
55
|
readonly env?: Record<string, string>;
|
|
56
|
+
/** When false, session capture is disabled. Default: true. */
|
|
57
|
+
readonly captureSessions?: boolean;
|
|
49
58
|
}
|
|
50
59
|
export declare const claudeCode: (model: string, options?: ClaudeCodeOptions | undefined) => AgentProvider;
|
|
51
60
|
//# sourceMappingURL=AgentProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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,GACjD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAoE9C,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,0BAA0B,EAAE,OAAO,CAAC;IAC7C,wFAAwF;IACxF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+HAA+H;IAC/H,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,gHAAgH;IAChH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAAC;IACxD,oBAAoB,CAAC,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAAC;IAC9D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAAC;CACpD;AAED,eAAO,MAAM,aAAa,oBAAoB,CAAC;AA2D/C,yCAAyC;AACzC,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,EAAE,mEAkBb,CAAC;AAwCH,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACtD,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,KAAK,sEAwBhB,CAAC;AAMH,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,QAAQ,yEAqBnB,CAAC;AAMH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IACpD,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,eAAO,MAAM,UAAU,2EAsCrB,CAAC"}
|
package/dist/AgentProvider.js
CHANGED
|
@@ -46,6 +46,11 @@ const parseStreamJsonLine = (line) => {
|
|
|
46
46
|
if (obj.type === "result" && typeof obj.result === "string") {
|
|
47
47
|
return [{ type: "result", result: obj.result }];
|
|
48
48
|
}
|
|
49
|
+
if (obj.type === "system" &&
|
|
50
|
+
obj.subtype === "init" &&
|
|
51
|
+
typeof obj.session_id === "string") {
|
|
52
|
+
return [{ type: "session_id", sessionId: obj.session_id }];
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
catch {
|
|
51
56
|
// Not valid JSON — skip
|
|
@@ -111,6 +116,7 @@ const parsePiStreamLine = (line) => {
|
|
|
111
116
|
export const pi = (model, options) => ({
|
|
112
117
|
name: "pi",
|
|
113
118
|
env: options?.env ?? {},
|
|
119
|
+
captureSessions: false,
|
|
114
120
|
buildPrintCommand({ prompt }) {
|
|
115
121
|
return `pi -p --mode json --no-session --model ${shellEscape(model)} ${shellEscape(prompt)}`;
|
|
116
122
|
},
|
|
@@ -158,6 +164,7 @@ const parseCodexStreamLine = (line) => {
|
|
|
158
164
|
export const codex = (model, options) => ({
|
|
159
165
|
name: "codex",
|
|
160
166
|
env: options?.env ?? {},
|
|
167
|
+
captureSessions: false,
|
|
161
168
|
buildPrintCommand({ prompt }) {
|
|
162
169
|
const effortFlag = options?.effort
|
|
163
170
|
? ` -c ${shellEscape(`model_reasoning_effort="${options.effort}"`)}`
|
|
@@ -177,6 +184,7 @@ export const codex = (model, options) => ({
|
|
|
177
184
|
export const opencode = (model, options) => ({
|
|
178
185
|
name: "opencode",
|
|
179
186
|
env: options?.env ?? {},
|
|
187
|
+
captureSessions: false,
|
|
180
188
|
buildPrintCommand({ prompt }) {
|
|
181
189
|
return `opencode run --model ${shellEscape(model)} ${shellEscape(prompt)}`;
|
|
182
190
|
},
|
|
@@ -193,12 +201,16 @@ export const opencode = (model, options) => ({
|
|
|
193
201
|
export const claudeCode = (model, options) => ({
|
|
194
202
|
name: "claude-code",
|
|
195
203
|
env: options?.env ?? {},
|
|
196
|
-
|
|
204
|
+
captureSessions: options?.captureSessions ?? true,
|
|
205
|
+
buildPrintCommand({ prompt, dangerouslySkipPermissions, resumeSession, }) {
|
|
197
206
|
const skipPerms = dangerouslySkipPermissions
|
|
198
207
|
? " --dangerously-skip-permissions"
|
|
199
208
|
: "";
|
|
200
209
|
const effortFlag = options?.effort ? ` --effort ${options.effort}` : "";
|
|
201
|
-
|
|
210
|
+
const resumeFlag = resumeSession
|
|
211
|
+
? ` --resume ${shellEscape(resumeSession)}`
|
|
212
|
+
: "";
|
|
213
|
+
return `claude --print --verbose${skipPerms} --output-format stream-json --model ${shellEscape(model)}${effortFlag}${resumeFlag} -p ${shellEscape(prompt)}`;
|
|
202
214
|
},
|
|
203
215
|
buildInteractiveArgs({ prompt, dangerouslySkipPermissions, }) {
|
|
204
216
|
const args = ["claude"];
|