@snappedly-tools/shipyard 0.7.0-staging.36081473933 → 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.
- package/README.md +41 -34
- package/dist/MountConfig-bZoCs4Dd.d.ts +26 -0
- package/dist/SandboxProvider-XJQqEdSf.d.ts +261 -0
- package/dist/{chunk-JI3HDDMS.js → chunk-56TDSWFU.js} +3 -3
- package/dist/{chunk-JI3HDDMS.js.map → chunk-56TDSWFU.js.map} +1 -1
- package/dist/chunk-ACD46ZM4.js +136 -0
- package/dist/chunk-ACD46ZM4.js.map +1 -0
- package/dist/{chunk-L6PX5QTU.js → chunk-FDYOTN55.js} +887 -262
- package/dist/chunk-FDYOTN55.js.map +1 -0
- package/dist/chunk-KMGNFXKN.js +38 -0
- package/dist/chunk-KMGNFXKN.js.map +1 -0
- package/dist/chunk-SOJTAJTF.js +78 -0
- package/dist/chunk-SOJTAJTF.js.map +1 -0
- package/dist/{chunk-44I2BL6E.js → chunk-WAXEMZUV.js} +33 -225
- package/dist/chunk-WAXEMZUV.js.map +1 -0
- package/dist/index.d.ts +97 -117
- package/dist/index.js +344 -322
- package/dist/index.js.map +1 -1
- package/dist/main.js +139 -235
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +3 -1
- package/dist/sandboxes/docker.js +4 -2
- package/dist/sandboxes/no-sandbox.d.ts +37 -0
- package/dist/sandboxes/no-sandbox.js +4 -0
- package/dist/sandboxes/no-sandbox.js.map +1 -0
- package/dist/sandboxes/vercel.d.ts +104 -0
- package/dist/sandboxes/vercel.js +166 -0
- package/dist/sandboxes/vercel.js.map +1 -0
- package/dist/templates/blank/main.mts +13 -0
- package/dist/templates/blank/prompt.md +12 -0
- package/dist/templates/blank/template.json +4 -0
- package/dist/templates/parallel-planner/main.mts +9 -67
- package/dist/templates/parallel-planner-with-review/main.mts +12 -70
- package/dist/templates/sequential-reviewer/main.mts +3 -52
- package/dist/templates/simple-loop/main.mts +2 -51
- package/package.json +17 -1
- package/dist/MountConfig-BHnKnA4h.d.ts +0 -145
- package/dist/chunk-44I2BL6E.js.map +0 -1
- package/dist/chunk-L6PX5QTU.js.map +0 -1
- package/dist/templates/parallel-planner/planner-branch.mts +0 -151
- package/dist/templates/parallel-planner-with-review/planner-branch.mts +0 -151
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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';
|
|
3
4
|
import * as clack from '@clack/prompts';
|
|
4
5
|
import { FileSystem } from '@effect/platform';
|
|
5
|
-
import {
|
|
6
|
+
import { join, basename, posix, dirname, resolve, normalize, relative, sep, isAbsolute } from 'path';
|
|
6
7
|
import { Context, Layer, Effect, Data, Exit, Duration, Option, Ref, Deferred, Schedule } from 'effect';
|
|
7
8
|
import { styleText, promisify } from 'util';
|
|
9
|
+
import { tmpdir, homedir } from 'os';
|
|
8
10
|
import { randomBytes } from 'crypto';
|
|
9
11
|
import { exec, execFile } from 'child_process';
|
|
10
|
-
import { tmpdir } from 'os';
|
|
11
12
|
|
|
12
|
-
// src/Display.ts
|
|
13
13
|
var Display = class extends Context.Tag("Display")() {
|
|
14
14
|
};
|
|
15
15
|
var SilentDisplay = {
|
|
@@ -259,67 +259,6 @@ var ClackDisplay = {
|
|
|
259
259
|
)
|
|
260
260
|
})
|
|
261
261
|
};
|
|
262
|
-
|
|
263
|
-
// src/SandboxProvider.ts
|
|
264
|
-
var createIsolatedSandboxProvider = (config) => ({
|
|
265
|
-
tag: "isolated",
|
|
266
|
-
name: config.name,
|
|
267
|
-
env: config.env ?? {},
|
|
268
|
-
create: config.create
|
|
269
|
-
});
|
|
270
|
-
var toSessionTransferHandle = (handle) => handle === void 0 ? void 0 : "copyFileIn" in handle ? handle : {
|
|
271
|
-
worktreePath: handle.worktreePath,
|
|
272
|
-
exec: (command, options) => handle.exec(command, options),
|
|
273
|
-
interactiveExec: handle.interactiveExec?.bind(handle),
|
|
274
|
-
copyFileIn: (source, destination) => handle.copyIn(source, destination),
|
|
275
|
-
copyFileOut: (source, destination) => handle.copyFileOut(source, destination),
|
|
276
|
-
close: () => handle.close()
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
// src/shutdownRegistry.ts
|
|
280
|
-
var teardownCallbacks = /* @__PURE__ */ new Set();
|
|
281
|
-
var listenersInstalled = false;
|
|
282
|
-
var runTeardowns = () => {
|
|
283
|
-
for (const teardown of teardownCallbacks) {
|
|
284
|
-
try {
|
|
285
|
-
teardown();
|
|
286
|
-
} catch {
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
var handleExit = () => {
|
|
291
|
-
runTeardowns();
|
|
292
|
-
};
|
|
293
|
-
var handleSignal = () => {
|
|
294
|
-
detachListeners();
|
|
295
|
-
runTeardowns();
|
|
296
|
-
process.exit(1);
|
|
297
|
-
};
|
|
298
|
-
var attachListeners = () => {
|
|
299
|
-
if (listenersInstalled) return;
|
|
300
|
-
listenersInstalled = true;
|
|
301
|
-
process.on("exit", handleExit);
|
|
302
|
-
process.on("SIGINT", handleSignal);
|
|
303
|
-
process.on("SIGTERM", handleSignal);
|
|
304
|
-
};
|
|
305
|
-
var detachListeners = () => {
|
|
306
|
-
if (!listenersInstalled) return;
|
|
307
|
-
listenersInstalled = false;
|
|
308
|
-
process.removeListener("exit", handleExit);
|
|
309
|
-
process.removeListener("SIGINT", handleSignal);
|
|
310
|
-
process.removeListener("SIGTERM", handleSignal);
|
|
311
|
-
};
|
|
312
|
-
var registerShutdown = (teardown) => {
|
|
313
|
-
teardownCallbacks.add(teardown);
|
|
314
|
-
attachListeners();
|
|
315
|
-
let active = true;
|
|
316
|
-
return () => {
|
|
317
|
-
if (!active) return;
|
|
318
|
-
active = false;
|
|
319
|
-
teardownCallbacks.delete(teardown);
|
|
320
|
-
if (teardownCallbacks.size === 0) detachListeners();
|
|
321
|
-
};
|
|
322
|
-
};
|
|
323
262
|
var CwdError = class extends Data.TaggedError("CwdError") {
|
|
324
263
|
};
|
|
325
264
|
var resolveCwd = (cwd) => Effect.gen(function* () {
|
|
@@ -341,6 +280,8 @@ var resolveCwd = (cwd) => Effect.gen(function* () {
|
|
|
341
280
|
}
|
|
342
281
|
return resolved;
|
|
343
282
|
});
|
|
283
|
+
|
|
284
|
+
// src/errors.ts
|
|
344
285
|
var ExecError = class extends Data.TaggedError("ExecError") {
|
|
345
286
|
};
|
|
346
287
|
var ExecHostError = class extends Data.TaggedError("ExecHostError") {
|
|
@@ -423,7 +364,10 @@ var WORKTREES_DIR = "worktrees";
|
|
|
423
364
|
var PATCHES_DIR = "patches";
|
|
424
365
|
var LOCKS_DIR = "locks";
|
|
425
366
|
var RUNNER_DIR = "runner";
|
|
367
|
+
var RUNNER_SANDBOX_MASK_DIR = "runner-sandbox-mask";
|
|
368
|
+
var PARENT_GIT_SANDBOX_DIR = "/.shipyard-parent-git";
|
|
426
369
|
var SYNC_BASE_REF = "refs/shipyard/sync-base";
|
|
370
|
+
var CONTAINER_NAME_PREFIX = "shipyard-";
|
|
427
371
|
var WINDOWS_DRIVE_PATH = /^[A-Za-z]:/;
|
|
428
372
|
var assertSafeRelativePath = (relativePath, label = "relative path") => {
|
|
429
373
|
if (relativePath.length === 0 || relativePath.includes("\0") || isAbsolute(relativePath) || WINDOWS_DRIVE_PATH.test(relativePath) || relativePath.startsWith("\\\\") || relativePath.split(/[\\/]/).some((part) => part === "..")) {
|
|
@@ -496,7 +440,7 @@ var SAFE_CONFIG = [
|
|
|
496
440
|
["checkout.recurseSubmodules", "false"]
|
|
497
441
|
];
|
|
498
442
|
var COMMAND_CONFIG_PATTERN = "^(filter\\..*\\.(clean|smudge|process|required)|merge\\..*\\.driver|diff\\..*\\.(command|textconv)|difftool\\..*\\.cmd|mergetool\\..*\\.cmd)$";
|
|
499
|
-
var runGit = (args, cwd, options = {}) => new Promise((
|
|
443
|
+
var runGit = (args, cwd, options = {}) => new Promise((resolve6, reject) => {
|
|
500
444
|
execFile(
|
|
501
445
|
"git",
|
|
502
446
|
[...args],
|
|
@@ -514,11 +458,11 @@ var runGit = (args, cwd, options = {}) => new Promise((resolve4, reject) => {
|
|
|
514
458
|
},
|
|
515
459
|
(error, stdout, stderr) => {
|
|
516
460
|
if (!error) {
|
|
517
|
-
|
|
461
|
+
resolve6({ stdout, stderr, exitCode: 0 });
|
|
518
462
|
return;
|
|
519
463
|
}
|
|
520
464
|
if (typeof error.code === "number") {
|
|
521
|
-
|
|
465
|
+
resolve6({ stdout, stderr, exitCode: error.code });
|
|
522
466
|
return;
|
|
523
467
|
}
|
|
524
468
|
reject(error);
|
|
@@ -925,14 +869,6 @@ var pruneStale = (repoDir) => Effect.gen(function* () {
|
|
|
925
869
|
})
|
|
926
870
|
)
|
|
927
871
|
);
|
|
928
|
-
|
|
929
|
-
// src/shellQuote.ts
|
|
930
|
-
var shellQuote = (value) => {
|
|
931
|
-
if (value.includes("\0")) {
|
|
932
|
-
throw new Error("Cannot quote a string containing a NUL byte");
|
|
933
|
-
}
|
|
934
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
935
|
-
};
|
|
936
872
|
var PROTECTED_RUNNER_PATH = `${CONFIG_DIR}/${RUNNER_DIR}`;
|
|
937
873
|
var pathKey = (value) => value.normalize("NFC").toLocaleLowerCase("en-US");
|
|
938
874
|
var SAFE_RUNNER_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -964,14 +900,121 @@ var assertExcludesRepositoryRunner = (relativePath) => {
|
|
|
964
900
|
);
|
|
965
901
|
}
|
|
966
902
|
};
|
|
967
|
-
var
|
|
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) => {
|
|
968
924
|
if (!inspect) return;
|
|
969
925
|
const runner = await inspect(runnerDir);
|
|
926
|
+
const mask = await inspect(maskDir);
|
|
970
927
|
if (!runner.directory || runner.symbolicLink) {
|
|
971
928
|
throw new Error(
|
|
972
929
|
`The protected repository runner path must be a real directory: ${runnerDir}`
|
|
973
930
|
);
|
|
974
931
|
}
|
|
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
|
+
);
|
|
975
1018
|
};
|
|
976
1019
|
|
|
977
1020
|
// src/RecoveryMessage.ts
|
|
@@ -1481,6 +1524,8 @@ ${msg}`);
|
|
|
1481
1524
|
});
|
|
1482
1525
|
}
|
|
1483
1526
|
});
|
|
1527
|
+
|
|
1528
|
+
// src/SandboxLifecycle.ts
|
|
1484
1529
|
var GIT_SETUP_TIMEOUT_MS = 1e4;
|
|
1485
1530
|
var HOOK_TIMEOUT_MS = 6e4;
|
|
1486
1531
|
var COMMIT_COLLECTION_TIMEOUT_MS = 3e4;
|
|
@@ -1814,169 +1859,6 @@ ${err instanceof Error ? err.message : String(err)}`
|
|
|
1814
1859
|
}
|
|
1815
1860
|
return { result, branch: finalBranch, commits };
|
|
1816
1861
|
});
|
|
1817
|
-
var makeSandboxFromHandle = (handle) => ({
|
|
1818
|
-
exec: (command, options) => Effect.tryPromise({
|
|
1819
|
-
try: () => handle.exec(command, options),
|
|
1820
|
-
catch: (e) => new ExecError({
|
|
1821
|
-
command,
|
|
1822
|
-
message: `exec failed: ${e instanceof Error ? e.message : String(e)}`
|
|
1823
|
-
})
|
|
1824
|
-
}),
|
|
1825
|
-
copyIn: (hostPath, sandboxPath) => Effect.tryPromise({
|
|
1826
|
-
try: () => handle.copyIn(hostPath, sandboxPath),
|
|
1827
|
-
catch: (e) => new CopyError({
|
|
1828
|
-
message: `copyIn failed: ${e instanceof Error ? e.message : String(e)}`
|
|
1829
|
-
})
|
|
1830
|
-
}),
|
|
1831
|
-
copyFileOut: (sandboxPath, hostPath) => Effect.tryPromise({
|
|
1832
|
-
try: () => handle.copyFileOut(sandboxPath, hostPath),
|
|
1833
|
-
catch: (e) => new CopyError({
|
|
1834
|
-
message: `copyFileOut failed: ${e instanceof Error ? e.message : String(e)}`
|
|
1835
|
-
})
|
|
1836
|
-
})
|
|
1837
|
-
});
|
|
1838
|
-
var SANDBOX_REPO_DIR = "/home/agent/workspace";
|
|
1839
|
-
var SandboxFactory = class extends Context.Tag("SandboxFactory")() {
|
|
1840
|
-
};
|
|
1841
|
-
var SandboxConfig = class extends Context.Tag("SandboxConfig")() {
|
|
1842
|
-
};
|
|
1843
|
-
var printWorktreePreservedMessage = (worktreePath, reason) => {
|
|
1844
|
-
console.error(`
|
|
1845
|
-
${reason}`);
|
|
1846
|
-
console.error(` To review: cd ${worktreePath}`);
|
|
1847
|
-
console.error(` To clean up: git worktree remove --force ${worktreePath}`);
|
|
1848
|
-
};
|
|
1849
|
-
var cleanupWorktree = (worktreePath, exit) => hasUncommittedChanges(worktreePath).pipe(
|
|
1850
|
-
Effect.catchAll(() => Effect.succeed(false)),
|
|
1851
|
-
Effect.flatMap((isDirty) => {
|
|
1852
|
-
if (isDirty) {
|
|
1853
|
-
printWorktreePreservedMessage(
|
|
1854
|
-
worktreePath,
|
|
1855
|
-
Exit.isSuccess(exit) ? `Run succeeded but worktree has uncommitted changes at ${worktreePath}` : `Worktree preserved at ${worktreePath}`
|
|
1856
|
-
);
|
|
1857
|
-
return Effect.succeed(worktreePath);
|
|
1858
|
-
}
|
|
1859
|
-
if (!Exit.isSuccess(exit)) {
|
|
1860
|
-
console.error(`
|
|
1861
|
-
Worktree removed (no uncommitted changes)`);
|
|
1862
|
-
}
|
|
1863
|
-
return remove(worktreePath).pipe(
|
|
1864
|
-
Effect.map(() => void 0)
|
|
1865
|
-
);
|
|
1866
|
-
})
|
|
1867
|
-
);
|
|
1868
|
-
var attachPreservedPath = (path, e) => {
|
|
1869
|
-
if (path !== void 0) {
|
|
1870
|
-
if (e instanceof AgentIdleTimeoutError) {
|
|
1871
|
-
return new AgentIdleTimeoutError({
|
|
1872
|
-
message: e.message,
|
|
1873
|
-
timeoutMs: e.timeoutMs,
|
|
1874
|
-
preservedWorktreePath: path
|
|
1875
|
-
});
|
|
1876
|
-
}
|
|
1877
|
-
if (e instanceof AgentError) {
|
|
1878
|
-
return new AgentError({
|
|
1879
|
-
message: e.message,
|
|
1880
|
-
preservedWorktreePath: path
|
|
1881
|
-
});
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
return e;
|
|
1885
|
-
};
|
|
1886
|
-
var WorktreeDockerSandboxFactory = {
|
|
1887
|
-
layer: Layer.effect(
|
|
1888
|
-
SandboxFactory,
|
|
1889
|
-
Effect.gen(function* () {
|
|
1890
|
-
const {
|
|
1891
|
-
env,
|
|
1892
|
-
hostRepoDir,
|
|
1893
|
-
copyToWorktree: copyPaths,
|
|
1894
|
-
name,
|
|
1895
|
-
sandboxProvider,
|
|
1896
|
-
branchStrategy,
|
|
1897
|
-
hooks,
|
|
1898
|
-
signal,
|
|
1899
|
-
timeouts
|
|
1900
|
-
} = yield* SandboxConfig;
|
|
1901
|
-
const branch = branchStrategy.type === "branch" ? branchStrategy.branch : void 0;
|
|
1902
|
-
const baseBranch = branchStrategy.type === "branch" ? branchStrategy.baseBranch : void 0;
|
|
1903
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
1904
|
-
const pruneAndCreate = () => pruneStale(hostRepoDir).pipe(
|
|
1905
|
-
Effect.catchAll(
|
|
1906
|
-
(e) => Effect.sync(() => {
|
|
1907
|
-
console.error(
|
|
1908
|
-
"[shipyard] Warning: failed to prune stale worktrees:",
|
|
1909
|
-
e.message
|
|
1910
|
-
);
|
|
1911
|
-
})
|
|
1912
|
-
),
|
|
1913
|
-
Effect.andThen(
|
|
1914
|
-
branch ? create(hostRepoDir, { branch, baseBranch }) : create(hostRepoDir, { name })
|
|
1915
|
-
),
|
|
1916
|
-
Effect.provideService(FileSystem.FileSystem, fileSystem)
|
|
1917
|
-
);
|
|
1918
|
-
return {
|
|
1919
|
-
withSandbox: (makeEffect) => {
|
|
1920
|
-
{
|
|
1921
|
-
let preservedPath;
|
|
1922
|
-
return Effect.acquireUseRelease(
|
|
1923
|
-
pruneAndCreate(),
|
|
1924
|
-
(worktreeInfo) => (hooks?.host?.onWorktreeReady?.length ? runHostHooks(
|
|
1925
|
-
hooks.host.onWorktreeReady,
|
|
1926
|
-
worktreeInfo.path,
|
|
1927
|
-
signal
|
|
1928
|
-
) : Effect.void).pipe(
|
|
1929
|
-
Effect.andThen(
|
|
1930
|
-
Effect.acquireUseRelease(
|
|
1931
|
-
startSandbox({
|
|
1932
|
-
provider: sandboxProvider,
|
|
1933
|
-
hostRepoDir: worktreeInfo.path,
|
|
1934
|
-
sourceRepoDir: hostRepoDir,
|
|
1935
|
-
env,
|
|
1936
|
-
copyPaths,
|
|
1937
|
-
copyTimeoutMs: timeouts?.copyToWorktreeMs
|
|
1938
|
-
}),
|
|
1939
|
-
({ sandbox, worktreePath, handle }) => makeEffect(
|
|
1940
|
-
{
|
|
1941
|
-
hostWorktreePath: worktreeInfo.path,
|
|
1942
|
-
sandboxRepoPath: worktreePath,
|
|
1943
|
-
sessionTransferHandle: toSessionTransferHandle(handle),
|
|
1944
|
-
applyToHost: () => syncOut(
|
|
1945
|
-
worktreeInfo.path,
|
|
1946
|
-
handle
|
|
1947
|
-
)
|
|
1948
|
-
},
|
|
1949
|
-
sandbox
|
|
1950
|
-
),
|
|
1951
|
-
({ handle }) => Effect.tryPromise({
|
|
1952
|
-
try: () => handle.close(),
|
|
1953
|
-
catch: () => void 0
|
|
1954
|
-
}).pipe(Effect.orDie)
|
|
1955
|
-
)
|
|
1956
|
-
)
|
|
1957
|
-
),
|
|
1958
|
-
(worktreeInfo, exit) => cleanupWorktree(worktreeInfo.path, exit).pipe(
|
|
1959
|
-
Effect.tap((p) => {
|
|
1960
|
-
preservedPath = p;
|
|
1961
|
-
}),
|
|
1962
|
-
Effect.asVoid,
|
|
1963
|
-
Effect.orDie
|
|
1964
|
-
)
|
|
1965
|
-
).pipe(
|
|
1966
|
-
Effect.map((value) => ({
|
|
1967
|
-
value,
|
|
1968
|
-
preservedWorktreePath: preservedPath
|
|
1969
|
-
})),
|
|
1970
|
-
Effect.mapError(
|
|
1971
|
-
(e) => attachPreservedPath(preservedPath, e)
|
|
1972
|
-
)
|
|
1973
|
-
);
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
};
|
|
1977
|
-
})
|
|
1978
|
-
)
|
|
1979
|
-
};
|
|
1980
1862
|
var execHostGit3 = (args, cwd) => Effect.tryPromise({
|
|
1981
1863
|
try: () => execHostGit(args, cwd),
|
|
1982
1864
|
catch: (e) => new SyncError({
|
|
@@ -2082,17 +1964,101 @@ var syncIn = (hostRepoDir, handle) => Effect.gen(function* () {
|
|
|
2082
1964
|
// src/startSandbox.ts
|
|
2083
1965
|
var CONTAINER_START_TIMEOUT_MS = 12e4;
|
|
2084
1966
|
var SYNC_IN_TIMEOUT_MS = 12e4;
|
|
2085
|
-
var COPY_PATHS_TIMEOUT_MS =
|
|
2086
|
-
var startSandbox = (options) =>
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
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(
|
|
2096
2062
|
withTimeout(
|
|
2097
2063
|
CONTAINER_START_TIMEOUT_MS,
|
|
2098
2064
|
() => new ContainerStartTimeoutError({
|
|
@@ -2113,8 +2079,6 @@ var startSandbox = (options) => Effect.gen(function* () {
|
|
|
2113
2079
|
);
|
|
2114
2080
|
if (options.copyPaths && options.copyPaths.length > 0) {
|
|
2115
2081
|
const pathsToCopy = options.copyPaths;
|
|
2116
|
-
const copySourceDir = options.copySourceDir ?? options.sourceRepoDir ?? options.hostRepoDir;
|
|
2117
|
-
const copyTimeoutMs = options.copyTimeoutMs ?? COPY_PATHS_TIMEOUT_MS;
|
|
2118
2082
|
yield* Effect.gen(function* () {
|
|
2119
2083
|
for (const relativePath of pathsToCopy) {
|
|
2120
2084
|
let hostPath;
|
|
@@ -2122,7 +2086,7 @@ var startSandbox = (options) => Effect.gen(function* () {
|
|
|
2122
2086
|
try {
|
|
2123
2087
|
assertExcludesRepositoryRunner(relativePath);
|
|
2124
2088
|
hostPath = resolveSafeRelativePath(
|
|
2125
|
-
|
|
2089
|
+
options.sourceRepoDir ?? options.hostRepoDir,
|
|
2126
2090
|
relativePath,
|
|
2127
2091
|
"copy path"
|
|
2128
2092
|
);
|
|
@@ -2145,7 +2109,7 @@ var startSandbox = (options) => Effect.gen(function* () {
|
|
|
2145
2109
|
yield* Effect.tryPromise({
|
|
2146
2110
|
try: async () => {
|
|
2147
2111
|
await assertNoSymlinkComponents(
|
|
2148
|
-
|
|
2112
|
+
options.sourceRepoDir ?? options.hostRepoDir,
|
|
2149
2113
|
hostPath,
|
|
2150
2114
|
"copy source"
|
|
2151
2115
|
);
|
|
@@ -2163,10 +2127,10 @@ var startSandbox = (options) => Effect.gen(function* () {
|
|
|
2163
2127
|
}
|
|
2164
2128
|
}).pipe(
|
|
2165
2129
|
withTimeout(
|
|
2166
|
-
|
|
2130
|
+
COPY_PATHS_TIMEOUT_MS,
|
|
2167
2131
|
() => new CopyToWorktreeTimeoutError({
|
|
2168
|
-
message: `Copying paths to
|
|
2169
|
-
timeoutMs:
|
|
2132
|
+
message: `Copying paths to worktree timed out after ${COPY_PATHS_TIMEOUT_MS}ms`,
|
|
2133
|
+
timeoutMs: COPY_PATHS_TIMEOUT_MS,
|
|
2170
2134
|
paths: pathsToCopy
|
|
2171
2135
|
})
|
|
2172
2136
|
)
|
|
@@ -2180,6 +2144,667 @@ var startSandbox = (options) => Effect.gen(function* () {
|
|
|
2180
2144
|
}).pipe(Effect.onError(() => Effect.promise(() => handle.close())));
|
|
2181
2145
|
});
|
|
2182
2146
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
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
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
return e;
|
|
2238
|
+
};
|
|
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
|
+
var WorktreeDockerSandboxFactory = {
|
|
2258
|
+
layer: Layer.effect(
|
|
2259
|
+
SandboxFactory,
|
|
2260
|
+
Effect.gen(function* () {
|
|
2261
|
+
const {
|
|
2262
|
+
env,
|
|
2263
|
+
hostRepoDir,
|
|
2264
|
+
copyToWorktree: copyPaths,
|
|
2265
|
+
name,
|
|
2266
|
+
sandboxProvider,
|
|
2267
|
+
branchStrategy,
|
|
2268
|
+
hooks,
|
|
2269
|
+
signal,
|
|
2270
|
+
timeouts
|
|
2271
|
+
} = yield* SandboxConfig;
|
|
2272
|
+
const isHeadMode = branchStrategy.type === "head";
|
|
2273
|
+
const branch = branchStrategy.type === "branch" ? branchStrategy.branch : void 0;
|
|
2274
|
+
const baseBranch = branchStrategy.type === "branch" ? branchStrategy.baseBranch : void 0;
|
|
2275
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
2276
|
+
const display = yield* Display;
|
|
2277
|
+
const pruneAndCreate = () => pruneStale(hostRepoDir).pipe(
|
|
2278
|
+
Effect.catchAll(
|
|
2279
|
+
(e) => Effect.sync(() => {
|
|
2280
|
+
console.error(
|
|
2281
|
+
"[shipyard] Warning: failed to prune stale worktrees:",
|
|
2282
|
+
e.message
|
|
2283
|
+
);
|
|
2284
|
+
})
|
|
2285
|
+
),
|
|
2286
|
+
Effect.andThen(
|
|
2287
|
+
branch ? create(hostRepoDir, { branch, baseBranch }) : create(hostRepoDir, { name })
|
|
2288
|
+
),
|
|
2289
|
+
Effect.provideService(FileSystem.FileSystem, fileSystem)
|
|
2290
|
+
);
|
|
2291
|
+
return {
|
|
2292
|
+
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") {
|
|
2387
|
+
let preservedPath;
|
|
2388
|
+
return Effect.acquireUseRelease(
|
|
2389
|
+
pruneAndCreate(),
|
|
2390
|
+
(worktreeInfo) => (hooks?.host?.onWorktreeReady?.length ? runHostHooks(
|
|
2391
|
+
hooks.host.onWorktreeReady,
|
|
2392
|
+
worktreeInfo.path,
|
|
2393
|
+
signal
|
|
2394
|
+
) : Effect.void).pipe(
|
|
2395
|
+
Effect.andThen(
|
|
2396
|
+
Effect.acquireUseRelease(
|
|
2397
|
+
startSandbox({
|
|
2398
|
+
provider: sandboxProvider,
|
|
2399
|
+
hostRepoDir: worktreeInfo.path,
|
|
2400
|
+
sourceRepoDir: hostRepoDir,
|
|
2401
|
+
env,
|
|
2402
|
+
copyPaths
|
|
2403
|
+
}),
|
|
2404
|
+
({ sandbox, worktreePath, handle }) => makeEffect(
|
|
2405
|
+
{
|
|
2406
|
+
hostWorktreePath: worktreeInfo.path,
|
|
2407
|
+
sandboxRepoPath: worktreePath,
|
|
2408
|
+
bindMountHandle: toSessionTransferHandle(handle),
|
|
2409
|
+
applyToHost: () => syncOut(
|
|
2410
|
+
worktreeInfo.path,
|
|
2411
|
+
handle
|
|
2412
|
+
)
|
|
2413
|
+
},
|
|
2414
|
+
sandbox
|
|
2415
|
+
),
|
|
2416
|
+
({ handle }) => Effect.tryPromise({
|
|
2417
|
+
try: () => handle.close(),
|
|
2418
|
+
catch: () => void 0
|
|
2419
|
+
}).pipe(Effect.orDie)
|
|
2420
|
+
)
|
|
2421
|
+
)
|
|
2422
|
+
),
|
|
2423
|
+
(worktreeInfo, exit) => cleanupWorktree(worktreeInfo.path, exit).pipe(
|
|
2424
|
+
Effect.tap((p) => {
|
|
2425
|
+
preservedPath = p;
|
|
2426
|
+
}),
|
|
2427
|
+
Effect.asVoid,
|
|
2428
|
+
Effect.orDie
|
|
2429
|
+
)
|
|
2430
|
+
).pipe(
|
|
2431
|
+
Effect.map((value) => ({
|
|
2432
|
+
value,
|
|
2433
|
+
preservedWorktreePath: preservedPath
|
|
2434
|
+
})),
|
|
2435
|
+
Effect.mapError(
|
|
2436
|
+
(e) => attachPreservedPath(preservedPath, e)
|
|
2437
|
+
)
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
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
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
})
|
|
2580
|
+
)
|
|
2581
|
+
};
|
|
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})` : "")
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
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)}`
|
|
2695
|
+
})
|
|
2696
|
+
});
|
|
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`
|
|
2710
|
+
});
|
|
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.`
|
|
2746
|
+
);
|
|
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.`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
|
|
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 {
|
|
2771
|
+
}
|
|
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
|
+
};
|
|
2807
|
+
|
|
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
|