@rallycry/conveyor-agent 10.13.18 → 10.13.50

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-I353EAK6.js} +156 -125
  2. package/dist/boot-I353EAK6.js.map +1 -0
  3. package/dist/{chunk-3GDXX3OX.js → chunk-36VMMHYD.js} +5578 -2111
  4. package/dist/chunk-36VMMHYD.js.map +1 -0
  5. package/dist/{chunk-AGGVNADW.js → chunk-3Z4YQNJV.js} +59 -13
  6. package/dist/chunk-3Z4YQNJV.js.map +1 -0
  7. package/dist/chunk-5OQQSDVT.js +142 -0
  8. package/dist/chunk-5OQQSDVT.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-UDSRAHKP.js → chunk-QOJTJCYZ.js} +67 -19
  12. package/dist/chunk-QOJTJCYZ.js.map +1 -0
  13. package/dist/chunk-ULS4QPRE.js +138 -0
  14. package/dist/chunk-ULS4QPRE.js.map +1 -0
  15. package/dist/{chunk-72AEN6LB.js → chunk-XR6H326I.js} +26 -2
  16. package/dist/chunk-XR6H326I.js.map +1 -0
  17. package/dist/cli.js +196 -334
  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-NIOBJ46G.js +10 -0
  26. package/dist/server-NIOBJ46G.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,7 +1,12 @@
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
8
  ClaudeTuiAdapter,
9
+ CodespacePortVisibility,
5
10
  DEFAULT_LIFECYCLE_CONFIG,
6
11
  DEFAULT_SONNET_MODEL,
7
12
  Lifecycle,
@@ -12,14 +17,15 @@ import {
12
17
  TuiUnavailableError,
13
18
  applyBootstrapToEnv,
14
19
  awaitGitReady,
20
+ buildProjectTools,
15
21
  buildSessionPreviewPorts,
16
22
  buildSynthesizedCredentials,
17
23
  claudeJsonPath,
18
24
  cleanTerminalOutput,
19
25
  createServiceLogger,
20
- defineTool,
21
26
  fetchBootstrap,
22
27
  findOnPath,
28
+ isPermissionDeniedError,
23
29
  loadConveyorConfig,
24
30
  loadForwardPorts,
25
31
  parseUsageGauges,
@@ -27,32 +33,32 @@ import {
27
33
  resolveSessionStart,
28
34
  runUsageProbe,
29
35
  sampleKeyUsage,
30
- textResult
31
- } from "./chunk-3GDXX3OX.js";
32
- import "./chunk-7TQO4ZF4.js";
36
+ workspacePathExists
37
+ } from "./chunk-36VMMHYD.js";
33
38
  import {
34
- getWorkbenchClient
35
- } from "./chunk-UDSRAHKP.js";
39
+ reportBootMilestone
40
+ } from "./chunk-KCB7CSWJ.js";
41
+ import "./chunk-5OQQSDVT.js";
36
42
  import {
37
- TOMBSTONE_MESSAGE,
38
- isLegacyEntrypointLaunch
39
- } from "./chunk-6GS5ADIY.js";
40
- import "./chunk-UZTJJD7Y.js";
43
+ getWorkbenchClient
44
+ } from "./chunk-QOJTJCYZ.js";
41
45
  import {
42
46
  workbenchEnabled
43
47
  } from "./chunk-4VUQ2NPF.js";
44
48
  import {
45
49
  buildPromptBytes,
46
50
  inheritedEnv,
47
- loadPtySpawn,
51
+ resolvePtySpawn,
52
+ runSetupCommand,
48
53
  runStartCommand,
54
+ sessionTempBase,
49
55
  terminateProcessGroup
50
- } from "./chunk-72AEN6LB.js";
56
+ } from "./chunk-XR6H326I.js";
51
57
  import "./chunk-JIGG755T.js";
52
58
 
53
59
  // src/cli.ts
54
60
  import { readFileSync } from "fs";
55
- import { join as join3, dirname as dirname2 } from "path";
61
+ import { join as join4, dirname as dirname3 } from "path";
56
62
  import { fileURLToPath } from "url";
57
63
 
58
64
  // src/setup/sidecars.ts
@@ -202,31 +208,71 @@ async function waitForSidecars(opts = {}) {
202
208
  );
203
209
  }
204
210
 
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);
211
+ // src/setup/db-schema-sync.ts
212
+ import { basename, dirname, join } from "path";
213
+ var PRISMA_SCHEMA_CANDIDATES = [
214
+ "packages/db/prisma/schema.prisma",
215
+ "prisma/schema.prisma",
216
+ "apps/api/prisma/schema.prisma",
217
+ "packages/db/schema.prisma"
218
+ ];
219
+ var PRISMA_DB_PUSH = "bunx prisma db push";
220
+ var SCHEMA_SYNC_TIMEOUT_MS = 18e4;
221
+ function depsInclude(rawDeps, dep) {
222
+ return (rawDeps ?? "").split(",").map((entry) => entry.trim()).includes(dep);
223
+ }
224
+ function isDisabled(env) {
225
+ const flag = env.CONVEYOR_DB_SCHEMA_SYNC?.trim().toLowerCase();
226
+ return flag === "0" || flag === "false";
227
+ }
228
+ function packageRootForSchema(schemaPath) {
229
+ const dir = dirname(schemaPath);
230
+ return basename(dir) === "prisma" ? dirname(dir) : dir;
231
+ }
232
+ async function resolveSchemaSyncPlan(options) {
233
+ const { workspaceDir, env } = options;
234
+ const pathExists = options.pathExists ?? workspacePathExists;
235
+ if (isDisabled(env)) return null;
236
+ const override = env.CONVEYOR_DB_SCHEMA_SYNC_COMMAND?.trim();
237
+ if (override) return { command: override, cwd: workspaceDir, source: "override" };
238
+ if (!depsInclude(env.CONVEYOR_DEPS, "postgresql")) return null;
239
+ if (!env.DATABASE_URL?.trim()) return null;
240
+ for (const relative of PRISMA_SCHEMA_CANDIDATES) {
241
+ const schemaPath = join(workspaceDir, relative);
242
+ if (await pathExists(schemaPath)) {
243
+ return { command: PRISMA_DB_PUSH, cwd: packageRootForSchema(schemaPath), source: "prisma" };
244
+ }
245
+ }
246
+ return null;
247
+ }
248
+ async function runDbSchemaSync(options) {
249
+ let plan = null;
215
250
  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;
251
+ plan = await resolveSchemaSyncPlan(options);
226
252
  } catch {
227
- return false;
253
+ return "skipped";
254
+ }
255
+ if (!plan) return "skipped";
256
+ const { onOutput, runCommand } = options;
257
+ onOutput("stdout", `[db] syncing schema (${plan.source}): ${plan.command}
258
+ `);
259
+ const controller = new AbortController();
260
+ const abort = () => controller.abort();
261
+ options.signal?.addEventListener("abort", abort, { once: true });
262
+ const timer = setTimeout(abort, options.timeoutMs ?? SCHEMA_SYNC_TIMEOUT_MS);
263
+ timer.unref();
264
+ try {
265
+ await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
266
+ onOutput("stdout", "[db] schema in sync\n");
267
+ return "synced";
268
+ } catch (error) {
269
+ const message = error instanceof Error ? error.message : String(error);
270
+ onOutput("stderr", `[db] schema sync failed: ${message}
271
+ `);
272
+ return "failed";
228
273
  } finally {
229
274
  clearTimeout(timer);
275
+ options.signal?.removeEventListener("abort", abort);
230
276
  }
231
277
  }
232
278
 
@@ -235,10 +281,11 @@ function defaultCommandExecutors() {
235
281
  if (workbenchEnabled()) {
236
282
  const client = getWorkbenchClient();
237
283
  return {
238
- runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput)
284
+ runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput),
285
+ runShellCommand: (cmd, cwd, onOutput, signal) => client.runSetupCommand(cmd, cwd, onOutput, signal)
239
286
  };
240
287
  }
241
- return { runStartCommand };
288
+ return { runStartCommand, runShellCommand: runSetupCommand };
242
289
  }
243
290
  var defaultWriteOutput = (stream, data) => {
244
291
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
@@ -263,6 +310,7 @@ var WorkspaceCommandSupervisor = class {
263
310
  awaitGitReadyFn;
264
311
  waitForSidecarsFn;
265
312
  runStartCommandFn;
313
+ syncDbSchemaFn;
266
314
  loadForwardPortsFn;
267
315
  writeOutput;
268
316
  terminateStartCommand;
@@ -301,6 +349,13 @@ var WorkspaceCommandSupervisor = class {
301
349
  this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, signal: opts.signal }));
302
350
  const executors = defaultCommandExecutors();
303
351
  this.runStartCommandFn = options.runStartCommand ?? executors.runStartCommand;
352
+ this.syncDbSchemaFn = options.syncDbSchema ?? ((opts) => runDbSchemaSync({
353
+ workspaceDir: this.workspaceDir,
354
+ env: this.env,
355
+ runCommand: executors.runShellCommand,
356
+ onOutput: opts.onOutput,
357
+ signal: opts.signal
358
+ }));
304
359
  this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
305
360
  this.writeOutput = options.writeOutput ?? defaultWriteOutput;
306
361
  this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
@@ -352,7 +407,6 @@ var WorkspaceCommandSupervisor = class {
352
407
  });
353
408
  return;
354
409
  }
355
- if (gitState === "ready") this.reportBootMilestoneFn("git_ready");
356
410
  await this.waitForSidecarsFn({
357
411
  onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
358
412
  `),
@@ -362,6 +416,8 @@ var WorkspaceCommandSupervisor = class {
362
416
  this.reportBootMilestoneFn("sidecars_ready");
363
417
  await this.loopReady;
364
418
  if (this.stopped) return;
419
+ await this.syncDbSchema();
420
+ if (this.stopped) return;
365
421
  const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
366
422
  if (this.stopped) return;
367
423
  this.reportBootMilestoneFn("start_command_launched");
@@ -374,6 +430,20 @@ var WorkspaceCommandSupervisor = class {
374
430
  ...previewPorts.length > 0 ? { previewPorts } : {}
375
431
  });
376
432
  }
433
+ /** Run the schema sync, forwarding its output as setup output so it lands in
434
+ * the card's setup log. A failure is announced rather than thrown: it is the
435
+ * exact condition that used to render as a silent "Displaying 0 results". */
436
+ async syncDbSchema() {
437
+ const outcome = await this.syncDbSchemaFn({
438
+ onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
439
+ signal: this.abortController.signal
440
+ });
441
+ if (outcome !== "failed" || this.stopped) return;
442
+ this.connection.sendEvent({
443
+ type: "setup_error",
444
+ message: "Database schema sync failed \u2014 the app may error on queries touching recently added columns (see pod logs)"
445
+ });
446
+ }
377
447
  restartStartCommand() {
378
448
  if (this.stopped || !this.config?.startCommand) return;
379
449
  this.connection.sendEvent({
@@ -696,8 +766,7 @@ var ProjectSessionRunner = class {
696
766
 
697
767
  // src/usage/multi-key-probe.ts
698
768
  import { mkdtemp, writeFile, copyFile, rm } from "fs/promises";
699
- import { tmpdir } from "os";
700
- import { join } from "path";
769
+ import { join as join2 } from "path";
701
770
  var logger = createServiceLogger("multi-key-probe");
702
771
  function gaugesToSamples(stdout) {
703
772
  const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
@@ -725,12 +794,12 @@ function gaugesToSamples(stdout) {
725
794
  async function isolatedProbe(token, now) {
726
795
  let dir = null;
727
796
  try {
728
- dir = await mkdtemp(join(tmpdir(), "conveyor-usage-"));
729
- await writeFile(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
797
+ dir = await mkdtemp(join2(sessionTempBase(), "conveyor-usage-"));
798
+ await writeFile(join2(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
730
799
  encoding: "utf8",
731
800
  mode: 384
732
801
  });
733
- await copyFile(claudeJsonPath(), join(dir, ".claude.json")).catch(() => {
802
+ await copyFile(claudeJsonPath(), join2(dir, ".claude.json")).catch(() => {
734
803
  });
735
804
  return await runUsageProbe({ env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
736
805
  } catch (error) {
@@ -767,18 +836,18 @@ async function probeKeysUsage(keys, deps = {}) {
767
836
 
768
837
  // src/harness/pty/adapters/opencode-auth.ts
769
838
  import { promises as fs } from "fs";
770
- import { dirname, join as join2 } from "path";
839
+ import { dirname as dirname2, join as join3 } from "path";
771
840
  import { homedir } from "os";
772
841
  var logger2 = createServiceLogger("opencode-auth");
773
842
  var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
774
843
  var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
775
844
  function opencodeAuthPath(env) {
776
- const dataHome = env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local", "share");
777
- return join2(dataHome, "opencode", "auth.json");
845
+ const dataHome = env.XDG_DATA_HOME ?? join3(env.HOME ?? homedir(), ".local", "share");
846
+ return join3(dataHome, "opencode", "auth.json");
778
847
  }
779
848
  function opencodeConfigPath(env) {
780
- const configHome = env.XDG_CONFIG_HOME ?? join2(env.HOME ?? homedir(), ".config");
781
- return join2(configHome, "opencode", "opencode.json");
849
+ const configHome = env.XDG_CONFIG_HOME ?? join3(env.HOME ?? homedir(), ".config");
850
+ return join3(configHome, "opencode", "opencode.json");
782
851
  }
783
852
  function parseOauthSeed(b64) {
784
853
  if (!b64) return null;
@@ -808,7 +877,7 @@ async function readJsonFile(path) {
808
877
  }
809
878
  }
810
879
  async function writeJsonFile(path, value) {
811
- await fs.mkdir(dirname(path), { recursive: true });
880
+ await fs.mkdir(dirname2(path), { recursive: true });
812
881
  await fs.writeFile(path, `${JSON.stringify(value, null, 2)}
813
882
  `, { mode: 384 });
814
883
  }
@@ -926,267 +995,6 @@ function resolveTuiAdapter(kind = "claude-code") {
926
995
  }
927
996
  }
928
997
 
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
998
  // src/runner/adhoc-session-runner.ts
1191
999
  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
1000
  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.";
@@ -1205,11 +1013,14 @@ function resolveAdhocTui(env) {
1205
1013
  if (TUI_KINDS.includes(raw)) return raw;
1206
1014
  throw new Error(`Unknown TUI "${raw}" in CONVEYOR_TUI (expected: ${TUI_KINDS.join(", ")})`);
1207
1015
  }
1208
- function buildAdhocPtyBridge(connection) {
1016
+ function buildAdhocPtyBridge(connection, onInputActivity) {
1209
1017
  return {
1210
1018
  sendOutput: (data, dims) => connection.sendPtyOutput(data, dims),
1211
1019
  sendChatEvent: (event) => connection.sendPtyChatEvent(event),
1212
- onInput: (handler) => connection.onPtyInput(handler),
1020
+ onInput: (handler) => connection.onPtyInput((data) => {
1021
+ onInputActivity?.();
1022
+ handler(data);
1023
+ }),
1213
1024
  onResize: (handler) => connection.onPtyResize(handler)
1214
1025
  };
1215
1026
  }
@@ -1258,11 +1069,18 @@ var AdhocSessionRunner = class {
1258
1069
  this.callbacks = callbacks;
1259
1070
  this.connection = deps?.connection ?? new AgentConnection(config.connection);
1260
1071
  this.harness = deps?.harness ?? new PtyHarness(
1261
- buildAdhocPtyBridge(this.connection),
1072
+ // A keystroke is real human activity — re-arm the idle backstop so an
1073
+ // actively-typing human (or a >60-min audit run) is never hard-stopped
1074
+ // by the idle timer, mirroring ShellSessionRunner. `this.lifecycle` is
1075
+ // assigned later in this constructor but is always set by the time a
1076
+ // keystroke can arrive at runtime.
1077
+ buildAdhocPtyBridge(this.connection, () => this.lifecycle.startIdleTimer()),
1262
1078
  resolveTuiAdapter(resolveAdhocTui(process.env))
1263
1079
  );
1080
+ const portVisibility = new CodespacePortVisibility();
1264
1081
  this.portDiscovery = deps?.portDiscovery ?? new PortDiscovery({
1265
- report: (ports) => this.connection.reportDiscoveredPorts(ports)
1082
+ report: (ports) => this.connection.reportDiscoveredPorts(ports),
1083
+ onReported: (ports) => portVisibility.ensureVisible(ports.map(({ port }) => port))
1266
1084
  });
1267
1085
  this.commandSupervisor = deps?.commandSupervisor ?? new WorkspaceCommandSupervisor({
1268
1086
  config: loadConveyorConfig(),
@@ -1344,7 +1162,11 @@ var AdhocSessionRunner = class {
1344
1162
  initialPrompt,
1345
1163
  conveyorMcpServer: this.harness.createMcpServer({
1346
1164
  name: "conveyor",
1347
- tools: buildProjectTools(this.connection, this.config.projectId)
1165
+ tools: buildProjectTools(
1166
+ this.connection,
1167
+ this.config.projectId,
1168
+ this.config.workspaceDir
1169
+ )
1348
1170
  })
1349
1171
  } : void 0;
1350
1172
  const options = buildAdhocQueryOptions(
@@ -1463,9 +1285,9 @@ function buildReviewChildEnv(baseEnv, data) {
1463
1285
  env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
1464
1286
  env.CONVEYOR_SESSION_ID = data.sessionId;
1465
1287
  env.CONVEYOR_MODE = "code-review";
1288
+ env.CONVEYOR_WORKBENCH_TOKEN ??= baseEnv.POD_BOOTSTRAP_TOKEN;
1466
1289
  delete env.POD_BOOTSTRAP_TOKEN;
1467
1290
  delete env.CONVEYOR_BOOTSTRAP_TOKEN;
1468
- delete env.CONVEYOR_SETUP_COMMAND;
1469
1291
  delete env.CONVEYOR_START_COMMAND;
1470
1292
  delete env.CONVEYOR_GIT_READY;
1471
1293
  delete env.CONVEYOR_GIT_READY_MARKER;
@@ -1621,7 +1443,6 @@ function buildSpawnedChildEnv(baseEnv, data) {
1621
1443
  env.CONVEYOR_PROJECT_ID = data.projectId;
1622
1444
  delete env.POD_BOOTSTRAP_TOKEN;
1623
1445
  delete env.CONVEYOR_BOOTSTRAP_TOKEN;
1624
- delete env.CONVEYOR_SETUP_COMMAND;
1625
1446
  delete env.CONVEYOR_START_COMMAND;
1626
1447
  delete env.CONVEYOR_AGENT_MODE;
1627
1448
  delete env.CONVEYOR_IS_AUTO;
@@ -1759,7 +1580,7 @@ var ShellSessionRunner = class {
1759
1580
  constructor(config, deps) {
1760
1581
  this.config = config;
1761
1582
  this.connection = deps?.connection ?? new AgentConnection(config.connection);
1762
- this.loadSpawn = deps?.loadSpawn ?? loadPtySpawn;
1583
+ this.loadSpawn = deps?.loadSpawn ?? resolvePtySpawn;
1763
1584
  this.lifecycle = new Lifecycle(
1764
1585
  // No git flush — the human commits/pushes explicitly from the shell.
1765
1586
  { ...DEFAULT_LIFECYCLE_CONFIG, gitFlushIntervalMs: 0, ...config.lifecycle },
@@ -1898,9 +1719,14 @@ function createSpawnedChildRunner(inputs) {
1898
1719
  );
1899
1720
  }
1900
1721
 
1722
+ // src/runner/host-modes.ts
1723
+ function hostsSpawnedChildren(mode) {
1724
+ return mode === "task" || mode === "pack";
1725
+ }
1726
+
1901
1727
  // src/cli.ts
1902
1728
  if (process.argv[2] === "boot") {
1903
- const { runBoot } = await import("./boot-YZCOIV3Q.js");
1729
+ const { runBoot } = await import("./boot-I353EAK6.js");
1904
1730
  process.exit(await runBoot(process.argv.slice(3)));
1905
1731
  }
1906
1732
  if (isLegacyEntrypointLaunch(process.env)) {
@@ -1909,8 +1735,8 @@ if (isLegacyEntrypointLaunch(process.env)) {
1909
1735
  process.exit(1);
1910
1736
  }
1911
1737
  if (process.argv.includes("--version")) {
1912
- const __dirname = dirname2(fileURLToPath(import.meta.url));
1913
- const pkgPath = join3(__dirname, "..", "package.json");
1738
+ const __dirname = dirname3(fileURLToPath(import.meta.url));
1739
+ const pkgPath = join4(__dirname, "..", "package.json");
1914
1740
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1915
1741
  process.stdout.write(pkg.version + "\n");
1916
1742
  process.exit(0);
@@ -1918,6 +1744,7 @@ if (process.argv.includes("--version")) {
1918
1744
  var logger6 = createServiceLogger("CLI");
1919
1745
  var exitContext = {};
1920
1746
  var exitLogged = false;
1747
+ var shuttingDown = false;
1921
1748
  function logAgentExit(reason, opts) {
1922
1749
  if (exitLogged) return;
1923
1750
  exitLogged = true;
@@ -1970,28 +1797,39 @@ function isExpectedAbortError(error) {
1970
1797
  const hasAbortCode = !("code" in error) || error.code === void 0 || error.code === "ABORT_ERR";
1971
1798
  return hasAbortMessage && hasAbortCode;
1972
1799
  }
1800
+ function exitOnPermissionDenial(err) {
1801
+ if (!isPermissionDeniedError(err)) return false;
1802
+ logger6.error("Not authorized for this project \u2014 parking instead of crash-looping", {
1803
+ error: err.message
1804
+ });
1805
+ logAgentExit("unauthorized", { exitCode: 0 });
1806
+ process.exit(0);
1807
+ }
1973
1808
  process.on("uncaughtException", (err) => {
1974
1809
  if (err.code === "EPIPE") return;
1975
- if (isExpectedAbortError(err)) {
1810
+ if (shuttingDown && isExpectedAbortError(err)) {
1976
1811
  logger6.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
1977
1812
  return;
1978
1813
  }
1814
+ if (exitOnPermissionDenial(err)) return;
1979
1815
  logger6.error("Uncaught exception", { error: err.message, code: err.code });
1980
1816
  logAgentExit("uncaught_exception", { exitCode: 1 });
1981
1817
  process.exit(1);
1982
1818
  });
1983
1819
  process.on("unhandledRejection", (reason) => {
1984
1820
  const err = reason instanceof Error ? reason : new Error(String(reason));
1985
- if (isExpectedAbortError(err)) {
1821
+ if (shuttingDown && isExpectedAbortError(err)) {
1986
1822
  logger6.info("Ignored expected abort rejection after shutdown", { error: err.message });
1987
1823
  return;
1988
1824
  }
1825
+ if (exitOnPermissionDenial(err)) return;
1989
1826
  logger6.error("Unhandled rejection", { error: err.message });
1990
1827
  logAgentExit("unhandled_rejection", { exitCode: 1 });
1991
1828
  process.exit(1);
1992
1829
  });
1993
1830
  if (process.env.CONVEYOR_MODE === "workbench") {
1994
- const { startWorkbenchServer } = await import("./server-BXAFMYDM.js");
1831
+ const { startWorkbenchServer } = await import("./server-NIOBJ46G.js");
1832
+ const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-U7JERHA2.js");
1995
1833
  const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QLVS5W6O.js");
1996
1834
  const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
1997
1835
  const token = process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? "";
@@ -1999,12 +1837,17 @@ if (process.env.CONVEYOR_MODE === "workbench") {
1999
1837
  logger6.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
2000
1838
  process.exit(1);
2001
1839
  }
2002
- const pkgDir = dirname2(fileURLToPath(import.meta.url));
2003
- const pkg = JSON.parse(readFileSync(join3(pkgDir, "..", "package.json"), "utf-8"));
1840
+ const pkgDir = dirname3(fileURLToPath(import.meta.url));
1841
+ const pkg = JSON.parse(readFileSync(join4(pkgDir, "..", "package.json"), "utf-8"));
2004
1842
  const handle = await startWorkbenchServer({
2005
1843
  port,
2006
1844
  token,
2007
- version: pkg.version ?? "unknown"
1845
+ version: pkg.version ?? "unknown",
1846
+ oomWatchdog: oomWatchdogOptionsFromEnv({
1847
+ info: (m) => logger6.info(m),
1848
+ warn: (m) => logger6.warn(m),
1849
+ error: (m) => logger6.error(m)
1850
+ })
2008
1851
  });
2009
1852
  logger6.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
2010
1853
  process.on("SIGTERM", () => process.exit(0));
@@ -2060,8 +1903,14 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
2060
1903
  exitContext.sessionId = projectIdentity.sessionId;
2061
1904
  exitContext.workspaceId = projectIdentity.workspaceId;
2062
1905
  exitContext.runnerMode = "adhoc";
2063
- process.on("SIGTERM", () => adhocRunner.stop());
2064
- process.on("SIGINT", () => adhocRunner.stop());
1906
+ process.on("SIGTERM", () => {
1907
+ shuttingDown = true;
1908
+ adhocRunner.stop();
1909
+ });
1910
+ process.on("SIGINT", () => {
1911
+ shuttingDown = true;
1912
+ adhocRunner.stop();
1913
+ });
2065
1914
  await adhocRunner.run();
2066
1915
  const adhocError = adhocRunner.finalState === "error";
2067
1916
  logAgentExit(adhocError ? "error" : "clean", {
@@ -2093,8 +1942,14 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
2093
1942
  exitContext.sessionId = projectIdentity.sessionId;
2094
1943
  exitContext.workspaceId = projectIdentity.workspaceId;
2095
1944
  exitContext.runnerMode = "pm";
2096
- process.on("SIGTERM", () => projectRunner.stop());
2097
- process.on("SIGINT", () => projectRunner.stop());
1945
+ process.on("SIGTERM", () => {
1946
+ shuttingDown = true;
1947
+ projectRunner.stop();
1948
+ });
1949
+ process.on("SIGINT", () => {
1950
+ shuttingDown = true;
1951
+ projectRunner.stop();
1952
+ });
2098
1953
  await projectRunner.run();
2099
1954
  const projectError = projectRunner.finalState === "error";
2100
1955
  logAgentExit(projectError ? "error" : "clean", {
@@ -2145,8 +2000,14 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
2145
2000
  mode: CONVEYOR_MODE,
2146
2001
  sessionId: spawnedSessionId
2147
2002
  });
2148
- process.on("SIGTERM", () => childRunner.stop());
2149
- process.on("SIGINT", () => childRunner.stop());
2003
+ process.on("SIGTERM", () => {
2004
+ shuttingDown = true;
2005
+ childRunner.stop();
2006
+ });
2007
+ process.on("SIGINT", () => {
2008
+ shuttingDown = true;
2009
+ childRunner.stop();
2010
+ });
2150
2011
  await childRunner.run();
2151
2012
  const childError = childRunner.finalState === "error";
2152
2013
  logAgentExit(childError ? "error" : "clean", {
@@ -2191,6 +2052,7 @@ var shutdownSignal;
2191
2052
  var workspaceCommandSupervisor = null;
2192
2053
  var shutdownCompletion = null;
2193
2054
  var shutdownAgent = (signal) => {
2055
+ shuttingDown = true;
2194
2056
  logger6.info(`Received ${signal}, flushing git and stopping agent`);
2195
2057
  const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
2196
2058
  shutdownSignal = signal;
@@ -2254,7 +2116,7 @@ void checkSessionTaskIdentity({
2254
2116
  },
2255
2117
  logger: logger6
2256
2118
  });
2257
- if (CONVEYOR_MODE === "task") {
2119
+ if (hostsSpawnedChildren(CONVEYOR_MODE)) {
2258
2120
  runner.connection.onSpawnReview((data) => {
2259
2121
  void reviewChildren.spawn(data).catch((err) => {
2260
2122
  logger6.error("Review child spawn threw", {
@@ -2267,7 +2129,7 @@ if (CONVEYOR_MODE === "task") {
2267
2129
  });
2268
2130
  });
2269
2131
  }
2270
- if (CONVEYOR_MODE === "task" || CONVEYOR_MODE === "pack") {
2132
+ if (hostsSpawnedChildren(CONVEYOR_MODE)) {
2271
2133
  runner.connection.onSpawnTui((data) => {
2272
2134
  void sessionChildren.spawn(data).catch((err) => {
2273
2135
  logger6.error("Session child spawn threw", {