@rallycry/conveyor-agent 10.13.18 → 10.13.51

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.
Files changed (39) hide show
  1. package/dist/{boot-YZCOIV3Q.js → boot-BZE36LT6.js} +156 -125
  2. package/dist/boot-BZE36LT6.js.map +1 -0
  3. package/dist/chunk-5OQQSDVT.js +142 -0
  4. package/dist/chunk-5OQQSDVT.js.map +1 -0
  5. package/dist/{chunk-3GDXX3OX.js → chunk-EQO7QFGN.js} +6201 -2125
  6. package/dist/chunk-EQO7QFGN.js.map +1 -0
  7. package/dist/{chunk-AGGVNADW.js → chunk-HVSPADKP.js} +59 -13
  8. package/dist/chunk-HVSPADKP.js.map +1 -0
  9. package/dist/{chunk-UZTJJD7Y.js → chunk-KCB7CSWJ.js} +43 -2
  10. package/dist/chunk-KCB7CSWJ.js.map +1 -0
  11. package/dist/{chunk-72AEN6LB.js → chunk-KZQBYSMN.js} +137 -2
  12. package/dist/chunk-KZQBYSMN.js.map +1 -0
  13. package/dist/{chunk-UDSRAHKP.js → chunk-QOJTJCYZ.js} +67 -19
  14. package/dist/chunk-QOJTJCYZ.js.map +1 -0
  15. package/dist/chunk-ULS4QPRE.js +138 -0
  16. package/dist/chunk-ULS4QPRE.js.map +1 -0
  17. package/dist/cli.js +245 -551
  18. package/dist/cli.js.map +1 -1
  19. package/dist/{client-BU4XA7CV.js → client-LRVVHTNG.js} +2 -2
  20. package/dist/heartbeat-worker.js +10 -12
  21. package/dist/heartbeat-worker.js.map +1 -1
  22. package/dist/index.d.ts +153 -8
  23. package/dist/index.js +5 -5
  24. package/dist/oom-watchdog-U7JERHA2.js +11 -0
  25. package/dist/server-PPTELJWY.js +10 -0
  26. package/dist/server-PPTELJWY.js.map +1 -0
  27. package/package.json +3 -3
  28. package/runtime/entrypoint.sh +5 -4
  29. package/dist/boot-YZCOIV3Q.js.map +0 -1
  30. package/dist/chunk-3GDXX3OX.js.map +0 -1
  31. package/dist/chunk-72AEN6LB.js.map +0 -1
  32. package/dist/chunk-7TQO4ZF4.js +0 -60
  33. package/dist/chunk-7TQO4ZF4.js.map +0 -1
  34. package/dist/chunk-AGGVNADW.js.map +0 -1
  35. package/dist/chunk-UDSRAHKP.js.map +0 -1
  36. package/dist/chunk-UZTJJD7Y.js.map +0 -1
  37. package/dist/server-BXAFMYDM.js +0 -9
  38. /package/dist/{client-BU4XA7CV.js.map → client-LRVVHTNG.js.map} +0 -0
  39. /package/dist/{server-BXAFMYDM.js.map → oom-watchdog-U7JERHA2.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,53 +1,55 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ TOMBSTONE_MESSAGE,
4
+ isLegacyEntrypointLaunch
5
+ } from "./chunk-6GS5ADIY.js";
2
6
  import {
3
7
  AgentConnection,
4
- ClaudeTuiAdapter,
8
+ CodespacePortVisibility,
5
9
  DEFAULT_LIFECYCLE_CONFIG,
6
10
  DEFAULT_SONNET_MODEL,
7
11
  Lifecycle,
8
12
  PortDiscovery,
9
13
  PtyHarness,
10
14
  SessionRunner,
11
- TUI_KINDS,
12
- TuiUnavailableError,
13
15
  applyBootstrapToEnv,
14
16
  awaitGitReady,
17
+ buildProjectTools,
15
18
  buildSessionPreviewPorts,
16
19
  buildSynthesizedCredentials,
17
20
  claudeJsonPath,
18
- cleanTerminalOutput,
19
21
  createServiceLogger,
20
- defineTool,
21
22
  fetchBootstrap,
22
- findOnPath,
23
+ isPermissionDeniedError,
23
24
  loadConveyorConfig,
24
25
  loadForwardPorts,
25
26
  parseUsageGauges,
26
27
  resolvePlaywrightMcpServer,
27
28
  resolveSessionStart,
29
+ resolveTuiAdapter,
30
+ resolveTuiKindFromEnv,
28
31
  runUsageProbe,
29
32
  sampleKeyUsage,
30
- textResult
31
- } from "./chunk-3GDXX3OX.js";
32
- import "./chunk-7TQO4ZF4.js";
33
+ workspacePathExists
34
+ } from "./chunk-EQO7QFGN.js";
33
35
  import {
34
- getWorkbenchClient
35
- } from "./chunk-UDSRAHKP.js";
36
+ reportBootMilestone
37
+ } from "./chunk-KCB7CSWJ.js";
38
+ import "./chunk-5OQQSDVT.js";
36
39
  import {
37
- TOMBSTONE_MESSAGE,
38
- isLegacyEntrypointLaunch
39
- } from "./chunk-6GS5ADIY.js";
40
- import "./chunk-UZTJJD7Y.js";
40
+ getWorkbenchClient
41
+ } from "./chunk-QOJTJCYZ.js";
41
42
  import {
42
43
  workbenchEnabled
43
44
  } from "./chunk-4VUQ2NPF.js";
44
45
  import {
45
- buildPromptBytes,
46
46
  inheritedEnv,
47
- loadPtySpawn,
47
+ resolvePtySpawn,
48
+ runSetupCommand,
48
49
  runStartCommand,
50
+ sessionTempBase,
49
51
  terminateProcessGroup
50
- } from "./chunk-72AEN6LB.js";
52
+ } from "./chunk-KZQBYSMN.js";
51
53
  import "./chunk-JIGG755T.js";
52
54
 
53
55
  // src/cli.ts
@@ -202,31 +204,71 @@ async function waitForSidecars(opts = {}) {
202
204
  );
203
205
  }
204
206
 
205
- // src/setup/boot-milestone.ts
206
- var REPORT_TIMEOUT_MS = 5e3;
207
- async function reportBootMilestone(opts) {
208
- const env = opts.env ?? process.env;
209
- const apiUrl = env.CONVEYOR_API_URL;
210
- const token = env.POD_BOOTSTRAP_TOKEN;
211
- if (!apiUrl || !token) return false;
212
- const fetchFn = opts.fetchFn ?? fetch;
213
- const controller = new AbortController();
214
- const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? REPORT_TIMEOUT_MS);
207
+ // src/setup/db-schema-sync.ts
208
+ import { basename, dirname, join } from "path";
209
+ var PRISMA_SCHEMA_CANDIDATES = [
210
+ "packages/db/prisma/schema.prisma",
211
+ "prisma/schema.prisma",
212
+ "apps/api/prisma/schema.prisma",
213
+ "packages/db/schema.prisma"
214
+ ];
215
+ var PRISMA_DB_PUSH = "bunx prisma db push";
216
+ var SCHEMA_SYNC_TIMEOUT_MS = 18e4;
217
+ function depsInclude(rawDeps, dep) {
218
+ return (rawDeps ?? "").split(",").map((entry) => entry.trim()).includes(dep);
219
+ }
220
+ function isDisabled(env) {
221
+ const flag = env.CONVEYOR_DB_SCHEMA_SYNC?.trim().toLowerCase();
222
+ return flag === "0" || flag === "false";
223
+ }
224
+ function packageRootForSchema(schemaPath) {
225
+ const dir = dirname(schemaPath);
226
+ return basename(dir) === "prisma" ? dirname(dir) : dir;
227
+ }
228
+ async function resolveSchemaSyncPlan(options) {
229
+ const { workspaceDir, env } = options;
230
+ const pathExists = options.pathExists ?? workspacePathExists;
231
+ if (isDisabled(env)) return null;
232
+ const override = env.CONVEYOR_DB_SCHEMA_SYNC_COMMAND?.trim();
233
+ if (override) return { command: override, cwd: workspaceDir, source: "override" };
234
+ if (!depsInclude(env.CONVEYOR_DEPS, "postgresql")) return null;
235
+ if (!env.DATABASE_URL?.trim()) return null;
236
+ for (const relative of PRISMA_SCHEMA_CANDIDATES) {
237
+ const schemaPath = join(workspaceDir, relative);
238
+ if (await pathExists(schemaPath)) {
239
+ return { command: PRISMA_DB_PUSH, cwd: packageRootForSchema(schemaPath), source: "prisma" };
240
+ }
241
+ }
242
+ return null;
243
+ }
244
+ async function runDbSchemaSync(options) {
245
+ let plan = null;
215
246
  try {
216
- const res = await fetchFn(`${apiUrl.replace(/\/$/, "")}/api/v3/pods/boot-milestone`, {
217
- method: "POST",
218
- headers: {
219
- "content-type": "application/json",
220
- authorization: `Bearer ${token}`
221
- },
222
- body: JSON.stringify({ key: opts.key }),
223
- signal: controller.signal
224
- });
225
- return res.ok;
247
+ plan = await resolveSchemaSyncPlan(options);
226
248
  } catch {
227
- return false;
249
+ return "skipped";
250
+ }
251
+ if (!plan) return "skipped";
252
+ const { onOutput, runCommand } = options;
253
+ onOutput("stdout", `[db] syncing schema (${plan.source}): ${plan.command}
254
+ `);
255
+ const controller = new AbortController();
256
+ const abort = () => controller.abort();
257
+ options.signal?.addEventListener("abort", abort, { once: true });
258
+ const timer = setTimeout(abort, options.timeoutMs ?? SCHEMA_SYNC_TIMEOUT_MS);
259
+ timer.unref();
260
+ try {
261
+ await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
262
+ onOutput("stdout", "[db] schema in sync\n");
263
+ return "synced";
264
+ } catch (error) {
265
+ const message = error instanceof Error ? error.message : String(error);
266
+ onOutput("stderr", `[db] schema sync failed: ${message}
267
+ `);
268
+ return "failed";
228
269
  } finally {
229
270
  clearTimeout(timer);
271
+ options.signal?.removeEventListener("abort", abort);
230
272
  }
231
273
  }
232
274
 
@@ -235,10 +277,11 @@ function defaultCommandExecutors() {
235
277
  if (workbenchEnabled()) {
236
278
  const client = getWorkbenchClient();
237
279
  return {
238
- runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput)
280
+ runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput),
281
+ runShellCommand: (cmd, cwd, onOutput, signal) => client.runSetupCommand(cmd, cwd, onOutput, signal)
239
282
  };
240
283
  }
241
- return { runStartCommand };
284
+ return { runStartCommand, runShellCommand: runSetupCommand };
242
285
  }
243
286
  var defaultWriteOutput = (stream, data) => {
244
287
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
@@ -263,6 +306,7 @@ var WorkspaceCommandSupervisor = class {
263
306
  awaitGitReadyFn;
264
307
  waitForSidecarsFn;
265
308
  runStartCommandFn;
309
+ syncDbSchemaFn;
266
310
  loadForwardPortsFn;
267
311
  writeOutput;
268
312
  terminateStartCommand;
@@ -301,6 +345,13 @@ var WorkspaceCommandSupervisor = class {
301
345
  this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, signal: opts.signal }));
302
346
  const executors = defaultCommandExecutors();
303
347
  this.runStartCommandFn = options.runStartCommand ?? executors.runStartCommand;
348
+ this.syncDbSchemaFn = options.syncDbSchema ?? ((opts) => runDbSchemaSync({
349
+ workspaceDir: this.workspaceDir,
350
+ env: this.env,
351
+ runCommand: executors.runShellCommand,
352
+ onOutput: opts.onOutput,
353
+ signal: opts.signal
354
+ }));
304
355
  this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
305
356
  this.writeOutput = options.writeOutput ?? defaultWriteOutput;
306
357
  this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
@@ -352,7 +403,6 @@ var WorkspaceCommandSupervisor = class {
352
403
  });
353
404
  return;
354
405
  }
355
- if (gitState === "ready") this.reportBootMilestoneFn("git_ready");
356
406
  await this.waitForSidecarsFn({
357
407
  onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
358
408
  `),
@@ -362,6 +412,8 @@ var WorkspaceCommandSupervisor = class {
362
412
  this.reportBootMilestoneFn("sidecars_ready");
363
413
  await this.loopReady;
364
414
  if (this.stopped) return;
415
+ await this.syncDbSchema();
416
+ if (this.stopped) return;
365
417
  const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
366
418
  if (this.stopped) return;
367
419
  this.reportBootMilestoneFn("start_command_launched");
@@ -374,6 +426,20 @@ var WorkspaceCommandSupervisor = class {
374
426
  ...previewPorts.length > 0 ? { previewPorts } : {}
375
427
  });
376
428
  }
429
+ /** Run the schema sync, forwarding its output as setup output so it lands in
430
+ * the card's setup log. A failure is announced rather than thrown: it is the
431
+ * exact condition that used to render as a silent "Displaying 0 results". */
432
+ async syncDbSchema() {
433
+ const outcome = await this.syncDbSchemaFn({
434
+ onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
435
+ signal: this.abortController.signal
436
+ });
437
+ if (outcome !== "failed" || this.stopped) return;
438
+ this.connection.sendEvent({
439
+ type: "setup_error",
440
+ message: "Database schema sync failed \u2014 the app may error on queries touching recently added columns (see pod logs)"
441
+ });
442
+ }
377
443
  restartStartCommand() {
378
444
  if (this.stopped || !this.config?.startCommand) return;
379
445
  this.connection.sendEvent({
@@ -497,14 +563,14 @@ var WorkspaceCommandSupervisor = class {
497
563
 
498
564
  // src/utils/session-identity.ts
499
565
  async function checkSessionTaskIdentity(params) {
500
- const { sessionId, taskId, fetchSessionTaskId, logger: logger7 } = params;
566
+ const { sessionId, taskId, fetchSessionTaskId, logger: logger6 } = params;
501
567
  if (!sessionId || sessionId === taskId) return "skipped";
502
568
  let sessionTaskId;
503
569
  try {
504
570
  sessionTaskId = await fetchSessionTaskId(sessionId);
505
571
  } catch (err) {
506
572
  const message = err instanceof Error ? err.message : String(err);
507
- logger7.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
573
+ logger6.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
508
574
  sessionId,
509
575
  taskId,
510
576
  error: message
@@ -512,7 +578,7 @@ async function checkSessionTaskIdentity(params) {
512
578
  return "error";
513
579
  }
514
580
  if (sessionTaskId !== taskId) {
515
- logger7.warn(
581
+ logger6.warn(
516
582
  "!!! 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.",
517
583
  { sessionId, envTaskId: taskId, sessionTaskId }
518
584
  );
@@ -696,8 +762,7 @@ var ProjectSessionRunner = class {
696
762
 
697
763
  // src/usage/multi-key-probe.ts
698
764
  import { mkdtemp, writeFile, copyFile, rm } from "fs/promises";
699
- import { tmpdir } from "os";
700
- import { join } from "path";
765
+ import { join as join2 } from "path";
701
766
  var logger = createServiceLogger("multi-key-probe");
702
767
  function gaugesToSamples(stdout) {
703
768
  const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
@@ -725,12 +790,12 @@ function gaugesToSamples(stdout) {
725
790
  async function isolatedProbe(token, now) {
726
791
  let dir = null;
727
792
  try {
728
- dir = await mkdtemp(join(tmpdir(), "conveyor-usage-"));
729
- await writeFile(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
793
+ dir = await mkdtemp(join2(sessionTempBase(), "conveyor-usage-"));
794
+ await writeFile(join2(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
730
795
  encoding: "utf8",
731
796
  mode: 384
732
797
  });
733
- await copyFile(claudeJsonPath(), join(dir, ".claude.json")).catch(() => {
798
+ await copyFile(claudeJsonPath(), join2(dir, ".claude.json")).catch(() => {
734
799
  });
735
800
  return await runUsageProbe({ env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
736
801
  } catch (error) {
@@ -765,428 +830,6 @@ async function probeKeysUsage(keys, deps = {}) {
765
830
  return results;
766
831
  }
767
832
 
768
- // src/harness/pty/adapters/opencode-auth.ts
769
- import { promises as fs } from "fs";
770
- import { dirname, join as join2 } from "path";
771
- import { homedir } from "os";
772
- var logger2 = createServiceLogger("opencode-auth");
773
- var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
774
- var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
775
- function opencodeAuthPath(env) {
776
- const dataHome = env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local", "share");
777
- return join2(dataHome, "opencode", "auth.json");
778
- }
779
- function opencodeConfigPath(env) {
780
- const configHome = env.XDG_CONFIG_HOME ?? join2(env.HOME ?? homedir(), ".config");
781
- return join2(configHome, "opencode", "opencode.json");
782
- }
783
- function parseOauthSeed(b64) {
784
- if (!b64) return null;
785
- try {
786
- const parsed = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
787
- if (typeof parsed.access === "string" && typeof parsed.refresh === "string" && typeof parsed.expires === "number") {
788
- return { access: parsed.access, refresh: parsed.refresh, expires: parsed.expires };
789
- }
790
- return null;
791
- } catch {
792
- return null;
793
- }
794
- }
795
- function shouldSeed(existingEntry, seed) {
796
- if (!existingEntry || typeof existingEntry !== "object") return true;
797
- const entry = existingEntry;
798
- if (entry.type !== "oauth") return true;
799
- if (typeof entry.access !== "string" || typeof entry.refresh !== "string") return true;
800
- if (typeof entry.expires !== "number") return true;
801
- return entry.expires < seed.expires;
802
- }
803
- async function readJsonFile(path) {
804
- try {
805
- return JSON.parse(await fs.readFile(path, "utf8"));
806
- } catch {
807
- return {};
808
- }
809
- }
810
- async function writeJsonFile(path, value) {
811
- await fs.mkdir(dirname(path), { recursive: true });
812
- await fs.writeFile(path, `${JSON.stringify(value, null, 2)}
813
- `, { mode: 384 });
814
- }
815
- async function ensureAuthEntry(env, seed) {
816
- const path = opencodeAuthPath(env);
817
- const store = await readJsonFile(path);
818
- if (!shouldSeed(store.openai, seed)) {
819
- logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
820
- return;
821
- }
822
- store.openai = {
823
- type: "oauth",
824
- access: seed.access,
825
- refresh: seed.refresh,
826
- expires: seed.expires
827
- };
828
- await writeJsonFile(path, store);
829
- logger2.info("seeded opencode oauth store entry");
830
- }
831
- async function ensurePluginConfig(env) {
832
- const path = opencodeConfigPath(env);
833
- const config = await readJsonFile(path);
834
- const plugins = Array.isArray(config.plugin) ? config.plugin : [];
835
- const isOurs = (p) => typeof p === "string" && (p === PLUGIN_PACKAGE || p.startsWith(`${PLUGIN_PACKAGE}@`));
836
- const hasExactPin = plugins.includes(OPENCODE_CODEX_PLUGIN);
837
- const hasStalePin = plugins.some((p) => isOurs(p) && p !== OPENCODE_CODEX_PLUGIN);
838
- if (hasExactPin && !hasStalePin) return;
839
- const kept = plugins.filter((p) => !isOurs(p));
840
- config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
841
- await writeJsonFile(path, config);
842
- logger2.info("ensured opencode codex-auth plugin in config");
843
- }
844
- async function seedOpenCodeOauth(env) {
845
- const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
846
- if (!seed) return;
847
- try {
848
- await ensureAuthEntry(env, seed);
849
- await ensurePluginConfig(env);
850
- } catch (err) {
851
- logger2.warn(
852
- `failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
853
- );
854
- }
855
- }
856
-
857
- // src/harness/pty/adapters/opencode.ts
858
- var PROVIDER_KEY_ENV = {
859
- openai: "OPENAI_API_KEY",
860
- anthropic: "ANTHROPIC_API_KEY"
861
- };
862
- var OpenCodeTuiAdapter = class {
863
- constructor(env = process.env) {
864
- this.env = env;
865
- }
866
- env;
867
- id = "opencode";
868
- capabilities = {
869
- resume: false,
870
- structuredEvents: false,
871
- prefill: false,
872
- passiveTurns: false
873
- };
874
- resolveBinary(env = this.env) {
875
- const override = env.CONVEYOR_OPENCODE_BIN;
876
- const found = override ? findOnPath(override, env) : findOnPath("opencode", env);
877
- if (!found) {
878
- throw new TuiUnavailableError(
879
- "opencode",
880
- "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."
881
- );
882
- }
883
- return found;
884
- }
885
- buildSpawn(input) {
886
- const env = { ...inheritedEnv() };
887
- delete env.CONVEYOR_AGENT_KEY;
888
- delete env.CONVEYOR_OPENCODE_OAUTH;
889
- const oauthActive = Boolean(this.env.CONVEYOR_OPENCODE_OAUTH);
890
- const key = this.env.CONVEYOR_AGENT_KEY;
891
- const provider = this.env.CONVEYOR_AGENT_PROVIDER ?? "openai";
892
- const keyEnvVar = PROVIDER_KEY_ENV[provider];
893
- if (key && keyEnvVar && !oauthActive) env[keyEnvVar] = key;
894
- const model = this.env.CONVEYOR_AGENT_MODEL ?? input.options.model;
895
- const args = [];
896
- if (model) args.push("--model", model.includes("/") ? model : `${provider}/${model}`);
897
- return { file: this.resolveBinary(), args, env };
898
- }
899
- async prepareEnvironment() {
900
- await seedOpenCodeOauth(this.env);
901
- }
902
- spawnFingerprint(input) {
903
- return JSON.stringify(["opencode", input.model, input.cwd]);
904
- }
905
- encodePromptBytes(text) {
906
- return buildPromptBytes(text);
907
- }
908
- buildExitErrors(exitCode, rawOutput) {
909
- const errors = [`opencode exited (code ${exitCode}) without a result`];
910
- const tail = cleanTerminalOutput(rawOutput);
911
- if (tail) errors.push(`Last terminal output before exit:
912
- ${tail}`);
913
- return errors;
914
- }
915
- };
916
-
917
- // src/harness/pty/adapters/index.ts
918
- function resolveTuiAdapter(kind = "claude-code") {
919
- switch (kind) {
920
- case "claude-code":
921
- return new ClaudeTuiAdapter();
922
- case "opencode":
923
- return new OpenCodeTuiAdapter();
924
- default:
925
- throw new Error(`Unknown TUI kind: ${kind}`);
926
- }
927
- }
928
-
929
- // src/tools/project-tools.ts
930
- import { z } from "zod";
931
- var CONTEXT_PATH_SHAPE = z.object({
932
- type: z.enum(["rule", "doc", "file", "folder"]).describe("Link kind"),
933
- path: z.string().min(1).max(500).describe("Repo-relative path"),
934
- label: z.string().max(100).optional()
935
- });
936
- function errText(prefix, error) {
937
- return textResult(`${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`);
938
- }
939
- function buildListTagsTool(connection, projectId) {
940
- return defineTool(
941
- "list_tags",
942
- "List this project's tags (id, name, color). Use the ids with update_tag.",
943
- {},
944
- async () => {
945
- try {
946
- const tags = await connection.call("listProjectTags", { projectId });
947
- return textResult(JSON.stringify(tags, null, 2));
948
- } catch (error) {
949
- return errText("Failed to list tags", error);
950
- }
951
- },
952
- { annotations: { readOnlyHint: true } }
953
- );
954
- }
955
- function buildCreateTagTool(connection, projectId) {
956
- return defineTool(
957
- "create_tag",
958
- "Create a project tag. Include a crisp description and contextPaths (rule/doc/file/folder links agents auto-load when working on matching tasks). Fails if the name already exists.",
959
- {
960
- name: z.string().min(1).max(50),
961
- color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("#RRGGBB (default gray)"),
962
- description: z.string().max(500).optional(),
963
- contextPaths: z.array(CONTEXT_PATH_SHAPE).max(20).optional()
964
- },
965
- async ({ name, color, description, contextPaths }) => {
966
- try {
967
- const result = await connection.call("createProjectTag", {
968
- projectId,
969
- name,
970
- color,
971
- description,
972
- contextPaths
973
- });
974
- return textResult(`Tag created: ${result.id}`);
975
- } catch (error) {
976
- return errText("Failed to create tag", error);
977
- }
978
- }
979
- );
980
- }
981
- function buildUpdateTagTool(connection, projectId) {
982
- return defineTool(
983
- "update_tag",
984
- "Update a tag's name, color, description, or contextPaths. contextPaths is a FULL replacement \u2014 include existing links you want to keep. Verify paths exist in the repo first.",
985
- {
986
- tagId: z.string().describe("Tag id from list_tags"),
987
- name: z.string().min(1).max(50).optional(),
988
- color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
989
- description: z.string().max(500).optional(),
990
- contextPaths: z.array(CONTEXT_PATH_SHAPE).max(20).optional()
991
- },
992
- async ({ tagId, name, color, description, contextPaths }) => {
993
- try {
994
- await connection.call("updateProjectTag", {
995
- projectId,
996
- tagId,
997
- name,
998
- color,
999
- description,
1000
- contextPaths
1001
- });
1002
- return textResult(`Tag updated: ${tagId}`);
1003
- } catch (error) {
1004
- return errText("Failed to update tag", error);
1005
- }
1006
- }
1007
- );
1008
- }
1009
- function buildCreateSuggestionTool(connection, projectId) {
1010
- return defineTool(
1011
- "create_suggestion",
1012
- "File a project suggestion (idea/improvement for maintainers to review). Duplicates are AI-deduped into an existing suggestion with an upvote. Returns the suggestion id.",
1013
- {
1014
- title: z.string().min(1).describe("Short title"),
1015
- description: z.string().optional().describe("1-3 sentences: what should change and why"),
1016
- tag_names: z.array(z.string()).optional().describe("Tag names to categorize")
1017
- },
1018
- async ({ title, description, tag_names }) => {
1019
- try {
1020
- const result = await connection.call("createProjectSuggestion", {
1021
- projectId,
1022
- title,
1023
- description,
1024
- tagNames: tag_names
1025
- });
1026
- return textResult(
1027
- result.merged ? `Merged into existing suggestion ${result.mergedIntoId ?? result.id} (id: ${result.id})` : `Suggestion created: ${result.id}`
1028
- );
1029
- } catch (error) {
1030
- return errText("Failed to create suggestion", error);
1031
- }
1032
- }
1033
- );
1034
- }
1035
- function buildPostToProjectChatTool(connection, projectId) {
1036
- return defineTool(
1037
- "post_to_project_chat",
1038
- "Post a markdown message to the PROJECT chat \u2014 use once at the end of an audit for the summary the team reads.",
1039
- {
1040
- message: z.string().min(1).max(2e4),
1041
- kind: z.enum(["tag_audit_summary"]).optional().describe(
1042
- "Set to 'tag_audit_summary' when posting a tag-audit summary so it is also saved to the persistent tag history"
1043
- )
1044
- },
1045
- async ({ message, kind }) => {
1046
- try {
1047
- await connection.call("postToProjectChat", { projectId, content: message, kind });
1048
- return textResult("Posted to project chat");
1049
- } catch (error) {
1050
- return errText("Failed to post to project chat", error);
1051
- }
1052
- }
1053
- );
1054
- }
1055
- function buildGetProjectTaskTool(connection, projectId) {
1056
- return defineTool(
1057
- "get_project_task",
1058
- "Fetch any task in the project by id or slug: title, description, plan, status, and metadata. The audit evidence trail starts here.",
1059
- {
1060
- taskId: z.string().describe("Task id or slug")
1061
- },
1062
- async ({ taskId }) => {
1063
- try {
1064
- const task = await connection.call("getProjectTask", { projectId, taskId });
1065
- return textResult(JSON.stringify(task, null, 2));
1066
- } catch (error) {
1067
- return errText("Failed to get task", error);
1068
- }
1069
- },
1070
- { annotations: { readOnlyHint: true } }
1071
- );
1072
- }
1073
- function buildReadProjectTaskChatTool(connection, projectId) {
1074
- return defineTool(
1075
- "read_project_task_chat",
1076
- "Read any project task's chat messages (newest last). role 'user' rows are HUMAN turns; 'assistant'/'system' rows are agent posts and activity-log entries.",
1077
- {
1078
- taskId: z.string().describe("Task id or slug"),
1079
- limit: z.number().int().min(1).max(200).optional().describe("Messages to fetch (default 50)")
1080
- },
1081
- async ({ taskId, limit }) => {
1082
- try {
1083
- const chat = await connection.call("getProjectTaskChat", {
1084
- projectId,
1085
- taskId,
1086
- limit: limit ?? 50
1087
- });
1088
- return textResult(JSON.stringify(chat, null, 2));
1089
- } catch (error) {
1090
- return errText("Failed to read task chat", error);
1091
- }
1092
- },
1093
- { annotations: { readOnlyHint: true } }
1094
- );
1095
- }
1096
- function buildGetProjectTaskLogsTool(connection, projectId) {
1097
- return defineTool(
1098
- "get_project_task_logs",
1099
- "Read any project task's persisted agent event stream (message / tool_use / turn_end / error / completed). Turn boundaries are turn_end events. Entries are truncated to ~2KB each; max 500 per call.",
1100
- {
1101
- taskId: z.string().describe("Task id or slug"),
1102
- limit: z.number().int().min(1).max(500).optional().describe("Entries to fetch (default 50)"),
1103
- source: z.enum(["agent", "application"]).optional().describe("Filter: 'agent' = model events (default useful for grading)")
1104
- },
1105
- async ({ taskId, limit, source }) => {
1106
- try {
1107
- const logs = await connection.call("getProjectTaskCli", {
1108
- projectId,
1109
- taskId,
1110
- limit: limit ?? 50,
1111
- source
1112
- });
1113
- return textResult(JSON.stringify(logs, null, 2));
1114
- } catch (error) {
1115
- return errText("Failed to get task logs", error);
1116
- }
1117
- },
1118
- { annotations: { readOnlyHint: true } }
1119
- );
1120
- }
1121
- var TURN_GRADE_SHAPE = z.object({
1122
- turnIndex: z.number().int().min(0),
1123
- phase: z.enum(["planning", "building", "human"]),
1124
- grade: z.enum(["correct", "neutral", "blunder"]),
1125
- reasoning: z.string(),
1126
- eventType: z.string().describe('e.g. "message", "tool_use", "human_message"'),
1127
- eventSummary: z.string().max(200).describe("\u2264120 chars of what happened this turn")
1128
- });
1129
- var HUMAN_EVAL_SHAPE = z.object({
1130
- messageIndex: z.number().int().min(0).describe("Index into the task's human messages, oldest first"),
1131
- rating: z.number().int().min(-1).max(1),
1132
- reasoning: z.string()
1133
- });
1134
- function buildReportTaskAuditResultTool(connection, projectId) {
1135
- return defineTool(
1136
- "report_task_audit_result",
1137
- "Persist one audited task's grades (call once per task after grading it). Pass error instead to mark the audit failed when the evidence is unusable.",
1138
- {
1139
- taskId: z.string().describe("The audited task's id (NOT slug)"),
1140
- summary: z.string().describe("3-6 sentences: what went well, what was wasted"),
1141
- turnGrades: z.array(TURN_GRADE_SHAPE),
1142
- planningAccuracy: z.number().min(0).max(1).nullable(),
1143
- buildingAccuracy: z.number().min(0).max(1).nullable(),
1144
- humanAccuracy: z.number().min(0).max(1).nullable(),
1145
- planningCorrect: z.number().int().min(0),
1146
- planningNeutral: z.number().int().min(0),
1147
- planningBlunder: z.number().int().min(0),
1148
- buildingCorrect: z.number().int().min(0),
1149
- buildingNeutral: z.number().int().min(0),
1150
- buildingBlunder: z.number().int().min(0),
1151
- humanCorrect: z.number().int().min(0),
1152
- humanNeutral: z.number().int().min(0),
1153
- humanBlunder: z.number().int().min(0),
1154
- humanEvaluations: z.array(HUMAN_EVAL_SHAPE).optional(),
1155
- suggestionIds: z.array(z.string()).describe("Suggestion ids filed for this task, or []"),
1156
- auditCostUsd: z.number().nullable(),
1157
- model: z.string().nullable().describe("The model you are running as"),
1158
- error: z.string().optional().describe("Set ONLY to mark this task's audit failed")
1159
- },
1160
- async (input) => {
1161
- try {
1162
- await connection.call("reportTaskAuditResult", {
1163
- projectId,
1164
- ...input,
1165
- humanEvaluations: input.humanEvaluations ?? []
1166
- });
1167
- return textResult(
1168
- input.error ? `Audit for ${input.taskId} marked failed` : `Audit result saved for ${input.taskId}`
1169
- );
1170
- } catch (error) {
1171
- return errText("Failed to report audit result", error);
1172
- }
1173
- }
1174
- );
1175
- }
1176
- function buildProjectTools(connection, projectId) {
1177
- return [
1178
- buildListTagsTool(connection, projectId),
1179
- buildCreateTagTool(connection, projectId),
1180
- buildUpdateTagTool(connection, projectId),
1181
- buildCreateSuggestionTool(connection, projectId),
1182
- buildPostToProjectChatTool(connection, projectId),
1183
- buildGetProjectTaskTool(connection, projectId),
1184
- buildReadProjectTaskChatTool(connection, projectId),
1185
- buildGetProjectTaskLogsTool(connection, projectId),
1186
- buildReportTaskAuditResultTool(connection, projectId)
1187
- ];
1188
- }
1189
-
1190
833
  // src/runner/adhoc-session-runner.ts
1191
834
  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.";
1192
835
  var HEADLESS_SYSTEM_NOTE = "You are running a HEADLESS Conveyor session on the project's repository \u2014 your instructions were auto-submitted as the first message. Work them to completion autonomously and never wait for user input; a human may attach to this terminal to watch or interject, but none is required.";
@@ -1200,16 +843,15 @@ function resolveInitialPrompt(env = process.env) {
1200
843
  return null;
1201
844
  }
1202
845
  }
1203
- function resolveAdhocTui(env) {
1204
- const raw = env.CONVEYOR_TUI ?? "claude-code";
1205
- if (TUI_KINDS.includes(raw)) return raw;
1206
- throw new Error(`Unknown TUI "${raw}" in CONVEYOR_TUI (expected: ${TUI_KINDS.join(", ")})`);
1207
- }
1208
- function buildAdhocPtyBridge(connection) {
846
+ var resolveAdhocTui = resolveTuiKindFromEnv;
847
+ function buildAdhocPtyBridge(connection, onInputActivity) {
1209
848
  return {
1210
849
  sendOutput: (data, dims) => connection.sendPtyOutput(data, dims),
1211
850
  sendChatEvent: (event) => connection.sendPtyChatEvent(event),
1212
- onInput: (handler) => connection.onPtyInput(handler),
851
+ onInput: (handler) => connection.onPtyInput((data) => {
852
+ onInputActivity?.();
853
+ handler(data);
854
+ }),
1213
855
  onResize: (handler) => connection.onPtyResize(handler)
1214
856
  };
1215
857
  }
@@ -1258,11 +900,18 @@ var AdhocSessionRunner = class {
1258
900
  this.callbacks = callbacks;
1259
901
  this.connection = deps?.connection ?? new AgentConnection(config.connection);
1260
902
  this.harness = deps?.harness ?? new PtyHarness(
1261
- buildAdhocPtyBridge(this.connection),
903
+ // A keystroke is real human activity — re-arm the idle backstop so an
904
+ // actively-typing human (or a >60-min audit run) is never hard-stopped
905
+ // by the idle timer, mirroring ShellSessionRunner. `this.lifecycle` is
906
+ // assigned later in this constructor but is always set by the time a
907
+ // keystroke can arrive at runtime.
908
+ buildAdhocPtyBridge(this.connection, () => this.lifecycle.startIdleTimer()),
1262
909
  resolveTuiAdapter(resolveAdhocTui(process.env))
1263
910
  );
911
+ const portVisibility = new CodespacePortVisibility();
1264
912
  this.portDiscovery = deps?.portDiscovery ?? new PortDiscovery({
1265
- report: (ports) => this.connection.reportDiscoveredPorts(ports)
913
+ report: (ports) => this.connection.reportDiscoveredPorts(ports),
914
+ onReported: (ports) => portVisibility.ensureVisible(ports.map(({ port }) => port))
1266
915
  });
1267
916
  this.commandSupervisor = deps?.commandSupervisor ?? new WorkspaceCommandSupervisor({
1268
917
  config: loadConveyorConfig(),
@@ -1344,7 +993,11 @@ var AdhocSessionRunner = class {
1344
993
  initialPrompt,
1345
994
  conveyorMcpServer: this.harness.createMcpServer({
1346
995
  name: "conveyor",
1347
- tools: buildProjectTools(this.connection, this.config.projectId)
996
+ tools: buildProjectTools(
997
+ this.connection,
998
+ this.config.projectId,
999
+ this.config.workspaceDir
1000
+ )
1348
1001
  })
1349
1002
  } : void 0;
1350
1003
  const options = buildAdhocQueryOptions(
@@ -1455,7 +1108,7 @@ var AdhocSessionRunner = class {
1455
1108
  import { spawn as nodeSpawn, execFile } from "child_process";
1456
1109
  import { promisify } from "util";
1457
1110
  var execFileAsync = promisify(execFile);
1458
- var logger3 = createServiceLogger("ReviewChild");
1111
+ var logger2 = createServiceLogger("ReviewChild");
1459
1112
  var SPAWN_FAILURE_GRACE_MS = 15e3;
1460
1113
  var KILL_WAIT_MS = 5e3;
1461
1114
  function buildReviewChildEnv(baseEnv, data) {
@@ -1463,14 +1116,15 @@ function buildReviewChildEnv(baseEnv, data) {
1463
1116
  env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
1464
1117
  env.CONVEYOR_SESSION_ID = data.sessionId;
1465
1118
  env.CONVEYOR_MODE = "code-review";
1119
+ env.CONVEYOR_WORKBENCH_TOKEN ??= baseEnv.POD_BOOTSTRAP_TOKEN;
1466
1120
  delete env.POD_BOOTSTRAP_TOKEN;
1467
1121
  delete env.CONVEYOR_BOOTSTRAP_TOKEN;
1468
- delete env.CONVEYOR_SETUP_COMMAND;
1469
1122
  delete env.CONVEYOR_START_COMMAND;
1470
1123
  delete env.CONVEYOR_GIT_READY;
1471
1124
  delete env.CONVEYOR_GIT_READY_MARKER;
1472
1125
  delete env.CONVEYOR_AGENT_MODE;
1473
1126
  delete env.CONVEYOR_IS_AUTO;
1127
+ delete env.CONVEYOR_TUI;
1474
1128
  return env;
1475
1129
  }
1476
1130
  var ReviewChildSupervisor = class {
@@ -1486,7 +1140,7 @@ var ReviewChildSupervisor = class {
1486
1140
  childSessionId = null;
1487
1141
  /** Spawn (or replace) the review child for a `session:spawnReview` push. */
1488
1142
  async spawn(data) {
1489
- logger3.info("Spawning same-pod review child", {
1143
+ logger2.info("Spawning same-pod review child", {
1490
1144
  reviewSessionId: data.sessionId,
1491
1145
  branch: data.branch ?? void 0,
1492
1146
  prNumber: data.prNumber ?? void 0
@@ -1529,7 +1183,7 @@ var ReviewChildSupervisor = class {
1529
1183
  process.stderr.write(`[review-child] ${chunk.toString()}`);
1530
1184
  });
1531
1185
  child.on("error", (err) => {
1532
- logger3.error("Review child spawn error", { error: err.message });
1186
+ logger2.error("Review child spawn error", { error: err.message });
1533
1187
  if (this.child === child) {
1534
1188
  this.child = null;
1535
1189
  this.childSessionId = null;
@@ -1542,7 +1196,7 @@ var ReviewChildSupervisor = class {
1542
1196
  this.child = null;
1543
1197
  this.childSessionId = null;
1544
1198
  }
1545
- logger3.info("Review child exited", { code, signal, reviewSessionId: data.sessionId });
1199
+ logger2.info("Review child exited", { code, signal, reviewSessionId: data.sessionId });
1546
1200
  const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
1547
1201
  if (wasCurrent && withinGrace && code !== null && code !== 0) {
1548
1202
  reportOnce(`review child exited with code ${code} within startup grace`);
@@ -1560,7 +1214,7 @@ var ReviewChildSupervisor = class {
1560
1214
  this.childSessionId = null;
1561
1215
  return;
1562
1216
  }
1563
- logger3.info("Stopping review child", { reason, reviewSessionId: this.childSessionId });
1217
+ logger2.info("Stopping review child", { reason, reviewSessionId: this.childSessionId });
1564
1218
  this.child = null;
1565
1219
  this.childSessionId = null;
1566
1220
  await new Promise((resolve) => {
@@ -1602,7 +1256,7 @@ var ReviewChildSupervisor = class {
1602
1256
  timeout: 3e4
1603
1257
  });
1604
1258
  } catch (err) {
1605
- logger3.warn("Review checkout freshen skipped", {
1259
+ logger2.warn("Review checkout freshen skipped", {
1606
1260
  branch,
1607
1261
  error: err instanceof Error ? err.message : String(err)
1608
1262
  });
@@ -1612,7 +1266,7 @@ var ReviewChildSupervisor = class {
1612
1266
 
1613
1267
  // src/runner/session-child.ts
1614
1268
  import { spawn as nodeSpawn2 } from "child_process";
1615
- var logger4 = createServiceLogger("SessionChild");
1269
+ var logger3 = createServiceLogger("SessionChild");
1616
1270
  function buildSpawnedChildEnv(baseEnv, data) {
1617
1271
  const env = { ...baseEnv };
1618
1272
  env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
@@ -1621,7 +1275,6 @@ function buildSpawnedChildEnv(baseEnv, data) {
1621
1275
  env.CONVEYOR_PROJECT_ID = data.projectId;
1622
1276
  delete env.POD_BOOTSTRAP_TOKEN;
1623
1277
  delete env.CONVEYOR_BOOTSTRAP_TOKEN;
1624
- delete env.CONVEYOR_SETUP_COMMAND;
1625
1278
  delete env.CONVEYOR_START_COMMAND;
1626
1279
  delete env.CONVEYOR_AGENT_MODE;
1627
1280
  delete env.CONVEYOR_IS_AUTO;
@@ -1643,7 +1296,7 @@ var SessionChildSupervisor = class {
1643
1296
  }
1644
1297
  /** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
1645
1298
  async spawn(data) {
1646
- logger4.info("Spawning same-pod session child", {
1299
+ logger3.info("Spawning same-pod session child", {
1647
1300
  spawnedSessionId: data.sessionId,
1648
1301
  mode: data.mode
1649
1302
  });
@@ -1684,7 +1337,7 @@ var SessionChildSupervisor = class {
1684
1337
  process.stderr.write(`${prefix} ${chunk.toString()}`);
1685
1338
  });
1686
1339
  child.on("error", (err) => {
1687
- logger4.error("Session child spawn error", { error: err.message });
1340
+ logger3.error("Session child spawn error", { error: err.message });
1688
1341
  if (this.children.get(data.sessionId) === child) {
1689
1342
  this.children.delete(data.sessionId);
1690
1343
  }
@@ -1693,7 +1346,7 @@ var SessionChildSupervisor = class {
1693
1346
  child.on("exit", (code, signal) => {
1694
1347
  const wasCurrent = this.children.get(data.sessionId) === child;
1695
1348
  if (wasCurrent) this.children.delete(data.sessionId);
1696
- logger4.info("Session child exited", {
1349
+ logger3.info("Session child exited", {
1697
1350
  code,
1698
1351
  signal,
1699
1352
  spawnedSessionId: data.sessionId,
@@ -1714,7 +1367,7 @@ var SessionChildSupervisor = class {
1714
1367
  const child = this.children.get(sessionId);
1715
1368
  this.children.delete(sessionId);
1716
1369
  if (!child || child.exitCode !== null || child.killed) return;
1717
- logger4.info("Stopping session child", { reason, spawnedSessionId: sessionId });
1370
+ logger3.info("Stopping session child", { reason, spawnedSessionId: sessionId });
1718
1371
  await new Promise((resolve) => {
1719
1372
  const timer = setTimeout(() => {
1720
1373
  try {
@@ -1759,7 +1412,7 @@ var ShellSessionRunner = class {
1759
1412
  constructor(config, deps) {
1760
1413
  this.config = config;
1761
1414
  this.connection = deps?.connection ?? new AgentConnection(config.connection);
1762
- this.loadSpawn = deps?.loadSpawn ?? loadPtySpawn;
1415
+ this.loadSpawn = deps?.loadSpawn ?? resolvePtySpawn;
1763
1416
  this.lifecycle = new Lifecycle(
1764
1417
  // No git flush — the human commits/pushes explicitly from the shell.
1765
1418
  { ...DEFAULT_LIFECYCLE_CONFIG, gitFlushIntervalMs: 0, ...config.lifecycle },
@@ -1862,7 +1515,7 @@ var ShellSessionRunner = class {
1862
1515
  };
1863
1516
 
1864
1517
  // src/runner/spawned-child-boot.ts
1865
- var logger5 = createServiceLogger("SpawnedChildBoot");
1518
+ var logger4 = createServiceLogger("SpawnedChildBoot");
1866
1519
  function createSpawnedChildRunner(inputs) {
1867
1520
  if (inputs.mode === "shell") {
1868
1521
  return new ShellSessionRunner({
@@ -1892,15 +1545,20 @@ function createSpawnedChildRunner(inputs) {
1892
1545
  },
1893
1546
  {
1894
1547
  onEvent: (event) => {
1895
- logger5.info("Spawned TUI event", { eventType: event.type });
1548
+ logger4.info("Spawned TUI event", { eventType: event.type });
1896
1549
  }
1897
1550
  }
1898
1551
  );
1899
1552
  }
1900
1553
 
1554
+ // src/runner/host-modes.ts
1555
+ function hostsSpawnedChildren(mode) {
1556
+ return mode === "task" || mode === "pack";
1557
+ }
1558
+
1901
1559
  // src/cli.ts
1902
1560
  if (process.argv[2] === "boot") {
1903
- const { runBoot } = await import("./boot-YZCOIV3Q.js");
1561
+ const { runBoot } = await import("./boot-BZE36LT6.js");
1904
1562
  process.exit(await runBoot(process.argv.slice(3)));
1905
1563
  }
1906
1564
  if (isLegacyEntrypointLaunch(process.env)) {
@@ -1915,9 +1573,10 @@ if (process.argv.includes("--version")) {
1915
1573
  process.stdout.write(pkg.version + "\n");
1916
1574
  process.exit(0);
1917
1575
  }
1918
- var logger6 = createServiceLogger("CLI");
1576
+ var logger5 = createServiceLogger("CLI");
1919
1577
  var exitContext = {};
1920
1578
  var exitLogged = false;
1579
+ var shuttingDown = false;
1921
1580
  function logAgentExit(reason, opts) {
1922
1581
  if (exitLogged) return;
1923
1582
  exitLogged = true;
@@ -1929,12 +1588,12 @@ function logAgentExit(reason, opts) {
1929
1588
  uptimeSec: process.uptime(),
1930
1589
  identity: exitContext
1931
1590
  });
1932
- logger6[exitLogLevel(reason)]("agent_exit", payload);
1591
+ logger5[exitLogLevel(reason)]("agent_exit", payload);
1933
1592
  }
1934
1593
  async function bootstrapFromCodespace(apiUrl, instanceName) {
1935
1594
  const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
1936
1595
  const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
1937
- logger6.info("Bootstrapping from codespace", {
1596
+ logger5.info("Bootstrapping from codespace", {
1938
1597
  codespace: instanceName,
1939
1598
  apiUrl,
1940
1599
  apiUrlFromEnv,
@@ -1946,7 +1605,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
1946
1605
  bootstrapToken
1947
1606
  });
1948
1607
  if (!result.ok) {
1949
- logger6.error("Bootstrap failed after retries", {
1608
+ logger5.error("Bootstrap failed after retries", {
1950
1609
  reason: result.reason,
1951
1610
  attempts: result.attempts,
1952
1611
  status: result.status,
@@ -1959,7 +1618,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
1959
1618
  process.exit(1);
1960
1619
  }
1961
1620
  applyBootstrapToEnv(result.config);
1962
- logger6.info("Bootstrap complete", {
1621
+ logger5.info("Bootstrap complete", {
1963
1622
  taskId: result.config.taskId,
1964
1623
  attempts: result.attempts
1965
1624
  });
@@ -1970,33 +1629,44 @@ function isExpectedAbortError(error) {
1970
1629
  const hasAbortCode = !("code" in error) || error.code === void 0 || error.code === "ABORT_ERR";
1971
1630
  return hasAbortMessage && hasAbortCode;
1972
1631
  }
1632
+ function exitOnPermissionDenial(err) {
1633
+ if (!isPermissionDeniedError(err)) return false;
1634
+ logger5.error("Not authorized for this project \u2014 parking instead of crash-looping", {
1635
+ error: err.message
1636
+ });
1637
+ logAgentExit("unauthorized", { exitCode: 0 });
1638
+ process.exit(0);
1639
+ }
1973
1640
  process.on("uncaughtException", (err) => {
1974
1641
  if (err.code === "EPIPE") return;
1975
- if (isExpectedAbortError(err)) {
1976
- logger6.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
1642
+ if (shuttingDown && isExpectedAbortError(err)) {
1643
+ logger5.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
1977
1644
  return;
1978
1645
  }
1979
- logger6.error("Uncaught exception", { error: err.message, code: err.code });
1646
+ if (exitOnPermissionDenial(err)) return;
1647
+ logger5.error("Uncaught exception", { error: err.message, code: err.code });
1980
1648
  logAgentExit("uncaught_exception", { exitCode: 1 });
1981
1649
  process.exit(1);
1982
1650
  });
1983
1651
  process.on("unhandledRejection", (reason) => {
1984
1652
  const err = reason instanceof Error ? reason : new Error(String(reason));
1985
- if (isExpectedAbortError(err)) {
1986
- logger6.info("Ignored expected abort rejection after shutdown", { error: err.message });
1653
+ if (shuttingDown && isExpectedAbortError(err)) {
1654
+ logger5.info("Ignored expected abort rejection after shutdown", { error: err.message });
1987
1655
  return;
1988
1656
  }
1989
- logger6.error("Unhandled rejection", { error: err.message });
1657
+ if (exitOnPermissionDenial(err)) return;
1658
+ logger5.error("Unhandled rejection", { error: err.message });
1990
1659
  logAgentExit("unhandled_rejection", { exitCode: 1 });
1991
1660
  process.exit(1);
1992
1661
  });
1993
1662
  if (process.env.CONVEYOR_MODE === "workbench") {
1994
- const { startWorkbenchServer } = await import("./server-BXAFMYDM.js");
1663
+ const { startWorkbenchServer } = await import("./server-PPTELJWY.js");
1664
+ const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-U7JERHA2.js");
1995
1665
  const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QLVS5W6O.js");
1996
1666
  const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
1997
1667
  const token = process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? "";
1998
1668
  if (!token) {
1999
- logger6.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
1669
+ logger5.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
2000
1670
  process.exit(1);
2001
1671
  }
2002
1672
  const pkgDir = dirname2(fileURLToPath(import.meta.url));
@@ -2004,9 +1674,14 @@ if (process.env.CONVEYOR_MODE === "workbench") {
2004
1674
  const handle = await startWorkbenchServer({
2005
1675
  port,
2006
1676
  token,
2007
- version: pkg.version ?? "unknown"
1677
+ version: pkg.version ?? "unknown",
1678
+ oomWatchdog: oomWatchdogOptionsFromEnv({
1679
+ info: (m) => logger5.info(m),
1680
+ warn: (m) => logger5.warn(m),
1681
+ error: (m) => logger5.error(m)
1682
+ })
2008
1683
  });
2009
- logger6.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
1684
+ logger5.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
2010
1685
  process.on("SIGTERM", () => process.exit(0));
2011
1686
  await new Promise(() => {
2012
1687
  });
@@ -2016,7 +1691,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
2016
1691
  var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
2017
1692
  if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
2018
1693
  if (!conveyorApiUrl) {
2019
- logger6.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
1694
+ logger5.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
2020
1695
  process.exit(1);
2021
1696
  }
2022
1697
  await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
@@ -2034,7 +1709,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
2034
1709
  exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
2035
1710
  var projectIdentity = resolveProjectRunnerIdentity(process.env);
2036
1711
  if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
2037
- logger6.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
1712
+ logger5.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
2038
1713
  const adhocRunner = new AdhocSessionRunner(
2039
1714
  {
2040
1715
  connection: {
@@ -2053,15 +1728,21 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
2053
1728
  },
2054
1729
  {
2055
1730
  onEvent: (event) => {
2056
- logger6.info("Ad-hoc runner event", { eventType: event.type });
1731
+ logger5.info("Ad-hoc runner event", { eventType: event.type });
2057
1732
  }
2058
1733
  }
2059
1734
  );
2060
1735
  exitContext.sessionId = projectIdentity.sessionId;
2061
1736
  exitContext.workspaceId = projectIdentity.workspaceId;
2062
1737
  exitContext.runnerMode = "adhoc";
2063
- process.on("SIGTERM", () => adhocRunner.stop());
2064
- process.on("SIGINT", () => adhocRunner.stop());
1738
+ process.on("SIGTERM", () => {
1739
+ shuttingDown = true;
1740
+ adhocRunner.stop();
1741
+ });
1742
+ process.on("SIGINT", () => {
1743
+ shuttingDown = true;
1744
+ adhocRunner.stop();
1745
+ });
2065
1746
  await adhocRunner.run();
2066
1747
  const adhocError = adhocRunner.finalState === "error";
2067
1748
  logAgentExit(adhocError ? "error" : "clean", {
@@ -2071,7 +1752,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
2071
1752
  process.exit(adhocError ? 1 : 0);
2072
1753
  }
2073
1754
  if (!CONVEYOR_TASK_ID && projectIdentity) {
2074
- logger6.info("Starting project agent", { projectId: projectIdentity.projectId });
1755
+ logger5.info("Starting project agent", { projectId: projectIdentity.projectId });
2075
1756
  const projectRunner = new ProjectSessionRunner(
2076
1757
  {
2077
1758
  connection: {
@@ -2086,15 +1767,21 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
2086
1767
  },
2087
1768
  {
2088
1769
  onEvent: (event) => {
2089
- logger6.info("Project runner event", { eventType: event.type });
1770
+ logger5.info("Project runner event", { eventType: event.type });
2090
1771
  }
2091
1772
  }
2092
1773
  );
2093
1774
  exitContext.sessionId = projectIdentity.sessionId;
2094
1775
  exitContext.workspaceId = projectIdentity.workspaceId;
2095
1776
  exitContext.runnerMode = "pm";
2096
- process.on("SIGTERM", () => projectRunner.stop());
2097
- process.on("SIGINT", () => projectRunner.stop());
1777
+ process.on("SIGTERM", () => {
1778
+ shuttingDown = true;
1779
+ projectRunner.stop();
1780
+ });
1781
+ process.on("SIGINT", () => {
1782
+ shuttingDown = true;
1783
+ projectRunner.stop();
1784
+ });
2098
1785
  await projectRunner.run();
2099
1786
  const projectError = projectRunner.finalState === "error";
2100
1787
  logAgentExit(projectError ? "error" : "clean", {
@@ -2104,22 +1791,22 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
2104
1791
  process.exit(projectError ? 1 : 0);
2105
1792
  }
2106
1793
  if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
2107
- logger6.error("Missing required environment variables");
2108
- logger6.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
2109
- logger6.error(" CONVEYOR_TASK_ID - ID of the task to execute");
2110
- logger6.error("");
2111
- logger6.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
2112
- logger6.error("");
2113
- logger6.error("Optional:");
2114
- logger6.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
2115
- logger6.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
2116
- logger6.error(
1794
+ logger5.error("Missing required environment variables");
1795
+ logger5.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
1796
+ logger5.error(" CONVEYOR_TASK_ID - ID of the task to execute");
1797
+ logger5.error("");
1798
+ logger5.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
1799
+ logger5.error("");
1800
+ logger5.error("Optional:");
1801
+ logger5.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
1802
+ logger5.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
1803
+ logger5.error(
2117
1804
  " Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
2118
1805
  );
2119
1806
  process.exit(1);
2120
1807
  }
2121
1808
  if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack" && CONVEYOR_MODE !== "shell") {
2122
- logger6.error("Invalid CONVEYOR_MODE", {
1809
+ logger5.error("Invalid CONVEYOR_MODE", {
2123
1810
  mode: CONVEYOR_MODE,
2124
1811
  expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
2125
1812
  });
@@ -2128,7 +1815,7 @@ if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "cod
2128
1815
  if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
2129
1816
  const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
2130
1817
  if (!spawnedSessionId) {
2131
- logger6.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
1818
+ logger5.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
2132
1819
  process.exit(1);
2133
1820
  }
2134
1821
  exitContext.sessionId = spawnedSessionId;
@@ -2141,12 +1828,18 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
2141
1828
  workspaceDir: CONVEYOR_WORKSPACE,
2142
1829
  projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
2143
1830
  });
2144
- logger6.info("Starting spawned session child", {
1831
+ logger5.info("Starting spawned session child", {
2145
1832
  mode: CONVEYOR_MODE,
2146
1833
  sessionId: spawnedSessionId
2147
1834
  });
2148
- process.on("SIGTERM", () => childRunner.stop());
2149
- process.on("SIGINT", () => childRunner.stop());
1835
+ process.on("SIGTERM", () => {
1836
+ shuttingDown = true;
1837
+ childRunner.stop();
1838
+ });
1839
+ process.on("SIGINT", () => {
1840
+ shuttingDown = true;
1841
+ childRunner.stop();
1842
+ });
2150
1843
  await childRunner.run();
2151
1844
  const childError = childRunner.finalState === "error";
2152
1845
  logAgentExit(childError ? "error" : "clean", {
@@ -2155,7 +1848,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
2155
1848
  });
2156
1849
  process.exit(childError ? 1 : 0);
2157
1850
  }
2158
- logger6.info("Starting agent", { mode: CONVEYOR_MODE });
1851
+ logger5.info("Starting agent", { mode: CONVEYOR_MODE });
2159
1852
  var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
2160
1853
  var runner = new SessionRunner(
2161
1854
  {
@@ -2175,12 +1868,12 @@ var runner = new SessionRunner(
2175
1868
  },
2176
1869
  {
2177
1870
  onStatusChange: (status) => {
2178
- logger6.info("Status changed", { status });
1871
+ logger5.info("Status changed", { status });
2179
1872
  },
2180
1873
  onEvent: (event) => {
2181
1874
  const detail = event.message ?? event.content ?? event.summary ?? "";
2182
1875
  if (detail) {
2183
- logger6.info(detail, { eventType: event.type });
1876
+ logger5.info(detail, { eventType: event.type });
2184
1877
  }
2185
1878
  }
2186
1879
  }
@@ -2191,7 +1884,8 @@ var shutdownSignal;
2191
1884
  var workspaceCommandSupervisor = null;
2192
1885
  var shutdownCompletion = null;
2193
1886
  var shutdownAgent = (signal) => {
2194
- logger6.info(`Received ${signal}, flushing git and stopping agent`);
1887
+ shuttingDown = true;
1888
+ logger5.info(`Received ${signal}, flushing git and stopping agent`);
2195
1889
  const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
2196
1890
  shutdownSignal = signal;
2197
1891
  shutdownCompletion ??= (async () => {
@@ -2208,8 +1902,8 @@ var shutdownAgent = (signal) => {
2208
1902
  })();
2209
1903
  void shutdownCompletion;
2210
1904
  setTimeout(() => {
2211
- logger6.warn(`Forcing exit after ${signal} timeout`);
2212
- logger6.warn(
1905
+ logger5.warn(`Forcing exit after ${signal} timeout`);
1906
+ logger5.warn(
2213
1907
  "agent_exit",
2214
1908
  buildAgentExitLog({
2215
1909
  reason: "force_timeout",
@@ -2235,7 +1929,7 @@ workspaceCommandSupervisor = await startWorkspaceCommandsAfterConnect({
2235
1929
  })
2236
1930
  });
2237
1931
  if (!workspaceCommandSupervisor) {
2238
- logger6.info("Agent stopped before workspace commands were started");
1932
+ logger5.info("Agent stopped before workspace commands were started");
2239
1933
  if (shutdownCompletion) await shutdownCompletion;
2240
1934
  logAgentExit(shutdownSignal ? "signal" : "clean", {
2241
1935
  exitCode: 0,
@@ -2252,12 +1946,12 @@ void checkSessionTaskIdentity({
2252
1946
  const ctx = await runner.connection.call("getTaskContext", { sessionId });
2253
1947
  return ctx.id;
2254
1948
  },
2255
- logger: logger6
1949
+ logger: logger5
2256
1950
  });
2257
- if (CONVEYOR_MODE === "task") {
1951
+ if (hostsSpawnedChildren(CONVEYOR_MODE)) {
2258
1952
  runner.connection.onSpawnReview((data) => {
2259
1953
  void reviewChildren.spawn(data).catch((err) => {
2260
- logger6.error("Review child spawn threw", {
1954
+ logger5.error("Review child spawn threw", {
2261
1955
  error: err instanceof Error ? err.message : String(err)
2262
1956
  });
2263
1957
  runner.connection.reportReviewSpawnFailure(
@@ -2267,10 +1961,10 @@ if (CONVEYOR_MODE === "task") {
2267
1961
  });
2268
1962
  });
2269
1963
  }
2270
- if (CONVEYOR_MODE === "task" || CONVEYOR_MODE === "pack") {
1964
+ if (hostsSpawnedChildren(CONVEYOR_MODE)) {
2271
1965
  runner.connection.onSpawnTui((data) => {
2272
1966
  void sessionChildren.spawn(data).catch((err) => {
2273
- logger6.error("Session child spawn threw", {
1967
+ logger5.error("Session child spawn threw", {
2274
1968
  error: err instanceof Error ? err.message : String(err)
2275
1969
  });
2276
1970
  runner.connection.reportSessionSpawnFailure(
@@ -2293,7 +1987,7 @@ runner.run().then(async () => {
2293
1987
  }).catch(async (error) => {
2294
1988
  await stopWorkspaceCommands(workspaceCommandSupervisor);
2295
1989
  const msg = error instanceof Error ? error.message : String(error);
2296
- logger6.error("Agent runner failed", { error: msg });
1990
+ logger5.error("Agent runner failed", { error: msg });
2297
1991
  logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
2298
1992
  process.exit(1);
2299
1993
  });