@gethmy/agent 1.41.0 → 1.42.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 +639 -997
- package/dist/index.js +541 -966
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -861,6 +861,9 @@ function isDaemonAuthoredComment(comment, identity) {
|
|
|
861
861
|
return false;
|
|
862
862
|
return true;
|
|
863
863
|
}
|
|
864
|
+
// ../harmony-shared/dist/agentPauseAccounting.js
|
|
865
|
+
var init_agentPauseAccounting = () => {};
|
|
866
|
+
|
|
864
867
|
// ../harmony-shared/dist/agentStaleness.js
|
|
865
868
|
function isPoolNotice(session) {
|
|
866
869
|
return !!session && session.driver === NOTICE_DRIVER && !session.agent_id;
|
|
@@ -874,6 +877,12 @@ var init_agentStaleness = __esm(() => {
|
|
|
874
877
|
AGENT_SWEEP_PAUSED_MS = 4 * 60 * 60 * 1000;
|
|
875
878
|
ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
|
|
876
879
|
});
|
|
880
|
+
|
|
881
|
+
// ../harmony-shared/dist/agentRunControl.js
|
|
882
|
+
var init_agentRunControl = __esm(() => {
|
|
883
|
+
init_agentPauseAccounting();
|
|
884
|
+
init_agentStaleness();
|
|
885
|
+
});
|
|
877
886
|
// ../harmony-shared/dist/branchRef.js
|
|
878
887
|
function extractBranchRef(description) {
|
|
879
888
|
if (!description)
|
|
@@ -2434,6 +2443,9 @@ var init_stageHandoff = __esm(() => {
|
|
|
2434
2443
|
HANDOFF_BLOCK_RE = new RegExp("```json\\s*\\n//\\s*" + HANDOFF_MARKER + "\\s*\\n([\\s\\S]*?)\\n```", "m");
|
|
2435
2444
|
});
|
|
2436
2445
|
|
|
2446
|
+
// ../harmony-shared/dist/sweepPresence.js
|
|
2447
|
+
var init_sweepPresence = () => {};
|
|
2448
|
+
|
|
2437
2449
|
// ../harmony-shared/dist/types.js
|
|
2438
2450
|
var init_types = () => {};
|
|
2439
2451
|
|
|
@@ -2469,6 +2481,8 @@ function untrustedDataBlock(text, options) {
|
|
|
2469
2481
|
|
|
2470
2482
|
// ../harmony-shared/dist/index.js
|
|
2471
2483
|
var init_dist = __esm(() => {
|
|
2484
|
+
init_agentPauseAccounting();
|
|
2485
|
+
init_agentRunControl();
|
|
2472
2486
|
init_agentStaleness();
|
|
2473
2487
|
init_branchRef();
|
|
2474
2488
|
init_cardLifecycle();
|
|
@@ -2491,6 +2505,7 @@ var init_dist = __esm(() => {
|
|
|
2491
2505
|
init_reviewTools();
|
|
2492
2506
|
init_runRedaction();
|
|
2493
2507
|
init_stageHandoff();
|
|
2508
|
+
init_sweepPresence();
|
|
2494
2509
|
init_types();
|
|
2495
2510
|
});
|
|
2496
2511
|
|
|
@@ -6512,332 +6527,20 @@ class ReportedConfigCache {
|
|
|
6512
6527
|
var TAG17 = "reported-config";
|
|
6513
6528
|
var init_reported_config_cache = () => {};
|
|
6514
6529
|
|
|
6515
|
-
// src/episode-writer.ts
|
|
6516
|
-
import { log as log19 } from "@gethmy/harness";
|
|
6517
|
-
function computeQualityScore(result, opts) {
|
|
6518
|
-
if (!result.passed)
|
|
6519
|
-
return 0;
|
|
6520
|
-
let score = 0;
|
|
6521
|
-
if (result.buildErrors.length === 0)
|
|
6522
|
-
score += 0.4;
|
|
6523
|
-
if (result.lintWarnings.length === 0)
|
|
6524
|
-
score += 0.2;
|
|
6525
|
-
if (!opts.errorThrown)
|
|
6526
|
-
score += 0.2;
|
|
6527
|
-
if (opts.runCompletedCleanly)
|
|
6528
|
-
score += 0.2;
|
|
6529
|
-
return Math.min(1, score);
|
|
6530
|
-
}
|
|
6531
|
-
function clampConfidence(qualityScore) {
|
|
6532
|
-
return Math.max(0.4, Math.min(1, qualityScore));
|
|
6533
|
-
}
|
|
6534
|
-
function trimApproachSummary(text) {
|
|
6535
|
-
const trimmed = text.trim();
|
|
6536
|
-
if (trimmed.length === 0)
|
|
6537
|
-
return "(no approach summary captured)";
|
|
6538
|
-
if (trimmed.length <= MAX_APPROACH_SUMMARY_CHARS)
|
|
6539
|
-
return trimmed;
|
|
6540
|
-
return `${trimmed.slice(0, MAX_APPROACH_SUMMARY_CHARS - 1).trimEnd()}…`;
|
|
6541
|
-
}
|
|
6542
|
-
function trimReviewRationale(text) {
|
|
6543
|
-
const trimmed = text.trim();
|
|
6544
|
-
if (trimmed.length === 0)
|
|
6545
|
-
return "(no review rationale captured)";
|
|
6546
|
-
if (trimmed.length <= MAX_REVIEW_RATIONALE_CHARS)
|
|
6547
|
-
return trimmed;
|
|
6548
|
-
return `${trimmed.slice(0, MAX_REVIEW_RATIONALE_CHARS - 1).trimEnd()}…`;
|
|
6549
|
-
}
|
|
6550
|
-
function buildRichApproachSummary(blocks, fallback) {
|
|
6551
|
-
const cleaned = (blocks ?? []).map((b) => b.trim()).filter((b) => b.length > 0);
|
|
6552
|
-
if (cleaned.length === 0)
|
|
6553
|
-
return trimApproachSummary(fallback);
|
|
6554
|
-
const picked = [];
|
|
6555
|
-
let total = 0;
|
|
6556
|
-
for (let i = cleaned.length - 1;i >= 0; i--) {
|
|
6557
|
-
const block = cleaned[i];
|
|
6558
|
-
const cost = block.length + (picked.length > 0 ? 2 : 0);
|
|
6559
|
-
if (total + cost > MAX_RICH_APPROACH_CHARS && picked.length > 0)
|
|
6560
|
-
break;
|
|
6561
|
-
picked.unshift(block);
|
|
6562
|
-
total += cost;
|
|
6563
|
-
if (total >= MAX_RICH_APPROACH_CHARS)
|
|
6564
|
-
break;
|
|
6565
|
-
}
|
|
6566
|
-
const joined = picked.join(`
|
|
6567
|
-
|
|
6568
|
-
`).trim();
|
|
6569
|
-
if (joined.length === 0)
|
|
6570
|
-
return trimApproachSummary(fallback);
|
|
6571
|
-
if (joined.length <= MAX_RICH_APPROACH_CHARS)
|
|
6572
|
-
return joined;
|
|
6573
|
-
return `${joined.slice(0, MAX_RICH_APPROACH_CHARS - 1).trimEnd()}…`;
|
|
6574
|
-
}
|
|
6575
|
-
function extractKeyInsight(blocks, fallback) {
|
|
6576
|
-
const source = blocks && blocks.length > 0 ? blocks.join(`
|
|
6577
|
-
`) : fallback;
|
|
6578
|
-
const lines = source.split(/\n|(?<=[.!?])\s+/).map((l) => l.trim()).filter((l) => l.length >= 20 && l.length <= 280);
|
|
6579
|
-
for (const line of lines) {
|
|
6580
|
-
if (INSIGHT_RE.test(line))
|
|
6581
|
-
return line;
|
|
6582
|
-
}
|
|
6583
|
-
return;
|
|
6584
|
-
}
|
|
6585
|
-
function buildEpisodePayload(input, projectId) {
|
|
6586
|
-
if (input.kind === "implement") {
|
|
6587
|
-
const qualityScore2 = computeQualityScore(input.result, {
|
|
6588
|
-
errorThrown: input.errorMessage !== undefined,
|
|
6589
|
-
runCompletedCleanly: input.result.passed
|
|
6590
|
-
});
|
|
6591
|
-
const type = input.outcome === "success" ? "solution" : "error";
|
|
6592
|
-
const importance = input.outcome === "success" ? 7 : 5;
|
|
6593
|
-
const approachSummary = buildRichApproachSummary(input.approachBlocks, input.approachSummary);
|
|
6594
|
-
const keyInsight = extractKeyInsight(input.approachBlocks, input.approachSummary);
|
|
6595
|
-
const changedFiles = (input.changedFiles ?? []).slice(0, MAX_CHANGED_FILES2);
|
|
6596
|
-
const outcomeRationale = input.outcome === "success" ? `Build ${input.result.buildErrors.length === 0 ? "passed" : "failed"}, lint ${input.result.lintWarnings.length === 0 ? "clean" : "issues"}.` : `Verification failed: ${input.errorMessage ?? "see findings"}.`;
|
|
6597
|
-
const metadata2 = {
|
|
6598
|
-
episode_kind: "implement",
|
|
6599
|
-
card_short_id: input.card.short_id,
|
|
6600
|
-
card_title: input.card.title,
|
|
6601
|
-
approach_summary: approachSummary,
|
|
6602
|
-
outcome: input.outcome,
|
|
6603
|
-
quality_score: qualityScore2,
|
|
6604
|
-
duration_ms: input.cost?.durationMs ?? 0,
|
|
6605
|
-
token_cost: {
|
|
6606
|
-
input: input.cost?.totalInputTokens ?? 0,
|
|
6607
|
-
output: input.cost?.totalOutputTokens ?? 0,
|
|
6608
|
-
usd: input.cost?.totalCostUsd ?? 0
|
|
6609
|
-
},
|
|
6610
|
-
files_touched: input.filesEdited,
|
|
6611
|
-
num_turns: input.cost?.numTurns ?? 0,
|
|
6612
|
-
origin: {
|
|
6613
|
-
source: "agent-run",
|
|
6614
|
-
source_card_id: input.card.id,
|
|
6615
|
-
author: "harmony-agent",
|
|
6616
|
-
...input.agentSessionId ? { source_session_id: input.agentSessionId } : {}
|
|
6617
|
-
}
|
|
6618
|
-
};
|
|
6619
|
-
if (input.errorMessage)
|
|
6620
|
-
metadata2.error = input.errorMessage;
|
|
6621
|
-
if (changedFiles.length > 0)
|
|
6622
|
-
metadata2.changed_files = changedFiles;
|
|
6623
|
-
if (input.churn)
|
|
6624
|
-
metadata2.churn = input.churn;
|
|
6625
|
-
if (keyInsight)
|
|
6626
|
-
metadata2.key_insight = keyInsight;
|
|
6627
|
-
if (input.agentSessionId)
|
|
6628
|
-
metadata2.agent_session_id = input.agentSessionId;
|
|
6629
|
-
const changedFilesLine = changedFiles.length > 0 ? `
|
|
6630
|
-
|
|
6631
|
-
Changed files (${changedFiles.length}): ${changedFiles.join(", ")}` : "";
|
|
6632
|
-
const keyInsightLine = keyInsight ? `
|
|
6633
|
-
|
|
6634
|
-
Key insight: ${keyInsight}` : "";
|
|
6635
|
-
return {
|
|
6636
|
-
workspace_id: input.workspaceId,
|
|
6637
|
-
project_id: projectId,
|
|
6638
|
-
type,
|
|
6639
|
-
memory_tier: "episode",
|
|
6640
|
-
scope: "project",
|
|
6641
|
-
title: `Agent run implement — #${input.card.short_id}: ${input.card.title}`,
|
|
6642
|
-
content: `${approachSummary}
|
|
6643
|
-
|
|
6644
|
-
Outcome: ${outcomeRationale}${keyInsightLine}${changedFilesLine}`,
|
|
6645
|
-
metadata: metadata2,
|
|
6646
|
-
importance,
|
|
6647
|
-
confidence: clampConfidence(qualityScore2),
|
|
6648
|
-
tags: ["implement", input.outcome, `card:${input.card.short_id}`],
|
|
6649
|
-
agent_identifier: "harmony-agent"
|
|
6650
|
-
};
|
|
6651
|
-
}
|
|
6652
|
-
const qualityScore = input.verdict === "approved" ? 1 : 0.4;
|
|
6653
|
-
const summary = trimReviewRationale(input.summary || "");
|
|
6654
|
-
const metadata = {
|
|
6655
|
-
episode_kind: "review",
|
|
6656
|
-
card_short_id: input.card.short_id,
|
|
6657
|
-
card_title: input.card.title,
|
|
6658
|
-
approach_summary: summary,
|
|
6659
|
-
outcome: input.verdict === "approved" ? "success" : "failure",
|
|
6660
|
-
quality_score: qualityScore,
|
|
6661
|
-
duration_ms: input.cost?.durationMs ?? 0,
|
|
6662
|
-
token_cost: {
|
|
6663
|
-
input: input.cost?.totalInputTokens ?? 0,
|
|
6664
|
-
output: input.cost?.totalOutputTokens ?? 0,
|
|
6665
|
-
usd: input.cost?.totalCostUsd ?? 0
|
|
6666
|
-
},
|
|
6667
|
-
files_touched: 0,
|
|
6668
|
-
num_turns: input.cost?.numTurns ?? 0,
|
|
6669
|
-
origin: {
|
|
6670
|
-
source: "agent-run",
|
|
6671
|
-
source_card_id: input.card.id,
|
|
6672
|
-
author: "harmony-agent",
|
|
6673
|
-
...input.reviewSessionId ?? input.agentSessionId ? {
|
|
6674
|
-
source_session_id: input.reviewSessionId ?? input.agentSessionId
|
|
6675
|
-
} : {}
|
|
6676
|
-
}
|
|
6677
|
-
};
|
|
6678
|
-
if (input.agentSessionId)
|
|
6679
|
-
metadata.agent_session_id = input.agentSessionId;
|
|
6680
|
-
if (input.reviewSessionId)
|
|
6681
|
-
metadata.review_session_id = input.reviewSessionId;
|
|
6682
|
-
if (input.originalEpisodeId)
|
|
6683
|
-
metadata.original_episode_id = input.originalEpisodeId;
|
|
6684
|
-
return {
|
|
6685
|
-
workspace_id: input.workspaceId,
|
|
6686
|
-
project_id: projectId,
|
|
6687
|
-
type: "decision",
|
|
6688
|
-
memory_tier: "episode",
|
|
6689
|
-
scope: "project",
|
|
6690
|
-
title: `Agent run review — #${input.card.short_id}: ${input.card.title}`,
|
|
6691
|
-
content: `Review verdict: ${input.verdict}.
|
|
6692
|
-
|
|
6693
|
-
${summary}`,
|
|
6694
|
-
metadata,
|
|
6695
|
-
importance: 8,
|
|
6696
|
-
confidence: clampConfidence(qualityScore),
|
|
6697
|
-
tags: ["review", input.verdict, `card:${input.card.short_id}`],
|
|
6698
|
-
agent_identifier: "harmony-agent"
|
|
6699
|
-
};
|
|
6700
|
-
}
|
|
6701
|
-
async function writeEpisode(client, input, options) {
|
|
6702
|
-
const payload = buildEpisodePayload(input, input.card.project_id);
|
|
6703
|
-
let content = payload.content;
|
|
6704
|
-
if (options?.distiller) {
|
|
6705
|
-
try {
|
|
6706
|
-
const distilled = await options.distiller(input, payload.content);
|
|
6707
|
-
if (distilled && distilled.trim().length > 0) {
|
|
6708
|
-
content = distilled.trim();
|
|
6709
|
-
}
|
|
6710
|
-
} catch (err) {
|
|
6711
|
-
log19.warn(TAG18, `episode distillation failed for #${input.card.short_id}`, {
|
|
6712
|
-
cardId: input.card.id,
|
|
6713
|
-
event: "episode_distill_failed",
|
|
6714
|
-
kind: input.kind,
|
|
6715
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6716
|
-
});
|
|
6717
|
-
}
|
|
6718
|
-
}
|
|
6719
|
-
const metadata = payload.metadata;
|
|
6720
|
-
try {
|
|
6721
|
-
const existingId = await findRollingEpisode(client, input.workspaceId, input.card.project_id, input.card.short_id, input.kind);
|
|
6722
|
-
if (existingId) {
|
|
6723
|
-
await client.updateMemoryEntity(existingId, {
|
|
6724
|
-
title: payload.title,
|
|
6725
|
-
content,
|
|
6726
|
-
metadata,
|
|
6727
|
-
confidence: payload.confidence,
|
|
6728
|
-
tags: payload.tags,
|
|
6729
|
-
type: payload.type
|
|
6730
|
-
});
|
|
6731
|
-
log19.info(TAG18, `episode rolled for #${input.card.short_id}`, {
|
|
6732
|
-
cardId: input.card.id,
|
|
6733
|
-
event: "episode_rolled",
|
|
6734
|
-
kind: input.kind,
|
|
6735
|
-
entityId: existingId
|
|
6736
|
-
});
|
|
6737
|
-
return existingId;
|
|
6738
|
-
}
|
|
6739
|
-
const { entity } = await client.createMemoryEntity({
|
|
6740
|
-
...payload,
|
|
6741
|
-
content,
|
|
6742
|
-
metadata
|
|
6743
|
-
});
|
|
6744
|
-
const id = entity && typeof entity === "object" && "id" in entity ? entity.id ?? null : null;
|
|
6745
|
-
log19.info(TAG18, `episode written for #${input.card.short_id}`, {
|
|
6746
|
-
cardId: input.card.id,
|
|
6747
|
-
event: "episode_write",
|
|
6748
|
-
kind: input.kind
|
|
6749
|
-
});
|
|
6750
|
-
return id;
|
|
6751
|
-
} catch (err) {
|
|
6752
|
-
log19.warn(TAG18, `episode write failed for #${input.card.short_id}`, {
|
|
6753
|
-
cardId: input.card.id,
|
|
6754
|
-
event: "episode_write_failed",
|
|
6755
|
-
kind: input.kind,
|
|
6756
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6757
|
-
});
|
|
6758
|
-
return null;
|
|
6759
|
-
}
|
|
6760
|
-
}
|
|
6761
|
-
async function findRollingEpisode(client, workspaceId, projectId, cardShortId, kind) {
|
|
6762
|
-
const type = kind === "implement" ? ["solution", "error"] : ["decision"];
|
|
6763
|
-
try {
|
|
6764
|
-
const { entities } = await client.harmonyRecall({
|
|
6765
|
-
workspaceId,
|
|
6766
|
-
projectId,
|
|
6767
|
-
type,
|
|
6768
|
-
memory_tier: "episode",
|
|
6769
|
-
scope: "project",
|
|
6770
|
-
tags: [`card:${cardShortId}`],
|
|
6771
|
-
topK: 10,
|
|
6772
|
-
includeEpisodes: true
|
|
6773
|
-
});
|
|
6774
|
-
for (const entity of entities) {
|
|
6775
|
-
if (!entity || typeof entity !== "object")
|
|
6776
|
-
continue;
|
|
6777
|
-
const meta = entity.metadata;
|
|
6778
|
-
if (meta?.episode_kind !== kind || meta?.origin?.source !== "agent-run") {
|
|
6779
|
-
continue;
|
|
6780
|
-
}
|
|
6781
|
-
const id = entity.id;
|
|
6782
|
-
if (typeof id === "string")
|
|
6783
|
-
return id;
|
|
6784
|
-
}
|
|
6785
|
-
return null;
|
|
6786
|
-
} catch (err) {
|
|
6787
|
-
log19.warn(TAG18, "rolling-episode lookup failed", {
|
|
6788
|
-
event: "episode_lookup_failed",
|
|
6789
|
-
cardShortId,
|
|
6790
|
-
kind,
|
|
6791
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6792
|
-
});
|
|
6793
|
-
return null;
|
|
6794
|
-
}
|
|
6795
|
-
}
|
|
6796
|
-
async function findLatestImplementEpisode(client, workspaceId, projectId, cardShortId) {
|
|
6797
|
-
return findRollingEpisode(client, workspaceId, projectId, cardShortId, "implement");
|
|
6798
|
-
}
|
|
6799
|
-
async function backfillReviewVerdict(client, originalEpisodeId, verdict, reviewEpisodeId) {
|
|
6800
|
-
try {
|
|
6801
|
-
if (verdict === "approved") {
|
|
6802
|
-
const { entity } = await client.getMemoryEntity(originalEpisodeId);
|
|
6803
|
-
const current = entity?.confidence ?? 0.4;
|
|
6804
|
-
const bumped = Math.min(1, current + 0.05);
|
|
6805
|
-
await client.updateMemoryEntity(originalEpisodeId, {
|
|
6806
|
-
confidence: bumped
|
|
6807
|
-
});
|
|
6808
|
-
} else {
|
|
6809
|
-
await client.updateMemoryEntity(originalEpisodeId, {
|
|
6810
|
-
superseded_by: reviewEpisodeId
|
|
6811
|
-
});
|
|
6812
|
-
}
|
|
6813
|
-
} catch (err) {
|
|
6814
|
-
log19.warn(TAG18, "review back-fill failed", {
|
|
6815
|
-
event: "episode_backfill_failed",
|
|
6816
|
-
originalEpisodeId,
|
|
6817
|
-
verdict,
|
|
6818
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6819
|
-
});
|
|
6820
|
-
}
|
|
6821
|
-
}
|
|
6822
|
-
var TAG18 = "episode-writer", MAX_APPROACH_SUMMARY_CHARS = 400, MAX_RICH_APPROACH_CHARS = 1500, MAX_CHANGED_FILES2 = 30, MAX_REVIEW_RATIONALE_CHARS = 2000, INSIGHT_RE;
|
|
6823
|
-
var init_episode_writer = __esm(() => {
|
|
6824
|
-
INSIGHT_RE = /\b(root cause|turned out|the (?:issue|problem|bug) (?:was|is)|the fix (?:was|is)|gotcha|caused by|because|the key (?:was|insight)|note that|caveat|the trick (?:was|is))\b/i;
|
|
6825
|
-
});
|
|
6826
|
-
|
|
6827
6530
|
// src/run-closeout.ts
|
|
6828
|
-
import { log as
|
|
6531
|
+
import { log as log19 } from "@gethmy/harness";
|
|
6829
6532
|
async function transferCardToCompletion(deps, card, moveToColumn, onPromoted) {
|
|
6830
6533
|
await moveCardToColumn(deps.client, card, moveToColumn);
|
|
6831
6534
|
try {
|
|
6832
6535
|
await releaseAssignedAgent(deps.client, card.id);
|
|
6833
6536
|
} catch (err) {
|
|
6834
|
-
|
|
6537
|
+
log19.warn(deps.tag, `assignment release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6835
6538
|
}
|
|
6836
6539
|
if (onPromoted) {
|
|
6837
6540
|
try {
|
|
6838
6541
|
await onPromoted(card);
|
|
6839
6542
|
} catch (err) {
|
|
6840
|
-
|
|
6543
|
+
log19.warn(deps.tag, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6841
6544
|
}
|
|
6842
6545
|
}
|
|
6843
6546
|
}
|
|
@@ -6851,7 +6554,7 @@ async function endRunSession(deps, card, disposition, extraPayload, onError) {
|
|
|
6851
6554
|
} catch (err) {
|
|
6852
6555
|
if (onError === "throw")
|
|
6853
6556
|
throw err;
|
|
6854
|
-
|
|
6557
|
+
log19.error(deps.tag, `endAgentSession after the run failed on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6855
6558
|
}
|
|
6856
6559
|
}
|
|
6857
6560
|
var init_run_closeout = __esm(() => {
|
|
@@ -6862,13 +6565,12 @@ var init_run_closeout = __esm(() => {
|
|
|
6862
6565
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
6863
6566
|
import {
|
|
6864
6567
|
attemptAutoFix,
|
|
6865
|
-
captureDiffStat,
|
|
6866
6568
|
createPullRequest,
|
|
6867
6569
|
detectGitProvider as detectGitProvider3,
|
|
6868
6570
|
extractPrUrl as extractPrUrl2,
|
|
6869
6571
|
GIT_NO_HOOKS as GIT_NO_HOOKS6,
|
|
6870
6572
|
getBranchWebUrl,
|
|
6871
|
-
log as
|
|
6573
|
+
log as log20,
|
|
6872
6574
|
pushBranch,
|
|
6873
6575
|
reportFindings,
|
|
6874
6576
|
runFormatFix,
|
|
@@ -6906,15 +6608,7 @@ function buildTokenPayload(stats) {
|
|
|
6906
6608
|
numTurns: stats.cost.numTurns
|
|
6907
6609
|
};
|
|
6908
6610
|
}
|
|
6909
|
-
async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionIdentifier, agentId, sessionStats,
|
|
6910
|
-
let verificationResult = {
|
|
6911
|
-
passed: true,
|
|
6912
|
-
buildErrors: [],
|
|
6913
|
-
testFailures: [],
|
|
6914
|
-
lintWarnings: [],
|
|
6915
|
-
reviewFindings: [],
|
|
6916
|
-
revertWarnings: []
|
|
6917
|
-
};
|
|
6611
|
+
async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionIdentifier, agentId, sessionStats, agentSessionId, stateStore, onMovedToCompletion, onBeforeWorktreeCleanup, runBaselineSha, effectiveMaxTurns, expectsCommits) {
|
|
6918
6612
|
if (config.verification.enabled && config.verification.lint) {
|
|
6919
6613
|
await runFormatFix(worktreePath, config.verification.timeout, workerId, verificationSandbox(config), config.commands);
|
|
6920
6614
|
}
|
|
@@ -6923,24 +6617,24 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6923
6617
|
if (!hasCommits) {
|
|
6924
6618
|
const { maxTurnsExhausted, failureSummary } = describeNoCommitFailure(sessionStats?.cost?.numTurns ?? 0, effectiveMaxTurns ?? config.claude.maxTurns);
|
|
6925
6619
|
if (noCommitOutcome(maxTurnsExhausted, config.budget.pause.enabled) === "park") {
|
|
6926
|
-
|
|
6620
|
+
log20.warn(TAG18, `No commits on branch ${branchName} — ${failureSummary}; parking for a decision`);
|
|
6927
6621
|
return "park";
|
|
6928
6622
|
}
|
|
6929
6623
|
if (expectsCommits === false) {
|
|
6930
|
-
|
|
6624
|
+
log20.info(TAG18, `No commits on branch ${branchName} — this stage's deliverable is not a diff; its gate decides the run`);
|
|
6931
6625
|
if (onBeforeWorktreeCleanup) {
|
|
6932
6626
|
try {
|
|
6933
6627
|
await onBeforeWorktreeCleanup(worktreePath);
|
|
6934
6628
|
} catch (err) {
|
|
6935
|
-
|
|
6629
|
+
log20.warn(TAG18, `onBeforeWorktreeCleanup hook failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6936
6630
|
}
|
|
6937
6631
|
} else {
|
|
6938
|
-
await endRunSession({ client, tag:
|
|
6632
|
+
await endRunSession({ client, tag: TAG18 }, card, { status: "completed" }, buildTokenPayload(sessionStats), "throw");
|
|
6939
6633
|
}
|
|
6940
6634
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6941
6635
|
return true;
|
|
6942
6636
|
}
|
|
6943
|
-
|
|
6637
|
+
log20.warn(TAG18, `No commits on branch ${branchName} — ${failureSummary}; counting as a failed attempt`);
|
|
6944
6638
|
const noCommitHandback = await guardedHandback(client, card.id, {
|
|
6945
6639
|
agentId,
|
|
6946
6640
|
workingColumnId: card.column_id
|
|
@@ -6957,13 +6651,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6957
6651
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6958
6652
|
return false;
|
|
6959
6653
|
}
|
|
6960
|
-
|
|
6654
|
+
log20.info(TAG18, `Pushing branch ${branchName} (pre-verify)...`);
|
|
6961
6655
|
let lastPushedSha = null;
|
|
6962
6656
|
try {
|
|
6963
6657
|
pushBranch(branchName, worktreePath);
|
|
6964
6658
|
lastPushedSha = readHeadSha(worktreePath);
|
|
6965
6659
|
} catch (err) {
|
|
6966
|
-
|
|
6660
|
+
log20.error(TAG18, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
6967
6661
|
}
|
|
6968
6662
|
const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
|
|
6969
6663
|
if (lastPushedSha) {
|
|
@@ -6981,7 +6675,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6981
6675
|
let autoFixAttempts = 0;
|
|
6982
6676
|
if (!result.passed && config.verification.autoFix) {
|
|
6983
6677
|
for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
|
|
6984
|
-
|
|
6678
|
+
log20.info(TAG18, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
|
|
6985
6679
|
await client.updateAgentProgress(card.id, {
|
|
6986
6680
|
agentIdentifier: sessionIdentifier,
|
|
6987
6681
|
agentName: AGENT_NAME,
|
|
@@ -6998,30 +6692,29 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6998
6692
|
result = await runVerification(worktreePath, config, workerId);
|
|
6999
6693
|
autoFixAttempts = attempt + 1;
|
|
7000
6694
|
if (result.passed) {
|
|
7001
|
-
|
|
6695
|
+
log20.info(TAG18, `Auto-fix succeeded on attempt ${attempt + 1}`);
|
|
7002
6696
|
const sha = readHeadSha(worktreePath);
|
|
7003
6697
|
if (sha && sha !== lastPushedSha) {
|
|
7004
6698
|
try {
|
|
7005
6699
|
pushBranch(branchName, worktreePath);
|
|
7006
6700
|
lastPushedSha = sha;
|
|
7007
6701
|
} catch (err) {
|
|
7008
|
-
|
|
6702
|
+
log20.warn(TAG18, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
7009
6703
|
}
|
|
7010
6704
|
}
|
|
7011
6705
|
break;
|
|
7012
6706
|
}
|
|
7013
6707
|
}
|
|
7014
6708
|
}
|
|
7015
|
-
verificationResult = result;
|
|
7016
6709
|
if (!result.passed) {
|
|
7017
|
-
|
|
6710
|
+
log20.warn(TAG18, `Verification failed for #${card.short_id} — reporting findings`);
|
|
7018
6711
|
const failSha = readHeadSha(worktreePath);
|
|
7019
6712
|
if (failSha && failSha !== lastPushedSha) {
|
|
7020
6713
|
try {
|
|
7021
6714
|
pushBranch(branchName, worktreePath);
|
|
7022
6715
|
lastPushedSha = failSha;
|
|
7023
6716
|
} catch (err) {
|
|
7024
|
-
|
|
6717
|
+
log20.warn(TAG18, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
7025
6718
|
}
|
|
7026
6719
|
}
|
|
7027
6720
|
const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
|
|
@@ -7032,7 +6725,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7032
6725
|
recoveryBranch: branchName
|
|
7033
6726
|
});
|
|
7034
6727
|
} catch (err) {
|
|
7035
|
-
|
|
6728
|
+
log20.debug(TAG18, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
7036
6729
|
}
|
|
7037
6730
|
await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
|
|
7038
6731
|
const verifyHandback = await guardedHandback(client, card.id, {
|
|
@@ -7052,7 +6745,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7052
6745
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
7053
6746
|
return false;
|
|
7054
6747
|
}
|
|
7055
|
-
|
|
6748
|
+
log20.info(TAG18, `Verification passed for #${card.short_id}`);
|
|
7056
6749
|
}
|
|
7057
6750
|
let prUrl = null;
|
|
7058
6751
|
if (config.completion.createPR) {
|
|
@@ -7060,7 +6753,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7060
6753
|
prUrl = createPullRequest(card, branchName, worktreePath, config, provider);
|
|
7061
6754
|
}
|
|
7062
6755
|
if (config.completion.moveToColumn) {
|
|
7063
|
-
await transferCardToCompletion({ client, tag:
|
|
6756
|
+
await transferCardToCompletion({ client, tag: TAG18 }, card, config.completion.moveToColumn, onMovedToCompletion);
|
|
7064
6757
|
}
|
|
7065
6758
|
if (config.completion.postSummary) {
|
|
7066
6759
|
await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch, sessionStats);
|
|
@@ -7069,35 +6762,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7069
6762
|
try {
|
|
7070
6763
|
await onBeforeWorktreeCleanup(worktreePath);
|
|
7071
6764
|
} catch (err) {
|
|
7072
|
-
|
|
6765
|
+
log20.warn(TAG18, `onBeforeWorktreeCleanup hook failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7073
6766
|
}
|
|
7074
6767
|
}
|
|
7075
6768
|
if (!onBeforeWorktreeCleanup) {
|
|
7076
|
-
await endRunSession({ client, tag:
|
|
7077
|
-
}
|
|
7078
|
-
if (workspaceId) {
|
|
7079
|
-
const diffStat = captureDiffStat(worktreePath, config.worktree.baseBranch);
|
|
7080
|
-
const changedFiles = diffStat && diffStat.files.length > 0 ? diffStat.files : sessionStats?.filesEditedPaths ?? [];
|
|
7081
|
-
await writeEpisode(client, {
|
|
7082
|
-
kind: "implement",
|
|
7083
|
-
card,
|
|
7084
|
-
workspaceId,
|
|
7085
|
-
outcome: "success",
|
|
7086
|
-
approachSummary: sessionStats?.lastAssistantText ?? "",
|
|
7087
|
-
approachBlocks: sessionStats?.assistantTextBlocks,
|
|
7088
|
-
result: verificationResult,
|
|
7089
|
-
cost: sessionStats?.cost ?? null,
|
|
7090
|
-
filesEdited: sessionStats?.filesEdited ?? 0,
|
|
7091
|
-
changedFiles,
|
|
7092
|
-
churn: diffStat ? {
|
|
7093
|
-
insertions: diffStat.insertions,
|
|
7094
|
-
deletions: diffStat.deletions
|
|
7095
|
-
} : undefined,
|
|
7096
|
-
agentSessionId: agentSessionId ?? null
|
|
7097
|
-
});
|
|
6769
|
+
await endRunSession({ client, tag: TAG18 }, card, { status: "completed" }, buildTokenPayload(sessionStats), "throw");
|
|
7098
6770
|
}
|
|
7099
6771
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
7100
|
-
|
|
6772
|
+
log20.info(TAG18, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
|
|
7101
6773
|
return true;
|
|
7102
6774
|
}
|
|
7103
6775
|
function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
@@ -7139,7 +6811,7 @@ function commitUncommittedChanges(worktreePath, card) {
|
|
|
7139
6811
|
encoding: "utf-8"
|
|
7140
6812
|
}).trim();
|
|
7141
6813
|
} catch (err) {
|
|
7142
|
-
|
|
6814
|
+
log20.warn(TAG18, `git status failed in ${worktreePath}: ${err instanceof Error ? err.message : err}`);
|
|
7143
6815
|
return false;
|
|
7144
6816
|
}
|
|
7145
6817
|
if (status.length === 0)
|
|
@@ -7155,10 +6827,10 @@ function commitUncommittedChanges(worktreePath, card) {
|
|
|
7155
6827
|
cwd: worktreePath,
|
|
7156
6828
|
encoding: "utf-8"
|
|
7157
6829
|
});
|
|
7158
|
-
|
|
6830
|
+
log20.warn(TAG18, `Auto-committed uncommitted worktree changes for #${card.short_id} — agent ended without committing`);
|
|
7159
6831
|
return true;
|
|
7160
6832
|
} catch (err) {
|
|
7161
|
-
|
|
6833
|
+
log20.error(TAG18, `auto-commit failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7162
6834
|
return false;
|
|
7163
6835
|
}
|
|
7164
6836
|
}
|
|
@@ -7207,9 +6879,9 @@ async function recordBranchProvenance(client, card, branchName) {
|
|
|
7207
6879
|
${BRANCH_PROVENANCE_MARKER} (pushed — a later run continues it)
|
|
7208
6880
|
Branch: \`${branchName}\``;
|
|
7209
6881
|
await client.updateCard(card.id, { description: description + block });
|
|
7210
|
-
|
|
6882
|
+
log20.info(TAG18, `Recorded branch provenance on #${card.short_id}`);
|
|
7211
6883
|
} catch (err) {
|
|
7212
|
-
|
|
6884
|
+
log20.warn(TAG18, `Failed to record branch provenance on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7213
6885
|
}
|
|
7214
6886
|
}
|
|
7215
6887
|
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
|
|
@@ -7263,25 +6935,24 @@ ${commitLog}
|
|
|
7263
6935
|
description: baseDesc + parts.join(`
|
|
7264
6936
|
`)
|
|
7265
6937
|
});
|
|
7266
|
-
|
|
6938
|
+
log20.info(TAG18, `Posted completion summary to #${card.short_id}`);
|
|
7267
6939
|
} catch (err) {
|
|
7268
|
-
|
|
6940
|
+
log20.error(TAG18, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
|
|
7269
6941
|
}
|
|
7270
6942
|
}
|
|
7271
|
-
var
|
|
6943
|
+
var TAG18 = "completion", SUMMARY_MARKER = `---
|
|
7272
6944
|
**Agent completed**`, BRANCH_PROVENANCE_MARKER = `---
|
|
7273
6945
|
**Agent branch**`;
|
|
7274
6946
|
var init_completion = __esm(() => {
|
|
7275
6947
|
init_dist();
|
|
7276
6948
|
init_board_helpers();
|
|
7277
|
-
init_episode_writer();
|
|
7278
6949
|
init_handback();
|
|
7279
6950
|
init_run_closeout();
|
|
7280
6951
|
init_types2();
|
|
7281
6952
|
});
|
|
7282
6953
|
|
|
7283
6954
|
// src/progress-tracker.ts
|
|
7284
|
-
import { log as
|
|
6955
|
+
import { log as log21 } from "@gethmy/harness";
|
|
7285
6956
|
function truncate2(str, max) {
|
|
7286
6957
|
return str.length > max ? `${str.slice(0, max - 3)}...` : str;
|
|
7287
6958
|
}
|
|
@@ -7310,7 +6981,6 @@ class ProgressTracker {
|
|
|
7310
6981
|
runEventSink = null;
|
|
7311
6982
|
lastEmittedProgress = -1;
|
|
7312
6983
|
lastAssistantText = "";
|
|
7313
|
-
assistantTextBlocks = [];
|
|
7314
6984
|
constructor(client, cardId, sessionIdentifier, subtasks, initialPhase = "exploring") {
|
|
7315
6985
|
this.client = client;
|
|
7316
6986
|
this.cardId = cardId;
|
|
@@ -7393,17 +7063,15 @@ class ProgressTracker {
|
|
|
7393
7063
|
get stats() {
|
|
7394
7064
|
return {
|
|
7395
7065
|
filesEdited: this.filesEdited.size,
|
|
7396
|
-
filesEditedPaths: [...this.filesEdited],
|
|
7397
7066
|
filesRead: this.filesRead.size,
|
|
7398
7067
|
toolCalls: this.toolCallCount,
|
|
7399
7068
|
cost: this.lastCost,
|
|
7400
|
-
lastAssistantText: this.lastAssistantText
|
|
7401
|
-
assistantTextBlocks: [...this.assistantTextBlocks]
|
|
7069
|
+
lastAssistantText: this.lastAssistantText
|
|
7402
7070
|
};
|
|
7403
7071
|
}
|
|
7404
7072
|
onToolStart(name, input) {
|
|
7405
7073
|
this.toolCallCount++;
|
|
7406
|
-
|
|
7074
|
+
log21.debug(TAG19, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
|
|
7407
7075
|
const filePath = this.extractString(input, "file_path");
|
|
7408
7076
|
if (filePath) {
|
|
7409
7077
|
if (EDIT_TOOLS.has(name)) {
|
|
@@ -7459,10 +7127,6 @@ class ProgressTracker {
|
|
|
7459
7127
|
if (trimmed.length < 10)
|
|
7460
7128
|
return;
|
|
7461
7129
|
this.lastAssistantText = trimmed;
|
|
7462
|
-
this.assistantTextBlocks.push(trimmed);
|
|
7463
|
-
if (this.assistantTextBlocks.length > MAX_TEXT_BLOCKS) {
|
|
7464
|
-
this.assistantTextBlocks.shift();
|
|
7465
|
-
}
|
|
7466
7130
|
const end = trimmed.search(SENTENCE_SPLIT);
|
|
7467
7131
|
const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
|
|
7468
7132
|
if (firstLine.length >= 10 && firstLine.length <= 200) {
|
|
@@ -7474,7 +7138,7 @@ class ProgressTracker {
|
|
|
7474
7138
|
transitionTo(newPhase) {
|
|
7475
7139
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
7476
7140
|
return;
|
|
7477
|
-
|
|
7141
|
+
log21.info(TAG19, `Phase: ${this.phase} → ${newPhase}`);
|
|
7478
7142
|
const previousPhase = this.phase;
|
|
7479
7143
|
this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
|
|
7480
7144
|
this.phase = newPhase;
|
|
@@ -7579,7 +7243,7 @@ class ProgressTracker {
|
|
|
7579
7243
|
}
|
|
7580
7244
|
sendUpdate(currentTask) {
|
|
7581
7245
|
this.lastUpdateAt = Date.now();
|
|
7582
|
-
|
|
7246
|
+
log21.debug(TAG19, `Progress: ${this.progress}% — ${currentTask}`);
|
|
7583
7247
|
this.client.updateAgentProgress(this.cardId, {
|
|
7584
7248
|
agentIdentifier: this.sessionIdentifier,
|
|
7585
7249
|
agentName: AGENT_NAME,
|
|
@@ -7596,7 +7260,7 @@ class ProgressTracker {
|
|
|
7596
7260
|
modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
|
|
7597
7261
|
numTurns: this.lastCost?.numTurns ?? 0
|
|
7598
7262
|
}).catch((err) => {
|
|
7599
|
-
|
|
7263
|
+
log21.warn(TAG19, `Failed to send progress update: ${err}`);
|
|
7600
7264
|
});
|
|
7601
7265
|
if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
|
|
7602
7266
|
this.lastEmittedProgress = this.progress;
|
|
@@ -7627,7 +7291,7 @@ class ProgressTracker {
|
|
|
7627
7291
|
return null;
|
|
7628
7292
|
}
|
|
7629
7293
|
}
|
|
7630
|
-
var
|
|
7294
|
+
var TAG19 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
|
|
7631
7295
|
var init_progress_tracker = __esm(() => {
|
|
7632
7296
|
init_types2();
|
|
7633
7297
|
SENTENCE_SPLIT = /\.\s|\n/;
|
|
@@ -7661,7 +7325,7 @@ var init_progress_tracker = __esm(() => {
|
|
|
7661
7325
|
});
|
|
7662
7326
|
|
|
7663
7327
|
// src/prompt.ts
|
|
7664
|
-
import { log as
|
|
7328
|
+
import { log as log22 } from "@gethmy/harness";
|
|
7665
7329
|
import {
|
|
7666
7330
|
buildMemoryQuery
|
|
7667
7331
|
} from "@gethmy/mcp/src/api-client.js";
|
|
@@ -7699,10 +7363,7 @@ function renderPreviousAttemptsSection(failures) {
|
|
|
7699
7363
|
}
|
|
7700
7364
|
async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId, onRecallOutcome) {
|
|
7701
7365
|
const { card } = enriched;
|
|
7702
|
-
const
|
|
7703
|
-
renderPastEpisodesSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome),
|
|
7704
|
-
renderReferenceSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome)
|
|
7705
|
-
]);
|
|
7366
|
+
const referenceSection = await renderReferenceSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome);
|
|
7706
7367
|
try {
|
|
7707
7368
|
const result = await client.generateCardPrompt({
|
|
7708
7369
|
cardId: card.id,
|
|
@@ -7713,13 +7374,13 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
7713
7374
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
7714
7375
|
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.`
|
|
7715
7376
|
});
|
|
7716
|
-
|
|
7717
|
-
return result.prompt +
|
|
7377
|
+
log22.info(TAG20, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
7378
|
+
return result.prompt + referenceSection;
|
|
7718
7379
|
} catch (err) {
|
|
7719
7380
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7720
|
-
|
|
7381
|
+
log22.warn(TAG20, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
7721
7382
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
7722
|
-
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection +
|
|
7383
|
+
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + referenceSection;
|
|
7723
7384
|
}
|
|
7724
7385
|
}
|
|
7725
7386
|
async function renderCommentsSection(client, cardId) {
|
|
@@ -7738,95 +7399,41 @@ ${untrustedDataBlock(section, {
|
|
|
7738
7399
|
purpose: "discussion to take into account"
|
|
7739
7400
|
})}` : "";
|
|
7740
7401
|
} catch (err) {
|
|
7741
|
-
|
|
7402
|
+
log22.warn(TAG20, "comment-thread fetch failed", {
|
|
7742
7403
|
event: "comment_fetch_failed",
|
|
7743
7404
|
error: err instanceof Error ? err.message : String(err)
|
|
7744
7405
|
});
|
|
7745
7406
|
return "";
|
|
7746
7407
|
}
|
|
7747
7408
|
}
|
|
7748
|
-
async function renderPastEpisodesSection(client, title, description, workspaceId, projectId, onOutcome) {
|
|
7749
|
-
if (!projectId)
|
|
7750
|
-
return "";
|
|
7751
|
-
try {
|
|
7752
|
-
const query = buildMemoryQuery(title, description);
|
|
7753
|
-
const { entities } = await client.harmonyRecall({
|
|
7754
|
-
workspaceId,
|
|
7755
|
-
projectId,
|
|
7756
|
-
query,
|
|
7757
|
-
type: ["solution", "error"],
|
|
7758
|
-
memory_tier: "episode",
|
|
7759
|
-
scope: "project",
|
|
7760
|
-
topK: 3,
|
|
7761
|
-
includeEpisodes: true,
|
|
7762
|
-
consumer: "agent-prompt"
|
|
7763
|
-
});
|
|
7764
|
-
onOutcome?.({ flavour: "past_episodes", count: entities.length });
|
|
7765
|
-
if (entities.length === 0)
|
|
7766
|
-
return "";
|
|
7767
|
-
const bullets = entities.map((entity) => {
|
|
7768
|
-
const e = entity;
|
|
7769
|
-
const meta = e.metadata ?? {};
|
|
7770
|
-
const outcomeTag = meta.outcome ? `[${meta.outcome}]` : "[?]";
|
|
7771
|
-
const approach = meta.approach_summary ?? "";
|
|
7772
|
-
const lines = [
|
|
7773
|
-
`- ${outcomeTag} ${e.title ?? "(untitled episode)"}`,
|
|
7774
|
-
` Approach: ${approach}`
|
|
7775
|
-
];
|
|
7776
|
-
if (meta.key_insight)
|
|
7777
|
-
lines.push(` Key insight: ${meta.key_insight}`);
|
|
7778
|
-
if (meta.changed_files && meta.changed_files.length > 0) {
|
|
7779
|
-
const shown = meta.changed_files.slice(0, 8);
|
|
7780
|
-
const extra = meta.changed_files.length - shown.length;
|
|
7781
|
-
const suffix = extra > 0 ? ` (+${extra} more)` : "";
|
|
7782
|
-
lines.push(` Changed files: ${shown.join(", ")}${suffix}`);
|
|
7783
|
-
}
|
|
7784
|
-
return lines.join(`
|
|
7785
|
-
`);
|
|
7786
|
-
}).join(`
|
|
7787
|
-
`);
|
|
7788
|
-
return `
|
|
7789
|
-
|
|
7790
|
-
## Similar past tasks
|
|
7791
|
-
${bullets}`;
|
|
7792
|
-
} catch (err) {
|
|
7793
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
7794
|
-
log23.warn(TAG21, "past-episodes recall failed", {
|
|
7795
|
-
event: "episode_recall_failed",
|
|
7796
|
-
error
|
|
7797
|
-
});
|
|
7798
|
-
onOutcome?.({ flavour: "past_episodes", count: 0, failure: error });
|
|
7799
|
-
return "";
|
|
7800
|
-
}
|
|
7801
|
-
}
|
|
7802
7409
|
async function renderReferenceSection(client, title, description, workspaceId, projectId, onOutcome) {
|
|
7803
7410
|
try {
|
|
7804
7411
|
const query = buildMemoryQuery(title, description);
|
|
7805
|
-
const { entities } = await client.
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
query,
|
|
7809
|
-
memory_tier: "reference",
|
|
7810
|
-
topK: 5,
|
|
7811
|
-
consumer: "agent-prompt"
|
|
7412
|
+
const { entities } = await client.searchMemoryEntities(workspaceId, query, {
|
|
7413
|
+
project_id: projectId,
|
|
7414
|
+
limit: 5
|
|
7812
7415
|
});
|
|
7813
7416
|
onOutcome?.({ flavour: "reference", count: entities.length });
|
|
7814
7417
|
if (entities.length === 0)
|
|
7815
7418
|
return "";
|
|
7816
7419
|
const bullets = entities.map((entity) => {
|
|
7817
7420
|
const e = entity;
|
|
7421
|
+
const type = e.type ? ` (${e.type})` : "";
|
|
7818
7422
|
const content = (e.content ?? "").slice(0, 300);
|
|
7819
|
-
return `- ${e.title ?? "(untitled)"}
|
|
7423
|
+
return `- ${e.title ?? "(untitled)"}${type}
|
|
7820
7424
|
${content}`;
|
|
7821
7425
|
}).join(`
|
|
7822
7426
|
`);
|
|
7823
7427
|
return `
|
|
7824
7428
|
|
|
7825
|
-
|
|
7826
|
-
${bullets}
|
|
7429
|
+
${untrustedDataBlock(`## How we work here
|
|
7430
|
+
${bullets}`, {
|
|
7431
|
+
label: "workspace memory",
|
|
7432
|
+
purpose: "conventions to apply where they fit the task"
|
|
7433
|
+
})}`;
|
|
7827
7434
|
} catch (err) {
|
|
7828
7435
|
const error = err instanceof Error ? err.message : String(err);
|
|
7829
|
-
|
|
7436
|
+
log22.warn(TAG20, "reference recall failed", {
|
|
7830
7437
|
event: "reference_recall_failed",
|
|
7831
7438
|
error
|
|
7832
7439
|
});
|
|
@@ -7868,7 +7475,7 @@ ${subtaskStr}
|
|
|
7868
7475
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
7869
7476
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
7870
7477
|
}
|
|
7871
|
-
var
|
|
7478
|
+
var TAG20 = "prompt", RESUME_CONTINUATION = `Continue from where you stopped.
|
|
7872
7479
|
|
|
7873
7480
|
This is the same session. The task you were given, the work you have already done,
|
|
7874
7481
|
and everything you read are above in this conversation. None of it has changed, and
|
|
@@ -7893,7 +7500,7 @@ import {
|
|
|
7893
7500
|
extractPrUrl as extractPrUrl3,
|
|
7894
7501
|
getBranchWebUrl as getBranchWebUrl2,
|
|
7895
7502
|
getHeadSha,
|
|
7896
|
-
log as
|
|
7503
|
+
log as log23,
|
|
7897
7504
|
pushBranch as pushBranch2,
|
|
7898
7505
|
renameRemoteBranch,
|
|
7899
7506
|
sanitizeCiText as sanitizeCiText2,
|
|
@@ -8019,7 +7626,7 @@ function parseReviewOutput(stdout) {
|
|
|
8019
7626
|
try {
|
|
8020
7627
|
const parsed = JSON.parse(raw);
|
|
8021
7628
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
8022
|
-
|
|
7629
|
+
log23.debug(TAG21, "Parsed review output from fenced JSON block");
|
|
8023
7630
|
return extractResult(parsed);
|
|
8024
7631
|
}
|
|
8025
7632
|
} catch {}
|
|
@@ -8045,21 +7652,21 @@ function parseReviewOutput(stdout) {
|
|
|
8045
7652
|
try {
|
|
8046
7653
|
const parsed = JSON.parse(candidates[i]);
|
|
8047
7654
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
8048
|
-
|
|
7655
|
+
log23.debug(TAG21, "Parsed review output from raw JSON object");
|
|
8049
7656
|
return extractResult(parsed);
|
|
8050
7657
|
}
|
|
8051
7658
|
} catch {}
|
|
8052
7659
|
}
|
|
8053
7660
|
const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
|
|
8054
7661
|
if (verdictMatch) {
|
|
8055
|
-
|
|
7662
|
+
log23.warn(TAG21, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
|
|
8056
7663
|
return {
|
|
8057
7664
|
verdict: verdictMatch[1].toLowerCase(),
|
|
8058
7665
|
summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
|
|
8059
7666
|
findings: []
|
|
8060
7667
|
};
|
|
8061
7668
|
}
|
|
8062
|
-
|
|
7669
|
+
log23.warn(TAG21, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
|
|
8063
7670
|
return {
|
|
8064
7671
|
verdict: "error",
|
|
8065
7672
|
summary: stdout.slice(0, 500),
|
|
@@ -8095,10 +7702,10 @@ async function postReviewComment(client, card, commentType, body, agentSessionId
|
|
|
8095
7702
|
agentSessionId: agentSessionId ?? undefined
|
|
8096
7703
|
});
|
|
8097
7704
|
} catch (err) {
|
|
8098
|
-
|
|
7705
|
+
log23.error(TAG21, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8099
7706
|
}
|
|
8100
7707
|
}
|
|
8101
|
-
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath,
|
|
7708
|
+
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, agentSessionId, stateStore, agentId, resolvedFromPrUrl, visualLensSkipReason) {
|
|
8102
7709
|
let freshDesc;
|
|
8103
7710
|
let freshCard = null;
|
|
8104
7711
|
let handbackVerdict;
|
|
@@ -8119,7 +7726,7 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
8119
7726
|
};
|
|
8120
7727
|
}
|
|
8121
7728
|
if (!handbackVerdict.proceed) {
|
|
8122
|
-
|
|
7729
|
+
log23.info(TAG21, `#${card.short_id}: review outcome will not move the card — ${handbackVerdict.detail} (${handbackVerdict.reason})`);
|
|
8123
7730
|
}
|
|
8124
7731
|
const lensLine = visualLensSkipReason ? `Visual QA did not run — ${sanitizeCiText2(visualLensSkipReason, REVIEW_FAILURE_REASON_MAX)}. Anything that needs a rendered page is ungraded.` : "";
|
|
8125
7732
|
const LOOPING_REFUSALS = new Set(["unreadable", "released", "done"]);
|
|
@@ -8129,19 +7736,19 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
8129
7736
|
}
|
|
8130
7737
|
try {
|
|
8131
7738
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
8132
|
-
|
|
7739
|
+
log23.warn(TAG21, `#${card.short_id} labelled "${NEED_REVIEW_LABEL}" (${handbackVerdict.reason}) so the daemon stops re-claiming and re-reviewing it`);
|
|
8133
7740
|
} catch (err) {
|
|
8134
|
-
|
|
7741
|
+
log23.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
8135
7742
|
}
|
|
8136
7743
|
}
|
|
8137
7744
|
const currentCycle = getReviewCycle(freshDesc) + 1;
|
|
8138
7745
|
const maxCycles = config.review.maxReviewCycles;
|
|
8139
7746
|
if (result.verdict === "error") {
|
|
8140
|
-
|
|
7747
|
+
log23.warn(TAG21, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
|
|
8141
7748
|
try {
|
|
8142
7749
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
8143
7750
|
} catch (err) {
|
|
8144
|
-
|
|
7751
|
+
log23.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
8145
7752
|
}
|
|
8146
7753
|
if (config.review.postFindings) {
|
|
8147
7754
|
const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
|
|
@@ -8184,7 +7791,7 @@ ${runLogTail}
|
|
|
8184
7791
|
renameRemoteBranch(branchName, newRef, worktreePath);
|
|
8185
7792
|
approvedBranch = newRef;
|
|
8186
7793
|
} catch (err) {
|
|
8187
|
-
|
|
7794
|
+
log23.warn(TAG21, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
|
|
8188
7795
|
}
|
|
8189
7796
|
}
|
|
8190
7797
|
if (config.review.createPR && approvedBranch) {
|
|
@@ -8217,14 +7824,14 @@ ${runLogTail}
|
|
|
8217
7824
|
await client.updateCard(card.id, { description: desc });
|
|
8218
7825
|
}
|
|
8219
7826
|
} catch (err) {
|
|
8220
|
-
|
|
7827
|
+
log23.warn(TAG21, `Failed to persist PR URL / branch rename to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
|
|
8221
7828
|
}
|
|
8222
7829
|
}
|
|
8223
7830
|
if (branchName) {
|
|
8224
7831
|
try {
|
|
8225
7832
|
await persistReviewedSha(client, card, worktreePath);
|
|
8226
7833
|
} catch (err) {
|
|
8227
|
-
|
|
7834
|
+
log23.warn(TAG21, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8228
7835
|
}
|
|
8229
7836
|
}
|
|
8230
7837
|
if (config.review.postFindings) {
|
|
@@ -8247,7 +7854,7 @@ ${runLogTail}
|
|
|
8247
7854
|
progressPercent: 100,
|
|
8248
7855
|
...buildTokenPayload(sessionStats)
|
|
8249
7856
|
});
|
|
8250
|
-
|
|
7857
|
+
log23.info(TAG21, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
|
|
8251
7858
|
} else {
|
|
8252
7859
|
const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
|
|
8253
7860
|
const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
|
|
@@ -8255,7 +7862,7 @@ ${runLogTail}
|
|
|
8255
7862
|
const linkedFindings = [...criticalFindings, ...majorFindings];
|
|
8256
7863
|
const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
|
|
8257
7864
|
if (currentCycle >= maxCycles) {
|
|
8258
|
-
|
|
7865
|
+
log23.warn(TAG21, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
|
|
8259
7866
|
if (handbackVerdict.proceed) {
|
|
8260
7867
|
await moveCardToColumn(client, freshCard ?? card, config.review.moveToColumn);
|
|
8261
7868
|
} else {
|
|
@@ -8273,22 +7880,6 @@ ${runLogTail}
|
|
|
8273
7880
|
status: "completed",
|
|
8274
7881
|
...buildTokenPayload(sessionStats)
|
|
8275
7882
|
});
|
|
8276
|
-
if (workspaceId) {
|
|
8277
|
-
const origId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
8278
|
-
const reviewId = await writeEpisode(client, {
|
|
8279
|
-
kind: "review",
|
|
8280
|
-
card,
|
|
8281
|
-
workspaceId,
|
|
8282
|
-
verdict: "rejected",
|
|
8283
|
-
summary: `Reached max review cycles (${maxCycles}). ${result.summary}`,
|
|
8284
|
-
cost: sessionStats?.cost ?? null,
|
|
8285
|
-
agentSessionId: agentSessionId ?? null,
|
|
8286
|
-
originalEpisodeId: origId
|
|
8287
|
-
});
|
|
8288
|
-
if (origId) {
|
|
8289
|
-
await backfillReviewVerdict(client, origId, "rejected", reviewId);
|
|
8290
|
-
}
|
|
8291
|
-
}
|
|
8292
7883
|
if (branchName) {
|
|
8293
7884
|
cleanupWorktree3(worktreePath, branchName);
|
|
8294
7885
|
}
|
|
@@ -8299,7 +7890,7 @@ ${runLogTail}
|
|
|
8299
7890
|
try {
|
|
8300
7891
|
await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
|
|
8301
7892
|
} catch (err) {
|
|
8302
|
-
|
|
7893
|
+
log23.error(TAG21, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
|
|
8303
7894
|
}
|
|
8304
7895
|
}));
|
|
8305
7896
|
if (linkedFindings.length > 0) {
|
|
@@ -8311,7 +7902,7 @@ ${runLogTail}
|
|
|
8311
7902
|
try {
|
|
8312
7903
|
await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
|
|
8313
7904
|
} catch (err) {
|
|
8314
|
-
|
|
7905
|
+
log23.error(TAG21, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
8315
7906
|
}
|
|
8316
7907
|
}));
|
|
8317
7908
|
if (handbackVerdict.proceed) {
|
|
@@ -8320,7 +7911,7 @@ ${runLogTail}
|
|
|
8320
7911
|
try {
|
|
8321
7912
|
await client.updateCard(card.id, { description: updatedDesc });
|
|
8322
7913
|
} catch (err) {
|
|
8323
|
-
|
|
7914
|
+
log23.error(TAG21, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
|
|
8324
7915
|
}
|
|
8325
7916
|
}
|
|
8326
7917
|
const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
|
|
@@ -8339,9 +7930,9 @@ ${runLogTail}
|
|
|
8339
7930
|
if (handbackVerdict.proceed && config.planning.enabled && card.plan_id) {
|
|
8340
7931
|
try {
|
|
8341
7932
|
await client.updateCard(card.id, { needsPlanRefresh: true });
|
|
8342
|
-
|
|
7933
|
+
log23.info(TAG21, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
|
|
8343
7934
|
} catch (err) {
|
|
8344
|
-
|
|
7935
|
+
log23.warn(TAG21, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8345
7936
|
}
|
|
8346
7937
|
}
|
|
8347
7938
|
if (handbackVerdict.proceed) {
|
|
@@ -8359,10 +7950,10 @@ ${runLogTail}
|
|
|
8359
7950
|
recoveryBranch
|
|
8360
7951
|
});
|
|
8361
7952
|
} catch (err) {
|
|
8362
|
-
|
|
7953
|
+
log23.debug(TAG21, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
8363
7954
|
}
|
|
8364
7955
|
if (recoveryBranch) {
|
|
8365
|
-
|
|
7956
|
+
log23.info(TAG21, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
|
|
8366
7957
|
}
|
|
8367
7958
|
await client.endAgentSession(card.id, {
|
|
8368
7959
|
status: "failed",
|
|
@@ -8371,35 +7962,18 @@ ${runLogTail}
|
|
|
8371
7962
|
recoveryBranch,
|
|
8372
7963
|
...buildTokenPayload(sessionStats)
|
|
8373
7964
|
});
|
|
8374
|
-
|
|
8375
|
-
}
|
|
8376
|
-
if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
|
|
8377
|
-
const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
8378
|
-
const reviewEpisodeId = await writeEpisode(client, {
|
|
8379
|
-
kind: "review",
|
|
8380
|
-
card,
|
|
8381
|
-
workspaceId,
|
|
8382
|
-
verdict: result.verdict,
|
|
8383
|
-
summary: result.summary,
|
|
8384
|
-
cost: sessionStats?.cost ?? null,
|
|
8385
|
-
agentSessionId: agentSessionId ?? null,
|
|
8386
|
-
originalEpisodeId
|
|
8387
|
-
});
|
|
8388
|
-
if (originalEpisodeId) {
|
|
8389
|
-
await backfillReviewVerdict(client, originalEpisodeId, result.verdict, reviewEpisodeId);
|
|
8390
|
-
}
|
|
7965
|
+
log23.info(TAG21, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
|
|
8391
7966
|
}
|
|
8392
7967
|
if (branchName) {
|
|
8393
7968
|
cleanupWorktree3(worktreePath, branchName);
|
|
8394
7969
|
}
|
|
8395
7970
|
}
|
|
8396
|
-
var
|
|
7971
|
+
var TAG21 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
|
|
8397
7972
|
**Review:`, RUN_LOG_TAIL_BYTES = 2048;
|
|
8398
7973
|
var init_review_completion = __esm(() => {
|
|
8399
7974
|
init_dist();
|
|
8400
7975
|
init_board_helpers();
|
|
8401
7976
|
init_completion();
|
|
8402
|
-
init_episode_writer();
|
|
8403
7977
|
init_handback();
|
|
8404
7978
|
init_types2();
|
|
8405
7979
|
});
|
|
@@ -8624,7 +8198,7 @@ import {
|
|
|
8624
8198
|
mkdirSync as mkdirSync3
|
|
8625
8199
|
} from "node:fs";
|
|
8626
8200
|
import { join as join5 } from "node:path";
|
|
8627
|
-
import { log as
|
|
8201
|
+
import { log as log24 } from "@gethmy/harness";
|
|
8628
8202
|
function runLogPath(runId, shortId) {
|
|
8629
8203
|
return join5(runsDir(), `${runId}-card-${shortId}.log`);
|
|
8630
8204
|
}
|
|
@@ -8638,7 +8212,7 @@ function appendRunLog(tag, runId, shortId, line) {
|
|
|
8638
8212
|
`, "utf-8");
|
|
8639
8213
|
return path;
|
|
8640
8214
|
} catch (err) {
|
|
8641
|
-
|
|
8215
|
+
log24.warn(tag, `Failed to append to run log: ${err instanceof Error ? err.message : err}`);
|
|
8642
8216
|
return null;
|
|
8643
8217
|
}
|
|
8644
8218
|
}
|
|
@@ -8650,11 +8224,11 @@ function openRunLog(tag, runId, shortId) {
|
|
|
8650
8224
|
const path = runLogPath(runId, shortId);
|
|
8651
8225
|
const stream = createWriteStream(path, { flags: "a" });
|
|
8652
8226
|
stream.on("error", (err) => {
|
|
8653
|
-
|
|
8227
|
+
log24.warn(tag, `Run log ${path} failed: ${err.message}`);
|
|
8654
8228
|
});
|
|
8655
8229
|
return { path, stream };
|
|
8656
8230
|
} catch (err) {
|
|
8657
|
-
|
|
8231
|
+
log24.warn(tag, `Failed to open run log: ${err instanceof Error ? err.message : err}`);
|
|
8658
8232
|
return null;
|
|
8659
8233
|
}
|
|
8660
8234
|
}
|
|
@@ -8691,7 +8265,7 @@ function isOwnNoticeConflict(err) {
|
|
|
8691
8265
|
const driver = sessionConflictDriver(err);
|
|
8692
8266
|
return driver === null || driver === NOTICE_DRIVER;
|
|
8693
8267
|
}
|
|
8694
|
-
async function startSessionYieldingNotice(start, client, cardId,
|
|
8268
|
+
async function startSessionYieldingNotice(start, client, cardId, log25) {
|
|
8695
8269
|
try {
|
|
8696
8270
|
return await start();
|
|
8697
8271
|
} catch (err) {
|
|
@@ -8700,12 +8274,12 @@ async function startSessionYieldingNotice(start, client, cardId, log26) {
|
|
|
8700
8274
|
try {
|
|
8701
8275
|
const { session } = await client.getAgentSession(cardId);
|
|
8702
8276
|
if (!session) {
|
|
8703
|
-
|
|
8277
|
+
log25.info(`The session that refused the start is already gone — retrying: ${err instanceof Error ? err.message : String(err)}`);
|
|
8704
8278
|
} else if (!isPoolNotice(session)) {
|
|
8705
|
-
|
|
8279
|
+
log25.warn("The session that refused the start is not a pool notice on a re-read — leaving it alone");
|
|
8706
8280
|
throw err;
|
|
8707
8281
|
} else {
|
|
8708
|
-
|
|
8282
|
+
log25.info(`Ending this pool's own notice on the card and retrying the session start: ${err instanceof Error ? err.message : String(err)}`);
|
|
8709
8283
|
await client.endAgentSession(cardId, {
|
|
8710
8284
|
status: "failed",
|
|
8711
8285
|
failureReason: "stale",
|
|
@@ -8715,7 +8289,7 @@ async function startSessionYieldingNotice(start, client, cardId, log26) {
|
|
|
8715
8289
|
} catch (endErr) {
|
|
8716
8290
|
if (endErr === err)
|
|
8717
8291
|
throw err;
|
|
8718
|
-
|
|
8292
|
+
log25.warn(`Could not yield this pool's own notice: ${endErr instanceof Error ? endErr.message : String(endErr)}`);
|
|
8719
8293
|
throw err;
|
|
8720
8294
|
}
|
|
8721
8295
|
return await start();
|
|
@@ -8752,7 +8326,7 @@ import {
|
|
|
8752
8326
|
writeFileSync as writeFileSync2
|
|
8753
8327
|
} from "node:fs";
|
|
8754
8328
|
import { dirname as dirname2, join as join6 } from "node:path";
|
|
8755
|
-
import { log as
|
|
8329
|
+
import { log as log25 } from "@gethmy/harness";
|
|
8756
8330
|
function emptyTokens() {
|
|
8757
8331
|
return cloneWithoutPrototype({
|
|
8758
8332
|
inputTokens: 0,
|
|
@@ -8891,7 +8465,7 @@ class StateStore {
|
|
|
8891
8465
|
const raw = readFileSync6(this.path, "utf-8");
|
|
8892
8466
|
const parsed = parseJsonWithoutPrototypes(raw);
|
|
8893
8467
|
if (parsed?.version !== SCHEMA_VERSION) {
|
|
8894
|
-
|
|
8468
|
+
log25.warn(TAG22, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
|
|
8895
8469
|
return cloneWithoutPrototype({
|
|
8896
8470
|
version: SCHEMA_VERSION,
|
|
8897
8471
|
daemonId: null,
|
|
@@ -8916,7 +8490,7 @@ class StateStore {
|
|
|
8916
8490
|
account: parsed.account ?? emptyAccount()
|
|
8917
8491
|
});
|
|
8918
8492
|
} catch (err) {
|
|
8919
|
-
|
|
8493
|
+
log25.error(TAG22, `failed to read state file: ${err instanceof Error ? err.message : err}`);
|
|
8920
8494
|
return emptyState();
|
|
8921
8495
|
}
|
|
8922
8496
|
}
|
|
@@ -9274,7 +8848,7 @@ class StateStore {
|
|
|
9274
8848
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
9275
8849
|
}
|
|
9276
8850
|
}
|
|
9277
|
-
var
|
|
8851
|
+
var TAG22 = "state-store", SCHEMA_VERSION = 1;
|
|
9278
8852
|
var init_state_store = __esm(() => {
|
|
9279
8853
|
init_dist();
|
|
9280
8854
|
init_paths();
|
|
@@ -9282,7 +8856,7 @@ var init_state_store = __esm(() => {
|
|
|
9282
8856
|
|
|
9283
8857
|
// src/stream-parser.ts
|
|
9284
8858
|
import { EventEmitter } from "node:events";
|
|
9285
|
-
import { log as
|
|
8859
|
+
import { log as log26 } from "@gethmy/harness";
|
|
9286
8860
|
function normalizeToolResultContent(raw) {
|
|
9287
8861
|
if (raw == null)
|
|
9288
8862
|
return;
|
|
@@ -9303,7 +8877,7 @@ function normalizeToolResultContent(raw) {
|
|
|
9303
8877
|
return String(raw);
|
|
9304
8878
|
}
|
|
9305
8879
|
}
|
|
9306
|
-
var
|
|
8880
|
+
var TAG23 = "stream-parser", StreamParser;
|
|
9307
8881
|
var init_stream_parser = __esm(() => {
|
|
9308
8882
|
StreamParser = class StreamParser extends EventEmitter {
|
|
9309
8883
|
buffer = "";
|
|
@@ -9350,14 +8924,14 @@ var init_stream_parser = __esm(() => {
|
|
|
9350
8924
|
try {
|
|
9351
8925
|
msg = JSON.parse(line);
|
|
9352
8926
|
} catch {
|
|
9353
|
-
|
|
8927
|
+
log26.debug(TAG23, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
9354
8928
|
return;
|
|
9355
8929
|
}
|
|
9356
8930
|
try {
|
|
9357
8931
|
this.handleMessage(msg);
|
|
9358
8932
|
} catch (err) {
|
|
9359
8933
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9360
|
-
|
|
8934
|
+
log26.warn(TAG23, `Error handling stream event: ${errMsg}`);
|
|
9361
8935
|
this.emit("parse_error", errMsg);
|
|
9362
8936
|
}
|
|
9363
8937
|
}
|
|
@@ -9433,7 +9007,7 @@ var init_stream_parser = __esm(() => {
|
|
|
9433
9007
|
});
|
|
9434
9008
|
|
|
9435
9009
|
// src/transitions.ts
|
|
9436
|
-
import { log as
|
|
9010
|
+
import { log as log27 } from "@gethmy/harness";
|
|
9437
9011
|
async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
9438
9012
|
let lastErr;
|
|
9439
9013
|
for (let i = 0;i < attempts; i++) {
|
|
@@ -9444,7 +9018,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
9444
9018
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
9445
9019
|
if (i < attempts - 1) {
|
|
9446
9020
|
const wait = backoffMs * 2 ** i;
|
|
9447
|
-
|
|
9021
|
+
log27.warn(TAG24, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
9448
9022
|
await new Promise((r) => setTimeout(r, wait));
|
|
9449
9023
|
}
|
|
9450
9024
|
}
|
|
@@ -9468,10 +9042,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9468
9042
|
if (opts.strictColumn) {
|
|
9469
9043
|
throw new TransitionError("move", 1, msg);
|
|
9470
9044
|
}
|
|
9471
|
-
|
|
9045
|
+
log27.warn(TAG24, `#${shortId}: ${msg} — skipping move`);
|
|
9472
9046
|
} else if (card.column_id !== target.id) {
|
|
9473
9047
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
9474
|
-
|
|
9048
|
+
log27.info(TAG24, `#${shortId} → "${target.name}"`);
|
|
9475
9049
|
card.column_id = target.id;
|
|
9476
9050
|
moveLanded = true;
|
|
9477
9051
|
} else {
|
|
@@ -9490,7 +9064,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9490
9064
|
continue;
|
|
9491
9065
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
9492
9066
|
existing.add(labelId);
|
|
9493
|
-
|
|
9067
|
+
log27.info(TAG24, `#${shortId} +label "${name}"`);
|
|
9494
9068
|
}
|
|
9495
9069
|
card.labelIds = Array.from(existing);
|
|
9496
9070
|
}
|
|
@@ -9502,23 +9076,23 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9502
9076
|
continue;
|
|
9503
9077
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
9504
9078
|
existing.delete(match.id);
|
|
9505
|
-
|
|
9079
|
+
log27.info(TAG24, `#${shortId} -label "${name}"`);
|
|
9506
9080
|
}
|
|
9507
9081
|
card.labelIds = Array.from(existing);
|
|
9508
9082
|
}
|
|
9509
9083
|
if (plan.updateCard) {
|
|
9510
9084
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
9511
|
-
|
|
9085
|
+
log27.info(TAG24, `#${shortId} updated`);
|
|
9512
9086
|
}
|
|
9513
9087
|
if (plan.endSession) {
|
|
9514
9088
|
const endResult = await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
9515
9089
|
result.endSession = endResult;
|
|
9516
|
-
|
|
9090
|
+
log27.info(TAG24, `#${shortId} session ended (${plan.endSession.status})`);
|
|
9517
9091
|
}
|
|
9518
9092
|
if (plan.assignAgent !== undefined) {
|
|
9519
9093
|
const assignedAgentId = plan.assignAgent;
|
|
9520
9094
|
await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
|
|
9521
|
-
|
|
9095
|
+
log27.info(TAG24, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
|
|
9522
9096
|
}
|
|
9523
9097
|
if (opts.store && opts.runId) {
|
|
9524
9098
|
try {
|
|
@@ -9532,11 +9106,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
9532
9106
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
9533
9107
|
return result?.label?.id ?? null;
|
|
9534
9108
|
} catch (err) {
|
|
9535
|
-
|
|
9109
|
+
log27.warn(TAG24, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
9536
9110
|
return null;
|
|
9537
9111
|
}
|
|
9538
9112
|
}
|
|
9539
|
-
var
|
|
9113
|
+
var TAG24 = "transition", TransitionError;
|
|
9540
9114
|
var init_transitions = __esm(() => {
|
|
9541
9115
|
TransitionError = class TransitionError extends Error {
|
|
9542
9116
|
step;
|
|
@@ -9566,7 +9140,7 @@ import {
|
|
|
9566
9140
|
formatDiffSummary,
|
|
9567
9141
|
GIT_NO_HOOKS as GIT_NO_HOOKS7,
|
|
9568
9142
|
implementRunContainmentCliArgs,
|
|
9569
|
-
log as
|
|
9143
|
+
log as log28,
|
|
9570
9144
|
probeDevServer,
|
|
9571
9145
|
removeSandboxContainer,
|
|
9572
9146
|
resolveStageGate,
|
|
@@ -9662,11 +9236,11 @@ class ReviewWorker {
|
|
|
9662
9236
|
cliSessionId: this.cliSessionId
|
|
9663
9237
|
});
|
|
9664
9238
|
} catch (err) {
|
|
9665
|
-
|
|
9239
|
+
log28.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
9666
9240
|
}
|
|
9667
9241
|
}
|
|
9668
9242
|
get tag() {
|
|
9669
|
-
return `${
|
|
9243
|
+
return `${TAG25}:${this.id}`;
|
|
9670
9244
|
}
|
|
9671
9245
|
get isIdle() {
|
|
9672
9246
|
return this.state === "idle";
|
|
@@ -9704,7 +9278,7 @@ class ReviewWorker {
|
|
|
9704
9278
|
try {
|
|
9705
9279
|
const after = await this.stateStore.addTurns(cardId, numTurns);
|
|
9706
9280
|
if (crossedTurnCap(before, after, cap)) {
|
|
9707
|
-
|
|
9281
|
+
log28.warn(TAG25, `#${this.stateStore.getRun(this.runId ?? "")?.cardShortId ?? cardId} reached its per-card turn cap on a review leg — ${after} of ${cap} turns. The implement pipeline will not pick it up again until it is reassigned or the cap is raised.`);
|
|
9708
9282
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
9709
9283
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), {
|
|
9710
9284
|
commentType: "blocker",
|
|
@@ -9746,12 +9320,12 @@ class ReviewWorker {
|
|
|
9746
9320
|
resumeMessage: null
|
|
9747
9321
|
});
|
|
9748
9322
|
} catch (err) {
|
|
9749
|
-
|
|
9323
|
+
log28.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
9750
9324
|
}
|
|
9751
9325
|
}
|
|
9752
9326
|
try {
|
|
9753
9327
|
this.state = "preparing";
|
|
9754
|
-
|
|
9328
|
+
log28.info(this.tag, resuming ? `Resuming review of #${card.short_id} "${card.title}" with ${this.grantedTurns ?? "the default"} more turns` : `Preparing review for #${card.short_id} "${card.title}"`);
|
|
9755
9329
|
this.startHeartbeat();
|
|
9756
9330
|
if (!resuming) {
|
|
9757
9331
|
await this.stateStore.insertRun({
|
|
@@ -9779,7 +9353,7 @@ class ReviewWorker {
|
|
|
9779
9353
|
const resolution = await resolveReviewBranch(card.description, repoRoot);
|
|
9780
9354
|
if (resolution.kind !== "branch") {
|
|
9781
9355
|
const why = resolution.kind === "skip" ? resolution.reason : "no branch or PR reference";
|
|
9782
|
-
|
|
9356
|
+
log28.info(this.tag, `#${card.short_id} not auto-reviewable (${why}) — marking for human review (staying in Review)`);
|
|
9783
9357
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9784
9358
|
return;
|
|
9785
9359
|
}
|
|
@@ -9790,13 +9364,13 @@ class ReviewWorker {
|
|
|
9790
9364
|
reviewedSha: extractReviewedSha2(card.description ?? null)
|
|
9791
9365
|
});
|
|
9792
9366
|
if (continuity.kind === "skip") {
|
|
9793
|
-
|
|
9367
|
+
log28.warn(this.tag, `#${card.short_id} refusing the PR-derived re-review (${continuity.reason}) — marking for human review (staying in Review)`);
|
|
9794
9368
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9795
9369
|
return;
|
|
9796
9370
|
}
|
|
9797
9371
|
}
|
|
9798
9372
|
this.branchName = resolution.branch;
|
|
9799
|
-
|
|
9373
|
+
log28.info(this.tag, `Review branch: ${this.branchName}`);
|
|
9800
9374
|
let reviewSession;
|
|
9801
9375
|
try {
|
|
9802
9376
|
const startSession = () => this.client.startAgentSession(card.id, {
|
|
@@ -9812,14 +9386,14 @@ class ReviewWorker {
|
|
|
9812
9386
|
awaitingDecisionUntil: null
|
|
9813
9387
|
});
|
|
9814
9388
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
9815
|
-
info: (message) =>
|
|
9816
|
-
warn: (message) =>
|
|
9389
|
+
info: (message) => log28.info(this.tag, message),
|
|
9390
|
+
warn: (message) => log28.warn(this.tag, message)
|
|
9817
9391
|
});
|
|
9818
9392
|
reviewSession = started.session;
|
|
9819
9393
|
} catch (err) {
|
|
9820
9394
|
if (isSessionConflict(err)) {
|
|
9821
9395
|
this.sessionConflict = true;
|
|
9822
|
-
|
|
9396
|
+
log28.info(this.tag, `Skipping review of #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9823
9397
|
return;
|
|
9824
9398
|
}
|
|
9825
9399
|
throw err;
|
|
@@ -9840,7 +9414,7 @@ class ReviewWorker {
|
|
|
9840
9414
|
const cwd = this.worktreePath;
|
|
9841
9415
|
const devServer = await this.startDevServer(card, cwd, port);
|
|
9842
9416
|
if (devServer.skipReason) {
|
|
9843
|
-
|
|
9417
|
+
log28.warn(this.tag, `Reviewing #${card.short_id} without the visual lens — ${devServer.skipReason}`);
|
|
9844
9418
|
}
|
|
9845
9419
|
await this.client.updateAgentProgress(card.id, {
|
|
9846
9420
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9878,10 +9452,10 @@ class ReviewWorker {
|
|
|
9878
9452
|
pinnedContract = extractPinnedContract(comments, this.identity);
|
|
9879
9453
|
}
|
|
9880
9454
|
} catch (err) {
|
|
9881
|
-
|
|
9455
|
+
log28.warn(this.tag, `pinned-contract fetch failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9882
9456
|
}
|
|
9883
9457
|
if (pinnedContract) {
|
|
9884
|
-
|
|
9458
|
+
log28.info(this.tag, `Grading pinned contract for #${card.short_id} (${pinnedContract.assertions.length} criteria)`);
|
|
9885
9459
|
}
|
|
9886
9460
|
}
|
|
9887
9461
|
const systemPrompt = buildReviewSystemPrompt();
|
|
@@ -9896,7 +9470,7 @@ class ReviewWorker {
|
|
|
9896
9470
|
} else {
|
|
9897
9471
|
const planCriteria2 = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
9898
9472
|
if (planCriteria2.length > 0) {
|
|
9899
|
-
|
|
9473
|
+
log28.info(this.tag, `Grading ${planCriteria2.length} plan criteria for #${card.short_id}`);
|
|
9900
9474
|
}
|
|
9901
9475
|
userPrompt = buildReviewUserPrompt(enriched, this.branchName, cwd, previewUrl, diffSummary, this.config.worktree.baseBranch, undefined, pinnedContract, planCriteria2, devServer.skipReason);
|
|
9902
9476
|
if (resuming && this.resumeMessage) {
|
|
@@ -9913,7 +9487,7 @@ ${userPrompt}`;
|
|
|
9913
9487
|
contextIncluded: { source: "review-knowledge", mode: "review" }
|
|
9914
9488
|
});
|
|
9915
9489
|
} catch (err) {
|
|
9916
|
-
|
|
9490
|
+
log28.warn(this.tag, `prompt_history persistence skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
9917
9491
|
}
|
|
9918
9492
|
await this.client.updateAgentProgress(card.id, {
|
|
9919
9493
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9923,7 +9497,7 @@ ${userPrompt}`;
|
|
|
9923
9497
|
progressPercent: 20
|
|
9924
9498
|
});
|
|
9925
9499
|
this.timeoutTimer = setTimeout(() => {
|
|
9926
|
-
|
|
9500
|
+
log28.warn(this.tag, `Review timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
9927
9501
|
this.timedOut = true;
|
|
9928
9502
|
this.cancel("timeout");
|
|
9929
9503
|
}, this.config.review.maxTimeout);
|
|
@@ -9945,10 +9519,10 @@ ${userPrompt}`;
|
|
|
9945
9519
|
}
|
|
9946
9520
|
this.state = "completing";
|
|
9947
9521
|
await this.recordPhase("completing");
|
|
9948
|
-
|
|
9522
|
+
log28.info(this.tag, `Claude review finished for #${card.short_id}`);
|
|
9949
9523
|
this.killDevServer();
|
|
9950
9524
|
const result = parseReviewOutput(stdout);
|
|
9951
|
-
|
|
9525
|
+
log28.info(this.tag, `Review verdict: ${result.verdict} (${result.findings.length} finding(s))`);
|
|
9952
9526
|
await this.client.updateAgentProgress(card.id, {
|
|
9953
9527
|
agentIdentifier: this.sessionIdentifier,
|
|
9954
9528
|
agentName: `${AGENT_NAME} (Review)`,
|
|
@@ -9956,7 +9530,7 @@ ${userPrompt}`;
|
|
|
9956
9530
|
currentTask: `Processing ${result.verdict} verdict`,
|
|
9957
9531
|
progressPercent: 80
|
|
9958
9532
|
});
|
|
9959
|
-
await runReviewCompletion(this.client, card, result, this.config, cwd, this.branchName, sessionStats, this.lastRunLogPath, this.
|
|
9533
|
+
await runReviewCompletion(this.client, card, result, this.config, cwd, this.branchName, sessionStats, this.lastRunLogPath, this.sessionId, this.stateStore, this.identity.agentId, reviewedFromPrUrl(card.description), devServer.skipReason);
|
|
9960
9534
|
await this.collectReviewGate(card, result);
|
|
9961
9535
|
} catch (err) {
|
|
9962
9536
|
if (err instanceof BudgetPauseError) {
|
|
@@ -9970,7 +9544,7 @@ ${userPrompt}`;
|
|
|
9970
9544
|
this.state = "error";
|
|
9971
9545
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9972
9546
|
this.lastErrorMessage = msg;
|
|
9973
|
-
|
|
9547
|
+
log28.error(this.tag, `Error reviewing #${card.short_id}: ${msg}`);
|
|
9974
9548
|
this.lastRunLogPath = appendRunLog(this.tag, this.runId, card.short_id, `[${new Date().toISOString()}] review failed before completion: ${msg}`) ?? this.lastRunLogPath;
|
|
9975
9549
|
const reviewHandback = await guardedHandback(this.client, card.id, {
|
|
9976
9550
|
agentId: this.identity.agentId,
|
|
@@ -9986,7 +9560,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9986
9560
|
agentSessionId: this.sessionId ?? undefined
|
|
9987
9561
|
});
|
|
9988
9562
|
} catch (cErr) {
|
|
9989
|
-
|
|
9563
|
+
log28.warn(this.tag, `Failed to post the failure reason to #${card.short_id}: ${cErr instanceof Error ? cErr.message : cErr}`);
|
|
9990
9564
|
}
|
|
9991
9565
|
}
|
|
9992
9566
|
try {
|
|
@@ -9998,21 +9572,21 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9998
9572
|
}
|
|
9999
9573
|
});
|
|
10000
9574
|
} catch (tErr) {
|
|
10001
|
-
|
|
9575
|
+
log28.error(this.tag, `endAgentSession unrecoverable on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
10002
9576
|
}
|
|
10003
9577
|
if (reviewHandback.verdict.proceed) {
|
|
10004
9578
|
try {
|
|
10005
9579
|
await moveCardToColumn(this.client, reviewHandback.card ?? card, this.config.review.failColumn);
|
|
10006
|
-
|
|
9580
|
+
log28.info(this.tag, `Moved #${card.short_id} to "${this.config.review.failColumn}" after error`);
|
|
10007
9581
|
} catch {
|
|
10008
|
-
|
|
9582
|
+
log28.warn(this.tag, "Failed to move card to fail column after error");
|
|
10009
9583
|
}
|
|
10010
9584
|
} else if (reviewHandback.verdict.reason === "unreadable" || reviewHandback.verdict.reason === "released" || reviewHandback.verdict.reason === "done") {
|
|
10011
9585
|
try {
|
|
10012
9586
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
10013
|
-
|
|
9587
|
+
log28.warn(this.tag, `#${card.short_id} could not be re-read — labelled "${NEED_REVIEW_LABEL}" so reconcile stops re-enqueueing the review`);
|
|
10014
9588
|
} catch {
|
|
10015
|
-
|
|
9589
|
+
log28.warn(this.tag, `Failed to add "${NEED_REVIEW_LABEL}" label after an unreadable card`);
|
|
10016
9590
|
}
|
|
10017
9591
|
}
|
|
10018
9592
|
if (this.runId) {
|
|
@@ -10056,7 +9630,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10056
9630
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
10057
9631
|
const waitHours = this.config.budget.pause.waitHours;
|
|
10058
9632
|
const until = computeDecisionDeadline(waitHours);
|
|
10059
|
-
|
|
9633
|
+
log28.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
10060
9634
|
try {
|
|
10061
9635
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
10062
9636
|
holderMessage,
|
|
@@ -10068,7 +9642,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10068
9642
|
agentSessionId: this.sessionId ?? undefined
|
|
10069
9643
|
});
|
|
10070
9644
|
} catch (commentErr) {
|
|
10071
|
-
|
|
9645
|
+
log28.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
10072
9646
|
}
|
|
10073
9647
|
if (this.runId) {
|
|
10074
9648
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -10079,7 +9653,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10079
9653
|
awaitingDecisionUntil: until
|
|
10080
9654
|
});
|
|
10081
9655
|
} catch (storeErr) {
|
|
10082
|
-
|
|
9656
|
+
log28.error(this.tag, `#${card.short_id} could not be re-parked after a resume conflict — the daemon has no hold on it: ${storeErr}`);
|
|
10083
9657
|
}
|
|
10084
9658
|
}
|
|
10085
9659
|
}
|
|
@@ -10092,7 +9666,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10092
9666
|
this.progressTracker = null;
|
|
10093
9667
|
const waitHours = this.config.budget.pause.waitHours;
|
|
10094
9668
|
const until = computeDecisionDeadline(waitHours);
|
|
10095
|
-
|
|
9669
|
+
log28.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
10096
9670
|
const body = formatBudgetComment({
|
|
10097
9671
|
trigger,
|
|
10098
9672
|
numTurns: stats?.cost?.numTurns ?? 0,
|
|
@@ -10114,7 +9688,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10114
9688
|
});
|
|
10115
9689
|
commentId = res?.comment?.id ?? null;
|
|
10116
9690
|
} catch (err) {
|
|
10117
|
-
|
|
9691
|
+
log28.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
10118
9692
|
}
|
|
10119
9693
|
try {
|
|
10120
9694
|
await this.client.updateAgentProgress(card.id, {
|
|
@@ -10125,7 +9699,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10125
9699
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
10126
9700
|
});
|
|
10127
9701
|
} catch (err) {
|
|
10128
|
-
|
|
9702
|
+
log28.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
10129
9703
|
}
|
|
10130
9704
|
if (this.runId) {
|
|
10131
9705
|
try {
|
|
@@ -10136,14 +9710,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10136
9710
|
...this.endLedger()
|
|
10137
9711
|
});
|
|
10138
9712
|
} catch (err) {
|
|
10139
|
-
|
|
9713
|
+
log28.error(this.tag, `#${card.short_id} parkRun failed after the blocker comment + blocked session were already written — the daemon has no local record of this park: ${err}`);
|
|
10140
9714
|
}
|
|
10141
9715
|
}
|
|
10142
9716
|
}
|
|
10143
9717
|
async pause() {
|
|
10144
9718
|
if (!this.isActive || !this.process || this.process.killed)
|
|
10145
9719
|
return;
|
|
10146
|
-
|
|
9720
|
+
log28.info(this.tag, `Pausing review on ${this.cardId}`);
|
|
10147
9721
|
signalGroup(this.process, "SIGSTOP");
|
|
10148
9722
|
if (this.timeoutTimer) {
|
|
10149
9723
|
clearTimeout(this.timeoutTimer);
|
|
@@ -10157,17 +9731,17 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10157
9731
|
status: "paused"
|
|
10158
9732
|
});
|
|
10159
9733
|
} catch {
|
|
10160
|
-
|
|
9734
|
+
log28.warn(this.tag, "Failed to update agent session to paused");
|
|
10161
9735
|
}
|
|
10162
9736
|
}
|
|
10163
9737
|
}
|
|
10164
9738
|
async resume() {
|
|
10165
9739
|
if (!this.isActive || !this.process || this.process.killed)
|
|
10166
9740
|
return;
|
|
10167
|
-
|
|
9741
|
+
log28.info(this.tag, `Resuming review on ${this.cardId}`);
|
|
10168
9742
|
signalGroup(this.process, "SIGCONT");
|
|
10169
9743
|
this.timeoutTimer = setTimeout(() => {
|
|
10170
|
-
|
|
9744
|
+
log28.warn(this.tag, `Timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
10171
9745
|
this.timedOut = true;
|
|
10172
9746
|
this.cancel("timeout");
|
|
10173
9747
|
}, this.config.review.maxTimeout);
|
|
@@ -10179,7 +9753,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10179
9753
|
status: "working"
|
|
10180
9754
|
});
|
|
10181
9755
|
} catch {
|
|
10182
|
-
|
|
9756
|
+
log28.warn(this.tag, "Failed to update agent session to working");
|
|
10183
9757
|
}
|
|
10184
9758
|
}
|
|
10185
9759
|
}
|
|
@@ -10188,7 +9762,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10188
9762
|
return;
|
|
10189
9763
|
this.aborted = true;
|
|
10190
9764
|
this.state = "cancelling";
|
|
10191
|
-
|
|
9765
|
+
log28.info(this.tag, `Cancelling review on ${this.cardId}`);
|
|
10192
9766
|
const snapshotStats = this.lastSessionStats ?? this.progressTracker?.stats;
|
|
10193
9767
|
if (this.progressTracker) {
|
|
10194
9768
|
this.progressTracker?.stop();
|
|
@@ -10241,11 +9815,11 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10241
9815
|
"--",
|
|
10242
9816
|
prompt
|
|
10243
9817
|
];
|
|
10244
|
-
|
|
9818
|
+
log28.info(this.tag, `Spawning review: claude ${args.slice(0, 5).join(" ")} ...`);
|
|
10245
9819
|
const runLog = openRunLog(this.tag, this.runId, shortId);
|
|
10246
9820
|
this.lastRunLogPath = runLog?.path ?? null;
|
|
10247
9821
|
if (runLog) {
|
|
10248
|
-
|
|
9822
|
+
log28.info(this.tag, `Run log: ${runLog.path}`);
|
|
10249
9823
|
runLog.stream.write(`# run=${this.runId} card=#${shortId} pipeline=review started=${new Date().toISOString()}
|
|
10250
9824
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
10251
9825
|
|
|
@@ -10264,7 +9838,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10264
9838
|
this.captureCliSessionId(parser.sessionId);
|
|
10265
9839
|
});
|
|
10266
9840
|
parser.on("parse_error", (msg) => {
|
|
10267
|
-
|
|
9841
|
+
log28.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
10268
9842
|
runLog?.stream.write(`
|
|
10269
9843
|
[parse_error] ${msg}
|
|
10270
9844
|
`);
|
|
@@ -10343,16 +9917,16 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10343
9917
|
const evidence = await collectGateEvidence(registry, context);
|
|
10344
9918
|
const evaluation = gateEvaluate(resolved.gate, evidence);
|
|
10345
9919
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, toStageGateEvidenceInsert(context, evidence));
|
|
10346
|
-
|
|
9920
|
+
log28.info(this.tag, `Recorded review_passed gate evidence for #${card.short_id} stage "${resolved.stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
10347
9921
|
} catch (err) {
|
|
10348
|
-
|
|
9922
|
+
log28.warn(this.tag, `review gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
10349
9923
|
}
|
|
10350
9924
|
}
|
|
10351
9925
|
async startDevServer(card, cwd, port) {
|
|
10352
9926
|
const declined = devServerSkipReason(cwd, this.config.commands);
|
|
10353
9927
|
if (declined)
|
|
10354
9928
|
return { previewUrl: null, skipReason: declined };
|
|
10355
|
-
|
|
9929
|
+
log28.info(this.tag, `Starting dev server on port ${port}...`);
|
|
10356
9930
|
const launch = devServerLaunch({
|
|
10357
9931
|
worktreePath: cwd,
|
|
10358
9932
|
port,
|
|
@@ -10393,14 +9967,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10393
9967
|
skipReason: launch.origin ? `${launch.origin}, and ${failure}` : failure
|
|
10394
9968
|
};
|
|
10395
9969
|
}
|
|
10396
|
-
|
|
9970
|
+
log28.info(this.tag, `Dev server ready on port ${port}`);
|
|
10397
9971
|
return { previewUrl: `http://localhost:${port}`, skipReason: null };
|
|
10398
9972
|
}
|
|
10399
9973
|
killDevServer() {
|
|
10400
9974
|
if (this.devServerProcess && !this.devServerProcess.killed) {
|
|
10401
9975
|
signalGroup(this.devServerProcess, "SIGTERM");
|
|
10402
9976
|
this.devServerProcess = null;
|
|
10403
|
-
|
|
9977
|
+
log28.debug(this.tag, "Killed dev server group");
|
|
10404
9978
|
}
|
|
10405
9979
|
if (this.devServerContainer) {
|
|
10406
9980
|
const name = this.devServerContainer;
|
|
@@ -10423,7 +9997,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10423
9997
|
try {
|
|
10424
9998
|
cleanupWorktree4(this.worktreePath);
|
|
10425
9999
|
} catch {
|
|
10426
|
-
|
|
10000
|
+
log28.warn(this.tag, "Failed to cleanup review worktree");
|
|
10427
10001
|
}
|
|
10428
10002
|
}
|
|
10429
10003
|
this.process = null;
|
|
@@ -10435,7 +10009,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10435
10009
|
this.lastSessionStats = null;
|
|
10436
10010
|
}
|
|
10437
10011
|
}
|
|
10438
|
-
var
|
|
10012
|
+
var TAG25 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
10439
10013
|
var init_review_worker = __esm(() => {
|
|
10440
10014
|
init_dist();
|
|
10441
10015
|
init_board_helpers();
|
|
@@ -10459,7 +10033,7 @@ var init_review_worker = __esm(() => {
|
|
|
10459
10033
|
|
|
10460
10034
|
// src/sleep-guard.ts
|
|
10461
10035
|
import { spawn } from "node:child_process";
|
|
10462
|
-
import { log as
|
|
10036
|
+
import { log as log29 } from "@gethmy/harness";
|
|
10463
10037
|
|
|
10464
10038
|
class SleepGuard {
|
|
10465
10039
|
platform;
|
|
@@ -10487,7 +10061,7 @@ class SleepGuard {
|
|
|
10487
10061
|
if (!this.child.killed)
|
|
10488
10062
|
this.child.kill("SIGTERM");
|
|
10489
10063
|
this.child = null;
|
|
10490
|
-
|
|
10064
|
+
log29.info(TAG26, "sleep assertion released");
|
|
10491
10065
|
}
|
|
10492
10066
|
}
|
|
10493
10067
|
start() {
|
|
@@ -10502,7 +10076,7 @@ class SleepGuard {
|
|
|
10502
10076
|
spawned = true;
|
|
10503
10077
|
});
|
|
10504
10078
|
child.on("error", (err) => {
|
|
10505
|
-
|
|
10079
|
+
log29.warn(TAG26, `caffeinate unavailable: ${err.message}`);
|
|
10506
10080
|
if (this.child === child)
|
|
10507
10081
|
this.child = null;
|
|
10508
10082
|
});
|
|
@@ -10515,23 +10089,23 @@ class SleepGuard {
|
|
|
10515
10089
|
});
|
|
10516
10090
|
child.unref();
|
|
10517
10091
|
this.child = child;
|
|
10518
|
-
|
|
10092
|
+
log29.info(TAG26, "sleep assertion acquired (caffeinate -i)");
|
|
10519
10093
|
} catch (err) {
|
|
10520
|
-
|
|
10094
|
+
log29.warn(TAG26, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
10521
10095
|
}
|
|
10522
10096
|
}
|
|
10523
10097
|
}
|
|
10524
|
-
var
|
|
10098
|
+
var TAG26 = "sleep-guard";
|
|
10525
10099
|
var init_sleep_guard = () => {};
|
|
10526
10100
|
|
|
10527
10101
|
// src/unblock.ts
|
|
10528
|
-
import { log as
|
|
10102
|
+
import { log as log30 } from "@gethmy/harness";
|
|
10529
10103
|
async function fetchBlocksLinks(client, cardId) {
|
|
10530
10104
|
try {
|
|
10531
10105
|
const { links } = await client.getCardLinks(cardId);
|
|
10532
10106
|
return links.filter((l) => l.link_type === "blocks");
|
|
10533
10107
|
} catch (err) {
|
|
10534
|
-
|
|
10108
|
+
log30.warn(TAG27, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10535
10109
|
return null;
|
|
10536
10110
|
}
|
|
10537
10111
|
}
|
|
@@ -10565,31 +10139,31 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
10565
10139
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
10566
10140
|
if (successors.length === 0)
|
|
10567
10141
|
return;
|
|
10568
|
-
|
|
10142
|
+
log30.info(TAG27, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
10569
10143
|
for (const link of successors) {
|
|
10570
10144
|
const successorId = link.target_card.id;
|
|
10571
10145
|
try {
|
|
10572
10146
|
const { card } = await deps.client.getCard(successorId);
|
|
10573
10147
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
10574
|
-
|
|
10148
|
+
log30.info(TAG27, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
10575
10149
|
await deps.client.updateCard(successorId, {
|
|
10576
10150
|
assignedAgentId: deps.agentId
|
|
10577
10151
|
});
|
|
10578
10152
|
} else {
|
|
10579
|
-
|
|
10153
|
+
log30.debug(TAG27, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
10580
10154
|
continue;
|
|
10581
10155
|
}
|
|
10582
10156
|
await deps.enqueue(successorId);
|
|
10583
10157
|
} catch (err) {
|
|
10584
|
-
|
|
10158
|
+
log30.warn(TAG27, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
10585
10159
|
}
|
|
10586
10160
|
}
|
|
10587
10161
|
}
|
|
10588
|
-
var
|
|
10162
|
+
var TAG27 = "unblock";
|
|
10589
10163
|
var init_unblock = () => {};
|
|
10590
10164
|
|
|
10591
10165
|
// src/cli-agent-runner.ts
|
|
10592
|
-
import { log as
|
|
10166
|
+
import { log as log31 } from "@gethmy/harness";
|
|
10593
10167
|
function truncateOutput(value) {
|
|
10594
10168
|
return value === undefined ? undefined : value.slice(0, MAX_OUTPUT_CHARS);
|
|
10595
10169
|
}
|
|
@@ -10754,7 +10328,7 @@ class CliAgentRunner {
|
|
|
10754
10328
|
return;
|
|
10755
10329
|
const draft = sanitizeRunEventDraft(body);
|
|
10756
10330
|
if (runEventPayloadExceedsLimit(draft.payload)) {
|
|
10757
|
-
|
|
10331
|
+
log31.warn(TAG28, `Dropping ${body.kind} event: payload exceeds ${MAX_RUN_EVENT_PAYLOAD_BYTES} bytes, or cannot be serialized`);
|
|
10758
10332
|
return;
|
|
10759
10333
|
}
|
|
10760
10334
|
this.buffer.push({ ...draft, createdAt: new Date().toISOString() });
|
|
@@ -10786,15 +10360,15 @@ class CliAgentRunner {
|
|
|
10786
10360
|
this.requeue(batch);
|
|
10787
10361
|
if (batch.length > 1) {
|
|
10788
10362
|
this.nextBatchSize = Math.ceil(batch.length / 2);
|
|
10789
|
-
|
|
10363
|
+
log31.warn(TAG28, `Flush of ${batch.length} run event(s) failed ${MAX_BATCH_ATTEMPTS}×; bisecting to ${this.nextBatchSize}: ${err}`);
|
|
10790
10364
|
return;
|
|
10791
10365
|
}
|
|
10792
10366
|
const dropped = this.buffer.shift();
|
|
10793
10367
|
this.nextBatchSize = MAX_BUFFER;
|
|
10794
|
-
|
|
10368
|
+
log31.warn(TAG28, `Dropping one ${dropped?.kind} event the API refused ${MAX_BATCH_ATTEMPTS}×: ${err}`);
|
|
10795
10369
|
return;
|
|
10796
10370
|
}
|
|
10797
|
-
|
|
10371
|
+
log31.warn(TAG28, `Failed to flush run events (attempt ${this.batchAttempts}/${MAX_BATCH_ATTEMPTS}): ${err}`);
|
|
10798
10372
|
this.requeue(batch);
|
|
10799
10373
|
} finally {
|
|
10800
10374
|
this.flushing = false;
|
|
@@ -10837,26 +10411,26 @@ function mapCost(cost) {
|
|
|
10837
10411
|
durationMs: cost.durationMs
|
|
10838
10412
|
};
|
|
10839
10413
|
}
|
|
10840
|
-
var
|
|
10414
|
+
var TAG28 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN = 8000, MAX_BATCH_ATTEMPTS = 3, FINAL_FLUSH_ATTEMPTS = 40;
|
|
10841
10415
|
var init_cli_agent_runner = __esm(() => {
|
|
10842
10416
|
init_dist();
|
|
10843
10417
|
});
|
|
10844
10418
|
|
|
10845
10419
|
// src/fanout.ts
|
|
10846
|
-
import { log as
|
|
10420
|
+
import { log as log32 } from "@gethmy/harness";
|
|
10847
10421
|
async function fetchLinks(client, cardId) {
|
|
10848
10422
|
try {
|
|
10849
10423
|
const { links } = await client.getCardLinks(cardId);
|
|
10850
10424
|
return links;
|
|
10851
10425
|
} catch (err) {
|
|
10852
|
-
|
|
10426
|
+
log32.warn(TAG29, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10853
10427
|
return null;
|
|
10854
10428
|
}
|
|
10855
10429
|
}
|
|
10856
10430
|
async function isFanoutChildOf(card, stage, client) {
|
|
10857
10431
|
const links = await fetchLinks(client, card.id);
|
|
10858
10432
|
if (links === null) {
|
|
10859
|
-
|
|
10433
|
+
log32.warn(TAG29, `#${card.short_id}: link read failed — treating as a fan-out child (fail closed, no recursive dispatch)`);
|
|
10860
10434
|
return true;
|
|
10861
10435
|
}
|
|
10862
10436
|
const parents = links.filter((l) => l.direction === "outgoing" && l.link_type === "is_part_of");
|
|
@@ -10943,7 +10517,7 @@ async function readStageHandoff(card, fromStage, deps) {
|
|
|
10943
10517
|
}
|
|
10944
10518
|
return null;
|
|
10945
10519
|
} catch (err) {
|
|
10946
|
-
|
|
10520
|
+
log32.warn(TAG29, `handoff read failed for #${card.short_id} stage "${fromStage}": ${err instanceof Error ? err.message : err}`);
|
|
10947
10521
|
return null;
|
|
10948
10522
|
}
|
|
10949
10523
|
}
|
|
@@ -11044,7 +10618,7 @@ async function dispatchWave(parent, stage, plan, items, deps) {
|
|
|
11044
10618
|
});
|
|
11045
10619
|
spawned = res.children ?? [];
|
|
11046
10620
|
} catch (err) {
|
|
11047
|
-
|
|
10621
|
+
log32.warn(TAG29, `child spawn failed for #${parent.short_id} stage "${stage.id}": ${err instanceof Error ? err.message : err}`);
|
|
11048
10622
|
return 0;
|
|
11049
10623
|
}
|
|
11050
10624
|
let created = 0;
|
|
@@ -11099,10 +10673,10 @@ async function seedChildHandoff(parent, stage, item, total, childId, deps) {
|
|
|
11099
10673
|
try {
|
|
11100
10674
|
await deps.client.addComment(childId, body, { commentType: "decision" });
|
|
11101
10675
|
} catch (err) {
|
|
11102
|
-
|
|
10676
|
+
log32.warn(TAG29, `seed handoff failed for child ${childId}: ${err instanceof Error ? err.message : err}`);
|
|
11103
10677
|
}
|
|
11104
10678
|
}
|
|
11105
|
-
var
|
|
10679
|
+
var TAG29 = "fanout";
|
|
11106
10680
|
var init_fanout = __esm(() => {
|
|
11107
10681
|
init_dist();
|
|
11108
10682
|
});
|
|
@@ -11337,7 +10911,7 @@ function runSizedPayload(input) {
|
|
|
11337
10911
|
}
|
|
11338
10912
|
|
|
11339
10913
|
// src/stage-advance.ts
|
|
11340
|
-
import { gateConfigErrorReason, log as
|
|
10914
|
+
import { gateConfigErrorReason, log as log33 } from "@gethmy/harness";
|
|
11341
10915
|
function endDispositionFor(outcome) {
|
|
11342
10916
|
return "endDisposition" in outcome && outcome.endDisposition ? outcome.endDisposition : { status: "completed" };
|
|
11343
10917
|
}
|
|
@@ -11368,7 +10942,7 @@ async function resolveStageColumnName(client, card, stage) {
|
|
|
11368
10942
|
const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
|
|
11369
10943
|
return match ? match.name : null;
|
|
11370
10944
|
} catch (err) {
|
|
11371
|
-
|
|
10945
|
+
log33.warn(TAG30, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11372
10946
|
return null;
|
|
11373
10947
|
}
|
|
11374
10948
|
}
|
|
@@ -11407,7 +10981,7 @@ async function holdGateMisconfigured(card, stage, detail, deps) {
|
|
|
11407
10981
|
});
|
|
11408
10982
|
} catch {}
|
|
11409
10983
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, { agentSessionId: deps.agentSessionId });
|
|
11410
|
-
|
|
10984
|
+
log33.info(TAG30, `#${card.short_id} GateMisconfigured: ${reason}`);
|
|
11411
10985
|
return { kind: "held_misconfigured", reason, endDisposition };
|
|
11412
10986
|
}
|
|
11413
10987
|
function firstErrorMessage(evaluation) {
|
|
@@ -11438,7 +11012,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11438
11012
|
evidence,
|
|
11439
11013
|
summary
|
|
11440
11014
|
});
|
|
11441
|
-
|
|
11015
|
+
log33.info(TAG30, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
|
|
11442
11016
|
if (decision === "exit") {
|
|
11443
11017
|
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
11444
11018
|
deps.sink?.recordLoopCompleted?.({
|
|
@@ -11483,7 +11057,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11483
11057
|
blockers: [reason],
|
|
11484
11058
|
agentSessionId: deps.agentSessionId
|
|
11485
11059
|
});
|
|
11486
|
-
|
|
11060
|
+
log33.info(TAG30, `#${card.short_id} LoopExhausted: ${reason}`);
|
|
11487
11061
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11488
11062
|
}
|
|
11489
11063
|
const guard = await guardStageReclaim(card, `converge-loop iteration of "${stage.name}"`, deps);
|
|
@@ -11508,7 +11082,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11508
11082
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11509
11083
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11510
11084
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11511
|
-
|
|
11085
|
+
log33.info(TAG30, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
|
|
11512
11086
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11513
11087
|
}
|
|
11514
11088
|
async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
|
|
@@ -11530,7 +11104,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
|
|
|
11530
11104
|
agentSessionId: deps.agentSessionId ?? undefined
|
|
11531
11105
|
});
|
|
11532
11106
|
} catch (err) {
|
|
11533
|
-
|
|
11107
|
+
log33.warn(TAG30, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11534
11108
|
}
|
|
11535
11109
|
}
|
|
11536
11110
|
async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
|
|
@@ -11558,7 +11132,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11558
11132
|
reason: "Playbook complete — final stage gate passed."
|
|
11559
11133
|
});
|
|
11560
11134
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11561
|
-
|
|
11135
|
+
log33.info(TAG30, `#${card.short_id} terminal stage "${stage.name}" passed — playbook complete (done left to the column)`);
|
|
11562
11136
|
return { kind: "completed_terminal" };
|
|
11563
11137
|
}
|
|
11564
11138
|
if (next.kind === "out_of_range") {
|
|
@@ -11590,7 +11164,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11590
11164
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11591
11165
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11592
11166
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11593
|
-
|
|
11167
|
+
log33.info(TAG30, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
|
|
11594
11168
|
if (next.stage.owner === "human") {
|
|
11595
11169
|
const reason = `Stage "${next.stage.name}" is yours: ${handoffText(next.stage)}`;
|
|
11596
11170
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
|
|
@@ -11627,7 +11201,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11627
11201
|
blockers: [reason],
|
|
11628
11202
|
agentSessionId: deps.agentSessionId
|
|
11629
11203
|
});
|
|
11630
|
-
|
|
11204
|
+
log33.info(TAG30, `#${card.short_id} GateUnmetExhausted: ${reason}`);
|
|
11631
11205
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11632
11206
|
}
|
|
11633
11207
|
const guard = await guardStageReclaim(card, `gate-unmet re-run of "${stage.name}"`, deps);
|
|
@@ -11650,7 +11224,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11650
11224
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11651
11225
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11652
11226
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11653
|
-
|
|
11227
|
+
log33.info(TAG30, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
|
|
11654
11228
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11655
11229
|
}
|
|
11656
11230
|
async function guardStageReclaim(card, what, deps) {
|
|
@@ -11676,10 +11250,10 @@ async function guardStageReclaim(card, what, deps) {
|
|
|
11676
11250
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11677
11251
|
released = stillOurs;
|
|
11678
11252
|
} catch (err) {
|
|
11679
|
-
|
|
11253
|
+
log33.warn(TAG30, `#${card.short_id} could not hand the card over after refusing the ${what}: ${err instanceof Error ? err.message : err}${stillOurs ? " — the claim is still ours, so the next reconcile tick may re-pick the card" : ""}`);
|
|
11680
11254
|
}
|
|
11681
11255
|
}
|
|
11682
|
-
|
|
11256
|
+
log33.info(TAG30, `#${card.short_id} ${what} refused — ${detail} (${reason})${released ? "; released the daemon's claim so the stage router stops here" : ""}`);
|
|
11683
11257
|
try {
|
|
11684
11258
|
await deps.client.addComment(card.id, `Stopped the ${what} — ${detail}. The card is not the daemon's to reclaim, so this stage will not re-run until it is assigned back.`, {
|
|
11685
11259
|
commentType: "decision",
|
|
@@ -11701,7 +11275,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11701
11275
|
try {
|
|
11702
11276
|
await runTransition(client, card, { removeLabels: [AGENT_LABEL] }, { store: stateStore, runId });
|
|
11703
11277
|
} catch (err) {
|
|
11704
|
-
|
|
11278
|
+
log33.warn(TAG30, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11705
11279
|
}
|
|
11706
11280
|
return {
|
|
11707
11281
|
status: opts.endStatus ?? "paused",
|
|
@@ -11710,7 +11284,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11710
11284
|
failureSummary: reason.slice(0, 300)
|
|
11711
11285
|
};
|
|
11712
11286
|
}
|
|
11713
|
-
var
|
|
11287
|
+
var TAG30 = "stage-advance", AGENT_LABEL = "agent";
|
|
11714
11288
|
var init_stage_advance = __esm(() => {
|
|
11715
11289
|
init_dist();
|
|
11716
11290
|
init_handback();
|
|
@@ -11786,7 +11360,7 @@ import {
|
|
|
11786
11360
|
GIT_NO_HOOKS as GIT_NO_HOOKS8,
|
|
11787
11361
|
implementRunContainment,
|
|
11788
11362
|
implementRunContainmentCliArgs as implementRunContainmentCliArgs2,
|
|
11789
|
-
log as
|
|
11363
|
+
log as log34,
|
|
11790
11364
|
makeBranchName,
|
|
11791
11365
|
normalizeGateSpec,
|
|
11792
11366
|
pushBranch as pushBranch3,
|
|
@@ -11992,11 +11566,11 @@ class Worker {
|
|
|
11992
11566
|
sessionId: this.sessionId
|
|
11993
11567
|
});
|
|
11994
11568
|
} catch (err) {
|
|
11995
|
-
|
|
11569
|
+
log34.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
11996
11570
|
}
|
|
11997
11571
|
}
|
|
11998
11572
|
get tag() {
|
|
11999
|
-
return `${
|
|
11573
|
+
return `${TAG31}:${this.id}`;
|
|
12000
11574
|
}
|
|
12001
11575
|
get isIdle() {
|
|
12002
11576
|
return this.state === "idle";
|
|
@@ -12058,12 +11632,12 @@ class Worker {
|
|
|
12058
11632
|
}
|
|
12059
11633
|
if (!crossedTurnCap(before, after, cap))
|
|
12060
11634
|
return;
|
|
12061
|
-
|
|
11635
|
+
log34.warn(TAG31, `#${shortId ?? cardId} reached its per-card turn cap — ${after} of ${cap} turns. It will not be picked up again until it is reassigned or the cap is raised.`);
|
|
12062
11636
|
try {
|
|
12063
11637
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
12064
11638
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), { commentType: "blocker", agentSessionId: this.sessionId ?? undefined });
|
|
12065
11639
|
} catch (err) {
|
|
12066
|
-
|
|
11640
|
+
log34.warn(TAG31, `Could not post the turn-cap notice for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
12067
11641
|
}
|
|
12068
11642
|
}
|
|
12069
11643
|
async run(card, column, labels, subtasks) {
|
|
@@ -12118,7 +11692,7 @@ class Worker {
|
|
|
12118
11692
|
resumeReason: null
|
|
12119
11693
|
});
|
|
12120
11694
|
} catch (err) {
|
|
12121
|
-
|
|
11695
|
+
log34.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
12122
11696
|
}
|
|
12123
11697
|
}
|
|
12124
11698
|
try {
|
|
@@ -12126,20 +11700,20 @@ class Worker {
|
|
|
12126
11700
|
if (!resuming) {
|
|
12127
11701
|
this.branchName = makeBranchName(card.short_id, card.title, this.config.worktree.failedBranchPrefix);
|
|
12128
11702
|
}
|
|
12129
|
-
|
|
11703
|
+
log34.info(this.tag, resuming ? `Resuming #${card.short_id} "${card.title}" with ${this.grantedTurns ?? "the default"} more turns` : `Preparing #${card.short_id} "${card.title}"`);
|
|
12130
11704
|
const attemptCount = await this.stateStore.incrementAttempt(card.id);
|
|
12131
11705
|
const isRework = attemptCount > 1;
|
|
12132
11706
|
const runStarts = this.stateStore.getCardRunStarts(card.id);
|
|
12133
11707
|
const runCap = this.config.budget.maxRunsPerCard;
|
|
12134
11708
|
if (crossedTurnCap(runStarts - 1, runStarts, runCap)) {
|
|
12135
|
-
|
|
11709
|
+
log34.warn(this.tag, `#${card.short_id} reached its per-card run backstop — ${runStarts} of ${runCap} run starts. It will not be picked up again until it is reassigned or the cap is raised.`);
|
|
12136
11710
|
try {
|
|
12137
11711
|
await this.client.addComment(card.id, buildRunCapComment(runStarts, runCap), {
|
|
12138
11712
|
commentType: "blocker",
|
|
12139
11713
|
agentSessionId: this.sessionId ?? undefined
|
|
12140
11714
|
});
|
|
12141
11715
|
} catch (err) {
|
|
12142
|
-
|
|
11716
|
+
log34.warn(this.tag, `Could not post the run-backstop notice for ${card.id}: ${err instanceof Error ? err.message : err}`);
|
|
12143
11717
|
}
|
|
12144
11718
|
}
|
|
12145
11719
|
const recordedBranch = recordedBranchForCard(card.description, card.short_id, [
|
|
@@ -12149,10 +11723,10 @@ class Worker {
|
|
|
12149
11723
|
const continuesPushedWork = isRework || recordedBranch !== null;
|
|
12150
11724
|
if (!resuming && recordedBranch) {
|
|
12151
11725
|
if (recordedBranch !== this.branchName) {
|
|
12152
|
-
|
|
11726
|
+
log34.info(this.tag, `Card records pushed work on ${recordedBranch} — continuing that branch instead of ${this.branchName}`);
|
|
12153
11727
|
this.branchName = recordedBranch;
|
|
12154
11728
|
} else if (!isRework) {
|
|
12155
|
-
|
|
11729
|
+
log34.info(this.tag, `Card records pushed work on ${this.branchName} — continuing that branch instead of rebuilding from ${this.config.worktree.baseBranch}`);
|
|
12156
11730
|
}
|
|
12157
11731
|
}
|
|
12158
11732
|
this.startHeartbeat();
|
|
@@ -12194,14 +11768,14 @@ class Worker {
|
|
|
12194
11768
|
awaitingDecisionUntil: null
|
|
12195
11769
|
});
|
|
12196
11770
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
12197
|
-
info: (message) =>
|
|
12198
|
-
warn: (message) =>
|
|
11771
|
+
info: (message) => log34.info(this.tag, message),
|
|
11772
|
+
warn: (message) => log34.warn(this.tag, message)
|
|
12199
11773
|
});
|
|
12200
11774
|
session = started.session;
|
|
12201
11775
|
} catch (err) {
|
|
12202
11776
|
if (isSessionConflict(err)) {
|
|
12203
11777
|
this.sessionConflict = true;
|
|
12204
|
-
|
|
11778
|
+
log34.info(this.tag, `Skipping #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12205
11779
|
await this.stateStore.decrementAttempt(card.id);
|
|
12206
11780
|
return;
|
|
12207
11781
|
}
|
|
@@ -12209,7 +11783,7 @@ class Worker {
|
|
|
12209
11783
|
}
|
|
12210
11784
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
12211
11785
|
if (!sid) {
|
|
12212
|
-
|
|
11786
|
+
log34.warn(TAG31, "startAgentSession returned no session id");
|
|
12213
11787
|
}
|
|
12214
11788
|
this.sessionId = sid;
|
|
12215
11789
|
}
|
|
@@ -12227,7 +11801,7 @@ class Worker {
|
|
|
12227
11801
|
if (!resuming) {
|
|
12228
11802
|
const moved = await moveCardAndAddLabel(this.client, card, IN_PROGRESS_COLUMN, "agent");
|
|
12229
11803
|
if (!moved) {
|
|
12230
|
-
|
|
11804
|
+
log34.warn(this.tag, `Card #${card.short_id} was NOT moved to "In Progress" — check API logs`);
|
|
12231
11805
|
}
|
|
12232
11806
|
}
|
|
12233
11807
|
if (this.aborted)
|
|
@@ -12254,10 +11828,10 @@ class Worker {
|
|
|
12254
11828
|
}).trim();
|
|
12255
11829
|
const target = resolveContinuationTarget(this.branchName, continueRequested, this.config.worktree.failedBranchPrefix, this.config.worktree.approvedBranchPrefix, (ref) => fetchExistingBranch(repoRoot, ref));
|
|
12256
11830
|
if (target.branchName !== this.branchName) {
|
|
12257
|
-
|
|
11831
|
+
log34.info(this.tag, `Branch ${this.branchName} is gone from origin but its approved rename ${target.branchName} exists — continuing that branch`);
|
|
12258
11832
|
this.branchName = target.branchName;
|
|
12259
11833
|
} else if (target.reason === "exists_on_origin") {
|
|
12260
|
-
|
|
11834
|
+
log34.info(this.tag, `Branch ${this.branchName} exists on origin with no card record — continuing it rather than resetting it`);
|
|
12261
11835
|
}
|
|
12262
11836
|
this.worktreePath = createWorktree(this.config.worktree.basePath, this.config.worktree.baseBranch, this.branchName, {
|
|
12263
11837
|
continueExisting: target.continueExisting,
|
|
@@ -12291,7 +11865,7 @@ class Worker {
|
|
|
12291
11865
|
if (this.aborted)
|
|
12292
11866
|
return;
|
|
12293
11867
|
if (parked) {
|
|
12294
|
-
|
|
11868
|
+
log34.info(this.tag, `#${card.short_id} parked for plan approval — ending run`);
|
|
12295
11869
|
return;
|
|
12296
11870
|
}
|
|
12297
11871
|
}
|
|
@@ -12312,7 +11886,7 @@ class Worker {
|
|
|
12312
11886
|
progressPercent: 10
|
|
12313
11887
|
});
|
|
12314
11888
|
this.timeoutTimer = setTimeout(() => {
|
|
12315
|
-
|
|
11889
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
12316
11890
|
this.timedOut = true;
|
|
12317
11891
|
this.cancel("timeout");
|
|
12318
11892
|
}, this.config.maxTimeout);
|
|
@@ -12339,7 +11913,7 @@ class Worker {
|
|
|
12339
11913
|
}
|
|
12340
11914
|
this.state = "verifying";
|
|
12341
11915
|
await this.recordPhase("verifying");
|
|
12342
|
-
|
|
11916
|
+
log34.info(this.tag, `Claude finished for #${card.short_id}, running verification & completion`);
|
|
12343
11917
|
await this.client.updateAgentProgress(card.id, {
|
|
12344
11918
|
agentIdentifier: this.sessionIdentifier,
|
|
12345
11919
|
agentName: AGENT_NAME,
|
|
@@ -12355,7 +11929,7 @@ class Worker {
|
|
|
12355
11929
|
const onBeforeWorktreeCleanup = stageRun ? async (worktreePath) => {
|
|
12356
11930
|
stageGateEvaluation = await this.collectStageGateEvidence(card, stageRun.stage, worktreePath, subtasks);
|
|
12357
11931
|
} : undefined;
|
|
12358
|
-
const completed = await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.sessionIdentifier, this.identity.agentId, this.lastSessionStats, this.
|
|
11932
|
+
const completed = await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.sessionIdentifier, this.identity.agentId, this.lastSessionStats, this.sessionId, this.stateStore, this.onCardCompleted, onBeforeWorktreeCleanup, this.runBaselineSha, this.effectiveMaxTurns, this.runExpectsCommits);
|
|
12359
11933
|
if (completed === "park") {
|
|
12360
11934
|
await this.parkForDecision(card, "max_turns");
|
|
12361
11935
|
return;
|
|
@@ -12374,7 +11948,7 @@ class Worker {
|
|
|
12374
11948
|
break;
|
|
12375
11949
|
case "reclaim_refused":
|
|
12376
11950
|
this.held = true;
|
|
12377
|
-
|
|
11951
|
+
log34.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
12378
11952
|
break;
|
|
12379
11953
|
case "advanced":
|
|
12380
11954
|
case "completed_terminal":
|
|
@@ -12412,7 +11986,7 @@ class Worker {
|
|
|
12412
11986
|
return;
|
|
12413
11987
|
}
|
|
12414
11988
|
this.state = "error";
|
|
12415
|
-
|
|
11989
|
+
log34.error(this.tag, `Error on #${card.short_id}: ${msg}`);
|
|
12416
11990
|
const apiError = errClass.kind !== null;
|
|
12417
11991
|
const baseError = err instanceof WorktreeBaseError;
|
|
12418
11992
|
const noBudgetBurn = apiError || baseError;
|
|
@@ -12430,7 +12004,7 @@ class Worker {
|
|
|
12430
12004
|
try {
|
|
12431
12005
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12432
12006
|
} catch {
|
|
12433
|
-
|
|
12007
|
+
log34.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12434
12008
|
}
|
|
12435
12009
|
this.worktreePath = null;
|
|
12436
12010
|
}
|
|
@@ -12455,7 +12029,7 @@ class Worker {
|
|
|
12455
12029
|
}
|
|
12456
12030
|
});
|
|
12457
12031
|
} catch (tErr) {
|
|
12458
|
-
|
|
12032
|
+
log34.error(this.tag, `error transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12459
12033
|
}
|
|
12460
12034
|
if (this.runId) {
|
|
12461
12035
|
try {
|
|
@@ -12491,7 +12065,7 @@ class Worker {
|
|
|
12491
12065
|
try {
|
|
12492
12066
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12493
12067
|
} catch {
|
|
12494
|
-
|
|
12068
|
+
log34.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12495
12069
|
}
|
|
12496
12070
|
this.worktreePath = null;
|
|
12497
12071
|
}
|
|
@@ -12514,7 +12088,7 @@ class Worker {
|
|
|
12514
12088
|
}
|
|
12515
12089
|
});
|
|
12516
12090
|
} catch (tErr) {
|
|
12517
|
-
|
|
12091
|
+
log34.error(this.tag, `timeout transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12518
12092
|
}
|
|
12519
12093
|
try {
|
|
12520
12094
|
await this.stateStore.endRun(this.runId, "failed", {
|
|
@@ -12528,15 +12102,15 @@ class Worker {
|
|
|
12528
12102
|
try {
|
|
12529
12103
|
await this.client.updateCard(card.id, { assignedAgentId: null });
|
|
12530
12104
|
} catch (err) {
|
|
12531
|
-
|
|
12105
|
+
log34.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
|
|
12532
12106
|
}
|
|
12533
12107
|
try {
|
|
12534
12108
|
await runTransition(this.client, card, { removeLabels: ["agent"] });
|
|
12535
12109
|
} catch (tErr) {
|
|
12536
|
-
|
|
12110
|
+
log34.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12537
12111
|
}
|
|
12538
12112
|
} else {
|
|
12539
|
-
|
|
12113
|
+
log34.info(this.tag, `cancel arrived after completion on #${card.short_id} — keeping assignment so review picks it up (#585)`);
|
|
12540
12114
|
}
|
|
12541
12115
|
try {
|
|
12542
12116
|
await this.stateStore.endRun(this.runId, "paused", {
|
|
@@ -12600,28 +12174,28 @@ class Worker {
|
|
|
12600
12174
|
};
|
|
12601
12175
|
const { pick, reason } = selectAutoPlaybook(subject, playbooks ?? []);
|
|
12602
12176
|
if (!pick) {
|
|
12603
|
-
|
|
12177
|
+
log34.info(this.tag, `No playbook auto-bound to #${card.short_id}: ${reason}`);
|
|
12604
12178
|
return card;
|
|
12605
12179
|
}
|
|
12606
12180
|
const undeclared = undeclaredGateMetricsIn(pick, declaredMetricSet(this.config));
|
|
12607
12181
|
if (undeclared.length > 0) {
|
|
12608
|
-
|
|
12182
|
+
log34.warn(this.tag, `Not binding "${pick.name}" to #${card.short_id}: stage ` + undeclared.map((u) => `"${u.stageName}" gates on metric "${u.metric}" (declare \`agent.playbooks.metrics.${u.metric}\`)`).join(", ") + " — this daemon cannot measure it, so the card runs the generic path instead.");
|
|
12609
12183
|
return card;
|
|
12610
12184
|
}
|
|
12611
12185
|
const applyResult = await this.client.request("POST", `/cards/${card.id}/apply-playbook`, {
|
|
12612
12186
|
playbookId: pick.id
|
|
12613
12187
|
});
|
|
12614
|
-
|
|
12188
|
+
log34.info(this.tag, `Auto-bound #${card.short_id} to playbook "${pick.name}": ${reason}`);
|
|
12615
12189
|
try {
|
|
12616
12190
|
await this.client.addComment(card.id, `Bound playbook "${pick.name}" automatically — ${reason} Apply a different playbook from the card's stage rail to override, or turn the rule off in the playbook editor.`);
|
|
12617
12191
|
} catch (commentErr) {
|
|
12618
|
-
|
|
12192
|
+
log34.warn(this.tag, `Auto-bind comment failed for #${card.short_id}: ${commentErr instanceof Error ? commentErr.message : String(commentErr)}`);
|
|
12619
12193
|
}
|
|
12620
12194
|
try {
|
|
12621
12195
|
const { card: fresh } = await this.client.getCard(card.id);
|
|
12622
12196
|
return fresh;
|
|
12623
12197
|
} catch (fetchErr) {
|
|
12624
|
-
|
|
12198
|
+
log34.warn(this.tag, `Auto-bind re-fetch failed for #${card.short_id} after binding playbook "${pick.name}" — using the apply-playbook response's fields for this pickup: ${fetchErr instanceof Error ? fetchErr.message : String(fetchErr)}`);
|
|
12625
12199
|
return {
|
|
12626
12200
|
...card,
|
|
12627
12201
|
playbook_id: applyResult.card.playbook_id,
|
|
@@ -12630,7 +12204,7 @@ class Worker {
|
|
|
12630
12204
|
};
|
|
12631
12205
|
}
|
|
12632
12206
|
} catch (err) {
|
|
12633
|
-
|
|
12207
|
+
log34.warn(this.tag, `Auto-bind playbook check failed for #${card.short_id}, continuing unbound: ${err instanceof Error ? err.message : String(err)}`);
|
|
12634
12208
|
return card;
|
|
12635
12209
|
}
|
|
12636
12210
|
}
|
|
@@ -12742,7 +12316,7 @@ class Worker {
|
|
|
12742
12316
|
});
|
|
12743
12317
|
} catch (err) {
|
|
12744
12318
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12745
|
-
|
|
12319
|
+
log34.warn(this.tag, `fan-out tick failed on #${card.short_id}: ${detail}`);
|
|
12746
12320
|
await this.holdStageCard(card, `Fan-out stage "${ctx.stage.name}" could not run: ${detail}`);
|
|
12747
12321
|
return;
|
|
12748
12322
|
}
|
|
@@ -12751,7 +12325,7 @@ class Worker {
|
|
|
12751
12325
|
case "waiting": {
|
|
12752
12326
|
const total = outcome.kind === "dispatched" ? outcome.total : outcome.total;
|
|
12753
12327
|
const note = outcome.kind === "dispatched" ? `Fan-out stage "${ctx.stage.name}": dispatched ${outcome.created} of ${total} item(s); ${outcome.inFlight} in flight.` : `Fan-out stage "${ctx.stage.name}": ${outcome.settled} of ${total} item(s) settled; waiting on the rest.`;
|
|
12754
|
-
|
|
12328
|
+
log34.info(this.tag, `#${card.short_id} ${note}`);
|
|
12755
12329
|
await this.client.updateAgentProgress(card.id, {
|
|
12756
12330
|
agentIdentifier: "claude-code-stage",
|
|
12757
12331
|
agentName: "Harmony Agent",
|
|
@@ -12779,7 +12353,7 @@ class Worker {
|
|
|
12779
12353
|
if (advance.kind === "advanced" || advance.kind === "completed_terminal") {
|
|
12780
12354
|
this.held = false;
|
|
12781
12355
|
}
|
|
12782
|
-
|
|
12356
|
+
log34.info(this.tag, `#${card.short_id} ${summary} → ${advance.kind}`);
|
|
12783
12357
|
return;
|
|
12784
12358
|
}
|
|
12785
12359
|
case "halted": {
|
|
@@ -12799,7 +12373,7 @@ class Worker {
|
|
|
12799
12373
|
}
|
|
12800
12374
|
}
|
|
12801
12375
|
async holdStageCard(card, reason, wait = false) {
|
|
12802
|
-
|
|
12376
|
+
log34.info(this.tag, `Holding #${card.short_id}: ${reason}`);
|
|
12803
12377
|
await this.stateStore.decrementAttempt(card.id);
|
|
12804
12378
|
try {
|
|
12805
12379
|
await this.client.addComment(card.id, reason, {
|
|
@@ -12818,7 +12392,7 @@ class Worker {
|
|
|
12818
12392
|
}
|
|
12819
12393
|
});
|
|
12820
12394
|
} catch (tErr) {
|
|
12821
|
-
|
|
12395
|
+
log34.warn(this.tag, `hold transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12822
12396
|
}
|
|
12823
12397
|
if (this.runId) {
|
|
12824
12398
|
try {
|
|
@@ -12836,7 +12410,7 @@ class Worker {
|
|
|
12836
12410
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
12837
12411
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12838
12412
|
const until = computeDecisionDeadline(waitHours);
|
|
12839
|
-
|
|
12413
|
+
log34.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
12840
12414
|
try {
|
|
12841
12415
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
12842
12416
|
holderMessage,
|
|
@@ -12848,7 +12422,7 @@ class Worker {
|
|
|
12848
12422
|
agentSessionId: this.sessionId ?? undefined
|
|
12849
12423
|
});
|
|
12850
12424
|
} catch (commentErr) {
|
|
12851
|
-
|
|
12425
|
+
log34.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
12852
12426
|
}
|
|
12853
12427
|
if (this.runId) {
|
|
12854
12428
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -12859,7 +12433,7 @@ class Worker {
|
|
|
12859
12433
|
awaitingDecisionUntil: until
|
|
12860
12434
|
});
|
|
12861
12435
|
} catch (storeErr) {
|
|
12862
|
-
|
|
12436
|
+
log34.error(this.tag, `#${card.short_id} could not be re-parked after a resume conflict — the daemon has no hold on it: ${storeErr}`);
|
|
12863
12437
|
}
|
|
12864
12438
|
}
|
|
12865
12439
|
}
|
|
@@ -12871,7 +12445,7 @@ class Worker {
|
|
|
12871
12445
|
this.progressTracker = null;
|
|
12872
12446
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12873
12447
|
const until = computeDecisionDeadline(waitHours);
|
|
12874
|
-
|
|
12448
|
+
log34.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
12875
12449
|
const measured = this.worktreePath && this.runExpectsCommits ? measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha) : "unknown";
|
|
12876
12450
|
const hasCommits = measured === "unknown" ? undefined : measured === "committed";
|
|
12877
12451
|
if (hasCommits === false && !this.noCommitNoticeSettled) {
|
|
@@ -12901,7 +12475,7 @@ class Worker {
|
|
|
12901
12475
|
});
|
|
12902
12476
|
commentId = res?.comment?.id ?? null;
|
|
12903
12477
|
} catch (err) {
|
|
12904
|
-
|
|
12478
|
+
log34.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
12905
12479
|
}
|
|
12906
12480
|
const cardCostCents = this.stateStore.getCardCostCents(card.id) + Math.max(0, this.runCostCents - this.chargedCents);
|
|
12907
12481
|
const readout = [
|
|
@@ -12922,7 +12496,7 @@ class Worker {
|
|
|
12922
12496
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
12923
12497
|
});
|
|
12924
12498
|
} catch (err) {
|
|
12925
|
-
|
|
12499
|
+
log34.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
12926
12500
|
}
|
|
12927
12501
|
if (this.runId) {
|
|
12928
12502
|
try {
|
|
@@ -12933,7 +12507,7 @@ class Worker {
|
|
|
12933
12507
|
...this.runLedger()
|
|
12934
12508
|
});
|
|
12935
12509
|
} catch (err) {
|
|
12936
|
-
|
|
12510
|
+
log34.error(this.tag, `#${card.short_id} parkRun failed after the blocker comment + blocked session were already written — the daemon has no local record of this park: ${err}`);
|
|
12937
12511
|
}
|
|
12938
12512
|
}
|
|
12939
12513
|
}
|
|
@@ -12964,11 +12538,11 @@ class Worker {
|
|
|
12964
12538
|
});
|
|
12965
12539
|
const gate = normalizeGateSpec(ctx.stage.gate);
|
|
12966
12540
|
const metricsPath = gate?.kind === "custom" ? writeMetricsFile(this.config.playbooks.metrics ?? {}) : null;
|
|
12967
|
-
|
|
12541
|
+
log34.info(this.tag, `Running stage "${ctx.stage.name}" (role ${ctx.role}) for #${card.short_id} under the harness motor`);
|
|
12968
12542
|
const motorAbort = new AbortController;
|
|
12969
12543
|
this.motorAbort = motorAbort;
|
|
12970
12544
|
this.timeoutTimer = setTimeout(() => {
|
|
12971
|
-
|
|
12545
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms) during the motor stage run, cancelling`);
|
|
12972
12546
|
this.timedOut = true;
|
|
12973
12547
|
this.cancel("timeout");
|
|
12974
12548
|
}, this.config.maxTimeout);
|
|
@@ -12979,12 +12553,12 @@ class Worker {
|
|
|
12979
12553
|
let motorRunSettled = false;
|
|
12980
12554
|
const onMotorLine = (line) => {
|
|
12981
12555
|
if (line.type !== "agent_event") {
|
|
12982
|
-
|
|
12556
|
+
log34.info(this.tag, `motor: ${line.type}`);
|
|
12983
12557
|
return;
|
|
12984
12558
|
}
|
|
12985
12559
|
if (motorRunSettled)
|
|
12986
12560
|
return;
|
|
12987
|
-
|
|
12561
|
+
log34.debug(this.tag, `motor: agent_event ${line.event.kind}`);
|
|
12988
12562
|
if (line.event.kind === "tool_started" && STAGE_DAEMON_OWNED_TOOLS.includes(line.event.payload.toolName)) {
|
|
12989
12563
|
return;
|
|
12990
12564
|
}
|
|
@@ -13004,7 +12578,7 @@ class Worker {
|
|
|
13004
12578
|
currentTask: motorTask,
|
|
13005
12579
|
progressPercent: MOTOR_RUN_PROGRESS_PERCENT
|
|
13006
12580
|
}).catch((err) => {
|
|
13007
|
-
|
|
12581
|
+
log34.warn(this.tag, `motor session heartbeat failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13008
12582
|
});
|
|
13009
12583
|
}, MOTOR_SESSION_HEARTBEAT_MS);
|
|
13010
12584
|
heartbeat.unref?.();
|
|
@@ -13078,7 +12652,7 @@ class Worker {
|
|
|
13078
12652
|
break;
|
|
13079
12653
|
case "reclaim_refused":
|
|
13080
12654
|
this.held = true;
|
|
13081
|
-
|
|
12655
|
+
log34.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
13082
12656
|
break;
|
|
13083
12657
|
case "advanced":
|
|
13084
12658
|
case "completed_terminal":
|
|
@@ -13100,7 +12674,7 @@ class Worker {
|
|
|
13100
12674
|
try {
|
|
13101
12675
|
pushBranch3(this.branchName, worktreePath);
|
|
13102
12676
|
} catch (err) {
|
|
13103
|
-
|
|
12677
|
+
log34.error(this.tag, `push after the motor stage "${ctx.stage.name}" failed for ${this.branchName}: ${err instanceof Error ? err.message : err}`);
|
|
13104
12678
|
}
|
|
13105
12679
|
}
|
|
13106
12680
|
}
|
|
@@ -13108,7 +12682,7 @@ class Worker {
|
|
|
13108
12682
|
if (completionColumn) {
|
|
13109
12683
|
await transferCardToCompletion({ client: this.client, tag: this.tag }, card, completionColumn, this.onCardCompleted);
|
|
13110
12684
|
} else {
|
|
13111
|
-
|
|
12685
|
+
log34.warn(this.tag, `completion.moveToColumn is empty — #${card.short_id} stays in its current column after the motor stage run`);
|
|
13112
12686
|
}
|
|
13113
12687
|
await this.closeoutMotorWorktree(card);
|
|
13114
12688
|
}
|
|
@@ -13119,7 +12693,7 @@ class Worker {
|
|
|
13119
12693
|
try {
|
|
13120
12694
|
await teardownWorktree2(this.client, card.id, worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
13121
12695
|
} catch {
|
|
13122
|
-
|
|
12696
|
+
log34.warn(this.tag, "Failed to cleanup worktree after the motor stage run");
|
|
13123
12697
|
}
|
|
13124
12698
|
this.worktreePath = null;
|
|
13125
12699
|
}
|
|
@@ -13176,7 +12750,7 @@ ${prompt}`;
|
|
|
13176
12750
|
});
|
|
13177
12751
|
return handoff ? renderInheritedHandoffSection(handoff) : "";
|
|
13178
12752
|
} catch (err) {
|
|
13179
|
-
|
|
12753
|
+
log34.warn(this.tag, `inherited-handoff fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13180
12754
|
return "";
|
|
13181
12755
|
}
|
|
13182
12756
|
}
|
|
@@ -13196,9 +12770,9 @@ ${prompt}`;
|
|
|
13196
12770
|
commentType: "decision",
|
|
13197
12771
|
agentSessionId: this.sessionId ?? undefined
|
|
13198
12772
|
});
|
|
13199
|
-
|
|
12773
|
+
log34.info(this.tag, `Wrote stage handoff for #${card.short_id} (stage "${stage.name}")`);
|
|
13200
12774
|
} catch (err) {
|
|
13201
|
-
|
|
12775
|
+
log34.warn(this.tag, `stage-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13202
12776
|
}
|
|
13203
12777
|
}
|
|
13204
12778
|
async collectStageGateEvidence(card, stage, worktreePath, subtasks) {
|
|
@@ -13210,12 +12784,12 @@ ${prompt}`;
|
|
|
13210
12784
|
return null;
|
|
13211
12785
|
}
|
|
13212
12786
|
if (gate.pendingEngine === true) {
|
|
13213
|
-
|
|
12787
|
+
log34.info(this.tag, `Stage "${stage.name}" gate "${gate.kind}" is advisory — skipping enforcement`);
|
|
13214
12788
|
return null;
|
|
13215
12789
|
}
|
|
13216
12790
|
const review = gate.kind === "review_passed" ? parseReviewOutput(this.lastRunText) : undefined;
|
|
13217
12791
|
if (review) {
|
|
13218
|
-
|
|
12792
|
+
log34.info(this.tag, `Review-gated stage "${stage.name}" verdict: ${review.verdict} (${review.findings.length} finding(s))`);
|
|
13219
12793
|
}
|
|
13220
12794
|
const registry = buildGateCollectorRegistry2({
|
|
13221
12795
|
build: {
|
|
@@ -13232,7 +12806,7 @@ ${prompt}`;
|
|
|
13232
12806
|
return [];
|
|
13233
12807
|
const criteria = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
13234
12808
|
if (criteria.length > 0) {
|
|
13235
|
-
|
|
12809
|
+
log34.info(this.tag, `Grading the artifact gate for #${card.short_id} against ${criteria.length} plan criteria instead of the stage's own rubric`);
|
|
13236
12810
|
}
|
|
13237
12811
|
return criteria;
|
|
13238
12812
|
})()
|
|
@@ -13254,10 +12828,10 @@ ${prompt}`;
|
|
|
13254
12828
|
const evaluation = gateEvaluate(gate, evidence);
|
|
13255
12829
|
const insert = toStageGateEvidenceInsert(context, evidence);
|
|
13256
12830
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, insert);
|
|
13257
|
-
|
|
12831
|
+
log34.info(this.tag, `Recorded ${gate.kind} gate evidence for #${card.short_id} stage "${stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
13258
12832
|
return evaluation;
|
|
13259
12833
|
} catch (err) {
|
|
13260
|
-
|
|
12834
|
+
log34.warn(this.tag, `stage-gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13261
12835
|
return null;
|
|
13262
12836
|
}
|
|
13263
12837
|
}
|
|
@@ -13275,7 +12849,7 @@ ${prompt}`;
|
|
|
13275
12849
|
agentSessionId: this.sessionId
|
|
13276
12850
|
});
|
|
13277
12851
|
} catch (err) {
|
|
13278
|
-
|
|
12852
|
+
log34.warn(this.tag, `stage advancement failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13279
12853
|
return { kind: "no_advance" };
|
|
13280
12854
|
}
|
|
13281
12855
|
}
|
|
@@ -13301,7 +12875,7 @@ ${prompt}`;
|
|
|
13301
12875
|
this.modelChoice = choice;
|
|
13302
12876
|
const { model, escalated, source } = choice;
|
|
13303
12877
|
if (source !== "policy" || escalated) {
|
|
13304
|
-
|
|
12878
|
+
log34.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${this.sizing?.tier ?? "none"})`);
|
|
13305
12879
|
}
|
|
13306
12880
|
return model;
|
|
13307
12881
|
}
|
|
@@ -13316,7 +12890,7 @@ ${prompt}`;
|
|
|
13316
12890
|
encoding: "utf-8"
|
|
13317
12891
|
}).trim();
|
|
13318
12892
|
} catch (err) {
|
|
13319
|
-
|
|
12893
|
+
log34.warn(this.tag, `Sizing #${card.short_id} failed (spawn): could not resolve the repo root (${err instanceof Error ? err.message : String(err)}) — using the policy fallback`);
|
|
13320
12894
|
return { status: "failed", reason: "spawn" };
|
|
13321
12895
|
}
|
|
13322
12896
|
const outcome = await sizeRun({
|
|
@@ -13330,21 +12904,22 @@ ${prompt}`;
|
|
|
13330
12904
|
scoreBands: claude.scoreBands
|
|
13331
12905
|
});
|
|
13332
12906
|
if (outcome.status === "sized") {
|
|
13333
|
-
|
|
12907
|
+
log34.info(this.tag, `Sized #${card.short_id}: complexity ${outcome.sizing.complexity}/10 -> ${outcome.sizing.tier}`);
|
|
13334
12908
|
} else if (outcome.status === "failed") {
|
|
13335
|
-
|
|
12909
|
+
log34.warn(this.tag, `Sizing #${card.short_id} failed (${outcome.reason}) — using the policy fallback`);
|
|
13336
12910
|
}
|
|
13337
12911
|
return outcome;
|
|
13338
12912
|
}
|
|
13339
12913
|
recordPromptAssembled(outcomes) {
|
|
13340
|
-
const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count
|
|
12914
|
+
const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count;
|
|
13341
12915
|
const failures = outcomes.flatMap((o) => o.failure ? [{ flavour: o.flavour, message: o.failure.slice(0, 300) }] : []);
|
|
12916
|
+
const episodeCount = countOf("past_episodes");
|
|
13342
12917
|
this.cliRunner?.record({
|
|
13343
12918
|
kind: "prompt_assembled",
|
|
13344
12919
|
source: "system",
|
|
13345
12920
|
payload: {
|
|
13346
|
-
episodeCount:
|
|
13347
|
-
referenceCount: countOf("reference"),
|
|
12921
|
+
...episodeCount === undefined ? {} : { episodeCount },
|
|
12922
|
+
referenceCount: countOf("reference") ?? 0,
|
|
13348
12923
|
...failures.length > 0 ? { recallFailures: failures } : {}
|
|
13349
12924
|
}
|
|
13350
12925
|
});
|
|
@@ -13365,7 +12940,7 @@ ${prompt}`;
|
|
|
13365
12940
|
const measured = measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha);
|
|
13366
12941
|
if (measured !== "none") {
|
|
13367
12942
|
if (measured === "unknown") {
|
|
13368
|
-
|
|
12943
|
+
log34.debug(this.tag, `no-commit check could not attribute this run's commits — staying quiet on ${this.branchName ?? "this branch"}`);
|
|
13369
12944
|
}
|
|
13370
12945
|
return;
|
|
13371
12946
|
}
|
|
@@ -13374,7 +12949,7 @@ ${prompt}`;
|
|
|
13374
12949
|
recordNoCommitsYet(trigger, numTurns) {
|
|
13375
12950
|
const turnBudget = this.effectiveMaxTurns;
|
|
13376
12951
|
const threshold = noCommitNoticeThreshold(turnBudget);
|
|
13377
|
-
|
|
12952
|
+
log34.warn(this.tag, `No commits yet on ${this.branchName ?? "this run's branch"} — ${this.runToolCalls} tool calls into a ${turnBudget}-turn budget (${trigger})`);
|
|
13378
12953
|
this.cliRunner?.record({
|
|
13379
12954
|
kind: "no_commits_yet",
|
|
13380
12955
|
source: "system",
|
|
@@ -13426,9 +13001,9 @@ ${prompt}`;
|
|
|
13426
13001
|
agentSessionId: this.sessionId ?? undefined
|
|
13427
13002
|
});
|
|
13428
13003
|
giveUpCommentId = res?.comment?.id ?? null;
|
|
13429
|
-
|
|
13004
|
+
log34.warn(this.tag, `gave up on ${cardId} after ${attempts} attempts`);
|
|
13430
13005
|
} catch (err) {
|
|
13431
|
-
|
|
13006
|
+
log34.warn(this.tag, `failed to post give-up comment for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13432
13007
|
}
|
|
13433
13008
|
if (this.config.budget.pause.enabled) {
|
|
13434
13009
|
const waitHours = this.config.budget.pause.waitHours;
|
|
@@ -13442,7 +13017,7 @@ ${prompt}`;
|
|
|
13442
13017
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
13443
13018
|
});
|
|
13444
13019
|
} catch (err) {
|
|
13445
|
-
|
|
13020
|
+
log34.warn(this.tag, `Failed to mark the attempt cap pending for ${cardId}: ${err}`);
|
|
13446
13021
|
}
|
|
13447
13022
|
try {
|
|
13448
13023
|
await this.stateStore.markAwaitingDecision(cardId, {
|
|
@@ -13451,19 +13026,19 @@ ${prompt}`;
|
|
|
13451
13026
|
agentIdentifier: this.sessionIdentifier
|
|
13452
13027
|
});
|
|
13453
13028
|
} catch (err) {
|
|
13454
|
-
|
|
13029
|
+
log34.warn(this.tag, `Failed to record the attempt-cap decision locally for ${cardId}: ${err}`);
|
|
13455
13030
|
}
|
|
13456
13031
|
}
|
|
13457
13032
|
}
|
|
13458
13033
|
}
|
|
13459
13034
|
} catch (err) {
|
|
13460
|
-
|
|
13035
|
+
log34.warn(this.tag, `recordOutcome(${outcome}) failed: ${err instanceof Error ? err.message : err}`);
|
|
13461
13036
|
}
|
|
13462
13037
|
}
|
|
13463
13038
|
async pause() {
|
|
13464
13039
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13465
13040
|
return;
|
|
13466
|
-
|
|
13041
|
+
log34.info(this.tag, `Pausing work on ${this.cardId}`);
|
|
13467
13042
|
signalGroup2(this.process, "SIGSTOP");
|
|
13468
13043
|
if (this.timeoutTimer) {
|
|
13469
13044
|
clearTimeout(this.timeoutTimer);
|
|
@@ -13477,17 +13052,17 @@ ${prompt}`;
|
|
|
13477
13052
|
status: "paused"
|
|
13478
13053
|
});
|
|
13479
13054
|
} catch {
|
|
13480
|
-
|
|
13055
|
+
log34.warn(this.tag, "Failed to update agent session to paused");
|
|
13481
13056
|
}
|
|
13482
13057
|
}
|
|
13483
13058
|
}
|
|
13484
13059
|
async resume() {
|
|
13485
13060
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13486
13061
|
return;
|
|
13487
|
-
|
|
13062
|
+
log34.info(this.tag, `Resuming work on ${this.cardId}`);
|
|
13488
13063
|
signalGroup2(this.process, "SIGCONT");
|
|
13489
13064
|
this.timeoutTimer = setTimeout(() => {
|
|
13490
|
-
|
|
13065
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
13491
13066
|
this.timedOut = true;
|
|
13492
13067
|
this.cancel("timeout");
|
|
13493
13068
|
}, this.config.maxTimeout);
|
|
@@ -13499,7 +13074,7 @@ ${prompt}`;
|
|
|
13499
13074
|
status: "working"
|
|
13500
13075
|
});
|
|
13501
13076
|
} catch {
|
|
13502
|
-
|
|
13077
|
+
log34.warn(this.tag, "Failed to update agent session to working");
|
|
13503
13078
|
}
|
|
13504
13079
|
}
|
|
13505
13080
|
}
|
|
@@ -13508,7 +13083,7 @@ ${prompt}`;
|
|
|
13508
13083
|
return;
|
|
13509
13084
|
this.aborted = true;
|
|
13510
13085
|
this.state = "cancelling";
|
|
13511
|
-
|
|
13086
|
+
log34.info(this.tag, `Cancelling work on ${this.cardId}`);
|
|
13512
13087
|
this.motorAbort?.abort();
|
|
13513
13088
|
if (this.sdkRunner) {
|
|
13514
13089
|
await this.sdkRunner.stop(this.timedOut ? "timeout" : "user_requested");
|
|
@@ -13526,14 +13101,14 @@ ${prompt}`;
|
|
|
13526
13101
|
...buildTokenPayload(stats)
|
|
13527
13102
|
});
|
|
13528
13103
|
} catch (err) {
|
|
13529
|
-
|
|
13104
|
+
log34.warn(this.tag, `endAgentSession after cancel failed: ${err instanceof Error ? err.message : err}`);
|
|
13530
13105
|
}
|
|
13531
13106
|
}
|
|
13532
13107
|
}
|
|
13533
13108
|
async runPlanningPhase(enriched) {
|
|
13534
13109
|
const planning = this.config.planning;
|
|
13535
13110
|
const { card } = enriched;
|
|
13536
|
-
|
|
13111
|
+
log34.info(this.tag, `Planning pass for #${card.short_id} (mode=${planning.mode}, model=${planning.model})`);
|
|
13537
13112
|
await this.client.updateAgentProgress(card.id, {
|
|
13538
13113
|
agentIdentifier: this.sessionIdentifier,
|
|
13539
13114
|
agentName: AGENT_NAME,
|
|
@@ -13546,7 +13121,7 @@ ${prompt}`;
|
|
|
13546
13121
|
let planTimedOut = false;
|
|
13547
13122
|
const planTimeout = setTimeout(() => {
|
|
13548
13123
|
planTimedOut = true;
|
|
13549
|
-
|
|
13124
|
+
log34.warn(this.tag, "Planning pass exceeded timeout — abandoning, implementing directly");
|
|
13550
13125
|
if (this.sdkRunner) {
|
|
13551
13126
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13552
13127
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13564,7 +13139,7 @@ ${prompt}`;
|
|
|
13564
13139
|
initialPhase: "planning"
|
|
13565
13140
|
});
|
|
13566
13141
|
} catch (err) {
|
|
13567
|
-
|
|
13142
|
+
log34.warn(this.tag, `Planning pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13568
13143
|
return false;
|
|
13569
13144
|
} finally {
|
|
13570
13145
|
clearTimeout(planTimeout);
|
|
@@ -13575,7 +13150,7 @@ ${prompt}`;
|
|
|
13575
13150
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13576
13151
|
const planText = stats?.lastAssistantText ?? "";
|
|
13577
13152
|
if (!planText.trim()) {
|
|
13578
|
-
|
|
13153
|
+
log34.warn(this.tag, `Planning pass for #${card.short_id} produced no text — implementing directly`);
|
|
13579
13154
|
return false;
|
|
13580
13155
|
}
|
|
13581
13156
|
const artifact = extractPlanArtifact(planText, card.title);
|
|
@@ -13596,9 +13171,9 @@ ${prompt}`;
|
|
|
13596
13171
|
});
|
|
13597
13172
|
planId = createdId;
|
|
13598
13173
|
}
|
|
13599
|
-
|
|
13174
|
+
log34.info(this.tag, `Stored plan ${planId ?? "(unlinked)"} for #${card.short_id} (${artifact.tasks.length} tasks)`);
|
|
13600
13175
|
} catch (err) {
|
|
13601
|
-
|
|
13176
|
+
log34.warn(this.tag, `Failed to store/link plan (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13602
13177
|
}
|
|
13603
13178
|
if (planning.mode === "gated" && planId) {
|
|
13604
13179
|
try {
|
|
@@ -13617,11 +13192,11 @@ ${prompt}`;
|
|
|
13617
13192
|
...buildTokenPayload(stats)
|
|
13618
13193
|
}
|
|
13619
13194
|
}, { store: this.stateStore, runId: this.runId ?? undefined });
|
|
13620
|
-
|
|
13195
|
+
log34.info(this.tag, `#${card.short_id} parked in "${planning.awaitingApprovalColumn}" for plan approval`);
|
|
13621
13196
|
this.lastSessionStats = undefined;
|
|
13622
13197
|
return true;
|
|
13623
13198
|
} catch (err) {
|
|
13624
|
-
|
|
13199
|
+
log34.warn(this.tag, `Gated park failed for #${card.short_id} (non-fatal, implementing directly): ${err instanceof TransitionError ? err.detail : err instanceof Error ? err.message : err}`);
|
|
13625
13200
|
}
|
|
13626
13201
|
}
|
|
13627
13202
|
if (planId && planning.postComment) {
|
|
@@ -13631,7 +13206,7 @@ ${prompt}`;
|
|
|
13631
13206
|
agentSessionId: this.sessionId ?? undefined
|
|
13632
13207
|
});
|
|
13633
13208
|
} catch (err) {
|
|
13634
|
-
|
|
13209
|
+
log34.warn(this.tag, `Failed to post advisory plan comment (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13635
13210
|
}
|
|
13636
13211
|
}
|
|
13637
13212
|
return false;
|
|
@@ -13641,10 +13216,10 @@ ${prompt}`;
|
|
|
13641
13216
|
const { card } = enriched;
|
|
13642
13217
|
const existing = await this.loadPinnedContract(card.id);
|
|
13643
13218
|
if (existing) {
|
|
13644
|
-
|
|
13219
|
+
log34.info(this.tag, `Contract already pinned for #${card.short_id} (${existing.assertions.length} assertions) — reusing`);
|
|
13645
13220
|
return;
|
|
13646
13221
|
}
|
|
13647
|
-
|
|
13222
|
+
log34.info(this.tag, `Contract pass for #${card.short_id} (model=${contractCfg.model})`);
|
|
13648
13223
|
await this.client.updateAgentProgress(card.id, {
|
|
13649
13224
|
agentIdentifier: this.sessionIdentifier,
|
|
13650
13225
|
agentName: AGENT_NAME,
|
|
@@ -13657,7 +13232,7 @@ ${prompt}`;
|
|
|
13657
13232
|
let contractTimedOut = false;
|
|
13658
13233
|
const contractTimeout = setTimeout(() => {
|
|
13659
13234
|
contractTimedOut = true;
|
|
13660
|
-
|
|
13235
|
+
log34.warn(this.tag, "Contract pass exceeded timeout — abandoning, implementing directly");
|
|
13661
13236
|
if (this.sdkRunner) {
|
|
13662
13237
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13663
13238
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13675,7 +13250,7 @@ ${prompt}`;
|
|
|
13675
13250
|
initialPhase: "planning"
|
|
13676
13251
|
});
|
|
13677
13252
|
} catch (err) {
|
|
13678
|
-
|
|
13253
|
+
log34.warn(this.tag, `Contract pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13679
13254
|
return;
|
|
13680
13255
|
} finally {
|
|
13681
13256
|
clearTimeout(contractTimeout);
|
|
@@ -13686,12 +13261,12 @@ ${prompt}`;
|
|
|
13686
13261
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13687
13262
|
const contractText = stats?.lastAssistantText ?? "";
|
|
13688
13263
|
if (!contractText.trim()) {
|
|
13689
|
-
|
|
13264
|
+
log34.warn(this.tag, `Contract pass for #${card.short_id} produced no text — implementing directly`);
|
|
13690
13265
|
return;
|
|
13691
13266
|
}
|
|
13692
13267
|
const contract = extractContract(contractText, card);
|
|
13693
13268
|
if (contract.assertions.length < contractCfg.minAssertions) {
|
|
13694
|
-
|
|
13269
|
+
log34.warn(this.tag, `Contract for #${card.short_id} had ${contract.assertions.length} assertion(s) (< ${contractCfg.minAssertions}) — not pinning, implementing directly`);
|
|
13695
13270
|
return;
|
|
13696
13271
|
}
|
|
13697
13272
|
try {
|
|
@@ -13699,9 +13274,9 @@ ${prompt}`;
|
|
|
13699
13274
|
commentType: "decision",
|
|
13700
13275
|
agentSessionId: this.sessionId ?? undefined
|
|
13701
13276
|
});
|
|
13702
|
-
|
|
13277
|
+
log34.info(this.tag, `Pinned acceptance contract for #${card.short_id} (${contract.assertions.length} assertions)`);
|
|
13703
13278
|
} catch (err) {
|
|
13704
|
-
|
|
13279
|
+
log34.warn(this.tag, `Failed to pin contract (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13705
13280
|
}
|
|
13706
13281
|
}
|
|
13707
13282
|
async loadPinnedContract(cardId) {
|
|
@@ -13711,7 +13286,7 @@ ${prompt}`;
|
|
|
13711
13286
|
return null;
|
|
13712
13287
|
return extractPinnedContract(comments, this.identity);
|
|
13713
13288
|
} catch (err) {
|
|
13714
|
-
|
|
13289
|
+
log34.warn(this.tag, `pinned-contract fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13715
13290
|
return null;
|
|
13716
13291
|
}
|
|
13717
13292
|
}
|
|
@@ -13724,13 +13299,13 @@ ${prompt}`;
|
|
|
13724
13299
|
const res = await this.client.getPendingUserMessages(this.cardId, this.sessionId, this.lastDrainedSeq);
|
|
13725
13300
|
messages = res.messages ?? [];
|
|
13726
13301
|
} catch (err) {
|
|
13727
|
-
|
|
13302
|
+
log34.warn(this.tag, `Failed to fetch steering messages (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13728
13303
|
return;
|
|
13729
13304
|
}
|
|
13730
13305
|
if (messages.length === 0)
|
|
13731
13306
|
return;
|
|
13732
13307
|
this.lastDrainedSeq = Math.max(this.lastDrainedSeq, ...messages.map((m) => m.seq));
|
|
13733
|
-
|
|
13308
|
+
log34.info(this.tag, `Steering #${card.short_id}: resuming with ${messages.length} queued message(s)`);
|
|
13734
13309
|
this.state = "running";
|
|
13735
13310
|
await this.recordPhase("running");
|
|
13736
13311
|
try {
|
|
@@ -13742,7 +13317,7 @@ ${prompt}`;
|
|
|
13742
13317
|
...this.activeRunSpawnOpts ?? {}
|
|
13743
13318
|
});
|
|
13744
13319
|
} catch (err) {
|
|
13745
|
-
|
|
13320
|
+
log34.warn(this.tag, `Steering resume failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13746
13321
|
return;
|
|
13747
13322
|
}
|
|
13748
13323
|
}
|
|
@@ -13777,10 +13352,10 @@ ${prompt}`;
|
|
|
13777
13352
|
"--",
|
|
13778
13353
|
prompt
|
|
13779
13354
|
];
|
|
13780
|
-
|
|
13355
|
+
log34.info(this.tag, `Spawning: claude ${args.slice(0, 4).join(" ")} ...`);
|
|
13781
13356
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13782
13357
|
if (runLog) {
|
|
13783
|
-
|
|
13358
|
+
log34.info(this.tag, `Run log: ${runLog.path}`);
|
|
13784
13359
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} started=${new Date().toISOString()}
|
|
13785
13360
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
13786
13361
|
|
|
@@ -13817,7 +13392,7 @@ ${prompt}`;
|
|
|
13817
13392
|
});
|
|
13818
13393
|
}
|
|
13819
13394
|
parser.on("parse_error", (msg) => {
|
|
13820
|
-
|
|
13395
|
+
log34.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
13821
13396
|
runLog?.stream.write(`
|
|
13822
13397
|
[parse_error] ${msg}
|
|
13823
13398
|
`);
|
|
@@ -13890,10 +13465,10 @@ ${prompt}`;
|
|
|
13890
13465
|
const disallowedTools = opts.disallowedTools ? opts.disallowedTools.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
13891
13466
|
const initialPhase = opts.initialPhase ?? "exploring";
|
|
13892
13467
|
const maxBudgetUsd = this.effectiveMaxBudgetUsd;
|
|
13893
|
-
|
|
13468
|
+
log34.info(this.tag, `Spawning Agent SDK runner (model=${model}, maxBudgetUsd=${maxBudgetUsd || "off"}, maxTurns=${maxTurns}${opts.resumeSessionId ? ", resume" : ""})`);
|
|
13894
13469
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13895
13470
|
if (runLog) {
|
|
13896
|
-
|
|
13471
|
+
log34.info(this.tag, `Run log: ${runLog.path}`);
|
|
13897
13472
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} runner=sdk started=${new Date().toISOString()}
|
|
13898
13473
|
` + `# model=${model} maxTurns=${maxTurns} <prompt:${prompt.length} chars>
|
|
13899
13474
|
|
|
@@ -14028,7 +13603,7 @@ ${prompt}`;
|
|
|
14028
13603
|
try {
|
|
14029
13604
|
await teardownWorktree2(this.client, this.cardId, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
14030
13605
|
} catch {
|
|
14031
|
-
|
|
13606
|
+
log34.warn(this.tag, "Failed to cleanup worktree");
|
|
14032
13607
|
}
|
|
14033
13608
|
}
|
|
14034
13609
|
this.process = null;
|
|
@@ -14047,7 +13622,7 @@ ${prompt}`;
|
|
|
14047
13622
|
this.chargedTokens = emptyTokens();
|
|
14048
13623
|
}
|
|
14049
13624
|
}
|
|
14050
|
-
var
|
|
13625
|
+
var TAG31 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, MOTOR_SESSION_HEARTBEAT_MS = 60000, MOTOR_RUN_PROGRESS_PERCENT = 10, 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, MAX_STAGE_NAME_CHARS = 80, MAX_HANDOFF_CHARS = 400;
|
|
14051
13626
|
var init_worker = __esm(() => {
|
|
14052
13627
|
init_dist();
|
|
14053
13628
|
init_board_helpers();
|
|
@@ -14078,7 +13653,7 @@ var init_worker = __esm(() => {
|
|
|
14078
13653
|
});
|
|
14079
13654
|
|
|
14080
13655
|
// src/workspace-model-cache.ts
|
|
14081
|
-
import { log as
|
|
13656
|
+
import { log as log35 } from "@gethmy/harness";
|
|
14082
13657
|
|
|
14083
13658
|
class WorkspaceModelCache {
|
|
14084
13659
|
fetcher;
|
|
@@ -14113,7 +13688,7 @@ class WorkspaceModelCache {
|
|
|
14113
13688
|
this.stale = false;
|
|
14114
13689
|
} catch (err) {
|
|
14115
13690
|
this.stale = true;
|
|
14116
|
-
|
|
13691
|
+
log35.warn("config", `Could not refresh a pickup-cached value, using ${this.everFetched ? "the last known value" : "local config only"}: ${err instanceof Error ? err.message : String(err)}`);
|
|
14117
13692
|
}
|
|
14118
13693
|
return this.value;
|
|
14119
13694
|
}
|
|
@@ -14124,7 +13699,7 @@ var init_workspace_model_cache = () => {};
|
|
|
14124
13699
|
import {
|
|
14125
13700
|
cooldownMsFor,
|
|
14126
13701
|
describeApiError as describeApiError3,
|
|
14127
|
-
log as
|
|
13702
|
+
log as log36
|
|
14128
13703
|
} from "@gethmy/harness";
|
|
14129
13704
|
async function routeBudgetDecision(d, run, actions, cardId) {
|
|
14130
13705
|
if (!run) {
|
|
@@ -14220,11 +13795,11 @@ class Pool {
|
|
|
14220
13795
|
}
|
|
14221
13796
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
14222
13797
|
if (card.done || card.archived_at) {
|
|
14223
|
-
|
|
13798
|
+
log36.debug(TAG32, `#${card.short_id} skipped — ${card.done ? "done" : "archived"} (finished work is never enqueued, #1102)`);
|
|
14224
13799
|
return;
|
|
14225
13800
|
}
|
|
14226
13801
|
if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
|
|
14227
|
-
|
|
13802
|
+
log36.debug(TAG32, `Card ${card.id} already queued, active, or reserved, skipping`);
|
|
14228
13803
|
return;
|
|
14229
13804
|
}
|
|
14230
13805
|
this.reservations.add(card.id);
|
|
@@ -14232,19 +13807,19 @@ class Pool {
|
|
|
14232
13807
|
try {
|
|
14233
13808
|
const account = this.accountGuard?.check();
|
|
14234
13809
|
if (account && !account.running) {
|
|
14235
|
-
|
|
13810
|
+
log36.debug(TAG32, `#${card.short_id} held — ${account.detail}`);
|
|
14236
13811
|
await this.emitWaiting(card.id, account.detail);
|
|
14237
13812
|
return;
|
|
14238
13813
|
}
|
|
14239
13814
|
if (mode === "implement") {
|
|
14240
13815
|
if (this.authPaused) {
|
|
14241
|
-
|
|
13816
|
+
log36.debug(TAG32, `#${card.short_id} held — agent paused (auth error)`);
|
|
14242
13817
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
14243
13818
|
return;
|
|
14244
13819
|
}
|
|
14245
13820
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
14246
13821
|
if (cooldownMs > 0) {
|
|
14247
|
-
|
|
13822
|
+
log36.debug(TAG32, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
14248
13823
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
14249
13824
|
return;
|
|
14250
13825
|
}
|
|
@@ -14253,19 +13828,19 @@ class Pool {
|
|
|
14253
13828
|
if (decision.reason === "daily_budget") {
|
|
14254
13829
|
await this.denyDailyBudget(card, decision.detail);
|
|
14255
13830
|
} else {
|
|
14256
|
-
|
|
13831
|
+
log36.debug(TAG32, `#${card.short_id} skipped (${decision.reason}): ${decision.detail}`);
|
|
14257
13832
|
}
|
|
14258
13833
|
return;
|
|
14259
13834
|
}
|
|
14260
13835
|
const { blockers, successors } = await getChainSignals(this.client, card, this.projectId);
|
|
14261
13836
|
chainSuccessors = successors;
|
|
14262
13837
|
if (blockers === null) {
|
|
14263
|
-
|
|
13838
|
+
log36.warn(TAG32, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
14264
13839
|
return;
|
|
14265
13840
|
}
|
|
14266
13841
|
if (blockers.length > 0) {
|
|
14267
13842
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
14268
|
-
|
|
13843
|
+
log36.info(TAG32, `#${card.short_id} blocked by ${list} — waiting`);
|
|
14269
13844
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
14270
13845
|
return;
|
|
14271
13846
|
}
|
|
@@ -14297,9 +13872,9 @@ class Pool {
|
|
|
14297
13872
|
const reason = `Daily budget reached — waiting for reset (${detail})`;
|
|
14298
13873
|
const line = `#${card.short_id} skipped (daily_budget): ${detail}`;
|
|
14299
13874
|
if (this.lastWaitingEmit.get(card.id) === reason)
|
|
14300
|
-
|
|
13875
|
+
log36.debug(TAG32, line);
|
|
14301
13876
|
else
|
|
14302
|
-
|
|
13877
|
+
log36.warn(TAG32, line);
|
|
14303
13878
|
await this.emitWaiting(card.id, reason);
|
|
14304
13879
|
}
|
|
14305
13880
|
lastWaitingEmit = new Map;
|
|
@@ -14316,7 +13891,7 @@ class Pool {
|
|
|
14316
13891
|
});
|
|
14317
13892
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
14318
13893
|
} catch (err) {
|
|
14319
|
-
|
|
13894
|
+
log36.debug(TAG32, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14320
13895
|
}
|
|
14321
13896
|
}
|
|
14322
13897
|
noteApiError(err) {
|
|
@@ -14324,13 +13899,13 @@ class Pool {
|
|
|
14324
13899
|
return;
|
|
14325
13900
|
if (err.kind === "spend_limit") {
|
|
14326
13901
|
this.accountGuard?.halt("spend_limit").catch((hErr) => {
|
|
14327
|
-
|
|
13902
|
+
log36.error(TAG32, `Failed to latch the account halt: ${hErr}`);
|
|
14328
13903
|
});
|
|
14329
13904
|
return;
|
|
14330
13905
|
}
|
|
14331
13906
|
if (err.kind === "auth") {
|
|
14332
13907
|
if (!this.authPaused) {
|
|
14333
|
-
|
|
13908
|
+
log36.error(TAG32, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
14334
13909
|
}
|
|
14335
13910
|
this.authPaused = true;
|
|
14336
13911
|
return;
|
|
@@ -14339,7 +13914,7 @@ class Pool {
|
|
|
14339
13914
|
const until = Date.now() + cooldownMs;
|
|
14340
13915
|
if (until > this.apiCooldownUntil) {
|
|
14341
13916
|
this.apiCooldownUntil = until;
|
|
14342
|
-
|
|
13917
|
+
log36.warn(TAG32, `${describeApiError3(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
14343
13918
|
}
|
|
14344
13919
|
}
|
|
14345
13920
|
apiCooldownRemainingMs() {
|
|
@@ -14353,13 +13928,13 @@ class Pool {
|
|
|
14353
13928
|
const removed = queue.remove(cardId);
|
|
14354
13929
|
if (removed) {
|
|
14355
13930
|
this.cardDataCache.delete(cardId);
|
|
14356
|
-
|
|
13931
|
+
log36.info(TAG32, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
14357
13932
|
return;
|
|
14358
13933
|
}
|
|
14359
13934
|
}
|
|
14360
13935
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
14361
13936
|
if (worker) {
|
|
14362
|
-
|
|
13937
|
+
log36.info(TAG32, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
14363
13938
|
await worker.cancel("unassigned");
|
|
14364
13939
|
}
|
|
14365
13940
|
}
|
|
@@ -14416,10 +13991,10 @@ class Pool {
|
|
|
14416
13991
|
}
|
|
14417
13992
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
14418
13993
|
if (!worker) {
|
|
14419
|
-
|
|
13994
|
+
log36.debug(TAG32, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
14420
13995
|
return;
|
|
14421
13996
|
}
|
|
14422
|
-
|
|
13997
|
+
log36.info(TAG32, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
14423
13998
|
switch (command) {
|
|
14424
13999
|
case "pause":
|
|
14425
14000
|
await worker.pause();
|
|
@@ -14467,7 +14042,7 @@ class Pool {
|
|
|
14467
14042
|
};
|
|
14468
14043
|
}
|
|
14469
14044
|
async shutdown() {
|
|
14470
|
-
|
|
14045
|
+
log36.info(TAG32, "Shutting down pool...");
|
|
14471
14046
|
this.shuttingDown = true;
|
|
14472
14047
|
const active = [
|
|
14473
14048
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -14475,7 +14050,7 @@ class Pool {
|
|
|
14475
14050
|
];
|
|
14476
14051
|
await Promise.all(active.map((w) => w.cancel("shutdown")));
|
|
14477
14052
|
this.sleepGuard.stop();
|
|
14478
|
-
|
|
14053
|
+
log36.info(TAG32, "Pool shutdown complete");
|
|
14479
14054
|
}
|
|
14480
14055
|
async drainBudgetDecisions(cardId) {
|
|
14481
14056
|
const targets = cardId ? [cardId] : [
|
|
@@ -14506,7 +14081,7 @@ class Pool {
|
|
|
14506
14081
|
try {
|
|
14507
14082
|
({ decisions } = await this.client.getBudgetDecisions(cardId, new Date(sinceMs).toISOString()));
|
|
14508
14083
|
} catch (err) {
|
|
14509
|
-
|
|
14084
|
+
log36.warn(TAG32, `getBudgetDecisions failed for ${cardId}: ${err}`);
|
|
14510
14085
|
return;
|
|
14511
14086
|
}
|
|
14512
14087
|
if (decisions.length > 0) {
|
|
@@ -14541,7 +14116,7 @@ class Pool {
|
|
|
14541
14116
|
...run.blockerCommentId ? { replyToId: run.blockerCommentId } : {}
|
|
14542
14117
|
});
|
|
14543
14118
|
} catch (err) {
|
|
14544
|
-
|
|
14119
|
+
log36.warn(TAG32, `Failed to post the expired-park closing comment for ${run.cardId}: ${err}`);
|
|
14545
14120
|
}
|
|
14546
14121
|
try {
|
|
14547
14122
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14555,14 +14130,14 @@ class Pool {
|
|
|
14555
14130
|
});
|
|
14556
14131
|
}
|
|
14557
14132
|
} catch (err) {
|
|
14558
|
-
|
|
14133
|
+
log36.error(TAG32, `Failed to move #${run.cardShortId} after an expired park: ${err}`);
|
|
14559
14134
|
}
|
|
14560
14135
|
try {
|
|
14561
14136
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14562
14137
|
errorMessage: "budget decision expired"
|
|
14563
14138
|
});
|
|
14564
14139
|
} catch (err) {
|
|
14565
|
-
|
|
14140
|
+
log36.warn(TAG32, `Failed to release the expired park for ${run.cardId}: ${err}`);
|
|
14566
14141
|
}
|
|
14567
14142
|
}
|
|
14568
14143
|
async releaseExpiredAttemptCap(cardId, blockerCommentId) {
|
|
@@ -14572,7 +14147,7 @@ class Pool {
|
|
|
14572
14147
|
...blockerCommentId ? { replyToId: blockerCommentId } : {}
|
|
14573
14148
|
});
|
|
14574
14149
|
} catch (err) {
|
|
14575
|
-
|
|
14150
|
+
log36.warn(TAG32, `Failed to post the expired attempt-cap note for ${cardId}: ${err}`);
|
|
14576
14151
|
}
|
|
14577
14152
|
try {
|
|
14578
14153
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14581,7 +14156,7 @@ class Pool {
|
|
|
14581
14156
|
failureSummary: "The attempt-budget decision expired with no answer. Reassign the card to grant a fresh attempt."
|
|
14582
14157
|
});
|
|
14583
14158
|
} catch (err) {
|
|
14584
|
-
|
|
14159
|
+
log36.warn(TAG32, `Failed to end the expired attempt-cap session for ${cardId}: ${err}`);
|
|
14585
14160
|
}
|
|
14586
14161
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14587
14162
|
}
|
|
@@ -14592,16 +14167,16 @@ class Pool {
|
|
|
14592
14167
|
}
|
|
14593
14168
|
const account = this.accountGuard?.check();
|
|
14594
14169
|
if (account && !account.running) {
|
|
14595
|
-
|
|
14170
|
+
log36.debug(TAG32, `#${run.cardShortId}: granted continue held — ${account.detail}`);
|
|
14596
14171
|
return;
|
|
14597
14172
|
}
|
|
14598
14173
|
const first = !this.adoptAnnounced.has(run.runId);
|
|
14599
14174
|
this.adoptAnnounced.add(run.runId);
|
|
14600
14175
|
const line = `#${run.cardShortId}: granted continue never reached a worker — re-enqueueing (${run.pipeline})`;
|
|
14601
14176
|
if (first)
|
|
14602
|
-
|
|
14177
|
+
log36.warn(TAG32, line);
|
|
14603
14178
|
else
|
|
14604
|
-
|
|
14179
|
+
log36.debug(TAG32, line);
|
|
14605
14180
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14606
14181
|
}
|
|
14607
14182
|
adoptAnnounced = new Set;
|
|
@@ -14620,7 +14195,7 @@ class Pool {
|
|
|
14620
14195
|
async continueRun(run, opts) {
|
|
14621
14196
|
const turns = this.budget.checkCardTurns(run.cardId);
|
|
14622
14197
|
if (!turns.allow) {
|
|
14623
|
-
|
|
14198
|
+
log36.warn(TAG32, `Refusing a Continue on ${run.cardId} — ${turns.detail}. Handing the card back: reassign it to clear the count, or raise budget.maxTurnsPerCard.`);
|
|
14624
14199
|
try {
|
|
14625
14200
|
await this.client.addComment(run.cardId, `**That Continue cannot be granted.** ${turns.detail}, which is this card's ceiling (\`budget.maxTurnsPerCard\`), so the implement pipeline would refuse it again the moment it was dispatched.
|
|
14626
14201
|
|
|
@@ -14629,14 +14204,14 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14629
14204
|
agentSessionId: run.sessionId ?? undefined
|
|
14630
14205
|
});
|
|
14631
14206
|
} catch (err) {
|
|
14632
|
-
|
|
14207
|
+
log36.warn(TAG32, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14633
14208
|
}
|
|
14634
14209
|
await this.stopRun(run, "Continue refused: the card is at its per-card turn cap.");
|
|
14635
14210
|
return;
|
|
14636
14211
|
}
|
|
14637
14212
|
const runsSpent = this.budget.checkCardRuns(run.cardId);
|
|
14638
14213
|
if (!runsSpent.allow) {
|
|
14639
|
-
|
|
14214
|
+
log36.warn(TAG32, `Refusing a Continue on ${run.cardId} — ${runsSpent.detail}. Handing the card back: reassign it to clear the count, or raise budget.maxRunsPerCard.`);
|
|
14640
14215
|
try {
|
|
14641
14216
|
await this.client.addComment(run.cardId, `**That Continue cannot be granted.** ${runsSpent.detail}, which is this card's run backstop (\`budget.maxRunsPerCard\`), so the implement pipeline would refuse it again the moment it was dispatched.
|
|
14642
14217
|
|
|
@@ -14645,7 +14220,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14645
14220
|
agentSessionId: run.sessionId ?? undefined
|
|
14646
14221
|
});
|
|
14647
14222
|
} catch (err) {
|
|
14648
|
-
|
|
14223
|
+
log36.warn(TAG32, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14649
14224
|
}
|
|
14650
14225
|
await this.stopRun(run, "Continue refused: the card is at its per-card run backstop.");
|
|
14651
14226
|
return;
|
|
@@ -14669,7 +14244,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14669
14244
|
awaitingDecisionUntil: null
|
|
14670
14245
|
});
|
|
14671
14246
|
} catch (err) {
|
|
14672
|
-
|
|
14247
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14673
14248
|
}
|
|
14674
14249
|
if (run.blockerCommentId) {
|
|
14675
14250
|
try {
|
|
@@ -14677,7 +14252,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14677
14252
|
resolve: true
|
|
14678
14253
|
});
|
|
14679
14254
|
} catch (err) {
|
|
14680
|
-
|
|
14255
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14681
14256
|
}
|
|
14682
14257
|
}
|
|
14683
14258
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
@@ -14701,7 +14276,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14701
14276
|
awaitingDecisionUntil: null
|
|
14702
14277
|
});
|
|
14703
14278
|
} catch (err) {
|
|
14704
|
-
|
|
14279
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14705
14280
|
}
|
|
14706
14281
|
if (run.blockerCommentId) {
|
|
14707
14282
|
try {
|
|
@@ -14709,13 +14284,13 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14709
14284
|
resolve: true
|
|
14710
14285
|
});
|
|
14711
14286
|
} catch (err) {
|
|
14712
|
-
|
|
14287
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14713
14288
|
}
|
|
14714
14289
|
}
|
|
14715
14290
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14716
14291
|
released += 1;
|
|
14717
14292
|
} catch (err) {
|
|
14718
|
-
|
|
14293
|
+
log36.error(TAG32, `Failed to release the account-limit park on ${run.cardId}: ${err}`);
|
|
14719
14294
|
}
|
|
14720
14295
|
}
|
|
14721
14296
|
return released;
|
|
@@ -14727,7 +14302,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14727
14302
|
resolve: true
|
|
14728
14303
|
});
|
|
14729
14304
|
} catch (err) {
|
|
14730
|
-
|
|
14305
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14731
14306
|
}
|
|
14732
14307
|
}
|
|
14733
14308
|
try {
|
|
@@ -14736,7 +14311,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14736
14311
|
awaitingDecisionUntil: null
|
|
14737
14312
|
});
|
|
14738
14313
|
} catch (err) {
|
|
14739
|
-
|
|
14314
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14740
14315
|
}
|
|
14741
14316
|
try {
|
|
14742
14317
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14758,26 +14333,26 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14758
14333
|
await this.client.endAgentSession(run.cardId, endSession);
|
|
14759
14334
|
}
|
|
14760
14335
|
} catch (err) {
|
|
14761
|
-
|
|
14336
|
+
log36.error(TAG32, `Failed to hand #${run.cardShortId} back after a stop: ${err}`);
|
|
14762
14337
|
}
|
|
14763
14338
|
try {
|
|
14764
14339
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14765
14340
|
errorMessage: "budget_decision_stop"
|
|
14766
14341
|
});
|
|
14767
14342
|
} catch (err) {
|
|
14768
|
-
|
|
14343
|
+
log36.warn(TAG32, `Failed to end the local run record for ${run.cardId}: ${err}`);
|
|
14769
14344
|
}
|
|
14770
14345
|
}
|
|
14771
14346
|
async grantAttempt(cardId) {
|
|
14772
14347
|
const turns = this.budget.checkCardTurns(cardId);
|
|
14773
14348
|
if (!turns.allow) {
|
|
14774
|
-
|
|
14349
|
+
log36.warn(TAG32, `Attempt grant on ${cardId} cannot start a run — ${turns.detail}. Clearing the attempt budget anyway; reassign the card to clear the turn count, or raise budget.maxTurnsPerCard.`);
|
|
14775
14350
|
try {
|
|
14776
14351
|
await this.client.addComment(cardId, `**Attempt budget cleared, but this card is at its turn cap.** ${turns.detail}, which is its ceiling (\`budget.maxTurnsPerCard\`), so I will not pick it up even with attempts restored.
|
|
14777
14352
|
|
|
14778
14353
|
Reassign the card — that clears the turn count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14779
14354
|
} catch (err) {
|
|
14780
|
-
|
|
14355
|
+
log36.warn(TAG32, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14781
14356
|
}
|
|
14782
14357
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14783
14358
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card turn cap.");
|
|
@@ -14785,13 +14360,13 @@ Reassign the card — that clears the turn count and starts it fresh — or rais
|
|
|
14785
14360
|
}
|
|
14786
14361
|
const runsSpent = this.budget.checkCardRuns(cardId);
|
|
14787
14362
|
if (!runsSpent.allow) {
|
|
14788
|
-
|
|
14363
|
+
log36.warn(TAG32, `Attempt grant on ${cardId} cannot start a run — ${runsSpent.detail}. Clearing the attempt budget anyway; reassign the card to clear the run count, or raise budget.maxRunsPerCard.`);
|
|
14789
14364
|
try {
|
|
14790
14365
|
await this.client.addComment(cardId, `**Attempt budget cleared, but this card is at its run backstop.** ${runsSpent.detail}, which is its ceiling (\`budget.maxRunsPerCard\`), so I will not pick it up even with attempts restored.
|
|
14791
14366
|
|
|
14792
14367
|
Reassign the card — that clears the run count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14793
14368
|
} catch (err) {
|
|
14794
|
-
|
|
14369
|
+
log36.warn(TAG32, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14795
14370
|
}
|
|
14796
14371
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14797
14372
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card run backstop.");
|
|
@@ -14806,7 +14381,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14806
14381
|
awaitingDecisionUntil: null
|
|
14807
14382
|
});
|
|
14808
14383
|
} catch (err) {
|
|
14809
|
-
|
|
14384
|
+
log36.warn(TAG32, `Failed to clear the attempt-cap decision deadline for ${cardId}: ${err}`);
|
|
14810
14385
|
}
|
|
14811
14386
|
await this.enqueueCard(cardId, "implement");
|
|
14812
14387
|
}
|
|
@@ -14817,7 +14392,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14817
14392
|
try {
|
|
14818
14393
|
await this.client.updateComment(blockerCommentId, { resolve: true });
|
|
14819
14394
|
} catch (err) {
|
|
14820
|
-
|
|
14395
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14821
14396
|
}
|
|
14822
14397
|
}
|
|
14823
14398
|
try {
|
|
@@ -14826,7 +14401,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14826
14401
|
awaitingDecisionUntil: null
|
|
14827
14402
|
});
|
|
14828
14403
|
} catch (err) {
|
|
14829
|
-
|
|
14404
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${cardId}: ${err}`);
|
|
14830
14405
|
}
|
|
14831
14406
|
try {
|
|
14832
14407
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14835,7 +14410,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14835
14410
|
failureSummary
|
|
14836
14411
|
});
|
|
14837
14412
|
} catch (err) {
|
|
14838
|
-
|
|
14413
|
+
log36.warn(TAG32, `Failed to end the attempt-cap session for ${cardId}: ${err}`);
|
|
14839
14414
|
}
|
|
14840
14415
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14841
14416
|
}
|
|
@@ -14866,12 +14441,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14866
14441
|
const subtasks = card.subtasks ?? [];
|
|
14867
14442
|
await this.enqueue(card, column, cardLabels, subtasks, mode);
|
|
14868
14443
|
} catch (err) {
|
|
14869
|
-
|
|
14444
|
+
log36.error(TAG32, `Failed to re-enqueue ${cardId} after a budget decision: ${err}`);
|
|
14870
14445
|
}
|
|
14871
14446
|
}
|
|
14872
14447
|
async retireUnroutableRuns(shortId, cardId, reason = "column not found on this board") {
|
|
14873
14448
|
const unended = this.stateStore.getRunsForCard(cardId).filter((r) => r.endedAt === null);
|
|
14874
|
-
|
|
14449
|
+
log36.warn(TAG32, `#${shortId}: ${reason} — retiring ${unended.length} run record(s) instead of re-enqueueing`);
|
|
14875
14450
|
for (const run of unended) {
|
|
14876
14451
|
await this.stateStore.endRun(run.runId, "orphaned", {
|
|
14877
14452
|
errorMessage: `${reason} at re-enqueue — retired as unroutable (#1057/#1102)`
|
|
@@ -14884,12 +14459,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14884
14459
|
if (this.shuttingDown)
|
|
14885
14460
|
return false;
|
|
14886
14461
|
if (this.accountGuard && !this.accountGuard.check().running) {
|
|
14887
|
-
|
|
14462
|
+
log36.debug(TAG32, `${label} dispatch held — the account is halted`);
|
|
14888
14463
|
return false;
|
|
14889
14464
|
}
|
|
14890
14465
|
const idle = workers.find((w) => w.isIdle);
|
|
14891
14466
|
if (!idle) {
|
|
14892
|
-
|
|
14467
|
+
log36.debug(TAG32, `No idle ${label} workers (queue: ${queue.length})`);
|
|
14893
14468
|
return false;
|
|
14894
14469
|
}
|
|
14895
14470
|
const next = queue.dequeue();
|
|
@@ -14897,18 +14472,18 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14897
14472
|
return false;
|
|
14898
14473
|
const data = this.cardDataCache.get(next.cardId);
|
|
14899
14474
|
if (!data) {
|
|
14900
|
-
|
|
14475
|
+
log36.warn(TAG32, `No cached data for card ${next.cardId}, skipping`);
|
|
14901
14476
|
return false;
|
|
14902
14477
|
}
|
|
14903
14478
|
this.cardDataCache.delete(next.cardId);
|
|
14904
14479
|
this.lastWaitingEmit.delete(next.cardId);
|
|
14905
|
-
|
|
14480
|
+
log36.info(TAG32, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
14906
14481
|
this.sleepGuard.acquire();
|
|
14907
14482
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
14908
14483
|
return true;
|
|
14909
14484
|
}
|
|
14910
14485
|
}
|
|
14911
|
-
var
|
|
14486
|
+
var TAG32 = "pool";
|
|
14912
14487
|
var init_pool = __esm(() => {
|
|
14913
14488
|
init_dist();
|
|
14914
14489
|
init_board_helpers();
|
|
@@ -14942,7 +14517,7 @@ import {
|
|
|
14942
14517
|
writeFileSync as writeFileSync4
|
|
14943
14518
|
} from "node:fs";
|
|
14944
14519
|
import { dirname as dirname5 } from "node:path";
|
|
14945
|
-
import { log as
|
|
14520
|
+
import { log as log37 } from "@gethmy/harness";
|
|
14946
14521
|
function defaultRegistryPath() {
|
|
14947
14522
|
return portRegistryPath();
|
|
14948
14523
|
}
|
|
@@ -14957,7 +14532,7 @@ function load(path) {
|
|
|
14957
14532
|
}
|
|
14958
14533
|
return emptyRegistry();
|
|
14959
14534
|
} catch (err) {
|
|
14960
|
-
|
|
14535
|
+
log37.warn(TAG33, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
14961
14536
|
return emptyRegistry();
|
|
14962
14537
|
}
|
|
14963
14538
|
}
|
|
@@ -14978,7 +14553,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
14978
14553
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
14979
14554
|
save(path, registry);
|
|
14980
14555
|
} catch (err) {
|
|
14981
|
-
|
|
14556
|
+
log37.warn(TAG33, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
14982
14557
|
}
|
|
14983
14558
|
}
|
|
14984
14559
|
function daemonBaseUrl(bindAddr, port) {
|
|
@@ -14998,17 +14573,17 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
14998
14573
|
delete registry[projectId];
|
|
14999
14574
|
save(path, registry);
|
|
15000
14575
|
} catch (err) {
|
|
15001
|
-
|
|
14576
|
+
log37.warn(TAG33, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
15002
14577
|
}
|
|
15003
14578
|
}
|
|
15004
|
-
var
|
|
14579
|
+
var TAG33 = "port-registry";
|
|
15005
14580
|
var init_port_registry = __esm(() => {
|
|
15006
14581
|
init_dist();
|
|
15007
14582
|
init_paths();
|
|
15008
14583
|
});
|
|
15009
14584
|
|
|
15010
14585
|
// src/recovery.ts
|
|
15011
|
-
import { log as
|
|
14586
|
+
import { log as log38, teardownWorktree as teardownWorktree3 } from "@gethmy/harness";
|
|
15012
14587
|
function isProcessAlive(pid, currentPid) {
|
|
15013
14588
|
if (pid === currentPid)
|
|
15014
14589
|
return true;
|
|
@@ -15024,7 +14599,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
15024
14599
|
const { card } = await client.getCard(cardId);
|
|
15025
14600
|
return card;
|
|
15026
14601
|
} catch (err) {
|
|
15027
|
-
|
|
14602
|
+
log38.warn(TAG34, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15028
14603
|
return null;
|
|
15029
14604
|
}
|
|
15030
14605
|
}
|
|
@@ -15034,7 +14609,7 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
15034
14609
|
return [];
|
|
15035
14610
|
}
|
|
15036
14611
|
const outcomes = [];
|
|
15037
|
-
|
|
14612
|
+
log38.info(TAG34, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
15038
14613
|
for (const run of active) {
|
|
15039
14614
|
const outcome = {
|
|
15040
14615
|
runId: run.runId,
|
|
@@ -15046,16 +14621,16 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
15046
14621
|
};
|
|
15047
14622
|
outcomes.push(outcome);
|
|
15048
14623
|
if (isBudgetHeldRun(run)) {
|
|
15049
|
-
|
|
14624
|
+
log38.info(TAG34, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15050
14625
|
outcome.actions.push("skipped: held for a human budget decision");
|
|
15051
14626
|
continue;
|
|
15052
14627
|
}
|
|
15053
14628
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
15054
|
-
|
|
14629
|
+
log38.warn(TAG34, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
15055
14630
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
15056
14631
|
continue;
|
|
15057
14632
|
}
|
|
15058
|
-
|
|
14633
|
+
log38.info(TAG34, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
15059
14634
|
await recoverRun(run, store, client, config, outcome, {
|
|
15060
14635
|
rollbackAttempt: true,
|
|
15061
14636
|
agentId: opts.agentId
|
|
@@ -15076,7 +14651,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15076
14651
|
} catch (err) {
|
|
15077
14652
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15078
14653
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
15079
|
-
|
|
14654
|
+
log38.warn(TAG34, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
15080
14655
|
}
|
|
15081
14656
|
const card = await fetchCardSafely(client, run.cardId);
|
|
15082
14657
|
if (card) {
|
|
@@ -15136,9 +14711,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15136
14711
|
outcome.errors.push(`decrementAttempt: ${msg}`);
|
|
15137
14712
|
}
|
|
15138
14713
|
}
|
|
15139
|
-
|
|
14714
|
+
log38.info(TAG34, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
15140
14715
|
}
|
|
15141
|
-
var
|
|
14716
|
+
var TAG34 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
15142
14717
|
var init_recovery = __esm(() => {
|
|
15143
14718
|
init_board_helpers();
|
|
15144
14719
|
init_handback();
|
|
@@ -15160,18 +14735,18 @@ var init_repo_dirs = __esm(() => {
|
|
|
15160
14735
|
});
|
|
15161
14736
|
|
|
15162
14737
|
// src/claim.ts
|
|
15163
|
-
import { log as
|
|
14738
|
+
import { log as log39 } from "@gethmy/harness";
|
|
15164
14739
|
async function claimUnassignedCard(client, cardId, agentId, opts) {
|
|
15165
14740
|
try {
|
|
15166
14741
|
const { claimed } = opts ? await client.claimCard(cardId, agentId, opts) : await client.claimCard(cardId, agentId);
|
|
15167
|
-
|
|
14742
|
+
log39.debug(TAG35, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
15168
14743
|
return claimed;
|
|
15169
14744
|
} catch (err) {
|
|
15170
|
-
|
|
14745
|
+
log39.error(TAG35, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
15171
14746
|
return false;
|
|
15172
14747
|
}
|
|
15173
14748
|
}
|
|
15174
|
-
var
|
|
14749
|
+
var TAG35 = "claim";
|
|
15175
14750
|
var init_claim = () => {};
|
|
15176
14751
|
|
|
15177
14752
|
// src/strand-recovery.ts
|
|
@@ -15179,7 +14754,7 @@ var exports_strand_recovery = {};
|
|
|
15179
14754
|
__export(exports_strand_recovery, {
|
|
15180
14755
|
reclaimPreReviewStrands: () => reclaimPreReviewStrands
|
|
15181
14756
|
});
|
|
15182
|
-
import { log as
|
|
14757
|
+
import { log as log40, resolvePrUrl as resolvePrUrl2 } from "@gethmy/harness";
|
|
15183
14758
|
async function reclaimPreReviewStrands(opts) {
|
|
15184
14759
|
const {
|
|
15185
14760
|
client,
|
|
@@ -15223,22 +14798,22 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15223
14798
|
continue;
|
|
15224
14799
|
const won = await claimUnassignedCard(client, card.id, agentId);
|
|
15225
14800
|
if (!won) {
|
|
15226
|
-
|
|
14801
|
+
log40.debug(TAG36, `#${card.short_id} — lost the review claim race, skipping`);
|
|
15227
14802
|
continue;
|
|
15228
14803
|
}
|
|
15229
|
-
|
|
14804
|
+
log40.warn(TAG36, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
15230
14805
|
reclaimed.push(card.id);
|
|
15231
14806
|
if (opts.onClaimed) {
|
|
15232
14807
|
try {
|
|
15233
14808
|
await opts.onClaimed(card);
|
|
15234
14809
|
} catch (err) {
|
|
15235
|
-
|
|
14810
|
+
log40.error(TAG36, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
15236
14811
|
}
|
|
15237
14812
|
}
|
|
15238
14813
|
}
|
|
15239
14814
|
return reclaimed;
|
|
15240
14815
|
}
|
|
15241
|
-
var
|
|
14816
|
+
var TAG36 = "strand-recovery";
|
|
15242
14817
|
var init_strand_recovery = __esm(() => {
|
|
15243
14818
|
init_board_helpers();
|
|
15244
14819
|
init_claim();
|
|
@@ -15404,7 +14979,7 @@ var init_scope = __esm(() => {
|
|
|
15404
14979
|
});
|
|
15405
14980
|
|
|
15406
14981
|
// src/sweep.ts
|
|
15407
|
-
import { log as
|
|
14982
|
+
import { log as log41 } from "@gethmy/harness";
|
|
15408
14983
|
function rankSweepCandidates(opts) {
|
|
15409
14984
|
const {
|
|
15410
14985
|
agentId,
|
|
@@ -15523,10 +15098,10 @@ async function sweepForCard(opts) {
|
|
|
15523
15098
|
let { candidates, skipped, scope } = rankSweepCandidates(opts);
|
|
15524
15099
|
const cohesionCounts = opts.ranking.cohesionWeight !== 0;
|
|
15525
15100
|
if (cohesionCounts) {
|
|
15526
|
-
|
|
15101
|
+
log41.debug(TAG37, describeActiveScope(scope.active));
|
|
15527
15102
|
}
|
|
15528
15103
|
if (candidates.length === 0) {
|
|
15529
|
-
|
|
15104
|
+
log41.debug(TAG37, `no claimable card (${skipped.length} skipped in a pickup column)`);
|
|
15530
15105
|
return { claimed: null, skipped, probed: 0 };
|
|
15531
15106
|
}
|
|
15532
15107
|
const probeWindow = candidates.slice(0, maxProbes);
|
|
@@ -15542,12 +15117,12 @@ async function sweepForCard(opts) {
|
|
|
15542
15117
|
chainNeighbors: chainNeighbors(chain2)
|
|
15543
15118
|
}));
|
|
15544
15119
|
} else if (chainTermsCount && candidates.length > 1) {
|
|
15545
|
-
|
|
15120
|
+
log41.debug(TAG37, `chain signals not ranked — maxProbesPerTick=${maxProbes} leaves nothing to reorder among ${candidates.length} candidates`);
|
|
15546
15121
|
}
|
|
15547
15122
|
let probed = 0;
|
|
15548
15123
|
for (const candidate of candidates) {
|
|
15549
15124
|
if (probed >= maxProbes) {
|
|
15550
|
-
|
|
15125
|
+
log41.debug(TAG37, `probe budget ${maxProbes} spent — ${candidates.length - probed} candidate(s) deferred to the next tick`);
|
|
15551
15126
|
break;
|
|
15552
15127
|
}
|
|
15553
15128
|
probed++;
|
|
@@ -15625,10 +15200,10 @@ async function sweepForCard(opts) {
|
|
|
15625
15200
|
continue;
|
|
15626
15201
|
}
|
|
15627
15202
|
verified.card.assigned_agent_id = agentId;
|
|
15628
|
-
|
|
15203
|
+
log41.info(TAG37, claimLine(verified, probed, candidates, skipped, cohesionCounts ? scope.active : null));
|
|
15629
15204
|
return { claimed: verified, skipped, probed };
|
|
15630
15205
|
}
|
|
15631
|
-
|
|
15206
|
+
log41.debug(TAG37, `claimed nothing this sweep (${probed} probed, ${skipped.length} skipped)`);
|
|
15632
15207
|
return { claimed: null, skipped, probed };
|
|
15633
15208
|
}
|
|
15634
15209
|
function claimLine(claimed, rank, candidates, skipped, scope) {
|
|
@@ -15649,7 +15224,7 @@ async function refetchCard(client, cardId) {
|
|
|
15649
15224
|
const { card } = await client.getCard(cardId);
|
|
15650
15225
|
return card ?? null;
|
|
15651
15226
|
} catch (err) {
|
|
15652
|
-
|
|
15227
|
+
log41.warn(TAG37, `re-read failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15653
15228
|
return null;
|
|
15654
15229
|
}
|
|
15655
15230
|
}
|
|
@@ -15658,11 +15233,11 @@ async function hasLiveSession(client, cardId) {
|
|
|
15658
15233
|
const { session } = await client.getAgentSession(cardId);
|
|
15659
15234
|
return session != null;
|
|
15660
15235
|
} catch (err) {
|
|
15661
|
-
|
|
15236
|
+
log41.warn(TAG37, `session lookup failed for ${cardId} — treating as busy: ${err instanceof Error ? err.message : err}`);
|
|
15662
15237
|
return true;
|
|
15663
15238
|
}
|
|
15664
15239
|
}
|
|
15665
|
-
var
|
|
15240
|
+
var TAG37 = "sweep";
|
|
15666
15241
|
var init_sweep = __esm(() => {
|
|
15667
15242
|
init_board_helpers();
|
|
15668
15243
|
init_claim();
|
|
@@ -15673,7 +15248,7 @@ var init_sweep = __esm(() => {
|
|
|
15673
15248
|
});
|
|
15674
15249
|
|
|
15675
15250
|
// src/sweep-commands.ts
|
|
15676
|
-
import { log as
|
|
15251
|
+
import { log as log42 } from "@gethmy/harness";
|
|
15677
15252
|
function parseSweepCommands(payload) {
|
|
15678
15253
|
if (!payload || typeof payload !== "object")
|
|
15679
15254
|
return [];
|
|
@@ -15700,14 +15275,14 @@ function parseSweepCommands(payload) {
|
|
|
15700
15275
|
async function claimSweepCommands(client, agentId, projectId, daemonId) {
|
|
15701
15276
|
const request = client.request?.bind(client);
|
|
15702
15277
|
if (typeof request !== "function") {
|
|
15703
|
-
|
|
15278
|
+
log42.warn(TAG38, "installed @gethmy/mcp has no generic request method — board sweep commands cannot be claimed; use `harmony-agent sweep stop|resume` until it is upgraded");
|
|
15704
15279
|
return [];
|
|
15705
15280
|
}
|
|
15706
15281
|
try {
|
|
15707
15282
|
const res = await request("POST", `/agents/${encodeURIComponent(agentId)}/sweep-commands/claim`, { projectId, daemonId });
|
|
15708
15283
|
return parseSweepCommands(res);
|
|
15709
15284
|
} catch (err) {
|
|
15710
|
-
|
|
15285
|
+
log42.warn(TAG38, `could not claim sweep commands: ${err instanceof Error ? err.message : err}`);
|
|
15711
15286
|
return [];
|
|
15712
15287
|
}
|
|
15713
15288
|
}
|
|
@@ -15720,15 +15295,15 @@ async function applySweepCommands(commands, actions) {
|
|
|
15720
15295
|
await actions.resume();
|
|
15721
15296
|
}
|
|
15722
15297
|
applied = command.verb;
|
|
15723
|
-
|
|
15298
|
+
log42.info(TAG38, `applied "${command.verb}" from the board (${command.id})`);
|
|
15724
15299
|
}
|
|
15725
15300
|
return applied;
|
|
15726
15301
|
}
|
|
15727
|
-
var
|
|
15302
|
+
var TAG38 = "sweep-cmd";
|
|
15728
15303
|
var init_sweep_commands = () => {};
|
|
15729
15304
|
|
|
15730
15305
|
// src/sweep-guard.ts
|
|
15731
|
-
import { log as
|
|
15306
|
+
import { log as log43 } from "@gethmy/harness";
|
|
15732
15307
|
|
|
15733
15308
|
class SweepGuard {
|
|
15734
15309
|
config;
|
|
@@ -15784,7 +15359,7 @@ class SweepGuard {
|
|
|
15784
15359
|
await this.store.haltSweep(reason);
|
|
15785
15360
|
if (!already) {
|
|
15786
15361
|
const claimed = this.store.getSweep().claimed;
|
|
15787
|
-
|
|
15362
|
+
log43.info(TAG39, `claiming stopped (${reason}) after ${claimed} claimed`);
|
|
15788
15363
|
}
|
|
15789
15364
|
return this.snapshot();
|
|
15790
15365
|
}
|
|
@@ -15793,7 +15368,7 @@ class SweepGuard {
|
|
|
15793
15368
|
const was = before.haltReason ? this.describeStop(before.haltReason) : null;
|
|
15794
15369
|
await this.store.resumeSweep();
|
|
15795
15370
|
if (was)
|
|
15796
|
-
|
|
15371
|
+
log43.info(TAG39, `claiming resumed by the operator — was stopped: ${was}`);
|
|
15797
15372
|
return this.snapshot();
|
|
15798
15373
|
}
|
|
15799
15374
|
snapshot() {
|
|
@@ -15855,11 +15430,11 @@ function sweepBannerLine(config) {
|
|
|
15855
15430
|
const authors = extra ? `you + ${extra} trusted author${extra === 1 ? "" : "s"}` : "you only";
|
|
15856
15431
|
return `Sweep ON — claims ${scope} in ${config.pickupColumns.join(", ")}, ` + `authored by ${authors}; caps: ${describeCaps(sweep, config.budget.dailyBudgetCents)}; ` + `${sweep.maxProbesPerTick} probes/tick`;
|
|
15857
15432
|
}
|
|
15858
|
-
var
|
|
15433
|
+
var TAG39 = "sweep";
|
|
15859
15434
|
var init_sweep_guard = () => {};
|
|
15860
15435
|
|
|
15861
15436
|
// src/reconcile.ts
|
|
15862
|
-
import { detectGitProvider as detectGitProvider5, log as
|
|
15437
|
+
import { detectGitProvider as detectGitProvider5, log as log44 } from "@gethmy/harness";
|
|
15863
15438
|
function daemonProcessLabels(approvedLabel) {
|
|
15864
15439
|
const names = ["agent", "agent-recovered", NEED_REVIEW_LABEL, approvedLabel];
|
|
15865
15440
|
return new Set(names.filter(Boolean).map((n) => n.toLowerCase()));
|
|
@@ -15922,7 +15497,7 @@ class Reconciler {
|
|
|
15922
15497
|
clearInterval(this.timer);
|
|
15923
15498
|
this.timer = null;
|
|
15924
15499
|
}
|
|
15925
|
-
|
|
15500
|
+
log44.info(TAG40, "Heartbeat stopped");
|
|
15926
15501
|
}
|
|
15927
15502
|
async recoverStaleRuns() {
|
|
15928
15503
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -15933,7 +15508,7 @@ class Reconciler {
|
|
|
15933
15508
|
const pool = this.pool;
|
|
15934
15509
|
for (const run of active) {
|
|
15935
15510
|
if (isBudgetHeldRun(run)) {
|
|
15936
|
-
|
|
15511
|
+
log44.info(TAG40, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15937
15512
|
continue;
|
|
15938
15513
|
}
|
|
15939
15514
|
const foreignDaemon = run.daemonPid !== process.pid;
|
|
@@ -15943,7 +15518,7 @@ class Reconciler {
|
|
|
15943
15518
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
15944
15519
|
continue;
|
|
15945
15520
|
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`;
|
|
15946
|
-
|
|
15521
|
+
log44.warn(TAG40, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
15947
15522
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
15948
15523
|
runId: run.runId,
|
|
15949
15524
|
cardId: run.cardId,
|
|
@@ -15970,11 +15545,11 @@ class Reconciler {
|
|
|
15970
15545
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
15971
15546
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
15972
15547
|
continue;
|
|
15973
|
-
|
|
15548
|
+
log44.warn(TAG40, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
15974
15549
|
try {
|
|
15975
15550
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
15976
15551
|
} catch (err) {
|
|
15977
|
-
|
|
15552
|
+
log44.error(TAG40, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
15978
15553
|
}
|
|
15979
15554
|
}
|
|
15980
15555
|
}
|
|
@@ -16006,7 +15581,7 @@ class Reconciler {
|
|
|
16006
15581
|
return;
|
|
16007
15582
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
16008
15583
|
const subtasks = card.subtasks ?? [];
|
|
16009
|
-
|
|
15584
|
+
log44.info(TAG40, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
16010
15585
|
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
16011
15586
|
}
|
|
16012
15587
|
});
|
|
@@ -16030,11 +15605,11 @@ class Reconciler {
|
|
|
16030
15605
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
16031
15606
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
16032
15607
|
continue;
|
|
16033
|
-
|
|
15608
|
+
log44.warn(TAG40, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
16034
15609
|
try {
|
|
16035
15610
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
16036
15611
|
} catch (err) {
|
|
16037
|
-
|
|
15612
|
+
log44.error(TAG40, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
16038
15613
|
}
|
|
16039
15614
|
}
|
|
16040
15615
|
}
|
|
@@ -16069,7 +15644,7 @@ class Reconciler {
|
|
|
16069
15644
|
}
|
|
16070
15645
|
this.lastSweepStopReported = null;
|
|
16071
15646
|
if (!this.pool.hasFreeImplementSlot()) {
|
|
16072
|
-
|
|
15647
|
+
log44.debug(TAG40, "sweep skipped — no free implement slot");
|
|
16073
15648
|
return;
|
|
16074
15649
|
}
|
|
16075
15650
|
const { claimed } = await sweepForCard({
|
|
@@ -16088,9 +15663,9 @@ class Reconciler {
|
|
|
16088
15663
|
reportSweepStopped(reason, detail) {
|
|
16089
15664
|
const line = `sweep stopped claiming — ${detail}`;
|
|
16090
15665
|
if (this.lastSweepStopReported === reason)
|
|
16091
|
-
|
|
15666
|
+
log44.debug(TAG40, line);
|
|
16092
15667
|
else
|
|
16093
|
-
|
|
15668
|
+
log44.warn(TAG40, line);
|
|
16094
15669
|
this.lastSweepStopReported = reason;
|
|
16095
15670
|
}
|
|
16096
15671
|
setSweepPresenceSink(sink) {
|
|
@@ -16128,7 +15703,7 @@ class Reconciler {
|
|
|
16128
15703
|
return;
|
|
16129
15704
|
this.lastSweepNudgeAt = now;
|
|
16130
15705
|
this.drainSweepCommands().catch((err) => {
|
|
16131
|
-
|
|
15706
|
+
log44.error(TAG40, `sweep command nudge failed: ${err instanceof Error ? err.message : err}`);
|
|
16132
15707
|
});
|
|
16133
15708
|
}
|
|
16134
15709
|
resolveRepoDirectories() {
|
|
@@ -16137,9 +15712,9 @@ class Reconciler {
|
|
|
16137
15712
|
const dirs = readRepoDirectories(process.cwd());
|
|
16138
15713
|
this.repoDirectories = dirs;
|
|
16139
15714
|
if (dirs.size === 0) {
|
|
16140
|
-
|
|
15715
|
+
log44.warn(TAG40, `Could not read top-level directories of ${process.cwd()} — sweep cohesion runs without its path signal`);
|
|
16141
15716
|
} else {
|
|
16142
|
-
|
|
15717
|
+
log44.debug(TAG40, `Path signal allows ${dirs.size} top-level directories`);
|
|
16143
15718
|
}
|
|
16144
15719
|
return dirs;
|
|
16145
15720
|
}
|
|
@@ -16175,7 +15750,7 @@ class Reconciler {
|
|
|
16175
15750
|
if (accountHalt !== this.lastAccountHoldReported) {
|
|
16176
15751
|
if (accountHalt) {
|
|
16177
15752
|
const unqueued = assignedCards.filter((c) => !knownCardIds.has(c.id)).length;
|
|
16178
|
-
|
|
15753
|
+
log44.warn(TAG40, `Account halted (${accountHalt}) — ${unqueued} assigned card(s) not queued; nothing is dispatched until \`harmony-agent resume\``);
|
|
16179
15754
|
}
|
|
16180
15755
|
this.lastAccountHoldReported = accountHalt;
|
|
16181
15756
|
}
|
|
@@ -16191,24 +15766,24 @@ class Reconciler {
|
|
|
16191
15766
|
const subtasks = card.subtasks ?? [];
|
|
16192
15767
|
const mode = route.mode;
|
|
16193
15768
|
if (route.stage) {
|
|
16194
|
-
|
|
15769
|
+
log44.info(TAG40, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
|
|
16195
15770
|
}
|
|
16196
15771
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
16197
|
-
|
|
15772
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
16198
15773
|
continue;
|
|
16199
15774
|
}
|
|
16200
15775
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
16201
|
-
|
|
15776
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
16202
15777
|
continue;
|
|
16203
15778
|
}
|
|
16204
15779
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
16205
|
-
|
|
15780
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
|
|
16206
15781
|
continue;
|
|
16207
15782
|
}
|
|
16208
15783
|
if (accountHalt) {
|
|
16209
|
-
|
|
15784
|
+
log44.debug(TAG40, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — holding, account halted (${accountHalt})`);
|
|
16210
15785
|
} else {
|
|
16211
|
-
|
|
15786
|
+
log44.info(TAG40, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
16212
15787
|
}
|
|
16213
15788
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
16214
15789
|
}
|
|
@@ -16219,13 +15794,13 @@ class Reconciler {
|
|
|
16219
15794
|
try {
|
|
16220
15795
|
await this.pool.drainBudgetDecisions();
|
|
16221
15796
|
} catch (err) {
|
|
16222
|
-
|
|
15797
|
+
log44.error(TAG40, `budget decisions were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16223
15798
|
}
|
|
16224
15799
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
16225
15800
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
16226
15801
|
for (const knownId of knownCardIds) {
|
|
16227
15802
|
if (!allAgentCardIds.has(knownId)) {
|
|
16228
|
-
|
|
15803
|
+
log44.info(TAG40, `Missed unassign: ${knownId} — removing`);
|
|
16229
15804
|
await this.pool.removeCard(knownId);
|
|
16230
15805
|
}
|
|
16231
15806
|
}
|
|
@@ -16233,20 +15808,20 @@ class Reconciler {
|
|
|
16233
15808
|
try {
|
|
16234
15809
|
await this.drainSweepCommands();
|
|
16235
15810
|
} catch (err) {
|
|
16236
|
-
|
|
15811
|
+
log44.error(TAG40, `sweep commands were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16237
15812
|
}
|
|
16238
15813
|
try {
|
|
16239
15814
|
await this.sweepForWork(cards, columns, labelMap, pickupConfig);
|
|
16240
15815
|
} catch (err) {
|
|
16241
|
-
|
|
15816
|
+
log44.error(TAG40, `sweep failed this tick: ${err instanceof Error ? err.message : err}`);
|
|
16242
15817
|
}
|
|
16243
|
-
|
|
15818
|
+
log44.debug(TAG40, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
16244
15819
|
} catch (err) {
|
|
16245
|
-
|
|
15820
|
+
log44.error(TAG40, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
16246
15821
|
}
|
|
16247
15822
|
}
|
|
16248
15823
|
}
|
|
16249
|
-
var
|
|
15824
|
+
var TAG40 = "reconcile", SWEEP_NUDGE_COOLDOWN_MS = 15000;
|
|
16250
15825
|
var init_reconcile = __esm(() => {
|
|
16251
15826
|
init_board_helpers();
|
|
16252
15827
|
init_recovery();
|
|
@@ -16266,7 +15841,7 @@ __export(exports_startup_banner, {
|
|
|
16266
15841
|
createStartupBanner: () => createStartupBanner,
|
|
16267
15842
|
contextSourceLine: () => contextSourceLine
|
|
16268
15843
|
});
|
|
16269
|
-
import { isPretty, log as
|
|
15844
|
+
import { isPretty, log as log45 } from "@gethmy/harness";
|
|
16270
15845
|
function createStartupBanner(config, version) {
|
|
16271
15846
|
return isPretty() ? prettyBanner(config, version) : jsonBanner(config, version);
|
|
16272
15847
|
}
|
|
@@ -16291,7 +15866,7 @@ function prettyBanner(config, version) {
|
|
|
16291
15866
|
checks.push({ kind: "ok", message });
|
|
16292
15867
|
},
|
|
16293
15868
|
warn(message) {
|
|
16294
|
-
|
|
15869
|
+
log45.warn(TAG41, message);
|
|
16295
15870
|
checks.push({ kind: "warn", message: message.split(`
|
|
16296
15871
|
`, 1)[0] });
|
|
16297
15872
|
},
|
|
@@ -16316,26 +15891,26 @@ function prettyBanner(config, version) {
|
|
|
16316
15891
|
};
|
|
16317
15892
|
}
|
|
16318
15893
|
function jsonBanner(config, version) {
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
15894
|
+
log45.info(TAG41, `Harmony Agent Daemon v${version} starting...`);
|
|
15895
|
+
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
15896
|
+
log45.info(TAG41, `Config: ${contextSourceLine(config)}`);
|
|
16322
15897
|
if (config.agent.review.enabled) {
|
|
16323
|
-
|
|
15898
|
+
log45.info(TAG41, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
16324
15899
|
}
|
|
16325
15900
|
let failed = false;
|
|
16326
15901
|
return {
|
|
16327
15902
|
setProjectName(_name) {},
|
|
16328
15903
|
setGitProvider(provider) {
|
|
16329
|
-
|
|
15904
|
+
log45.info(TAG41, `Git provider: ${provider}`);
|
|
16330
15905
|
},
|
|
16331
15906
|
setHttpPort(port) {
|
|
16332
|
-
|
|
15907
|
+
log45.info(TAG41, `HTTP server on port ${port}`);
|
|
16333
15908
|
},
|
|
16334
15909
|
check(message) {
|
|
16335
|
-
|
|
15910
|
+
log45.info(TAG41, message);
|
|
16336
15911
|
},
|
|
16337
15912
|
warn(message) {
|
|
16338
|
-
|
|
15913
|
+
log45.warn(TAG41, message);
|
|
16339
15914
|
},
|
|
16340
15915
|
fail() {
|
|
16341
15916
|
failed = true;
|
|
@@ -16343,7 +15918,7 @@ function jsonBanner(config, version) {
|
|
|
16343
15918
|
async ready(message) {
|
|
16344
15919
|
if (failed)
|
|
16345
15920
|
return;
|
|
16346
|
-
|
|
15921
|
+
log45.info(TAG41, message);
|
|
16347
15922
|
}
|
|
16348
15923
|
};
|
|
16349
15924
|
}
|
|
@@ -16436,7 +16011,7 @@ function cyan(s) {
|
|
|
16436
16011
|
function yellow(s) {
|
|
16437
16012
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
16438
16013
|
}
|
|
16439
|
-
var
|
|
16014
|
+
var TAG41 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
16440
16015
|
var init_startup_banner = __esm(() => {
|
|
16441
16016
|
init_port_registry();
|
|
16442
16017
|
ANSI = {
|
|
@@ -16457,7 +16032,7 @@ import {
|
|
|
16457
16032
|
writeFileSync as writeFileSync5
|
|
16458
16033
|
} from "node:fs";
|
|
16459
16034
|
import { dirname as dirname6, resolve as resolve2, sep as sep2 } from "node:path";
|
|
16460
|
-
import { log as
|
|
16035
|
+
import { log as log46 } from "@gethmy/harness";
|
|
16461
16036
|
function migrationMarkerPath(targetPath) {
|
|
16462
16037
|
return `${targetPath}.migrated`;
|
|
16463
16038
|
}
|
|
@@ -16531,7 +16106,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16531
16106
|
throw new Error("not an object");
|
|
16532
16107
|
legacy = parsed;
|
|
16533
16108
|
} catch (err) {
|
|
16534
|
-
|
|
16109
|
+
log46.warn(TAG42, `legacy state file unreadable — skipping the split this start; it retries on the next (${err instanceof Error ? err.message : err})`);
|
|
16535
16110
|
return NO_MIGRATION;
|
|
16536
16111
|
}
|
|
16537
16112
|
const runs = Array.isArray(legacy.runs) ? legacy.runs : [];
|
|
@@ -16553,13 +16128,13 @@ function migrateLegacyStateFile(opts) {
|
|
|
16553
16128
|
const legacyVersion = typeof legacy.version === "number" ? legacy.version : SCHEMA_VERSION;
|
|
16554
16129
|
const existing = existsSync5(targetPath) ? readTargetFile(targetPath) : null;
|
|
16555
16130
|
if (existsSync5(targetPath) && !existing) {
|
|
16556
|
-
|
|
16131
|
+
log46.warn(TAG42, "existing project state file unreadable — the split overwrites it");
|
|
16557
16132
|
}
|
|
16558
16133
|
let target;
|
|
16559
16134
|
if (existing) {
|
|
16560
16135
|
const existingVersion = typeof existing.version === "number" ? existing.version : SCHEMA_VERSION;
|
|
16561
16136
|
if (existingVersion !== legacyVersion) {
|
|
16562
|
-
|
|
16137
|
+
log46.warn(TAG42, `legacy state is schema v${legacyVersion} but the project store is v${existingVersion} — skipping the split`);
|
|
16563
16138
|
return NO_MIGRATION;
|
|
16564
16139
|
}
|
|
16565
16140
|
const existingRuns = Array.isArray(existing.runs) ? existing.runs : [];
|
|
@@ -16605,7 +16180,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16605
16180
|
if (!existsSync5(backupPath))
|
|
16606
16181
|
copyFileSync(legacyPath, backupPath);
|
|
16607
16182
|
} catch (err) {
|
|
16608
|
-
|
|
16183
|
+
log46.warn(TAG42, `could not back up the legacy state file (continuing): ${err instanceof Error ? err.message : err}`);
|
|
16609
16184
|
}
|
|
16610
16185
|
const remainingRuns = runs.filter((r) => !ownRunIds.has(r.runId));
|
|
16611
16186
|
const remainingCards = cards.filter((c) => !ownCardIds.has(c.cardId));
|
|
@@ -16629,10 +16204,10 @@ function migrateLegacyStateFile(opts) {
|
|
|
16629
16204
|
copiedIndeterminateCards: indeterminateCards.length,
|
|
16630
16205
|
foreignUnendedRuns: remainingRuns.filter((r) => r.endedAt === null).length
|
|
16631
16206
|
};
|
|
16632
|
-
|
|
16207
|
+
log46.info(TAG42, `split legacy state into ${targetPath}: moved ${result.movedRuns} run(s) ` + `(${result.movedUnendedRuns} unended) and ${result.movedCards} card record(s), ` + `copied ${result.copiedIndeterminateCards} unattributable card record(s); ` + `left ${remainingRuns.length} run(s) (${result.foreignUnendedRuns} unended) for other projects`);
|
|
16633
16208
|
return result;
|
|
16634
16209
|
}
|
|
16635
|
-
var
|
|
16210
|
+
var TAG42 = "state-migration", NO_MIGRATION;
|
|
16636
16211
|
var init_state_migration = __esm(() => {
|
|
16637
16212
|
init_dist();
|
|
16638
16213
|
init_state_store();
|
|
@@ -16739,7 +16314,7 @@ var init_stream_parser_selftest = __esm(() => {
|
|
|
16739
16314
|
|
|
16740
16315
|
// src/watcher.ts
|
|
16741
16316
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16742
|
-
import { isPretty as isPretty2, log as
|
|
16317
|
+
import { isPretty as isPretty2, log as log47 } from "@gethmy/harness";
|
|
16743
16318
|
import { createClient } from "@supabase/supabase-js";
|
|
16744
16319
|
function toPresenceColumns(names) {
|
|
16745
16320
|
return names.map((n) => n.trim()).filter((n) => n.length > 0).slice(0, MAX_PRESENCE_COLUMNS).map((n) => n.slice(0, MAX_PRESENCE_COLUMN_LENGTH));
|
|
@@ -16800,7 +16375,7 @@ class Watcher {
|
|
|
16800
16375
|
const stillHeld = this.supabase?.getChannels().some((c) => c.topic === old.topic) ?? false;
|
|
16801
16376
|
if (!stillHeld)
|
|
16802
16377
|
return true;
|
|
16803
|
-
|
|
16378
|
+
log47.warn(TAG43, `removeChannel(${old.topic}) returned "${status}" and the client still holds that topic — retrying the removal`);
|
|
16804
16379
|
return false;
|
|
16805
16380
|
}
|
|
16806
16381
|
constructor(credentials, projectId, identity, routing, onCardBroadcast, onAgentCommand, onSweepCommandNudge) {
|
|
@@ -16814,7 +16389,7 @@ class Watcher {
|
|
|
16814
16389
|
}
|
|
16815
16390
|
async start() {
|
|
16816
16391
|
if (!isPretty2()) {
|
|
16817
|
-
|
|
16392
|
+
log47.info(TAG43, "Connecting to Supabase realtime (broadcast)...");
|
|
16818
16393
|
}
|
|
16819
16394
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
16820
16395
|
this.subscribeBroadcast();
|
|
@@ -16852,7 +16427,7 @@ class Watcher {
|
|
|
16852
16427
|
status = `threw (${String(err)})`;
|
|
16853
16428
|
}
|
|
16854
16429
|
if (status !== "ok") {
|
|
16855
|
-
|
|
16430
|
+
log47.debug(TAG43, `Presence sweep update returned "${status}"`);
|
|
16856
16431
|
}
|
|
16857
16432
|
})();
|
|
16858
16433
|
}
|
|
@@ -16863,7 +16438,7 @@ class Watcher {
|
|
|
16863
16438
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
16864
16439
|
this.presenceChannel = presenceChannel;
|
|
16865
16440
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
16866
|
-
|
|
16441
|
+
log47.debug(TAG43, "Presence sync");
|
|
16867
16442
|
}).subscribe(async (status) => {
|
|
16868
16443
|
if (gen !== this.presenceGen)
|
|
16869
16444
|
return;
|
|
@@ -16879,13 +16454,13 @@ class Watcher {
|
|
|
16879
16454
|
if (trackStatus !== "ok") {
|
|
16880
16455
|
this.presenceTracked = false;
|
|
16881
16456
|
if (!this.stopping) {
|
|
16882
|
-
|
|
16457
|
+
log47.warn(TAG43, `Presence track returned "${trackStatus}" — scheduling reconnect`);
|
|
16883
16458
|
this.schedulePresenceReconnect();
|
|
16884
16459
|
}
|
|
16885
16460
|
return;
|
|
16886
16461
|
}
|
|
16887
16462
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16888
|
-
|
|
16463
|
+
log47.info(TAG43, "Presence tracked on board-presence channel");
|
|
16889
16464
|
}
|
|
16890
16465
|
this.presenceTracked = true;
|
|
16891
16466
|
this.presenceReconnectAttempts = 0;
|
|
@@ -16893,7 +16468,7 @@ class Watcher {
|
|
|
16893
16468
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16894
16469
|
this.presenceTracked = false;
|
|
16895
16470
|
if (!this.stopping) {
|
|
16896
|
-
|
|
16471
|
+
log47.warn(TAG43, `Presence subscription ${status} — scheduling reconnect`);
|
|
16897
16472
|
this.schedulePresenceReconnect();
|
|
16898
16473
|
}
|
|
16899
16474
|
}
|
|
@@ -16912,7 +16487,7 @@ class Watcher {
|
|
|
16912
16487
|
async reconnectPresence() {
|
|
16913
16488
|
if (this.stopping || !this.supabase)
|
|
16914
16489
|
return;
|
|
16915
|
-
|
|
16490
|
+
log47.warn(TAG43, `Reconnecting presence subscription (attempt ${this.presenceReconnectAttempts})`);
|
|
16916
16491
|
if (this.presenceChannel) {
|
|
16917
16492
|
const old = this.presenceChannel;
|
|
16918
16493
|
this.presenceChannel = null;
|
|
@@ -16935,13 +16510,13 @@ class Watcher {
|
|
|
16935
16510
|
return;
|
|
16936
16511
|
const gen = ++this.broadcastGen;
|
|
16937
16512
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
16938
|
-
|
|
16513
|
+
log47.debug(TAG43, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
16939
16514
|
this.onCardBroadcast({
|
|
16940
16515
|
event: "card_update",
|
|
16941
16516
|
payload: msg.payload ?? {}
|
|
16942
16517
|
});
|
|
16943
16518
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
16944
|
-
|
|
16519
|
+
log47.debug(TAG43, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
16945
16520
|
this.onCardBroadcast({
|
|
16946
16521
|
event: "card_created",
|
|
16947
16522
|
payload: msg.payload ?? {}
|
|
@@ -16951,11 +16526,11 @@ class Watcher {
|
|
|
16951
16526
|
const cardId = payload.card_id;
|
|
16952
16527
|
const command = payload.command;
|
|
16953
16528
|
if (cardId && command) {
|
|
16954
|
-
|
|
16529
|
+
log47.info(TAG43, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
16955
16530
|
this.onAgentCommand?.({ cardId, command });
|
|
16956
16531
|
}
|
|
16957
16532
|
}).on("broadcast", { event: "sweep_command" }, () => {
|
|
16958
|
-
|
|
16533
|
+
log47.debug(TAG43, "Broadcast: sweep_command — draining");
|
|
16959
16534
|
this.onSweepCommandNudge?.();
|
|
16960
16535
|
}).subscribe((status) => {
|
|
16961
16536
|
if (gen !== this.broadcastGen)
|
|
@@ -16964,13 +16539,13 @@ class Watcher {
|
|
|
16964
16539
|
this.connected = true;
|
|
16965
16540
|
this.reconnectAttempts = 0;
|
|
16966
16541
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16967
|
-
|
|
16542
|
+
log47.info(TAG43, "Broadcast subscription active");
|
|
16968
16543
|
}
|
|
16969
16544
|
this.maybeResolveReady();
|
|
16970
16545
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16971
16546
|
this.connected = false;
|
|
16972
16547
|
if (!this.stopping) {
|
|
16973
|
-
|
|
16548
|
+
log47.warn(TAG43, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
16974
16549
|
this.scheduleReconnect();
|
|
16975
16550
|
}
|
|
16976
16551
|
}
|
|
@@ -16989,7 +16564,7 @@ class Watcher {
|
|
|
16989
16564
|
async reconnectBroadcast() {
|
|
16990
16565
|
if (this.stopping || !this.supabase)
|
|
16991
16566
|
return;
|
|
16992
|
-
|
|
16567
|
+
log47.warn(TAG43, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
16993
16568
|
if (this.channel) {
|
|
16994
16569
|
const old = this.channel;
|
|
16995
16570
|
this.channel = null;
|
|
@@ -17031,10 +16606,10 @@ class Watcher {
|
|
|
17031
16606
|
}
|
|
17032
16607
|
this.connected = false;
|
|
17033
16608
|
this.presenceTracked = false;
|
|
17034
|
-
|
|
16609
|
+
log47.info(TAG43, "Broadcast subscription stopped");
|
|
17035
16610
|
}
|
|
17036
16611
|
}
|
|
17037
|
-
var
|
|
16612
|
+
var TAG43 = "watcher", MAX_PRESENCE_COLUMNS = 24, MAX_PRESENCE_COLUMN_LENGTH = 120;
|
|
17038
16613
|
var init_watcher = __esm(() => {
|
|
17039
16614
|
init_sweep_guard();
|
|
17040
16615
|
});
|
|
@@ -17308,7 +16883,7 @@ __export(exports_run_gc, {
|
|
|
17308
16883
|
pruneRunLogs: () => pruneRunLogs
|
|
17309
16884
|
});
|
|
17310
16885
|
import { unlinkSync as unlinkSync2 } from "node:fs";
|
|
17311
|
-
import { log as
|
|
16886
|
+
import { log as log48 } from "@gethmy/harness";
|
|
17312
16887
|
function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
17313
16888
|
let deleted = 0;
|
|
17314
16889
|
let bytesFreed = 0;
|
|
@@ -17322,7 +16897,7 @@ function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
|
17322
16897
|
deleted++;
|
|
17323
16898
|
bytesFreed += file.sizeBytes;
|
|
17324
16899
|
} catch (err) {
|
|
17325
|
-
|
|
16900
|
+
log48.debug(TAG44, `Could not remove ${file.path}: ${err instanceof Error ? err.message : err}`);
|
|
17326
16901
|
}
|
|
17327
16902
|
}
|
|
17328
16903
|
return { deleted, bytesFreed };
|
|
@@ -17345,7 +16920,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17345
16920
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
17346
16921
|
}
|
|
17347
16922
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
17348
|
-
|
|
16923
|
+
log48.info(TAG44, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
17349
16924
|
}
|
|
17350
16925
|
return {
|
|
17351
16926
|
runRecordsPurged,
|
|
@@ -17353,7 +16928,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17353
16928
|
logBytesFreed: logs.bytesFreed
|
|
17354
16929
|
};
|
|
17355
16930
|
}
|
|
17356
|
-
var
|
|
16931
|
+
var TAG44 = "gc", DAY_MS2 = 86400000;
|
|
17357
16932
|
var init_run_gc = __esm(() => {
|
|
17358
16933
|
init_paths();
|
|
17359
16934
|
init_run_stats();
|
|
@@ -17370,7 +16945,7 @@ __export(exports_worktree_gc, {
|
|
|
17370
16945
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
17371
16946
|
import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
17372
16947
|
import { resolve as resolve3 } from "node:path";
|
|
17373
|
-
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as
|
|
16948
|
+
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as log49 } from "@gethmy/harness";
|
|
17374
16949
|
function isTransientGitNetworkError(message) {
|
|
17375
16950
|
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
17376
16951
|
}
|
|
@@ -17483,10 +17058,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
17483
17058
|
});
|
|
17484
17059
|
} catch {}
|
|
17485
17060
|
if (result.removed.length > 0) {
|
|
17486
|
-
|
|
17061
|
+
log49.info(TAG45, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
17487
17062
|
}
|
|
17488
17063
|
if (result.errors.length > 0) {
|
|
17489
|
-
|
|
17064
|
+
log49.warn(TAG45, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
17490
17065
|
}
|
|
17491
17066
|
return result;
|
|
17492
17067
|
}
|
|
@@ -17516,7 +17091,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17516
17091
|
} catch (err) {
|
|
17517
17092
|
const detail = gitErrorDetail2(err);
|
|
17518
17093
|
if (isTransientGitNetworkError(detail)) {
|
|
17519
|
-
|
|
17094
|
+
log49.debug(TAG45, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
17520
17095
|
return result;
|
|
17521
17096
|
}
|
|
17522
17097
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -17556,7 +17131,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17556
17131
|
continue;
|
|
17557
17132
|
}
|
|
17558
17133
|
if (clock() > sweepDeadline) {
|
|
17559
|
-
|
|
17134
|
+
log49.debug(TAG45, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
17560
17135
|
break;
|
|
17561
17136
|
}
|
|
17562
17137
|
try {
|
|
@@ -17569,17 +17144,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17569
17144
|
} catch (err) {
|
|
17570
17145
|
const detail = gitErrorDetail2(err);
|
|
17571
17146
|
if (isTransientGitNetworkError(detail)) {
|
|
17572
|
-
|
|
17147
|
+
log49.debug(TAG45, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
17573
17148
|
break;
|
|
17574
17149
|
}
|
|
17575
17150
|
result.errors.push({ ref, error: detail });
|
|
17576
17151
|
}
|
|
17577
17152
|
}
|
|
17578
17153
|
if (result.removed.length > 0) {
|
|
17579
|
-
|
|
17154
|
+
log49.info(TAG45, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
17580
17155
|
}
|
|
17581
17156
|
if (result.errors.length > 0) {
|
|
17582
|
-
|
|
17157
|
+
log49.warn(TAG45, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
17583
17158
|
}
|
|
17584
17159
|
return result;
|
|
17585
17160
|
}
|
|
@@ -17612,18 +17187,18 @@ class WorktreeGc {
|
|
|
17612
17187
|
try {
|
|
17613
17188
|
runWorktreeGc(this.basePath, this.store);
|
|
17614
17189
|
} catch (err) {
|
|
17615
|
-
|
|
17190
|
+
log49.warn(TAG45, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17616
17191
|
}
|
|
17617
17192
|
if (this.remoteOpts) {
|
|
17618
17193
|
try {
|
|
17619
17194
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
17620
17195
|
} catch (err) {
|
|
17621
|
-
|
|
17196
|
+
log49.warn(TAG45, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17622
17197
|
}
|
|
17623
17198
|
}
|
|
17624
17199
|
if (this.retentionOpts) {
|
|
17625
17200
|
runRetentionGc(this.store, this.retentionOpts).catch((err) => {
|
|
17626
|
-
|
|
17201
|
+
log49.warn(TAG45, `Retention GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17627
17202
|
});
|
|
17628
17203
|
}
|
|
17629
17204
|
}
|
|
@@ -17637,7 +17212,7 @@ function getRepoRoot2() {
|
|
|
17637
17212
|
return null;
|
|
17638
17213
|
}
|
|
17639
17214
|
}
|
|
17640
|
-
var
|
|
17215
|
+
var TAG45 = "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;
|
|
17641
17216
|
var init_worktree_gc = __esm(() => {
|
|
17642
17217
|
init_run_gc();
|
|
17643
17218
|
GIT_NETWORK_EXEC = {
|
|
@@ -17678,7 +17253,7 @@ import { resolve as resolve4 } from "node:path";
|
|
|
17678
17253
|
import {
|
|
17679
17254
|
detectGitProvider as detectGitProvider6,
|
|
17680
17255
|
GIT_NO_HOOKS as GIT_NO_HOOKS10,
|
|
17681
|
-
log as
|
|
17256
|
+
log as log50,
|
|
17682
17257
|
validateGitProviderCli
|
|
17683
17258
|
} from "@gethmy/harness";
|
|
17684
17259
|
async function validatePrerequisites(config, banner) {
|
|
@@ -17755,7 +17330,7 @@ async function main() {
|
|
|
17755
17330
|
} catch (err) {
|
|
17756
17331
|
if (err instanceof ConfigValidationError) {
|
|
17757
17332
|
banner.fail();
|
|
17758
|
-
|
|
17333
|
+
log50.error(TAG46, err.message);
|
|
17759
17334
|
process.exit(1);
|
|
17760
17335
|
}
|
|
17761
17336
|
throw err;
|
|
@@ -17773,7 +17348,7 @@ async function main() {
|
|
|
17773
17348
|
} catch (err) {
|
|
17774
17349
|
if (err instanceof ConfigValidationError) {
|
|
17775
17350
|
banner.fail();
|
|
17776
|
-
|
|
17351
|
+
log50.error(TAG46, err.message);
|
|
17777
17352
|
process.exit(1);
|
|
17778
17353
|
}
|
|
17779
17354
|
throw err;
|
|
@@ -17797,7 +17372,7 @@ async function main() {
|
|
|
17797
17372
|
try {
|
|
17798
17373
|
await client.reportAgentConfig(config.workspaceId, agentId, effectiveModelReport(config.agent));
|
|
17799
17374
|
} catch (err) {
|
|
17800
|
-
|
|
17375
|
+
log50.warn(TAG46, `Could not report model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
17801
17376
|
}
|
|
17802
17377
|
migrateConfigDir();
|
|
17803
17378
|
try {
|
|
@@ -17807,7 +17382,7 @@ async function main() {
|
|
|
17807
17382
|
worktreePrefix: resolve4(config.repoRoot, config.agent.worktree.basePath)
|
|
17808
17383
|
});
|
|
17809
17384
|
} catch (err) {
|
|
17810
|
-
|
|
17385
|
+
log50.error(TAG46, `legacy state split failed — starting with the project store as-is: ${err instanceof Error ? err.message : err}`);
|
|
17811
17386
|
}
|
|
17812
17387
|
const stateStore = StateStore.open({ projectId: config.projectId });
|
|
17813
17388
|
const daemonId = randomUUID4();
|
|
@@ -17958,7 +17533,7 @@ async function main() {
|
|
|
17958
17533
|
if (shuttingDown)
|
|
17959
17534
|
return;
|
|
17960
17535
|
shuttingDown = true;
|
|
17961
|
-
|
|
17536
|
+
log50.info(TAG46, `Received ${signal}, shutting down gracefully...`);
|
|
17962
17537
|
reconciler.stop();
|
|
17963
17538
|
mergeMonitor?.stop();
|
|
17964
17539
|
worktreeGc.stop();
|
|
@@ -17969,19 +17544,19 @@ async function main() {
|
|
|
17969
17544
|
}
|
|
17970
17545
|
await watcher.stop();
|
|
17971
17546
|
await pool.shutdown();
|
|
17972
|
-
|
|
17547
|
+
log50.info(TAG46, "Daemon stopped.");
|
|
17973
17548
|
process.exit(exitCode);
|
|
17974
17549
|
};
|
|
17975
17550
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
17976
17551
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
17977
17552
|
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
17978
17553
|
process.on("uncaughtException", (err) => {
|
|
17979
|
-
|
|
17554
|
+
log50.error(TAG46, `Uncaught exception: ${err.message}`);
|
|
17980
17555
|
exitCode = 1;
|
|
17981
17556
|
shutdown("uncaughtException");
|
|
17982
17557
|
});
|
|
17983
17558
|
process.on("unhandledRejection", (reason) => {
|
|
17984
|
-
|
|
17559
|
+
log50.error(TAG46, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
17985
17560
|
exitCode = 1;
|
|
17986
17561
|
shutdown("unhandledRejection");
|
|
17987
17562
|
});
|
|
@@ -18041,33 +17616,33 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
18041
17616
|
if (assignedAgentId === undefined)
|
|
18042
17617
|
return;
|
|
18043
17618
|
if (assignedAgentId === agentId) {
|
|
18044
|
-
|
|
17619
|
+
log50.info(TAG46, `Broadcast: card ${cardId} assigned to agent`);
|
|
18045
17620
|
try {
|
|
18046
17621
|
await pool.resetAttemptsForReassign(cardId);
|
|
18047
17622
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
18048
17623
|
} catch (err) {
|
|
18049
|
-
|
|
17624
|
+
log50.error(TAG46, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
18050
17625
|
}
|
|
18051
17626
|
} else if (pool.isCardKnown(cardId)) {
|
|
18052
|
-
|
|
17627
|
+
log50.info(TAG46, `Broadcast: card ${cardId} unassigned from agent`);
|
|
18053
17628
|
await pool.removeCard(cardId);
|
|
18054
17629
|
}
|
|
18055
17630
|
}
|
|
18056
17631
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
18057
17632
|
const { card } = await client.getCard(cardId);
|
|
18058
17633
|
if (card.assigned_agent_id !== agentId) {
|
|
18059
|
-
|
|
17634
|
+
log50.debug(TAG46, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
18060
17635
|
return;
|
|
18061
17636
|
}
|
|
18062
17637
|
if (card.done || card.archived_at) {
|
|
18063
|
-
|
|
17638
|
+
log50.debug(TAG46, `Card ${cardId} is finished — skipping`);
|
|
18064
17639
|
return;
|
|
18065
17640
|
}
|
|
18066
17641
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
18067
17642
|
const columns = board.columns;
|
|
18068
17643
|
const column = columns.find((c) => c.id === card.column_id);
|
|
18069
17644
|
if (!column) {
|
|
18070
|
-
|
|
17645
|
+
log50.warn(TAG46, `Column not found for card ${cardId}`);
|
|
18071
17646
|
return;
|
|
18072
17647
|
}
|
|
18073
17648
|
const route = classifyPickup(card, column.name, {
|
|
@@ -18076,31 +17651,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
18076
17651
|
playbooks: config.agent.playbooks
|
|
18077
17652
|
});
|
|
18078
17653
|
if (!route) {
|
|
18079
|
-
|
|
17654
|
+
log50.info(TAG46, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
18080
17655
|
return;
|
|
18081
17656
|
}
|
|
18082
17657
|
if (route.stage) {
|
|
18083
|
-
|
|
17658
|
+
log50.info(TAG46, `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`);
|
|
18084
17659
|
}
|
|
18085
17660
|
const mode = route.mode;
|
|
18086
17661
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
18087
17662
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
18088
17663
|
const subtasks = card.subtasks ?? [];
|
|
18089
17664
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
18090
|
-
|
|
17665
|
+
log50.debug(TAG46, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
18091
17666
|
return;
|
|
18092
17667
|
}
|
|
18093
17668
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
18094
|
-
|
|
17669
|
+
log50.debug(TAG46, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
|
|
18095
17670
|
return;
|
|
18096
17671
|
}
|
|
18097
17672
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
18098
|
-
|
|
17673
|
+
log50.info(TAG46, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
|
|
18099
17674
|
return;
|
|
18100
17675
|
}
|
|
18101
17676
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
18102
17677
|
}
|
|
18103
|
-
var
|
|
17678
|
+
var TAG46 = "daemon", BASE_REMOTE = "origin", PKG_VERSION;
|
|
18104
17679
|
var init_src = __esm(() => {
|
|
18105
17680
|
init_account_guard();
|
|
18106
17681
|
init_account_probe();
|
|
@@ -18224,22 +17799,22 @@ function realOrResolved(path) {
|
|
|
18224
17799
|
var init_local_pin_migration = () => {};
|
|
18225
17800
|
|
|
18226
17801
|
// src/head-anchor.ts
|
|
18227
|
-
import { log as
|
|
17802
|
+
import { log as log51 } from "@gethmy/harness";
|
|
18228
17803
|
function sealProvenHead(sha, provenance) {
|
|
18229
17804
|
if (!/^[0-9a-f]{40}$/.test(sha)) {
|
|
18230
|
-
|
|
17805
|
+
log51.warn(TAG47, `refusing to seal "${sha}" as a head anchor (${provenance}) — an anchor is compared against the pull request's own head for equality, so an abbreviated or malformed sha would read as a mismatch rather than as the unreadable head it is`);
|
|
18231
17806
|
return;
|
|
18232
17807
|
}
|
|
18233
17808
|
return sha;
|
|
18234
17809
|
}
|
|
18235
|
-
var
|
|
17810
|
+
var TAG47 = "head-anchor";
|
|
18236
17811
|
var init_head_anchor = () => {};
|
|
18237
17812
|
|
|
18238
17813
|
// src/merge-cleanup.ts
|
|
18239
17814
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
18240
17815
|
import { existsSync as existsSync7, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
|
18241
17816
|
import { basename, dirname as dirname8, isAbsolute, resolve as resolve6, sep as sep4 } from "node:path";
|
|
18242
|
-
import { cleanupWorktree as cleanupWorktree6, GIT_NO_HOOKS as GIT_NO_HOOKS12, log as
|
|
17817
|
+
import { cleanupWorktree as cleanupWorktree6, GIT_NO_HOOKS as GIT_NO_HOOKS12, log as log52 } from "@gethmy/harness";
|
|
18243
17818
|
function canonicalize(p) {
|
|
18244
17819
|
const original = isAbsolute(p) ? p : `${process.cwd()}${sep4}${p}`;
|
|
18245
17820
|
try {
|
|
@@ -18314,7 +17889,7 @@ function linkedWorktreePaths(cwd) {
|
|
|
18314
17889
|
if (main2 !== null && records.some((r) => r.path === main2)) {
|
|
18315
17890
|
return records.filter((r) => r.path !== main2).map((r) => r.path);
|
|
18316
17891
|
}
|
|
18317
|
-
|
|
17892
|
+
log52.warn(TAG48, `could not identify the main working tree of ${cwd} by path (git derived ${main2 ?? "nothing"} from --git-common-dir, which names no worktree in the listing) — falling back to dropping the first record, which git documents as the main tree. A merge or cleanup that then refuses a worktree it should accept is this fallback, not a forged registration.`);
|
|
18318
17893
|
return records.slice(1).map((r) => r.path);
|
|
18319
17894
|
}
|
|
18320
17895
|
function sameDirectoryIgnoringCase(asked, registered) {
|
|
@@ -18345,18 +17920,18 @@ function isRegisteredWorktree(worktreePath, cwd) {
|
|
|
18345
17920
|
function cleanupMergedWorktree(args) {
|
|
18346
17921
|
if (!isRegisteredWorktree(args.worktreePath, args.cwd)) {
|
|
18347
17922
|
const reason = `refusing to clean up ${args.worktreePath} — git does not list it as a ` + `worktree of the repository at ${args.cwd}, and removing a path git ` + `does not manage would be a recursive delete of an unknown directory. ` + `Pass the worktree path Step B5 created, and re-run from the ` + `repository root.`;
|
|
18348
|
-
|
|
17923
|
+
log52.warn(TAG48, reason);
|
|
18349
17924
|
return { removed: false, reason };
|
|
18350
17925
|
}
|
|
18351
17926
|
if (cwdIsInsideWorktree(args.cwd, args.worktreePath) || cwdIsInsideWorktree(process.cwd(), args.worktreePath)) {
|
|
18352
17927
|
const reason = `refusing to clean up ${args.worktreePath} from inside it — the branch ` + `delete would run with a deleted cwd and fail silently. Re-run from the ` + `repository root.`;
|
|
18353
|
-
|
|
17928
|
+
log52.warn(TAG48, reason);
|
|
18354
17929
|
return { removed: false, reason };
|
|
18355
17930
|
}
|
|
18356
17931
|
cleanupWorktree6(args.worktreePath, args.branchName ?? undefined);
|
|
18357
17932
|
return { removed: !existsSync7(args.worktreePath) };
|
|
18358
17933
|
}
|
|
18359
|
-
var
|
|
17934
|
+
var TAG48 = "merge-cleanup";
|
|
18360
17935
|
var init_merge_cleanup = () => {};
|
|
18361
17936
|
|
|
18362
17937
|
// src/merge-command.ts
|
|
@@ -18371,7 +17946,7 @@ import { resolve as resolve7 } from "node:path";
|
|
|
18371
17946
|
import { promisify as promisify2 } from "node:util";
|
|
18372
17947
|
import {
|
|
18373
17948
|
GIT_NO_HOOKS as GIT_NO_HOOKS13,
|
|
18374
|
-
log as
|
|
17949
|
+
log as log53,
|
|
18375
17950
|
resolvePrHeadBranch as resolvePrHeadBranch3
|
|
18376
17951
|
} from "@gethmy/harness";
|
|
18377
17952
|
function buildFindingsBlock(items) {
|
|
@@ -18398,7 +17973,7 @@ async function gitRevParse(what, at) {
|
|
|
18398
17973
|
async function cwdIsRepositoryTopLevel(cwd) {
|
|
18399
17974
|
const top = await gitRevParse("--show-toplevel", cwd);
|
|
18400
17975
|
if (!top || realpathOr(top) !== realpathOr(cwd)) {
|
|
18401
|
-
|
|
17976
|
+
log53.warn(TAG49, `not reading a local head from ${cwd} — it is not the top level of a git checkout (git answered ${top ?? "nothing"}). Run merge from the repository root, or pass --worktree.`);
|
|
18402
17977
|
return false;
|
|
18403
17978
|
}
|
|
18404
17979
|
const gitDir = await gitRevParse("--git-dir", cwd);
|
|
@@ -18410,7 +17985,7 @@ async function cwdIsRepositoryTopLevel(cwd) {
|
|
|
18410
17985
|
}
|
|
18411
17986
|
if (isRegisteredWorktree(cwd, cwd))
|
|
18412
17987
|
return true;
|
|
18413
|
-
|
|
17988
|
+
log53.warn(TAG49, `not reading a local head from ${cwd} — git does not list it as a worktree of its own repository.`);
|
|
18414
17989
|
return false;
|
|
18415
17990
|
}
|
|
18416
17991
|
function realpathOr(p) {
|
|
@@ -18435,7 +18010,7 @@ async function readValidatedWorktreeHead(deps) {
|
|
|
18435
18010
|
const registration = classifyWorktreeRegistration(worktreePath, cwd);
|
|
18436
18011
|
if (registration.kind !== "registered") {
|
|
18437
18012
|
const detail = registration.kind === "case-mismatch" ? ` The worktree ${worktreePath} is the same directory as the registered ${registration.registered} and differs from it only in case, but git's registration carries the spelling above and this merge matches it exactly. Pass the registered spelling.` : registration.kind === "unanswerable" ? ` The worktree registrations of ${cwd} could not be listed at all, so ${worktreePath} could not be proved to be one of them.` : ` No validated worktree HEAD was available for ${worktreePath} — git does not list it as a worktree of the repository at ${cwd} — so the merge could not be bound to the head this run built and held for a person.`;
|
|
18438
|
-
|
|
18013
|
+
log53.warn(TAG49, `not reading a local head from ${worktreePath} (${registration.kind}).${detail} The merge will refuse: an interactive merge is only allowed for a head this run can prove it built.`);
|
|
18439
18014
|
return { sha: undefined, detail };
|
|
18440
18015
|
}
|
|
18441
18016
|
}
|
|
@@ -18449,7 +18024,7 @@ async function readValidatedWorktreeHead(deps) {
|
|
|
18449
18024
|
const raw = await gitRevParse("HEAD", readAt);
|
|
18450
18025
|
const sha = raw ? sealProvenHead(raw, worktreePath ? "registered-worktree" : "repository-top-level") : undefined;
|
|
18451
18026
|
if (!sha) {
|
|
18452
|
-
|
|
18027
|
+
log53.warn(TAG49, `could not read a HEAD sha from ${readAt} (git answered ${raw ? `"${raw}"` : "nothing"}) — the merge will refuse, since it can only run for a head this run can prove it built`);
|
|
18453
18028
|
return {
|
|
18454
18029
|
sha: undefined,
|
|
18455
18030
|
detail: ` No HEAD sha could be read from ${readAt}, so the merge could not be bound to the head this run built and held for a person.`
|
|
@@ -18458,14 +18033,14 @@ async function readValidatedWorktreeHead(deps) {
|
|
|
18458
18033
|
if (worktreePath) {
|
|
18459
18034
|
const resolved = await resolvePrHeadBranch3(deps.prUrl, cwd, deps.provider);
|
|
18460
18035
|
if (resolved.kind === "skip") {
|
|
18461
|
-
|
|
18036
|
+
log53.warn(TAG49, `not corroborating ${worktreePath} against the head branch of ${deps.prUrl}: ${resolved.reason}. The anchor stands on the head sha alone.`);
|
|
18462
18037
|
} else {
|
|
18463
18038
|
const branch = await checkedOutBranch(worktreePath);
|
|
18464
18039
|
if (branch === null) {
|
|
18465
|
-
|
|
18040
|
+
log53.warn(TAG49, `${worktreePath} has no branch checked out (detached HEAD), so it could not be corroborated against ${resolved.branch}. The anchor stands on the head sha alone.`);
|
|
18466
18041
|
} else if (branch !== resolved.branch) {
|
|
18467
18042
|
const detail = ` The worktree ${worktreePath} is on "${branch}", and ${deps.prUrl} has "${resolved.branch}" as its head branch — so that worktree is not where this pull request was built. --worktree is composed from a model's memory of the Step B5 path, which is why it is checked against the pull request and not only against git.`;
|
|
18468
|
-
|
|
18043
|
+
log53.warn(TAG49, `refusing the anchor from ${worktreePath}.${detail}`);
|
|
18469
18044
|
return { sha: undefined, detail };
|
|
18470
18045
|
}
|
|
18471
18046
|
}
|
|
@@ -18515,7 +18090,7 @@ async function runMergeCommand(deps) {
|
|
|
18515
18090
|
});
|
|
18516
18091
|
} catch (err) {
|
|
18517
18092
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18518
|
-
|
|
18093
|
+
log53.warn(TAG49, `merge attempt failed: ${detail}`);
|
|
18519
18094
|
deps.emit({
|
|
18520
18095
|
type: "parked",
|
|
18521
18096
|
reason: `the merge attempt failed: ${detail}`
|
|
@@ -18536,7 +18111,7 @@ async function runMergeCommand(deps) {
|
|
|
18536
18111
|
if (outcome === "report-findings") {
|
|
18537
18112
|
const reported = findings[0];
|
|
18538
18113
|
if (!reported) {
|
|
18539
|
-
|
|
18114
|
+
log53.warn(TAG49, "the merge reported findings but none arrived through the callback — parking rather than reporting an empty list");
|
|
18540
18115
|
deps.emit({
|
|
18541
18116
|
type: "parked",
|
|
18542
18117
|
reason: "the independent review posted blocking findings, but they could not be reported — the card stays in Review for a person"
|
|
@@ -18586,7 +18161,7 @@ async function branchRefGone(branchName, cwd) {
|
|
|
18586
18161
|
const stderr = String(failure.stderr ?? "").trim();
|
|
18587
18162
|
if (failure.code === 1 && !stderr)
|
|
18588
18163
|
return true;
|
|
18589
|
-
|
|
18164
|
+
log53.warn(TAG49, `could not determine whether ${branchName} still exists (code=${String(failure.code)}${stderr ? `, ${stderr}` : ""}) — reporting it as possibly present`);
|
|
18590
18165
|
return false;
|
|
18591
18166
|
}
|
|
18592
18167
|
}
|
|
@@ -18597,7 +18172,7 @@ async function finish(deps) {
|
|
|
18597
18172
|
const resolved = await resolvePrHeadBranch3(deps.prUrl, deps.cwd, deps.provider);
|
|
18598
18173
|
const branch = resolved.kind === "branch" ? resolved.branch : null;
|
|
18599
18174
|
if (resolved.kind === "skip") {
|
|
18600
|
-
|
|
18175
|
+
log53.warn(TAG49, `no head branch for ${deps.prUrl}: ${resolved.reason}`);
|
|
18601
18176
|
}
|
|
18602
18177
|
const out = cleanupMergedWorktree({
|
|
18603
18178
|
worktreePath,
|
|
@@ -18606,12 +18181,12 @@ async function finish(deps) {
|
|
|
18606
18181
|
});
|
|
18607
18182
|
if (!out.removed) {
|
|
18608
18183
|
const reason = out.reason ?? "the worktree directory still exists after cleanup";
|
|
18609
|
-
|
|
18184
|
+
log53.warn(TAG49, `cleanup refused for ${worktreePath}: ${reason}`);
|
|
18610
18185
|
deps.emit({ type: "cleanup_refused", worktree: worktreePath, reason });
|
|
18611
18186
|
} else {
|
|
18612
18187
|
const branchGone = branch ? await branchRefGone(branch, deps.cwd) : true;
|
|
18613
18188
|
if (branch && !branchGone) {
|
|
18614
|
-
|
|
18189
|
+
log53.warn(TAG49, `local branch ${branch} may still exist after cleanup`);
|
|
18615
18190
|
}
|
|
18616
18191
|
deps.emit({
|
|
18617
18192
|
type: "cleaned",
|
|
@@ -18621,7 +18196,7 @@ async function finish(deps) {
|
|
|
18621
18196
|
}
|
|
18622
18197
|
} catch (err) {
|
|
18623
18198
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18624
|
-
|
|
18199
|
+
log53.warn(TAG49, `worktree cleanup failed for ${worktreePath}: ${detail}`);
|
|
18625
18200
|
deps.emit({
|
|
18626
18201
|
type: "cleanup_refused",
|
|
18627
18202
|
worktree: worktreePath,
|
|
@@ -18639,16 +18214,16 @@ async function finish(deps) {
|
|
|
18639
18214
|
});
|
|
18640
18215
|
if (!out.moved) {
|
|
18641
18216
|
const reason = out.reason ?? "the card could not be moved out of Review";
|
|
18642
|
-
|
|
18217
|
+
log53.warn(TAG49, `board not updated after merge: ${reason}`);
|
|
18643
18218
|
deps.emit({ type: "board_incomplete", reason });
|
|
18644
18219
|
}
|
|
18645
18220
|
} catch (err) {
|
|
18646
18221
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18647
|
-
|
|
18222
|
+
log53.warn(TAG49, `board not updated after merge: ${detail}`);
|
|
18648
18223
|
deps.emit({ type: "board_incomplete", reason: detail });
|
|
18649
18224
|
}
|
|
18650
18225
|
}
|
|
18651
|
-
var
|
|
18226
|
+
var TAG49 = "merge-command", POLL_INTERVAL_MS = 30000, execFileAsync2, KEEP_WAITING, PARK_REASONS;
|
|
18652
18227
|
var init_merge_command = __esm(() => {
|
|
18653
18228
|
init_dist();
|
|
18654
18229
|
init_auto_merge();
|
|
@@ -18787,9 +18362,10 @@ var init_scan_commands = __esm(() => {
|
|
|
18787
18362
|
});
|
|
18788
18363
|
|
|
18789
18364
|
// src/cli.ts
|
|
18790
|
-
import { realpathSync as realpathSync4 } from "node:fs";
|
|
18365
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync4 } from "node:fs";
|
|
18366
|
+
import { dirname as dirname9, join as join10 } from "node:path";
|
|
18791
18367
|
import { fileURLToPath } from "node:url";
|
|
18792
|
-
import { log as
|
|
18368
|
+
import { log as log54 } from "@gethmy/harness";
|
|
18793
18369
|
var USAGE = `
|
|
18794
18370
|
Harmony Agent — push-based daemon + ops toolkit.
|
|
18795
18371
|
|
|
@@ -18831,6 +18407,11 @@ Usage:
|
|
|
18831
18407
|
Search across run logs
|
|
18832
18408
|
harmony-agent stats [--json] Aggregate cost/turns/failure bottlenecks
|
|
18833
18409
|
harmony-agent help Show this help
|
|
18410
|
+
harmony-agent --version Print the package version AND the resolved
|
|
18411
|
+
entry point. Both, because a command that
|
|
18412
|
+
resolves can still be an old build: an npx
|
|
18413
|
+
cache freezes what it first installed, so the
|
|
18414
|
+
path is what says WHICH build answered.
|
|
18834
18415
|
|
|
18835
18416
|
Flags:
|
|
18836
18417
|
--pretty Force colored human log output
|
|
@@ -19033,6 +18614,35 @@ async function fixLocalPinCommand(cwd) {
|
|
|
19033
18614
|
return 1;
|
|
19034
18615
|
}
|
|
19035
18616
|
}
|
|
18617
|
+
async function reportBuildFreshness(version) {
|
|
18618
|
+
process.stderr.write(`preflight: @gethmy/agent ${version}
|
|
18619
|
+
`);
|
|
18620
|
+
try {
|
|
18621
|
+
process.stderr.write(`preflight: running ${fileURLToPath(import.meta.url)}
|
|
18622
|
+
`);
|
|
18623
|
+
} catch {}
|
|
18624
|
+
const { execFile: execFile3 } = await import("node:child_process");
|
|
18625
|
+
const { promisify: promisify3 } = await import("node:util");
|
|
18626
|
+
let published;
|
|
18627
|
+
try {
|
|
18628
|
+
const { stdout } = await promisify3(execFile3)("npm", ["view", "@gethmy/agent", "version"], { timeout: 1e4 });
|
|
18629
|
+
published = stdout.trim();
|
|
18630
|
+
} catch (err) {
|
|
18631
|
+
process.stderr.write(`preflight: could not check npm for a newer build (${err instanceof Error ? err.message.split(`
|
|
18632
|
+
`)[0] : String(err)})
|
|
18633
|
+
`);
|
|
18634
|
+
return;
|
|
18635
|
+
}
|
|
18636
|
+
if (!/^\d+\.\d+\.\d+/.test(published)) {
|
|
18637
|
+
process.stderr.write(`preflight: could not check npm for a newer build (unparseable answer ${JSON.stringify(published)})
|
|
18638
|
+
`);
|
|
18639
|
+
return;
|
|
18640
|
+
}
|
|
18641
|
+
if (published === version)
|
|
18642
|
+
return;
|
|
18643
|
+
process.stderr.write(`preflight: npm serves ${published}. If that is NEWER than the build above, this one is stale — ` + `\`npm i -g @gethmy/agent@latest\`, or clear the npx cache (\`rm -rf ~/.npm/_npx\`), because \`@latest\` alone does not re-resolve it.
|
|
18644
|
+
`);
|
|
18645
|
+
}
|
|
19036
18646
|
async function doctorCommand() {
|
|
19037
18647
|
const { loadDaemonConfig: loadDaemonConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
19038
18648
|
const { validatePrerequisites: validatePrerequisites2 } = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
@@ -19061,6 +18671,7 @@ async function doctorCommand() {
|
|
|
19061
18671
|
validateRetentionConfig2(config.agent);
|
|
19062
18672
|
validateVerificationConfig2(config.agent);
|
|
19063
18673
|
validateHttpConfig2(config.agent);
|
|
18674
|
+
await reportBuildFreshness(version);
|
|
19064
18675
|
const banner = createStartupBanner2(config, version);
|
|
19065
18676
|
const userId = await validatePrerequisites2(config, banner);
|
|
19066
18677
|
await banner.ready(`preflight ok — agent user ${userId}`);
|
|
@@ -19369,8 +18980,8 @@ async function runsShowCommand(rest) {
|
|
|
19369
18980
|
`);
|
|
19370
18981
|
let content;
|
|
19371
18982
|
try {
|
|
19372
|
-
const { readFileSync:
|
|
19373
|
-
content =
|
|
18983
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
18984
|
+
content = readFileSync12(logFile.path, "utf-8");
|
|
19374
18985
|
} catch (err) {
|
|
19375
18986
|
process.stderr.write(`could not read log: ${err instanceof Error ? err.message : err}
|
|
19376
18987
|
`);
|
|
@@ -19547,6 +19158,11 @@ async function dispatch(argv) {
|
|
|
19547
19158
|
return runsCommand(args.slice(1));
|
|
19548
19159
|
case "stats":
|
|
19549
19160
|
return statsCommand();
|
|
19161
|
+
case "--version":
|
|
19162
|
+
case "-v":
|
|
19163
|
+
process.stdout.write(`${versionReport(import.meta.url)}
|
|
19164
|
+
`);
|
|
19165
|
+
return 0;
|
|
19550
19166
|
case "help":
|
|
19551
19167
|
case "--help":
|
|
19552
19168
|
case "-h":
|
|
@@ -19560,6 +19176,31 @@ ${USAGE}
|
|
|
19560
19176
|
return 2;
|
|
19561
19177
|
}
|
|
19562
19178
|
}
|
|
19179
|
+
function versionReport(moduleUrl) {
|
|
19180
|
+
let entry = moduleUrl;
|
|
19181
|
+
try {
|
|
19182
|
+
entry = fileURLToPath(moduleUrl);
|
|
19183
|
+
} catch {
|
|
19184
|
+
return `unknown
|
|
19185
|
+
${moduleUrl}`;
|
|
19186
|
+
}
|
|
19187
|
+
let dir = dirname9(entry);
|
|
19188
|
+
for (let up = 0;up < 4; up++) {
|
|
19189
|
+
try {
|
|
19190
|
+
const manifest = JSON.parse(readFileSync11(join10(dir, "package.json"), "utf8"));
|
|
19191
|
+
if (manifest.version) {
|
|
19192
|
+
return `${manifest.name ?? "unknown"} ${manifest.version}
|
|
19193
|
+
${entry}`;
|
|
19194
|
+
}
|
|
19195
|
+
} catch {}
|
|
19196
|
+
const parent = dirname9(dir);
|
|
19197
|
+
if (parent === dir)
|
|
19198
|
+
break;
|
|
19199
|
+
dir = parent;
|
|
19200
|
+
}
|
|
19201
|
+
return `unknown
|
|
19202
|
+
${entry}`;
|
|
19203
|
+
}
|
|
19563
19204
|
function isMainModule() {
|
|
19564
19205
|
const entry = process.argv[1];
|
|
19565
19206
|
if (!entry)
|
|
@@ -19575,11 +19216,12 @@ if (isMainModule()) {
|
|
|
19575
19216
|
if (code !== 0)
|
|
19576
19217
|
process.exit(code);
|
|
19577
19218
|
}).catch((err) => {
|
|
19578
|
-
|
|
19219
|
+
log54.error("cli", err instanceof Error ? err.message : String(err));
|
|
19579
19220
|
process.exit(1);
|
|
19580
19221
|
});
|
|
19581
19222
|
}
|
|
19582
19223
|
export {
|
|
19224
|
+
versionReport,
|
|
19583
19225
|
statsCommand,
|
|
19584
19226
|
runsShowCommand,
|
|
19585
19227
|
runsListCommand,
|