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