@gethmy/agent 1.23.2 → 1.25.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 +546 -204
  2. package/dist/index.js +546 -204
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -592,7 +592,6 @@ var init_agentStaleness = __esm(() => {
592
592
  SWEPT_SESSION_WRITE_GRACE_MS = 60 * 60 * 1000;
593
593
  ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
594
594
  });
595
-
596
595
  // ../harmony-shared/dist/branchRef.js
597
596
  function extractBranchRef(description) {
598
597
  if (!description)
@@ -655,6 +654,14 @@ var init_classification = __esm(() => {
655
654
  MODEL_TIERS = ["simple", "advanced", "research"];
656
655
  });
657
656
 
657
+ // ../harmony-shared/dist/columnSort.js
658
+ var init_columnSort = () => {};
659
+
660
+ // ../harmony-shared/dist/columnCardSplit.js
661
+ var init_columnCardSplit = __esm(() => {
662
+ init_columnSort();
663
+ });
664
+
658
665
  // ../harmony-shared/dist/commentSerializer.js
659
666
  function sanitizeHeaderField(value) {
660
667
  return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
@@ -835,7 +842,7 @@ function gateEvaluate(gateSpec, evidence) {
835
842
  findings: [
836
843
  {
837
844
  level: "error",
838
- message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
845
+ message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".${blockedDetail(safeStructured)}` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
839
846
  }
840
847
  ],
841
848
  structured: safeStructured
@@ -853,7 +860,7 @@ function gateEvaluate(gateSpec, evidence) {
853
860
  if (result === "blocked") {
854
861
  findings.unshift({
855
862
  level: "error",
856
- message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".`
863
+ message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".${blockedDetail(safeStructured)}`
857
864
  });
858
865
  return { passed: false, findings, structured: safeStructured };
859
866
  }
@@ -949,6 +956,15 @@ function evaluateCondition(raw, structured) {
949
956
  function isPlainObject(value) {
950
957
  return typeof value === "object" && value !== null && !Array.isArray(value);
951
958
  }
959
+ function blockedDetail(structured) {
960
+ const reason = structured.reason;
961
+ if (typeof reason !== "string")
962
+ return "";
963
+ const trimmed = reason.trim();
964
+ if (!trimmed)
965
+ return "";
966
+ return ` ${trimmed.length > MAX_REASON_CHARS ? `${trimmed.slice(0, MAX_REASON_CHARS)}…` : trimmed}`;
967
+ }
952
968
  function resolvePath(root, path) {
953
969
  const segments = path.split(".");
954
970
  let current = root;
@@ -1021,7 +1037,7 @@ function formatValue(value) {
1021
1037
  return "[unserializable]";
1022
1038
  }
1023
1039
  }
1024
- var GATE_KINDS, GATE_OPERATORS;
1040
+ var GATE_KINDS, GATE_OPERATORS, MAX_REASON_CHARS = 400;
1025
1041
  var init_gateEvaluate = __esm(() => {
1026
1042
  GATE_KINDS = [
1027
1043
  "build_green",
@@ -1432,6 +1448,8 @@ var init_dist = __esm(() => {
1432
1448
  init_branchRef();
1433
1449
  init_cardLinks();
1434
1450
  init_classification();
1451
+ init_columnCardSplit();
1452
+ init_columnSort();
1435
1453
  init_commentSerializer();
1436
1454
  init_constants();
1437
1455
  init_gateEvaluate();
@@ -1752,7 +1770,7 @@ function agentIdentifier(workerId) {
1752
1770
  function endStatusForCancel(reason) {
1753
1771
  return reason === "human_stop" ? "cancelled" : "paused";
1754
1772
  }
1755
- var DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
1773
+ var DEFAULT_METRIC_TIMEOUT_MS = 300000, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
1756
1774
  var init_types2 = __esm(() => {
1757
1775
  init_board_review();
1758
1776
  init_contract_phase();
@@ -1845,7 +1863,7 @@ var init_types2 = __esm(() => {
1845
1863
  worktreeGcIntervalMs: 5 * 60000
1846
1864
  },
1847
1865
  planning: DEFAULT_PLANNING_CONFIG,
1848
- playbooks: { enabled: true, humanStageColumns: [] },
1866
+ playbooks: { enabled: true, humanStageColumns: [], metrics: {} },
1849
1867
  contractFirst: DEFAULT_CONTRACT_CONFIG,
1850
1868
  boardReview: DEFAULT_BOARD_REVIEW_CONFIG
1851
1869
  };
@@ -5676,6 +5694,278 @@ var init_artifact_judge = __esm(() => {
5676
5694
  init_sdk_agent_runner();
5677
5695
  });
5678
5696
 
5697
+ // src/gate-config-error.ts
5698
+ function gateConfigErrorReason(evaluation) {
5699
+ if (!evaluation || evaluation.passed)
5700
+ return null;
5701
+ const structured = evaluation.structured;
5702
+ if (!structured || typeof structured !== "object")
5703
+ return null;
5704
+ if (!Object.hasOwn(structured, GATE_CONFIG_ERROR_KEY))
5705
+ return null;
5706
+ if (structured[GATE_CONFIG_ERROR_KEY] !== true) {
5707
+ return null;
5708
+ }
5709
+ const reason = structured.reason;
5710
+ return typeof reason === "string" && reason.trim().length > 0 ? reason.trim() : "the gate cannot be measured as configured";
5711
+ }
5712
+ var GATE_CONFIG_ERROR_KEY = "configError", GATE_CONFIG_ERROR_MARK;
5713
+ var init_gate_config_error = __esm(() => {
5714
+ GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
5715
+ });
5716
+
5717
+ // src/command-metric.ts
5718
+ function parseParseMode(parse) {
5719
+ if (typeof parse !== "string" || parse.length === 0) {
5720
+ return { kind: "invalid", reason: "`parse` must be a non-empty string" };
5721
+ }
5722
+ if (parse === "number")
5723
+ return { kind: "number" };
5724
+ if (parse.startsWith("json:")) {
5725
+ const path = parse.slice("json:".length).trim();
5726
+ if (!path) {
5727
+ return { kind: "invalid", reason: '`parse` "json:" is missing a path' };
5728
+ }
5729
+ return { kind: "json", path };
5730
+ }
5731
+ return {
5732
+ kind: "invalid",
5733
+ reason: `unknown \`parse\` mode "${parse}" (expected "number" or "json:<path>")`
5734
+ };
5735
+ }
5736
+ function parseMetricValue(mode, stdout) {
5737
+ if (mode.kind === "invalid")
5738
+ return { ok: false, reason: mode.reason };
5739
+ if (mode.kind === "number") {
5740
+ const trimmed = stdout.trim();
5741
+ if (!trimmed) {
5742
+ return {
5743
+ ok: false,
5744
+ reason: "command produced no output to read a number from"
5745
+ };
5746
+ }
5747
+ const value = Number(trimmed);
5748
+ if (!Number.isFinite(value)) {
5749
+ return {
5750
+ ok: false,
5751
+ reason: `command output is not a finite number: ${JSON.stringify(truncate(trimmed, 120))}`
5752
+ };
5753
+ }
5754
+ return { ok: true, value };
5755
+ }
5756
+ let doc;
5757
+ try {
5758
+ doc = JSON.parse(stdout);
5759
+ } catch (err) {
5760
+ const msg = err instanceof Error ? err.message : String(err);
5761
+ return { ok: false, reason: `command output is not valid JSON: ${msg}` };
5762
+ }
5763
+ const resolved = resolvePath(doc, mode.path);
5764
+ if (resolved === undefined) {
5765
+ return {
5766
+ ok: false,
5767
+ reason: `JSON path "${mode.path}" is absent in the command output`
5768
+ };
5769
+ }
5770
+ if (resolved !== null && typeof resolved !== "number" && typeof resolved !== "string" && typeof resolved !== "boolean") {
5771
+ return {
5772
+ ok: false,
5773
+ reason: `JSON path "${mode.path}" resolved to a ${Array.isArray(resolved) ? "array" : typeof resolved}, which no gate operator can compare`
5774
+ };
5775
+ }
5776
+ return { ok: true, value: resolved };
5777
+ }
5778
+ function runMetricCommand(args) {
5779
+ return new Promise((resolve3, reject) => {
5780
+ let child;
5781
+ try {
5782
+ child = spawnInGroup(args.command, args.args, {
5783
+ cwd: args.cwd,
5784
+ stdio: ["ignore", "pipe", "pipe"]
5785
+ });
5786
+ } catch (err) {
5787
+ reject(err);
5788
+ return;
5789
+ }
5790
+ const pgid = child.pid;
5791
+ const chunks = [];
5792
+ let stdoutBytes = 0;
5793
+ let stderr = "";
5794
+ let settled = false;
5795
+ let killReason = null;
5796
+ let timer;
5797
+ let drainTimer;
5798
+ const settle = (failure) => {
5799
+ if (settled)
5800
+ return;
5801
+ settled = true;
5802
+ if (timer)
5803
+ clearTimeout(timer);
5804
+ if (drainTimer)
5805
+ clearTimeout(drainTimer);
5806
+ reapGroup(pgid);
5807
+ if (failure)
5808
+ reject(failure);
5809
+ else
5810
+ resolve3(Buffer.concat(chunks).toString("utf8"));
5811
+ };
5812
+ const killTree = (reason) => {
5813
+ if (settled || killReason)
5814
+ return;
5815
+ killReason = reason;
5816
+ terminateGroup(child, {
5817
+ sigintTimeoutMs: METRIC_SIGINT_GRACE_MS,
5818
+ sigtermTimeoutMs: METRIC_SIGTERM_GRACE_MS
5819
+ }).catch(() => {}).then(() => {
5820
+ settle(reason === "timeout" ? Object.assign(new Error(`command timed out after ${args.timeoutMs}ms`), { code: "ETIMEDOUT" }) : Object.assign(new Error("stdout maxBuffer exceeded"), {
5821
+ code: "ENOBUFS"
5822
+ }));
5823
+ });
5824
+ };
5825
+ child.stdout?.on("data", (chunk) => {
5826
+ stdoutBytes += chunk.length;
5827
+ if (stdoutBytes > MAX_OUTPUT_BUFFER2) {
5828
+ killTree("overflow");
5829
+ return;
5830
+ }
5831
+ chunks.push(chunk);
5832
+ });
5833
+ child.stderr?.on("data", (chunk) => {
5834
+ if (stderr.length >= MAX_STDERR_CHARS)
5835
+ return;
5836
+ stderr += chunk.toString("utf8");
5837
+ });
5838
+ child.once("error", (err) => settle(err));
5839
+ const settleFromExit = (code, signal) => {
5840
+ if (drainTimer)
5841
+ clearTimeout(drainTimer);
5842
+ if (code === 0) {
5843
+ settle(null);
5844
+ return;
5845
+ }
5846
+ const detail = signal ? `terminated by signal ${signal}` : `exited ${code}`;
5847
+ settle(Object.assign(new Error(`command ${detail}`), {
5848
+ status: typeof code === "number" ? code : null,
5849
+ stderr
5850
+ }));
5851
+ };
5852
+ child.once("exit", (code, signal) => {
5853
+ if (killReason)
5854
+ return;
5855
+ if (timer)
5856
+ clearTimeout(timer);
5857
+ reapGroup(pgid);
5858
+ drainTimer = setTimeout(() => settleFromExit(code, signal), STDIO_DRAIN_GRACE_MS);
5859
+ child.once("close", () => settleFromExit(code, signal));
5860
+ });
5861
+ timer = setTimeout(() => killTree("timeout"), args.timeoutMs);
5862
+ });
5863
+ }
5864
+
5865
+ class CommandMetricCollector {
5866
+ deps;
5867
+ kind = "custom";
5868
+ constructor(deps) {
5869
+ this.deps = deps;
5870
+ }
5871
+ async collect(context) {
5872
+ const name = context.gate.metric;
5873
+ if (typeof name !== "string" || name.length === 0) {
5874
+ return configError(null, 'Gate kind "custom" needs a `metric` name naming an allowlisted command (e.g. { "kind": "custom", "metric": "lighthouse_performance" }).');
5875
+ }
5876
+ const def = Object.hasOwn(this.deps.metrics, name) ? this.deps.metrics[name] : undefined;
5877
+ if (!def) {
5878
+ return configError(name, `Metric "${name}" is not declared in this daemon's allowlist — add it under \`agent.playbooks.metrics\` to permit it.`);
5879
+ }
5880
+ if (typeof def.command !== "string" || def.command.length === 0) {
5881
+ return configError(name, `Metric "${name}" declares no \`command\`.`);
5882
+ }
5883
+ const mode = parseParseMode(def.parse);
5884
+ if (mode.kind === "invalid") {
5885
+ return configError(name, `Metric "${name}": ${mode.reason}.`);
5886
+ }
5887
+ const requested = typeof def.timeoutMs === "number" && def.timeoutMs > 0 ? Math.floor(def.timeoutMs) : DEFAULT_METRIC_TIMEOUT_MS;
5888
+ const timeoutMs = Math.min(requested, MAX_METRIC_TIMEOUT_MS);
5889
+ if (timeoutMs < requested) {
5890
+ log.warn(TAG19, `Metric "${name}" declares timeoutMs=${requested}, clamped to the ${MAX_METRIC_TIMEOUT_MS}ms ceiling`);
5891
+ }
5892
+ const run = this.deps.runCommand ?? runMetricCommand;
5893
+ let stdout;
5894
+ try {
5895
+ stdout = await run({
5896
+ command: def.command,
5897
+ args: def.args ?? [],
5898
+ cwd: this.deps.worktreePath,
5899
+ timeoutMs
5900
+ });
5901
+ } catch (err) {
5902
+ const reason = describeRunFailure(err, timeoutMs);
5903
+ log.warn(TAG19, `Metric "${name}" did not produce a measurement: ${reason}`);
5904
+ return blocked(name, reason);
5905
+ }
5906
+ const parsed = parseMetricValue(mode, stdout);
5907
+ if (!parsed.ok) {
5908
+ log.warn(TAG19, `Metric "${name}" output unusable: ${parsed.reason}`);
5909
+ return blocked(name, `Metric "${name}": ${parsed.reason}.`, stdout);
5910
+ }
5911
+ log.info(TAG19, `Metric "${name}" measured: ${JSON.stringify(parsed.value)}`);
5912
+ return {
5913
+ result: "passed",
5914
+ structured: {
5915
+ metric: name,
5916
+ value: parsed.value,
5917
+ raw: truncate(stdout, MAX_RAW_CHARS)
5918
+ }
5919
+ };
5920
+ }
5921
+ }
5922
+ function blocked(metric, reason, raw) {
5923
+ return {
5924
+ result: "blocked",
5925
+ structured: {
5926
+ metric,
5927
+ reason,
5928
+ ...raw === undefined ? {} : { raw: truncate(raw, MAX_RAW_CHARS) }
5929
+ }
5930
+ };
5931
+ }
5932
+ function configError(metric, reason) {
5933
+ const evidence = blocked(metric, reason);
5934
+ return {
5935
+ ...evidence,
5936
+ structured: { ...evidence.structured, ...GATE_CONFIG_ERROR_MARK }
5937
+ };
5938
+ }
5939
+ function describeRunFailure(err, timeoutMs) {
5940
+ const e = err;
5941
+ if (e?.code === "ENOBUFS") {
5942
+ return `command produced more than ${MAX_OUTPUT_BUFFER2} bytes of output (use a flag that prints only the metric, or parse a smaller report)`;
5943
+ }
5944
+ if (e?.code === "ETIMEDOUT" || e?.signal === "SIGTERM") {
5945
+ return `command timed out after ${timeoutMs}ms`;
5946
+ }
5947
+ if (e?.code === "ENOENT") {
5948
+ return "command not found on PATH";
5949
+ }
5950
+ const stderr = typeof e?.stderr === "string" ? e.stderr : e?.stderr instanceof Buffer ? e.stderr.toString("utf8") : "";
5951
+ const detail = stderr.trim() || String(e?.message ?? err);
5952
+ const status = typeof e?.status === "number" ? e.status : null;
5953
+ return status === null ? `command failed: ${truncate(detail, 400)}` : `command exited ${status}: ${truncate(detail, 400)}`;
5954
+ }
5955
+ function truncate(value, max) {
5956
+ return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
5957
+ }
5958
+ var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_STDERR_CHARS, MAX_METRIC_TIMEOUT_MS = 900000, METRIC_SIGINT_GRACE_MS = 2000, METRIC_SIGTERM_GRACE_MS = 3000, STDIO_DRAIN_GRACE_MS = 500;
5959
+ var init_command_metric = __esm(() => {
5960
+ init_dist();
5961
+ init_gate_config_error();
5962
+ init_log();
5963
+ init_process_group();
5964
+ init_types2();
5965
+ MAX_OUTPUT_BUFFER2 = 10 * 1024 * 1024;
5966
+ MAX_STDERR_CHARS = 64 * 1024;
5967
+ });
5968
+
5679
5969
  // src/gate-collectors.ts
5680
5970
  async function resolveStageGate(client, card) {
5681
5971
  const currentStage = card.current_stage;
@@ -5694,7 +5984,7 @@ async function resolveStageGate(client, card) {
5694
5984
  return null;
5695
5985
  return { stage: resolution.stage, gate };
5696
5986
  } catch (err) {
5697
- log.warn(TAG19, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
5987
+ log.warn(TAG20, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
5698
5988
  return null;
5699
5989
  }
5700
5990
  }
@@ -5714,6 +6004,9 @@ function normalizeGateSpec(gate) {
5714
6004
  const mode = gate.mode;
5715
6005
  if (mode === "all" || mode === "any")
5716
6006
  spec.mode = mode;
6007
+ const metric = gate.metric;
6008
+ if (typeof metric === "string" && metric.length > 0)
6009
+ spec.metric = metric;
5717
6010
  return spec;
5718
6011
  }
5719
6012
 
@@ -5811,12 +6104,15 @@ function buildGateCollectorRegistry(deps) {
5811
6104
  if (deps.artifact) {
5812
6105
  registry.artifact = new ArtifactCollector(deps.artifact);
5813
6106
  }
6107
+ if (deps.command) {
6108
+ registry.custom = new CommandMetricCollector(deps.command);
6109
+ }
5814
6110
  return registry;
5815
6111
  }
5816
6112
  async function collectGateEvidence(registry, context) {
5817
6113
  const collector = registry[context.gate.kind];
5818
6114
  if (!collector) {
5819
- log.info(TAG19, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
6115
+ log.info(TAG20, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
5820
6116
  return {
5821
6117
  result: "blocked",
5822
6118
  structured: {
@@ -5828,20 +6124,21 @@ async function collectGateEvidence(registry, context) {
5828
6124
  return await collector.collect(context);
5829
6125
  } catch (err) {
5830
6126
  const msg = err instanceof Error ? err.message : String(err);
5831
- log.warn(TAG19, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
6127
+ log.warn(TAG20, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
5832
6128
  return { result: "blocked", structured: { error: msg } };
5833
6129
  }
5834
6130
  }
5835
- var TAG19 = "gate-collectors";
6131
+ var TAG20 = "gate-collectors";
5836
6132
  var init_gate_collectors = __esm(() => {
5837
6133
  init_dist();
5838
6134
  init_artifact_judge();
6135
+ init_command_metric();
5839
6136
  init_log();
5840
6137
  init_verification();
5841
6138
  });
5842
6139
 
5843
6140
  // src/progress-tracker.ts
5844
- function truncate(str, max) {
6141
+ function truncate2(str, max) {
5845
6142
  return str.length > max ? `${str.slice(0, max - 3)}...` : str;
5846
6143
  }
5847
6144
 
@@ -5865,6 +6162,7 @@ class ProgressTracker {
5865
6162
  filesEdited = new Set;
5866
6163
  filesRead = new Set;
5867
6164
  lastCost = null;
6165
+ requestedModel = null;
5868
6166
  runEventSink = null;
5869
6167
  lastEmittedProgress = -1;
5870
6168
  lastAssistantText = "";
@@ -5882,6 +6180,9 @@ class ProgressTracker {
5882
6180
  setRunEventSink(sink) {
5883
6181
  this.runEventSink = sink;
5884
6182
  }
6183
+ setRequestedModel(model) {
6184
+ this.requestedModel = model;
6185
+ }
5885
6186
  attach(parser) {
5886
6187
  parser.on("tool_start", (name, input) => {
5887
6188
  this.onToolStart(name, input);
@@ -5952,7 +6253,7 @@ class ProgressTracker {
5952
6253
  }
5953
6254
  onToolStart(name, input) {
5954
6255
  this.toolCallCount++;
5955
- log.debug(TAG20, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
6256
+ log.debug(TAG21, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
5956
6257
  const filePath = this.extractString(input, "file_path");
5957
6258
  if (filePath) {
5958
6259
  if (EDIT_TOOLS.has(name)) {
@@ -6016,14 +6317,14 @@ class ProgressTracker {
6016
6317
  const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
6017
6318
  if (firstLine.length >= 10 && firstLine.length <= 200) {
6018
6319
  if (ACTION_PREFIX.test(firstLine)) {
6019
- this.lastAction = truncate(firstLine, MAX_TASK_LENGTH);
6320
+ this.lastAction = truncate2(firstLine, MAX_TASK_LENGTH);
6020
6321
  }
6021
6322
  }
6022
6323
  }
6023
6324
  transitionTo(newPhase) {
6024
6325
  if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
6025
6326
  return;
6026
- log.info(TAG20, `Phase: ${this.phase} → ${newPhase}`);
6327
+ log.info(TAG21, `Phase: ${this.phase} → ${newPhase}`);
6027
6328
  const previousPhase = this.phase;
6028
6329
  this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
6029
6330
  this.phase = newPhase;
@@ -6072,16 +6373,16 @@ class ProgressTracker {
6072
6373
  }
6073
6374
  case "Grep": {
6074
6375
  const pattern = this.extractString(input, "pattern");
6075
- return pattern ? `Searching for "${truncate(pattern, 40)}"` : "Searching code";
6376
+ return pattern ? `Searching for "${truncate2(pattern, 40)}"` : "Searching code";
6076
6377
  }
6077
6378
  case "Bash": {
6078
6379
  const cmd = this.extractString(input, "command");
6079
- return cmd ? `Running: ${truncate(cmd.split(`
6380
+ return cmd ? `Running: ${truncate2(cmd.split(`
6080
6381
  `)[0], 80)}` : "Running command";
6081
6382
  }
6082
6383
  case "Agent": {
6083
6384
  const desc = this.extractString(input, "description");
6084
- return desc ? `Sub-agent: ${truncate(desc, 60)}` : "Delegating to sub-agent";
6385
+ return desc ? `Sub-agent: ${truncate2(desc, 60)}` : "Delegating to sub-agent";
6085
6386
  }
6086
6387
  default: {
6087
6388
  if (name.startsWith("mcp__harmony__harmony_")) {
@@ -6125,12 +6426,12 @@ class ProgressTracker {
6125
6426
  }
6126
6427
  sendUpdate(currentTask) {
6127
6428
  this.lastUpdateAt = Date.now();
6128
- log.debug(TAG20, `Progress: ${this.progress}% — ${currentTask}`);
6429
+ log.debug(TAG21, `Progress: ${this.progress}% — ${currentTask}`);
6129
6430
  this.client.updateAgentProgress(this.cardId, {
6130
6431
  agentIdentifier: agentIdentifier(this.workerId),
6131
6432
  agentName: AGENT_NAME,
6132
6433
  status: "working",
6133
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
6434
+ currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
6134
6435
  progressPercent: this.progress,
6135
6436
  phase: this.phase,
6136
6437
  filesChanged: this.filesEdited.size,
@@ -6139,16 +6440,16 @@ class ProgressTracker {
6139
6440
  outputTokens: this.lastCost?.totalOutputTokens ?? 0,
6140
6441
  cacheCreationInputTokens: this.lastCost?.totalCacheCreationInputTokens ?? 0,
6141
6442
  cacheReadInputTokens: this.lastCost?.totalCacheReadInputTokens ?? 0,
6142
- modelName: this.lastCost?.modelName,
6443
+ modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
6143
6444
  numTurns: this.lastCost?.numTurns ?? 0
6144
6445
  }).catch((err) => {
6145
- log.warn(TAG20, `Failed to send progress update: ${err}`);
6446
+ log.warn(TAG21, `Failed to send progress update: ${err}`);
6146
6447
  });
6147
6448
  if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
6148
6449
  this.lastEmittedProgress = this.progress;
6149
6450
  this.runEventSink.recordProgress({
6150
6451
  progressPercent: this.progress,
6151
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
6452
+ currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
6152
6453
  phase: this.phase,
6153
6454
  filesChanged: this.filesEdited.size
6154
6455
  });
@@ -6161,7 +6462,7 @@ class ProgressTracker {
6161
6462
  this.heartbeatTimer = setTimeout(() => {
6162
6463
  if (!this.stopped) {
6163
6464
  const task = this.lastAction ? `Still working — ${this.lastAction}` : "Still working...";
6164
- this.sendUpdate(truncate(task, MAX_TASK_LENGTH));
6465
+ this.sendUpdate(truncate2(task, MAX_TASK_LENGTH));
6165
6466
  this.startHeartbeat();
6166
6467
  }
6167
6468
  }, HEARTBEAT_MS);
@@ -6173,7 +6474,7 @@ class ProgressTracker {
6173
6474
  return null;
6174
6475
  }
6175
6476
  }
6176
- var TAG20 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
6477
+ var TAG21 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
6177
6478
  var init_progress_tracker = __esm(() => {
6178
6479
  init_log();
6179
6480
  init_types2();
@@ -6329,7 +6630,7 @@ function parseReviewOutput(stdout) {
6329
6630
  try {
6330
6631
  const parsed = JSON.parse(raw);
6331
6632
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
6332
- log.debug(TAG21, "Parsed review output from fenced JSON block");
6633
+ log.debug(TAG22, "Parsed review output from fenced JSON block");
6333
6634
  return extractResult(parsed);
6334
6635
  }
6335
6636
  } catch {}
@@ -6355,21 +6656,21 @@ function parseReviewOutput(stdout) {
6355
6656
  try {
6356
6657
  const parsed = JSON.parse(candidates[i]);
6357
6658
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
6358
- log.debug(TAG21, "Parsed review output from raw JSON object");
6659
+ log.debug(TAG22, "Parsed review output from raw JSON object");
6359
6660
  return extractResult(parsed);
6360
6661
  }
6361
6662
  } catch {}
6362
6663
  }
6363
6664
  const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
6364
6665
  if (verdictMatch) {
6365
- log.warn(TAG21, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
6666
+ log.warn(TAG22, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
6366
6667
  return {
6367
6668
  verdict: verdictMatch[1].toLowerCase(),
6368
6669
  summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
6369
6670
  findings: []
6370
6671
  };
6371
6672
  }
6372
- log.warn(TAG21, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
6673
+ log.warn(TAG22, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
6373
6674
  return {
6374
6675
  verdict: "error",
6375
6676
  summary: stdout.slice(0, 500),
@@ -6402,7 +6703,7 @@ async function postReviewComment(client, card, commentType, body) {
6402
6703
  try {
6403
6704
  await client.addComment(card.id, body, { commentType });
6404
6705
  } catch (err) {
6405
- log.error(TAG21, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6706
+ log.error(TAG22, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6406
6707
  }
6407
6708
  }
6408
6709
  async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore, resolvedFromPrUrl) {
@@ -6416,11 +6717,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
6416
6717
  const currentCycle = getReviewCycle(freshDesc) + 1;
6417
6718
  const maxCycles = config.review.maxReviewCycles;
6418
6719
  if (result.verdict === "error") {
6419
- log.warn(TAG21, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
6720
+ log.warn(TAG22, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
6420
6721
  try {
6421
6722
  await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
6422
6723
  } catch (err) {
6423
- log.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
6724
+ log.warn(TAG22, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
6424
6725
  }
6425
6726
  if (config.review.postFindings) {
6426
6727
  const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
@@ -6463,7 +6764,7 @@ ${runLogTail}
6463
6764
  renameRemoteBranch(branchName, newRef, worktreePath);
6464
6765
  approvedBranch = newRef;
6465
6766
  } catch (err) {
6466
- log.warn(TAG21, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
6767
+ log.warn(TAG22, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
6467
6768
  }
6468
6769
  }
6469
6770
  if (config.review.createPR && approvedBranch) {
@@ -6484,14 +6785,14 @@ ${runLogTail}
6484
6785
  });
6485
6786
  }
6486
6787
  } catch (err) {
6487
- log.warn(TAG21, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
6788
+ log.warn(TAG22, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
6488
6789
  }
6489
6790
  }
6490
6791
  if (branchName) {
6491
6792
  try {
6492
6793
  await persistReviewedSha(client, card, worktreePath);
6493
6794
  } catch (err) {
6494
- log.warn(TAG21, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6795
+ log.warn(TAG22, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6495
6796
  }
6496
6797
  }
6497
6798
  if (config.review.postFindings) {
@@ -6513,7 +6814,7 @@ ${runLogTail}
6513
6814
  progressPercent: 100,
6514
6815
  ...buildTokenPayload(sessionStats)
6515
6816
  });
6516
- log.info(TAG21, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
6817
+ log.info(TAG22, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
6517
6818
  } else {
6518
6819
  const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
6519
6820
  const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
@@ -6521,7 +6822,7 @@ ${runLogTail}
6521
6822
  const linkedFindings = [...criticalFindings, ...majorFindings];
6522
6823
  const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
6523
6824
  if (currentCycle >= maxCycles) {
6524
- log.warn(TAG21, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
6825
+ log.warn(TAG22, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
6525
6826
  await moveCardToColumn(client, card, config.review.moveToColumn);
6526
6827
  const body = [
6527
6828
  "**Review — needs human review.**",
@@ -6561,7 +6862,7 @@ ${runLogTail}
6561
6862
  try {
6562
6863
  await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
6563
6864
  } catch (err) {
6564
- log.error(TAG21, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
6865
+ log.error(TAG22, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
6565
6866
  }
6566
6867
  }));
6567
6868
  if (linkedFindings.length > 0) {
@@ -6573,7 +6874,7 @@ ${runLogTail}
6573
6874
  try {
6574
6875
  await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
6575
6876
  } catch (err) {
6576
- log.error(TAG21, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
6877
+ log.error(TAG22, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
6577
6878
  }
6578
6879
  }));
6579
6880
  const baseDesc = stripReviewSummary(freshDesc);
@@ -6581,7 +6882,7 @@ ${runLogTail}
6581
6882
  try {
6582
6883
  await client.updateCard(card.id, { description: updatedDesc });
6583
6884
  } catch (err) {
6584
- log.error(TAG21, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
6885
+ log.error(TAG22, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
6585
6886
  }
6586
6887
  const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
6587
6888
  const body = [
@@ -6598,9 +6899,9 @@ ${runLogTail}
6598
6899
  if (config.planning.enabled && card.plan_id) {
6599
6900
  try {
6600
6901
  await client.updateCard(card.id, { needsPlanRefresh: true });
6601
- log.info(TAG21, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
6902
+ log.info(TAG22, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
6602
6903
  } catch (err) {
6603
- log.warn(TAG21, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6904
+ log.warn(TAG22, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6604
6905
  }
6605
6906
  }
6606
6907
  await moveCardToColumn(client, card, config.review.failColumn);
@@ -6614,10 +6915,10 @@ ${runLogTail}
6614
6915
  recoveryBranch
6615
6916
  });
6616
6917
  } catch (err) {
6617
- log.debug(TAG21, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
6918
+ log.debug(TAG22, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
6618
6919
  }
6619
6920
  if (recoveryBranch) {
6620
- log.info(TAG21, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
6921
+ log.info(TAG22, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
6621
6922
  }
6622
6923
  await client.endAgentSession(card.id, {
6623
6924
  status: "failed",
@@ -6626,7 +6927,7 @@ ${runLogTail}
6626
6927
  recoveryBranch,
6627
6928
  ...buildTokenPayload(sessionStats)
6628
6929
  });
6629
- log.info(TAG21, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
6930
+ log.info(TAG22, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
6630
6931
  }
6631
6932
  if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
6632
6933
  const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
@@ -6648,7 +6949,7 @@ ${runLogTail}
6648
6949
  cleanupWorktree(worktreePath, branchName);
6649
6950
  }
6650
6951
  }
6651
- var TAG21 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
6952
+ var TAG22 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
6652
6953
  **Review:`, RUN_LOG_TAIL_BYTES = 2048;
6653
6954
  var init_review_completion = __esm(() => {
6654
6955
  init_board_helpers();
@@ -6877,7 +7178,7 @@ class StateStore {
6877
7178
  const raw = readFileSync4(this.path, "utf-8");
6878
7179
  const parsed = JSON.parse(raw);
6879
7180
  if (parsed?.version !== SCHEMA_VERSION) {
6880
- log.warn(TAG22, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
7181
+ log.warn(TAG23, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
6881
7182
  return {
6882
7183
  version: SCHEMA_VERSION,
6883
7184
  daemonId: null,
@@ -6898,7 +7199,7 @@ class StateStore {
6898
7199
  daily: parsed.daily ?? []
6899
7200
  };
6900
7201
  } catch (err) {
6901
- log.error(TAG22, `failed to read state file: ${err instanceof Error ? err.message : err}`);
7202
+ log.error(TAG23, `failed to read state file: ${err instanceof Error ? err.message : err}`);
6902
7203
  return emptyState();
6903
7204
  }
6904
7205
  }
@@ -7087,7 +7388,7 @@ class StateStore {
7087
7388
  return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
7088
7389
  }
7089
7390
  }
7090
- var TAG22 = "state-store", SCHEMA_VERSION = 1;
7391
+ var TAG23 = "state-store", SCHEMA_VERSION = 1;
7091
7392
  var init_state_store = __esm(() => {
7092
7393
  init_log();
7093
7394
  });
@@ -7114,7 +7415,7 @@ function normalizeToolResultContent(raw) {
7114
7415
  return String(raw);
7115
7416
  }
7116
7417
  }
7117
- var TAG23 = "stream-parser", StreamParser;
7418
+ var TAG24 = "stream-parser", StreamParser;
7118
7419
  var init_stream_parser = __esm(() => {
7119
7420
  init_log();
7120
7421
  StreamParser = class StreamParser extends EventEmitter {
@@ -7162,14 +7463,14 @@ var init_stream_parser = __esm(() => {
7162
7463
  try {
7163
7464
  msg = JSON.parse(line);
7164
7465
  } catch {
7165
- log.debug(TAG23, `Non-JSON line: ${line.slice(0, 100)}`);
7466
+ log.debug(TAG24, `Non-JSON line: ${line.slice(0, 100)}`);
7166
7467
  return;
7167
7468
  }
7168
7469
  try {
7169
7470
  this.handleMessage(msg);
7170
7471
  } catch (err) {
7171
7472
  const errMsg = err instanceof Error ? err.message : String(err);
7172
- log.warn(TAG23, `Error handling stream event: ${errMsg}`);
7473
+ log.warn(TAG24, `Error handling stream event: ${errMsg}`);
7173
7474
  this.emit("parse_error", errMsg);
7174
7475
  }
7175
7476
  }
@@ -7255,7 +7556,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
7255
7556
  const msg2 = err instanceof Error ? err.message : String(err);
7256
7557
  if (i < attempts - 1) {
7257
7558
  const wait = backoffMs * 2 ** i;
7258
- log.warn(TAG24, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
7559
+ log.warn(TAG25, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
7259
7560
  await new Promise((r) => setTimeout(r, wait));
7260
7561
  }
7261
7562
  }
@@ -7278,10 +7579,10 @@ async function runTransition(client, card, plan, opts = {}) {
7278
7579
  if (opts.strictColumn) {
7279
7580
  throw new TransitionError("move", 1, msg);
7280
7581
  }
7281
- log.warn(TAG24, `#${shortId}: ${msg} — skipping move`);
7582
+ log.warn(TAG25, `#${shortId}: ${msg} — skipping move`);
7282
7583
  } else if (card.column_id !== target.id) {
7283
7584
  await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
7284
- log.info(TAG24, `#${shortId} → "${target.name}"`);
7585
+ log.info(TAG25, `#${shortId} → "${target.name}"`);
7285
7586
  card.column_id = target.id;
7286
7587
  moveLanded = true;
7287
7588
  } else {
@@ -7300,7 +7601,7 @@ async function runTransition(client, card, plan, opts = {}) {
7300
7601
  continue;
7301
7602
  await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
7302
7603
  existing.add(labelId);
7303
- log.info(TAG24, `#${shortId} +label "${name}"`);
7604
+ log.info(TAG25, `#${shortId} +label "${name}"`);
7304
7605
  }
7305
7606
  card.labelIds = Array.from(existing);
7306
7607
  }
@@ -7312,22 +7613,22 @@ async function runTransition(client, card, plan, opts = {}) {
7312
7613
  continue;
7313
7614
  await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
7314
7615
  existing.delete(match.id);
7315
- log.info(TAG24, `#${shortId} -label "${name}"`);
7616
+ log.info(TAG25, `#${shortId} -label "${name}"`);
7316
7617
  }
7317
7618
  card.labelIds = Array.from(existing);
7318
7619
  }
7319
7620
  if (plan.updateCard) {
7320
7621
  await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
7321
- log.info(TAG24, `#${shortId} updated`);
7622
+ log.info(TAG25, `#${shortId} updated`);
7322
7623
  }
7323
7624
  if (plan.endSession) {
7324
7625
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
7325
- log.info(TAG24, `#${shortId} session ended (${plan.endSession.status})`);
7626
+ log.info(TAG25, `#${shortId} session ended (${plan.endSession.status})`);
7326
7627
  }
7327
7628
  if (plan.assignAgent !== undefined) {
7328
7629
  const assignedAgentId = plan.assignAgent;
7329
7630
  await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
7330
- log.info(TAG24, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
7631
+ log.info(TAG25, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
7331
7632
  }
7332
7633
  if (opts.store && opts.runId) {
7333
7634
  try {
@@ -7340,11 +7641,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
7340
7641
  const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
7341
7642
  return result?.label?.id ?? null;
7342
7643
  } catch (err) {
7343
- log.warn(TAG24, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
7644
+ log.warn(TAG25, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
7344
7645
  return null;
7345
7646
  }
7346
7647
  }
7347
- var TAG24 = "transition", TransitionError;
7648
+ var TAG25 = "transition", TransitionError;
7348
7649
  var init_transitions = __esm(() => {
7349
7650
  init_log();
7350
7651
  TransitionError = class TransitionError extends Error {
@@ -7428,7 +7729,7 @@ class ReviewWorker {
7428
7729
  }
7429
7730
  }
7430
7731
  get tag() {
7431
- return `${TAG25}:${this.id}`;
7732
+ return `${TAG26}:${this.id}`;
7432
7733
  }
7433
7734
  get isIdle() {
7434
7735
  return this.state === "idle";
@@ -7499,7 +7800,8 @@ class ReviewWorker {
7499
7800
  agentId: this.identity.agentId,
7500
7801
  status: "working",
7501
7802
  currentTask: "Setting up review worktree",
7502
- progressPercent: 5
7803
+ progressPercent: 5,
7804
+ modelName: this.config.claude.reviewModel
7503
7805
  });
7504
7806
  this.sessionId = reviewSession && typeof reviewSession === "object" && "id" in reviewSession ? reviewSession.id ?? null : null;
7505
7807
  const labelPromise = addLabelByName(this.client, card, "agent", "#8b5cf6");
@@ -7600,6 +7902,7 @@ class ReviewWorker {
7600
7902
  this.cancel("timeout");
7601
7903
  }, this.config.review.maxTimeout);
7602
7904
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks);
7905
+ this.progressTracker.setRequestedModel(this.config.claude.reviewModel);
7603
7906
  const stdout = await this.spawnClaude(userPrompt, systemPrompt, this.progressTracker, card.short_id);
7604
7907
  this.lastSessionStats = this.progressTracker?.stats ?? null;
7605
7908
  this.progressTracker?.stop();
@@ -7891,7 +8194,7 @@ class ReviewWorker {
7891
8194
  this.lastSessionStats = null;
7892
8195
  }
7893
8196
  }
7894
- var TAG25 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
8197
+ var TAG26 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
7895
8198
  var init_review_worker = __esm(() => {
7896
8199
  init_dist();
7897
8200
  init_board_helpers();
@@ -7944,7 +8247,7 @@ class SleepGuard {
7944
8247
  if (!this.child.killed)
7945
8248
  this.child.kill("SIGTERM");
7946
8249
  this.child = null;
7947
- log.info(TAG26, "sleep assertion released");
8250
+ log.info(TAG27, "sleep assertion released");
7948
8251
  }
7949
8252
  }
7950
8253
  start() {
@@ -7959,7 +8262,7 @@ class SleepGuard {
7959
8262
  spawned = true;
7960
8263
  });
7961
8264
  child.on("error", (err) => {
7962
- log.warn(TAG26, `caffeinate unavailable: ${err.message}`);
8265
+ log.warn(TAG27, `caffeinate unavailable: ${err.message}`);
7963
8266
  if (this.child === child)
7964
8267
  this.child = null;
7965
8268
  });
@@ -7972,13 +8275,13 @@ class SleepGuard {
7972
8275
  });
7973
8276
  child.unref();
7974
8277
  this.child = child;
7975
- log.info(TAG26, "sleep assertion acquired (caffeinate -i)");
8278
+ log.info(TAG27, "sleep assertion acquired (caffeinate -i)");
7976
8279
  } catch (err) {
7977
- log.warn(TAG26, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
8280
+ log.warn(TAG27, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
7978
8281
  }
7979
8282
  }
7980
8283
  }
7981
- var TAG26 = "sleep-guard";
8284
+ var TAG27 = "sleep-guard";
7982
8285
  var init_sleep_guard = __esm(() => {
7983
8286
  init_log();
7984
8287
  });
@@ -7989,7 +8292,7 @@ async function fetchBlocksLinks(client, cardId) {
7989
8292
  const { links } = await client.getCardLinks(cardId);
7990
8293
  return links.filter((l) => l.link_type === "blocks");
7991
8294
  } catch (err) {
7992
- log.warn(TAG27, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
8295
+ log.warn(TAG28, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
7993
8296
  return null;
7994
8297
  }
7995
8298
  }
@@ -8021,27 +8324,27 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
8021
8324
  const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
8022
8325
  if (successors.length === 0)
8023
8326
  return;
8024
- log.info(TAG27, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
8327
+ log.info(TAG28, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
8025
8328
  for (const link of successors) {
8026
8329
  const successorId = link.target_card.id;
8027
8330
  try {
8028
8331
  const { card } = await deps.client.getCard(successorId);
8029
8332
  if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
8030
- log.info(TAG27, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
8333
+ log.info(TAG28, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
8031
8334
  await deps.client.updateCard(successorId, {
8032
8335
  assignedAgentId: deps.agentId
8033
8336
  });
8034
8337
  } else {
8035
- log.debug(TAG27, `successor #${card.short_id} assigned to different entity — skipping`);
8338
+ log.debug(TAG28, `successor #${card.short_id} assigned to different entity — skipping`);
8036
8339
  continue;
8037
8340
  }
8038
8341
  await deps.enqueue(successorId);
8039
8342
  } catch (err) {
8040
- log.warn(TAG27, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
8343
+ log.warn(TAG28, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
8041
8344
  }
8042
8345
  }
8043
8346
  }
8044
- var TAG27 = "unblock";
8347
+ var TAG28 = "unblock";
8045
8348
  var init_unblock = __esm(() => {
8046
8349
  init_log();
8047
8350
  });
@@ -8196,7 +8499,7 @@ class CliAgentRunner {
8196
8499
  events: batch
8197
8500
  });
8198
8501
  } catch (err) {
8199
- log.warn(TAG28, `Failed to flush run events: ${err}`);
8502
+ log.warn(TAG29, `Failed to flush run events: ${err}`);
8200
8503
  this.buffer.unshift(...batch);
8201
8504
  if (this.buffer.length > MAX_BUFFER) {
8202
8505
  this.buffer.length = MAX_BUFFER;
@@ -8233,7 +8536,7 @@ function mapCost(cost) {
8233
8536
  durationMs: cost.durationMs
8234
8537
  };
8235
8538
  }
8236
- var TAG28 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
8539
+ var TAG29 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
8237
8540
  var init_cli_agent_runner = __esm(() => {
8238
8541
  init_log();
8239
8542
  });
@@ -8266,11 +8569,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
8266
8569
  Do NOT push to main. All your work stays on \`${branchName}\`.
8267
8570
  The daemon owns the run lifecycle: once your work is committed it ends the agent session, pushes the branch, and moves the card to Review for you. Do NOT call harmony_end_agent_session, do NOT start a new session, and do NOT move the card or change its column yourself. If the skill driving this work tells you to move the card or end the session as a final step, SKIP it — it is handled for you (those tools are disabled for this run). Finish the implementation, commit, and stop.`
8268
8571
  });
8269
- log.info(TAG29, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
8572
+ log.info(TAG30, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
8270
8573
  return result.prompt + pastEpisodesSection;
8271
8574
  } catch (err) {
8272
8575
  const msg = err instanceof Error ? err.message : String(err);
8273
- log.warn(TAG29, `Failed to generate prompt via API, using fallback: ${msg}`);
8576
+ log.warn(TAG30, `Failed to generate prompt via API, using fallback: ${msg}`);
8274
8577
  const commentsSection = await renderCommentsSection(client, card.id);
8275
8578
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
8276
8579
  }
@@ -8288,7 +8591,7 @@ async function renderCommentsSection(client, cardId) {
8288
8591
 
8289
8592
  ${section}` : "";
8290
8593
  } catch (err) {
8291
- log.warn(TAG29, "comment-thread fetch failed", {
8594
+ log.warn(TAG30, "comment-thread fetch failed", {
8292
8595
  event: "comment_fetch_failed",
8293
8596
  error: err instanceof Error ? err.message : String(err)
8294
8597
  });
@@ -8338,7 +8641,7 @@ ${description}`.trim();
8338
8641
  ## Similar past tasks
8339
8642
  ${bullets}`;
8340
8643
  } catch (err) {
8341
- log.warn(TAG29, "past-episodes recall failed", {
8644
+ log.warn(TAG30, "past-episodes recall failed", {
8342
8645
  event: "episode_recall_failed",
8343
8646
  error: err instanceof Error ? err.message : String(err)
8344
8647
  });
@@ -8379,15 +8682,18 @@ ${subtaskStr}
8379
8682
  You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
8380
8683
  Do NOT push to main. All your work stays on \`${branchName}\`.`;
8381
8684
  }
8382
- var TAG29 = "prompt";
8685
+ var TAG30 = "prompt";
8383
8686
  var init_prompt = __esm(() => {
8384
8687
  init_dist();
8385
8688
  init_log();
8386
8689
  });
8387
8690
 
8388
8691
  // src/stage-advance.ts
8692
+ function gateKindOf(stage) {
8693
+ return stage.gate && typeof stage.gate === "object" ? String(stage.gate.kind ?? "gate") : "gate";
8694
+ }
8389
8695
  function gateSummary(stage, evaluation) {
8390
- const kind = stage.gate && typeof stage.gate === "object" ? String(stage.gate.kind ?? "gate") : "gate";
8696
+ const kind = gateKindOf(stage);
8391
8697
  if (evaluation.passed)
8392
8698
  return `${kind} passed`;
8393
8699
  const firstError = evaluation.findings.find((f) => f.level === "error");
@@ -8402,7 +8708,7 @@ async function resolveStageColumnName(client, card, stage) {
8402
8708
  const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
8403
8709
  return match ? match.name : null;
8404
8710
  } catch (err) {
8405
- log.warn(TAG30, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8711
+ log.warn(TAG31, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8406
8712
  return null;
8407
8713
  }
8408
8714
  }
@@ -8410,6 +8716,10 @@ async function persistStagePointer(client, card, body) {
8410
8716
  await client.request("POST", `/cards/${encodeURIComponent(card.id)}/advance-stage`, body);
8411
8717
  }
8412
8718
  async function advanceStageRun(card, stage, stageIndex, def, evaluation, deps) {
8719
+ const configErrorDetail = gateConfigErrorReason(evaluation);
8720
+ if (configErrorDetail) {
8721
+ return holdGateMisconfigured(card, stage, configErrorDetail, deps);
8722
+ }
8413
8723
  const loop = getStageLoop(stage);
8414
8724
  if (!isConvergeLoop(loop)) {
8415
8725
  if (!evaluation)
@@ -8418,6 +8728,28 @@ async function advanceStageRun(card, stage, stageIndex, def, evaluation, deps) {
8418
8728
  }
8419
8729
  return advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loop, deps);
8420
8730
  }
8731
+ async function holdGateMisconfigured(card, stage, detail, deps) {
8732
+ const reason = `Stage "${stage.name}" cannot be evaluated as configured — ${detail} Holding for a human: this is a configuration defect, so re-running the stage would reach the same result. No attempt was charged.`;
8733
+ deps.sink?.recordStageGateEvaluated({
8734
+ stageId: stage.id,
8735
+ gateId: gateKindOf(stage),
8736
+ verdict: "blocked",
8737
+ evidence: detail,
8738
+ summary: `${gateKindOf(stage)} misconfigured`
8739
+ });
8740
+ await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
8741
+ try {
8742
+ await deps.client.updateAgentProgress(card.id, {
8743
+ agentIdentifier: "claude-code-stage",
8744
+ agentName: "Harmony Agent",
8745
+ status: "waiting",
8746
+ currentTask: reason
8747
+ });
8748
+ } catch {}
8749
+ await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore);
8750
+ log.info(TAG31, `#${card.short_id} GateMisconfigured: ${reason}`);
8751
+ return { kind: "held_misconfigured", reason };
8752
+ }
8421
8753
  function firstErrorMessage(evaluation) {
8422
8754
  const e = evaluation?.findings.find((f) => f.level === "error");
8423
8755
  return e ? e.message : null;
@@ -8446,7 +8778,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8446
8778
  evidence,
8447
8779
  summary
8448
8780
  });
8449
- log.info(TAG30, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
8781
+ log.info(TAG31, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
8450
8782
  if (decision === "exit") {
8451
8783
  await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
8452
8784
  deps.sink?.recordLoopCompleted?.({
@@ -8488,7 +8820,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8488
8820
  await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
8489
8821
  keepAttempts: true
8490
8822
  });
8491
- log.info(TAG30, `#${card.short_id} LoopExhausted: ${reason}`);
8823
+ log.info(TAG31, `#${card.short_id} LoopExhausted: ${reason}`);
8492
8824
  return { kind: "held_gate_unmet", reason };
8493
8825
  }
8494
8826
  await deps.stateStore.decrementAttempt(card.id).catch(() => {});
@@ -8502,7 +8834,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
8502
8834
  addLabels: [{ name: AGENT_LABEL }],
8503
8835
  ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
8504
8836
  }, { store: deps.stateStore, runId: deps.runId });
8505
- log.info(TAG30, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
8837
+ log.info(TAG31, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
8506
8838
  return { kind: "requeued_gate_unmet", toColumn };
8507
8839
  }
8508
8840
  async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
@@ -8521,14 +8853,14 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
8521
8853
  });
8522
8854
  await deps.client.addComment(card.id, body, { commentType: "decision" });
8523
8855
  } catch (err) {
8524
- log.warn(TAG30, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8856
+ log.warn(TAG31, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8525
8857
  }
8526
8858
  }
8527
8859
  async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
8528
8860
  const summary = gateSummary(stage, evaluation);
8529
8861
  deps.sink?.recordStageGateEvaluated({
8530
8862
  stageId: stage.id,
8531
- gateId: stage.gate && typeof stage.gate === "object" ? String(stage.gate.kind ?? "gate") : "gate",
8863
+ gateId: gateKindOf(stage),
8532
8864
  verdict: evaluation.passed ? "passed" : "failed",
8533
8865
  evidence: evaluation.findings.map((f) => `[${f.level}] ${f.message}`).join(`
8534
8866
  `),
@@ -8552,7 +8884,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
8552
8884
  reason: "Playbook complete — final stage gate passed."
8553
8885
  });
8554
8886
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
8555
- log.info(TAG30, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
8887
+ log.info(TAG31, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
8556
8888
  return { kind: "completed_terminal" };
8557
8889
  }
8558
8890
  if (next.kind === "out_of_range") {
@@ -8584,7 +8916,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
8584
8916
  ...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
8585
8917
  }, { store: deps.stateStore, runId: deps.runId });
8586
8918
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
8587
- log.info(TAG30, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
8919
+ log.info(TAG31, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
8588
8920
  return { kind: "advanced", toStageId: next.stage.id, toColumn };
8589
8921
  }
8590
8922
  async function handleGateUnmet(card, stage, summary, deps) {
@@ -8603,7 +8935,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
8603
8935
  await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
8604
8936
  keepAttempts: true
8605
8937
  });
8606
- log.info(TAG30, `#${card.short_id} GateUnmetExhausted: ${reason}`);
8938
+ log.info(TAG31, `#${card.short_id} GateUnmetExhausted: ${reason}`);
8607
8939
  return { kind: "held_gate_unmet", reason };
8608
8940
  }
8609
8941
  const toColumn = await resolveStageColumnName(deps.client, card, stage) ?? deps.fallbackColumn;
@@ -8615,7 +8947,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
8615
8947
  addLabels: [{ name: AGENT_LABEL }],
8616
8948
  ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
8617
8949
  }, { store: deps.stateStore, runId: deps.runId });
8618
- log.info(TAG30, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
8950
+ log.info(TAG31, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
8619
8951
  return { kind: "requeued_gate_unmet", toColumn };
8620
8952
  }
8621
8953
  async function holdForHuman(client, card, reason, runId, stateStore, opts = {}) {
@@ -8635,12 +8967,13 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
8635
8967
  }
8636
8968
  }, { store: stateStore, runId });
8637
8969
  } catch (err) {
8638
- log.warn(TAG30, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8970
+ log.warn(TAG31, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
8639
8971
  }
8640
8972
  }
8641
- var TAG30 = "stage-advance", AGENT_LABEL = "agent";
8973
+ var TAG31 = "stage-advance", AGENT_LABEL = "agent";
8642
8974
  var init_stage_advance = __esm(() => {
8643
8975
  init_dist();
8976
+ init_gate_config_error();
8644
8977
  init_log();
8645
8978
  init_transitions();
8646
8979
  });
@@ -8785,7 +9118,7 @@ class Worker {
8785
9118
  }
8786
9119
  }
8787
9120
  get tag() {
8788
- return `${TAG31}:${this.id}`;
9121
+ return `${TAG32}:${this.id}`;
8789
9122
  }
8790
9123
  get isIdle() {
8791
9124
  return this.state === "idle";
@@ -8841,24 +9174,26 @@ class Worker {
8841
9174
  costCents: 0,
8842
9175
  numTurns: 0
8843
9176
  });
9177
+ const implementModel = this.selectImplementModel(card);
8844
9178
  const { session } = await this.client.startAgentSession(card.id, {
8845
9179
  agentIdentifier: agentIdentifier(this.id),
8846
9180
  agentName: AGENT_NAME,
8847
9181
  agentId: this.identity.agentId,
8848
9182
  status: "working",
8849
9183
  currentTask: "Setting up worktree",
8850
- progressPercent: 5
9184
+ progressPercent: 5,
9185
+ modelName: implementModel
8851
9186
  });
8852
9187
  const sid = session && typeof session === "object" && "id" in session ? session.id : null;
8853
9188
  if (!sid) {
8854
- log.warn(TAG31, "startAgentSession returned no session id");
9189
+ log.warn(TAG32, "startAgentSession returned no session id");
8855
9190
  }
8856
9191
  this.sessionId = sid;
8857
9192
  if (this.sessionId) {
8858
9193
  this.cliRunner = new CliAgentRunner(this.client, card.id, this.sessionId);
8859
9194
  this.cliRunner.recordRunStarted({
8860
9195
  runner: this.config.runner,
8861
- model: this.config.claude.model
9196
+ model: implementModel
8862
9197
  });
8863
9198
  }
8864
9199
  await this.recordPhase("preparing");
@@ -8947,7 +9282,7 @@ ${basePrompt}`;
8947
9282
  }, this.config.maxTimeout);
8948
9283
  this.activeRunSpawnOpts = computeRunSpawnGating(stageCtx.kind === "run" ? stageCtx.allowedTools : null);
8949
9284
  await this.spawnClaude(prompt, card, subtasks, {
8950
- model: this.selectImplementModel(card),
9285
+ model: implementModel,
8951
9286
  ...this.activeRunSpawnOpts ?? {}
8952
9287
  });
8953
9288
  if (this.aborted)
@@ -9308,6 +9643,10 @@ ${basePrompt}`;
9308
9643
  artifactType: stage.artifact_type
9309
9644
  },
9310
9645
  checklist: { subtasks, cardDone: card.done ?? false },
9646
+ command: {
9647
+ worktreePath,
9648
+ metrics: this.config.playbooks.metrics ?? {}
9649
+ },
9311
9650
  ...review ? { review } : {}
9312
9651
  });
9313
9652
  const context = {
@@ -9718,6 +10057,7 @@ ${basePrompt}`;
9718
10057
  });
9719
10058
  const parser = new StreamParser;
9720
10059
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
10060
+ this.progressTracker.setRequestedModel(model);
9721
10061
  this.progressTracker.attach(parser);
9722
10062
  this.cliRunner?.attach(parser);
9723
10063
  if (this.cliRunner) {
@@ -9798,6 +10138,7 @@ ${basePrompt}`;
9798
10138
  `);
9799
10139
  }
9800
10140
  this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
10141
+ this.progressTracker.setRequestedModel(model);
9801
10142
  if (this.cliRunner) {
9802
10143
  this.progressTracker.setRunEventSink(this.cliRunner);
9803
10144
  }
@@ -9906,7 +10247,7 @@ ${basePrompt}`;
9906
10247
  this.runTurns = 0;
9907
10248
  }
9908
10249
  }
9909
- var TAG31 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
10250
+ var TAG32 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
9910
10251
  var init_worker = __esm(() => {
9911
10252
  init_dist();
9912
10253
  init_board_helpers();
@@ -9984,41 +10325,41 @@ class Pool {
9984
10325
  }
9985
10326
  async enqueue(card, column, labels, subtasks, mode = "implement") {
9986
10327
  if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
9987
- log.debug(TAG32, `Card ${card.id} already queued, active, or reserved, skipping`);
10328
+ log.debug(TAG33, `Card ${card.id} already queued, active, or reserved, skipping`);
9988
10329
  return;
9989
10330
  }
9990
10331
  this.reservations.add(card.id);
9991
10332
  try {
9992
10333
  if (mode === "implement") {
9993
10334
  if (this.authPaused) {
9994
- log.debug(TAG32, `#${card.short_id} held — agent paused (auth error)`);
10335
+ log.debug(TAG33, `#${card.short_id} held — agent paused (auth error)`);
9995
10336
  await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
9996
10337
  return;
9997
10338
  }
9998
10339
  const cooldownMs = this.apiCooldownRemainingMs();
9999
10340
  if (cooldownMs > 0) {
10000
- log.debug(TAG32, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
10341
+ log.debug(TAG33, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
10001
10342
  await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
10002
10343
  return;
10003
10344
  }
10004
10345
  const decision = this.budget.check(card.id);
10005
10346
  if (!decision.allow) {
10006
10347
  if (decision.reason === "daily_budget") {
10007
- log.warn(TAG32, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
10348
+ log.warn(TAG33, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
10008
10349
  await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
10009
10350
  } else {
10010
- log.debug(TAG32, `#${card.short_id} gave up: ${decision.detail}`);
10351
+ log.debug(TAG33, `#${card.short_id} gave up: ${decision.detail}`);
10011
10352
  }
10012
10353
  return;
10013
10354
  }
10014
10355
  const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
10015
10356
  if (blockers === null) {
10016
- log.warn(TAG32, `#${card.short_id} blocker check failed — deferring to next tick`);
10357
+ log.warn(TAG33, `#${card.short_id} blocker check failed — deferring to next tick`);
10017
10358
  return;
10018
10359
  }
10019
10360
  if (blockers.length > 0) {
10020
10361
  const list = blockers.map((b) => `#${b.shortId}`).join(", ");
10021
- log.info(TAG32, `#${card.short_id} blocked by ${list} — waiting`);
10362
+ log.info(TAG33, `#${card.short_id} blocked by ${list} — waiting`);
10022
10363
  await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
10023
10364
  return;
10024
10365
  }
@@ -10050,7 +10391,7 @@ class Pool {
10050
10391
  });
10051
10392
  this.lastWaitingEmit.set(cardId, currentTask);
10052
10393
  } catch (err) {
10053
- log.debug(TAG32, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
10394
+ log.debug(TAG33, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
10054
10395
  }
10055
10396
  }
10056
10397
  noteApiError(err) {
@@ -10058,7 +10399,7 @@ class Pool {
10058
10399
  return;
10059
10400
  if (err.kind === "auth") {
10060
10401
  if (!this.authPaused) {
10061
- log.error(TAG32, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
10402
+ log.error(TAG33, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
10062
10403
  }
10063
10404
  this.authPaused = true;
10064
10405
  return;
@@ -10067,7 +10408,7 @@ class Pool {
10067
10408
  const until = Date.now() + cooldownMs;
10068
10409
  if (until > this.apiCooldownUntil) {
10069
10410
  this.apiCooldownUntil = until;
10070
- log.warn(TAG32, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
10411
+ log.warn(TAG33, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
10071
10412
  }
10072
10413
  }
10073
10414
  apiCooldownRemainingMs() {
@@ -10081,13 +10422,13 @@ class Pool {
10081
10422
  const removed = queue.remove(cardId);
10082
10423
  if (removed) {
10083
10424
  this.cardDataCache.delete(cardId);
10084
- log.info(TAG32, `Removed #${removed.shortId} from ${removed.mode} queue`);
10425
+ log.info(TAG33, `Removed #${removed.shortId} from ${removed.mode} queue`);
10085
10426
  return;
10086
10427
  }
10087
10428
  }
10088
10429
  const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
10089
10430
  if (worker) {
10090
- log.info(TAG32, `Cancelling worker ${worker.id} for card ${cardId}`);
10431
+ log.info(TAG33, `Cancelling worker ${worker.id} for card ${cardId}`);
10091
10432
  await worker.cancel("unassigned");
10092
10433
  }
10093
10434
  }
@@ -10120,10 +10461,10 @@ class Pool {
10120
10461
  async handleAgentCommand(cardId, command) {
10121
10462
  const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
10122
10463
  if (!worker) {
10123
- log.debug(TAG32, `No active worker for card ${cardId}, ignoring ${command}`);
10464
+ log.debug(TAG33, `No active worker for card ${cardId}, ignoring ${command}`);
10124
10465
  return;
10125
10466
  }
10126
- log.info(TAG32, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
10467
+ log.info(TAG33, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
10127
10468
  switch (command) {
10128
10469
  case "pause":
10129
10470
  await worker.pause();
@@ -10171,7 +10512,7 @@ class Pool {
10171
10512
  };
10172
10513
  }
10173
10514
  async shutdown() {
10174
- log.info(TAG32, "Shutting down pool...");
10515
+ log.info(TAG33, "Shutting down pool...");
10175
10516
  this.shuttingDown = true;
10176
10517
  const active = [
10177
10518
  ...this.implWorkers.filter((w) => w.isActive),
@@ -10179,7 +10520,7 @@ class Pool {
10179
10520
  ];
10180
10521
  await Promise.all(active.map((w) => w.cancel("shutdown")));
10181
10522
  this.sleepGuard.stop();
10182
- log.info(TAG32, "Pool shutdown complete");
10523
+ log.info(TAG33, "Pool shutdown complete");
10183
10524
  }
10184
10525
  reservations = new Set;
10185
10526
  cardDataCache = new Map;
@@ -10188,7 +10529,7 @@ class Pool {
10188
10529
  return false;
10189
10530
  const idle = workers.find((w) => w.isIdle);
10190
10531
  if (!idle) {
10191
- log.debug(TAG32, `No idle ${label} workers (queue: ${queue.length})`);
10532
+ log.debug(TAG33, `No idle ${label} workers (queue: ${queue.length})`);
10192
10533
  return false;
10193
10534
  }
10194
10535
  const next = queue.dequeue();
@@ -10196,18 +10537,18 @@ class Pool {
10196
10537
  return false;
10197
10538
  const data = this.cardDataCache.get(next.cardId);
10198
10539
  if (!data) {
10199
- log.warn(TAG32, `No cached data for card ${next.cardId}, skipping`);
10540
+ log.warn(TAG33, `No cached data for card ${next.cardId}, skipping`);
10200
10541
  return false;
10201
10542
  }
10202
10543
  this.cardDataCache.delete(next.cardId);
10203
10544
  this.lastWaitingEmit.delete(next.cardId);
10204
- log.info(TAG32, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
10545
+ log.info(TAG33, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
10205
10546
  this.sleepGuard.acquire();
10206
10547
  idle.run(data.card, data.column, data.labels, data.subtasks);
10207
10548
  return true;
10208
10549
  }
10209
10550
  }
10210
- var TAG32 = "pool";
10551
+ var TAG33 = "pool";
10211
10552
  var init_pool = __esm(() => {
10212
10553
  init_error_classifier();
10213
10554
  init_log();
@@ -10249,7 +10590,7 @@ function load(path) {
10249
10590
  return parsed;
10250
10591
  return {};
10251
10592
  } catch (err) {
10252
- log.warn(TAG33, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
10593
+ log.warn(TAG34, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
10253
10594
  return {};
10254
10595
  }
10255
10596
  }
@@ -10267,7 +10608,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
10267
10608
  registry[projectId] = { ...entry, updatedAt: Date.now() };
10268
10609
  save(path, registry);
10269
10610
  } catch (err) {
10270
- log.warn(TAG33, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10611
+ log.warn(TAG34, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10271
10612
  }
10272
10613
  }
10273
10614
  function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
@@ -10283,10 +10624,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
10283
10624
  delete registry[projectId];
10284
10625
  save(path, registry);
10285
10626
  } catch (err) {
10286
- log.warn(TAG33, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10627
+ log.warn(TAG34, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
10287
10628
  }
10288
10629
  }
10289
- var TAG33 = "port-registry";
10630
+ var TAG34 = "port-registry";
10290
10631
  var init_port_registry = __esm(() => {
10291
10632
  init_log();
10292
10633
  });
@@ -10307,7 +10648,7 @@ async function fetchCardSafely(client, cardId) {
10307
10648
  const { card } = await client.getCard(cardId);
10308
10649
  return card;
10309
10650
  } catch (err) {
10310
- log.warn(TAG34, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
10651
+ log.warn(TAG35, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
10311
10652
  return null;
10312
10653
  }
10313
10654
  }
@@ -10317,7 +10658,7 @@ async function recoverOrphans(store, client, config) {
10317
10658
  return [];
10318
10659
  }
10319
10660
  const outcomes = [];
10320
- log.info(TAG34, `recovering ${active.length} orphan run(s) from prior daemon`);
10661
+ log.info(TAG35, `recovering ${active.length} orphan run(s) from prior daemon`);
10321
10662
  for (const run of active) {
10322
10663
  const outcome = {
10323
10664
  runId: run.runId,
@@ -10329,11 +10670,11 @@ async function recoverOrphans(store, client, config) {
10329
10670
  };
10330
10671
  outcomes.push(outcome);
10331
10672
  if (isProcessAlive(run.daemonPid, process.pid)) {
10332
- log.warn(TAG34, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
10673
+ log.warn(TAG35, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
10333
10674
  outcome.actions.push("skipped: daemon pid still alive");
10334
10675
  continue;
10335
10676
  }
10336
- log.info(TAG34, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
10677
+ log.info(TAG35, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
10337
10678
  await recoverRun(run, store, client, config, outcome, {
10338
10679
  rollbackAttempt: true
10339
10680
  });
@@ -10353,7 +10694,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
10353
10694
  } catch (err) {
10354
10695
  const msg = err instanceof Error ? err.message : String(err);
10355
10696
  outcome.errors.push(`endAgentSession: ${msg}`);
10356
- log.warn(TAG34, `endAgentSession failed for ${run.cardId}: ${msg}`);
10697
+ log.warn(TAG35, `endAgentSession failed for ${run.cardId}: ${msg}`);
10357
10698
  }
10358
10699
  const card = await fetchCardSafely(client, run.cardId);
10359
10700
  if (card) {
@@ -10405,9 +10746,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
10405
10746
  outcome.errors.push(`decrementAttempt: ${msg}`);
10406
10747
  }
10407
10748
  }
10408
- log.info(TAG34, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
10749
+ log.info(TAG35, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
10409
10750
  }
10410
- var TAG34 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
10751
+ var TAG35 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
10411
10752
  var init_recovery = __esm(() => {
10412
10753
  init_board_helpers();
10413
10754
  init_log();
@@ -10418,14 +10759,14 @@ var init_recovery = __esm(() => {
10418
10759
  async function claimReviewCard(client, cardId, agentId) {
10419
10760
  try {
10420
10761
  const { claimed } = await client.claimCard(cardId, agentId);
10421
- log.debug(TAG35, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
10762
+ log.debug(TAG36, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
10422
10763
  return claimed;
10423
10764
  } catch (err) {
10424
- log.error(TAG35, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
10765
+ log.error(TAG36, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
10425
10766
  return false;
10426
10767
  }
10427
10768
  }
10428
- var TAG35 = "claim";
10769
+ var TAG36 = "claim";
10429
10770
  var init_claim = __esm(() => {
10430
10771
  init_log();
10431
10772
  });
@@ -10478,22 +10819,22 @@ async function reclaimPreReviewStrands(opts) {
10478
10819
  continue;
10479
10820
  const won = await claimReviewCard(client, card.id, agentId);
10480
10821
  if (!won) {
10481
- log.debug(TAG36, `#${card.short_id} — lost the review claim race, skipping`);
10822
+ log.debug(TAG37, `#${card.short_id} — lost the review claim race, skipping`);
10482
10823
  continue;
10483
10824
  }
10484
- log.warn(TAG36, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
10825
+ log.warn(TAG37, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
10485
10826
  reclaimed.push(card.id);
10486
10827
  if (opts.onClaimed) {
10487
10828
  try {
10488
10829
  await opts.onClaimed(card);
10489
10830
  } catch (err) {
10490
- log.error(TAG36, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
10831
+ log.error(TAG37, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
10491
10832
  }
10492
10833
  }
10493
10834
  }
10494
10835
  return reclaimed;
10495
10836
  }
10496
- var TAG36 = "strand-recovery";
10837
+ var TAG37 = "strand-recovery";
10497
10838
  var init_strand_recovery = __esm(() => {
10498
10839
  init_board_helpers();
10499
10840
  init_claim();
@@ -10545,7 +10886,7 @@ class Reconciler {
10545
10886
  clearInterval(this.timer);
10546
10887
  this.timer = null;
10547
10888
  }
10548
- log.info(TAG37, "Heartbeat stopped");
10889
+ log.info(TAG38, "Heartbeat stopped");
10549
10890
  }
10550
10891
  async recoverStaleRuns() {
10551
10892
  if (!this.stateStore || !this.agentConfig)
@@ -10562,7 +10903,7 @@ class Reconciler {
10562
10903
  if (!daemonDead && !(heartbeatStale && ourZombie))
10563
10904
  continue;
10564
10905
  const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
10565
- log.warn(TAG37, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
10906
+ log.warn(TAG38, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
10566
10907
  await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
10567
10908
  runId: run.runId,
10568
10909
  cardId: run.cardId,
@@ -10589,11 +10930,11 @@ class Reconciler {
10589
10930
  const stalledAt = Date.parse(card.updated_at ?? "");
10590
10931
  if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
10591
10932
  continue;
10592
- log.warn(TAG37, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
10933
+ log.warn(TAG38, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
10593
10934
  try {
10594
10935
  await this.client.moveCard(card.id, pickupCol.id);
10595
10936
  } catch (err) {
10596
- log.error(TAG37, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10937
+ log.error(TAG38, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10597
10938
  }
10598
10939
  }
10599
10940
  }
@@ -10625,7 +10966,7 @@ class Reconciler {
10625
10966
  return;
10626
10967
  const cardLabels = resolveCardLabels(card, labelMap);
10627
10968
  const subtasks = card.subtasks ?? [];
10628
- log.info(TAG37, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
10969
+ log.info(TAG38, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
10629
10970
  await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
10630
10971
  }
10631
10972
  });
@@ -10649,11 +10990,11 @@ class Reconciler {
10649
10990
  const parkedAt = Date.parse(card.updated_at ?? "");
10650
10991
  if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
10651
10992
  continue;
10652
- log.warn(TAG37, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
10993
+ log.warn(TAG38, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
10653
10994
  try {
10654
10995
  await this.client.moveCard(card.id, pickupCol.id);
10655
10996
  } catch (err) {
10656
- log.error(TAG37, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10997
+ log.error(TAG38, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
10657
10998
  }
10658
10999
  }
10659
11000
  }
@@ -10673,7 +11014,8 @@ class Reconciler {
10673
11014
  reviewColumns: this.reviewColumns,
10674
11015
  playbooks: this.agentConfig?.playbooks ?? {
10675
11016
  enabled: false,
10676
- humanStageColumns: []
11017
+ humanStageColumns: [],
11018
+ metrics: {}
10677
11019
  }
10678
11020
  };
10679
11021
  const assignedCards = cards.filter((c) => {
@@ -10696,21 +11038,21 @@ class Reconciler {
10696
11038
  const subtasks = card.subtasks ?? [];
10697
11039
  const mode = route.mode;
10698
11040
  if (route.stage) {
10699
- log.info(TAG37, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
11041
+ log.info(TAG38, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
10700
11042
  }
10701
11043
  if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
10702
- log.debug(TAG37, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
11044
+ log.debug(TAG38, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
10703
11045
  continue;
10704
11046
  }
10705
11047
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
10706
- log.debug(TAG37, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
11048
+ log.debug(TAG38, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
10707
11049
  continue;
10708
11050
  }
10709
11051
  if (mode === "review" && !qualifiesForAutoReview(card.description)) {
10710
- log.debug(TAG37, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
11052
+ log.debug(TAG38, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
10711
11053
  continue;
10712
11054
  }
10713
- log.info(TAG37, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
11055
+ log.info(TAG38, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
10714
11056
  await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
10715
11057
  }
10716
11058
  }
@@ -10721,18 +11063,18 @@ class Reconciler {
10721
11063
  await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
10722
11064
  for (const knownId of knownCardIds) {
10723
11065
  if (!allAgentCardIds.has(knownId)) {
10724
- log.info(TAG37, `Missed unassign: ${knownId} — removing`);
11066
+ log.info(TAG38, `Missed unassign: ${knownId} — removing`);
10725
11067
  await this.pool.removeCard(knownId);
10726
11068
  }
10727
11069
  }
10728
11070
  await this.releaseStalledApprovals(cards, columns, knownCardIds);
10729
- log.debug(TAG37, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
11071
+ log.debug(TAG38, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
10730
11072
  } catch (err) {
10731
- log.error(TAG37, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
11073
+ log.error(TAG38, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
10732
11074
  }
10733
11075
  }
10734
11076
  }
10735
- var TAG37 = "reconcile";
11077
+ var TAG38 = "reconcile";
10736
11078
  var init_reconcile = __esm(() => {
10737
11079
  init_board_helpers();
10738
11080
  init_git_pr();
@@ -10772,7 +11114,7 @@ function prettyBanner(config, version) {
10772
11114
  checks.push({ kind: "ok", message });
10773
11115
  },
10774
11116
  warn(message) {
10775
- log.warn(TAG38, message);
11117
+ log.warn(TAG39, message);
10776
11118
  checks.push({ kind: "warn", message: message.split(`
10777
11119
  `, 1)[0] });
10778
11120
  },
@@ -10797,25 +11139,25 @@ function prettyBanner(config, version) {
10797
11139
  };
10798
11140
  }
10799
11141
  function jsonBanner(config, version) {
10800
- log.info(TAG38, `Harmony Agent Daemon v${version} starting...`);
10801
- log.info(TAG38, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
11142
+ log.info(TAG39, `Harmony Agent Daemon v${version} starting...`);
11143
+ log.info(TAG39, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
10802
11144
  if (config.agent.review.enabled) {
10803
- log.info(TAG38, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
11145
+ log.info(TAG39, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
10804
11146
  }
10805
11147
  let failed = false;
10806
11148
  return {
10807
11149
  setProjectName(_name) {},
10808
11150
  setGitProvider(provider) {
10809
- log.info(TAG38, `Git provider: ${provider}`);
11151
+ log.info(TAG39, `Git provider: ${provider}`);
10810
11152
  },
10811
11153
  setHttpPort(port) {
10812
- log.info(TAG38, `HTTP server on port ${port}`);
11154
+ log.info(TAG39, `HTTP server on port ${port}`);
10813
11155
  },
10814
11156
  check(message) {
10815
- log.info(TAG38, message);
11157
+ log.info(TAG39, message);
10816
11158
  },
10817
11159
  warn(message) {
10818
- log.warn(TAG38, message);
11160
+ log.warn(TAG39, message);
10819
11161
  },
10820
11162
  fail() {
10821
11163
  failed = true;
@@ -10823,7 +11165,7 @@ function jsonBanner(config, version) {
10823
11165
  async ready(message) {
10824
11166
  if (failed)
10825
11167
  return;
10826
- log.info(TAG38, message);
11168
+ log.info(TAG39, message);
10827
11169
  }
10828
11170
  };
10829
11171
  }
@@ -10904,7 +11246,7 @@ function cyan(s) {
10904
11246
  function yellow(s) {
10905
11247
  return `${ANSI.yellow}${s}${ANSI.reset}`;
10906
11248
  }
10907
- var TAG38 = "daemon", RULE_WIDTH = 70, ANSI;
11249
+ var TAG39 = "daemon", RULE_WIDTH = 70, ANSI;
10908
11250
  var init_startup_banner = __esm(() => {
10909
11251
  init_log();
10910
11252
  ANSI = {
@@ -11055,13 +11397,13 @@ class Watcher {
11055
11397
  }
11056
11398
  async start() {
11057
11399
  if (!isPretty()) {
11058
- log.info(TAG39, "Connecting to Supabase realtime (broadcast)...");
11400
+ log.info(TAG40, "Connecting to Supabase realtime (broadcast)...");
11059
11401
  }
11060
11402
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
11061
11403
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
11062
11404
  this.subscribeBroadcast();
11063
11405
  presenceChannel.on("presence", { event: "sync" }, () => {
11064
- log.debug(TAG39, "Presence sync");
11406
+ log.debug(TAG40, "Presence sync");
11065
11407
  }).subscribe(async (status) => {
11066
11408
  if (status === "SUBSCRIBED") {
11067
11409
  await presenceChannel.track({
@@ -11074,7 +11416,7 @@ class Watcher {
11074
11416
  agentName: this.identity.agentName
11075
11417
  });
11076
11418
  if (!isPretty() || !this.suppressStartupLogs) {
11077
- log.info(TAG39, "Presence tracked on board-presence channel");
11419
+ log.info(TAG40, "Presence tracked on board-presence channel");
11078
11420
  }
11079
11421
  this.presenceTracked = true;
11080
11422
  this.maybeResolveReady();
@@ -11087,13 +11429,13 @@ class Watcher {
11087
11429
  return;
11088
11430
  const gen = ++this.broadcastGen;
11089
11431
  this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
11090
- log.debug(TAG39, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
11432
+ log.debug(TAG40, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
11091
11433
  this.onCardBroadcast({
11092
11434
  event: "card_update",
11093
11435
  payload: msg.payload ?? {}
11094
11436
  });
11095
11437
  }).on("broadcast", { event: "card_created" }, (msg) => {
11096
- log.debug(TAG39, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
11438
+ log.debug(TAG40, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
11097
11439
  this.onCardBroadcast({
11098
11440
  event: "card_created",
11099
11441
  payload: msg.payload ?? {}
@@ -11103,7 +11445,7 @@ class Watcher {
11103
11445
  const cardId = payload.card_id;
11104
11446
  const command = payload.command;
11105
11447
  if (cardId && command) {
11106
- log.info(TAG39, `Broadcast: agent_command ${command} for ${cardId}`);
11448
+ log.info(TAG40, `Broadcast: agent_command ${command} for ${cardId}`);
11107
11449
  this.onAgentCommand?.({ cardId, command });
11108
11450
  }
11109
11451
  }).subscribe((status) => {
@@ -11113,13 +11455,13 @@ class Watcher {
11113
11455
  this.connected = true;
11114
11456
  this.reconnectAttempts = 0;
11115
11457
  if (!isPretty() || !this.suppressStartupLogs) {
11116
- log.info(TAG39, "Broadcast subscription active");
11458
+ log.info(TAG40, "Broadcast subscription active");
11117
11459
  }
11118
11460
  this.maybeResolveReady();
11119
11461
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
11120
11462
  this.connected = false;
11121
11463
  if (!this.stopping) {
11122
- log.warn(TAG39, `Broadcast subscription ${status} — scheduling reconnect`);
11464
+ log.warn(TAG40, `Broadcast subscription ${status} — scheduling reconnect`);
11123
11465
  this.scheduleReconnect();
11124
11466
  }
11125
11467
  }
@@ -11138,7 +11480,7 @@ class Watcher {
11138
11480
  async reconnectBroadcast() {
11139
11481
  if (this.stopping || !this.supabase)
11140
11482
  return;
11141
- log.warn(TAG39, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
11483
+ log.warn(TAG40, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
11142
11484
  if (this.channel) {
11143
11485
  const old = this.channel;
11144
11486
  this.channel = null;
@@ -11168,10 +11510,10 @@ class Watcher {
11168
11510
  this.supabase = null;
11169
11511
  }
11170
11512
  this.connected = false;
11171
- log.info(TAG39, "Broadcast subscription stopped");
11513
+ log.info(TAG40, "Broadcast subscription stopped");
11172
11514
  }
11173
11515
  }
11174
- var TAG39 = "watcher";
11516
+ var TAG40 = "watcher";
11175
11517
  var init_watcher = __esm(() => {
11176
11518
  init_log();
11177
11519
  });
@@ -11258,10 +11600,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
11258
11600
  });
11259
11601
  } catch {}
11260
11602
  if (result.removed.length > 0) {
11261
- log.info(TAG40, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
11603
+ log.info(TAG41, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
11262
11604
  }
11263
11605
  if (result.errors.length > 0) {
11264
- log.warn(TAG40, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
11606
+ log.warn(TAG41, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
11265
11607
  }
11266
11608
  return result;
11267
11609
  }
@@ -11291,7 +11633,7 @@ function pruneFailedRemoteBranches(opts) {
11291
11633
  } catch (err) {
11292
11634
  const detail = gitErrorDetail2(err);
11293
11635
  if (isTransientGitNetworkError(detail)) {
11294
- log.debug(TAG40, `Remote branch GC skipped — remote unreachable: ${detail}`);
11636
+ log.debug(TAG41, `Remote branch GC skipped — remote unreachable: ${detail}`);
11295
11637
  return result;
11296
11638
  }
11297
11639
  result.errors.push({ ref: "fetch", error: detail });
@@ -11330,7 +11672,7 @@ function pruneFailedRemoteBranches(opts) {
11330
11672
  continue;
11331
11673
  }
11332
11674
  if (clock() > sweepDeadline) {
11333
- log.debug(TAG40, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
11675
+ log.debug(TAG41, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
11334
11676
  break;
11335
11677
  }
11336
11678
  try {
@@ -11343,17 +11685,17 @@ function pruneFailedRemoteBranches(opts) {
11343
11685
  } catch (err) {
11344
11686
  const detail = gitErrorDetail2(err);
11345
11687
  if (isTransientGitNetworkError(detail)) {
11346
- log.debug(TAG40, `Remote branch GC interrupted — remote unreachable: ${detail}`);
11688
+ log.debug(TAG41, `Remote branch GC interrupted — remote unreachable: ${detail}`);
11347
11689
  break;
11348
11690
  }
11349
11691
  result.errors.push({ ref, error: detail });
11350
11692
  }
11351
11693
  }
11352
11694
  if (result.removed.length > 0) {
11353
- log.info(TAG40, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
11695
+ log.info(TAG41, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
11354
11696
  }
11355
11697
  if (result.errors.length > 0) {
11356
- log.warn(TAG40, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
11698
+ log.warn(TAG41, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
11357
11699
  }
11358
11700
  return result;
11359
11701
  }
@@ -11384,13 +11726,13 @@ class WorktreeGc {
11384
11726
  try {
11385
11727
  runWorktreeGc(this.basePath, this.store);
11386
11728
  } catch (err) {
11387
- log.warn(TAG40, `GC tick failed: ${err instanceof Error ? err.message : err}`);
11729
+ log.warn(TAG41, `GC tick failed: ${err instanceof Error ? err.message : err}`);
11388
11730
  }
11389
11731
  if (this.remoteOpts) {
11390
11732
  try {
11391
11733
  pruneFailedRemoteBranches(this.remoteOpts);
11392
11734
  } catch (err) {
11393
- log.warn(TAG40, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
11735
+ log.warn(TAG41, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
11394
11736
  }
11395
11737
  }
11396
11738
  }
@@ -11404,7 +11746,7 @@ function getRepoRoot2() {
11404
11746
  return null;
11405
11747
  }
11406
11748
  }
11407
- var TAG40 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
11749
+ var TAG41 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
11408
11750
  var init_worktree_gc = __esm(() => {
11409
11751
  init_log();
11410
11752
  init_worktree();
@@ -11509,7 +11851,7 @@ async function main() {
11509
11851
  } catch (err) {
11510
11852
  if (err instanceof ConfigValidationError) {
11511
11853
  banner.fail();
11512
- log.error(TAG41, err.message);
11854
+ log.error(TAG42, err.message);
11513
11855
  process.exit(1);
11514
11856
  }
11515
11857
  throw err;
@@ -11519,7 +11861,7 @@ async function main() {
11519
11861
  } catch (err) {
11520
11862
  if (err instanceof ConfigValidationError) {
11521
11863
  banner.fail();
11522
- log.error(TAG41, err.message);
11864
+ log.error(TAG42, err.message);
11523
11865
  process.exit(1);
11524
11866
  }
11525
11867
  throw err;
@@ -11634,7 +11976,7 @@ async function main() {
11634
11976
  if (shuttingDown)
11635
11977
  return;
11636
11978
  shuttingDown = true;
11637
- log.info(TAG41, `Received ${signal}, shutting down gracefully...`);
11979
+ log.info(TAG42, `Received ${signal}, shutting down gracefully...`);
11638
11980
  reconciler.stop();
11639
11981
  mergeMonitor?.stop();
11640
11982
  worktreeGc.stop();
@@ -11645,18 +11987,18 @@ async function main() {
11645
11987
  }
11646
11988
  await watcher.stop();
11647
11989
  await pool.shutdown();
11648
- log.info(TAG41, "Daemon stopped.");
11990
+ log.info(TAG42, "Daemon stopped.");
11649
11991
  process.exit(exitCode);
11650
11992
  };
11651
11993
  process.on("SIGINT", () => shutdown("SIGINT"));
11652
11994
  process.on("SIGTERM", () => shutdown("SIGTERM"));
11653
11995
  process.on("uncaughtException", (err) => {
11654
- log.error(TAG41, `Uncaught exception: ${err.message}`);
11996
+ log.error(TAG42, `Uncaught exception: ${err.message}`);
11655
11997
  exitCode = 1;
11656
11998
  shutdown("uncaughtException");
11657
11999
  });
11658
12000
  process.on("unhandledRejection", (reason) => {
11659
- log.error(TAG41, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
12001
+ log.error(TAG42, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
11660
12002
  exitCode = 1;
11661
12003
  shutdown("unhandledRejection");
11662
12004
  });
@@ -11715,29 +12057,29 @@ async function handleBroadcast(event, client, pool, config, agentId) {
11715
12057
  if (assignedAgentId === undefined)
11716
12058
  return;
11717
12059
  if (assignedAgentId === agentId) {
11718
- log.info(TAG41, `Broadcast: card ${cardId} assigned to agent`);
12060
+ log.info(TAG42, `Broadcast: card ${cardId} assigned to agent`);
11719
12061
  try {
11720
12062
  await pool.resetAttemptsForReassign(cardId);
11721
12063
  await tryEnqueueCard(cardId, client, pool, config, agentId);
11722
12064
  } catch (err) {
11723
- log.error(TAG41, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
12065
+ log.error(TAG42, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
11724
12066
  }
11725
12067
  } else if (pool.isCardKnown(cardId)) {
11726
- log.info(TAG41, `Broadcast: card ${cardId} unassigned from agent`);
12068
+ log.info(TAG42, `Broadcast: card ${cardId} unassigned from agent`);
11727
12069
  await pool.removeCard(cardId);
11728
12070
  }
11729
12071
  }
11730
12072
  async function tryEnqueueCard(cardId, client, pool, config, agentId) {
11731
12073
  const { card } = await client.getCard(cardId);
11732
12074
  if (card.assigned_agent_id !== agentId) {
11733
- log.debug(TAG41, `Card ${cardId} no longer assigned to agent — skipping`);
12075
+ log.debug(TAG42, `Card ${cardId} no longer assigned to agent — skipping`);
11734
12076
  return;
11735
12077
  }
11736
12078
  const board = await client.getBoard(config.projectId, { summary: true });
11737
12079
  const columns = board.columns;
11738
12080
  const column = columns.find((c) => c.id === card.column_id);
11739
12081
  if (!column) {
11740
- log.warn(TAG41, `Column not found for card ${cardId}`);
12082
+ log.warn(TAG42, `Column not found for card ${cardId}`);
11741
12083
  return;
11742
12084
  }
11743
12085
  const route = classifyPickup(card, column.name, {
@@ -11746,31 +12088,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
11746
12088
  playbooks: config.agent.playbooks
11747
12089
  });
11748
12090
  if (!route) {
11749
- log.info(TAG41, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
12091
+ log.info(TAG42, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
11750
12092
  return;
11751
12093
  }
11752
12094
  if (route.stage) {
11753
- log.info(TAG41, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
12095
+ log.info(TAG42, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
11754
12096
  }
11755
12097
  const mode = route.mode;
11756
12098
  const labelMap = buildLabelMap(board.labels ?? []);
11757
12099
  const cardLabels = resolveCardLabels(card, labelMap);
11758
12100
  const subtasks = card.subtasks ?? [];
11759
12101
  if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
11760
- log.debug(TAG41, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
12102
+ log.debug(TAG42, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
11761
12103
  return;
11762
12104
  }
11763
12105
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
11764
- log.debug(TAG41, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
12106
+ log.debug(TAG42, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
11765
12107
  return;
11766
12108
  }
11767
12109
  if (mode === "review" && !qualifiesForAutoReview(card.description)) {
11768
- log.info(TAG41, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
12110
+ log.info(TAG42, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
11769
12111
  return;
11770
12112
  }
11771
12113
  await pool.enqueue(card, column, cardLabels, subtasks, mode);
11772
12114
  }
11773
- var TAG41 = "daemon", PKG_VERSION;
12115
+ var TAG42 = "daemon", PKG_VERSION;
11774
12116
  var init_src = __esm(() => {
11775
12117
  init_board_helpers();
11776
12118
  init_board_reviewer();