@pitboss/cli 0.4.8 → 0.4.10

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.
@@ -59,7 +59,8 @@ var BASE_TASK_LOOP = (agentId, repoPath) => `You are an AI worker agent (ID: ${a
59
59
  a one-line status on the task thread naming what you are checking) when you begin, then
60
60
  run the tests, compare the diff against the spec the task names, then
61
61
  \`mcp__relai__submit_review\` \u2014 \`approve\`, or \`reject\` with a specific note the author
62
- can act on. Do this even when get_my_tasks is empty.
62
+ can act on. When the task is a goal's build with a PR, pass \`headSha\`: the full sha of the
63
+ commit you reviewed. Do this even when get_my_tasks is empty.
63
64
  If the change touches a long-running service or a third-party runtime integration (SSE/event streams, launchd/systemd, auth headers, a client library), run the changed path once before approving and quote what it printed. A type cast past a library's own types is a reason to stop and run it.
64
65
 
65
66
  4. Call \`mcp__relai__get_my_tasks\` \u2014 if it returns no tasks, stop immediately.
@@ -301,7 +302,11 @@ Find the default branch with \`git remote show origin\`.
301
302
  \`specialization\` or \`domains\` only to values agents in \`mcp__relai__list_agents\` declare:
302
303
  the router never hands out a task whose tag no agent matches. When the repo has \`writer\` agents, set
303
304
  \`specialization: "writer"\` on build tasks, so they wait for a writer instead of going to a reviewer.
304
- Never name a build task's assignee as the goal's reviewer; the API refuses it. Make them small
305
+ Before writing any build task, choose the goal's reviewer (for a \`reviewer_agent\` gate): a
306
+ \`reviewer\` agent, on a different \`workerType\` from the writers when one exists. Never assign that
307
+ agent a build task, and never leave the only reviewers busy building: a goal whose every reviewer
308
+ built part of it has nobody left to review it. The API defaults a build task with no
309
+ \`specialization\` to \`writer\` when the repo has writers. Make them small
305
310
  and independently testable so several agents can work at once. Use \`blockedBy\` only where
306
311
  one task truly needs another's result. Each description is self-contained: what to change,
307
312
  which files, how to test it, and the acceptance criteria.
@@ -444,6 +449,9 @@ var USAGE_NUMBER_KEYS = [
444
449
  "cache_read_input_tokens",
445
450
  "cache_creation_input_tokens"
446
451
  ];
452
+ function notReportedUsage(reason) {
453
+ return { notReported: true, reason };
454
+ }
447
455
  function finiteNumber(value) {
448
456
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
449
457
  }
@@ -511,14 +519,32 @@ async function getTaskStatus(conn, id, logPrefix) {
511
519
  return void 0;
512
520
  }
513
521
  }
514
- async function sessionTouchedTaskIds(conn, candidates, logPrefix) {
522
+ async function listUpdatedSince(conn, since, logPrefix) {
523
+ try {
524
+ const res = await fetch(
525
+ `${conn.apiUrl}/tasks?repoId=${conn.repoId}&assignedTo=${conn.agentId}&updatedAfter=${encodeURIComponent(since)}`,
526
+ { headers: { Authorization: `Bearer ${conn.apiSecret}` } }
527
+ );
528
+ if (!res.ok) throw new Error(`task list failed (${res.status} ${res.statusText})`);
529
+ const { data } = await res.json();
530
+ return (data ?? []).filter((row) => typeof row.id === "string" && row.id.length > 0).filter((row) => row.status !== "assigned").map((row) => row.id);
531
+ } catch (err) {
532
+ console.error(
533
+ `${logPrefix} Could not list tasks updated since the session started:`,
534
+ err instanceof Error ? err.message : String(err)
535
+ );
536
+ return [];
537
+ }
538
+ }
539
+ async function sessionTouchedTaskIds(conn, candidates, logPrefix, since) {
515
540
  const touched = [];
516
541
  for (const id of candidates) {
517
542
  const status = await getTaskStatus(conn, id, logPrefix);
518
543
  if (status !== void 0 && status !== "assigned") touched.push(id);
519
544
  }
520
545
  const inProgressAtClose = await listSessionTaskIds(conn, logPrefix, "in_progress");
521
- return unionTaskIds(touched, inProgressAtClose);
546
+ const updated = since ? await listUpdatedSince(conn, since, logPrefix) : [];
547
+ return unionTaskIds(touched, inProgressAtClose, updated);
522
548
  }
523
549
  function unionTaskIds(...lists) {
524
550
  const seen = /* @__PURE__ */ new Set();
@@ -532,34 +558,99 @@ function unionTaskIds(...lists) {
532
558
  }
533
559
  return out;
534
560
  }
535
- async function writeSessionUsage(conn, taskIds, usage, logPrefix) {
536
- const sessionUsage = {
537
- ...usage,
538
- at: (/* @__PURE__ */ new Date()).toISOString(),
539
- sessionTaskIds: [...taskIds]
540
- };
561
+ async function writeSessionUsage(conn, taskIds, usage, logPrefix, opts = {}) {
541
562
  const headers = { "Content-Type": "application/json", Authorization: `Bearer ${conn.apiSecret}` };
542
563
  for (const id of taskIds) {
564
+ const slice = taskIds.length > 1 && !usage.notReported ? opts.slices?.[id] : void 0;
565
+ const share = usage.notReported ? void 0 : slice || taskIds.length === 1 ? "task" : "session";
566
+ const body = {
567
+ usage: slice ?? usage,
568
+ sessionTaskIds: taskIds,
569
+ ...opts.model ? { model: opts.model } : {},
570
+ ...share ? { share } : {}
571
+ };
543
572
  try {
544
- const put = await fetch(`${conn.apiUrl}/tasks/${id}`, {
545
- method: "PUT",
573
+ const res = await fetch(`${conn.apiUrl}/tasks/${id}/usage-report`, {
574
+ method: "POST",
546
575
  headers,
547
- body: JSON.stringify({ metadata: { sessionUsage } })
576
+ body: JSON.stringify(body)
548
577
  });
549
- if (!put.ok) {
550
- console.error(`${logPrefix} Could not write sessionUsage on ${id} (${put.status} ${put.statusText})`);
578
+ if (!res.ok) {
579
+ console.error(`${logPrefix} Could not report usage on ${id} (${res.status} ${res.statusText})`);
551
580
  }
552
581
  } catch (err) {
553
582
  console.error(
554
- `${logPrefix} Could not write sessionUsage on ${id}:`,
583
+ `${logPrefix} Could not report usage on ${id}:`,
555
584
  err instanceof Error ? err.message : String(err)
556
585
  );
557
586
  }
558
587
  }
559
588
  }
560
- async function recordSessionUsage(conn, startIds, usage, logPrefix) {
589
+ async function recordSessionUsage(conn, startIds, usage, logPrefix, opts = {}) {
561
590
  const start = await startIds;
562
- await writeSessionUsage(conn, await sessionTouchedTaskIds(conn, start, logPrefix), usage, logPrefix);
591
+ const { since, ...rest } = opts;
592
+ await writeSessionUsage(conn, await sessionTouchedTaskIds(conn, start, logPrefix, since), usage, logPrefix, rest);
593
+ }
594
+ var BOUNDARY_STATUSES = /* @__PURE__ */ new Set(["completed", "blocked", "pending_verification"]);
595
+ function addUsage(into, from) {
596
+ into.num_turns = (into.num_turns ?? 0) + (from.num_turns ?? 0);
597
+ const total = into.usage ?? {};
598
+ for (const key of USAGE_NUMBER_KEYS) {
599
+ const n = from.usage?.[key];
600
+ if (n !== void 0) total[key] = (total[key] ?? 0) + n;
601
+ }
602
+ into.usage = total;
603
+ }
604
+ function createUsageTracker() {
605
+ let model;
606
+ let pending = /* @__PURE__ */ new Map();
607
+ const slices = {};
608
+ let anon = 0;
609
+ function cut(taskId) {
610
+ const slice = slices[taskId] ?? {};
611
+ for (const messageUsage of pending.values()) addUsage(slice, messageUsage);
612
+ pending = /* @__PURE__ */ new Map();
613
+ if (slice.usage) slices[taskId] = slice;
614
+ }
615
+ return {
616
+ observe(event) {
617
+ if (event.type === "system" && event.subtype === "init") {
618
+ if (typeof event.model === "string" && event.model) model = event.model;
619
+ return;
620
+ }
621
+ if (event.type !== "assistant") return;
622
+ const message = event.message ?? {};
623
+ const raw = message.usage;
624
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
625
+ const usage = pickNumbers(raw, USAGE_NUMBER_KEYS);
626
+ if (Object.keys(usage).length > 0) {
627
+ pending.set(typeof message.id === "string" ? message.id : `anon_${anon++}`, { num_turns: 1, usage });
628
+ }
629
+ }
630
+ const content = Array.isArray(message.content) ? message.content : [];
631
+ for (const block of content) {
632
+ if (block.type !== "tool_use" || typeof block.name !== "string" || !block.name.endsWith("update_task_status")) continue;
633
+ const input = block.input ?? {};
634
+ if (typeof input.taskId === "string" && typeof input.status === "string" && BOUNDARY_STATUSES.has(input.status)) {
635
+ cut(input.taskId);
636
+ }
637
+ }
638
+ },
639
+ model: () => model,
640
+ opts: () => ({ ...model ? { model } : {}, slices })
641
+ };
642
+ }
643
+
644
+ // ../claude-worker/src/model.ts
645
+ import { readFileSync } from "node:fs";
646
+ function pinnedModel(env = process.env) {
647
+ const file = env.PITBOSS_MODEL_FILE;
648
+ if (!file) return void 0;
649
+ try {
650
+ return readFileSync(file, "utf-8").trim() || void 0;
651
+ } catch {
652
+ return void 0;
653
+ }
563
654
  }
564
655
 
565
656
  // ../claude-worker/src/session.ts
@@ -594,26 +685,36 @@ function writeMcpConfig(config2, prefix = "claude-worker") {
594
685
  writeFileSync(path, JSON.stringify(mcpConfig), { mode: 384 });
595
686
  return path;
596
687
  }
688
+ function claudeArgs(opts) {
689
+ return [
690
+ "--print",
691
+ "--mcp-config",
692
+ opts.mcpConfigPath,
693
+ "--no-session-persistence",
694
+ "--dangerously-skip-permissions",
695
+ "--output-format",
696
+ "stream-json",
697
+ "--verbose",
698
+ "--model",
699
+ opts.model,
700
+ "--add-dir",
701
+ opts.repoPath,
702
+ ...opts.isolateUserConfig ? ["--setting-sources", "project,local"] : []
703
+ ];
704
+ }
597
705
  async function runClaudeSession(config2) {
598
706
  const prompt = buildPrompt(config2);
599
707
  const mcpConfigPath = writeMcpConfig(config2);
708
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
600
709
  const taskIdsPromise = listSessionTaskIds(config2, "[claude-worker]");
601
710
  try {
602
711
  return await new Promise((resolve, reject) => {
603
- const args = [
604
- "--print",
605
- "--mcp-config",
712
+ const args = claudeArgs({
606
713
  mcpConfigPath,
607
- "--no-session-persistence",
608
- "--dangerously-skip-permissions",
609
- "--output-format",
610
- "stream-json",
611
- "--verbose",
612
- "--model",
613
- config2.model,
614
- "--add-dir",
615
- config2.repoPath
616
- ];
714
+ model: pinnedModel() ?? config2.model,
715
+ repoPath: config2.repoPath,
716
+ isolateUserConfig: process.env.PITBOSS_ISOLATE_USER_CONFIG === "1"
717
+ });
617
718
  console.log(`[claude-worker] Spawning: ${config2.claudeBin} ${args.slice(0, 4).join(" ")} ...`);
618
719
  console.log(`[claude-worker] cwd: ${config2.repoPath}`);
619
720
  const { ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ...workerEnv } = process.env;
@@ -629,6 +730,8 @@ async function runClaudeSession(config2) {
629
730
  let buffer = "";
630
731
  let resultError = "";
631
732
  let sessionUsage;
733
+ let sawResult = false;
734
+ const tracker = createUsageTracker();
632
735
  proc.stdout.on("data", (chunk) => {
633
736
  buffer += chunk.toString();
634
737
  const lines = buffer.split("\n");
@@ -637,6 +740,7 @@ async function runClaudeSession(config2) {
637
740
  if (!line.trim()) continue;
638
741
  try {
639
742
  const event = JSON.parse(line);
743
+ tracker.observe(event);
640
744
  if (event.type === "assistant") {
641
745
  const content = event.message?.content ?? [];
642
746
  for (const block of content) {
@@ -650,6 +754,7 @@ async function runClaudeSession(config2) {
650
754
  }
651
755
  }
652
756
  } else if (event.type === "result") {
757
+ sawResult = true;
653
758
  const parsed = parseResultUsage(event);
654
759
  if (parsed) sessionUsage = parsed;
655
760
  if (event.is_error) {
@@ -671,9 +776,9 @@ async function runClaudeSession(config2) {
671
776
  proc.on("close", (code, signal) => {
672
777
  const unique = [...new Set(toolsUsed)];
673
778
  console.log(`[claude-worker] Done \u2014 tools used: ${unique.length ? unique.join(", ") : "none"}`);
674
- const usage = sessionUsage;
675
- if (usage) console.log(formatSessionUsageLine("[claude-worker]", usage));
676
- const finish = usage ? recordSessionUsage(config2, taskIdsPromise, usage, "[claude-worker]") : Promise.resolve();
779
+ const usage = sessionUsage ?? notReportedUsage(sawResult ? "adapter" : "killed");
780
+ if (sessionUsage) console.log(formatSessionUsageLine("[claude-worker]", sessionUsage));
781
+ const finish = recordSessionUsage(config2, taskIdsPromise, usage, "[claude-worker]", { since: startedAt, ...tracker.opts() });
677
782
  const settle = () => {
678
783
  if (signal || code !== 0 && code !== null) {
679
784
  const detail = (resultError || stderrOutput || "").trim();
@@ -731,8 +836,8 @@ function classifySessionError(text) {
731
836
 
732
837
  // ../claude-worker/src/adapters/cursor.ts
733
838
  var bin = () => process.env.CURSOR_BIN ?? "cursor-agent";
734
- function cursorArgs(repoPath, prompt) {
735
- return ["-p", "--trust", "--force", "--output-format", "stream-json", "--workspace", repoPath, prompt];
839
+ function cursorArgs(repoPath, prompt, model) {
840
+ return ["-p", "--trust", "--force", "--output-format", "stream-json", "--workspace", repoPath, ...model ? ["--model", model] : [], prompt];
736
841
  }
737
842
  function readCursorEvent(line) {
738
843
  let event;
@@ -741,6 +846,9 @@ function readCursorEvent(line) {
741
846
  } catch {
742
847
  return {};
743
848
  }
849
+ if (event.type === "system" && event.subtype === "init") {
850
+ return typeof event.model === "string" && event.model ? { model: event.model } : {};
851
+ }
744
852
  if (event.type === "tool_call" && event.subtype === "started") {
745
853
  const call = event.tool_call ?? {};
746
854
  const [kind] = Object.keys(call);
@@ -756,8 +864,9 @@ function readCursorEvent(line) {
756
864
  return {};
757
865
  }
758
866
  async function runCursorSession(config2) {
759
- const args = cursorArgs(config2.repoPath, buildPrompt(config2));
867
+ const args = cursorArgs(config2.repoPath, buildPrompt(config2), pinnedModel());
760
868
  console.log(`[cursor-worker] Spawning: ${bin()} ${args.slice(0, 5).join(" ")} ...`);
869
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
761
870
  const taskIdsPromise = listSessionTaskIds(config2, "[cursor-worker]");
762
871
  return new Promise((resolve, reject) => {
763
872
  const proc = spawn2(bin(), args, { cwd: config2.repoPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
@@ -767,12 +876,14 @@ async function runCursorSession(config2) {
767
876
  let resultError = "";
768
877
  let stderrOutput = "";
769
878
  let sessionUsage;
879
+ let model;
770
880
  proc.stdout.on("data", (chunk) => {
771
881
  buffer += chunk.toString();
772
882
  const lines = buffer.split("\n");
773
883
  buffer = lines.pop() ?? "";
774
884
  for (const line of lines) {
775
- const { tool, error, usage } = readCursorEvent(line);
885
+ const { tool, error, usage, model: initModel } = readCursorEvent(line);
886
+ if (initModel) model = initModel;
776
887
  if (tool) {
777
888
  toolsUsed.push(tool);
778
889
  process.stdout.write(` \u2699 ${tool}
@@ -790,9 +901,9 @@ async function runCursorSession(config2) {
790
901
  });
791
902
  proc.on("close", (code, signal) => {
792
903
  console.log(`[cursor-worker] Done \u2014 tools used: ${toolsUsed.length ? [...new Set(toolsUsed)].join(", ") : "none"}`);
793
- const usage = sessionUsage;
794
- if (usage) console.log(formatSessionUsageLine("[cursor-worker]", usage));
795
- const finish = usage ? recordSessionUsage(config2, taskIdsPromise, usage, "[cursor-worker]") : Promise.resolve();
904
+ const usage = sessionUsage ?? notReportedUsage("killed");
905
+ if (sessionUsage) console.log(formatSessionUsageLine("[cursor-worker]", sessionUsage));
906
+ const finish = recordSessionUsage(config2, taskIdsPromise, usage, "[cursor-worker]", { since: startedAt, ...model ? { model } : {} });
796
907
  const settle = () => {
797
908
  if (signal || code !== 0 && code !== null) {
798
909
  const detail = (resultError || stderrOutput).trim();
@@ -837,10 +948,11 @@ import { unlinkSync as unlinkSync2 } from "fs";
837
948
  var bin2 = () => process.env.COPILOT_BIN ?? "copilot";
838
949
  var POLICY_BLOCKS = [/access denied by policy/i, /third-party mcp servers are disabled/i];
839
950
  var QUOTA = [/premium request/i, /monthly (?:limit|allowance|quota)/i];
840
- function copilotArgs(repoPath, prompt, mcpConfigPath) {
951
+ function copilotArgs(repoPath, prompt, mcpConfigPath, model) {
841
952
  return [
842
953
  "-p",
843
954
  prompt,
955
+ ...model ? ["--model", model] : [],
844
956
  "--allow-all-tools",
845
957
  "--output-format",
846
958
  "json",
@@ -862,6 +974,7 @@ function readCopilotEvent(line) {
862
974
  const type = typeof event.type === "string" ? event.type : "";
863
975
  const data = event.data ?? {};
864
976
  const message = typeof data.message === "string" ? data.message : "";
977
+ if (type === "session.tools_updated" && typeof data.model === "string" && data.model) return { model: data.model };
865
978
  if (/^tool\..*start/.test(type) && typeof data.toolName === "string") return { tool: data.toolName };
866
979
  if (type === "session.error") return { error: message || "copilot session error" };
867
980
  if (type === "result" && typeof event.exitCode === "number" && event.exitCode !== 0) return { error: `copilot finished with exit code ${event.exitCode}` };
@@ -870,7 +983,9 @@ function readCopilotEvent(line) {
870
983
  }
871
984
  async function runCopilotSession(config2) {
872
985
  const mcpConfigPath = writeMcpConfig(config2, "copilot-worker");
873
- const args = copilotArgs(config2.repoPath, buildPrompt(config2), mcpConfigPath);
986
+ const args = copilotArgs(config2.repoPath, buildPrompt(config2), mcpConfigPath, pinnedModel());
987
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
988
+ const taskIdsPromise = listSessionTaskIds(config2, "[copilot-worker]");
874
989
  console.log(`[copilot-worker] Spawning: ${bin2()} -p <prompt> ${args.slice(2, 6).join(" ")} ...`);
875
990
  return new Promise((resolve, reject) => {
876
991
  const proc = spawn3(bin2(), args, { cwd: config2.repoPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
@@ -879,12 +994,14 @@ async function runCopilotSession(config2) {
879
994
  let buffer = "";
880
995
  let sessionError = "";
881
996
  let stderrOutput = "";
997
+ let model;
882
998
  proc.stdout.on("data", (chunk) => {
883
999
  buffer += chunk.toString();
884
1000
  const lines = buffer.split("\n");
885
1001
  buffer = lines.pop() ?? "";
886
1002
  for (const line of lines) {
887
- const { tool, error } = readCopilotEvent(line);
1003
+ const { tool, error, model: initModel } = readCopilotEvent(line);
1004
+ if (initModel) model = initModel;
888
1005
  if (tool) {
889
1006
  toolsUsed.push(tool);
890
1007
  process.stdout.write(` \u2699 ${tool}
@@ -905,12 +1022,15 @@ async function runCopilotSession(config2) {
905
1022
  } catch {
906
1023
  }
907
1024
  console.log(`[copilot-worker] Done \u2014 tools used: ${toolsUsed.length ? [...new Set(toolsUsed)].join(", ") : "none"}`);
908
- if (sessionError || signal || code !== 0 && code !== null) {
909
- const detail = (sessionError || stderrOutput).trim();
910
- reject(new Error(`copilot exited with ${signal ?? `code ${code}`}${detail ? `: ${detail}` : ""}`));
911
- } else {
912
- resolve([...new Set(toolsUsed)]);
913
- }
1025
+ const settle = () => {
1026
+ if (sessionError || signal || code !== 0 && code !== null) {
1027
+ const detail = (sessionError || stderrOutput).trim();
1028
+ reject(new Error(`copilot exited with ${signal ?? `code ${code}`}${detail ? `: ${detail}` : ""}`));
1029
+ } else {
1030
+ resolve([...new Set(toolsUsed)]);
1031
+ }
1032
+ };
1033
+ recordSessionUsage(config2, taskIdsPromise, notReportedUsage("adapter"), "[copilot-worker]", { since: startedAt, ...model ? { model } : {} }).then(settle, settle);
914
1034
  });
915
1035
  proc.on("error", (err) => {
916
1036
  try {
@@ -943,7 +1063,7 @@ var bin3 = () => process.env.CODEX_BIN ?? "codex";
943
1063
  var codexHome = () => process.env.CODEX_HOME ?? join3(homedir(), ".codex");
944
1064
  var QUOTA2 = [/usage limit/i, /rate limit reached for/i, /upgrade to (plus|pro)/i];
945
1065
  var CREDENTIALS = [/not logged in/i, /codex login/i, /401 unauthorized/i];
946
- function codexArgs(repoPath, prompt, profile) {
1066
+ function codexArgs(repoPath, prompt, profile, model) {
947
1067
  return [
948
1068
  "exec",
949
1069
  "--json",
@@ -954,6 +1074,7 @@ function codexArgs(repoPath, prompt, profile) {
954
1074
  repoPath,
955
1075
  "-p",
956
1076
  profile,
1077
+ ...model ? ["-m", model] : [],
957
1078
  prompt
958
1079
  ];
959
1080
  }
@@ -1002,7 +1123,9 @@ async function runCodexSession(config2) {
1002
1123
  } catch {
1003
1124
  }
1004
1125
  };
1005
- const args = codexArgs(config2.repoPath, buildPrompt(config2), profile);
1126
+ const args = codexArgs(config2.repoPath, buildPrompt(config2), profile, pinnedModel());
1127
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1128
+ const taskIdsPromise = listSessionTaskIds(config2, "[codex-worker]");
1006
1129
  console.log(`[codex-worker] Spawning: ${bin3()} ${args.slice(0, 6).join(" ")} ...`);
1007
1130
  return new Promise((resolve, reject) => {
1008
1131
  const proc = spawn4(bin3(), args, { cwd: config2.repoPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
@@ -1034,12 +1157,15 @@ async function runCodexSession(config2) {
1034
1157
  proc.on("close", (code, signal) => {
1035
1158
  cleanup();
1036
1159
  console.log(`[codex-worker] Done \u2014 tools used: ${toolsUsed.length ? [...new Set(toolsUsed)].join(", ") : "none"}`);
1037
- if (sessionError || signal || code !== 0 && code !== null) {
1038
- const detail = (sessionError || stderrOutput).trim();
1039
- reject(new Error(`codex exited with ${signal ?? `code ${code}`}${detail ? `: ${detail}` : ""}`));
1040
- } else {
1041
- resolve([...new Set(toolsUsed)]);
1042
- }
1160
+ const settle = () => {
1161
+ if (sessionError || signal || code !== 0 && code !== null) {
1162
+ const detail = (sessionError || stderrOutput).trim();
1163
+ reject(new Error(`codex exited with ${signal ?? `code ${code}`}${detail ? `: ${detail}` : ""}`));
1164
+ } else {
1165
+ resolve([...new Set(toolsUsed)]);
1166
+ }
1167
+ };
1168
+ recordSessionUsage(config2, taskIdsPromise, notReportedUsage("adapter"), "[codex-worker]", { since: startedAt }).then(settle, settle);
1043
1169
  });
1044
1170
  proc.on("error", (err) => {
1045
1171
  cleanup();
@@ -1287,6 +1413,10 @@ function loadConfig2() {
1287
1413
  };
1288
1414
  }
1289
1415
 
1416
+ // ../event-worker/src/worker.ts
1417
+ import { mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
1418
+ import { dirname as dirname2 } from "node:path";
1419
+
1290
1420
  // ../../node_modules/.pnpm/eventsource-parser@3.0.7/node_modules/eventsource-parser/dist/index.js
1291
1421
  var ParseError = class extends Error {
1292
1422
  constructor(message, options) {
@@ -1847,7 +1977,16 @@ async function runEventWorker(config2) {
1847
1977
  }
1848
1978
  try {
1849
1979
  console.log("[event-worker] Event received \u2014 running session...");
1850
- await getAdapter(config2.runtime).runSession(config2);
1980
+ const activityFile = process.env.PITBOSS_ACTIVITY_FILE;
1981
+ if (activityFile) {
1982
+ mkdirSync2(dirname2(activityFile), { recursive: true });
1983
+ writeFileSync3(activityFile, String(Date.now()));
1984
+ }
1985
+ try {
1986
+ await getAdapter(config2.runtime).runSession(config2);
1987
+ } finally {
1988
+ if (activityFile) rmSync(activityFile, { force: true });
1989
+ }
1851
1990
  await clearUnavailable(config2, "[event-worker]");
1852
1991
  } catch (err) {
1853
1992
  const text = err instanceof Error ? err.message : String(err);