@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
@@ -5,59 +5,9 @@ import { existsSync } from "node:fs";
5
5
  import * as shipyard from "@snappedly-tools/shipyard";
6
6
  import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
7
7
  import { z } from "zod";
8
- import { resolvePlannerBranch } from "./planner-branch.mjs";
9
8
 
10
9
  if (process.loadEnvFile && existsSync(".shipyard/.env"))
11
10
  process.loadEnvFile(".shipyard/.env");
12
- type ModelRole = "routine" | "strong";
13
- const CODEX_PROVIDER = true;
14
- const agentFactory = shipyard.codex;
15
- type AgentModel = Parameters<typeof agentFactory>[0];
16
- const readRoleModel = (role: ModelRole): string | undefined => {
17
- const envName = `SHIPYARD_${role.toUpperCase()}_MODEL`;
18
- const model = process.env[envName];
19
- if (model !== undefined && model.trim().length === 0)
20
- throw new Error(`${envName} must not be empty`);
21
- return model;
22
- };
23
- const roleModels = {
24
- routine: readRoleModel("routine"),
25
- strong: readRoleModel("strong"),
26
- };
27
- const CODEX_REASONING_EFFORTS = shipyard.CODEX_REASONING_EFFORTS;
28
- type CodexReasoningEffort = shipyard.CodexReasoningEffort;
29
- const readCodexReasoningEffort = (
30
- role: ModelRole,
31
- ): CodexReasoningEffort | undefined => {
32
- if (!CODEX_PROVIDER) return undefined;
33
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
34
- const effort = process.env[envName]?.trim();
35
- if (!effort) return undefined;
36
- if (!(CODEX_REASONING_EFFORTS as readonly string[]).includes(effort))
37
- throw new Error(
38
- `${envName} must be one of ${CODEX_REASONING_EFFORTS.join(", ")}; received "${effort}"`,
39
- );
40
- return effort as CodexReasoningEffort;
41
- };
42
- const roleEfforts = {
43
- routine: readCodexReasoningEffort("routine"),
44
- strong: readCodexReasoningEffort("strong"),
45
- };
46
- const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
47
- if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
48
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`;
49
- const model = process.env[envName]?.trim();
50
- return model ? { ...defaultModel, model } : defaultModel;
51
- };
52
- const roleAgent = (role: ModelRole, defaultModel: AgentModel) => {
53
- const model = roleModels[role] ?? readCodexRoleModel(role, defaultModel);
54
- const effort = roleEfforts[role];
55
- if (typeof model !== "string")
56
- return effort === undefined
57
- ? agentFactory(model)
58
- : agentFactory(model, { effort });
59
- return agentFactory(model, { effort: effort ?? null });
60
- };
61
11
  const targetBranch = execFileSync("git", ["branch", "--show-current"], {
62
12
  encoding: "utf8",
63
13
  }).trim();
@@ -174,7 +124,7 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
174
124
  await sandbox.run({
175
125
  name: `triage #${ticket.id}`,
176
126
  maxIterations: 1,
177
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
127
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
178
128
  promptFile: "./.shipyard/triage-prompt.md",
179
129
  promptArgs: { TASK_ID: ticket.id },
180
130
  });
@@ -182,7 +132,7 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
182
132
  const implementation = await sandbox.run({
183
133
  name: "implementer",
184
134
  maxIterations: 100,
185
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
135
+ agent: shipyard.codex(shipyard.CODEX_MODELS.routine),
186
136
  promptFile: "./.shipyard/implement-prompt.md",
187
137
  promptArgs: {
188
138
  TASK_ID: ticket.id,
@@ -195,7 +145,7 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
195
145
  const review = await sandbox.run({
196
146
  name: "reviewer",
197
147
  maxIterations: 1,
198
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
148
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
199
149
  promptFile: "./.shipyard/review-prompt.md",
200
150
  promptArgs: {
201
151
  TASK_ID: ticket.id,
@@ -216,21 +166,13 @@ for (let iteration = 0; iteration < 10; iteration++) {
216
166
  execFileSync("node", [".shipyard/select-issues.mjs"], { encoding: "utf8" }),
217
167
  ) as Scope[];
218
168
  if (!scopes.length) break;
219
- const localBranches = execFileSync(
220
- "git",
221
- ["for-each-ref", "--format=%(refname:short)", "refs/heads"],
222
- { encoding: "utf8" },
223
- )
224
- .split(/\r?\n/)
225
- .filter(Boolean);
226
- const plannerBranch = await resolvePlannerBranch(localBranches);
227
169
  const plan = await shipyard.run({
228
170
  hooks,
229
171
  sandbox: docker(),
230
172
  name: "planner",
231
- branchStrategy: { type: "branch", branch: plannerBranch },
173
+ branchStrategy: { type: "branch", branch: "shipyard/planner" },
232
174
  maxIterations: 1,
233
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
175
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
234
176
  promptFile: "./.shipyard/plan-prompt.md",
235
177
  output: shipyard.Output.object({ tag: "plan", schema: planSchema }),
236
178
  });
@@ -358,7 +300,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
358
300
  const resolution = await wave.run({
359
301
  name: "conflict-resolver",
360
302
  maxIterations: 10,
361
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
303
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
362
304
  promptFile: "./.shipyard/conflict-prompt.md",
363
305
  promptArgs: {
364
306
  TASK_ID: ready[index]!.id,
@@ -412,7 +354,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
412
354
  const integrated = await wave.run({
413
355
  name: "spec-integrator",
414
356
  maxIterations: 10,
415
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
357
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
416
358
  promptFile: "./.shipyard/spec-wave-prompt.md",
417
359
  promptArgs: {
418
360
  TASK_ID: id,
@@ -452,7 +394,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
452
394
  await integration.run({
453
395
  name: `triage #${id}`,
454
396
  maxIterations: 1,
455
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
397
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
456
398
  promptFile: "./.shipyard/triage-prompt.md",
457
399
  promptArgs: { TASK_ID: id },
458
400
  });
@@ -460,7 +402,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
460
402
  const standalone = await integration.run({
461
403
  name: "implementer",
462
404
  maxIterations: 100,
463
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
405
+ agent: shipyard.codex(shipyard.CODEX_MODELS.routine),
464
406
  promptFile: "./.shipyard/implement-prompt.md",
465
407
  promptArgs: {
466
408
  TASK_ID: id,
@@ -474,7 +416,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
474
416
  const review = await integration.run({
475
417
  name: "reviewer",
476
418
  maxIterations: 1,
477
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
419
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
478
420
  promptFile: "./.shipyard/review-prompt.md",
479
421
  promptArgs: {
480
422
  TASK_ID: id,
@@ -487,7 +429,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
487
429
  const final = await integration.run({
488
430
  name: "merger",
489
431
  maxIterations: 1,
490
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
432
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
491
433
  promptFile: "./.shipyard/merge-prompt.md",
492
434
  promptArgs: {
493
435
  TASK_ID: id,
@@ -503,7 +445,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
503
445
  const finalReview = await integration.run({
504
446
  name: "reviewer",
505
447
  maxIterations: 1,
506
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
448
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
507
449
  promptFile: "./.shipyard/review-prompt.md",
508
450
  promptArgs: {
509
451
  TASK_ID: id,
@@ -7,55 +7,6 @@ import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
7
7
 
8
8
  if (process.loadEnvFile && existsSync(".shipyard/.env"))
9
9
  process.loadEnvFile(".shipyard/.env");
10
- type ModelRole = "routine" | "strong";
11
- const CODEX_PROVIDER = true;
12
- const agentFactory = shipyard.codex;
13
- type AgentModel = Parameters<typeof agentFactory>[0];
14
- const readRoleModel = (role: ModelRole): string | undefined => {
15
- const envName = `SHIPYARD_${role.toUpperCase()}_MODEL`;
16
- const model = process.env[envName];
17
- if (model !== undefined && model.trim().length === 0)
18
- throw new Error(`${envName} must not be empty`);
19
- return model;
20
- };
21
- const roleModels = {
22
- routine: readRoleModel("routine"),
23
- strong: readRoleModel("strong"),
24
- };
25
- const CODEX_REASONING_EFFORTS = shipyard.CODEX_REASONING_EFFORTS;
26
- type CodexReasoningEffort = shipyard.CodexReasoningEffort;
27
- const readCodexReasoningEffort = (
28
- role: ModelRole,
29
- ): CodexReasoningEffort | undefined => {
30
- if (!CODEX_PROVIDER) return undefined;
31
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
32
- const effort = process.env[envName]?.trim();
33
- if (!effort) return undefined;
34
- if (!(CODEX_REASONING_EFFORTS as readonly string[]).includes(effort))
35
- throw new Error(
36
- `${envName} must be one of ${CODEX_REASONING_EFFORTS.join(", ")}; received "${effort}"`,
37
- );
38
- return effort as CodexReasoningEffort;
39
- };
40
- const roleEfforts = {
41
- routine: readCodexReasoningEffort("routine"),
42
- strong: readCodexReasoningEffort("strong"),
43
- };
44
- const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
45
- if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
46
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`;
47
- const model = process.env[envName]?.trim();
48
- return model ? { ...defaultModel, model } : defaultModel;
49
- };
50
- const roleAgent = (role: ModelRole, defaultModel: AgentModel) => {
51
- const model = roleModels[role] ?? readCodexRoleModel(role, defaultModel);
52
- const effort = roleEfforts[role];
53
- if (typeof model !== "string")
54
- return effort === undefined
55
- ? agentFactory(model)
56
- : agentFactory(model, { effort });
57
- return agentFactory(model, { effort: effort ?? null });
58
- };
59
10
  const targetBranch = execFileSync("git", ["branch", "--show-current"], {
60
11
  encoding: "utf8",
61
12
  }).trim();
@@ -185,7 +136,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
185
136
  : [issue.id]) {
186
137
  await sandbox.run({
187
138
  name: `triage #${ticketId}`,
188
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
139
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
189
140
  maxIterations: 1,
190
141
  promptFile: "./.shipyard/triage-prompt.md",
191
142
  promptArgs: { TASK_ID: ticketId },
@@ -195,7 +146,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
195
146
  const implement = await sandbox.run({
196
147
  name: "implementer",
197
148
  maxIterations: 1,
198
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
149
+ agent: shipyard.codex(shipyard.CODEX_MODELS.routine),
199
150
  promptFile: "./.shipyard/implement-prompt.md",
200
151
  promptArgs: {
201
152
  TASK_ID: issue.id,
@@ -214,7 +165,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
214
165
  const review = await sandbox.run({
215
166
  name: "reviewer",
216
167
  maxIterations: 1,
217
- agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
168
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
218
169
  promptFile: "./.shipyard/review-prompt.md",
219
170
  promptArgs: {
220
171
  BRANCH: issue.branch,
@@ -7,55 +7,6 @@ import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
7
7
 
8
8
  if (process.loadEnvFile && existsSync(".shipyard/.env"))
9
9
  process.loadEnvFile(".shipyard/.env");
10
- type ModelRole = "routine" | "strong";
11
- const CODEX_PROVIDER = true;
12
- const agentFactory = shipyard.codex;
13
- type AgentModel = Parameters<typeof agentFactory>[0];
14
- const readRoleModel = (role: ModelRole): string | undefined => {
15
- const envName = `SHIPYARD_${role.toUpperCase()}_MODEL`;
16
- const model = process.env[envName];
17
- if (model !== undefined && model.trim().length === 0)
18
- throw new Error(`${envName} must not be empty`);
19
- return model;
20
- };
21
- const roleModels = {
22
- routine: readRoleModel("routine"),
23
- strong: readRoleModel("strong"),
24
- };
25
- const CODEX_REASONING_EFFORTS = shipyard.CODEX_REASONING_EFFORTS;
26
- type CodexReasoningEffort = shipyard.CodexReasoningEffort;
27
- const readCodexReasoningEffort = (
28
- role: ModelRole,
29
- ): CodexReasoningEffort | undefined => {
30
- if (!CODEX_PROVIDER) return undefined;
31
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
32
- const effort = process.env[envName]?.trim();
33
- if (!effort) return undefined;
34
- if (!(CODEX_REASONING_EFFORTS as readonly string[]).includes(effort))
35
- throw new Error(
36
- `${envName} must be one of ${CODEX_REASONING_EFFORTS.join(", ")}; received "${effort}"`,
37
- );
38
- return effort as CodexReasoningEffort;
39
- };
40
- const roleEfforts = {
41
- routine: readCodexReasoningEffort("routine"),
42
- strong: readCodexReasoningEffort("strong"),
43
- };
44
- const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
45
- if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
46
- const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`;
47
- const model = process.env[envName]?.trim();
48
- return model ? { ...defaultModel, model } : defaultModel;
49
- };
50
- const roleAgent = (role: ModelRole, defaultModel: AgentModel) => {
51
- const model = roleModels[role] ?? readCodexRoleModel(role, defaultModel);
52
- const effort = roleEfforts[role];
53
- if (typeof model !== "string")
54
- return effort === undefined
55
- ? agentFactory(model)
56
- : agentFactory(model, { effort });
57
- return agentFactory(model, { effort: effort ?? null });
58
- };
59
10
  const targetBranch = execFileSync("git", ["branch", "--show-current"], {
60
11
  encoding: "utf8",
61
12
  }).trim();
@@ -182,7 +133,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
182
133
  : [issue.id]) {
183
134
  await sandbox.run({
184
135
  name: `triage #${ticketId}`,
185
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
136
+ agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
186
137
  maxIterations: 1,
187
138
  promptFile: "./.shipyard/triage-prompt.md",
188
139
  promptArgs: { TASK_ID: ticketId },
@@ -191,7 +142,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
191
142
  }
192
143
  const result = await sandbox.run({
193
144
  name: "implementer",
194
- agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
145
+ agent: shipyard.codex(shipyard.CODEX_MODELS.routine),
195
146
  maxIterations: 1,
196
147
  promptFile: "./.shipyard/prompt.md",
197
148
  promptArgs: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snappedly-tools/shipyard",
3
- "version": "0.7.0-staging.36081473933",
3
+ "version": "0.7.0",
4
4
  "description": "Run AI coding agents in isolated sandboxes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,6 +13,14 @@
13
13
  "./sandboxes/docker": {
14
14
  "import": "./dist/sandboxes/docker.js",
15
15
  "types": "./dist/sandboxes/docker.d.ts"
16
+ },
17
+ "./sandboxes/vercel": {
18
+ "import": "./dist/sandboxes/vercel.js",
19
+ "types": "./dist/sandboxes/vercel.d.ts"
20
+ },
21
+ "./sandboxes/no-sandbox": {
22
+ "import": "./dist/sandboxes/no-sandbox.js",
23
+ "types": "./dist/sandboxes/no-sandbox.d.ts"
16
24
  }
17
25
  },
18
26
  "bin": {
@@ -82,6 +90,14 @@
82
90
  "tsx": "^4.23.13",
83
91
  "zod": "^4.6.5"
84
92
  },
93
+ "peerDependencies": {
94
+ "@vercel/sandbox": ">=1.0.0"
95
+ },
96
+ "peerDependenciesMeta": {
97
+ "@vercel/sandbox": {
98
+ "optional": true
99
+ }
100
+ },
85
101
  "files": [
86
102
  "dist",
87
103
  "LICENSE",
@@ -1,145 +0,0 @@
1
- /** Result of executing a command inside a sandbox. */
2
- interface ExecResult {
3
- /** Collected standard output. */
4
- readonly stdout: string;
5
- /** Collected standard error. */
6
- readonly stderr: string;
7
- /** Process exit status. */
8
- readonly exitCode: number;
9
- }
10
- /** Streams supplied when launching an interactive agent process. */
11
- interface InteractiveExecOptions {
12
- /** Input stream forwarded to the agent process. */
13
- readonly stdin: NodeJS.ReadableStream;
14
- /** Output stream receiving agent standard output. */
15
- readonly stdout: NodeJS.WritableStream;
16
- /** Output stream receiving agent standard error. */
17
- readonly stderr: NodeJS.WritableStream;
18
- /** Working directory inside Docker. */
19
- readonly cwd?: string;
20
- /** Abort signal that terminates the interactive process. */
21
- readonly signal?: AbortSignal;
22
- }
23
- /** Handle to a running isolated sandbox. Docker implements this contract. */
24
- interface IsolatedSandboxHandle {
25
- /** Absolute Git workspace path inside Docker. */
26
- readonly worktreePath: string;
27
- /**
28
- * Execute a command in Docker. Deliver `onLine` as output arrives so idle
29
- * timeouts and live feedback work; buffered delivery does not satisfy this
30
- * contract. Pipe `stdin` to the child rather than passing it as an argument.
31
- */
32
- exec(command: string, options?: {
33
- /** Live output callback. */
34
- onLine?: (line: string) => void;
35
- /** Working directory inside Docker. */
36
- cwd?: string;
37
- /** Execute as root when supported. */
38
- sudo?: boolean;
39
- /** Input piped to the child process. */
40
- stdin?: string;
41
- /** Abort signal that terminates the command. */
42
- signal?: AbortSignal;
43
- /** Maximum combined output bytes before termination. */
44
- maxOutputBytes?: number;
45
- }): Promise<ExecResult>;
46
- /** Launch an interactive process and terminate it when `signal` aborts. */
47
- interactiveExec?(args: string[], options: InteractiveExecOptions): Promise<{
48
- exitCode: number;
49
- }>;
50
- /** Copy a file or directory from the host into Docker. */
51
- copyIn(hostPath: string, sandboxPath: string): Promise<void>;
52
- /** Copy a file from Docker to the host. */
53
- copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;
54
- /** Tear down the container. */
55
- close(): Promise<void>;
56
- }
57
- /** File transfer shape used by agent session storage. */
58
- interface SessionTransferHandle {
59
- /** Absolute Git workspace path inside Docker. */
60
- readonly worktreePath: string;
61
- /** Run a command in the sandbox. */
62
- exec: IsolatedSandboxHandle["exec"];
63
- /** Launch an interactive command when available. */
64
- interactiveExec?: IsolatedSandboxHandle["interactiveExec"];
65
- /** Copy one host file into the sandbox. */
66
- copyFileIn(hostPath: string, sandboxPath: string): Promise<void>;
67
- /** Copy one sandbox file to the host. */
68
- copyFileOut(sandboxPath: string, hostPath: string): Promise<void>;
69
- /** Tear down the sandbox. */
70
- close(): Promise<void>;
71
- }
72
- /** Inputs to Docker sandbox creation. */
73
- interface IsolatedCreateOptions {
74
- /** Original repository path used to derive the image name; never mounted. */
75
- readonly hostRepoPath?: string;
76
- /** Environment injected into the Docker container. */
77
- readonly env: Record<string, string>;
78
- }
79
- /** Configuration for the isolated Docker provider contract. */
80
- interface IsolatedSandboxProviderConfig {
81
- /** Human-readable provider name. */
82
- readonly name: string;
83
- /** Provider environment merged into sandbox startup. */
84
- readonly env?: Record<string, string>;
85
- /** Start a sandbox and return its command and file-transfer handle. */
86
- readonly create: (options: IsolatedCreateOptions) => Promise<IsolatedSandboxHandle>;
87
- }
88
- /** Docker's isolated filesystem provider contract. */
89
- interface IsolatedSandboxProvider {
90
- /** Isolated filesystem discriminator. */
91
- readonly tag: "isolated";
92
- /** Human-readable provider name. */
93
- readonly name: string;
94
- /** Environment variables injected into Docker. */
95
- readonly env: Record<string, string>;
96
- /** Start a sandbox. */
97
- readonly create: (options: IsolatedCreateOptions) => Promise<IsolatedSandboxHandle>;
98
- }
99
- /** Provider accepted by Shipyard's Docker execution path. */
100
- type SandboxProvider = IsolatedSandboxProvider;
101
- /** Create a temporary branch and merge its commits back to host HEAD. */
102
- interface MergeToHeadBranchStrategy {
103
- /** Branch strategy discriminator. */
104
- readonly type: "merge-to-head";
105
- }
106
- /** Run on a caller-named branch. */
107
- interface NamedBranchStrategy {
108
- /** Branch strategy discriminator. */
109
- readonly type: "branch";
110
- /** Branch to create or reuse. */
111
- readonly branch: string;
112
- /** Starting ref for a new branch. */
113
- readonly baseBranch?: string;
114
- }
115
- /** Supported Docker branch strategies. */
116
- type BranchStrategy = MergeToHeadBranchStrategy | NamedBranchStrategy;
117
- /** Construct the isolated provider contract used by Docker. */
118
- declare const createIsolatedSandboxProvider: (config: IsolatedSandboxProviderConfig) => IsolatedSandboxProvider;
119
-
120
- /**
121
- * User-facing mount configuration for Docker.
122
- *
123
- * Each entry describes a host directory to mount into the sandbox container.
124
- */
125
- /** A single bind-mount descriptor for the Docker provider. */
126
- interface MountConfig {
127
- /**
128
- * Path on the host. Supports:
129
- * - Absolute paths (`/data/cache`)
130
- * - Tilde-expanded paths (`~/data` → `<home>/data`)
131
- * - Relative paths (`data` or `./data`) — resolved from `process.cwd()`
132
- */
133
- readonly hostPath: string;
134
- /**
135
- * Path inside the sandbox container. Supports:
136
- * - Absolute paths (`/mnt/data`)
137
- * - Tilde-expanded paths (`~/.npm` → `/home/agent/.npm`) — expanded using the provider's sandbox home directory
138
- * - Relative paths (`data` or `./data`) — resolved from the worktree directory (`/home/agent/workspace`)
139
- */
140
- readonly sandboxPath: string;
141
- /** Mount as read-only. Defaults to `false`. */
142
- readonly readonly?: boolean;
143
- }
144
-
145
- export { type BranchStrategy as B, type ExecResult as E, type IsolatedSandboxProvider as I, type MountConfig as M, type NamedBranchStrategy as N, type SessionTransferHandle as S, type SandboxProvider as a, type MergeToHeadBranchStrategy as b, type InteractiveExecOptions as c, type IsolatedCreateOptions as d, type IsolatedSandboxHandle as e, type IsolatedSandboxProviderConfig as f, createIsolatedSandboxProvider as g };