@snappedly-tools/shipyard 0.7.0 → 0.9.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 (63) hide show
  1. package/README.md +46 -43
  2. package/dist/{MountConfig-bZoCs4Dd.d.ts → MountConfig-K5ILnfht.d.ts} +1 -1
  3. package/dist/SandboxProvider-oUAwYlWm.d.ts +120 -0
  4. package/dist/{chunk-FDYOTN55.js → chunk-57EEKW3R.js} +361 -984
  5. package/dist/chunk-57EEKW3R.js.map +1 -0
  6. package/dist/{chunk-56TDSWFU.js → chunk-HXSZM52J.js} +3 -3
  7. package/dist/{chunk-56TDSWFU.js.map → chunk-HXSZM52J.js.map} +1 -1
  8. package/dist/{chunk-WAXEMZUV.js → chunk-JZUBT4WG.js} +241 -33
  9. package/dist/chunk-JZUBT4WG.js.map +1 -0
  10. package/dist/chunk-NQRFVKCU.js +1840 -0
  11. package/dist/chunk-NQRFVKCU.js.map +1 -0
  12. package/dist/chunk-Z5C4LHVP.js +137 -0
  13. package/dist/chunk-Z5C4LHVP.js.map +1 -0
  14. package/dist/createSandbox-DmbnWAZv.d.ts +739 -0
  15. package/dist/index.d.ts +99 -2606
  16. package/dist/index.js +412 -7487
  17. package/dist/index.js.map +1 -1
  18. package/dist/integrations/github.d.ts +52 -0
  19. package/dist/integrations/github.js +1049 -0
  20. package/dist/integrations/github.js.map +1 -0
  21. package/dist/integrations/releases.d.ts +136 -0
  22. package/dist/integrations/releases.js +500 -0
  23. package/dist/integrations/releases.js.map +1 -0
  24. package/dist/main.js +826 -472
  25. package/dist/main.js.map +1 -1
  26. package/dist/publication-BPoy_M9M.d.ts +1200 -0
  27. package/dist/sandboxes/docker.d.ts +2 -3
  28. package/dist/sandboxes/docker.js +2 -4
  29. package/dist/templates/parallel-planner/main.mts +78 -16
  30. package/dist/templates/parallel-planner/planner-branch.mts +151 -0
  31. package/dist/templates/parallel-planner/setup.sh +1 -0
  32. package/dist/templates/parallel-planner-with-review/main.mts +81 -19
  33. package/dist/templates/parallel-planner-with-review/planner-branch.mts +151 -0
  34. package/dist/templates/parallel-planner-with-review/setup.sh +1 -0
  35. package/dist/templates/sequential-reviewer/main.mts +59 -6
  36. package/dist/templates/sequential-reviewer/setup.sh +1 -0
  37. package/dist/templates/shared/setup.sh +1 -0
  38. package/dist/templates/simple-loop/main.mts +58 -5
  39. package/dist/templates/simple-loop/setup.sh +1 -0
  40. package/dist/workflow/coordinator/migrations/002_workflow_phase_records.sql +11 -0
  41. package/dist/workflow/coordinator/migrations/003_phase_record_schema_version.sql +6 -0
  42. package/dist/workflow.d.ts +472 -0
  43. package/dist/workflow.js +4345 -0
  44. package/dist/workflow.js.map +1 -0
  45. package/package.json +11 -15
  46. package/dist/SandboxProvider-XJQqEdSf.d.ts +0 -261
  47. package/dist/chunk-ACD46ZM4.js +0 -136
  48. package/dist/chunk-ACD46ZM4.js.map +0 -1
  49. package/dist/chunk-FDYOTN55.js.map +0 -1
  50. package/dist/chunk-KMGNFXKN.js +0 -38
  51. package/dist/chunk-KMGNFXKN.js.map +0 -1
  52. package/dist/chunk-SOJTAJTF.js +0 -78
  53. package/dist/chunk-SOJTAJTF.js.map +0 -1
  54. package/dist/chunk-WAXEMZUV.js.map +0 -1
  55. package/dist/sandboxes/no-sandbox.d.ts +0 -37
  56. package/dist/sandboxes/no-sandbox.js +0 -4
  57. package/dist/sandboxes/no-sandbox.js.map +0 -1
  58. package/dist/sandboxes/vercel.d.ts +0 -104
  59. package/dist/sandboxes/vercel.js +0 -166
  60. package/dist/sandboxes/vercel.js.map +0 -1
  61. package/dist/templates/blank/main.mts +0 -13
  62. package/dist/templates/blank/prompt.md +0 -12
  63. package/dist/templates/blank/template.json +0 -4
@@ -1,15 +1,15 @@
1
- import { toSessionTransferHandle, shellQuote } from './chunk-KMGNFXKN.js';
2
- import { existsSync, constants, lstatSync, realpathSync, statSync } from 'fs';
3
- import { mkdtemp, writeFile, chmod, mkdir, stat, copyFile, rm, readdir, lstat, readFile, open } from 'fs/promises';
1
+ import { existsSync, constants } from 'fs';
2
+ import { writeFile, chmod, mkdir, stat, copyFile, rm, readdir, mkdtemp, lstat, readFile, open } from 'fs/promises';
4
3
  import * as clack from '@clack/prompts';
5
4
  import { FileSystem } from '@effect/platform';
6
- import { join, basename, posix, dirname, resolve, normalize, relative, sep, isAbsolute } from 'path';
5
+ import { posix, join, basename, dirname, resolve, relative, sep, isAbsolute, normalize } from 'path';
7
6
  import { Context, Layer, Effect, Data, Exit, Duration, Option, Ref, Deferred, Schedule } from 'effect';
8
7
  import { styleText, promisify } from 'util';
9
- import { tmpdir, homedir } from 'os';
10
8
  import { randomBytes } from 'crypto';
11
9
  import { exec, execFile } from 'child_process';
10
+ import { tmpdir } from 'os';
12
11
 
12
+ // src/Display.ts
13
13
  var Display = class extends Context.Tag("Display")() {
14
14
  };
15
15
  var SilentDisplay = {
@@ -31,12 +31,21 @@ var SilentDisplay = {
31
31
  ),
32
32
  progress: (title, effect) => Effect.gen(function* () {
33
33
  const updates = [];
34
- const result = yield* effect((update) => updates.push(update));
34
+ const report = Object.assign(
35
+ (update) => {
36
+ updates.push(update);
37
+ },
38
+ { pause: () => {
39
+ }, resume: () => {
40
+ } }
41
+ );
42
+ const exit = yield* Effect.exit(effect(report));
35
43
  yield* Ref.update(ref, (entries) => [
36
44
  ...entries,
37
45
  { _tag: "progress", title, updates: [...updates] }
38
46
  ]);
39
- return result;
47
+ if (Exit.isFailure(exit)) return yield* Effect.failCause(exit.cause);
48
+ return exit.value;
40
49
  }),
41
50
  summary: (title, rows) => Ref.update(ref, (entries) => [
42
51
  ...entries,
@@ -140,7 +149,15 @@ var FileDisplay = {
140
149
  yield* appendToLog(`${title}...`);
141
150
  const start = Date.now();
142
151
  const updates = [];
143
- const result = yield* effect((update) => updates.push(update));
152
+ const report = Object.assign(
153
+ (update) => {
154
+ updates.push(update);
155
+ },
156
+ { pause: () => {
157
+ }, resume: () => {
158
+ } }
159
+ );
160
+ const result = yield* effect(report);
144
161
  for (const update of updates) {
145
162
  yield* appendToLog(
146
163
  ` ${update.message} (${update.current}/${update.total})`
@@ -211,25 +228,39 @@ var ClackDisplay = {
211
228
  ),
212
229
  progress: (title, effect) => Effect.acquireUseRelease(
213
230
  Effect.sync(() => {
214
- const progress2 = clack.progress({ max: 100 });
231
+ const progress2 = clack.progress({ max: 100, withGuide: false });
215
232
  let percent = 0;
233
+ let currentMessage = title;
234
+ let paused = false;
216
235
  progress2.start(title);
217
- const report = (update) => {
236
+ const report = ((update) => {
218
237
  const total = Math.max(1, update.total);
219
238
  const current = Math.min(total, Math.max(0, update.current));
220
239
  const nextPercent = Math.round(current / total * 100);
221
240
  const message = `${update.message} (${current}/${total})`;
241
+ currentMessage = message;
222
242
  if (nextPercent > percent) {
223
243
  progress2.advance(nextPercent - percent, message);
224
244
  } else {
225
245
  progress2.message(message);
226
246
  }
227
247
  percent = nextPercent;
248
+ });
249
+ report.pause = () => {
250
+ if (paused) return;
251
+ progress2.clear();
252
+ paused = true;
253
+ };
254
+ report.resume = () => {
255
+ if (!paused) return;
256
+ progress2.start(currentMessage);
257
+ paused = false;
228
258
  };
229
259
  return { progress: progress2, report };
230
260
  }),
231
261
  ({ report }) => effect(report),
232
- ({ progress: progress2 }, exit) => Effect.sync(() => {
262
+ ({ progress: progress2, report }, exit) => Effect.sync(() => {
263
+ report.resume();
233
264
  if (exit._tag === "Success") {
234
265
  progress2.stop(`${title} complete`);
235
266
  } else {
@@ -259,6 +290,67 @@ var ClackDisplay = {
259
290
  )
260
291
  })
261
292
  };
293
+
294
+ // src/SandboxProvider.ts
295
+ var createIsolatedSandboxProvider = (config) => ({
296
+ tag: "isolated",
297
+ name: config.name,
298
+ env: config.env ?? {},
299
+ create: config.create
300
+ });
301
+ var toSessionTransferHandle = (handle) => handle === void 0 ? void 0 : "copyFileIn" in handle ? handle : {
302
+ worktreePath: handle.worktreePath,
303
+ exec: (command, options) => handle.exec(command, options),
304
+ interactiveExec: handle.interactiveExec?.bind(handle),
305
+ copyFileIn: (source, destination) => handle.copyIn(source, destination),
306
+ copyFileOut: (source, destination) => handle.copyFileOut(source, destination),
307
+ close: () => handle.close()
308
+ };
309
+
310
+ // src/shutdownRegistry.ts
311
+ var teardownCallbacks = /* @__PURE__ */ new Set();
312
+ var listenersInstalled = false;
313
+ var runTeardowns = () => {
314
+ for (const teardown of teardownCallbacks) {
315
+ try {
316
+ teardown();
317
+ } catch {
318
+ }
319
+ }
320
+ };
321
+ var handleExit = () => {
322
+ runTeardowns();
323
+ };
324
+ var handleSignal = () => {
325
+ detachListeners();
326
+ runTeardowns();
327
+ process.exit(1);
328
+ };
329
+ var attachListeners = () => {
330
+ if (listenersInstalled) return;
331
+ listenersInstalled = true;
332
+ process.on("exit", handleExit);
333
+ process.on("SIGINT", handleSignal);
334
+ process.on("SIGTERM", handleSignal);
335
+ };
336
+ var detachListeners = () => {
337
+ if (!listenersInstalled) return;
338
+ listenersInstalled = false;
339
+ process.removeListener("exit", handleExit);
340
+ process.removeListener("SIGINT", handleSignal);
341
+ process.removeListener("SIGTERM", handleSignal);
342
+ };
343
+ var registerShutdown = (teardown) => {
344
+ teardownCallbacks.add(teardown);
345
+ attachListeners();
346
+ let active = true;
347
+ return () => {
348
+ if (!active) return;
349
+ active = false;
350
+ teardownCallbacks.delete(teardown);
351
+ if (teardownCallbacks.size === 0) detachListeners();
352
+ };
353
+ };
262
354
  var CwdError = class extends Data.TaggedError("CwdError") {
263
355
  };
264
356
  var resolveCwd = (cwd) => Effect.gen(function* () {
@@ -280,8 +372,6 @@ var resolveCwd = (cwd) => Effect.gen(function* () {
280
372
  }
281
373
  return resolved;
282
374
  });
283
-
284
- // src/errors.ts
285
375
  var ExecError = class extends Data.TaggedError("ExecError") {
286
376
  };
287
377
  var ExecHostError = class extends Data.TaggedError("ExecHostError") {
@@ -364,10 +454,7 @@ var WORKTREES_DIR = "worktrees";
364
454
  var PATCHES_DIR = "patches";
365
455
  var LOCKS_DIR = "locks";
366
456
  var RUNNER_DIR = "runner";
367
- var RUNNER_SANDBOX_MASK_DIR = "runner-sandbox-mask";
368
- var PARENT_GIT_SANDBOX_DIR = "/.shipyard-parent-git";
369
457
  var SYNC_BASE_REF = "refs/shipyard/sync-base";
370
- var CONTAINER_NAME_PREFIX = "shipyard-";
371
458
  var WINDOWS_DRIVE_PATH = /^[A-Za-z]:/;
372
459
  var assertSafeRelativePath = (relativePath, label = "relative path") => {
373
460
  if (relativePath.length === 0 || relativePath.includes("\0") || isAbsolute(relativePath) || WINDOWS_DRIVE_PATH.test(relativePath) || relativePath.startsWith("\\\\") || relativePath.split(/[\\/]/).some((part) => part === "..")) {
@@ -440,7 +527,7 @@ var SAFE_CONFIG = [
440
527
  ["checkout.recurseSubmodules", "false"]
441
528
  ];
442
529
  var COMMAND_CONFIG_PATTERN = "^(filter\\..*\\.(clean|smudge|process|required)|merge\\..*\\.driver|diff\\..*\\.(command|textconv)|difftool\\..*\\.cmd|mergetool\\..*\\.cmd)$";
443
- var runGit = (args, cwd, options = {}) => new Promise((resolve6, reject) => {
530
+ var runGit = (args, cwd, options = {}) => new Promise((resolve4, reject) => {
444
531
  execFile(
445
532
  "git",
446
533
  [...args],
@@ -458,11 +545,11 @@ var runGit = (args, cwd, options = {}) => new Promise((resolve6, reject) => {
458
545
  },
459
546
  (error, stdout, stderr) => {
460
547
  if (!error) {
461
- resolve6({ stdout, stderr, exitCode: 0 });
548
+ resolve4({ stdout, stderr, exitCode: 0 });
462
549
  return;
463
550
  }
464
551
  if (typeof error.code === "number") {
465
- resolve6({ stdout, stderr, exitCode: error.code });
552
+ resolve4({ stdout, stderr, exitCode: error.code });
466
553
  return;
467
554
  }
468
555
  reject(error);
@@ -869,6 +956,14 @@ var pruneStale = (repoDir) => Effect.gen(function* () {
869
956
  })
870
957
  )
871
958
  );
959
+
960
+ // src/shellQuote.ts
961
+ var shellQuote = (value) => {
962
+ if (value.includes("\0")) {
963
+ throw new Error("Cannot quote a string containing a NUL byte");
964
+ }
965
+ return `'${value.replaceAll("'", "'\\''")}'`;
966
+ };
872
967
  var PROTECTED_RUNNER_PATH = `${CONFIG_DIR}/${RUNNER_DIR}`;
873
968
  var pathKey = (value) => value.normalize("NFC").toLocaleLowerCase("en-US");
874
969
  var SAFE_RUNNER_ENV_KEYS = /* @__PURE__ */ new Set([
@@ -900,121 +995,14 @@ var assertExcludesRepositoryRunner = (relativePath) => {
900
995
  );
901
996
  }
902
997
  };
903
- var assertSafeRepositoryRunnerDirectories = (repoDir) => {
904
- const runnerDir = join(repoDir, CONFIG_DIR, RUNNER_DIR);
905
- const maskDir = join(repoDir, CONFIG_DIR, RUNNER_SANDBOX_MASK_DIR);
906
- for (const [name, path] of [
907
- ["repository runner", runnerDir],
908
- ["repository runner sandbox mask", maskDir]
909
- ]) {
910
- const stat2 = lstatSync(path);
911
- if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
912
- throw new Error(
913
- `The protected ${name} path must be a real directory: ${path}`
914
- );
915
- }
916
- }
917
- if (pathKey(realpathSync(runnerDir)) === pathKey(realpathSync(maskDir))) {
918
- throw new Error(
919
- "The protected repository runner and sandbox mask paths must be distinct directories."
920
- );
921
- }
922
- };
923
- var assertProtectedDirectoryIdentities = async (runnerDir, maskDir, inspect) => {
998
+ var assertProtectedDirectoryIdentities = async (runnerDir, inspect) => {
924
999
  if (!inspect) return;
925
1000
  const runner = await inspect(runnerDir);
926
- const mask = await inspect(maskDir);
927
1001
  if (!runner.directory || runner.symbolicLink) {
928
1002
  throw new Error(
929
1003
  `The protected repository runner path must be a real directory: ${runnerDir}`
930
1004
  );
931
1005
  }
932
- if (!mask.directory || mask.symbolicLink) {
933
- throw new Error(
934
- `The protected repository runner sandbox mask path must be a real directory: ${maskDir}`
935
- );
936
- }
937
- if (pathKey(runner.realPath) === pathKey(mask.realPath)) {
938
- throw new Error(
939
- "The protected repository runner and sandbox mask paths must be distinct directories."
940
- );
941
- }
942
- };
943
-
944
- // src/CopyToWorktree.ts
945
- var COPY_TO_WORKTREE_TIMEOUT_MS = 6e4;
946
- var getCopyOnWriteFlags = (platform) => platform === "darwin" ? ["-cR"] : ["-R", "--reflink=auto"];
947
- var copyToWorktree = (paths, hostRepoDir, worktreePath, timeoutMs) => {
948
- const effectiveTimeout = timeoutMs ?? COPY_TO_WORKTREE_TIMEOUT_MS;
949
- return Effect.gen(function* () {
950
- const cowFlags = getCopyOnWriteFlags(process.platform);
951
- for (const relativePath of paths) {
952
- let src;
953
- let dest;
954
- try {
955
- assertExcludesRepositoryRunner(relativePath);
956
- src = resolveSafeRelativePath(hostRepoDir, relativePath, "copy path");
957
- dest = resolveSafeRelativePath(worktreePath, relativePath, "copy path");
958
- } catch (error) {
959
- return yield* Effect.fail(
960
- new CopyToWorktreeError({
961
- message: `Refusing unsafe copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
962
- path: relativePath,
963
- stderr: error instanceof Error ? error.message : String(error),
964
- exitCode: null
965
- })
966
- );
967
- }
968
- if (!existsSync(src)) {
969
- continue;
970
- }
971
- yield* Effect.tryPromise({
972
- try: async () => {
973
- await assertNoSymlinkComponents(hostRepoDir, src, "copy source");
974
- await assertNoSymlinkComponents(worktreePath, dest, "copy target");
975
- },
976
- catch: (error) => new CopyToWorktreeError({
977
- message: `Refusing symlinked copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
978
- path: relativePath,
979
- stderr: error instanceof Error ? error.message : String(error),
980
- exitCode: null
981
- })
982
- });
983
- yield* Effect.async((resume) => {
984
- execFile("cp", [...cowFlags, src, dest], (error) => {
985
- if (error) {
986
- execFile("cp", ["-R", src, dest], (fallbackError, _, stderr) => {
987
- if (fallbackError) {
988
- resume(
989
- Effect.fail(
990
- new CopyToWorktreeError({
991
- message: `Failed to copy ${relativePath} to worktree: ${stderr || fallbackError.message}`,
992
- path: relativePath,
993
- stderr: stderr || fallbackError.message,
994
- exitCode: typeof fallbackError.code === "number" ? fallbackError.code : null
995
- })
996
- )
997
- );
998
- } else {
999
- resume(Effect.succeed(void 0));
1000
- }
1001
- });
1002
- } else {
1003
- resume(Effect.succeed(void 0));
1004
- }
1005
- });
1006
- });
1007
- }
1008
- }).pipe(
1009
- withTimeout(
1010
- effectiveTimeout,
1011
- () => new CopyToWorktreeTimeoutError({
1012
- message: `Copying files to worktree timed out after ${effectiveTimeout}ms`,
1013
- timeoutMs: effectiveTimeout,
1014
- paths
1015
- })
1016
- )
1017
- );
1018
1006
  };
1019
1007
 
1020
1008
  // src/RecoveryMessage.ts
@@ -1096,22 +1084,21 @@ var formatCommandBlock = (commands) => {
1096
1084
  }
1097
1085
  return commands.map((cmd, i) => i < commands.length - 1 ? ` ${cmd} && \\` : ` ${cmd}`).join("\n");
1098
1086
  };
1099
-
1100
- // src/syncOut.ts
1101
1087
  var execHostGit2 = (args, cwd) => Effect.tryPromise({
1102
1088
  try: () => execHostGit(args, cwd),
1103
- catch: (e) => new SyncError({
1089
+ catch: (error) => new SyncError({
1104
1090
  message: `Host command failed: git ${args.join(" ")}
1105
- ${e instanceof Error ? e.message : String(e)}`
1091
+ ${error instanceof Error ? error.message : String(error)}`
1106
1092
  })
1107
1093
  });
1108
- var execOk = (handle, command, options) => Effect.tryPromise({
1094
+ var execSandbox = (handle, command, options) => Effect.tryPromise({
1109
1095
  try: () => handle.exec(command, options),
1110
- catch: (e) => new SyncError({
1096
+ catch: (error) => new SyncError({
1111
1097
  message: `Sandbox exec failed: ${command}
1112
- ${e instanceof Error ? e.message : String(e)}`
1098
+ ${error instanceof Error ? error.message : String(error)}`
1113
1099
  })
1114
- }).pipe(
1100
+ });
1101
+ var execOk = (handle, command, options) => execSandbox(handle, command, options).pipe(
1115
1102
  Effect.flatMap(
1116
1103
  (result) => result.exitCode !== 0 ? Effect.fail(
1117
1104
  new SyncError({
@@ -1121,13 +1108,8 @@ ${result.stderr}`
1121
1108
  ) : Effect.succeed(result)
1122
1109
  )
1123
1110
  );
1124
- var execSandbox = (handle, command, options) => Effect.tryPromise({
1125
- try: () => handle.exec(command, options),
1126
- catch: (e) => new SyncError({
1127
- message: `Sandbox exec failed: ${command}
1128
- ${e instanceof Error ? e.message : String(e)}`
1129
- })
1130
- });
1111
+
1112
+ // src/syncOut.ts
1131
1113
  var isEmptyPatch = (patchPath) => Effect.tryPromise({
1132
1114
  try: async () => {
1133
1115
  const info = await stat(patchPath);
@@ -1524,8 +1506,6 @@ ${msg}`);
1524
1506
  });
1525
1507
  }
1526
1508
  });
1527
-
1528
- // src/SandboxLifecycle.ts
1529
1509
  var GIT_SETUP_TIMEOUT_MS = 1e4;
1530
1510
  var HOOK_TIMEOUT_MS = 6e4;
1531
1511
  var COMMIT_COLLECTION_TIMEOUT_MS = 3e4;
@@ -1859,401 +1839,75 @@ ${err instanceof Error ? err.message : String(err)}`
1859
1839
  }
1860
1840
  return { result, branch: finalBranch, commits };
1861
1841
  });
1862
- var execHostGit3 = (args, cwd) => Effect.tryPromise({
1863
- try: () => execHostGit(args, cwd),
1864
- catch: (e) => new SyncError({
1865
- message: `Host command failed: git ${args.join(" ")}
1866
- ${e instanceof Error ? e.message : String(e)}`
1842
+ var makeSandboxFromHandle = (handle) => ({
1843
+ exec: (command, options) => Effect.tryPromise({
1844
+ try: () => handle.exec(command, options),
1845
+ catch: (e) => new ExecError({
1846
+ command,
1847
+ message: `exec failed: ${e instanceof Error ? e.message : String(e)}`
1848
+ })
1849
+ }),
1850
+ copyIn: (hostPath, sandboxPath) => Effect.tryPromise({
1851
+ try: () => handle.copyIn(hostPath, sandboxPath),
1852
+ catch: (e) => new CopyError({
1853
+ message: `copyIn failed: ${e instanceof Error ? e.message : String(e)}`
1854
+ })
1855
+ }),
1856
+ copyFileOut: (sandboxPath, hostPath) => Effect.tryPromise({
1857
+ try: () => handle.copyFileOut(sandboxPath, hostPath),
1858
+ catch: (e) => new CopyError({
1859
+ message: `copyFileOut failed: ${e instanceof Error ? e.message : String(e)}`
1860
+ })
1867
1861
  })
1868
1862
  });
1869
- var execOk3 = (handle, command, options) => Effect.tryPromise({
1870
- try: () => handle.exec(command, options),
1871
- catch: (e) => new SyncError({
1872
- message: `Sandbox exec failed: ${command}
1873
- ${e instanceof Error ? e.message : String(e)}`
1863
+ var SANDBOX_REPO_DIR = "/home/agent/workspace";
1864
+ var SandboxFactory = class extends Context.Tag("SandboxFactory")() {
1865
+ };
1866
+ var SandboxConfig = class extends Context.Tag("SandboxConfig")() {
1867
+ };
1868
+ var printWorktreePreservedMessage = (worktreePath, reason) => {
1869
+ console.error(`
1870
+ ${reason}`);
1871
+ console.error(` To review: cd ${worktreePath}`);
1872
+ console.error(` To clean up: git worktree remove --force ${worktreePath}`);
1873
+ };
1874
+ var cleanupWorktree = (worktreePath, exit) => hasUncommittedChanges(worktreePath).pipe(
1875
+ Effect.catchAll(() => Effect.succeed(false)),
1876
+ Effect.flatMap((isDirty) => {
1877
+ if (isDirty) {
1878
+ printWorktreePreservedMessage(
1879
+ worktreePath,
1880
+ Exit.isSuccess(exit) ? `Run succeeded but worktree has uncommitted changes at ${worktreePath}` : `Worktree preserved at ${worktreePath}`
1881
+ );
1882
+ return Effect.succeed(worktreePath);
1883
+ }
1884
+ if (!Exit.isSuccess(exit)) {
1885
+ console.error(`
1886
+ Worktree removed (no uncommitted changes)`);
1887
+ }
1888
+ return remove(worktreePath).pipe(
1889
+ Effect.map(() => void 0)
1890
+ );
1874
1891
  })
1875
- }).pipe(
1876
- Effect.flatMap(
1877
- (result) => result.exitCode !== 0 ? Effect.fail(
1878
- new SyncError({
1879
- message: `Sandbox command failed (exit ${result.exitCode}): ${command}
1880
- ${result.stderr}`
1881
- })
1882
- ) : Effect.succeed(result)
1883
- )
1884
1892
  );
1885
- var syncIn = (hostRepoDir, handle) => Effect.gen(function* () {
1886
- const protectedRunnerPath = `${CONFIG_DIR}/${RUNNER_DIR}`;
1887
- const runnerObjects = yield* execHostGit3(
1888
- ["rev-list", "--objects", "--all", "--", protectedRunnerPath],
1889
- hostRepoDir
1890
- );
1891
- if (runnerObjects.trim().length > 0) {
1892
- return yield* Effect.fail(
1893
- new SyncError({
1894
- message: `Refusing to copy repository history into the sandbox because it contains protected repository runner files under ${protectedRunnerPath}. Remove those files from all refs before retrying.`
1895
- })
1896
- );
1897
- }
1898
- const branch = (yield* execHostGit3(
1899
- ["rev-parse", "--abbrev-ref", "HEAD"],
1900
- hostRepoDir
1901
- )).trim();
1902
- const bundleDir = yield* Effect.tryPromise({
1903
- try: () => mkdtemp(join(tmpdir(), `${RUNTIME_NAMESPACE}-bundle-`)),
1904
- catch: (e) => new SyncError({
1905
- message: `Failed to create temp dir: ${e instanceof Error ? e.message : String(e)}`
1906
- })
1907
- });
1908
- const bundleHostPath = join(bundleDir, "repo.bundle");
1909
- yield* Effect.ensuring(
1910
- Effect.gen(function* () {
1911
- yield* execHostGit3(
1912
- ["bundle", "create", bundleHostPath, "--all"],
1913
- hostRepoDir
1914
- );
1915
- const mkTempResult = yield* execOk3(
1916
- handle,
1917
- `mktemp -d -t ${RUNTIME_NAMESPACE}-XXXXXX`
1918
- );
1919
- const sandboxTmpDir = mkTempResult.stdout.trim();
1920
- const bundleSandboxPath = `${sandboxTmpDir}/repo.bundle`;
1921
- yield* Effect.tryPromise({
1922
- try: () => handle.copyIn(bundleHostPath, bundleSandboxPath),
1923
- catch: (e) => new SyncError({
1924
- message: `Failed to copy bundle into sandbox: ${e instanceof Error ? e.message : String(e)}`
1925
- })
1926
- });
1927
- const worktreePath = handle.worktreePath;
1928
- yield* execOk3(
1929
- handle,
1930
- `git clone ${shellQuote(bundleSandboxPath)} ${shellQuote(`${worktreePath}_clone`)}`
1931
- );
1932
- yield* execOk3(
1933
- handle,
1934
- `rm -rf ${shellQuote(worktreePath)} && mv ${shellQuote(`${worktreePath}_clone`)} ${shellQuote(worktreePath)}`
1935
- );
1936
- yield* execOk3(handle, `git checkout ${shellQuote(branch)}`, {
1937
- cwd: worktreePath
1893
+ var attachPreservedPath = (path, e) => {
1894
+ if (path !== void 0) {
1895
+ if (e instanceof AgentIdleTimeoutError) {
1896
+ return new AgentIdleTimeoutError({
1897
+ message: e.message,
1898
+ timeoutMs: e.timeoutMs,
1899
+ preservedWorktreePath: path
1938
1900
  });
1939
- yield* Effect.tryPromise({
1940
- try: () => handle.exec(`rm -rf ${shellQuote(sandboxTmpDir)}`),
1941
- catch: () => new SyncError({ message: "Failed to clean up sandbox temp dir" })
1901
+ }
1902
+ if (e instanceof AgentError) {
1903
+ return new AgentError({
1904
+ message: e.message,
1905
+ preservedWorktreePath: path
1942
1906
  });
1943
- const hostHead = (yield* execHostGit3(
1944
- ["rev-parse", "HEAD"],
1945
- hostRepoDir
1946
- )).trim();
1947
- const sandboxHead = (yield* execOk3(handle, "git rev-parse HEAD", {
1948
- cwd: worktreePath
1949
- })).stdout.trim();
1950
- if (hostHead !== sandboxHead) {
1951
- yield* Effect.fail(
1952
- new SyncError({
1953
- message: `HEAD mismatch after sync-in: host=${hostHead} sandbox=${sandboxHead}`
1954
- })
1955
- );
1956
- }
1957
- }),
1958
- // Clean up host-side bundle temp dir (runs regardless of success/failure)
1959
- Effect.promise(() => rm(bundleDir, { recursive: true, force: true }))
1960
- );
1961
- return { branch };
1962
- });
1963
-
1964
- // src/startSandbox.ts
1965
- var CONTAINER_START_TIMEOUT_MS = 12e4;
1966
- var SYNC_IN_TIMEOUT_MS = 12e4;
1967
- var COPY_PATHS_TIMEOUT_MS = 12e4;
1968
- var startSandbox = (options) => {
1969
- if (options.provider.tag === "bind-mount") {
1970
- return startBindMountSandbox(options);
1971
- }
1972
- if (options.provider.tag === "none") {
1973
- return startNoSandbox(options);
1974
- }
1975
- return startIsolatedSandbox(options);
1976
- };
1977
- var startNoSandbox = (options) => Effect.tryPromise({
1978
- try: () => options.provider.create({
1979
- worktreePath: options.worktreeOrRepoPath,
1980
- env: options.env
1981
- }),
1982
- catch: (e) => new WorktreeError({
1983
- message: `Provider '${options.provider.name}' create failed: ${e instanceof Error ? e.message : String(e)}`
1984
- })
1985
- }).pipe(
1986
- Effect.map((handle) => ({
1987
- handle,
1988
- sandbox: makeSandboxFromHandle(handle),
1989
- worktreePath: handle.worktreePath
1990
- }))
1991
- );
1992
- var startBindMountSandbox = (options) => Effect.tryPromise({
1993
- try: () => {
1994
- const usesRunnerMask = options.worktreeOrRepoPath === options.hostRepoDir && existsSync(join(options.hostRepoDir, CONFIG_DIR, RUNNER_DIR)) && existsSync(
1995
- join(options.hostRepoDir, CONFIG_DIR, RUNNER_SANDBOX_MASK_DIR)
1996
- );
1997
- if (usesRunnerMask) {
1998
- assertSafeRepositoryRunnerDirectories(options.hostRepoDir);
1999
- }
2000
- const rawMounts = [
2001
- {
2002
- hostPath: options.worktreeOrRepoPath,
2003
- sandboxPath: options.repoDir
2004
- },
2005
- ...options.gitMounts,
2006
- ...usesRunnerMask ? [
2007
- {
2008
- hostPath: join(
2009
- options.hostRepoDir,
2010
- CONFIG_DIR,
2011
- RUNNER_SANDBOX_MASK_DIR
2012
- ),
2013
- sandboxPath: posix.join(
2014
- options.repoDir,
2015
- CONFIG_DIR,
2016
- RUNNER_DIR
2017
- ),
2018
- readonly: true
2019
- }
2020
- ] : []
2021
- ];
2022
- const mounts = normalizeMounts(
2023
- rawMounts,
2024
- options.worktreeOrRepoPath,
2025
- SANDBOX_REPO_DIR
2026
- );
2027
- const worktreePath = process.platform === "win32" ? options.worktreeOrRepoPath.replace(/\\/g, "/") : options.worktreeOrRepoPath;
2028
- return options.provider.create({
2029
- worktreePath,
2030
- hostRepoPath: options.hostRepoDir,
2031
- mounts,
2032
- env: options.env
2033
- });
2034
- },
2035
- catch: (e) => new WorktreeError({
2036
- message: `Provider '${options.provider.name}' create failed: ${e instanceof Error ? e.message : String(e)}`
2037
- })
2038
- }).pipe(
2039
- Effect.map((handle) => ({
2040
- handle,
2041
- sandbox: makeSandboxFromHandle(handle),
2042
- worktreePath: handle.worktreePath
2043
- })),
2044
- withTimeout(
2045
- CONTAINER_START_TIMEOUT_MS,
2046
- () => new ContainerStartTimeoutError({
2047
- message: `Sandbox container start timed out after ${CONTAINER_START_TIMEOUT_MS}ms`,
2048
- timeoutMs: CONTAINER_START_TIMEOUT_MS
2049
- })
2050
- )
2051
- );
2052
- var startIsolatedSandbox = (options) => Effect.gen(function* () {
2053
- const handle = yield* Effect.tryPromise({
2054
- try: () => options.provider.create({
2055
- env: options.env,
2056
- hostRepoPath: options.sourceRepoDir ?? options.hostRepoDir
2057
- }),
2058
- catch: (e) => new WorktreeError({
2059
- message: `Isolated provider '${options.provider.name}' setup failed: ${e instanceof Error ? e.message : String(e)}`
2060
- })
2061
- }).pipe(
2062
- withTimeout(
2063
- CONTAINER_START_TIMEOUT_MS,
2064
- () => new ContainerStartTimeoutError({
2065
- message: `Isolated sandbox container start timed out after ${CONTAINER_START_TIMEOUT_MS}ms`,
2066
- timeoutMs: CONTAINER_START_TIMEOUT_MS
2067
- })
2068
- )
2069
- );
2070
- return yield* Effect.gen(function* () {
2071
- yield* syncIn(options.hostRepoDir, handle).pipe(
2072
- withTimeout(
2073
- SYNC_IN_TIMEOUT_MS,
2074
- () => new SyncInTimeoutError({
2075
- message: `Sync-in timed out after ${SYNC_IN_TIMEOUT_MS}ms`,
2076
- timeoutMs: SYNC_IN_TIMEOUT_MS
2077
- })
2078
- )
2079
- );
2080
- if (options.copyPaths && options.copyPaths.length > 0) {
2081
- const pathsToCopy = options.copyPaths;
2082
- yield* Effect.gen(function* () {
2083
- for (const relativePath of pathsToCopy) {
2084
- let hostPath;
2085
- let sandboxPath;
2086
- try {
2087
- assertExcludesRepositoryRunner(relativePath);
2088
- hostPath = resolveSafeRelativePath(
2089
- options.sourceRepoDir ?? options.hostRepoDir,
2090
- relativePath,
2091
- "copy path"
2092
- );
2093
- resolveSafeRelativePath(
2094
- handle.worktreePath,
2095
- relativePath,
2096
- "copy path"
2097
- );
2098
- sandboxPath = posix.join(handle.worktreePath, relativePath);
2099
- } catch (error) {
2100
- return yield* Effect.fail(
2101
- new WorktreeError({
2102
- message: `Refusing unsafe copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`
2103
- })
2104
- );
2105
- }
2106
- if (!existsSync(hostPath)) {
2107
- continue;
2108
- }
2109
- yield* Effect.tryPromise({
2110
- try: async () => {
2111
- await assertNoSymlinkComponents(
2112
- options.sourceRepoDir ?? options.hostRepoDir,
2113
- hostPath,
2114
- "copy source"
2115
- );
2116
- },
2117
- catch: (error) => new WorktreeError({
2118
- message: `Refusing symlinked copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`
2119
- })
2120
- });
2121
- yield* Effect.tryPromise({
2122
- try: () => handle.copyIn(hostPath, sandboxPath),
2123
- catch: (e) => new WorktreeError({
2124
- message: `Failed to copy ${relativePath} into sandbox: ${e instanceof Error ? e.message : String(e)}`
2125
- })
2126
- });
2127
- }
2128
- }).pipe(
2129
- withTimeout(
2130
- COPY_PATHS_TIMEOUT_MS,
2131
- () => new CopyToWorktreeTimeoutError({
2132
- message: `Copying paths to worktree timed out after ${COPY_PATHS_TIMEOUT_MS}ms`,
2133
- timeoutMs: COPY_PATHS_TIMEOUT_MS,
2134
- paths: pathsToCopy
2135
- })
2136
- )
2137
- );
2138
- }
2139
- return {
2140
- handle,
2141
- sandbox: makeSandboxFromHandle(handle),
2142
- worktreePath: handle.worktreePath
2143
- };
2144
- }).pipe(Effect.onError(() => Effect.promise(() => handle.close())));
2145
- });
2146
-
2147
- // src/SandboxFactory.ts
2148
- var getCopyIn = (handle) => {
2149
- if ("copyIn" in handle) {
2150
- return (hostPath, sandboxPath) => Effect.tryPromise({
2151
- try: () => handle.copyIn(hostPath, sandboxPath),
2152
- catch: (e) => new CopyError({
2153
- message: `copyIn failed: ${e instanceof Error ? e.message : String(e)}`
2154
- })
2155
- });
2156
- }
2157
- if ("copyFileIn" in handle) {
2158
- return (hostPath, sandboxPath) => Effect.tryPromise({
2159
- try: () => handle.copyFileIn(hostPath, sandboxPath),
2160
- catch: (e) => new CopyError({
2161
- message: `copyFileIn failed: ${e instanceof Error ? e.message : String(e)}`
2162
- })
2163
- });
2164
- }
2165
- return () => Effect.fail(
2166
- new CopyError({
2167
- message: "copyIn is not supported for this sandbox provider"
2168
- })
2169
- );
2170
- };
2171
- var makeSandboxFromHandle = (handle) => ({
2172
- exec: (command, options) => Effect.tryPromise({
2173
- try: () => handle.exec(command, options),
2174
- catch: (e) => new ExecError({
2175
- command,
2176
- message: `exec failed: ${e instanceof Error ? e.message : String(e)}`
2177
- })
2178
- }),
2179
- copyIn: getCopyIn(handle),
2180
- copyFileOut: "copyFileOut" in handle ? (sandboxPath, hostPath) => Effect.tryPromise({
2181
- try: () => handle.copyFileOut(sandboxPath, hostPath),
2182
- catch: (e) => new CopyError({
2183
- message: `copyFileOut failed: ${e instanceof Error ? e.message : String(e)}`
2184
- })
2185
- }) : () => Effect.fail(
2186
- new CopyError({
2187
- message: "copyFileOut is not supported for this sandbox provider"
2188
- })
2189
- )
2190
- });
2191
- var SANDBOX_REPO_DIR = "/home/agent/workspace";
2192
- var SandboxFactory = class extends Context.Tag("SandboxFactory")() {
2193
- };
2194
- var SandboxConfig = class extends Context.Tag("SandboxConfig")() {
2195
- };
2196
- var printWorktreePreservedMessage = (worktreePath, reason) => {
2197
- console.error(`
2198
- ${reason}`);
2199
- console.error(` To review: cd ${worktreePath}`);
2200
- console.error(` To clean up: git worktree remove --force ${worktreePath}`);
2201
- };
2202
- var cleanupWorktree = (worktreePath, exit) => hasUncommittedChanges(worktreePath).pipe(
2203
- Effect.catchAll(() => Effect.succeed(false)),
2204
- Effect.flatMap((isDirty) => {
2205
- if (isDirty) {
2206
- printWorktreePreservedMessage(
2207
- worktreePath,
2208
- Exit.isSuccess(exit) ? `Run succeeded but worktree has uncommitted changes at ${worktreePath}` : `Worktree preserved at ${worktreePath}`
2209
- );
2210
- return Effect.succeed(worktreePath);
2211
- }
2212
- if (!Exit.isSuccess(exit)) {
2213
- console.error(`
2214
- Worktree removed (no uncommitted changes)`);
2215
- }
2216
- return remove(worktreePath).pipe(
2217
- Effect.map(() => void 0)
2218
- );
2219
- })
2220
- );
2221
- var attachPreservedPath = (path, e) => {
2222
- if (path !== void 0) {
2223
- if (e instanceof AgentIdleTimeoutError) {
2224
- return new AgentIdleTimeoutError({
2225
- message: e.message,
2226
- timeoutMs: e.timeoutMs,
2227
- preservedWorktreePath: path
2228
- });
2229
- }
2230
- if (e instanceof AgentError) {
2231
- return new AgentError({
2232
- message: e.message,
2233
- preservedWorktreePath: path
2234
- });
2235
- }
1907
+ }
2236
1908
  }
2237
1909
  return e;
2238
1910
  };
2239
- var resolveGitMounts = (gitPath) => Effect.gen(function* () {
2240
- const fs = yield* FileSystem.FileSystem;
2241
- const stat2 = yield* fs.stat(gitPath);
2242
- if (stat2.type === "Directory") {
2243
- return [{ hostPath: gitPath, sandboxPath: gitPath }];
2244
- }
2245
- const content = (yield* fs.readFileString(gitPath)).trim();
2246
- const match = content.match(/^gitdir:\s*(.+)$/);
2247
- if (!match) {
2248
- return [{ hostPath: gitPath, sandboxPath: gitPath }];
2249
- }
2250
- const gitdirPath = match[1];
2251
- const parentGitDir = resolve(gitdirPath, "..", "..");
2252
- return [
2253
- { hostPath: gitPath, sandboxPath: gitPath },
2254
- { hostPath: parentGitDir, sandboxPath: parentGitDir }
2255
- ];
2256
- });
2257
1911
  var WorktreeDockerSandboxFactory = {
2258
1912
  layer: Layer.effect(
2259
1913
  SandboxFactory,
@@ -2269,11 +1923,9 @@ var WorktreeDockerSandboxFactory = {
2269
1923
  signal,
2270
1924
  timeouts
2271
1925
  } = yield* SandboxConfig;
2272
- const isHeadMode = branchStrategy.type === "head";
2273
1926
  const branch = branchStrategy.type === "branch" ? branchStrategy.branch : void 0;
2274
1927
  const baseBranch = branchStrategy.type === "branch" ? branchStrategy.baseBranch : void 0;
2275
1928
  const fileSystem = yield* FileSystem.FileSystem;
2276
- const display = yield* Display;
2277
1929
  const pruneAndCreate = () => pruneStale(hostRepoDir).pipe(
2278
1930
  Effect.catchAll(
2279
1931
  (e) => Effect.sync(() => {
@@ -2290,100 +1942,7 @@ var WorktreeDockerSandboxFactory = {
2290
1942
  );
2291
1943
  return {
2292
1944
  withSandbox: (makeEffect) => {
2293
- if (sandboxProvider.tag === "none") {
2294
- let preservedPath;
2295
- if (isHeadMode) {
2296
- return (hooks?.host?.onWorktreeReady?.length ? runHostHooks(
2297
- hooks.host.onWorktreeReady,
2298
- hostRepoDir,
2299
- signal
2300
- ) : Effect.void).pipe(
2301
- Effect.andThen(
2302
- Effect.acquireUseRelease(
2303
- startSandbox({
2304
- provider: sandboxProvider,
2305
- hostRepoDir,
2306
- env,
2307
- worktreeOrRepoPath: hostRepoDir
2308
- }),
2309
- ({ sandbox, worktreePath }) => makeEffect(
2310
- {
2311
- hostWorktreePath: hostRepoDir,
2312
- sandboxRepoPath: worktreePath
2313
- },
2314
- sandbox
2315
- ),
2316
- ({ handle }) => Effect.tryPromise({
2317
- try: () => handle.close(),
2318
- catch: () => void 0
2319
- }).pipe(Effect.orDie)
2320
- ).pipe(
2321
- Effect.map((value) => ({
2322
- value,
2323
- preservedWorktreePath: void 0
2324
- }))
2325
- )
2326
- )
2327
- );
2328
- }
2329
- return Effect.acquireUseRelease(
2330
- pruneAndCreate(),
2331
- (worktreeInfo) => (copyPaths && copyPaths.length > 0 ? display.spinner(
2332
- "Copying to worktree",
2333
- copyToWorktree(
2334
- copyPaths,
2335
- hostRepoDir,
2336
- worktreeInfo.path,
2337
- timeouts?.copyToWorktreeMs
2338
- )
2339
- ) : Effect.succeed(void 0)).pipe(
2340
- Effect.andThen(
2341
- hooks?.host?.onWorktreeReady?.length ? runHostHooks(
2342
- hooks.host.onWorktreeReady,
2343
- worktreeInfo.path,
2344
- signal
2345
- ) : Effect.void
2346
- ),
2347
- Effect.andThen(
2348
- Effect.acquireUseRelease(
2349
- startSandbox({
2350
- provider: sandboxProvider,
2351
- hostRepoDir,
2352
- env,
2353
- worktreeOrRepoPath: worktreeInfo.path
2354
- }),
2355
- ({ sandbox, worktreePath }) => makeEffect(
2356
- {
2357
- hostWorktreePath: worktreeInfo.path,
2358
- sandboxRepoPath: worktreePath
2359
- },
2360
- sandbox
2361
- ),
2362
- ({ handle }) => Effect.tryPromise({
2363
- try: () => handle.close(),
2364
- catch: () => void 0
2365
- }).pipe(Effect.orDie)
2366
- )
2367
- )
2368
- ),
2369
- (worktreeInfo, exit) => cleanupWorktree(worktreeInfo.path, exit).pipe(
2370
- Effect.tap((p) => {
2371
- preservedPath = p;
2372
- }),
2373
- Effect.asVoid,
2374
- Effect.orDie
2375
- )
2376
- ).pipe(
2377
- Effect.map((value) => ({
2378
- value,
2379
- preservedWorktreePath: preservedPath
2380
- })),
2381
- Effect.mapError(
2382
- (e) => attachPreservedPath(preservedPath, e)
2383
- )
2384
- );
2385
- }
2386
- if (sandboxProvider.tag === "isolated") {
1945
+ {
2387
1946
  let preservedPath;
2388
1947
  return Effect.acquireUseRelease(
2389
1948
  pruneAndCreate(),
@@ -2399,13 +1958,14 @@ var WorktreeDockerSandboxFactory = {
2399
1958
  hostRepoDir: worktreeInfo.path,
2400
1959
  sourceRepoDir: hostRepoDir,
2401
1960
  env,
2402
- copyPaths
1961
+ copyPaths,
1962
+ copyTimeoutMs: timeouts?.copyToWorktreeMs
2403
1963
  }),
2404
1964
  ({ sandbox, worktreePath, handle }) => makeEffect(
2405
1965
  {
2406
1966
  hostWorktreePath: worktreeInfo.path,
2407
1967
  sandboxRepoPath: worktreePath,
2408
- bindMountHandle: toSessionTransferHandle(handle),
1968
+ sessionTransferHandle: toSessionTransferHandle(handle),
2409
1969
  applyToHost: () => syncOut(
2410
1970
  worktreeInfo.path,
2411
1971
  handle
@@ -2437,374 +1997,191 @@ var WorktreeDockerSandboxFactory = {
2437
1997
  )
2438
1998
  );
2439
1999
  }
2440
- if (isHeadMode) {
2441
- const gitPath = join(hostRepoDir, ".git");
2442
- return (hooks?.host?.onWorktreeReady?.length ? runHostHooks(hooks.host.onWorktreeReady, hostRepoDir, signal) : Effect.void).pipe(
2443
- Effect.andThen(resolveGitMounts(gitPath)),
2444
- Effect.provideService(FileSystem.FileSystem, fileSystem),
2445
- Effect.mapError(
2446
- (e) => new WorktreeError({
2447
- message: `Failed to resolve git mounts: ${e}`
2448
- })
2449
- ),
2450
- Effect.flatMap(
2451
- (gitMounts) => (
2452
- // Patch git mounts for Windows worktree compatibility (ADR-0006)
2453
- patchGitMountsForWindows(
2454
- gitMounts,
2455
- hostRepoDir,
2456
- SANDBOX_REPO_DIR
2457
- )
2458
- )
2459
- ),
2460
- Effect.flatMap(
2461
- (gitMounts) => Effect.acquireUseRelease(
2462
- startSandbox({
2463
- provider: sandboxProvider,
2464
- hostRepoDir,
2465
- env,
2466
- worktreeOrRepoPath: hostRepoDir,
2467
- gitMounts,
2468
- repoDir: SANDBOX_REPO_DIR
2469
- }),
2470
- // Use
2471
- ({ sandbox, worktreePath, handle }) => makeEffect(
2472
- {
2473
- hostWorktreePath: hostRepoDir,
2474
- sandboxRepoPath: worktreePath,
2475
- bindMountHandle: handle
2476
- },
2477
- sandbox
2478
- ),
2479
- // Release
2480
- ({ handle }) => Effect.tryPromise({
2481
- try: () => handle.close(),
2482
- catch: () => void 0
2483
- }).pipe(Effect.orDie)
2484
- ).pipe(
2485
- Effect.map((value) => ({
2486
- value,
2487
- preservedWorktreePath: void 0
2488
- }))
2489
- )
2490
- )
2491
- );
2492
- }
2493
- let preservedWorktreePath;
2494
- return Effect.acquireUseRelease(
2495
- // Acquire: prune stale worktrees (best-effort), then create the worktree.
2496
- pruneAndCreate(),
2497
- // Use: copy files, run host hooks, resolve+patch git mounts, then start
2498
- // the sandbox under a nested acquireUseRelease.
2499
- (worktreeInfo) => (copyPaths && copyPaths.length > 0 ? display.spinner(
2500
- "Copying to worktree",
2501
- copyToWorktree(
2502
- copyPaths,
2503
- hostRepoDir,
2504
- worktreeInfo.path,
2505
- timeouts?.copyToWorktreeMs
2506
- )
2507
- ) : Effect.succeed(void 0)).pipe(
2508
- Effect.andThen(
2509
- hooks?.host?.onWorktreeReady?.length ? runHostHooks(
2510
- hooks.host.onWorktreeReady,
2511
- worktreeInfo.path,
2512
- signal
2513
- ) : Effect.void
2514
- ),
2515
- Effect.andThen(
2516
- resolveGitMounts(join(hostRepoDir, ".git")).pipe(
2517
- Effect.provideService(FileSystem.FileSystem, fileSystem),
2518
- Effect.mapError(
2519
- (e) => new WorktreeError({
2520
- message: `Failed to resolve git mounts: ${e}`
2521
- })
2522
- )
2523
- )
2524
- ),
2525
- // Patch git mounts for Windows worktree compatibility (ADR-0006)
2526
- Effect.flatMap(
2527
- (gitMounts) => patchGitMountsForWindows(
2528
- gitMounts,
2529
- worktreeInfo.path,
2530
- SANDBOX_REPO_DIR
2531
- )
2532
- ),
2533
- Effect.flatMap(
2534
- (gitMounts) => Effect.acquireUseRelease(
2535
- // sandboxProvider is guaranteed bind-mount here
2536
- // (isolated providers return early above)
2537
- startSandbox({
2538
- provider: sandboxProvider,
2539
- hostRepoDir,
2540
- env,
2541
- worktreeOrRepoPath: worktreeInfo.path,
2542
- gitMounts,
2543
- repoDir: SANDBOX_REPO_DIR
2544
- }),
2545
- ({ sandbox, worktreePath, handle }) => makeEffect(
2546
- {
2547
- hostWorktreePath: worktreeInfo.path,
2548
- sandboxRepoPath: worktreePath,
2549
- bindMountHandle: handle
2550
- },
2551
- sandbox
2552
- ),
2553
- ({ handle }) => Effect.tryPromise({
2554
- try: () => handle.close(),
2555
- catch: () => void 0
2556
- }).pipe(Effect.orDie)
2557
- )
2558
- )
2559
- ),
2560
- // Release: remove or preserve the worktree based on dirty state.
2561
- (worktreeInfo, exit) => cleanupWorktree(worktreeInfo.path, exit).pipe(
2562
- Effect.tap((p) => {
2563
- preservedWorktreePath = p;
2564
- }),
2565
- Effect.asVoid,
2566
- Effect.orDie
2567
- )
2568
- ).pipe(
2569
- Effect.map((value) => ({
2570
- value,
2571
- preservedWorktreePath
2572
- })),
2573
- Effect.mapError(
2574
- (e) => attachPreservedPath(preservedWorktreePath, e)
2575
- )
2576
- );
2577
2000
  }
2578
2001
  };
2579
2002
  })
2580
2003
  )
2581
2004
  };
2582
-
2583
- // src/mountUtils.ts
2584
- var defaultImageName = (repoDir) => {
2585
- const dirName = repoDir.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "local";
2586
- const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
2587
- return `${RUNTIME_NAMESPACE}:${sanitized || "local"}`;
2588
- };
2589
- var expandTilde = (p, homeDirPath) => {
2590
- const home = homeDirPath ?? homedir();
2591
- if (p === "~") return home;
2592
- if (p.startsWith("~/") || p.startsWith("~\\")) return home + "/" + p.slice(2);
2593
- return p;
2594
- };
2595
- var resolveHostPath = (hostPath) => {
2596
- const expanded = expandTilde(hostPath);
2597
- return isAbsolute(expanded) ? expanded : resolve(process.cwd(), expanded);
2598
- };
2599
- var resolveSandboxPath = (sandboxPath, sandboxHomedir) => {
2600
- const hasTilde = sandboxPath === "~" || sandboxPath.startsWith("~/") || sandboxPath.startsWith("~\\");
2601
- if (hasTilde && sandboxHomedir === void 0) {
2602
- throw new Error(
2603
- `sandboxPath "${sandboxPath}" contains a tilde but the provider has no sandboxHomedir set`
2604
- );
2605
- }
2606
- const expanded = hasTilde ? expandTilde(sandboxPath, sandboxHomedir) : sandboxPath;
2607
- return isAbsolute(expanded) ? resolve(expanded) : resolve(SANDBOX_REPO_DIR, expanded);
2608
- };
2609
- var resolveUserMounts = (mounts, sandboxHomedir) => mounts.map((m) => {
2610
- const resolvedHostPath = resolveHostPath(m.hostPath);
2611
- if (!existsSync(resolvedHostPath)) {
2612
- throw new Error(
2613
- `Mount hostPath does not exist: ${m.hostPath}` + (m.hostPath !== resolvedHostPath ? ` (resolved to ${resolvedHostPath})` : "")
2005
+ var syncIn = (hostRepoDir, handle) => Effect.gen(function* () {
2006
+ const protectedRunnerPath = `${CONFIG_DIR}/${RUNNER_DIR}`;
2007
+ const runnerObjects = yield* execHostGit2(
2008
+ ["rev-list", "--objects", "--all", "--", protectedRunnerPath],
2009
+ hostRepoDir
2010
+ );
2011
+ if (runnerObjects.trim().length > 0) {
2012
+ return yield* Effect.fail(
2013
+ new SyncError({
2014
+ message: `Refusing to copy repository history into the sandbox because it contains protected repository runner files under ${protectedRunnerPath}. Remove those files from all refs before retrying.`
2015
+ })
2614
2016
  );
2615
2017
  }
2616
- return {
2617
- hostPath: resolvedHostPath,
2618
- sandboxPath: resolveSandboxPath(m.sandboxPath, sandboxHomedir),
2619
- ...m.readonly ? { readonly: true } : {}
2620
- };
2621
- });
2622
- var normalizeMounts = (mounts, worktreeHostPath, sandboxRepoDir, platform = process.platform) => {
2623
- if (platform !== "win32") return mounts;
2624
- const normalizedWorktree = worktreeHostPath.replace(/\\/g, "/");
2625
- return mounts.map((m) => {
2626
- const hostPath = m.hostPath.replace(/\\/g, "/");
2627
- let sandboxPath = m.sandboxPath;
2628
- if (/^[A-Za-z]:[/\\]/.test(sandboxPath) || sandboxPath.includes("\\")) {
2629
- const normalizedSandboxPath = sandboxPath.replace(/\\/g, "/");
2630
- if (normalizedSandboxPath.startsWith(normalizedWorktree + "/")) {
2631
- const relativeSuffix = normalizedSandboxPath.slice(
2632
- normalizedWorktree.length
2633
- );
2634
- sandboxPath = sandboxRepoDir + relativeSuffix;
2635
- } else if (normalizedSandboxPath === normalizedWorktree) {
2636
- sandboxPath = sandboxRepoDir;
2637
- } else {
2638
- sandboxPath = normalizedSandboxPath;
2639
- }
2640
- }
2641
- return { ...m, hostPath, sandboxPath };
2642
- });
2643
- };
2644
- var parseGitdirPath = (gitdirPath) => {
2645
- const normalized = gitdirPath.replace(/\\/g, "/").replace(/\/+$/, "");
2646
- const segments = normalized.split("/");
2647
- const worktreeName = segments.pop();
2648
- segments.pop();
2649
- const parentGitDir = segments.join("/");
2650
- return { worktreeName, parentGitDir };
2651
- };
2652
- var patchGitMountsForWindows = (gitMounts, worktreeHostPath, sandboxRepoDir, readFile2, statFile, platform = process.platform) => Effect.gen(function* () {
2653
- if (platform !== "win32") return gitMounts;
2654
- const _readFile = readFile2 ?? (async (p) => {
2655
- const { readFile: rf } = await import('fs/promises');
2656
- return rf(p, "utf-8");
2657
- });
2658
- const _statFile = statFile ?? (async (p) => {
2659
- const { stat: stat2 } = await import('fs/promises');
2660
- const s = await stat2(p);
2661
- return s.isDirectory() ? "directory" : "file";
2662
- });
2663
- const gitEntryPath = join(worktreeHostPath, ".git");
2664
- const gitEntryType = yield* Effect.tryPromise({
2665
- try: () => _statFile(gitEntryPath),
2666
- catch: () => null
2667
- }).pipe(Effect.catchAll(() => Effect.succeed(null)));
2668
- if (gitEntryType === null) return gitMounts;
2669
- if (gitEntryType === "directory") return gitMounts;
2670
- const content = yield* Effect.tryPromise({
2671
- try: () => _readFile(gitEntryPath),
2672
- catch: () => null
2673
- }).pipe(
2674
- Effect.map((s) => s.trim()),
2675
- Effect.catchAll(() => Effect.succeed(null))
2676
- );
2677
- if (content === null) return gitMounts;
2678
- const match = content.match(/^gitdir:\s*(.+)$/);
2679
- if (!match) return gitMounts;
2680
- const gitdirPath = match[1];
2681
- const { parentGitDir, worktreeName } = parseGitdirPath(gitdirPath);
2682
- const correctedGitdir = `${PARENT_GIT_SANDBOX_DIR}/worktrees/${worktreeName}`;
2683
- const tempDir = yield* Effect.tryPromise({
2684
- try: () => mkdtemp(join(tmpdir(), `${CONTAINER_NAME_PREFIX}git-`)),
2685
- catch: (e) => new WorktreeError({
2686
- message: `Failed to create temp dir for git override: ${e instanceof Error ? e.message : String(e)}`
2687
- })
2688
- });
2689
- const tempGitFile = join(tempDir, "git-override");
2690
- yield* Effect.tryPromise({
2691
- try: () => writeFile(tempGitFile, `gitdir: ${correctedGitdir}
2692
- `),
2693
- catch: (e) => new WorktreeError({
2694
- message: `Failed to write git override file: ${e instanceof Error ? e.message : String(e)}`
2018
+ const branch = (yield* execHostGit2(
2019
+ ["rev-parse", "--abbrev-ref", "HEAD"],
2020
+ hostRepoDir
2021
+ )).trim();
2022
+ const bundleDir = yield* Effect.tryPromise({
2023
+ try: () => mkdtemp(join(tmpdir(), `${RUNTIME_NAMESPACE}-bundle-`)),
2024
+ catch: (e) => new SyncError({
2025
+ message: `Failed to create temp dir: ${e instanceof Error ? e.message : String(e)}`
2695
2026
  })
2696
2027
  });
2697
- const normalizedParentGitDir = parentGitDir.replace(/\\/g, "/");
2698
- const gitFileHostPath = gitEntryPath.replace(/\\/g, "/");
2699
- const correctedMounts = [];
2700
- let replacedGitFile = false;
2701
- for (const m of gitMounts) {
2702
- const normalizedHostPath = m.hostPath.replace(/\\/g, "/");
2703
- if (normalizedHostPath === normalizedParentGitDir) {
2704
- correctedMounts.push({ ...m, sandboxPath: PARENT_GIT_SANDBOX_DIR });
2705
- } else if (normalizedHostPath === gitFileHostPath) {
2706
- correctedMounts.push({
2707
- ...m,
2708
- hostPath: tempGitFile,
2709
- sandboxPath: `${sandboxRepoDir}/.git`
2028
+ const bundleHostPath = join(bundleDir, "repo.bundle");
2029
+ yield* Effect.ensuring(
2030
+ Effect.gen(function* () {
2031
+ yield* execHostGit2(
2032
+ ["bundle", "create", bundleHostPath, "--all"],
2033
+ hostRepoDir
2034
+ );
2035
+ const mkTempResult = yield* execOk(
2036
+ handle,
2037
+ `mktemp -d -t ${RUNTIME_NAMESPACE}-XXXXXX`
2038
+ );
2039
+ const sandboxTmpDir = mkTempResult.stdout.trim();
2040
+ const bundleSandboxPath = `${sandboxTmpDir}/repo.bundle`;
2041
+ yield* Effect.tryPromise({
2042
+ try: () => handle.copyIn(bundleHostPath, bundleSandboxPath),
2043
+ catch: (e) => new SyncError({
2044
+ message: `Failed to copy bundle into sandbox: ${e instanceof Error ? e.message : String(e)}`
2045
+ })
2710
2046
  });
2711
- replacedGitFile = true;
2712
- } else {
2713
- correctedMounts.push(m);
2714
- }
2715
- }
2716
- if (!replacedGitFile) {
2717
- correctedMounts.push({
2718
- hostPath: tempGitFile,
2719
- sandboxPath: `${sandboxRepoDir}/.git`
2720
- });
2721
- }
2722
- return correctedMounts;
2723
- });
2724
- var formatVolumeMount = (mount, selinuxLabel) => {
2725
- const base = `${mount.hostPath}:${mount.sandboxPath}`;
2726
- const options = [mount.readonly ? "ro" : void 0, selinuxLabel || void 0].filter((option) => option !== void 0).join(",");
2727
- return options ? `${base}:${options}` : base;
2728
- };
2729
- var processFileMountParents = (mounts, sandboxHomedir, statFn = statSync) => {
2730
- const parentDirs = /* @__PURE__ */ new Set();
2731
- for (const mount of mounts) {
2732
- let isFile;
2733
- try {
2734
- isFile = statFn(mount.hostPath).isFile();
2735
- } catch {
2736
- continue;
2737
- }
2738
- if (!isFile) continue;
2739
- const parentDir = resolve(dirname(mount.sandboxPath));
2740
- const homeDir = resolve(sandboxHomedir);
2741
- if (parentDir === homeDir) continue;
2742
- const fromHome = relative(homeDir, parentDir);
2743
- if (fromHome === ".." || fromHome.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(fromHome)) {
2744
- throw new Error(
2745
- `Cannot mount file to '${mount.sandboxPath}': parent directory '${parentDir}' is outside the sandbox home directory ('${homeDir}'). Mount the parent directory instead, or rebuild the image with '${parentDir}' pre-created.`
2047
+ const worktreePath = handle.worktreePath;
2048
+ yield* execOk(
2049
+ handle,
2050
+ `git clone ${shellQuote(bundleSandboxPath)} ${shellQuote(`${worktreePath}_clone`)}`
2746
2051
  );
2747
- }
2748
- parentDirs.add(parentDir);
2749
- }
2750
- return [...parentDirs];
2751
- };
2752
- var assertIsolatedWorkspaceMounts = (mounts) => {
2753
- for (const mount of mounts) {
2754
- const path = posix.resolve(mount.sandboxPath);
2755
- if (path === "/" || path === SANDBOX_REPO_DIR || path.startsWith(`${SANDBOX_REPO_DIR}/`) || SANDBOX_REPO_DIR.startsWith(`${path}/`)) {
2756
- throw new Error(
2757
- `Mount overlaps the isolated workspace: ${mount.sandboxPath}. Mount outside ${SANDBOX_REPO_DIR} or use copyToWorktree.`
2052
+ yield* execOk(
2053
+ handle,
2054
+ `rm -rf ${shellQuote(worktreePath)} && mv ${shellQuote(`${worktreePath}_clone`)} ${shellQuote(worktreePath)}`
2758
2055
  );
2759
- }
2760
- }
2761
- };
2056
+ yield* execOk(handle, `git checkout ${shellQuote(branch)}`, {
2057
+ cwd: worktreePath
2058
+ });
2059
+ yield* Effect.tryPromise({
2060
+ try: () => handle.exec(`rm -rf ${shellQuote(sandboxTmpDir)}`),
2061
+ catch: () => new SyncError({ message: "Failed to clean up sandbox temp dir" })
2062
+ });
2063
+ const hostHead = (yield* execHostGit2(
2064
+ ["rev-parse", "HEAD"],
2065
+ hostRepoDir
2066
+ )).trim();
2067
+ const sandboxHead = (yield* execOk(handle, "git rev-parse HEAD", {
2068
+ cwd: worktreePath
2069
+ })).stdout.trim();
2070
+ if (hostHead !== sandboxHead) {
2071
+ yield* Effect.fail(
2072
+ new SyncError({
2073
+ message: `HEAD mismatch after sync-in: host=${hostHead} sandbox=${sandboxHead}`
2074
+ })
2075
+ );
2076
+ }
2077
+ }),
2078
+ // Clean up host-side bundle temp dir (runs regardless of success/failure)
2079
+ Effect.promise(() => rm(bundleDir, { recursive: true, force: true }))
2080
+ );
2081
+ return { branch };
2082
+ });
2762
2083
 
2763
- // src/shutdownRegistry.ts
2764
- var teardownCallbacks = /* @__PURE__ */ new Set();
2765
- var listenersInstalled = false;
2766
- var runTeardowns = () => {
2767
- for (const teardown of teardownCallbacks) {
2768
- try {
2769
- teardown();
2770
- } catch {
2084
+ // src/startSandbox.ts
2085
+ var CONTAINER_START_TIMEOUT_MS = 12e4;
2086
+ var SYNC_IN_TIMEOUT_MS = 12e4;
2087
+ var COPY_PATHS_TIMEOUT_MS = 6e4;
2088
+ var startSandbox = (options) => Effect.gen(function* () {
2089
+ const handle = yield* Effect.tryPromise({
2090
+ try: () => options.provider.create({
2091
+ env: options.env,
2092
+ hostRepoPath: options.sourceRepoDir ?? options.hostRepoDir
2093
+ }),
2094
+ catch: (e) => new WorktreeError({
2095
+ message: `Isolated provider '${options.provider.name}' setup failed: ${e instanceof Error ? e.message : String(e)}`
2096
+ })
2097
+ }).pipe(
2098
+ withTimeout(
2099
+ CONTAINER_START_TIMEOUT_MS,
2100
+ () => new ContainerStartTimeoutError({
2101
+ message: `Isolated sandbox container start timed out after ${CONTAINER_START_TIMEOUT_MS}ms`,
2102
+ timeoutMs: CONTAINER_START_TIMEOUT_MS
2103
+ })
2104
+ )
2105
+ );
2106
+ return yield* Effect.gen(function* () {
2107
+ yield* syncIn(options.hostRepoDir, handle).pipe(
2108
+ withTimeout(
2109
+ SYNC_IN_TIMEOUT_MS,
2110
+ () => new SyncInTimeoutError({
2111
+ message: `Sync-in timed out after ${SYNC_IN_TIMEOUT_MS}ms`,
2112
+ timeoutMs: SYNC_IN_TIMEOUT_MS
2113
+ })
2114
+ )
2115
+ );
2116
+ if (options.copyPaths && options.copyPaths.length > 0) {
2117
+ const pathsToCopy = options.copyPaths;
2118
+ const copySourceDir = options.copySourceDir ?? options.sourceRepoDir ?? options.hostRepoDir;
2119
+ const copyTimeoutMs = options.copyTimeoutMs ?? COPY_PATHS_TIMEOUT_MS;
2120
+ yield* Effect.gen(function* () {
2121
+ for (const relativePath of pathsToCopy) {
2122
+ let hostPath;
2123
+ let sandboxPath;
2124
+ try {
2125
+ assertExcludesRepositoryRunner(relativePath);
2126
+ hostPath = resolveSafeRelativePath(
2127
+ copySourceDir,
2128
+ relativePath,
2129
+ "copy path"
2130
+ );
2131
+ resolveSafeRelativePath(
2132
+ handle.worktreePath,
2133
+ relativePath,
2134
+ "copy path"
2135
+ );
2136
+ sandboxPath = posix.join(handle.worktreePath, relativePath);
2137
+ } catch (error) {
2138
+ return yield* Effect.fail(
2139
+ new WorktreeError({
2140
+ message: `Refusing unsafe copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`
2141
+ })
2142
+ );
2143
+ }
2144
+ if (!existsSync(hostPath)) {
2145
+ continue;
2146
+ }
2147
+ yield* Effect.tryPromise({
2148
+ try: async () => {
2149
+ await assertNoSymlinkComponents(
2150
+ copySourceDir,
2151
+ hostPath,
2152
+ "copy source"
2153
+ );
2154
+ },
2155
+ catch: (error) => new WorktreeError({
2156
+ message: `Refusing symlinked copy path ${relativePath}: ${error instanceof Error ? error.message : String(error)}`
2157
+ })
2158
+ });
2159
+ yield* Effect.tryPromise({
2160
+ try: () => handle.copyIn(hostPath, sandboxPath),
2161
+ catch: (e) => new WorktreeError({
2162
+ message: `Failed to copy ${relativePath} into sandbox: ${e instanceof Error ? e.message : String(e)}`
2163
+ })
2164
+ });
2165
+ }
2166
+ }).pipe(
2167
+ withTimeout(
2168
+ copyTimeoutMs,
2169
+ () => new CopyToWorktreeTimeoutError({
2170
+ message: `Copying paths to sandbox timed out after ${copyTimeoutMs}ms`,
2171
+ timeoutMs: copyTimeoutMs,
2172
+ paths: pathsToCopy
2173
+ })
2174
+ )
2175
+ );
2771
2176
  }
2772
- }
2773
- };
2774
- var handleExit = () => {
2775
- runTeardowns();
2776
- };
2777
- var handleSignal = () => {
2778
- detachListeners();
2779
- runTeardowns();
2780
- process.exit(1);
2781
- };
2782
- var attachListeners = () => {
2783
- if (listenersInstalled) return;
2784
- listenersInstalled = true;
2785
- process.on("exit", handleExit);
2786
- process.on("SIGINT", handleSignal);
2787
- process.on("SIGTERM", handleSignal);
2788
- };
2789
- var detachListeners = () => {
2790
- if (!listenersInstalled) return;
2791
- listenersInstalled = false;
2792
- process.removeListener("exit", handleExit);
2793
- process.removeListener("SIGINT", handleSignal);
2794
- process.removeListener("SIGTERM", handleSignal);
2795
- };
2796
- var registerShutdown = (teardown) => {
2797
- teardownCallbacks.add(teardown);
2798
- attachListeners();
2799
- let active = true;
2800
- return () => {
2801
- if (!active) return;
2802
- active = false;
2803
- teardownCallbacks.delete(teardown);
2804
- if (teardownCallbacks.size === 0) detachListeners();
2805
- };
2806
- };
2177
+ return {
2178
+ handle,
2179
+ sandbox: makeSandboxFromHandle(handle),
2180
+ worktreePath: handle.worktreePath
2181
+ };
2182
+ }).pipe(Effect.onError(() => Effect.promise(() => handle.close())));
2183
+ });
2807
2184
 
2808
- export { ACTIVATION_LABEL, AgentError, AgentIdleTimeoutError, CLI_NAME, CONFIG_DIR, ClackDisplay, ConfigDirError, CwdError, Display, DockerError, ExecHostError, FileDisplay, InitError, LOCKS_DIR, LOGS_DIR, PRODUCT_NAME, PromptError, PromptExpansionTimeoutError, RUNNER_DIR, RUNNER_SANDBOX_MASK_DIR, SANDBOX_REPO_DIR, SandboxConfig, SandboxFactory, SessionCaptureError, SilentDisplay, WorktreeDockerSandboxFactory, assertIsolatedWorkspaceMounts, assertNoSymlinkComponents, assertProtectedDirectoryIdentities, copyToWorktree, create, defaultImageName, formatVolumeMount, generateTempBranchName, getCurrentBranch, hasUncommittedChanges, makeSandboxFromHandle, patchGitMountsForWindows, processFileMountParents, pruneStale, registerShutdown, remove, repositoryRunnerEnvironment, resolveCwd, resolveGitMounts, resolveUserMounts, runHostHooks, startSandbox, syncOut, withSandboxLifecycle };
2809
- //# sourceMappingURL=chunk-FDYOTN55.js.map
2810
- //# sourceMappingURL=chunk-FDYOTN55.js.map
2185
+ export { ACTIVATION_LABEL, AgentError, AgentIdleTimeoutError, CLI_NAME, CONFIG_DIR, ClackDisplay, ConfigDirError, CopyToWorktreeError, CopyToWorktreeTimeoutError, CwdError, Display, DockerError, ExecHostError, FileDisplay, InitError, LOCKS_DIR, LOGS_DIR, PRODUCT_NAME, PromptError, PromptExpansionTimeoutError, RUNNER_DIR, RUNTIME_NAMESPACE, SANDBOX_REPO_DIR, SandboxConfig, SandboxFactory, SessionCaptureError, SilentDisplay, WorktreeDockerSandboxFactory, assertExcludesRepositoryRunner, assertNoSymlinkComponents, assertProtectedDirectoryIdentities, create, createIsolatedSandboxProvider, generateTempBranchName, getCurrentBranch, hasUncommittedChanges, makeSandboxFromHandle, pruneStale, registerShutdown, remove, repositoryRunnerEnvironment, resolveCwd, resolveSafeRelativePath, runHostHooks, shellQuote, startSandbox, syncOut, toSessionTransferHandle, withSandboxLifecycle, withTimeout };
2186
+ //# sourceMappingURL=chunk-57EEKW3R.js.map
2187
+ //# sourceMappingURL=chunk-57EEKW3R.js.map