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