@replayio/app-building 1.0.3 → 1.1.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 +58 -7
- package/dist/container-registry.d.ts +9 -1
- package/dist/container-registry.js +1 -1
- package/dist/container.d.ts +2 -1
- package/dist/container.js +14 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,10 +13,9 @@ npm install @replayio/app-building
|
|
|
13
13
|
```ts
|
|
14
14
|
import {
|
|
15
15
|
loadDotEnv,
|
|
16
|
-
|
|
16
|
+
FileContainerRegistry,
|
|
17
17
|
type ContainerConfig,
|
|
18
18
|
type RepoOptions,
|
|
19
|
-
startContainer,
|
|
20
19
|
startRemoteContainer,
|
|
21
20
|
stopRemoteContainer,
|
|
22
21
|
httpGet,
|
|
@@ -27,9 +26,9 @@ import {
|
|
|
27
26
|
// Assemble config once at startup
|
|
28
27
|
const envVars = loadDotEnv("/path/to/project");
|
|
29
28
|
const config: ContainerConfig = {
|
|
30
|
-
projectRoot: "/path/to/project",
|
|
29
|
+
projectRoot: "/path/to/project", // optional — only needed for local Docker operations
|
|
31
30
|
envVars,
|
|
32
|
-
registry: new
|
|
31
|
+
registry: new FileContainerRegistry("/path/to/.container-registry.jsonl"),
|
|
33
32
|
flyToken: envVars.FLY_API_TOKEN,
|
|
34
33
|
flyApp: envVars.FLY_APP_NAME,
|
|
35
34
|
};
|
|
@@ -58,9 +57,10 @@ await stopRemoteContainer(config, state);
|
|
|
58
57
|
|
|
59
58
|
| Export | Description |
|
|
60
59
|
|---|---|
|
|
61
|
-
| `ContainerConfig` | Interface bundling all external state: `projectRoot
|
|
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. |
|
|
62
61
|
| `RepoOptions` | Per-invocation git settings: `repoUrl`, `cloneBranch`, `pushBranch`. |
|
|
63
|
-
| `ContainerRegistry` |
|
|
62
|
+
| `ContainerRegistry` | Interface for container registry storage. Methods: `log`, `markStopped`, `clearStopped`, `getRecent`, `find`, `findAlive`. |
|
|
63
|
+
| `FileContainerRegistry` | Built-in file-backed implementation of `ContainerRegistry`, backed by a `.jsonl` file. |
|
|
64
64
|
|
|
65
65
|
### Container lifecycle
|
|
66
66
|
|
|
@@ -76,7 +76,7 @@ await stopRemoteContainer(config, state);
|
|
|
76
76
|
|
|
77
77
|
**Types:** `AgentState`, `ContainerConfig`, `RepoOptions`
|
|
78
78
|
|
|
79
|
-
### Container registry (`ContainerRegistry` class)
|
|
79
|
+
### Container registry (`ContainerRegistry` interface / `FileContainerRegistry` class)
|
|
80
80
|
|
|
81
81
|
| Method | Description |
|
|
82
82
|
|---|---|
|
|
@@ -122,3 +122,54 @@ await stopRemoteContainer(config, state);
|
|
|
122
122
|
| Export | Description |
|
|
123
123
|
|---|---|
|
|
124
124
|
| `getImageRef()` | Returns `CONTAINER_IMAGE_REF` env var, or `ghcr.io/replayio/app-building:latest` by default. Used by `startRemoteContainer`. |
|
|
125
|
+
|
|
126
|
+
## Webhooks
|
|
127
|
+
|
|
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).
|
|
129
|
+
|
|
130
|
+
### Payload format
|
|
131
|
+
|
|
132
|
+
Every POST body has this shape:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"type": "container.started",
|
|
137
|
+
"containerName": "app-building-abc123",
|
|
138
|
+
"timestamp": "2026-02-28T12:00:00.000Z",
|
|
139
|
+
"data": { ... }
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Field | Type | Description |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `type` | `string` | Event type (see table below). |
|
|
146
|
+
| `containerName` | `string` | Name of the container that emitted the event. |
|
|
147
|
+
| `timestamp` | `string` | ISO-8601 timestamp. |
|
|
148
|
+
| `data` | `object` | Event-specific payload. Present on all events; contents vary by type. |
|
|
149
|
+
|
|
150
|
+
### Events
|
|
151
|
+
|
|
152
|
+
| Type | When | `data` fields |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `container.started` | HTTP server is listening | `pushBranch`, `revision` |
|
|
155
|
+
| `container.idle` | State transitions to idle | `pendingTasks`, `queueLength` |
|
|
156
|
+
| `container.processing` | State transitions to processing | `iteration` |
|
|
157
|
+
| `container.stopping` | State transitions to stopping | _(empty)_ |
|
|
158
|
+
| `container.stopped` | State transitions to stopped | _(empty)_ |
|
|
159
|
+
| `message.queued` | `POST /message` received | `messageId`, `prompt` |
|
|
160
|
+
| `message.done` | Message processing complete | `messageId`, `cost_usd`, `duration_ms`, `num_turns` |
|
|
161
|
+
| `message.error` | Message processing failed | `messageId`, `error` |
|
|
162
|
+
| `task.started` | Task processing begins | `pendingTasks` |
|
|
163
|
+
| `task.done` | Task processing complete | `tasksProcessed`, `totalCost` |
|
|
164
|
+
| `log` | Each log line | `line` |
|
|
165
|
+
|
|
166
|
+
### Example
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const config: ContainerConfig = {
|
|
170
|
+
projectRoot: "/path/to/project",
|
|
171
|
+
envVars: loadDotEnv("/path/to/project"),
|
|
172
|
+
registry: new FileContainerRegistry("/path/to/.container-registry.jsonl"),
|
|
173
|
+
webhookUrl: "https://example.com/hooks/container-events",
|
|
174
|
+
};
|
|
175
|
+
```
|
|
@@ -3,7 +3,15 @@ export interface RegistryEntry extends AgentState {
|
|
|
3
3
|
startedAt: string;
|
|
4
4
|
stoppedAt?: string;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
6
|
+
export interface ContainerRegistry {
|
|
7
|
+
log(state: AgentState): void;
|
|
8
|
+
markStopped(containerName?: string): void;
|
|
9
|
+
clearStopped(containerName: string): void;
|
|
10
|
+
getRecent(limit?: number): RegistryEntry[];
|
|
11
|
+
find(containerName: string): RegistryEntry | null;
|
|
12
|
+
findAlive(): Promise<RegistryEntry[]>;
|
|
13
|
+
}
|
|
14
|
+
export declare class FileContainerRegistry implements ContainerRegistry {
|
|
7
15
|
private filePath;
|
|
8
16
|
constructor(filePath: string);
|
|
9
17
|
private readRegistry;
|
package/dist/container.d.ts
CHANGED
|
@@ -8,12 +8,13 @@ export interface AgentState {
|
|
|
8
8
|
flyMachineId?: string;
|
|
9
9
|
}
|
|
10
10
|
export interface ContainerConfig {
|
|
11
|
-
projectRoot
|
|
11
|
+
projectRoot?: string;
|
|
12
12
|
envVars: Record<string, string>;
|
|
13
13
|
registry: ContainerRegistry;
|
|
14
14
|
flyToken?: string;
|
|
15
15
|
flyApp?: string;
|
|
16
16
|
imageRef?: string;
|
|
17
|
+
webhookUrl?: string;
|
|
17
18
|
}
|
|
18
19
|
export interface RepoOptions {
|
|
19
20
|
repoUrl: string;
|
package/dist/container.js
CHANGED
|
@@ -28,6 +28,8 @@ export function loadDotEnv(projectRoot) {
|
|
|
28
28
|
return vars;
|
|
29
29
|
}
|
|
30
30
|
export function buildImage(config) {
|
|
31
|
+
if (!config.projectRoot)
|
|
32
|
+
throw new Error("projectRoot is required for local Docker operations");
|
|
31
33
|
console.log("Building Docker image...");
|
|
32
34
|
execFileSync("docker", ["build", "--network", "host", "-t", IMAGE_NAME, config.projectRoot], {
|
|
33
35
|
stdio: "inherit",
|
|
@@ -92,10 +94,13 @@ export async function startContainer(config, repo) {
|
|
|
92
94
|
const uniqueId = Math.random().toString(36).slice(2, 8);
|
|
93
95
|
const containerName = `app-building-${uniqueId}`;
|
|
94
96
|
const hostPort = findFreePort();
|
|
95
|
-
const
|
|
97
|
+
const extra = {
|
|
96
98
|
PORT: String(hostPort),
|
|
97
99
|
CONTAINER_NAME: containerName,
|
|
98
|
-
}
|
|
100
|
+
};
|
|
101
|
+
if (config.webhookUrl)
|
|
102
|
+
extra.WEBHOOK_URL = config.webhookUrl;
|
|
103
|
+
const containerEnv = buildContainerEnv(repo, config.envVars, extra);
|
|
99
104
|
// Build docker run args
|
|
100
105
|
const args = ["run", "-d", "--rm", "--name", containerName];
|
|
101
106
|
// --network host: container shares host network stack (no -p needed)
|
|
@@ -163,10 +168,13 @@ export async function startRemoteContainer(config, repo) {
|
|
|
163
168
|
const uniqueId = Math.random().toString(36).slice(2, 8);
|
|
164
169
|
const machineName = `app-building-${uniqueId}`;
|
|
165
170
|
// Build env vars for the machine
|
|
166
|
-
const
|
|
171
|
+
const remoteExtra = {
|
|
167
172
|
PORT: "3000",
|
|
168
173
|
CONTAINER_NAME: machineName,
|
|
169
|
-
}
|
|
174
|
+
};
|
|
175
|
+
if (config.webhookUrl)
|
|
176
|
+
remoteExtra.WEBHOOK_URL = config.webhookUrl;
|
|
177
|
+
const containerEnv = buildContainerEnv(repo, config.envVars, remoteExtra);
|
|
170
178
|
// Remove Fly-specific vars from container env (not needed inside)
|
|
171
179
|
delete containerEnv.FLY_API_TOKEN;
|
|
172
180
|
delete containerEnv.FLY_APP_NAME;
|
|
@@ -260,6 +268,8 @@ export function stopContainer(config, containerName) {
|
|
|
260
268
|
config.registry.markStopped(containerName);
|
|
261
269
|
}
|
|
262
270
|
export function spawnTestContainer(config) {
|
|
271
|
+
if (!config.projectRoot)
|
|
272
|
+
throw new Error("projectRoot is required for local Docker operations");
|
|
263
273
|
ensureImageExists(config.projectRoot);
|
|
264
274
|
const uniqueId = Math.random().toString(36).slice(2, 8);
|
|
265
275
|
const containerName = `app-building-test-${uniqueId}`;
|