@ai-hero/sandcastle 0.2.4 → 0.3.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 +26 -13
- package/dist/SandboxFactory.d.ts +15 -4
- package/dist/SandboxFactory.d.ts.map +1 -1
- package/dist/SandboxFactory.js +156 -203
- package/dist/SandboxFactory.js.map +1 -1
- package/dist/SandboxProvider.d.ts +114 -0
- package/dist/SandboxProvider.d.ts.map +1 -0
- package/dist/SandboxProvider.js +25 -0
- package/dist/SandboxProvider.js.map +1 -0
- package/dist/cli.d.ts +7 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -9
- package/dist/cli.js.map +1 -1
- package/dist/createSandbox.d.ts +3 -2
- package/dist/createSandbox.d.ts.map +1 -1
- package/dist/createSandbox.js +19 -32
- package/dist/createSandbox.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/run.d.ts +4 -9
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -15
- package/dist/run.js.map +1 -1
- package/dist/sandboxExec.d.ts +12 -0
- package/dist/sandboxExec.d.ts.map +1 -0
- package/dist/sandboxExec.js +26 -0
- package/dist/sandboxExec.js.map +1 -0
- package/dist/sandboxes/docker.d.ts +26 -0
- package/dist/sandboxes/docker.d.ts.map +1 -0
- package/dist/sandboxes/docker.js +133 -0
- package/dist/sandboxes/docker.js.map +1 -0
- package/dist/sandboxes/test-isolated.d.ts +17 -0
- package/dist/sandboxes/test-isolated.d.ts.map +1 -0
- package/dist/sandboxes/test-isolated.js +86 -0
- package/dist/sandboxes/test-isolated.js.map +1 -0
- package/dist/syncIn.d.ts +22 -0
- package/dist/syncIn.d.ts.map +1 -0
- package/dist/syncIn.js +57 -0
- package/dist/syncIn.js.map +1 -0
- package/dist/syncOut.d.ts +25 -0
- package/dist/syncOut.d.ts.map +1 -0
- package/dist/syncOut.js +192 -0
- package/dist/syncOut.js.map +1 -0
- package/dist/templates/blank/main.mts +2 -0
- package/dist/templates/parallel-planner/main.mts +4 -0
- package/dist/templates/sequential-reviewer/main.mts +3 -0
- package/dist/templates/simple-loop/main.mts +4 -0
- package/package.json +5 -1
package/dist/syncOut.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync-out: extract changes from an isolated sandbox back to the host.
|
|
3
|
+
*
|
|
4
|
+
* Two-phase approach:
|
|
5
|
+
* 1. Save phase: eagerly save all artifacts (patches, diff, untracked files)
|
|
6
|
+
* to `.sandcastle/patches/<timestamp>/` before attempting to apply.
|
|
7
|
+
* 2. Apply phase: apply from the saved directory.
|
|
8
|
+
* - On success: clean up the patch directory.
|
|
9
|
+
* - On failure: preserve the patch directory and print recovery commands.
|
|
10
|
+
*
|
|
11
|
+
* Three-prong extraction within each phase:
|
|
12
|
+
* 1. Committed changes: `git format-patch` + `git am --3way`
|
|
13
|
+
* 2. Uncommitted changes (staged + unstaged): `git diff HEAD` + `git apply`
|
|
14
|
+
* 3. Untracked files: `git ls-files --others` + `copyOut` each file
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile, } from "node:fs/promises";
|
|
18
|
+
import { basename, dirname, join } from "node:path";
|
|
19
|
+
import { buildRecoveryMessage } from "./RecoveryMessage.js";
|
|
20
|
+
import { execHost, execOk } from "./sandboxExec.js";
|
|
21
|
+
/**
|
|
22
|
+
* Check if a patch file is empty or header-only.
|
|
23
|
+
* Merge commits produce patches with headers but no diff content.
|
|
24
|
+
* A patch is considered empty if it has no lines starting with "diff --git".
|
|
25
|
+
*/
|
|
26
|
+
const isEmptyPatch = async (patchPath) => {
|
|
27
|
+
const info = await stat(patchPath);
|
|
28
|
+
if (info.size === 0)
|
|
29
|
+
return true;
|
|
30
|
+
const content = await readFile(patchPath, "utf-8");
|
|
31
|
+
return !content.includes("diff --git");
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Generate a YYYYMMDD-HHMMSS timestamp directory name.
|
|
35
|
+
* Appends a counter suffix (-1, -2, ...) if the directory already exists.
|
|
36
|
+
*/
|
|
37
|
+
const createPatchDir = async (hostRepoDir) => {
|
|
38
|
+
const now = new Date();
|
|
39
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
40
|
+
const base = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
41
|
+
const patchesRoot = join(hostRepoDir, ".sandcastle", "patches");
|
|
42
|
+
await mkdir(patchesRoot, { recursive: true });
|
|
43
|
+
let dirName = base;
|
|
44
|
+
let counter = 0;
|
|
45
|
+
while (existsSync(join(patchesRoot, dirName))) {
|
|
46
|
+
counter++;
|
|
47
|
+
dirName = `${base}-${counter}`;
|
|
48
|
+
}
|
|
49
|
+
const patchDir = join(patchesRoot, dirName);
|
|
50
|
+
await mkdir(patchDir, { recursive: true });
|
|
51
|
+
return patchDir;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Sync changes from an isolated sandbox back to the host repo.
|
|
55
|
+
*
|
|
56
|
+
* Two-phase extraction with artifact persistence:
|
|
57
|
+
* 1. Save all artifacts to `.sandcastle/patches/<timestamp>/`
|
|
58
|
+
* 2. Apply from saved directory; on failure, preserve artifacts and print recovery
|
|
59
|
+
*/
|
|
60
|
+
export const syncOut = async (hostRepoDir, handle) => {
|
|
61
|
+
const workspacePath = handle.workspacePath;
|
|
62
|
+
const hostHead = (await execHost("git rev-parse HEAD", hostRepoDir)).trim();
|
|
63
|
+
const sandboxHead = (await execOk(handle, "git rev-parse HEAD", { cwd: workspacePath })).stdout.trim();
|
|
64
|
+
const hasCommits = hostHead !== sandboxHead;
|
|
65
|
+
// Check for uncommitted changes
|
|
66
|
+
const diffResult = await handle.exec("git diff HEAD", { cwd: workspacePath });
|
|
67
|
+
const hasDiff = diffResult.exitCode === 0 && diffResult.stdout.trim().length > 0;
|
|
68
|
+
// Check for untracked files
|
|
69
|
+
const lsFilesResult = await handle.exec("git ls-files --others --exclude-standard", { cwd: workspacePath });
|
|
70
|
+
const hasUntracked = lsFilesResult.exitCode === 0 && lsFilesResult.stdout.trim().length > 0;
|
|
71
|
+
const untrackedFiles = hasUntracked
|
|
72
|
+
? lsFilesResult.stdout
|
|
73
|
+
.trim()
|
|
74
|
+
.split("\n")
|
|
75
|
+
.filter((f) => f.length > 0)
|
|
76
|
+
: [];
|
|
77
|
+
// Nothing to sync
|
|
78
|
+
if (!hasCommits && !hasDiff && !hasUntracked) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// --- Phase 1: Save all artifacts ---
|
|
82
|
+
const patchDir = await createPatchDir(hostRepoDir);
|
|
83
|
+
const relativePatchDir = join(".sandcastle", "patches", basename(patchDir));
|
|
84
|
+
const nonEmptyPatches = [];
|
|
85
|
+
// Save committed patches
|
|
86
|
+
if (hasCommits) {
|
|
87
|
+
const mkTempResult = await execOk(handle, "mktemp -d -t sandcastle-patches-XXXXXX");
|
|
88
|
+
const sandboxPatchDir = mkTempResult.stdout.trim();
|
|
89
|
+
try {
|
|
90
|
+
await execOk(handle, `git format-patch "${hostHead}..HEAD" -o "${sandboxPatchDir}"`, { cwd: workspacePath });
|
|
91
|
+
const lsResult = await execOk(handle, `ls -1 "${sandboxPatchDir}"`);
|
|
92
|
+
const patchNames = lsResult.stdout
|
|
93
|
+
.trim()
|
|
94
|
+
.split("\n")
|
|
95
|
+
.filter((name) => name.length > 0);
|
|
96
|
+
for (const patchName of patchNames) {
|
|
97
|
+
const sandboxPatchPath = `${sandboxPatchDir}/${patchName}`;
|
|
98
|
+
const hostPatchPath = join(patchDir, patchName);
|
|
99
|
+
await handle.copyOut(sandboxPatchPath, hostPatchPath);
|
|
100
|
+
if (!(await isEmptyPatch(hostPatchPath))) {
|
|
101
|
+
nonEmptyPatches.push(hostPatchPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await handle.exec(`rm -rf "${sandboxPatchDir}"`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Save uncommitted diff
|
|
110
|
+
if (hasDiff) {
|
|
111
|
+
const diffPath = join(patchDir, "changes.patch");
|
|
112
|
+
await writeFile(diffPath, diffResult.stdout);
|
|
113
|
+
}
|
|
114
|
+
// Save untracked files
|
|
115
|
+
if (hasUntracked) {
|
|
116
|
+
const untrackedDir = join(patchDir, "untracked");
|
|
117
|
+
for (const relPath of untrackedFiles) {
|
|
118
|
+
const sandboxFilePath = `${workspacePath}/${relPath}`;
|
|
119
|
+
const hostFilePath = join(untrackedDir, relPath);
|
|
120
|
+
await mkdir(dirname(hostFilePath), { recursive: true });
|
|
121
|
+
await handle.copyOut(sandboxFilePath, hostFilePath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// --- Phase 2: Apply from saved directory ---
|
|
125
|
+
let failedStep;
|
|
126
|
+
// Apply committed patches
|
|
127
|
+
if (nonEmptyPatches.length > 0) {
|
|
128
|
+
try {
|
|
129
|
+
await execHost("git am --abort", hostRepoDir).catch(() => { });
|
|
130
|
+
const patchArgs = nonEmptyPatches.map((p) => `"${p}"`).join(" ");
|
|
131
|
+
await execHost(`git am --3way ${patchArgs}`, hostRepoDir);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
failedStep = "commits";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Apply uncommitted diff
|
|
138
|
+
if (!failedStep && hasDiff) {
|
|
139
|
+
const diffPath = join(patchDir, "changes.patch");
|
|
140
|
+
try {
|
|
141
|
+
await execHost(`git apply "${diffPath}"`, hostRepoDir);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
failedStep = "diff";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Copy untracked files
|
|
148
|
+
if (!failedStep && hasUntracked) {
|
|
149
|
+
try {
|
|
150
|
+
const untrackedDir = join(patchDir, "untracked");
|
|
151
|
+
for (const relPath of untrackedFiles) {
|
|
152
|
+
const srcPath = join(untrackedDir, relPath);
|
|
153
|
+
const destPath = join(hostRepoDir, relPath);
|
|
154
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
155
|
+
const content = await readFile(srcPath);
|
|
156
|
+
await writeFile(destPath, content);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
failedStep = "untracked";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// --- Cleanup or preserve ---
|
|
164
|
+
if (failedStep) {
|
|
165
|
+
const msg = buildRecoveryMessage({
|
|
166
|
+
patchDir: relativePatchDir,
|
|
167
|
+
failedStep,
|
|
168
|
+
hasCommits: nonEmptyPatches.length > 0,
|
|
169
|
+
hasDiff,
|
|
170
|
+
hasUntracked,
|
|
171
|
+
});
|
|
172
|
+
console.error(`\n${msg}`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
await rm(patchDir, { recursive: true, force: true });
|
|
176
|
+
// Clean up empty parent dirs
|
|
177
|
+
const patchesRoot = join(hostRepoDir, ".sandcastle", "patches");
|
|
178
|
+
try {
|
|
179
|
+
const remaining = await readdir(patchesRoot);
|
|
180
|
+
if (remaining.length === 0) {
|
|
181
|
+
await rm(join(hostRepoDir, ".sandcastle"), {
|
|
182
|
+
recursive: true,
|
|
183
|
+
force: true,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// ignore
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=syncOut.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncOut.js","sourceRoot":"","sources":["../src/syncOut.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { run, claudeCode } from "@ai-hero/sandcastle";
|
|
2
|
+
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
2
3
|
|
|
3
4
|
// Blank template: customize this to build your own orchestration.
|
|
4
5
|
// Run this with: npx tsx .sandcastle/main.mts
|
|
@@ -6,5 +7,6 @@ import { run, claudeCode } from "@ai-hero/sandcastle";
|
|
|
6
7
|
|
|
7
8
|
await run({
|
|
8
9
|
agent: claudeCode("claude-opus-4-6"),
|
|
10
|
+
sandbox: docker(),
|
|
9
11
|
promptFile: "./.sandcastle/prompt.md",
|
|
10
12
|
});
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// "scripts": { "sandcastle": "npx tsx .sandcastle/main.mts" }
|
|
18
18
|
|
|
19
19
|
import * as sandcastle from "@ai-hero/sandcastle";
|
|
20
|
+
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Configuration
|
|
@@ -56,6 +57,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
|
|
56
57
|
const plan = await sandcastle.run({
|
|
57
58
|
hooks,
|
|
58
59
|
copyToSandbox,
|
|
60
|
+
sandbox: docker(),
|
|
59
61
|
name: "planner",
|
|
60
62
|
// One iteration is enough: the planner just needs to read and reason,
|
|
61
63
|
// not write code.
|
|
@@ -105,6 +107,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
|
|
105
107
|
sandcastle.run({
|
|
106
108
|
hooks,
|
|
107
109
|
copyToSandbox,
|
|
110
|
+
sandbox: docker(),
|
|
108
111
|
name: "implementer",
|
|
109
112
|
// Give each agent plenty of room to implement and iterate on tests.
|
|
110
113
|
maxIterations: 100,
|
|
@@ -179,6 +182,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
|
|
179
182
|
await sandcastle.run({
|
|
180
183
|
hooks,
|
|
181
184
|
copyToSandbox,
|
|
185
|
+
sandbox: docker(),
|
|
182
186
|
name: "merger",
|
|
183
187
|
maxIterations: 10,
|
|
184
188
|
// Sonnet is sufficient for merge conflict resolution.
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// "scripts": { "sandcastle": "npx tsx .sandcastle/main.mts" }
|
|
18
18
|
|
|
19
19
|
import * as sandcastle from "@ai-hero/sandcastle";
|
|
20
|
+
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Configuration
|
|
@@ -57,6 +58,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
|
|
57
58
|
const implement = await sandcastle.run({
|
|
58
59
|
hooks,
|
|
59
60
|
copyToSandbox,
|
|
61
|
+
sandbox: docker(),
|
|
60
62
|
name: "implementer",
|
|
61
63
|
maxIterations: 100,
|
|
62
64
|
agent: sandcastle.claudeCode("claude-sonnet-4-6"),
|
|
@@ -84,6 +86,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
|
|
84
86
|
await sandcastle.run({
|
|
85
87
|
hooks,
|
|
86
88
|
copyToSandbox,
|
|
89
|
+
sandbox: docker(),
|
|
87
90
|
name: "reviewer",
|
|
88
91
|
maxIterations: 10,
|
|
89
92
|
agent: sandcastle.claudeCode("claude-sonnet-4-6"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { run, claudeCode } from "@ai-hero/sandcastle";
|
|
2
|
+
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
2
3
|
|
|
3
4
|
// Simple loop: an agent that picks open GitHub issues one by one and closes them.
|
|
4
5
|
// Run this with: npx tsx .sandcastle/main.mts
|
|
@@ -8,6 +9,9 @@ await run({
|
|
|
8
9
|
// A name for this run, shown as a prefix in log output.
|
|
9
10
|
name: "worker",
|
|
10
11
|
|
|
12
|
+
// Sandbox provider — Docker is the default runtime.
|
|
13
|
+
sandbox: docker(),
|
|
14
|
+
|
|
11
15
|
// The agent provider. Pass a model string to claudeCode() — sonnet balances
|
|
12
16
|
// capability and speed for most tasks. Switch to claude-opus-4-6 for harder
|
|
13
17
|
// problems, or claude-haiku-4-5-20251001 for speed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-hero/sandcastle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for orchestrating AI agents in isolated sandbox environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./sandboxes/docker": {
|
|
14
|
+
"import": "./dist/sandboxes/docker.js",
|
|
15
|
+
"types": "./dist/sandboxes/docker.d.ts"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"bin": {
|