@gethmy/agent 1.23.1 → 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 +424 -214
  2. package/dist/index.js +424 -214
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -581,6 +581,17 @@ function isDaemonAuthoredComment(comment, identity) {
581
581
  return false;
582
582
  return true;
583
583
  }
584
+ // ../harmony-shared/dist/agentStaleness.js
585
+ var AGENT_HEARTBEAT_LIVENESS_MS, AGENT_MILESTONE_LIVENESS_MS, AGENT_SWEEP_DAEMON_MS, AGENT_SWEEP_INTERACTIVE_MS, AGENT_SWEEP_PAUSED_MS, SWEPT_SESSION_WRITE_GRACE_MS, ACTIVE_STATUSES;
586
+ var init_agentStaleness = __esm(() => {
587
+ AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
588
+ AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
589
+ AGENT_SWEEP_DAEMON_MS = 30 * 60 * 1000;
590
+ AGENT_SWEEP_INTERACTIVE_MS = 2 * 60 * 60 * 1000;
591
+ AGENT_SWEEP_PAUSED_MS = 4 * 60 * 60 * 1000;
592
+ SWEPT_SESSION_WRITE_GRACE_MS = 60 * 60 * 1000;
593
+ ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
594
+ });
584
595
  // ../harmony-shared/dist/branchRef.js
585
596
  function extractBranchRef(description) {
586
597
  if (!description)
@@ -823,7 +834,7 @@ function gateEvaluate(gateSpec, evidence) {
823
834
  findings: [
824
835
  {
825
836
  level: "error",
826
- message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
837
+ message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".${blockedDetail(safeStructured)}` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
827
838
  }
828
839
  ],
829
840
  structured: safeStructured
@@ -841,7 +852,7 @@ function gateEvaluate(gateSpec, evidence) {
841
852
  if (result === "blocked") {
842
853
  findings.unshift({
843
854
  level: "error",
844
- message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".`
855
+ message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".${blockedDetail(safeStructured)}`
845
856
  });
846
857
  return { passed: false, findings, structured: safeStructured };
847
858
  }
@@ -937,6 +948,15 @@ function evaluateCondition(raw, structured) {
937
948
  function isPlainObject(value) {
938
949
  return typeof value === "object" && value !== null && !Array.isArray(value);
939
950
  }
951
+ function blockedDetail(structured) {
952
+ const reason = structured.reason;
953
+ if (typeof reason !== "string")
954
+ return "";
955
+ const trimmed = reason.trim();
956
+ if (!trimmed)
957
+ return "";
958
+ return ` ${trimmed.length > MAX_REASON_CHARS ? `${trimmed.slice(0, MAX_REASON_CHARS)}…` : trimmed}`;
959
+ }
940
960
  function resolvePath(root, path) {
941
961
  const segments = path.split(".");
942
962
  let current = root;
@@ -1009,7 +1029,7 @@ function formatValue(value) {
1009
1029
  return "[unserializable]";
1010
1030
  }
1011
1031
  }
1012
- var GATE_KINDS, GATE_OPERATORS;
1032
+ var GATE_KINDS, GATE_OPERATORS, MAX_REASON_CHARS = 400;
1013
1033
  var init_gateEvaluate = __esm(() => {
1014
1034
  GATE_KINDS = [
1015
1035
  "build_green",
@@ -1416,6 +1436,7 @@ var init_types = () => {};
1416
1436
 
1417
1437
  // ../harmony-shared/dist/index.js
1418
1438
  var init_dist = __esm(() => {
1439
+ init_agentStaleness();
1419
1440
  init_branchRef();
1420
1441
  init_cardLinks();
1421
1442
  init_classification();
@@ -1739,7 +1760,7 @@ function agentIdentifier(workerId) {
1739
1760
  function endStatusForCancel(reason) {
1740
1761
  return reason === "human_stop" ? "cancelled" : "paused";
1741
1762
  }
1742
- var DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
1763
+ var DEFAULT_METRIC_TIMEOUT_MS = 300000, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
1743
1764
  var init_types2 = __esm(() => {
1744
1765
  init_board_review();
1745
1766
  init_contract_phase();
@@ -1832,7 +1853,7 @@ var init_types2 = __esm(() => {
1832
1853
  worktreeGcIntervalMs: 5 * 60000
1833
1854
  },
1834
1855
  planning: DEFAULT_PLANNING_CONFIG,
1835
- playbooks: { enabled: true, humanStageColumns: [] },
1856
+ playbooks: { enabled: true, humanStageColumns: [], metrics: {} },
1836
1857
  contractFirst: DEFAULT_CONTRACT_CONFIG,
1837
1858
  boardReview: DEFAULT_BOARD_REVIEW_CONFIG
1838
1859
  };
@@ -5663,6 +5684,173 @@ var init_artifact_judge = __esm(() => {
5663
5684
  init_sdk_agent_runner();
5664
5685
  });
5665
5686
 
5687
+ // src/command-metric.ts
5688
+ import { execFileSync as execFileSync10 } from "node:child_process";
5689
+ function parseParseMode(parse) {
5690
+ if (typeof parse !== "string" || parse.length === 0) {
5691
+ return { kind: "invalid", reason: "`parse` must be a non-empty string" };
5692
+ }
5693
+ if (parse === "number")
5694
+ return { kind: "number" };
5695
+ if (parse.startsWith("json:")) {
5696
+ const path = parse.slice("json:".length).trim();
5697
+ if (!path) {
5698
+ return { kind: "invalid", reason: '`parse` "json:" is missing a path' };
5699
+ }
5700
+ return { kind: "json", path };
5701
+ }
5702
+ return {
5703
+ kind: "invalid",
5704
+ reason: `unknown \`parse\` mode "${parse}" (expected "number" or "json:<path>")`
5705
+ };
5706
+ }
5707
+ function parseMetricValue(mode, stdout) {
5708
+ if (mode.kind === "invalid")
5709
+ return { ok: false, reason: mode.reason };
5710
+ if (mode.kind === "number") {
5711
+ const trimmed = stdout.trim();
5712
+ if (!trimmed) {
5713
+ return {
5714
+ ok: false,
5715
+ reason: "command produced no output to read a number from"
5716
+ };
5717
+ }
5718
+ const value = Number(trimmed);
5719
+ if (!Number.isFinite(value)) {
5720
+ return {
5721
+ ok: false,
5722
+ reason: `command output is not a finite number: ${JSON.stringify(truncate(trimmed, 120))}`
5723
+ };
5724
+ }
5725
+ return { ok: true, value };
5726
+ }
5727
+ let doc;
5728
+ try {
5729
+ doc = JSON.parse(stdout);
5730
+ } catch (err) {
5731
+ const msg = err instanceof Error ? err.message : String(err);
5732
+ return { ok: false, reason: `command output is not valid JSON: ${msg}` };
5733
+ }
5734
+ const resolved = resolvePath(doc, mode.path);
5735
+ if (resolved === undefined) {
5736
+ return {
5737
+ ok: false,
5738
+ reason: `JSON path "${mode.path}" is absent in the command output`
5739
+ };
5740
+ }
5741
+ if (resolved !== null && typeof resolved !== "number" && typeof resolved !== "string" && typeof resolved !== "boolean") {
5742
+ return {
5743
+ ok: false,
5744
+ reason: `JSON path "${mode.path}" resolved to a ${Array.isArray(resolved) ? "array" : typeof resolved}, which no gate operator can compare`
5745
+ };
5746
+ }
5747
+ return { ok: true, value: resolved };
5748
+ }
5749
+ function defaultRunCommand(args) {
5750
+ const out = execFileSync10(args.command, args.args, {
5751
+ cwd: args.cwd,
5752
+ timeout: args.timeoutMs,
5753
+ stdio: "pipe",
5754
+ maxBuffer: MAX_OUTPUT_BUFFER2,
5755
+ encoding: "utf8"
5756
+ });
5757
+ return typeof out === "string" ? out : String(out);
5758
+ }
5759
+
5760
+ class CommandMetricCollector {
5761
+ deps;
5762
+ kind = "custom";
5763
+ constructor(deps) {
5764
+ this.deps = deps;
5765
+ }
5766
+ async collect(context) {
5767
+ const name = context.gate.metric;
5768
+ if (typeof name !== "string" || name.length === 0) {
5769
+ return blocked(null, 'Gate kind "custom" needs a `metric` name naming an allowlisted command (e.g. { "kind": "custom", "metric": "lighthouse_performance" }).');
5770
+ }
5771
+ const def = Object.hasOwn(this.deps.metrics, name) ? this.deps.metrics[name] : undefined;
5772
+ if (!def) {
5773
+ return blocked(name, `Metric "${name}" is not declared in this daemon's allowlist — add it under \`agent.playbooks.metrics\` to permit it.`);
5774
+ }
5775
+ if (typeof def.command !== "string" || def.command.length === 0) {
5776
+ return blocked(name, `Metric "${name}" declares no \`command\`.`);
5777
+ }
5778
+ const mode = parseParseMode(def.parse);
5779
+ if (mode.kind === "invalid") {
5780
+ return blocked(name, `Metric "${name}": ${mode.reason}.`);
5781
+ }
5782
+ const requested = typeof def.timeoutMs === "number" && def.timeoutMs > 0 ? Math.floor(def.timeoutMs) : DEFAULT_METRIC_TIMEOUT_MS;
5783
+ const timeoutMs = Math.min(requested, MAX_METRIC_TIMEOUT_MS);
5784
+ if (timeoutMs < requested) {
5785
+ log.warn(TAG19, `Metric "${name}" declares timeoutMs=${requested}, clamped to the ${MAX_METRIC_TIMEOUT_MS}ms ceiling`);
5786
+ }
5787
+ const run = this.deps.runCommand ?? defaultRunCommand;
5788
+ let stdout;
5789
+ try {
5790
+ stdout = run({
5791
+ command: def.command,
5792
+ args: def.args ?? [],
5793
+ cwd: this.deps.worktreePath,
5794
+ timeoutMs
5795
+ });
5796
+ } catch (err) {
5797
+ const reason = describeRunFailure(err, timeoutMs);
5798
+ log.warn(TAG19, `Metric "${name}" did not produce a measurement: ${reason}`);
5799
+ return blocked(name, reason);
5800
+ }
5801
+ const parsed = parseMetricValue(mode, stdout);
5802
+ if (!parsed.ok) {
5803
+ log.warn(TAG19, `Metric "${name}" output unusable: ${parsed.reason}`);
5804
+ return blocked(name, `Metric "${name}": ${parsed.reason}.`, stdout);
5805
+ }
5806
+ log.info(TAG19, `Metric "${name}" measured: ${JSON.stringify(parsed.value)}`);
5807
+ return {
5808
+ result: "passed",
5809
+ structured: {
5810
+ metric: name,
5811
+ value: parsed.value,
5812
+ raw: truncate(stdout, MAX_RAW_CHARS)
5813
+ }
5814
+ };
5815
+ }
5816
+ }
5817
+ function blocked(metric, reason, raw) {
5818
+ return {
5819
+ result: "blocked",
5820
+ structured: {
5821
+ metric,
5822
+ reason,
5823
+ ...raw === undefined ? {} : { raw: truncate(raw, MAX_RAW_CHARS) }
5824
+ }
5825
+ };
5826
+ }
5827
+ function describeRunFailure(err, timeoutMs) {
5828
+ const e = err;
5829
+ if (e?.code === "ENOBUFS") {
5830
+ return `command produced more than ${MAX_OUTPUT_BUFFER2} bytes of output (use a flag that prints only the metric, or parse a smaller report)`;
5831
+ }
5832
+ if (e?.code === "ETIMEDOUT" || e?.signal === "SIGTERM") {
5833
+ return `command timed out after ${timeoutMs}ms`;
5834
+ }
5835
+ if (e?.code === "ENOENT") {
5836
+ return "command not found on PATH";
5837
+ }
5838
+ const stderr = typeof e?.stderr === "string" ? e.stderr : e?.stderr instanceof Buffer ? e.stderr.toString("utf8") : "";
5839
+ const detail = stderr.trim() || String(e?.message ?? err);
5840
+ const status = typeof e?.status === "number" ? e.status : null;
5841
+ return status === null ? `command failed: ${truncate(detail, 400)}` : `command exited ${status}: ${truncate(detail, 400)}`;
5842
+ }
5843
+ function truncate(value, max) {
5844
+ return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
5845
+ }
5846
+ var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_METRIC_TIMEOUT_MS = 900000;
5847
+ var init_command_metric = __esm(() => {
5848
+ init_dist();
5849
+ init_log();
5850
+ init_types2();
5851
+ MAX_OUTPUT_BUFFER2 = 10 * 1024 * 1024;
5852
+ });
5853
+
5666
5854
  // src/gate-collectors.ts
5667
5855
  async function resolveStageGate(client, card) {
5668
5856
  const currentStage = card.current_stage;
@@ -5681,7 +5869,7 @@ async function resolveStageGate(client, card) {
5681
5869
  return null;
5682
5870
  return { stage: resolution.stage, gate };
5683
5871
  } catch (err) {
5684
- log.warn(TAG19, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
5872
+ log.warn(TAG20, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
5685
5873
  return null;
5686
5874
  }
5687
5875
  }
@@ -5701,6 +5889,9 @@ function normalizeGateSpec(gate) {
5701
5889
  const mode = gate.mode;
5702
5890
  if (mode === "all" || mode === "any")
5703
5891
  spec.mode = mode;
5892
+ const metric = gate.metric;
5893
+ if (typeof metric === "string" && metric.length > 0)
5894
+ spec.metric = metric;
5704
5895
  return spec;
5705
5896
  }
5706
5897
 
@@ -5798,12 +5989,15 @@ function buildGateCollectorRegistry(deps) {
5798
5989
  if (deps.artifact) {
5799
5990
  registry.artifact = new ArtifactCollector(deps.artifact);
5800
5991
  }
5992
+ if (deps.command) {
5993
+ registry.custom = new CommandMetricCollector(deps.command);
5994
+ }
5801
5995
  return registry;
5802
5996
  }
5803
5997
  async function collectGateEvidence(registry, context) {
5804
5998
  const collector = registry[context.gate.kind];
5805
5999
  if (!collector) {
5806
- log.info(TAG19, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
6000
+ log.info(TAG20, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
5807
6001
  return {
5808
6002
  result: "blocked",
5809
6003
  structured: {
@@ -5815,20 +6009,21 @@ async function collectGateEvidence(registry, context) {
5815
6009
  return await collector.collect(context);
5816
6010
  } catch (err) {
5817
6011
  const msg = err instanceof Error ? err.message : String(err);
5818
- log.warn(TAG19, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
6012
+ log.warn(TAG20, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
5819
6013
  return { result: "blocked", structured: { error: msg } };
5820
6014
  }
5821
6015
  }
5822
- var TAG19 = "gate-collectors";
6016
+ var TAG20 = "gate-collectors";
5823
6017
  var init_gate_collectors = __esm(() => {
5824
6018
  init_dist();
5825
6019
  init_artifact_judge();
6020
+ init_command_metric();
5826
6021
  init_log();
5827
6022
  init_verification();
5828
6023
  });
5829
6024
 
5830
6025
  // src/progress-tracker.ts
5831
- function truncate(str, max) {
6026
+ function truncate2(str, max) {
5832
6027
  return str.length > max ? `${str.slice(0, max - 3)}...` : str;
5833
6028
  }
5834
6029
 
@@ -5852,6 +6047,7 @@ class ProgressTracker {
5852
6047
  filesEdited = new Set;
5853
6048
  filesRead = new Set;
5854
6049
  lastCost = null;
6050
+ requestedModel = null;
5855
6051
  runEventSink = null;
5856
6052
  lastEmittedProgress = -1;
5857
6053
  lastAssistantText = "";
@@ -5869,6 +6065,9 @@ class ProgressTracker {
5869
6065
  setRunEventSink(sink) {
5870
6066
  this.runEventSink = sink;
5871
6067
  }
6068
+ setRequestedModel(model) {
6069
+ this.requestedModel = model;
6070
+ }
5872
6071
  attach(parser) {
5873
6072
  parser.on("tool_start", (name, input) => {
5874
6073
  this.onToolStart(name, input);
@@ -5939,7 +6138,7 @@ class ProgressTracker {
5939
6138
  }
5940
6139
  onToolStart(name, input) {
5941
6140
  this.toolCallCount++;
5942
- log.debug(TAG20, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
6141
+ log.debug(TAG21, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
5943
6142
  const filePath = this.extractString(input, "file_path");
5944
6143
  if (filePath) {
5945
6144
  if (EDIT_TOOLS.has(name)) {
@@ -6003,14 +6202,14 @@ class ProgressTracker {
6003
6202
  const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
6004
6203
  if (firstLine.length >= 10 && firstLine.length <= 200) {
6005
6204
  if (ACTION_PREFIX.test(firstLine)) {
6006
- this.lastAction = truncate(firstLine, MAX_TASK_LENGTH);
6205
+ this.lastAction = truncate2(firstLine, MAX_TASK_LENGTH);
6007
6206
  }
6008
6207
  }
6009
6208
  }
6010
6209
  transitionTo(newPhase) {
6011
6210
  if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
6012
6211
  return;
6013
- log.info(TAG20, `Phase: ${this.phase} → ${newPhase}`);
6212
+ log.info(TAG21, `Phase: ${this.phase} → ${newPhase}`);
6014
6213
  const previousPhase = this.phase;
6015
6214
  this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
6016
6215
  this.phase = newPhase;
@@ -6059,16 +6258,16 @@ class ProgressTracker {
6059
6258
  }
6060
6259
  case "Grep": {
6061
6260
  const pattern = this.extractString(input, "pattern");
6062
- return pattern ? `Searching for "${truncate(pattern, 40)}"` : "Searching code";
6261
+ return pattern ? `Searching for "${truncate2(pattern, 40)}"` : "Searching code";
6063
6262
  }
6064
6263
  case "Bash": {
6065
6264
  const cmd = this.extractString(input, "command");
6066
- return cmd ? `Running: ${truncate(cmd.split(`
6265
+ return cmd ? `Running: ${truncate2(cmd.split(`
6067
6266
  `)[0], 80)}` : "Running command";
6068
6267
  }
6069
6268
  case "Agent": {
6070
6269
  const desc = this.extractString(input, "description");
6071
- return desc ? `Sub-agent: ${truncate(desc, 60)}` : "Delegating to sub-agent";
6270
+ return desc ? `Sub-agent: ${truncate2(desc, 60)}` : "Delegating to sub-agent";
6072
6271
  }
6073
6272
  default: {
6074
6273
  if (name.startsWith("mcp__harmony__harmony_")) {
@@ -6112,12 +6311,12 @@ class ProgressTracker {
6112
6311
  }
6113
6312
  sendUpdate(currentTask) {
6114
6313
  this.lastUpdateAt = Date.now();
6115
- log.debug(TAG20, `Progress: ${this.progress}% — ${currentTask}`);
6314
+ log.debug(TAG21, `Progress: ${this.progress}% — ${currentTask}`);
6116
6315
  this.client.updateAgentProgress(this.cardId, {
6117
6316
  agentIdentifier: agentIdentifier(this.workerId),
6118
6317
  agentName: AGENT_NAME,
6119
6318
  status: "working",
6120
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
6319
+ currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
6121
6320
  progressPercent: this.progress,
6122
6321
  phase: this.phase,
6123
6322
  filesChanged: this.filesEdited.size,
@@ -6126,16 +6325,16 @@ class ProgressTracker {
6126
6325
  outputTokens: this.lastCost?.totalOutputTokens ?? 0,
6127
6326
  cacheCreationInputTokens: this.lastCost?.totalCacheCreationInputTokens ?? 0,
6128
6327
  cacheReadInputTokens: this.lastCost?.totalCacheReadInputTokens ?? 0,
6129
- modelName: this.lastCost?.modelName,
6328
+ modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
6130
6329
  numTurns: this.lastCost?.numTurns ?? 0
6131
6330
  }).catch((err) => {
6132
- log.warn(TAG20, `Failed to send progress update: ${err}`);
6331
+ log.warn(TAG21, `Failed to send progress update: ${err}`);
6133
6332
  });
6134
6333
  if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
6135
6334
  this.lastEmittedProgress = this.progress;
6136
6335
  this.runEventSink.recordProgress({
6137
6336
  progressPercent: this.progress,
6138
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
6337
+ currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
6139
6338
  phase: this.phase,
6140
6339
  filesChanged: this.filesEdited.size
6141
6340
  });
@@ -6148,7 +6347,7 @@ class ProgressTracker {
6148
6347
  this.heartbeatTimer = setTimeout(() => {
6149
6348
  if (!this.stopped) {
6150
6349
  const task = this.lastAction ? `Still working — ${this.lastAction}` : "Still working...";
6151
- this.sendUpdate(truncate(task, MAX_TASK_LENGTH));
6350
+ this.sendUpdate(truncate2(task, MAX_TASK_LENGTH));
6152
6351
  this.startHeartbeat();
6153
6352
  }
6154
6353
  }, HEARTBEAT_MS);
@@ -6160,7 +6359,7 @@ class ProgressTracker {
6160
6359
  return null;
6161
6360
  }
6162
6361
  }
6163
- var TAG20 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
6362
+ var TAG21 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
6164
6363
  var init_progress_tracker = __esm(() => {
6165
6364
  init_log();
6166
6365
  init_types2();
@@ -6316,7 +6515,7 @@ function parseReviewOutput(stdout) {
6316
6515
  try {
6317
6516
  const parsed = JSON.parse(raw);
6318
6517
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
6319
- log.debug(TAG21, "Parsed review output from fenced JSON block");
6518
+ log.debug(TAG22, "Parsed review output from fenced JSON block");
6320
6519
  return extractResult(parsed);
6321
6520
  }
6322
6521
  } catch {}
@@ -6342,21 +6541,21 @@ function parseReviewOutput(stdout) {
6342
6541
  try {
6343
6542
  const parsed = JSON.parse(candidates[i]);
6344
6543
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
6345
- log.debug(TAG21, "Parsed review output from raw JSON object");
6544
+ log.debug(TAG22, "Parsed review output from raw JSON object");
6346
6545
  return extractResult(parsed);
6347
6546
  }
6348
6547
  } catch {}
6349
6548
  }
6350
6549
  const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
6351
6550
  if (verdictMatch) {
6352
- log.warn(TAG21, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
6551
+ log.warn(TAG22, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
6353
6552
  return {
6354
6553
  verdict: verdictMatch[1].toLowerCase(),
6355
6554
  summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
6356
6555
  findings: []
6357
6556
  };
6358
6557
  }
6359
- log.warn(TAG21, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
6558
+ log.warn(TAG22, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
6360
6559
  return {
6361
6560
  verdict: "error",
6362
6561
  summary: stdout.slice(0, 500),
@@ -6389,7 +6588,7 @@ async function postReviewComment(client, card, commentType, body) {
6389
6588
  try {
6390
6589
  await client.addComment(card.id, body, { commentType });
6391
6590
  } catch (err) {
6392
- log.error(TAG21, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6591
+ log.error(TAG22, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6393
6592
  }
6394
6593
  }
6395
6594
  async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore, resolvedFromPrUrl) {
@@ -6403,11 +6602,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
6403
6602
  const currentCycle = getReviewCycle(freshDesc) + 1;
6404
6603
  const maxCycles = config.review.maxReviewCycles;
6405
6604
  if (result.verdict === "error") {
6406
- log.warn(TAG21, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
6605
+ log.warn(TAG22, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
6407
6606
  try {
6408
6607
  await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
6409
6608
  } catch (err) {
6410
- log.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
6609
+ log.warn(TAG22, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
6411
6610
  }
6412
6611
  if (config.review.postFindings) {
6413
6612
  const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
@@ -6450,7 +6649,7 @@ ${runLogTail}
6450
6649
  renameRemoteBranch(branchName, newRef, worktreePath);
6451
6650
  approvedBranch = newRef;
6452
6651
  } catch (err) {
6453
- log.warn(TAG21, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
6652
+ log.warn(TAG22, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
6454
6653
  }
6455
6654
  }
6456
6655
  if (config.review.createPR && approvedBranch) {
@@ -6471,14 +6670,14 @@ ${runLogTail}
6471
6670
  });
6472
6671
  }
6473
6672
  } catch (err) {
6474
- log.warn(TAG21, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
6673
+ log.warn(TAG22, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
6475
6674
  }
6476
6675
  }
6477
6676
  if (branchName) {
6478
6677
  try {
6479
6678
  await persistReviewedSha(client, card, worktreePath);
6480
6679
  } catch (err) {
6481
- log.warn(TAG21, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6680
+ log.warn(TAG22, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6482
6681
  }
6483
6682
  }
6484
6683
  if (config.review.postFindings) {
@@ -6500,7 +6699,7 @@ ${runLogTail}
6500
6699
  progressPercent: 100,
6501
6700
  ...buildTokenPayload(sessionStats)
6502
6701
  });
6503
- log.info(TAG21, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
6702
+ log.info(TAG22, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
6504
6703
  } else {
6505
6704
  const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
6506
6705
  const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
@@ -6508,7 +6707,7 @@ ${runLogTail}
6508
6707
  const linkedFindings = [...criticalFindings, ...majorFindings];
6509
6708
  const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
6510
6709
  if (currentCycle >= maxCycles) {
6511
- log.warn(TAG21, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
6710
+ log.warn(TAG22, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
6512
6711
  await moveCardToColumn(client, card, config.review.moveToColumn);
6513
6712
  const body = [
6514
6713
  "**Review — needs human review.**",
@@ -6548,7 +6747,7 @@ ${runLogTail}
6548
6747
  try {
6549
6748
  await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
6550
6749
  } catch (err) {
6551
- log.error(TAG21, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
6750
+ log.error(TAG22, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
6552
6751
  }
6553
6752
  }));
6554
6753
  if (linkedFindings.length > 0) {
@@ -6560,7 +6759,7 @@ ${runLogTail}
6560
6759
  try {
6561
6760
  await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
6562
6761
  } catch (err) {
6563
- log.error(TAG21, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
6762
+ log.error(TAG22, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
6564
6763
  }
6565
6764
  }));
6566
6765
  const baseDesc = stripReviewSummary(freshDesc);
@@ -6568,7 +6767,7 @@ ${runLogTail}
6568
6767
  try {
6569
6768
  await client.updateCard(card.id, { description: updatedDesc });
6570
6769
  } catch (err) {
6571
- log.error(TAG21, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
6770
+ log.error(TAG22, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
6572
6771
  }
6573
6772
  const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
6574
6773
  const body = [
@@ -6585,9 +6784,9 @@ ${runLogTail}
6585
6784
  if (config.planning.enabled && card.plan_id) {
6586
6785
  try {
6587
6786
  await client.updateCard(card.id, { needsPlanRefresh: true });
6588
- log.info(TAG21, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
6787
+ log.info(TAG22, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
6589
6788
  } catch (err) {
6590
- log.warn(TAG21, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6789
+ log.warn(TAG22, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6591
6790
  }
6592
6791
  }
6593
6792
  await moveCardToColumn(client, card, config.review.failColumn);
@@ -6601,10 +6800,10 @@ ${runLogTail}
6601
6800
  recoveryBranch
6602
6801
  });
6603
6802
  } catch (err) {
6604
- log.debug(TAG21, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
6803
+ log.debug(TAG22, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
6605
6804
  }
6606
6805
  if (recoveryBranch) {
6607
- log.info(TAG21, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
6806
+ log.info(TAG22, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
6608
6807
  }
6609
6808
  await client.endAgentSession(card.id, {
6610
6809
  status: "failed",
@@ -6613,7 +6812,7 @@ ${runLogTail}
6613
6812
  recoveryBranch,
6614
6813
  ...buildTokenPayload(sessionStats)
6615
6814
  });
6616
- log.info(TAG21, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
6815
+ log.info(TAG22, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
6617
6816
  }
6618
6817
  if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
6619
6818
  const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
@@ -6635,7 +6834,7 @@ ${runLogTail}
6635
6834
  cleanupWorktree(worktreePath, branchName);
6636
6835
  }
6637
6836
  }
6638
- var TAG21 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
6837
+ var TAG22 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
6639
6838
  **Review:`, RUN_LOG_TAIL_BYTES = 2048;
6640
6839
  var init_review_completion = __esm(() => {
6641
6840
  init_board_helpers();
@@ -6864,7 +7063,7 @@ class StateStore {
6864
7063
  const raw = readFileSync4(this.path, "utf-8");
6865
7064
  const parsed = JSON.parse(raw);
6866
7065
  if (parsed?.version !== SCHEMA_VERSION) {
6867
- log.warn(TAG22, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
7066
+ log.warn(TAG23, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
6868
7067
  return {
6869
7068
  version: SCHEMA_VERSION,
6870
7069
  daemonId: null,
@@ -6885,7 +7084,7 @@ class StateStore {
6885
7084
  daily: parsed.daily ?? []
6886
7085
  };
6887
7086
  } catch (err) {
6888
- log.error(TAG22, `failed to read state file: ${err instanceof Error ? err.message : err}`);
7087
+ log.error(TAG23, `failed to read state file: ${err instanceof Error ? err.message : err}`);
6889
7088
  return emptyState();
6890
7089
  }
6891
7090
  }
@@ -7074,7 +7273,7 @@ class StateStore {
7074
7273
  return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
7075
7274
  }
7076
7275
  }
7077
- var TAG22 = "state-store", SCHEMA_VERSION = 1;
7276
+ var TAG23 = "state-store", SCHEMA_VERSION = 1;
7078
7277
  var init_state_store = __esm(() => {
7079
7278
  init_log();
7080
7279
  });
@@ -7101,7 +7300,7 @@ function normalizeToolResultContent(raw) {
7101
7300
  return String(raw);
7102
7301
  }
7103
7302
  }
7104
- var TAG23 = "stream-parser", StreamParser;
7303
+ var TAG24 = "stream-parser", StreamParser;
7105
7304
  var init_stream_parser = __esm(() => {
7106
7305
  init_log();
7107
7306
  StreamParser = class StreamParser extends EventEmitter {
@@ -7149,14 +7348,14 @@ var init_stream_parser = __esm(() => {
7149
7348
  try {
7150
7349
  msg = JSON.parse(line);
7151
7350
  } catch {
7152
- log.debug(TAG23, `Non-JSON line: ${line.slice(0, 100)}`);
7351
+ log.debug(TAG24, `Non-JSON line: ${line.slice(0, 100)}`);
7153
7352
  return;
7154
7353
  }
7155
7354
  try {
7156
7355
  this.handleMessage(msg);
7157
7356
  } catch (err) {
7158
7357
  const errMsg = err instanceof Error ? err.message : String(err);
7159
- log.warn(TAG23, `Error handling stream event: ${errMsg}`);
7358
+ log.warn(TAG24, `Error handling stream event: ${errMsg}`);
7160
7359
  this.emit("parse_error", errMsg);
7161
7360
  }
7162
7361
  }
@@ -7242,7 +7441,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
7242
7441
  const msg2 = err instanceof Error ? err.message : String(err);
7243
7442
  if (i < attempts - 1) {
7244
7443
  const wait = backoffMs * 2 ** i;
7245
- log.warn(TAG24, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
7444
+ log.warn(TAG25, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
7246
7445
  await new Promise((r) => setTimeout(r, wait));
7247
7446
  }
7248
7447
  }
@@ -7265,10 +7464,10 @@ async function runTransition(client, card, plan, opts = {}) {
7265
7464
  if (opts.strictColumn) {
7266
7465
  throw new TransitionError("move", 1, msg);
7267
7466
  }
7268
- log.warn(TAG24, `#${shortId}: ${msg} — skipping move`);
7467
+ log.warn(TAG25, `#${shortId}: ${msg} — skipping move`);
7269
7468
  } else if (card.column_id !== target.id) {
7270
7469
  await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
7271
- log.info(TAG24, `#${shortId} → "${target.name}"`);
7470
+ log.info(TAG25, `#${shortId} → "${target.name}"`);
7272
7471
  card.column_id = target.id;
7273
7472
  moveLanded = true;
7274
7473
  } else {
@@ -7287,7 +7486,7 @@ async function runTransition(client, card, plan, opts = {}) {
7287
7486
  continue;
7288
7487
  await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
7289
7488
  existing.add(labelId);
7290
- log.info(TAG24, `#${shortId} +label "${name}"`);
7489
+ log.info(TAG25, `#${shortId} +label "${name}"`);
7291
7490
  }
7292
7491
  card.labelIds = Array.from(existing);
7293
7492
  }
@@ -7299,22 +7498,22 @@ async function runTransition(client, card, plan, opts = {}) {
7299
7498
  continue;
7300
7499
  await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
7301
7500
  existing.delete(match.id);
7302
- log.info(TAG24, `#${shortId} -label "${name}"`);
7501
+ log.info(TAG25, `#${shortId} -label "${name}"`);
7303
7502
  }
7304
7503
  card.labelIds = Array.from(existing);
7305
7504
  }
7306
7505
  if (plan.updateCard) {
7307
7506
  await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
7308
- log.info(TAG24, `#${shortId} updated`);
7507
+ log.info(TAG25, `#${shortId} updated`);
7309
7508
  }
7310
7509
  if (plan.endSession) {
7311
7510
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
7312
- log.info(TAG24, `#${shortId} session ended (${plan.endSession.status})`);
7511
+ log.info(TAG25, `#${shortId} session ended (${plan.endSession.status})`);
7313
7512
  }
7314
7513
  if (plan.assignAgent !== undefined) {
7315
7514
  const assignedAgentId = plan.assignAgent;
7316
7515
  await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
7317
- log.info(TAG24, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
7516
+ log.info(TAG25, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
7318
7517
  }
7319
7518
  if (opts.store && opts.runId) {
7320
7519
  try {
@@ -7327,11 +7526,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
7327
7526
  const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
7328
7527
  return result?.label?.id ?? null;
7329
7528
  } catch (err) {
7330
- log.warn(TAG24, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
7529
+ log.warn(TAG25, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
7331
7530
  return null;
7332
7531
  }
7333
7532
  }
7334
- var TAG24 = "transition", TransitionError;
7533
+ var TAG25 = "transition", TransitionError;
7335
7534
  var init_transitions = __esm(() => {
7336
7535
  init_log();
7337
7536
  TransitionError = class TransitionError extends Error {
@@ -7349,7 +7548,7 @@ var init_transitions = __esm(() => {
7349
7548
  });
7350
7549
 
7351
7550
  // src/review-worker.ts
7352
- import { execFileSync as execFileSync10 } from "node:child_process";
7551
+ import { execFileSync as execFileSync11 } from "node:child_process";
7353
7552
 
7354
7553
  class ReviewWorker {
7355
7554
  config;
@@ -7415,7 +7614,7 @@ class ReviewWorker {
7415
7614
  }
7416
7615
  }
7417
7616
  get tag() {
7418
- return `${TAG25}:${this.id}`;
7617
+ return `${TAG26}:${this.id}`;
7419
7618
  }
7420
7619
  get isIdle() {
7421
7620
  return this.state === "idle";
@@ -7467,7 +7666,7 @@ class ReviewWorker {
7467
7666
  costCents: 0,
7468
7667
  numTurns: 0
7469
7668
  });
7470
- const repoRoot = execFileSync10("git", ["rev-parse", "--show-toplevel"], {
7669
+ const repoRoot = execFileSync11("git", ["rev-parse", "--show-toplevel"], {
7471
7670
  encoding: "utf-8",
7472
7671
  timeout: 5000
7473
7672
  }).trim();
@@ -7486,7 +7685,8 @@ class ReviewWorker {
7486
7685
  agentId: this.identity.agentId,
7487
7686
  status: "working",
7488
7687
  currentTask: "Setting up review worktree",
7489
- progressPercent: 5
7688
+ progressPercent: 5,
7689
+ modelName: this.config.claude.reviewModel
7490
7690
  });
7491
7691
  this.sessionId = reviewSession && typeof reviewSession === "object" && "id" in reviewSession ? reviewSession.id ?? null : null;
7492
7692
  const labelPromise = addLabelByName(this.client, card, "agent", "#8b5cf6");
@@ -7535,7 +7735,7 @@ class ReviewWorker {
7535
7735
  return;
7536
7736
  let diff = "";
7537
7737
  try {
7538
- diff = execFileSync10("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
7738
+ diff = execFileSync11("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
7539
7739
  } catch {
7540
7740
  diff = "(unable to retrieve diff)";
7541
7741
  }
@@ -7587,6 +7787,7 @@ class ReviewWorker {
7587
7787
  this.cancel("timeout");
7588
7788
  }, this.config.review.maxTimeout);
7589
7789
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks);
7790
+ this.progressTracker.setRequestedModel(this.config.claude.reviewModel);
7590
7791
  const stdout = await this.spawnClaude(userPrompt, systemPrompt, this.progressTracker, card.short_id);
7591
7792
  this.lastSessionStats = this.progressTracker?.stats ?? null;
7592
7793
  this.progressTracker?.stop();
@@ -7878,7 +8079,7 @@ class ReviewWorker {
7878
8079
  this.lastSessionStats = null;
7879
8080
  }
7880
8081
  }
7881
- var TAG25 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
8082
+ var TAG26 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
7882
8083
  var init_review_worker = __esm(() => {
7883
8084
  init_dist();
7884
8085
  init_board_helpers();
@@ -7931,7 +8132,7 @@ class SleepGuard {
7931
8132
  if (!this.child.killed)
7932
8133
  this.child.kill("SIGTERM");
7933
8134
  this.child = null;
7934
- log.info(TAG26, "sleep assertion released");
8135
+ log.info(TAG27, "sleep assertion released");
7935
8136
  }
7936
8137
  }
7937
8138
  start() {
@@ -7946,7 +8147,7 @@ class SleepGuard {
7946
8147
  spawned = true;
7947
8148
  });
7948
8149
  child.on("error", (err) => {
7949
- log.warn(TAG26, `caffeinate unavailable: ${err.message}`);
8150
+ log.warn(TAG27, `caffeinate unavailable: ${err.message}`);
7950
8151
  if (this.child === child)
7951
8152
  this.child = null;
7952
8153
  });
@@ -7959,13 +8160,13 @@ class SleepGuard {
7959
8160
  });
7960
8161
  child.unref();
7961
8162
  this.child = child;
7962
- log.info(TAG26, "sleep assertion acquired (caffeinate -i)");
8163
+ log.info(TAG27, "sleep assertion acquired (caffeinate -i)");
7963
8164
  } catch (err) {
7964
- log.warn(TAG26, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
8165
+ log.warn(TAG27, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
7965
8166
  }
7966
8167
  }
7967
8168
  }
7968
- var TAG26 = "sleep-guard";
8169
+ var TAG27 = "sleep-guard";
7969
8170
  var init_sleep_guard = __esm(() => {
7970
8171
  init_log();
7971
8172
  });
@@ -7976,7 +8177,7 @@ async function fetchBlocksLinks(client, cardId) {
7976
8177
  const { links } = await client.getCardLinks(cardId);
7977
8178
  return links.filter((l) => l.link_type === "blocks");
7978
8179
  } catch (err) {
7979
- log.warn(TAG27, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
8180
+ log.warn(TAG28, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
7980
8181
  return null;
7981
8182
  }
7982
8183
  }
@@ -8008,27 +8209,27 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
8008
8209
  const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
8009
8210
  if (successors.length === 0)
8010
8211
  return;
8011
- log.info(TAG27, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
8212
+ log.info(TAG28, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
8012
8213
  for (const link of successors) {
8013
8214
  const successorId = link.target_card.id;
8014
8215
  try {
8015
8216
  const { card } = await deps.client.getCard(successorId);
8016
8217
  if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
8017
- log.info(TAG27, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
8218
+ log.info(TAG28, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
8018
8219
  await deps.client.updateCard(successorId, {
8019
8220
  assignedAgentId: deps.agentId
8020
8221
  });
8021
8222
  } else {
8022
- log.debug(TAG27, `successor #${card.short_id} assigned to different entity — skipping`);
8223
+ log.debug(TAG28, `successor #${card.short_id} assigned to different entity — skipping`);
8023
8224
  continue;
8024
8225
  }
8025
8226
  await deps.enqueue(successorId);
8026
8227
  } catch (err) {
8027
- log.warn(TAG27, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
8228
+ log.warn(TAG28, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
8028
8229
  }
8029
8230
  }
8030
8231
  }
8031
- var TAG27 = "unblock";
8232
+ var TAG28 = "unblock";
8032
8233
  var init_unblock = __esm(() => {
8033
8234
  init_log();
8034
8235
  });
@@ -8183,7 +8384,7 @@ class CliAgentRunner {
8183
8384
  events: batch
8184
8385
  });
8185
8386
  } catch (err) {
8186
- log.warn(TAG28, `Failed to flush run events: ${err}`);
8387
+ log.warn(TAG29, `Failed to flush run events: ${err}`);
8187
8388
  this.buffer.unshift(...batch);
8188
8389
  if (this.buffer.length > MAX_BUFFER) {
8189
8390
  this.buffer.length = MAX_BUFFER;
@@ -8220,7 +8421,7 @@ function mapCost(cost) {
8220
8421
  durationMs: cost.durationMs
8221
8422
  };
8222
8423
  }
8223
- var TAG28 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
8424
+ var TAG29 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
8224
8425
  var init_cli_agent_runner = __esm(() => {
8225
8426
  init_log();
8226
8427
  });
@@ -8253,11 +8454,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
8253
8454
  Do NOT push to main. All your work stays on \`${branchName}\`.
8254
8455
  The daemon owns the run lifecycle: once your work is committed it ends the agent session, pushes the branch, and moves the card to Review for you. Do NOT call harmony_end_agent_session, do NOT start a new session, and do NOT move the card or change its column yourself. If the skill driving this work tells you to move the card or end the session as a final step, SKIP it — it is handled for you (those tools are disabled for this run). Finish the implementation, commit, and stop.`
8255
8456
  });
8256
- log.info(TAG29, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
8457
+ log.info(TAG30, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
8257
8458
  return result.prompt + pastEpisodesSection;
8258
8459
  } catch (err) {
8259
8460
  const msg = err instanceof Error ? err.message : String(err);
8260
- log.warn(TAG29, `Failed to generate prompt via API, using fallback: ${msg}`);
8461
+ log.warn(TAG30, `Failed to generate prompt via API, using fallback: ${msg}`);
8261
8462
  const commentsSection = await renderCommentsSection(client, card.id);
8262
8463
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
8263
8464
  }
@@ -8275,7 +8476,7 @@ async function renderCommentsSection(client, cardId) {
8275
8476
 
8276
8477
  ${section}` : "";
8277
8478
  } catch (err) {
8278
- log.warn(TAG29, "comment-thread fetch failed", {
8479
+ log.warn(TAG30, "comment-thread fetch failed", {
8279
8480
  event: "comment_fetch_failed",
8280
8481
  error: err instanceof Error ? err.message : String(err)
8281
8482
  });
@@ -8325,7 +8526,7 @@ ${description}`.trim();
8325
8526
  ## Similar past tasks
8326
8527
  ${bullets}`;
8327
8528
  } catch (err) {
8328
- log.warn(TAG29, "past-episodes recall failed", {
8529
+ log.warn(TAG30, "past-episodes recall failed", {
8329
8530
  event: "episode_recall_failed",
8330
8531
  error: err instanceof Error ? err.message : String(err)
8331
8532
  });
@@ -8366,7 +8567,7 @@ ${subtaskStr}
8366
8567
  You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
8367
8568
  Do NOT push to main. All your work stays on \`${branchName}\`.`;
8368
8569
  }
8369
- var TAG29 = "prompt";
8570
+ var TAG30 = "prompt";
8370
8571
  var init_prompt = __esm(() => {
8371
8572
  init_dist();
8372
8573
  init_log();
@@ -8389,7 +8590,7 @@ async function resolveStageColumnName(client, card, stage) {
8389
8590
  const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
8390
8591
  return match ? match.name : null;
8391
8592
  } catch (err) {
8392
- log.warn(TAG30, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8593
+ log.warn(TAG31, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8393
8594
  return null;
8394
8595
  }
8395
8596
  }
@@ -8433,7 +8634,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8433
8634
  evidence,
8434
8635
  summary
8435
8636
  });
8436
- log.info(TAG30, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
8637
+ log.info(TAG31, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
8437
8638
  if (decision === "exit") {
8438
8639
  await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
8439
8640
  deps.sink?.recordLoopCompleted?.({
@@ -8475,7 +8676,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8475
8676
  await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
8476
8677
  keepAttempts: true
8477
8678
  });
8478
- log.info(TAG30, `#${card.short_id} LoopExhausted: ${reason}`);
8679
+ log.info(TAG31, `#${card.short_id} LoopExhausted: ${reason}`);
8479
8680
  return { kind: "held_gate_unmet", reason };
8480
8681
  }
8481
8682
  await deps.stateStore.decrementAttempt(card.id).catch(() => {});
@@ -8489,7 +8690,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8489
8690
  addLabels: [{ name: AGENT_LABEL }],
8490
8691
  ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
8491
8692
  }, { store: deps.stateStore, runId: deps.runId });
8492
- log.info(TAG30, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
8693
+ log.info(TAG31, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
8493
8694
  return { kind: "requeued_gate_unmet", toColumn };
8494
8695
  }
8495
8696
  async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
@@ -8508,7 +8709,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
8508
8709
  });
8509
8710
  await deps.client.addComment(card.id, body, { commentType: "decision" });
8510
8711
  } catch (err) {
8511
- log.warn(TAG30, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8712
+ log.warn(TAG31, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8512
8713
  }
8513
8714
  }
8514
8715
  async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
@@ -8539,7 +8740,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
8539
8740
  reason: "Playbook complete — final stage gate passed."
8540
8741
  });
8541
8742
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
8542
- log.info(TAG30, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
8743
+ log.info(TAG31, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
8543
8744
  return { kind: "completed_terminal" };
8544
8745
  }
8545
8746
  if (next.kind === "out_of_range") {
@@ -8571,7 +8772,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
8571
8772
  ...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
8572
8773
  }, { store: deps.stateStore, runId: deps.runId });
8573
8774
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
8574
- log.info(TAG30, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
8775
+ log.info(TAG31, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
8575
8776
  return { kind: "advanced", toStageId: next.stage.id, toColumn };
8576
8777
  }
8577
8778
  async function handleGateUnmet(card, stage, summary, deps) {
@@ -8590,7 +8791,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
8590
8791
  await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
8591
8792
  keepAttempts: true
8592
8793
  });
8593
- log.info(TAG30, `#${card.short_id} GateUnmetExhausted: ${reason}`);
8794
+ log.info(TAG31, `#${card.short_id} GateUnmetExhausted: ${reason}`);
8594
8795
  return { kind: "held_gate_unmet", reason };
8595
8796
  }
8596
8797
  const toColumn = await resolveStageColumnName(deps.client, card, stage) ?? deps.fallbackColumn;
@@ -8602,7 +8803,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
8602
8803
  addLabels: [{ name: AGENT_LABEL }],
8603
8804
  ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
8604
8805
  }, { store: deps.stateStore, runId: deps.runId });
8605
- log.info(TAG30, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
8806
+ log.info(TAG31, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
8606
8807
  return { kind: "requeued_gate_unmet", toColumn };
8607
8808
  }
8608
8809
  async function holdForHuman(client, card, reason, runId, stateStore, opts = {}) {
@@ -8622,10 +8823,10 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
8622
8823
  }
8623
8824
  }, { store: stateStore, runId });
8624
8825
  } catch (err) {
8625
- log.warn(TAG30, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8826
+ log.warn(TAG31, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8626
8827
  }
8627
8828
  }
8628
- var TAG30 = "stage-advance", AGENT_LABEL = "agent";
8829
+ var TAG31 = "stage-advance", AGENT_LABEL = "agent";
8629
8830
  var init_stage_advance = __esm(() => {
8630
8831
  init_dist();
8631
8832
  init_log();
@@ -8772,7 +8973,7 @@ class Worker {
8772
8973
  }
8773
8974
  }
8774
8975
  get tag() {
8775
- return `${TAG31}:${this.id}`;
8976
+ return `${TAG32}:${this.id}`;
8776
8977
  }
8777
8978
  get isIdle() {
8778
8979
  return this.state === "idle";
@@ -8828,24 +9029,26 @@ class Worker {
8828
9029
  costCents: 0,
8829
9030
  numTurns: 0
8830
9031
  });
9032
+ const implementModel = this.selectImplementModel(card);
8831
9033
  const { session } = await this.client.startAgentSession(card.id, {
8832
9034
  agentIdentifier: agentIdentifier(this.id),
8833
9035
  agentName: AGENT_NAME,
8834
9036
  agentId: this.identity.agentId,
8835
9037
  status: "working",
8836
9038
  currentTask: "Setting up worktree",
8837
- progressPercent: 5
9039
+ progressPercent: 5,
9040
+ modelName: implementModel
8838
9041
  });
8839
9042
  const sid = session && typeof session === "object" && "id" in session ? session.id : null;
8840
9043
  if (!sid) {
8841
- log.warn(TAG31, "startAgentSession returned no session id");
9044
+ log.warn(TAG32, "startAgentSession returned no session id");
8842
9045
  }
8843
9046
  this.sessionId = sid;
8844
9047
  if (this.sessionId) {
8845
9048
  this.cliRunner = new CliAgentRunner(this.client, card.id, this.sessionId);
8846
9049
  this.cliRunner.recordRunStarted({
8847
9050
  runner: this.config.runner,
8848
- model: this.config.claude.model
9051
+ model: implementModel
8849
9052
  });
8850
9053
  }
8851
9054
  await this.recordPhase("preparing");
@@ -8934,7 +9137,7 @@ ${basePrompt}`;
8934
9137
  }, this.config.maxTimeout);
8935
9138
  this.activeRunSpawnOpts = computeRunSpawnGating(stageCtx.kind === "run" ? stageCtx.allowedTools : null);
8936
9139
  await this.spawnClaude(prompt, card, subtasks, {
8937
- model: this.selectImplementModel(card),
9140
+ model: implementModel,
8938
9141
  ...this.activeRunSpawnOpts ?? {}
8939
9142
  });
8940
9143
  if (this.aborted)
@@ -9295,6 +9498,10 @@ ${basePrompt}`;
9295
9498
  artifactType: stage.artifact_type
9296
9499
  },
9297
9500
  checklist: { subtasks, cardDone: card.done ?? false },
9501
+ command: {
9502
+ worktreePath,
9503
+ metrics: this.config.playbooks.metrics ?? {}
9504
+ },
9298
9505
  ...review ? { review } : {}
9299
9506
  });
9300
9507
  const context = {
@@ -9705,6 +9912,7 @@ ${basePrompt}`;
9705
9912
  });
9706
9913
  const parser = new StreamParser;
9707
9914
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
9915
+ this.progressTracker.setRequestedModel(model);
9708
9916
  this.progressTracker.attach(parser);
9709
9917
  this.cliRunner?.attach(parser);
9710
9918
  if (this.cliRunner) {
@@ -9785,6 +9993,7 @@ ${basePrompt}`;
9785
9993
  `);
9786
9994
  }
9787
9995
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
9996
+ this.progressTracker.setRequestedModel(model);
9788
9997
  if (this.cliRunner) {
9789
9998
  this.progressTracker.setRunEventSink(this.cliRunner);
9790
9999
  }
@@ -9893,7 +10102,7 @@ ${basePrompt}`;
9893
10102
  this.runTurns = 0;
9894
10103
  }
9895
10104
  }
9896
- var TAG31 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
10105
+ var TAG32 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
9897
10106
  var init_worker = __esm(() => {
9898
10107
  init_dist();
9899
10108
  init_board_helpers();
@@ -9971,41 +10180,41 @@ class Pool {
9971
10180
  }
9972
10181
  async enqueue(card, column, labels, subtasks, mode = "implement") {
9973
10182
  if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
9974
- log.debug(TAG32, `Card ${card.id} already queued, active, or reserved, skipping`);
10183
+ log.debug(TAG33, `Card ${card.id} already queued, active, or reserved, skipping`);
9975
10184
  return;
9976
10185
  }
9977
10186
  this.reservations.add(card.id);
9978
10187
  try {
9979
10188
  if (mode === "implement") {
9980
10189
  if (this.authPaused) {
9981
- log.debug(TAG32, `#${card.short_id} held — agent paused (auth error)`);
10190
+ log.debug(TAG33, `#${card.short_id} held — agent paused (auth error)`);
9982
10191
  await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
9983
10192
  return;
9984
10193
  }
9985
10194
  const cooldownMs = this.apiCooldownRemainingMs();
9986
10195
  if (cooldownMs > 0) {
9987
- log.debug(TAG32, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
10196
+ log.debug(TAG33, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
9988
10197
  await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
9989
10198
  return;
9990
10199
  }
9991
10200
  const decision = this.budget.check(card.id);
9992
10201
  if (!decision.allow) {
9993
10202
  if (decision.reason === "daily_budget") {
9994
- log.warn(TAG32, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
10203
+ log.warn(TAG33, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
9995
10204
  await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
9996
10205
  } else {
9997
- log.debug(TAG32, `#${card.short_id} gave up: ${decision.detail}`);
10206
+ log.debug(TAG33, `#${card.short_id} gave up: ${decision.detail}`);
9998
10207
  }
9999
10208
  return;
10000
10209
  }
10001
10210
  const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
10002
10211
  if (blockers === null) {
10003
- log.warn(TAG32, `#${card.short_id} blocker check failed — deferring to next tick`);
10212
+ log.warn(TAG33, `#${card.short_id} blocker check failed — deferring to next tick`);
10004
10213
  return;
10005
10214
  }
10006
10215
  if (blockers.length > 0) {
10007
10216
  const list = blockers.map((b) => `#${b.shortId}`).join(", ");
10008
- log.info(TAG32, `#${card.short_id} blocked by ${list} — waiting`);
10217
+ log.info(TAG33, `#${card.short_id} blocked by ${list} — waiting`);
10009
10218
  await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
10010
10219
  return;
10011
10220
  }
@@ -10037,7 +10246,7 @@ class Pool {
10037
10246
  });
10038
10247
  this.lastWaitingEmit.set(cardId, currentTask);
10039
10248
  } catch (err) {
10040
- log.debug(TAG32, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
10249
+ log.debug(TAG33, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
10041
10250
  }
10042
10251
  }
10043
10252
  noteApiError(err) {
@@ -10045,7 +10254,7 @@ class Pool {
10045
10254
  return;
10046
10255
  if (err.kind === "auth") {
10047
10256
  if (!this.authPaused) {
10048
- log.error(TAG32, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
10257
+ log.error(TAG33, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
10049
10258
  }
10050
10259
  this.authPaused = true;
10051
10260
  return;
@@ -10054,7 +10263,7 @@ class Pool {
10054
10263
  const until = Date.now() + cooldownMs;
10055
10264
  if (until > this.apiCooldownUntil) {
10056
10265
  this.apiCooldownUntil = until;
10057
- log.warn(TAG32, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
10266
+ log.warn(TAG33, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
10058
10267
  }
10059
10268
  }
10060
10269
  apiCooldownRemainingMs() {
@@ -10068,13 +10277,13 @@ class Pool {
10068
10277
  const removed = queue.remove(cardId);
10069
10278
  if (removed) {
10070
10279
  this.cardDataCache.delete(cardId);
10071
- log.info(TAG32, `Removed #${removed.shortId} from ${removed.mode} queue`);
10280
+ log.info(TAG33, `Removed #${removed.shortId} from ${removed.mode} queue`);
10072
10281
  return;
10073
10282
  }
10074
10283
  }
10075
10284
  const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
10076
10285
  if (worker) {
10077
- log.info(TAG32, `Cancelling worker ${worker.id} for card ${cardId}`);
10286
+ log.info(TAG33, `Cancelling worker ${worker.id} for card ${cardId}`);
10078
10287
  await worker.cancel("unassigned");
10079
10288
  }
10080
10289
  }
@@ -10107,10 +10316,10 @@ class Pool {
10107
10316
  async handleAgentCommand(cardId, command) {
10108
10317
  const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
10109
10318
  if (!worker) {
10110
- log.debug(TAG32, `No active worker for card ${cardId}, ignoring ${command}`);
10319
+ log.debug(TAG33, `No active worker for card ${cardId}, ignoring ${command}`);
10111
10320
  return;
10112
10321
  }
10113
- log.info(TAG32, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
10322
+ log.info(TAG33, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
10114
10323
  switch (command) {
10115
10324
  case "pause":
10116
10325
  await worker.pause();
@@ -10158,7 +10367,7 @@ class Pool {
10158
10367
  };
10159
10368
  }
10160
10369
  async shutdown() {
10161
- log.info(TAG32, "Shutting down pool...");
10370
+ log.info(TAG33, "Shutting down pool...");
10162
10371
  this.shuttingDown = true;
10163
10372
  const active = [
10164
10373
  ...this.implWorkers.filter((w) => w.isActive),
@@ -10166,7 +10375,7 @@ class Pool {
10166
10375
  ];
10167
10376
  await Promise.all(active.map((w) => w.cancel("shutdown")));
10168
10377
  this.sleepGuard.stop();
10169
- log.info(TAG32, "Pool shutdown complete");
10378
+ log.info(TAG33, "Pool shutdown complete");
10170
10379
  }
10171
10380
  reservations = new Set;
10172
10381
  cardDataCache = new Map;
@@ -10175,7 +10384,7 @@ class Pool {
10175
10384
  return false;
10176
10385
  const idle = workers.find((w) => w.isIdle);
10177
10386
  if (!idle) {
10178
- log.debug(TAG32, `No idle ${label} workers (queue: ${queue.length})`);
10387
+ log.debug(TAG33, `No idle ${label} workers (queue: ${queue.length})`);
10179
10388
  return false;
10180
10389
  }
10181
10390
  const next = queue.dequeue();
@@ -10183,18 +10392,18 @@ class Pool {
10183
10392
  return false;
10184
10393
  const data = this.cardDataCache.get(next.cardId);
10185
10394
  if (!data) {
10186
- log.warn(TAG32, `No cached data for card ${next.cardId}, skipping`);
10395
+ log.warn(TAG33, `No cached data for card ${next.cardId}, skipping`);
10187
10396
  return false;
10188
10397
  }
10189
10398
  this.cardDataCache.delete(next.cardId);
10190
10399
  this.lastWaitingEmit.delete(next.cardId);
10191
- log.info(TAG32, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
10400
+ log.info(TAG33, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
10192
10401
  this.sleepGuard.acquire();
10193
10402
  idle.run(data.card, data.column, data.labels, data.subtasks);
10194
10403
  return true;
10195
10404
  }
10196
10405
  }
10197
- var TAG32 = "pool";
10406
+ var TAG33 = "pool";
10198
10407
  var init_pool = __esm(() => {
10199
10408
  init_error_classifier();
10200
10409
  init_log();
@@ -10236,7 +10445,7 @@ function load(path) {
10236
10445
  return parsed;
10237
10446
  return {};
10238
10447
  } catch (err) {
10239
- log.warn(TAG33, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
10448
+ log.warn(TAG34, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
10240
10449
  return {};
10241
10450
  }
10242
10451
  }
@@ -10254,7 +10463,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
10254
10463
  registry[projectId] = { ...entry, updatedAt: Date.now() };
10255
10464
  save(path, registry);
10256
10465
  } catch (err) {
10257
- log.warn(TAG33, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10466
+ log.warn(TAG34, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10258
10467
  }
10259
10468
  }
10260
10469
  function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
@@ -10270,10 +10479,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
10270
10479
  delete registry[projectId];
10271
10480
  save(path, registry);
10272
10481
  } catch (err) {
10273
- log.warn(TAG33, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10482
+ log.warn(TAG34, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10274
10483
  }
10275
10484
  }
10276
- var TAG33 = "port-registry";
10485
+ var TAG34 = "port-registry";
10277
10486
  var init_port_registry = __esm(() => {
10278
10487
  init_log();
10279
10488
  });
@@ -10294,7 +10503,7 @@ async function fetchCardSafely(client, cardId) {
10294
10503
  const { card } = await client.getCard(cardId);
10295
10504
  return card;
10296
10505
  } catch (err) {
10297
- log.warn(TAG34, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
10506
+ log.warn(TAG35, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
10298
10507
  return null;
10299
10508
  }
10300
10509
  }
@@ -10304,7 +10513,7 @@ async function recoverOrphans(store, client, config) {
10304
10513
  return [];
10305
10514
  }
10306
10515
  const outcomes = [];
10307
- log.info(TAG34, `recovering ${active.length} orphan run(s) from prior daemon`);
10516
+ log.info(TAG35, `recovering ${active.length} orphan run(s) from prior daemon`);
10308
10517
  for (const run of active) {
10309
10518
  const outcome = {
10310
10519
  runId: run.runId,
@@ -10316,11 +10525,11 @@ async function recoverOrphans(store, client, config) {
10316
10525
  };
10317
10526
  outcomes.push(outcome);
10318
10527
  if (isProcessAlive(run.daemonPid, process.pid)) {
10319
- log.warn(TAG34, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
10528
+ log.warn(TAG35, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
10320
10529
  outcome.actions.push("skipped: daemon pid still alive");
10321
10530
  continue;
10322
10531
  }
10323
- log.info(TAG34, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
10532
+ log.info(TAG35, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
10324
10533
  await recoverRun(run, store, client, config, outcome, {
10325
10534
  rollbackAttempt: true
10326
10535
  });
@@ -10340,7 +10549,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
10340
10549
  } catch (err) {
10341
10550
  const msg = err instanceof Error ? err.message : String(err);
10342
10551
  outcome.errors.push(`endAgentSession: ${msg}`);
10343
- log.warn(TAG34, `endAgentSession failed for ${run.cardId}: ${msg}`);
10552
+ log.warn(TAG35, `endAgentSession failed for ${run.cardId}: ${msg}`);
10344
10553
  }
10345
10554
  const card = await fetchCardSafely(client, run.cardId);
10346
10555
  if (card) {
@@ -10392,9 +10601,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
10392
10601
  outcome.errors.push(`decrementAttempt: ${msg}`);
10393
10602
  }
10394
10603
  }
10395
- log.info(TAG34, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
10604
+ log.info(TAG35, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
10396
10605
  }
10397
- var TAG34 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
10606
+ var TAG35 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
10398
10607
  var init_recovery = __esm(() => {
10399
10608
  init_board_helpers();
10400
10609
  init_log();
@@ -10405,14 +10614,14 @@ var init_recovery = __esm(() => {
10405
10614
  async function claimReviewCard(client, cardId, agentId) {
10406
10615
  try {
10407
10616
  const { claimed } = await client.claimCard(cardId, agentId);
10408
- log.debug(TAG35, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
10617
+ log.debug(TAG36, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
10409
10618
  return claimed;
10410
10619
  } catch (err) {
10411
- log.error(TAG35, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
10620
+ log.error(TAG36, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
10412
10621
  return false;
10413
10622
  }
10414
10623
  }
10415
- var TAG35 = "claim";
10624
+ var TAG36 = "claim";
10416
10625
  var init_claim = __esm(() => {
10417
10626
  init_log();
10418
10627
  });
@@ -10465,22 +10674,22 @@ async function reclaimPreReviewStrands(opts) {
10465
10674
  continue;
10466
10675
  const won = await claimReviewCard(client, card.id, agentId);
10467
10676
  if (!won) {
10468
- log.debug(TAG36, `#${card.short_id} — lost the review claim race, skipping`);
10677
+ log.debug(TAG37, `#${card.short_id} — lost the review claim race, skipping`);
10469
10678
  continue;
10470
10679
  }
10471
- log.warn(TAG36, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
10680
+ log.warn(TAG37, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
10472
10681
  reclaimed.push(card.id);
10473
10682
  if (opts.onClaimed) {
10474
10683
  try {
10475
10684
  await opts.onClaimed(card);
10476
10685
  } catch (err) {
10477
- log.error(TAG36, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
10686
+ log.error(TAG37, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
10478
10687
  }
10479
10688
  }
10480
10689
  }
10481
10690
  return reclaimed;
10482
10691
  }
10483
- var TAG36 = "strand-recovery";
10692
+ var TAG37 = "strand-recovery";
10484
10693
  var init_strand_recovery = __esm(() => {
10485
10694
  init_board_helpers();
10486
10695
  init_claim();
@@ -10532,7 +10741,7 @@ class Reconciler {
10532
10741
  clearInterval(this.timer);
10533
10742
  this.timer = null;
10534
10743
  }
10535
- log.info(TAG37, "Heartbeat stopped");
10744
+ log.info(TAG38, "Heartbeat stopped");
10536
10745
  }
10537
10746
  async recoverStaleRuns() {
10538
10747
  if (!this.stateStore || !this.agentConfig)
@@ -10549,7 +10758,7 @@ class Reconciler {
10549
10758
  if (!daemonDead && !(heartbeatStale && ourZombie))
10550
10759
  continue;
10551
10760
  const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
10552
- log.warn(TAG37, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
10761
+ log.warn(TAG38, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
10553
10762
  await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
10554
10763
  runId: run.runId,
10555
10764
  cardId: run.cardId,
@@ -10576,11 +10785,11 @@ class Reconciler {
10576
10785
  const stalledAt = Date.parse(card.updated_at ?? "");
10577
10786
  if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
10578
10787
  continue;
10579
- log.warn(TAG37, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
10788
+ log.warn(TAG38, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
10580
10789
  try {
10581
10790
  await this.client.moveCard(card.id, pickupCol.id);
10582
10791
  } catch (err) {
10583
- log.error(TAG37, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10792
+ log.error(TAG38, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10584
10793
  }
10585
10794
  }
10586
10795
  }
@@ -10612,7 +10821,7 @@ class Reconciler {
10612
10821
  return;
10613
10822
  const cardLabels = resolveCardLabels(card, labelMap);
10614
10823
  const subtasks = card.subtasks ?? [];
10615
- log.info(TAG37, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
10824
+ log.info(TAG38, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
10616
10825
  await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
10617
10826
  }
10618
10827
  });
@@ -10636,11 +10845,11 @@ class Reconciler {
10636
10845
  const parkedAt = Date.parse(card.updated_at ?? "");
10637
10846
  if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
10638
10847
  continue;
10639
- log.warn(TAG37, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
10848
+ log.warn(TAG38, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
10640
10849
  try {
10641
10850
  await this.client.moveCard(card.id, pickupCol.id);
10642
10851
  } catch (err) {
10643
- log.error(TAG37, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10852
+ log.error(TAG38, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10644
10853
  }
10645
10854
  }
10646
10855
  }
@@ -10660,7 +10869,8 @@ class Reconciler {
10660
10869
  reviewColumns: this.reviewColumns,
10661
10870
  playbooks: this.agentConfig?.playbooks ?? {
10662
10871
  enabled: false,
10663
- humanStageColumns: []
10872
+ humanStageColumns: [],
10873
+ metrics: {}
10664
10874
  }
10665
10875
  };
10666
10876
  const assignedCards = cards.filter((c) => {
@@ -10683,21 +10893,21 @@ class Reconciler {
10683
10893
  const subtasks = card.subtasks ?? [];
10684
10894
  const mode = route.mode;
10685
10895
  if (route.stage) {
10686
- log.info(TAG37, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
10896
+ log.info(TAG38, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
10687
10897
  }
10688
10898
  if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
10689
- log.debug(TAG37, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
10899
+ log.debug(TAG38, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
10690
10900
  continue;
10691
10901
  }
10692
10902
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
10693
- log.debug(TAG37, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
10903
+ log.debug(TAG38, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
10694
10904
  continue;
10695
10905
  }
10696
10906
  if (mode === "review" && !qualifiesForAutoReview(card.description)) {
10697
- log.debug(TAG37, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
10907
+ log.debug(TAG38, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
10698
10908
  continue;
10699
10909
  }
10700
- log.info(TAG37, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
10910
+ log.info(TAG38, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
10701
10911
  await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
10702
10912
  }
10703
10913
  }
@@ -10708,18 +10918,18 @@ class Reconciler {
10708
10918
  await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
10709
10919
  for (const knownId of knownCardIds) {
10710
10920
  if (!allAgentCardIds.has(knownId)) {
10711
- log.info(TAG37, `Missed unassign: ${knownId} — removing`);
10921
+ log.info(TAG38, `Missed unassign: ${knownId} — removing`);
10712
10922
  await this.pool.removeCard(knownId);
10713
10923
  }
10714
10924
  }
10715
10925
  await this.releaseStalledApprovals(cards, columns, knownCardIds);
10716
- log.debug(TAG37, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
10926
+ log.debug(TAG38, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
10717
10927
  } catch (err) {
10718
- log.error(TAG37, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
10928
+ log.error(TAG38, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
10719
10929
  }
10720
10930
  }
10721
10931
  }
10722
- var TAG37 = "reconcile";
10932
+ var TAG38 = "reconcile";
10723
10933
  var init_reconcile = __esm(() => {
10724
10934
  init_board_helpers();
10725
10935
  init_git_pr();
@@ -10759,7 +10969,7 @@ function prettyBanner(config, version) {
10759
10969
  checks.push({ kind: "ok", message });
10760
10970
  },
10761
10971
  warn(message) {
10762
- log.warn(TAG38, message);
10972
+ log.warn(TAG39, message);
10763
10973
  checks.push({ kind: "warn", message: message.split(`
10764
10974
  `, 1)[0] });
10765
10975
  },
@@ -10784,25 +10994,25 @@ function prettyBanner(config, version) {
10784
10994
  };
10785
10995
  }
10786
10996
  function jsonBanner(config, version) {
10787
- log.info(TAG38, `Harmony Agent Daemon v${version} starting...`);
10788
- log.info(TAG38, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
10997
+ log.info(TAG39, `Harmony Agent Daemon v${version} starting...`);
10998
+ log.info(TAG39, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
10789
10999
  if (config.agent.review.enabled) {
10790
- log.info(TAG38, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
11000
+ log.info(TAG39, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
10791
11001
  }
10792
11002
  let failed = false;
10793
11003
  return {
10794
11004
  setProjectName(_name) {},
10795
11005
  setGitProvider(provider) {
10796
- log.info(TAG38, `Git provider: ${provider}`);
11006
+ log.info(TAG39, `Git provider: ${provider}`);
10797
11007
  },
10798
11008
  setHttpPort(port) {
10799
- log.info(TAG38, `HTTP server on port ${port}`);
11009
+ log.info(TAG39, `HTTP server on port ${port}`);
10800
11010
  },
10801
11011
  check(message) {
10802
- log.info(TAG38, message);
11012
+ log.info(TAG39, message);
10803
11013
  },
10804
11014
  warn(message) {
10805
- log.warn(TAG38, message);
11015
+ log.warn(TAG39, message);
10806
11016
  },
10807
11017
  fail() {
10808
11018
  failed = true;
@@ -10810,7 +11020,7 @@ function jsonBanner(config, version) {
10810
11020
  async ready(message) {
10811
11021
  if (failed)
10812
11022
  return;
10813
- log.info(TAG38, message);
11023
+ log.info(TAG39, message);
10814
11024
  }
10815
11025
  };
10816
11026
  }
@@ -10891,7 +11101,7 @@ function cyan(s) {
10891
11101
  function yellow(s) {
10892
11102
  return `${ANSI.yellow}${s}${ANSI.reset}`;
10893
11103
  }
10894
- var TAG38 = "daemon", RULE_WIDTH = 70, ANSI;
11104
+ var TAG39 = "daemon", RULE_WIDTH = 70, ANSI;
10895
11105
  var init_startup_banner = __esm(() => {
10896
11106
  init_log();
10897
11107
  ANSI = {
@@ -11042,13 +11252,13 @@ class Watcher {
11042
11252
  }
11043
11253
  async start() {
11044
11254
  if (!isPretty()) {
11045
- log.info(TAG39, "Connecting to Supabase realtime (broadcast)...");
11255
+ log.info(TAG40, "Connecting to Supabase realtime (broadcast)...");
11046
11256
  }
11047
11257
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
11048
11258
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
11049
11259
  this.subscribeBroadcast();
11050
11260
  presenceChannel.on("presence", { event: "sync" }, () => {
11051
- log.debug(TAG39, "Presence sync");
11261
+ log.debug(TAG40, "Presence sync");
11052
11262
  }).subscribe(async (status) => {
11053
11263
  if (status === "SUBSCRIBED") {
11054
11264
  await presenceChannel.track({
@@ -11061,7 +11271,7 @@ class Watcher {
11061
11271
  agentName: this.identity.agentName
11062
11272
  });
11063
11273
  if (!isPretty() || !this.suppressStartupLogs) {
11064
- log.info(TAG39, "Presence tracked on board-presence channel");
11274
+ log.info(TAG40, "Presence tracked on board-presence channel");
11065
11275
  }
11066
11276
  this.presenceTracked = true;
11067
11277
  this.maybeResolveReady();
@@ -11074,13 +11284,13 @@ class Watcher {
11074
11284
  return;
11075
11285
  const gen = ++this.broadcastGen;
11076
11286
  this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
11077
- log.debug(TAG39, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
11287
+ log.debug(TAG40, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
11078
11288
  this.onCardBroadcast({
11079
11289
  event: "card_update",
11080
11290
  payload: msg.payload ?? {}
11081
11291
  });
11082
11292
  }).on("broadcast", { event: "card_created" }, (msg) => {
11083
- log.debug(TAG39, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
11293
+ log.debug(TAG40, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
11084
11294
  this.onCardBroadcast({
11085
11295
  event: "card_created",
11086
11296
  payload: msg.payload ?? {}
@@ -11090,7 +11300,7 @@ class Watcher {
11090
11300
  const cardId = payload.card_id;
11091
11301
  const command = payload.command;
11092
11302
  if (cardId && command) {
11093
- log.info(TAG39, `Broadcast: agent_command ${command} for ${cardId}`);
11303
+ log.info(TAG40, `Broadcast: agent_command ${command} for ${cardId}`);
11094
11304
  this.onAgentCommand?.({ cardId, command });
11095
11305
  }
11096
11306
  }).subscribe((status) => {
@@ -11100,13 +11310,13 @@ class Watcher {
11100
11310
  this.connected = true;
11101
11311
  this.reconnectAttempts = 0;
11102
11312
  if (!isPretty() || !this.suppressStartupLogs) {
11103
- log.info(TAG39, "Broadcast subscription active");
11313
+ log.info(TAG40, "Broadcast subscription active");
11104
11314
  }
11105
11315
  this.maybeResolveReady();
11106
11316
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
11107
11317
  this.connected = false;
11108
11318
  if (!this.stopping) {
11109
- log.warn(TAG39, `Broadcast subscription ${status} — scheduling reconnect`);
11319
+ log.warn(TAG40, `Broadcast subscription ${status} — scheduling reconnect`);
11110
11320
  this.scheduleReconnect();
11111
11321
  }
11112
11322
  }
@@ -11125,7 +11335,7 @@ class Watcher {
11125
11335
  async reconnectBroadcast() {
11126
11336
  if (this.stopping || !this.supabase)
11127
11337
  return;
11128
- log.warn(TAG39, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
11338
+ log.warn(TAG40, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
11129
11339
  if (this.channel) {
11130
11340
  const old = this.channel;
11131
11341
  this.channel = null;
@@ -11155,10 +11365,10 @@ class Watcher {
11155
11365
  this.supabase = null;
11156
11366
  }
11157
11367
  this.connected = false;
11158
- log.info(TAG39, "Broadcast subscription stopped");
11368
+ log.info(TAG40, "Broadcast subscription stopped");
11159
11369
  }
11160
11370
  }
11161
- var TAG39 = "watcher";
11371
+ var TAG40 = "watcher";
11162
11372
  var init_watcher = __esm(() => {
11163
11373
  init_log();
11164
11374
  });
@@ -11171,7 +11381,7 @@ __export(exports_worktree_gc, {
11171
11381
  isTransientGitNetworkError: () => isTransientGitNetworkError,
11172
11382
  WorktreeGc: () => WorktreeGc
11173
11383
  });
11174
- import { execFileSync as execFileSync11 } from "node:child_process";
11384
+ import { execFileSync as execFileSync12 } from "node:child_process";
11175
11385
  import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
11176
11386
  import { resolve as resolve3 } from "node:path";
11177
11387
  function isTransientGitNetworkError(message) {
@@ -11239,16 +11449,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
11239
11449
  }
11240
11450
  }
11241
11451
  try {
11242
- execFileSync11("git", ["worktree", "prune", "--expire=now"], {
11452
+ execFileSync12("git", ["worktree", "prune", "--expire=now"], {
11243
11453
  cwd: repoRoot,
11244
11454
  stdio: "pipe"
11245
11455
  });
11246
11456
  } catch {}
11247
11457
  if (result.removed.length > 0) {
11248
- log.info(TAG40, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
11458
+ log.info(TAG41, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
11249
11459
  }
11250
11460
  if (result.errors.length > 0) {
11251
- log.warn(TAG40, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
11461
+ log.warn(TAG41, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
11252
11462
  }
11253
11463
  return result;
11254
11464
  }
@@ -11270,7 +11480,7 @@ function pruneFailedRemoteBranches(opts) {
11270
11480
  return result;
11271
11481
  }
11272
11482
  try {
11273
- execFileSync11("git", ["fetch", "--prune", "origin"], {
11483
+ execFileSync12("git", ["fetch", "--prune", "origin"], {
11274
11484
  cwd: repoRoot,
11275
11485
  stdio: "pipe",
11276
11486
  ...GIT_NETWORK_EXEC
@@ -11278,7 +11488,7 @@ function pruneFailedRemoteBranches(opts) {
11278
11488
  } catch (err) {
11279
11489
  const detail = gitErrorDetail2(err);
11280
11490
  if (isTransientGitNetworkError(detail)) {
11281
- log.debug(TAG40, `Remote branch GC skipped — remote unreachable: ${detail}`);
11491
+ log.debug(TAG41, `Remote branch GC skipped — remote unreachable: ${detail}`);
11282
11492
  return result;
11283
11493
  }
11284
11494
  result.errors.push({ ref: "fetch", error: detail });
@@ -11286,7 +11496,7 @@ function pruneFailedRemoteBranches(opts) {
11286
11496
  const refPattern = `refs/remotes/origin/${opts.prefix}*`;
11287
11497
  let listing = "";
11288
11498
  try {
11289
- listing = execFileSync11("git", [
11499
+ listing = execFileSync12("git", [
11290
11500
  "for-each-ref",
11291
11501
  "--format=%(refname:strip=3) %(committerdate:unix)",
11292
11502
  refPattern
@@ -11317,11 +11527,11 @@ function pruneFailedRemoteBranches(opts) {
11317
11527
  continue;
11318
11528
  }
11319
11529
  if (clock() > sweepDeadline) {
11320
- log.debug(TAG40, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
11530
+ log.debug(TAG41, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
11321
11531
  break;
11322
11532
  }
11323
11533
  try {
11324
- execFileSync11("git", ["push", "origin", `:refs/heads/${ref}`], {
11534
+ execFileSync12("git", ["push", "origin", `:refs/heads/${ref}`], {
11325
11535
  cwd: repoRoot,
11326
11536
  stdio: "pipe",
11327
11537
  ...GIT_NETWORK_EXEC
@@ -11330,17 +11540,17 @@ function pruneFailedRemoteBranches(opts) {
11330
11540
  } catch (err) {
11331
11541
  const detail = gitErrorDetail2(err);
11332
11542
  if (isTransientGitNetworkError(detail)) {
11333
- log.debug(TAG40, `Remote branch GC interrupted — remote unreachable: ${detail}`);
11543
+ log.debug(TAG41, `Remote branch GC interrupted — remote unreachable: ${detail}`);
11334
11544
  break;
11335
11545
  }
11336
11546
  result.errors.push({ ref, error: detail });
11337
11547
  }
11338
11548
  }
11339
11549
  if (result.removed.length > 0) {
11340
- log.info(TAG40, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
11550
+ log.info(TAG41, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
11341
11551
  }
11342
11552
  if (result.errors.length > 0) {
11343
- log.warn(TAG40, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
11553
+ log.warn(TAG41, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
11344
11554
  }
11345
11555
  return result;
11346
11556
  }
@@ -11371,27 +11581,27 @@ class WorktreeGc {
11371
11581
  try {
11372
11582
  runWorktreeGc(this.basePath, this.store);
11373
11583
  } catch (err) {
11374
- log.warn(TAG40, `GC tick failed: ${err instanceof Error ? err.message : err}`);
11584
+ log.warn(TAG41, `GC tick failed: ${err instanceof Error ? err.message : err}`);
11375
11585
  }
11376
11586
  if (this.remoteOpts) {
11377
11587
  try {
11378
11588
  pruneFailedRemoteBranches(this.remoteOpts);
11379
11589
  } catch (err) {
11380
- log.warn(TAG40, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
11590
+ log.warn(TAG41, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
11381
11591
  }
11382
11592
  }
11383
11593
  }
11384
11594
  }
11385
11595
  function getRepoRoot2() {
11386
11596
  try {
11387
- return execFileSync11("git", ["rev-parse", "--show-toplevel"], {
11597
+ return execFileSync12("git", ["rev-parse", "--show-toplevel"], {
11388
11598
  encoding: "utf-8"
11389
11599
  }).trim();
11390
11600
  } catch {
11391
11601
  return null;
11392
11602
  }
11393
11603
  }
11394
- var TAG40 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
11604
+ var TAG41 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
11395
11605
  var init_worktree_gc = __esm(() => {
11396
11606
  init_log();
11397
11607
  init_worktree();
@@ -11425,12 +11635,12 @@ __export(exports_src, {
11425
11635
  validatePrerequisites: () => validatePrerequisites,
11426
11636
  main: () => main
11427
11637
  });
11428
- import { execFileSync as execFileSync12 } from "node:child_process";
11638
+ import { execFileSync as execFileSync13 } from "node:child_process";
11429
11639
  import { randomUUID as randomUUID3 } from "node:crypto";
11430
11640
  import { createRequire as createRequire2 } from "node:module";
11431
11641
  async function validatePrerequisites(config, banner) {
11432
11642
  try {
11433
- const ver = execFileSync12("claude", ["--version"], {
11643
+ const ver = execFileSync13("claude", ["--version"], {
11434
11644
  encoding: "utf-8"
11435
11645
  }).trim();
11436
11646
  banner.check(`Claude CLI ${ver}`);
@@ -11445,14 +11655,14 @@ async function validatePrerequisites(config, banner) {
11445
11655
  validateGitProviderCli(provider);
11446
11656
  }
11447
11657
  try {
11448
- const status = execFileSync12("git", ["status", "--porcelain"], {
11658
+ const status = execFileSync13("git", ["status", "--porcelain"], {
11449
11659
  encoding: "utf-8"
11450
11660
  }).trim();
11451
11661
  if (status) {
11452
11662
  banner.warn(`Working directory has uncommitted changes:
11453
11663
  ${status}`);
11454
11664
  }
11455
- execFileSync12("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
11665
+ execFileSync13("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
11456
11666
  encoding: "utf-8",
11457
11667
  stdio: "pipe"
11458
11668
  });
@@ -11496,7 +11706,7 @@ async function main() {
11496
11706
  } catch (err) {
11497
11707
  if (err instanceof ConfigValidationError) {
11498
11708
  banner.fail();
11499
- log.error(TAG41, err.message);
11709
+ log.error(TAG42, err.message);
11500
11710
  process.exit(1);
11501
11711
  }
11502
11712
  throw err;
@@ -11506,7 +11716,7 @@ async function main() {
11506
11716
  } catch (err) {
11507
11717
  if (err instanceof ConfigValidationError) {
11508
11718
  banner.fail();
11509
- log.error(TAG41, err.message);
11719
+ log.error(TAG42, err.message);
11510
11720
  process.exit(1);
11511
11721
  }
11512
11722
  throw err;
@@ -11621,7 +11831,7 @@ async function main() {
11621
11831
  if (shuttingDown)
11622
11832
  return;
11623
11833
  shuttingDown = true;
11624
- log.info(TAG41, `Received ${signal}, shutting down gracefully...`);
11834
+ log.info(TAG42, `Received ${signal}, shutting down gracefully...`);
11625
11835
  reconciler.stop();
11626
11836
  mergeMonitor?.stop();
11627
11837
  worktreeGc.stop();
@@ -11632,18 +11842,18 @@ async function main() {
11632
11842
  }
11633
11843
  await watcher.stop();
11634
11844
  await pool.shutdown();
11635
- log.info(TAG41, "Daemon stopped.");
11845
+ log.info(TAG42, "Daemon stopped.");
11636
11846
  process.exit(exitCode);
11637
11847
  };
11638
11848
  process.on("SIGINT", () => shutdown("SIGINT"));
11639
11849
  process.on("SIGTERM", () => shutdown("SIGTERM"));
11640
11850
  process.on("uncaughtException", (err) => {
11641
- log.error(TAG41, `Uncaught exception: ${err.message}`);
11851
+ log.error(TAG42, `Uncaught exception: ${err.message}`);
11642
11852
  exitCode = 1;
11643
11853
  shutdown("uncaughtException");
11644
11854
  });
11645
11855
  process.on("unhandledRejection", (reason) => {
11646
- log.error(TAG41, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
11856
+ log.error(TAG42, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
11647
11857
  exitCode = 1;
11648
11858
  shutdown("unhandledRejection");
11649
11859
  });
@@ -11702,29 +11912,29 @@ async function handleBroadcast(event, client, pool, config, agentId) {
11702
11912
  if (assignedAgentId === undefined)
11703
11913
  return;
11704
11914
  if (assignedAgentId === agentId) {
11705
- log.info(TAG41, `Broadcast: card ${cardId} assigned to agent`);
11915
+ log.info(TAG42, `Broadcast: card ${cardId} assigned to agent`);
11706
11916
  try {
11707
11917
  await pool.resetAttemptsForReassign(cardId);
11708
11918
  await tryEnqueueCard(cardId, client, pool, config, agentId);
11709
11919
  } catch (err) {
11710
- log.error(TAG41, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
11920
+ log.error(TAG42, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
11711
11921
  }
11712
11922
  } else if (pool.isCardKnown(cardId)) {
11713
- log.info(TAG41, `Broadcast: card ${cardId} unassigned from agent`);
11923
+ log.info(TAG42, `Broadcast: card ${cardId} unassigned from agent`);
11714
11924
  await pool.removeCard(cardId);
11715
11925
  }
11716
11926
  }
11717
11927
  async function tryEnqueueCard(cardId, client, pool, config, agentId) {
11718
11928
  const { card } = await client.getCard(cardId);
11719
11929
  if (card.assigned_agent_id !== agentId) {
11720
- log.debug(TAG41, `Card ${cardId} no longer assigned to agent — skipping`);
11930
+ log.debug(TAG42, `Card ${cardId} no longer assigned to agent — skipping`);
11721
11931
  return;
11722
11932
  }
11723
11933
  const board = await client.getBoard(config.projectId, { summary: true });
11724
11934
  const columns = board.columns;
11725
11935
  const column = columns.find((c) => c.id === card.column_id);
11726
11936
  if (!column) {
11727
- log.warn(TAG41, `Column not found for card ${cardId}`);
11937
+ log.warn(TAG42, `Column not found for card ${cardId}`);
11728
11938
  return;
11729
11939
  }
11730
11940
  const route = classifyPickup(card, column.name, {
@@ -11733,31 +11943,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
11733
11943
  playbooks: config.agent.playbooks
11734
11944
  });
11735
11945
  if (!route) {
11736
- log.info(TAG41, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
11946
+ log.info(TAG42, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
11737
11947
  return;
11738
11948
  }
11739
11949
  if (route.stage) {
11740
- log.info(TAG41, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
11950
+ log.info(TAG42, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
11741
11951
  }
11742
11952
  const mode = route.mode;
11743
11953
  const labelMap = buildLabelMap(board.labels ?? []);
11744
11954
  const cardLabels = resolveCardLabels(card, labelMap);
11745
11955
  const subtasks = card.subtasks ?? [];
11746
11956
  if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
11747
- log.debug(TAG41, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
11957
+ log.debug(TAG42, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
11748
11958
  return;
11749
11959
  }
11750
11960
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
11751
- log.debug(TAG41, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
11961
+ log.debug(TAG42, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
11752
11962
  return;
11753
11963
  }
11754
11964
  if (mode === "review" && !qualifiesForAutoReview(card.description)) {
11755
- log.info(TAG41, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
11965
+ log.info(TAG42, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
11756
11966
  return;
11757
11967
  }
11758
11968
  await pool.enqueue(card, column, cardLabels, subtasks, mode);
11759
11969
  }
11760
- var TAG41 = "daemon", PKG_VERSION;
11970
+ var TAG42 = "daemon", PKG_VERSION;
11761
11971
  var init_src = __esm(() => {
11762
11972
  init_board_helpers();
11763
11973
  init_board_reviewer();