@rallycry/conveyor-agent 10.13.71 → 11.0.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/dist/cli.js CHANGED
@@ -4,750 +4,77 @@ import {
4
4
  isLegacyEntrypointLaunch
5
5
  } from "./chunk-R3FDJQL6.js";
6
6
  import {
7
- AgentConnection,
8
- CodespacePortVisibility,
9
- DEFAULT_LIFECYCLE_CONFIG,
7
+ WorkspaceCommandSupervisor,
8
+ startWorkspaceCommandsAfterConnect,
9
+ stopWorkspaceCommands
10
+ } from "./chunk-JQVAWRVL.js";
11
+ import {
10
12
  DEFAULT_SONNET_MODEL,
11
- Lifecycle,
12
- PortDiscovery,
13
13
  PtyHarness,
14
+ RUNNER_MODES,
14
15
  SessionRunner,
15
- applyBootstrapToEnv,
16
- awaitGitReady,
17
16
  buildProjectTools,
18
17
  buildRateLimitEvents,
19
- buildSessionPreviewPorts,
20
18
  buildSynthesizedCredentials,
21
19
  buildUnmeasurableEvent,
22
20
  claudeJsonPath,
23
- createServiceLogger,
24
- fetchBootstrap,
25
21
  isPermissionDeniedError,
26
- loadConveyorConfig,
27
- loadForwardPorts,
22
+ parsePackExecution,
28
23
  parseUsageGauges,
29
- readWorkspaceBytes,
30
24
  resolvePlaywrightMcpServer,
31
25
  resolveSessionStart,
32
26
  resolveTuiAdapter,
33
27
  resolveTuiKindFromEnv,
34
28
  runUsageProbe,
35
- sampleKeyUsage,
36
- statWorkspacePath,
37
- workspacePathExists
38
- } from "./chunk-7MMECTTJ.js";
29
+ sampleKeyUsage
30
+ } from "./chunk-PEEGCZAR.js";
31
+ import "./chunk-XORJ6SII.js";
39
32
  import {
40
- reportBootMilestone
41
- } from "./chunk-QU53HND5.js";
33
+ AgentConnection,
34
+ CodespacePortVisibility,
35
+ DEFAULT_LIFECYCLE_CONFIG,
36
+ Lifecycle,
37
+ PortDiscovery,
38
+ applyBootstrapToEnv,
39
+ createServiceLogger,
40
+ fetchBootstrap,
41
+ loadConveyorConfig
42
+ } from "./chunk-N4WSUTGV.js";
43
+ import "./chunk-GL2DIQEQ.js";
42
44
  import "./chunk-IA45XHOA.js";
43
- import {
44
- getWorkbenchClient
45
- } from "./chunk-EXQ6AHOY.js";
46
- import {
47
- workbenchEnabled
48
- } from "./chunk-KMB3BU4S.js";
45
+ import "./chunk-EXQ6AHOY.js";
46
+ import "./chunk-KMB3BU4S.js";
49
47
  import {
50
48
  inheritedEnv,
51
49
  resolvePtySpawn,
52
- runSetupCommand,
53
- runStartCommand,
54
- sessionTempBase,
55
- terminateProcessGroup
56
- } from "./chunk-GJXAAPJ6.js";
50
+ sessionTempBase
51
+ } from "./chunk-3F4ZZKCA.js";
52
+ import "./chunk-W4LZ7R6Z.js";
57
53
  import "./chunk-6Q6LQBWO.js";
58
54
 
59
55
  // src/cli.ts
60
56
  import { readFileSync } from "fs";
61
- import { join as join4, dirname as dirname2 } from "path";
57
+ import { join as join2, dirname } from "path";
62
58
  import { fileURLToPath } from "url";
63
59
 
64
- // src/setup/sidecars.ts
65
- import net from "net";
66
- var POSTGRES_TIMEOUT_MS = 12e4;
67
- var FIREBASE_TIMEOUT_MS = 6e4;
68
- var FALLBACK_TIMEOUT_MS = 3e4;
69
- var DEFAULT_SIDECAR_POLL_INTERVAL_MS = 1e3;
70
- var DEFAULT_PROBE_TIMEOUT_MS = 2e3;
71
- var POSTGRES_DEFAULT_PORT = 5432;
72
- var FIREBASE_DEFAULT_PORT = 9099;
73
- function parseHostPort(value, defaultPort) {
74
- const trimmed = value.trim().replace(/^[a-z]+:\/\//i, "");
75
- if (!trimmed) return null;
76
- const idx = trimmed.lastIndexOf(":");
77
- if (idx === -1) {
78
- return { host: trimmed, port: defaultPort };
79
- }
80
- const host = trimmed.slice(0, idx) || "localhost";
81
- const port = Number(trimmed.slice(idx + 1));
82
- return { host, port: Number.isFinite(port) ? port : defaultPort };
83
- }
84
- function resolveSidecarTargets(env = process.env) {
85
- const targets = [];
86
- const databaseUrl = env.DATABASE_URL;
87
- if (databaseUrl) {
88
- try {
89
- const url = new URL(databaseUrl);
90
- const host = url.hostname || "localhost";
91
- const port = url.port ? Number(url.port) : POSTGRES_DEFAULT_PORT;
92
- if (Number.isFinite(port)) {
93
- targets.push({ name: "postgres", host, port, timeoutMs: POSTGRES_TIMEOUT_MS });
94
- }
95
- } catch {
96
- }
97
- }
98
- const firebaseHost = env.FIREBASE_AUTH_EMULATOR_HOST;
99
- if (firebaseHost) {
100
- const parsed = parseHostPort(firebaseHost, FIREBASE_DEFAULT_PORT);
101
- if (parsed) {
102
- targets.push({
103
- name: "firebase auth emulator",
104
- host: parsed.host,
105
- port: parsed.port,
106
- timeoutMs: FIREBASE_TIMEOUT_MS
107
- });
108
- }
109
- }
110
- return targets;
111
- }
112
- function abortError() {
113
- const error = new Error("Operation aborted");
114
- error.name = "AbortError";
115
- return error;
116
- }
117
- function throwIfAborted(signal) {
118
- if (signal?.aborted) throw abortError();
119
- }
120
- function defaultProbe(target, signal) {
121
- throwIfAborted(signal);
122
- return new Promise((resolve) => {
123
- let settled = false;
124
- const socket = net.createConnection({ host: target.host, port: target.port });
125
- const done = (ok) => {
126
- if (settled) return;
127
- settled = true;
128
- socket.destroy();
129
- signal?.removeEventListener("abort", onAbort);
130
- resolve(ok);
131
- };
132
- const onAbort = () => done(false);
133
- socket.once("connect", () => done(true));
134
- socket.once("error", () => done(false));
135
- socket.setTimeout(DEFAULT_PROBE_TIMEOUT_MS, () => done(false));
136
- signal?.addEventListener("abort", onAbort, { once: true });
137
- });
138
- }
139
- var delay = (ms, signal) => {
140
- throwIfAborted(signal);
141
- return new Promise((resolve, reject) => {
142
- const timer = setTimeout(() => {
143
- signal?.removeEventListener("abort", onAbort);
144
- resolve();
145
- }, ms);
146
- const onAbort = () => {
147
- clearTimeout(timer);
148
- reject(abortError());
149
- };
150
- signal?.addEventListener("abort", onAbort, { once: true });
151
- });
152
- };
153
- function raceWithAbort(promise, signal) {
154
- if (!signal) return promise;
155
- throwIfAborted(signal);
156
- return new Promise((resolve, reject) => {
157
- const onAbort = () => reject(abortError());
158
- signal.addEventListener("abort", onAbort, { once: true });
159
- void promise.then(
160
- (value) => {
161
- signal.removeEventListener("abort", onAbort);
162
- resolve(value);
163
- },
164
- (error) => {
165
- signal.removeEventListener("abort", onAbort);
166
- reject(error);
167
- }
168
- );
169
- });
170
- }
171
- async function waitForTarget(target, opts) {
172
- const { onLog, pollIntervalMs, probe, signal } = opts;
173
- const timeoutMs = opts.timeoutMs ?? target.timeoutMs ?? FALLBACK_TIMEOUT_MS;
174
- const deadline = Date.now() + timeoutMs;
175
- onLog(`Waiting for ${target.name} on ${target.host}:${target.port}...`);
176
- while (true) {
177
- throwIfAborted(signal);
178
- if (await raceWithAbort(probe(target, signal), signal)) {
179
- onLog(`${target.name} is ready`);
180
- return;
181
- }
182
- if (Date.now() >= deadline) {
183
- onLog(
184
- `WARNING: ${target.name} not ready after ${Math.round(timeoutMs / 1e3)}s, continuing anyway`
185
- );
186
- return;
187
- }
188
- await delay(pollIntervalMs, signal);
189
- }
190
- }
191
- async function waitForSidecars(opts = {}) {
192
- const {
193
- env = process.env,
194
- onLog = () => {
195
- },
196
- timeoutMs,
197
- pollIntervalMs = DEFAULT_SIDECAR_POLL_INTERVAL_MS,
198
- probe = defaultProbe,
199
- signal
200
- } = opts;
201
- throwIfAborted(signal);
202
- const targets = resolveSidecarTargets(env);
203
- if (targets.length === 0) return;
204
- await Promise.all(
205
- targets.map(
206
- (target) => waitForTarget(target, { onLog, timeoutMs, pollIntervalMs, probe, signal })
207
- )
208
- );
209
- }
210
-
211
- // src/setup/db-schema-sync.ts
212
- import { basename, dirname, join } from "path";
213
- var PRISMA_SCHEMA_CANDIDATES = [
214
- "packages/db/prisma/schema.prisma",
215
- "prisma/schema.prisma",
216
- "apps/api/prisma/schema.prisma",
217
- "packages/db/schema.prisma"
218
- ];
219
- var PRISMA_DB_PUSH = "bunx prisma db push";
220
- var SCHEMA_SYNC_TIMEOUT_MS = 18e4;
221
- function depsInclude(rawDeps, dep) {
222
- return (rawDeps ?? "").split(",").map((entry) => entry.trim()).includes(dep);
223
- }
224
- function isDisabled(env) {
225
- const flag = env.CONVEYOR_DB_SCHEMA_SYNC?.trim().toLowerCase();
226
- return flag === "0" || flag === "false";
227
- }
228
- function packageRootForSchema(schemaPath) {
229
- const dir = dirname(schemaPath);
230
- return basename(dir) === "prisma" ? dirname(dir) : dir;
231
- }
232
- async function resolveSchemaSyncPlan(options) {
233
- const { workspaceDir, env } = options;
234
- const pathExists = options.pathExists ?? workspacePathExists;
235
- if (isDisabled(env)) return null;
236
- const override = env.CONVEYOR_DB_SCHEMA_SYNC_COMMAND?.trim();
237
- if (override) return { command: override, cwd: workspaceDir, source: "override" };
238
- if (!depsInclude(env.CONVEYOR_DEPS, "postgresql")) return null;
239
- if (!env.DATABASE_URL?.trim()) return null;
240
- for (const relative of PRISMA_SCHEMA_CANDIDATES) {
241
- const schemaPath = join(workspaceDir, relative);
242
- if (await pathExists(schemaPath)) {
243
- return { command: PRISMA_DB_PUSH, cwd: packageRootForSchema(schemaPath), source: "prisma" };
244
- }
245
- }
246
- return null;
247
- }
248
- async function runDbSchemaSync(options) {
249
- let plan = null;
250
- try {
251
- plan = await resolveSchemaSyncPlan(options);
252
- } catch {
253
- return "skipped";
254
- }
255
- if (!plan) return "skipped";
256
- const { onOutput, runCommand } = options;
257
- onOutput("stdout", `[db] syncing schema (${plan.source}): ${plan.command}
258
- `);
259
- const controller = new AbortController();
260
- const abort = () => controller.abort();
261
- options.signal?.addEventListener("abort", abort, { once: true });
262
- const timer = setTimeout(abort, options.timeoutMs ?? SCHEMA_SYNC_TIMEOUT_MS);
263
- timer.unref();
264
- try {
265
- await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
266
- onOutput("stdout", "[db] schema in sync\n");
267
- return "synced";
268
- } catch (error) {
269
- const message = error instanceof Error ? error.message : String(error);
270
- onOutput("stderr", `[db] schema sync failed: ${message}
271
- `);
272
- return "failed";
273
- } finally {
274
- clearTimeout(timer);
275
- options.signal?.removeEventListener("abort", abort);
276
- }
277
- }
278
-
279
- // src/setup/deps-sync.ts
280
- import { join as join2 } from "path";
281
- var INSTALLER_SCRIPT_CANDIDATES = ["scripts/install-deps.sh"];
282
- var BUN_INSTALL = "bun install --frozen-lockfile";
283
- var DEPS_SYNC_TIMEOUT_MS = 9e5;
284
- var REGISTRY_KEY_ENV = "NPM_CACHE_READER_KEY";
285
- function isDisabled2(env) {
286
- const flag = env.CONVEYOR_DEPS_SYNC?.trim().toLowerCase();
287
- return flag === "0" || flag === "false";
288
- }
289
- var CKSUM_POLYNOMIAL = 79764919;
290
- var CKSUM_TABLE = (() => {
291
- const table = new Uint32Array(256);
292
- for (let index = 0; index < 256; index++) {
293
- let value = index << 24;
294
- for (let bit = 0; bit < 8; bit++) {
295
- value = value & 2147483648 ? (value << 1 ^ CKSUM_POLYNOMIAL) >>> 0 : value << 1 >>> 0;
296
- }
297
- table[index] = value >>> 0;
298
- }
299
- return table;
300
- })();
301
- function posixCksum(bytes) {
302
- let crc = 0;
303
- for (const byte of bytes) {
304
- crc = (crc << 8 ^ CKSUM_TABLE[(crc >>> 24 ^ byte) & 255]) >>> 0;
305
- }
306
- for (let length = bytes.length; length > 0; length >>>= 8) {
307
- crc = (crc << 8 ^ CKSUM_TABLE[(crc >>> 24 ^ length & 255) & 255]) >>> 0;
308
- }
309
- return ~crc >>> 0;
310
- }
311
- function bunLockFingerprint(bytes) {
312
- return `${posixCksum(bytes)}:${bytes.length}`;
313
- }
314
- async function defaultDirExists(path) {
315
- return (await statWorkspacePath(path)).isDirectory;
316
- }
317
- async function readLockDrift(options) {
318
- const { workspaceDir, readBytes } = options;
319
- let baked;
320
- try {
321
- const raw = await readBytes(join2(workspaceDir, ".conveyor/prebake-cache.json"));
322
- const parsed = JSON.parse(Buffer.from(raw).toString("utf8"));
323
- const value = parsed?.bunLockCrc;
324
- if (typeof value !== "string" || !value) return null;
325
- baked = value;
326
- } catch {
327
- return null;
328
- }
329
- try {
330
- const actual = bunLockFingerprint(await readBytes(join2(workspaceDir, "bun.lock")));
331
- return actual === baked ? null : { baked, actual };
332
- } catch {
333
- return null;
334
- }
335
- }
336
- async function resolveDepsSyncPlan(options) {
337
- const { workspaceDir, env } = options;
338
- const pathExists = options.pathExists ?? workspacePathExists;
339
- const dirExists = options.dirExists ?? defaultDirExists;
340
- const readBytes = options.readBytes ?? readWorkspaceBytes;
341
- if (isDisabled2(env)) return null;
342
- if (!await pathExists(join2(workspaceDir, "package.json"))) return null;
343
- const override = env.CONVEYOR_DEPS_SYNC_COMMAND?.trim();
344
- const resolveCommand = async () => {
345
- if (override) return override;
346
- for (const relative of INSTALLER_SCRIPT_CANDIDATES) {
347
- if (await pathExists(join2(workspaceDir, relative))) return `bash ${relative}`;
348
- }
349
- return BUN_INSTALL;
350
- };
351
- if (!await dirExists(join2(workspaceDir, "node_modules"))) {
352
- return {
353
- command: await resolveCommand(),
354
- cwd: workspaceDir,
355
- reason: "missing_node_modules",
356
- detail: "the pod booted with no node_modules \u2014 the baked dependency tree is absent"
357
- };
358
- }
359
- const drift = await readLockDrift({ workspaceDir, readBytes });
360
- if (drift) {
361
- return {
362
- command: await resolveCommand(),
363
- cwd: workspaceDir,
364
- reason: "lockfile_drift",
365
- detail: `bun.lock changed since the image bake (baked ${drift.baked}, checked out ${drift.actual})`
366
- };
367
- }
368
- return null;
369
- }
370
- function describeFailure(plan, error, env) {
371
- const cause = error instanceof Error ? error.message : String(error);
372
- const parts = [
373
- `Dependency install failed (${plan.reason}): ${plan.detail}.`,
374
- `Ran \`${plan.command}\` in ${plan.cwd} \u2014 ${cause}.`
375
- ];
376
- if (!env[REGISTRY_KEY_ENV]?.trim()) {
377
- parts.push(
378
- `${REGISTRY_KEY_ENV} is not set on this pod, so packages on the private registry cache cannot be fetched \u2014 set it as a project secret.`
379
- );
380
- }
381
- return parts.join(" ");
382
- }
383
- async function runDepsSync(options) {
384
- let plan = null;
385
- try {
386
- plan = await resolveDepsSyncPlan(options);
387
- } catch {
388
- return { outcome: "skipped" };
389
- }
390
- if (!plan) return { outcome: "skipped" };
391
- const { onOutput, runCommand, env } = options;
392
- onOutput("stdout", `[deps] ${plan.detail}
393
- `);
394
- onOutput("stdout", `[deps] installing (${plan.reason}): ${plan.command}
395
- `);
396
- const controller = new AbortController();
397
- const abort = () => controller.abort();
398
- options.signal?.addEventListener("abort", abort, { once: true });
399
- const timer = setTimeout(abort, options.timeoutMs ?? DEPS_SYNC_TIMEOUT_MS);
400
- timer.unref();
401
- try {
402
- await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
403
- onOutput("stdout", "[deps] dependencies installed\n");
404
- return { outcome: "installed" };
405
- } catch (error) {
406
- const message = describeFailure(plan, error, env);
407
- onOutput("stderr", `[deps] ${message}
408
- `);
409
- return { outcome: "failed", message };
410
- } finally {
411
- clearTimeout(timer);
412
- options.signal?.removeEventListener("abort", abort);
413
- }
414
- }
415
-
416
- // src/setup/workspace-command-supervisor.ts
417
- function defaultCommandExecutors() {
418
- if (workbenchEnabled()) {
419
- const client = getWorkbenchClient();
420
- return {
421
- runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput),
422
- runShellCommand: (cmd, cwd, onOutput, signal) => client.runSetupCommand(cmd, cwd, onOutput, signal)
423
- };
424
- }
425
- return { runStartCommand, runShellCommand: runSetupCommand };
426
- }
427
- var defaultWriteOutput = (stream, data) => {
428
- (stream === "stderr" ? process.stderr : process.stdout).write(data);
429
- };
430
- function stopWorkspaceCommands(supervisor) {
431
- return supervisor?.stop() ?? Promise.resolve();
432
- }
433
- async function startWorkspaceCommandsAfterConnect(options) {
434
- const connected = await options.connect();
435
- if (!connected || options.isShuttingDown()) return null;
436
- const supervisor = options.create();
437
- supervisor.start();
438
- return supervisor;
439
- }
440
- var WorkspaceCommandSupervisor = class {
441
- abortController = new AbortController();
442
- expectedStartCommandStops = /* @__PURE__ */ new WeakSet();
443
- config;
444
- workspaceDir;
445
- connection;
446
- env;
447
- awaitGitReadyFn;
448
- waitForSidecarsFn;
449
- runStartCommandFn;
450
- syncDbSchemaFn;
451
- syncDepsFn;
452
- loadForwardPortsFn;
453
- writeOutput;
454
- terminateStartCommand;
455
- reportBootMilestoneFn;
456
- startCommandChild = null;
457
- liveStartCommandChildren = /* @__PURE__ */ new Set();
458
- startCommandTerminations = /* @__PURE__ */ new WeakMap();
459
- startCommandEndPromises = /* @__PURE__ */ new WeakMap();
460
- resolveStartCommandEnd = /* @__PURE__ */ new WeakMap();
461
- backgroundTasks = /* @__PURE__ */ new Set();
462
- startCommandQueue = Promise.resolve();
463
- shutdownPromise = null;
464
- startCommandLaunchRequested = false;
465
- started = false;
466
- stopped = false;
467
- /** Resolved by notifyLoopReady() once the PTY is live — the runner fires it
468
- * on the first harness event of the initial query (or when a prefill
469
- * parks), NOT after the first turn completes, so the app's start command
470
- * isn't held behind a long working turn. Also resolved by stop() if
471
- * shutdown arrives first — either way unblocks the awaiter in
472
- * runSetupAndStart so a supervisor stopped before the loop ever signals
473
- * doesn't hang its background task (and therefore stop()) forever. */
474
- resolveLoopReady;
475
- loopReady = new Promise((resolve) => {
476
- this.resolveLoopReady = resolve;
477
- });
478
- constructor(options) {
479
- this.config = options.config;
480
- this.workspaceDir = options.workspaceDir;
481
- this.connection = options.connection;
482
- this.env = options.env ?? process.env;
483
- this.awaitGitReadyFn = options.awaitGitReady ?? ((opts) => awaitGitReady({
484
- onLog: opts.onLog,
485
- signal: opts.signal
486
- }));
487
- this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, signal: opts.signal }));
488
- const executors = defaultCommandExecutors();
489
- this.runStartCommandFn = options.runStartCommand ?? executors.runStartCommand;
490
- this.syncDbSchemaFn = options.syncDbSchema ?? ((opts) => runDbSchemaSync({
491
- workspaceDir: this.workspaceDir,
492
- env: this.env,
493
- runCommand: executors.runShellCommand,
494
- onOutput: opts.onOutput,
495
- signal: opts.signal
496
- }));
497
- this.syncDepsFn = options.syncDeps ?? ((opts) => runDepsSync({
498
- workspaceDir: this.workspaceDir,
499
- env: this.env,
500
- runCommand: executors.runShellCommand,
501
- onOutput: opts.onOutput,
502
- signal: opts.signal
503
- }));
504
- this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
505
- this.writeOutput = options.writeOutput ?? defaultWriteOutput;
506
- this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
507
- this.reportBootMilestoneFn = options.reportBootMilestone ?? ((key) => {
508
- void reportBootMilestone({ key, env: this.env });
509
- });
510
- }
511
- start() {
512
- if (this.started || this.stopped) return;
513
- this.started = true;
514
- this.connection.onRunStartCommand(() => this.restartStartCommand());
515
- if (this.config) {
516
- this.trackBackgroundTask(
517
- this.runSetupAndStart().catch((error) => this.reportUnexpectedError(error))
518
- );
519
- }
520
- }
521
- /** Release the start-command launch — called once the runner's core loop
522
- * (the PTY) is live. Idempotent; resolving an already-settled promise is a
523
- * no-op, so a late/duplicate call (or one after stop() already resolved
524
- * it) is harmless. */
525
- notifyLoopReady() {
526
- this.resolveLoopReady();
527
- }
528
- stop() {
529
- if (this.shutdownPromise) return this.shutdownPromise;
530
- this.stopped = true;
531
- this.resolveLoopReady();
532
- this.abortController.abort();
533
- const backgroundTasks = [...this.backgroundTasks];
534
- const termination = this.terminateAllStartCommands();
535
- this.shutdownPromise = (async () => {
536
- await Promise.allSettled([termination, this.startCommandQueue, ...backgroundTasks]);
537
- await this.terminateAllStartCommands();
538
- })();
539
- return this.shutdownPromise;
540
- }
541
- async runSetupAndStart() {
542
- const gitState = await this.awaitGitReadyFn({
543
- onLog: (message) => this.forwardSetupOutput("stdout", `[git] ${message}
544
- `),
545
- signal: this.abortController.signal
546
- });
547
- if (this.stopped) return;
548
- if (gitState === "failed" || gitState === "timeout") {
549
- this.connection.sendEvent({
550
- type: "setup_error",
551
- message: "Workspace not ready \u2014 skipping setup/start"
552
- });
553
- return;
554
- }
555
- await this.waitForSidecarsFn({
556
- onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
557
- `),
558
- signal: this.abortController.signal
559
- });
560
- if (this.stopped) return;
561
- this.reportBootMilestoneFn("sidecars_ready");
562
- await this.loopReady;
563
- if (this.stopped) return;
564
- await this.syncDeps();
565
- if (this.stopped) return;
566
- await this.syncDbSchema();
567
- if (this.stopped) return;
568
- const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
569
- if (this.stopped) return;
570
- this.reportBootMilestoneFn("start_command_launched");
571
- const forwardPorts = await this.loadForwardPortsFn(this.workspaceDir);
572
- if (this.stopped) return;
573
- const previewPorts = buildSessionPreviewPorts(forwardPorts);
574
- this.connection.sendEvent({
575
- type: "setup_complete",
576
- startCommandRunning,
577
- // Reported separately so the server can tell "nothing to run" apart from
578
- // "the dev server failed to launch" — both report startCommandRunning:false.
579
- startCommandConfigured: Boolean(this.config?.startCommand),
580
- ...previewPorts.length > 0 ? { previewPorts } : {}
581
- });
582
- }
583
- /** Run the dependency sync, forwarding its output as setup output so it lands
584
- * in the card's setup log. A failure is announced with its own cause rather
585
- * than thrown: the start command still launches, but the card now carries
586
- * the reason instead of a generic `start command exited with code 1`. */
587
- async syncDeps() {
588
- const result = await this.syncDepsFn({
589
- onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
590
- signal: this.abortController.signal
591
- });
592
- if (result.outcome !== "failed" || this.stopped) return;
593
- this.connection.sendEvent({
594
- type: "setup_error",
595
- message: result.message ?? "Dependency install failed \u2014 the start command will fail until dependencies are installed (see pod logs)"
596
- });
597
- }
598
- /** Run the schema sync, forwarding its output as setup output so it lands in
599
- * the card's setup log. A failure is announced rather than thrown: it is the
600
- * exact condition that used to render as a silent "Displaying 0 results". */
601
- async syncDbSchema() {
602
- const outcome = await this.syncDbSchemaFn({
603
- onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
604
- signal: this.abortController.signal
605
- });
606
- if (outcome !== "failed" || this.stopped) return;
607
- this.connection.sendEvent({
608
- type: "setup_error",
609
- message: "Database schema sync failed \u2014 the app may error on queries touching recently added columns (see pod logs)"
610
- });
611
- }
612
- restartStartCommand() {
613
- if (this.stopped || !this.config?.startCommand) return;
614
- this.connection.sendEvent({
615
- type: "start_command_output",
616
- stream: "stdout",
617
- data: "[conveyor-agent] Restarting start command...\n"
618
- });
619
- void this.enqueueStartCommandReplacement(this.config.startCommand);
620
- }
621
- async ensureStartCommandLaunched(command) {
622
- if (!this.startCommandLaunchRequested) {
623
- return this.enqueueStartCommandReplacement(command);
624
- }
625
- await this.startCommandQueue;
626
- return !this.stopped && this.liveStartCommandChildren.size > 0;
627
- }
628
- enqueueStartCommandReplacement(command) {
629
- this.startCommandLaunchRequested = true;
630
- const operation = this.startCommandQueue.then(async () => {
631
- if (this.stopped) return false;
632
- await this.terminateAllStartCommands();
633
- if (this.stopped) return false;
634
- return this.launchStartCommand(command);
635
- });
636
- this.startCommandQueue = operation.then(
637
- () => void 0,
638
- () => void 0
639
- );
640
- return operation;
641
- }
642
- launchStartCommand(command) {
643
- if (this.stopped) return false;
644
- this.connection.sendEvent({ type: "start_command_started" });
645
- try {
646
- const child = this.runStartCommandFn(command, this.workspaceDir, (stream, data) => {
647
- if (this.stopped) return;
648
- this.connection.sendEvent({ type: "start_command_output", stream, data });
649
- this.writeOutput(stream, data);
650
- });
651
- this.liveStartCommandChildren.add(child);
652
- const ended = new Promise((resolve) => {
653
- this.resolveStartCommandEnd.set(child, resolve);
654
- });
655
- this.startCommandEndPromises.set(child, ended);
656
- if (this.stopped) {
657
- void this.terminateStartCommandChild(child);
658
- return false;
659
- }
660
- this.startCommandChild = child;
661
- child.on("exit", (code, signal) => {
662
- if (this.startCommandChild === child) this.startCommandChild = null;
663
- this.liveStartCommandChildren.delete(child);
664
- this.settleStartCommandEnd(child);
665
- if (this.stopped || this.expectedStartCommandStops.has(child)) return;
666
- const message = `start command exited${code === null ? "" : ` with code ${code}`}${signal ? ` (signal: ${signal})` : ""}`;
667
- this.connection.sendEvent({ type: "start_command_exited", code, signal, message });
668
- if (code !== null && code !== 0) {
669
- this.connection.sendEvent({
670
- type: "start_command_error",
671
- message: `start command exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`
672
- });
673
- }
674
- });
675
- child.on("error", (error) => {
676
- if (child.pid === void 0) {
677
- if (this.startCommandChild === child) this.startCommandChild = null;
678
- this.liveStartCommandChildren.delete(child);
679
- this.settleStartCommandEnd(child);
680
- this.startCommandTerminations.delete(child);
681
- }
682
- if (this.stopped) return;
683
- this.connection.sendEvent({ type: "start_command_error", message: error.message });
684
- });
685
- return true;
686
- } catch (error) {
687
- if (!this.stopped) {
688
- this.connection.sendEvent({
689
- type: "start_command_error",
690
- message: error instanceof Error ? error.message : String(error)
691
- });
692
- }
693
- return false;
694
- }
695
- }
696
- terminateStartCommandChild(child) {
697
- const existing = this.startCommandTerminations.get(child);
698
- if (existing) return existing;
699
- this.expectedStartCommandStops.add(child);
700
- const processTermination = Promise.resolve(this.terminateStartCommand(child));
701
- const ended = this.startCommandEndPromises.get(child);
702
- const termination = (ended ? Promise.race([processTermination, ended]) : processTermination).then(() => void 0).finally(() => this.startCommandTerminations.delete(child));
703
- this.startCommandTerminations.set(child, termination);
704
- return termination;
705
- }
706
- async terminateAllStartCommands() {
707
- const live = [...this.liveStartCommandChildren].filter((child) => child.exitCode === null);
708
- await Promise.all(live.map((child) => this.terminateStartCommandChild(child)));
709
- }
710
- settleStartCommandEnd(child) {
711
- this.resolveStartCommandEnd.get(child)?.();
712
- this.resolveStartCommandEnd.delete(child);
713
- this.startCommandEndPromises.delete(child);
714
- }
715
- trackBackgroundTask(task) {
716
- this.backgroundTasks.add(task);
717
- void task.finally(() => this.backgroundTasks.delete(task));
718
- }
719
- forwardSetupOutput(stream, data) {
720
- if (this.stopped) return;
721
- this.connection.sendEvent({ type: "setup_output", stream, data });
722
- this.writeOutput(stream, data);
723
- }
724
- reportUnexpectedError(error) {
725
- if (this.stopped) return;
726
- this.connection.sendEvent({
727
- type: "setup_error",
728
- message: error instanceof Error ? error.message : String(error)
729
- });
730
- }
731
- };
732
-
733
60
  // src/utils/session-identity.ts
734
61
  async function checkSessionTaskIdentity(params) {
735
- const { sessionId, taskId, fetchSessionTaskId, logger: logger6 } = params;
62
+ const { sessionId, taskId, fetchSessionTaskId, logger: logger7 } = params;
736
63
  if (!sessionId || sessionId === taskId) return "skipped";
737
64
  let sessionTaskId;
738
65
  try {
739
66
  sessionTaskId = await fetchSessionTaskId(sessionId);
740
67
  } catch (err) {
741
- const message = err instanceof Error ? err.message : String(err);
742
- logger6.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
68
+ const message2 = err instanceof Error ? err.message : String(err);
69
+ logger7.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
743
70
  sessionId,
744
71
  taskId,
745
- error: message
72
+ error: message2
746
73
  });
747
74
  return "error";
748
75
  }
749
76
  if (sessionTaskId !== taskId) {
750
- logger6.warn(
77
+ logger7.warn(
751
78
  "!!! CONVEYOR_SESSION_ID is bound to a different task than CONVEYOR_TASK_ID \u2014 server-side guards should still block mutations, but this indicates a misconfiguration or replayed token.",
752
79
  { sessionId, envTaskId: taskId, sessionTaskId }
753
80
  );
@@ -863,10 +190,10 @@ var ProjectSessionRunner = class {
863
190
  await this.waitUntilStopped();
864
191
  this._finalState = "finished";
865
192
  } catch (error) {
866
- const message = error instanceof Error ? error.message : String(error);
867
- process.stderr.write(`[conveyor-agent] Project runner failed: ${message}
193
+ const message2 = error instanceof Error ? error.message : String(error);
194
+ process.stderr.write(`[conveyor-agent] Project runner failed: ${message2}
868
195
  `);
869
- this.connection.sendEvent({ type: "error", message });
196
+ this.connection.sendEvent({ type: "error", message: message2 });
870
197
  this._finalState = "error";
871
198
  } finally {
872
199
  this.shutdown();
@@ -929,7 +256,7 @@ var ProjectSessionRunner = class {
929
256
 
930
257
  // src/usage/multi-key-probe.ts
931
258
  import { mkdtemp, writeFile, copyFile, rm } from "fs/promises";
932
- import { join as join3 } from "path";
259
+ import { join } from "path";
933
260
  var logger = createServiceLogger("multi-key-probe");
934
261
  function gaugesToSamples(stdout) {
935
262
  const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
@@ -957,12 +284,12 @@ function gaugesToSamples(stdout) {
957
284
  async function isolatedProbe(token, now) {
958
285
  let dir = null;
959
286
  try {
960
- dir = await mkdtemp(join3(sessionTempBase(), "conveyor-usage-"));
961
- await writeFile(join3(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
287
+ dir = await mkdtemp(join(sessionTempBase(), "conveyor-usage-"));
288
+ await writeFile(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
962
289
  encoding: "utf8",
963
290
  mode: 384
964
291
  });
965
- await copyFile(claudeJsonPath(), join3(dir, ".claude.json")).catch(() => {
292
+ await copyFile(claudeJsonPath(), join(dir, ".claude.json")).catch(() => {
966
293
  });
967
294
  return await runUsageProbe({ env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
968
295
  } catch (error) {
@@ -1138,10 +465,10 @@ var AdhocSessionRunner = class {
1138
465
  await this.runInteractiveTui();
1139
466
  this._finalState = "finished";
1140
467
  } catch (error) {
1141
- const message = error instanceof Error ? error.message : String(error);
1142
- process.stderr.write(`[conveyor-agent] Ad-hoc runner failed: ${message}
468
+ const message2 = error instanceof Error ? error.message : String(error);
469
+ process.stderr.write(`[conveyor-agent] Ad-hoc runner failed: ${message2}
1143
470
  `);
1144
- this.connection.sendEvent({ type: "error", message });
471
+ this.connection.sendEvent({ type: "error", message: message2 });
1145
472
  this._finalState = "error";
1146
473
  } finally {
1147
474
  await this.shutdown();
@@ -1295,6 +622,10 @@ var ReviewChildSupervisor = class {
1295
622
  spawnFn;
1296
623
  child = null;
1297
624
  childSessionId = null;
625
+ /** The review session this supervisor currently hosts, if any. */
626
+ get activeSessionId() {
627
+ return this.childSessionId;
628
+ }
1298
629
  /** Spawn (or replace) the review child for a `session:spawnReview` push. */
1299
630
  async spawn(data) {
1300
631
  logger2.info("Spawning same-pod review child", {
@@ -1328,10 +659,10 @@ var ReviewChildSupervisor = class {
1328
659
  this.childSessionId = data.sessionId;
1329
660
  const spawnedAt = Date.now();
1330
661
  let reported = false;
1331
- const reportOnce = (message) => {
662
+ const reportOnce = (message2) => {
1332
663
  if (reported) return;
1333
664
  reported = true;
1334
- this.connection.reportReviewSpawnFailure(data.sessionId, message);
665
+ this.connection.reportReviewSpawnFailure(data.sessionId, message2);
1335
666
  };
1336
667
  child.stdout?.on("data", (chunk) => {
1337
668
  process.stdout.write(`[review-child] ${chunk.toString()}`);
@@ -1451,6 +782,10 @@ var SessionChildSupervisor = class {
1451
782
  get size() {
1452
783
  return this.children.size;
1453
784
  }
785
+ /** Sessions this supervisor currently hosts. Drives the shutdown deferral. */
786
+ get activeSessionIds() {
787
+ return [...this.children.keys()];
788
+ }
1454
789
  /** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
1455
790
  async spawn(data) {
1456
791
  logger3.info("Spawning same-pod session child", {
@@ -1481,10 +816,10 @@ var SessionChildSupervisor = class {
1481
816
  this.children.set(data.sessionId, child);
1482
817
  const spawnedAt = Date.now();
1483
818
  let reported = false;
1484
- const reportOnce = (message) => {
819
+ const reportOnce = (message2) => {
1485
820
  if (reported) return;
1486
821
  reported = true;
1487
- this.connection.reportSessionSpawnFailure(data.sessionId, message);
822
+ this.connection.reportSessionSpawnFailure(data.sessionId, message2);
1488
823
  };
1489
824
  const prefix = `[${data.mode}-child]`;
1490
825
  child.stdout?.on("data", (chunk) => {
@@ -1611,10 +946,10 @@ var ShellSessionRunner = class {
1611
946
  await this.runShell();
1612
947
  this._finalState = "finished";
1613
948
  } catch (error) {
1614
- const message = error instanceof Error ? error.message : String(error);
1615
- process.stderr.write(`[conveyor-agent] Shell runner failed: ${message}
949
+ const message2 = error instanceof Error ? error.message : String(error);
950
+ process.stderr.write(`[conveyor-agent] Shell runner failed: ${message2}
1616
951
  `);
1617
- this.connection.sendEvent({ type: "error", message });
952
+ this.connection.sendEvent({ type: "error", message: message2 });
1618
953
  this._finalState = "error";
1619
954
  } finally {
1620
955
  this.shutdown();
@@ -1708,14 +1043,158 @@ function createSpawnedChildRunner(inputs) {
1708
1043
  );
1709
1044
  }
1710
1045
 
1046
+ // src/runner/builder-child.ts
1047
+ import { spawn as nodeSpawn3 } from "child_process";
1048
+ var logger5 = createServiceLogger("BuilderChild");
1049
+ function buildBuilderChildEnv(baseEnv, data) {
1050
+ const env = { ...baseEnv };
1051
+ env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
1052
+ env.CONVEYOR_SESSION_ID = data.sessionId;
1053
+ env.CONVEYOR_MODE = data.mode ?? "task";
1054
+ env.CONVEYOR_WORKBENCH_TOKEN ??= baseEnv.POD_BOOTSTRAP_TOKEN;
1055
+ delete env.POD_BOOTSTRAP_TOKEN;
1056
+ delete env.CONVEYOR_BOOTSTRAP_TOKEN;
1057
+ delete env.CONVEYOR_START_COMMAND;
1058
+ delete env.CONVEYOR_GIT_READY;
1059
+ delete env.CONVEYOR_GIT_READY_MARKER;
1060
+ delete env.CONVEYOR_AGENT_MODE;
1061
+ delete env.CONVEYOR_IS_AUTO;
1062
+ return env;
1063
+ }
1064
+ var BuilderChildSupervisor = class {
1065
+ constructor(connection, workspaceDir, spawnFn = nodeSpawn3) {
1066
+ this.connection = connection;
1067
+ this.workspaceDir = workspaceDir;
1068
+ this.spawnFn = spawnFn;
1069
+ }
1070
+ connection;
1071
+ workspaceDir;
1072
+ spawnFn;
1073
+ child = null;
1074
+ childSessionId = null;
1075
+ /** The build session this supervisor currently hosts, if any. */
1076
+ get activeSessionId() {
1077
+ return this.childSessionId;
1078
+ }
1079
+ /** Spawn (or replace) the builder child for a `session:spawnBuilder` push. */
1080
+ async spawn(data) {
1081
+ if (this.childSessionId === data.sessionId && this.child && this.child.exitCode === null) {
1082
+ logger5.info("Builder child already running for this session; ignoring re-push", {
1083
+ buildSessionId: data.sessionId
1084
+ });
1085
+ return;
1086
+ }
1087
+ logger5.info("Spawning same-pod builder child", {
1088
+ buildSessionId: data.sessionId,
1089
+ branch: data.branch ?? void 0,
1090
+ agentName: data.agentName ?? void 0
1091
+ });
1092
+ await this.stopCurrent("superseded by new builder spawn");
1093
+ const cliPath = process.argv[1];
1094
+ if (!cliPath) {
1095
+ this.connection.reportBuilderSpawnFailure(data.sessionId, "cannot resolve CLI entry path");
1096
+ return;
1097
+ }
1098
+ let child;
1099
+ try {
1100
+ child = this.spawnFn(process.execPath, [cliPath], {
1101
+ cwd: this.workspaceDir,
1102
+ env: buildBuilderChildEnv(process.env, data),
1103
+ stdio: ["ignore", "pipe", "pipe"]
1104
+ });
1105
+ } catch (err) {
1106
+ this.connection.reportBuilderSpawnFailure(
1107
+ data.sessionId,
1108
+ err instanceof Error ? err.message : String(err)
1109
+ );
1110
+ return;
1111
+ }
1112
+ this.child = child;
1113
+ this.childSessionId = data.sessionId;
1114
+ this.watchForEarlyExit(child, data.sessionId);
1115
+ }
1116
+ /**
1117
+ * A non-zero exit inside the grace window is a spawn failure, not a finished
1118
+ * build — the child never got far enough to connect. Reporting it lets the
1119
+ * server End the orphaned session and fall back to a cold boot instead of
1120
+ * leaving the card parked with a Builder tab that never appears.
1121
+ */
1122
+ watchForEarlyExit(child, sessionId) {
1123
+ const spawnedAt = Date.now();
1124
+ child.on("exit", (code) => {
1125
+ if (this.childSessionId === sessionId) {
1126
+ this.child = null;
1127
+ this.childSessionId = null;
1128
+ }
1129
+ const early = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
1130
+ if (early && code !== 0) {
1131
+ logger5.warn("Builder child exited inside the spawn grace window", { sessionId, code });
1132
+ this.connection.reportBuilderSpawnFailure(sessionId, `child exited with code ${code}`);
1133
+ }
1134
+ });
1135
+ }
1136
+ /** SIGTERM the current child, escalating to SIGKILL after KILL_WAIT_MS. */
1137
+ async stopCurrent(reason) {
1138
+ const child = this.child;
1139
+ if (!child || child.exitCode !== null) {
1140
+ this.child = null;
1141
+ this.childSessionId = null;
1142
+ return;
1143
+ }
1144
+ logger5.info("Stopping builder child", { reason, buildSessionId: this.childSessionId });
1145
+ child.kill("SIGTERM");
1146
+ await new Promise((resolve) => {
1147
+ const timer = setTimeout(() => {
1148
+ if (child.exitCode === null) child.kill("SIGKILL");
1149
+ resolve();
1150
+ }, KILL_WAIT_MS);
1151
+ child.once("exit", () => {
1152
+ clearTimeout(timer);
1153
+ resolve();
1154
+ });
1155
+ });
1156
+ this.child = null;
1157
+ this.childSessionId = null;
1158
+ }
1159
+ };
1160
+
1711
1161
  // src/runner/host-modes.ts
1712
1162
  function hostsSpawnedChildren(mode) {
1713
- return mode === "task" || mode === "pack";
1163
+ return mode === "task" || mode === "plan" || mode === "pack";
1164
+ }
1165
+
1166
+ // src/runner/wire-spawn-children.ts
1167
+ var message = (err) => err instanceof Error ? err.message : String(err);
1168
+ function wireSpawnChildren(mode, connection, supervisors, logger7) {
1169
+ if (hostsSpawnedChildren(mode)) {
1170
+ connection.onSpawnReview((data) => {
1171
+ void supervisors.review.spawn(data).catch((err) => {
1172
+ logger7.error("Review child spawn threw", { error: message(err) });
1173
+ connection.reportReviewSpawnFailure(data.sessionId, message(err));
1174
+ });
1175
+ });
1176
+ }
1177
+ if (mode === "plan") {
1178
+ connection.onSpawnBuilder((data) => {
1179
+ void supervisors.builder.spawn(data).catch((err) => {
1180
+ logger7.error("Builder child spawn threw", { error: message(err) });
1181
+ connection.reportBuilderSpawnFailure(data.sessionId, message(err));
1182
+ });
1183
+ });
1184
+ }
1185
+ if (hostsSpawnedChildren(mode)) {
1186
+ connection.onSpawnTui((data) => {
1187
+ void supervisors.session.spawn(data).catch((err) => {
1188
+ logger7.error("Session child spawn threw", { error: message(err) });
1189
+ connection.reportSessionSpawnFailure(data.sessionId, message(err));
1190
+ });
1191
+ });
1192
+ }
1714
1193
  }
1715
1194
 
1716
1195
  // src/cli.ts
1717
1196
  if (process.argv[2] === "boot") {
1718
- const { runBoot } = await import("./boot-PKHZUDAC.js");
1197
+ const { runBoot } = await import("./boot-PKQ2I66D.js");
1719
1198
  process.exit(await runBoot(process.argv.slice(3)));
1720
1199
  }
1721
1200
  if (isLegacyEntrypointLaunch(process.env)) {
@@ -1724,13 +1203,13 @@ if (isLegacyEntrypointLaunch(process.env)) {
1724
1203
  process.exit(1);
1725
1204
  }
1726
1205
  if (process.argv.includes("--version")) {
1727
- const __dirname = dirname2(fileURLToPath(import.meta.url));
1728
- const pkgPath = join4(__dirname, "..", "package.json");
1206
+ const __dirname = dirname(fileURLToPath(import.meta.url));
1207
+ const pkgPath = join2(__dirname, "..", "package.json");
1729
1208
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1730
1209
  process.stdout.write(pkg.version + "\n");
1731
1210
  process.exit(0);
1732
1211
  }
1733
- var logger5 = createServiceLogger("CLI");
1212
+ var logger6 = createServiceLogger("CLI");
1734
1213
  var exitContext = {};
1735
1214
  var exitLogged = false;
1736
1215
  var shuttingDown = false;
@@ -1745,12 +1224,12 @@ function logAgentExit(reason, opts) {
1745
1224
  uptimeSec: process.uptime(),
1746
1225
  identity: exitContext
1747
1226
  });
1748
- logger5[exitLogLevel(reason)]("agent_exit", payload);
1227
+ logger6[exitLogLevel(reason)]("agent_exit", payload);
1749
1228
  }
1750
1229
  async function bootstrapFromCodespace(apiUrl, instanceName) {
1751
1230
  const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
1752
1231
  const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
1753
- logger5.info("Bootstrapping from codespace", {
1232
+ logger6.info("Bootstrapping from codespace", {
1754
1233
  codespace: instanceName,
1755
1234
  apiUrl,
1756
1235
  apiUrlFromEnv,
@@ -1762,7 +1241,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
1762
1241
  bootstrapToken
1763
1242
  });
1764
1243
  if (!result.ok) {
1765
- logger5.error("Bootstrap failed after retries", {
1244
+ logger6.error("Bootstrap failed after retries", {
1766
1245
  reason: result.reason,
1767
1246
  attempts: result.attempts,
1768
1247
  status: result.status,
@@ -1775,20 +1254,20 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
1775
1254
  process.exit(1);
1776
1255
  }
1777
1256
  applyBootstrapToEnv(result.config);
1778
- logger5.info("Bootstrap complete", {
1257
+ logger6.info("Bootstrap complete", {
1779
1258
  taskId: result.config.taskId,
1780
1259
  attempts: result.attempts
1781
1260
  });
1782
1261
  }
1783
1262
  function isExpectedAbortError(error) {
1784
- const message = error.message || "";
1785
- const hasAbortMessage = /operation aborted/i.test(message) || /abort/i.test(message);
1263
+ const message2 = error.message || "";
1264
+ const hasAbortMessage = /operation aborted/i.test(message2) || /abort/i.test(message2);
1786
1265
  const hasAbortCode = !("code" in error) || error.code === void 0 || error.code === "ABORT_ERR";
1787
1266
  return hasAbortMessage && hasAbortCode;
1788
1267
  }
1789
1268
  function exitOnPermissionDenial(err) {
1790
1269
  if (!isPermissionDeniedError(err)) return false;
1791
- logger5.error("Not authorized for this project \u2014 parking instead of crash-looping", {
1270
+ logger6.error("Not authorized for this project \u2014 parking instead of crash-looping", {
1792
1271
  error: err.message
1793
1272
  });
1794
1273
  logAgentExit("unauthorized", { exitCode: 0 });
@@ -1797,48 +1276,48 @@ function exitOnPermissionDenial(err) {
1797
1276
  process.on("uncaughtException", (err) => {
1798
1277
  if (err.code === "EPIPE") return;
1799
1278
  if (shuttingDown && isExpectedAbortError(err)) {
1800
- logger5.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
1279
+ logger6.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
1801
1280
  return;
1802
1281
  }
1803
1282
  if (exitOnPermissionDenial(err)) return;
1804
- logger5.error("Uncaught exception", { error: err.message, code: err.code });
1283
+ logger6.error("Uncaught exception", { error: err.message, code: err.code });
1805
1284
  logAgentExit("uncaught_exception", { exitCode: 1 });
1806
1285
  process.exit(1);
1807
1286
  });
1808
1287
  process.on("unhandledRejection", (reason) => {
1809
1288
  const err = reason instanceof Error ? reason : new Error(String(reason));
1810
1289
  if (shuttingDown && isExpectedAbortError(err)) {
1811
- logger5.info("Ignored expected abort rejection after shutdown", { error: err.message });
1290
+ logger6.info("Ignored expected abort rejection after shutdown", { error: err.message });
1812
1291
  return;
1813
1292
  }
1814
1293
  if (exitOnPermissionDenial(err)) return;
1815
- logger5.error("Unhandled rejection", { error: err.message });
1294
+ logger6.error("Unhandled rejection", { error: err.message });
1816
1295
  logAgentExit("unhandled_rejection", { exitCode: 1 });
1817
1296
  process.exit(1);
1818
1297
  });
1819
1298
  if (process.env.CONVEYOR_MODE === "workbench") {
1820
- const { startWorkbenchServer } = await import("./server-CC7KUJOK.js");
1299
+ const { startWorkbenchServer } = await import("./server-7XH7RYUX.js");
1821
1300
  const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
1822
1301
  const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
1823
1302
  const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
1824
1303
  const token = process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? "";
1825
1304
  if (!token) {
1826
- logger5.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
1305
+ logger6.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
1827
1306
  process.exit(1);
1828
1307
  }
1829
- const pkgDir = dirname2(fileURLToPath(import.meta.url));
1830
- const pkg = JSON.parse(readFileSync(join4(pkgDir, "..", "package.json"), "utf-8"));
1308
+ const pkgDir = dirname(fileURLToPath(import.meta.url));
1309
+ const pkg = JSON.parse(readFileSync(join2(pkgDir, "..", "package.json"), "utf-8"));
1831
1310
  const handle = await startWorkbenchServer({
1832
1311
  port,
1833
1312
  token,
1834
1313
  version: pkg.version ?? "unknown",
1835
1314
  oomWatchdog: oomWatchdogOptionsFromEnv({
1836
- info: (m) => logger5.info(m),
1837
- warn: (m) => logger5.warn(m),
1838
- error: (m) => logger5.error(m)
1315
+ info: (m) => logger6.info(m),
1316
+ warn: (m) => logger6.warn(m),
1317
+ error: (m) => logger6.error(m)
1839
1318
  })
1840
1319
  });
1841
- logger5.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
1320
+ logger6.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
1842
1321
  process.on("SIGTERM", () => process.exit(0));
1843
1322
  await new Promise(() => {
1844
1323
  });
@@ -1848,7 +1327,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
1848
1327
  var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
1849
1328
  if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
1850
1329
  if (!conveyorApiUrl) {
1851
- logger5.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
1330
+ logger6.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
1852
1331
  process.exit(1);
1853
1332
  }
1854
1333
  await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
@@ -1866,7 +1345,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
1866
1345
  exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
1867
1346
  var projectIdentity = resolveProjectRunnerIdentity(process.env);
1868
1347
  if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
1869
- logger5.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
1348
+ logger6.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
1870
1349
  const adhocRunner = new AdhocSessionRunner(
1871
1350
  {
1872
1351
  connection: {
@@ -1885,7 +1364,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
1885
1364
  },
1886
1365
  {
1887
1366
  onEvent: (event) => {
1888
- logger5.info("Ad-hoc runner event", { eventType: event.type });
1367
+ logger6.info("Ad-hoc runner event", { eventType: event.type });
1889
1368
  }
1890
1369
  }
1891
1370
  );
@@ -1909,7 +1388,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
1909
1388
  process.exit(adhocError ? 1 : 0);
1910
1389
  }
1911
1390
  if (!CONVEYOR_TASK_ID && projectIdentity) {
1912
- logger5.info("Starting project agent", { projectId: projectIdentity.projectId });
1391
+ logger6.info("Starting project agent", { projectId: projectIdentity.projectId });
1913
1392
  const projectRunner = new ProjectSessionRunner(
1914
1393
  {
1915
1394
  connection: {
@@ -1924,7 +1403,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
1924
1403
  },
1925
1404
  {
1926
1405
  onEvent: (event) => {
1927
- logger5.info("Project runner event", { eventType: event.type });
1406
+ logger6.info("Project runner event", { eventType: event.type });
1928
1407
  }
1929
1408
  }
1930
1409
  );
@@ -1948,31 +1427,46 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
1948
1427
  process.exit(projectError ? 1 : 0);
1949
1428
  }
1950
1429
  if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
1951
- logger5.error("Missing required environment variables");
1952
- logger5.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
1953
- logger5.error(" CONVEYOR_TASK_ID - ID of the task to execute");
1954
- logger5.error("");
1955
- logger5.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
1956
- logger5.error("");
1957
- logger5.error("Optional:");
1958
- logger5.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
1959
- logger5.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
1960
- logger5.error(
1430
+ logger6.error("Missing required environment variables");
1431
+ logger6.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
1432
+ logger6.error(" CONVEYOR_TASK_ID - ID of the task to execute");
1433
+ logger6.error("");
1434
+ logger6.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
1435
+ logger6.error("");
1436
+ logger6.error("Optional:");
1437
+ logger6.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
1438
+ logger6.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
1439
+ logger6.error(
1961
1440
  " Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
1962
1441
  );
1963
1442
  process.exit(1);
1964
1443
  }
1965
- if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack" && CONVEYOR_MODE !== "shell") {
1966
- logger5.error("Invalid CONVEYOR_MODE", {
1967
- mode: CONVEYOR_MODE,
1968
- expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
1969
- });
1444
+ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
1445
+ logger6.error("Invalid CONVEYOR_MODE", { mode: CONVEYOR_MODE, expected: RUNNER_MODES });
1970
1446
  process.exit(1);
1971
1447
  }
1448
+ if (CONVEYOR_MODE === "serving") {
1449
+ const { runServingSession } = await import("./serve-boot-YPAUKENG.js");
1450
+ exitContext.runnerMode = "serving";
1451
+ exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
1452
+ const outcome = await runServingSession({
1453
+ apiUrl: conveyorApiUrl ?? "",
1454
+ taskToken: CONVEYOR_TASK_TOKEN,
1455
+ sessionId: process.env.CONVEYOR_SESSION_ID,
1456
+ taskId: CONVEYOR_TASK_ID,
1457
+ workspaceDir: CONVEYOR_WORKSPACE,
1458
+ // BRANCH is the bundle gitPlan's branch (boot/bundle.ts buildChildEnv):
1459
+ // the branch the preview must check out and serve.
1460
+ branch: process.env.BRANCH,
1461
+ onSignal: () => void (shuttingDown = true)
1462
+ });
1463
+ logAgentExit(outcome.reason, { exitCode: outcome.exitCode, finalState: outcome.finalState });
1464
+ process.exit(outcome.exitCode);
1465
+ }
1972
1466
  if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
1973
1467
  const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
1974
1468
  if (!spawnedSessionId) {
1975
- logger5.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
1469
+ logger6.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
1976
1470
  process.exit(1);
1977
1471
  }
1978
1472
  exitContext.sessionId = spawnedSessionId;
@@ -1985,7 +1479,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
1985
1479
  workspaceDir: CONVEYOR_WORKSPACE,
1986
1480
  projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
1987
1481
  });
1988
- logger5.info("Starting spawned session child", {
1482
+ logger6.info("Starting spawned session child", {
1989
1483
  mode: CONVEYOR_MODE,
1990
1484
  sessionId: spawnedSessionId
1991
1485
  });
@@ -2005,7 +1499,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
2005
1499
  });
2006
1500
  process.exit(childError ? 1 : 0);
2007
1501
  }
2008
- logger5.info("Starting agent", { mode: CONVEYOR_MODE });
1502
+ logger6.info("Starting agent", { mode: CONVEYOR_MODE });
2009
1503
  var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
2010
1504
  var runner = new SessionRunner(
2011
1505
  {
@@ -2018,6 +1512,10 @@ var runner = new SessionRunner(
2018
1512
  runnerMode: CONVEYOR_MODE
2019
1513
  },
2020
1514
  runnerMode: CONVEYOR_MODE,
1515
+ // Only read when the mode is "pack". Parsed rather than cast so an unset or
1516
+ // malformed value falls back to single-pod instead of selecting a third,
1517
+ // non-existent behavior.
1518
+ packExecution: parsePackExecution(process.env.CONVEYOR_PACK_EXECUTION),
2021
1519
  isAuto: CONVEYOR_IS_AUTO,
2022
1520
  workspaceDir: CONVEYOR_WORKSPACE,
2023
1521
  agentMode: CONVEYOR_AGENT_MODE,
@@ -2025,29 +1523,51 @@ var runner = new SessionRunner(
2025
1523
  },
2026
1524
  {
2027
1525
  onStatusChange: (status) => {
2028
- logger5.info("Status changed", { status });
1526
+ logger6.info("Status changed", { status });
2029
1527
  },
2030
1528
  onEvent: (event) => {
2031
1529
  const detail = event.message ?? event.content ?? event.summary ?? "";
2032
1530
  if (detail) {
2033
- logger5.info(detail, { eventType: event.type });
1531
+ logger6.info(detail, { eventType: event.type });
2034
1532
  }
2035
1533
  }
2036
1534
  }
2037
1535
  );
2038
1536
  var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
2039
1537
  var sessionChildren = new SessionChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
1538
+ var builderChildren = new BuilderChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
1539
+ runner.setLiveChildSources([
1540
+ {
1541
+ kind: "builder",
1542
+ activeSessionIds: () => {
1543
+ const id = builderChildren.activeSessionId;
1544
+ return id ? [id] : [];
1545
+ }
1546
+ },
1547
+ {
1548
+ kind: "review",
1549
+ activeSessionIds: () => {
1550
+ const id = reviewChildren.activeSessionId;
1551
+ return id ? [id] : [];
1552
+ }
1553
+ },
1554
+ { kind: "tui", activeSessionIds: () => sessionChildren.activeSessionIds }
1555
+ ]);
2040
1556
  var shutdownSignal;
2041
1557
  var workspaceCommandSupervisor = null;
2042
1558
  var shutdownCompletion = null;
2043
1559
  var shutdownAgent = (signal) => {
2044
1560
  shuttingDown = true;
2045
- logger5.info(`Received ${signal}, flushing git and stopping agent`);
1561
+ logger6.info(`Received ${signal}, flushing git and stopping agent`);
2046
1562
  const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
2047
1563
  shutdownSignal = signal;
2048
1564
  shutdownCompletion ??= (async () => {
2049
1565
  try {
2050
- await Promise.all([reviewChildren.stopAll(), sessionChildren.stopAll()]);
1566
+ await Promise.all([
1567
+ reviewChildren.stopAll(),
1568
+ sessionChildren.stopAll(),
1569
+ builderChildren.stopCurrent("agent shutdown")
1570
+ ]);
2051
1571
  } catch {
2052
1572
  }
2053
1573
  try {
@@ -2059,8 +1579,8 @@ var shutdownAgent = (signal) => {
2059
1579
  })();
2060
1580
  void shutdownCompletion;
2061
1581
  setTimeout(() => {
2062
- logger5.warn(`Forcing exit after ${signal} timeout`);
2063
- logger5.warn(
1582
+ logger6.warn(`Forcing exit after ${signal} timeout`);
1583
+ logger6.warn(
2064
1584
  "agent_exit",
2065
1585
  buildAgentExitLog({
2066
1586
  reason: "force_timeout",
@@ -2086,7 +1606,7 @@ workspaceCommandSupervisor = await startWorkspaceCommandsAfterConnect({
2086
1606
  })
2087
1607
  });
2088
1608
  if (!workspaceCommandSupervisor) {
2089
- logger5.info("Agent stopped before workspace commands were started");
1609
+ logger6.info("Agent stopped before workspace commands were started");
2090
1610
  if (shutdownCompletion) await shutdownCompletion;
2091
1611
  logAgentExit(shutdownSignal ? "signal" : "clean", {
2092
1612
  exitCode: 0,
@@ -2106,34 +1626,14 @@ void checkSessionTaskIdentity({
2106
1626
  });
2107
1627
  return ctx.id;
2108
1628
  },
2109
- logger: logger5
1629
+ logger: logger6
2110
1630
  });
2111
- if (hostsSpawnedChildren(CONVEYOR_MODE)) {
2112
- runner.connection.onSpawnReview((data) => {
2113
- void reviewChildren.spawn(data).catch((err) => {
2114
- logger5.error("Review child spawn threw", {
2115
- error: err instanceof Error ? err.message : String(err)
2116
- });
2117
- runner.connection.reportReviewSpawnFailure(
2118
- data.sessionId,
2119
- err instanceof Error ? err.message : String(err)
2120
- );
2121
- });
2122
- });
2123
- }
2124
- if (hostsSpawnedChildren(CONVEYOR_MODE)) {
2125
- runner.connection.onSpawnTui((data) => {
2126
- void sessionChildren.spawn(data).catch((err) => {
2127
- logger5.error("Session child spawn threw", {
2128
- error: err instanceof Error ? err.message : String(err)
2129
- });
2130
- runner.connection.reportSessionSpawnFailure(
2131
- data.sessionId,
2132
- err instanceof Error ? err.message : String(err)
2133
- );
2134
- });
2135
- });
2136
- }
1631
+ wireSpawnChildren(
1632
+ CONVEYOR_MODE,
1633
+ runner.connection,
1634
+ { review: reviewChildren, builder: builderChildren, session: sessionChildren },
1635
+ logger6
1636
+ );
2137
1637
  runner.run().then(async () => {
2138
1638
  await stopWorkspaceCommands(workspaceCommandSupervisor);
2139
1639
  const errored = runner.finalState === "error";
@@ -2147,7 +1647,7 @@ runner.run().then(async () => {
2147
1647
  }).catch(async (error) => {
2148
1648
  await stopWorkspaceCommands(workspaceCommandSupervisor);
2149
1649
  const msg = error instanceof Error ? error.message : String(error);
2150
- logger5.error("Agent runner failed", { error: msg });
1650
+ logger6.error("Agent runner failed", { error: msg });
2151
1651
  logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
2152
1652
  process.exit(1);
2153
1653
  });