@replayio/app-building 1.15.0 → 1.16.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
@@ -15,7 +15,6 @@ import {
15
15
  loadDotEnv,
16
16
  FileContainerRegistry,
17
17
  getInfisicalConfig,
18
- resolveContainerSecrets,
19
18
  createMachine,
20
19
  destroyMachine,
21
20
  type ContainerConfig,
@@ -25,14 +24,22 @@ import {
25
24
  httpOptsFor,
26
25
  } from "@replayio/app-building";
27
26
 
28
- // Load orchestration vars from .env, then fetch build secrets from Infisical
27
+ // Load orchestration vars from .env, then get Infisical credentials
29
28
  const orchestrationVars = loadDotEnv("/path/to/project");
30
- const infisicalConfig = getInfisicalConfig(orchestrationVars);
31
- const containerSecrets = await resolveContainerSecrets(infisicalConfig);
29
+ const infisicalConfig = await getInfisicalConfig(orchestrationVars);
30
+
31
+ // Only pass Infisical credentials to the container — not actual secrets.
32
+ // The container fetches secrets from Infisical at startup and manages them
33
+ // via an internal secrets server. The agent never has direct access to secrets.
34
+ const containerEnvVars: Record<string, string> = {
35
+ INFISICAL_TOKEN: infisicalConfig.token,
36
+ INFISICAL_PROJECT_ID: infisicalConfig.projectId,
37
+ INFISICAL_ENVIRONMENT: infisicalConfig.environment,
38
+ };
32
39
 
33
40
  const config: ContainerConfig = {
34
41
  projectRoot: "/path/to/project", // optional — only needed for local Docker operations
35
- envVars: containerSecrets,
42
+ envVars: containerEnvVars,
36
43
  registry: new FileContainerRegistry("/path/to/.container-registry.jsonl"),
37
44
  flyToken: orchestrationVars.FLY_API_TOKEN,
38
45
  flyApp: orchestrationVars.FLY_APP_NAME,
@@ -53,13 +60,32 @@ const alive = await config.registry.findAlive();
53
60
  await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId);
54
61
  ```
55
62
 
63
+ ## Secrets architecture
64
+
65
+ Secrets are never passed directly to the container or agent. Instead:
66
+
67
+ 1. The orchestration host passes **Infisical credentials** (token, project ID, environment) to the container.
68
+ 2. At startup, the container fetches all secrets from Infisical and stores them in memory.
69
+ 3. A **secrets server** (`127.0.0.1:9119`) runs inside the container, accessible only locally.
70
+ 4. The agent process runs with a **restricted environment** — only `ANTHROPIC_API_KEY` (required for the Claude CLI) is present.
71
+ 5. When the agent needs to run a command that requires secrets, it uses `exec-secrets`:
72
+
73
+ ```bash
74
+ exec-secrets NEON_API_KEY -- curl -s -H "Authorization: Bearer $NEON_API_KEY" https://...
75
+ exec-secrets NETLIFY_AUTH_TOKEN NETLIFY_ACCOUNT_SLUG -- netlify deploy --prod
76
+ ```
77
+
78
+ The secrets server spawns the command with the requested secrets in its environment and **redacts all secret values** from the output.
79
+
80
+ The agent can also run `list-secrets` to see which secrets are available.
81
+
56
82
  ## Exported API
57
83
 
58
84
  ### Domain objects
59
85
 
60
86
  | Export | Description |
61
87
  |---|---|
62
- | `ContainerConfig` | Interface bundling all external state: optional `projectRoot` (only needed for local Docker operations), `envVars` (build secrets from Infisical), `registry`, optional `flyToken`/`flyApp`/`imageRef`/`webhookUrl`/`webhookSecret`/`detached`/`initialPrompt`/`localPort`. See [Webhooks](#webhooks) and [Container lifecycle](#container-lifecycle) below. |
88
+ | `ContainerConfig` | Interface bundling all external state: optional `projectRoot` (only needed for local Docker operations), `envVars` (Infisical credentials), `registry`, optional `flyToken`/`flyApp`/`imageRef`/`webhookUrl`/`webhookSecret`/`detached`/`initialPrompt`/`localPort`/`absorbTasks`. See [Webhooks](#webhooks) and [Container lifecycle](#container-lifecycle) below. |
63
89
  | `RepoOptions` | Per-invocation git settings: `repoUrl`, `cloneBranch`, `pushBranch`. |
64
90
  | `ContainerRegistry` | Interface for container registry storage. Methods: `log`, `markStopped`, `clearStopped`, `getRecent`, `find`, `findAlive`. |
65
91
  | `FileContainerRegistry` | Built-in file-backed implementation of `ContainerRegistry`, backed by a `.jsonl` file. |
@@ -123,8 +149,7 @@ await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId);
123
149
 
124
150
  | Export | Description |
125
151
  |---|---|
126
- | `getInfisicalConfig(envVars)` | Extract `InfisicalConfig` from env vars. Returns `null` if any required var is missing (enables fallback to raw `.env`). |
127
- | `resolveContainerSecrets(config)` | Fetch global build secrets from Infisical and merge with Infisical config vars. Returns a `Record<string, string>` suitable for `ContainerConfig.envVars`. |
152
+ | `getInfisicalConfig(envVars)` | Extract `InfisicalConfig` from env vars and log in. Requires `INFISICAL_CLIENT_ID`, `INFISICAL_CLIENT_SECRET`, `INFISICAL_PROJECT_ID`, `INFISICAL_ENVIRONMENT`. |
128
153
  | `fetchGlobalSecrets(config)` | Fetch secrets from the `/global/` path. |
129
154
  | `fetchBranchSecrets(config, branch)` | Fetch secrets from `/branches/<branch>/`. |
130
155
  | `fetchInfisicalSecrets(config, path)` | Raw fetch from any Infisical folder path. |
@@ -135,13 +160,15 @@ await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId);
135
160
 
136
161
  ```ts
137
162
  const orchestrationVars = loadDotEnv(projectRoot);
138
- const infisicalConfig = getInfisicalConfig(orchestrationVars);
139
- const containerSecrets = infisicalConfig
140
- ? await resolveContainerSecrets(infisicalConfig)
141
- : orchestrationVars; // fallback for local dev without Infisical
163
+ const infisicalConfig = await getInfisicalConfig(orchestrationVars);
142
164
 
165
+ // Pass only Infisical credentials to the container
143
166
  const config: ContainerConfig = {
144
- envVars: containerSecrets,
167
+ envVars: {
168
+ INFISICAL_TOKEN: infisicalConfig.token,
169
+ INFISICAL_PROJECT_ID: infisicalConfig.projectId,
170
+ INFISICAL_ENVIRONMENT: infisicalConfig.environment,
171
+ },
145
172
  flyToken: orchestrationVars.FLY_API_TOKEN,
146
173
  flyApp: orchestrationVars.FLY_APP_NAME,
147
174
  ...
@@ -186,6 +213,12 @@ A container stays running and accepts messages until it receives a **detach** or
186
213
  Without either signal, the container waits indefinitely for new messages — this is intentional
187
214
  so that interactive users can send follow-up messages at any time.
188
215
 
216
+ ### Task absorption
217
+
218
+ Set `config.absorbTasks = true` to have the container absorb task files from other containers
219
+ at startup. This is off by default. When enabled, the container scans `tasks/` for task files
220
+ belonging to other containers, merges their tasks into its own queue, and deletes the foreign files.
221
+
189
222
  ## Webhooks
190
223
 
191
224
  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.
@@ -230,12 +263,15 @@ Every POST body has this shape:
230
263
 
231
264
  ```ts
232
265
  const orchestrationVars = loadDotEnv("/path/to/project");
233
- const infisicalConfig = getInfisicalConfig(orchestrationVars);
234
- const containerSecrets = await resolveContainerSecrets(infisicalConfig);
266
+ const infisicalConfig = await getInfisicalConfig(orchestrationVars);
235
267
 
236
268
  const config: ContainerConfig = {
237
269
  projectRoot: "/path/to/project",
238
- envVars: containerSecrets,
270
+ envVars: {
271
+ INFISICAL_TOKEN: infisicalConfig.token,
272
+ INFISICAL_PROJECT_ID: infisicalConfig.projectId,
273
+ INFISICAL_ENVIRONMENT: infisicalConfig.environment,
274
+ },
239
275
  registry: new FileContainerRegistry("/path/to/.container-registry.jsonl"),
240
276
  webhookUrl: "https://example.com/hooks/container-events",
241
277
  webhookSecret: "your-webhook-secret",
@@ -23,6 +23,8 @@ export interface ContainerConfig {
23
23
  initialPrompt?: string;
24
24
  /** Override the host port for local containers (default: auto-selected). */
25
25
  localPort?: number;
26
+ /** Absorb task files from other containers at startup. Default: false. */
27
+ absorbTasks?: boolean;
26
28
  }
27
29
  export interface RepoOptions {
28
30
  repoUrl: string;
package/dist/container.js CHANGED
@@ -119,6 +119,8 @@ export async function startContainer(config, repo) {
119
119
  extra.DETACHED = "1";
120
120
  if (config.initialPrompt)
121
121
  extra.INITIAL_PROMPT = config.initialPrompt;
122
+ if (config.absorbTasks)
123
+ extra.ABSORB_TASKS = "1";
122
124
  const containerEnv = buildContainerEnv(repo, config.envVars, extra);
123
125
  // Build docker run args
124
126
  const args = ["run", "-d", "--rm", "--name", containerName];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio/app-building",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Library for managing agentic app-building containers",
5
5
  "type": "module",
6
6
  "exports": {