@gethmy/agent 1.23.1 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +424 -214
- package/dist/index.js +424 -214
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -581,6 +581,17 @@ function isDaemonAuthoredComment(comment, identity) {
|
|
|
581
581
|
return false;
|
|
582
582
|
return true;
|
|
583
583
|
}
|
|
584
|
+
// ../harmony-shared/dist/agentStaleness.js
|
|
585
|
+
var AGENT_HEARTBEAT_LIVENESS_MS, AGENT_MILESTONE_LIVENESS_MS, AGENT_SWEEP_DAEMON_MS, AGENT_SWEEP_INTERACTIVE_MS, AGENT_SWEEP_PAUSED_MS, SWEPT_SESSION_WRITE_GRACE_MS, ACTIVE_STATUSES;
|
|
586
|
+
var init_agentStaleness = __esm(() => {
|
|
587
|
+
AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
|
|
588
|
+
AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
|
|
589
|
+
AGENT_SWEEP_DAEMON_MS = 30 * 60 * 1000;
|
|
590
|
+
AGENT_SWEEP_INTERACTIVE_MS = 2 * 60 * 60 * 1000;
|
|
591
|
+
AGENT_SWEEP_PAUSED_MS = 4 * 60 * 60 * 1000;
|
|
592
|
+
SWEPT_SESSION_WRITE_GRACE_MS = 60 * 60 * 1000;
|
|
593
|
+
ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
|
|
594
|
+
});
|
|
584
595
|
// ../harmony-shared/dist/branchRef.js
|
|
585
596
|
function extractBranchRef(description) {
|
|
586
597
|
if (!description)
|
|
@@ -823,7 +834,7 @@ function gateEvaluate(gateSpec, evidence) {
|
|
|
823
834
|
findings: [
|
|
824
835
|
{
|
|
825
836
|
level: "error",
|
|
826
|
-
message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}"
|
|
837
|
+
message: resultIsKnown ? `Gate "${spec.kind}" not satisfied: evidence result is "${String(result)}".${blockedDetail(safeStructured)}` : `Gate "${spec.kind}" not satisfied: evidence result is missing or invalid.`
|
|
827
838
|
}
|
|
828
839
|
],
|
|
829
840
|
structured: safeStructured
|
|
@@ -841,7 +852,7 @@ function gateEvaluate(gateSpec, evidence) {
|
|
|
841
852
|
if (result === "blocked") {
|
|
842
853
|
findings.unshift({
|
|
843
854
|
level: "error",
|
|
844
|
-
message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked"
|
|
855
|
+
message: `Gate "${spec.kind}" cannot pass: evidence result is "blocked".${blockedDetail(safeStructured)}`
|
|
845
856
|
});
|
|
846
857
|
return { passed: false, findings, structured: safeStructured };
|
|
847
858
|
}
|
|
@@ -937,6 +948,15 @@ function evaluateCondition(raw, structured) {
|
|
|
937
948
|
function isPlainObject(value) {
|
|
938
949
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
939
950
|
}
|
|
951
|
+
function blockedDetail(structured) {
|
|
952
|
+
const reason = structured.reason;
|
|
953
|
+
if (typeof reason !== "string")
|
|
954
|
+
return "";
|
|
955
|
+
const trimmed = reason.trim();
|
|
956
|
+
if (!trimmed)
|
|
957
|
+
return "";
|
|
958
|
+
return ` ${trimmed.length > MAX_REASON_CHARS ? `${trimmed.slice(0, MAX_REASON_CHARS)}…` : trimmed}`;
|
|
959
|
+
}
|
|
940
960
|
function resolvePath(root, path) {
|
|
941
961
|
const segments = path.split(".");
|
|
942
962
|
let current = root;
|
|
@@ -1009,7 +1029,7 @@ function formatValue(value) {
|
|
|
1009
1029
|
return "[unserializable]";
|
|
1010
1030
|
}
|
|
1011
1031
|
}
|
|
1012
|
-
var GATE_KINDS, GATE_OPERATORS;
|
|
1032
|
+
var GATE_KINDS, GATE_OPERATORS, MAX_REASON_CHARS = 400;
|
|
1013
1033
|
var init_gateEvaluate = __esm(() => {
|
|
1014
1034
|
GATE_KINDS = [
|
|
1015
1035
|
"build_green",
|
|
@@ -1416,6 +1436,7 @@ var init_types = () => {};
|
|
|
1416
1436
|
|
|
1417
1437
|
// ../harmony-shared/dist/index.js
|
|
1418
1438
|
var init_dist = __esm(() => {
|
|
1439
|
+
init_agentStaleness();
|
|
1419
1440
|
init_branchRef();
|
|
1420
1441
|
init_cardLinks();
|
|
1421
1442
|
init_classification();
|
|
@@ -1739,7 +1760,7 @@ function agentIdentifier(workerId) {
|
|
|
1739
1760
|
function endStatusForCancel(reason) {
|
|
1740
1761
|
return reason === "human_stop" ? "cancelled" : "paused";
|
|
1741
1762
|
}
|
|
1742
|
-
var DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
|
|
1763
|
+
var DEFAULT_METRIC_TIMEOUT_MS = 300000, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", AGENT_NAME = "Harmony Agent";
|
|
1743
1764
|
var init_types2 = __esm(() => {
|
|
1744
1765
|
init_board_review();
|
|
1745
1766
|
init_contract_phase();
|
|
@@ -1832,7 +1853,7 @@ var init_types2 = __esm(() => {
|
|
|
1832
1853
|
worktreeGcIntervalMs: 5 * 60000
|
|
1833
1854
|
},
|
|
1834
1855
|
planning: DEFAULT_PLANNING_CONFIG,
|
|
1835
|
-
playbooks: { enabled: true, humanStageColumns: [] },
|
|
1856
|
+
playbooks: { enabled: true, humanStageColumns: [], metrics: {} },
|
|
1836
1857
|
contractFirst: DEFAULT_CONTRACT_CONFIG,
|
|
1837
1858
|
boardReview: DEFAULT_BOARD_REVIEW_CONFIG
|
|
1838
1859
|
};
|
|
@@ -5663,6 +5684,173 @@ var init_artifact_judge = __esm(() => {
|
|
|
5663
5684
|
init_sdk_agent_runner();
|
|
5664
5685
|
});
|
|
5665
5686
|
|
|
5687
|
+
// src/command-metric.ts
|
|
5688
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
5689
|
+
function parseParseMode(parse) {
|
|
5690
|
+
if (typeof parse !== "string" || parse.length === 0) {
|
|
5691
|
+
return { kind: "invalid", reason: "`parse` must be a non-empty string" };
|
|
5692
|
+
}
|
|
5693
|
+
if (parse === "number")
|
|
5694
|
+
return { kind: "number" };
|
|
5695
|
+
if (parse.startsWith("json:")) {
|
|
5696
|
+
const path = parse.slice("json:".length).trim();
|
|
5697
|
+
if (!path) {
|
|
5698
|
+
return { kind: "invalid", reason: '`parse` "json:" is missing a path' };
|
|
5699
|
+
}
|
|
5700
|
+
return { kind: "json", path };
|
|
5701
|
+
}
|
|
5702
|
+
return {
|
|
5703
|
+
kind: "invalid",
|
|
5704
|
+
reason: `unknown \`parse\` mode "${parse}" (expected "number" or "json:<path>")`
|
|
5705
|
+
};
|
|
5706
|
+
}
|
|
5707
|
+
function parseMetricValue(mode, stdout) {
|
|
5708
|
+
if (mode.kind === "invalid")
|
|
5709
|
+
return { ok: false, reason: mode.reason };
|
|
5710
|
+
if (mode.kind === "number") {
|
|
5711
|
+
const trimmed = stdout.trim();
|
|
5712
|
+
if (!trimmed) {
|
|
5713
|
+
return {
|
|
5714
|
+
ok: false,
|
|
5715
|
+
reason: "command produced no output to read a number from"
|
|
5716
|
+
};
|
|
5717
|
+
}
|
|
5718
|
+
const value = Number(trimmed);
|
|
5719
|
+
if (!Number.isFinite(value)) {
|
|
5720
|
+
return {
|
|
5721
|
+
ok: false,
|
|
5722
|
+
reason: `command output is not a finite number: ${JSON.stringify(truncate(trimmed, 120))}`
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
return { ok: true, value };
|
|
5726
|
+
}
|
|
5727
|
+
let doc;
|
|
5728
|
+
try {
|
|
5729
|
+
doc = JSON.parse(stdout);
|
|
5730
|
+
} catch (err) {
|
|
5731
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5732
|
+
return { ok: false, reason: `command output is not valid JSON: ${msg}` };
|
|
5733
|
+
}
|
|
5734
|
+
const resolved = resolvePath(doc, mode.path);
|
|
5735
|
+
if (resolved === undefined) {
|
|
5736
|
+
return {
|
|
5737
|
+
ok: false,
|
|
5738
|
+
reason: `JSON path "${mode.path}" is absent in the command output`
|
|
5739
|
+
};
|
|
5740
|
+
}
|
|
5741
|
+
if (resolved !== null && typeof resolved !== "number" && typeof resolved !== "string" && typeof resolved !== "boolean") {
|
|
5742
|
+
return {
|
|
5743
|
+
ok: false,
|
|
5744
|
+
reason: `JSON path "${mode.path}" resolved to a ${Array.isArray(resolved) ? "array" : typeof resolved}, which no gate operator can compare`
|
|
5745
|
+
};
|
|
5746
|
+
}
|
|
5747
|
+
return { ok: true, value: resolved };
|
|
5748
|
+
}
|
|
5749
|
+
function defaultRunCommand(args) {
|
|
5750
|
+
const out = execFileSync10(args.command, args.args, {
|
|
5751
|
+
cwd: args.cwd,
|
|
5752
|
+
timeout: args.timeoutMs,
|
|
5753
|
+
stdio: "pipe",
|
|
5754
|
+
maxBuffer: MAX_OUTPUT_BUFFER2,
|
|
5755
|
+
encoding: "utf8"
|
|
5756
|
+
});
|
|
5757
|
+
return typeof out === "string" ? out : String(out);
|
|
5758
|
+
}
|
|
5759
|
+
|
|
5760
|
+
class CommandMetricCollector {
|
|
5761
|
+
deps;
|
|
5762
|
+
kind = "custom";
|
|
5763
|
+
constructor(deps) {
|
|
5764
|
+
this.deps = deps;
|
|
5765
|
+
}
|
|
5766
|
+
async collect(context) {
|
|
5767
|
+
const name = context.gate.metric;
|
|
5768
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
5769
|
+
return blocked(null, 'Gate kind "custom" needs a `metric` name naming an allowlisted command (e.g. { "kind": "custom", "metric": "lighthouse_performance" }).');
|
|
5770
|
+
}
|
|
5771
|
+
const def = Object.hasOwn(this.deps.metrics, name) ? this.deps.metrics[name] : undefined;
|
|
5772
|
+
if (!def) {
|
|
5773
|
+
return blocked(name, `Metric "${name}" is not declared in this daemon's allowlist — add it under \`agent.playbooks.metrics\` to permit it.`);
|
|
5774
|
+
}
|
|
5775
|
+
if (typeof def.command !== "string" || def.command.length === 0) {
|
|
5776
|
+
return blocked(name, `Metric "${name}" declares no \`command\`.`);
|
|
5777
|
+
}
|
|
5778
|
+
const mode = parseParseMode(def.parse);
|
|
5779
|
+
if (mode.kind === "invalid") {
|
|
5780
|
+
return blocked(name, `Metric "${name}": ${mode.reason}.`);
|
|
5781
|
+
}
|
|
5782
|
+
const requested = typeof def.timeoutMs === "number" && def.timeoutMs > 0 ? Math.floor(def.timeoutMs) : DEFAULT_METRIC_TIMEOUT_MS;
|
|
5783
|
+
const timeoutMs = Math.min(requested, MAX_METRIC_TIMEOUT_MS);
|
|
5784
|
+
if (timeoutMs < requested) {
|
|
5785
|
+
log.warn(TAG19, `Metric "${name}" declares timeoutMs=${requested}, clamped to the ${MAX_METRIC_TIMEOUT_MS}ms ceiling`);
|
|
5786
|
+
}
|
|
5787
|
+
const run = this.deps.runCommand ?? defaultRunCommand;
|
|
5788
|
+
let stdout;
|
|
5789
|
+
try {
|
|
5790
|
+
stdout = run({
|
|
5791
|
+
command: def.command,
|
|
5792
|
+
args: def.args ?? [],
|
|
5793
|
+
cwd: this.deps.worktreePath,
|
|
5794
|
+
timeoutMs
|
|
5795
|
+
});
|
|
5796
|
+
} catch (err) {
|
|
5797
|
+
const reason = describeRunFailure(err, timeoutMs);
|
|
5798
|
+
log.warn(TAG19, `Metric "${name}" did not produce a measurement: ${reason}`);
|
|
5799
|
+
return blocked(name, reason);
|
|
5800
|
+
}
|
|
5801
|
+
const parsed = parseMetricValue(mode, stdout);
|
|
5802
|
+
if (!parsed.ok) {
|
|
5803
|
+
log.warn(TAG19, `Metric "${name}" output unusable: ${parsed.reason}`);
|
|
5804
|
+
return blocked(name, `Metric "${name}": ${parsed.reason}.`, stdout);
|
|
5805
|
+
}
|
|
5806
|
+
log.info(TAG19, `Metric "${name}" measured: ${JSON.stringify(parsed.value)}`);
|
|
5807
|
+
return {
|
|
5808
|
+
result: "passed",
|
|
5809
|
+
structured: {
|
|
5810
|
+
metric: name,
|
|
5811
|
+
value: parsed.value,
|
|
5812
|
+
raw: truncate(stdout, MAX_RAW_CHARS)
|
|
5813
|
+
}
|
|
5814
|
+
};
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5817
|
+
function blocked(metric, reason, raw) {
|
|
5818
|
+
return {
|
|
5819
|
+
result: "blocked",
|
|
5820
|
+
structured: {
|
|
5821
|
+
metric,
|
|
5822
|
+
reason,
|
|
5823
|
+
...raw === undefined ? {} : { raw: truncate(raw, MAX_RAW_CHARS) }
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
}
|
|
5827
|
+
function describeRunFailure(err, timeoutMs) {
|
|
5828
|
+
const e = err;
|
|
5829
|
+
if (e?.code === "ENOBUFS") {
|
|
5830
|
+
return `command produced more than ${MAX_OUTPUT_BUFFER2} bytes of output (use a flag that prints only the metric, or parse a smaller report)`;
|
|
5831
|
+
}
|
|
5832
|
+
if (e?.code === "ETIMEDOUT" || e?.signal === "SIGTERM") {
|
|
5833
|
+
return `command timed out after ${timeoutMs}ms`;
|
|
5834
|
+
}
|
|
5835
|
+
if (e?.code === "ENOENT") {
|
|
5836
|
+
return "command not found on PATH";
|
|
5837
|
+
}
|
|
5838
|
+
const stderr = typeof e?.stderr === "string" ? e.stderr : e?.stderr instanceof Buffer ? e.stderr.toString("utf8") : "";
|
|
5839
|
+
const detail = stderr.trim() || String(e?.message ?? err);
|
|
5840
|
+
const status = typeof e?.status === "number" ? e.status : null;
|
|
5841
|
+
return status === null ? `command failed: ${truncate(detail, 400)}` : `command exited ${status}: ${truncate(detail, 400)}`;
|
|
5842
|
+
}
|
|
5843
|
+
function truncate(value, max) {
|
|
5844
|
+
return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
|
|
5845
|
+
}
|
|
5846
|
+
var TAG19 = "command-metric", MAX_RAW_CHARS = 2000, MAX_OUTPUT_BUFFER2, MAX_METRIC_TIMEOUT_MS = 900000;
|
|
5847
|
+
var init_command_metric = __esm(() => {
|
|
5848
|
+
init_dist();
|
|
5849
|
+
init_log();
|
|
5850
|
+
init_types2();
|
|
5851
|
+
MAX_OUTPUT_BUFFER2 = 10 * 1024 * 1024;
|
|
5852
|
+
});
|
|
5853
|
+
|
|
5666
5854
|
// src/gate-collectors.ts
|
|
5667
5855
|
async function resolveStageGate(client, card) {
|
|
5668
5856
|
const currentStage = card.current_stage;
|
|
@@ -5681,7 +5869,7 @@ async function resolveStageGate(client, card) {
|
|
|
5681
5869
|
return null;
|
|
5682
5870
|
return { stage: resolution.stage, gate };
|
|
5683
5871
|
} catch (err) {
|
|
5684
|
-
log.warn(
|
|
5872
|
+
log.warn(TAG20, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
|
|
5685
5873
|
return null;
|
|
5686
5874
|
}
|
|
5687
5875
|
}
|
|
@@ -5701,6 +5889,9 @@ function normalizeGateSpec(gate) {
|
|
|
5701
5889
|
const mode = gate.mode;
|
|
5702
5890
|
if (mode === "all" || mode === "any")
|
|
5703
5891
|
spec.mode = mode;
|
|
5892
|
+
const metric = gate.metric;
|
|
5893
|
+
if (typeof metric === "string" && metric.length > 0)
|
|
5894
|
+
spec.metric = metric;
|
|
5704
5895
|
return spec;
|
|
5705
5896
|
}
|
|
5706
5897
|
|
|
@@ -5798,12 +5989,15 @@ function buildGateCollectorRegistry(deps) {
|
|
|
5798
5989
|
if (deps.artifact) {
|
|
5799
5990
|
registry.artifact = new ArtifactCollector(deps.artifact);
|
|
5800
5991
|
}
|
|
5992
|
+
if (deps.command) {
|
|
5993
|
+
registry.custom = new CommandMetricCollector(deps.command);
|
|
5994
|
+
}
|
|
5801
5995
|
return registry;
|
|
5802
5996
|
}
|
|
5803
5997
|
async function collectGateEvidence(registry, context) {
|
|
5804
5998
|
const collector = registry[context.gate.kind];
|
|
5805
5999
|
if (!collector) {
|
|
5806
|
-
log.info(
|
|
6000
|
+
log.info(TAG20, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
|
|
5807
6001
|
return {
|
|
5808
6002
|
result: "blocked",
|
|
5809
6003
|
structured: {
|
|
@@ -5815,20 +6009,21 @@ async function collectGateEvidence(registry, context) {
|
|
|
5815
6009
|
return await collector.collect(context);
|
|
5816
6010
|
} catch (err) {
|
|
5817
6011
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5818
|
-
log.warn(
|
|
6012
|
+
log.warn(TAG20, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
|
|
5819
6013
|
return { result: "blocked", structured: { error: msg } };
|
|
5820
6014
|
}
|
|
5821
6015
|
}
|
|
5822
|
-
var
|
|
6016
|
+
var TAG20 = "gate-collectors";
|
|
5823
6017
|
var init_gate_collectors = __esm(() => {
|
|
5824
6018
|
init_dist();
|
|
5825
6019
|
init_artifact_judge();
|
|
6020
|
+
init_command_metric();
|
|
5826
6021
|
init_log();
|
|
5827
6022
|
init_verification();
|
|
5828
6023
|
});
|
|
5829
6024
|
|
|
5830
6025
|
// src/progress-tracker.ts
|
|
5831
|
-
function
|
|
6026
|
+
function truncate2(str, max) {
|
|
5832
6027
|
return str.length > max ? `${str.slice(0, max - 3)}...` : str;
|
|
5833
6028
|
}
|
|
5834
6029
|
|
|
@@ -5852,6 +6047,7 @@ class ProgressTracker {
|
|
|
5852
6047
|
filesEdited = new Set;
|
|
5853
6048
|
filesRead = new Set;
|
|
5854
6049
|
lastCost = null;
|
|
6050
|
+
requestedModel = null;
|
|
5855
6051
|
runEventSink = null;
|
|
5856
6052
|
lastEmittedProgress = -1;
|
|
5857
6053
|
lastAssistantText = "";
|
|
@@ -5869,6 +6065,9 @@ class ProgressTracker {
|
|
|
5869
6065
|
setRunEventSink(sink) {
|
|
5870
6066
|
this.runEventSink = sink;
|
|
5871
6067
|
}
|
|
6068
|
+
setRequestedModel(model) {
|
|
6069
|
+
this.requestedModel = model;
|
|
6070
|
+
}
|
|
5872
6071
|
attach(parser) {
|
|
5873
6072
|
parser.on("tool_start", (name, input) => {
|
|
5874
6073
|
this.onToolStart(name, input);
|
|
@@ -5939,7 +6138,7 @@ class ProgressTracker {
|
|
|
5939
6138
|
}
|
|
5940
6139
|
onToolStart(name, input) {
|
|
5941
6140
|
this.toolCallCount++;
|
|
5942
|
-
log.debug(
|
|
6141
|
+
log.debug(TAG21, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
|
|
5943
6142
|
const filePath = this.extractString(input, "file_path");
|
|
5944
6143
|
if (filePath) {
|
|
5945
6144
|
if (EDIT_TOOLS.has(name)) {
|
|
@@ -6003,14 +6202,14 @@ class ProgressTracker {
|
|
|
6003
6202
|
const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
|
|
6004
6203
|
if (firstLine.length >= 10 && firstLine.length <= 200) {
|
|
6005
6204
|
if (ACTION_PREFIX.test(firstLine)) {
|
|
6006
|
-
this.lastAction =
|
|
6205
|
+
this.lastAction = truncate2(firstLine, MAX_TASK_LENGTH);
|
|
6007
6206
|
}
|
|
6008
6207
|
}
|
|
6009
6208
|
}
|
|
6010
6209
|
transitionTo(newPhase) {
|
|
6011
6210
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
6012
6211
|
return;
|
|
6013
|
-
log.info(
|
|
6212
|
+
log.info(TAG21, `Phase: ${this.phase} → ${newPhase}`);
|
|
6014
6213
|
const previousPhase = this.phase;
|
|
6015
6214
|
this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
|
|
6016
6215
|
this.phase = newPhase;
|
|
@@ -6059,16 +6258,16 @@ class ProgressTracker {
|
|
|
6059
6258
|
}
|
|
6060
6259
|
case "Grep": {
|
|
6061
6260
|
const pattern = this.extractString(input, "pattern");
|
|
6062
|
-
return pattern ? `Searching for "${
|
|
6261
|
+
return pattern ? `Searching for "${truncate2(pattern, 40)}"` : "Searching code";
|
|
6063
6262
|
}
|
|
6064
6263
|
case "Bash": {
|
|
6065
6264
|
const cmd = this.extractString(input, "command");
|
|
6066
|
-
return cmd ? `Running: ${
|
|
6265
|
+
return cmd ? `Running: ${truncate2(cmd.split(`
|
|
6067
6266
|
`)[0], 80)}` : "Running command";
|
|
6068
6267
|
}
|
|
6069
6268
|
case "Agent": {
|
|
6070
6269
|
const desc = this.extractString(input, "description");
|
|
6071
|
-
return desc ? `Sub-agent: ${
|
|
6270
|
+
return desc ? `Sub-agent: ${truncate2(desc, 60)}` : "Delegating to sub-agent";
|
|
6072
6271
|
}
|
|
6073
6272
|
default: {
|
|
6074
6273
|
if (name.startsWith("mcp__harmony__harmony_")) {
|
|
@@ -6112,12 +6311,12 @@ class ProgressTracker {
|
|
|
6112
6311
|
}
|
|
6113
6312
|
sendUpdate(currentTask) {
|
|
6114
6313
|
this.lastUpdateAt = Date.now();
|
|
6115
|
-
log.debug(
|
|
6314
|
+
log.debug(TAG21, `Progress: ${this.progress}% — ${currentTask}`);
|
|
6116
6315
|
this.client.updateAgentProgress(this.cardId, {
|
|
6117
6316
|
agentIdentifier: agentIdentifier(this.workerId),
|
|
6118
6317
|
agentName: AGENT_NAME,
|
|
6119
6318
|
status: "working",
|
|
6120
|
-
currentTask:
|
|
6319
|
+
currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
|
|
6121
6320
|
progressPercent: this.progress,
|
|
6122
6321
|
phase: this.phase,
|
|
6123
6322
|
filesChanged: this.filesEdited.size,
|
|
@@ -6126,16 +6325,16 @@ class ProgressTracker {
|
|
|
6126
6325
|
outputTokens: this.lastCost?.totalOutputTokens ?? 0,
|
|
6127
6326
|
cacheCreationInputTokens: this.lastCost?.totalCacheCreationInputTokens ?? 0,
|
|
6128
6327
|
cacheReadInputTokens: this.lastCost?.totalCacheReadInputTokens ?? 0,
|
|
6129
|
-
modelName: this.lastCost?.modelName,
|
|
6328
|
+
modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
|
|
6130
6329
|
numTurns: this.lastCost?.numTurns ?? 0
|
|
6131
6330
|
}).catch((err) => {
|
|
6132
|
-
log.warn(
|
|
6331
|
+
log.warn(TAG21, `Failed to send progress update: ${err}`);
|
|
6133
6332
|
});
|
|
6134
6333
|
if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
|
|
6135
6334
|
this.lastEmittedProgress = this.progress;
|
|
6136
6335
|
this.runEventSink.recordProgress({
|
|
6137
6336
|
progressPercent: this.progress,
|
|
6138
|
-
currentTask:
|
|
6337
|
+
currentTask: truncate2(currentTask, MAX_TASK_LENGTH),
|
|
6139
6338
|
phase: this.phase,
|
|
6140
6339
|
filesChanged: this.filesEdited.size
|
|
6141
6340
|
});
|
|
@@ -6148,7 +6347,7 @@ class ProgressTracker {
|
|
|
6148
6347
|
this.heartbeatTimer = setTimeout(() => {
|
|
6149
6348
|
if (!this.stopped) {
|
|
6150
6349
|
const task = this.lastAction ? `Still working — ${this.lastAction}` : "Still working...";
|
|
6151
|
-
this.sendUpdate(
|
|
6350
|
+
this.sendUpdate(truncate2(task, MAX_TASK_LENGTH));
|
|
6152
6351
|
this.startHeartbeat();
|
|
6153
6352
|
}
|
|
6154
6353
|
}, HEARTBEAT_MS);
|
|
@@ -6160,7 +6359,7 @@ class ProgressTracker {
|
|
|
6160
6359
|
return null;
|
|
6161
6360
|
}
|
|
6162
6361
|
}
|
|
6163
|
-
var
|
|
6362
|
+
var TAG21 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
|
|
6164
6363
|
var init_progress_tracker = __esm(() => {
|
|
6165
6364
|
init_log();
|
|
6166
6365
|
init_types2();
|
|
@@ -6316,7 +6515,7 @@ function parseReviewOutput(stdout) {
|
|
|
6316
6515
|
try {
|
|
6317
6516
|
const parsed = JSON.parse(raw);
|
|
6318
6517
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
6319
|
-
log.debug(
|
|
6518
|
+
log.debug(TAG22, "Parsed review output from fenced JSON block");
|
|
6320
6519
|
return extractResult(parsed);
|
|
6321
6520
|
}
|
|
6322
6521
|
} catch {}
|
|
@@ -6342,21 +6541,21 @@ function parseReviewOutput(stdout) {
|
|
|
6342
6541
|
try {
|
|
6343
6542
|
const parsed = JSON.parse(candidates[i]);
|
|
6344
6543
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
6345
|
-
log.debug(
|
|
6544
|
+
log.debug(TAG22, "Parsed review output from raw JSON object");
|
|
6346
6545
|
return extractResult(parsed);
|
|
6347
6546
|
}
|
|
6348
6547
|
} catch {}
|
|
6349
6548
|
}
|
|
6350
6549
|
const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
|
|
6351
6550
|
if (verdictMatch) {
|
|
6352
|
-
log.warn(
|
|
6551
|
+
log.warn(TAG22, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
|
|
6353
6552
|
return {
|
|
6354
6553
|
verdict: verdictMatch[1].toLowerCase(),
|
|
6355
6554
|
summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
|
|
6356
6555
|
findings: []
|
|
6357
6556
|
};
|
|
6358
6557
|
}
|
|
6359
|
-
log.warn(
|
|
6558
|
+
log.warn(TAG22, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
|
|
6360
6559
|
return {
|
|
6361
6560
|
verdict: "error",
|
|
6362
6561
|
summary: stdout.slice(0, 500),
|
|
@@ -6389,7 +6588,7 @@ async function postReviewComment(client, card, commentType, body) {
|
|
|
6389
6588
|
try {
|
|
6390
6589
|
await client.addComment(card.id, body, { commentType });
|
|
6391
6590
|
} catch (err) {
|
|
6392
|
-
log.error(
|
|
6591
|
+
log.error(TAG22, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6393
6592
|
}
|
|
6394
6593
|
}
|
|
6395
6594
|
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore, resolvedFromPrUrl) {
|
|
@@ -6403,11 +6602,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
6403
6602
|
const currentCycle = getReviewCycle(freshDesc) + 1;
|
|
6404
6603
|
const maxCycles = config.review.maxReviewCycles;
|
|
6405
6604
|
if (result.verdict === "error") {
|
|
6406
|
-
log.warn(
|
|
6605
|
+
log.warn(TAG22, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
|
|
6407
6606
|
try {
|
|
6408
6607
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
6409
6608
|
} catch (err) {
|
|
6410
|
-
log.warn(
|
|
6609
|
+
log.warn(TAG22, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
6411
6610
|
}
|
|
6412
6611
|
if (config.review.postFindings) {
|
|
6413
6612
|
const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
|
|
@@ -6450,7 +6649,7 @@ ${runLogTail}
|
|
|
6450
6649
|
renameRemoteBranch(branchName, newRef, worktreePath);
|
|
6451
6650
|
approvedBranch = newRef;
|
|
6452
6651
|
} catch (err) {
|
|
6453
|
-
log.warn(
|
|
6652
|
+
log.warn(TAG22, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
|
|
6454
6653
|
}
|
|
6455
6654
|
}
|
|
6456
6655
|
if (config.review.createPR && approvedBranch) {
|
|
@@ -6471,14 +6670,14 @@ ${runLogTail}
|
|
|
6471
6670
|
});
|
|
6472
6671
|
}
|
|
6473
6672
|
} catch (err) {
|
|
6474
|
-
log.warn(
|
|
6673
|
+
log.warn(TAG22, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
|
|
6475
6674
|
}
|
|
6476
6675
|
}
|
|
6477
6676
|
if (branchName) {
|
|
6478
6677
|
try {
|
|
6479
6678
|
await persistReviewedSha(client, card, worktreePath);
|
|
6480
6679
|
} catch (err) {
|
|
6481
|
-
log.warn(
|
|
6680
|
+
log.warn(TAG22, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6482
6681
|
}
|
|
6483
6682
|
}
|
|
6484
6683
|
if (config.review.postFindings) {
|
|
@@ -6500,7 +6699,7 @@ ${runLogTail}
|
|
|
6500
6699
|
progressPercent: 100,
|
|
6501
6700
|
...buildTokenPayload(sessionStats)
|
|
6502
6701
|
});
|
|
6503
|
-
log.info(
|
|
6702
|
+
log.info(TAG22, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
|
|
6504
6703
|
} else {
|
|
6505
6704
|
const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
|
|
6506
6705
|
const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
|
|
@@ -6508,7 +6707,7 @@ ${runLogTail}
|
|
|
6508
6707
|
const linkedFindings = [...criticalFindings, ...majorFindings];
|
|
6509
6708
|
const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
|
|
6510
6709
|
if (currentCycle >= maxCycles) {
|
|
6511
|
-
log.warn(
|
|
6710
|
+
log.warn(TAG22, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
|
|
6512
6711
|
await moveCardToColumn(client, card, config.review.moveToColumn);
|
|
6513
6712
|
const body = [
|
|
6514
6713
|
"**Review — needs human review.**",
|
|
@@ -6548,7 +6747,7 @@ ${runLogTail}
|
|
|
6548
6747
|
try {
|
|
6549
6748
|
await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
|
|
6550
6749
|
} catch (err) {
|
|
6551
|
-
log.error(
|
|
6750
|
+
log.error(TAG22, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
|
|
6552
6751
|
}
|
|
6553
6752
|
}));
|
|
6554
6753
|
if (linkedFindings.length > 0) {
|
|
@@ -6560,7 +6759,7 @@ ${runLogTail}
|
|
|
6560
6759
|
try {
|
|
6561
6760
|
await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
|
|
6562
6761
|
} catch (err) {
|
|
6563
|
-
log.error(
|
|
6762
|
+
log.error(TAG22, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
6564
6763
|
}
|
|
6565
6764
|
}));
|
|
6566
6765
|
const baseDesc = stripReviewSummary(freshDesc);
|
|
@@ -6568,7 +6767,7 @@ ${runLogTail}
|
|
|
6568
6767
|
try {
|
|
6569
6768
|
await client.updateCard(card.id, { description: updatedDesc });
|
|
6570
6769
|
} catch (err) {
|
|
6571
|
-
log.error(
|
|
6770
|
+
log.error(TAG22, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
|
|
6572
6771
|
}
|
|
6573
6772
|
const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
|
|
6574
6773
|
const body = [
|
|
@@ -6585,9 +6784,9 @@ ${runLogTail}
|
|
|
6585
6784
|
if (config.planning.enabled && card.plan_id) {
|
|
6586
6785
|
try {
|
|
6587
6786
|
await client.updateCard(card.id, { needsPlanRefresh: true });
|
|
6588
|
-
log.info(
|
|
6787
|
+
log.info(TAG22, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
|
|
6589
6788
|
} catch (err) {
|
|
6590
|
-
log.warn(
|
|
6789
|
+
log.warn(TAG22, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6591
6790
|
}
|
|
6592
6791
|
}
|
|
6593
6792
|
await moveCardToColumn(client, card, config.review.failColumn);
|
|
@@ -6601,10 +6800,10 @@ ${runLogTail}
|
|
|
6601
6800
|
recoveryBranch
|
|
6602
6801
|
});
|
|
6603
6802
|
} catch (err) {
|
|
6604
|
-
log.debug(
|
|
6803
|
+
log.debug(TAG22, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
6605
6804
|
}
|
|
6606
6805
|
if (recoveryBranch) {
|
|
6607
|
-
log.info(
|
|
6806
|
+
log.info(TAG22, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
|
|
6608
6807
|
}
|
|
6609
6808
|
await client.endAgentSession(card.id, {
|
|
6610
6809
|
status: "failed",
|
|
@@ -6613,7 +6812,7 @@ ${runLogTail}
|
|
|
6613
6812
|
recoveryBranch,
|
|
6614
6813
|
...buildTokenPayload(sessionStats)
|
|
6615
6814
|
});
|
|
6616
|
-
log.info(
|
|
6815
|
+
log.info(TAG22, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
|
|
6617
6816
|
}
|
|
6618
6817
|
if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
|
|
6619
6818
|
const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
@@ -6635,7 +6834,7 @@ ${runLogTail}
|
|
|
6635
6834
|
cleanupWorktree(worktreePath, branchName);
|
|
6636
6835
|
}
|
|
6637
6836
|
}
|
|
6638
|
-
var
|
|
6837
|
+
var TAG22 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
|
|
6639
6838
|
**Review:`, RUN_LOG_TAIL_BYTES = 2048;
|
|
6640
6839
|
var init_review_completion = __esm(() => {
|
|
6641
6840
|
init_board_helpers();
|
|
@@ -6864,7 +7063,7 @@ class StateStore {
|
|
|
6864
7063
|
const raw = readFileSync4(this.path, "utf-8");
|
|
6865
7064
|
const parsed = JSON.parse(raw);
|
|
6866
7065
|
if (parsed?.version !== SCHEMA_VERSION) {
|
|
6867
|
-
log.warn(
|
|
7066
|
+
log.warn(TAG23, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
|
|
6868
7067
|
return {
|
|
6869
7068
|
version: SCHEMA_VERSION,
|
|
6870
7069
|
daemonId: null,
|
|
@@ -6885,7 +7084,7 @@ class StateStore {
|
|
|
6885
7084
|
daily: parsed.daily ?? []
|
|
6886
7085
|
};
|
|
6887
7086
|
} catch (err) {
|
|
6888
|
-
log.error(
|
|
7087
|
+
log.error(TAG23, `failed to read state file: ${err instanceof Error ? err.message : err}`);
|
|
6889
7088
|
return emptyState();
|
|
6890
7089
|
}
|
|
6891
7090
|
}
|
|
@@ -7074,7 +7273,7 @@ class StateStore {
|
|
|
7074
7273
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
7075
7274
|
}
|
|
7076
7275
|
}
|
|
7077
|
-
var
|
|
7276
|
+
var TAG23 = "state-store", SCHEMA_VERSION = 1;
|
|
7078
7277
|
var init_state_store = __esm(() => {
|
|
7079
7278
|
init_log();
|
|
7080
7279
|
});
|
|
@@ -7101,7 +7300,7 @@ function normalizeToolResultContent(raw) {
|
|
|
7101
7300
|
return String(raw);
|
|
7102
7301
|
}
|
|
7103
7302
|
}
|
|
7104
|
-
var
|
|
7303
|
+
var TAG24 = "stream-parser", StreamParser;
|
|
7105
7304
|
var init_stream_parser = __esm(() => {
|
|
7106
7305
|
init_log();
|
|
7107
7306
|
StreamParser = class StreamParser extends EventEmitter {
|
|
@@ -7149,14 +7348,14 @@ var init_stream_parser = __esm(() => {
|
|
|
7149
7348
|
try {
|
|
7150
7349
|
msg = JSON.parse(line);
|
|
7151
7350
|
} catch {
|
|
7152
|
-
log.debug(
|
|
7351
|
+
log.debug(TAG24, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
7153
7352
|
return;
|
|
7154
7353
|
}
|
|
7155
7354
|
try {
|
|
7156
7355
|
this.handleMessage(msg);
|
|
7157
7356
|
} catch (err) {
|
|
7158
7357
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7159
|
-
log.warn(
|
|
7358
|
+
log.warn(TAG24, `Error handling stream event: ${errMsg}`);
|
|
7160
7359
|
this.emit("parse_error", errMsg);
|
|
7161
7360
|
}
|
|
7162
7361
|
}
|
|
@@ -7242,7 +7441,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
7242
7441
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
7243
7442
|
if (i < attempts - 1) {
|
|
7244
7443
|
const wait = backoffMs * 2 ** i;
|
|
7245
|
-
log.warn(
|
|
7444
|
+
log.warn(TAG25, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
7246
7445
|
await new Promise((r) => setTimeout(r, wait));
|
|
7247
7446
|
}
|
|
7248
7447
|
}
|
|
@@ -7265,10 +7464,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
7265
7464
|
if (opts.strictColumn) {
|
|
7266
7465
|
throw new TransitionError("move", 1, msg);
|
|
7267
7466
|
}
|
|
7268
|
-
log.warn(
|
|
7467
|
+
log.warn(TAG25, `#${shortId}: ${msg} — skipping move`);
|
|
7269
7468
|
} else if (card.column_id !== target.id) {
|
|
7270
7469
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
7271
|
-
log.info(
|
|
7470
|
+
log.info(TAG25, `#${shortId} → "${target.name}"`);
|
|
7272
7471
|
card.column_id = target.id;
|
|
7273
7472
|
moveLanded = true;
|
|
7274
7473
|
} else {
|
|
@@ -7287,7 +7486,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
7287
7486
|
continue;
|
|
7288
7487
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
7289
7488
|
existing.add(labelId);
|
|
7290
|
-
log.info(
|
|
7489
|
+
log.info(TAG25, `#${shortId} +label "${name}"`);
|
|
7291
7490
|
}
|
|
7292
7491
|
card.labelIds = Array.from(existing);
|
|
7293
7492
|
}
|
|
@@ -7299,22 +7498,22 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
7299
7498
|
continue;
|
|
7300
7499
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
7301
7500
|
existing.delete(match.id);
|
|
7302
|
-
log.info(
|
|
7501
|
+
log.info(TAG25, `#${shortId} -label "${name}"`);
|
|
7303
7502
|
}
|
|
7304
7503
|
card.labelIds = Array.from(existing);
|
|
7305
7504
|
}
|
|
7306
7505
|
if (plan.updateCard) {
|
|
7307
7506
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
7308
|
-
log.info(
|
|
7507
|
+
log.info(TAG25, `#${shortId} updated`);
|
|
7309
7508
|
}
|
|
7310
7509
|
if (plan.endSession) {
|
|
7311
7510
|
await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
7312
|
-
log.info(
|
|
7511
|
+
log.info(TAG25, `#${shortId} session ended (${plan.endSession.status})`);
|
|
7313
7512
|
}
|
|
7314
7513
|
if (plan.assignAgent !== undefined) {
|
|
7315
7514
|
const assignedAgentId = plan.assignAgent;
|
|
7316
7515
|
await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
|
|
7317
|
-
log.info(
|
|
7516
|
+
log.info(TAG25, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
|
|
7318
7517
|
}
|
|
7319
7518
|
if (opts.store && opts.runId) {
|
|
7320
7519
|
try {
|
|
@@ -7327,11 +7526,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
7327
7526
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
7328
7527
|
return result?.label?.id ?? null;
|
|
7329
7528
|
} catch (err) {
|
|
7330
|
-
log.warn(
|
|
7529
|
+
log.warn(TAG25, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
7331
7530
|
return null;
|
|
7332
7531
|
}
|
|
7333
7532
|
}
|
|
7334
|
-
var
|
|
7533
|
+
var TAG25 = "transition", TransitionError;
|
|
7335
7534
|
var init_transitions = __esm(() => {
|
|
7336
7535
|
init_log();
|
|
7337
7536
|
TransitionError = class TransitionError extends Error {
|
|
@@ -7349,7 +7548,7 @@ var init_transitions = __esm(() => {
|
|
|
7349
7548
|
});
|
|
7350
7549
|
|
|
7351
7550
|
// src/review-worker.ts
|
|
7352
|
-
import { execFileSync as
|
|
7551
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
7353
7552
|
|
|
7354
7553
|
class ReviewWorker {
|
|
7355
7554
|
config;
|
|
@@ -7415,7 +7614,7 @@ class ReviewWorker {
|
|
|
7415
7614
|
}
|
|
7416
7615
|
}
|
|
7417
7616
|
get tag() {
|
|
7418
|
-
return `${
|
|
7617
|
+
return `${TAG26}:${this.id}`;
|
|
7419
7618
|
}
|
|
7420
7619
|
get isIdle() {
|
|
7421
7620
|
return this.state === "idle";
|
|
@@ -7467,7 +7666,7 @@ class ReviewWorker {
|
|
|
7467
7666
|
costCents: 0,
|
|
7468
7667
|
numTurns: 0
|
|
7469
7668
|
});
|
|
7470
|
-
const repoRoot =
|
|
7669
|
+
const repoRoot = execFileSync11("git", ["rev-parse", "--show-toplevel"], {
|
|
7471
7670
|
encoding: "utf-8",
|
|
7472
7671
|
timeout: 5000
|
|
7473
7672
|
}).trim();
|
|
@@ -7486,7 +7685,8 @@ class ReviewWorker {
|
|
|
7486
7685
|
agentId: this.identity.agentId,
|
|
7487
7686
|
status: "working",
|
|
7488
7687
|
currentTask: "Setting up review worktree",
|
|
7489
|
-
progressPercent: 5
|
|
7688
|
+
progressPercent: 5,
|
|
7689
|
+
modelName: this.config.claude.reviewModel
|
|
7490
7690
|
});
|
|
7491
7691
|
this.sessionId = reviewSession && typeof reviewSession === "object" && "id" in reviewSession ? reviewSession.id ?? null : null;
|
|
7492
7692
|
const labelPromise = addLabelByName(this.client, card, "agent", "#8b5cf6");
|
|
@@ -7535,7 +7735,7 @@ class ReviewWorker {
|
|
|
7535
7735
|
return;
|
|
7536
7736
|
let diff = "";
|
|
7537
7737
|
try {
|
|
7538
|
-
diff =
|
|
7738
|
+
diff = execFileSync11("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
|
|
7539
7739
|
} catch {
|
|
7540
7740
|
diff = "(unable to retrieve diff)";
|
|
7541
7741
|
}
|
|
@@ -7587,6 +7787,7 @@ class ReviewWorker {
|
|
|
7587
7787
|
this.cancel("timeout");
|
|
7588
7788
|
}, this.config.review.maxTimeout);
|
|
7589
7789
|
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks);
|
|
7790
|
+
this.progressTracker.setRequestedModel(this.config.claude.reviewModel);
|
|
7590
7791
|
const stdout = await this.spawnClaude(userPrompt, systemPrompt, this.progressTracker, card.short_id);
|
|
7591
7792
|
this.lastSessionStats = this.progressTracker?.stats ?? null;
|
|
7592
7793
|
this.progressTracker?.stop();
|
|
@@ -7878,7 +8079,7 @@ class ReviewWorker {
|
|
|
7878
8079
|
this.lastSessionStats = null;
|
|
7879
8080
|
}
|
|
7880
8081
|
}
|
|
7881
|
-
var
|
|
8082
|
+
var TAG26 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
7882
8083
|
var init_review_worker = __esm(() => {
|
|
7883
8084
|
init_dist();
|
|
7884
8085
|
init_board_helpers();
|
|
@@ -7931,7 +8132,7 @@ class SleepGuard {
|
|
|
7931
8132
|
if (!this.child.killed)
|
|
7932
8133
|
this.child.kill("SIGTERM");
|
|
7933
8134
|
this.child = null;
|
|
7934
|
-
log.info(
|
|
8135
|
+
log.info(TAG27, "sleep assertion released");
|
|
7935
8136
|
}
|
|
7936
8137
|
}
|
|
7937
8138
|
start() {
|
|
@@ -7946,7 +8147,7 @@ class SleepGuard {
|
|
|
7946
8147
|
spawned = true;
|
|
7947
8148
|
});
|
|
7948
8149
|
child.on("error", (err) => {
|
|
7949
|
-
log.warn(
|
|
8150
|
+
log.warn(TAG27, `caffeinate unavailable: ${err.message}`);
|
|
7950
8151
|
if (this.child === child)
|
|
7951
8152
|
this.child = null;
|
|
7952
8153
|
});
|
|
@@ -7959,13 +8160,13 @@ class SleepGuard {
|
|
|
7959
8160
|
});
|
|
7960
8161
|
child.unref();
|
|
7961
8162
|
this.child = child;
|
|
7962
|
-
log.info(
|
|
8163
|
+
log.info(TAG27, "sleep assertion acquired (caffeinate -i)");
|
|
7963
8164
|
} catch (err) {
|
|
7964
|
-
log.warn(
|
|
8165
|
+
log.warn(TAG27, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
7965
8166
|
}
|
|
7966
8167
|
}
|
|
7967
8168
|
}
|
|
7968
|
-
var
|
|
8169
|
+
var TAG27 = "sleep-guard";
|
|
7969
8170
|
var init_sleep_guard = __esm(() => {
|
|
7970
8171
|
init_log();
|
|
7971
8172
|
});
|
|
@@ -7976,7 +8177,7 @@ async function fetchBlocksLinks(client, cardId) {
|
|
|
7976
8177
|
const { links } = await client.getCardLinks(cardId);
|
|
7977
8178
|
return links.filter((l) => l.link_type === "blocks");
|
|
7978
8179
|
} catch (err) {
|
|
7979
|
-
log.warn(
|
|
8180
|
+
log.warn(TAG28, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
7980
8181
|
return null;
|
|
7981
8182
|
}
|
|
7982
8183
|
}
|
|
@@ -8008,27 +8209,27 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
8008
8209
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
8009
8210
|
if (successors.length === 0)
|
|
8010
8211
|
return;
|
|
8011
|
-
log.info(
|
|
8212
|
+
log.info(TAG28, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
8012
8213
|
for (const link of successors) {
|
|
8013
8214
|
const successorId = link.target_card.id;
|
|
8014
8215
|
try {
|
|
8015
8216
|
const { card } = await deps.client.getCard(successorId);
|
|
8016
8217
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
8017
|
-
log.info(
|
|
8218
|
+
log.info(TAG28, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
8018
8219
|
await deps.client.updateCard(successorId, {
|
|
8019
8220
|
assignedAgentId: deps.agentId
|
|
8020
8221
|
});
|
|
8021
8222
|
} else {
|
|
8022
|
-
log.debug(
|
|
8223
|
+
log.debug(TAG28, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
8023
8224
|
continue;
|
|
8024
8225
|
}
|
|
8025
8226
|
await deps.enqueue(successorId);
|
|
8026
8227
|
} catch (err) {
|
|
8027
|
-
log.warn(
|
|
8228
|
+
log.warn(TAG28, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
8028
8229
|
}
|
|
8029
8230
|
}
|
|
8030
8231
|
}
|
|
8031
|
-
var
|
|
8232
|
+
var TAG28 = "unblock";
|
|
8032
8233
|
var init_unblock = __esm(() => {
|
|
8033
8234
|
init_log();
|
|
8034
8235
|
});
|
|
@@ -8183,7 +8384,7 @@ class CliAgentRunner {
|
|
|
8183
8384
|
events: batch
|
|
8184
8385
|
});
|
|
8185
8386
|
} catch (err) {
|
|
8186
|
-
log.warn(
|
|
8387
|
+
log.warn(TAG29, `Failed to flush run events: ${err}`);
|
|
8187
8388
|
this.buffer.unshift(...batch);
|
|
8188
8389
|
if (this.buffer.length > MAX_BUFFER) {
|
|
8189
8390
|
this.buffer.length = MAX_BUFFER;
|
|
@@ -8220,7 +8421,7 @@ function mapCost(cost) {
|
|
|
8220
8421
|
durationMs: cost.durationMs
|
|
8221
8422
|
};
|
|
8222
8423
|
}
|
|
8223
|
-
var
|
|
8424
|
+
var TAG29 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000;
|
|
8224
8425
|
var init_cli_agent_runner = __esm(() => {
|
|
8225
8426
|
init_log();
|
|
8226
8427
|
});
|
|
@@ -8253,11 +8454,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
8253
8454
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
8254
8455
|
The daemon owns the run lifecycle: once your work is committed it ends the agent session, pushes the branch, and moves the card to Review for you. Do NOT call harmony_end_agent_session, do NOT start a new session, and do NOT move the card or change its column yourself. If the skill driving this work tells you to move the card or end the session as a final step, SKIP it — it is handled for you (those tools are disabled for this run). Finish the implementation, commit, and stop.`
|
|
8255
8456
|
});
|
|
8256
|
-
log.info(
|
|
8457
|
+
log.info(TAG30, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
8257
8458
|
return result.prompt + pastEpisodesSection;
|
|
8258
8459
|
} catch (err) {
|
|
8259
8460
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8260
|
-
log.warn(
|
|
8461
|
+
log.warn(TAG30, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
8261
8462
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
8262
8463
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
|
|
8263
8464
|
}
|
|
@@ -8275,7 +8476,7 @@ async function renderCommentsSection(client, cardId) {
|
|
|
8275
8476
|
|
|
8276
8477
|
${section}` : "";
|
|
8277
8478
|
} catch (err) {
|
|
8278
|
-
log.warn(
|
|
8479
|
+
log.warn(TAG30, "comment-thread fetch failed", {
|
|
8279
8480
|
event: "comment_fetch_failed",
|
|
8280
8481
|
error: err instanceof Error ? err.message : String(err)
|
|
8281
8482
|
});
|
|
@@ -8325,7 +8526,7 @@ ${description}`.trim();
|
|
|
8325
8526
|
## Similar past tasks
|
|
8326
8527
|
${bullets}`;
|
|
8327
8528
|
} catch (err) {
|
|
8328
|
-
log.warn(
|
|
8529
|
+
log.warn(TAG30, "past-episodes recall failed", {
|
|
8329
8530
|
event: "episode_recall_failed",
|
|
8330
8531
|
error: err instanceof Error ? err.message : String(err)
|
|
8331
8532
|
});
|
|
@@ -8366,7 +8567,7 @@ ${subtaskStr}
|
|
|
8366
8567
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
8367
8568
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
8368
8569
|
}
|
|
8369
|
-
var
|
|
8570
|
+
var TAG30 = "prompt";
|
|
8370
8571
|
var init_prompt = __esm(() => {
|
|
8371
8572
|
init_dist();
|
|
8372
8573
|
init_log();
|
|
@@ -8389,7 +8590,7 @@ async function resolveStageColumnName(client, card, stage) {
|
|
|
8389
8590
|
const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
|
|
8390
8591
|
return match ? match.name : null;
|
|
8391
8592
|
} catch (err) {
|
|
8392
|
-
log.warn(
|
|
8593
|
+
log.warn(TAG31, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8393
8594
|
return null;
|
|
8394
8595
|
}
|
|
8395
8596
|
}
|
|
@@ -8433,7 +8634,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
8433
8634
|
evidence,
|
|
8434
8635
|
summary
|
|
8435
8636
|
});
|
|
8436
|
-
log.info(
|
|
8637
|
+
log.info(TAG31, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
|
|
8437
8638
|
if (decision === "exit") {
|
|
8438
8639
|
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
8439
8640
|
deps.sink?.recordLoopCompleted?.({
|
|
@@ -8475,7 +8676,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
8475
8676
|
await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
|
|
8476
8677
|
keepAttempts: true
|
|
8477
8678
|
});
|
|
8478
|
-
log.info(
|
|
8679
|
+
log.info(TAG31, `#${card.short_id} LoopExhausted: ${reason}`);
|
|
8479
8680
|
return { kind: "held_gate_unmet", reason };
|
|
8480
8681
|
}
|
|
8481
8682
|
await deps.stateStore.decrementAttempt(card.id).catch(() => {});
|
|
@@ -8489,7 +8690,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
8489
8690
|
addLabels: [{ name: AGENT_LABEL }],
|
|
8490
8691
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
8491
8692
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
8492
|
-
log.info(
|
|
8693
|
+
log.info(TAG31, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
|
|
8493
8694
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
8494
8695
|
}
|
|
8495
8696
|
async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
|
|
@@ -8508,7 +8709,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
|
|
|
8508
8709
|
});
|
|
8509
8710
|
await deps.client.addComment(card.id, body, { commentType: "decision" });
|
|
8510
8711
|
} catch (err) {
|
|
8511
|
-
log.warn(
|
|
8712
|
+
log.warn(TAG31, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8512
8713
|
}
|
|
8513
8714
|
}
|
|
8514
8715
|
async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
|
|
@@ -8539,7 +8740,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
8539
8740
|
reason: "Playbook complete — final stage gate passed."
|
|
8540
8741
|
});
|
|
8541
8742
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
8542
|
-
log.info(
|
|
8743
|
+
log.info(TAG31, `#${card.short_id} terminal stage "${stage.name}" passed — marked done`);
|
|
8543
8744
|
return { kind: "completed_terminal" };
|
|
8544
8745
|
}
|
|
8545
8746
|
if (next.kind === "out_of_range") {
|
|
@@ -8571,7 +8772,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
8571
8772
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
8572
8773
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
8573
8774
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
8574
|
-
log.info(
|
|
8775
|
+
log.info(TAG31, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
|
|
8575
8776
|
return { kind: "advanced", toStageId: next.stage.id, toColumn };
|
|
8576
8777
|
}
|
|
8577
8778
|
async function handleGateUnmet(card, stage, summary, deps) {
|
|
@@ -8590,7 +8791,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
8590
8791
|
await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
|
|
8591
8792
|
keepAttempts: true
|
|
8592
8793
|
});
|
|
8593
|
-
log.info(
|
|
8794
|
+
log.info(TAG31, `#${card.short_id} GateUnmetExhausted: ${reason}`);
|
|
8594
8795
|
return { kind: "held_gate_unmet", reason };
|
|
8595
8796
|
}
|
|
8596
8797
|
const toColumn = await resolveStageColumnName(deps.client, card, stage) ?? deps.fallbackColumn;
|
|
@@ -8602,7 +8803,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
8602
8803
|
addLabels: [{ name: AGENT_LABEL }],
|
|
8603
8804
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
8604
8805
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
8605
|
-
log.info(
|
|
8806
|
+
log.info(TAG31, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
|
|
8606
8807
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
8607
8808
|
}
|
|
8608
8809
|
async function holdForHuman(client, card, reason, runId, stateStore, opts = {}) {
|
|
@@ -8622,10 +8823,10 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
8622
8823
|
}
|
|
8623
8824
|
}, { store: stateStore, runId });
|
|
8624
8825
|
} catch (err) {
|
|
8625
|
-
log.warn(
|
|
8826
|
+
log.warn(TAG31, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8626
8827
|
}
|
|
8627
8828
|
}
|
|
8628
|
-
var
|
|
8829
|
+
var TAG31 = "stage-advance", AGENT_LABEL = "agent";
|
|
8629
8830
|
var init_stage_advance = __esm(() => {
|
|
8630
8831
|
init_dist();
|
|
8631
8832
|
init_log();
|
|
@@ -8772,7 +8973,7 @@ class Worker {
|
|
|
8772
8973
|
}
|
|
8773
8974
|
}
|
|
8774
8975
|
get tag() {
|
|
8775
|
-
return `${
|
|
8976
|
+
return `${TAG32}:${this.id}`;
|
|
8776
8977
|
}
|
|
8777
8978
|
get isIdle() {
|
|
8778
8979
|
return this.state === "idle";
|
|
@@ -8828,24 +9029,26 @@ class Worker {
|
|
|
8828
9029
|
costCents: 0,
|
|
8829
9030
|
numTurns: 0
|
|
8830
9031
|
});
|
|
9032
|
+
const implementModel = this.selectImplementModel(card);
|
|
8831
9033
|
const { session } = await this.client.startAgentSession(card.id, {
|
|
8832
9034
|
agentIdentifier: agentIdentifier(this.id),
|
|
8833
9035
|
agentName: AGENT_NAME,
|
|
8834
9036
|
agentId: this.identity.agentId,
|
|
8835
9037
|
status: "working",
|
|
8836
9038
|
currentTask: "Setting up worktree",
|
|
8837
|
-
progressPercent: 5
|
|
9039
|
+
progressPercent: 5,
|
|
9040
|
+
modelName: implementModel
|
|
8838
9041
|
});
|
|
8839
9042
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
8840
9043
|
if (!sid) {
|
|
8841
|
-
log.warn(
|
|
9044
|
+
log.warn(TAG32, "startAgentSession returned no session id");
|
|
8842
9045
|
}
|
|
8843
9046
|
this.sessionId = sid;
|
|
8844
9047
|
if (this.sessionId) {
|
|
8845
9048
|
this.cliRunner = new CliAgentRunner(this.client, card.id, this.sessionId);
|
|
8846
9049
|
this.cliRunner.recordRunStarted({
|
|
8847
9050
|
runner: this.config.runner,
|
|
8848
|
-
model:
|
|
9051
|
+
model: implementModel
|
|
8849
9052
|
});
|
|
8850
9053
|
}
|
|
8851
9054
|
await this.recordPhase("preparing");
|
|
@@ -8934,7 +9137,7 @@ ${basePrompt}`;
|
|
|
8934
9137
|
}, this.config.maxTimeout);
|
|
8935
9138
|
this.activeRunSpawnOpts = computeRunSpawnGating(stageCtx.kind === "run" ? stageCtx.allowedTools : null);
|
|
8936
9139
|
await this.spawnClaude(prompt, card, subtasks, {
|
|
8937
|
-
model:
|
|
9140
|
+
model: implementModel,
|
|
8938
9141
|
...this.activeRunSpawnOpts ?? {}
|
|
8939
9142
|
});
|
|
8940
9143
|
if (this.aborted)
|
|
@@ -9295,6 +9498,10 @@ ${basePrompt}`;
|
|
|
9295
9498
|
artifactType: stage.artifact_type
|
|
9296
9499
|
},
|
|
9297
9500
|
checklist: { subtasks, cardDone: card.done ?? false },
|
|
9501
|
+
command: {
|
|
9502
|
+
worktreePath,
|
|
9503
|
+
metrics: this.config.playbooks.metrics ?? {}
|
|
9504
|
+
},
|
|
9298
9505
|
...review ? { review } : {}
|
|
9299
9506
|
});
|
|
9300
9507
|
const context = {
|
|
@@ -9705,6 +9912,7 @@ ${basePrompt}`;
|
|
|
9705
9912
|
});
|
|
9706
9913
|
const parser = new StreamParser;
|
|
9707
9914
|
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
|
|
9915
|
+
this.progressTracker.setRequestedModel(model);
|
|
9708
9916
|
this.progressTracker.attach(parser);
|
|
9709
9917
|
this.cliRunner?.attach(parser);
|
|
9710
9918
|
if (this.cliRunner) {
|
|
@@ -9785,6 +9993,7 @@ ${basePrompt}`;
|
|
|
9785
9993
|
`);
|
|
9786
9994
|
}
|
|
9787
9995
|
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
|
|
9996
|
+
this.progressTracker.setRequestedModel(model);
|
|
9788
9997
|
if (this.cliRunner) {
|
|
9789
9998
|
this.progressTracker.setRunEventSink(this.cliRunner);
|
|
9790
9999
|
}
|
|
@@ -9893,7 +10102,7 @@ ${basePrompt}`;
|
|
|
9893
10102
|
this.runTurns = 0;
|
|
9894
10103
|
}
|
|
9895
10104
|
}
|
|
9896
|
-
var
|
|
10105
|
+
var TAG32 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
|
|
9897
10106
|
var init_worker = __esm(() => {
|
|
9898
10107
|
init_dist();
|
|
9899
10108
|
init_board_helpers();
|
|
@@ -9971,41 +10180,41 @@ class Pool {
|
|
|
9971
10180
|
}
|
|
9972
10181
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
9973
10182
|
if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
|
|
9974
|
-
log.debug(
|
|
10183
|
+
log.debug(TAG33, `Card ${card.id} already queued, active, or reserved, skipping`);
|
|
9975
10184
|
return;
|
|
9976
10185
|
}
|
|
9977
10186
|
this.reservations.add(card.id);
|
|
9978
10187
|
try {
|
|
9979
10188
|
if (mode === "implement") {
|
|
9980
10189
|
if (this.authPaused) {
|
|
9981
|
-
log.debug(
|
|
10190
|
+
log.debug(TAG33, `#${card.short_id} held — agent paused (auth error)`);
|
|
9982
10191
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
9983
10192
|
return;
|
|
9984
10193
|
}
|
|
9985
10194
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
9986
10195
|
if (cooldownMs > 0) {
|
|
9987
|
-
log.debug(
|
|
10196
|
+
log.debug(TAG33, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
9988
10197
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
9989
10198
|
return;
|
|
9990
10199
|
}
|
|
9991
10200
|
const decision = this.budget.check(card.id);
|
|
9992
10201
|
if (!decision.allow) {
|
|
9993
10202
|
if (decision.reason === "daily_budget") {
|
|
9994
|
-
log.warn(
|
|
10203
|
+
log.warn(TAG33, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
9995
10204
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
9996
10205
|
} else {
|
|
9997
|
-
log.debug(
|
|
10206
|
+
log.debug(TAG33, `#${card.short_id} gave up: ${decision.detail}`);
|
|
9998
10207
|
}
|
|
9999
10208
|
return;
|
|
10000
10209
|
}
|
|
10001
10210
|
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
10002
10211
|
if (blockers === null) {
|
|
10003
|
-
log.warn(
|
|
10212
|
+
log.warn(TAG33, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
10004
10213
|
return;
|
|
10005
10214
|
}
|
|
10006
10215
|
if (blockers.length > 0) {
|
|
10007
10216
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
10008
|
-
log.info(
|
|
10217
|
+
log.info(TAG33, `#${card.short_id} blocked by ${list} — waiting`);
|
|
10009
10218
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
10010
10219
|
return;
|
|
10011
10220
|
}
|
|
@@ -10037,7 +10246,7 @@ class Pool {
|
|
|
10037
10246
|
});
|
|
10038
10247
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
10039
10248
|
} catch (err) {
|
|
10040
|
-
log.debug(
|
|
10249
|
+
log.debug(TAG33, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10041
10250
|
}
|
|
10042
10251
|
}
|
|
10043
10252
|
noteApiError(err) {
|
|
@@ -10045,7 +10254,7 @@ class Pool {
|
|
|
10045
10254
|
return;
|
|
10046
10255
|
if (err.kind === "auth") {
|
|
10047
10256
|
if (!this.authPaused) {
|
|
10048
|
-
log.error(
|
|
10257
|
+
log.error(TAG33, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
10049
10258
|
}
|
|
10050
10259
|
this.authPaused = true;
|
|
10051
10260
|
return;
|
|
@@ -10054,7 +10263,7 @@ class Pool {
|
|
|
10054
10263
|
const until = Date.now() + cooldownMs;
|
|
10055
10264
|
if (until > this.apiCooldownUntil) {
|
|
10056
10265
|
this.apiCooldownUntil = until;
|
|
10057
|
-
log.warn(
|
|
10266
|
+
log.warn(TAG33, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
10058
10267
|
}
|
|
10059
10268
|
}
|
|
10060
10269
|
apiCooldownRemainingMs() {
|
|
@@ -10068,13 +10277,13 @@ class Pool {
|
|
|
10068
10277
|
const removed = queue.remove(cardId);
|
|
10069
10278
|
if (removed) {
|
|
10070
10279
|
this.cardDataCache.delete(cardId);
|
|
10071
|
-
log.info(
|
|
10280
|
+
log.info(TAG33, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
10072
10281
|
return;
|
|
10073
10282
|
}
|
|
10074
10283
|
}
|
|
10075
10284
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
10076
10285
|
if (worker) {
|
|
10077
|
-
log.info(
|
|
10286
|
+
log.info(TAG33, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
10078
10287
|
await worker.cancel("unassigned");
|
|
10079
10288
|
}
|
|
10080
10289
|
}
|
|
@@ -10107,10 +10316,10 @@ class Pool {
|
|
|
10107
10316
|
async handleAgentCommand(cardId, command) {
|
|
10108
10317
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
10109
10318
|
if (!worker) {
|
|
10110
|
-
log.debug(
|
|
10319
|
+
log.debug(TAG33, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
10111
10320
|
return;
|
|
10112
10321
|
}
|
|
10113
|
-
log.info(
|
|
10322
|
+
log.info(TAG33, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
10114
10323
|
switch (command) {
|
|
10115
10324
|
case "pause":
|
|
10116
10325
|
await worker.pause();
|
|
@@ -10158,7 +10367,7 @@ class Pool {
|
|
|
10158
10367
|
};
|
|
10159
10368
|
}
|
|
10160
10369
|
async shutdown() {
|
|
10161
|
-
log.info(
|
|
10370
|
+
log.info(TAG33, "Shutting down pool...");
|
|
10162
10371
|
this.shuttingDown = true;
|
|
10163
10372
|
const active = [
|
|
10164
10373
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -10166,7 +10375,7 @@ class Pool {
|
|
|
10166
10375
|
];
|
|
10167
10376
|
await Promise.all(active.map((w) => w.cancel("shutdown")));
|
|
10168
10377
|
this.sleepGuard.stop();
|
|
10169
|
-
log.info(
|
|
10378
|
+
log.info(TAG33, "Pool shutdown complete");
|
|
10170
10379
|
}
|
|
10171
10380
|
reservations = new Set;
|
|
10172
10381
|
cardDataCache = new Map;
|
|
@@ -10175,7 +10384,7 @@ class Pool {
|
|
|
10175
10384
|
return false;
|
|
10176
10385
|
const idle = workers.find((w) => w.isIdle);
|
|
10177
10386
|
if (!idle) {
|
|
10178
|
-
log.debug(
|
|
10387
|
+
log.debug(TAG33, `No idle ${label} workers (queue: ${queue.length})`);
|
|
10179
10388
|
return false;
|
|
10180
10389
|
}
|
|
10181
10390
|
const next = queue.dequeue();
|
|
@@ -10183,18 +10392,18 @@ class Pool {
|
|
|
10183
10392
|
return false;
|
|
10184
10393
|
const data = this.cardDataCache.get(next.cardId);
|
|
10185
10394
|
if (!data) {
|
|
10186
|
-
log.warn(
|
|
10395
|
+
log.warn(TAG33, `No cached data for card ${next.cardId}, skipping`);
|
|
10187
10396
|
return false;
|
|
10188
10397
|
}
|
|
10189
10398
|
this.cardDataCache.delete(next.cardId);
|
|
10190
10399
|
this.lastWaitingEmit.delete(next.cardId);
|
|
10191
|
-
log.info(
|
|
10400
|
+
log.info(TAG33, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
10192
10401
|
this.sleepGuard.acquire();
|
|
10193
10402
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
10194
10403
|
return true;
|
|
10195
10404
|
}
|
|
10196
10405
|
}
|
|
10197
|
-
var
|
|
10406
|
+
var TAG33 = "pool";
|
|
10198
10407
|
var init_pool = __esm(() => {
|
|
10199
10408
|
init_error_classifier();
|
|
10200
10409
|
init_log();
|
|
@@ -10236,7 +10445,7 @@ function load(path) {
|
|
|
10236
10445
|
return parsed;
|
|
10237
10446
|
return {};
|
|
10238
10447
|
} catch (err) {
|
|
10239
|
-
log.warn(
|
|
10448
|
+
log.warn(TAG34, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
10240
10449
|
return {};
|
|
10241
10450
|
}
|
|
10242
10451
|
}
|
|
@@ -10254,7 +10463,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
10254
10463
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
10255
10464
|
save(path, registry);
|
|
10256
10465
|
} catch (err) {
|
|
10257
|
-
log.warn(
|
|
10466
|
+
log.warn(TAG34, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
10258
10467
|
}
|
|
10259
10468
|
}
|
|
10260
10469
|
function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
|
|
@@ -10270,10 +10479,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
10270
10479
|
delete registry[projectId];
|
|
10271
10480
|
save(path, registry);
|
|
10272
10481
|
} catch (err) {
|
|
10273
|
-
log.warn(
|
|
10482
|
+
log.warn(TAG34, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
10274
10483
|
}
|
|
10275
10484
|
}
|
|
10276
|
-
var
|
|
10485
|
+
var TAG34 = "port-registry";
|
|
10277
10486
|
var init_port_registry = __esm(() => {
|
|
10278
10487
|
init_log();
|
|
10279
10488
|
});
|
|
@@ -10294,7 +10503,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
10294
10503
|
const { card } = await client.getCard(cardId);
|
|
10295
10504
|
return card;
|
|
10296
10505
|
} catch (err) {
|
|
10297
|
-
log.warn(
|
|
10506
|
+
log.warn(TAG35, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10298
10507
|
return null;
|
|
10299
10508
|
}
|
|
10300
10509
|
}
|
|
@@ -10304,7 +10513,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
10304
10513
|
return [];
|
|
10305
10514
|
}
|
|
10306
10515
|
const outcomes = [];
|
|
10307
|
-
log.info(
|
|
10516
|
+
log.info(TAG35, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
10308
10517
|
for (const run of active) {
|
|
10309
10518
|
const outcome = {
|
|
10310
10519
|
runId: run.runId,
|
|
@@ -10316,11 +10525,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
10316
10525
|
};
|
|
10317
10526
|
outcomes.push(outcome);
|
|
10318
10527
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
10319
|
-
log.warn(
|
|
10528
|
+
log.warn(TAG35, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
10320
10529
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
10321
10530
|
continue;
|
|
10322
10531
|
}
|
|
10323
|
-
log.info(
|
|
10532
|
+
log.info(TAG35, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
10324
10533
|
await recoverRun(run, store, client, config, outcome, {
|
|
10325
10534
|
rollbackAttempt: true
|
|
10326
10535
|
});
|
|
@@ -10340,7 +10549,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
10340
10549
|
} catch (err) {
|
|
10341
10550
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10342
10551
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
10343
|
-
log.warn(
|
|
10552
|
+
log.warn(TAG35, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
10344
10553
|
}
|
|
10345
10554
|
const card = await fetchCardSafely(client, run.cardId);
|
|
10346
10555
|
if (card) {
|
|
@@ -10392,9 +10601,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
10392
10601
|
outcome.errors.push(`decrementAttempt: ${msg}`);
|
|
10393
10602
|
}
|
|
10394
10603
|
}
|
|
10395
|
-
log.info(
|
|
10604
|
+
log.info(TAG35, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
10396
10605
|
}
|
|
10397
|
-
var
|
|
10606
|
+
var TAG35 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
10398
10607
|
var init_recovery = __esm(() => {
|
|
10399
10608
|
init_board_helpers();
|
|
10400
10609
|
init_log();
|
|
@@ -10405,14 +10614,14 @@ var init_recovery = __esm(() => {
|
|
|
10405
10614
|
async function claimReviewCard(client, cardId, agentId) {
|
|
10406
10615
|
try {
|
|
10407
10616
|
const { claimed } = await client.claimCard(cardId, agentId);
|
|
10408
|
-
log.debug(
|
|
10617
|
+
log.debug(TAG36, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
10409
10618
|
return claimed;
|
|
10410
10619
|
} catch (err) {
|
|
10411
|
-
log.error(
|
|
10620
|
+
log.error(TAG36, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
10412
10621
|
return false;
|
|
10413
10622
|
}
|
|
10414
10623
|
}
|
|
10415
|
-
var
|
|
10624
|
+
var TAG36 = "claim";
|
|
10416
10625
|
var init_claim = __esm(() => {
|
|
10417
10626
|
init_log();
|
|
10418
10627
|
});
|
|
@@ -10465,22 +10674,22 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
10465
10674
|
continue;
|
|
10466
10675
|
const won = await claimReviewCard(client, card.id, agentId);
|
|
10467
10676
|
if (!won) {
|
|
10468
|
-
log.debug(
|
|
10677
|
+
log.debug(TAG37, `#${card.short_id} — lost the review claim race, skipping`);
|
|
10469
10678
|
continue;
|
|
10470
10679
|
}
|
|
10471
|
-
log.warn(
|
|
10680
|
+
log.warn(TAG37, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
10472
10681
|
reclaimed.push(card.id);
|
|
10473
10682
|
if (opts.onClaimed) {
|
|
10474
10683
|
try {
|
|
10475
10684
|
await opts.onClaimed(card);
|
|
10476
10685
|
} catch (err) {
|
|
10477
|
-
log.error(
|
|
10686
|
+
log.error(TAG37, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
10478
10687
|
}
|
|
10479
10688
|
}
|
|
10480
10689
|
}
|
|
10481
10690
|
return reclaimed;
|
|
10482
10691
|
}
|
|
10483
|
-
var
|
|
10692
|
+
var TAG37 = "strand-recovery";
|
|
10484
10693
|
var init_strand_recovery = __esm(() => {
|
|
10485
10694
|
init_board_helpers();
|
|
10486
10695
|
init_claim();
|
|
@@ -10532,7 +10741,7 @@ class Reconciler {
|
|
|
10532
10741
|
clearInterval(this.timer);
|
|
10533
10742
|
this.timer = null;
|
|
10534
10743
|
}
|
|
10535
|
-
log.info(
|
|
10744
|
+
log.info(TAG38, "Heartbeat stopped");
|
|
10536
10745
|
}
|
|
10537
10746
|
async recoverStaleRuns() {
|
|
10538
10747
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -10549,7 +10758,7 @@ class Reconciler {
|
|
|
10549
10758
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
10550
10759
|
continue;
|
|
10551
10760
|
const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
|
|
10552
|
-
log.warn(
|
|
10761
|
+
log.warn(TAG38, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
10553
10762
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
10554
10763
|
runId: run.runId,
|
|
10555
10764
|
cardId: run.cardId,
|
|
@@ -10576,11 +10785,11 @@ class Reconciler {
|
|
|
10576
10785
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
10577
10786
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
10578
10787
|
continue;
|
|
10579
|
-
log.warn(
|
|
10788
|
+
log.warn(TAG38, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
10580
10789
|
try {
|
|
10581
10790
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
10582
10791
|
} catch (err) {
|
|
10583
|
-
log.error(
|
|
10792
|
+
log.error(TAG38, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
10584
10793
|
}
|
|
10585
10794
|
}
|
|
10586
10795
|
}
|
|
@@ -10612,7 +10821,7 @@ class Reconciler {
|
|
|
10612
10821
|
return;
|
|
10613
10822
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
10614
10823
|
const subtasks = card.subtasks ?? [];
|
|
10615
|
-
log.info(
|
|
10824
|
+
log.info(TAG38, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
10616
10825
|
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
10617
10826
|
}
|
|
10618
10827
|
});
|
|
@@ -10636,11 +10845,11 @@ class Reconciler {
|
|
|
10636
10845
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
10637
10846
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
10638
10847
|
continue;
|
|
10639
|
-
log.warn(
|
|
10848
|
+
log.warn(TAG38, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
10640
10849
|
try {
|
|
10641
10850
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
10642
10851
|
} catch (err) {
|
|
10643
|
-
log.error(
|
|
10852
|
+
log.error(TAG38, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
10644
10853
|
}
|
|
10645
10854
|
}
|
|
10646
10855
|
}
|
|
@@ -10660,7 +10869,8 @@ class Reconciler {
|
|
|
10660
10869
|
reviewColumns: this.reviewColumns,
|
|
10661
10870
|
playbooks: this.agentConfig?.playbooks ?? {
|
|
10662
10871
|
enabled: false,
|
|
10663
|
-
humanStageColumns: []
|
|
10872
|
+
humanStageColumns: [],
|
|
10873
|
+
metrics: {}
|
|
10664
10874
|
}
|
|
10665
10875
|
};
|
|
10666
10876
|
const assignedCards = cards.filter((c) => {
|
|
@@ -10683,21 +10893,21 @@ class Reconciler {
|
|
|
10683
10893
|
const subtasks = card.subtasks ?? [];
|
|
10684
10894
|
const mode = route.mode;
|
|
10685
10895
|
if (route.stage) {
|
|
10686
|
-
log.info(
|
|
10896
|
+
log.info(TAG38, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
|
|
10687
10897
|
}
|
|
10688
10898
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
10689
|
-
log.debug(
|
|
10899
|
+
log.debug(TAG38, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
10690
10900
|
continue;
|
|
10691
10901
|
}
|
|
10692
10902
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
10693
|
-
log.debug(
|
|
10903
|
+
log.debug(TAG38, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
10694
10904
|
continue;
|
|
10695
10905
|
}
|
|
10696
10906
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
10697
|
-
log.debug(
|
|
10907
|
+
log.debug(TAG38, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
|
|
10698
10908
|
continue;
|
|
10699
10909
|
}
|
|
10700
|
-
log.info(
|
|
10910
|
+
log.info(TAG38, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
10701
10911
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
10702
10912
|
}
|
|
10703
10913
|
}
|
|
@@ -10708,18 +10918,18 @@ class Reconciler {
|
|
|
10708
10918
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
10709
10919
|
for (const knownId of knownCardIds) {
|
|
10710
10920
|
if (!allAgentCardIds.has(knownId)) {
|
|
10711
|
-
log.info(
|
|
10921
|
+
log.info(TAG38, `Missed unassign: ${knownId} — removing`);
|
|
10712
10922
|
await this.pool.removeCard(knownId);
|
|
10713
10923
|
}
|
|
10714
10924
|
}
|
|
10715
10925
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
10716
|
-
log.debug(
|
|
10926
|
+
log.debug(TAG38, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
10717
10927
|
} catch (err) {
|
|
10718
|
-
log.error(
|
|
10928
|
+
log.error(TAG38, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
10719
10929
|
}
|
|
10720
10930
|
}
|
|
10721
10931
|
}
|
|
10722
|
-
var
|
|
10932
|
+
var TAG38 = "reconcile";
|
|
10723
10933
|
var init_reconcile = __esm(() => {
|
|
10724
10934
|
init_board_helpers();
|
|
10725
10935
|
init_git_pr();
|
|
@@ -10759,7 +10969,7 @@ function prettyBanner(config, version) {
|
|
|
10759
10969
|
checks.push({ kind: "ok", message });
|
|
10760
10970
|
},
|
|
10761
10971
|
warn(message) {
|
|
10762
|
-
log.warn(
|
|
10972
|
+
log.warn(TAG39, message);
|
|
10763
10973
|
checks.push({ kind: "warn", message: message.split(`
|
|
10764
10974
|
`, 1)[0] });
|
|
10765
10975
|
},
|
|
@@ -10784,25 +10994,25 @@ function prettyBanner(config, version) {
|
|
|
10784
10994
|
};
|
|
10785
10995
|
}
|
|
10786
10996
|
function jsonBanner(config, version) {
|
|
10787
|
-
log.info(
|
|
10788
|
-
log.info(
|
|
10997
|
+
log.info(TAG39, `Harmony Agent Daemon v${version} starting...`);
|
|
10998
|
+
log.info(TAG39, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
10789
10999
|
if (config.agent.review.enabled) {
|
|
10790
|
-
log.info(
|
|
11000
|
+
log.info(TAG39, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
10791
11001
|
}
|
|
10792
11002
|
let failed = false;
|
|
10793
11003
|
return {
|
|
10794
11004
|
setProjectName(_name) {},
|
|
10795
11005
|
setGitProvider(provider) {
|
|
10796
|
-
log.info(
|
|
11006
|
+
log.info(TAG39, `Git provider: ${provider}`);
|
|
10797
11007
|
},
|
|
10798
11008
|
setHttpPort(port) {
|
|
10799
|
-
log.info(
|
|
11009
|
+
log.info(TAG39, `HTTP server on port ${port}`);
|
|
10800
11010
|
},
|
|
10801
11011
|
check(message) {
|
|
10802
|
-
log.info(
|
|
11012
|
+
log.info(TAG39, message);
|
|
10803
11013
|
},
|
|
10804
11014
|
warn(message) {
|
|
10805
|
-
log.warn(
|
|
11015
|
+
log.warn(TAG39, message);
|
|
10806
11016
|
},
|
|
10807
11017
|
fail() {
|
|
10808
11018
|
failed = true;
|
|
@@ -10810,7 +11020,7 @@ function jsonBanner(config, version) {
|
|
|
10810
11020
|
async ready(message) {
|
|
10811
11021
|
if (failed)
|
|
10812
11022
|
return;
|
|
10813
|
-
log.info(
|
|
11023
|
+
log.info(TAG39, message);
|
|
10814
11024
|
}
|
|
10815
11025
|
};
|
|
10816
11026
|
}
|
|
@@ -10891,7 +11101,7 @@ function cyan(s) {
|
|
|
10891
11101
|
function yellow(s) {
|
|
10892
11102
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
10893
11103
|
}
|
|
10894
|
-
var
|
|
11104
|
+
var TAG39 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
10895
11105
|
var init_startup_banner = __esm(() => {
|
|
10896
11106
|
init_log();
|
|
10897
11107
|
ANSI = {
|
|
@@ -11042,13 +11252,13 @@ class Watcher {
|
|
|
11042
11252
|
}
|
|
11043
11253
|
async start() {
|
|
11044
11254
|
if (!isPretty()) {
|
|
11045
|
-
log.info(
|
|
11255
|
+
log.info(TAG40, "Connecting to Supabase realtime (broadcast)...");
|
|
11046
11256
|
}
|
|
11047
11257
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
11048
11258
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
11049
11259
|
this.subscribeBroadcast();
|
|
11050
11260
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
11051
|
-
log.debug(
|
|
11261
|
+
log.debug(TAG40, "Presence sync");
|
|
11052
11262
|
}).subscribe(async (status) => {
|
|
11053
11263
|
if (status === "SUBSCRIBED") {
|
|
11054
11264
|
await presenceChannel.track({
|
|
@@ -11061,7 +11271,7 @@ class Watcher {
|
|
|
11061
11271
|
agentName: this.identity.agentName
|
|
11062
11272
|
});
|
|
11063
11273
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
11064
|
-
log.info(
|
|
11274
|
+
log.info(TAG40, "Presence tracked on board-presence channel");
|
|
11065
11275
|
}
|
|
11066
11276
|
this.presenceTracked = true;
|
|
11067
11277
|
this.maybeResolveReady();
|
|
@@ -11074,13 +11284,13 @@ class Watcher {
|
|
|
11074
11284
|
return;
|
|
11075
11285
|
const gen = ++this.broadcastGen;
|
|
11076
11286
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
11077
|
-
log.debug(
|
|
11287
|
+
log.debug(TAG40, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
11078
11288
|
this.onCardBroadcast({
|
|
11079
11289
|
event: "card_update",
|
|
11080
11290
|
payload: msg.payload ?? {}
|
|
11081
11291
|
});
|
|
11082
11292
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
11083
|
-
log.debug(
|
|
11293
|
+
log.debug(TAG40, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
11084
11294
|
this.onCardBroadcast({
|
|
11085
11295
|
event: "card_created",
|
|
11086
11296
|
payload: msg.payload ?? {}
|
|
@@ -11090,7 +11300,7 @@ class Watcher {
|
|
|
11090
11300
|
const cardId = payload.card_id;
|
|
11091
11301
|
const command = payload.command;
|
|
11092
11302
|
if (cardId && command) {
|
|
11093
|
-
log.info(
|
|
11303
|
+
log.info(TAG40, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
11094
11304
|
this.onAgentCommand?.({ cardId, command });
|
|
11095
11305
|
}
|
|
11096
11306
|
}).subscribe((status) => {
|
|
@@ -11100,13 +11310,13 @@ class Watcher {
|
|
|
11100
11310
|
this.connected = true;
|
|
11101
11311
|
this.reconnectAttempts = 0;
|
|
11102
11312
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
11103
|
-
log.info(
|
|
11313
|
+
log.info(TAG40, "Broadcast subscription active");
|
|
11104
11314
|
}
|
|
11105
11315
|
this.maybeResolveReady();
|
|
11106
11316
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
11107
11317
|
this.connected = false;
|
|
11108
11318
|
if (!this.stopping) {
|
|
11109
|
-
log.warn(
|
|
11319
|
+
log.warn(TAG40, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
11110
11320
|
this.scheduleReconnect();
|
|
11111
11321
|
}
|
|
11112
11322
|
}
|
|
@@ -11125,7 +11335,7 @@ class Watcher {
|
|
|
11125
11335
|
async reconnectBroadcast() {
|
|
11126
11336
|
if (this.stopping || !this.supabase)
|
|
11127
11337
|
return;
|
|
11128
|
-
log.warn(
|
|
11338
|
+
log.warn(TAG40, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
11129
11339
|
if (this.channel) {
|
|
11130
11340
|
const old = this.channel;
|
|
11131
11341
|
this.channel = null;
|
|
@@ -11155,10 +11365,10 @@ class Watcher {
|
|
|
11155
11365
|
this.supabase = null;
|
|
11156
11366
|
}
|
|
11157
11367
|
this.connected = false;
|
|
11158
|
-
log.info(
|
|
11368
|
+
log.info(TAG40, "Broadcast subscription stopped");
|
|
11159
11369
|
}
|
|
11160
11370
|
}
|
|
11161
|
-
var
|
|
11371
|
+
var TAG40 = "watcher";
|
|
11162
11372
|
var init_watcher = __esm(() => {
|
|
11163
11373
|
init_log();
|
|
11164
11374
|
});
|
|
@@ -11171,7 +11381,7 @@ __export(exports_worktree_gc, {
|
|
|
11171
11381
|
isTransientGitNetworkError: () => isTransientGitNetworkError,
|
|
11172
11382
|
WorktreeGc: () => WorktreeGc
|
|
11173
11383
|
});
|
|
11174
|
-
import { execFileSync as
|
|
11384
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
11175
11385
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
11176
11386
|
import { resolve as resolve3 } from "node:path";
|
|
11177
11387
|
function isTransientGitNetworkError(message) {
|
|
@@ -11239,16 +11449,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
11239
11449
|
}
|
|
11240
11450
|
}
|
|
11241
11451
|
try {
|
|
11242
|
-
|
|
11452
|
+
execFileSync12("git", ["worktree", "prune", "--expire=now"], {
|
|
11243
11453
|
cwd: repoRoot,
|
|
11244
11454
|
stdio: "pipe"
|
|
11245
11455
|
});
|
|
11246
11456
|
} catch {}
|
|
11247
11457
|
if (result.removed.length > 0) {
|
|
11248
|
-
log.info(
|
|
11458
|
+
log.info(TAG41, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
11249
11459
|
}
|
|
11250
11460
|
if (result.errors.length > 0) {
|
|
11251
|
-
log.warn(
|
|
11461
|
+
log.warn(TAG41, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
11252
11462
|
}
|
|
11253
11463
|
return result;
|
|
11254
11464
|
}
|
|
@@ -11270,7 +11480,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11270
11480
|
return result;
|
|
11271
11481
|
}
|
|
11272
11482
|
try {
|
|
11273
|
-
|
|
11483
|
+
execFileSync12("git", ["fetch", "--prune", "origin"], {
|
|
11274
11484
|
cwd: repoRoot,
|
|
11275
11485
|
stdio: "pipe",
|
|
11276
11486
|
...GIT_NETWORK_EXEC
|
|
@@ -11278,7 +11488,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11278
11488
|
} catch (err) {
|
|
11279
11489
|
const detail = gitErrorDetail2(err);
|
|
11280
11490
|
if (isTransientGitNetworkError(detail)) {
|
|
11281
|
-
log.debug(
|
|
11491
|
+
log.debug(TAG41, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
11282
11492
|
return result;
|
|
11283
11493
|
}
|
|
11284
11494
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -11286,7 +11496,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11286
11496
|
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
11287
11497
|
let listing = "";
|
|
11288
11498
|
try {
|
|
11289
|
-
listing =
|
|
11499
|
+
listing = execFileSync12("git", [
|
|
11290
11500
|
"for-each-ref",
|
|
11291
11501
|
"--format=%(refname:strip=3) %(committerdate:unix)",
|
|
11292
11502
|
refPattern
|
|
@@ -11317,11 +11527,11 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11317
11527
|
continue;
|
|
11318
11528
|
}
|
|
11319
11529
|
if (clock() > sweepDeadline) {
|
|
11320
|
-
log.debug(
|
|
11530
|
+
log.debug(TAG41, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
11321
11531
|
break;
|
|
11322
11532
|
}
|
|
11323
11533
|
try {
|
|
11324
|
-
|
|
11534
|
+
execFileSync12("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
11325
11535
|
cwd: repoRoot,
|
|
11326
11536
|
stdio: "pipe",
|
|
11327
11537
|
...GIT_NETWORK_EXEC
|
|
@@ -11330,17 +11540,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
11330
11540
|
} catch (err) {
|
|
11331
11541
|
const detail = gitErrorDetail2(err);
|
|
11332
11542
|
if (isTransientGitNetworkError(detail)) {
|
|
11333
|
-
log.debug(
|
|
11543
|
+
log.debug(TAG41, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
11334
11544
|
break;
|
|
11335
11545
|
}
|
|
11336
11546
|
result.errors.push({ ref, error: detail });
|
|
11337
11547
|
}
|
|
11338
11548
|
}
|
|
11339
11549
|
if (result.removed.length > 0) {
|
|
11340
|
-
log.info(
|
|
11550
|
+
log.info(TAG41, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
11341
11551
|
}
|
|
11342
11552
|
if (result.errors.length > 0) {
|
|
11343
|
-
log.warn(
|
|
11553
|
+
log.warn(TAG41, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
11344
11554
|
}
|
|
11345
11555
|
return result;
|
|
11346
11556
|
}
|
|
@@ -11371,27 +11581,27 @@ class WorktreeGc {
|
|
|
11371
11581
|
try {
|
|
11372
11582
|
runWorktreeGc(this.basePath, this.store);
|
|
11373
11583
|
} catch (err) {
|
|
11374
|
-
log.warn(
|
|
11584
|
+
log.warn(TAG41, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
11375
11585
|
}
|
|
11376
11586
|
if (this.remoteOpts) {
|
|
11377
11587
|
try {
|
|
11378
11588
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
11379
11589
|
} catch (err) {
|
|
11380
|
-
log.warn(
|
|
11590
|
+
log.warn(TAG41, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
11381
11591
|
}
|
|
11382
11592
|
}
|
|
11383
11593
|
}
|
|
11384
11594
|
}
|
|
11385
11595
|
function getRepoRoot2() {
|
|
11386
11596
|
try {
|
|
11387
|
-
return
|
|
11597
|
+
return execFileSync12("git", ["rev-parse", "--show-toplevel"], {
|
|
11388
11598
|
encoding: "utf-8"
|
|
11389
11599
|
}).trim();
|
|
11390
11600
|
} catch {
|
|
11391
11601
|
return null;
|
|
11392
11602
|
}
|
|
11393
11603
|
}
|
|
11394
|
-
var
|
|
11604
|
+
var TAG41 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
|
|
11395
11605
|
var init_worktree_gc = __esm(() => {
|
|
11396
11606
|
init_log();
|
|
11397
11607
|
init_worktree();
|
|
@@ -11425,12 +11635,12 @@ __export(exports_src, {
|
|
|
11425
11635
|
validatePrerequisites: () => validatePrerequisites,
|
|
11426
11636
|
main: () => main
|
|
11427
11637
|
});
|
|
11428
|
-
import { execFileSync as
|
|
11638
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
11429
11639
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11430
11640
|
import { createRequire as createRequire2 } from "node:module";
|
|
11431
11641
|
async function validatePrerequisites(config, banner) {
|
|
11432
11642
|
try {
|
|
11433
|
-
const ver =
|
|
11643
|
+
const ver = execFileSync13("claude", ["--version"], {
|
|
11434
11644
|
encoding: "utf-8"
|
|
11435
11645
|
}).trim();
|
|
11436
11646
|
banner.check(`Claude CLI ${ver}`);
|
|
@@ -11445,14 +11655,14 @@ async function validatePrerequisites(config, banner) {
|
|
|
11445
11655
|
validateGitProviderCli(provider);
|
|
11446
11656
|
}
|
|
11447
11657
|
try {
|
|
11448
|
-
const status =
|
|
11658
|
+
const status = execFileSync13("git", ["status", "--porcelain"], {
|
|
11449
11659
|
encoding: "utf-8"
|
|
11450
11660
|
}).trim();
|
|
11451
11661
|
if (status) {
|
|
11452
11662
|
banner.warn(`Working directory has uncommitted changes:
|
|
11453
11663
|
${status}`);
|
|
11454
11664
|
}
|
|
11455
|
-
|
|
11665
|
+
execFileSync13("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
|
|
11456
11666
|
encoding: "utf-8",
|
|
11457
11667
|
stdio: "pipe"
|
|
11458
11668
|
});
|
|
@@ -11496,7 +11706,7 @@ async function main() {
|
|
|
11496
11706
|
} catch (err) {
|
|
11497
11707
|
if (err instanceof ConfigValidationError) {
|
|
11498
11708
|
banner.fail();
|
|
11499
|
-
log.error(
|
|
11709
|
+
log.error(TAG42, err.message);
|
|
11500
11710
|
process.exit(1);
|
|
11501
11711
|
}
|
|
11502
11712
|
throw err;
|
|
@@ -11506,7 +11716,7 @@ async function main() {
|
|
|
11506
11716
|
} catch (err) {
|
|
11507
11717
|
if (err instanceof ConfigValidationError) {
|
|
11508
11718
|
banner.fail();
|
|
11509
|
-
log.error(
|
|
11719
|
+
log.error(TAG42, err.message);
|
|
11510
11720
|
process.exit(1);
|
|
11511
11721
|
}
|
|
11512
11722
|
throw err;
|
|
@@ -11621,7 +11831,7 @@ async function main() {
|
|
|
11621
11831
|
if (shuttingDown)
|
|
11622
11832
|
return;
|
|
11623
11833
|
shuttingDown = true;
|
|
11624
|
-
log.info(
|
|
11834
|
+
log.info(TAG42, `Received ${signal}, shutting down gracefully...`);
|
|
11625
11835
|
reconciler.stop();
|
|
11626
11836
|
mergeMonitor?.stop();
|
|
11627
11837
|
worktreeGc.stop();
|
|
@@ -11632,18 +11842,18 @@ async function main() {
|
|
|
11632
11842
|
}
|
|
11633
11843
|
await watcher.stop();
|
|
11634
11844
|
await pool.shutdown();
|
|
11635
|
-
log.info(
|
|
11845
|
+
log.info(TAG42, "Daemon stopped.");
|
|
11636
11846
|
process.exit(exitCode);
|
|
11637
11847
|
};
|
|
11638
11848
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
11639
11849
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
11640
11850
|
process.on("uncaughtException", (err) => {
|
|
11641
|
-
log.error(
|
|
11851
|
+
log.error(TAG42, `Uncaught exception: ${err.message}`);
|
|
11642
11852
|
exitCode = 1;
|
|
11643
11853
|
shutdown("uncaughtException");
|
|
11644
11854
|
});
|
|
11645
11855
|
process.on("unhandledRejection", (reason) => {
|
|
11646
|
-
log.error(
|
|
11856
|
+
log.error(TAG42, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
11647
11857
|
exitCode = 1;
|
|
11648
11858
|
shutdown("unhandledRejection");
|
|
11649
11859
|
});
|
|
@@ -11702,29 +11912,29 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
11702
11912
|
if (assignedAgentId === undefined)
|
|
11703
11913
|
return;
|
|
11704
11914
|
if (assignedAgentId === agentId) {
|
|
11705
|
-
log.info(
|
|
11915
|
+
log.info(TAG42, `Broadcast: card ${cardId} assigned to agent`);
|
|
11706
11916
|
try {
|
|
11707
11917
|
await pool.resetAttemptsForReassign(cardId);
|
|
11708
11918
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
11709
11919
|
} catch (err) {
|
|
11710
|
-
log.error(
|
|
11920
|
+
log.error(TAG42, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
11711
11921
|
}
|
|
11712
11922
|
} else if (pool.isCardKnown(cardId)) {
|
|
11713
|
-
log.info(
|
|
11923
|
+
log.info(TAG42, `Broadcast: card ${cardId} unassigned from agent`);
|
|
11714
11924
|
await pool.removeCard(cardId);
|
|
11715
11925
|
}
|
|
11716
11926
|
}
|
|
11717
11927
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
11718
11928
|
const { card } = await client.getCard(cardId);
|
|
11719
11929
|
if (card.assigned_agent_id !== agentId) {
|
|
11720
|
-
log.debug(
|
|
11930
|
+
log.debug(TAG42, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
11721
11931
|
return;
|
|
11722
11932
|
}
|
|
11723
11933
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
11724
11934
|
const columns = board.columns;
|
|
11725
11935
|
const column = columns.find((c) => c.id === card.column_id);
|
|
11726
11936
|
if (!column) {
|
|
11727
|
-
log.warn(
|
|
11937
|
+
log.warn(TAG42, `Column not found for card ${cardId}`);
|
|
11728
11938
|
return;
|
|
11729
11939
|
}
|
|
11730
11940
|
const route = classifyPickup(card, column.name, {
|
|
@@ -11733,31 +11943,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
11733
11943
|
playbooks: config.agent.playbooks
|
|
11734
11944
|
});
|
|
11735
11945
|
if (!route) {
|
|
11736
|
-
log.info(
|
|
11946
|
+
log.info(TAG42, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
11737
11947
|
return;
|
|
11738
11948
|
}
|
|
11739
11949
|
if (route.stage) {
|
|
11740
|
-
log.info(
|
|
11950
|
+
log.info(TAG42, `Card #${card.short_id} is a playbook stage card (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement pool) regardless of column`);
|
|
11741
11951
|
}
|
|
11742
11952
|
const mode = route.mode;
|
|
11743
11953
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
11744
11954
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
11745
11955
|
const subtasks = card.subtasks ?? [];
|
|
11746
11956
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
11747
|
-
log.debug(
|
|
11957
|
+
log.debug(TAG42, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
11748
11958
|
return;
|
|
11749
11959
|
}
|
|
11750
11960
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
11751
|
-
log.debug(
|
|
11961
|
+
log.debug(TAG42, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
|
|
11752
11962
|
return;
|
|
11753
11963
|
}
|
|
11754
11964
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
11755
|
-
log.info(
|
|
11965
|
+
log.info(TAG42, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
|
|
11756
11966
|
return;
|
|
11757
11967
|
}
|
|
11758
11968
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
11759
11969
|
}
|
|
11760
|
-
var
|
|
11970
|
+
var TAG42 = "daemon", PKG_VERSION;
|
|
11761
11971
|
var init_src = __esm(() => {
|
|
11762
11972
|
init_board_helpers();
|
|
11763
11973
|
init_board_reviewer();
|