@replayio/app-building 1.1.0 → 1.3.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
@@ -33,11 +33,12 @@ const config: ContainerConfig = {
33
33
  flyApp: envVars.FLY_APP_NAME,
34
34
  };
35
35
 
36
- // Start a remote container
36
+ // Start a remote container (detached — will exit after processing all work)
37
+ config.detached = true;
37
38
  const repo: RepoOptions = { repoUrl: "https://github.com/...", cloneBranch: "main", pushBranch: "main" };
38
39
  const state = await startRemoteContainer(config, repo);
39
40
 
40
- // Send a prompt and wait for completion
41
+ // Send a prompt — the container will process it and exit when done
41
42
  const httpOpts = httpOptsFor(state);
42
43
  const { id } = await httpPost(`${state.baseUrl}/message`, { prompt: "Build the app" }, httpOpts);
43
44
 
@@ -57,7 +58,7 @@ await stopRemoteContainer(config, state);
57
58
 
58
59
  | Export | Description |
59
60
  |---|---|
60
- | `ContainerConfig` | Interface bundling all external state: optional `projectRoot` (only needed for local Docker operations), `envVars`, `registry`, optional `flyToken`/`flyApp`/`imageRef`/`webhookUrl`. See [Webhooks](#webhooks) below. |
61
+ | `ContainerConfig` | Interface bundling all external state: optional `projectRoot` (only needed for local Docker operations), `envVars`, `registry`, optional `flyToken`/`flyApp`/`imageRef`/`webhookUrl`/`detached`. See [Webhooks](#webhooks) and [Container lifecycle](#container-lifecycle) below. |
61
62
  | `RepoOptions` | Per-invocation git settings: `repoUrl`, `cloneBranch`, `pushBranch`. |
62
63
  | `ContainerRegistry` | Interface for container registry storage. Methods: `log`, `markStopped`, `clearStopped`, `getRecent`, `find`, `findAlive`. |
63
64
  | `FileContainerRegistry` | Built-in file-backed implementation of `ContainerRegistry`, backed by a `.jsonl` file. |
@@ -123,9 +124,41 @@ await stopRemoteContainer(config, state);
123
124
  |---|---|
124
125
  | `getImageRef()` | Returns `CONTAINER_IMAGE_REF` env var, or `ghcr.io/replayio/app-building:latest` by default. Used by `startRemoteContainer`. |
125
126
 
127
+ ## Container HTTP API
128
+
129
+ Each container runs an HTTP server that accepts the following requests:
130
+
131
+ | Method | Path | Description |
132
+ |---|---|---|
133
+ | `POST /message` | `{ prompt: string }` | Queue a message for processing. Returns `{ id }`. |
134
+ | `GET /message/:id` | | Poll message status. Returns `{ id, status, result, error }`. |
135
+ | `POST /detach` | | Signal the container to exit once all queued messages and tasks are done. |
136
+ | `POST /stop` | | Force-stop the container immediately. Interrupts any running work, commits remaining changes, then exits. |
137
+ | `POST /interrupt` | | Kill the currently running Claude process without stopping the container. |
138
+ | `GET /status` | | Container state, queue depth, iteration count, cost, revision, etc. |
139
+ | `GET /events?offset=N` | | Stream of Claude events (JSON lines) since offset. |
140
+ | `GET /logs?offset=N` | | Stream of log lines since offset. |
141
+
142
+ ### Container lifecycle
143
+
144
+ A container stays running and accepts messages until it receives a **detach** or **stop** signal:
145
+
146
+ - **Detached at startup** (`config.detached = true`): Set `detached` on `ContainerConfig` to start
147
+ the container in detached mode. The container processes its initial message and any queued tasks,
148
+ then exits cleanly. This is the preferred way to run fire-and-forget jobs — no race between
149
+ container startup and a subsequent `POST /detach`.
150
+ - **Detach** (`POST /detach`): Signal a running container to exit once all in-flight and queued
151
+ work is done. In the CLI, interactive mode (`npm run agent -- -i`) sends `/detach` automatically
152
+ when the user disconnects (Ctrl+C/D).
153
+ - **Stop** (`POST /stop`): The container exits immediately, interrupting any running Claude
154
+ process. It commits any remaining work before shutting down. This is the forced shutdown path.
155
+
156
+ Without either signal, the container waits indefinitely for new messages — this is intentional
157
+ so that interactive users can send follow-up messages at any time.
158
+
126
159
  ## Webhooks
127
160
 
128
- Set `webhookUrl` on `ContainerConfig` to receive real-time notifications of container activity. The container POSTs fire-and-forget JSON to that URL on key events (no retries, failures are silently ignored).
161
+ Set `webhookUrl` on `ContainerConfig` to receive real-time notifications of container activity. The container POSTs JSON to that URL on key events (no retries; failures are logged to stderr). If `WEBHOOK_SECRET` is set in the environment, the container sends it as a `Bearer` token in the `Authorization` header.
129
162
 
130
163
  ### Payload format
131
164
 
@@ -15,6 +15,8 @@ export interface ContainerConfig {
15
15
  flyApp?: string;
16
16
  imageRef?: string;
17
17
  webhookUrl?: string;
18
+ /** Start the container in detached mode. It will exit after processing all messages and tasks. */
19
+ detached?: boolean;
18
20
  }
19
21
  export interface RepoOptions {
20
22
  repoUrl: string;
package/dist/container.js CHANGED
@@ -100,6 +100,8 @@ export async function startContainer(config, repo) {
100
100
  };
101
101
  if (config.webhookUrl)
102
102
  extra.WEBHOOK_URL = config.webhookUrl;
103
+ if (config.detached)
104
+ extra.DETACHED = "1";
103
105
  const containerEnv = buildContainerEnv(repo, config.envVars, extra);
104
106
  // Build docker run args
105
107
  const args = ["run", "-d", "--rm", "--name", containerName];
@@ -174,10 +176,9 @@ export async function startRemoteContainer(config, repo) {
174
176
  };
175
177
  if (config.webhookUrl)
176
178
  remoteExtra.WEBHOOK_URL = config.webhookUrl;
179
+ if (config.detached)
180
+ remoteExtra.DETACHED = "1";
177
181
  const containerEnv = buildContainerEnv(repo, config.envVars, remoteExtra);
178
- // Remove Fly-specific vars from container env (not needed inside)
179
- delete containerEnv.FLY_API_TOKEN;
180
- delete containerEnv.FLY_APP_NAME;
181
182
  // Log existing machines (but don't destroy — multiple containers may run concurrently)
182
183
  const existing = await listMachines(config.flyApp, config.flyToken);
183
184
  if (existing.length > 0) {
package/dist/fly.js CHANGED
@@ -63,11 +63,12 @@ export async function createMachine(app, token, image, env, name) {
63
63
  config: {
64
64
  image,
65
65
  env,
66
+ auto_destroy: true,
66
67
  restart: { policy: "no" },
67
68
  guest: {
68
- cpu_kind: "shared",
69
- cpus: 4,
70
- memory_mb: 4096,
69
+ cpu_kind: "performance",
70
+ cpus: 16,
71
+ memory_mb: 32768,
71
72
  },
72
73
  services: [
73
74
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio/app-building",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Library for managing agentic app-building containers",
5
5
  "type": "module",
6
6
  "exports": {