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