@rallycry/conveyor-agent 10.2.2 → 10.2.5

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
@@ -1,27 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AgentConnection,
4
+ ClaudeTuiAdapter,
4
5
  DEFAULT_LIFECYCLE_CONFIG,
5
6
  DEFAULT_SONNET_MODEL,
6
7
  Lifecycle,
7
8
  PtyHarness,
8
9
  SessionRunner,
10
+ TUI_KINDS,
9
11
  applyBootstrapToEnv,
10
12
  awaitGitReady,
13
+ buildPromptBytes,
11
14
  buildSessionPreviewPorts,
15
+ cleanTerminalOutput,
12
16
  createServiceLogger,
13
17
  fetchBootstrap,
18
+ inheritedEnv,
14
19
  loadConveyorConfig,
15
20
  loadForwardPorts,
16
21
  resolveSessionStart,
17
22
  runSetupCommand,
18
23
  runStartCommand,
19
24
  sampleKeyUsage
20
- } from "./chunk-53ZLFIAI.js";
25
+ } from "./chunk-CFELRV35.js";
26
+ import "./chunk-7TQO4ZF4.js";
21
27
 
22
28
  // src/cli.ts
23
29
  import { readFileSync } from "fs";
24
- import { join, dirname as dirname2 } from "path";
30
+ import { join as join2, dirname as dirname2 } from "path";
25
31
  import { fileURLToPath } from "url";
26
32
 
27
33
  // src/setup/sidecars.ts
@@ -169,6 +175,29 @@ async function checkSessionTaskIdentity(params) {
169
175
  return "match";
170
176
  }
171
177
 
178
+ // src/utils/agent-exit.ts
179
+ function exitLogLevel(reason) {
180
+ return reason === "clean" ? "info" : "warn";
181
+ }
182
+ function buildAgentExitLog(args) {
183
+ const log = {
184
+ event: "agent_exit",
185
+ reason: args.reason,
186
+ exitCode: args.exitCode,
187
+ uptimeSec: Math.round(args.uptimeSec * 10) / 10
188
+ };
189
+ if (args.signal !== void 0) log.signal = args.signal;
190
+ if (args.finalState !== void 0) log.finalState = args.finalState;
191
+ const id = args.identity;
192
+ if (id) {
193
+ if (id.sessionId !== void 0) log.sessionId = id.sessionId;
194
+ if (id.taskId !== void 0) log.taskId = id.taskId;
195
+ if (id.workspaceId !== void 0) log.workspaceId = id.workspaceId;
196
+ if (id.runnerMode !== void 0) log.runnerMode = id.runnerMode;
197
+ }
198
+ return log;
199
+ }
200
+
172
201
  // src/setup/project-identity.ts
173
202
  function resolveProjectRunnerIdentity(env) {
174
203
  if (env.CONVEYOR_TASK_ID) return null;
@@ -317,11 +346,118 @@ var ProjectSessionRunner = class {
317
346
  }
318
347
  };
319
348
 
349
+ // src/harness/pty/adapters/types.ts
350
+ import { accessSync, constants, statSync } from "fs";
351
+ import { join } from "path";
352
+ var TuiUnavailableError = class extends Error {
353
+ constructor(tui, message) {
354
+ super(message);
355
+ this.tui = tui;
356
+ this.name = "TuiUnavailableError";
357
+ }
358
+ tui;
359
+ };
360
+ function isExecutable(path) {
361
+ try {
362
+ if (!statSync(path).isFile()) return false;
363
+ accessSync(path, constants.X_OK);
364
+ return true;
365
+ } catch {
366
+ return false;
367
+ }
368
+ }
369
+ function findOnPath(binary, env = process.env) {
370
+ if (binary.includes("/")) {
371
+ return isExecutable(binary) ? binary : null;
372
+ }
373
+ for (const dir of (env.PATH ?? "").split(":")) {
374
+ if (!dir) continue;
375
+ const candidate = join(dir, binary);
376
+ if (isExecutable(candidate)) return candidate;
377
+ }
378
+ return null;
379
+ }
380
+
381
+ // src/harness/pty/adapters/opencode.ts
382
+ var PROVIDER_KEY_ENV = {
383
+ openai: "OPENAI_API_KEY",
384
+ anthropic: "ANTHROPIC_API_KEY"
385
+ };
386
+ var OpenCodeTuiAdapter = class {
387
+ constructor(env = process.env) {
388
+ this.env = env;
389
+ }
390
+ env;
391
+ id = "opencode";
392
+ capabilities = {
393
+ resume: false,
394
+ structuredEvents: false,
395
+ prefill: false,
396
+ passiveTurns: false
397
+ };
398
+ resolveBinary(env = this.env) {
399
+ const override = env.CONVEYOR_OPENCODE_BIN;
400
+ const found = override ? findOnPath(override, env) : findOnPath("opencode", env);
401
+ if (!found) {
402
+ throw new TuiUnavailableError(
403
+ "opencode",
404
+ "The opencode CLI is not available in this environment. It must be baked into the pod image (see Dockerfile.base) \u2014 re-run Build Image for this project, or set CONVEYOR_OPENCODE_BIN to its absolute path."
405
+ );
406
+ }
407
+ return found;
408
+ }
409
+ buildSpawn(input) {
410
+ const env = { ...inheritedEnv() };
411
+ delete env.CONVEYOR_AGENT_KEY;
412
+ const key = this.env.CONVEYOR_AGENT_KEY;
413
+ const provider = this.env.CONVEYOR_AGENT_PROVIDER ?? "openai";
414
+ const keyEnvVar = PROVIDER_KEY_ENV[provider];
415
+ if (key && keyEnvVar) env[keyEnvVar] = key;
416
+ const model = this.env.CONVEYOR_AGENT_MODEL ?? input.options.model;
417
+ const args = [];
418
+ if (model) args.push("--model", model.includes("/") ? model : `${provider}/${model}`);
419
+ return { file: this.resolveBinary(), args, env };
420
+ }
421
+ async prepareEnvironment() {
422
+ }
423
+ spawnFingerprint(input) {
424
+ return JSON.stringify(["opencode", input.model, input.cwd]);
425
+ }
426
+ encodePromptBytes(text) {
427
+ return buildPromptBytes(text);
428
+ }
429
+ buildExitErrors(exitCode, rawOutput) {
430
+ const errors = [`opencode exited (code ${exitCode}) without a result`];
431
+ const tail = cleanTerminalOutput(rawOutput);
432
+ if (tail) errors.push(`Last terminal output before exit:
433
+ ${tail}`);
434
+ return errors;
435
+ }
436
+ };
437
+
438
+ // src/harness/pty/adapters/index.ts
439
+ function resolveTuiAdapter(kind = "claude-code") {
440
+ switch (kind) {
441
+ case "claude-code":
442
+ return new ClaudeTuiAdapter();
443
+ case "opencode":
444
+ return new OpenCodeTuiAdapter();
445
+ default:
446
+ throw new Error(`Unknown TUI kind: ${kind}`);
447
+ }
448
+ }
449
+
320
450
  // src/runner/adhoc-session-runner.ts
321
451
  var ADHOC_SYSTEM_NOTE = "You are running in an ad-hoc Conveyor scratch pod \u2014 an interactive terminal on the project's repository checked out at its default branch. There is no task or plan; help the human with whatever they ask directly.";
452
+ function resolveAdhocTui(env) {
453
+ const raw = env.CONVEYOR_TUI ?? "claude-code";
454
+ if (TUI_KINDS.includes(raw)) return raw;
455
+ throw new Error(`Unknown TUI "${raw}" in CONVEYOR_TUI (expected: ${TUI_KINDS.join(", ")})`);
456
+ }
322
457
  function buildAdhocPtyBridge(connection) {
323
458
  return {
324
459
  sendOutput: (data, dims) => connection.sendPtyOutput(data, dims),
460
+ sendChatEvent: (event) => connection.sendPtyChatEvent(event),
325
461
  onInput: (handler) => connection.onPtyInput(handler),
326
462
  onResize: (handler) => connection.onPtyResize(handler)
327
463
  };
@@ -359,7 +495,10 @@ var AdhocSessionRunner = class {
359
495
  this.config = config;
360
496
  this.callbacks = callbacks;
361
497
  this.connection = deps?.connection ?? new AgentConnection(config.connection);
362
- this.harness = deps?.harness ?? new PtyHarness(buildAdhocPtyBridge(this.connection));
498
+ this.harness = deps?.harness ?? new PtyHarness(
499
+ buildAdhocPtyBridge(this.connection),
500
+ resolveTuiAdapter(resolveAdhocTui(process.env))
501
+ );
363
502
  this.lifecycle = new Lifecycle(
364
503
  // No git flush: the WIP snapshot machinery is branch/task-shaped; the human
365
504
  // commits/pushes explicitly from the interactive shell.
@@ -419,7 +558,7 @@ var AdhocSessionRunner = class {
419
558
  * loop blocks (keeping the pod alive) until the human exits or stop() aborts.
420
559
  */
421
560
  async runInteractiveTui() {
422
- const model = this.config.model ?? process.env.CONVEYOR_ADHOC_MODEL ?? DEFAULT_SONNET_MODEL;
561
+ const model = this.config.model ?? process.env.CONVEYOR_AGENT_MODEL ?? process.env.CONVEYOR_ADHOC_MODEL ?? DEFAULT_SONNET_MODEL;
423
562
  const session = resolveSessionStart(this.config.workspaceId, this.config.workspaceDir);
424
563
  const options = buildAdhocQueryOptions(
425
564
  this.config.workspaceDir,
@@ -428,8 +567,11 @@ var AdhocSessionRunner = class {
428
567
  session
429
568
  );
430
569
  try {
431
- for await (const _event of this.harness.executeQuery({ prompt: "", options })) {
570
+ for await (const event of this.harness.executeQuery({ prompt: "", options })) {
432
571
  if (this.stopped) break;
572
+ if (event.type === "result" && event.subtype === "error") {
573
+ throw new Error(event.errors.join("\n"));
574
+ }
433
575
  }
434
576
  } catch (error) {
435
577
  if (!this.stopped) throw error;
@@ -628,12 +770,27 @@ var ReviewChildSupervisor = class {
628
770
  // src/cli.ts
629
771
  if (process.argv.includes("--version")) {
630
772
  const __dirname = dirname2(fileURLToPath(import.meta.url));
631
- const pkgPath = join(__dirname, "..", "package.json");
773
+ const pkgPath = join2(__dirname, "..", "package.json");
632
774
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
633
775
  process.stdout.write(pkg.version + "\n");
634
776
  process.exit(0);
635
777
  }
636
778
  var logger2 = createServiceLogger("CLI");
779
+ var exitContext = {};
780
+ var exitLogged = false;
781
+ function logAgentExit(reason, opts) {
782
+ if (exitLogged) return;
783
+ exitLogged = true;
784
+ const payload = buildAgentExitLog({
785
+ reason,
786
+ exitCode: opts.exitCode,
787
+ signal: opts.signal,
788
+ finalState: opts.finalState,
789
+ uptimeSec: process.uptime(),
790
+ identity: exitContext
791
+ });
792
+ logger2[exitLogLevel(reason)]("agent_exit", payload);
793
+ }
637
794
  async function bootstrapFromCodespace(apiUrl, instanceName) {
638
795
  const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
639
796
  const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
@@ -680,6 +837,7 @@ process.on("uncaughtException", (err) => {
680
837
  return;
681
838
  }
682
839
  logger2.error("Uncaught exception", { error: err.message, code: err.code });
840
+ logAgentExit("uncaught_exception", { exitCode: 1 });
683
841
  process.exit(1);
684
842
  });
685
843
  process.on("unhandledRejection", (reason) => {
@@ -689,6 +847,7 @@ process.on("unhandledRejection", (reason) => {
689
847
  return;
690
848
  }
691
849
  logger2.error("Unhandled rejection", { error: err.message });
850
+ logAgentExit("unhandled_rejection", { exitCode: 1 });
692
851
  process.exit(1);
693
852
  });
694
853
  var DEFAULT_CONVEYOR_API_URL = "https://api.conveyor.rallycryapp.com";
@@ -708,6 +867,10 @@ var CONVEYOR_WORKSPACE = process.env.CONVEYOR_WORKSPACE ?? process.cwd();
708
867
  var CONVEYOR_MODE = process.env.CONVEYOR_MODE ?? "task";
709
868
  var CONVEYOR_AGENT_MODE = process.env.CONVEYOR_AGENT_MODE || void 0;
710
869
  var CONVEYOR_IS_AUTO = CONVEYOR_AGENT_MODE ? CONVEYOR_AGENT_MODE === "auto" : process.env.CONVEYOR_IS_AUTO === "true";
870
+ exitContext.taskId = CONVEYOR_TASK_ID;
871
+ exitContext.workspaceId = process.env.CONVEYOR_WORKSPACE_ID;
872
+ exitContext.runnerMode = CONVEYOR_MODE;
873
+ exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
711
874
  var projectIdentity = resolveProjectRunnerIdentity(process.env);
712
875
  if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
713
876
  logger2.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
@@ -730,10 +893,18 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
730
893
  }
731
894
  }
732
895
  );
896
+ exitContext.sessionId = projectIdentity.sessionId;
897
+ exitContext.workspaceId = projectIdentity.workspaceId;
898
+ exitContext.runnerMode = "adhoc";
733
899
  process.on("SIGTERM", () => adhocRunner.stop());
734
900
  process.on("SIGINT", () => adhocRunner.stop());
735
901
  await adhocRunner.run();
736
- process.exit(adhocRunner.finalState === "error" ? 1 : 0);
902
+ const adhocError = adhocRunner.finalState === "error";
903
+ logAgentExit(adhocError ? "error" : "clean", {
904
+ exitCode: adhocError ? 1 : 0,
905
+ finalState: adhocRunner.finalState ?? void 0
906
+ });
907
+ process.exit(adhocError ? 1 : 0);
737
908
  }
738
909
  if (!CONVEYOR_TASK_ID && projectIdentity) {
739
910
  logger2.info("Starting project agent", { projectId: projectIdentity.projectId });
@@ -755,10 +926,18 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
755
926
  }
756
927
  }
757
928
  );
929
+ exitContext.sessionId = projectIdentity.sessionId;
930
+ exitContext.workspaceId = projectIdentity.workspaceId;
931
+ exitContext.runnerMode = "pm";
758
932
  process.on("SIGTERM", () => projectRunner.stop());
759
933
  process.on("SIGINT", () => projectRunner.stop());
760
934
  await projectRunner.run();
761
- process.exit(projectRunner.finalState === "error" ? 1 : 0);
935
+ const projectError = projectRunner.finalState === "error";
936
+ logAgentExit(projectError ? "error" : "clean", {
937
+ exitCode: projectError ? 1 : 0,
938
+ finalState: projectRunner.finalState ?? void 0
939
+ });
940
+ process.exit(projectError ? 1 : 0);
762
941
  }
763
942
  if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
764
943
  logger2.error("Missing required environment variables");
@@ -813,8 +992,10 @@ var runner = new SessionRunner(
813
992
  }
814
993
  );
815
994
  var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
995
+ var shutdownSignal;
816
996
  var shutdownAgent = (signal) => {
817
997
  logger2.info(`Received ${signal}, flushing git and stopping agent`);
998
+ shutdownSignal = signal;
818
999
  void (async () => {
819
1000
  try {
820
1001
  await reviewChildren.stopAll();
@@ -828,6 +1009,17 @@ var shutdownAgent = (signal) => {
828
1009
  })();
829
1010
  setTimeout(() => {
830
1011
  logger2.warn(`Forcing exit after ${signal} timeout`);
1012
+ logger2.warn(
1013
+ "agent_exit",
1014
+ buildAgentExitLog({
1015
+ reason: "force_timeout",
1016
+ exitCode: 1,
1017
+ signal,
1018
+ finalState: runner.finalState ?? void 0,
1019
+ uptimeSec: process.uptime(),
1020
+ identity: exitContext
1021
+ })
1022
+ );
831
1023
  process.exit(1);
832
1024
  }, 45e3).unref();
833
1025
  };
@@ -964,16 +1156,23 @@ if (conveyorConfig) {
964
1156
  runner.connection.sendEvent({
965
1157
  type: "setup_complete",
966
1158
  startCommandRunning,
967
- previewPort: conveyorConfig.previewPort,
968
1159
  ...previewPorts.length > 0 ? { previewPorts } : {}
969
1160
  });
970
1161
  })();
971
1162
  }
972
1163
  runner.run().then(() => {
973
- process.exit(runner.finalState === "error" ? 1 : 0);
1164
+ const errored = runner.finalState === "error";
1165
+ const reason = shutdownSignal ? "signal" : errored ? "error" : "clean";
1166
+ logAgentExit(reason, {
1167
+ exitCode: errored ? 1 : 0,
1168
+ signal: shutdownSignal,
1169
+ finalState: runner.finalState ?? void 0
1170
+ });
1171
+ process.exit(errored ? 1 : 0);
974
1172
  }).catch((error) => {
975
1173
  const msg = error instanceof Error ? error.message : String(error);
976
1174
  logger2.error("Agent runner failed", { error: msg });
1175
+ logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
977
1176
  process.exit(1);
978
1177
  });
979
1178
  //# sourceMappingURL=cli.js.map