@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.
Files changed (41) hide show
  1. package/README.md +41 -34
  2. package/dist/MountConfig-bZoCs4Dd.d.ts +26 -0
  3. package/dist/SandboxProvider-XJQqEdSf.d.ts +261 -0
  4. package/dist/{chunk-JI3HDDMS.js → chunk-56TDSWFU.js} +3 -3
  5. package/dist/{chunk-JI3HDDMS.js.map → chunk-56TDSWFU.js.map} +1 -1
  6. package/dist/chunk-ACD46ZM4.js +136 -0
  7. package/dist/chunk-ACD46ZM4.js.map +1 -0
  8. package/dist/{chunk-L6PX5QTU.js → chunk-FDYOTN55.js} +887 -262
  9. package/dist/chunk-FDYOTN55.js.map +1 -0
  10. package/dist/chunk-KMGNFXKN.js +38 -0
  11. package/dist/chunk-KMGNFXKN.js.map +1 -0
  12. package/dist/chunk-SOJTAJTF.js +78 -0
  13. package/dist/chunk-SOJTAJTF.js.map +1 -0
  14. package/dist/{chunk-44I2BL6E.js → chunk-WAXEMZUV.js} +33 -225
  15. package/dist/chunk-WAXEMZUV.js.map +1 -0
  16. package/dist/index.d.ts +97 -117
  17. package/dist/index.js +344 -322
  18. package/dist/index.js.map +1 -1
  19. package/dist/main.js +139 -235
  20. package/dist/main.js.map +1 -1
  21. package/dist/sandboxes/docker.d.ts +3 -1
  22. package/dist/sandboxes/docker.js +4 -2
  23. package/dist/sandboxes/no-sandbox.d.ts +37 -0
  24. package/dist/sandboxes/no-sandbox.js +4 -0
  25. package/dist/sandboxes/no-sandbox.js.map +1 -0
  26. package/dist/sandboxes/vercel.d.ts +104 -0
  27. package/dist/sandboxes/vercel.js +166 -0
  28. package/dist/sandboxes/vercel.js.map +1 -0
  29. package/dist/templates/blank/main.mts +13 -0
  30. package/dist/templates/blank/prompt.md +12 -0
  31. package/dist/templates/blank/template.json +4 -0
  32. package/dist/templates/parallel-planner/main.mts +9 -67
  33. package/dist/templates/parallel-planner-with-review/main.mts +12 -70
  34. package/dist/templates/sequential-reviewer/main.mts +3 -52
  35. package/dist/templates/simple-loop/main.mts +2 -51
  36. package/package.json +17 -1
  37. package/dist/MountConfig-BHnKnA4h.d.ts +0 -145
  38. package/dist/chunk-44I2BL6E.js.map +0 -1
  39. package/dist/chunk-L6PX5QTU.js.map +0 -1
  40. package/dist/templates/parallel-planner/planner-branch.mts +0 -151
  41. package/dist/templates/parallel-planner-with-review/planner-branch.mts +0 -151
@@ -0,0 +1,38 @@
1
+ // src/SandboxProvider.ts
2
+ var createBindMountSandboxProvider = (config) => ({
3
+ tag: "bind-mount",
4
+ name: config.name,
5
+ env: config.env ?? {},
6
+ sandboxHomedir: config.sandboxHomedir,
7
+ create: config.create
8
+ });
9
+ var createIsolatedSandboxProvider = (config) => ({
10
+ tag: "isolated",
11
+ name: config.name,
12
+ env: config.env ?? {},
13
+ create: config.create
14
+ });
15
+ var toSessionTransferHandle = (handle) => {
16
+ if (handle === void 0 || !("copyFileOut" in handle)) return void 0;
17
+ if ("copyFileIn" in handle) return handle;
18
+ return {
19
+ worktreePath: handle.worktreePath,
20
+ exec: (command, options) => handle.exec(command, options),
21
+ interactiveExec: handle.interactiveExec?.bind(handle),
22
+ copyFileIn: (source, destination) => handle.copyIn(source, destination),
23
+ copyFileOut: (source, destination) => handle.copyFileOut(source, destination),
24
+ close: () => handle.close()
25
+ };
26
+ };
27
+
28
+ // src/shellQuote.ts
29
+ var shellQuote = (value) => {
30
+ if (value.includes("\0")) {
31
+ throw new Error("Cannot quote a string containing a NUL byte");
32
+ }
33
+ return `'${value.replaceAll("'", "'\\''")}'`;
34
+ };
35
+
36
+ export { createBindMountSandboxProvider, createIsolatedSandboxProvider, shellQuote, toSessionTransferHandle };
37
+ //# sourceMappingURL=chunk-KMGNFXKN.js.map
38
+ //# sourceMappingURL=chunk-KMGNFXKN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/SandboxProvider.ts","../src/shellQuote.ts"],"names":[],"mappings":";AAsUO,IAAM,8BAAA,GAAiC,CAC5C,MAAA,MAC8B;AAAA,EAC9B,GAAA,EAAK,YAAA;AAAA,EACL,MAAM,MAAA,CAAO,IAAA;AAAA,EACb,GAAA,EAAK,MAAA,CAAO,GAAA,IAAO,EAAC;AAAA,EACpB,gBAAgB,MAAA,CAAO,cAAA;AAAA,EACvB,QAAQ,MAAA,CAAO;AACjB,CAAA;AAMO,IAAM,6BAAA,GAAgC,CAC3C,MAAA,MAC6B;AAAA,EAC7B,GAAA,EAAK,UAAA;AAAA,EACL,MAAM,MAAA,CAAO,IAAA;AAAA,EACb,GAAA,EAAK,MAAA,CAAO,GAAA,IAAO,EAAC;AAAA,EACpB,QAAQ,MAAA,CAAO;AACjB,CAAA;AAGO,IAAM,uBAAA,GAA0B,CACrC,MAAA,KAKuC;AACvC,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,EAAE,aAAA,IAAiB,SAAS,OAAO,MAAA;AAC/D,EAAA,IAAI,YAAA,IAAgB,QAAQ,OAAO,MAAA;AACnC,EAAA,OAAO;AAAA,IACL,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,MAAM,CAAC,OAAA,EAAS,YAAY,MAAA,CAAO,IAAA,CAAK,SAAS,OAAO,CAAA;AAAA,IACxD,eAAA,EAAiB,MAAA,CAAO,eAAA,EAAiB,IAAA,CAAK,MAAM,CAAA;AAAA,IACpD,YAAY,CAAC,MAAA,EAAQ,gBAAgB,MAAA,CAAO,MAAA,CAAO,QAAQ,WAAW,CAAA;AAAA,IACtE,aAAa,CAAC,MAAA,EAAQ,gBACpB,MAAA,CAAO,WAAA,CAAY,QAAQ,WAAW,CAAA;AAAA,IACxC,KAAA,EAAO,MAAM,MAAA,CAAO,KAAA;AAAM,GAC5B;AACF;;;AC/WO,IAAM,UAAA,GAAa,CAAC,KAAA,KAA0B;AACnD,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,EAAG;AACxB,IAAA,MAAM,IAAI,MAAM,6CAA6C,CAAA;AAAA,EAC/D;AACA,EAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,UAAA,CAAW,GAAA,EAAK,OAAO,CAAC,CAAA,CAAA,CAAA;AAC3C","file":"chunk-KMGNFXKN.js","sourcesContent":["/**\n * Sandbox provider types — the pluggable interface for sandbox runtimes.\n *\n * Provider authors implement a small Promise-based interface. Shipyard\n * handles worktree creation, git mount resolution, and commit extraction.\n */\n\n/** Result of executing a command inside a sandbox. */\nexport interface ExecResult {\n readonly stdout: string;\n readonly stderr: string;\n readonly exitCode: number;\n}\n\n/** Options for interactiveExec — the streams the provider should wire to the spawned process. */\nexport interface InteractiveExecOptions {\n readonly stdin: NodeJS.ReadableStream;\n readonly stdout: NodeJS.WritableStream;\n readonly stderr: NodeJS.WritableStream;\n readonly cwd?: string;\n /** Terminate the interactive process when the caller cancels the session. */\n readonly signal?: AbortSignal;\n}\n\n/** Handle to a running bind-mount sandbox. */\nexport interface BindMountSandboxHandle {\n /** Absolute path to the worktree inside the sandbox. */\n readonly worktreePath: string;\n /**\n * Execute a command in the sandbox.\n *\n * Implementations MUST support line-by-line streaming via `onLine`. This is\n * how Shipyard delivers live feedback to the user and enforces idle timeouts —\n * without a streaming implementation, neither will work. A buffered/batch\n * implementation that only calls `onLine` after the process exits does NOT\n * satisfy this contract.\n *\n * When `stdin` is set, the implementation pipes the string to the child\n * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.\n */\n exec(\n command: string,\n options?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n /** Abort the command when the caller's operation is cancelled. */\n signal?: AbortSignal;\n /** Reject/terminate the command after this many combined output bytes. */\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult>;\n /**\n * Launch an interactive process inside the sandbox.\n * Optional — providers that support interactive sessions implement this.\n * The provider detects TTY mode from the streams (e.g. stdin.isTTY) and\n * allocates a pseudo-terminal accordingly.\n * Implementations MUST terminate the underlying process when `signal` aborts.\n */\n interactiveExec?(\n args: string[],\n options: InteractiveExecOptions,\n ): Promise<{ exitCode: number }>;\n /** Copy a single file from the host into the sandbox. */\n copyFileIn(hostPath: string, sandboxPath: string): Promise<void>;\n /** Copy a single file from the sandbox to the host. */\n copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;\n /** Tear down the sandbox. */\n close(): Promise<void>;\n}\n\n/** Options passed to a bind-mount provider's `create` function. */\nexport interface BindMountCreateOptions {\n /** Host-side path to the worktree directory. */\n readonly worktreePath: string;\n /** Host-side path to the original repo root. */\n readonly hostRepoPath: string;\n /** Volume mounts to apply (host:sandbox pairs). */\n readonly mounts: Array<{\n hostPath: string;\n sandboxPath: string;\n readonly?: boolean;\n }>;\n /** Environment variables to inject into the sandbox. */\n readonly env: Record<string, string>;\n}\n\n/** Configuration for createBindMountSandboxProvider. */\nexport interface BindMountSandboxProviderConfig {\n /** Human-readable name for this provider (e.g. \"docker\"). */\n readonly name: string;\n /** Environment variables injected by this provider. Merged at launch time. */\n readonly env?: Record<string, string>;\n /**\n * Absolute path to the home directory inside the sandbox (e.g. `\"/home/agent\"`).\n * Used to expand `~` in user-provided `sandboxPath` mount configs.\n * Set to `undefined` for providers that do not have a fixed home directory.\n */\n readonly sandboxHomedir?: string;\n /** Create a sandbox handle from the given options. */\n readonly create: (\n options: BindMountCreateOptions,\n ) => Promise<BindMountSandboxHandle>;\n}\n\n/** Handle to a running isolated sandbox (extends bind-mount with file transfer). */\nexport interface IsolatedSandboxHandle {\n /** Absolute path to the worktree inside the sandbox. */\n readonly worktreePath: string;\n /**\n * Execute a command in the sandbox.\n *\n * Implementations MUST support line-by-line streaming via `onLine`. This is\n * how Shipyard delivers live feedback to the user and enforces idle timeouts —\n * without a streaming implementation, neither will work. A buffered/batch\n * implementation that only calls `onLine` after the process exits does NOT\n * satisfy this contract.\n *\n * When `stdin` is set, the implementation pipes the string to the child\n * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.\n */\n exec(\n command: string,\n options?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n /** Abort the command when the caller's operation is cancelled. */\n signal?: AbortSignal;\n /** Reject/terminate the command after this many combined output bytes. */\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult>;\n /**\n * Launch an interactive process inside the sandbox.\n * Optional — providers that support interactive sessions implement this.\n * The provider detects TTY mode from the streams (e.g. stdin.isTTY) and\n * allocates a pseudo-terminal accordingly.\n * Implementations MUST terminate the underlying process when `signal` aborts.\n */\n interactiveExec?(\n args: string[],\n options: InteractiveExecOptions,\n ): Promise<{ exitCode: number }>;\n /** Copy a file or directory from the host into the sandbox. */\n copyIn(hostPath: string, sandboxPath: string): Promise<void>;\n /** Copy a single file from the sandbox to the host. */\n copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;\n /** Tear down the sandbox. */\n close(): Promise<void>;\n}\n\n/** Options passed to an isolated provider's `create` function. */\nexport interface IsolatedCreateOptions {\n /** Original host repository path, used only for image naming; never mounted. */\n readonly hostRepoPath?: string;\n /** Environment variables to inject into the sandbox. */\n readonly env: Record<string, string>;\n}\n\n/** Configuration for createIsolatedSandboxProvider. */\nexport interface IsolatedSandboxProviderConfig {\n /** Human-readable name for this provider (e.g. \"vercel\"). */\n readonly name: string;\n /** Environment variables injected by this provider. Merged at launch time. */\n readonly env?: Record<string, string>;\n /** Create an isolated sandbox handle from the given options. */\n readonly create: (\n options: IsolatedCreateOptions,\n ) => Promise<IsolatedSandboxHandle>;\n}\n\n/** A bind-mount sandbox provider. */\nexport interface BindMountSandboxProvider {\n /** @internal Discriminator for internal dispatch. */\n readonly tag: \"bind-mount\";\n /** Human-readable provider name. */\n readonly name: string;\n /** Environment variables injected by this provider. */\n readonly env: Record<string, string>;\n /**\n * Absolute path to the home directory inside the sandbox (e.g. `\"/home/agent\"`).\n * `undefined` when the provider does not declare a sandbox home directory.\n */\n readonly sandboxHomedir: string | undefined;\n /** @internal Create a sandbox handle. */\n readonly create: (\n options: BindMountCreateOptions,\n ) => Promise<BindMountSandboxHandle>;\n}\n\n/** An isolated sandbox provider. */\nexport interface IsolatedSandboxProvider {\n /** @internal Discriminator for internal dispatch. */\n readonly tag: \"isolated\";\n /** Human-readable provider name. */\n readonly name: string;\n /** Environment variables injected by this provider. */\n readonly env: Record<string, string>;\n /** @internal Create an isolated sandbox handle. */\n readonly create: (\n options: IsolatedCreateOptions,\n ) => Promise<IsolatedSandboxHandle>;\n}\n\n/** Handle to a no-sandbox session — runs commands directly on the host. */\nexport interface NoSandboxHandle {\n /** Absolute path to the worktree on the host. */\n readonly worktreePath: string;\n /**\n * Execute a command on the host.\n *\n * Implementations MUST support line-by-line streaming via `onLine`. This is\n * how Shipyard delivers live feedback to the user and enforces idle timeouts —\n * without a streaming implementation, neither will work.\n *\n * When `stdin` is set, the implementation pipes the string to the child\n * process's stdin and closes it. This avoids the Linux 128 KB per-arg limit.\n */\n exec(\n command: string,\n options?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n /** Abort the command when the caller's operation is cancelled. */\n signal?: AbortSignal;\n /** Reject/terminate the command after this many combined output bytes. */\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult>;\n /**\n * Launch an interactive process on the host with inherited stdio.\n */\n interactiveExec(\n args: string[],\n options: InteractiveExecOptions,\n ): Promise<{ exitCode: number }>;\n /** No-op — no container to tear down. */\n close(): Promise<void>;\n}\n\n/** A no-sandbox provider — runs the agent directly on the host with no container isolation. */\nexport interface NoSandboxProvider {\n /** @internal Discriminator for internal dispatch. */\n readonly tag: \"none\";\n /** Human-readable provider name. */\n readonly name: string;\n /** Environment variables injected by this provider. */\n readonly env: Record<string, string>;\n /** @internal Create a no-sandbox handle. */\n readonly create: (options: {\n readonly worktreePath: string;\n readonly env: Record<string, string>;\n }) => Promise<NoSandboxHandle>;\n}\n\n// ---------- Branch strategy types ----------\n\n/** Head strategy: agent writes directly to host working directory. Bind-mount only. */\nexport interface HeadBranchStrategy {\n readonly type: \"head\";\n}\n\n/** Merge-to-head strategy: temp branch, merge back to HEAD, delete temp branch. */\nexport interface MergeToHeadBranchStrategy {\n readonly type: \"merge-to-head\";\n}\n\n/** Branch strategy: commits land on an explicit named branch. */\nexport interface NamedBranchStrategy {\n readonly type: \"branch\";\n readonly branch: string;\n /**\n * Git ref to use as the starting point when creating a new branch.\n * Only used when the branch doesn't already exist — ignored otherwise.\n * Callers are responsible for ensuring the ref is current (e.g. `git fetch`).\n * Defaults to `HEAD` when omitted.\n */\n readonly baseBranch?: string;\n}\n\n/** Branch strategy for bind-mount providers (all three variants). */\nexport type BindMountBranchStrategy =\n | HeadBranchStrategy\n | MergeToHeadBranchStrategy\n | NamedBranchStrategy;\n\n/** Branch strategy for isolated providers (no head — can't write to host). */\nexport type IsolatedBranchStrategy =\n | MergeToHeadBranchStrategy\n | NamedBranchStrategy;\n\n/** Branch strategy for no-sandbox providers (all three — same as bind-mount). */\nexport type NoSandboxBranchStrategy =\n | HeadBranchStrategy\n | MergeToHeadBranchStrategy\n | NamedBranchStrategy;\n\n/** Union of all branch strategy variants. */\nexport type BranchStrategy =\n | BindMountBranchStrategy\n | IsolatedBranchStrategy\n | NoSandboxBranchStrategy;\n\n/**\n * A sandbox provider — the pluggable unit that `run()`, `interactive()`, and\n * `createSandbox()` accept. Tagged for internal dispatch: \"bind-mount\",\n * \"isolated\", or \"none\". When `NoSandboxProvider` is used, the agent runs\n * directly on the host with no container isolation — opt in at your own risk.\n */\nexport type SandboxProvider =\n | BindMountSandboxProvider\n | IsolatedSandboxProvider\n | NoSandboxProvider;\n\n/** @deprecated Use `SandboxProvider` — it now includes `NoSandboxProvider`. */\nexport type AnySandboxProvider = SandboxProvider;\n\n/**\n * Create a bind-mount sandbox provider from a config object.\n * The returned provider can be passed to `run()` or `createSandbox()`.\n */\nexport const createBindMountSandboxProvider = (\n config: BindMountSandboxProviderConfig,\n): BindMountSandboxProvider => ({\n tag: \"bind-mount\",\n name: config.name,\n env: config.env ?? {},\n sandboxHomedir: config.sandboxHomedir,\n create: config.create,\n});\n\n/**\n * Create an isolated sandbox provider from a config object.\n * The returned provider can be passed to `run()` or `createSandbox()`.\n */\nexport const createIsolatedSandboxProvider = (\n config: IsolatedSandboxProviderConfig,\n): IsolatedSandboxProvider => ({\n tag: \"isolated\",\n name: config.name,\n env: config.env ?? {},\n create: config.create,\n});\n\n/** @internal Adapt isolated file transfer to the existing agent-session boundary. */\nexport const toSessionTransferHandle = (\n handle:\n | BindMountSandboxHandle\n | IsolatedSandboxHandle\n | NoSandboxHandle\n | undefined,\n): BindMountSandboxHandle | undefined => {\n if (handle === undefined || !(\"copyFileOut\" in handle)) return undefined;\n if (\"copyFileIn\" in handle) return handle;\n return {\n worktreePath: handle.worktreePath,\n exec: (command, options) => handle.exec(command, options),\n interactiveExec: handle.interactiveExec?.bind(handle),\n copyFileIn: (source, destination) => handle.copyIn(source, destination),\n copyFileOut: (source, destination) =>\n handle.copyFileOut(source, destination),\n close: () => handle.close(),\n };\n};\n","/** Quote a value for use as one POSIX-shell argument. */\nexport const shellQuote = (value: string): string => {\n if (value.includes(\"\\0\")) {\n throw new Error(\"Cannot quote a string containing a NUL byte\");\n }\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\n};\n"]}
@@ -0,0 +1,78 @@
1
+ import { MAX_TAIL_CHARS, collectProcessOutput } from './chunk-ACD46ZM4.js';
2
+ import { spawn } from 'child_process';
3
+
4
+ var noSandbox = (options) => ({
5
+ tag: "none",
6
+ name: "no-sandbox",
7
+ env: options?.env ?? {},
8
+ create: async (createOptions) => {
9
+ const worktreePath = createOptions.worktreePath;
10
+ const processEnv = { ...process.env, ...createOptions.env };
11
+ const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;
12
+ const handle = {
13
+ worktreePath,
14
+ exec: (command, opts) => {
15
+ const cwd = opts?.cwd ?? worktreePath;
16
+ const isWindows = process.platform === "win32";
17
+ const shellCmd = isWindows ? "cmd.exe" : "sh";
18
+ const shellArgs = isWindows ? ["/d", "/s", "/c", command] : ["-c", command];
19
+ return new Promise((resolve, reject) => {
20
+ const proc = spawn(shellCmd, shellArgs, {
21
+ cwd,
22
+ env: processEnv,
23
+ signal: opts?.signal,
24
+ stdio: [
25
+ opts?.stdin !== void 0 ? "pipe" : "ignore",
26
+ "pipe",
27
+ "pipe"
28
+ ],
29
+ windowsVerbatimArguments: isWindows
30
+ });
31
+ if (opts?.stdin !== void 0) {
32
+ proc.stdin.write(opts.stdin);
33
+ proc.stdin.end();
34
+ }
35
+ proc.on("error", (error) => {
36
+ reject(new Error(`exec failed: ${error.message}`));
37
+ });
38
+ collectProcessOutput(
39
+ {
40
+ stdout: proc.stdout,
41
+ stderr: proc.stderr,
42
+ kill: () => proc.kill("SIGKILL"),
43
+ onClose: (listener) => proc.on("close", listener)
44
+ },
45
+ { ...opts, maxOutputTailChars },
46
+ resolve,
47
+ reject
48
+ );
49
+ });
50
+ },
51
+ interactiveExec: (args, opts) => {
52
+ return new Promise((resolve, reject) => {
53
+ const [cmd, ...rest] = args;
54
+ const proc = spawn(cmd, rest, {
55
+ cwd: opts.cwd ?? worktreePath,
56
+ env: processEnv,
57
+ stdio: [opts.stdin, opts.stdout, opts.stderr],
58
+ shell: process.platform === "win32",
59
+ signal: opts.signal
60
+ });
61
+ proc.on("error", (error) => {
62
+ reject(new Error(`exec failed: ${error.message}`));
63
+ });
64
+ proc.on("close", (code) => {
65
+ resolve({ exitCode: code ?? 0 });
66
+ });
67
+ });
68
+ },
69
+ close: async () => {
70
+ }
71
+ };
72
+ return handle;
73
+ }
74
+ });
75
+
76
+ export { noSandbox };
77
+ //# sourceMappingURL=chunk-SOJTAJTF.js.map
78
+ //# sourceMappingURL=chunk-SOJTAJTF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sandboxes/no-sandbox.ts"],"names":[],"mappings":"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","file":"chunk-SOJTAJTF.js","sourcesContent":["/**\n * No-sandbox provider — runs the agent directly on the host with no container isolation.\n *\n * Usage:\n * import { noSandbox } from \"@snappedly-tools/shipyard/sandboxes/no-sandbox\";\n * await run({ agent: codex(CODEX_MODELS.routine), sandbox: noSandbox(), prompt: \"...\" });\n *\n * Accepted by `run()`, `interactive()`, and `createSandbox()`. It skips\n * container isolation entirely and does not pass\n * `--dangerously-skip-permissions` to the agent.\n */\n\nimport { spawn, type StdioOptions } from \"node:child_process\";\nimport type {\n NoSandboxProvider,\n NoSandboxHandle,\n ExecResult,\n InteractiveExecOptions,\n} from \"../SandboxProvider.js\";\nimport { MAX_TAIL_CHARS } from \"../boundedTail.js\";\nimport { collectProcessOutput } from \"../processOutput.js\";\n\nexport interface NoSandboxOptions {\n /** Environment variables injected by this provider. Merged at launch time. */\n readonly env?: Record<string, string>;\n /**\n * Maximum number of characters of streamed `exec` output retained per stream\n * (stdout and stderr) when an `onLine` callback is supplied (default: 64KiB).\n *\n * Output is delivered live to `onLine` regardless; this only bounds the tail\n * returned in `ExecResult`, preventing a long-running agent's output from\n * overflowing V8's max string length and crashing the run.\n */\n readonly maxOutputTailChars?: number;\n}\n\n/**\n * Create a no-sandbox provider.\n *\n * The returned provider runs the agent directly on the host. All three\n * branch strategies are supported (head, merge-to-head, branch),\n * defaulting to head.\n */\nexport const noSandbox = (options?: NoSandboxOptions): NoSandboxProvider => ({\n tag: \"none\",\n name: \"no-sandbox\",\n env: options?.env ?? {},\n create: async (createOptions): Promise<NoSandboxHandle> => {\n const worktreePath = createOptions.worktreePath;\n const processEnv = { ...process.env, ...createOptions.env };\n const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;\n\n const handle: NoSandboxHandle = {\n worktreePath,\n\n exec: (\n command: string,\n opts?: {\n onLine?: (line: string) => void;\n cwd?: string;\n sudo?: boolean;\n stdin?: string;\n signal?: AbortSignal;\n maxOutputBytes?: number;\n },\n ): Promise<ExecResult> => {\n // sudo is a no-op for no-sandbox — the user is already on the host\n const cwd = opts?.cwd ?? worktreePath;\n const isWindows = process.platform === \"win32\";\n // PowerShell and cmd.exe don't ship `sh`, so on Windows route the\n // command string through cmd.exe instead. `/d` skips AutoRun, `/s`\n // preserves the quoted command verbatim, `/c` runs it and exits.\n // `windowsVerbatimArguments` keeps Node from re-quoting our args.\n const shellCmd = isWindows ? \"cmd.exe\" : \"sh\";\n const shellArgs = isWindows\n ? [\"/d\", \"/s\", \"/c\", command]\n : [\"-c\", command];\n\n return new Promise((resolve, reject) => {\n const proc = spawn(shellCmd, shellArgs, {\n cwd,\n env: processEnv,\n signal: opts?.signal,\n stdio: [\n opts?.stdin !== undefined ? \"pipe\" : \"ignore\",\n \"pipe\",\n \"pipe\",\n ],\n windowsVerbatimArguments: isWindows,\n });\n\n if (opts?.stdin !== undefined) {\n proc.stdin!.write(opts.stdin);\n proc.stdin!.end();\n }\n\n proc.on(\"error\", (error) => {\n reject(new Error(`exec failed: ${error.message}`));\n });\n\n collectProcessOutput(\n {\n stdout: proc.stdout!,\n stderr: proc.stderr!,\n kill: () => proc.kill(\"SIGKILL\"),\n onClose: (listener) => proc.on(\"close\", listener),\n },\n { ...opts, maxOutputTailChars },\n resolve,\n reject,\n );\n });\n },\n\n interactiveExec: (\n args: string[],\n opts: InteractiveExecOptions,\n ): Promise<{ exitCode: number }> => {\n return new Promise((resolve, reject) => {\n const [cmd, ...rest] = args;\n // Agent CLIs on Windows are typically installed as `.cmd`/`.ps1`\n // npm wrappers; bare `spawn(\"claude\", …)` only resolves `.exe`\n // without `shell: true`, so let cmd.exe handle PATHEXT lookup.\n const proc = spawn(cmd!, rest, {\n cwd: opts.cwd ?? worktreePath,\n env: processEnv,\n stdio: [opts.stdin, opts.stdout, opts.stderr] as StdioOptions,\n shell: process.platform === \"win32\",\n signal: opts.signal,\n });\n\n proc.on(\"error\", (error: Error) => {\n reject(new Error(`exec failed: ${error.message}`));\n });\n\n proc.on(\"close\", (code: number | null) => {\n resolve({ exitCode: code ?? 0 });\n });\n });\n },\n\n close: async (): Promise<void> => {\n // No-op — no container to tear down\n },\n };\n\n return handle;\n },\n});\n"]}
@@ -1,12 +1,12 @@
1
- import { RUNTIME_NAMESPACE, assertProtectedDirectoryIdentities, ACTIVATION_LABEL, CONFIG_DIR, RUNNER_DIR, createIsolatedSandboxProvider, registerShutdown, DockerError, repositoryRunnerEnvironment, SANDBOX_REPO_DIR } from './chunk-L6PX5QTU.js';
1
+ import { assertProtectedDirectoryIdentities, ACTIVATION_LABEL, CONFIG_DIR, RUNNER_DIR, RUNNER_SANDBOX_MASK_DIR, resolveUserMounts, assertIsolatedWorkspaceMounts, processFileMountParents, defaultImageName, registerShutdown, DockerError, repositoryRunnerEnvironment, formatVolumeMount } from './chunk-FDYOTN55.js';
2
+ import { createIsolatedSandboxProvider } from './chunk-KMGNFXKN.js';
3
+ import { MAX_TAIL_CHARS, collectProcessOutput } from './chunk-ACD46ZM4.js';
2
4
  import { execFile, execFileSync, spawn } from 'child_process';
3
- import { resolve, join, posix, dirname, relative, isAbsolute } from 'path';
5
+ import { resolve, join, posix } from 'path';
4
6
  import { promisify } from 'util';
5
7
  import { randomUUID } from 'crypto';
6
8
  import { Effect } from 'effect';
7
- import { constants, existsSync, statSync } from 'fs';
8
- import { homedir } from 'os';
9
- import { createInterface } from 'readline';
9
+ import { constants } from 'fs';
10
10
  import { rm, lstat, realpath, mkdir, readFile, access } from 'fs/promises';
11
11
 
12
12
  var exec = promisify(execFile);
@@ -38,79 +38,6 @@ var copyIntoContainer = async (engine, container, owner, hostPath, sandboxPath)
38
38
  );
39
39
  }
40
40
  };
41
- var defaultImageName = (repoDir) => {
42
- const dirName = repoDir.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "local";
43
- const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
44
- return `${RUNTIME_NAMESPACE}:${sanitized || "local"}`;
45
- };
46
- var expandTilde = (p, homeDirPath) => {
47
- const home = homeDirPath ?? homedir();
48
- if (p === "~") return home;
49
- if (p.startsWith("~/") || p.startsWith("~\\")) return home + "/" + p.slice(2);
50
- return p;
51
- };
52
- var resolveHostPath = (hostPath) => {
53
- const expanded = expandTilde(hostPath);
54
- return isAbsolute(expanded) ? expanded : resolve(process.cwd(), expanded);
55
- };
56
- var resolveSandboxPath = (sandboxPath, sandboxHomedir) => {
57
- const hasTilde = sandboxPath === "~" || sandboxPath.startsWith("~/") || sandboxPath.startsWith("~\\");
58
- const expanded = hasTilde ? expandTilde(sandboxPath, sandboxHomedir) : sandboxPath;
59
- return isAbsolute(expanded) ? resolve(expanded) : resolve(SANDBOX_REPO_DIR, expanded);
60
- };
61
- var resolveUserMounts = (mounts, sandboxHomedir) => mounts.map((m) => {
62
- const resolvedHostPath = resolveHostPath(m.hostPath);
63
- if (!existsSync(resolvedHostPath)) {
64
- throw new Error(
65
- `Mount hostPath does not exist: ${m.hostPath}` + (m.hostPath !== resolvedHostPath ? ` (resolved to ${resolvedHostPath})` : "")
66
- );
67
- }
68
- return {
69
- hostPath: resolvedHostPath,
70
- sandboxPath: resolveSandboxPath(m.sandboxPath, sandboxHomedir),
71
- ...m.readonly ? { readonly: true } : {}
72
- };
73
- });
74
- var formatVolumeMount = (mount, selinuxLabel) => {
75
- const base = `${mount.hostPath}:${mount.sandboxPath}`;
76
- const options = [mount.readonly ? "ro" : void 0, selinuxLabel || void 0].filter((option) => option !== void 0).join(",");
77
- return options ? `${base}:${options}` : base;
78
- };
79
- var processFileMountParents = (mounts, sandboxHomedir, statFn = statSync) => {
80
- const parentDirs = /* @__PURE__ */ new Set();
81
- for (const mount of mounts) {
82
- let isFile;
83
- try {
84
- isFile = statFn(mount.hostPath).isFile();
85
- } catch {
86
- continue;
87
- }
88
- if (!isFile) continue;
89
- const parentDir = resolve(dirname(mount.sandboxPath));
90
- const homeDir = resolve(sandboxHomedir);
91
- if (parentDir === homeDir) continue;
92
- const fromHome = relative(homeDir, parentDir);
93
- if (fromHome === ".." || fromHome.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(fromHome)) {
94
- throw new Error(
95
- `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.`
96
- );
97
- }
98
- parentDirs.add(parentDir);
99
- }
100
- return [...parentDirs];
101
- };
102
- var assertIsolatedWorkspaceMounts = (mounts) => {
103
- for (const mount of mounts) {
104
- const path = posix.resolve(mount.sandboxPath);
105
- if (path === "/" || path === SANDBOX_REPO_DIR || path.startsWith(`${SANDBOX_REPO_DIR}/`) || SANDBOX_REPO_DIR.startsWith(`${path}/`)) {
106
- throw new Error(
107
- `Mount overlaps the isolated workspace: ${mount.sandboxPath}. Mount outside ${SANDBOX_REPO_DIR} or use copyToWorktree.`
108
- );
109
- }
110
- }
111
- };
112
-
113
- // src/DockerLifecycle.ts
114
41
  var dockerExec = (args) => Effect.async((resume) => {
115
42
  execFile(
116
43
  "docker",
@@ -221,137 +148,6 @@ var removeContainer = (containerName) => Effect.gen(function* () {
221
148
  var removeImage = (imageName) => Effect.gen(function* () {
222
149
  yield* dockerExec(["rmi", imageName]);
223
150
  });
224
-
225
- // src/boundedTail.ts
226
- var MAX_TAIL_CHARS = 64 * 1024;
227
- var OutputByteCounter = class {
228
- constructor(maxBytes) {
229
- this.maxBytes = maxBytes;
230
- }
231
- totalBytes = 0;
232
- limitExceeded = false;
233
- add(chunk) {
234
- this.totalBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
235
- if (this.totalBytes > this.maxBytes) this.limitExceeded = true;
236
- }
237
- get exceeded() {
238
- return this.limitExceeded;
239
- }
240
- };
241
- var BoundedTail = class {
242
- items = [];
243
- totalChars = 0;
244
- maxChars;
245
- separator;
246
- /**
247
- * @param maxChars Maximum length of the joined tail. Defaults to {@link MAX_TAIL_CHARS}.
248
- * @param separator String placed between items by {@link toString}. Must match
249
- * how the caller would otherwise have joined the accumulated chunks (e.g.
250
- * `"\n"` for line streams, `""` for raw chunk streams).
251
- */
252
- constructor(maxChars = MAX_TAIL_CHARS, separator = "") {
253
- this.maxChars = maxChars;
254
- this.separator = separator;
255
- }
256
- /** Append one item to the tail, evicting oldest items to stay within budget. */
257
- push(item) {
258
- const bounded = item.length > this.maxChars ? item.slice(item.length - this.maxChars) : item;
259
- this.totalChars += bounded.length + (this.items.length > 0 ? this.separator.length : 0);
260
- this.items.push(bounded);
261
- while (this.totalChars > this.maxChars && this.items.length > 1) {
262
- const dropped = this.items.shift();
263
- this.totalChars -= dropped.length + this.separator.length;
264
- }
265
- }
266
- /** Join the retained tail into a single string (length ≤ `maxChars`). */
267
- toString() {
268
- return this.items.join(this.separator);
269
- }
270
- };
271
- var StreamedProcessOutput = class {
272
- maxOutputBytes;
273
- byteCounter;
274
- stdoutTail;
275
- stderrTail;
276
- onLine;
277
- outputLimitError;
278
- constructor(options) {
279
- this.maxOutputBytes = options.maxOutputBytes;
280
- this.byteCounter = options.maxOutputBytes === void 0 ? void 0 : new OutputByteCounter(options.maxOutputBytes);
281
- const tailChars = options.maxOutputBytes ?? options.maxOutputTailChars ?? MAX_TAIL_CHARS;
282
- this.stdoutTail = new BoundedTail(tailChars, "\n");
283
- this.stderrTail = new BoundedTail(tailChars, "");
284
- this.onLine = options.onLine ?? (() => {
285
- });
286
- }
287
- checkLimit(chunk) {
288
- if (this.byteCounter === void 0 || this.outputLimitError !== void 0) {
289
- return this.outputLimitError;
290
- }
291
- this.byteCounter.add(chunk);
292
- if (this.byteCounter.exceeded) {
293
- this.outputLimitError = new Error(
294
- `Sandbox command output exceeded ${this.maxOutputBytes} bytes`
295
- );
296
- }
297
- return this.outputLimitError;
298
- }
299
- addStdoutLine(line) {
300
- this.stdoutTail.push(line);
301
- this.onLine(line);
302
- }
303
- addStderr(chunk) {
304
- this.stderrTail.push(chunk);
305
- }
306
- get error() {
307
- return this.outputLimitError;
308
- }
309
- result(exitCode) {
310
- return {
311
- stdout: this.stdoutTail.toString(),
312
- stderr: this.stderrTail.toString(),
313
- exitCode
314
- };
315
- }
316
- };
317
- var collectProcessOutput = (process2, options, resolve3, reject) => {
318
- if (options.onLine || options.maxOutputBytes !== void 0) {
319
- const output = new StreamedProcessOutput(options);
320
- const checkLimit = (chunk) => {
321
- if (output.checkLimit(chunk) !== void 0) process2.kill();
322
- };
323
- process2.stdout.on("data", checkLimit);
324
- process2.stderr.on("data", checkLimit);
325
- const lines = createInterface({ input: process2.stdout });
326
- lines.on("line", (line) => output.addStdoutLine(line));
327
- process2.stderr.on("data", (chunk) => {
328
- output.addStderr(chunk.toString());
329
- });
330
- process2.onClose((code) => {
331
- if (output.error !== void 0) {
332
- reject(output.error);
333
- return;
334
- }
335
- resolve3(output.result(code ?? 0));
336
- });
337
- return;
338
- }
339
- const stdoutChunks = [];
340
- const stderrChunks = [];
341
- process2.stdout.on("data", (chunk) => {
342
- stdoutChunks.push(chunk.toString());
343
- });
344
- process2.stderr.on("data", (chunk) => {
345
- stderrChunks.push(chunk.toString());
346
- });
347
- process2.onClose((code) => {
348
- resolve3({
349
- stdout: stdoutChunks.join(""),
350
- stderr: stderrChunks.join(""),
351
- exitCode: code ?? 0
352
- });
353
- });
354
- };
355
151
  var execFileAsync = promisify(execFile);
356
152
  var RUNNER_INSTALL_METADATA = ".shipyard-install.json";
357
153
  var RUNNER_CONTROLLER_LOCK = ".shipyard-controller.lock";
@@ -420,7 +216,7 @@ var defaultAdapters = {
420
216
  }
421
217
  },
422
218
  signalProcess: (pid, signal) => process.kill(pid, signal),
423
- pause: (milliseconds) => new Promise((resolve3) => setTimeout(resolve3, milliseconds))
219
+ pause: (milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds))
424
220
  };
425
221
  var lockOwnsProcess = async (lock, adapters) => lock !== void 0 && Number.isInteger(lock.pid) && typeof lock.processStartedAt === "string" && lock.processStartedAt.length > 0 && adapters.isProcessRunning(lock.pid) && await adapters.processIdentity(lock.pid) === lock.processStartedAt;
426
222
  var lifecycleFailure = (purpose, error) => error instanceof RunnerLifecycleError ? error : new RunnerLifecycleError(
@@ -478,8 +274,12 @@ var listRemoteRunners = async (repository, repoDir, environment, adapters) => {
478
274
  return parseRemoteRunners(response.stdout);
479
275
  };
480
276
  var validateExistingRepositoryRunner = async (options, adapters = defaultAdapters) => {
277
+ if (!await adapters.exists(options.maskDir)) {
278
+ await adapters.makeDirectory(options.maskDir);
279
+ }
481
280
  await assertProtectedDirectoryIdentities(
482
281
  options.runnerDir,
282
+ options.maskDir,
483
283
  adapters.inspectDirectory
484
284
  ).catch((error) => {
485
285
  throw lifecycleFailure("Validating protected runner directories", error);
@@ -579,8 +379,12 @@ var recoverRepositoryRunner = async (options, adapters = defaultAdapters) => {
579
379
  if (lock) await adapters.remove(lockPath);
580
380
  const workDir = join(options.runnerDir, RUNNER_WORK_DIR);
581
381
  if (await adapters.exists(workDir)) await adapters.remove(workDir);
382
+ if (await adapters.exists(options.maskDir))
383
+ await adapters.remove(options.maskDir);
384
+ await adapters.makeDirectory(options.maskDir);
582
385
  await assertProtectedDirectoryIdentities(
583
386
  options.runnerDir,
387
+ options.maskDir,
584
388
  adapters.inspectDirectory
585
389
  ).catch((error) => {
586
390
  throw lifecycleFailure("Validating protected runner directories", error);
@@ -732,6 +536,7 @@ var unregisterRepositoryRunner = async (metadata, repoDir, runnerDir, environmen
732
536
  var removeRepositoryRunner = async (options, adapters = defaultAdapters) => {
733
537
  const configDir = join(options.repoDir, CONFIG_DIR);
734
538
  const runnerDir = join(configDir, RUNNER_DIR);
539
+ const maskDir = join(configDir, RUNNER_SANDBOX_MASK_DIR);
735
540
  if (!await adapters.exists(runnerDir)) {
736
541
  throw new RunnerLifecycleError(
737
542
  `No repository runner is installed at ${runnerDir}.`
@@ -743,6 +548,7 @@ var removeRepositoryRunner = async (options, adapters = defaultAdapters) => {
743
548
  } catch (error) {
744
549
  if (!options.force) throw error;
745
550
  await adapters.remove(runnerDir);
551
+ if (await adapters.exists(maskDir)) await adapters.remove(maskDir);
746
552
  return {
747
553
  removed: true,
748
554
  forced: true,
@@ -772,12 +578,14 @@ var removeRepositoryRunner = async (options, adapters = defaultAdapters) => {
772
578
  );
773
579
  }
774
580
  await adapters.remove(runnerDir);
581
+ if (await adapters.exists(maskDir)) await adapters.remove(maskDir);
775
582
  return {
776
583
  removed: true,
777
584
  forced: true,
778
585
  manualCleanup: `Remove runner ${metadata.name} from ${metadata.repository} in GitHub Settings > Actions > Runners.`
779
586
  };
780
587
  }
588
+ if (await adapters.exists(maskDir)) await adapters.remove(maskDir);
781
589
  await adapters.remove(runnerDir);
782
590
  return { removed: true, forced: false };
783
591
  };
@@ -834,7 +642,7 @@ var docker = (options) => {
834
642
  )
835
643
  );
836
644
  for (const dir of parentDirsToCreate) {
837
- await new Promise((resolve3, reject) => {
645
+ await new Promise((resolve2, reject) => {
838
646
  execFile(
839
647
  "docker",
840
648
  [
@@ -857,7 +665,7 @@ var docker = (options) => {
857
665
  )
858
666
  );
859
667
  } else {
860
- resolve3();
668
+ resolve2();
861
669
  }
862
670
  }
863
671
  );
@@ -880,7 +688,7 @@ var docker = (options) => {
880
688
  if (opts?.stdin !== void 0) args.push("-i");
881
689
  if (opts?.cwd) args.push("-w", opts.cwd);
882
690
  args.push(containerName, "sh", "-c", effectiveCommand);
883
- return new Promise((resolve3, reject) => {
691
+ return new Promise((resolve2, reject) => {
884
692
  const proc = spawn("docker", args, {
885
693
  signal: opts?.signal,
886
694
  stdio: [
@@ -904,13 +712,13 @@ var docker = (options) => {
904
712
  onClose: (listener) => proc.on("close", listener)
905
713
  },
906
714
  { ...opts, maxOutputTailChars },
907
- resolve3,
715
+ resolve2,
908
716
  reject
909
717
  );
910
718
  });
911
719
  },
912
720
  interactiveExec: (args, opts) => {
913
- return new Promise((resolve3, reject) => {
721
+ return new Promise((resolve2, reject) => {
914
722
  opts.signal?.throwIfAborted();
915
723
  const executionId = randomUUID();
916
724
  const pidFile = `/tmp/shipyard-interactive-${executionId}.pid`;
@@ -942,7 +750,7 @@ var docker = (options) => {
942
750
  settled = true;
943
751
  opts.signal?.removeEventListener("abort", onAbort);
944
752
  if ("error" in result) reject(result.error);
945
- else resolve3(result);
753
+ else resolve2(result);
946
754
  };
947
755
  const onAbort = () => {
948
756
  if (cancelling || settled) return;
@@ -1005,7 +813,7 @@ var docker = (options) => {
1005
813
  hostPath,
1006
814
  sandboxPath
1007
815
  ),
1008
- copyFileOut: (sandboxPath, hostPath) => new Promise((resolve3, reject) => {
816
+ copyFileOut: (sandboxPath, hostPath) => new Promise((resolve2, reject) => {
1009
817
  execFile(
1010
818
  "docker",
1011
819
  ["cp", `${containerName}:${sandboxPath}`, hostPath],
@@ -1013,7 +821,7 @@ var docker = (options) => {
1013
821
  if (error) {
1014
822
  reject(new Error(`docker cp (out) failed: ${error.message}`));
1015
823
  } else {
1016
- resolve3();
824
+ resolve2();
1017
825
  }
1018
826
  }
1019
827
  );
@@ -1027,7 +835,7 @@ var docker = (options) => {
1027
835
  }
1028
836
  });
1029
837
  };
1030
- var checkImageUid = (imageName, expectedUid) => new Promise((resolve3, reject) => {
838
+ var checkImageUid = (imageName, expectedUid) => new Promise((resolve2, reject) => {
1031
839
  execFile(
1032
840
  "docker",
1033
841
  ["image", "inspect", imageName, "--format", "{{.Config.User}}"],
@@ -1042,13 +850,13 @@ var checkImageUid = (imageName, expectedUid) => new Promise((resolve3, reject) =
1042
850
  }
1043
851
  const imageUser = (stdout ?? "").toString().trim();
1044
852
  if (!imageUser) {
1045
- resolve3();
853
+ resolve2();
1046
854
  return;
1047
855
  }
1048
856
  const uidPart = imageUser.split(":")[0];
1049
857
  const imageUid = parseInt(uidPart, 10);
1050
858
  if (isNaN(imageUid)) {
1051
- resolve3();
859
+ resolve2();
1052
860
  return;
1053
861
  }
1054
862
  if (imageUid !== expectedUid) {
@@ -1058,12 +866,12 @@ var checkImageUid = (imageName, expectedUid) => new Promise((resolve3, reject) =
1058
866
  )
1059
867
  );
1060
868
  } else {
1061
- resolve3();
869
+ resolve2();
1062
870
  }
1063
871
  }
1064
872
  );
1065
873
  });
1066
874
 
1067
- export { REPOSITORY_RUNNER_OWNER_ENV, RUNNER_CONTROLLER_LOCK, RUNNER_INSTALL_METADATA, RUNNER_WORK_DIR, RunnerLifecycleError, buildImage, defaultImageName, docker, recoverRepositoryRunner, removeImage, removeOwnedRepositoryRunnerContainers, removeRepositoryRunner, validateExistingRepositoryRunner };
1068
- //# sourceMappingURL=chunk-44I2BL6E.js.map
1069
- //# sourceMappingURL=chunk-44I2BL6E.js.map
875
+ export { REPOSITORY_RUNNER_OWNER_ENV, RUNNER_CONTROLLER_LOCK, RUNNER_INSTALL_METADATA, RUNNER_WORK_DIR, RunnerLifecycleError, buildImage, docker, recoverRepositoryRunner, removeImage, removeOwnedRepositoryRunnerContainers, removeRepositoryRunner, validateExistingRepositoryRunner };
876
+ //# sourceMappingURL=chunk-WAXEMZUV.js.map
877
+ //# sourceMappingURL=chunk-WAXEMZUV.js.map