@replayio/app-building 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,12 +108,15 @@ await stopRemoteContainer(config, state);
108
108
  | Export | Description |
109
109
  |---|---|
110
110
  | `createApp(token, name, org?)` | Create a Fly app and allocate IPs. |
111
- | `createMachine(app, token, image, env, name)` | Create a Fly machine. Returns machine ID. |
111
+ | `createMachine(app, token, image, env, name, volumeId?)` | Create a Fly machine. If `volumeId` is provided, mounts it at `/repo`. Returns machine ID. |
112
112
  | `waitForMachine(app, token, machineId, timeout?)` | Wait for a machine to reach `started` state. |
113
113
  | `destroyMachine(app, token, machineId)` | Force-destroy a machine. |
114
114
  | `listMachines(app, token)` | List all machines for an app. |
115
+ | `createVolume(app, token, name, sizeGb?)` | Create a Fly volume (default 50 GB). Returns volume ID. |
116
+ | `deleteVolume(app, token, volumeId)` | Delete a Fly volume. |
117
+ | `listVolumes(app, token)` | List all volumes for an app. |
115
118
 
116
- **Types:** `FlyMachineInfo`
119
+ **Types:** `FlyMachineInfo`, `FlyVolumeInfo`
117
120
 
118
121
  ### Image ref
119
122
 
@@ -190,8 +193,8 @@ Every POST body has this shape:
190
193
  | `message.started` | Message processing begins | `iteration`, `prompt` |
191
194
  | `message.done` | Message processing complete | `messageId`, `cost_usd`, `duration_ms`, `num_turns` |
192
195
  | `message.error` | Message processing failed | `messageId`, `error` |
193
- | `task.started` | Task processing begins | `iteration`, `pendingTasks` |
194
- | `task.done` | Task processing complete | `tasksProcessed`, `totalCost` |
196
+ | `task.started` | Task processing begins | `iteration`, `skill`, `subtasks` |
197
+ | `task.done` | Task processing complete | `skill`, `cost`, `totalCost`, `failed` |
195
198
  | `log` | Each log line | `line` |
196
199
 
197
200
  ### Example
@@ -6,6 +6,7 @@ export interface AgentState {
6
6
  baseUrl: string;
7
7
  flyApp?: string;
8
8
  flyMachineId?: string;
9
+ flyVolumeId?: string;
9
10
  }
10
11
  export interface ContainerConfig {
11
12
  projectRoot?: string;
package/dist/container.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { execFileSync, spawn } from "child_process";
2
2
  import { readFileSync, existsSync } from "fs";
3
3
  import { resolve } from "path";
4
- import { createMachine, waitForMachine, destroyMachine, listMachines } from "./fly";
4
+ import { createMachine, waitForMachine, destroyMachine, listMachines, createVolume, deleteVolume } from "./fly";
5
5
  import { getImageRef } from "./image-ref";
6
6
  const IMAGE_NAME = "app-building";
7
7
  function debugLog(...args) {
@@ -215,12 +215,16 @@ export async function startRemoteContainer(config, repo) {
215
215
  console.log(` ${m.id} (${m.name}) — ${m.state}`);
216
216
  }
217
217
  }
218
+ // Create a volume for /repo storage
219
+ console.log("Creating Fly volume...");
220
+ const volumeId = await createVolume(config.flyApp, config.flyToken, `repo-${uniqueId}`);
221
+ console.log(`Volume created: ${volumeId}`);
218
222
  // Retry machine creation — the registry tag may take a moment to propagate
219
223
  console.log("Creating Fly machine...");
220
224
  let machineId = "";
221
225
  for (let attempt = 0; attempt < 5; attempt++) {
222
226
  try {
223
- machineId = await createMachine(config.flyApp, config.flyToken, imageRef, containerEnv, machineName);
227
+ machineId = await createMachine(config.flyApp, config.flyToken, imageRef, containerEnv, machineName, volumeId);
224
228
  break;
225
229
  }
226
230
  catch (err) {
@@ -230,6 +234,8 @@ export async function startRemoteContainer(config, repo) {
230
234
  await new Promise((r) => setTimeout(r, 5000));
231
235
  continue;
232
236
  }
237
+ // Clean up volume if machine creation fails
238
+ await deleteVolume(config.flyApp, config.flyToken, volumeId).catch(() => { });
233
239
  throw err;
234
240
  }
235
241
  }
@@ -243,6 +249,7 @@ export async function startRemoteContainer(config, repo) {
243
249
  baseUrl,
244
250
  flyApp: config.flyApp,
245
251
  flyMachineId: machineId,
252
+ flyVolumeId: volumeId,
246
253
  };
247
254
  config.registry.log(agentState);
248
255
  console.log("Waiting for machine to start...");
@@ -269,9 +276,10 @@ export async function startRemoteContainer(config, repo) {
269
276
  await new Promise((r) => setTimeout(r, interval));
270
277
  }
271
278
  if (!ready) {
272
- // Clean up machine if we can't reach it
279
+ // Clean up machine and volume if we can't reach it
273
280
  console.log("Timed out waiting for machine, destroying...");
274
281
  await destroyMachine(config.flyApp, config.flyToken, machineId).catch(() => { });
282
+ await deleteVolume(config.flyApp, config.flyToken, volumeId).catch(() => { });
275
283
  throw new Error("Remote container did not become ready within timeout");
276
284
  }
277
285
  return agentState;
@@ -285,6 +293,13 @@ export async function stopRemoteContainer(config, state) {
285
293
  console.log(`Destroying Fly machine ${state.flyMachineId}...`);
286
294
  await destroyMachine(state.flyApp, config.flyToken, state.flyMachineId);
287
295
  console.log("Machine destroyed.");
296
+ if (state.flyVolumeId) {
297
+ console.log(`Deleting Fly volume ${state.flyVolumeId}...`);
298
+ await deleteVolume(state.flyApp, config.flyToken, state.flyVolumeId).catch((err) => {
299
+ console.log(`Warning: failed to delete volume: ${err instanceof Error ? err.message : err}`);
300
+ });
301
+ console.log("Volume deleted.");
302
+ }
288
303
  config.registry.markStopped(state.containerName);
289
304
  }
290
305
  export function stopContainer(config, containerName) {
package/dist/fly.d.ts CHANGED
@@ -2,11 +2,21 @@
2
2
  * Create a Fly app via the Machines API and allocate IPs so .fly.dev DNS works.
3
3
  */
4
4
  export declare function createApp(token: string, name: string, org?: string): Promise<void>;
5
+ /**
6
+ * Create a Fly Volume in the app's primary region.
7
+ * Returns the volume ID.
8
+ */
9
+ export declare function createVolume(app: string, token: string, name: string, sizeGb?: number): Promise<string>;
10
+ /**
11
+ * Delete a Fly Volume.
12
+ */
13
+ export declare function deleteVolume(app: string, token: string, volumeId: string): Promise<void>;
5
14
  /**
6
15
  * Create a Fly Machine with the given image and env vars.
16
+ * If volumeId is provided, it is mounted at /repo.
7
17
  * Returns the machine ID.
8
18
  */
9
- export declare function createMachine(app: string, token: string, image: string, env: Record<string, string>, name: string): Promise<string>;
19
+ export declare function createMachine(app: string, token: string, image: string, env: Record<string, string>, name: string, volumeId?: string): Promise<string>;
10
20
  /**
11
21
  * Wait for a Fly Machine to reach the "started" state.
12
22
  */
@@ -26,3 +36,16 @@ export interface FlyMachineInfo {
26
36
  * List all machines for a Fly app.
27
37
  */
28
38
  export declare function listMachines(app: string, token: string): Promise<FlyMachineInfo[]>;
39
+ export interface FlyVolumeInfo {
40
+ id: string;
41
+ name: string;
42
+ state: string;
43
+ size_gb: number;
44
+ region: string;
45
+ created_at: string;
46
+ attached_machine_id: string | null;
47
+ }
48
+ /**
49
+ * List all volumes for a Fly app.
50
+ */
51
+ export declare function listVolumes(app: string, token: string): Promise<FlyVolumeInfo[]>;
package/dist/fly.js CHANGED
@@ -52,39 +52,66 @@ export async function createApp(token, name, org) {
52
52
  await gqlFetch(allocateMutation, { input: { appId: name, type: "v6" } });
53
53
  }
54
54
  /**
55
- * Create a Fly Machine with the given image and env vars.
56
- * Returns the machine ID.
55
+ * Create a Fly Volume in the app's primary region.
56
+ * Returns the volume ID.
57
57
  */
58
- export async function createMachine(app, token, image, env, name) {
59
- const res = await flyFetch(`/apps/${app}/machines`, token, {
58
+ export async function createVolume(app, token, name, sizeGb = 50) {
59
+ const res = await flyFetch(`/apps/${app}/volumes`, token, {
60
60
  method: "POST",
61
61
  body: JSON.stringify({
62
62
  name,
63
- config: {
64
- image,
65
- env,
66
- auto_destroy: true,
67
- restart: { policy: "no" },
68
- guest: {
69
- cpu_kind: "performance",
70
- cpus: 16,
71
- memory_mb: 32768,
72
- },
73
- services: [
74
- {
75
- ports: [{ port: 443, handlers: ["tls", "http"] }],
76
- protocol: "tcp",
77
- internal_port: 3000,
78
- autostart: false,
79
- autostop: "off",
80
- },
81
- ],
82
- },
63
+ size_gb: sizeGb,
64
+ encrypted: true,
65
+ require_unique_zone: false,
83
66
  }),
84
67
  });
85
68
  const data = (await res.json());
86
69
  return data.id;
87
70
  }
71
+ /**
72
+ * Delete a Fly Volume.
73
+ */
74
+ export async function deleteVolume(app, token, volumeId) {
75
+ await flyFetch(`/apps/${app}/volumes/${volumeId}`, token, {
76
+ method: "DELETE",
77
+ });
78
+ }
79
+ /**
80
+ * Create a Fly Machine with the given image and env vars.
81
+ * If volumeId is provided, it is mounted at /repo.
82
+ * Returns the machine ID.
83
+ */
84
+ export async function createMachine(app, token, image, env, name, volumeId) {
85
+ const config = {
86
+ image,
87
+ env,
88
+ auto_destroy: true,
89
+ restart: { policy: "no" },
90
+ guest: {
91
+ cpu_kind: "performance",
92
+ cpus: 16,
93
+ memory_mb: 32768,
94
+ },
95
+ services: [
96
+ {
97
+ ports: [{ port: 443, handlers: ["tls", "http"] }],
98
+ protocol: "tcp",
99
+ internal_port: 3000,
100
+ autostart: false,
101
+ autostop: "off",
102
+ },
103
+ ],
104
+ };
105
+ if (volumeId) {
106
+ config.mounts = [{ volume: volumeId, path: "/repo" }];
107
+ }
108
+ const res = await flyFetch(`/apps/${app}/machines`, token, {
109
+ method: "POST",
110
+ body: JSON.stringify({ name, config }),
111
+ });
112
+ const data = (await res.json());
113
+ return data.id;
114
+ }
88
115
  /**
89
116
  * Wait for a Fly Machine to reach the "started" state.
90
117
  */
@@ -117,3 +144,10 @@ export async function listMachines(app, token) {
117
144
  const res = await flyFetch(`/apps/${app}/machines`, token);
118
145
  return (await res.json());
119
146
  }
147
+ /**
148
+ * List all volumes for a Fly app.
149
+ */
150
+ export async function listVolumes(app, token) {
151
+ const res = await flyFetch(`/apps/${app}/volumes`, token);
152
+ return (await res.json());
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio/app-building",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Library for managing agentic app-building containers",
5
5
  "type": "module",
6
6
  "exports": {