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