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