@snappedly-tools/shipyard 0.5.0 → 0.7.0-staging.36081473933
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -117
- package/dist/MountConfig-BHnKnA4h.d.ts +145 -0
- package/dist/{chunk-WAXEMZUV.js → chunk-44I2BL6E.js} +225 -33
- package/dist/chunk-44I2BL6E.js.map +1 -0
- package/dist/{chunk-56TDSWFU.js → chunk-JI3HDDMS.js} +3 -3
- package/dist/{chunk-56TDSWFU.js.map → chunk-JI3HDDMS.js.map} +1 -1
- package/dist/{chunk-FDYOTN55.js → chunk-L6PX5QTU.js} +253 -878
- package/dist/chunk-L6PX5QTU.js.map +1 -0
- package/dist/index.d.ts +117 -97
- package/dist/index.js +349 -353
- package/dist/index.js.map +1 -1
- package/dist/main.js +285 -153
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.d.ts +1 -3
- package/dist/sandboxes/docker.js +2 -4
- package/dist/templates/parallel-planner/block-scope.sh +160 -0
- package/dist/templates/parallel-planner/conflict-prompt.md +22 -0
- package/dist/templates/parallel-planner/handoff.sh +175 -0
- package/dist/templates/parallel-planner/implement-prompt.md +10 -56
- package/dist/templates/parallel-planner/main.mts +493 -189
- package/dist/templates/parallel-planner/merge-prompt.md +9 -21
- package/dist/templates/parallel-planner/plan-prompt.md +5 -31
- package/dist/templates/parallel-planner/planner-branch.mts +151 -0
- package/dist/templates/parallel-planner/select-issues.mjs +788 -0
- package/dist/templates/parallel-planner/setup.sh +51 -0
- package/dist/templates/parallel-planner/spec-wave-prompt.md +16 -0
- package/dist/templates/parallel-planner/template.json +1 -1
- package/dist/templates/parallel-planner/triage-prompt.md +5 -0
- package/dist/templates/parallel-planner/verify-triage.sh +19 -0
- package/dist/templates/parallel-planner-with-review/block-scope.sh +160 -0
- package/dist/templates/parallel-planner-with-review/conflict-prompt.md +22 -0
- package/dist/templates/parallel-planner-with-review/handoff.sh +175 -0
- package/dist/templates/parallel-planner-with-review/implement-prompt.md +10 -56
- package/dist/templates/parallel-planner-with-review/main.mts +533 -207
- package/dist/templates/parallel-planner-with-review/merge-prompt.md +9 -21
- package/dist/templates/parallel-planner-with-review/plan-prompt.md +5 -31
- package/dist/templates/parallel-planner-with-review/planner-branch.mts +151 -0
- package/dist/templates/parallel-planner-with-review/review-prompt.md +11 -49
- package/dist/templates/parallel-planner-with-review/select-issues.mjs +788 -0
- package/dist/templates/parallel-planner-with-review/setup.sh +51 -0
- package/dist/templates/parallel-planner-with-review/spec-wave-prompt.md +16 -0
- package/dist/templates/parallel-planner-with-review/template.json +1 -1
- package/dist/templates/parallel-planner-with-review/triage-prompt.md +5 -0
- package/dist/templates/parallel-planner-with-review/verify-triage.sh +19 -0
- package/dist/templates/sequential-reviewer/block-scope.sh +160 -0
- package/dist/templates/sequential-reviewer/handoff.sh +175 -0
- package/dist/templates/sequential-reviewer/implement-prompt.md +12 -46
- package/dist/templates/sequential-reviewer/main.mts +248 -103
- package/dist/templates/sequential-reviewer/review-prompt.md +11 -49
- package/dist/templates/sequential-reviewer/select-issues.mjs +788 -0
- package/dist/templates/sequential-reviewer/setup.sh +51 -0
- package/dist/templates/sequential-reviewer/template.json +1 -1
- package/dist/templates/sequential-reviewer/triage-prompt.md +5 -0
- package/dist/templates/sequential-reviewer/verify-triage.sh +19 -0
- package/dist/templates/shared/block-scope.sh +160 -0
- package/dist/templates/shared/handoff.sh +175 -0
- package/dist/templates/shared/select-issues.mjs +788 -0
- package/dist/templates/shared/setup.sh +51 -0
- package/dist/templates/shared/triage-prompt.md +5 -0
- package/dist/templates/shared/verify-triage.sh +19 -0
- package/dist/templates/simple-loop/block-scope.sh +160 -0
- package/dist/templates/simple-loop/handoff.sh +175 -0
- package/dist/templates/simple-loop/main.mts +239 -45
- package/dist/templates/simple-loop/prompt.md +12 -46
- package/dist/templates/simple-loop/select-issues.mjs +788 -0
- package/dist/templates/simple-loop/setup.sh +51 -0
- package/dist/templates/simple-loop/template.json +1 -1
- package/dist/templates/simple-loop/triage-prompt.md +5 -0
- package/dist/templates/simple-loop/verify-triage.sh +19 -0
- package/package.json +1 -17
- package/dist/MountConfig-bZoCs4Dd.d.ts +0 -26
- package/dist/SandboxProvider-XJQqEdSf.d.ts +0 -261
- package/dist/chunk-ACD46ZM4.js +0 -136
- package/dist/chunk-ACD46ZM4.js.map +0 -1
- package/dist/chunk-FDYOTN55.js.map +0 -1
- package/dist/chunk-KMGNFXKN.js +0 -38
- package/dist/chunk-KMGNFXKN.js.map +0 -1
- package/dist/chunk-SOJTAJTF.js +0 -78
- package/dist/chunk-SOJTAJTF.js.map +0 -1
- package/dist/chunk-WAXEMZUV.js.map +0 -1
- package/dist/sandboxes/no-sandbox.d.ts +0 -37
- package/dist/sandboxes/no-sandbox.js +0 -4
- package/dist/sandboxes/no-sandbox.js.map +0 -1
- package/dist/sandboxes/vercel.d.ts +0 -104
- package/dist/sandboxes/vercel.js +0 -166
- package/dist/sandboxes/vercel.js.map +0 -1
- package/dist/templates/blank/main.mts +0 -13
- package/dist/templates/blank/prompt.md +0 -12
- package/dist/templates/blank/template.json +0 -4
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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,
|
|
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 = {
|
|
@@ -259,6 +259,67 @@ 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
|
+
};
|
|
262
323
|
var CwdError = class extends Data.TaggedError("CwdError") {
|
|
263
324
|
};
|
|
264
325
|
var resolveCwd = (cwd) => Effect.gen(function* () {
|
|
@@ -280,8 +341,6 @@ var resolveCwd = (cwd) => Effect.gen(function* () {
|
|
|
280
341
|
}
|
|
281
342
|
return resolved;
|
|
282
343
|
});
|
|
283
|
-
|
|
284
|
-
// src/errors.ts
|
|
285
344
|
var ExecError = class extends Data.TaggedError("ExecError") {
|
|
286
345
|
};
|
|
287
346
|
var ExecHostError = class extends Data.TaggedError("ExecHostError") {
|
|
@@ -364,10 +423,7 @@ var WORKTREES_DIR = "worktrees";
|
|
|
364
423
|
var PATCHES_DIR = "patches";
|
|
365
424
|
var LOCKS_DIR = "locks";
|
|
366
425
|
var RUNNER_DIR = "runner";
|
|
367
|
-
var RUNNER_SANDBOX_MASK_DIR = "runner-sandbox-mask";
|
|
368
|
-
var PARENT_GIT_SANDBOX_DIR = "/.shipyard-parent-git";
|
|
369
426
|
var SYNC_BASE_REF = "refs/shipyard/sync-base";
|
|
370
|
-
var CONTAINER_NAME_PREFIX = "shipyard-";
|
|
371
427
|
var WINDOWS_DRIVE_PATH = /^[A-Za-z]:/;
|
|
372
428
|
var assertSafeRelativePath = (relativePath, label = "relative path") => {
|
|
373
429
|
if (relativePath.length === 0 || relativePath.includes("\0") || isAbsolute(relativePath) || WINDOWS_DRIVE_PATH.test(relativePath) || relativePath.startsWith("\\\\") || relativePath.split(/[\\/]/).some((part) => part === "..")) {
|
|
@@ -440,7 +496,7 @@ var SAFE_CONFIG = [
|
|
|
440
496
|
["checkout.recurseSubmodules", "false"]
|
|
441
497
|
];
|
|
442
498
|
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((
|
|
499
|
+
var runGit = (args, cwd, options = {}) => new Promise((resolve4, reject) => {
|
|
444
500
|
execFile(
|
|
445
501
|
"git",
|
|
446
502
|
[...args],
|
|
@@ -458,11 +514,11 @@ var runGit = (args, cwd, options = {}) => new Promise((resolve6, reject) => {
|
|
|
458
514
|
},
|
|
459
515
|
(error, stdout, stderr) => {
|
|
460
516
|
if (!error) {
|
|
461
|
-
|
|
517
|
+
resolve4({ stdout, stderr, exitCode: 0 });
|
|
462
518
|
return;
|
|
463
519
|
}
|
|
464
520
|
if (typeof error.code === "number") {
|
|
465
|
-
|
|
521
|
+
resolve4({ stdout, stderr, exitCode: error.code });
|
|
466
522
|
return;
|
|
467
523
|
}
|
|
468
524
|
reject(error);
|
|
@@ -869,6 +925,14 @@ var pruneStale = (repoDir) => Effect.gen(function* () {
|
|
|
869
925
|
})
|
|
870
926
|
)
|
|
871
927
|
);
|
|
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
|
+
};
|
|
872
936
|
var PROTECTED_RUNNER_PATH = `${CONFIG_DIR}/${RUNNER_DIR}`;
|
|
873
937
|
var pathKey = (value) => value.normalize("NFC").toLocaleLowerCase("en-US");
|
|
874
938
|
var SAFE_RUNNER_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -900,121 +964,14 @@ var assertExcludesRepositoryRunner = (relativePath) => {
|
|
|
900
964
|
);
|
|
901
965
|
}
|
|
902
966
|
};
|
|
903
|
-
var
|
|
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) => {
|
|
967
|
+
var assertProtectedDirectoryIdentities = async (runnerDir, inspect) => {
|
|
924
968
|
if (!inspect) return;
|
|
925
969
|
const runner = await inspect(runnerDir);
|
|
926
|
-
const mask = await inspect(maskDir);
|
|
927
970
|
if (!runner.directory || runner.symbolicLink) {
|
|
928
971
|
throw new Error(
|
|
929
972
|
`The protected repository runner path must be a real directory: ${runnerDir}`
|
|
930
973
|
);
|
|
931
974
|
}
|
|
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
975
|
};
|
|
1019
976
|
|
|
1020
977
|
// src/RecoveryMessage.ts
|
|
@@ -1524,8 +1481,6 @@ ${msg}`);
|
|
|
1524
1481
|
});
|
|
1525
1482
|
}
|
|
1526
1483
|
});
|
|
1527
|
-
|
|
1528
|
-
// src/SandboxLifecycle.ts
|
|
1529
1484
|
var GIT_SETUP_TIMEOUT_MS = 1e4;
|
|
1530
1485
|
var HOOK_TIMEOUT_MS = 6e4;
|
|
1531
1486
|
var COMMIT_COLLECTION_TIMEOUT_MS = 3e4;
|
|
@@ -1859,6 +1814,169 @@ ${err instanceof Error ? err.message : String(err)}`
|
|
|
1859
1814
|
}
|
|
1860
1815
|
return { result, branch: finalBranch, commits };
|
|
1861
1816
|
});
|
|
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
|
+
};
|
|
1862
1980
|
var execHostGit3 = (args, cwd) => Effect.tryPromise({
|
|
1863
1981
|
try: () => execHostGit(args, cwd),
|
|
1864
1982
|
catch: (e) => new SyncError({
|
|
@@ -1964,92 +2082,8 @@ var syncIn = (hostRepoDir, handle) => Effect.gen(function* () {
|
|
|
1964
2082
|
// src/startSandbox.ts
|
|
1965
2083
|
var CONTAINER_START_TIMEOUT_MS = 12e4;
|
|
1966
2084
|
var SYNC_IN_TIMEOUT_MS = 12e4;
|
|
1967
|
-
var COPY_PATHS_TIMEOUT_MS =
|
|
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* () {
|
|
2085
|
+
var COPY_PATHS_TIMEOUT_MS = 6e4;
|
|
2086
|
+
var startSandbox = (options) => Effect.gen(function* () {
|
|
2053
2087
|
const handle = yield* Effect.tryPromise({
|
|
2054
2088
|
try: () => options.provider.create({
|
|
2055
2089
|
env: options.env,
|
|
@@ -2079,6 +2113,8 @@ var startIsolatedSandbox = (options) => Effect.gen(function* () {
|
|
|
2079
2113
|
);
|
|
2080
2114
|
if (options.copyPaths && options.copyPaths.length > 0) {
|
|
2081
2115
|
const pathsToCopy = options.copyPaths;
|
|
2116
|
+
const copySourceDir = options.copySourceDir ?? options.sourceRepoDir ?? options.hostRepoDir;
|
|
2117
|
+
const copyTimeoutMs = options.copyTimeoutMs ?? COPY_PATHS_TIMEOUT_MS;
|
|
2082
2118
|
yield* Effect.gen(function* () {
|
|
2083
2119
|
for (const relativePath of pathsToCopy) {
|
|
2084
2120
|
let hostPath;
|
|
@@ -2086,7 +2122,7 @@ var startIsolatedSandbox = (options) => Effect.gen(function* () {
|
|
|
2086
2122
|
try {
|
|
2087
2123
|
assertExcludesRepositoryRunner(relativePath);
|
|
2088
2124
|
hostPath = resolveSafeRelativePath(
|
|
2089
|
-
|
|
2125
|
+
copySourceDir,
|
|
2090
2126
|
relativePath,
|
|
2091
2127
|
"copy path"
|
|
2092
2128
|
);
|
|
@@ -2109,7 +2145,7 @@ var startIsolatedSandbox = (options) => Effect.gen(function* () {
|
|
|
2109
2145
|
yield* Effect.tryPromise({
|
|
2110
2146
|
try: async () => {
|
|
2111
2147
|
await assertNoSymlinkComponents(
|
|
2112
|
-
|
|
2148
|
+
copySourceDir,
|
|
2113
2149
|
hostPath,
|
|
2114
2150
|
"copy source"
|
|
2115
2151
|
);
|
|
@@ -2127,10 +2163,10 @@ var startIsolatedSandbox = (options) => Effect.gen(function* () {
|
|
|
2127
2163
|
}
|
|
2128
2164
|
}).pipe(
|
|
2129
2165
|
withTimeout(
|
|
2130
|
-
|
|
2166
|
+
copyTimeoutMs,
|
|
2131
2167
|
() => new CopyToWorktreeTimeoutError({
|
|
2132
|
-
message: `Copying paths to
|
|
2133
|
-
timeoutMs:
|
|
2168
|
+
message: `Copying paths to sandbox timed out after ${copyTimeoutMs}ms`,
|
|
2169
|
+
timeoutMs: copyTimeoutMs,
|
|
2134
2170
|
paths: pathsToCopy
|
|
2135
2171
|
})
|
|
2136
2172
|
)
|
|
@@ -2144,667 +2180,6 @@ var startIsolatedSandbox = (options) => Effect.gen(function* () {
|
|
|
2144
2180
|
}).pipe(Effect.onError(() => Effect.promise(() => handle.close())));
|
|
2145
2181
|
});
|
|
2146
2182
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
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
|
|
2183
|
+
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 };
|
|
2184
|
+
//# sourceMappingURL=chunk-L6PX5QTU.js.map
|
|
2185
|
+
//# sourceMappingURL=chunk-L6PX5QTU.js.map
|