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