@snappedly-tools/shipyard 0.7.0-staging.36082887921 → 0.7.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.
Files changed (41) hide show
  1. package/README.md +41 -34
  2. package/dist/MountConfig-bZoCs4Dd.d.ts +26 -0
  3. package/dist/SandboxProvider-XJQqEdSf.d.ts +261 -0
  4. package/dist/{chunk-JI3HDDMS.js → chunk-56TDSWFU.js} +3 -3
  5. package/dist/{chunk-JI3HDDMS.js.map → chunk-56TDSWFU.js.map} +1 -1
  6. package/dist/chunk-ACD46ZM4.js +136 -0
  7. package/dist/chunk-ACD46ZM4.js.map +1 -0
  8. package/dist/{chunk-L6PX5QTU.js → chunk-FDYOTN55.js} +887 -262
  9. package/dist/chunk-FDYOTN55.js.map +1 -0
  10. package/dist/chunk-KMGNFXKN.js +38 -0
  11. package/dist/chunk-KMGNFXKN.js.map +1 -0
  12. package/dist/chunk-SOJTAJTF.js +78 -0
  13. package/dist/chunk-SOJTAJTF.js.map +1 -0
  14. package/dist/{chunk-44I2BL6E.js → chunk-WAXEMZUV.js} +33 -225
  15. package/dist/chunk-WAXEMZUV.js.map +1 -0
  16. package/dist/index.d.ts +97 -117
  17. package/dist/index.js +344 -322
  18. package/dist/index.js.map +1 -1
  19. package/dist/main.js +139 -235
  20. package/dist/main.js.map +1 -1
  21. package/dist/sandboxes/docker.d.ts +3 -1
  22. package/dist/sandboxes/docker.js +4 -2
  23. package/dist/sandboxes/no-sandbox.d.ts +37 -0
  24. package/dist/sandboxes/no-sandbox.js +4 -0
  25. package/dist/sandboxes/no-sandbox.js.map +1 -0
  26. package/dist/sandboxes/vercel.d.ts +104 -0
  27. package/dist/sandboxes/vercel.js +166 -0
  28. package/dist/sandboxes/vercel.js.map +1 -0
  29. package/dist/templates/blank/main.mts +13 -0
  30. package/dist/templates/blank/prompt.md +12 -0
  31. package/dist/templates/blank/template.json +4 -0
  32. package/dist/templates/parallel-planner/main.mts +9 -67
  33. package/dist/templates/parallel-planner-with-review/main.mts +12 -70
  34. package/dist/templates/sequential-reviewer/main.mts +3 -52
  35. package/dist/templates/simple-loop/main.mts +2 -51
  36. package/package.json +17 -1
  37. package/dist/MountConfig-BHnKnA4h.d.ts +0 -145
  38. package/dist/chunk-44I2BL6E.js.map +0 -1
  39. package/dist/chunk-L6PX5QTU.js.map +0 -1
  40. package/dist/templates/parallel-planner/planner-branch.mts +0 -151
  41. package/dist/templates/parallel-planner-with-review/planner-branch.mts +0 -151
package/dist/index.js CHANGED
@@ -1,25 +1,36 @@
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';
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';
5
8
  import { NodeContext, NodeFileSystem } from '@effect/platform-node';
6
- import { existsSync, mkdirSync, chmodSync, appendFileSync, constants } from 'fs';
9
+ import { mkdirSync, chmodSync, appendFileSync, constants } from 'fs';
7
10
  import path, { join, posix, dirname, relative } from 'path';
8
11
  import { styleText } from 'util';
9
12
  import { Context, Effect, Layer, Clock, Duration, Option, Deferred, Ref, Fiber } from 'effect';
10
13
  import { FileSystem } from '@effect/platform';
11
14
  import * as clack from '@clack/prompts';
12
- import { execFile } from 'child_process';
13
15
  import { readdir, access, readFile, mkdir, mkdtemp, writeFile, rm, lstat, open, chmod } from 'fs/promises';
14
16
  import { homedir, tmpdir } from 'os';
15
17
  import { createHmac, timingSafeEqual, createHash, randomUUID } from 'crypto';
16
18
 
17
19
  // src/resumePrecheck.ts
18
20
  var assertResumeSessionExists = async (params) => {
19
- const { provider, hostRepoDir, resumeSession } = params;
21
+ const { provider, sandboxTag, hostRepoDir, resumeSession } = params;
20
22
  if (!provider.sessionStorage) {
21
23
  throw new Error(`${provider.name} does not support resumeSession`);
22
24
  }
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
+ }
23
34
  const exists = await provider.sessionStorage.existsOnHost(
24
35
  hostRepoDir,
25
36
  resumeSession
@@ -413,12 +424,7 @@ var orchestrate = (options) => {
413
424
  yield* checkAbort();
414
425
  yield* display.status(label(`Iteration ${i}/${iterations}`), "info");
415
426
  const sandboxResult = yield* factory.withSandbox(
416
- ({
417
- hostWorktreePath,
418
- sandboxRepoPath,
419
- applyToHost,
420
- sessionTransferHandle
421
- }, sandbox) => withSandboxLifecycle(
427
+ ({ hostWorktreePath, sandboxRepoPath, applyToHost, bindMountHandle }, sandbox) => withSandboxLifecycle(
422
428
  {
423
429
  hostRepoDir,
424
430
  sandboxRepoDir: sandboxRepoPath,
@@ -434,14 +440,14 @@ var orchestrate = (options) => {
434
440
  (ctx) => Effect.gen(function* () {
435
441
  const iterationResumeSession = i === 1 ? options.resumeSession : void 0;
436
442
  const iterationForkSession = i === 1 ? options.forkSession : void 0;
437
- if (iterationResumeSession && sessionTransferHandle && provider.sessionStorage) {
443
+ if (iterationResumeSession && bindMountHandle && provider.sessionStorage) {
438
444
  yield* display.status(label("Resuming session"), "info");
439
445
  yield* Effect.tryPromise({
440
446
  try: () => provider.sessionStorage.resumeIntoSandbox({
441
447
  hostCwd: hostRepoDir,
442
448
  sandboxCwd: ctx.sandboxRepoDir,
443
449
  sessionId: iterationResumeSession,
444
- handle: sessionTransferHandle
450
+ handle: bindMountHandle
445
451
  }),
446
452
  catch: (e) => new SessionCaptureError({
447
453
  message: `Session resume failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -533,14 +539,14 @@ var orchestrate = (options) => {
533
539
  yield* display.status(label("Agent stopped"), "info");
534
540
  let sessionFilePath;
535
541
  let usage = streamUsage;
536
- if (provider.captureSessions && provider.sessionStorage && sessionId && sessionTransferHandle) {
542
+ if (provider.captureSessions && provider.sessionStorage && sessionId && bindMountHandle) {
537
543
  yield* display.status(label("Capturing session"), "info");
538
544
  yield* Effect.tryPromise({
539
545
  try: () => provider.sessionStorage.captureToHost({
540
546
  hostCwd: hostRepoDir,
541
547
  sandboxCwd: ctx.sandboxRepoDir,
542
548
  sessionId,
543
- handle: sessionTransferHandle
549
+ handle: bindMountHandle
544
550
  }),
545
551
  catch: (e) => new SessionCaptureError({
546
552
  message: `Session capture failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1013,9 +1019,18 @@ async function run(options) {
1013
1019
  agent: provider
1014
1020
  } = options;
1015
1021
  validateMaxIterations(maxIterations);
1016
- const branchStrategy = options.branchStrategy ?? {
1017
- type: "merge-to-head"
1018
- };
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
+ }
1019
1034
  if (options.resumeSession && maxIterations > 1) {
1020
1035
  throw new Error(
1021
1036
  "resumeSession cannot be combined with maxIterations > 1. Resume applies to iteration 1 only; multi-iteration resume semantics are not supported."
@@ -1049,6 +1064,7 @@ async function run(options) {
1049
1064
  if (options.resumeSession) {
1050
1065
  await assertResumeSessionExists({
1051
1066
  provider,
1067
+ sandboxTag: options.sandbox.tag,
1052
1068
  hostRepoDir,
1053
1069
  resumeSession: options.resumeSession
1054
1070
  });
@@ -1080,8 +1096,8 @@ async function run(options) {
1080
1096
  const currentHostBranch = await Effect.runPromise(
1081
1097
  getCurrentBranch(hostRepoDir)
1082
1098
  );
1083
- const resolvedBranch = branch ?? generateTempBranchName(options.name);
1084
- const targetBranch = branchStrategy.type === "merge-to-head" ? currentHostBranch : void 0;
1099
+ const resolvedBranch = effectiveBranchType === "head" ? currentHostBranch : branch ?? generateTempBranchName(options.name);
1100
+ const targetBranch = effectiveBranchType === "merge-to-head" ? currentHostBranch : void 0;
1085
1101
  const resolvedLogging = options.logging ?? {
1086
1102
  type: "file",
1087
1103
  path: buildDefaultLogPath(
@@ -1166,7 +1182,7 @@ async function run(options) {
1166
1182
  builtInArgKeysSet
1167
1183
  );
1168
1184
  }
1169
- const orchestrateBranch = branch;
1185
+ const orchestrateBranch = effectiveBranchType === "head" ? currentHostBranch : branch;
1170
1186
  const orchestrateResult = yield* orchestrate({
1171
1187
  hostRepoDir,
1172
1188
  iterations: maxIterations,
@@ -1317,16 +1333,25 @@ var raceAbortSignal = (effect, signal) => {
1317
1333
  var interactive = async (options) => {
1318
1334
  options.signal?.throwIfAborted();
1319
1335
  const { prompt, promptFile, hooks, agent: provider } = options;
1320
- const resolvedSandbox = options.sandbox;
1321
- const branchStrategy = options.branchStrategy ?? {
1322
- type: "merge-to-head"
1323
- };
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
+ }
1324
1348
  if (!provider.buildInteractiveArgs) {
1325
1349
  throw new Error(
1326
1350
  `Agent provider "${provider.name}" does not support buildInteractiveArgs, required for interactive sessions.`
1327
1351
  );
1328
1352
  }
1329
1353
  const branch = branchStrategy.type === "branch" ? branchStrategy.branch : void 0;
1354
+ const isHeadMode = branchStrategy.type === "head";
1330
1355
  const sandboxProvider = resolvedSandbox;
1331
1356
  const inner = Effect.gen(function* () {
1332
1357
  const hostRepoDir = yield* resolveCwd(options.cwd);
@@ -1343,7 +1368,7 @@ var interactive = async (options) => {
1343
1368
  });
1344
1369
  const effectiveEnv = { ...env, ...options.env ?? {} };
1345
1370
  const currentHostBranch = yield* getCurrentBranch(hostRepoDir);
1346
- const resolvedBranch = branch ?? generateTempBranchName(options.name);
1371
+ const resolvedBranch = branchStrategy.type === "head" ? currentHostBranch : branch ?? generateTempBranchName(options.name);
1347
1372
  let substitutedPrompt = rawPrompt;
1348
1373
  if (hasPromptSource && !isInlinePrompt) {
1349
1374
  const userArgs = options.promptArgs ?? {};
@@ -1383,38 +1408,86 @@ var interactive = async (options) => {
1383
1408
  const userArgs = options.promptArgs ?? {};
1384
1409
  yield* validateNoArgsWithInlinePrompt(userArgs);
1385
1410
  }
1386
- const lifecycleBranch = branch;
1411
+ const lifecycleBranch = isHeadMode ? currentHostBranch : branch;
1387
1412
  yield* d.intro(options.name ?? `${CLI_NAME} interactive`);
1388
1413
  yield* d.summary("Interactive Session", {
1389
1414
  Agent: options.name ?? provider.name,
1390
1415
  Sandbox: sandboxProvider.name,
1391
1416
  Branch: resolvedBranch
1392
1417
  });
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 })
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
+ )
1399
1427
  )
1400
- )
1401
- );
1428
+ );
1429
+ }
1402
1430
  const handle = yield* Effect.gen(function* () {
1403
- if (hooks?.host?.onWorktreeReady?.length) {
1404
- yield* runHostHooks(hooks.host.onWorktreeReady, worktreeInfo.path);
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;
1405
1490
  }
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;
1418
1491
  }).pipe(
1419
1492
  Effect.tapError(
1420
1493
  () => worktreeInfo ? remove(worktreeInfo.path).pipe(
@@ -1431,14 +1504,14 @@ var interactive = async (options) => {
1431
1504
  const interactiveExecFn = handle.interactiveExec.bind(handle);
1432
1505
  const sandbox = makeSandboxFromHandle(handle);
1433
1506
  const worktreePath = handle.worktreePath;
1434
- const applyToHost = () => syncOut(worktreeInfo.path, handle);
1507
+ const applyToHost = sandboxProvider.tag === "isolated" && worktreeInfo ? () => syncOut(worktreeInfo.path, handle) : () => Effect.void;
1435
1508
  const lifecycleEffect = withSandboxLifecycle(
1436
1509
  {
1437
1510
  hostRepoDir,
1438
1511
  sandboxRepoDir: worktreePath,
1439
1512
  hooks,
1440
1513
  branch: lifecycleBranch,
1441
- hostWorktreePath: worktreeInfo.path,
1514
+ hostWorktreePath: isHeadMode ? hostRepoDir : worktreeInfo?.path,
1442
1515
  applyToHost,
1443
1516
  timeouts: options.timeouts
1444
1517
  },
@@ -1451,7 +1524,7 @@ var interactive = async (options) => {
1451
1524
  );
1452
1525
  const interactiveArgs = provider.buildInteractiveArgs({
1453
1526
  prompt: fullPrompt,
1454
- dangerouslySkipPermissions: true
1527
+ dangerouslySkipPermissions: sandboxProvider.tag !== "none"
1455
1528
  });
1456
1529
  const result4 = yield* raceAbortSignal(
1457
1530
  Effect.promise(
@@ -1540,7 +1613,7 @@ var buildSandboxHandle = (ctx, close) => {
1540
1613
  sandboxRepoDir,
1541
1614
  sandbox,
1542
1615
  providerHandle,
1543
- sessionTransferHandle,
1616
+ bindMountHandle,
1544
1617
  applyToHost,
1545
1618
  timeouts,
1546
1619
  branchStrategy
@@ -1571,6 +1644,7 @@ var buildSandboxHandle = (ctx, close) => {
1571
1644
  if (runOptions.resumeSession) {
1572
1645
  await assertResumeSessionExists({
1573
1646
  provider,
1647
+ sandboxTag: ctx.providerTag,
1574
1648
  hostRepoDir,
1575
1649
  resumeSession: runOptions.resumeSession
1576
1650
  });
@@ -1641,7 +1715,7 @@ var buildSandboxHandle = (ctx, close) => {
1641
1715
  hostWorktreePath: worktreePath,
1642
1716
  sandboxRepoPath: sandboxRepoDir,
1643
1717
  applyToHost,
1644
- sessionTransferHandle
1718
+ bindMountHandle
1645
1719
  },
1646
1720
  sandbox
1647
1721
  ).pipe(
@@ -1861,13 +1935,24 @@ var buildSandboxHandle = (ctx, close) => {
1861
1935
  var createSandboxFromWorktree = async (options) => {
1862
1936
  const { branch, worktreePath, hostRepoDir } = options;
1863
1937
  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
+ }
1864
1948
  let providerHandle;
1865
1949
  let sandbox;
1866
1950
  let sandboxRepoDir;
1951
+ const isIsolated = options.sandbox.tag === "isolated";
1867
1952
  if (isTestMode) {
1868
1953
  sandbox = options._test.buildSandbox(worktreePath);
1869
1954
  sandboxRepoDir = worktreePath;
1870
- providerHandle = options._test.sessionTransferHandle;
1955
+ providerHandle = options._test.bindMountHandle;
1871
1956
  } else {
1872
1957
  const resolvedEnv = await Effect.runPromise(
1873
1958
  resolveEnv(hostRepoDir).pipe(Effect.provide(NodeContext.layer))
@@ -1878,15 +1963,42 @@ var createSandboxFromWorktree = async (options) => {
1878
1963
  sandboxProviderEnv: options.sandbox.env
1879
1964
  });
1880
1965
  const provider = options.sandbox;
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
- });
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
+ }
1890
2002
  const startResult = await Effect.runPromise(startEffect);
1891
2003
  providerHandle = startResult.handle;
1892
2004
  sandbox = startResult.sandbox;
@@ -1923,9 +2035,9 @@ var createSandboxFromWorktree = async (options) => {
1923
2035
  )
1924
2036
  );
1925
2037
  }
1926
- const applyToHost = providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
2038
+ const applyToHost = isIsolated && providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
1927
2039
  let closed = false;
1928
- const sessionTransferHandle = toSessionTransferHandle(providerHandle);
2040
+ const bindMountHandle = toSessionTransferHandle(providerHandle);
1929
2041
  return buildSandboxHandle(
1930
2042
  {
1931
2043
  branch,
@@ -1934,7 +2046,8 @@ var createSandboxFromWorktree = async (options) => {
1934
2046
  sandboxRepoDir,
1935
2047
  sandbox,
1936
2048
  providerHandle,
1937
- sessionTransferHandle,
2049
+ bindMountHandle,
2050
+ providerTag: options.sandbox.tag,
1938
2051
  applyToHost,
1939
2052
  timeouts: options.timeouts,
1940
2053
  branchStrategy: options.branchStrategy
@@ -1950,6 +2063,7 @@ var createSandboxFromWorktree = async (options) => {
1950
2063
  var createSandbox = async (options) => {
1951
2064
  const { branch } = options;
1952
2065
  const isTestMode = !!options._test?.buildSandbox;
2066
+ const isIsolated = options.sandbox.tag === "isolated";
1953
2067
  const { hostRepoDir, worktreePath, providerHandle, sandbox, sandboxRepoDir } = await Effect.runPromise(
1954
2068
  Effect.gen(function* () {
1955
2069
  const hostRepoDir2 = yield* resolveCwd(options.cwd);
@@ -1961,6 +2075,14 @@ var createSandbox = async (options) => {
1961
2075
  { branch, baseBranch: options.baseBranch }
1962
2076
  );
1963
2077
  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
+ }
1964
2086
  if (options.hooks?.host?.onWorktreeReady?.length) {
1965
2087
  yield* runHostHooks(
1966
2088
  options.hooks.host.onWorktreeReady,
@@ -1973,7 +2095,7 @@ var createSandbox = async (options) => {
1973
2095
  if (isTestMode) {
1974
2096
  sandbox2 = options._test.buildSandbox(worktreePath2);
1975
2097
  sandboxRepoDir2 = worktreePath2;
1976
- providerHandle2 = options._test.sessionTransferHandle;
2098
+ providerHandle2 = options._test.bindMountHandle;
1977
2099
  } else {
1978
2100
  const resolvedEnv = yield* resolveEnv(hostRepoDir2);
1979
2101
  const env = mergeProviderEnv({
@@ -1982,14 +2104,39 @@ var createSandbox = async (options) => {
1982
2104
  sandboxProviderEnv: options.sandbox.env
1983
2105
  });
1984
2106
  const provider = options.sandbox;
1985
- const startResult = yield* startSandbox({
2107
+ const startResult = yield* provider.tag === "isolated" ? startSandbox({
1986
2108
  provider,
1987
2109
  hostRepoDir: worktreePath2,
1988
2110
  sourceRepoDir: hostRepoDir2,
1989
2111
  env,
1990
- copyPaths: options.copyToWorktree,
1991
- copyTimeoutMs: options.timeouts?.copyToWorktreeMs
1992
- });
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
+ );
1993
2140
  providerHandle2 = startResult.handle;
1994
2141
  sandbox2 = startResult.sandbox;
1995
2142
  sandboxRepoDir2 = startResult.worktreePath;
@@ -2034,7 +2181,7 @@ var createSandbox = async (options) => {
2034
2181
  return { hostRepoDir: hostRepoDir2, worktreePath: worktreePath2, ...prepared };
2035
2182
  }).pipe(Effect.provide(NodeContext.layer))
2036
2183
  );
2037
- const applyToHost = providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
2184
+ const applyToHost = isIsolated && providerHandle ? () => syncOut(worktreePath, providerHandle) : () => Effect.void;
2038
2185
  let closed = false;
2039
2186
  const forceCleanup = () => {
2040
2187
  console.error(`
@@ -2064,7 +2211,7 @@ Worktree preserved at ${worktreePath}`);
2064
2211
  })
2065
2212
  );
2066
2213
  };
2067
- const sessionTransferHandle = toSessionTransferHandle(providerHandle);
2214
+ const bindMountHandle = toSessionTransferHandle(providerHandle);
2068
2215
  return buildSandboxHandle(
2069
2216
  {
2070
2217
  branch,
@@ -2073,7 +2220,8 @@ Worktree preserved at ${worktreePath}`);
2073
2220
  sandboxRepoDir,
2074
2221
  sandbox,
2075
2222
  providerHandle,
2076
- sessionTransferHandle,
2223
+ bindMountHandle,
2224
+ providerTag: options.sandbox.tag,
2077
2225
  applyToHost,
2078
2226
  timeouts: options.timeouts
2079
2227
  },
@@ -2083,82 +2231,6 @@ Worktree preserved at ${worktreePath}`);
2083
2231
  }
2084
2232
  );
2085
2233
  };
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
2162
2234
  var createWorktree = async (options) => {
2163
2235
  const branch = options.branchStrategy.type === "branch" ? options.branchStrategy.branch : void 0;
2164
2236
  const baseBranch = options.branchStrategy.type === "branch" ? options.branchStrategy.baseBranch : void 0;
@@ -2205,7 +2277,7 @@ var createWorktree = async (options) => {
2205
2277
  const worktreeInteractive = async (opts) => {
2206
2278
  opts.signal?.throwIfAborted();
2207
2279
  const { prompt, promptFile, hooks, agent: provider } = opts;
2208
- const resolvedSandbox = opts.sandbox;
2280
+ const resolvedSandbox = opts.sandbox ?? noSandbox();
2209
2281
  if (!provider.buildInteractiveArgs) {
2210
2282
  throw new Error(
2211
2283
  `Agent provider "${provider.name}" does not support buildInteractiveArgs, required for interactive sessions.`
@@ -2248,19 +2320,46 @@ var createWorktree = async (options) => {
2248
2320
  Sandbox: resolvedSandbox.name,
2249
2321
  Branch: worktreeInfo.branch
2250
2322
  });
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;
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
+ }
2264
2363
  return yield* Effect.gen(function* () {
2265
2364
  if (!handle.interactiveExec) {
2266
2365
  throw new Error(
@@ -2270,7 +2369,7 @@ var createWorktree = async (options) => {
2270
2369
  const interactiveExecFn = handle.interactiveExec.bind(handle);
2271
2370
  const sandbox = makeSandboxFromHandle(handle);
2272
2371
  const worktreePath = handle.worktreePath;
2273
- const applyToHost = () => syncOut(worktreeInfo.path, handle);
2372
+ const applyToHost = resolvedSandbox.tag === "isolated" ? () => syncOut(worktreeInfo.path, handle) : () => Effect.void;
2274
2373
  const lifecycleEffect = withSandboxLifecycle(
2275
2374
  {
2276
2375
  hostRepoDir,
@@ -2294,7 +2393,7 @@ var createWorktree = async (options) => {
2294
2393
  );
2295
2394
  const interactiveArgs = provider.buildInteractiveArgs({
2296
2395
  prompt: fullPrompt,
2297
- dangerouslySkipPermissions: true
2396
+ dangerouslySkipPermissions: resolvedSandbox.tag !== "none"
2298
2397
  });
2299
2398
  const result3 = yield* raceAbortSignal(
2300
2399
  Effect.promise(
@@ -2357,6 +2456,7 @@ var createWorktree = async (options) => {
2357
2456
  if (opts.resumeSession) {
2358
2457
  await assertResumeSessionExists({
2359
2458
  provider,
2459
+ sandboxTag: sandboxProvider.tag,
2360
2460
  hostRepoDir,
2361
2461
  resumeSession: opts.resumeSession
2362
2462
  });
@@ -2391,19 +2491,47 @@ var createWorktree = async (options) => {
2391
2491
  builtInArgKeysSet
2392
2492
  );
2393
2493
  }
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;
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
+ }
2405
2533
  const sandbox = makeSandboxFromHandle(handle);
2406
- const applyToHost = () => syncOut(worktreeInfo.path, handle);
2534
+ const applyToHost = sandboxProvider.tag === "isolated" ? () => syncOut(worktreeInfo.path, handle) : () => Effect.void;
2407
2535
  const resolvedLogging = opts.logging ?? {
2408
2536
  type: "file",
2409
2537
  path: buildDefaultLogPath(
@@ -2436,14 +2564,14 @@ var createWorktree = async (options) => {
2436
2564
  NodeFileSystem.layer
2437
2565
  );
2438
2566
  })() : ClackDisplay.layer;
2439
- const sessionTransferHandle = toSessionTransferHandle(handle);
2567
+ const bindMountHandle = toSessionTransferHandle(handle);
2440
2568
  const reuseFactoryLayer = Layer.succeed(SandboxFactory, {
2441
2569
  withSandbox: (makeEffect) => makeEffect(
2442
2570
  {
2443
2571
  hostWorktreePath: worktreeInfo.path,
2444
2572
  sandboxRepoPath: sandboxRepoDir,
2445
2573
  applyToHost,
2446
- sessionTransferHandle
2574
+ bindMountHandle
2447
2575
  },
2448
2576
  sandbox
2449
2577
  ).pipe(
@@ -2524,28 +2652,13 @@ var createWorktree = async (options) => {
2524
2652
  }
2525
2653
  };
2526
2654
  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
- }
2537
2655
  return createSandboxFromWorktree({
2538
2656
  branch: worktreeInfo.branch,
2539
2657
  worktreePath: worktreeInfo.path,
2540
2658
  hostRepoDir,
2541
2659
  sandbox: opts.sandbox,
2542
2660
  hooks: opts.hooks,
2543
- copyToWorktree: [
2544
- .../* @__PURE__ */ new Set([
2545
- ...options.copyToWorktree ?? [],
2546
- ...opts.copyToWorktree ?? []
2547
- ])
2548
- ],
2661
+ copyToWorktree: opts.copyToWorktree,
2549
2662
  timeouts: opts.timeouts,
2550
2663
  branchStrategy: options.branchStrategy,
2551
2664
  _test: opts._test
@@ -2914,7 +3027,8 @@ var makeClaudeSessionStorage = (options) => {
2914
3027
  sandboxProjectsDir
2915
3028
  );
2916
3029
  await writeSandboxFile(handle, sandboxPath, rewritten, "claude-res");
2917
- }
3030
+ },
3031
+ findByIdOnHost: (id) => findClaudeSessionOnHost(id, hostProjectsDir)
2918
3032
  };
2919
3033
  };
2920
3034
  var makeCodexSessionStorage = (options) => {
@@ -2952,7 +3066,8 @@ var makeCodexSessionStorage = (options) => {
2952
3066
  const rewritten = transferCodexSession(jsonl, hostCwd, sandboxCwd);
2953
3067
  const target = posix.join(sandboxSessionsDir, located.relativePath);
2954
3068
  await writeSandboxFile(handle, target, rewritten, "codex-res");
2955
- }
3069
+ },
3070
+ findByIdOnHost: (id) => findCodexSessionOnHost(id, hostSessionsDir)
2956
3071
  };
2957
3072
  };
2958
3073
  var parseCodexUsage = (usage) => {
@@ -2999,7 +3114,7 @@ var parseCodexStreamLine = (line) => {
2999
3114
  };
3000
3115
  var codex = (model, options) => {
3001
3116
  const modelId = typeof model === "string" ? model : model.model;
3002
- const effort = options?.effort === null ? void 0 : options?.effort ?? (typeof model === "string" ? CONFIGURED_CODEX_MODEL_EFFORTS.get(modelId) : model.effort);
3117
+ const effort = options?.effort ?? (typeof model === "string" ? CONFIGURED_CODEX_MODEL_EFFORTS.get(modelId) : model.effort);
3003
3118
  return {
3004
3119
  name: "codex",
3005
3120
  env: options?.env ?? {},
@@ -3260,18 +3375,7 @@ var createWorkBrief = (input) => {
3260
3375
  "acceptanceCriteria"
3261
3376
  ),
3262
3377
  exclusions: stringArray(input.exclusions, "exclusions"),
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
- },
3378
+ risk: enumValue(input.risk, ["low", "medium", "high", "critical"], "risk"),
3275
3379
  verification: parseVerification(input.verification),
3276
3380
  unresolvedQuestions: stringArray(
3277
3381
  input.unresolvedQuestions,
@@ -3311,18 +3415,7 @@ var parseWorkBrief = (value) => {
3311
3415
  "acceptanceCriteria"
3312
3416
  ),
3313
3417
  exclusions: stringArray(value.exclusions, "exclusions"),
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
- },
3418
+ risk: enumValue(value.risk, ["low", "medium", "high", "critical"], "risk"),
3326
3419
  verification: parseVerification(value.verification),
3327
3420
  unresolvedQuestions: stringArray(
3328
3421
  value.unresolvedQuestions,
@@ -3378,28 +3471,6 @@ var parseRepositoryPolicy = (value) => {
3378
3471
  if (!isRecord(value.worker)) {
3379
3472
  throw new ContractValidationError("policy.worker must be an object");
3380
3473
  }
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
- })();
3403
3474
  if (!Array.isArray(value.checks)) {
3404
3475
  throw new ContractValidationError("policy.checks must be an array");
3405
3476
  }
@@ -3493,7 +3564,7 @@ var parseRepositoryPolicy = (value) => {
3493
3564
  authorization: { required, allowedActors, autoStartRisk },
3494
3565
  worker: {
3495
3566
  provider: nonEmptyString(value.worker.provider, "policy.worker.provider"),
3496
- ...workerModels === void 0 ? { model: nonEmptyString(value.worker.model, "policy.worker.model") } : { models: workerModels },
3567
+ model: nonEmptyString(value.worker.model, "policy.worker.model"),
3497
3568
  sandbox: nonEmptyString(value.worker.sandbox, "policy.worker.sandbox"),
3498
3569
  skillRevision: nonEmptyString(
3499
3570
  value.worker.skillRevision,
@@ -3509,16 +3580,6 @@ var createRepositoryPolicy = (input) => parseRepositoryPolicy({
3509
3580
  contractVersion: WORKFLOW_CONTRACT_VERSION,
3510
3581
  ...input
3511
3582
  });
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
- };
3522
3583
  var parseCheckEvidence = (value) => {
3523
3584
  if (!isRecord(value)) {
3524
3585
  throw new ContractValidationError("check evidence must be an object");
@@ -3657,12 +3718,6 @@ var createAssignment = (input) => {
3657
3718
  briefHash: brief.hash,
3658
3719
  policyRevision: input.policy.revision,
3659
3720
  skillRevision: brief.skillRevision,
3660
- agentSelection: resolveAgentSelection(
3661
- input.policy,
3662
- input.phase,
3663
- brief.risk,
3664
- brief.scope
3665
- ),
3666
3721
  base: brief.base,
3667
3722
  head: input.head,
3668
3723
  createdAt: nonEmptyString(input.createdAt, "assignment.createdAt")
@@ -4032,25 +4087,6 @@ var parseAssignment = (value) => {
4032
4087
  throw new Error("Stored assignment base must be an object");
4033
4088
  }
4034
4089
  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
- }
4054
4090
  const revision = (value2, path2) => {
4055
4091
  if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) {
4056
4092
  throw new Error(`${path2} must be an object`);
@@ -4094,7 +4130,6 @@ var parseAssignment = (value) => {
4094
4130
  candidate.skillRevision,
4095
4131
  "assignment.skillRevision"
4096
4132
  ),
4097
- ...parsedAgentSelection === void 0 ? {} : { agentSelection: parsedAgentSelection },
4098
4133
  base: revision(base, "assignment.base"),
4099
4134
  head: head === void 0 ? void 0 : revision(head, "assignment.head"),
4100
4135
  createdAt: requiredString(candidate.createdAt, "assignment.createdAt")
@@ -4284,6 +4319,9 @@ var PostgresTransaction = class {
4284
4319
  async one(text2, values, path2) {
4285
4320
  return row(await this.client.query(text2, values), path2);
4286
4321
  }
4322
+ async optional(text2, values) {
4323
+ return optionalRow(await this.client.query(text2, values));
4324
+ }
4287
4325
  async insertEventIfAbsent(event) {
4288
4326
  const inserted = await this.client.query(
4289
4327
  `INSERT INTO shipyard_events (
@@ -6011,23 +6049,11 @@ var cancelledResult = (assignment, response, artifacts, message, kind) => ({
6011
6049
  evidence: response.report.evidence
6012
6050
  })
6013
6051
  });
6014
- var sameAgentSelection = (left, right) => left.provider === right.provider && left.model === right.model && left.role === right.role;
6015
6052
  var executePhase = async (options) => {
6016
6053
  const assignment = deepFreeze(structuredClone(options.assignment));
6017
6054
  const trusted = deepFreeze(structuredClone(options.trusted));
6018
6055
  const untrusted = deepFreeze(structuredClone(options.untrusted));
6019
6056
  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;
6031
6057
  const secretValues = /* @__PURE__ */ new Set();
6032
6058
  const controller = new AbortController();
6033
6059
  let timedOut = false;
@@ -6041,7 +6067,6 @@ var executePhase = async (options) => {
6041
6067
  }, options.controls.timeoutSeconds * 1e3);
6042
6068
  const request = {
6043
6069
  assignment,
6044
- agentSelection,
6045
6070
  trusted,
6046
6071
  untrusted,
6047
6072
  controls,
@@ -6286,23 +6311,6 @@ var executePhase = async (options) => {
6286
6311
  })
6287
6312
  };
6288
6313
  };
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
- };
6306
6314
  var runResultToResponse = (result3, branch) => {
6307
6315
  const commits = result3.commits.map((commit) => commit.sha);
6308
6316
  const headSha = commits.at(-1) ?? "";
@@ -6327,12 +6335,11 @@ var runResultToResponse = (result3, branch) => {
6327
6335
  };
6328
6336
  var createRunPhaseEngineAdapter = (options) => ({
6329
6337
  execute: async (request) => {
6330
- const agent = resolvePhaseAgent(options, request);
6331
- assertToolAllowlistSupport(agent);
6338
+ assertToolAllowlistSupport(options.agent);
6332
6339
  const branch = request.checkout.branch;
6333
6340
  const baseBranch = request.checkout.immutable ? request.checkout.candidate?.sha ?? request.assignment.base.sha : request.assignment.base.sha;
6334
6341
  const result3 = await options.run({
6335
- agent,
6342
+ agent: options.agent,
6336
6343
  sandbox: options.sandbox,
6337
6344
  cwd: options.cwd,
6338
6345
  prompt: buildPrompt(request),
@@ -6370,8 +6377,7 @@ var sandboxResultToResponse = (result3, branch) => {
6370
6377
  };
6371
6378
  var createCreateSandboxPhaseEngineAdapter = (options) => ({
6372
6379
  execute: async (request) => {
6373
- const agent = resolvePhaseAgent(options, request);
6374
- assertToolAllowlistSupport(agent);
6380
+ assertToolAllowlistSupport(options.agent);
6375
6381
  const branch = request.checkout.branch;
6376
6382
  const baseBranch = request.checkout.immutable ? request.checkout.candidate?.sha ?? request.assignment.base.sha : request.assignment.base.sha;
6377
6383
  const sandbox = await options.createSandbox({
@@ -6382,7 +6388,7 @@ var createCreateSandboxPhaseEngineAdapter = (options) => ({
6382
6388
  });
6383
6389
  try {
6384
6390
  const result3 = await sandbox.run({
6385
- agent,
6391
+ agent: options.agent,
6386
6392
  prompt: buildPrompt(request),
6387
6393
  toolAllowlist: request.controls.toolAllowlist,
6388
6394
  maxIterations: request.controls.maxIterations,
@@ -9024,6 +9030,22 @@ var GitHubIntegration = class {
9024
9030
  reason: ingest.reason
9025
9031
  };
9026
9032
  }
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
+ }
9027
9049
  async rejectStored(delivery, reason) {
9028
9050
  await this.options.deliveryStore.updateDelivery({
9029
9051
  ...delivery,
@@ -9948,6 +9970,6 @@ var ReleaseIntegration = class {
9948
9970
  };
9949
9971
  var candidateMatchesApproval = (candidate, approval) => sameCandidate(candidate, approval.candidate);
9950
9972
 
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 };
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 };
9952
9974
  //# sourceMappingURL=index.js.map
9953
9975
  //# sourceMappingURL=index.js.map