@gethmy/agent 1.23.2 → 1.24.0

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 (3) hide show
  1. package/dist/cli.js +412 -215
  2. package/dist/index.js +412 -215
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -592,7 +592,6 @@ var init_agentStaleness = __esm(() => {
592
592
  SWEPT_SESSION_WRITE_GRACE_MS = 60 * 60 * 1000;
593
593
  ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
594
594
  });
595
-
596
595
  // ../harmony-shared/dist/branchRef.js
597
596
  function extractBranchRef(description) {
598
597
  if (!description)
@@ -835,7 +834,7 @@ function gateEvaluate(gateSpec, evidence) {
835
834
  findings: [
836
835
  {
837
836
  level: "error",
838
- message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
837
+ message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".${blockedDetail(safeStructured)}` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
839
838
  }
840
839
  ],
841
840
  structured: safeStructured
@@ -853,7 +852,7 @@ function gateEvaluate(gateSpec, evidence) {
853
852
  if (result === "blocked") {
854
853
  findings.unshift({
855
854
  level: "error",
856
- message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".`
855
+ message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".${blockedDetail(safeStructured)}`
857
856
  });
858
857
  return { passed: false, findings, structured: safeStructured };
859
858
  }
@@ -949,6 +948,15 @@ function evaluateCondition(raw, structured) {
949
948
  function isPlainObject(value) {
950
949
  return typeof value === "object" && value !== null && !Array.isArray(value);
951
950
  }
951
+ function blockedDetail(structured) {
952
+ const reason = structured.reason;
953
+ if (typeof reason !== "string")
954
+ return "";
955
+ const trimmed = reason.trim();
956
+ if (!trimmed)
957
+ return "";
958
+ return ` ${trimmed.length > MAX_REASON_CHARS ? `${trimmed.slice(0, MAX_REASON_CHARS)}…` : trimmed}`;
959
+ }
952
960
  function resolvePath(root, path) {
953
961
  const segments = path.split(".");
954
962
  let current = root;
@@ -1021,7 +1029,7 @@ function formatValue(value) {
1021
1029
  return "[unserializable]";
1022
1030
  }
1023
1031
  }
1024
- var GATE_KINDS, GATE_OPERATORS;
1032
+ var GATE_KINDS, GATE_OPERATORS, MAX_REASON_CHARS = 400;
1025
1033
  var init_gateEvaluate = __esm(() => {
1026
1034
  GATE_KINDS = [
1027
1035
  "build_green",
@@ -1752,7 +1760,7 @@ function agentIdentifier(workerId) {
1752
1760
  function endStatusForCancel(reason) {
1753
1761
  return reason === "human_stop" ? "cancelled" : "paused";
1754
1762
  }
1755
- var DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
1763
+ var DEFAULT_METRIC_TIMEOUT_MS = 300000, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
1756
1764
  var init_types2 = __esm(() => {
1757
1765
  init_board_review();
1758
1766
  init_contract_phase();
@@ -1845,7 +1853,7 @@ var init_types2 = __esm(() => {
1845
1853
  worktreeGcIntervalMs: 5 * 60000
1846
1854
  },
1847
1855
  planning: DEFAULT_PLANNING_CONFIG,
1848
- playbooks: { enabled: true, humanStageColumns: [] },
1856
+ playbooks: { enabled: true, humanStageColumns: [], metrics: {} },
1849
1857
  contractFirst: DEFAULT_CONTRACT_CONFIG,
1850
1858
  boardReview: DEFAULT_BOARD_REVIEW_CONFIG
1851
1859
  };
@@ -5676,6 +5684,173 @@ var init_artifact_judge = __esm(() => {
5676
5684
  init_sdk_agent_runner();
5677
5685
  });
5678
5686
 
5687
+ // src/command-metric.ts
5688
+ import { execFileSync as execFileSync10 } from "node:child_process";
5689
+ function parseParseMode(parse) {
5690
+ if (typeof parse !== "string" || parse.length === 0) {
5691
+ return { kind: "invalid", reason: "`parse` must be a non-empty string" };
5692
+ }
5693
+ if (parse === "number")
5694
+ return { kind: "number" };
5695
+ if (parse.startsWith("json:")) {
5696
+ const path = parse.slice("json:".length).trim();
5697
+ if (!path) {
5698
+ return { kind: "invalid", reason: '`parse` "json:" is missing a path' };
5699
+ }
5700
+ return { kind: "json", path };
5701
+ }
5702
+ return {
5703
+ kind: "invalid",
5704
+ reason: `unknown \`parse\` mode "${parse}" (expected "number" or "json:<path>")`
5705
+ };
5706
+ }
5707
+ function parseMetricValue(mode, stdout) {
5708
+ if (mode.kind === "invalid")
5709
+ return { ok: false, reason: mode.reason };
5710
+ if (mode.kind === "number") {
5711
+ const trimmed = stdout.trim();
5712
+ if (!trimmed) {
5713
+ return {
5714
+ ok: false,
5715
+ reason: "command produced no output to read a number from"
5716
+ };
5717
+ }
5718
+ const value = Number(trimmed);
5719
+ if (!Number.isFinite(value)) {
5720
+ return {
5721
+ ok: false,
5722
+ reason: `command output is not a finite number: ${JSON.stringify(truncate(trimmed, 120))}`
5723
+ };
5724
+ }
5725
+ return { ok: true, value };
5726
+ }
5727
+ let doc;
5728
+ try {
5729
+ doc = JSON.parse(stdout);
5730
+ } catch (err) {
5731
+ const msg = err instanceof Error ? err.message : String(err);
5732
+ return { ok: false, reason: `command output is not valid JSON: ${msg}` };
5733
+ }
5734
+ const resolved = resolvePath(doc, mode.path);
5735
+ if (resolved === undefined) {
5736
+ return {
5737
+ ok: false,
5738
+ reason: `JSON path "${mode.path}" is absent in the command output`
5739
+ };
5740
+ }
5741
+ if (resolved !== null && typeof resolved !== "number" && typeof resolved !== "string" && typeof resolved !== "boolean") {
5742
+ return {
5743
+ ok: false,
5744
+ reason: `JSON path "${mode.path}" resolved to a ${Array.isArray(resolved) ? "array" : typeof resolved}, which no gate operator can compare`
5745
+ };
5746
+ }
5747
+ return { ok: true, value: resolved };
5748
+ }
5749
+ function defaultRunCommand(args) {
5750
+ const out = execFileSync10(args.command, args.args, {
5751
+ cwd: args.cwd,
5752
+ timeout: args.timeoutMs,
5753
+ stdio: "pipe",
5754
+ maxBuffer: MAX_OUTPUT_BUFFER2,
5755
+ encoding: "utf8"
5756
+ });
5757
+ return typeof out === "string" ? out : String(out);
5758
+ }
5759
+
5760
+ class CommandMetricCollector {
5761
+ deps;
5762
+ kind = "custom";
5763
+ constructor(deps) {
5764
+ this.deps = deps;
5765
+ }
5766
+ async collect(context) {
5767
+ const name = context.gate.metric;
5768
+ if (typeof name !== "string" || name.length === 0) {
5769
+ return blocked(null, 'Gate kind "custom" needs a `metric` name naming an allowlisted command (e.g. { "kind": "custom", "metric": "lighthouse_performance" }).');
5770
+ }
5771
+ const def = Object.hasOwn(this.deps.metrics, name) ? this.deps.metrics[name] : undefined;
5772
+ if (!def) {
5773
+ return blocked(name, `Metric "${name}" is not declared in this daemon's allowlist — add it under \`agent.playbooks.metrics\` to permit it.`);
5774
+ }
5775
+ if (typeof def.command !== "string" || def.command.length === 0) {
5776
+ return blocked(name, `Metric "${name}" declares no \`command\`.`);
5777
+ }
5778
+ const mode = parseParseMode(def.parse);
5779
+ if (mode.kind === "invalid") {
5780
+ return blocked(name, `Metric "${name}": ${mode.reason}.`);
5781
+ }
5782
+ const requested = typeof def.timeoutMs === "number" && def.timeoutMs > 0 ? Math.floor(def.timeoutMs) : DEFAULT_METRIC_TIMEOUT_MS;
5783
+ const timeoutMs = Math.min(requested, MAX_METRIC_TIMEOUT_MS);
5784
+ if (timeoutMs < requested) {
5785
+ log.warn(TAG19, `Metric "${name}" declares timeoutMs=${requested}, clamped to the ${MAX_METRIC_TIMEOUT_MS}ms ceiling`);
5786
+ }
5787
+ const run = this.deps.runCommand ?? defaultRunCommand;
5788
+ let stdout;
5789
+ try {
5790
+ stdout = run({
5791
+ command: def.command,
5792
+ args: def.args ?? [],
5793
+ cwd: this.deps.worktreePath,
5794
+ timeoutMs
5795
+ });
5796
+ } catch (err) {
5797
+ const reason = describeRunFailure(err, timeoutMs);
5798
+ log.warn(TAG19, `Metric "${name}" did not produce a measurement: ${reason}`);
5799
+ return blocked(name, reason);
5800
+ }
5801
+ const parsed = parseMetricValue(mode, stdout);
5802
+ if (!parsed.ok) {
5803
+ log.warn(TAG19, `Metric "${name}" output unusable: ${parsed.reason}`);
5804
+ return blocked(name, `Metric "${name}": ${parsed.reason}.`, stdout);
5805
+ }
5806
+ log.info(TAG19, `Metric "${name}" measured: ${JSON.stringify(parsed.value)}`);
5807
+ return {
5808
+ result: "passed",
5809
+ structured: {
5810
+ metric: name,
5811
+ value: parsed.value,
5812
+ raw: truncate(stdout, MAX_RAW_CHARS)
5813
+ }
5814
+ };
5815
+ }
5816
+ }
5817
+ function blocked(metric, reason, raw) {
5818
+ return {
5819
+ result: "blocked",
5820
+ structured: {
5821
+ metric,
5822
+ reason,
5823
+ ...raw === undefined ? {} : { raw: truncate(raw, MAX_RAW_CHARS) }
5824
+ }
5825
+ };
5826
+ }
5827
+ function describeRunFailure(err, timeoutMs) {
5828
+ const e = err;
5829
+ if (e?.code === "ENOBUFS") {
5830
+ return `command produced more than ${MAX_OUTPUT_BUFFER2} bytes of output (use a flag that prints only the metric, or parse a smaller report)`;
5831
+ }
5832
+ if (e?.code === "ETIMEDOUT" || e?.signal === "SIGTERM") {
5833
+ return `command timed out after ${timeoutMs}ms`;
5834
+ }
5835
+ if (e?.code === "ENOENT") {
5836
+ return "command not found on PATH";
5837
+ }
5838
+ const stderr = typeof e?.stderr === "string" ? e.stderr : e?.stderr instanceof Buffer ? e.stderr.toString("utf8") : "";
5839
+ const detail = stderr.trim() || String(e?.message ?? err);
5840
+ const status = typeof e?.status === "number" ? e.status : null;
5841
+ return status === null ? `command failed: ${truncate(detail, 400)}` : `command exited ${status}: ${truncate(detail, 400)}`;
5842
+ }
5843
+ function truncate(value, max) {
5844
+ return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
5845
+ }
5846
+ var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_METRIC_TIMEOUT_MS = 900000;
5847
+ var init_command_metric = __esm(() => {
5848
+ init_dist();
5849
+ init_log();
5850
+ init_types2();
5851
+ MAX_OUTPUT_BUFFER2 = 10 * 1024 * 1024;
5852
+ });
5853
+
5679
5854
  // src/gate-collectors.ts
5680
5855
  async function resolveStageGate(client, card) {
5681
5856
  const currentStage = card.current_stage;
@@ -5694,7 +5869,7 @@ async function resolveStageGate(client, card) {
5694
5869
  return null;
5695
5870
  return { stage: resolution.stage, gate };
5696
5871
  } catch (err) {
5697
- log.warn(TAG19, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
5872
+ log.warn(TAG20, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
5698
5873
  return null;
5699
5874
  }
5700
5875
  }
@@ -5714,6 +5889,9 @@ function normalizeGateSpec(gate) {
5714
5889
  const mode = gate.mode;
5715
5890
  if (mode === "all" || mode === "any")
5716
5891
  spec.mode = mode;
5892
+ const metric = gate.metric;
5893
+ if (typeof metric === "string" && metric.length > 0)
5894
+ spec.metric = metric;
5717
5895
  return spec;
5718
5896
  }
5719
5897
 
@@ -5811,12 +5989,15 @@ function buildGateCollectorRegistry(deps) {
5811
5989
  if (deps.artifact) {
5812
5990
  registry.artifact = new ArtifactCollector(deps.artifact);
5813
5991
  }
5992
+ if (deps.command) {
5993
+ registry.custom = new CommandMetricCollector(deps.command);
5994
+ }
5814
5995
  return registry;
5815
5996
  }
5816
5997
  async function collectGateEvidence(registry, context) {
5817
5998
  const collector = registry[context.gate.kind];
5818
5999
  if (!collector) {
5819
- log.info(TAG19, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
6000
+ log.info(TAG20, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
5820
6001
  return {
5821
6002
  result: "blocked",
5822
6003
  structured: {
@@ -5828,20 +6009,21 @@ async function collectGateEvidence(registry, context) {
5828
6009
  return await collector.collect(context);
5829
6010
  } catch (err) {
5830
6011
  const msg = err instanceof Error ? err.message : String(err);
5831
- log.warn(TAG19, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
6012
+ log.warn(TAG20, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
5832
6013
  return { result: "blocked", structured: { error: msg } };
5833
6014
  }
5834
6015
  }
5835
- var TAG19 = "gate-collectors";
6016
+ var TAG20 = "gate-collectors";
5836
6017
  var init_gate_collectors = __esm(() => {
5837
6018
  init_dist();
5838
6019
  init_artifact_judge();
6020
+ init_command_metric();
5839
6021
  init_log();
5840
6022
  init_verification();
5841
6023
  });
5842
6024
 
5843
6025
  // src/progress-tracker.ts
5844
- function truncate(str, max) {
6026
+ function truncate2(str, max) {
5845
6027
  return str.length > max ? `${str.slice(0, max - 3)}...` : str;
5846
6028
  }
5847
6029
 
@@ -5865,6 +6047,7 @@ class ProgressTracker {
5865
6047
  filesEdited = new Set;
5866
6048
  filesRead = new Set;
5867
6049
  lastCost = null;
6050
+ requestedModel = null;
5868
6051
  runEventSink = null;
5869
6052
  lastEmittedProgress = -1;
5870
6053
  lastAssistantText = "";
@@ -5882,6 +6065,9 @@ class ProgressTracker {
5882
6065
  setRunEventSink(sink) {
5883
6066
  this.runEventSink = sink;
5884
6067
  }
6068
+ setRequestedModel(model) {
6069
+ this.requestedModel = model;
6070
+ }
5885
6071
  attach(parser) {
5886
6072
  parser.on("tool_start", (name, input) => {
5887
6073
  this.onToolStart(name, input);
@@ -5952,7 +6138,7 @@ class ProgressTracker {
5952
6138
  }
5953
6139
  onToolStart(name, input) {
5954
6140
  this.toolCallCount++;
5955
- log.debug(TAG20, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
6141
+ log.debug(TAG21, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
5956
6142
  const filePath = this.extractString(input, "file_path");
5957
6143
  if (filePath) {
5958
6144
  if (EDIT_TOOLS.has(name)) {
@@ -6016,14 +6202,14 @@ class ProgressTracker {
6016
6202
  const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
6017
6203
  if (firstLine.length >= 10 && firstLine.length <= 200) {
6018
6204
  if (ACTION_PREFIX.test(firstLine)) {
6019
- this.lastAction = truncate(firstLine, MAX_TASK_LENGTH);
6205
+ this.lastAction = truncate2(firstLine, MAX_TASK_LENGTH);
6020
6206
  }
6021
6207
  }
6022
6208
  }
6023
6209
  transitionTo(newPhase) {
6024
6210
  if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
6025
6211
  return;
6026
- log.info(TAG20, `Phase: ${this.phase} → ${newPhase}`);
6212
+ log.info(TAG21, `Phase: ${this.phase} → ${newPhase}`);
6027
6213
  const previousPhase = this.phase;
6028
6214
  this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
6029
6215
  this.phase = newPhase;
@@ -6072,16 +6258,16 @@ class ProgressTracker {
6072
6258
  }
6073
6259
  case "Grep": {
6074
6260
  const pattern = this.extractString(input, "pattern");
6075
- return pattern ? `Searching for "${truncate(pattern, 40)}"` : "Searching code";
6261
+ return pattern ? `Searching for "${truncate2(pattern, 40)}"` : "Searching code";
6076
6262
  }
6077
6263
  case "Bash": {
6078
6264
  const cmd = this.extractString(input, "command");
6079
- return cmd ? `Running: ${truncate(cmd.split(`
6265
+ return cmd ? `Running: ${truncate2(cmd.split(`
6080
6266
  `)[0], 80)}` : "Running command";
6081
6267
  }
6082
6268
  case "Agent": {
6083
6269
  const desc = this.extractString(input, "description");
6084
- return desc ? `Sub-agent: ${truncate(desc, 60)}` : "Delegating to sub-agent";
6270
+ return desc ? `Sub-agent: ${truncate2(desc, 60)}` : "Delegating to sub-agent";
6085
6271
  }
6086
6272
  default: {
6087
6273
  if (name.startsWith("mcp__harmony__harmony_")) {
@@ -6125,12 +6311,12 @@ class ProgressTracker {
6125
6311
  }
6126
6312
  sendUpdate(currentTask) {
6127
6313
  this.lastUpdateAt = Date.now();
6128
- log.debug(TAG20, `Progress: ${this.progress}% — ${currentTask}`);
6314
+ log.debug(TAG21, `Progress: ${this.progress}% — ${currentTask}`);
6129
6315
  this.client.updateAgentProgress(this.cardId, {
6130
6316
  agentIdentifier: agentIdentifier(this.workerId),
6131
6317
  agentName: AGENT_NAME,
6132
6318
  status: "working",
6133
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
6319
+ currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
6134
6320
  progressPercent: this.progress,
6135
6321
  phase: this.phase,
6136
6322
  filesChanged: this.filesEdited.size,
@@ -6139,16 +6325,16 @@ class ProgressTracker {
6139
6325
  outputTokens: this.lastCost?.totalOutputTokens ?? 0,
6140
6326
  cacheCreationInputTokens: this.lastCost?.totalCacheCreationInputTokens ?? 0,
6141
6327
  cacheReadInputTokens: this.lastCost?.totalCacheReadInputTokens ?? 0,
6142
- modelName: this.lastCost?.modelName,
6328
+ modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
6143
6329
  numTurns: this.lastCost?.numTurns ?? 0
6144
6330
  }).catch((err) => {
6145
- log.warn(TAG20, `Failed to send progress update: ${err}`);
6331
+ log.warn(TAG21, `Failed to send progress update: ${err}`);
6146
6332
  });
6147
6333
  if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
6148
6334
  this.lastEmittedProgress = this.progress;
6149
6335
  this.runEventSink.recordProgress({
6150
6336
  progressPercent: this.progress,
6151
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
6337
+ currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
6152
6338
  phase: this.phase,
6153
6339
  filesChanged: this.filesEdited.size
6154
6340
  });
@@ -6161,7 +6347,7 @@ class ProgressTracker {
6161
6347
  this.heartbeatTimer = setTimeout(() => {
6162
6348
  if (!this.stopped) {
6163
6349
  const task = this.lastAction ? `Still working — ${this.lastAction}` : "Still working...";
6164
- this.sendUpdate(truncate(task, MAX_TASK_LENGTH));
6350
+ this.sendUpdate(truncate2(task, MAX_TASK_LENGTH));
6165
6351
  this.startHeartbeat();
6166
6352
  }
6167
6353
  }, HEARTBEAT_MS);
@@ -6173,7 +6359,7 @@ class ProgressTracker {
6173
6359
  return null;
6174
6360
  }
6175
6361
  }
6176
- var TAG20 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
6362
+ var TAG21 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
6177
6363
  var init_progress_tracker = __esm(() => {
6178
6364
  init_log();
6179
6365
  init_types2();
@@ -6329,7 +6515,7 @@ function parseReviewOutput(stdout) {
6329
6515
  try {
6330
6516
  const parsed = JSON.parse(raw);
6331
6517
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
6332
- log.debug(TAG21, "Parsed review output from fenced JSON block");
6518
+ log.debug(TAG22, "Parsed review output from fenced JSON block");
6333
6519
  return extractResult(parsed);
6334
6520
  }
6335
6521
  } catch {}
@@ -6355,21 +6541,21 @@ function parseReviewOutput(stdout) {
6355
6541
  try {
6356
6542
  const parsed = JSON.parse(candidates[i]);
6357
6543
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
6358
- log.debug(TAG21, "Parsed review output from raw JSON object");
6544
+ log.debug(TAG22, "Parsed review output from raw JSON object");
6359
6545
  return extractResult(parsed);
6360
6546
  }
6361
6547
  } catch {}
6362
6548
  }
6363
6549
  const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
6364
6550
  if (verdictMatch) {
6365
- log.warn(TAG21, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
6551
+ log.warn(TAG22, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
6366
6552
  return {
6367
6553
  verdict: verdictMatch[1].toLowerCase(),
6368
6554
  summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
6369
6555
  findings: []
6370
6556
  };
6371
6557
  }
6372
- log.warn(TAG21, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
6558
+ log.warn(TAG22, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
6373
6559
  return {
6374
6560
  verdict: "error",
6375
6561
  summary: stdout.slice(0, 500),
@@ -6402,7 +6588,7 @@ async function postReviewComment(client, card, commentType, body) {
6402
6588
  try {
6403
6589
  await client.addComment(card.id, body, { commentType });
6404
6590
  } catch (err) {
6405
- log.error(TAG21, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6591
+ log.error(TAG22, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6406
6592
  }
6407
6593
  }
6408
6594
  async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore, resolvedFromPrUrl) {
@@ -6416,11 +6602,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
6416
6602
  const currentCycle = getReviewCycle(freshDesc) + 1;
6417
6603
  const maxCycles = config.review.maxReviewCycles;
6418
6604
  if (result.verdict === "error") {
6419
- log.warn(TAG21, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
6605
+ log.warn(TAG22, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
6420
6606
  try {
6421
6607
  await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
6422
6608
  } catch (err) {
6423
- log.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
6609
+ log.warn(TAG22, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
6424
6610
  }
6425
6611
  if (config.review.postFindings) {
6426
6612
  const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
@@ -6463,7 +6649,7 @@ ${runLogTail}
6463
6649
  renameRemoteBranch(branchName, newRef, worktreePath);
6464
6650
  approvedBranch = newRef;
6465
6651
  } catch (err) {
6466
- log.warn(TAG21, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
6652
+ log.warn(TAG22, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
6467
6653
  }
6468
6654
  }
6469
6655
  if (config.review.createPR && approvedBranch) {
@@ -6484,14 +6670,14 @@ ${runLogTail}
6484
6670
  });
6485
6671
  }
6486
6672
  } catch (err) {
6487
- log.warn(TAG21, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
6673
+ log.warn(TAG22, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
6488
6674
  }
6489
6675
  }
6490
6676
  if (branchName) {
6491
6677
  try {
6492
6678
  await persistReviewedSha(client, card, worktreePath);
6493
6679
  } catch (err) {
6494
- log.warn(TAG21, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6680
+ log.warn(TAG22, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6495
6681
  }
6496
6682
  }
6497
6683
  if (config.review.postFindings) {
@@ -6513,7 +6699,7 @@ ${runLogTail}
6513
6699
  progressPercent: 100,
6514
6700
  ...buildTokenPayload(sessionStats)
6515
6701
  });
6516
- log.info(TAG21, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
6702
+ log.info(TAG22, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
6517
6703
  } else {
6518
6704
  const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
6519
6705
  const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
@@ -6521,7 +6707,7 @@ ${runLogTail}
6521
6707
  const linkedFindings = [...criticalFindings, ...majorFindings];
6522
6708
  const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
6523
6709
  if (currentCycle >= maxCycles) {
6524
- log.warn(TAG21, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
6710
+ log.warn(TAG22, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
6525
6711
  await moveCardToColumn(client, card, config.review.moveToColumn);
6526
6712
  const body = [
6527
6713
  "**Review — needs human review.**",
@@ -6561,7 +6747,7 @@ ${runLogTail}
6561
6747
  try {
6562
6748
  await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
6563
6749
  } catch (err) {
6564
- log.error(TAG21, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
6750
+ log.error(TAG22, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
6565
6751
  }
6566
6752
  }));
6567
6753
  if (linkedFindings.length > 0) {
@@ -6573,7 +6759,7 @@ ${runLogTail}
6573
6759
  try {
6574
6760
  await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
6575
6761
  } catch (err) {
6576
- log.error(TAG21, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
6762
+ log.error(TAG22, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
6577
6763
  }
6578
6764
  }));
6579
6765
  const baseDesc = stripReviewSummary(freshDesc);
@@ -6581,7 +6767,7 @@ ${runLogTail}
6581
6767
  try {
6582
6768
  await client.updateCard(card.id, { description: updatedDesc });
6583
6769
  } catch (err) {
6584
- log.error(TAG21, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
6770
+ log.error(TAG22, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
6585
6771
  }
6586
6772
  const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
6587
6773
  const body = [
@@ -6598,9 +6784,9 @@ ${runLogTail}
6598
6784
  if (config.planning.enabled && card.plan_id) {
6599
6785
  try {
6600
6786
  await client.updateCard(card.id, { needsPlanRefresh: true });
6601
- log.info(TAG21, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
6787
+ log.info(TAG22, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
6602
6788
  } catch (err) {
6603
- log.warn(TAG21, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6789
+ log.warn(TAG22, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6604
6790
  }
6605
6791
  }
6606
6792
  await moveCardToColumn(client, card, config.review.failColumn);
@@ -6614,10 +6800,10 @@ ${runLogTail}
6614
6800
  recoveryBranch
6615
6801
  });
6616
6802
  } catch (err) {
6617
- log.debug(TAG21, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
6803
+ log.debug(TAG22, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
6618
6804
  }
6619
6805
  if (recoveryBranch) {
6620
- log.info(TAG21, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
6806
+ log.info(TAG22, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
6621
6807
  }
6622
6808
  await client.endAgentSession(card.id, {
6623
6809
  status: "failed",
@@ -6626,7 +6812,7 @@ ${runLogTail}
6626
6812
  recoveryBranch,
6627
6813
  ...buildTokenPayload(sessionStats)
6628
6814
  });
6629
- log.info(TAG21, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
6815
+ log.info(TAG22, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
6630
6816
  }
6631
6817
  if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
6632
6818
  const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
@@ -6648,7 +6834,7 @@ ${runLogTail}
6648
6834
  cleanupWorktree(worktreePath, branchName);
6649
6835
  }
6650
6836
  }
6651
- var TAG21 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
6837
+ var TAG22 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
6652
6838
  **Review:`, RUN_LOG_TAIL_BYTES = 2048;
6653
6839
  var init_review_completion = __esm(() => {
6654
6840
  init_board_helpers();
@@ -6877,7 +7063,7 @@ class StateStore {
6877
7063
  const raw = readFileSync4(this.path, "utf-8");
6878
7064
  const parsed = JSON.parse(raw);
6879
7065
  if (parsed?.version !== SCHEMA_VERSION) {
6880
- log.warn(TAG22, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
7066
+ log.warn(TAG23, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
6881
7067
  return {
6882
7068
  version: SCHEMA_VERSION,
6883
7069
  daemonId: null,
@@ -6898,7 +7084,7 @@ class StateStore {
6898
7084
  daily: parsed.daily ?? []
6899
7085
  };
6900
7086
  } catch (err) {
6901
- log.error(TAG22, `failed to read state file: ${err instanceof Error ? err.message : err}`);
7087
+ log.error(TAG23, `failed to read state file: ${err instanceof Error ? err.message : err}`);
6902
7088
  return emptyState();
6903
7089
  }
6904
7090
  }
@@ -7087,7 +7273,7 @@ class StateStore {
7087
7273
  return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
7088
7274
  }
7089
7275
  }
7090
- var TAG22 = "state-store", SCHEMA_VERSION = 1;
7276
+ var TAG23 = "state-store", SCHEMA_VERSION = 1;
7091
7277
  var init_state_store = __esm(() => {
7092
7278
  init_log();
7093
7279
  });
@@ -7114,7 +7300,7 @@ function normalizeToolResultContent(raw) {
7114
7300
  return String(raw);
7115
7301
  }
7116
7302
  }
7117
- var TAG23 = "stream-parser", StreamParser;
7303
+ var TAG24 = "stream-parser", StreamParser;
7118
7304
  var init_stream_parser = __esm(() => {
7119
7305
  init_log();
7120
7306
  StreamParser = class StreamParser extends EventEmitter {
@@ -7162,14 +7348,14 @@ var init_stream_parser = __esm(() => {
7162
7348
  try {
7163
7349
  msg = JSON.parse(line);
7164
7350
  } catch {
7165
- log.debug(TAG23, `Non-JSON line: ${line.slice(0, 100)}`);
7351
+ log.debug(TAG24, `Non-JSON line: ${line.slice(0, 100)}`);
7166
7352
  return;
7167
7353
  }
7168
7354
  try {
7169
7355
  this.handleMessage(msg);
7170
7356
  } catch (err) {
7171
7357
  const errMsg = err instanceof Error ? err.message : String(err);
7172
- log.warn(TAG23, `Error handling stream event: ${errMsg}`);
7358
+ log.warn(TAG24, `Error handling stream event: ${errMsg}`);
7173
7359
  this.emit("parse_error", errMsg);
7174
7360
  }
7175
7361
  }
@@ -7255,7 +7441,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
7255
7441
  const msg2 = err instanceof Error ? err.message : String(err);
7256
7442
  if (i < attempts - 1) {
7257
7443
  const wait = backoffMs * 2 ** i;
7258
- log.warn(TAG24, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
7444
+ log.warn(TAG25, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
7259
7445
  await new Promise((r) => setTimeout(r, wait));
7260
7446
  }
7261
7447
  }
@@ -7278,10 +7464,10 @@ async function runTransition(client, card, plan, opts = {}) {
7278
7464
  if (opts.strictColumn) {
7279
7465
  throw new TransitionError("move", 1, msg);
7280
7466
  }
7281
- log.warn(TAG24, `#${shortId}: ${msg} — skipping move`);
7467
+ log.warn(TAG25, `#${shortId}: ${msg} — skipping move`);
7282
7468
  } else if (card.column_id !== target.id) {
7283
7469
  await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
7284
- log.info(TAG24, `#${shortId} → "${target.name}"`);
7470
+ log.info(TAG25, `#${shortId} → "${target.name}"`);
7285
7471
  card.column_id = target.id;
7286
7472
  moveLanded = true;
7287
7473
  } else {
@@ -7300,7 +7486,7 @@ async function runTransition(client, card, plan, opts = {}) {
7300
7486
  continue;
7301
7487
  await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
7302
7488
  existing.add(labelId);
7303
- log.info(TAG24, `#${shortId} +label "${name}"`);
7489
+ log.info(TAG25, `#${shortId} +label "${name}"`);
7304
7490
  }
7305
7491
  card.labelIds = Array.from(existing);
7306
7492
  }
@@ -7312,22 +7498,22 @@ async function runTransition(client, card, plan, opts = {}) {
7312
7498
  continue;
7313
7499
  await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
7314
7500
  existing.delete(match.id);
7315
- log.info(TAG24, `#${shortId} -label "${name}"`);
7501
+ log.info(TAG25, `#${shortId} -label "${name}"`);
7316
7502
  }
7317
7503
  card.labelIds = Array.from(existing);
7318
7504
  }
7319
7505
  if (plan.updateCard) {
7320
7506
  await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
7321
- log.info(TAG24, `#${shortId} updated`);
7507
+ log.info(TAG25, `#${shortId} updated`);
7322
7508
  }
7323
7509
  if (plan.endSession) {
7324
7510
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
7325
- log.info(TAG24, `#${shortId} session ended (${plan.endSession.status})`);
7511
+ log.info(TAG25, `#${shortId} session ended (${plan.endSession.status})`);
7326
7512
  }
7327
7513
  if (plan.assignAgent !== undefined) {
7328
7514
  const assignedAgentId = plan.assignAgent;
7329
7515
  await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
7330
- log.info(TAG24, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
7516
+ log.info(TAG25, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
7331
7517
  }
7332
7518
  if (opts.store && opts.runId) {
7333
7519
  try {
@@ -7340,11 +7526,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
7340
7526
  const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
7341
7527
  return result?.label?.id ?? null;
7342
7528
  } catch (err) {
7343
- log.warn(TAG24, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
7529
+ log.warn(TAG25, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
7344
7530
  return null;
7345
7531
  }
7346
7532
  }
7347
- var TAG24 = "transition", TransitionError;
7533
+ var TAG25 = "transition", TransitionError;
7348
7534
  var init_transitions = __esm(() => {
7349
7535
  init_log();
7350
7536
  TransitionError = class TransitionError extends Error {
@@ -7362,7 +7548,7 @@ var init_transitions = __esm(() => {
7362
7548
  });
7363
7549
 
7364
7550
  // src/review-worker.ts
7365
- import { execFileSync as execFileSync10 } from "node:child_process";
7551
+ import { execFileSync as execFileSync11 } from "node:child_process";
7366
7552
 
7367
7553
  class ReviewWorker {
7368
7554
  config;
@@ -7428,7 +7614,7 @@ class ReviewWorker {
7428
7614
  }
7429
7615
  }
7430
7616
  get tag() {
7431
- return `${TAG25}:${this.id}`;
7617
+ return `${TAG26}:${this.id}`;
7432
7618
  }
7433
7619
  get isIdle() {
7434
7620
  return this.state === "idle";
@@ -7480,7 +7666,7 @@ class ReviewWorker {
7480
7666
  costCents: 0,
7481
7667
  numTurns: 0
7482
7668
  });
7483
- const repoRoot = execFileSync10("git", ["rev-parse", "--show-toplevel"], {
7669
+ const repoRoot = execFileSync11("git", ["rev-parse", "--show-toplevel"], {
7484
7670
  encoding: "utf-8",
7485
7671
  timeout: 5000
7486
7672
  }).trim();
@@ -7499,7 +7685,8 @@ class ReviewWorker {
7499
7685
  agentId: this.identity.agentId,
7500
7686
  status: "working",
7501
7687
  currentTask: "Setting up review worktree",
7502
- progressPercent: 5
7688
+ progressPercent: 5,
7689
+ modelName: this.config.claude.reviewModel
7503
7690
  });
7504
7691
  this.sessionId = reviewSession && typeof reviewSession === "object" && "id" in reviewSession ? reviewSession.id ?? null : null;
7505
7692
  const labelPromise = addLabelByName(this.client, card, "agent", "#8b5cf6");
@@ -7548,7 +7735,7 @@ class ReviewWorker {
7548
7735
  return;
7549
7736
  let diff = "";
7550
7737
  try {
7551
- diff = execFileSync10("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
7738
+ diff = execFileSync11("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
7552
7739
  } catch {
7553
7740
  diff = "(unable to retrieve diff)";
7554
7741
  }
@@ -7600,6 +7787,7 @@ class ReviewWorker {
7600
7787
  this.cancel("timeout");
7601
7788
  }, this.config.review.maxTimeout);
7602
7789
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks);
7790
+ this.progressTracker.setRequestedModel(this.config.claude.reviewModel);
7603
7791
  const stdout = await this.spawnClaude(userPrompt, systemPrompt, this.progressTracker, card.short_id);
7604
7792
  this.lastSessionStats = this.progressTracker?.stats ?? null;
7605
7793
  this.progressTracker?.stop();
@@ -7891,7 +8079,7 @@ class ReviewWorker {
7891
8079
  this.lastSessionStats = null;
7892
8080
  }
7893
8081
  }
7894
- var TAG25 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
8082
+ var TAG26 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
7895
8083
  var init_review_worker = __esm(() => {
7896
8084
  init_dist();
7897
8085
  init_board_helpers();
@@ -7944,7 +8132,7 @@ class SleepGuard {
7944
8132
  if (!this.child.killed)
7945
8133
  this.child.kill("SIGTERM");
7946
8134
  this.child = null;
7947
- log.info(TAG26, "sleep assertion released");
8135
+ log.info(TAG27, "sleep assertion released");
7948
8136
  }
7949
8137
  }
7950
8138
  start() {
@@ -7959,7 +8147,7 @@ class SleepGuard {
7959
8147
  spawned = true;
7960
8148
  });
7961
8149
  child.on("error", (err) => {
7962
- log.warn(TAG26, `caffeinate unavailable: ${err.message}`);
8150
+ log.warn(TAG27, `caffeinate unavailable: ${err.message}`);
7963
8151
  if (this.child === child)
7964
8152
  this.child = null;
7965
8153
  });
@@ -7972,13 +8160,13 @@ class SleepGuard {
7972
8160
  });
7973
8161
  child.unref();
7974
8162
  this.child = child;
7975
- log.info(TAG26, "sleep assertion acquired (caffeinate -i)");
8163
+ log.info(TAG27, "sleep assertion acquired (caffeinate -i)");
7976
8164
  } catch (err) {
7977
- log.warn(TAG26, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
8165
+ log.warn(TAG27, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
7978
8166
  }
7979
8167
  }
7980
8168
  }
7981
- var TAG26 = "sleep-guard";
8169
+ var TAG27 = "sleep-guard";
7982
8170
  var init_sleep_guard = __esm(() => {
7983
8171
  init_log();
7984
8172
  });
@@ -7989,7 +8177,7 @@ async function fetchBlocksLinks(client, cardId) {
7989
8177
  const { links } = await client.getCardLinks(cardId);
7990
8178
  return links.filter((l) => l.link_type === "blocks");
7991
8179
  } catch (err) {
7992
- log.warn(TAG27, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
8180
+ log.warn(TAG28, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
7993
8181
  return null;
7994
8182
  }
7995
8183
  }
@@ -8021,27 +8209,27 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
8021
8209
  const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
8022
8210
  if (successors.length === 0)
8023
8211
  return;
8024
- log.info(TAG27, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
8212
+ log.info(TAG28, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
8025
8213
  for (const link of successors) {
8026
8214
  const successorId = link.target_card.id;
8027
8215
  try {
8028
8216
  const { card } = await deps.client.getCard(successorId);
8029
8217
  if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
8030
- log.info(TAG27, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
8218
+ log.info(TAG28, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
8031
8219
  await deps.client.updateCard(successorId, {
8032
8220
  assignedAgentId: deps.agentId
8033
8221
  });
8034
8222
  } else {
8035
- log.debug(TAG27, `successor #${card.short_id} assigned to different entity — skipping`);
8223
+ log.debug(TAG28, `successor #${card.short_id} assigned to different entity — skipping`);
8036
8224
  continue;
8037
8225
  }
8038
8226
  await deps.enqueue(successorId);
8039
8227
  } catch (err) {
8040
- log.warn(TAG27, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
8228
+ log.warn(TAG28, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
8041
8229
  }
8042
8230
  }
8043
8231
  }
8044
- var TAG27 = "unblock";
8232
+ var TAG28 = "unblock";
8045
8233
  var init_unblock = __esm(() => {
8046
8234
  init_log();
8047
8235
  });
@@ -8196,7 +8384,7 @@ class CliAgentRunner {
8196
8384
  events: batch
8197
8385
  });
8198
8386
  } catch (err) {
8199
- log.warn(TAG28, `Failed to flush run events: ${err}`);
8387
+ log.warn(TAG29, `Failed to flush run events: ${err}`);
8200
8388
  this.buffer.unshift(...batch);
8201
8389
  if (this.buffer.length > MAX_BUFFER) {
8202
8390
  this.buffer.length = MAX_BUFFER;
@@ -8233,7 +8421,7 @@ function mapCost(cost) {
8233
8421
  durationMs: cost.durationMs
8234
8422
  };
8235
8423
  }
8236
- var TAG28 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
8424
+ var TAG29 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
8237
8425
  var init_cli_agent_runner = __esm(() => {
8238
8426
  init_log();
8239
8427
  });
@@ -8266,11 +8454,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
8266
8454
  Do NOT push to main. All your work stays on \`${branchName}\`.
8267
8455
  The daemon owns the run lifecycle: once your work is committed it ends the agent session, pushes the branch, and moves the card to Review for you. Do NOT call harmony_end_agent_session, do NOT start a new session, and do NOT move the card or change its column yourself. If the skill driving this work tells you to move the card or end the session as a final step, SKIP it — it is handled for you (those tools are disabled for this run). Finish the implementation, commit, and stop.`
8268
8456
  });
8269
- log.info(TAG29, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
8457
+ log.info(TAG30, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
8270
8458
  return result.prompt + pastEpisodesSection;
8271
8459
  } catch (err) {
8272
8460
  const msg = err instanceof Error ? err.message : String(err);
8273
- log.warn(TAG29, `Failed to generate prompt via API, using fallback: ${msg}`);
8461
+ log.warn(TAG30, `Failed to generate prompt via API, using fallback: ${msg}`);
8274
8462
  const commentsSection = await renderCommentsSection(client, card.id);
8275
8463
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
8276
8464
  }
@@ -8288,7 +8476,7 @@ async function renderCommentsSection(client, cardId) {
8288
8476
 
8289
8477
  ${section}` : "";
8290
8478
  } catch (err) {
8291
- log.warn(TAG29, "comment-thread fetch failed", {
8479
+ log.warn(TAG30, "comment-thread fetch failed", {
8292
8480
  event: "comment_fetch_failed",
8293
8481
  error: err instanceof Error ? err.message : String(err)
8294
8482
  });
@@ -8338,7 +8526,7 @@ ${description}`.trim();
8338
8526
  ## Similar past tasks
8339
8527
  ${bullets}`;
8340
8528
  } catch (err) {
8341
- log.warn(TAG29, "past-episodes recall failed", {
8529
+ log.warn(TAG30, "past-episodes recall failed", {
8342
8530
  event: "episode_recall_failed",
8343
8531
  error: err instanceof Error ? err.message : String(err)
8344
8532
  });
@@ -8379,7 +8567,7 @@ ${subtaskStr}
8379
8567
  You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
8380
8568
  Do NOT push to main. All your work stays on \`${branchName}\`.`;
8381
8569
  }
8382
- var TAG29 = "prompt";
8570
+ var TAG30 = "prompt";
8383
8571
  var init_prompt = __esm(() => {
8384
8572
  init_dist();
8385
8573
  init_log();
@@ -8402,7 +8590,7 @@ async function resolveStageColumnName(client, card, stage) {
8402
8590
  const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
8403
8591
  return match ? match.name : null;
8404
8592
  } catch (err) {
8405
- log.warn(TAG30, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8593
+ log.warn(TAG31, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8406
8594
  return null;
8407
8595
  }
8408
8596
  }
@@ -8446,7 +8634,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8446
8634
  evidence,
8447
8635
  summary
8448
8636
  });
8449
- log.info(TAG30, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
8637
+ log.info(TAG31, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
8450
8638
  if (decision === "exit") {
8451
8639
  await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
8452
8640
  deps.sink?.recordLoopCompleted?.({
@@ -8488,7 +8676,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8488
8676
  await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
8489
8677
  keepAttempts: true
8490
8678
  });
8491
- log.info(TAG30, `#${card.short_id} LoopExhausted: ${reason}`);
8679
+ log.info(TAG31, `#${card.short_id} LoopExhausted: ${reason}`);
8492
8680
  return { kind: "held_gate_unmet", reason };
8493
8681
  }
8494
8682
  await deps.stateStore.decrementAttempt(card.id).catch(() => {});
@@ -8502,7 +8690,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8502
8690
  addLabels: [{ name: AGENT_LABEL }],
8503
8691
  ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
8504
8692
  }, { store: deps.stateStore, runId: deps.runId });
8505
- log.info(TAG30, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
8693
+ log.info(TAG31, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
8506
8694
  return { kind: "requeued_gate_unmet", toColumn };
8507
8695
  }
8508
8696
  async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
@@ -8521,7 +8709,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
8521
8709
  });
8522
8710
  await deps.client.addComment(card.id, body, { commentType: "decision" });
8523
8711
  } catch (err) {
8524
- log.warn(TAG30, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8712
+ log.warn(TAG31, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8525
8713
  }
8526
8714
  }
8527
8715
  async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
@@ -8552,7 +8740,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
8552
8740
  reason: "Playbook complete — final stage gate passed."
8553
8741
  });
8554
8742
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
8555
- log.info(TAG30, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
8743
+ log.info(TAG31, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
8556
8744
  return { kind: "completed_terminal" };
8557
8745
  }
8558
8746
  if (next.kind === "out_of_range") {
@@ -8584,7 +8772,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
8584
8772
  ...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
8585
8773
  }, { store: deps.stateStore, runId: deps.runId });
8586
8774
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
8587
- log.info(TAG30, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
8775
+ log.info(TAG31, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
8588
8776
  return { kind: "advanced", toStageId: next.stage.id, toColumn };
8589
8777
  }
8590
8778
  async function handleGateUnmet(card, stage, summary, deps) {
@@ -8603,7 +8791,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
8603
8791
  await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
8604
8792
  keepAttempts: true
8605
8793
  });
8606
- log.info(TAG30, `#${card.short_id} GateUnmetExhausted: ${reason}`);
8794
+ log.info(TAG31, `#${card.short_id} GateUnmetExhausted: ${reason}`);
8607
8795
  return { kind: "held_gate_unmet", reason };
8608
8796
  }
8609
8797
  const toColumn = await resolveStageColumnName(deps.client, card, stage) ?? deps.fallbackColumn;
@@ -8615,7 +8803,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
8615
8803
  addLabels: [{ name: AGENT_LABEL }],
8616
8804
  ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
8617
8805
  }, { store: deps.stateStore, runId: deps.runId });
8618
- log.info(TAG30, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
8806
+ log.info(TAG31, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
8619
8807
  return { kind: "requeued_gate_unmet", toColumn };
8620
8808
  }
8621
8809
  async function holdForHuman(client, card, reason, runId, stateStore, opts = {}) {
@@ -8635,10 +8823,10 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
8635
8823
  }
8636
8824
  }, { store: stateStore, runId });
8637
8825
  } catch (err) {
8638
- log.warn(TAG30, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8826
+ log.warn(TAG31, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8639
8827
  }
8640
8828
  }
8641
- var TAG30 = "stage-advance", AGENT_LABEL = "agent";
8829
+ var TAG31 = "stage-advance", AGENT_LABEL = "agent";
8642
8830
  var init_stage_advance = __esm(() => {
8643
8831
  init_dist();
8644
8832
  init_log();
@@ -8785,7 +8973,7 @@ class Worker {
8785
8973
  }
8786
8974
  }
8787
8975
  get tag() {
8788
- return `${TAG31}:${this.id}`;
8976
+ return `${TAG32}:${this.id}`;
8789
8977
  }
8790
8978
  get isIdle() {
8791
8979
  return this.state === "idle";
@@ -8841,24 +9029,26 @@ class Worker {
8841
9029
  costCents: 0,
8842
9030
  numTurns: 0
8843
9031
  });
9032
+ const implementModel = this.selectImplementModel(card);
8844
9033
  const { session } = await this.client.startAgentSession(card.id, {
8845
9034
  agentIdentifier: agentIdentifier(this.id),
8846
9035
  agentName: AGENT_NAME,
8847
9036
  agentId: this.identity.agentId,
8848
9037
  status: "working",
8849
9038
  currentTask: "Setting up worktree",
8850
- progressPercent: 5
9039
+ progressPercent: 5,
9040
+ modelName: implementModel
8851
9041
  });
8852
9042
  const sid = session && typeof session === "object" && "id" in session ? session.id : null;
8853
9043
  if (!sid) {
8854
- log.warn(TAG31, "startAgentSession returned no session id");
9044
+ log.warn(TAG32, "startAgentSession returned no session id");
8855
9045
  }
8856
9046
  this.sessionId = sid;
8857
9047
  if (this.sessionId) {
8858
9048
  this.cliRunner = new CliAgentRunner(this.client, card.id, this.sessionId);
8859
9049
  this.cliRunner.recordRunStarted({
8860
9050
  runner: this.config.runner,
8861
- model: this.config.claude.model
9051
+ model: implementModel
8862
9052
  });
8863
9053
  }
8864
9054
  await this.recordPhase("preparing");
@@ -8947,7 +9137,7 @@ ${basePrompt}`;
8947
9137
  }, this.config.maxTimeout);
8948
9138
  this.activeRunSpawnOpts = computeRunSpawnGating(stageCtx.kind === "run" ? stageCtx.allowedTools : null);
8949
9139
  await this.spawnClaude(prompt, card, subtasks, {
8950
- model: this.selectImplementModel(card),
9140
+ model: implementModel,
8951
9141
  ...this.activeRunSpawnOpts ?? {}
8952
9142
  });
8953
9143
  if (this.aborted)
@@ -9308,6 +9498,10 @@ ${basePrompt}`;
9308
9498
  artifactType: stage.artifact_type
9309
9499
  },
9310
9500
  checklist: { subtasks, cardDone: card.done ?? false },
9501
+ command: {
9502
+ worktreePath,
9503
+ metrics: this.config.playbooks.metrics ?? {}
9504
+ },
9311
9505
  ...review ? { review } : {}
9312
9506
  });
9313
9507
  const context = {
@@ -9718,6 +9912,7 @@ ${basePrompt}`;
9718
9912
  });
9719
9913
  const parser = new StreamParser;
9720
9914
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
9915
+ this.progressTracker.setRequestedModel(model);
9721
9916
  this.progressTracker.attach(parser);
9722
9917
  this.cliRunner?.attach(parser);
9723
9918
  if (this.cliRunner) {
@@ -9798,6 +9993,7 @@ ${basePrompt}`;
9798
9993
  `);
9799
9994
  }
9800
9995
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
9996
+ this.progressTracker.setRequestedModel(model);
9801
9997
  if (this.cliRunner) {
9802
9998
  this.progressTracker.setRunEventSink(this.cliRunner);
9803
9999
  }
@@ -9906,7 +10102,7 @@ ${basePrompt}`;
9906
10102
  this.runTurns = 0;
9907
10103
  }
9908
10104
  }
9909
- var TAG31 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
10105
+ var TAG32 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
9910
10106
  var init_worker = __esm(() => {
9911
10107
  init_dist();
9912
10108
  init_board_helpers();
@@ -9984,41 +10180,41 @@ class Pool {
9984
10180
  }
9985
10181
  async enqueue(card, column, labels, subtasks, mode = "implement") {
9986
10182
  if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
9987
- log.debug(TAG32, `Card ${card.id} already queued, active, or reserved, skipping`);
10183
+ log.debug(TAG33, `Card ${card.id} already queued, active, or reserved, skipping`);
9988
10184
  return;
9989
10185
  }
9990
10186
  this.reservations.add(card.id);
9991
10187
  try {
9992
10188
  if (mode === "implement") {
9993
10189
  if (this.authPaused) {
9994
- log.debug(TAG32, `#${card.short_id} held — agent paused (auth error)`);
10190
+ log.debug(TAG33, `#${card.short_id} held — agent paused (auth error)`);
9995
10191
  await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
9996
10192
  return;
9997
10193
  }
9998
10194
  const cooldownMs = this.apiCooldownRemainingMs();
9999
10195
  if (cooldownMs > 0) {
10000
- log.debug(TAG32, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
10196
+ log.debug(TAG33, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
10001
10197
  await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
10002
10198
  return;
10003
10199
  }
10004
10200
  const decision = this.budget.check(card.id);
10005
10201
  if (!decision.allow) {
10006
10202
  if (decision.reason === "daily_budget") {
10007
- log.warn(TAG32, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
10203
+ log.warn(TAG33, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
10008
10204
  await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
10009
10205
  } else {
10010
- log.debug(TAG32, `#${card.short_id} gave up: ${decision.detail}`);
10206
+ log.debug(TAG33, `#${card.short_id} gave up: ${decision.detail}`);
10011
10207
  }
10012
10208
  return;
10013
10209
  }
10014
10210
  const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
10015
10211
  if (blockers === null) {
10016
- log.warn(TAG32, `#${card.short_id} blocker check failed — deferring to next tick`);
10212
+ log.warn(TAG33, `#${card.short_id} blocker check failed — deferring to next tick`);
10017
10213
  return;
10018
10214
  }
10019
10215
  if (blockers.length > 0) {
10020
10216
  const list = blockers.map((b) => `#${b.shortId}`).join(", ");
10021
- log.info(TAG32, `#${card.short_id} blocked by ${list} — waiting`);
10217
+ log.info(TAG33, `#${card.short_id} blocked by ${list} — waiting`);
10022
10218
  await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
10023
10219
  return;
10024
10220
  }
@@ -10050,7 +10246,7 @@ class Pool {
10050
10246
  });
10051
10247
  this.lastWaitingEmit.set(cardId, currentTask);
10052
10248
  } catch (err) {
10053
- log.debug(TAG32, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
10249
+ log.debug(TAG33, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
10054
10250
  }
10055
10251
  }
10056
10252
  noteApiError(err) {
@@ -10058,7 +10254,7 @@ class Pool {
10058
10254
  return;
10059
10255
  if (err.kind === "auth") {
10060
10256
  if (!this.authPaused) {
10061
- log.error(TAG32, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
10257
+ log.error(TAG33, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
10062
10258
  }
10063
10259
  this.authPaused = true;
10064
10260
  return;
@@ -10067,7 +10263,7 @@ class Pool {
10067
10263
  const until = Date.now() + cooldownMs;
10068
10264
  if (until > this.apiCooldownUntil) {
10069
10265
  this.apiCooldownUntil = until;
10070
- log.warn(TAG32, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
10266
+ log.warn(TAG33, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
10071
10267
  }
10072
10268
  }
10073
10269
  apiCooldownRemainingMs() {
@@ -10081,13 +10277,13 @@ class Pool {
10081
10277
  const removed = queue.remove(cardId);
10082
10278
  if (removed) {
10083
10279
  this.cardDataCache.delete(cardId);
10084
- log.info(TAG32, `Removed #${removed.shortId} from ${removed.mode} queue`);
10280
+ log.info(TAG33, `Removed #${removed.shortId} from ${removed.mode} queue`);
10085
10281
  return;
10086
10282
  }
10087
10283
  }
10088
10284
  const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
10089
10285
  if (worker) {
10090
- log.info(TAG32, `Cancelling worker ${worker.id} for card ${cardId}`);
10286
+ log.info(TAG33, `Cancelling worker ${worker.id} for card ${cardId}`);
10091
10287
  await worker.cancel("unassigned");
10092
10288
  }
10093
10289
  }
@@ -10120,10 +10316,10 @@ class Pool {
10120
10316
  async handleAgentCommand(cardId, command) {
10121
10317
  const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
10122
10318
  if (!worker) {
10123
- log.debug(TAG32, `No active worker for card ${cardId}, ignoring ${command}`);
10319
+ log.debug(TAG33, `No active worker for card ${cardId}, ignoring ${command}`);
10124
10320
  return;
10125
10321
  }
10126
- log.info(TAG32, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
10322
+ log.info(TAG33, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
10127
10323
  switch (command) {
10128
10324
  case "pause":
10129
10325
  await worker.pause();
@@ -10171,7 +10367,7 @@ class Pool {
10171
10367
  };
10172
10368
  }
10173
10369
  async shutdown() {
10174
- log.info(TAG32, "Shutting down pool...");
10370
+ log.info(TAG33, "Shutting down pool...");
10175
10371
  this.shuttingDown = true;
10176
10372
  const active = [
10177
10373
  ...this.implWorkers.filter((w) => w.isActive),
@@ -10179,7 +10375,7 @@ class Pool {
10179
10375
  ];
10180
10376
  await Promise.all(active.map((w) => w.cancel("shutdown")));
10181
10377
  this.sleepGuard.stop();
10182
- log.info(TAG32, "Pool shutdown complete");
10378
+ log.info(TAG33, "Pool shutdown complete");
10183
10379
  }
10184
10380
  reservations = new Set;
10185
10381
  cardDataCache = new Map;
@@ -10188,7 +10384,7 @@ class Pool {
10188
10384
  return false;
10189
10385
  const idle = workers.find((w) => w.isIdle);
10190
10386
  if (!idle) {
10191
- log.debug(TAG32, `No idle ${label} workers (queue: ${queue.length})`);
10387
+ log.debug(TAG33, `No idle ${label} workers (queue: ${queue.length})`);
10192
10388
  return false;
10193
10389
  }
10194
10390
  const next = queue.dequeue();
@@ -10196,18 +10392,18 @@ class Pool {
10196
10392
  return false;
10197
10393
  const data = this.cardDataCache.get(next.cardId);
10198
10394
  if (!data) {
10199
- log.warn(TAG32, `No cached data for card ${next.cardId}, skipping`);
10395
+ log.warn(TAG33, `No cached data for card ${next.cardId}, skipping`);
10200
10396
  return false;
10201
10397
  }
10202
10398
  this.cardDataCache.delete(next.cardId);
10203
10399
  this.lastWaitingEmit.delete(next.cardId);
10204
- log.info(TAG32, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
10400
+ log.info(TAG33, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
10205
10401
  this.sleepGuard.acquire();
10206
10402
  idle.run(data.card, data.column, data.labels, data.subtasks);
10207
10403
  return true;
10208
10404
  }
10209
10405
  }
10210
- var TAG32 = "pool";
10406
+ var TAG33 = "pool";
10211
10407
  var init_pool = __esm(() => {
10212
10408
  init_error_classifier();
10213
10409
  init_log();
@@ -10249,7 +10445,7 @@ function load(path) {
10249
10445
  return parsed;
10250
10446
  return {};
10251
10447
  } catch (err) {
10252
- log.warn(TAG33, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
10448
+ log.warn(TAG34, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
10253
10449
  return {};
10254
10450
  }
10255
10451
  }
@@ -10267,7 +10463,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
10267
10463
  registry[projectId] = { ...entry, updatedAt: Date.now() };
10268
10464
  save(path, registry);
10269
10465
  } catch (err) {
10270
- log.warn(TAG33, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10466
+ log.warn(TAG34, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10271
10467
  }
10272
10468
  }
10273
10469
  function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
@@ -10283,10 +10479,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
10283
10479
  delete registry[projectId];
10284
10480
  save(path, registry);
10285
10481
  } catch (err) {
10286
- log.warn(TAG33, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10482
+ log.warn(TAG34, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10287
10483
  }
10288
10484
  }
10289
- var TAG33 = "port-registry";
10485
+ var TAG34 = "port-registry";
10290
10486
  var init_port_registry = __esm(() => {
10291
10487
  init_log();
10292
10488
  });
@@ -10307,7 +10503,7 @@ async function fetchCardSafely(client, cardId) {
10307
10503
  const { card } = await client.getCard(cardId);
10308
10504
  return card;
10309
10505
  } catch (err) {
10310
- log.warn(TAG34, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
10506
+ log.warn(TAG35, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
10311
10507
  return null;
10312
10508
  }
10313
10509
  }
@@ -10317,7 +10513,7 @@ async function recoverOrphans(store, client, config) {
10317
10513
  return [];
10318
10514
  }
10319
10515
  const outcomes = [];
10320
- log.info(TAG34, `recovering ${active.length} orphan run(s) from prior daemon`);
10516
+ log.info(TAG35, `recovering ${active.length} orphan run(s) from prior daemon`);
10321
10517
  for (const run of active) {
10322
10518
  const outcome = {
10323
10519
  runId: run.runId,
@@ -10329,11 +10525,11 @@ async function recoverOrphans(store, client, config) {
10329
10525
  };
10330
10526
  outcomes.push(outcome);
10331
10527
  if (isProcessAlive(run.daemonPid, process.pid)) {
10332
- log.warn(TAG34, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
10528
+ log.warn(TAG35, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
10333
10529
  outcome.actions.push("skipped: daemon pid still alive");
10334
10530
  continue;
10335
10531
  }
10336
- log.info(TAG34, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
10532
+ log.info(TAG35, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
10337
10533
  await recoverRun(run, store, client, config, outcome, {
10338
10534
  rollbackAttempt: true
10339
10535
  });
@@ -10353,7 +10549,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
10353
10549
  } catch (err) {
10354
10550
  const msg = err instanceof Error ? err.message : String(err);
10355
10551
  outcome.errors.push(`endAgentSession: ${msg}`);
10356
- log.warn(TAG34, `endAgentSession failed for ${run.cardId}: ${msg}`);
10552
+ log.warn(TAG35, `endAgentSession failed for ${run.cardId}: ${msg}`);
10357
10553
  }
10358
10554
  const card = await fetchCardSafely(client, run.cardId);
10359
10555
  if (card) {
@@ -10405,9 +10601,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
10405
10601
  outcome.errors.push(`decrementAttempt: ${msg}`);
10406
10602
  }
10407
10603
  }
10408
- log.info(TAG34, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
10604
+ log.info(TAG35, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
10409
10605
  }
10410
- var TAG34 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
10606
+ var TAG35 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
10411
10607
  var init_recovery = __esm(() => {
10412
10608
  init_board_helpers();
10413
10609
  init_log();
@@ -10418,14 +10614,14 @@ var init_recovery = __esm(() => {
10418
10614
  async function claimReviewCard(client, cardId, agentId) {
10419
10615
  try {
10420
10616
  const { claimed } = await client.claimCard(cardId, agentId);
10421
- log.debug(TAG35, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
10617
+ log.debug(TAG36, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
10422
10618
  return claimed;
10423
10619
  } catch (err) {
10424
- log.error(TAG35, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
10620
+ log.error(TAG36, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
10425
10621
  return false;
10426
10622
  }
10427
10623
  }
10428
- var TAG35 = "claim";
10624
+ var TAG36 = "claim";
10429
10625
  var init_claim = __esm(() => {
10430
10626
  init_log();
10431
10627
  });
@@ -10478,22 +10674,22 @@ async function reclaimPreReviewStrands(opts) {
10478
10674
  continue;
10479
10675
  const won = await claimReviewCard(client, card.id, agentId);
10480
10676
  if (!won) {
10481
- log.debug(TAG36, `#${card.short_id} — lost the review claim race, skipping`);
10677
+ log.debug(TAG37, `#${card.short_id} — lost the review claim race, skipping`);
10482
10678
  continue;
10483
10679
  }
10484
- log.warn(TAG36, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
10680
+ log.warn(TAG37, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
10485
10681
  reclaimed.push(card.id);
10486
10682
  if (opts.onClaimed) {
10487
10683
  try {
10488
10684
  await opts.onClaimed(card);
10489
10685
  } catch (err) {
10490
- log.error(TAG36, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
10686
+ log.error(TAG37, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
10491
10687
  }
10492
10688
  }
10493
10689
  }
10494
10690
  return reclaimed;
10495
10691
  }
10496
- var TAG36 = "strand-recovery";
10692
+ var TAG37 = "strand-recovery";
10497
10693
  var init_strand_recovery = __esm(() => {
10498
10694
  init_board_helpers();
10499
10695
  init_claim();
@@ -10545,7 +10741,7 @@ class Reconciler {
10545
10741
  clearInterval(this.timer);
10546
10742
  this.timer = null;
10547
10743
  }
10548
- log.info(TAG37, "Heartbeat stopped");
10744
+ log.info(TAG38, "Heartbeat stopped");
10549
10745
  }
10550
10746
  async recoverStaleRuns() {
10551
10747
  if (!this.stateStore || !this.agentConfig)
@@ -10562,7 +10758,7 @@ class Reconciler {
10562
10758
  if (!daemonDead && !(heartbeatStale && ourZombie))
10563
10759
  continue;
10564
10760
  const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
10565
- log.warn(TAG37, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
10761
+ log.warn(TAG38, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
10566
10762
  await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
10567
10763
  runId: run.runId,
10568
10764
  cardId: run.cardId,
@@ -10589,11 +10785,11 @@ class Reconciler {
10589
10785
  const stalledAt = Date.parse(card.updated_at ?? "");
10590
10786
  if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
10591
10787
  continue;
10592
- log.warn(TAG37, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
10788
+ log.warn(TAG38, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
10593
10789
  try {
10594
10790
  await this.client.moveCard(card.id, pickupCol.id);
10595
10791
  } catch (err) {
10596
- log.error(TAG37, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10792
+ log.error(TAG38, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10597
10793
  }
10598
10794
  }
10599
10795
  }
@@ -10625,7 +10821,7 @@ class Reconciler {
10625
10821
  return;
10626
10822
  const cardLabels = resolveCardLabels(card, labelMap);
10627
10823
  const subtasks = card.subtasks ?? [];
10628
- log.info(TAG37, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
10824
+ log.info(TAG38, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
10629
10825
  await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
10630
10826
  }
10631
10827
  });
@@ -10649,11 +10845,11 @@ class Reconciler {
10649
10845
  const parkedAt = Date.parse(card.updated_at ?? "");
10650
10846
  if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
10651
10847
  continue;
10652
- log.warn(TAG37, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
10848
+ log.warn(TAG38, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
10653
10849
  try {
10654
10850
  await this.client.moveCard(card.id, pickupCol.id);
10655
10851
  } catch (err) {
10656
- log.error(TAG37, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10852
+ log.error(TAG38, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10657
10853
  }
10658
10854
  }
10659
10855
  }
@@ -10673,7 +10869,8 @@ class Reconciler {
10673
10869
  reviewColumns: this.reviewColumns,
10674
10870
  playbooks: this.agentConfig?.playbooks ?? {
10675
10871
  enabled: false,
10676
- humanStageColumns: []
10872
+ humanStageColumns: [],
10873
+ metrics: {}
10677
10874
  }
10678
10875
  };
10679
10876
  const assignedCards = cards.filter((c) => {
@@ -10696,21 +10893,21 @@ class Reconciler {
10696
10893
  const subtasks = card.subtasks ?? [];
10697
10894
  const mode = route.mode;
10698
10895
  if (route.stage) {
10699
- log.info(TAG37, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
10896
+ log.info(TAG38, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
10700
10897
  }
10701
10898
  if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
10702
- log.debug(TAG37, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
10899
+ log.debug(TAG38, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
10703
10900
  continue;
10704
10901
  }
10705
10902
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
10706
- log.debug(TAG37, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
10903
+ log.debug(TAG38, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
10707
10904
  continue;
10708
10905
  }
10709
10906
  if (mode === "review" && !qualifiesForAutoReview(card.description)) {
10710
- log.debug(TAG37, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
10907
+ log.debug(TAG38, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
10711
10908
  continue;
10712
10909
  }
10713
- log.info(TAG37, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
10910
+ log.info(TAG38, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
10714
10911
  await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
10715
10912
  }
10716
10913
  }
@@ -10721,18 +10918,18 @@ class Reconciler {
10721
10918
  await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
10722
10919
  for (const knownId of knownCardIds) {
10723
10920
  if (!allAgentCardIds.has(knownId)) {
10724
- log.info(TAG37, `Missed unassign: ${knownId} — removing`);
10921
+ log.info(TAG38, `Missed unassign: ${knownId} — removing`);
10725
10922
  await this.pool.removeCard(knownId);
10726
10923
  }
10727
10924
  }
10728
10925
  await this.releaseStalledApprovals(cards, columns, knownCardIds);
10729
- log.debug(TAG37, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
10926
+ log.debug(TAG38, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
10730
10927
  } catch (err) {
10731
- log.error(TAG37, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
10928
+ log.error(TAG38, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
10732
10929
  }
10733
10930
  }
10734
10931
  }
10735
- var TAG37 = "reconcile";
10932
+ var TAG38 = "reconcile";
10736
10933
  var init_reconcile = __esm(() => {
10737
10934
  init_board_helpers();
10738
10935
  init_git_pr();
@@ -10772,7 +10969,7 @@ function prettyBanner(config, version) {
10772
10969
  checks.push({ kind: "ok", message });
10773
10970
  },
10774
10971
  warn(message) {
10775
- log.warn(TAG38, message);
10972
+ log.warn(TAG39, message);
10776
10973
  checks.push({ kind: "warn", message: message.split(`
10777
10974
  `, 1)[0] });
10778
10975
  },
@@ -10797,25 +10994,25 @@ function prettyBanner(config, version) {
10797
10994
  };
10798
10995
  }
10799
10996
  function jsonBanner(config, version) {
10800
- log.info(TAG38, `Harmony Agent Daemon v${version} starting...`);
10801
- log.info(TAG38, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
10997
+ log.info(TAG39, `Harmony Agent Daemon v${version} starting...`);
10998
+ log.info(TAG39, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
10802
10999
  if (config.agent.review.enabled) {
10803
- log.info(TAG38, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
11000
+ log.info(TAG39, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
10804
11001
  }
10805
11002
  let failed = false;
10806
11003
  return {
10807
11004
  setProjectName(_name) {},
10808
11005
  setGitProvider(provider) {
10809
- log.info(TAG38, `Git provider: ${provider}`);
11006
+ log.info(TAG39, `Git provider: ${provider}`);
10810
11007
  },
10811
11008
  setHttpPort(port) {
10812
- log.info(TAG38, `HTTP server on port ${port}`);
11009
+ log.info(TAG39, `HTTP server on port ${port}`);
10813
11010
  },
10814
11011
  check(message) {
10815
- log.info(TAG38, message);
11012
+ log.info(TAG39, message);
10816
11013
  },
10817
11014
  warn(message) {
10818
- log.warn(TAG38, message);
11015
+ log.warn(TAG39, message);
10819
11016
  },
10820
11017
  fail() {
10821
11018
  failed = true;
@@ -10823,7 +11020,7 @@ function jsonBanner(config, version) {
10823
11020
  async ready(message) {
10824
11021
  if (failed)
10825
11022
  return;
10826
- log.info(TAG38, message);
11023
+ log.info(TAG39, message);
10827
11024
  }
10828
11025
  };
10829
11026
  }
@@ -10904,7 +11101,7 @@ function cyan(s) {
10904
11101
  function yellow(s) {
10905
11102
  return `${ANSI.yellow}${s}${ANSI.reset}`;
10906
11103
  }
10907
- var TAG38 = "daemon", RULE_WIDTH = 70, ANSI;
11104
+ var TAG39 = "daemon", RULE_WIDTH = 70, ANSI;
10908
11105
  var init_startup_banner = __esm(() => {
10909
11106
  init_log();
10910
11107
  ANSI = {
@@ -11055,13 +11252,13 @@ class Watcher {
11055
11252
  }
11056
11253
  async start() {
11057
11254
  if (!isPretty()) {
11058
- log.info(TAG39, "Connecting to Supabase realtime (broadcast)...");
11255
+ log.info(TAG40, "Connecting to Supabase realtime (broadcast)...");
11059
11256
  }
11060
11257
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
11061
11258
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
11062
11259
  this.subscribeBroadcast();
11063
11260
  presenceChannel.on("presence", { event: "sync" }, () => {
11064
- log.debug(TAG39, "Presence sync");
11261
+ log.debug(TAG40, "Presence sync");
11065
11262
  }).subscribe(async (status) => {
11066
11263
  if (status === "SUBSCRIBED") {
11067
11264
  await presenceChannel.track({
@@ -11074,7 +11271,7 @@ class Watcher {
11074
11271
  agentName: this.identity.agentName
11075
11272
  });
11076
11273
  if (!isPretty() || !this.suppressStartupLogs) {
11077
- log.info(TAG39, "Presence tracked on board-presence channel");
11274
+ log.info(TAG40, "Presence tracked on board-presence channel");
11078
11275
  }
11079
11276
  this.presenceTracked = true;
11080
11277
  this.maybeResolveReady();
@@ -11087,13 +11284,13 @@ class Watcher {
11087
11284
  return;
11088
11285
  const gen = ++this.broadcastGen;
11089
11286
  this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
11090
- log.debug(TAG39, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
11287
+ log.debug(TAG40, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
11091
11288
  this.onCardBroadcast({
11092
11289
  event: "card_update",
11093
11290
  payload: msg.payload ?? {}
11094
11291
  });
11095
11292
  }).on("broadcast", { event: "card_created" }, (msg) => {
11096
- log.debug(TAG39, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
11293
+ log.debug(TAG40, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
11097
11294
  this.onCardBroadcast({
11098
11295
  event: "card_created",
11099
11296
  payload: msg.payload ?? {}
@@ -11103,7 +11300,7 @@ class Watcher {
11103
11300
  const cardId = payload.card_id;
11104
11301
  const command = payload.command;
11105
11302
  if (cardId && command) {
11106
- log.info(TAG39, `Broadcast: agent_command ${command} for ${cardId}`);
11303
+ log.info(TAG40, `Broadcast: agent_command ${command} for ${cardId}`);
11107
11304
  this.onAgentCommand?.({ cardId, command });
11108
11305
  }
11109
11306
  }).subscribe((status) => {
@@ -11113,13 +11310,13 @@ class Watcher {
11113
11310
  this.connected = true;
11114
11311
  this.reconnectAttempts = 0;
11115
11312
  if (!isPretty() || !this.suppressStartupLogs) {
11116
- log.info(TAG39, "Broadcast subscription active");
11313
+ log.info(TAG40, "Broadcast subscription active");
11117
11314
  }
11118
11315
  this.maybeResolveReady();
11119
11316
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
11120
11317
  this.connected = false;
11121
11318
  if (!this.stopping) {
11122
- log.warn(TAG39, `Broadcast subscription ${status} — scheduling reconnect`);
11319
+ log.warn(TAG40, `Broadcast subscription ${status} — scheduling reconnect`);
11123
11320
  this.scheduleReconnect();
11124
11321
  }
11125
11322
  }
@@ -11138,7 +11335,7 @@ class Watcher {
11138
11335
  async reconnectBroadcast() {
11139
11336
  if (this.stopping || !this.supabase)
11140
11337
  return;
11141
- log.warn(TAG39, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
11338
+ log.warn(TAG40, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
11142
11339
  if (this.channel) {
11143
11340
  const old = this.channel;
11144
11341
  this.channel = null;
@@ -11168,10 +11365,10 @@ class Watcher {
11168
11365
  this.supabase = null;
11169
11366
  }
11170
11367
  this.connected = false;
11171
- log.info(TAG39, "Broadcast subscription stopped");
11368
+ log.info(TAG40, "Broadcast subscription stopped");
11172
11369
  }
11173
11370
  }
11174
- var TAG39 = "watcher";
11371
+ var TAG40 = "watcher";
11175
11372
  var init_watcher = __esm(() => {
11176
11373
  init_log();
11177
11374
  });
@@ -11184,7 +11381,7 @@ __export(exports_worktree_gc, {
11184
11381
  isTransientGitNetworkError: () => isTransientGitNetworkError,
11185
11382
  WorktreeGc: () => WorktreeGc
11186
11383
  });
11187
- import { execFileSync as execFileSync11 } from "node:child_process";
11384
+ import { execFileSync as execFileSync12 } from "node:child_process";
11188
11385
  import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
11189
11386
  import { resolve as resolve3 } from "node:path";
11190
11387
  function isTransientGitNetworkError(message) {
@@ -11252,16 +11449,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
11252
11449
  }
11253
11450
  }
11254
11451
  try {
11255
- execFileSync11("git", ["worktree", "prune", "--expire=now"], {
11452
+ execFileSync12("git", ["worktree", "prune", "--expire=now"], {
11256
11453
  cwd: repoRoot,
11257
11454
  stdio: "pipe"
11258
11455
  });
11259
11456
  } catch {}
11260
11457
  if (result.removed.length > 0) {
11261
- log.info(TAG40, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
11458
+ log.info(TAG41, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
11262
11459
  }
11263
11460
  if (result.errors.length > 0) {
11264
- log.warn(TAG40, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
11461
+ log.warn(TAG41, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
11265
11462
  }
11266
11463
  return result;
11267
11464
  }
@@ -11283,7 +11480,7 @@ function pruneFailedRemoteBranches(opts) {
11283
11480
  return result;
11284
11481
  }
11285
11482
  try {
11286
- execFileSync11("git", ["fetch", "--prune", "origin"], {
11483
+ execFileSync12("git", ["fetch", "--prune", "origin"], {
11287
11484
  cwd: repoRoot,
11288
11485
  stdio: "pipe",
11289
11486
  ...GIT_NETWORK_EXEC
@@ -11291,7 +11488,7 @@ function pruneFailedRemoteBranches(opts) {
11291
11488
  } catch (err) {
11292
11489
  const detail = gitErrorDetail2(err);
11293
11490
  if (isTransientGitNetworkError(detail)) {
11294
- log.debug(TAG40, `Remote branch GC skipped — remote unreachable: ${detail}`);
11491
+ log.debug(TAG41, `Remote branch GC skipped — remote unreachable: ${detail}`);
11295
11492
  return result;
11296
11493
  }
11297
11494
  result.errors.push({ ref: "fetch", error: detail });
@@ -11299,7 +11496,7 @@ function pruneFailedRemoteBranches(opts) {
11299
11496
  const refPattern = `refs/remotes/origin/${opts.prefix}*`;
11300
11497
  let listing = "";
11301
11498
  try {
11302
- listing = execFileSync11("git", [
11499
+ listing = execFileSync12("git", [
11303
11500
  "for-each-ref",
11304
11501
  "--format=%(refname:strip=3) %(committerdate:unix)",
11305
11502
  refPattern
@@ -11330,11 +11527,11 @@ function pruneFailedRemoteBranches(opts) {
11330
11527
  continue;
11331
11528
  }
11332
11529
  if (clock() > sweepDeadline) {
11333
- log.debug(TAG40, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
11530
+ log.debug(TAG41, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
11334
11531
  break;
11335
11532
  }
11336
11533
  try {
11337
- execFileSync11("git", ["push", "origin", `:refs/heads/${ref}`], {
11534
+ execFileSync12("git", ["push", "origin", `:refs/heads/${ref}`], {
11338
11535
  cwd: repoRoot,
11339
11536
  stdio: "pipe",
11340
11537
  ...GIT_NETWORK_EXEC
@@ -11343,17 +11540,17 @@ function pruneFailedRemoteBranches(opts) {
11343
11540
  } catch (err) {
11344
11541
  const detail = gitErrorDetail2(err);
11345
11542
  if (isTransientGitNetworkError(detail)) {
11346
- log.debug(TAG40, `Remote branch GC interrupted — remote unreachable: ${detail}`);
11543
+ log.debug(TAG41, `Remote branch GC interrupted — remote unreachable: ${detail}`);
11347
11544
  break;
11348
11545
  }
11349
11546
  result.errors.push({ ref, error: detail });
11350
11547
  }
11351
11548
  }
11352
11549
  if (result.removed.length > 0) {
11353
- log.info(TAG40, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
11550
+ log.info(TAG41, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
11354
11551
  }
11355
11552
  if (result.errors.length > 0) {
11356
- log.warn(TAG40, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
11553
+ log.warn(TAG41, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
11357
11554
  }
11358
11555
  return result;
11359
11556
  }
@@ -11384,27 +11581,27 @@ class WorktreeGc {
11384
11581
  try {
11385
11582
  runWorktreeGc(this.basePath, this.store);
11386
11583
  } catch (err) {
11387
- log.warn(TAG40, `GC tick failed: ${err instanceof Error ? err.message : err}`);
11584
+ log.warn(TAG41, `GC tick failed: ${err instanceof Error ? err.message : err}`);
11388
11585
  }
11389
11586
  if (this.remoteOpts) {
11390
11587
  try {
11391
11588
  pruneFailedRemoteBranches(this.remoteOpts);
11392
11589
  } catch (err) {
11393
- log.warn(TAG40, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
11590
+ log.warn(TAG41, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
11394
11591
  }
11395
11592
  }
11396
11593
  }
11397
11594
  }
11398
11595
  function getRepoRoot2() {
11399
11596
  try {
11400
- return execFileSync11("git", ["rev-parse", "--show-toplevel"], {
11597
+ return execFileSync12("git", ["rev-parse", "--show-toplevel"], {
11401
11598
  encoding: "utf-8"
11402
11599
  }).trim();
11403
11600
  } catch {
11404
11601
  return null;
11405
11602
  }
11406
11603
  }
11407
- var TAG40 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
11604
+ var TAG41 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
11408
11605
  var init_worktree_gc = __esm(() => {
11409
11606
  init_log();
11410
11607
  init_worktree();
@@ -11438,12 +11635,12 @@ __export(exports_src, {
11438
11635
  validatePrerequisites: () => validatePrerequisites,
11439
11636
  main: () => main
11440
11637
  });
11441
- import { execFileSync as execFileSync12 } from "node:child_process";
11638
+ import { execFileSync as execFileSync13 } from "node:child_process";
11442
11639
  import { randomUUID as randomUUID3 } from "node:crypto";
11443
11640
  import { createRequire as createRequire2 } from "node:module";
11444
11641
  async function validatePrerequisites(config, banner) {
11445
11642
  try {
11446
- const ver = execFileSync12("claude", ["--version"], {
11643
+ const ver = execFileSync13("claude", ["--version"], {
11447
11644
  encoding: "utf-8"
11448
11645
  }).trim();
11449
11646
  banner.check(`Claude CLI ${ver}`);
@@ -11458,14 +11655,14 @@ async function validatePrerequisites(config, banner) {
11458
11655
  validateGitProviderCli(provider);
11459
11656
  }
11460
11657
  try {
11461
- const status = execFileSync12("git", ["status", "--porcelain"], {
11658
+ const status = execFileSync13("git", ["status", "--porcelain"], {
11462
11659
  encoding: "utf-8"
11463
11660
  }).trim();
11464
11661
  if (status) {
11465
11662
  banner.warn(`Working directory has uncommitted changes:
11466
11663
  ${status}`);
11467
11664
  }
11468
- execFileSync12("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
11665
+ execFileSync13("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
11469
11666
  encoding: "utf-8",
11470
11667
  stdio: "pipe"
11471
11668
  });
@@ -11509,7 +11706,7 @@ async function main() {
11509
11706
  } catch (err) {
11510
11707
  if (err instanceof ConfigValidationError) {
11511
11708
  banner.fail();
11512
- log.error(TAG41, err.message);
11709
+ log.error(TAG42, err.message);
11513
11710
  process.exit(1);
11514
11711
  }
11515
11712
  throw err;
@@ -11519,7 +11716,7 @@ async function main() {
11519
11716
  } catch (err) {
11520
11717
  if (err instanceof ConfigValidationError) {
11521
11718
  banner.fail();
11522
- log.error(TAG41, err.message);
11719
+ log.error(TAG42, err.message);
11523
11720
  process.exit(1);
11524
11721
  }
11525
11722
  throw err;
@@ -11634,7 +11831,7 @@ async function main() {
11634
11831
  if (shuttingDown)
11635
11832
  return;
11636
11833
  shuttingDown = true;
11637
- log.info(TAG41, `Received ${signal}, shutting down gracefully...`);
11834
+ log.info(TAG42, `Received ${signal}, shutting down gracefully...`);
11638
11835
  reconciler.stop();
11639
11836
  mergeMonitor?.stop();
11640
11837
  worktreeGc.stop();
@@ -11645,18 +11842,18 @@ async function main() {
11645
11842
  }
11646
11843
  await watcher.stop();
11647
11844
  await pool.shutdown();
11648
- log.info(TAG41, "Daemon stopped.");
11845
+ log.info(TAG42, "Daemon stopped.");
11649
11846
  process.exit(exitCode);
11650
11847
  };
11651
11848
  process.on("SIGINT", () => shutdown("SIGINT"));
11652
11849
  process.on("SIGTERM", () => shutdown("SIGTERM"));
11653
11850
  process.on("uncaughtException", (err) => {
11654
- log.error(TAG41, `Uncaught exception: ${err.message}`);
11851
+ log.error(TAG42, `Uncaught exception: ${err.message}`);
11655
11852
  exitCode = 1;
11656
11853
  shutdown("uncaughtException");
11657
11854
  });
11658
11855
  process.on("unhandledRejection", (reason) => {
11659
- log.error(TAG41, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
11856
+ log.error(TAG42, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
11660
11857
  exitCode = 1;
11661
11858
  shutdown("unhandledRejection");
11662
11859
  });
@@ -11715,29 +11912,29 @@ async function handleBroadcast(event, client, pool, config, agentId) {
11715
11912
  if (assignedAgentId === undefined)
11716
11913
  return;
11717
11914
  if (assignedAgentId === agentId) {
11718
- log.info(TAG41, `Broadcast: card ${cardId} assigned to agent`);
11915
+ log.info(TAG42, `Broadcast: card ${cardId} assigned to agent`);
11719
11916
  try {
11720
11917
  await pool.resetAttemptsForReassign(cardId);
11721
11918
  await tryEnqueueCard(cardId, client, pool, config, agentId);
11722
11919
  } catch (err) {
11723
- log.error(TAG41, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
11920
+ log.error(TAG42, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
11724
11921
  }
11725
11922
  } else if (pool.isCardKnown(cardId)) {
11726
- log.info(TAG41, `Broadcast: card ${cardId} unassigned from agent`);
11923
+ log.info(TAG42, `Broadcast: card ${cardId} unassigned from agent`);
11727
11924
  await pool.removeCard(cardId);
11728
11925
  }
11729
11926
  }
11730
11927
  async function tryEnqueueCard(cardId, client, pool, config, agentId) {
11731
11928
  const { card } = await client.getCard(cardId);
11732
11929
  if (card.assigned_agent_id !== agentId) {
11733
- log.debug(TAG41, `Card ${cardId} no longer assigned to agent — skipping`);
11930
+ log.debug(TAG42, `Card ${cardId} no longer assigned to agent — skipping`);
11734
11931
  return;
11735
11932
  }
11736
11933
  const board = await client.getBoard(config.projectId, { summary: true });
11737
11934
  const columns = board.columns;
11738
11935
  const column = columns.find((c) => c.id === card.column_id);
11739
11936
  if (!column) {
11740
- log.warn(TAG41, `Column not found for card ${cardId}`);
11937
+ log.warn(TAG42, `Column not found for card ${cardId}`);
11741
11938
  return;
11742
11939
  }
11743
11940
  const route = classifyPickup(card, column.name, {
@@ -11746,31 +11943,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
11746
11943
  playbooks: config.agent.playbooks
11747
11944
  });
11748
11945
  if (!route) {
11749
- log.info(TAG41, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
11946
+ log.info(TAG42, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
11750
11947
  return;
11751
11948
  }
11752
11949
  if (route.stage) {
11753
- log.info(TAG41, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
11950
+ log.info(TAG42, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
11754
11951
  }
11755
11952
  const mode = route.mode;
11756
11953
  const labelMap = buildLabelMap(board.labels ?? []);
11757
11954
  const cardLabels = resolveCardLabels(card, labelMap);
11758
11955
  const subtasks = card.subtasks ?? [];
11759
11956
  if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
11760
- log.debug(TAG41, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
11957
+ log.debug(TAG42, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
11761
11958
  return;
11762
11959
  }
11763
11960
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
11764
- log.debug(TAG41, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
11961
+ log.debug(TAG42, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
11765
11962
  return;
11766
11963
  }
11767
11964
  if (mode === "review" && !qualifiesForAutoReview(card.description)) {
11768
- log.info(TAG41, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
11965
+ log.info(TAG42, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
11769
11966
  return;
11770
11967
  }
11771
11968
  await pool.enqueue(card, column, cardLabels, subtasks, mode);
11772
11969
  }
11773
- var TAG41 = "daemon", PKG_VERSION;
11970
+ var TAG42 = "daemon", PKG_VERSION;
11774
11971
  var init_src = __esm(() => {
11775
11972
  init_board_helpers();
11776
11973
  init_board_reviewer();