@snappedly-tools/shipyard 0.6.0 → 0.7.0-staging.36081473933

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +64 -165
  2. package/dist/MountConfig-BHnKnA4h.d.ts +145 -0
  3. package/dist/{chunk-WAXEMZUV.js → chunk-44I2BL6E.js} +225 -33
  4. package/dist/chunk-44I2BL6E.js.map +1 -0
  5. package/dist/{chunk-56TDSWFU.js → chunk-JI3HDDMS.js} +3 -3
  6. package/dist/{chunk-56TDSWFU.js.map → chunk-JI3HDDMS.js.map} +1 -1
  7. package/dist/{chunk-FDYOTN55.js → chunk-L6PX5QTU.js} +253 -878
  8. package/dist/chunk-L6PX5QTU.js.map +1 -0
  9. package/dist/index.d.ts +117 -97
  10. package/dist/index.js +322 -344
  11. package/dist/index.js.map +1 -1
  12. package/dist/main.js +235 -139
  13. package/dist/main.js.map +1 -1
  14. package/dist/sandboxes/docker.d.ts +1 -3
  15. package/dist/sandboxes/docker.js +2 -4
  16. package/dist/templates/parallel-planner/main.mts +67 -9
  17. package/dist/templates/parallel-planner/planner-branch.mts +151 -0
  18. package/dist/templates/parallel-planner-with-review/main.mts +70 -12
  19. package/dist/templates/parallel-planner-with-review/planner-branch.mts +151 -0
  20. package/dist/templates/sequential-reviewer/main.mts +52 -3
  21. package/dist/templates/simple-loop/main.mts +51 -2
  22. package/package.json +1 -17
  23. package/dist/MountConfig-bZoCs4Dd.d.ts +0 -26
  24. package/dist/SandboxProvider-XJQqEdSf.d.ts +0 -261
  25. package/dist/chunk-ACD46ZM4.js +0 -136
  26. package/dist/chunk-ACD46ZM4.js.map +0 -1
  27. package/dist/chunk-FDYOTN55.js.map +0 -1
  28. package/dist/chunk-KMGNFXKN.js +0 -38
  29. package/dist/chunk-KMGNFXKN.js.map +0 -1
  30. package/dist/chunk-SOJTAJTF.js +0 -78
  31. package/dist/chunk-SOJTAJTF.js.map +0 -1
  32. package/dist/chunk-WAXEMZUV.js.map +0 -1
  33. package/dist/sandboxes/no-sandbox.d.ts +0 -37
  34. package/dist/sandboxes/no-sandbox.js +0 -4
  35. package/dist/sandboxes/no-sandbox.js.map +0 -1
  36. package/dist/sandboxes/vercel.d.ts +0 -104
  37. package/dist/sandboxes/vercel.js +0 -166
  38. package/dist/sandboxes/vercel.js.map +0 -1
  39. package/dist/templates/blank/main.mts +0 -13
  40. package/dist/templates/blank/prompt.md +0 -12
  41. package/dist/templates/blank/template.json +0 -4
package/dist/index.js CHANGED
@@ -1,36 +1,25 @@
1
- import { CODEX_MODELS, resolveEnv, formatErrorMessage, buildLogDirectoryName } from './chunk-56TDSWFU.js';
2
- export { CODEX_MODELS, CODEX_REASONING_EFFORTS } from './chunk-56TDSWFU.js';
3
- import { CwdError, resolveCwd, getCurrentBranch, generateTempBranchName, assertNoSymlinkComponents, FileDisplay, ClackDisplay, WorktreeDockerSandboxFactory, SandboxConfig, Display, CLI_NAME, PRODUCT_NAME, pruneStale, create, copyToWorktree, runHostHooks, startSandbox, resolveGitMounts, patchGitMountsForWindows, SANDBOX_REPO_DIR, remove, makeSandboxFromHandle, syncOut, withSandboxLifecycle, hasUncommittedChanges, registerShutdown, SandboxFactory, PromptError, CONFIG_DIR, LOGS_DIR, AgentError, SessionCaptureError, PromptExpansionTimeoutError, SilentDisplay, AgentIdleTimeoutError } from './chunk-FDYOTN55.js';
4
- import { shellQuote, toSessionTransferHandle } from './chunk-KMGNFXKN.js';
5
- export { createBindMountSandboxProvider, createIsolatedSandboxProvider } from './chunk-KMGNFXKN.js';
6
- import { noSandbox } from './chunk-SOJTAJTF.js';
7
- import './chunk-ACD46ZM4.js';
1
+ import { CODEX_MODELS, resolveEnv, formatErrorMessage, buildLogDirectoryName } from './chunk-JI3HDDMS.js';
2
+ export { CODEX_MODELS, CODEX_REASONING_EFFORTS } from './chunk-JI3HDDMS.js';
3
+ import { CwdError, resolveCwd, getCurrentBranch, generateTempBranchName, assertNoSymlinkComponents, FileDisplay, ClackDisplay, WorktreeDockerSandboxFactory, SandboxConfig, Display, CLI_NAME, PRODUCT_NAME, pruneStale, create, runHostHooks, startSandbox, remove, makeSandboxFromHandle, withSandboxLifecycle, hasUncommittedChanges, shellQuote, registerShutdown, toSessionTransferHandle, SandboxFactory, syncOut, PromptError, CONFIG_DIR, LOGS_DIR, AgentError, SessionCaptureError, PromptExpansionTimeoutError, assertExcludesRepositoryRunner, resolveSafeRelativePath, CopyToWorktreeError, withTimeout, CopyToWorktreeTimeoutError, SilentDisplay, AgentIdleTimeoutError } from './chunk-L6PX5QTU.js';
4
+ export { createIsolatedSandboxProvider } from './chunk-L6PX5QTU.js';
8
5
  import { NodeContext, NodeFileSystem } from '@effect/platform-node';
9
- import { mkdirSync, chmodSync, appendFileSync, constants } from 'fs';
6
+ import { existsSync, mkdirSync, chmodSync, appendFileSync, constants } from 'fs';
10
7
  import path, { join, posix, dirname, relative } from 'path';
11
8
  import { styleText } from 'util';
12
9
  import { Context, Effect, Layer, Clock, Duration, Option, Deferred, Ref, Fiber } from 'effect';
13
10
  import { FileSystem } from '@effect/platform';
14
11
  import * as clack from '@clack/prompts';
12
+ import { execFile } from 'child_process';
15
13
  import { readdir, access, readFile, mkdir, mkdtemp, writeFile, rm, lstat, open, chmod } from 'fs/promises';
16
14
  import { homedir, tmpdir } from 'os';
17
15
  import { createHmac, timingSafeEqual, createHash, randomUUID } from 'crypto';
18
16
 
19
17
  // src/resumePrecheck.ts
20
18
  var assertResumeSessionExists = async (params) => {
21
- const { provider, sandboxTag, hostRepoDir, resumeSession } = params;
19
+ const { provider, hostRepoDir, resumeSession } = params;
22
20
  if (!provider.sessionStorage) {
23
21
  throw new Error(`${provider.name} does not support resumeSession`);
24
22
  }
25
- if (sandboxTag === "none") {
26
- const found = await provider.sessionStorage.findByIdOnHost(resumeSession);
27
- if (!found.path) {
28
- throw new Error(
29
- `resumeSession "${resumeSession}" not found under ${found.searchedRoot}`
30
- );
31
- }
32
- return;
33
- }
34
23
  const exists = await provider.sessionStorage.existsOnHost(
35
24
  hostRepoDir,
36
25
  resumeSession
@@ -424,7 +413,12 @@ var orchestrate = (options) => {
424
413
  yield* checkAbort();
425
414
  yield* display.status(label(`Iteration ${i}/${iterations}`), "info");
426
415
  const sandboxResult = yield* factory.withSandbox(
427
- ({ hostWorktreePath, sandboxRepoPath, applyToHost, bindMountHandle }, sandbox) => withSandboxLifecycle(
416
+ ({
417
+ hostWorktreePath,
418
+ sandboxRepoPath,
419
+ applyToHost,
420
+ sessionTransferHandle
421
+ }, sandbox) => withSandboxLifecycle(
428
422
  {
429
423
  hostRepoDir,
430
424
  sandboxRepoDir: sandboxRepoPath,
@@ -440,14 +434,14 @@ var orchestrate = (options) => {
440
434
  (ctx) => Effect.gen(function* () {
441
435
  const iterationResumeSession = i === 1 ? options.resumeSession : void 0;
442
436
  const iterationForkSession = i === 1 ? options.forkSession : void 0;
443
- if (iterationResumeSession && bindMountHandle && provider.sessionStorage) {
437
+ if (iterationResumeSession && sessionTransferHandle && provider.sessionStorage) {
444
438
  yield* display.status(label("Resuming session"), "info");
445
439
  yield* Effect.tryPromise({
446
440
  try: () => provider.sessionStorage.resumeIntoSandbox({
447
441
  hostCwd: hostRepoDir,
448
442
  sandboxCwd: ctx.sandboxRepoDir,
449
443
  sessionId: iterationResumeSession,
450
- handle: bindMountHandle
444
+ handle: sessionTransferHandle
451
445
  }),
452
446
  catch: (e) => new SessionCaptureError({
453
447
  message: `Session resume failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -539,14 +533,14 @@ var orchestrate = (options) => {
539
533
  yield* display.status(label("Agent stopped"), "info");
540
534
  let sessionFilePath;
541
535
  let usage = streamUsage;
542
- if (provider.captureSessions && provider.sessionStorage && sessionId && bindMountHandle) {
536
+ if (provider.captureSessions && provider.sessionStorage && sessionId && sessionTransferHandle) {
543
537
  yield* display.status(label("Capturing session"), "info");
544
538
  yield* Effect.tryPromise({
545
539
  try: () => provider.sessionStorage.captureToHost({
546
540
  hostCwd: hostRepoDir,
547
541
  sandboxCwd: ctx.sandboxRepoDir,
548
542
  sessionId,
549
- handle: bindMountHandle
543
+ handle: sessionTransferHandle
550
544
  }),
551
545
  catch: (e) => new SessionCaptureError({
552
546
  message: `Session capture failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1019,18 +1013,9 @@ async function run(options) {
1019
1013
  agent: provider
1020
1014
  } = options;
1021
1015
  validateMaxIterations(maxIterations);
1022
- const branchStrategy = options.branchStrategy ?? (options.sandbox.tag === "isolated" ? { type: "merge-to-head" } : { type: "head" });
1023
- const effectiveBranchType = branchStrategy.type;
1024
- if (effectiveBranchType === "head" && options.sandbox.tag === "isolated") {
1025
- throw new Error(
1026
- "head branch strategy is not supported with isolated providers"
1027
- );
1028
- }
1029
- if (effectiveBranchType === "head" && options.copyToWorktree && options.copyToWorktree.length > 0) {
1030
- throw new Error(
1031
- "copyToWorktree is not supported with head branch strategy. In head mode the host working directory is bind-mounted directly."
1032
- );
1033
- }
1016
+ const branchStrategy = options.branchStrategy ?? {
1017
+ type: "merge-to-head"
1018
+ };
1034
1019
  if (options.resumeSession && maxIterations > 1) {
1035
1020
  throw new Error(
1036
1021
  "resumeSession cannot be combined with maxIterations > 1. Resume applies to iteration 1 only; multi-iteration resume semantics are not supported."
@@ -1064,7 +1049,6 @@ async function run(options) {
1064
1049
  if (options.resumeSession) {
1065
1050
  await assertResumeSessionExists({
1066
1051
  provider,
1067
- sandboxTag: options.sandbox.tag,
1068
1052
  hostRepoDir,
1069
1053
  resumeSession: options.resumeSession
1070
1054
  });
@@ -1096,8 +1080,8 @@ async function run(options) {
1096
1080
  const currentHostBranch = await Effect.runPromise(
1097
1081
  getCurrentBranch(hostRepoDir)
1098
1082
  );
1099
- const resolvedBranch = effectiveBranchType === "head" ? currentHostBranch : branch ?? generateTempBranchName(options.name);
1100
- const targetBranch = effectiveBranchType === "merge-to-head" ? currentHostBranch : void 0;
1083
+ const resolvedBranch = branch ?? generateTempBranchName(options.name);
1084
+ const targetBranch = branchStrategy.type === "merge-to-head" ? currentHostBranch : void 0;
1101
1085
  const resolvedLogging = options.logging ?? {
1102
1086
  type: "file",
1103
1087
  path: buildDefaultLogPath(
@@ -1182,7 +1166,7 @@ async function run(options) {
1182
1166
  builtInArgKeysSet
1183
1167
  );
1184
1168
  }
1185
- const orchestrateBranch = effectiveBranchType === "head" ? currentHostBranch : branch;
1169
+ const orchestrateBranch = branch;
1186
1170
  const orchestrateResult = yield* orchestrate({
1187
1171
  hostRepoDir,
1188
1172
  iterations: maxIterations,
@@ -1333,25 +1317,16 @@ var raceAbortSignal = (effect, signal) => {
1333
1317
  var interactive = async (options) => {
1334
1318
  options.signal?.throwIfAborted();
1335
1319
  const { prompt, promptFile, hooks, agent: provider } = options;
1336
- const resolvedSandbox = options.sandbox ?? noSandbox();
1337
- const branchStrategy = options.branchStrategy ?? (resolvedSandbox.tag === "isolated" ? { type: "merge-to-head" } : { type: "head" });
1338
- if (branchStrategy.type === "head" && resolvedSandbox.tag === "isolated") {
1339
- throw new Error(
1340
- "head branch strategy is not supported with isolated providers"
1341
- );
1342
- }
1343
- if (branchStrategy.type === "head" && options.copyToWorktree && options.copyToWorktree.length > 0) {
1344
- throw new Error(
1345
- "copyToWorktree is not supported with head branch strategy. In head mode the host working directory is bind-mounted directly."
1346
- );
1347
- }
1320
+ const resolvedSandbox = options.sandbox;
1321
+ const branchStrategy = options.branchStrategy ?? {
1322
+ type: "merge-to-head"
1323
+ };
1348
1324
  if (!provider.buildInteractiveArgs) {
1349
1325
  throw new Error(
1350
1326
  `Agent provider "${provider.name}" does not support buildInteractiveArgs, required for interactive sessions.`
1351
1327
  );
1352
1328
  }
1353
1329
  const branch = branchStrategy.type === "branch" ? branchStrategy.branch : void 0;
1354
- const isHeadMode = branchStrategy.type === "head";
1355
1330
  const sandboxProvider = resolvedSandbox;
1356
1331
  const inner = Effect.gen(function* () {
1357
1332
  const hostRepoDir = yield* resolveCwd(options.cwd);
@@ -1368,7 +1343,7 @@ var interactive = async (options) => {
1368
1343
  });
1369
1344
  const effectiveEnv = { ...env, ...options.env ?? {} };
1370
1345
  const currentHostBranch = yield* getCurrentBranch(hostRepoDir);
1371
- const resolvedBranch = branchStrategy.type === "head" ? currentHostBranch : branch ?? generateTempBranchName(options.name);
1346
+ const resolvedBranch = branch ?? generateTempBranchName(options.name);
1372
1347
  let substitutedPrompt = rawPrompt;
1373
1348
  if (hasPromptSource && !isInlinePrompt) {
1374
1349
  const userArgs = options.promptArgs ?? {};
@@ -1408,86 +1383,38 @@ var interactive = async (options) => {
1408
1383
  const userArgs = options.promptArgs ?? {};
1409
1384
  yield* validateNoArgsWithInlinePrompt(userArgs);
1410
1385
  }
1411
- const lifecycleBranch = isHeadMode ? currentHostBranch : branch;
1386
+ const lifecycleBranch = branch;
1412
1387
  yield* d.intro(options.name ?? `${CLI_NAME} interactive`);
1413
1388
  yield* d.summary("Interactive Session", {
1414
1389
  Agent: options.name ?? provider.name,
1415
1390
  Sandbox: sandboxProvider.name,
1416
1391
  Branch: resolvedBranch
1417
1392
  });
1418
- let worktreeInfo;
1419
- if (!isHeadMode) {
1420
- worktreeInfo = yield* d.taskLog(
1421
- "Creating worktree",
1422
- () => pruneStale(hostRepoDir).pipe(
1423
- Effect.catchAll(() => Effect.void),
1424
- Effect.andThen(
1425
- branch ? create(hostRepoDir, { branch }) : create(hostRepoDir, { name: options.name })
1426
- )
1393
+ const worktreeInfo = yield* d.taskLog(
1394
+ "Creating worktree",
1395
+ () => pruneStale(hostRepoDir).pipe(
1396
+ Effect.catchAll(() => Effect.void),
1397
+ Effect.andThen(
1398
+ branch ? create(hostRepoDir, { branch }) : create(hostRepoDir, { name: options.name })
1427
1399
  )
1428
- );
1429
- }
1400
+ )
1401
+ );
1430
1402
  const handle = yield* Effect.gen(function* () {
1431
- if (!isHeadMode) {
1432
- if ((sandboxProvider.tag === "bind-mount" || sandboxProvider.tag === "none") && options.copyToWorktree && options.copyToWorktree.length > 0) {
1433
- yield* d.taskLog(
1434
- "Copying files to worktree",
1435
- () => copyToWorktree(
1436
- options.copyToWorktree,
1437
- hostRepoDir,
1438
- worktreeInfo.path,
1439
- options.timeouts?.copyToWorktreeMs
1440
- )
1441
- );
1442
- }
1443
- if (hooks?.host?.onWorktreeReady?.length) {
1444
- yield* runHostHooks(hooks.host.onWorktreeReady, worktreeInfo.path);
1445
- }
1446
- } else if (hooks?.host?.onWorktreeReady?.length) {
1447
- yield* runHostHooks(hooks.host.onWorktreeReady, hostRepoDir);
1448
- }
1449
- if (sandboxProvider.tag === "none") {
1450
- const worktreePath = isHeadMode ? hostRepoDir : worktreeInfo.path;
1451
- return yield* Effect.promise(
1452
- () => sandboxProvider.create({
1453
- worktreePath,
1454
- env: effectiveEnv
1455
- })
1456
- );
1457
- } else if (sandboxProvider.tag === "isolated") {
1458
- const startResult = yield* d.taskLog(
1459
- "Starting sandbox",
1460
- () => startSandbox({
1461
- provider: sandboxProvider,
1462
- hostRepoDir: worktreeInfo.path,
1463
- sourceRepoDir: hostRepoDir,
1464
- env: effectiveEnv,
1465
- copyPaths: options.copyToWorktree
1466
- })
1467
- );
1468
- return startResult.handle;
1469
- } else {
1470
- const gitPath = join(hostRepoDir, ".git");
1471
- const rawGitMounts = yield* resolveGitMounts(gitPath);
1472
- const worktreeOrRepoPath = isHeadMode ? hostRepoDir : worktreeInfo.path;
1473
- const gitMounts = yield* patchGitMountsForWindows(
1474
- rawGitMounts,
1475
- worktreeOrRepoPath,
1476
- SANDBOX_REPO_DIR
1477
- );
1478
- const startResult = yield* d.taskLog(
1479
- "Starting sandbox",
1480
- () => startSandbox({
1481
- provider: sandboxProvider,
1482
- hostRepoDir,
1483
- env: effectiveEnv,
1484
- worktreeOrRepoPath,
1485
- gitMounts,
1486
- repoDir: SANDBOX_REPO_DIR
1487
- })
1488
- );
1489
- return startResult.handle;
1403
+ if (hooks?.host?.onWorktreeReady?.length) {
1404
+ yield* runHostHooks(hooks.host.onWorktreeReady, worktreeInfo.path);
1490
1405
  }
1406
+ const startResult = yield* d.taskLog(
1407
+ "Starting sandbox",
1408
+ () => startSandbox({
1409
+ provider: sandboxProvider,
1410
+ hostRepoDir: worktreeInfo.path,
1411
+ sourceRepoDir: hostRepoDir,
1412
+ env: effectiveEnv,
1413
+ copyPaths: options.copyToWorktree,
1414
+ copyTimeoutMs: options.timeouts?.copyToWorktreeMs
1415
+ })
1416
+ );
1417
+ return startResult.handle;
1491
1418
  }).pipe(
1492
1419
  Effect.tapError(
1493
1420
  () => worktreeInfo ? remove(worktreeInfo.path).pipe(
@@ -1504,14 +1431,14 @@ var interactive = async (options) => {
1504
1431
  const interactiveExecFn = handle.interactiveExec.bind(handle);
1505
1432
  const sandbox = makeSandboxFromHandle(handle);
1506
1433
  const worktreePath = handle.worktreePath;
1507
- const applyToHost = sandboxProvider.tag === "isolated" && worktreeInfo ? () => syncOut(worktreeInfo.path, handle) : () => Effect.void;
1434
+ const applyToHost = () => syncOut(worktreeInfo.path, handle);
1508
1435
  const lifecycleEffect = withSandboxLifecycle(
1509
1436
  {
1510
1437
  hostRepoDir,
1511
1438
  sandboxRepoDir: worktreePath,
1512
1439
  hooks,
1513
1440
  branch: lifecycleBranch,
1514
- hostWorktreePath: isHeadMode ? hostRepoDir : worktreeInfo?.path,
1441
+ hostWorktreePath: worktreeInfo.path,
1515
1442
  applyToHost,
1516
1443
  timeouts: options.timeouts
1517
1444
  },
@@ -1524,7 +1451,7 @@ var interactive = async (options) => {
1524
1451
  );
1525
1452
  const interactiveArgs = provider.buildInteractiveArgs({
1526
1453
  prompt: fullPrompt,
1527
- dangerouslySkipPermissions: sandboxProvider.tag !== "none"
1454
+ dangerouslySkipPermissions: true
1528
1455
  });
1529
1456
  const result4 = yield* raceAbortSignal(
1530
1457
  Effect.promise(
@@ -1613,7 +1540,7 @@ var buildSandboxHandle = (ctx, close) => {
1613
1540
  sandboxRepoDir,
1614
1541
  sandbox,
1615
1542
  providerHandle,
1616
- bindMountHandle,
1543
+ sessionTransferHandle,
1617
1544
  applyToHost,
1618
1545
  timeouts,
1619
1546
  branchStrategy
@@ -1644,7 +1571,6 @@ var buildSandboxHandle = (ctx, close) => {
1644
1571
  if (runOptions.resumeSession) {
1645
1572
  await assertResumeSessionExists({
1646
1573
  provider,
1647
- sandboxTag: ctx.providerTag,
1648
1574
  hostRepoDir,
1649
1575
  resumeSession: runOptions.resumeSession
1650
1576
  });
@@ -1715,7 +1641,7 @@ var buildSandboxHandle = (ctx, close) => {
1715
1641
  hostWorktreePath: worktreePath,
1716
1642
  sandboxRepoPath: sandboxRepoDir,
1717
1643
  applyToHost,
1718
- bindMountHandle
1644
+ sessionTransferHandle
1719
1645
  },
1720
1646
  sandbox
1721
1647
  ).pipe(
@@ -1935,24 +1861,13 @@ var buildSandboxHandle = (ctx, close) => {
1935
1861
  var createSandboxFromWorktree = async (options) => {
1936
1862
  const { branch, worktreePath, hostRepoDir } = options;
1937
1863
  const isTestMode = !!options._test?.buildSandbox;
1938
- if (options.copyToWorktree && options.copyToWorktree.length > 0 && options.sandbox.tag !== "isolated") {
1939
- await Effect.runPromise(
1940
- copyToWorktree(
1941
- options.copyToWorktree,
1942
- hostRepoDir,
1943
- worktreePath,
1944
- options.timeouts?.copyToWorktreeMs
1945
- )
1946
- );
1947
- }
1948
1864
  let providerHandle;
1949
1865
  let sandbox;
1950
1866
  let sandboxRepoDir;
1951
- const isIsolated = options.sandbox.tag === "isolated";
1952
1867
  if (isTestMode) {
1953
1868
  sandbox = options._test.buildSandbox(worktreePath);
1954
1869
  sandboxRepoDir = worktreePath;
1955
- providerHandle = options._test.bindMountHandle;
1870
+ providerHandle = options._test.sessionTransferHandle;
1956
1871
  } else {
1957
1872
  const resolvedEnv = await Effect.runPromise(
1958
1873
  resolveEnv(hostRepoDir).pipe(Effect.provide(NodeContext.layer))
@@ -1963,42 +1878,15 @@ var createSandboxFromWorktree = async (options) => {
1963
1878
  sandboxProviderEnv: options.sandbox.env
1964
1879
  });
1965
1880
  const provider = options.sandbox;
1966
- let startEffect;
1967
- if (provider.tag === "isolated") {
1968
- startEffect = startSandbox({
1969
- provider,
1970
- hostRepoDir: worktreePath,
1971
- sourceRepoDir: hostRepoDir,
1972
- env,
1973
- copyPaths: options.copyToWorktree
1974
- });
1975
- } else if (provider.tag === "none") {
1976
- startEffect = startSandbox({
1977
- provider,
1978
- hostRepoDir,
1979
- env,
1980
- worktreeOrRepoPath: worktreePath
1981
- });
1982
- } else {
1983
- startEffect = resolveGitMounts(join(hostRepoDir, ".git")).pipe(
1984
- Effect.provide(NodeFileSystem.layer),
1985
- Effect.catchAll(() => Effect.succeed([])),
1986
- // Patch git mounts for Windows worktree compatibility (ADR-0006)
1987
- Effect.flatMap(
1988
- (gitMounts) => patchGitMountsForWindows(gitMounts, worktreePath, SANDBOX_REPO_DIR)
1989
- ),
1990
- Effect.flatMap(
1991
- (gitMounts) => startSandbox({
1992
- provider,
1993
- hostRepoDir,
1994
- env,
1995
- worktreeOrRepoPath: worktreePath,
1996
- gitMounts,
1997
- repoDir: SANDBOX_REPO_DIR
1998
- })
1999
- )
2000
- );
2001
- }
1881
+ const startEffect = startSandbox({
1882
+ provider,
1883
+ hostRepoDir: worktreePath,
1884
+ sourceRepoDir: hostRepoDir,
1885
+ copySourceDir: worktreePath,
1886
+ env,
1887
+ copyPaths: options.copyToWorktree,
1888
+ copyTimeoutMs: options.timeouts?.copyToWorktreeMs
1889
+ });
2002
1890
  const startResult = await Effect.runPromise(startEffect);
2003
1891
  providerHandle = startResult.handle;
2004
1892
  sandbox = startResult.sandbox;
@@ -2035,9 +1923,9 @@ var createSandboxFromWorktree = async (options) => {
2035
1923
  )
2036
1924
  );
2037
1925
  }
2038
- const applyToHost = isIsolated && providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
1926
+ const applyToHost = providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
2039
1927
  let closed = false;
2040
- const bindMountHandle = toSessionTransferHandle(providerHandle);
1928
+ const sessionTransferHandle = toSessionTransferHandle(providerHandle);
2041
1929
  return buildSandboxHandle(
2042
1930
  {
2043
1931
  branch,
@@ -2046,8 +1934,7 @@ var createSandboxFromWorktree = async (options) => {
2046
1934
  sandboxRepoDir,
2047
1935
  sandbox,
2048
1936
  providerHandle,
2049
- bindMountHandle,
2050
- providerTag: options.sandbox.tag,
1937
+ sessionTransferHandle,
2051
1938
  applyToHost,
2052
1939
  timeouts: options.timeouts,
2053
1940
  branchStrategy: options.branchStrategy
@@ -2063,7 +1950,6 @@ var createSandboxFromWorktree = async (options) => {
2063
1950
  var createSandbox = async (options) => {
2064
1951
  const { branch } = options;
2065
1952
  const isTestMode = !!options._test?.buildSandbox;
2066
- const isIsolated = options.sandbox.tag === "isolated";
2067
1953
  const { hostRepoDir, worktreePath, providerHandle, sandbox, sandboxRepoDir } = await Effect.runPromise(
2068
1954
  Effect.gen(function* () {
2069
1955
  const hostRepoDir2 = yield* resolveCwd(options.cwd);
@@ -2075,14 +1961,6 @@ var createSandbox = async (options) => {
2075
1961
  { branch, baseBranch: options.baseBranch }
2076
1962
  );
2077
1963
  const prepared = yield* Effect.gen(function* () {
2078
- if (options.copyToWorktree && options.copyToWorktree.length > 0 && options.sandbox.tag !== "isolated") {
2079
- yield* copyToWorktree(
2080
- options.copyToWorktree,
2081
- hostRepoDir2,
2082
- worktreePath2,
2083
- options.timeouts?.copyToWorktreeMs
2084
- );
2085
- }
2086
1964
  if (options.hooks?.host?.onWorktreeReady?.length) {
2087
1965
  yield* runHostHooks(
2088
1966
  options.hooks.host.onWorktreeReady,
@@ -2095,7 +1973,7 @@ var createSandbox = async (options) => {
2095
1973
  if (isTestMode) {
2096
1974
  sandbox2 = options._test.buildSandbox(worktreePath2);
2097
1975
  sandboxRepoDir2 = worktreePath2;
2098
- providerHandle2 = options._test.bindMountHandle;
1976
+ providerHandle2 = options._test.sessionTransferHandle;
2099
1977
  } else {
2100
1978
  const resolvedEnv = yield* resolveEnv(hostRepoDir2);
2101
1979
  const env = mergeProviderEnv({
@@ -2104,39 +1982,14 @@ var createSandbox = async (options) => {
2104
1982
  sandboxProviderEnv: options.sandbox.env
2105
1983
  });
2106
1984
  const provider = options.sandbox;
2107
- const startResult = yield* provider.tag === "isolated" ? startSandbox({
1985
+ const startResult = yield* startSandbox({
2108
1986
  provider,
2109
1987
  hostRepoDir: worktreePath2,
2110
1988
  sourceRepoDir: hostRepoDir2,
2111
1989
  env,
2112
- copyPaths: options.copyToWorktree
2113
- }) : provider.tag === "none" ? startSandbox({
2114
- provider,
2115
- hostRepoDir: hostRepoDir2,
2116
- env,
2117
- worktreeOrRepoPath: worktreePath2
2118
- }) : resolveGitMounts(join(hostRepoDir2, ".git")).pipe(
2119
- Effect.provide(NodeFileSystem.layer),
2120
- Effect.catchAll(() => Effect.succeed([])),
2121
- // Patch git mounts for Windows worktree compatibility (ADR-0006)
2122
- Effect.flatMap(
2123
- (gitMounts) => patchGitMountsForWindows(
2124
- gitMounts,
2125
- worktreePath2,
2126
- SANDBOX_REPO_DIR
2127
- )
2128
- ),
2129
- Effect.flatMap(
2130
- (gitMounts) => startSandbox({
2131
- provider,
2132
- hostRepoDir: hostRepoDir2,
2133
- env,
2134
- worktreeOrRepoPath: worktreePath2,
2135
- gitMounts,
2136
- repoDir: SANDBOX_REPO_DIR
2137
- })
2138
- )
2139
- );
1990
+ copyPaths: options.copyToWorktree,
1991
+ copyTimeoutMs: options.timeouts?.copyToWorktreeMs
1992
+ });
2140
1993
  providerHandle2 = startResult.handle;
2141
1994
  sandbox2 = startResult.sandbox;
2142
1995
  sandboxRepoDir2 = startResult.worktreePath;
@@ -2181,7 +2034,7 @@ var createSandbox = async (options) => {
2181
2034
  return { hostRepoDir: hostRepoDir2, worktreePath: worktreePath2, ...prepared };
2182
2035
  }).pipe(Effect.provide(NodeContext.layer))
2183
2036
  );
2184
- const applyToHost = isIsolated && providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
2037
+ const applyToHost = providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
2185
2038
  let closed = false;
2186
2039
  const forceCleanup = () => {
2187
2040
  console.error(`
@@ -2211,7 +2064,7 @@ Worktree preserved at ${worktreePath}`);
2211
2064
  })
2212
2065
  );
2213
2066
  };
2214
- const bindMountHandle = toSessionTransferHandle(providerHandle);
2067
+ const sessionTransferHandle = toSessionTransferHandle(providerHandle);
2215
2068
  return buildSandboxHandle(
2216
2069
  {
2217
2070
  branch,
@@ -2220,8 +2073,7 @@ Worktree preserved at ${worktreePath}`);
2220
2073
  sandboxRepoDir,
2221
2074
  sandbox,
2222
2075
  providerHandle,
2223
- bindMountHandle,
2224
- providerTag: options.sandbox.tag,
2076
+ sessionTransferHandle,
2225
2077
  applyToHost,
2226
2078
  timeouts: options.timeouts
2227
2079
  },
@@ -2231,6 +2083,82 @@ Worktree preserved at ${worktreePath}`);
2231
2083
  }
2232
2084
  );
2233
2085
  };
2086
+ var COPY_TO_WORKTREE_TIMEOUT_MS = 6e4;
2087
+ var getCopyOnWriteFlags = (platform) => platform === "darwin" ? ["-cR"] : ["-R", "--reflink=auto"];
2088
+ var copyToWorktree = (paths, hostRepoDir, worktreePath, timeoutMs) => {
2089
+ const effectiveTimeout = timeoutMs ?? COPY_TO_WORKTREE_TIMEOUT_MS;
2090
+ return Effect.gen(function* () {
2091
+ const cowFlags = getCopyOnWriteFlags(process.platform);
2092
+ for (const relativePath of paths) {
2093
+ let src;
2094
+ let dest;
2095
+ try {
2096
+ assertExcludesRepositoryRunner(relativePath);
2097
+ src = resolveSafeRelativePath(hostRepoDir, relativePath, "copy path");
2098
+ dest = resolveSafeRelativePath(worktreePath, relativePath, "copy path");
2099
+ } catch (error) {
2100
+ return yield* Effect.fail(
2101
+ new CopyToWorktreeError({
2102
+ message: `Refusing unsafe copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
2103
+ path: relativePath,
2104
+ stderr: error instanceof Error ? error.message : String(error),
2105
+ exitCode: null
2106
+ })
2107
+ );
2108
+ }
2109
+ if (!existsSync(src)) {
2110
+ continue;
2111
+ }
2112
+ yield* Effect.tryPromise({
2113
+ try: async () => {
2114
+ await assertNoSymlinkComponents(hostRepoDir, src, "copy source");
2115
+ await assertNoSymlinkComponents(worktreePath, dest, "copy target");
2116
+ },
2117
+ catch: (error) => new CopyToWorktreeError({
2118
+ message: `Refusing symlinked copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
2119
+ path: relativePath,
2120
+ stderr: error instanceof Error ? error.message : String(error),
2121
+ exitCode: null
2122
+ })
2123
+ });
2124
+ yield* Effect.async((resume) => {
2125
+ execFile("cp", [...cowFlags, src, dest], (error) => {
2126
+ if (error) {
2127
+ execFile("cp", ["-R", src, dest], (fallbackError, _, stderr) => {
2128
+ if (fallbackError) {
2129
+ resume(
2130
+ Effect.fail(
2131
+ new CopyToWorktreeError({
2132
+ message: `Failed to copy ${relativePath} to worktree: ${stderr || fallbackError.message}`,
2133
+ path: relativePath,
2134
+ stderr: stderr || fallbackError.message,
2135
+ exitCode: typeof fallbackError.code === "number" ? fallbackError.code : null
2136
+ })
2137
+ )
2138
+ );
2139
+ } else {
2140
+ resume(Effect.succeed(void 0));
2141
+ }
2142
+ });
2143
+ } else {
2144
+ resume(Effect.succeed(void 0));
2145
+ }
2146
+ });
2147
+ });
2148
+ }
2149
+ }).pipe(
2150
+ withTimeout(
2151
+ effectiveTimeout,
2152
+ () => new CopyToWorktreeTimeoutError({
2153
+ message: `Copying files to worktree timed out after ${effectiveTimeout}ms`,
2154
+ timeoutMs: effectiveTimeout,
2155
+ paths
2156
+ })
2157
+ )
2158
+ );
2159
+ };
2160
+
2161
+ // src/createWorktree.ts
2234
2162
  var createWorktree = async (options) => {
2235
2163
  const branch = options.branchStrategy.type === "branch" ? options.branchStrategy.branch : void 0;
2236
2164
  const baseBranch = options.branchStrategy.type === "branch" ? options.branchStrategy.baseBranch : void 0;
@@ -2277,7 +2205,7 @@ var createWorktree = async (options) => {
2277
2205
  const worktreeInteractive = async (opts) => {
2278
2206
  opts.signal?.throwIfAborted();
2279
2207
  const { prompt, promptFile, hooks, agent: provider } = opts;
2280
- const resolvedSandbox = opts.sandbox ?? noSandbox();
2208
+ const resolvedSandbox = opts.sandbox;
2281
2209
  if (!provider.buildInteractiveArgs) {
2282
2210
  throw new Error(
2283
2211
  `Agent provider "${provider.name}" does not support buildInteractiveArgs, required for interactive sessions.`
@@ -2320,46 +2248,19 @@ var createWorktree = async (options) => {
2320
2248
  Sandbox: resolvedSandbox.name,
2321
2249
  Branch: worktreeInfo.branch
2322
2250
  });
2323
- let handle;
2324
- if (resolvedSandbox.tag === "none") {
2325
- handle = yield* Effect.promise(
2326
- () => resolvedSandbox.create({
2327
- worktreePath: worktreeInfo.path,
2328
- env: effectiveEnv
2329
- })
2330
- );
2331
- } else if (resolvedSandbox.tag === "isolated") {
2332
- const startResult = yield* d.taskLog(
2333
- "Starting sandbox",
2334
- () => startSandbox({
2335
- provider: resolvedSandbox,
2336
- hostRepoDir: worktreeInfo.path,
2337
- sourceRepoDir: hostRepoDir,
2338
- env: effectiveEnv
2339
- })
2340
- );
2341
- handle = startResult.handle;
2342
- } else {
2343
- const gitPath = join(hostRepoDir, ".git");
2344
- const rawGitMounts = yield* resolveGitMounts(gitPath);
2345
- const gitMounts = yield* patchGitMountsForWindows(
2346
- rawGitMounts,
2347
- worktreeInfo.path,
2348
- SANDBOX_REPO_DIR
2349
- );
2350
- const startResult = yield* d.taskLog(
2351
- "Starting sandbox",
2352
- () => startSandbox({
2353
- provider: resolvedSandbox,
2354
- hostRepoDir,
2355
- env: effectiveEnv,
2356
- worktreeOrRepoPath: worktreeInfo.path,
2357
- gitMounts,
2358
- repoDir: SANDBOX_REPO_DIR
2359
- })
2360
- );
2361
- handle = startResult.handle;
2362
- }
2251
+ const startResult = yield* d.taskLog(
2252
+ "Starting sandbox",
2253
+ () => startSandbox({
2254
+ provider: resolvedSandbox,
2255
+ hostRepoDir: worktreeInfo.path,
2256
+ sourceRepoDir: hostRepoDir,
2257
+ copySourceDir: worktreeInfo.path,
2258
+ env: effectiveEnv,
2259
+ copyPaths: options.copyToWorktree,
2260
+ copyTimeoutMs: options.timeouts?.copyToWorktreeMs
2261
+ })
2262
+ );
2263
+ const handle = startResult.handle;
2363
2264
  return yield* Effect.gen(function* () {
2364
2265
  if (!handle.interactiveExec) {
2365
2266
  throw new Error(
@@ -2369,7 +2270,7 @@ var createWorktree = async (options) => {
2369
2270
  const interactiveExecFn = handle.interactiveExec.bind(handle);
2370
2271
  const sandbox = makeSandboxFromHandle(handle);
2371
2272
  const worktreePath = handle.worktreePath;
2372
- const applyToHost = resolvedSandbox.tag === "isolated" ? () => syncOut(worktreeInfo.path, handle) : () => Effect.void;
2273
+ const applyToHost = () => syncOut(worktreeInfo.path, handle);
2373
2274
  const lifecycleEffect = withSandboxLifecycle(
2374
2275
  {
2375
2276
  hostRepoDir,
@@ -2393,7 +2294,7 @@ var createWorktree = async (options) => {
2393
2294
  );
2394
2295
  const interactiveArgs = provider.buildInteractiveArgs({
2395
2296
  prompt: fullPrompt,
2396
- dangerouslySkipPermissions: resolvedSandbox.tag !== "none"
2297
+ dangerouslySkipPermissions: true
2397
2298
  });
2398
2299
  const result3 = yield* raceAbortSignal(
2399
2300
  Effect.promise(
@@ -2456,7 +2357,6 @@ var createWorktree = async (options) => {
2456
2357
  if (opts.resumeSession) {
2457
2358
  await assertResumeSessionExists({
2458
2359
  provider,
2459
- sandboxTag: sandboxProvider.tag,
2460
2360
  hostRepoDir,
2461
2361
  resumeSession: opts.resumeSession
2462
2362
  });
@@ -2491,47 +2391,19 @@ var createWorktree = async (options) => {
2491
2391
  builtInArgKeysSet
2492
2392
  );
2493
2393
  }
2494
- let handle;
2495
- let sandboxRepoDir;
2496
- if (sandboxProvider.tag === "isolated") {
2497
- const startResult = yield* startSandbox({
2498
- provider: sandboxProvider,
2499
- hostRepoDir: worktreeInfo.path,
2500
- sourceRepoDir: hostRepoDir,
2501
- env: effectiveEnv
2502
- });
2503
- handle = startResult.handle;
2504
- sandboxRepoDir = startResult.worktreePath;
2505
- } else if (sandboxProvider.tag === "none") {
2506
- const startResult = yield* startSandbox({
2507
- provider: sandboxProvider,
2508
- hostRepoDir,
2509
- env: effectiveEnv,
2510
- worktreeOrRepoPath: worktreeInfo.path
2511
- });
2512
- handle = startResult.handle;
2513
- sandboxRepoDir = startResult.worktreePath;
2514
- } else {
2515
- const gitPath = join(hostRepoDir, ".git");
2516
- const rawGitMounts = yield* resolveGitMounts(gitPath);
2517
- const gitMounts = yield* patchGitMountsForWindows(
2518
- rawGitMounts,
2519
- worktreeInfo.path,
2520
- SANDBOX_REPO_DIR
2521
- );
2522
- const startResult = yield* startSandbox({
2523
- provider: sandboxProvider,
2524
- hostRepoDir,
2525
- env: effectiveEnv,
2526
- worktreeOrRepoPath: worktreeInfo.path,
2527
- gitMounts,
2528
- repoDir: SANDBOX_REPO_DIR
2529
- });
2530
- handle = startResult.handle;
2531
- sandboxRepoDir = startResult.worktreePath;
2532
- }
2394
+ const startResult = yield* startSandbox({
2395
+ provider: sandboxProvider,
2396
+ hostRepoDir: worktreeInfo.path,
2397
+ sourceRepoDir: hostRepoDir,
2398
+ copySourceDir: worktreeInfo.path,
2399
+ env: effectiveEnv,
2400
+ copyPaths: options.copyToWorktree,
2401
+ copyTimeoutMs: options.timeouts?.copyToWorktreeMs
2402
+ });
2403
+ const handle = startResult.handle;
2404
+ const sandboxRepoDir = startResult.worktreePath;
2533
2405
  const sandbox = makeSandboxFromHandle(handle);
2534
- const applyToHost = sandboxProvider.tag === "isolated" ? () => syncOut(worktreeInfo.path, handle) : () => Effect.void;
2406
+ const applyToHost = () => syncOut(worktreeInfo.path, handle);
2535
2407
  const resolvedLogging = opts.logging ?? {
2536
2408
  type: "file",
2537
2409
  path: buildDefaultLogPath(
@@ -2564,14 +2436,14 @@ var createWorktree = async (options) => {
2564
2436
  NodeFileSystem.layer
2565
2437
  );
2566
2438
  })() : ClackDisplay.layer;
2567
- const bindMountHandle = toSessionTransferHandle(handle);
2439
+ const sessionTransferHandle = toSessionTransferHandle(handle);
2568
2440
  const reuseFactoryLayer = Layer.succeed(SandboxFactory, {
2569
2441
  withSandbox: (makeEffect) => makeEffect(
2570
2442
  {
2571
2443
  hostWorktreePath: worktreeInfo.path,
2572
2444
  sandboxRepoPath: sandboxRepoDir,
2573
2445
  applyToHost,
2574
- bindMountHandle
2446
+ sessionTransferHandle
2575
2447
  },
2576
2448
  sandbox
2577
2449
  ).pipe(
@@ -2652,13 +2524,28 @@ var createWorktree = async (options) => {
2652
2524
  }
2653
2525
  };
2654
2526
  const worktreeCreateSandbox = async (opts) => {
2527
+ if (opts.copyToWorktree?.length) {
2528
+ await Effect.runPromise(
2529
+ copyToWorktree(
2530
+ opts.copyToWorktree,
2531
+ hostRepoDir,
2532
+ worktreeInfo.path,
2533
+ opts.timeouts?.copyToWorktreeMs ?? options.timeouts?.copyToWorktreeMs
2534
+ )
2535
+ );
2536
+ }
2655
2537
  return createSandboxFromWorktree({
2656
2538
  branch: worktreeInfo.branch,
2657
2539
  worktreePath: worktreeInfo.path,
2658
2540
  hostRepoDir,
2659
2541
  sandbox: opts.sandbox,
2660
2542
  hooks: opts.hooks,
2661
- copyToWorktree: opts.copyToWorktree,
2543
+ copyToWorktree: [
2544
+ .../* @__PURE__ */ new Set([
2545
+ ...options.copyToWorktree ?? [],
2546
+ ...opts.copyToWorktree ?? []
2547
+ ])
2548
+ ],
2662
2549
  timeouts: opts.timeouts,
2663
2550
  branchStrategy: options.branchStrategy,
2664
2551
  _test: opts._test
@@ -3027,8 +2914,7 @@ var makeClaudeSessionStorage = (options) => {
3027
2914
  sandboxProjectsDir
3028
2915
  );
3029
2916
  await writeSandboxFile(handle, sandboxPath, rewritten, "claude-res");
3030
- },
3031
- findByIdOnHost: (id) => findClaudeSessionOnHost(id, hostProjectsDir)
2917
+ }
3032
2918
  };
3033
2919
  };
3034
2920
  var makeCodexSessionStorage = (options) => {
@@ -3066,8 +2952,7 @@ var makeCodexSessionStorage = (options) => {
3066
2952
  const rewritten = transferCodexSession(jsonl, hostCwd, sandboxCwd);
3067
2953
  const target = posix.join(sandboxSessionsDir, located.relativePath);
3068
2954
  await writeSandboxFile(handle, target, rewritten, "codex-res");
3069
- },
3070
- findByIdOnHost: (id) => findCodexSessionOnHost(id, hostSessionsDir)
2955
+ }
3071
2956
  };
3072
2957
  };
3073
2958
  var parseCodexUsage = (usage) => {
@@ -3114,7 +2999,7 @@ var parseCodexStreamLine = (line) => {
3114
2999
  };
3115
3000
  var codex = (model, options) => {
3116
3001
  const modelId = typeof model === "string" ? model : model.model;
3117
- const effort = options?.effort ?? (typeof model === "string" ? CONFIGURED_CODEX_MODEL_EFFORTS.get(modelId) : model.effort);
3002
+ const effort = options?.effort === null ? void 0 : options?.effort ?? (typeof model === "string" ? CONFIGURED_CODEX_MODEL_EFFORTS.get(modelId) : model.effort);
3118
3003
  return {
3119
3004
  name: "codex",
3120
3005
  env: options?.env ?? {},
@@ -3375,7 +3260,18 @@ var createWorkBrief = (input) => {
3375
3260
  "acceptanceCriteria"
3376
3261
  ),
3377
3262
  exclusions: stringArray(input.exclusions, "exclusions"),
3378
- risk: enumValue(input.risk, ["low", "medium", "high", "critical"], "risk"),
3263
+ risk: enumValue(
3264
+ input.risk,
3265
+ ["low", "medium", "high", "critical", "unknown"],
3266
+ "risk"
3267
+ ),
3268
+ ...input.scope === void 0 ? {} : {
3269
+ scope: enumValue(
3270
+ input.scope,
3271
+ ["small", "substantial", "unknown"],
3272
+ "scope"
3273
+ )
3274
+ },
3379
3275
  verification: parseVerification(input.verification),
3380
3276
  unresolvedQuestions: stringArray(
3381
3277
  input.unresolvedQuestions,
@@ -3415,7 +3311,18 @@ var parseWorkBrief = (value) => {
3415
3311
  "acceptanceCriteria"
3416
3312
  ),
3417
3313
  exclusions: stringArray(value.exclusions, "exclusions"),
3418
- risk: enumValue(value.risk, ["low", "medium", "high", "critical"], "risk"),
3314
+ risk: enumValue(
3315
+ value.risk,
3316
+ ["low", "medium", "high", "critical", "unknown"],
3317
+ "risk"
3318
+ ),
3319
+ ...value.scope === void 0 ? {} : {
3320
+ scope: enumValue(
3321
+ value.scope,
3322
+ ["small", "substantial", "unknown"],
3323
+ "scope"
3324
+ )
3325
+ },
3419
3326
  verification: parseVerification(value.verification),
3420
3327
  unresolvedQuestions: stringArray(
3421
3328
  value.unresolvedQuestions,
@@ -3471,6 +3378,28 @@ var parseRepositoryPolicy = (value) => {
3471
3378
  if (!isRecord(value.worker)) {
3472
3379
  throw new ContractValidationError("policy.worker must be an object");
3473
3380
  }
3381
+ const workerModels = value.worker.models === void 0 ? void 0 : (() => {
3382
+ if (value.worker.model !== void 0) {
3383
+ throw new ContractValidationError(
3384
+ "policy.worker.model cannot be combined with policy.worker.models"
3385
+ );
3386
+ }
3387
+ if (!isRecord(value.worker.models)) {
3388
+ throw new ContractValidationError(
3389
+ "policy.worker.models must be an object"
3390
+ );
3391
+ }
3392
+ return {
3393
+ routine: nonEmptyString(
3394
+ value.worker.models.routine,
3395
+ "policy.worker.models.routine"
3396
+ ),
3397
+ strong: nonEmptyString(
3398
+ value.worker.models.strong,
3399
+ "policy.worker.models.strong"
3400
+ )
3401
+ };
3402
+ })();
3474
3403
  if (!Array.isArray(value.checks)) {
3475
3404
  throw new ContractValidationError("policy.checks must be an array");
3476
3405
  }
@@ -3564,7 +3493,7 @@ var parseRepositoryPolicy = (value) => {
3564
3493
  authorization: { required, allowedActors, autoStartRisk },
3565
3494
  worker: {
3566
3495
  provider: nonEmptyString(value.worker.provider, "policy.worker.provider"),
3567
- model: nonEmptyString(value.worker.model, "policy.worker.model"),
3496
+ ...workerModels === void 0 ? { model: nonEmptyString(value.worker.model, "policy.worker.model") } : { models: workerModels },
3568
3497
  sandbox: nonEmptyString(value.worker.sandbox, "policy.worker.sandbox"),
3569
3498
  skillRevision: nonEmptyString(
3570
3499
  value.worker.skillRevision,
@@ -3580,6 +3509,16 @@ var createRepositoryPolicy = (input) => parseRepositoryPolicy({
3580
3509
  contractVersion: WORKFLOW_CONTRACT_VERSION,
3581
3510
  ...input
3582
3511
  });
3512
+ var resolveAgentSelection = (policy, phase2, risk, scope) => {
3513
+ const role = phase2 === "review" && !(risk === "low" && scope === "small") ? "strong" : "routine";
3514
+ const worker = policy.worker;
3515
+ const modelPath = worker.models ? `policy.worker.models.${role}` : "policy.worker.model";
3516
+ return Object.freeze({
3517
+ provider: nonEmptyString(worker.provider, "policy.worker.provider"),
3518
+ model: nonEmptyString(worker.models?.[role] ?? worker.model, modelPath),
3519
+ role
3520
+ });
3521
+ };
3583
3522
  var parseCheckEvidence = (value) => {
3584
3523
  if (!isRecord(value)) {
3585
3524
  throw new ContractValidationError("check evidence must be an object");
@@ -3718,6 +3657,12 @@ var createAssignment = (input) => {
3718
3657
  briefHash: brief.hash,
3719
3658
  policyRevision: input.policy.revision,
3720
3659
  skillRevision: brief.skillRevision,
3660
+ agentSelection: resolveAgentSelection(
3661
+ input.policy,
3662
+ input.phase,
3663
+ brief.risk,
3664
+ brief.scope
3665
+ ),
3721
3666
  base: brief.base,
3722
3667
  head: input.head,
3723
3668
  createdAt: nonEmptyString(input.createdAt, "assignment.createdAt")
@@ -4087,6 +4032,25 @@ var parseAssignment = (value) => {
4087
4032
  throw new Error("Stored assignment base must be an object");
4088
4033
  }
4089
4034
  const head = candidate.head;
4035
+ const agentSelection = candidate.agentSelection;
4036
+ let parsedAgentSelection;
4037
+ if (agentSelection !== void 0) {
4038
+ if (typeof agentSelection !== "object" || agentSelection === null || Array.isArray(agentSelection)) {
4039
+ throw new Error("Stored assignment agent selection must be an object");
4040
+ }
4041
+ const selection = agentSelection;
4042
+ if (selection.role !== "routine" && selection.role !== "strong") {
4043
+ throw new Error("Stored assignment agent selection role is invalid");
4044
+ }
4045
+ parsedAgentSelection = {
4046
+ provider: requiredString(
4047
+ selection.provider,
4048
+ "assignment.agentSelection.provider"
4049
+ ),
4050
+ model: requiredString(selection.model, "assignment.agentSelection.model"),
4051
+ role: selection.role
4052
+ };
4053
+ }
4090
4054
  const revision = (value2, path2) => {
4091
4055
  if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) {
4092
4056
  throw new Error(`${path2} must be an object`);
@@ -4130,6 +4094,7 @@ var parseAssignment = (value) => {
4130
4094
  candidate.skillRevision,
4131
4095
  "assignment.skillRevision"
4132
4096
  ),
4097
+ ...parsedAgentSelection === void 0 ? {} : { agentSelection: parsedAgentSelection },
4133
4098
  base: revision(base, "assignment.base"),
4134
4099
  head: head === void 0 ? void 0 : revision(head, "assignment.head"),
4135
4100
  createdAt: requiredString(candidate.createdAt, "assignment.createdAt")
@@ -4319,9 +4284,6 @@ var PostgresTransaction = class {
4319
4284
  async one(text2, values, path2) {
4320
4285
  return row(await this.client.query(text2, values), path2);
4321
4286
  }
4322
- async optional(text2, values) {
4323
- return optionalRow(await this.client.query(text2, values));
4324
- }
4325
4287
  async insertEventIfAbsent(event) {
4326
4288
  const inserted = await this.client.query(
4327
4289
  `INSERT INTO shipyard_events (
@@ -6049,11 +6011,23 @@ var cancelledResult = (assignment, response, artifacts, message, kind) => ({
6049
6011
  evidence: response.report.evidence
6050
6012
  })
6051
6013
  });
6014
+ var sameAgentSelection = (left, right) => left.provider === right.provider && left.model === right.model && left.role === right.role;
6052
6015
  var executePhase = async (options) => {
6053
6016
  const assignment = deepFreeze(structuredClone(options.assignment));
6054
6017
  const trusted = deepFreeze(structuredClone(options.trusted));
6055
6018
  const untrusted = deepFreeze(structuredClone(options.untrusted));
6056
6019
  const controls = deepFreeze(structuredClone(options.controls));
6020
+ const resolvedSelection = resolveAgentSelection(
6021
+ trusted.policy,
6022
+ assignment.phase,
6023
+ trusted.brief.risk,
6024
+ trusted.brief.scope
6025
+ );
6026
+ const persistedSelection = assignment.agentSelection;
6027
+ if (persistedSelection !== void 0 && !sameAgentSelection(persistedSelection, resolvedSelection)) {
6028
+ throw new Error("Assignment agent selection does not match trusted policy");
6029
+ }
6030
+ const agentSelection = persistedSelection ?? resolvedSelection;
6057
6031
  const secretValues = /* @__PURE__ */ new Set();
6058
6032
  const controller = new AbortController();
6059
6033
  let timedOut = false;
@@ -6067,6 +6041,7 @@ var executePhase = async (options) => {
6067
6041
  }, options.controls.timeoutSeconds * 1e3);
6068
6042
  const request = {
6069
6043
  assignment,
6044
+ agentSelection,
6070
6045
  trusted,
6071
6046
  untrusted,
6072
6047
  controls,
@@ -6311,6 +6286,23 @@ var executePhase = async (options) => {
6311
6286
  })
6312
6287
  };
6313
6288
  };
6289
+ var resolvePhaseAgent = (options, request) => {
6290
+ if (options.resolveAgent === void 0 && request.trusted.policy.worker.models !== void 0) {
6291
+ throw new Error(
6292
+ "An agent resolver is required when repository policy defines worker models"
6293
+ );
6294
+ }
6295
+ const agent = options.resolveAgent === void 0 ? options.agent : options.resolveAgent(request.agentSelection);
6296
+ if (agent === void 0) {
6297
+ throw new Error("The phase engine has no agent for the selected model");
6298
+ }
6299
+ if (agent.name !== request.agentSelection.provider) {
6300
+ throw new Error(
6301
+ `Resolved agent provider "${agent.name}" does not match repository policy provider "${request.agentSelection.provider}"`
6302
+ );
6303
+ }
6304
+ return agent;
6305
+ };
6314
6306
  var runResultToResponse = (result3, branch) => {
6315
6307
  const commits = result3.commits.map((commit) => commit.sha);
6316
6308
  const headSha = commits.at(-1) ?? "";
@@ -6335,11 +6327,12 @@ var runResultToResponse = (result3, branch) => {
6335
6327
  };
6336
6328
  var createRunPhaseEngineAdapter = (options) => ({
6337
6329
  execute: async (request) => {
6338
- assertToolAllowlistSupport(options.agent);
6330
+ const agent = resolvePhaseAgent(options, request);
6331
+ assertToolAllowlistSupport(agent);
6339
6332
  const branch = request.checkout.branch;
6340
6333
  const baseBranch = request.checkout.immutable ? request.checkout.candidate?.sha ?? request.assignment.base.sha : request.assignment.base.sha;
6341
6334
  const result3 = await options.run({
6342
- agent: options.agent,
6335
+ agent,
6343
6336
  sandbox: options.sandbox,
6344
6337
  cwd: options.cwd,
6345
6338
  prompt: buildPrompt(request),
@@ -6377,7 +6370,8 @@ var sandboxResultToResponse = (result3, branch) => {
6377
6370
  };
6378
6371
  var createCreateSandboxPhaseEngineAdapter = (options) => ({
6379
6372
  execute: async (request) => {
6380
- assertToolAllowlistSupport(options.agent);
6373
+ const agent = resolvePhaseAgent(options, request);
6374
+ assertToolAllowlistSupport(agent);
6381
6375
  const branch = request.checkout.branch;
6382
6376
  const baseBranch = request.checkout.immutable ? request.checkout.candidate?.sha ?? request.assignment.base.sha : request.assignment.base.sha;
6383
6377
  const sandbox = await options.createSandbox({
@@ -6388,7 +6382,7 @@ var createCreateSandboxPhaseEngineAdapter = (options) => ({
6388
6382
  });
6389
6383
  try {
6390
6384
  const result3 = await sandbox.run({
6391
- agent: options.agent,
6385
+ agent,
6392
6386
  prompt: buildPrompt(request),
6393
6387
  toolAllowlist: request.controls.toolAllowlist,
6394
6388
  maxIterations: request.controls.maxIterations,
@@ -9030,22 +9024,6 @@ var GitHubIntegration = class {
9030
9024
  reason: ingest.reason
9031
9025
  };
9032
9026
  }
9033
- async recordRejected(input) {
9034
- const payloadRecord = record(input.payload);
9035
- const delivery = {
9036
- deliveryId: input.deliveryId,
9037
- eventName: input.eventName,
9038
- repository: payloadRecord ? repositoryName(payloadRecord) ?? "unknown" : "unknown",
9039
- senderLogin: payloadRecord ? sender(payloadRecord).login : "unknown",
9040
- receivedAt: this.now(),
9041
- payloadHash: payloadHash(input.body),
9042
- payload: input.payload,
9043
- status: "rejected",
9044
- reason: input.reason
9045
- };
9046
- const stored = await this.options.deliveryStore.recordDeliveryIfAbsent(delivery);
9047
- if (!stored.inserted) return;
9048
- }
9049
9027
  async rejectStored(delivery, reason) {
9050
9028
  await this.options.deliveryStore.updateDelivery({
9051
9029
  ...delivery,
@@ -9970,6 +9948,6 @@ var ReleaseIntegration = class {
9970
9948
  };
9971
9949
  var candidateMatchesApproval = (candidate, approval) => sameCandidate(candidate, approval.candidate);
9972
9950
 
9973
- export { ContractValidationError, CredentialNotAllowedError, CredentialUnavailableError, CwdError2 as CwdError, GitHubIntegration, GitHubPublication, InMemoryCoordinatorStorage, InMemoryGitHubStore, InMemoryReleaseStore, InMemoryRepairBatchStore, InMemoryTriageStore, LeaseBusyError, LeaseLostError, Output, PostgresCoordinatorStorage, ReleaseIntegration, StructuredOutputError, WORKFLOW_CONTRACT_VERSION, WorkflowCoordinator, WorkflowOperator, candidateMatchesApproval, claudeCode, claudeHostSessionPath, claudeSandboxSessionPath, closeSourceIssue, codex, completeSourceIssue, createAssignment, createCreateSandboxPhaseEngineAdapter, createFakePhaseEngineAdapter, createGitHubIssueBrief, createInMemoryArtifactStore, createRepositoryPolicy, createRunPhaseEngineAdapter, createSandbox, createWorkBrief, createWorktree, defaultInvestigator as defaultTriageInvestigator, encodeProjectPath, evaluateHandoffReadiness, executePhase, findClaudeSessionOnHost, findCodexSessionOnHost, interactive, isAuthorizationAllowed, mergeProtectedCandidate, parseCheckEvidence, parsePhaseResult, parseRepositoryPolicy, parseWorkBrief, prepareHumanHandoff, prepareIndependentReview, processHumanReviewDecision, redact2 as redactOperatorText, releaseCandidateKey, requireTransition, resolveHumanReviewDecision, reviewDecisionFor, run, runAuthorizedImplementation, runBoundedRepair, runIndependentReview, runTriage, scheduleBoundedRepair, transferClaudeSession, transferCodexSession, verifyGitHubWebhookSignature };
9951
+ export { ContractValidationError, CredentialNotAllowedError, CredentialUnavailableError, CwdError2 as CwdError, GitHubIntegration, GitHubPublication, InMemoryCoordinatorStorage, InMemoryGitHubStore, InMemoryReleaseStore, InMemoryRepairBatchStore, InMemoryTriageStore, LeaseBusyError, LeaseLostError, Output, PostgresCoordinatorStorage, ReleaseIntegration, StructuredOutputError, WORKFLOW_CONTRACT_VERSION, WorkflowCoordinator, WorkflowOperator, candidateMatchesApproval, claudeCode, claudeHostSessionPath, claudeSandboxSessionPath, closeSourceIssue, codex, completeSourceIssue, createAssignment, createCreateSandboxPhaseEngineAdapter, createFakePhaseEngineAdapter, createGitHubIssueBrief, createInMemoryArtifactStore, createRepositoryPolicy, createRunPhaseEngineAdapter, createSandbox, createWorkBrief, createWorktree, defaultInvestigator as defaultTriageInvestigator, encodeProjectPath, evaluateHandoffReadiness, executePhase, findClaudeSessionOnHost, findCodexSessionOnHost, interactive, isAuthorizationAllowed, mergeProtectedCandidate, parseCheckEvidence, parsePhaseResult, parseRepositoryPolicy, parseWorkBrief, prepareHumanHandoff, prepareIndependentReview, processHumanReviewDecision, redact2 as redactOperatorText, releaseCandidateKey, requireTransition, resolveAgentSelection, resolveHumanReviewDecision, reviewDecisionFor, run, runAuthorizedImplementation, runBoundedRepair, runIndependentReview, runTriage, scheduleBoundedRepair, transferClaudeSession, transferCodexSession, verifyGitHubWebhookSignature };
9974
9952
  //# sourceMappingURL=index.js.map
9975
9953
  //# sourceMappingURL=index.js.map