agent-conveyor 0.1.6 → 0.1.8

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.
@@ -76,7 +76,7 @@ export function runTypescriptRuntimeCommand(options) {
76
76
  return errorResult(`unknown command: ${parsed.command}`);
77
77
  }
78
78
  if (parsed.flags.help) {
79
- return textResult([`usage: ${program} ${parsed.command} [-h] [options]`]);
79
+ return textResult(commandHelpText(program, parsed.command));
80
80
  }
81
81
  if (parsed.error) {
82
82
  return errorResult(parsed.error);
@@ -337,6 +337,70 @@ export function runTypescriptRuntimeCommand(options) {
337
337
  return errorResult(error instanceof Error ? error.message : String(error));
338
338
  }
339
339
  }
340
+ function commandHelpText(program, command) {
341
+ const path = "[--path <workerctl.db>]";
342
+ const linesByCommand = {
343
+ criteria: [
344
+ `usage: ${program} criteria <task> [--list|--add --criterion <text> --source <source>|--accept ID|--satisfy ID|--defer ID|--reject ID] ${path} [--json]`,
345
+ "",
346
+ "Examples:",
347
+ ` ${program} criteria my-task --list --json --path /tmp/work/workerctl.db`,
348
+ ` ${program} criteria my-task --add --criterion "Note file exists" --source manager_inferred --status accepted --path /tmp/work/workerctl.db`,
349
+ ` ${program} criteria my-task --satisfy 1 --proof "File exists" --evidence-json '{"artifact":{"path":"docs/note.md"}}' --path /tmp/work/workerctl.db`,
350
+ ],
351
+ "finish-task": [
352
+ `usage: ${program} finish-task <task> --reason <reason> [--require-criteria-audit] ${path}`,
353
+ "",
354
+ "Examples:",
355
+ ` ${program} finish-task my-task --reason "Accepted criteria satisfied" --require-criteria-audit --path /tmp/work/workerctl.db`,
356
+ ],
357
+ "manager-ack": [
358
+ `usage: ${program} manager-ack <task> --from-stdin ${path}`,
359
+ `usage: ${program} manager-ack <task> --json ${path}`,
360
+ "",
361
+ "Example JSON:",
362
+ ` {"task":"my-task","manager_session":"mgr","supervision_contract":"I will supervise through Conveyor and verify criteria before finishing.","will_not_edit_project_files":true}`,
363
+ ],
364
+ nudge: [
365
+ `usage: ${program} nudge <worker-or-session> <message> ${path} [--dry-run]`,
366
+ `usage: ${program} session-nudge <session> <message> ${path} [--dry-run]`,
367
+ "",
368
+ "For task-routed delivery, prefer enqueue-nudge-worker plus dispatch:",
369
+ ` ${program} enqueue-nudge-worker my-task --message "Status and evidence?" --path /tmp/work/workerctl.db`,
370
+ ` ${program} dispatch --once --type nudge_worker --path /tmp/work/workerctl.db`,
371
+ ],
372
+ pair: [
373
+ `usage: ${program} pair --task <task> --worker-name <worker> --manager-name <manager> [options] ${path}`,
374
+ "",
375
+ "Options:",
376
+ " --task-goal <text> Task goal stored in Conveyor state.",
377
+ " --task-prompt <text> Initial worker prompt; defaults to task goal when omitted.",
378
+ " --manager-recipe <recipe> Seed a manager recipe, for example goalbuddy-conveyor.",
379
+ " --manager-acceptance <text> Seed an accepted manager criterion; repeat for multiple criteria.",
380
+ " --manager-tool <tool> Seed an expected manager/worker tool; repeat for multiple tools.",
381
+ " --manager-reference <path> Seed a manager reference path; repeat for multiple references.",
382
+ " --manager-question <text> Seed a manager setup question; repeat for multiple questions.",
383
+ " --manager-guideline <text> Seed a manager guideline; repeat for multiple guidelines.",
384
+ " --cwd <dir> Working directory for both Codex sessions.",
385
+ " --accept-trust Auto-accept the Codex trust prompt for the chosen cwd.",
386
+ " --no-dispatch Do not start Dispatch after launching the pair.",
387
+ " --dry-run Print the launch plan without creating sessions.",
388
+ " --json Emit JSON output.",
389
+ "",
390
+ "Examples:",
391
+ ` ${program} pair --task dogfood --worker-name dogfood-worker --manager-name dogfood-manager --task-goal "Create docs/note.md" --task-prompt "Create docs/note.md" --manager-recipe goalbuddy-conveyor --manager-acceptance "docs/note.md exists" --cwd /tmp/work --path /tmp/work/workerctl.db --accept-trust`,
392
+ ` ${program} pair --task dogfood --worker-name dogfood-worker --manager-name dogfood-manager --path /tmp/work/workerctl.db --dry-run --json`,
393
+ ],
394
+ "worker-ack": [
395
+ `usage: ${program} worker-ack <task> --from-stdin ${path}`,
396
+ `usage: ${program} worker-ack <task> --json ${path}`,
397
+ "",
398
+ "Example JSON:",
399
+ ` {"goal_restatement":"Create docs/dogfood-note.md","proposed_criteria":{"must_have":["note file exists"],"follow_up":[]},"expected_tools":["shell"],"open_questions":[],"ready_to_start":true}`,
400
+ ],
401
+ };
402
+ return linesByCommand[command] ?? [`usage: ${program} ${command} [-h] [options]`];
403
+ }
340
404
  function parseRuntimeArgs(args, env) {
341
405
  const flags = {
342
406
  format: "timeline",
@@ -361,6 +425,10 @@ function parseRuntimeArgs(args, env) {
361
425
  candidate: null,
362
426
  check: null,
363
427
  classifyPrompt: null,
428
+ workerCodexAppThreadId: null,
429
+ workerCodexAppThreadTitle: null,
430
+ managerCodexAppThreadId: null,
431
+ managerCodexAppThreadTitle: null,
364
432
  codexSession: null,
365
433
  create: null,
366
434
  createRun: null,
@@ -1883,6 +1951,31 @@ function parseRuntimeArgs(args, env) {
1883
1951
  flags.manager = value.value;
1884
1952
  index += 1;
1885
1953
  }
1954
+ else if (arg === "--worker-codex-app-thread-id"
1955
+ || arg === "--worker-codex-app-thread-title"
1956
+ || arg === "--manager-codex-app-thread-id"
1957
+ || arg === "--manager-codex-app-thread-title") {
1958
+ if (command !== "create-disposable-binding") {
1959
+ return { command, enabled, error: `Unsupported TypeScript runtime option: ${arg}`, explicit, flags, task };
1960
+ }
1961
+ const value = valueAfter(queue, index, arg);
1962
+ if (value.error) {
1963
+ return { command, enabled, error: value.error, explicit, flags, task };
1964
+ }
1965
+ if (arg === "--worker-codex-app-thread-id") {
1966
+ flags.workerCodexAppThreadId = value.value;
1967
+ }
1968
+ else if (arg === "--worker-codex-app-thread-title") {
1969
+ flags.workerCodexAppThreadTitle = value.value;
1970
+ }
1971
+ else if (arg === "--manager-codex-app-thread-id") {
1972
+ flags.managerCodexAppThreadId = value.value;
1973
+ }
1974
+ else {
1975
+ flags.managerCodexAppThreadTitle = value.value;
1976
+ }
1977
+ index += 1;
1978
+ }
1886
1979
  else if (arg === "--template") {
1887
1980
  if (command !== "create-disposable-binding" && command !== "loop-templates") {
1888
1981
  return { command, enabled, error: "Unsupported TypeScript runtime option: --template", explicit, flags, task };
@@ -3904,10 +3997,10 @@ function qaRunBuildClearLoop(context) {
3904
3997
  const artifactDir = qaArtifactDir(context, "build-clear-loop", slug, run.id);
3905
3998
  const buildReceipt = join(artifactDir, "build-passed.json");
3906
3999
  mkdirSync(dirname(buildReceipt), { recursive: true });
3907
- writeFileSync(buildReceipt, `${JSON.stringify(sortJson({ command: "scripts/run-unittests-isolated -k build_clear_loop", result: "pass", status: "build_passed" }), null, 2)}\n`);
4000
+ writeFileSync(buildReceipt, `${JSON.stringify(sortJson({ command: "npm test -- --runInBand", result: "pass", status: "build_passed" }), null, 2)}\n`);
3908
4001
  qaRecordLoopEvidence(context, task, run.id, "build_passed", "qa-run-build-clear-build-passed", {
3909
4002
  artifactPath: buildReceipt,
3910
- metadata: { command: "scripts/run-unittests-isolated -k build_clear_loop", result: "Focused build/test command passed before retry." },
4003
+ metadata: { command: "npm test -- --runInBand", result: "Focused build/test command passed before retry." },
3911
4004
  });
3912
4005
  enqueueQaContinue(context, task, run.id, "qa-run-build-clear-build-only", "Run after build evidence only.");
3913
4006
  const buildOnlyDispatch = qaDispatchContinueOnce(context, "qa-run-build-clear-build-only");
@@ -4820,19 +4913,14 @@ function dispatchWatchCommand(workerctlPath, dispatcherId, dbPath) {
4820
4913
  }
4821
4914
  function installableSkillSources() {
4822
4915
  const root = packageRootFromRuntimeModule();
4823
- const candidates = [
4824
- join(root, "skills"),
4825
- join(root, "workerctl", "assets", "skills"),
4826
- ];
4827
- for (const candidate of candidates) {
4828
- const skills = ["manage-codex-workers", "codex-review"]
4829
- .map((name) => ({ name, source: join(candidate, name) }))
4830
- .filter((skill) => existsSync(join(skill.source, "SKILL.md")));
4831
- if (skills.length === 2) {
4832
- return skills;
4833
- }
4916
+ const candidate = join(root, "skills");
4917
+ const skills = ["manage-codex-workers", "codex-review"]
4918
+ .map((name) => ({ name, source: join(candidate, name) }))
4919
+ .filter((skill) => existsSync(join(skill.source, "SKILL.md")));
4920
+ if (skills.length === 2) {
4921
+ return skills;
4834
4922
  }
4835
- throw new Error("Bundled Agent Conveyor skills not found in skills/ or workerctl/assets/skills.");
4923
+ throw new Error("Bundled Agent Conveyor skills not found in skills/.");
4836
4924
  }
4837
4925
  function runBindCommand(parsed, options) {
4838
4926
  const unsupported = unsupportedBindOptions(parsed);
@@ -4933,6 +5021,8 @@ function runCreateDisposableBindingCommand(parsed, options) {
4933
5021
  const workerRollout = writeDisposableRollout(sessionDir, workerName, cwd);
4934
5022
  const managerRollout = writeDisposableRollout(sessionDir, managerName, cwd);
4935
5023
  const worker = registerSessionSync(database, {
5024
+ codexAppThreadId: parsed.flags.workerCodexAppThreadId,
5025
+ codexAppThreadTitle: parsed.flags.workerCodexAppThreadTitle,
4936
5026
  codexSessionPath: workerRollout.path,
4937
5027
  cwd,
4938
5028
  name: workerName,
@@ -4941,6 +5031,8 @@ function runCreateDisposableBindingCommand(parsed, options) {
4941
5031
  tmuxSession: null,
4942
5032
  });
4943
5033
  const manager = registerSessionSync(database, {
5034
+ codexAppThreadId: parsed.flags.managerCodexAppThreadId,
5035
+ codexAppThreadTitle: parsed.flags.managerCodexAppThreadTitle,
4944
5036
  codexSessionPath: managerRollout.path,
4945
5037
  cwd,
4946
5038
  name: managerName,
@@ -4979,6 +5071,8 @@ function runCreateDisposableBindingCommand(parsed, options) {
4979
5071
  db_path: dbPath,
4980
5072
  manager: {
4981
5073
  communication: disposableSessionCommunication("manager", task.name, dbPath),
5074
+ codex_app_thread_id: manager.codex_app_thread_id,
5075
+ codex_app_thread_title: manager.codex_app_thread_title,
4982
5076
  id: manager.session_id,
4983
5077
  name: managerName,
4984
5078
  rollout_path: managerRollout.path,
@@ -5004,11 +5098,14 @@ function runCreateDisposableBindingCommand(parsed, options) {
5004
5098
  },
5005
5099
  worker: {
5006
5100
  communication: disposableSessionCommunication("worker", task.name, dbPath),
5101
+ codex_app_thread_id: worker.codex_app_thread_id,
5102
+ codex_app_thread_title: worker.codex_app_thread_title,
5007
5103
  id: worker.session_id,
5008
5104
  name: workerName,
5009
5105
  rollout_path: workerRollout.path,
5010
5106
  tmux_session: null,
5011
5107
  },
5108
+ heartbeat_recommendations: disposableHeartbeatRecommendations(task.name, dbPath),
5012
5109
  worker_handoff: disposableWorkerHandoff(task.name, run?.name ?? null, dbPath),
5013
5110
  };
5014
5111
  if (parsed.flags.json) {
@@ -5394,6 +5491,7 @@ function runStartSessionCommand(parsed, options, role) {
5394
5491
  const initialPrompt = role === "manager"
5395
5492
  ? startManagerBootstrapPrompt(database, {
5396
5493
  cwd,
5494
+ dbPath: runtimeDbPath(parsed, options),
5397
5495
  managerName: name,
5398
5496
  taskGoal: parsed.flags.taskGoal,
5399
5497
  taskName: parsed.flags.taskName,
@@ -5720,7 +5818,7 @@ function runPairCommand(parsed, options) {
5720
5818
  return unsupportedRuntimeResult(parsed, "pair requires --task, --worker-name, and --manager-name.");
5721
5819
  }
5722
5820
  const dbPath = runtimeDbPath(parsed, options);
5723
- const dispatch = pairDispatchPayload(parsed, dbPath);
5821
+ const dispatch = pairDispatchPayload(parsed, dbPath, options);
5724
5822
  const packageRoot = packageRootFromRuntimeModule();
5725
5823
  if (parsed.flags.dryRun) {
5726
5824
  return jsonResult({
@@ -5731,6 +5829,18 @@ function runPairCommand(parsed, options) {
5731
5829
  worker: workerName,
5732
5830
  });
5733
5831
  }
5832
+ const codexPreflight = ensureRequiredTool("codex", options);
5833
+ if (codexPreflight) {
5834
+ return codexPreflight;
5835
+ }
5836
+ const tmuxPreflight = ensureTmuxAvailable(options.tmuxRunner ?? defaultTmuxRunner);
5837
+ if (tmuxPreflight) {
5838
+ return tmuxPreflight;
5839
+ }
5840
+ const tmuxAccessPreflight = ensureTmuxServerAccessible(options.tmuxRunner ?? defaultTmuxRunner);
5841
+ if (tmuxAccessPreflight) {
5842
+ return tmuxAccessPreflight;
5843
+ }
5734
5844
  const cwd = parsed.flags.cwd ?? options.cwd ?? process.cwd();
5735
5845
  const database = openRuntimeDatabase(parsed, options);
5736
5846
  let taskId = null;
@@ -5848,7 +5958,7 @@ function runPairCommand(parsed, options) {
5848
5958
  acceptTrust: parsed.flags.acceptTrust,
5849
5959
  askForApproval: startup.askForApproval,
5850
5960
  cwd,
5851
- initialPrompt: workerAckTaskPrompt(taskName, parsed.flags.taskPrompt),
5961
+ initialPrompt: workerAckTaskPrompt(taskName, parsed.flags.taskPrompt, dbPath),
5852
5962
  name: workerName,
5853
5963
  role: "worker",
5854
5964
  sandbox: startup.sandbox,
@@ -5876,6 +5986,7 @@ function runPairCommand(parsed, options) {
5876
5986
  cwd,
5877
5987
  initialPrompt: startManagerBootstrapPrompt(database, {
5878
5988
  cwd,
5989
+ dbPath,
5879
5990
  managerName,
5880
5991
  taskGoal: task.goal,
5881
5992
  taskName,
@@ -6035,14 +6146,13 @@ function taskRowForPair(database, taskName) {
6035
6146
  `).get(taskName, taskName);
6036
6147
  return row ?? null;
6037
6148
  }
6038
- function pairDispatchPayload(parsed, dbPath) {
6149
+ function pairDispatchPayload(parsed, dbPath, options) {
6039
6150
  const dispatcherId = parsed.flags.dispatcherId;
6040
6151
  const ensureDispatch = dispatcherId !== null && !parsed.flags.noDispatch;
6041
- const packageRoot = packageRootFromRuntimeModule();
6042
6152
  return {
6043
6153
  dispatchCommand: ensureDispatch
6044
6154
  ? [
6045
- join(packageRoot, "scripts", "workerctl"),
6155
+ workerctlDispatchExecutable(options),
6046
6156
  "dispatch",
6047
6157
  "--watch",
6048
6158
  "--dispatcher-id",
@@ -6054,6 +6164,17 @@ function pairDispatchPayload(parsed, dbPath) {
6054
6164
  ensureDispatch,
6055
6165
  };
6056
6166
  }
6167
+ function workerctlDispatchExecutable(options) {
6168
+ const workerctlPath = commandPath("workerctl", options);
6169
+ if (workerctlPath) {
6170
+ return workerctlPath;
6171
+ }
6172
+ const workerctlScript = join(packageRootFromRuntimeModule(), "scripts", "workerctl");
6173
+ if (pathIsExecutable(workerctlScript)) {
6174
+ return workerctlScript;
6175
+ }
6176
+ throw new Error(`Cannot start Dispatch: workerctl is not on PATH and ${workerctlScript} is not executable.`);
6177
+ }
6057
6178
  function emitPairTelemetry(database, options) {
6058
6179
  emitTelemetrySync(database, {
6059
6180
  actor: "workerctl",
@@ -6407,20 +6528,24 @@ function spawnCodexAndRegisterPairSession(database, parsed, options, params) {
6407
6528
  tmux_session: registered.tmux_session,
6408
6529
  };
6409
6530
  }
6410
- function workerAckTaskPrompt(taskName, taskPrompt) {
6531
+ function workerAckTaskPrompt(taskName, taskPrompt, dbPath) {
6411
6532
  if (taskPrompt === null) {
6412
6533
  return null;
6413
6534
  }
6414
6535
  const taskRef = taskName ?? "<task>";
6536
+ const pathSuffix = commandPathSuffix(dbPath);
6415
6537
  return [
6416
6538
  taskPrompt,
6417
6539
  "",
6418
6540
  "Before editing files or running implementation work, acknowledge the task contract:",
6419
6541
  "",
6420
- `conveyor worker-ack ${taskRef} --from-stdin`,
6542
+ `conveyor worker-ack ${taskRef} --from-stdin${pathSuffix}`,
6543
+ "",
6544
+ "Use a JSON object like:",
6545
+ "",
6546
+ `{"goal_restatement":"Restate the assigned task.","proposed_criteria":{"must_have":["Current-task proof"],"follow_up":[]},"expected_tools":["shell"],"open_questions":[],"ready_to_start":true}`,
6421
6547
  "",
6422
- "Use a JSON object with goal_restatement, proposed_criteria, expected_tools,",
6423
- "open_questions, and ready_to_start.",
6548
+ "When your implementation is complete, leave a concise final reply with the files changed and verification you ran. Do not call `conveyor finish-task`; the manager owns criteria satisfaction and audited task closeout.",
6424
6549
  ].join("\n");
6425
6550
  }
6426
6551
  function createPairRunSync(database, options) {
@@ -16489,6 +16614,9 @@ function disposableWorkerHandoff(taskName, runName, dbPath) {
16489
16614
  `You are the worker for task ${taskName}${loopClause}.`,
16490
16615
  "Keep polling your Conveyor worker inbox until there are no items left or the loop reaches max_iterations. Consume the next item now, treat each consumed item as the manager's next instruction, complete the requested work, and report changed files, exact commands run, evidence, and any residual risk.",
16491
16616
  "",
16617
+ "Because this is a pull-required Codex app/no-tmux session, autonomous operation requires a heartbeat/wake layer that repeats this worker inbox poll while the thread is idle. If no heartbeat automation is available, report the loop as manual-poll only.",
16618
+ "Do not delete, pause, or disable heartbeat automation just because an inbox poll is idle; the manager or operator owns terminal loop teardown.",
16619
+ "",
16492
16620
  `Run: ${sessionPollCommand("worker", taskName, dbPath)}`,
16493
16621
  ].join("\n");
16494
16622
  }
@@ -16503,14 +16631,72 @@ function renderDisposableBindingText(result) {
16503
16631
  }
16504
16632
  lines.push("Replay commands:");
16505
16633
  lines.push(...result.replay_commands.map((command) => ` ${command}`));
16634
+ if (result.heartbeat_recommendations) {
16635
+ lines.push("Heartbeat recommendations:");
16636
+ lines.push(` interval: every ${result.heartbeat_recommendations.interval_minutes} minutes`);
16637
+ lines.push(` manager: ${result.heartbeat_recommendations.manager.poll_command}`);
16638
+ lines.push(` worker: ${result.heartbeat_recommendations.worker.poll_command}`);
16639
+ lines.push(` teardown: ${result.heartbeat_recommendations.teardown_policy.idle_poll}`);
16640
+ lines.push(` closeout: ${result.heartbeat_recommendations.teardown_policy.terminal_closeout_command}`);
16641
+ }
16506
16642
  lines.push("Worker handoff:");
16507
16643
  lines.push(result.worker_handoff);
16508
16644
  return `${lines.join("\n")}\n`;
16509
16645
  }
16646
+ function disposableHeartbeatRecommendations(taskName, dbPath) {
16647
+ const terminalCloseoutCommand = `${conveyorPollInvocation()} finish-task ${shellQuote(taskName)} --reason ${shellQuote("Verified terminal closeout")} --require-criteria-audit --path ${shellQuote(dbPath)}`;
16648
+ return {
16649
+ applies_when: {
16650
+ can_receive_push: false,
16651
+ delivery_mode: "pull_required",
16652
+ receive_style: "pull",
16653
+ session_kind: "codex_app",
16654
+ },
16655
+ interval_minutes: 2,
16656
+ note: "Dispatch can deliver pull-required inbox items, but Codex app/no-tmux sessions still need a heartbeat or operator wake-up to poll while idle.",
16657
+ teardown_policy: {
16658
+ idle_poll: "Never delete, pause, or disable a manager or worker heartbeat because an inbox poll returned no item; that is only a quiet poll interval.",
16659
+ owner: "manager_or_operator",
16660
+ terminal_closeout: "Only the manager or operator should tear down heartbeats, and only after a terminal manager decision plus verified task closeout, or after explicit operator instruction.",
16661
+ terminal_closeout_command: terminalCloseoutCommand,
16662
+ worker_rule: "The worker must not own loop teardown and must not remove heartbeat automation based on idle polling.",
16663
+ },
16664
+ manager: {
16665
+ kind: "thread_heartbeat",
16666
+ poll_command: sessionPollCommand("manager", taskName, dbPath),
16667
+ prompt: [
16668
+ "Use the manage-codex-workers skill.",
16669
+ `Poll the manager inbox for task ${taskName}.`,
16670
+ `Run: ${sessionPollCommand("manager", taskName, dbPath)}`,
16671
+ "If an item is consumed, execute only that manager instruction, verify worker claims before recording conclusions, update Conveyor state as appropriate, and produce exactly one next worker task.",
16672
+ "If no item is consumed, stop after a one-line idle receipt.",
16673
+ "Do not delete, pause, or disable manager or worker heartbeat automation after an idle poll; an idle poll is only a quiet interval.",
16674
+ `If all accepted criteria are satisfied, deferred, or rejected and there is no next worker task, record the terminal manager decision, run or report the result of: ${terminalCloseoutCommand}`,
16675
+ "After verified task closeout, explicitly report heartbeat teardown status; if the task remains managed/active, report that as a control-plane blocker instead of calling the loop complete.",
16676
+ ].join("\n"),
16677
+ },
16678
+ worker: {
16679
+ kind: "thread_heartbeat",
16680
+ poll_command: sessionPollCommand("worker", taskName, dbPath),
16681
+ prompt: [
16682
+ "Use the manage-codex-workers skill.",
16683
+ `Poll the worker inbox for task ${taskName}.`,
16684
+ `Run: ${sessionPollCommand("worker", taskName, dbPath)}`,
16685
+ "If an item is consumed, execute only that single worker instruction and return exact commands, compact evidence, blockers/residual risk, and exactly one next recommended worker task.",
16686
+ "If no item is consumed, stop after a one-line idle receipt.",
16687
+ "Do not delete, pause, or disable worker heartbeat automation after an idle poll; the manager or operator owns terminal loop teardown.",
16688
+ ].join("\n"),
16689
+ },
16690
+ };
16691
+ }
16510
16692
  function sessionPollCommand(role, taskName, dbPath) {
16511
16693
  const inbox = role === "worker" ? "worker-inbox" : "manager-inbox";
16512
16694
  const task = taskName ? shellQuote(taskName) : "<task>";
16513
- return `conveyor ${inbox} ${task} --consume-next --wait --timeout 60 --path ${shellQuote(dbPath)} --json`;
16695
+ return `${conveyorPollInvocation()} ${inbox} ${task} --consume-next --wait --timeout 60 --path ${shellQuote(dbPath)} --json`;
16696
+ }
16697
+ function conveyorPollInvocation() {
16698
+ const binDir = join(packageRootFromRuntimeModule(), "bin");
16699
+ return pathIsExecutable(join(binDir, "conveyor")) ? `PATH=${shellQuote(binDir)}:$PATH conveyor` : "conveyor";
16514
16700
  }
16515
16701
  function resolveCodexStartupOptions(options) {
16516
16702
  const defaults = {
@@ -16660,16 +16846,20 @@ function startManagerBootstrapPrompt(database, options) {
16660
16846
  const goalLine = options.taskGoal ?? context?.goal ?? "No task goal supplied yet.";
16661
16847
  const workerLine = options.workerName ?? "No worker session supplied yet.";
16662
16848
  const workerctl = "conveyor";
16849
+ const pathSuffix = commandPathSuffix(options.dbPath);
16663
16850
  const setupCommand = options.taskName
16664
- ? `${workerctl} manager-config ${taskLine} --questions`
16665
- : `${workerctl} manager-config <task> --questions`;
16666
- const cycleCommand = options.taskName ? `${workerctl} cycle ${taskLine}` : `${workerctl} cycle <task>`;
16851
+ ? `${workerctl} manager-config ${taskLine} --questions${pathSuffix}`
16852
+ : `${workerctl} manager-config <task> --questions${pathSuffix}`;
16853
+ const cycleCommand = options.taskName ? `${workerctl} cycle ${taskLine}${pathSuffix}` : `${workerctl} cycle <task>${pathSuffix}`;
16667
16854
  const managerAckCommand = options.taskName
16668
- ? `${workerctl} manager-ack ${taskLine} --from-stdin`
16669
- : `${workerctl} manager-ack <task> --from-stdin`;
16855
+ ? `${workerctl} manager-ack ${taskLine} --from-stdin${pathSuffix}`
16856
+ : `${workerctl} manager-ack <task> --from-stdin${pathSuffix}`;
16670
16857
  const workerAckCommand = options.taskName
16671
- ? `${workerctl} worker-ack ${taskLine} --json`
16672
- : `${workerctl} worker-ack <task> --json`;
16858
+ ? `${workerctl} worker-ack ${taskLine} --json${pathSuffix}`
16859
+ : `${workerctl} worker-ack <task> --json${pathSuffix}`;
16860
+ const satisfyCriterionCommand = options.taskName
16861
+ ? `${workerctl} criteria ${taskLine} --satisfy <id> --proof "<proof>" --evidence-json '{"status":"pass","command":"<command>","summary":"<what this proved>"}'${pathSuffix}`
16862
+ : `${workerctl} criteria <task> --satisfy <id> --proof "<proof>" --evidence-json '{"status":"pass","command":"<command>","summary":"<what this proved>"}'${pathSuffix}`;
16673
16863
  const config = context ? managerConfigSync(database, context.id) : null;
16674
16864
  const initialSetup = config
16675
16865
  ? seededManagerConfigSetup({ config, cycleCommand, managerAckCommand, workerAckCommand })
@@ -16677,11 +16867,12 @@ function startManagerBootstrapPrompt(database, options) {
16677
16867
  "Initial setup:",
16678
16868
  `1. Run \`${setupCommand}\`.`,
16679
16869
  "2. Ask the user the returned setup questions in this manager Codex chat.",
16680
- `3. Persist the answers with \`${workerctl} manager-config\`.`,
16870
+ `3. Persist the answers with \`${workerctl} manager-config${pathSuffix}\`.`,
16681
16871
  "4. Use `conveyor manager-config --interactive` only when a human is directly running conveyor in a terminal.",
16682
16872
  "",
16683
16873
  "Acknowledgement:",
16684
16874
  `- Before your first cycle, record the supervision contract you are committing to with \`${managerAckCommand}\`.`,
16875
+ ` Example JSON: {"task":"${taskLine}","manager_session":"${options.managerName}","supervision_contract":"I will supervise through Conveyor and verify criteria before finishing.","will_not_edit_project_files":true}`,
16685
16876
  `- Before nudging or finishing, inspect the worker acknowledgement with \`${workerAckCommand}\` when available.`,
16686
16877
  ].join("\n");
16687
16878
  return [
@@ -16704,7 +16895,8 @@ function startManagerBootstrapPrompt(database, options) {
16704
16895
  "- Inspect `manager_context.acceptance_criteria` each cycle.",
16705
16896
  "- If worker progress reveals new edge cases, tests, polish, or scope boundaries, ask the worker to propose must-have vs follow-up criteria.",
16706
16897
  "- Before finishing, compare worker receipts/verification against accepted open criteria.",
16707
- `- When all accepted criteria are satisfied, deferred, or rejected, finish the task with \`${workerctl} finish-task ${taskLine} --reason "Accepted criteria satisfied" --require-criteria-audit\`.`,
16898
+ `- For each accepted criterion that is proven, record evidence with \`${satisfyCriterionCommand}\`.`,
16899
+ `- When all accepted criteria are satisfied, deferred, or rejected, finish the task with \`${workerctl} finish-task ${taskLine} --reason "Accepted criteria satisfied" --require-criteria-audit${pathSuffix}\`.`,
16708
16900
  "- Communicate with the worker only through conveyor session/task commands.",
16709
16901
  "- Do not edit project files unless the user explicitly asks this manager session to change Agent Conveyor itself.",
16710
16902
  ].join("\n");
@@ -16719,7 +16911,7 @@ function seededManagerConfigSetup(options) {
16719
16911
  if (options.config.tools.length > 0) {
16720
16912
  lines.push(`Expected tools: ${options.config.tools.join(", ")}.`);
16721
16913
  }
16722
- lines.push("", "Acknowledgement:", `- Before your first cycle, record the supervision contract you are committing to with \`${options.managerAckCommand}\`.`, `- Before nudging or finishing, inspect the worker acknowledgement with \`${options.workerAckCommand}\` when available.`);
16914
+ lines.push("", "Acknowledgement:", `- Before your first cycle, record the supervision contract you are committing to with \`${options.managerAckCommand}\`.`, ` Example JSON: {"task":"${options.config.task_id}","supervision_contract":"I will supervise through Conveyor and verify criteria before finishing.","will_not_edit_project_files":true}`, `- Before nudging or finishing, inspect the worker acknowledgement with \`${options.workerAckCommand}\` when available.`);
16723
16915
  return lines.join("\n");
16724
16916
  }
16725
16917
  function startManagerTaskContext(database, taskName) {
@@ -16733,6 +16925,9 @@ function startManagerTaskContext(database, taskName) {
16733
16925
  function shellQuote(value) {
16734
16926
  return `'${value.replace(/'/g, "'\"'\"'")}'`;
16735
16927
  }
16928
+ function commandPathSuffix(dbPath) {
16929
+ return dbPath ? ` --path ${shellQuote(dbPath)}` : "";
16930
+ }
16736
16931
  function emitTelemetrySync(database, options) {
16737
16932
  const eventId = `telemetry-${randomUUID()}`;
16738
16933
  const attributesJson = stableJson(options.attributes);
@@ -18191,9 +18386,8 @@ The supported manager/worker setup is session-based:
18191
18386
 
18192
18387
  ${workerctl} worker-ack <task-name> --from-stdin
18193
18388
 
18194
- The JSON should include goal_restatement, proposed_criteria,
18195
- expected_tools, open_questions, and ready_to_start. Proposed criteria should
18196
- separate must-have and follow-up criteria.
18389
+ Example JSON:
18390
+ {"goal_restatement":"Restate the assigned task.","proposed_criteria":{"must_have":["Current-task proof"],"follow_up":[]},"expected_tools":["shell"],"open_questions":[],"ready_to_start":true}
18197
18391
 
18198
18392
  Required fields:
18199
18393
  - worker name
@@ -18280,6 +18474,14 @@ function ensureTmuxAvailable(runner) {
18280
18474
  const detail = (result.stderr || result.stdout || `exit code ${result.status}`).trim();
18281
18475
  return lifecycleWorkerErrorResult(tmuxCommandFailureMessage(["tmux", "-V"], detail));
18282
18476
  }
18477
+ function ensureTmuxServerAccessible(runner) {
18478
+ const result = runner(["tmux", "start-server"], { check: false });
18479
+ if (result.status === 0) {
18480
+ return null;
18481
+ }
18482
+ const detail = (result.stderr || result.stdout || `exit code ${result.status}`).trim();
18483
+ return lifecycleWorkerErrorResult(tmuxCommandFailureMessage(["tmux", "start-server"], detail));
18484
+ }
18283
18485
  const CONTENT_KEYS = new Set(["content", "message", "output", "segment_text", "text"]);
18284
18486
  function redactPayload(value) {
18285
18487
  if (Array.isArray(value)) {