@rallycry/conveyor-agent 10.2.1 → 10.2.4

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
@@ -13,10 +13,12 @@ import {
13
13
  fetchBootstrap,
14
14
  loadConveyorConfig,
15
15
  loadForwardPorts,
16
+ resolveSessionStart,
16
17
  runSetupCommand,
17
18
  runStartCommand,
18
19
  sampleKeyUsage
19
- } from "./chunk-YGLTLUGA.js";
20
+ } from "./chunk-DEMRCBJN.js";
21
+ import "./chunk-7TQO4ZF4.js";
20
22
 
21
23
  // src/cli.ts
22
24
  import { readFileSync } from "fs";
@@ -144,14 +146,14 @@ async function waitForSidecars(opts = {}) {
144
146
 
145
147
  // src/utils/session-identity.ts
146
148
  async function checkSessionTaskIdentity(params) {
147
- const { sessionId, taskId, fetchSessionTaskId, logger: logger2 } = params;
149
+ const { sessionId, taskId, fetchSessionTaskId, logger: logger3 } = params;
148
150
  if (!sessionId || sessionId === taskId) return "skipped";
149
151
  let sessionTaskId;
150
152
  try {
151
153
  sessionTaskId = await fetchSessionTaskId(sessionId);
152
154
  } catch (err) {
153
155
  const message = err instanceof Error ? err.message : String(err);
154
- logger2.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
156
+ logger3.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
155
157
  sessionId,
156
158
  taskId,
157
159
  error: message
@@ -159,7 +161,7 @@ async function checkSessionTaskIdentity(params) {
159
161
  return "error";
160
162
  }
161
163
  if (sessionTaskId !== taskId) {
162
- logger2.warn(
164
+ logger3.warn(
163
165
  "!!! 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.",
164
166
  { sessionId, envTaskId: taskId, sessionTaskId }
165
167
  );
@@ -168,15 +170,41 @@ async function checkSessionTaskIdentity(params) {
168
170
  return "match";
169
171
  }
170
172
 
173
+ // src/utils/agent-exit.ts
174
+ function exitLogLevel(reason) {
175
+ return reason === "clean" ? "info" : "warn";
176
+ }
177
+ function buildAgentExitLog(args) {
178
+ const log = {
179
+ event: "agent_exit",
180
+ reason: args.reason,
181
+ exitCode: args.exitCode,
182
+ uptimeSec: Math.round(args.uptimeSec * 10) / 10
183
+ };
184
+ if (args.signal !== void 0) log.signal = args.signal;
185
+ if (args.finalState !== void 0) log.finalState = args.finalState;
186
+ const id = args.identity;
187
+ if (id) {
188
+ if (id.sessionId !== void 0) log.sessionId = id.sessionId;
189
+ if (id.taskId !== void 0) log.taskId = id.taskId;
190
+ if (id.workspaceId !== void 0) log.workspaceId = id.workspaceId;
191
+ if (id.runnerMode !== void 0) log.runnerMode = id.runnerMode;
192
+ }
193
+ return log;
194
+ }
195
+
171
196
  // src/setup/project-identity.ts
172
197
  function resolveProjectRunnerIdentity(env) {
173
198
  if (env.CONVEYOR_TASK_ID) return null;
174
199
  if (!env.CONVEYOR_TASK_TOKEN) return null;
175
- if (!env.CONVEYOR_PROJECT_ID || !env.CONVEYOR_SESSION_ID) return null;
200
+ if (!env.CONVEYOR_PROJECT_ID || !env.CONVEYOR_SESSION_ID || !env.CONVEYOR_WORKSPACE_ID) {
201
+ return null;
202
+ }
176
203
  if (env.CONVEYOR_MODE !== "pm" && env.CONVEYOR_MODE !== "adhoc") return null;
177
204
  return {
178
205
  projectId: env.CONVEYOR_PROJECT_ID,
179
206
  sessionId: env.CONVEYOR_SESSION_ID,
207
+ workspaceId: env.CONVEYOR_WORKSPACE_ID,
180
208
  taskToken: env.CONVEYOR_TASK_TOKEN
181
209
  };
182
210
  }
@@ -318,11 +346,12 @@ var ADHOC_SYSTEM_NOTE = "You are running in an ad-hoc Conveyor scratch pod \u201
318
346
  function buildAdhocPtyBridge(connection) {
319
347
  return {
320
348
  sendOutput: (data, dims) => connection.sendPtyOutput(data, dims),
349
+ sendChatEvent: (event) => connection.sendPtyChatEvent(event),
321
350
  onInput: (handler) => connection.onPtyInput(handler),
322
351
  onResize: (handler) => connection.onPtyResize(handler)
323
352
  };
324
353
  }
325
- function buildAdhocQueryOptions(workspaceDir, model, abortController) {
354
+ function buildAdhocQueryOptions(workspaceDir, model, abortController, session) {
326
355
  return {
327
356
  model,
328
357
  systemPrompt: { type: "preset", preset: "claude_code", append: ADHOC_SYSTEM_NOTE },
@@ -336,7 +365,9 @@ function buildAdhocQueryOptions(workspaceDir, model, abortController) {
336
365
  settingSources: ["user", "project"],
337
366
  // Empty box, unsubmitted: the human types the first prompt themselves.
338
367
  promptDelivery: "prefill",
339
- abortController
368
+ abortController,
369
+ ...session?.sessionId ? { sessionId: session.sessionId } : {},
370
+ ...session?.resume ? { resume: session.resume } : {}
340
371
  };
341
372
  }
342
373
  var AdhocSessionRunner = class {
@@ -414,7 +445,13 @@ var AdhocSessionRunner = class {
414
445
  */
415
446
  async runInteractiveTui() {
416
447
  const model = this.config.model ?? process.env.CONVEYOR_ADHOC_MODEL ?? DEFAULT_SONNET_MODEL;
417
- const options = buildAdhocQueryOptions(this.config.workspaceDir, model, this.abortController);
448
+ const session = resolveSessionStart(this.config.workspaceId, this.config.workspaceDir);
449
+ const options = buildAdhocQueryOptions(
450
+ this.config.workspaceDir,
451
+ model,
452
+ this.abortController,
453
+ session
454
+ );
418
455
  try {
419
456
  for await (const _event of this.harness.executeQuery({ prompt: "", options })) {
420
457
  if (this.stopped) break;
@@ -456,6 +493,163 @@ var AdhocSessionRunner = class {
456
493
  }
457
494
  };
458
495
 
496
+ // src/runner/review-child.ts
497
+ import { spawn as nodeSpawn, execFile } from "child_process";
498
+ import { promisify } from "util";
499
+ var execFileAsync = promisify(execFile);
500
+ var logger = createServiceLogger("ReviewChild");
501
+ var SPAWN_FAILURE_GRACE_MS = 15e3;
502
+ var KILL_WAIT_MS = 5e3;
503
+ function buildReviewChildEnv(baseEnv, data) {
504
+ const env = { ...baseEnv };
505
+ env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
506
+ env.CONVEYOR_SESSION_ID = data.sessionId;
507
+ env.CONVEYOR_MODE = "code-review";
508
+ delete env.POD_BOOTSTRAP_TOKEN;
509
+ delete env.CONVEYOR_BOOTSTRAP_TOKEN;
510
+ delete env.CONVEYOR_SETUP_COMMAND;
511
+ delete env.CONVEYOR_START_COMMAND;
512
+ delete env.CONVEYOR_AGENT_MODE;
513
+ delete env.CONVEYOR_IS_AUTO;
514
+ return env;
515
+ }
516
+ var ReviewChildSupervisor = class {
517
+ constructor(connection, workspaceDir, spawnFn = nodeSpawn) {
518
+ this.connection = connection;
519
+ this.workspaceDir = workspaceDir;
520
+ this.spawnFn = spawnFn;
521
+ }
522
+ connection;
523
+ workspaceDir;
524
+ spawnFn;
525
+ child = null;
526
+ childSessionId = null;
527
+ /** Spawn (or replace) the review child for a `session:spawnReview` push. */
528
+ async spawn(data) {
529
+ logger.info("Spawning same-pod review child", {
530
+ reviewSessionId: data.sessionId,
531
+ branch: data.branch ?? void 0,
532
+ prNumber: data.prNumber ?? void 0
533
+ });
534
+ await this.stopCurrent("superseded by new review spawn");
535
+ await this.refreshCheckout(data.branch ?? null);
536
+ const env = buildReviewChildEnv(process.env, data);
537
+ const cliPath = process.argv[1];
538
+ if (!cliPath) {
539
+ this.connection.reportReviewSpawnFailure(data.sessionId, "cannot resolve CLI entry path");
540
+ return;
541
+ }
542
+ let child;
543
+ try {
544
+ child = this.spawnFn(process.execPath, [cliPath], {
545
+ cwd: this.workspaceDir,
546
+ env,
547
+ stdio: ["ignore", "pipe", "pipe"]
548
+ });
549
+ } catch (err) {
550
+ this.connection.reportReviewSpawnFailure(
551
+ data.sessionId,
552
+ err instanceof Error ? err.message : String(err)
553
+ );
554
+ return;
555
+ }
556
+ this.child = child;
557
+ this.childSessionId = data.sessionId;
558
+ const spawnedAt = Date.now();
559
+ let reported = false;
560
+ const reportOnce = (message) => {
561
+ if (reported) return;
562
+ reported = true;
563
+ this.connection.reportReviewSpawnFailure(data.sessionId, message);
564
+ };
565
+ child.stdout?.on("data", (chunk) => {
566
+ process.stdout.write(`[review-child] ${chunk.toString()}`);
567
+ });
568
+ child.stderr?.on("data", (chunk) => {
569
+ process.stderr.write(`[review-child] ${chunk.toString()}`);
570
+ });
571
+ child.on("error", (err) => {
572
+ logger.error("Review child spawn error", { error: err.message });
573
+ if (this.child === child) {
574
+ this.child = null;
575
+ this.childSessionId = null;
576
+ }
577
+ reportOnce(err.message);
578
+ });
579
+ child.on("exit", (code, signal) => {
580
+ const wasCurrent = this.child === child;
581
+ if (wasCurrent) {
582
+ this.child = null;
583
+ this.childSessionId = null;
584
+ }
585
+ logger.info("Review child exited", { code, signal, reviewSessionId: data.sessionId });
586
+ const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
587
+ if (wasCurrent && withinGrace && code !== null && code !== 0) {
588
+ reportOnce(`review child exited with code ${code} within startup grace`);
589
+ }
590
+ });
591
+ }
592
+ /** Stop any live child (parent shutdown / supersede). Best-effort. */
593
+ async stopAll() {
594
+ await this.stopCurrent("parent shutting down");
595
+ }
596
+ async stopCurrent(reason) {
597
+ const child = this.child;
598
+ if (!child || child.exitCode !== null || child.killed) {
599
+ this.child = null;
600
+ this.childSessionId = null;
601
+ return;
602
+ }
603
+ logger.info("Stopping review child", { reason, reviewSessionId: this.childSessionId });
604
+ this.child = null;
605
+ this.childSessionId = null;
606
+ await new Promise((resolve) => {
607
+ const timer = setTimeout(() => {
608
+ try {
609
+ child.kill("SIGKILL");
610
+ } catch {
611
+ }
612
+ resolve();
613
+ }, KILL_WAIT_MS);
614
+ timer.unref();
615
+ child.once("exit", () => {
616
+ clearTimeout(timer);
617
+ resolve();
618
+ });
619
+ try {
620
+ child.kill("SIGTERM");
621
+ } catch {
622
+ clearTimeout(timer);
623
+ resolve();
624
+ }
625
+ });
626
+ }
627
+ /**
628
+ * Best-effort branch freshen before the review reads the tree: fetch the PR
629
+ * branch and fast-forward only (never clobber local work — the builder's
630
+ * checkout is the source of truth on conflict). Failures are logged and
631
+ * skipped; the reviewer still reads the diff via the GitHub API.
632
+ */
633
+ async refreshCheckout(branch) {
634
+ if (!branch) return;
635
+ try {
636
+ await execFileAsync("git", ["fetch", "origin", branch], {
637
+ cwd: this.workspaceDir,
638
+ timeout: 6e4
639
+ });
640
+ await execFileAsync("git", ["merge", "--ff-only", `origin/${branch}`], {
641
+ cwd: this.workspaceDir,
642
+ timeout: 3e4
643
+ });
644
+ } catch (err) {
645
+ logger.warn("Review checkout freshen skipped", {
646
+ branch,
647
+ error: err instanceof Error ? err.message : String(err)
648
+ });
649
+ }
650
+ }
651
+ };
652
+
459
653
  // src/cli.ts
460
654
  if (process.argv.includes("--version")) {
461
655
  const __dirname = dirname2(fileURLToPath(import.meta.url));
@@ -464,11 +658,26 @@ if (process.argv.includes("--version")) {
464
658
  process.stdout.write(pkg.version + "\n");
465
659
  process.exit(0);
466
660
  }
467
- var logger = createServiceLogger("CLI");
661
+ var logger2 = createServiceLogger("CLI");
662
+ var exitContext = {};
663
+ var exitLogged = false;
664
+ function logAgentExit(reason, opts) {
665
+ if (exitLogged) return;
666
+ exitLogged = true;
667
+ const payload = buildAgentExitLog({
668
+ reason,
669
+ exitCode: opts.exitCode,
670
+ signal: opts.signal,
671
+ finalState: opts.finalState,
672
+ uptimeSec: process.uptime(),
673
+ identity: exitContext
674
+ });
675
+ logger2[exitLogLevel(reason)]("agent_exit", payload);
676
+ }
468
677
  async function bootstrapFromCodespace(apiUrl, instanceName) {
469
678
  const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
470
679
  const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
471
- logger.info("Bootstrapping from codespace", {
680
+ logger2.info("Bootstrapping from codespace", {
472
681
  codespace: instanceName,
473
682
  apiUrl,
474
683
  apiUrlFromEnv,
@@ -480,7 +689,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
480
689
  bootstrapToken
481
690
  });
482
691
  if (!result.ok) {
483
- logger.error("Bootstrap failed after retries", {
692
+ logger2.error("Bootstrap failed after retries", {
484
693
  reason: result.reason,
485
694
  attempts: result.attempts,
486
695
  status: result.status,
@@ -493,7 +702,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
493
702
  process.exit(1);
494
703
  }
495
704
  applyBootstrapToEnv(result.config);
496
- logger.info("Bootstrap complete", {
705
+ logger2.info("Bootstrap complete", {
497
706
  taskId: result.config.taskId,
498
707
  attempts: result.attempts
499
708
  });
@@ -507,19 +716,21 @@ function isExpectedAbortError(error) {
507
716
  process.on("uncaughtException", (err) => {
508
717
  if (err.code === "EPIPE") return;
509
718
  if (isExpectedAbortError(err)) {
510
- logger.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
719
+ logger2.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
511
720
  return;
512
721
  }
513
- logger.error("Uncaught exception", { error: err.message, code: err.code });
722
+ logger2.error("Uncaught exception", { error: err.message, code: err.code });
723
+ logAgentExit("uncaught_exception", { exitCode: 1 });
514
724
  process.exit(1);
515
725
  });
516
726
  process.on("unhandledRejection", (reason) => {
517
727
  const err = reason instanceof Error ? reason : new Error(String(reason));
518
728
  if (isExpectedAbortError(err)) {
519
- logger.info("Ignored expected abort rejection after shutdown", { error: err.message });
729
+ logger2.info("Ignored expected abort rejection after shutdown", { error: err.message });
520
730
  return;
521
731
  }
522
- logger.error("Unhandled rejection", { error: err.message });
732
+ logger2.error("Unhandled rejection", { error: err.message });
733
+ logAgentExit("unhandled_rejection", { exitCode: 1 });
523
734
  process.exit(1);
524
735
  });
525
736
  var DEFAULT_CONVEYOR_API_URL = "https://api.conveyor.rallycryapp.com";
@@ -527,7 +738,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
527
738
  var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
528
739
  if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
529
740
  if (!conveyorApiUrl) {
530
- logger.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
741
+ logger2.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
531
742
  process.exit(1);
532
743
  }
533
744
  await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
@@ -539,9 +750,13 @@ var CONVEYOR_WORKSPACE = process.env.CONVEYOR_WORKSPACE ?? process.cwd();
539
750
  var CONVEYOR_MODE = process.env.CONVEYOR_MODE ?? "task";
540
751
  var CONVEYOR_AGENT_MODE = process.env.CONVEYOR_AGENT_MODE || void 0;
541
752
  var CONVEYOR_IS_AUTO = CONVEYOR_AGENT_MODE ? CONVEYOR_AGENT_MODE === "auto" : process.env.CONVEYOR_IS_AUTO === "true";
753
+ exitContext.taskId = CONVEYOR_TASK_ID;
754
+ exitContext.workspaceId = process.env.CONVEYOR_WORKSPACE_ID;
755
+ exitContext.runnerMode = CONVEYOR_MODE;
756
+ exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
542
757
  var projectIdentity = resolveProjectRunnerIdentity(process.env);
543
758
  if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
544
- logger.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
759
+ logger2.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
545
760
  const adhocRunner = new AdhocSessionRunner(
546
761
  {
547
762
  connection: {
@@ -551,22 +766,31 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
551
766
  runnerMode: "adhoc"
552
767
  },
553
768
  projectId: projectIdentity.projectId,
769
+ workspaceId: projectIdentity.workspaceId,
554
770
  workspaceDir: CONVEYOR_WORKSPACE,
555
771
  ...process.env.CLAUDESPACE_NAME ? { lifecycle: { idleTimeoutMs: 60 * 60 * 1e3 } } : {}
556
772
  },
557
773
  {
558
774
  onEvent: (event) => {
559
- logger.info("Ad-hoc runner event", { eventType: event.type });
775
+ logger2.info("Ad-hoc runner event", { eventType: event.type });
560
776
  }
561
777
  }
562
778
  );
779
+ exitContext.sessionId = projectIdentity.sessionId;
780
+ exitContext.workspaceId = projectIdentity.workspaceId;
781
+ exitContext.runnerMode = "adhoc";
563
782
  process.on("SIGTERM", () => adhocRunner.stop());
564
783
  process.on("SIGINT", () => adhocRunner.stop());
565
784
  await adhocRunner.run();
566
- process.exit(adhocRunner.finalState === "error" ? 1 : 0);
785
+ const adhocError = adhocRunner.finalState === "error";
786
+ logAgentExit(adhocError ? "error" : "clean", {
787
+ exitCode: adhocError ? 1 : 0,
788
+ finalState: adhocRunner.finalState ?? void 0
789
+ });
790
+ process.exit(adhocError ? 1 : 0);
567
791
  }
568
792
  if (!CONVEYOR_TASK_ID && projectIdentity) {
569
- logger.info("Starting project agent", { projectId: projectIdentity.projectId });
793
+ logger2.info("Starting project agent", { projectId: projectIdentity.projectId });
570
794
  const projectRunner = new ProjectSessionRunner(
571
795
  {
572
796
  connection: {
@@ -581,38 +805,46 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
581
805
  },
582
806
  {
583
807
  onEvent: (event) => {
584
- logger.info("Project runner event", { eventType: event.type });
808
+ logger2.info("Project runner event", { eventType: event.type });
585
809
  }
586
810
  }
587
811
  );
812
+ exitContext.sessionId = projectIdentity.sessionId;
813
+ exitContext.workspaceId = projectIdentity.workspaceId;
814
+ exitContext.runnerMode = "pm";
588
815
  process.on("SIGTERM", () => projectRunner.stop());
589
816
  process.on("SIGINT", () => projectRunner.stop());
590
817
  await projectRunner.run();
591
- process.exit(projectRunner.finalState === "error" ? 1 : 0);
818
+ const projectError = projectRunner.finalState === "error";
819
+ logAgentExit(projectError ? "error" : "clean", {
820
+ exitCode: projectError ? 1 : 0,
821
+ finalState: projectRunner.finalState ?? void 0
822
+ });
823
+ process.exit(projectError ? 1 : 0);
592
824
  }
593
825
  if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
594
- logger.error("Missing required environment variables");
595
- logger.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
596
- logger.error(" CONVEYOR_TASK_ID - ID of the task to execute");
597
- logger.error("");
598
- logger.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
599
- logger.error("");
600
- logger.error("Optional:");
601
- logger.error(" CONVEYOR_MODE - Runner mode: 'task' (default) or 'pm'");
602
- logger.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
603
- logger.error(
826
+ logger2.error("Missing required environment variables");
827
+ logger2.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
828
+ logger2.error(" CONVEYOR_TASK_ID - ID of the task to execute");
829
+ logger2.error("");
830
+ logger2.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
831
+ logger2.error("");
832
+ logger2.error("Optional:");
833
+ logger2.error(" CONVEYOR_MODE - Runner mode: 'task' (default) or 'pm'");
834
+ logger2.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
835
+ logger2.error(
604
836
  " Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
605
837
  );
606
838
  process.exit(1);
607
839
  }
608
840
  if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc") {
609
- logger.error("Invalid CONVEYOR_MODE", {
841
+ logger2.error("Invalid CONVEYOR_MODE", {
610
842
  mode: CONVEYOR_MODE,
611
843
  expected: ["task", "pm", "code-review", "adhoc"]
612
844
  });
613
845
  process.exit(1);
614
846
  }
615
- logger.info("Starting agent", { mode: CONVEYOR_MODE });
847
+ logger2.info("Starting agent", { mode: CONVEYOR_MODE });
616
848
  var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
617
849
  var runner = new SessionRunner(
618
850
  {
@@ -632,19 +864,26 @@ var runner = new SessionRunner(
632
864
  },
633
865
  {
634
866
  onStatusChange: (status) => {
635
- logger.info("Status changed", { status });
867
+ logger2.info("Status changed", { status });
636
868
  },
637
869
  onEvent: (event) => {
638
870
  const detail = event.message ?? event.content ?? event.summary ?? "";
639
871
  if (detail) {
640
- logger.info(detail, { eventType: event.type });
872
+ logger2.info(detail, { eventType: event.type });
641
873
  }
642
874
  }
643
875
  }
644
876
  );
877
+ var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
878
+ var shutdownSignal;
645
879
  var shutdownAgent = (signal) => {
646
- logger.info(`Received ${signal}, flushing git and stopping agent`);
880
+ logger2.info(`Received ${signal}, flushing git and stopping agent`);
881
+ shutdownSignal = signal;
647
882
  void (async () => {
883
+ try {
884
+ await reviewChildren.stopAll();
885
+ } catch {
886
+ }
648
887
  try {
649
888
  await runner.flushGitOnShutdown();
650
889
  } finally {
@@ -652,7 +891,18 @@ var shutdownAgent = (signal) => {
652
891
  }
653
892
  })();
654
893
  setTimeout(() => {
655
- logger.warn(`Forcing exit after ${signal} timeout`);
894
+ logger2.warn(`Forcing exit after ${signal} timeout`);
895
+ logger2.warn(
896
+ "agent_exit",
897
+ buildAgentExitLog({
898
+ reason: "force_timeout",
899
+ exitCode: 1,
900
+ signal,
901
+ finalState: runner.finalState ?? void 0,
902
+ uptimeSec: process.uptime(),
903
+ identity: exitContext
904
+ })
905
+ );
656
906
  process.exit(1);
657
907
  }, 45e3).unref();
658
908
  };
@@ -680,7 +930,7 @@ var stopStartCommandChild = () => {
680
930
  }
681
931
  };
682
932
  var launchStartCommand = (command) => {
683
- logger.info("Running start command", { command });
933
+ logger2.info("Running start command", { command });
684
934
  runner.connection.sendEvent({ type: "start_command_started" });
685
935
  const child = runStartCommand(command, CONVEYOR_WORKSPACE, (stream, data) => {
686
936
  runner.connection.sendEvent({ type: "start_command_output", stream, data });
@@ -690,7 +940,7 @@ var launchStartCommand = (command) => {
690
940
  child.on("exit", (code, signal) => {
691
941
  if (startCommandChild === child) startCommandChild = null;
692
942
  if (expectedStartCommandStops.has(child)) return;
693
- logger.info("Start command exited", { code, signal });
943
+ logger2.info("Start command exited", { code, signal });
694
944
  runner.connection.sendEvent({
695
945
  type: "start_command_exited",
696
946
  code,
@@ -706,7 +956,7 @@ var launchStartCommand = (command) => {
706
956
  });
707
957
  child.on("error", (err) => {
708
958
  if (startCommandChild === child) startCommandChild = null;
709
- logger.error("Start command error", { error: err.message });
959
+ logger2.error("Start command error", { error: err.message });
710
960
  runner.connection.sendEvent({ type: "start_command_error", message: err.message });
711
961
  });
712
962
  };
@@ -722,8 +972,21 @@ void checkSessionTaskIdentity({
722
972
  const ctx = await runner.connection.call("getTaskContext", { sessionId });
723
973
  return ctx.id;
724
974
  },
725
- logger
975
+ logger: logger2
726
976
  });
977
+ if (CONVEYOR_MODE === "task") {
978
+ runner.connection.onSpawnReview((data) => {
979
+ void reviewChildren.spawn(data).catch((err) => {
980
+ logger2.error("Review child spawn threw", {
981
+ error: err instanceof Error ? err.message : String(err)
982
+ });
983
+ runner.connection.reportReviewSpawnFailure(
984
+ data.sessionId,
985
+ err instanceof Error ? err.message : String(err)
986
+ );
987
+ });
988
+ });
989
+ }
727
990
  var conveyorConfig = loadConveyorConfig();
728
991
  runner.connection.onRunStartCommand(() => {
729
992
  if (!conveyorConfig?.startCommand) return;
@@ -754,15 +1017,15 @@ if (conveyorConfig) {
754
1017
  startLazy: false
755
1018
  });
756
1019
  if (conveyorConfig.setupCommand) {
757
- logger.info("Running setup command (background)", {
1020
+ logger2.info("Running setup command (background)", {
758
1021
  command: conveyorConfig.setupCommand
759
1022
  });
760
1023
  try {
761
1024
  await runSetupCommand(conveyorConfig.setupCommand, CONVEYOR_WORKSPACE, logSetupOutput);
762
- logger.info("Setup command completed");
1025
+ logger2.info("Setup command completed");
763
1026
  } catch (error) {
764
1027
  const msg = error instanceof Error ? error.message : "Setup command failed";
765
- logger.error("Setup command failed", { error: msg });
1028
+ logger2.error("Setup command failed", { error: msg });
766
1029
  runner.connection.sendEvent({ type: "setup_error", message: msg });
767
1030
  }
768
1031
  }
@@ -776,16 +1039,23 @@ if (conveyorConfig) {
776
1039
  runner.connection.sendEvent({
777
1040
  type: "setup_complete",
778
1041
  startCommandRunning,
779
- previewPort: conveyorConfig.previewPort,
780
1042
  ...previewPorts.length > 0 ? { previewPorts } : {}
781
1043
  });
782
1044
  })();
783
1045
  }
784
1046
  runner.run().then(() => {
785
- process.exit(runner.finalState === "error" ? 1 : 0);
1047
+ const errored = runner.finalState === "error";
1048
+ const reason = shutdownSignal ? "signal" : errored ? "error" : "clean";
1049
+ logAgentExit(reason, {
1050
+ exitCode: errored ? 1 : 0,
1051
+ signal: shutdownSignal,
1052
+ finalState: runner.finalState ?? void 0
1053
+ });
1054
+ process.exit(errored ? 1 : 0);
786
1055
  }).catch((error) => {
787
1056
  const msg = error instanceof Error ? error.message : String(error);
788
- logger.error("Agent runner failed", { error: msg });
1057
+ logger2.error("Agent runner failed", { error: msg });
1058
+ logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
789
1059
  process.exit(1);
790
1060
  });
791
1061
  //# sourceMappingURL=cli.js.map