@replayio/app-building 1.24.0 → 1.25.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 +41 -1
- package/dist/container.d.ts +4 -0
- package/dist/container.js +4 -0
- package/dist/fly.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/tasks.d.ts +24 -0
- package/dist/tasks.js +53 -0
- package/package.json +1 -1
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
|
+
| `TaskInfo` | Interface with `id?`, `requiredTaskIds?`, `parentTaskId?` — the minimum fields needed for dependency resolution. |
|
|
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.
|
package/dist/container.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface ContainerConfig {
|
|
|
21
21
|
webhookSecret?: string;
|
|
22
22
|
/** GET endpoint to fetch the next task when the local queue is empty. Uses webhookSecret for auth. */
|
|
23
23
|
taskWebhookUrl?: string;
|
|
24
|
+
/** 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. */
|
|
25
|
+
addTaskWebhookUrl?: string;
|
|
24
26
|
/** Start the container in detached mode. It will exit after processing all messages and tasks. */
|
|
25
27
|
detached?: boolean;
|
|
26
28
|
/** Initial prompt to queue at container startup (before the HTTP server accepts external requests). */
|
|
@@ -31,6 +33,8 @@ export interface ContainerConfig {
|
|
|
31
33
|
absorbTasks?: boolean;
|
|
32
34
|
/** Container name prefix. Default: "app-building". */
|
|
33
35
|
namePrefix?: string;
|
|
36
|
+
/** Agent command override (e.g. "codex", "gemini"). Default: "claude". */
|
|
37
|
+
agentCommand?: string;
|
|
34
38
|
}
|
|
35
39
|
export interface RepoOptions {
|
|
36
40
|
repoUrl: string;
|
package/dist/container.js
CHANGED
|
@@ -104,6 +104,8 @@ function buildExtraEnv(config, containerName) {
|
|
|
104
104
|
extra.WEBHOOK_URL = config.webhookUrl;
|
|
105
105
|
if (config.taskWebhookUrl)
|
|
106
106
|
extra.TASK_WEBHOOK_URL = config.taskWebhookUrl;
|
|
107
|
+
if (config.addTaskWebhookUrl)
|
|
108
|
+
extra.ADD_TASK_WEBHOOK_URL = config.addTaskWebhookUrl;
|
|
107
109
|
if (config.webhookSecret)
|
|
108
110
|
extra.WEBHOOK_SECRET = config.webhookSecret;
|
|
109
111
|
if (config.detached)
|
|
@@ -112,6 +114,8 @@ function buildExtraEnv(config, containerName) {
|
|
|
112
114
|
extra.INITIAL_PROMPT = config.initialPrompt;
|
|
113
115
|
if (config.absorbTasks)
|
|
114
116
|
extra.ABSORB_TASKS = "1";
|
|
117
|
+
if (config.agentCommand)
|
|
118
|
+
extra.AGENT_COMMAND = config.agentCommand;
|
|
115
119
|
return extra;
|
|
116
120
|
}
|
|
117
121
|
function isRemote(config) {
|
package/dist/fly.js
CHANGED
|
@@ -90,6 +90,7 @@ export async function createMachine(app, token, image, env, name) {
|
|
|
90
90
|
// Delete unattached volumes in parallel with creating the new machine.
|
|
91
91
|
let cleanupDone;
|
|
92
92
|
for (const region of regions) {
|
|
93
|
+
console.log(`Creating machine in region ${region}...`);
|
|
93
94
|
const volumeId = await createVolume(app, token, volumeName, region, 50);
|
|
94
95
|
// Start cleanup on first attempt only
|
|
95
96
|
if (!cleanupDone) {
|
|
@@ -139,10 +140,10 @@ export async function createMachine(app, token, image, env, name) {
|
|
|
139
140
|
console.log(`Insufficient resources in ${region}, trying next region...`);
|
|
140
141
|
continue;
|
|
141
142
|
}
|
|
142
|
-
throw
|
|
143
|
+
throw new Error(`Failed to create machine in region ${region}: ${msg}`);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
|
-
throw new Error(
|
|
146
|
+
throw new Error(`Failed to create machine in any region (tried ${regions.join(", ")}): insufficient resources`);
|
|
146
147
|
}
|
|
147
148
|
/**
|
|
148
149
|
* Wait for a Fly Machine to reach the "started" state.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/tasks.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task dependency resolution helpers.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that take task arrays and return ready tasks — no file I/O.
|
|
5
|
+
*/
|
|
6
|
+
export interface TaskInfo {
|
|
7
|
+
id?: string;
|
|
8
|
+
requiredTaskIds?: string[];
|
|
9
|
+
parentTaskId?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Find the first task in `pendingTasks` whose dependencies are all satisfied.
|
|
13
|
+
* Returns the task, or null if all tasks are blocked.
|
|
14
|
+
*
|
|
15
|
+
* A required task is "fully complete" when:
|
|
16
|
+
* 1. It appears in `completedTasks`, AND
|
|
17
|
+
* 2. Every task in `completedTasks` that has it as `parentTaskId` is also
|
|
18
|
+
* fully complete (recursive — children's children must also be done), AND
|
|
19
|
+
* 3. No task in `pendingTasks` has it as `parentTaskId`.
|
|
20
|
+
*
|
|
21
|
+
* @param pendingTasks - Tasks waiting to run (ordered by priority).
|
|
22
|
+
* @param completedTasks - Tasks that have finished (need id and parentTaskId).
|
|
23
|
+
*/
|
|
24
|
+
export declare function findReadyTask<T extends TaskInfo>(pendingTasks: T[], completedTasks: TaskInfo[]): T | null;
|
package/dist/tasks.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task dependency resolution helpers.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that take task arrays and return ready tasks — no file I/O.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Find the first task in `pendingTasks` whose dependencies are all satisfied.
|
|
8
|
+
* Returns the task, or null if all tasks are blocked.
|
|
9
|
+
*
|
|
10
|
+
* A required task is "fully complete" when:
|
|
11
|
+
* 1. It appears in `completedTasks`, AND
|
|
12
|
+
* 2. Every task in `completedTasks` that has it as `parentTaskId` is also
|
|
13
|
+
* fully complete (recursive — children's children must also be done), AND
|
|
14
|
+
* 3. No task in `pendingTasks` has it as `parentTaskId`.
|
|
15
|
+
*
|
|
16
|
+
* @param pendingTasks - Tasks waiting to run (ordered by priority).
|
|
17
|
+
* @param completedTasks - Tasks that have finished (need id and parentTaskId).
|
|
18
|
+
*/
|
|
19
|
+
export function findReadyTask(pendingTasks, completedTasks) {
|
|
20
|
+
const completedIds = new Set(completedTasks.map((t) => t.id).filter(Boolean));
|
|
21
|
+
// Parents that still have pending children.
|
|
22
|
+
const pendingChildParents = new Set();
|
|
23
|
+
for (const t of pendingTasks) {
|
|
24
|
+
if (t.parentTaskId)
|
|
25
|
+
pendingChildParents.add(t.parentTaskId);
|
|
26
|
+
}
|
|
27
|
+
// Cache for recursive full-completion check.
|
|
28
|
+
const cache = new Map();
|
|
29
|
+
function isFullyComplete(taskId) {
|
|
30
|
+
if (cache.has(taskId))
|
|
31
|
+
return cache.get(taskId);
|
|
32
|
+
// Prevent infinite recursion on cycles.
|
|
33
|
+
cache.set(taskId, false);
|
|
34
|
+
if (!completedIds.has(taskId))
|
|
35
|
+
return false;
|
|
36
|
+
if (pendingChildParents.has(taskId))
|
|
37
|
+
return false;
|
|
38
|
+
// Check completed children are also fully complete.
|
|
39
|
+
for (const child of completedTasks) {
|
|
40
|
+
if (child.parentTaskId === taskId && child.id && !isFullyComplete(child.id)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
cache.set(taskId, true);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const idx = pendingTasks.findIndex((task) => {
|
|
48
|
+
if (!task.requiredTaskIds || task.requiredTaskIds.length === 0)
|
|
49
|
+
return true;
|
|
50
|
+
return task.requiredTaskIds.every((id) => isFullyComplete(id));
|
|
51
|
+
});
|
|
52
|
+
return idx === -1 ? null : pendingTasks[idx];
|
|
53
|
+
}
|