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