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