@replayio/app-building 1.24.1 → 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 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.
@@ -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/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./container-utils";
4
4
  export * from "./http-client";
5
5
  export * from "./image-ref";
6
6
  export * from "./secrets";
7
+ export * from "./tasks";
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export * from "./container-utils";
4
4
  export * from "./http-client";
5
5
  export * from "./image-ref";
6
6
  export * from "./secrets";
7
+ export * from "./tasks";
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio/app-building",
3
- "version": "1.24.1",
3
+ "version": "1.25.0",
4
4
  "description": "Library for managing agentic app-building containers",
5
5
  "type": "module",
6
6
  "exports": {