@replayio/app-building 1.24.1 → 1.26.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 +43 -3
- package/dist/index.d.ts +188 -6
- package/dist/index.js +799 -6
- package/package.json +6 -2
- package/dist/container-registry.d.ts +0 -25
- package/dist/container-registry.js +0 -78
- package/dist/container-utils.d.ts +0 -5
- package/dist/container-utils.js +0 -30
- package/dist/container.d.ts +0 -54
- package/dist/container.js +0 -357
- package/dist/fly.d.ts +0 -55
- package/dist/fly.js +0 -199
- package/dist/http-client.d.ts +0 -6
- package/dist/http-client.js +0 -30
- package/dist/image-ref.d.ts +0 -1
- package/dist/image-ref.js +0 -4
- package/dist/secrets.d.ts +0 -50
- package/dist/secrets.js +0 -209
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ The agent can also run `list-secrets` to see which secrets are available, and `s
|
|
|
80
80
|
|
|
81
81
|
| Export | Description |
|
|
82
82
|
|---|---|
|
|
83
|
-
| `ContainerConfig` | `infisical` (required `InfisicalConfig`), optional `projectRoot` (local Docker only), `registry`, `flyToken`/`flyApp` (set both for remote Fly.io), `imageRef`, `webhookUrl`/`webhookSecret`, `taskWebhookUrl` (GET endpoint for external task queue), `detached`, `initialPrompt`, `localPort`, `absorbTasks`, `namePrefix` (default: `"app-building"`). |
|
|
83
|
+
| `ContainerConfig` | `infisical` (required `InfisicalConfig`), optional `projectRoot` (local Docker only), `registry`, `flyToken`/`flyApp` (set both for remote Fly.io), `imageRef`, `webhookUrl`/`webhookSecret`, `taskWebhookUrl` (GET endpoint for external task queue), `addTaskWebhookUrl` (POST endpoint for tasks added by `add-task` script), `detached`, `initialPrompt`, `localPort`, `absorbTasks`, `namePrefix` (default: `"app-building"`). |
|
|
84
84
|
| `RepoOptions` | Per-invocation git settings: `repoUrl`, `cloneBranch`, `pushBranch`. |
|
|
85
85
|
| `AgentState` | Returned by `startContainer`. Contains `type`, `containerName`, `port`, `baseUrl`, and Fly-specific fields for remote containers. |
|
|
86
86
|
| `ContainerRegistry` | Interface for container registry storage. Methods: `log`, `markStopped`, `clearStopped`, `getRecent`, `find`, `findAlive`. |
|
|
@@ -144,6 +144,13 @@ The agent can also run `list-secrets` to see which secrets are available, and `s
|
|
|
144
144
|
|---|---|
|
|
145
145
|
| `getImageRef()` | Returns `CONTAINER_IMAGE_REF` env var, or `ghcr.io/replayio/app-building:latest` by default. |
|
|
146
146
|
|
|
147
|
+
### Task dependencies
|
|
148
|
+
|
|
149
|
+
| Export | Description |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `findReadyTask(pendingTasks, completedTasks)` | Find the first task in `pendingTasks` whose `requiredTaskIds` are all satisfied. A required task is satisfied when it appears in `completedTasks` and has no children still pending or with incomplete children of their own (recursive). Returns the task, or `null` if all are blocked. |
|
|
152
|
+
| `Task` | Interface for task queue entries — includes `skill`, `subtasks`, `timestamp`, and optional fields like `id`, `requiredTaskIds`, `parentTaskId`, `command`, etc. |
|
|
153
|
+
|
|
147
154
|
## Container HTTP API
|
|
148
155
|
|
|
149
156
|
Each container runs an HTTP server that accepts the following requests:
|
|
@@ -196,6 +203,32 @@ Each task in the queue (local or webhook) has the following fields:
|
|
|
196
203
|
| `command` | `string` | No | Custom command to run instead of the default `claude` CLI. |
|
|
197
204
|
| `maxAttempts` | `number` | No | Maximum attempts before giving up. Default: 5. |
|
|
198
205
|
| `timeoutMinutes` | `number` | No | Per-attempt time limit in minutes. Agent is killed if exceeded. |
|
|
206
|
+
| `setup` | `string` | No | Shell command to run once when the task starts (before the first agent attempt). Task fails if it exits non-zero. |
|
|
207
|
+
| `requiredTaskIds` | `string[]` | No | Task IDs that must complete before this task can start. A task is "complete" when it and all its child tasks have finished. |
|
|
208
|
+
| `parentTaskId` | `string` | No | ID of the task that spawned this task (set automatically by `add-task`). Used for dependency resolution — a required task isn't considered complete until all its children are done. |
|
|
209
|
+
|
|
210
|
+
### Task dependencies
|
|
211
|
+
|
|
212
|
+
Tasks can declare dependencies on other tasks via `requiredTaskIds`. The worker skips blocked
|
|
213
|
+
tasks and picks the first task whose dependencies are all satisfied. A dependency is satisfied
|
|
214
|
+
when the required task has completed **and** all of its child tasks (tasks with matching
|
|
215
|
+
`parentTaskId`) have also completed. This means a task that depends on a parent will
|
|
216
|
+
automatically wait for all work the parent spawned.
|
|
217
|
+
|
|
218
|
+
By default, `add-task` chains tasks serially — each task depends on the previous one. Use
|
|
219
|
+
`--parallel` to add tasks that can run in any order:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Serial (default): B waits for A, C waits for B
|
|
223
|
+
npx tsx /repo/scripts/add-task.ts <<'EOF'
|
|
224
|
+
[{ "skill": "...", "subtasks": ["A"] }, { "skill": "...", "subtasks": ["B"] }, { "skill": "...", "subtasks": ["C"] }]
|
|
225
|
+
EOF
|
|
226
|
+
|
|
227
|
+
# Parallel: A, B, C can run in any order
|
|
228
|
+
npx tsx /repo/scripts/add-task.ts --parallel <<'EOF'
|
|
229
|
+
[{ "skill": "...", "subtasks": ["A"] }, { "skill": "...", "subtasks": ["B"] }, { "skill": "...", "subtasks": ["C"] }]
|
|
230
|
+
EOF
|
|
231
|
+
```
|
|
199
232
|
|
|
200
233
|
### External task queue
|
|
201
234
|
|
|
@@ -209,6 +242,13 @@ local task queue is empty, the container GETs this URL (with the same `Bearer` t
|
|
|
209
242
|
The first task is processed immediately; any remaining tasks are added to the local queue.
|
|
210
243
|
Tasks use the same structure documented above.
|
|
211
244
|
|
|
245
|
+
### Add-task webhook
|
|
246
|
+
|
|
247
|
+
Set `config.addTaskWebhookUrl` to a POST endpoint that receives tasks added by the `add-task`
|
|
248
|
+
script. When set, the agent's `add-task` calls POST tasks to this URL instead of writing to
|
|
249
|
+
the local task file. The request body is `{ "tasks": [...] }` with the same task structure
|
|
250
|
+
documented above. Auth uses the same `Bearer` token from `webhookSecret`.
|
|
251
|
+
|
|
212
252
|
## Webhooks
|
|
213
253
|
|
|
214
254
|
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). Set `webhookSecret` to include a `Bearer` token in the `Authorization` header for authenticating webhook requests.
|
|
@@ -245,8 +285,8 @@ Every POST body has this shape:
|
|
|
245
285
|
| `message.started` | Message processing begins | `iteration`, `prompt` |
|
|
246
286
|
| `message.done` | Message processing complete | `messageId`, `cost_usd`, `duration_ms`, `num_turns` |
|
|
247
287
|
| `message.error` | Message processing failed | `messageId`, `error` |
|
|
248
|
-
| `task.started` | Task processing begins | `
|
|
249
|
-
| `task.done` | Task processing complete |
|
|
288
|
+
| `task.started` | Task processing begins | All `Task` fields (`id`, `skill`, `subtasks`, `timestamp`, `app`, `prompt`, `command`, `maxAttempts`, `timeoutMinutes`, `setup`, `requiredTaskIds`, `parentTaskId`) + `iteration` |
|
|
289
|
+
| `task.done` | Task processing complete | All `Task` fields + `cost`, `totalCost`, `failed`, `pendingTasks`, `duration_ms` |
|
|
250
290
|
| `log` | Each log line | `line` |
|
|
251
291
|
|
|
252
292
|
### Example
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,188 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
interface RegistryEntry extends AgentState {
|
|
2
|
+
startedAt: string;
|
|
3
|
+
stoppedAt?: string;
|
|
4
|
+
}
|
|
5
|
+
interface ContainerRegistry {
|
|
6
|
+
log(state: AgentState): void;
|
|
7
|
+
markStopped(containerName?: string): void;
|
|
8
|
+
clearStopped(containerName: string): void;
|
|
9
|
+
getRecent(limit?: number): RegistryEntry[];
|
|
10
|
+
find(containerName: string): RegistryEntry | null;
|
|
11
|
+
findAlive(): Promise<RegistryEntry[]>;
|
|
12
|
+
}
|
|
13
|
+
declare class FileContainerRegistry implements ContainerRegistry {
|
|
14
|
+
private filePath;
|
|
15
|
+
constructor(filePath: string);
|
|
16
|
+
private readRegistry;
|
|
17
|
+
private updateEntry;
|
|
18
|
+
log(state: AgentState): void;
|
|
19
|
+
markStopped(containerName?: string): void;
|
|
20
|
+
clearStopped(containerName: string): void;
|
|
21
|
+
getRecent(limit?: number): RegistryEntry[];
|
|
22
|
+
find(containerName: string): RegistryEntry | null;
|
|
23
|
+
findAlive(): Promise<RegistryEntry[]>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Infisical secrets API.
|
|
28
|
+
*
|
|
29
|
+
* API versions used (per https://infisical.com/docs/api-reference):
|
|
30
|
+
* - Auth: POST /api/v1/auth/universal-auth/login
|
|
31
|
+
* - Secrets: GET/POST/PATCH /api/v4/secrets[/{secretName}]
|
|
32
|
+
* - Folders: POST /api/v2/folders
|
|
33
|
+
*/
|
|
34
|
+
interface InfisicalConfig {
|
|
35
|
+
token: string;
|
|
36
|
+
projectId: string;
|
|
37
|
+
environment: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Log in to Infisical using Universal Auth (Client ID + Client Secret).
|
|
41
|
+
* POST /api/v1/auth/universal-auth/login
|
|
42
|
+
* Returns a short-lived access token.
|
|
43
|
+
*/
|
|
44
|
+
declare function infisicalLogin(clientId: string, clientSecret: string): Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Fetch secrets from an Infisical folder path.
|
|
47
|
+
* GET /api/v4/secrets?projectId=…&environment=…&secretPath=…
|
|
48
|
+
* Returns a key→value record.
|
|
49
|
+
*/
|
|
50
|
+
declare function fetchInfisicalSecrets(config: InfisicalConfig, secretPath: string): Promise<Record<string, string>>;
|
|
51
|
+
/**
|
|
52
|
+
* Fetch global build secrets from `/global/`.
|
|
53
|
+
*/
|
|
54
|
+
declare function fetchGlobalSecrets(config: InfisicalConfig): Promise<Record<string, string>>;
|
|
55
|
+
/**
|
|
56
|
+
* Fetch per-branch deployment secrets from `/branches/<branch>/`.
|
|
57
|
+
*/
|
|
58
|
+
declare function fetchBranchSecrets(config: InfisicalConfig, branch: string): Promise<Record<string, string>>;
|
|
59
|
+
/**
|
|
60
|
+
* Create or update a branch secret in Infisical.
|
|
61
|
+
* Creates the folder path if it doesn't exist yet.
|
|
62
|
+
*
|
|
63
|
+
* POST /api/v4/secrets/{name} — create
|
|
64
|
+
* PATCH /api/v4/secrets/{name} — update (if secret already exists)
|
|
65
|
+
*
|
|
66
|
+
* Body: { projectId, environment, secretPath, secretValue, type }
|
|
67
|
+
*/
|
|
68
|
+
declare function createBranchSecret(config: InfisicalConfig, branch: string, name: string, value: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Extract Infisical config from environment variables and log in.
|
|
71
|
+
* Reads INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_PROJECT_ID,
|
|
72
|
+
* and INFISICAL_ENVIRONMENT from the env vars.
|
|
73
|
+
* Throws if any required var is missing.
|
|
74
|
+
*/
|
|
75
|
+
declare function getInfisicalConfig(envVars: Record<string, string>): Promise<InfisicalConfig>;
|
|
76
|
+
|
|
77
|
+
interface AgentState {
|
|
78
|
+
type: "local" | "remote";
|
|
79
|
+
containerName: string;
|
|
80
|
+
port: number;
|
|
81
|
+
baseUrl: string;
|
|
82
|
+
flyApp?: string;
|
|
83
|
+
flyMachineId?: string;
|
|
84
|
+
flyVolumeId?: string;
|
|
85
|
+
}
|
|
86
|
+
interface ContainerConfig {
|
|
87
|
+
projectRoot?: string;
|
|
88
|
+
/** Infisical credentials — required for all containers. */
|
|
89
|
+
infisical: InfisicalConfig;
|
|
90
|
+
registry: ContainerRegistry;
|
|
91
|
+
flyToken?: string;
|
|
92
|
+
flyApp?: string;
|
|
93
|
+
imageRef?: string;
|
|
94
|
+
webhookUrl?: string;
|
|
95
|
+
webhookSecret?: string;
|
|
96
|
+
/** GET endpoint to fetch the next task when the local queue is empty. Uses webhookSecret for auth. */
|
|
97
|
+
taskWebhookUrl?: string;
|
|
98
|
+
/** POST endpoint to receive tasks added by the add-task script. When set, add-task POSTs tasks here instead of writing to the local task file. Uses webhookSecret for auth. */
|
|
99
|
+
addTaskWebhookUrl?: string;
|
|
100
|
+
/** Start the container in detached mode. It will exit after processing all messages and tasks. */
|
|
101
|
+
detached?: boolean;
|
|
102
|
+
/** Initial prompt to queue at container startup (before the HTTP server accepts external requests). */
|
|
103
|
+
initialPrompt?: string;
|
|
104
|
+
/** Override the host port for local containers (default: auto-selected). */
|
|
105
|
+
localPort?: number;
|
|
106
|
+
/** Absorb task files from other containers at startup. Default: false. */
|
|
107
|
+
absorbTasks?: boolean;
|
|
108
|
+
/** Container name prefix. Default: "app-building". */
|
|
109
|
+
namePrefix?: string;
|
|
110
|
+
/** Agent command override (e.g. "codex", "gemini"). Default: "claude". */
|
|
111
|
+
agentCommand?: string;
|
|
112
|
+
}
|
|
113
|
+
interface RepoOptions {
|
|
114
|
+
repoUrl: string;
|
|
115
|
+
cloneBranch: string;
|
|
116
|
+
pushBranch: string;
|
|
117
|
+
}
|
|
118
|
+
declare function loadDotEnv(projectRoot: string): Record<string, string>;
|
|
119
|
+
declare function buildImage(config: ContainerConfig): void;
|
|
120
|
+
/**
|
|
121
|
+
* Start a container (local Docker or remote Fly.io based on config).
|
|
122
|
+
* If flyToken and flyApp are set, starts remotely; otherwise locally.
|
|
123
|
+
*/
|
|
124
|
+
declare function startContainer(config: ContainerConfig, repo: RepoOptions): Promise<AgentState>;
|
|
125
|
+
/**
|
|
126
|
+
* Stop a container by its state or registry entry.
|
|
127
|
+
*/
|
|
128
|
+
declare function stopContainer(config: ContainerConfig, state: AgentState | RegistryEntry): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Spawn an interactive test container (local only).
|
|
131
|
+
*/
|
|
132
|
+
declare function spawnTestContainer(config: ContainerConfig): Promise<void>;
|
|
133
|
+
|
|
134
|
+
interface HttpOptions {
|
|
135
|
+
timeout?: number;
|
|
136
|
+
headers?: Record<string, string>;
|
|
137
|
+
}
|
|
138
|
+
declare function httpGet(url: string, opts?: HttpOptions): Promise<any>;
|
|
139
|
+
declare function httpPost(url: string, body?: unknown, opts?: HttpOptions): Promise<any>;
|
|
140
|
+
|
|
141
|
+
declare function httpOptsFor(state: AgentState): HttpOptions;
|
|
142
|
+
declare function probeAlive(entry: RegistryEntry): Promise<boolean>;
|
|
143
|
+
|
|
144
|
+
declare function getImageRef(): string;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Task dependency resolution helpers.
|
|
148
|
+
*
|
|
149
|
+
* Pure functions that take task arrays and return ready tasks — no file I/O.
|
|
150
|
+
*/
|
|
151
|
+
interface Task {
|
|
152
|
+
skill: string;
|
|
153
|
+
subtasks: string[];
|
|
154
|
+
timestamp: string;
|
|
155
|
+
app?: string;
|
|
156
|
+
/** Optional ID for coordination (e.g. from task webhook). Included in task.started/task.done events. */
|
|
157
|
+
id?: string;
|
|
158
|
+
/** Raw prompt for message-derived tasks (no skill file). */
|
|
159
|
+
prompt?: string;
|
|
160
|
+
/** Custom command (agent + args) to run instead of the default "claude" with extraArgs. */
|
|
161
|
+
command?: string;
|
|
162
|
+
/** Maximum number of attempts before giving up. Default: 5. */
|
|
163
|
+
maxAttempts?: number;
|
|
164
|
+
/** Maximum time in minutes for each attempt. Agent is killed if exceeded. */
|
|
165
|
+
timeoutMinutes?: number;
|
|
166
|
+
/** Shell command to run once when the task starts (before the first agent attempt). */
|
|
167
|
+
setup?: string;
|
|
168
|
+
/** Task IDs that must complete before this task can start. */
|
|
169
|
+
requiredTaskIds?: string[];
|
|
170
|
+
/** The ID of the task that spawned this task via add-task. */
|
|
171
|
+
parentTaskId?: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find the first task in `pendingTasks` whose dependencies are all satisfied.
|
|
175
|
+
* Returns the task, or null if all tasks are blocked.
|
|
176
|
+
*
|
|
177
|
+
* A required task is "fully complete" when:
|
|
178
|
+
* 1. It appears in `completedTasks`, AND
|
|
179
|
+
* 2. Every task in `completedTasks` that has it as `parentTaskId` is also
|
|
180
|
+
* fully complete (recursive — children's children must also be done), AND
|
|
181
|
+
* 3. No task in `pendingTasks` has it as `parentTaskId`.
|
|
182
|
+
*
|
|
183
|
+
* @param pendingTasks - Tasks waiting to run (ordered by priority).
|
|
184
|
+
* @param completedTasks - Tasks that have finished (need id and parentTaskId).
|
|
185
|
+
*/
|
|
186
|
+
declare function findReadyTask(pendingTasks: Task[], completedTasks: Pick<Task, "id" | "parentTaskId">[]): Task | null;
|
|
187
|
+
|
|
188
|
+
export { type AgentState, type ContainerConfig, type ContainerRegistry, FileContainerRegistry, type HttpOptions, type InfisicalConfig, type RegistryEntry, type RepoOptions, type Task, buildImage, createBranchSecret, fetchBranchSecrets, fetchGlobalSecrets, fetchInfisicalSecrets, findReadyTask, getImageRef, getInfisicalConfig, httpGet, httpOptsFor, httpPost, infisicalLogin, loadDotEnv, probeAlive, spawnTestContainer, startContainer, stopContainer };
|