@rallycry/conveyor-agent 10.4.4 → 10.6.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
@@ -20,12 +20,13 @@ import {
20
20
  inheritedEnv,
21
21
  loadConveyorConfig,
22
22
  loadForwardPorts,
23
+ loadPtySpawn,
23
24
  resolvePlaywrightMcpServer,
24
25
  resolveSessionStart,
25
26
  runSetupCommand,
26
27
  runStartCommand,
27
28
  sampleKeyUsage
28
- } from "./chunk-VCXKVINO.js";
29
+ } from "./chunk-5FRDOFI4.js";
29
30
  import "./chunk-7TQO4ZF4.js";
30
31
 
31
32
  // src/cli.ts
@@ -154,14 +155,14 @@ async function waitForSidecars(opts = {}) {
154
155
 
155
156
  // src/utils/session-identity.ts
156
157
  async function checkSessionTaskIdentity(params) {
157
- const { sessionId, taskId, fetchSessionTaskId, logger: logger4 } = params;
158
+ const { sessionId, taskId, fetchSessionTaskId, logger: logger6 } = params;
158
159
  if (!sessionId || sessionId === taskId) return "skipped";
159
160
  let sessionTaskId;
160
161
  try {
161
162
  sessionTaskId = await fetchSessionTaskId(sessionId);
162
163
  } catch (err) {
163
164
  const message = err instanceof Error ? err.message : String(err);
164
- logger4.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
165
+ logger6.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
165
166
  sessionId,
166
167
  taskId,
167
168
  error: message
@@ -169,7 +170,7 @@ async function checkSessionTaskIdentity(params) {
169
170
  return "error";
170
171
  }
171
172
  if (sessionTaskId !== taskId) {
172
- logger4.warn(
173
+ logger6.warn(
173
174
  "!!! 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.",
174
175
  { sessionId, envTaskId: taskId, sessionTaskId }
175
176
  );
@@ -834,6 +835,295 @@ var ReviewChildSupervisor = class {
834
835
  }
835
836
  };
836
837
 
838
+ // src/runner/session-child.ts
839
+ import { spawn as nodeSpawn2 } from "child_process";
840
+ var logger3 = createServiceLogger("SessionChild");
841
+ var KILL_WAIT_MS2 = 5e3;
842
+ function buildSpawnedChildEnv(baseEnv, data) {
843
+ const env = { ...baseEnv };
844
+ env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
845
+ env.CONVEYOR_SESSION_ID = data.sessionId;
846
+ env.CONVEYOR_MODE = data.mode;
847
+ env.CONVEYOR_PROJECT_ID = data.projectId;
848
+ delete env.POD_BOOTSTRAP_TOKEN;
849
+ delete env.CONVEYOR_BOOTSTRAP_TOKEN;
850
+ delete env.CONVEYOR_SETUP_COMMAND;
851
+ delete env.CONVEYOR_START_COMMAND;
852
+ delete env.CONVEYOR_AGENT_MODE;
853
+ delete env.CONVEYOR_IS_AUTO;
854
+ return env;
855
+ }
856
+ var SessionChildSupervisor = class {
857
+ constructor(connection, workspaceDir, spawnFn = nodeSpawn2) {
858
+ this.connection = connection;
859
+ this.workspaceDir = workspaceDir;
860
+ this.spawnFn = spawnFn;
861
+ }
862
+ connection;
863
+ workspaceDir;
864
+ spawnFn;
865
+ children = /* @__PURE__ */ new Map();
866
+ /** Live child count (for tests/diagnostics). */
867
+ get size() {
868
+ return this.children.size;
869
+ }
870
+ /** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
871
+ async spawn(data) {
872
+ logger3.info("Spawning same-pod session child", {
873
+ spawnedSessionId: data.sessionId,
874
+ mode: data.mode
875
+ });
876
+ await this.stopChild(data.sessionId, "superseded by duplicate spawn");
877
+ const env = buildSpawnedChildEnv(process.env, data);
878
+ const cliPath = process.argv[1];
879
+ if (!cliPath) {
880
+ this.connection.reportSessionSpawnFailure(data.sessionId, "cannot resolve CLI entry path");
881
+ return;
882
+ }
883
+ let child;
884
+ try {
885
+ child = this.spawnFn(process.execPath, [cliPath], {
886
+ cwd: this.workspaceDir,
887
+ env,
888
+ stdio: ["ignore", "pipe", "pipe"]
889
+ });
890
+ } catch (err) {
891
+ this.connection.reportSessionSpawnFailure(
892
+ data.sessionId,
893
+ err instanceof Error ? err.message : String(err)
894
+ );
895
+ return;
896
+ }
897
+ this.children.set(data.sessionId, child);
898
+ const spawnedAt = Date.now();
899
+ let reported = false;
900
+ const reportOnce = (message) => {
901
+ if (reported) return;
902
+ reported = true;
903
+ this.connection.reportSessionSpawnFailure(data.sessionId, message);
904
+ };
905
+ const prefix = `[${data.mode}-child]`;
906
+ child.stdout?.on("data", (chunk) => {
907
+ process.stdout.write(`${prefix} ${chunk.toString()}`);
908
+ });
909
+ child.stderr?.on("data", (chunk) => {
910
+ process.stderr.write(`${prefix} ${chunk.toString()}`);
911
+ });
912
+ child.on("error", (err) => {
913
+ logger3.error("Session child spawn error", { error: err.message });
914
+ if (this.children.get(data.sessionId) === child) {
915
+ this.children.delete(data.sessionId);
916
+ }
917
+ reportOnce(err.message);
918
+ });
919
+ child.on("exit", (code, signal) => {
920
+ const wasCurrent = this.children.get(data.sessionId) === child;
921
+ if (wasCurrent) this.children.delete(data.sessionId);
922
+ logger3.info("Session child exited", {
923
+ code,
924
+ signal,
925
+ spawnedSessionId: data.sessionId,
926
+ mode: data.mode
927
+ });
928
+ const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
929
+ if (wasCurrent && withinGrace && code !== null && code !== 0) {
930
+ reportOnce(`session child exited with code ${code} within startup grace`);
931
+ }
932
+ });
933
+ }
934
+ /** Stop every live child (parent shutdown). Best-effort. */
935
+ async stopAll() {
936
+ const ids = [...this.children.keys()];
937
+ await Promise.all(ids.map((id) => this.stopChild(id, "parent shutting down")));
938
+ }
939
+ async stopChild(sessionId, reason) {
940
+ const child = this.children.get(sessionId);
941
+ this.children.delete(sessionId);
942
+ if (!child || child.exitCode !== null || child.killed) return;
943
+ logger3.info("Stopping session child", { reason, spawnedSessionId: sessionId });
944
+ await new Promise((resolve) => {
945
+ const timer = setTimeout(() => {
946
+ try {
947
+ child.kill("SIGKILL");
948
+ } catch {
949
+ }
950
+ resolve();
951
+ }, KILL_WAIT_MS2);
952
+ timer.unref();
953
+ child.once("exit", () => {
954
+ clearTimeout(timer);
955
+ resolve();
956
+ });
957
+ try {
958
+ child.kill("SIGTERM");
959
+ } catch {
960
+ clearTimeout(timer);
961
+ resolve();
962
+ }
963
+ });
964
+ }
965
+ };
966
+
967
+ // src/runner/shell-session-runner.ts
968
+ var DEFAULT_COLS = 80;
969
+ var DEFAULT_ROWS = 24;
970
+ function buildShellEnv(baseEnv) {
971
+ const env = { ...baseEnv };
972
+ delete env.CONVEYOR_TASK_TOKEN;
973
+ delete env.CONVEYOR_SESSION_ID;
974
+ return env;
975
+ }
976
+ var ShellSessionRunner = class {
977
+ connection;
978
+ lifecycle;
979
+ config;
980
+ loadSpawn;
981
+ pty = null;
982
+ dims = { cols: DEFAULT_COLS, rows: DEFAULT_ROWS };
983
+ stopped = false;
984
+ _finalState = null;
985
+ constructor(config, deps) {
986
+ this.config = config;
987
+ this.connection = deps?.connection ?? new AgentConnection(config.connection);
988
+ this.loadSpawn = deps?.loadSpawn ?? loadPtySpawn;
989
+ this.lifecycle = new Lifecycle(
990
+ // No git flush — the human commits/pushes explicitly from the shell.
991
+ { ...DEFAULT_LIFECYCLE_CONFIG, gitFlushIntervalMs: 0, ...config.lifecycle },
992
+ {
993
+ onHeartbeat: () => this.connection.sendHeartbeat(),
994
+ onIdleTimeout: () => this.requestStop(),
995
+ onDormantTimeout: () => this.requestStop(),
996
+ onTokenRefresh: () => void this.connection.refreshTaskTokenFromBootstrap().catch(() => {
997
+ }),
998
+ onGitFlush: () => {
999
+ },
1000
+ onUsageSample: () => {
1001
+ }
1002
+ }
1003
+ );
1004
+ }
1005
+ get finalState() {
1006
+ return this._finalState;
1007
+ }
1008
+ get isStopped() {
1009
+ return this.stopped;
1010
+ }
1011
+ /** Connect, register, spawn the shell, and relay it until exit or stop. */
1012
+ async run() {
1013
+ try {
1014
+ await this.connection.connect();
1015
+ this.connection.sendEvent({
1016
+ type: "connected",
1017
+ sessionId: this.config.connection.sessionId
1018
+ });
1019
+ this.connection.onStop(() => this.requestStop());
1020
+ this.lifecycle.startHeartbeat();
1021
+ this.lifecycle.startTokenRefresh();
1022
+ await this.connection.call("connectAgent", {
1023
+ sessionId: this.config.connection.sessionId
1024
+ });
1025
+ await this.connection.emitStatus("running");
1026
+ this.lifecycle.startIdleTimer();
1027
+ await this.runShell();
1028
+ this._finalState = "finished";
1029
+ } catch (error) {
1030
+ const message = error instanceof Error ? error.message : String(error);
1031
+ process.stderr.write(`[conveyor-agent] Shell runner failed: ${message}
1032
+ `);
1033
+ this.connection.sendEvent({ type: "error", message });
1034
+ this._finalState = "error";
1035
+ } finally {
1036
+ this.shutdown();
1037
+ }
1038
+ }
1039
+ /** Spawn the login shell under a PTY and block until it exits or stop(). */
1040
+ async runShell() {
1041
+ const spawn = await this.loadSpawn();
1042
+ const shell = this.config.shell ?? process.env.SHELL ?? "bash";
1043
+ const pty = spawn(shell, ["-l"], {
1044
+ name: "xterm-256color",
1045
+ cols: this.dims.cols,
1046
+ rows: this.dims.rows,
1047
+ cwd: this.config.workspaceDir,
1048
+ env: buildShellEnv(inheritedEnv())
1049
+ });
1050
+ this.pty = pty;
1051
+ const unsubInput = this.connection.onPtyInput((data) => {
1052
+ this.lifecycle.startIdleTimer();
1053
+ pty.write(data);
1054
+ });
1055
+ const unsubResize = this.connection.onPtyResize((cols, rows) => {
1056
+ this.dims = { cols, rows };
1057
+ pty.resize(cols, rows);
1058
+ });
1059
+ pty.onData((data) => this.connection.sendPtyOutput(data, this.dims));
1060
+ try {
1061
+ await new Promise((resolve) => {
1062
+ pty.onExit(() => resolve());
1063
+ });
1064
+ } finally {
1065
+ unsubInput();
1066
+ unsubResize();
1067
+ this.pty = null;
1068
+ this.connection.sendPtyEnded();
1069
+ }
1070
+ }
1071
+ stop() {
1072
+ this.requestStop();
1073
+ }
1074
+ requestStop() {
1075
+ if (this.stopped) return;
1076
+ this.stopped = true;
1077
+ try {
1078
+ this.pty?.kill();
1079
+ } catch {
1080
+ }
1081
+ }
1082
+ shutdown() {
1083
+ this.stopped = true;
1084
+ this.lifecycle.destroy();
1085
+ this.connection.sendEvent({ type: "shutdown", reason: this._finalState ?? "finished" });
1086
+ this.connection.disconnect();
1087
+ }
1088
+ };
1089
+
1090
+ // src/runner/spawned-child-boot.ts
1091
+ var logger4 = createServiceLogger("SpawnedChildBoot");
1092
+ function createSpawnedChildRunner(inputs) {
1093
+ if (inputs.mode === "shell") {
1094
+ return new ShellSessionRunner({
1095
+ connection: {
1096
+ apiUrl: inputs.apiUrl,
1097
+ taskToken: inputs.taskToken,
1098
+ sessionId: inputs.sessionId,
1099
+ runnerMode: "shell"
1100
+ },
1101
+ workspaceDir: inputs.workspaceDir
1102
+ });
1103
+ }
1104
+ return new AdhocSessionRunner(
1105
+ {
1106
+ connection: {
1107
+ apiUrl: inputs.apiUrl,
1108
+ taskToken: inputs.taskToken,
1109
+ sessionId: inputs.sessionId,
1110
+ runnerMode: "adhoc"
1111
+ },
1112
+ projectId: inputs.projectId,
1113
+ // Session lineage key. NOT the inherited CONVEYOR_WORKSPACE_ID — that's
1114
+ // the builder's lineage; concurrent tabs must each be their own Claude
1115
+ // conversation, so key on the spawned session id.
1116
+ workspaceId: inputs.sessionId,
1117
+ workspaceDir: inputs.workspaceDir
1118
+ },
1119
+ {
1120
+ onEvent: (event) => {
1121
+ logger4.info("Spawned TUI event", { eventType: event.type });
1122
+ }
1123
+ }
1124
+ );
1125
+ }
1126
+
837
1127
  // src/cli.ts
838
1128
  if (process.argv.includes("--version")) {
839
1129
  const __dirname = dirname3(fileURLToPath(import.meta.url));
@@ -842,7 +1132,7 @@ if (process.argv.includes("--version")) {
842
1132
  process.stdout.write(pkg.version + "\n");
843
1133
  process.exit(0);
844
1134
  }
845
- var logger3 = createServiceLogger("CLI");
1135
+ var logger5 = createServiceLogger("CLI");
846
1136
  var exitContext = {};
847
1137
  var exitLogged = false;
848
1138
  function logAgentExit(reason, opts) {
@@ -856,12 +1146,12 @@ function logAgentExit(reason, opts) {
856
1146
  uptimeSec: process.uptime(),
857
1147
  identity: exitContext
858
1148
  });
859
- logger3[exitLogLevel(reason)]("agent_exit", payload);
1149
+ logger5[exitLogLevel(reason)]("agent_exit", payload);
860
1150
  }
861
1151
  async function bootstrapFromCodespace(apiUrl, instanceName) {
862
1152
  const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
863
1153
  const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
864
- logger3.info("Bootstrapping from codespace", {
1154
+ logger5.info("Bootstrapping from codespace", {
865
1155
  codespace: instanceName,
866
1156
  apiUrl,
867
1157
  apiUrlFromEnv,
@@ -873,7 +1163,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
873
1163
  bootstrapToken
874
1164
  });
875
1165
  if (!result.ok) {
876
- logger3.error("Bootstrap failed after retries", {
1166
+ logger5.error("Bootstrap failed after retries", {
877
1167
  reason: result.reason,
878
1168
  attempts: result.attempts,
879
1169
  status: result.status,
@@ -886,7 +1176,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
886
1176
  process.exit(1);
887
1177
  }
888
1178
  applyBootstrapToEnv(result.config);
889
- logger3.info("Bootstrap complete", {
1179
+ logger5.info("Bootstrap complete", {
890
1180
  taskId: result.config.taskId,
891
1181
  attempts: result.attempts
892
1182
  });
@@ -900,20 +1190,20 @@ function isExpectedAbortError(error) {
900
1190
  process.on("uncaughtException", (err) => {
901
1191
  if (err.code === "EPIPE") return;
902
1192
  if (isExpectedAbortError(err)) {
903
- logger3.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
1193
+ logger5.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
904
1194
  return;
905
1195
  }
906
- logger3.error("Uncaught exception", { error: err.message, code: err.code });
1196
+ logger5.error("Uncaught exception", { error: err.message, code: err.code });
907
1197
  logAgentExit("uncaught_exception", { exitCode: 1 });
908
1198
  process.exit(1);
909
1199
  });
910
1200
  process.on("unhandledRejection", (reason) => {
911
1201
  const err = reason instanceof Error ? reason : new Error(String(reason));
912
1202
  if (isExpectedAbortError(err)) {
913
- logger3.info("Ignored expected abort rejection after shutdown", { error: err.message });
1203
+ logger5.info("Ignored expected abort rejection after shutdown", { error: err.message });
914
1204
  return;
915
1205
  }
916
- logger3.error("Unhandled rejection", { error: err.message });
1206
+ logger5.error("Unhandled rejection", { error: err.message });
917
1207
  logAgentExit("unhandled_rejection", { exitCode: 1 });
918
1208
  process.exit(1);
919
1209
  });
@@ -922,7 +1212,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
922
1212
  var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
923
1213
  if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
924
1214
  if (!conveyorApiUrl) {
925
- logger3.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
1215
+ logger5.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
926
1216
  process.exit(1);
927
1217
  }
928
1218
  await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
@@ -940,7 +1230,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
940
1230
  exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
941
1231
  var projectIdentity = resolveProjectRunnerIdentity(process.env);
942
1232
  if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
943
- logger3.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
1233
+ logger5.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
944
1234
  const adhocRunner = new AdhocSessionRunner(
945
1235
  {
946
1236
  connection: {
@@ -956,7 +1246,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
956
1246
  },
957
1247
  {
958
1248
  onEvent: (event) => {
959
- logger3.info("Ad-hoc runner event", { eventType: event.type });
1249
+ logger5.info("Ad-hoc runner event", { eventType: event.type });
960
1250
  }
961
1251
  }
962
1252
  );
@@ -974,7 +1264,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
974
1264
  process.exit(adhocError ? 1 : 0);
975
1265
  }
976
1266
  if (!CONVEYOR_TASK_ID && projectIdentity) {
977
- logger3.info("Starting project agent", { projectId: projectIdentity.projectId });
1267
+ logger5.info("Starting project agent", { projectId: projectIdentity.projectId });
978
1268
  const projectRunner = new ProjectSessionRunner(
979
1269
  {
980
1270
  connection: {
@@ -989,7 +1279,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
989
1279
  },
990
1280
  {
991
1281
  onEvent: (event) => {
992
- logger3.info("Project runner event", { eventType: event.type });
1282
+ logger5.info("Project runner event", { eventType: event.type });
993
1283
  }
994
1284
  }
995
1285
  );
@@ -1007,28 +1297,58 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
1007
1297
  process.exit(projectError ? 1 : 0);
1008
1298
  }
1009
1299
  if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
1010
- logger3.error("Missing required environment variables");
1011
- logger3.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
1012
- logger3.error(" CONVEYOR_TASK_ID - ID of the task to execute");
1013
- logger3.error("");
1014
- logger3.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
1015
- logger3.error("");
1016
- logger3.error("Optional:");
1017
- logger3.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
1018
- logger3.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
1019
- logger3.error(
1300
+ logger5.error("Missing required environment variables");
1301
+ logger5.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
1302
+ logger5.error(" CONVEYOR_TASK_ID - ID of the task to execute");
1303
+ logger5.error("");
1304
+ logger5.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
1305
+ logger5.error("");
1306
+ logger5.error("Optional:");
1307
+ logger5.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
1308
+ logger5.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
1309
+ logger5.error(
1020
1310
  " Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
1021
1311
  );
1022
1312
  process.exit(1);
1023
1313
  }
1024
- if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack") {
1025
- logger3.error("Invalid CONVEYOR_MODE", {
1314
+ if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack" && CONVEYOR_MODE !== "shell") {
1315
+ logger5.error("Invalid CONVEYOR_MODE", {
1026
1316
  mode: CONVEYOR_MODE,
1027
- expected: ["task", "pm", "code-review", "adhoc", "pack"]
1317
+ expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
1028
1318
  });
1029
1319
  process.exit(1);
1030
1320
  }
1031
- logger3.info("Starting agent", { mode: CONVEYOR_MODE });
1321
+ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
1322
+ const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
1323
+ if (!spawnedSessionId) {
1324
+ logger5.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
1325
+ process.exit(1);
1326
+ }
1327
+ exitContext.sessionId = spawnedSessionId;
1328
+ exitContext.runnerMode = CONVEYOR_MODE;
1329
+ const childRunner = createSpawnedChildRunner({
1330
+ mode: CONVEYOR_MODE,
1331
+ apiUrl: conveyorApiUrl ?? "",
1332
+ taskToken: CONVEYOR_TASK_TOKEN,
1333
+ sessionId: spawnedSessionId,
1334
+ workspaceDir: CONVEYOR_WORKSPACE,
1335
+ projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
1336
+ });
1337
+ logger5.info("Starting spawned session child", {
1338
+ mode: CONVEYOR_MODE,
1339
+ sessionId: spawnedSessionId
1340
+ });
1341
+ process.on("SIGTERM", () => childRunner.stop());
1342
+ process.on("SIGINT", () => childRunner.stop());
1343
+ await childRunner.run();
1344
+ const childError = childRunner.finalState === "error";
1345
+ logAgentExit(childError ? "error" : "clean", {
1346
+ exitCode: childError ? 1 : 0,
1347
+ finalState: childRunner.finalState ?? void 0
1348
+ });
1349
+ process.exit(childError ? 1 : 0);
1350
+ }
1351
+ logger5.info("Starting agent", { mode: CONVEYOR_MODE });
1032
1352
  var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
1033
1353
  var runner = new SessionRunner(
1034
1354
  {
@@ -1048,24 +1368,25 @@ var runner = new SessionRunner(
1048
1368
  },
1049
1369
  {
1050
1370
  onStatusChange: (status) => {
1051
- logger3.info("Status changed", { status });
1371
+ logger5.info("Status changed", { status });
1052
1372
  },
1053
1373
  onEvent: (event) => {
1054
1374
  const detail = event.message ?? event.content ?? event.summary ?? "";
1055
1375
  if (detail) {
1056
- logger3.info(detail, { eventType: event.type });
1376
+ logger5.info(detail, { eventType: event.type });
1057
1377
  }
1058
1378
  }
1059
1379
  }
1060
1380
  );
1061
1381
  var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
1382
+ var sessionChildren = new SessionChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
1062
1383
  var shutdownSignal;
1063
1384
  var shutdownAgent = (signal) => {
1064
- logger3.info(`Received ${signal}, flushing git and stopping agent`);
1385
+ logger5.info(`Received ${signal}, flushing git and stopping agent`);
1065
1386
  shutdownSignal = signal;
1066
1387
  void (async () => {
1067
1388
  try {
1068
- await reviewChildren.stopAll();
1389
+ await Promise.all([reviewChildren.stopAll(), sessionChildren.stopAll()]);
1069
1390
  } catch {
1070
1391
  }
1071
1392
  try {
@@ -1075,8 +1396,8 @@ var shutdownAgent = (signal) => {
1075
1396
  }
1076
1397
  })();
1077
1398
  setTimeout(() => {
1078
- logger3.warn(`Forcing exit after ${signal} timeout`);
1079
- logger3.warn(
1399
+ logger5.warn(`Forcing exit after ${signal} timeout`);
1400
+ logger5.warn(
1080
1401
  "agent_exit",
1081
1402
  buildAgentExitLog({
1082
1403
  reason: "force_timeout",
@@ -1114,7 +1435,7 @@ var stopStartCommandChild = () => {
1114
1435
  }
1115
1436
  };
1116
1437
  var launchStartCommand = (command) => {
1117
- logger3.info("Running start command", { command });
1438
+ logger5.info("Running start command", { command });
1118
1439
  runner.connection.sendEvent({ type: "start_command_started" });
1119
1440
  const child = runStartCommand(command, CONVEYOR_WORKSPACE, (stream, data) => {
1120
1441
  runner.connection.sendEvent({ type: "start_command_output", stream, data });
@@ -1124,7 +1445,7 @@ var launchStartCommand = (command) => {
1124
1445
  child.on("exit", (code, signal) => {
1125
1446
  if (startCommandChild === child) startCommandChild = null;
1126
1447
  if (expectedStartCommandStops.has(child)) return;
1127
- logger3.info("Start command exited", { code, signal });
1448
+ logger5.info("Start command exited", { code, signal });
1128
1449
  runner.connection.sendEvent({
1129
1450
  type: "start_command_exited",
1130
1451
  code,
@@ -1140,7 +1461,7 @@ var launchStartCommand = (command) => {
1140
1461
  });
1141
1462
  child.on("error", (err) => {
1142
1463
  if (startCommandChild === child) startCommandChild = null;
1143
- logger3.error("Start command error", { error: err.message });
1464
+ logger5.error("Start command error", { error: err.message });
1144
1465
  runner.connection.sendEvent({ type: "start_command_error", message: err.message });
1145
1466
  });
1146
1467
  };
@@ -1156,12 +1477,12 @@ void checkSessionTaskIdentity({
1156
1477
  const ctx = await runner.connection.call("getTaskContext", { sessionId });
1157
1478
  return ctx.id;
1158
1479
  },
1159
- logger: logger3
1480
+ logger: logger5
1160
1481
  });
1161
1482
  if (CONVEYOR_MODE === "task") {
1162
1483
  runner.connection.onSpawnReview((data) => {
1163
1484
  void reviewChildren.spawn(data).catch((err) => {
1164
- logger3.error("Review child spawn threw", {
1485
+ logger5.error("Review child spawn threw", {
1165
1486
  error: err instanceof Error ? err.message : String(err)
1166
1487
  });
1167
1488
  runner.connection.reportReviewSpawnFailure(
@@ -1171,6 +1492,19 @@ if (CONVEYOR_MODE === "task") {
1171
1492
  });
1172
1493
  });
1173
1494
  }
1495
+ if (CONVEYOR_MODE === "task" || CONVEYOR_MODE === "pack") {
1496
+ runner.connection.onSpawnTui((data) => {
1497
+ void sessionChildren.spawn(data).catch((err) => {
1498
+ logger5.error("Session child spawn threw", {
1499
+ error: err instanceof Error ? err.message : String(err)
1500
+ });
1501
+ runner.connection.reportSessionSpawnFailure(
1502
+ data.sessionId,
1503
+ err instanceof Error ? err.message : String(err)
1504
+ );
1505
+ });
1506
+ });
1507
+ }
1174
1508
  var conveyorConfig = loadConveyorConfig();
1175
1509
  runner.connection.onRunStartCommand(() => {
1176
1510
  if (!conveyorConfig?.startCommand) return;
@@ -1201,15 +1535,15 @@ if (conveyorConfig) {
1201
1535
  startLazy: false
1202
1536
  });
1203
1537
  if (conveyorConfig.setupCommand) {
1204
- logger3.info("Running setup command (background)", {
1538
+ logger5.info("Running setup command (background)", {
1205
1539
  command: conveyorConfig.setupCommand
1206
1540
  });
1207
1541
  try {
1208
1542
  await runSetupCommand(conveyorConfig.setupCommand, CONVEYOR_WORKSPACE, logSetupOutput);
1209
- logger3.info("Setup command completed");
1543
+ logger5.info("Setup command completed");
1210
1544
  } catch (error) {
1211
1545
  const msg = error instanceof Error ? error.message : "Setup command failed";
1212
- logger3.error("Setup command failed", { error: msg });
1546
+ logger5.error("Setup command failed", { error: msg });
1213
1547
  runner.connection.sendEvent({ type: "setup_error", message: msg });
1214
1548
  }
1215
1549
  }
@@ -1238,7 +1572,7 @@ runner.run().then(() => {
1238
1572
  process.exit(errored ? 1 : 0);
1239
1573
  }).catch((error) => {
1240
1574
  const msg = error instanceof Error ? error.message : String(error);
1241
- logger3.error("Agent runner failed", { error: msg });
1575
+ logger5.error("Agent runner failed", { error: msg });
1242
1576
  logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
1243
1577
  process.exit(1);
1244
1578
  });