@ai-hero/sandcastle 0.9.0 → 0.12.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 +110 -52
- package/dist/{chunk-72UVAC7B.js → chunk-62WN33RK.js} +10 -5
- package/dist/chunk-62WN33RK.js.map +1 -0
- package/dist/{chunk-NSFQW6ML.js → chunk-CP3TYXZA.js} +3 -3
- package/dist/{chunk-NSFQW6ML.js.map → chunk-CP3TYXZA.js.map} +1 -1
- package/dist/{chunk-52CIJF45.js → chunk-DJRHWPEH.js} +3 -3
- package/dist/{chunk-52CIJF45.js.map → chunk-DJRHWPEH.js.map} +1 -1
- package/dist/{chunk-5VM5QZ26.js → chunk-VOG34SRF.js} +72 -48
- package/dist/{chunk-5VM5QZ26.js.map → chunk-VOG34SRF.js.map} +1 -1
- package/dist/index.d.ts +135 -21
- package/dist/index.js +253 -59
- package/dist/index.js.map +1 -1
- package/dist/main.js +6 -6
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +1 -1
- package/dist/sandboxes/docker.js +2 -2
- package/dist/sandboxes/no-sandbox.d.ts +1 -1
- package/dist/sandboxes/no-sandbox.js +1 -1
- package/dist/sandboxes/podman.d.ts +1 -1
- package/dist/sandboxes/podman.js +2 -2
- package/dist/sandboxes/podman.js.map +1 -1
- package/dist/sandboxes/vercel.d.ts +1 -1
- package/dist/sandboxes/vercel.js.map +1 -1
- package/dist/templates/blank/main.mts +1 -1
- package/dist/templates/parallel-planner/main.mts +1 -1
- package/dist/templates/parallel-planner-with-review/main.mts +1 -1
- package/dist/templates/simple-loop/main.mts +1 -1
- package/package.json +1 -1
- package/dist/chunk-72UVAC7B.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { NodeContext_exports, NodeFileSystem_exports, formatErrorMessage } from './chunk-
|
|
3
|
-
import { Context_exports, CwdError, Effect_exports, resolveCwd, getCurrentBranch, generateTempBranchName, Layer_exports, FileDisplay, ClackDisplay, WorktreeDockerSandboxFactory, SandboxConfig, Display, pruneStale, create, copyToWorktree, runHostHooks, startSandbox, resolveGitMounts, SANDBOX_REPO_DIR, remove, makeSandboxFromHandle, syncOut, withSandboxLifecycle, hasUncommittedChanges,
|
|
2
|
+
import { NodeContext_exports, NodeFileSystem_exports, formatErrorMessage } from './chunk-DJRHWPEH.js';
|
|
3
|
+
import { Context_exports, CwdError, Effect_exports, resolveCwd, getCurrentBranch, generateTempBranchName, Layer_exports, FileDisplay, ClackDisplay, WorktreeDockerSandboxFactory, SandboxConfig, Display, pruneStale, create, copyToWorktree, runHostHooks, startSandbox, resolveGitMounts, patchGitMountsForWindows, SANDBOX_REPO_DIR, remove, makeSandboxFromHandle, syncOut, withSandboxLifecycle, hasUncommittedChanges, registerShutdown, SandboxFactory, PromptError, FileSystem_exports, SessionCaptureError, Clock_exports, Duration_exports, Option_exports, PromptExpansionTimeoutError, Deferred_exports, AgentError, Ref_exports, SilentDisplay, AgentIdleTimeoutError, Fiber_exports } from './chunk-VOG34SRF.js';
|
|
4
4
|
export { createBindMountSandboxProvider, createIsolatedSandboxProvider } from './chunk-BIWNFKGV.js';
|
|
5
|
-
import { noSandbox } from './chunk-
|
|
5
|
+
import { noSandbox } from './chunk-62WN33RK.js';
|
|
6
6
|
import './chunk-NGBM7T3E.js';
|
|
7
|
+
import { mkdirSync, appendFileSync } from 'fs';
|
|
7
8
|
import path, { join, posix, dirname, relative } from 'path';
|
|
8
9
|
import { styleText } from 'util';
|
|
9
10
|
import * as clack from '@clack/prompts';
|
|
@@ -177,7 +178,7 @@ var TextDeltaBuffer = class {
|
|
|
177
178
|
|
|
178
179
|
// src/Orchestrator.ts
|
|
179
180
|
var IDLE_WARNING_INTERVAL_MS = 6e4;
|
|
180
|
-
var invokeAgent = (sandbox, sandboxRepoDir, prompt, provider, idleTimeoutMs, completionTimeoutMs, completionSignals, onText, onToolCall, onIdleWarning, onCompletionTimeout, idleWarningIntervalMs = IDLE_WARNING_INTERVAL_MS, resumeSession, forkSession, signal) => Effect_exports.gen(function* () {
|
|
181
|
+
var invokeAgent = (sandbox, sandboxRepoDir, prompt, provider, idleTimeoutMs, completionTimeoutMs, completionSignals, onText, onToolCall, onRawLine, onIdleWarning, onCompletionTimeout, idleWarningIntervalMs = IDLE_WARNING_INTERVAL_MS, resumeSession, forkSession, signal) => Effect_exports.gen(function* () {
|
|
181
182
|
let resultText = "";
|
|
182
183
|
let sessionId;
|
|
183
184
|
let usage;
|
|
@@ -256,6 +257,10 @@ var invokeAgent = (sandbox, sandboxRepoDir, prompt, provider, idleTimeoutMs, com
|
|
|
256
257
|
});
|
|
257
258
|
const execResult = yield* sandbox.exec(printCmd.command, {
|
|
258
259
|
onLine: (line) => {
|
|
260
|
+
try {
|
|
261
|
+
onRawLine(line);
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
259
264
|
for (const parsed of provider.parseStreamLine(line)) {
|
|
260
265
|
if (parsed.type === "text") {
|
|
261
266
|
onText(parsed.text);
|
|
@@ -367,7 +372,8 @@ var orchestrate = (options) => {
|
|
|
367
372
|
hostWorktreePath,
|
|
368
373
|
applyToHost,
|
|
369
374
|
signal: options.signal,
|
|
370
|
-
timeouts: options.timeouts
|
|
375
|
+
timeouts: options.timeouts,
|
|
376
|
+
keepSourceBranch: options.keepSourceBranch
|
|
371
377
|
},
|
|
372
378
|
sandbox,
|
|
373
379
|
(ctx) => Effect_exports.gen(function* () {
|
|
@@ -395,7 +401,7 @@ var orchestrate = (options) => {
|
|
|
395
401
|
);
|
|
396
402
|
yield* display.status(label("Agent started"), "success");
|
|
397
403
|
const textBuffer = new TextDeltaBuffer((chunk) => {
|
|
398
|
-
Effect_exports.runPromise(display.
|
|
404
|
+
Effect_exports.runPromise(display.textChunk(chunk));
|
|
399
405
|
Effect_exports.runPromise(
|
|
400
406
|
streamEmitter.emit({
|
|
401
407
|
type: "text",
|
|
@@ -421,6 +427,16 @@ var orchestrate = (options) => {
|
|
|
421
427
|
})
|
|
422
428
|
);
|
|
423
429
|
};
|
|
430
|
+
const onRawLine = (line) => {
|
|
431
|
+
Effect_exports.runPromise(
|
|
432
|
+
streamEmitter.emit({
|
|
433
|
+
type: "raw",
|
|
434
|
+
line,
|
|
435
|
+
iteration: i,
|
|
436
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
};
|
|
424
440
|
const onIdleWarning = (minutes) => {
|
|
425
441
|
const msg = minutes === 1 ? "Agent idle for 1 minute" : `Agent idle for ${minutes} minutes`;
|
|
426
442
|
Effect_exports.runPromise(display.status(label(msg), "warn"));
|
|
@@ -449,6 +465,7 @@ var orchestrate = (options) => {
|
|
|
449
465
|
completionSignals,
|
|
450
466
|
onText,
|
|
451
467
|
onToolCall,
|
|
468
|
+
onRawLine,
|
|
452
469
|
onIdleWarning,
|
|
453
470
|
onCompletionTimeout,
|
|
454
471
|
options._idleWarningIntervalMs,
|
|
@@ -735,20 +752,30 @@ var Output = {
|
|
|
735
752
|
* Declare an object-typed structured output extracted from an XML tag in
|
|
736
753
|
* the agent's stdout. The tag contents are JSON-parsed (with fence-aware
|
|
737
754
|
* unwrapping) and validated against the provided Standard Schema validator.
|
|
755
|
+
*
|
|
756
|
+
* Set `maxRetries` to have `run()` automatically resume the failed session
|
|
757
|
+
* and ask the agent to re-emit corrected output when extraction or
|
|
758
|
+
* validation fails. Default: `0` (no retries).
|
|
738
759
|
*/
|
|
739
760
|
object: (opts) => ({
|
|
740
761
|
_tag: "object",
|
|
741
762
|
tag: opts.tag,
|
|
742
|
-
schema: opts.schema
|
|
763
|
+
schema: opts.schema,
|
|
764
|
+
maxRetries: opts.maxRetries
|
|
743
765
|
}),
|
|
744
766
|
/**
|
|
745
767
|
* Declare a string-typed structured output extracted from an XML tag in
|
|
746
768
|
* the agent's stdout. The tag contents are whitespace-trimmed and returned
|
|
747
769
|
* as a plain string — no JSON parsing, no schema validation.
|
|
770
|
+
*
|
|
771
|
+
* Set `maxRetries` to have `run()` automatically resume the failed session
|
|
772
|
+
* and ask the agent to re-emit corrected output when extraction fails.
|
|
773
|
+
* Default: `0` (no retries).
|
|
748
774
|
*/
|
|
749
775
|
string: (opts) => ({
|
|
750
776
|
_tag: "string",
|
|
751
|
-
tag: opts.tag
|
|
777
|
+
tag: opts.tag,
|
|
778
|
+
maxRetries: opts.maxRetries
|
|
752
779
|
})
|
|
753
780
|
};
|
|
754
781
|
var StructuredOutputError = class extends Error {
|
|
@@ -854,6 +881,24 @@ var unwrapFences = (text2) => {
|
|
|
854
881
|
};
|
|
855
882
|
|
|
856
883
|
// src/run.ts
|
|
884
|
+
var buildStructuredOutputRetryFeedback = (error, retriesRemaining) => {
|
|
885
|
+
const raw = error.rawMatched === void 0 ? "(no matching tag was emitted)" : error.rawMatched;
|
|
886
|
+
const cause = error.cause === void 0 ? "(no parser detail)" : typeof error.cause === "string" ? error.cause : JSON.stringify(error.cause, null, 2);
|
|
887
|
+
return `Your previous response did not produce valid structured output.
|
|
888
|
+
|
|
889
|
+
Retries remaining after this attempt: ${retriesRemaining}.
|
|
890
|
+
|
|
891
|
+
Problem:
|
|
892
|
+
${error.message}
|
|
893
|
+
|
|
894
|
+
Parser detail:
|
|
895
|
+
${cause}
|
|
896
|
+
|
|
897
|
+
Previous matched output:
|
|
898
|
+
${raw}
|
|
899
|
+
|
|
900
|
+
Emit only a corrected <${error.tag}> block. Do not change files or run commands.`;
|
|
901
|
+
};
|
|
857
902
|
var DEFAULT_MAX_ITERATIONS = 1;
|
|
858
903
|
var sanitizeBranchForFilename = (branch) => branch.replace(/[/\\:*?"<>|]/g, "-");
|
|
859
904
|
var printFileDisplayStartup = (options) => {
|
|
@@ -896,6 +941,40 @@ var formatContextWindowSize = (usage) => {
|
|
|
896
941
|
return `${Math.ceil(total / 1e3)}k`;
|
|
897
942
|
};
|
|
898
943
|
var buildContextWindowLines = (iterations) => iterations.filter((it) => it.usage !== void 0).map((it) => `Context window: ${formatContextWindowSize(it.usage)}`);
|
|
944
|
+
var buildAgentStreamHandler = (logging) => {
|
|
945
|
+
const userHandler = logging.type === "file" ? logging.onAgentStreamEvent : void 0;
|
|
946
|
+
const verboseSink = logging.verbose ? buildVerboseRawLineSink(logging) : void 0;
|
|
947
|
+
if (!userHandler && !verboseSink) return void 0;
|
|
948
|
+
return (event) => {
|
|
949
|
+
if (userHandler) {
|
|
950
|
+
try {
|
|
951
|
+
userHandler(event);
|
|
952
|
+
} catch {
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (verboseSink && event.type === "raw") {
|
|
956
|
+
verboseSink(event.line);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
};
|
|
960
|
+
var buildVerboseRawLineSink = (logging) => {
|
|
961
|
+
if (logging.type === "file") {
|
|
962
|
+
const logPath = logging.path;
|
|
963
|
+
try {
|
|
964
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
965
|
+
} catch {
|
|
966
|
+
}
|
|
967
|
+
return (line) => {
|
|
968
|
+
try {
|
|
969
|
+
appendFileSync(logPath, line + "\n");
|
|
970
|
+
} catch {
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return (line) => {
|
|
975
|
+
process.stdout.write(line + "\n");
|
|
976
|
+
};
|
|
977
|
+
};
|
|
899
978
|
async function run(options) {
|
|
900
979
|
options.signal?.throwIfAborted();
|
|
901
980
|
const {
|
|
@@ -932,6 +1011,17 @@ async function run(options) {
|
|
|
932
1011
|
"output requires maxIterations to be 1. Structured output is only supported for single-iteration runs."
|
|
933
1012
|
);
|
|
934
1013
|
}
|
|
1014
|
+
const outputMaxRetries = options.output?.maxRetries ?? 0;
|
|
1015
|
+
if (outputMaxRetries < 0 || !Number.isInteger(outputMaxRetries)) {
|
|
1016
|
+
throw new Error(
|
|
1017
|
+
`output.maxRetries must be a non-negative integer. Received: ${outputMaxRetries}`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
if (outputMaxRetries > 0 && !provider.sessionStorage) {
|
|
1021
|
+
throw new Error(
|
|
1022
|
+
`output.maxRetries requires an agent provider that supports session resumption. The "${provider.name}" provider does not. Use claudeCode, codex, or pi, or set maxRetries to 0.`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
935
1025
|
const branch = branchStrategy.type === "branch" ? branchStrategy.branch : void 0;
|
|
936
1026
|
const hostRepoDir = await Effect_exports.runPromise(
|
|
937
1027
|
resolveCwd(options.cwd).pipe(Effect_exports.provide(NodeContext_exports.layer))
|
|
@@ -1013,7 +1103,7 @@ async function run(options) {
|
|
|
1013
1103
|
)
|
|
1014
1104
|
);
|
|
1015
1105
|
const streamEmitterLayer = agentStreamEmitterLayer(
|
|
1016
|
-
resolvedLogging
|
|
1106
|
+
buildAgentStreamHandler(resolvedLogging)
|
|
1017
1107
|
);
|
|
1018
1108
|
const runLayer = Layer_exports.mergeAll(
|
|
1019
1109
|
factoryLayer,
|
|
@@ -1133,18 +1223,41 @@ async function run(options) {
|
|
|
1133
1223
|
};
|
|
1134
1224
|
if (options.output) {
|
|
1135
1225
|
const lastIteration = baseResult.iterations.at(-1);
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1226
|
+
try {
|
|
1227
|
+
const output = await extractStructuredOutput(
|
|
1228
|
+
baseResult.stdout,
|
|
1229
|
+
options.output,
|
|
1230
|
+
{
|
|
1231
|
+
commits: baseResult.commits,
|
|
1232
|
+
branch: baseResult.branch,
|
|
1233
|
+
preservedWorktreePath: baseResult.preservedWorktreePath,
|
|
1234
|
+
sessionId: lastIteration?.sessionId,
|
|
1235
|
+
sessionFilePath: lastIteration?.sessionFilePath
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
return { ...baseResult, output };
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
if (error instanceof StructuredOutputError && outputMaxRetries > 0 && error.sessionId !== void 0) {
|
|
1241
|
+
const retriesRemainingAfter = outputMaxRetries - 1;
|
|
1242
|
+
const retryOutput = {
|
|
1243
|
+
...options.output,
|
|
1244
|
+
maxRetries: retriesRemainingAfter
|
|
1245
|
+
};
|
|
1246
|
+
return run({
|
|
1247
|
+
...options,
|
|
1248
|
+
prompt: buildStructuredOutputRetryFeedback(
|
|
1249
|
+
error,
|
|
1250
|
+
retriesRemainingAfter
|
|
1251
|
+
),
|
|
1252
|
+
promptFile: void 0,
|
|
1253
|
+
promptArgs: void 0,
|
|
1254
|
+
resumeSession: error.sessionId,
|
|
1255
|
+
forkSession: false,
|
|
1256
|
+
output: retryOutput
|
|
1257
|
+
});
|
|
1145
1258
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1259
|
+
throw error;
|
|
1260
|
+
}
|
|
1148
1261
|
}
|
|
1149
1262
|
return baseResult;
|
|
1150
1263
|
}
|
|
@@ -1313,14 +1426,20 @@ var interactive = async (options) => {
|
|
|
1313
1426
|
return startResult.handle;
|
|
1314
1427
|
} else {
|
|
1315
1428
|
const gitPath = join(hostRepoDir, ".git");
|
|
1316
|
-
const
|
|
1429
|
+
const rawGitMounts = yield* resolveGitMounts(gitPath);
|
|
1430
|
+
const worktreeOrRepoPath = isHeadMode ? hostRepoDir : worktreeInfo.path;
|
|
1431
|
+
const gitMounts = yield* patchGitMountsForWindows(
|
|
1432
|
+
rawGitMounts,
|
|
1433
|
+
worktreeOrRepoPath,
|
|
1434
|
+
SANDBOX_REPO_DIR
|
|
1435
|
+
);
|
|
1317
1436
|
const startResult = yield* d.taskLog(
|
|
1318
1437
|
"Starting sandbox",
|
|
1319
1438
|
() => startSandbox({
|
|
1320
1439
|
provider: sandboxProvider,
|
|
1321
1440
|
hostRepoDir,
|
|
1322
1441
|
env: effectiveEnv,
|
|
1323
|
-
worktreeOrRepoPath
|
|
1442
|
+
worktreeOrRepoPath,
|
|
1324
1443
|
gitMounts,
|
|
1325
1444
|
repoDir: SANDBOX_REPO_DIR
|
|
1326
1445
|
})
|
|
@@ -1442,9 +1561,12 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1442
1561
|
sandboxRepoDir,
|
|
1443
1562
|
sandbox,
|
|
1444
1563
|
providerHandle,
|
|
1564
|
+
bindMountHandle,
|
|
1445
1565
|
applyToHost,
|
|
1446
|
-
timeouts
|
|
1566
|
+
timeouts,
|
|
1567
|
+
branchStrategy
|
|
1447
1568
|
} = ctx;
|
|
1569
|
+
const mergeToHead = branchStrategy?.type === "merge-to-head";
|
|
1448
1570
|
const sandboxHandle = {
|
|
1449
1571
|
branch,
|
|
1450
1572
|
worktreePath,
|
|
@@ -1456,6 +1578,24 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1456
1578
|
promptFile,
|
|
1457
1579
|
maxIterations = 1
|
|
1458
1580
|
} = runOptions;
|
|
1581
|
+
if (runOptions.resumeSession && maxIterations > 1) {
|
|
1582
|
+
throw new Error(
|
|
1583
|
+
"resumeSession cannot be combined with maxIterations > 1. Resume applies to iteration 1 only; multi-iteration resume semantics are not supported."
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
if (runOptions.forkSession && !runOptions.resumeSession) {
|
|
1587
|
+
throw new Error(
|
|
1588
|
+
"forkSession requires resumeSession. Use sandboxRunResult.fork(prompt) to fork the most recent captured session."
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
if (runOptions.resumeSession) {
|
|
1592
|
+
await assertResumeSessionExists({
|
|
1593
|
+
provider,
|
|
1594
|
+
sandboxTag: ctx.providerTag,
|
|
1595
|
+
hostRepoDir,
|
|
1596
|
+
resumeSession: runOptions.resumeSession
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1459
1599
|
const resolved = await Effect_exports.runPromise(
|
|
1460
1600
|
resolvePrompt({ prompt, promptFile }).pipe(
|
|
1461
1601
|
Effect_exports.provide(NodeContext_exports.layer)
|
|
@@ -1514,7 +1654,8 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1514
1654
|
{
|
|
1515
1655
|
hostWorktreePath: worktreePath,
|
|
1516
1656
|
sandboxRepoPath: sandboxRepoDir,
|
|
1517
|
-
applyToHost
|
|
1657
|
+
applyToHost,
|
|
1658
|
+
bindMountHandle
|
|
1518
1659
|
},
|
|
1519
1660
|
sandbox
|
|
1520
1661
|
).pipe(
|
|
@@ -1525,7 +1666,7 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1525
1666
|
)
|
|
1526
1667
|
});
|
|
1527
1668
|
const streamEmitterLayer = agentStreamEmitterLayer(
|
|
1528
|
-
resolvedLogging
|
|
1669
|
+
buildAgentStreamHandler(resolvedLogging)
|
|
1529
1670
|
);
|
|
1530
1671
|
const runLayer = Layer_exports.mergeAll(
|
|
1531
1672
|
reuseFactoryLayer,
|
|
@@ -1542,15 +1683,18 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1542
1683
|
hostRepoDir,
|
|
1543
1684
|
iterations: maxIterations,
|
|
1544
1685
|
prompt: resolvedPrompt,
|
|
1545
|
-
branch,
|
|
1686
|
+
branch: mergeToHead ? void 0 : branch,
|
|
1546
1687
|
provider,
|
|
1547
1688
|
completionSignal: runOptions.completionSignal,
|
|
1548
1689
|
idleTimeoutSeconds: runOptions.idleTimeoutSeconds,
|
|
1549
1690
|
completionTimeoutSeconds: runOptions.completionTimeoutSeconds,
|
|
1550
1691
|
name: runOptions.name,
|
|
1692
|
+
resumeSession: runOptions.resumeSession,
|
|
1693
|
+
forkSession: runOptions.forkSession,
|
|
1551
1694
|
signal: runOptions.signal,
|
|
1552
1695
|
skipPromptExpansion: isInlinePrompt,
|
|
1553
|
-
timeouts
|
|
1696
|
+
timeouts,
|
|
1697
|
+
keepSourceBranch: mergeToHead
|
|
1554
1698
|
});
|
|
1555
1699
|
const completion = buildCompletionMessage(
|
|
1556
1700
|
orchestrateResult.completionSignal,
|
|
@@ -1569,13 +1713,38 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1569
1713
|
runOptions.signal?.throwIfAborted();
|
|
1570
1714
|
throw error;
|
|
1571
1715
|
}
|
|
1572
|
-
|
|
1716
|
+
const baseResult = {
|
|
1573
1717
|
iterations: result.iterations,
|
|
1574
1718
|
completionSignal: result.completionSignal,
|
|
1575
1719
|
stdout: result.stdout,
|
|
1576
1720
|
commits: result.commits,
|
|
1577
1721
|
logFilePath: resolvedLogging.type === "file" ? resolvedLogging.path : void 0
|
|
1578
1722
|
};
|
|
1723
|
+
const lastIteration = result.iterations.at(-1);
|
|
1724
|
+
if (provider.sessionStorage && lastIteration?.sessionId) {
|
|
1725
|
+
const capturedSessionId = lastIteration.sessionId;
|
|
1726
|
+
return {
|
|
1727
|
+
...baseResult,
|
|
1728
|
+
resume: (nextPrompt, resumeOptions) => sandboxHandle.run({
|
|
1729
|
+
...runOptions,
|
|
1730
|
+
...resumeOptions,
|
|
1731
|
+
prompt: nextPrompt,
|
|
1732
|
+
promptFile: void 0,
|
|
1733
|
+
maxIterations: 1,
|
|
1734
|
+
resumeSession: capturedSessionId
|
|
1735
|
+
}),
|
|
1736
|
+
fork: (nextPrompt, forkOptions) => sandboxHandle.run({
|
|
1737
|
+
...runOptions,
|
|
1738
|
+
...forkOptions,
|
|
1739
|
+
prompt: nextPrompt,
|
|
1740
|
+
promptFile: void 0,
|
|
1741
|
+
maxIterations: 1,
|
|
1742
|
+
resumeSession: capturedSessionId,
|
|
1743
|
+
forkSession: true
|
|
1744
|
+
})
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
return baseResult;
|
|
1579
1748
|
},
|
|
1580
1749
|
interactive: async (interactiveOptions) => {
|
|
1581
1750
|
interactiveOptions.signal?.throwIfAborted();
|
|
@@ -1624,10 +1793,11 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1624
1793
|
{
|
|
1625
1794
|
hostRepoDir,
|
|
1626
1795
|
sandboxRepoDir,
|
|
1627
|
-
branch,
|
|
1796
|
+
branch: mergeToHead ? void 0 : branch,
|
|
1628
1797
|
hostWorktreePath: worktreePath,
|
|
1629
1798
|
applyToHost,
|
|
1630
|
-
timeouts
|
|
1799
|
+
timeouts,
|
|
1800
|
+
keepSourceBranch: mergeToHead
|
|
1631
1801
|
},
|
|
1632
1802
|
sandbox,
|
|
1633
1803
|
(ctx2) => Effect_exports.gen(function* () {
|
|
@@ -1686,6 +1856,13 @@ var buildSandboxHandle = (ctx, close) => {
|
|
|
1686
1856
|
exitCode: lifecycleResult.result
|
|
1687
1857
|
};
|
|
1688
1858
|
},
|
|
1859
|
+
exec: async (command, options) => {
|
|
1860
|
+
const mergedOptions = { cwd: sandboxRepoDir, ...options };
|
|
1861
|
+
if (providerHandle) {
|
|
1862
|
+
return providerHandle.exec(command, mergedOptions);
|
|
1863
|
+
}
|
|
1864
|
+
return Effect_exports.runPromise(sandbox.exec(command, mergedOptions));
|
|
1865
|
+
},
|
|
1689
1866
|
close: async () => close(),
|
|
1690
1867
|
[Symbol.asyncDispose]: async () => {
|
|
1691
1868
|
await sandboxHandle.close();
|
|
@@ -1713,6 +1890,7 @@ var createSandboxFromWorktree = async (options) => {
|
|
|
1713
1890
|
if (isTestMode) {
|
|
1714
1891
|
sandbox = options._test.buildSandbox(worktreePath);
|
|
1715
1892
|
sandboxRepoDir = worktreePath;
|
|
1893
|
+
providerHandle = options._test.bindMountHandle;
|
|
1716
1894
|
} else {
|
|
1717
1895
|
const resolvedEnv = await Effect_exports.runPromise(
|
|
1718
1896
|
resolveEnv(hostRepoDir).pipe(Effect_exports.provide(NodeContext_exports.layer))
|
|
@@ -1744,16 +1922,7 @@ var createSandboxFromWorktree = async (options) => {
|
|
|
1744
1922
|
Effect_exports.catchAll(() => Effect_exports.succeed([])),
|
|
1745
1923
|
// Patch git mounts for Windows worktree compatibility (ADR-0006)
|
|
1746
1924
|
Effect_exports.flatMap(
|
|
1747
|
-
(gitMounts) =>
|
|
1748
|
-
try: () => patchGitMountsForWindows(
|
|
1749
|
-
gitMounts,
|
|
1750
|
-
worktreePath,
|
|
1751
|
-
SANDBOX_REPO_DIR
|
|
1752
|
-
),
|
|
1753
|
-
catch: (e) => new Error(
|
|
1754
|
-
`Failed to patch git mounts: ${e instanceof Error ? e.message : String(e)}`
|
|
1755
|
-
)
|
|
1756
|
-
})
|
|
1925
|
+
(gitMounts) => patchGitMountsForWindows(gitMounts, worktreePath, SANDBOX_REPO_DIR)
|
|
1757
1926
|
),
|
|
1758
1927
|
Effect_exports.flatMap(
|
|
1759
1928
|
(gitMounts) => startSandbox({
|
|
@@ -1798,6 +1967,7 @@ var createSandboxFromWorktree = async (options) => {
|
|
|
1798
1967
|
}
|
|
1799
1968
|
const applyToHost = isIsolated && providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect_exports.void;
|
|
1800
1969
|
let closed = false;
|
|
1970
|
+
const bindMountHandle = options.sandbox.tag === "bind-mount" ? providerHandle : void 0;
|
|
1801
1971
|
return buildSandboxHandle(
|
|
1802
1972
|
{
|
|
1803
1973
|
branch,
|
|
@@ -1806,8 +1976,11 @@ var createSandboxFromWorktree = async (options) => {
|
|
|
1806
1976
|
sandboxRepoDir,
|
|
1807
1977
|
sandbox,
|
|
1808
1978
|
providerHandle,
|
|
1979
|
+
bindMountHandle,
|
|
1980
|
+
providerTag: options.sandbox.tag,
|
|
1809
1981
|
applyToHost,
|
|
1810
|
-
timeouts: options.timeouts
|
|
1982
|
+
timeouts: options.timeouts,
|
|
1983
|
+
branchStrategy: options.branchStrategy
|
|
1811
1984
|
},
|
|
1812
1985
|
async () => {
|
|
1813
1986
|
if (closed) return { preservedWorktreePath: void 0 };
|
|
@@ -1852,6 +2025,7 @@ var createSandbox = async (options) => {
|
|
|
1852
2025
|
if (isTestMode) {
|
|
1853
2026
|
sandbox2 = options._test.buildSandbox(worktreePath2);
|
|
1854
2027
|
sandboxRepoDir2 = worktreePath2;
|
|
2028
|
+
providerHandle2 = options._test.bindMountHandle;
|
|
1855
2029
|
} else {
|
|
1856
2030
|
const resolvedEnv = yield* resolveEnv(hostRepoDir2);
|
|
1857
2031
|
const env = mergeProviderEnv({
|
|
@@ -1875,16 +2049,11 @@ var createSandbox = async (options) => {
|
|
|
1875
2049
|
Effect_exports.catchAll(() => Effect_exports.succeed([])),
|
|
1876
2050
|
// Patch git mounts for Windows worktree compatibility (ADR-0006)
|
|
1877
2051
|
Effect_exports.flatMap(
|
|
1878
|
-
(gitMounts) =>
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
),
|
|
1884
|
-
catch: (e) => new Error(
|
|
1885
|
-
`Failed to patch git mounts: ${e instanceof Error ? e.message : String(e)}`
|
|
1886
|
-
)
|
|
1887
|
-
})
|
|
2052
|
+
(gitMounts) => patchGitMountsForWindows(
|
|
2053
|
+
gitMounts,
|
|
2054
|
+
worktreePath2,
|
|
2055
|
+
SANDBOX_REPO_DIR
|
|
2056
|
+
)
|
|
1888
2057
|
),
|
|
1889
2058
|
Effect_exports.flatMap(
|
|
1890
2059
|
(gitMounts) => startSandbox({
|
|
@@ -1969,6 +2138,7 @@ Worktree preserved at ${worktreePath}`);
|
|
|
1969
2138
|
})
|
|
1970
2139
|
);
|
|
1971
2140
|
};
|
|
2141
|
+
const bindMountHandle = options.sandbox.tag === "bind-mount" ? providerHandle : void 0;
|
|
1972
2142
|
return buildSandboxHandle(
|
|
1973
2143
|
{
|
|
1974
2144
|
branch,
|
|
@@ -1977,6 +2147,8 @@ Worktree preserved at ${worktreePath}`);
|
|
|
1977
2147
|
sandboxRepoDir,
|
|
1978
2148
|
sandbox,
|
|
1979
2149
|
providerHandle,
|
|
2150
|
+
bindMountHandle,
|
|
2151
|
+
providerTag: options.sandbox.tag,
|
|
1980
2152
|
applyToHost,
|
|
1981
2153
|
timeouts: options.timeouts
|
|
1982
2154
|
},
|
|
@@ -1989,6 +2161,7 @@ Worktree preserved at ${worktreePath}`);
|
|
|
1989
2161
|
var createWorktree = async (options) => {
|
|
1990
2162
|
const branch = options.branchStrategy.type === "branch" ? options.branchStrategy.branch : void 0;
|
|
1991
2163
|
const baseBranch = options.branchStrategy.type === "branch" ? options.branchStrategy.baseBranch : void 0;
|
|
2164
|
+
const isMergeToHead = options.branchStrategy.type === "merge-to-head";
|
|
1992
2165
|
const { hostRepoDir, worktreeInfo } = await Effect_exports.gen(function* () {
|
|
1993
2166
|
const hostRepoDir2 = yield* resolveCwd(options.cwd);
|
|
1994
2167
|
yield* pruneStale(hostRepoDir2).pipe(
|
|
@@ -2094,7 +2267,12 @@ var createWorktree = async (options) => {
|
|
|
2094
2267
|
handle = startResult.handle;
|
|
2095
2268
|
} else {
|
|
2096
2269
|
const gitPath = join(hostRepoDir, ".git");
|
|
2097
|
-
const
|
|
2270
|
+
const rawGitMounts = yield* resolveGitMounts(gitPath);
|
|
2271
|
+
const gitMounts = yield* patchGitMountsForWindows(
|
|
2272
|
+
rawGitMounts,
|
|
2273
|
+
worktreeInfo.path,
|
|
2274
|
+
SANDBOX_REPO_DIR
|
|
2275
|
+
);
|
|
2098
2276
|
const startResult = yield* d.taskLog(
|
|
2099
2277
|
"Starting sandbox",
|
|
2100
2278
|
() => startSandbox({
|
|
@@ -2123,10 +2301,14 @@ var createWorktree = async (options) => {
|
|
|
2123
2301
|
hostRepoDir,
|
|
2124
2302
|
sandboxRepoDir: worktreePath,
|
|
2125
2303
|
hooks,
|
|
2126
|
-
|
|
2304
|
+
// merge-to-head: pass `undefined` so the lifecycle records the
|
|
2305
|
+
// host's current branch and merges the worktree's commits back into
|
|
2306
|
+
// it. branch strategy: pin to the worktree's branch.
|
|
2307
|
+
branch: isMergeToHead ? void 0 : worktreeInfo.branch,
|
|
2127
2308
|
hostWorktreePath: worktreeInfo.path,
|
|
2128
2309
|
applyToHost,
|
|
2129
|
-
timeouts: options.timeouts
|
|
2310
|
+
timeouts: options.timeouts,
|
|
2311
|
+
keepSourceBranch: isMergeToHead
|
|
2130
2312
|
},
|
|
2131
2313
|
sandbox,
|
|
2132
2314
|
(ctx) => Effect_exports.gen(function* () {
|
|
@@ -2254,7 +2436,12 @@ var createWorktree = async (options) => {
|
|
|
2254
2436
|
sandboxRepoDir = startResult.worktreePath;
|
|
2255
2437
|
} else {
|
|
2256
2438
|
const gitPath = join(hostRepoDir, ".git");
|
|
2257
|
-
const
|
|
2439
|
+
const rawGitMounts = yield* resolveGitMounts(gitPath);
|
|
2440
|
+
const gitMounts = yield* patchGitMountsForWindows(
|
|
2441
|
+
rawGitMounts,
|
|
2442
|
+
worktreeInfo.path,
|
|
2443
|
+
SANDBOX_REPO_DIR
|
|
2444
|
+
);
|
|
2258
2445
|
const startResult = yield* startSandbox({
|
|
2259
2446
|
provider: sandboxProvider,
|
|
2260
2447
|
hostRepoDir,
|
|
@@ -2288,12 +2475,14 @@ var createWorktree = async (options) => {
|
|
|
2288
2475
|
NodeFileSystem_exports.layer
|
|
2289
2476
|
);
|
|
2290
2477
|
})() : ClackDisplay.layer;
|
|
2478
|
+
const bindMountHandle = sandboxProvider.tag === "bind-mount" ? handle : void 0;
|
|
2291
2479
|
const reuseFactoryLayer = Layer_exports.succeed(SandboxFactory, {
|
|
2292
2480
|
withSandbox: (makeEffect) => makeEffect(
|
|
2293
2481
|
{
|
|
2294
2482
|
hostWorktreePath: worktreeInfo.path,
|
|
2295
2483
|
sandboxRepoPath: sandboxRepoDir,
|
|
2296
|
-
applyToHost
|
|
2484
|
+
applyToHost,
|
|
2485
|
+
bindMountHandle
|
|
2297
2486
|
},
|
|
2298
2487
|
sandbox
|
|
2299
2488
|
).pipe(
|
|
@@ -2304,7 +2493,7 @@ var createWorktree = async (options) => {
|
|
|
2304
2493
|
)
|
|
2305
2494
|
});
|
|
2306
2495
|
const streamEmitterLayer = agentStreamEmitterLayer(
|
|
2307
|
-
resolvedLogging
|
|
2496
|
+
buildAgentStreamHandler(resolvedLogging)
|
|
2308
2497
|
);
|
|
2309
2498
|
const runLayer = Layer_exports.mergeAll(
|
|
2310
2499
|
reuseFactoryLayer,
|
|
@@ -2319,7 +2508,10 @@ var createWorktree = async (options) => {
|
|
|
2319
2508
|
iterations: maxIterations,
|
|
2320
2509
|
hooks,
|
|
2321
2510
|
prompt: resolvedPrompt,
|
|
2322
|
-
|
|
2511
|
+
// merge-to-head: pass `undefined` so the lifecycle records the host's
|
|
2512
|
+
// current branch and routes through the merge step. branch strategy:
|
|
2513
|
+
// pin to the worktree's branch so commits stay there.
|
|
2514
|
+
branch: isMergeToHead ? void 0 : worktreeInfo.branch,
|
|
2323
2515
|
provider,
|
|
2324
2516
|
completionSignal: opts.completionSignal,
|
|
2325
2517
|
idleTimeoutSeconds: opts.idleTimeoutSeconds,
|
|
@@ -2328,7 +2520,8 @@ var createWorktree = async (options) => {
|
|
|
2328
2520
|
resumeSession: opts.resumeSession,
|
|
2329
2521
|
signal: opts.signal,
|
|
2330
2522
|
skipPromptExpansion: isInlinePrompt,
|
|
2331
|
-
timeouts: options.timeouts
|
|
2523
|
+
timeouts: options.timeouts,
|
|
2524
|
+
keepSourceBranch: isMergeToHead
|
|
2332
2525
|
});
|
|
2333
2526
|
const completion = buildCompletionMessage(
|
|
2334
2527
|
orchestrateResult.completionSignal,
|
|
@@ -2378,6 +2571,7 @@ var createWorktree = async (options) => {
|
|
|
2378
2571
|
hooks: opts.hooks,
|
|
2379
2572
|
copyToWorktree: opts.copyToWorktree,
|
|
2380
2573
|
timeouts: opts.timeouts,
|
|
2574
|
+
branchStrategy: options.branchStrategy,
|
|
2381
2575
|
_test: opts._test
|
|
2382
2576
|
});
|
|
2383
2577
|
};
|