@gethmy/agent 1.41.0 → 1.43.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 +676 -997
- package/dist/index.js +578 -966
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -860,6 +860,9 @@ function isDaemonAuthoredComment(comment, identity) {
|
|
|
860
860
|
return false;
|
|
861
861
|
return true;
|
|
862
862
|
}
|
|
863
|
+
// ../harmony-shared/dist/agentPauseAccounting.js
|
|
864
|
+
var init_agentPauseAccounting = () => {};
|
|
865
|
+
|
|
863
866
|
// ../harmony-shared/dist/agentStaleness.js
|
|
864
867
|
function isPoolNotice(session) {
|
|
865
868
|
return !!session && session.driver === NOTICE_DRIVER && !session.agent_id;
|
|
@@ -873,6 +876,12 @@ var init_agentStaleness = __esm(() => {
|
|
|
873
876
|
AGENT_SWEEP_PAUSED_MS = 4 * 60 * 60 * 1000;
|
|
874
877
|
ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
|
|
875
878
|
});
|
|
879
|
+
|
|
880
|
+
// ../harmony-shared/dist/agentRunControl.js
|
|
881
|
+
var init_agentRunControl = __esm(() => {
|
|
882
|
+
init_agentPauseAccounting();
|
|
883
|
+
init_agentStaleness();
|
|
884
|
+
});
|
|
876
885
|
// ../harmony-shared/dist/branchRef.js
|
|
877
886
|
function extractBranchRef(description) {
|
|
878
887
|
if (!description)
|
|
@@ -2433,6 +2442,9 @@ var init_stageHandoff = __esm(() => {
|
|
|
2433
2442
|
HANDOFF_BLOCK_RE = new RegExp("```json\\s*\\n//\\s*" + HANDOFF_MARKER + "\\s*\\n([\\s\\S]*?)\\n```", "m");
|
|
2434
2443
|
});
|
|
2435
2444
|
|
|
2445
|
+
// ../harmony-shared/dist/sweepPresence.js
|
|
2446
|
+
var init_sweepPresence = () => {};
|
|
2447
|
+
|
|
2436
2448
|
// ../harmony-shared/dist/types.js
|
|
2437
2449
|
var init_types = () => {};
|
|
2438
2450
|
|
|
@@ -2468,6 +2480,8 @@ function untrustedDataBlock(text, options) {
|
|
|
2468
2480
|
|
|
2469
2481
|
// ../harmony-shared/dist/index.js
|
|
2470
2482
|
var init_dist = __esm(() => {
|
|
2483
|
+
init_agentPauseAccounting();
|
|
2484
|
+
init_agentRunControl();
|
|
2471
2485
|
init_agentStaleness();
|
|
2472
2486
|
init_branchRef();
|
|
2473
2487
|
init_cardLifecycle();
|
|
@@ -2490,6 +2504,7 @@ var init_dist = __esm(() => {
|
|
|
2490
2504
|
init_reviewTools();
|
|
2491
2505
|
init_runRedaction();
|
|
2492
2506
|
init_stageHandoff();
|
|
2507
|
+
init_sweepPresence();
|
|
2493
2508
|
init_types();
|
|
2494
2509
|
});
|
|
2495
2510
|
|
|
@@ -6511,332 +6526,20 @@ class ReportedConfigCache {
|
|
|
6511
6526
|
var TAG17 = "reported-config";
|
|
6512
6527
|
var init_reported_config_cache = () => {};
|
|
6513
6528
|
|
|
6514
|
-
// src/episode-writer.ts
|
|
6515
|
-
import { log as log19 } from "@gethmy/harness";
|
|
6516
|
-
function computeQualityScore(result, opts) {
|
|
6517
|
-
if (!result.passed)
|
|
6518
|
-
return 0;
|
|
6519
|
-
let score = 0;
|
|
6520
|
-
if (result.buildErrors.length === 0)
|
|
6521
|
-
score += 0.4;
|
|
6522
|
-
if (result.lintWarnings.length === 0)
|
|
6523
|
-
score += 0.2;
|
|
6524
|
-
if (!opts.errorThrown)
|
|
6525
|
-
score += 0.2;
|
|
6526
|
-
if (opts.runCompletedCleanly)
|
|
6527
|
-
score += 0.2;
|
|
6528
|
-
return Math.min(1, score);
|
|
6529
|
-
}
|
|
6530
|
-
function clampConfidence(qualityScore) {
|
|
6531
|
-
return Math.max(0.4, Math.min(1, qualityScore));
|
|
6532
|
-
}
|
|
6533
|
-
function trimApproachSummary(text) {
|
|
6534
|
-
const trimmed = text.trim();
|
|
6535
|
-
if (trimmed.length === 0)
|
|
6536
|
-
return "(no approach summary captured)";
|
|
6537
|
-
if (trimmed.length <= MAX_APPROACH_SUMMARY_CHARS)
|
|
6538
|
-
return trimmed;
|
|
6539
|
-
return `${trimmed.slice(0, MAX_APPROACH_SUMMARY_CHARS - 1).trimEnd()}…`;
|
|
6540
|
-
}
|
|
6541
|
-
function trimReviewRationale(text) {
|
|
6542
|
-
const trimmed = text.trim();
|
|
6543
|
-
if (trimmed.length === 0)
|
|
6544
|
-
return "(no review rationale captured)";
|
|
6545
|
-
if (trimmed.length <= MAX_REVIEW_RATIONALE_CHARS)
|
|
6546
|
-
return trimmed;
|
|
6547
|
-
return `${trimmed.slice(0, MAX_REVIEW_RATIONALE_CHARS - 1).trimEnd()}…`;
|
|
6548
|
-
}
|
|
6549
|
-
function buildRichApproachSummary(blocks, fallback) {
|
|
6550
|
-
const cleaned = (blocks ?? []).map((b) => b.trim()).filter((b) => b.length > 0);
|
|
6551
|
-
if (cleaned.length === 0)
|
|
6552
|
-
return trimApproachSummary(fallback);
|
|
6553
|
-
const picked = [];
|
|
6554
|
-
let total = 0;
|
|
6555
|
-
for (let i = cleaned.length - 1;i >= 0; i--) {
|
|
6556
|
-
const block = cleaned[i];
|
|
6557
|
-
const cost = block.length + (picked.length > 0 ? 2 : 0);
|
|
6558
|
-
if (total + cost > MAX_RICH_APPROACH_CHARS && picked.length > 0)
|
|
6559
|
-
break;
|
|
6560
|
-
picked.unshift(block);
|
|
6561
|
-
total += cost;
|
|
6562
|
-
if (total >= MAX_RICH_APPROACH_CHARS)
|
|
6563
|
-
break;
|
|
6564
|
-
}
|
|
6565
|
-
const joined = picked.join(`
|
|
6566
|
-
|
|
6567
|
-
`).trim();
|
|
6568
|
-
if (joined.length === 0)
|
|
6569
|
-
return trimApproachSummary(fallback);
|
|
6570
|
-
if (joined.length <= MAX_RICH_APPROACH_CHARS)
|
|
6571
|
-
return joined;
|
|
6572
|
-
return `${joined.slice(0, MAX_RICH_APPROACH_CHARS - 1).trimEnd()}…`;
|
|
6573
|
-
}
|
|
6574
|
-
function extractKeyInsight(blocks, fallback) {
|
|
6575
|
-
const source = blocks && blocks.length > 0 ? blocks.join(`
|
|
6576
|
-
`) : fallback;
|
|
6577
|
-
const lines = source.split(/\n|(?<=[.!?])\s+/).map((l) => l.trim()).filter((l) => l.length >= 20 && l.length <= 280);
|
|
6578
|
-
for (const line of lines) {
|
|
6579
|
-
if (INSIGHT_RE.test(line))
|
|
6580
|
-
return line;
|
|
6581
|
-
}
|
|
6582
|
-
return;
|
|
6583
|
-
}
|
|
6584
|
-
function buildEpisodePayload(input, projectId) {
|
|
6585
|
-
if (input.kind === "implement") {
|
|
6586
|
-
const qualityScore2 = computeQualityScore(input.result, {
|
|
6587
|
-
errorThrown: input.errorMessage !== undefined,
|
|
6588
|
-
runCompletedCleanly: input.result.passed
|
|
6589
|
-
});
|
|
6590
|
-
const type = input.outcome === "success" ? "solution" : "error";
|
|
6591
|
-
const importance = input.outcome === "success" ? 7 : 5;
|
|
6592
|
-
const approachSummary = buildRichApproachSummary(input.approachBlocks, input.approachSummary);
|
|
6593
|
-
const keyInsight = extractKeyInsight(input.approachBlocks, input.approachSummary);
|
|
6594
|
-
const changedFiles = (input.changedFiles ?? []).slice(0, MAX_CHANGED_FILES2);
|
|
6595
|
-
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"}.`;
|
|
6596
|
-
const metadata2 = {
|
|
6597
|
-
episode_kind: "implement",
|
|
6598
|
-
card_short_id: input.card.short_id,
|
|
6599
|
-
card_title: input.card.title,
|
|
6600
|
-
approach_summary: approachSummary,
|
|
6601
|
-
outcome: input.outcome,
|
|
6602
|
-
quality_score: qualityScore2,
|
|
6603
|
-
duration_ms: input.cost?.durationMs ?? 0,
|
|
6604
|
-
token_cost: {
|
|
6605
|
-
input: input.cost?.totalInputTokens ?? 0,
|
|
6606
|
-
output: input.cost?.totalOutputTokens ?? 0,
|
|
6607
|
-
usd: input.cost?.totalCostUsd ?? 0
|
|
6608
|
-
},
|
|
6609
|
-
files_touched: input.filesEdited,
|
|
6610
|
-
num_turns: input.cost?.numTurns ?? 0,
|
|
6611
|
-
origin: {
|
|
6612
|
-
source: "agent-run",
|
|
6613
|
-
source_card_id: input.card.id,
|
|
6614
|
-
author: "harmony-agent",
|
|
6615
|
-
...input.agentSessionId ? { source_session_id: input.agentSessionId } : {}
|
|
6616
|
-
}
|
|
6617
|
-
};
|
|
6618
|
-
if (input.errorMessage)
|
|
6619
|
-
metadata2.error = input.errorMessage;
|
|
6620
|
-
if (changedFiles.length > 0)
|
|
6621
|
-
metadata2.changed_files = changedFiles;
|
|
6622
|
-
if (input.churn)
|
|
6623
|
-
metadata2.churn = input.churn;
|
|
6624
|
-
if (keyInsight)
|
|
6625
|
-
metadata2.key_insight = keyInsight;
|
|
6626
|
-
if (input.agentSessionId)
|
|
6627
|
-
metadata2.agent_session_id = input.agentSessionId;
|
|
6628
|
-
const changedFilesLine = changedFiles.length > 0 ? `
|
|
6629
|
-
|
|
6630
|
-
Changed files (${changedFiles.length}): ${changedFiles.join(", ")}` : "";
|
|
6631
|
-
const keyInsightLine = keyInsight ? `
|
|
6632
|
-
|
|
6633
|
-
Key insight: ${keyInsight}` : "";
|
|
6634
|
-
return {
|
|
6635
|
-
workspace_id: input.workspaceId,
|
|
6636
|
-
project_id: projectId,
|
|
6637
|
-
type,
|
|
6638
|
-
memory_tier: "episode",
|
|
6639
|
-
scope: "project",
|
|
6640
|
-
title: `Agent run implement — #${input.card.short_id}: ${input.card.title}`,
|
|
6641
|
-
content: `${approachSummary}
|
|
6642
|
-
|
|
6643
|
-
Outcome: ${outcomeRationale}${keyInsightLine}${changedFilesLine}`,
|
|
6644
|
-
metadata: metadata2,
|
|
6645
|
-
importance,
|
|
6646
|
-
confidence: clampConfidence(qualityScore2),
|
|
6647
|
-
tags: ["implement", input.outcome, `card:${input.card.short_id}`],
|
|
6648
|
-
agent_identifier: "harmony-agent"
|
|
6649
|
-
};
|
|
6650
|
-
}
|
|
6651
|
-
const qualityScore = input.verdict === "approved" ? 1 : 0.4;
|
|
6652
|
-
const summary = trimReviewRationale(input.summary || "");
|
|
6653
|
-
const metadata = {
|
|
6654
|
-
episode_kind: "review",
|
|
6655
|
-
card_short_id: input.card.short_id,
|
|
6656
|
-
card_title: input.card.title,
|
|
6657
|
-
approach_summary: summary,
|
|
6658
|
-
outcome: input.verdict === "approved" ? "success" : "failure",
|
|
6659
|
-
quality_score: qualityScore,
|
|
6660
|
-
duration_ms: input.cost?.durationMs ?? 0,
|
|
6661
|
-
token_cost: {
|
|
6662
|
-
input: input.cost?.totalInputTokens ?? 0,
|
|
6663
|
-
output: input.cost?.totalOutputTokens ?? 0,
|
|
6664
|
-
usd: input.cost?.totalCostUsd ?? 0
|
|
6665
|
-
},
|
|
6666
|
-
files_touched: 0,
|
|
6667
|
-
num_turns: input.cost?.numTurns ?? 0,
|
|
6668
|
-
origin: {
|
|
6669
|
-
source: "agent-run",
|
|
6670
|
-
source_card_id: input.card.id,
|
|
6671
|
-
author: "harmony-agent",
|
|
6672
|
-
...input.reviewSessionId ?? input.agentSessionId ? {
|
|
6673
|
-
source_session_id: input.reviewSessionId ?? input.agentSessionId
|
|
6674
|
-
} : {}
|
|
6675
|
-
}
|
|
6676
|
-
};
|
|
6677
|
-
if (input.agentSessionId)
|
|
6678
|
-
metadata.agent_session_id = input.agentSessionId;
|
|
6679
|
-
if (input.reviewSessionId)
|
|
6680
|
-
metadata.review_session_id = input.reviewSessionId;
|
|
6681
|
-
if (input.originalEpisodeId)
|
|
6682
|
-
metadata.original_episode_id = input.originalEpisodeId;
|
|
6683
|
-
return {
|
|
6684
|
-
workspace_id: input.workspaceId,
|
|
6685
|
-
project_id: projectId,
|
|
6686
|
-
type: "decision",
|
|
6687
|
-
memory_tier: "episode",
|
|
6688
|
-
scope: "project",
|
|
6689
|
-
title: `Agent run review — #${input.card.short_id}: ${input.card.title}`,
|
|
6690
|
-
content: `Review verdict: ${input.verdict}.
|
|
6691
|
-
|
|
6692
|
-
${summary}`,
|
|
6693
|
-
metadata,
|
|
6694
|
-
importance: 8,
|
|
6695
|
-
confidence: clampConfidence(qualityScore),
|
|
6696
|
-
tags: ["review", input.verdict, `card:${input.card.short_id}`],
|
|
6697
|
-
agent_identifier: "harmony-agent"
|
|
6698
|
-
};
|
|
6699
|
-
}
|
|
6700
|
-
async function writeEpisode(client, input, options) {
|
|
6701
|
-
const payload = buildEpisodePayload(input, input.card.project_id);
|
|
6702
|
-
let content = payload.content;
|
|
6703
|
-
if (options?.distiller) {
|
|
6704
|
-
try {
|
|
6705
|
-
const distilled = await options.distiller(input, payload.content);
|
|
6706
|
-
if (distilled && distilled.trim().length > 0) {
|
|
6707
|
-
content = distilled.trim();
|
|
6708
|
-
}
|
|
6709
|
-
} catch (err) {
|
|
6710
|
-
log19.warn(TAG18, `episode distillation failed for #${input.card.short_id}`, {
|
|
6711
|
-
cardId: input.card.id,
|
|
6712
|
-
event: "episode_distill_failed",
|
|
6713
|
-
kind: input.kind,
|
|
6714
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6715
|
-
});
|
|
6716
|
-
}
|
|
6717
|
-
}
|
|
6718
|
-
const metadata = payload.metadata;
|
|
6719
|
-
try {
|
|
6720
|
-
const existingId = await findRollingEpisode(client, input.workspaceId, input.card.project_id, input.card.short_id, input.kind);
|
|
6721
|
-
if (existingId) {
|
|
6722
|
-
await client.updateMemoryEntity(existingId, {
|
|
6723
|
-
title: payload.title,
|
|
6724
|
-
content,
|
|
6725
|
-
metadata,
|
|
6726
|
-
confidence: payload.confidence,
|
|
6727
|
-
tags: payload.tags,
|
|
6728
|
-
type: payload.type
|
|
6729
|
-
});
|
|
6730
|
-
log19.info(TAG18, `episode rolled for #${input.card.short_id}`, {
|
|
6731
|
-
cardId: input.card.id,
|
|
6732
|
-
event: "episode_rolled",
|
|
6733
|
-
kind: input.kind,
|
|
6734
|
-
entityId: existingId
|
|
6735
|
-
});
|
|
6736
|
-
return existingId;
|
|
6737
|
-
}
|
|
6738
|
-
const { entity } = await client.createMemoryEntity({
|
|
6739
|
-
...payload,
|
|
6740
|
-
content,
|
|
6741
|
-
metadata
|
|
6742
|
-
});
|
|
6743
|
-
const id = entity && typeof entity === "object" && "id" in entity ? entity.id ?? null : null;
|
|
6744
|
-
log19.info(TAG18, `episode written for #${input.card.short_id}`, {
|
|
6745
|
-
cardId: input.card.id,
|
|
6746
|
-
event: "episode_write",
|
|
6747
|
-
kind: input.kind
|
|
6748
|
-
});
|
|
6749
|
-
return id;
|
|
6750
|
-
} catch (err) {
|
|
6751
|
-
log19.warn(TAG18, `episode write failed for #${input.card.short_id}`, {
|
|
6752
|
-
cardId: input.card.id,
|
|
6753
|
-
event: "episode_write_failed",
|
|
6754
|
-
kind: input.kind,
|
|
6755
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6756
|
-
});
|
|
6757
|
-
return null;
|
|
6758
|
-
}
|
|
6759
|
-
}
|
|
6760
|
-
async function findRollingEpisode(client, workspaceId, projectId, cardShortId, kind) {
|
|
6761
|
-
const type = kind === "implement" ? ["solution", "error"] : ["decision"];
|
|
6762
|
-
try {
|
|
6763
|
-
const { entities } = await client.harmonyRecall({
|
|
6764
|
-
workspaceId,
|
|
6765
|
-
projectId,
|
|
6766
|
-
type,
|
|
6767
|
-
memory_tier: "episode",
|
|
6768
|
-
scope: "project",
|
|
6769
|
-
tags: [`card:${cardShortId}`],
|
|
6770
|
-
topK: 10,
|
|
6771
|
-
includeEpisodes: true
|
|
6772
|
-
});
|
|
6773
|
-
for (const entity of entities) {
|
|
6774
|
-
if (!entity || typeof entity !== "object")
|
|
6775
|
-
continue;
|
|
6776
|
-
const meta = entity.metadata;
|
|
6777
|
-
if (meta?.episode_kind !== kind || meta?.origin?.source !== "agent-run") {
|
|
6778
|
-
continue;
|
|
6779
|
-
}
|
|
6780
|
-
const id = entity.id;
|
|
6781
|
-
if (typeof id === "string")
|
|
6782
|
-
return id;
|
|
6783
|
-
}
|
|
6784
|
-
return null;
|
|
6785
|
-
} catch (err) {
|
|
6786
|
-
log19.warn(TAG18, "rolling-episode lookup failed", {
|
|
6787
|
-
event: "episode_lookup_failed",
|
|
6788
|
-
cardShortId,
|
|
6789
|
-
kind,
|
|
6790
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6791
|
-
});
|
|
6792
|
-
return null;
|
|
6793
|
-
}
|
|
6794
|
-
}
|
|
6795
|
-
async function findLatestImplementEpisode(client, workspaceId, projectId, cardShortId) {
|
|
6796
|
-
return findRollingEpisode(client, workspaceId, projectId, cardShortId, "implement");
|
|
6797
|
-
}
|
|
6798
|
-
async function backfillReviewVerdict(client, originalEpisodeId, verdict, reviewEpisodeId) {
|
|
6799
|
-
try {
|
|
6800
|
-
if (verdict === "approved") {
|
|
6801
|
-
const { entity } = await client.getMemoryEntity(originalEpisodeId);
|
|
6802
|
-
const current = entity?.confidence ?? 0.4;
|
|
6803
|
-
const bumped = Math.min(1, current + 0.05);
|
|
6804
|
-
await client.updateMemoryEntity(originalEpisodeId, {
|
|
6805
|
-
confidence: bumped
|
|
6806
|
-
});
|
|
6807
|
-
} else {
|
|
6808
|
-
await client.updateMemoryEntity(originalEpisodeId, {
|
|
6809
|
-
superseded_by: reviewEpisodeId
|
|
6810
|
-
});
|
|
6811
|
-
}
|
|
6812
|
-
} catch (err) {
|
|
6813
|
-
log19.warn(TAG18, "review back-fill failed", {
|
|
6814
|
-
event: "episode_backfill_failed",
|
|
6815
|
-
originalEpisodeId,
|
|
6816
|
-
verdict,
|
|
6817
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6818
|
-
});
|
|
6819
|
-
}
|
|
6820
|
-
}
|
|
6821
|
-
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;
|
|
6822
|
-
var init_episode_writer = __esm(() => {
|
|
6823
|
-
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;
|
|
6824
|
-
});
|
|
6825
|
-
|
|
6826
6529
|
// src/run-closeout.ts
|
|
6827
|
-
import { log as
|
|
6530
|
+
import { log as log19 } from "@gethmy/harness";
|
|
6828
6531
|
async function transferCardToCompletion(deps, card, moveToColumn, onPromoted) {
|
|
6829
6532
|
await moveCardToColumn(deps.client, card, moveToColumn);
|
|
6830
6533
|
try {
|
|
6831
6534
|
await releaseAssignedAgent(deps.client, card.id);
|
|
6832
6535
|
} catch (err) {
|
|
6833
|
-
|
|
6536
|
+
log19.warn(deps.tag, `assignment release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6834
6537
|
}
|
|
6835
6538
|
if (onPromoted) {
|
|
6836
6539
|
try {
|
|
6837
6540
|
await onPromoted(card);
|
|
6838
6541
|
} catch (err) {
|
|
6839
|
-
|
|
6542
|
+
log19.warn(deps.tag, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6840
6543
|
}
|
|
6841
6544
|
}
|
|
6842
6545
|
}
|
|
@@ -6850,7 +6553,7 @@ async function endRunSession(deps, card, disposition, extraPayload, onError) {
|
|
|
6850
6553
|
} catch (err) {
|
|
6851
6554
|
if (onError === "throw")
|
|
6852
6555
|
throw err;
|
|
6853
|
-
|
|
6556
|
+
log19.error(deps.tag, `endAgentSession after the run failed on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6854
6557
|
}
|
|
6855
6558
|
}
|
|
6856
6559
|
var init_run_closeout = __esm(() => {
|
|
@@ -6861,13 +6564,12 @@ var init_run_closeout = __esm(() => {
|
|
|
6861
6564
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
6862
6565
|
import {
|
|
6863
6566
|
attemptAutoFix,
|
|
6864
|
-
captureDiffStat,
|
|
6865
6567
|
createPullRequest,
|
|
6866
6568
|
detectGitProvider as detectGitProvider3,
|
|
6867
6569
|
extractPrUrl as extractPrUrl2,
|
|
6868
6570
|
GIT_NO_HOOKS as GIT_NO_HOOKS6,
|
|
6869
6571
|
getBranchWebUrl,
|
|
6870
|
-
log as
|
|
6572
|
+
log as log20,
|
|
6871
6573
|
pushBranch,
|
|
6872
6574
|
reportFindings,
|
|
6873
6575
|
runFormatFix,
|
|
@@ -6905,15 +6607,7 @@ function buildTokenPayload(stats) {
|
|
|
6905
6607
|
numTurns: stats.cost.numTurns
|
|
6906
6608
|
};
|
|
6907
6609
|
}
|
|
6908
|
-
async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionIdentifier, agentId, sessionStats,
|
|
6909
|
-
let verificationResult = {
|
|
6910
|
-
passed: true,
|
|
6911
|
-
buildErrors: [],
|
|
6912
|
-
testFailures: [],
|
|
6913
|
-
lintWarnings: [],
|
|
6914
|
-
reviewFindings: [],
|
|
6915
|
-
revertWarnings: []
|
|
6916
|
-
};
|
|
6610
|
+
async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionIdentifier, agentId, sessionStats, agentSessionId, stateStore, onMovedToCompletion, onBeforeWorktreeCleanup, runBaselineSha, effectiveMaxTurns, expectsCommits) {
|
|
6917
6611
|
if (config.verification.enabled && config.verification.lint) {
|
|
6918
6612
|
await runFormatFix(worktreePath, config.verification.timeout, workerId, verificationSandbox(config), config.commands);
|
|
6919
6613
|
}
|
|
@@ -6922,24 +6616,24 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6922
6616
|
if (!hasCommits) {
|
|
6923
6617
|
const { maxTurnsExhausted, failureSummary } = describeNoCommitFailure(sessionStats?.cost?.numTurns ?? 0, effectiveMaxTurns ?? config.claude.maxTurns);
|
|
6924
6618
|
if (noCommitOutcome(maxTurnsExhausted, config.budget.pause.enabled) === "park") {
|
|
6925
|
-
|
|
6619
|
+
log20.warn(TAG18, `No commits on branch ${branchName} — ${failureSummary}; parking for a decision`);
|
|
6926
6620
|
return "park";
|
|
6927
6621
|
}
|
|
6928
6622
|
if (expectsCommits === false) {
|
|
6929
|
-
|
|
6623
|
+
log20.info(TAG18, `No commits on branch ${branchName} — this stage's deliverable is not a diff; its gate decides the run`);
|
|
6930
6624
|
if (onBeforeWorktreeCleanup) {
|
|
6931
6625
|
try {
|
|
6932
6626
|
await onBeforeWorktreeCleanup(worktreePath);
|
|
6933
6627
|
} catch (err) {
|
|
6934
|
-
|
|
6628
|
+
log20.warn(TAG18, `onBeforeWorktreeCleanup hook failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6935
6629
|
}
|
|
6936
6630
|
} else {
|
|
6937
|
-
await endRunSession({ client, tag:
|
|
6631
|
+
await endRunSession({ client, tag: TAG18 }, card, { status: "completed" }, buildTokenPayload(sessionStats), "throw");
|
|
6938
6632
|
}
|
|
6939
6633
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6940
6634
|
return true;
|
|
6941
6635
|
}
|
|
6942
|
-
|
|
6636
|
+
log20.warn(TAG18, `No commits on branch ${branchName} — ${failureSummary}; counting as a failed attempt`);
|
|
6943
6637
|
const noCommitHandback = await guardedHandback(client, card.id, {
|
|
6944
6638
|
agentId,
|
|
6945
6639
|
workingColumnId: card.column_id
|
|
@@ -6956,13 +6650,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6956
6650
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6957
6651
|
return false;
|
|
6958
6652
|
}
|
|
6959
|
-
|
|
6653
|
+
log20.info(TAG18, `Pushing branch ${branchName} (pre-verify)...`);
|
|
6960
6654
|
let lastPushedSha = null;
|
|
6961
6655
|
try {
|
|
6962
6656
|
pushBranch(branchName, worktreePath);
|
|
6963
6657
|
lastPushedSha = readHeadSha(worktreePath);
|
|
6964
6658
|
} catch (err) {
|
|
6965
|
-
|
|
6659
|
+
log20.error(TAG18, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
6966
6660
|
}
|
|
6967
6661
|
const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
|
|
6968
6662
|
if (lastPushedSha) {
|
|
@@ -6980,7 +6674,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6980
6674
|
let autoFixAttempts = 0;
|
|
6981
6675
|
if (!result.passed && config.verification.autoFix) {
|
|
6982
6676
|
for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
|
|
6983
|
-
|
|
6677
|
+
log20.info(TAG18, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
|
|
6984
6678
|
await client.updateAgentProgress(card.id, {
|
|
6985
6679
|
agentIdentifier: sessionIdentifier,
|
|
6986
6680
|
agentName: AGENT_NAME,
|
|
@@ -6997,30 +6691,29 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6997
6691
|
result = await runVerification(worktreePath, config, workerId);
|
|
6998
6692
|
autoFixAttempts = attempt + 1;
|
|
6999
6693
|
if (result.passed) {
|
|
7000
|
-
|
|
6694
|
+
log20.info(TAG18, `Auto-fix succeeded on attempt ${attempt + 1}`);
|
|
7001
6695
|
const sha = readHeadSha(worktreePath);
|
|
7002
6696
|
if (sha && sha !== lastPushedSha) {
|
|
7003
6697
|
try {
|
|
7004
6698
|
pushBranch(branchName, worktreePath);
|
|
7005
6699
|
lastPushedSha = sha;
|
|
7006
6700
|
} catch (err) {
|
|
7007
|
-
|
|
6701
|
+
log20.warn(TAG18, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
7008
6702
|
}
|
|
7009
6703
|
}
|
|
7010
6704
|
break;
|
|
7011
6705
|
}
|
|
7012
6706
|
}
|
|
7013
6707
|
}
|
|
7014
|
-
verificationResult = result;
|
|
7015
6708
|
if (!result.passed) {
|
|
7016
|
-
|
|
6709
|
+
log20.warn(TAG18, `Verification failed for #${card.short_id} — reporting findings`);
|
|
7017
6710
|
const failSha = readHeadSha(worktreePath);
|
|
7018
6711
|
if (failSha && failSha !== lastPushedSha) {
|
|
7019
6712
|
try {
|
|
7020
6713
|
pushBranch(branchName, worktreePath);
|
|
7021
6714
|
lastPushedSha = failSha;
|
|
7022
6715
|
} catch (err) {
|
|
7023
|
-
|
|
6716
|
+
log20.warn(TAG18, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
7024
6717
|
}
|
|
7025
6718
|
}
|
|
7026
6719
|
const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
|
|
@@ -7031,7 +6724,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7031
6724
|
recoveryBranch: branchName
|
|
7032
6725
|
});
|
|
7033
6726
|
} catch (err) {
|
|
7034
|
-
|
|
6727
|
+
log20.debug(TAG18, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
7035
6728
|
}
|
|
7036
6729
|
await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
|
|
7037
6730
|
const verifyHandback = await guardedHandback(client, card.id, {
|
|
@@ -7051,7 +6744,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7051
6744
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
7052
6745
|
return false;
|
|
7053
6746
|
}
|
|
7054
|
-
|
|
6747
|
+
log20.info(TAG18, `Verification passed for #${card.short_id}`);
|
|
7055
6748
|
}
|
|
7056
6749
|
let prUrl = null;
|
|
7057
6750
|
if (config.completion.createPR) {
|
|
@@ -7059,7 +6752,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7059
6752
|
prUrl = createPullRequest(card, branchName, worktreePath, config, provider);
|
|
7060
6753
|
}
|
|
7061
6754
|
if (config.completion.moveToColumn) {
|
|
7062
|
-
await transferCardToCompletion({ client, tag:
|
|
6755
|
+
await transferCardToCompletion({ client, tag: TAG18 }, card, config.completion.moveToColumn, onMovedToCompletion);
|
|
7063
6756
|
}
|
|
7064
6757
|
if (config.completion.postSummary) {
|
|
7065
6758
|
await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch, sessionStats);
|
|
@@ -7068,35 +6761,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
7068
6761
|
try {
|
|
7069
6762
|
await onBeforeWorktreeCleanup(worktreePath);
|
|
7070
6763
|
} catch (err) {
|
|
7071
|
-
|
|
6764
|
+
log20.warn(TAG18, `onBeforeWorktreeCleanup hook failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7072
6765
|
}
|
|
7073
6766
|
}
|
|
7074
6767
|
if (!onBeforeWorktreeCleanup) {
|
|
7075
|
-
await endRunSession({ client, tag:
|
|
7076
|
-
}
|
|
7077
|
-
if (workspaceId) {
|
|
7078
|
-
const diffStat = captureDiffStat(worktreePath, config.worktree.baseBranch);
|
|
7079
|
-
const changedFiles = diffStat && diffStat.files.length > 0 ? diffStat.files : sessionStats?.filesEditedPaths ?? [];
|
|
7080
|
-
await writeEpisode(client, {
|
|
7081
|
-
kind: "implement",
|
|
7082
|
-
card,
|
|
7083
|
-
workspaceId,
|
|
7084
|
-
outcome: "success",
|
|
7085
|
-
approachSummary: sessionStats?.lastAssistantText ?? "",
|
|
7086
|
-
approachBlocks: sessionStats?.assistantTextBlocks,
|
|
7087
|
-
result: verificationResult,
|
|
7088
|
-
cost: sessionStats?.cost ?? null,
|
|
7089
|
-
filesEdited: sessionStats?.filesEdited ?? 0,
|
|
7090
|
-
changedFiles,
|
|
7091
|
-
churn: diffStat ? {
|
|
7092
|
-
insertions: diffStat.insertions,
|
|
7093
|
-
deletions: diffStat.deletions
|
|
7094
|
-
} : undefined,
|
|
7095
|
-
agentSessionId: agentSessionId ?? null
|
|
7096
|
-
});
|
|
6768
|
+
await endRunSession({ client, tag: TAG18 }, card, { status: "completed" }, buildTokenPayload(sessionStats), "throw");
|
|
7097
6769
|
}
|
|
7098
6770
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
7099
|
-
|
|
6771
|
+
log20.info(TAG18, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
|
|
7100
6772
|
return true;
|
|
7101
6773
|
}
|
|
7102
6774
|
function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
@@ -7138,7 +6810,7 @@ function commitUncommittedChanges(worktreePath, card) {
|
|
|
7138
6810
|
encoding: "utf-8"
|
|
7139
6811
|
}).trim();
|
|
7140
6812
|
} catch (err) {
|
|
7141
|
-
|
|
6813
|
+
log20.warn(TAG18, `git status failed in ${worktreePath}: ${err instanceof Error ? err.message : err}`);
|
|
7142
6814
|
return false;
|
|
7143
6815
|
}
|
|
7144
6816
|
if (status.length === 0)
|
|
@@ -7154,10 +6826,10 @@ function commitUncommittedChanges(worktreePath, card) {
|
|
|
7154
6826
|
cwd: worktreePath,
|
|
7155
6827
|
encoding: "utf-8"
|
|
7156
6828
|
});
|
|
7157
|
-
|
|
6829
|
+
log20.warn(TAG18, `Auto-committed uncommitted worktree changes for #${card.short_id} — agent ended without committing`);
|
|
7158
6830
|
return true;
|
|
7159
6831
|
} catch (err) {
|
|
7160
|
-
|
|
6832
|
+
log20.error(TAG18, `auto-commit failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7161
6833
|
return false;
|
|
7162
6834
|
}
|
|
7163
6835
|
}
|
|
@@ -7206,9 +6878,9 @@ async function recordBranchProvenance(client, card, branchName) {
|
|
|
7206
6878
|
${BRANCH_PROVENANCE_MARKER} (pushed — a later run continues it)
|
|
7207
6879
|
Branch: \`${branchName}\``;
|
|
7208
6880
|
await client.updateCard(card.id, { description: description + block });
|
|
7209
|
-
|
|
6881
|
+
log20.info(TAG18, `Recorded branch provenance on #${card.short_id}`);
|
|
7210
6882
|
} catch (err) {
|
|
7211
|
-
|
|
6883
|
+
log20.warn(TAG18, `Failed to record branch provenance on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7212
6884
|
}
|
|
7213
6885
|
}
|
|
7214
6886
|
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
|
|
@@ -7262,25 +6934,24 @@ ${commitLog}
|
|
|
7262
6934
|
description: baseDesc + parts.join(`
|
|
7263
6935
|
`)
|
|
7264
6936
|
});
|
|
7265
|
-
|
|
6937
|
+
log20.info(TAG18, `Posted completion summary to #${card.short_id}`);
|
|
7266
6938
|
} catch (err) {
|
|
7267
|
-
|
|
6939
|
+
log20.error(TAG18, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
|
|
7268
6940
|
}
|
|
7269
6941
|
}
|
|
7270
|
-
var
|
|
6942
|
+
var TAG18 = "completion", SUMMARY_MARKER = `---
|
|
7271
6943
|
**Agent completed**`, BRANCH_PROVENANCE_MARKER = `---
|
|
7272
6944
|
**Agent branch**`;
|
|
7273
6945
|
var init_completion = __esm(() => {
|
|
7274
6946
|
init_dist();
|
|
7275
6947
|
init_board_helpers();
|
|
7276
|
-
init_episode_writer();
|
|
7277
6948
|
init_handback();
|
|
7278
6949
|
init_run_closeout();
|
|
7279
6950
|
init_types2();
|
|
7280
6951
|
});
|
|
7281
6952
|
|
|
7282
6953
|
// src/progress-tracker.ts
|
|
7283
|
-
import { log as
|
|
6954
|
+
import { log as log21 } from "@gethmy/harness";
|
|
7284
6955
|
function truncate2(str, max) {
|
|
7285
6956
|
return str.length > max ? `${str.slice(0, max - 3)}...` : str;
|
|
7286
6957
|
}
|
|
@@ -7309,7 +6980,6 @@ class ProgressTracker {
|
|
|
7309
6980
|
runEventSink = null;
|
|
7310
6981
|
lastEmittedProgress = -1;
|
|
7311
6982
|
lastAssistantText = "";
|
|
7312
|
-
assistantTextBlocks = [];
|
|
7313
6983
|
constructor(client, cardId, sessionIdentifier, subtasks, initialPhase = "exploring") {
|
|
7314
6984
|
this.client = client;
|
|
7315
6985
|
this.cardId = cardId;
|
|
@@ -7392,17 +7062,15 @@ class ProgressTracker {
|
|
|
7392
7062
|
get stats() {
|
|
7393
7063
|
return {
|
|
7394
7064
|
filesEdited: this.filesEdited.size,
|
|
7395
|
-
filesEditedPaths: [...this.filesEdited],
|
|
7396
7065
|
filesRead: this.filesRead.size,
|
|
7397
7066
|
toolCalls: this.toolCallCount,
|
|
7398
7067
|
cost: this.lastCost,
|
|
7399
|
-
lastAssistantText: this.lastAssistantText
|
|
7400
|
-
assistantTextBlocks: [...this.assistantTextBlocks]
|
|
7068
|
+
lastAssistantText: this.lastAssistantText
|
|
7401
7069
|
};
|
|
7402
7070
|
}
|
|
7403
7071
|
onToolStart(name, input) {
|
|
7404
7072
|
this.toolCallCount++;
|
|
7405
|
-
|
|
7073
|
+
log21.debug(TAG19, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
|
|
7406
7074
|
const filePath = this.extractString(input, "file_path");
|
|
7407
7075
|
if (filePath) {
|
|
7408
7076
|
if (EDIT_TOOLS.has(name)) {
|
|
@@ -7458,10 +7126,6 @@ class ProgressTracker {
|
|
|
7458
7126
|
if (trimmed.length < 10)
|
|
7459
7127
|
return;
|
|
7460
7128
|
this.lastAssistantText = trimmed;
|
|
7461
|
-
this.assistantTextBlocks.push(trimmed);
|
|
7462
|
-
if (this.assistantTextBlocks.length > MAX_TEXT_BLOCKS) {
|
|
7463
|
-
this.assistantTextBlocks.shift();
|
|
7464
|
-
}
|
|
7465
7129
|
const end = trimmed.search(SENTENCE_SPLIT);
|
|
7466
7130
|
const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
|
|
7467
7131
|
if (firstLine.length >= 10 && firstLine.length <= 200) {
|
|
@@ -7473,7 +7137,7 @@ class ProgressTracker {
|
|
|
7473
7137
|
transitionTo(newPhase) {
|
|
7474
7138
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
7475
7139
|
return;
|
|
7476
|
-
|
|
7140
|
+
log21.info(TAG19, `Phase: ${this.phase} → ${newPhase}`);
|
|
7477
7141
|
const previousPhase = this.phase;
|
|
7478
7142
|
this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
|
|
7479
7143
|
this.phase = newPhase;
|
|
@@ -7578,7 +7242,7 @@ class ProgressTracker {
|
|
|
7578
7242
|
}
|
|
7579
7243
|
sendUpdate(currentTask) {
|
|
7580
7244
|
this.lastUpdateAt = Date.now();
|
|
7581
|
-
|
|
7245
|
+
log21.debug(TAG19, `Progress: ${this.progress}% — ${currentTask}`);
|
|
7582
7246
|
this.client.updateAgentProgress(this.cardId, {
|
|
7583
7247
|
agentIdentifier: this.sessionIdentifier,
|
|
7584
7248
|
agentName: AGENT_NAME,
|
|
@@ -7595,7 +7259,7 @@ class ProgressTracker {
|
|
|
7595
7259
|
modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
|
|
7596
7260
|
numTurns: this.lastCost?.numTurns ?? 0
|
|
7597
7261
|
}).catch((err) => {
|
|
7598
|
-
|
|
7262
|
+
log21.warn(TAG19, `Failed to send progress update: ${err}`);
|
|
7599
7263
|
});
|
|
7600
7264
|
if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
|
|
7601
7265
|
this.lastEmittedProgress = this.progress;
|
|
@@ -7626,7 +7290,7 @@ class ProgressTracker {
|
|
|
7626
7290
|
return null;
|
|
7627
7291
|
}
|
|
7628
7292
|
}
|
|
7629
|
-
var
|
|
7293
|
+
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;
|
|
7630
7294
|
var init_progress_tracker = __esm(() => {
|
|
7631
7295
|
init_types2();
|
|
7632
7296
|
SENTENCE_SPLIT = /\.\s|\n/;
|
|
@@ -7660,7 +7324,7 @@ var init_progress_tracker = __esm(() => {
|
|
|
7660
7324
|
});
|
|
7661
7325
|
|
|
7662
7326
|
// src/prompt.ts
|
|
7663
|
-
import { log as
|
|
7327
|
+
import { log as log22 } from "@gethmy/harness";
|
|
7664
7328
|
import {
|
|
7665
7329
|
buildMemoryQuery
|
|
7666
7330
|
} from "@gethmy/mcp/src/api-client.js";
|
|
@@ -7698,10 +7362,7 @@ function renderPreviousAttemptsSection(failures) {
|
|
|
7698
7362
|
}
|
|
7699
7363
|
async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId, onRecallOutcome) {
|
|
7700
7364
|
const { card } = enriched;
|
|
7701
|
-
const
|
|
7702
|
-
renderPastEpisodesSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome),
|
|
7703
|
-
renderReferenceSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome)
|
|
7704
|
-
]);
|
|
7365
|
+
const referenceSection = await renderReferenceSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome);
|
|
7705
7366
|
try {
|
|
7706
7367
|
const result = await client.generateCardPrompt({
|
|
7707
7368
|
cardId: card.id,
|
|
@@ -7712,13 +7373,13 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
7712
7373
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
7713
7374
|
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.`
|
|
7714
7375
|
});
|
|
7715
|
-
|
|
7716
|
-
return result.prompt +
|
|
7376
|
+
log22.info(TAG20, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
7377
|
+
return result.prompt + referenceSection;
|
|
7717
7378
|
} catch (err) {
|
|
7718
7379
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7719
|
-
|
|
7380
|
+
log22.warn(TAG20, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
7720
7381
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
7721
|
-
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection +
|
|
7382
|
+
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + referenceSection;
|
|
7722
7383
|
}
|
|
7723
7384
|
}
|
|
7724
7385
|
async function renderCommentsSection(client, cardId) {
|
|
@@ -7737,95 +7398,41 @@ ${untrustedDataBlock(section, {
|
|
|
7737
7398
|
purpose: "discussion to take into account"
|
|
7738
7399
|
})}` : "";
|
|
7739
7400
|
} catch (err) {
|
|
7740
|
-
|
|
7401
|
+
log22.warn(TAG20, "comment-thread fetch failed", {
|
|
7741
7402
|
event: "comment_fetch_failed",
|
|
7742
7403
|
error: err instanceof Error ? err.message : String(err)
|
|
7743
7404
|
});
|
|
7744
7405
|
return "";
|
|
7745
7406
|
}
|
|
7746
7407
|
}
|
|
7747
|
-
async function renderPastEpisodesSection(client, title, description, workspaceId, projectId, onOutcome) {
|
|
7748
|
-
if (!projectId)
|
|
7749
|
-
return "";
|
|
7750
|
-
try {
|
|
7751
|
-
const query = buildMemoryQuery(title, description);
|
|
7752
|
-
const { entities } = await client.harmonyRecall({
|
|
7753
|
-
workspaceId,
|
|
7754
|
-
projectId,
|
|
7755
|
-
query,
|
|
7756
|
-
type: ["solution", "error"],
|
|
7757
|
-
memory_tier: "episode",
|
|
7758
|
-
scope: "project",
|
|
7759
|
-
topK: 3,
|
|
7760
|
-
includeEpisodes: true,
|
|
7761
|
-
consumer: "agent-prompt"
|
|
7762
|
-
});
|
|
7763
|
-
onOutcome?.({ flavour: "past_episodes", count: entities.length });
|
|
7764
|
-
if (entities.length === 0)
|
|
7765
|
-
return "";
|
|
7766
|
-
const bullets = entities.map((entity) => {
|
|
7767
|
-
const e = entity;
|
|
7768
|
-
const meta = e.metadata ?? {};
|
|
7769
|
-
const outcomeTag = meta.outcome ? `[${meta.outcome}]` : "[?]";
|
|
7770
|
-
const approach = meta.approach_summary ?? "";
|
|
7771
|
-
const lines = [
|
|
7772
|
-
`- ${outcomeTag} ${e.title ?? "(untitled episode)"}`,
|
|
7773
|
-
` Approach: ${approach}`
|
|
7774
|
-
];
|
|
7775
|
-
if (meta.key_insight)
|
|
7776
|
-
lines.push(` Key insight: ${meta.key_insight}`);
|
|
7777
|
-
if (meta.changed_files && meta.changed_files.length > 0) {
|
|
7778
|
-
const shown = meta.changed_files.slice(0, 8);
|
|
7779
|
-
const extra = meta.changed_files.length - shown.length;
|
|
7780
|
-
const suffix = extra > 0 ? ` (+${extra} more)` : "";
|
|
7781
|
-
lines.push(` Changed files: ${shown.join(", ")}${suffix}`);
|
|
7782
|
-
}
|
|
7783
|
-
return lines.join(`
|
|
7784
|
-
`);
|
|
7785
|
-
}).join(`
|
|
7786
|
-
`);
|
|
7787
|
-
return `
|
|
7788
|
-
|
|
7789
|
-
## Similar past tasks
|
|
7790
|
-
${bullets}`;
|
|
7791
|
-
} catch (err) {
|
|
7792
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
7793
|
-
log23.warn(TAG21, "past-episodes recall failed", {
|
|
7794
|
-
event: "episode_recall_failed",
|
|
7795
|
-
error
|
|
7796
|
-
});
|
|
7797
|
-
onOutcome?.({ flavour: "past_episodes", count: 0, failure: error });
|
|
7798
|
-
return "";
|
|
7799
|
-
}
|
|
7800
|
-
}
|
|
7801
7408
|
async function renderReferenceSection(client, title, description, workspaceId, projectId, onOutcome) {
|
|
7802
7409
|
try {
|
|
7803
7410
|
const query = buildMemoryQuery(title, description);
|
|
7804
|
-
const { entities } = await client.
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
query,
|
|
7808
|
-
memory_tier: "reference",
|
|
7809
|
-
topK: 5,
|
|
7810
|
-
consumer: "agent-prompt"
|
|
7411
|
+
const { entities } = await client.searchMemoryEntities(workspaceId, query, {
|
|
7412
|
+
project_id: projectId,
|
|
7413
|
+
limit: 5
|
|
7811
7414
|
});
|
|
7812
7415
|
onOutcome?.({ flavour: "reference", count: entities.length });
|
|
7813
7416
|
if (entities.length === 0)
|
|
7814
7417
|
return "";
|
|
7815
7418
|
const bullets = entities.map((entity) => {
|
|
7816
7419
|
const e = entity;
|
|
7420
|
+
const type = e.type ? ` (${e.type})` : "";
|
|
7817
7421
|
const content = (e.content ?? "").slice(0, 300);
|
|
7818
|
-
return `- ${e.title ?? "(untitled)"}
|
|
7422
|
+
return `- ${e.title ?? "(untitled)"}${type}
|
|
7819
7423
|
${content}`;
|
|
7820
7424
|
}).join(`
|
|
7821
7425
|
`);
|
|
7822
7426
|
return `
|
|
7823
7427
|
|
|
7824
|
-
|
|
7825
|
-
${bullets}
|
|
7428
|
+
${untrustedDataBlock(`## How we work here
|
|
7429
|
+
${bullets}`, {
|
|
7430
|
+
label: "workspace memory",
|
|
7431
|
+
purpose: "conventions to apply where they fit the task"
|
|
7432
|
+
})}`;
|
|
7826
7433
|
} catch (err) {
|
|
7827
7434
|
const error = err instanceof Error ? err.message : String(err);
|
|
7828
|
-
|
|
7435
|
+
log22.warn(TAG20, "reference recall failed", {
|
|
7829
7436
|
event: "reference_recall_failed",
|
|
7830
7437
|
error
|
|
7831
7438
|
});
|
|
@@ -7867,7 +7474,7 @@ ${subtaskStr}
|
|
|
7867
7474
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
7868
7475
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
7869
7476
|
}
|
|
7870
|
-
var
|
|
7477
|
+
var TAG20 = "prompt", RESUME_CONTINUATION = `Continue from where you stopped.
|
|
7871
7478
|
|
|
7872
7479
|
This is the same session. The task you were given, the work you have already done,
|
|
7873
7480
|
and everything you read are above in this conversation. None of it has changed, and
|
|
@@ -7892,7 +7499,7 @@ import {
|
|
|
7892
7499
|
extractPrUrl as extractPrUrl3,
|
|
7893
7500
|
getBranchWebUrl as getBranchWebUrl2,
|
|
7894
7501
|
getHeadSha,
|
|
7895
|
-
log as
|
|
7502
|
+
log as log23,
|
|
7896
7503
|
pushBranch as pushBranch2,
|
|
7897
7504
|
renameRemoteBranch,
|
|
7898
7505
|
sanitizeCiText as sanitizeCiText2,
|
|
@@ -8018,7 +7625,7 @@ function parseReviewOutput(stdout) {
|
|
|
8018
7625
|
try {
|
|
8019
7626
|
const parsed = JSON.parse(raw);
|
|
8020
7627
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
8021
|
-
|
|
7628
|
+
log23.debug(TAG21, "Parsed review output from fenced JSON block");
|
|
8022
7629
|
return extractResult(parsed);
|
|
8023
7630
|
}
|
|
8024
7631
|
} catch {}
|
|
@@ -8044,21 +7651,21 @@ function parseReviewOutput(stdout) {
|
|
|
8044
7651
|
try {
|
|
8045
7652
|
const parsed = JSON.parse(candidates[i]);
|
|
8046
7653
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
8047
|
-
|
|
7654
|
+
log23.debug(TAG21, "Parsed review output from raw JSON object");
|
|
8048
7655
|
return extractResult(parsed);
|
|
8049
7656
|
}
|
|
8050
7657
|
} catch {}
|
|
8051
7658
|
}
|
|
8052
7659
|
const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
|
|
8053
7660
|
if (verdictMatch) {
|
|
8054
|
-
|
|
7661
|
+
log23.warn(TAG21, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
|
|
8055
7662
|
return {
|
|
8056
7663
|
verdict: verdictMatch[1].toLowerCase(),
|
|
8057
7664
|
summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
|
|
8058
7665
|
findings: []
|
|
8059
7666
|
};
|
|
8060
7667
|
}
|
|
8061
|
-
|
|
7668
|
+
log23.warn(TAG21, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
|
|
8062
7669
|
return {
|
|
8063
7670
|
verdict: "error",
|
|
8064
7671
|
summary: stdout.slice(0, 500),
|
|
@@ -8094,10 +7701,10 @@ async function postReviewComment(client, card, commentType, body, agentSessionId
|
|
|
8094
7701
|
agentSessionId: agentSessionId ?? undefined
|
|
8095
7702
|
});
|
|
8096
7703
|
} catch (err) {
|
|
8097
|
-
|
|
7704
|
+
log23.error(TAG21, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8098
7705
|
}
|
|
8099
7706
|
}
|
|
8100
|
-
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath,
|
|
7707
|
+
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, agentSessionId, stateStore, agentId, resolvedFromPrUrl, visualLensSkipReason) {
|
|
8101
7708
|
let freshDesc;
|
|
8102
7709
|
let freshCard = null;
|
|
8103
7710
|
let handbackVerdict;
|
|
@@ -8118,7 +7725,7 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
8118
7725
|
};
|
|
8119
7726
|
}
|
|
8120
7727
|
if (!handbackVerdict.proceed) {
|
|
8121
|
-
|
|
7728
|
+
log23.info(TAG21, `#${card.short_id}: review outcome will not move the card — ${handbackVerdict.detail} (${handbackVerdict.reason})`);
|
|
8122
7729
|
}
|
|
8123
7730
|
const lensLine = visualLensSkipReason ? `Visual QA did not run — ${sanitizeCiText2(visualLensSkipReason, REVIEW_FAILURE_REASON_MAX)}. Anything that needs a rendered page is ungraded.` : "";
|
|
8124
7731
|
const LOOPING_REFUSALS = new Set(["unreadable", "released", "done"]);
|
|
@@ -8128,19 +7735,19 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
8128
7735
|
}
|
|
8129
7736
|
try {
|
|
8130
7737
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
8131
|
-
|
|
7738
|
+
log23.warn(TAG21, `#${card.short_id} labelled "${NEED_REVIEW_LABEL}" (${handbackVerdict.reason}) so the daemon stops re-claiming and re-reviewing it`);
|
|
8132
7739
|
} catch (err) {
|
|
8133
|
-
|
|
7740
|
+
log23.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
8134
7741
|
}
|
|
8135
7742
|
}
|
|
8136
7743
|
const currentCycle = getReviewCycle(freshDesc) + 1;
|
|
8137
7744
|
const maxCycles = config.review.maxReviewCycles;
|
|
8138
7745
|
if (result.verdict === "error") {
|
|
8139
|
-
|
|
7746
|
+
log23.warn(TAG21, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
|
|
8140
7747
|
try {
|
|
8141
7748
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
8142
7749
|
} catch (err) {
|
|
8143
|
-
|
|
7750
|
+
log23.warn(TAG21, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
8144
7751
|
}
|
|
8145
7752
|
if (config.review.postFindings) {
|
|
8146
7753
|
const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
|
|
@@ -8183,7 +7790,7 @@ ${runLogTail}
|
|
|
8183
7790
|
renameRemoteBranch(branchName, newRef, worktreePath);
|
|
8184
7791
|
approvedBranch = newRef;
|
|
8185
7792
|
} catch (err) {
|
|
8186
|
-
|
|
7793
|
+
log23.warn(TAG21, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
|
|
8187
7794
|
}
|
|
8188
7795
|
}
|
|
8189
7796
|
if (config.review.createPR && approvedBranch) {
|
|
@@ -8216,14 +7823,14 @@ ${runLogTail}
|
|
|
8216
7823
|
await client.updateCard(card.id, { description: desc });
|
|
8217
7824
|
}
|
|
8218
7825
|
} catch (err) {
|
|
8219
|
-
|
|
7826
|
+
log23.warn(TAG21, `Failed to persist PR URL / branch rename to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
|
|
8220
7827
|
}
|
|
8221
7828
|
}
|
|
8222
7829
|
if (branchName) {
|
|
8223
7830
|
try {
|
|
8224
7831
|
await persistReviewedSha(client, card, worktreePath);
|
|
8225
7832
|
} catch (err) {
|
|
8226
|
-
|
|
7833
|
+
log23.warn(TAG21, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8227
7834
|
}
|
|
8228
7835
|
}
|
|
8229
7836
|
if (config.review.postFindings) {
|
|
@@ -8246,7 +7853,7 @@ ${runLogTail}
|
|
|
8246
7853
|
progressPercent: 100,
|
|
8247
7854
|
...buildTokenPayload(sessionStats)
|
|
8248
7855
|
});
|
|
8249
|
-
|
|
7856
|
+
log23.info(TAG21, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
|
|
8250
7857
|
} else {
|
|
8251
7858
|
const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
|
|
8252
7859
|
const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
|
|
@@ -8254,7 +7861,7 @@ ${runLogTail}
|
|
|
8254
7861
|
const linkedFindings = [...criticalFindings, ...majorFindings];
|
|
8255
7862
|
const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
|
|
8256
7863
|
if (currentCycle >= maxCycles) {
|
|
8257
|
-
|
|
7864
|
+
log23.warn(TAG21, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
|
|
8258
7865
|
if (handbackVerdict.proceed) {
|
|
8259
7866
|
await moveCardToColumn(client, freshCard ?? card, config.review.moveToColumn);
|
|
8260
7867
|
} else {
|
|
@@ -8272,22 +7879,6 @@ ${runLogTail}
|
|
|
8272
7879
|
status: "completed",
|
|
8273
7880
|
...buildTokenPayload(sessionStats)
|
|
8274
7881
|
});
|
|
8275
|
-
if (workspaceId) {
|
|
8276
|
-
const origId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
8277
|
-
const reviewId = await writeEpisode(client, {
|
|
8278
|
-
kind: "review",
|
|
8279
|
-
card,
|
|
8280
|
-
workspaceId,
|
|
8281
|
-
verdict: "rejected",
|
|
8282
|
-
summary: `Reached max review cycles (${maxCycles}). ${result.summary}`,
|
|
8283
|
-
cost: sessionStats?.cost ?? null,
|
|
8284
|
-
agentSessionId: agentSessionId ?? null,
|
|
8285
|
-
originalEpisodeId: origId
|
|
8286
|
-
});
|
|
8287
|
-
if (origId) {
|
|
8288
|
-
await backfillReviewVerdict(client, origId, "rejected", reviewId);
|
|
8289
|
-
}
|
|
8290
|
-
}
|
|
8291
7882
|
if (branchName) {
|
|
8292
7883
|
cleanupWorktree3(worktreePath, branchName);
|
|
8293
7884
|
}
|
|
@@ -8298,7 +7889,7 @@ ${runLogTail}
|
|
|
8298
7889
|
try {
|
|
8299
7890
|
await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
|
|
8300
7891
|
} catch (err) {
|
|
8301
|
-
|
|
7892
|
+
log23.error(TAG21, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
|
|
8302
7893
|
}
|
|
8303
7894
|
}));
|
|
8304
7895
|
if (linkedFindings.length > 0) {
|
|
@@ -8310,7 +7901,7 @@ ${runLogTail}
|
|
|
8310
7901
|
try {
|
|
8311
7902
|
await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
|
|
8312
7903
|
} catch (err) {
|
|
8313
|
-
|
|
7904
|
+
log23.error(TAG21, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
8314
7905
|
}
|
|
8315
7906
|
}));
|
|
8316
7907
|
if (handbackVerdict.proceed) {
|
|
@@ -8319,7 +7910,7 @@ ${runLogTail}
|
|
|
8319
7910
|
try {
|
|
8320
7911
|
await client.updateCard(card.id, { description: updatedDesc });
|
|
8321
7912
|
} catch (err) {
|
|
8322
|
-
|
|
7913
|
+
log23.error(TAG21, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
|
|
8323
7914
|
}
|
|
8324
7915
|
}
|
|
8325
7916
|
const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
|
|
@@ -8338,9 +7929,9 @@ ${runLogTail}
|
|
|
8338
7929
|
if (handbackVerdict.proceed && config.planning.enabled && card.plan_id) {
|
|
8339
7930
|
try {
|
|
8340
7931
|
await client.updateCard(card.id, { needsPlanRefresh: true });
|
|
8341
|
-
|
|
7932
|
+
log23.info(TAG21, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
|
|
8342
7933
|
} catch (err) {
|
|
8343
|
-
|
|
7934
|
+
log23.warn(TAG21, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8344
7935
|
}
|
|
8345
7936
|
}
|
|
8346
7937
|
if (handbackVerdict.proceed) {
|
|
@@ -8358,10 +7949,10 @@ ${runLogTail}
|
|
|
8358
7949
|
recoveryBranch
|
|
8359
7950
|
});
|
|
8360
7951
|
} catch (err) {
|
|
8361
|
-
|
|
7952
|
+
log23.debug(TAG21, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
8362
7953
|
}
|
|
8363
7954
|
if (recoveryBranch) {
|
|
8364
|
-
|
|
7955
|
+
log23.info(TAG21, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
|
|
8365
7956
|
}
|
|
8366
7957
|
await client.endAgentSession(card.id, {
|
|
8367
7958
|
status: "failed",
|
|
@@ -8370,35 +7961,18 @@ ${runLogTail}
|
|
|
8370
7961
|
recoveryBranch,
|
|
8371
7962
|
...buildTokenPayload(sessionStats)
|
|
8372
7963
|
});
|
|
8373
|
-
|
|
8374
|
-
}
|
|
8375
|
-
if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
|
|
8376
|
-
const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
8377
|
-
const reviewEpisodeId = await writeEpisode(client, {
|
|
8378
|
-
kind: "review",
|
|
8379
|
-
card,
|
|
8380
|
-
workspaceId,
|
|
8381
|
-
verdict: result.verdict,
|
|
8382
|
-
summary: result.summary,
|
|
8383
|
-
cost: sessionStats?.cost ?? null,
|
|
8384
|
-
agentSessionId: agentSessionId ?? null,
|
|
8385
|
-
originalEpisodeId
|
|
8386
|
-
});
|
|
8387
|
-
if (originalEpisodeId) {
|
|
8388
|
-
await backfillReviewVerdict(client, originalEpisodeId, result.verdict, reviewEpisodeId);
|
|
8389
|
-
}
|
|
7964
|
+
log23.info(TAG21, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
|
|
8390
7965
|
}
|
|
8391
7966
|
if (branchName) {
|
|
8392
7967
|
cleanupWorktree3(worktreePath, branchName);
|
|
8393
7968
|
}
|
|
8394
7969
|
}
|
|
8395
|
-
var
|
|
7970
|
+
var TAG21 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
|
|
8396
7971
|
**Review:`, RUN_LOG_TAIL_BYTES = 2048;
|
|
8397
7972
|
var init_review_completion = __esm(() => {
|
|
8398
7973
|
init_dist();
|
|
8399
7974
|
init_board_helpers();
|
|
8400
7975
|
init_completion();
|
|
8401
|
-
init_episode_writer();
|
|
8402
7976
|
init_handback();
|
|
8403
7977
|
init_types2();
|
|
8404
7978
|
});
|
|
@@ -8623,7 +8197,7 @@ import {
|
|
|
8623
8197
|
mkdirSync as mkdirSync3
|
|
8624
8198
|
} from "node:fs";
|
|
8625
8199
|
import { join as join5 } from "node:path";
|
|
8626
|
-
import { log as
|
|
8200
|
+
import { log as log24 } from "@gethmy/harness";
|
|
8627
8201
|
function runLogPath(runId, shortId) {
|
|
8628
8202
|
return join5(runsDir(), `${runId}-card-${shortId}.log`);
|
|
8629
8203
|
}
|
|
@@ -8637,7 +8211,7 @@ function appendRunLog(tag, runId, shortId, line) {
|
|
|
8637
8211
|
`, "utf-8");
|
|
8638
8212
|
return path;
|
|
8639
8213
|
} catch (err) {
|
|
8640
|
-
|
|
8214
|
+
log24.warn(tag, `Failed to append to run log: ${err instanceof Error ? err.message : err}`);
|
|
8641
8215
|
return null;
|
|
8642
8216
|
}
|
|
8643
8217
|
}
|
|
@@ -8649,11 +8223,11 @@ function openRunLog(tag, runId, shortId) {
|
|
|
8649
8223
|
const path = runLogPath(runId, shortId);
|
|
8650
8224
|
const stream = createWriteStream(path, { flags: "a" });
|
|
8651
8225
|
stream.on("error", (err) => {
|
|
8652
|
-
|
|
8226
|
+
log24.warn(tag, `Run log ${path} failed: ${err.message}`);
|
|
8653
8227
|
});
|
|
8654
8228
|
return { path, stream };
|
|
8655
8229
|
} catch (err) {
|
|
8656
|
-
|
|
8230
|
+
log24.warn(tag, `Failed to open run log: ${err instanceof Error ? err.message : err}`);
|
|
8657
8231
|
return null;
|
|
8658
8232
|
}
|
|
8659
8233
|
}
|
|
@@ -8690,7 +8264,7 @@ function isOwnNoticeConflict(err) {
|
|
|
8690
8264
|
const driver = sessionConflictDriver(err);
|
|
8691
8265
|
return driver === null || driver === NOTICE_DRIVER;
|
|
8692
8266
|
}
|
|
8693
|
-
async function startSessionYieldingNotice(start, client, cardId,
|
|
8267
|
+
async function startSessionYieldingNotice(start, client, cardId, log25) {
|
|
8694
8268
|
try {
|
|
8695
8269
|
return await start();
|
|
8696
8270
|
} catch (err) {
|
|
@@ -8699,12 +8273,12 @@ async function startSessionYieldingNotice(start, client, cardId, log26) {
|
|
|
8699
8273
|
try {
|
|
8700
8274
|
const { session } = await client.getAgentSession(cardId);
|
|
8701
8275
|
if (!session) {
|
|
8702
|
-
|
|
8276
|
+
log25.info(`The session that refused the start is already gone — retrying: ${err instanceof Error ? err.message : String(err)}`);
|
|
8703
8277
|
} else if (!isPoolNotice(session)) {
|
|
8704
|
-
|
|
8278
|
+
log25.warn("The session that refused the start is not a pool notice on a re-read — leaving it alone");
|
|
8705
8279
|
throw err;
|
|
8706
8280
|
} else {
|
|
8707
|
-
|
|
8281
|
+
log25.info(`Ending this pool's own notice on the card and retrying the session start: ${err instanceof Error ? err.message : String(err)}`);
|
|
8708
8282
|
await client.endAgentSession(cardId, {
|
|
8709
8283
|
status: "failed",
|
|
8710
8284
|
failureReason: "stale",
|
|
@@ -8714,7 +8288,7 @@ async function startSessionYieldingNotice(start, client, cardId, log26) {
|
|
|
8714
8288
|
} catch (endErr) {
|
|
8715
8289
|
if (endErr === err)
|
|
8716
8290
|
throw err;
|
|
8717
|
-
|
|
8291
|
+
log25.warn(`Could not yield this pool's own notice: ${endErr instanceof Error ? endErr.message : String(endErr)}`);
|
|
8718
8292
|
throw err;
|
|
8719
8293
|
}
|
|
8720
8294
|
return await start();
|
|
@@ -8751,7 +8325,7 @@ import {
|
|
|
8751
8325
|
writeFileSync as writeFileSync2
|
|
8752
8326
|
} from "node:fs";
|
|
8753
8327
|
import { dirname as dirname2, join as join6 } from "node:path";
|
|
8754
|
-
import { log as
|
|
8328
|
+
import { log as log25 } from "@gethmy/harness";
|
|
8755
8329
|
function emptyTokens() {
|
|
8756
8330
|
return cloneWithoutPrototype({
|
|
8757
8331
|
inputTokens: 0,
|
|
@@ -8890,7 +8464,7 @@ class StateStore {
|
|
|
8890
8464
|
const raw = readFileSync6(this.path, "utf-8");
|
|
8891
8465
|
const parsed = parseJsonWithoutPrototypes(raw);
|
|
8892
8466
|
if (parsed?.version !== SCHEMA_VERSION) {
|
|
8893
|
-
|
|
8467
|
+
log25.warn(TAG22, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
|
|
8894
8468
|
return cloneWithoutPrototype({
|
|
8895
8469
|
version: SCHEMA_VERSION,
|
|
8896
8470
|
daemonId: null,
|
|
@@ -8915,7 +8489,7 @@ class StateStore {
|
|
|
8915
8489
|
account: parsed.account ?? emptyAccount()
|
|
8916
8490
|
});
|
|
8917
8491
|
} catch (err) {
|
|
8918
|
-
|
|
8492
|
+
log25.error(TAG22, `failed to read state file: ${err instanceof Error ? err.message : err}`);
|
|
8919
8493
|
return emptyState();
|
|
8920
8494
|
}
|
|
8921
8495
|
}
|
|
@@ -9037,6 +8611,14 @@ class StateStore {
|
|
|
9037
8611
|
getCard(cardId) {
|
|
9038
8612
|
return this.state.cards.find((c) => c.cardId === cardId) ?? null;
|
|
9039
8613
|
}
|
|
8614
|
+
async forgetCard(cardId) {
|
|
8615
|
+
const before = this.state.cards.length;
|
|
8616
|
+
this.state.cards = this.state.cards.filter((c) => c.cardId !== cardId);
|
|
8617
|
+
if (this.state.cards.length === before)
|
|
8618
|
+
return false;
|
|
8619
|
+
await this.persist();
|
|
8620
|
+
return true;
|
|
8621
|
+
}
|
|
9040
8622
|
async incrementAttempt(cardId) {
|
|
9041
8623
|
const rec = this.ensureCard(cardId);
|
|
9042
8624
|
rec.attempts += 1;
|
|
@@ -9273,7 +8855,7 @@ class StateStore {
|
|
|
9273
8855
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
9274
8856
|
}
|
|
9275
8857
|
}
|
|
9276
|
-
var
|
|
8858
|
+
var TAG22 = "state-store", SCHEMA_VERSION = 1;
|
|
9277
8859
|
var init_state_store = __esm(() => {
|
|
9278
8860
|
init_dist();
|
|
9279
8861
|
init_paths();
|
|
@@ -9281,7 +8863,7 @@ var init_state_store = __esm(() => {
|
|
|
9281
8863
|
|
|
9282
8864
|
// src/stream-parser.ts
|
|
9283
8865
|
import { EventEmitter } from "node:events";
|
|
9284
|
-
import { log as
|
|
8866
|
+
import { log as log26 } from "@gethmy/harness";
|
|
9285
8867
|
function normalizeToolResultContent(raw) {
|
|
9286
8868
|
if (raw == null)
|
|
9287
8869
|
return;
|
|
@@ -9302,7 +8884,7 @@ function normalizeToolResultContent(raw) {
|
|
|
9302
8884
|
return String(raw);
|
|
9303
8885
|
}
|
|
9304
8886
|
}
|
|
9305
|
-
var
|
|
8887
|
+
var TAG23 = "stream-parser", StreamParser;
|
|
9306
8888
|
var init_stream_parser = __esm(() => {
|
|
9307
8889
|
StreamParser = class StreamParser extends EventEmitter {
|
|
9308
8890
|
buffer = "";
|
|
@@ -9349,14 +8931,14 @@ var init_stream_parser = __esm(() => {
|
|
|
9349
8931
|
try {
|
|
9350
8932
|
msg = JSON.parse(line);
|
|
9351
8933
|
} catch {
|
|
9352
|
-
|
|
8934
|
+
log26.debug(TAG23, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
9353
8935
|
return;
|
|
9354
8936
|
}
|
|
9355
8937
|
try {
|
|
9356
8938
|
this.handleMessage(msg);
|
|
9357
8939
|
} catch (err) {
|
|
9358
8940
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9359
|
-
|
|
8941
|
+
log26.warn(TAG23, `Error handling stream event: ${errMsg}`);
|
|
9360
8942
|
this.emit("parse_error", errMsg);
|
|
9361
8943
|
}
|
|
9362
8944
|
}
|
|
@@ -9432,7 +9014,7 @@ var init_stream_parser = __esm(() => {
|
|
|
9432
9014
|
});
|
|
9433
9015
|
|
|
9434
9016
|
// src/transitions.ts
|
|
9435
|
-
import { log as
|
|
9017
|
+
import { log as log27 } from "@gethmy/harness";
|
|
9436
9018
|
async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
9437
9019
|
let lastErr;
|
|
9438
9020
|
for (let i = 0;i < attempts; i++) {
|
|
@@ -9443,7 +9025,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
9443
9025
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
9444
9026
|
if (i < attempts - 1) {
|
|
9445
9027
|
const wait = backoffMs * 2 ** i;
|
|
9446
|
-
|
|
9028
|
+
log27.warn(TAG24, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
9447
9029
|
await new Promise((r) => setTimeout(r, wait));
|
|
9448
9030
|
}
|
|
9449
9031
|
}
|
|
@@ -9467,10 +9049,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9467
9049
|
if (opts.strictColumn) {
|
|
9468
9050
|
throw new TransitionError("move", 1, msg);
|
|
9469
9051
|
}
|
|
9470
|
-
|
|
9052
|
+
log27.warn(TAG24, `#${shortId}: ${msg} — skipping move`);
|
|
9471
9053
|
} else if (card.column_id !== target.id) {
|
|
9472
9054
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
9473
|
-
|
|
9055
|
+
log27.info(TAG24, `#${shortId} → "${target.name}"`);
|
|
9474
9056
|
card.column_id = target.id;
|
|
9475
9057
|
moveLanded = true;
|
|
9476
9058
|
} else {
|
|
@@ -9489,7 +9071,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9489
9071
|
continue;
|
|
9490
9072
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
9491
9073
|
existing.add(labelId);
|
|
9492
|
-
|
|
9074
|
+
log27.info(TAG24, `#${shortId} +label "${name}"`);
|
|
9493
9075
|
}
|
|
9494
9076
|
card.labelIds = Array.from(existing);
|
|
9495
9077
|
}
|
|
@@ -9501,23 +9083,23 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9501
9083
|
continue;
|
|
9502
9084
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
9503
9085
|
existing.delete(match.id);
|
|
9504
|
-
|
|
9086
|
+
log27.info(TAG24, `#${shortId} -label "${name}"`);
|
|
9505
9087
|
}
|
|
9506
9088
|
card.labelIds = Array.from(existing);
|
|
9507
9089
|
}
|
|
9508
9090
|
if (plan.updateCard) {
|
|
9509
9091
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
9510
|
-
|
|
9092
|
+
log27.info(TAG24, `#${shortId} updated`);
|
|
9511
9093
|
}
|
|
9512
9094
|
if (plan.endSession) {
|
|
9513
9095
|
const endResult = await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
9514
9096
|
result.endSession = endResult;
|
|
9515
|
-
|
|
9097
|
+
log27.info(TAG24, `#${shortId} session ended (${plan.endSession.status})`);
|
|
9516
9098
|
}
|
|
9517
9099
|
if (plan.assignAgent !== undefined) {
|
|
9518
9100
|
const assignedAgentId = plan.assignAgent;
|
|
9519
9101
|
await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
|
|
9520
|
-
|
|
9102
|
+
log27.info(TAG24, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
|
|
9521
9103
|
}
|
|
9522
9104
|
if (opts.store && opts.runId) {
|
|
9523
9105
|
try {
|
|
@@ -9531,11 +9113,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
9531
9113
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
9532
9114
|
return result?.label?.id ?? null;
|
|
9533
9115
|
} catch (err) {
|
|
9534
|
-
|
|
9116
|
+
log27.warn(TAG24, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
9535
9117
|
return null;
|
|
9536
9118
|
}
|
|
9537
9119
|
}
|
|
9538
|
-
var
|
|
9120
|
+
var TAG24 = "transition", TransitionError;
|
|
9539
9121
|
var init_transitions = __esm(() => {
|
|
9540
9122
|
TransitionError = class TransitionError extends Error {
|
|
9541
9123
|
step;
|
|
@@ -9565,7 +9147,7 @@ import {
|
|
|
9565
9147
|
formatDiffSummary,
|
|
9566
9148
|
GIT_NO_HOOKS as GIT_NO_HOOKS7,
|
|
9567
9149
|
implementRunContainmentCliArgs,
|
|
9568
|
-
log as
|
|
9150
|
+
log as log28,
|
|
9569
9151
|
probeDevServer,
|
|
9570
9152
|
removeSandboxContainer,
|
|
9571
9153
|
resolveStageGate,
|
|
@@ -9661,11 +9243,11 @@ class ReviewWorker {
|
|
|
9661
9243
|
cliSessionId: this.cliSessionId
|
|
9662
9244
|
});
|
|
9663
9245
|
} catch (err) {
|
|
9664
|
-
|
|
9246
|
+
log28.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
9665
9247
|
}
|
|
9666
9248
|
}
|
|
9667
9249
|
get tag() {
|
|
9668
|
-
return `${
|
|
9250
|
+
return `${TAG25}:${this.id}`;
|
|
9669
9251
|
}
|
|
9670
9252
|
get isIdle() {
|
|
9671
9253
|
return this.state === "idle";
|
|
@@ -9703,7 +9285,7 @@ class ReviewWorker {
|
|
|
9703
9285
|
try {
|
|
9704
9286
|
const after = await this.stateStore.addTurns(cardId, numTurns);
|
|
9705
9287
|
if (crossedTurnCap(before, after, cap)) {
|
|
9706
|
-
|
|
9288
|
+
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.`);
|
|
9707
9289
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
9708
9290
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), {
|
|
9709
9291
|
commentType: "blocker",
|
|
@@ -9745,12 +9327,12 @@ class ReviewWorker {
|
|
|
9745
9327
|
resumeMessage: null
|
|
9746
9328
|
});
|
|
9747
9329
|
} catch (err) {
|
|
9748
|
-
|
|
9330
|
+
log28.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
9749
9331
|
}
|
|
9750
9332
|
}
|
|
9751
9333
|
try {
|
|
9752
9334
|
this.state = "preparing";
|
|
9753
|
-
|
|
9335
|
+
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}"`);
|
|
9754
9336
|
this.startHeartbeat();
|
|
9755
9337
|
if (!resuming) {
|
|
9756
9338
|
await this.stateStore.insertRun({
|
|
@@ -9778,7 +9360,7 @@ class ReviewWorker {
|
|
|
9778
9360
|
const resolution = await resolveReviewBranch(card.description, repoRoot);
|
|
9779
9361
|
if (resolution.kind !== "branch") {
|
|
9780
9362
|
const why = resolution.kind === "skip" ? resolution.reason : "no branch or PR reference";
|
|
9781
|
-
|
|
9363
|
+
log28.info(this.tag, `#${card.short_id} not auto-reviewable (${why}) — marking for human review (staying in Review)`);
|
|
9782
9364
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9783
9365
|
return;
|
|
9784
9366
|
}
|
|
@@ -9789,13 +9371,13 @@ class ReviewWorker {
|
|
|
9789
9371
|
reviewedSha: extractReviewedSha2(card.description ?? null)
|
|
9790
9372
|
});
|
|
9791
9373
|
if (continuity.kind === "skip") {
|
|
9792
|
-
|
|
9374
|
+
log28.warn(this.tag, `#${card.short_id} refusing the PR-derived re-review (${continuity.reason}) — marking for human review (staying in Review)`);
|
|
9793
9375
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9794
9376
|
return;
|
|
9795
9377
|
}
|
|
9796
9378
|
}
|
|
9797
9379
|
this.branchName = resolution.branch;
|
|
9798
|
-
|
|
9380
|
+
log28.info(this.tag, `Review branch: ${this.branchName}`);
|
|
9799
9381
|
let reviewSession;
|
|
9800
9382
|
try {
|
|
9801
9383
|
const startSession = () => this.client.startAgentSession(card.id, {
|
|
@@ -9811,14 +9393,14 @@ class ReviewWorker {
|
|
|
9811
9393
|
awaitingDecisionUntil: null
|
|
9812
9394
|
});
|
|
9813
9395
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
9814
|
-
info: (message) =>
|
|
9815
|
-
warn: (message) =>
|
|
9396
|
+
info: (message) => log28.info(this.tag, message),
|
|
9397
|
+
warn: (message) => log28.warn(this.tag, message)
|
|
9816
9398
|
});
|
|
9817
9399
|
reviewSession = started.session;
|
|
9818
9400
|
} catch (err) {
|
|
9819
9401
|
if (isSessionConflict(err)) {
|
|
9820
9402
|
this.sessionConflict = true;
|
|
9821
|
-
|
|
9403
|
+
log28.info(this.tag, `Skipping review of #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9822
9404
|
return;
|
|
9823
9405
|
}
|
|
9824
9406
|
throw err;
|
|
@@ -9839,7 +9421,7 @@ class ReviewWorker {
|
|
|
9839
9421
|
const cwd = this.worktreePath;
|
|
9840
9422
|
const devServer = await this.startDevServer(card, cwd, port);
|
|
9841
9423
|
if (devServer.skipReason) {
|
|
9842
|
-
|
|
9424
|
+
log28.warn(this.tag, `Reviewing #${card.short_id} without the visual lens — ${devServer.skipReason}`);
|
|
9843
9425
|
}
|
|
9844
9426
|
await this.client.updateAgentProgress(card.id, {
|
|
9845
9427
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9877,10 +9459,10 @@ class ReviewWorker {
|
|
|
9877
9459
|
pinnedContract = extractPinnedContract(comments, this.identity);
|
|
9878
9460
|
}
|
|
9879
9461
|
} catch (err) {
|
|
9880
|
-
|
|
9462
|
+
log28.warn(this.tag, `pinned-contract fetch failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9881
9463
|
}
|
|
9882
9464
|
if (pinnedContract) {
|
|
9883
|
-
|
|
9465
|
+
log28.info(this.tag, `Grading pinned contract for #${card.short_id} (${pinnedContract.assertions.length} criteria)`);
|
|
9884
9466
|
}
|
|
9885
9467
|
}
|
|
9886
9468
|
const systemPrompt = buildReviewSystemPrompt();
|
|
@@ -9895,7 +9477,7 @@ class ReviewWorker {
|
|
|
9895
9477
|
} else {
|
|
9896
9478
|
const planCriteria2 = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
9897
9479
|
if (planCriteria2.length > 0) {
|
|
9898
|
-
|
|
9480
|
+
log28.info(this.tag, `Grading ${planCriteria2.length} plan criteria for #${card.short_id}`);
|
|
9899
9481
|
}
|
|
9900
9482
|
userPrompt = buildReviewUserPrompt(enriched, this.branchName, cwd, previewUrl, diffSummary, this.config.worktree.baseBranch, undefined, pinnedContract, planCriteria2, devServer.skipReason);
|
|
9901
9483
|
if (resuming && this.resumeMessage) {
|
|
@@ -9912,7 +9494,7 @@ ${userPrompt}`;
|
|
|
9912
9494
|
contextIncluded: { source: "review-knowledge", mode: "review" }
|
|
9913
9495
|
});
|
|
9914
9496
|
} catch (err) {
|
|
9915
|
-
|
|
9497
|
+
log28.warn(this.tag, `prompt_history persistence skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
9916
9498
|
}
|
|
9917
9499
|
await this.client.updateAgentProgress(card.id, {
|
|
9918
9500
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9922,7 +9504,7 @@ ${userPrompt}`;
|
|
|
9922
9504
|
progressPercent: 20
|
|
9923
9505
|
});
|
|
9924
9506
|
this.timeoutTimer = setTimeout(() => {
|
|
9925
|
-
|
|
9507
|
+
log28.warn(this.tag, `Review timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
9926
9508
|
this.timedOut = true;
|
|
9927
9509
|
this.cancel("timeout");
|
|
9928
9510
|
}, this.config.review.maxTimeout);
|
|
@@ -9944,10 +9526,10 @@ ${userPrompt}`;
|
|
|
9944
9526
|
}
|
|
9945
9527
|
this.state = "completing";
|
|
9946
9528
|
await this.recordPhase("completing");
|
|
9947
|
-
|
|
9529
|
+
log28.info(this.tag, `Claude review finished for #${card.short_id}`);
|
|
9948
9530
|
this.killDevServer();
|
|
9949
9531
|
const result = parseReviewOutput(stdout);
|
|
9950
|
-
|
|
9532
|
+
log28.info(this.tag, `Review verdict: ${result.verdict} (${result.findings.length} finding(s))`);
|
|
9951
9533
|
await this.client.updateAgentProgress(card.id, {
|
|
9952
9534
|
agentIdentifier: this.sessionIdentifier,
|
|
9953
9535
|
agentName: `${AGENT_NAME} (Review)`,
|
|
@@ -9955,7 +9537,7 @@ ${userPrompt}`;
|
|
|
9955
9537
|
currentTask: `Processing ${result.verdict} verdict`,
|
|
9956
9538
|
progressPercent: 80
|
|
9957
9539
|
});
|
|
9958
|
-
await runReviewCompletion(this.client, card, result, this.config, cwd, this.branchName, sessionStats, this.lastRunLogPath, this.
|
|
9540
|
+
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);
|
|
9959
9541
|
await this.collectReviewGate(card, result);
|
|
9960
9542
|
} catch (err) {
|
|
9961
9543
|
if (err instanceof BudgetPauseError) {
|
|
@@ -9969,7 +9551,7 @@ ${userPrompt}`;
|
|
|
9969
9551
|
this.state = "error";
|
|
9970
9552
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9971
9553
|
this.lastErrorMessage = msg;
|
|
9972
|
-
|
|
9554
|
+
log28.error(this.tag, `Error reviewing #${card.short_id}: ${msg}`);
|
|
9973
9555
|
this.lastRunLogPath = appendRunLog(this.tag, this.runId, card.short_id, `[${new Date().toISOString()}] review failed before completion: ${msg}`) ?? this.lastRunLogPath;
|
|
9974
9556
|
const reviewHandback = await guardedHandback(this.client, card.id, {
|
|
9975
9557
|
agentId: this.identity.agentId,
|
|
@@ -9985,7 +9567,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9985
9567
|
agentSessionId: this.sessionId ?? undefined
|
|
9986
9568
|
});
|
|
9987
9569
|
} catch (cErr) {
|
|
9988
|
-
|
|
9570
|
+
log28.warn(this.tag, `Failed to post the failure reason to #${card.short_id}: ${cErr instanceof Error ? cErr.message : cErr}`);
|
|
9989
9571
|
}
|
|
9990
9572
|
}
|
|
9991
9573
|
try {
|
|
@@ -9997,21 +9579,21 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9997
9579
|
}
|
|
9998
9580
|
});
|
|
9999
9581
|
} catch (tErr) {
|
|
10000
|
-
|
|
9582
|
+
log28.error(this.tag, `endAgentSession unrecoverable on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
10001
9583
|
}
|
|
10002
9584
|
if (reviewHandback.verdict.proceed) {
|
|
10003
9585
|
try {
|
|
10004
9586
|
await moveCardToColumn(this.client, reviewHandback.card ?? card, this.config.review.failColumn);
|
|
10005
|
-
|
|
9587
|
+
log28.info(this.tag, `Moved #${card.short_id} to "${this.config.review.failColumn}" after error`);
|
|
10006
9588
|
} catch {
|
|
10007
|
-
|
|
9589
|
+
log28.warn(this.tag, "Failed to move card to fail column after error");
|
|
10008
9590
|
}
|
|
10009
9591
|
} else if (reviewHandback.verdict.reason === "unreadable" || reviewHandback.verdict.reason === "released" || reviewHandback.verdict.reason === "done") {
|
|
10010
9592
|
try {
|
|
10011
9593
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
10012
|
-
|
|
9594
|
+
log28.warn(this.tag, `#${card.short_id} could not be re-read — labelled "${NEED_REVIEW_LABEL}" so reconcile stops re-enqueueing the review`);
|
|
10013
9595
|
} catch {
|
|
10014
|
-
|
|
9596
|
+
log28.warn(this.tag, `Failed to add "${NEED_REVIEW_LABEL}" label after an unreadable card`);
|
|
10015
9597
|
}
|
|
10016
9598
|
}
|
|
10017
9599
|
if (this.runId) {
|
|
@@ -10055,7 +9637,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10055
9637
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
10056
9638
|
const waitHours = this.config.budget.pause.waitHours;
|
|
10057
9639
|
const until = computeDecisionDeadline(waitHours);
|
|
10058
|
-
|
|
9640
|
+
log28.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
10059
9641
|
try {
|
|
10060
9642
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
10061
9643
|
holderMessage,
|
|
@@ -10067,7 +9649,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10067
9649
|
agentSessionId: this.sessionId ?? undefined
|
|
10068
9650
|
});
|
|
10069
9651
|
} catch (commentErr) {
|
|
10070
|
-
|
|
9652
|
+
log28.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
10071
9653
|
}
|
|
10072
9654
|
if (this.runId) {
|
|
10073
9655
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -10078,7 +9660,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10078
9660
|
awaitingDecisionUntil: until
|
|
10079
9661
|
});
|
|
10080
9662
|
} catch (storeErr) {
|
|
10081
|
-
|
|
9663
|
+
log28.error(this.tag, `#${card.short_id} could not be re-parked after a resume conflict — the daemon has no hold on it: ${storeErr}`);
|
|
10082
9664
|
}
|
|
10083
9665
|
}
|
|
10084
9666
|
}
|
|
@@ -10091,7 +9673,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10091
9673
|
this.progressTracker = null;
|
|
10092
9674
|
const waitHours = this.config.budget.pause.waitHours;
|
|
10093
9675
|
const until = computeDecisionDeadline(waitHours);
|
|
10094
|
-
|
|
9676
|
+
log28.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
10095
9677
|
const body = formatBudgetComment({
|
|
10096
9678
|
trigger,
|
|
10097
9679
|
numTurns: stats?.cost?.numTurns ?? 0,
|
|
@@ -10113,7 +9695,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10113
9695
|
});
|
|
10114
9696
|
commentId = res?.comment?.id ?? null;
|
|
10115
9697
|
} catch (err) {
|
|
10116
|
-
|
|
9698
|
+
log28.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
10117
9699
|
}
|
|
10118
9700
|
try {
|
|
10119
9701
|
await this.client.updateAgentProgress(card.id, {
|
|
@@ -10124,7 +9706,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10124
9706
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
10125
9707
|
});
|
|
10126
9708
|
} catch (err) {
|
|
10127
|
-
|
|
9709
|
+
log28.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
10128
9710
|
}
|
|
10129
9711
|
if (this.runId) {
|
|
10130
9712
|
try {
|
|
@@ -10135,14 +9717,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10135
9717
|
...this.endLedger()
|
|
10136
9718
|
});
|
|
10137
9719
|
} catch (err) {
|
|
10138
|
-
|
|
9720
|
+
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}`);
|
|
10139
9721
|
}
|
|
10140
9722
|
}
|
|
10141
9723
|
}
|
|
10142
9724
|
async pause() {
|
|
10143
9725
|
if (!this.isActive || !this.process || this.process.killed)
|
|
10144
9726
|
return;
|
|
10145
|
-
|
|
9727
|
+
log28.info(this.tag, `Pausing review on ${this.cardId}`);
|
|
10146
9728
|
signalGroup(this.process, "SIGSTOP");
|
|
10147
9729
|
if (this.timeoutTimer) {
|
|
10148
9730
|
clearTimeout(this.timeoutTimer);
|
|
@@ -10156,17 +9738,17 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10156
9738
|
status: "paused"
|
|
10157
9739
|
});
|
|
10158
9740
|
} catch {
|
|
10159
|
-
|
|
9741
|
+
log28.warn(this.tag, "Failed to update agent session to paused");
|
|
10160
9742
|
}
|
|
10161
9743
|
}
|
|
10162
9744
|
}
|
|
10163
9745
|
async resume() {
|
|
10164
9746
|
if (!this.isActive || !this.process || this.process.killed)
|
|
10165
9747
|
return;
|
|
10166
|
-
|
|
9748
|
+
log28.info(this.tag, `Resuming review on ${this.cardId}`);
|
|
10167
9749
|
signalGroup(this.process, "SIGCONT");
|
|
10168
9750
|
this.timeoutTimer = setTimeout(() => {
|
|
10169
|
-
|
|
9751
|
+
log28.warn(this.tag, `Timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
10170
9752
|
this.timedOut = true;
|
|
10171
9753
|
this.cancel("timeout");
|
|
10172
9754
|
}, this.config.review.maxTimeout);
|
|
@@ -10178,7 +9760,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10178
9760
|
status: "working"
|
|
10179
9761
|
});
|
|
10180
9762
|
} catch {
|
|
10181
|
-
|
|
9763
|
+
log28.warn(this.tag, "Failed to update agent session to working");
|
|
10182
9764
|
}
|
|
10183
9765
|
}
|
|
10184
9766
|
}
|
|
@@ -10187,7 +9769,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10187
9769
|
return;
|
|
10188
9770
|
this.aborted = true;
|
|
10189
9771
|
this.state = "cancelling";
|
|
10190
|
-
|
|
9772
|
+
log28.info(this.tag, `Cancelling review on ${this.cardId}`);
|
|
10191
9773
|
const snapshotStats = this.lastSessionStats ?? this.progressTracker?.stats;
|
|
10192
9774
|
if (this.progressTracker) {
|
|
10193
9775
|
this.progressTracker?.stop();
|
|
@@ -10240,11 +9822,11 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10240
9822
|
"--",
|
|
10241
9823
|
prompt
|
|
10242
9824
|
];
|
|
10243
|
-
|
|
9825
|
+
log28.info(this.tag, `Spawning review: claude ${args.slice(0, 5).join(" ")} ...`);
|
|
10244
9826
|
const runLog = openRunLog(this.tag, this.runId, shortId);
|
|
10245
9827
|
this.lastRunLogPath = runLog?.path ?? null;
|
|
10246
9828
|
if (runLog) {
|
|
10247
|
-
|
|
9829
|
+
log28.info(this.tag, `Run log: ${runLog.path}`);
|
|
10248
9830
|
runLog.stream.write(`# run=${this.runId} card=#${shortId} pipeline=review started=${new Date().toISOString()}
|
|
10249
9831
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
10250
9832
|
|
|
@@ -10263,7 +9845,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10263
9845
|
this.captureCliSessionId(parser.sessionId);
|
|
10264
9846
|
});
|
|
10265
9847
|
parser.on("parse_error", (msg) => {
|
|
10266
|
-
|
|
9848
|
+
log28.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
10267
9849
|
runLog?.stream.write(`
|
|
10268
9850
|
[parse_error] ${msg}
|
|
10269
9851
|
`);
|
|
@@ -10342,16 +9924,16 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10342
9924
|
const evidence = await collectGateEvidence(registry, context);
|
|
10343
9925
|
const evaluation = gateEvaluate(resolved.gate, evidence);
|
|
10344
9926
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, toStageGateEvidenceInsert(context, evidence));
|
|
10345
|
-
|
|
9927
|
+
log28.info(this.tag, `Recorded review_passed gate evidence for #${card.short_id} stage "${resolved.stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
10346
9928
|
} catch (err) {
|
|
10347
|
-
|
|
9929
|
+
log28.warn(this.tag, `review gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
10348
9930
|
}
|
|
10349
9931
|
}
|
|
10350
9932
|
async startDevServer(card, cwd, port) {
|
|
10351
9933
|
const declined = devServerSkipReason(cwd, this.config.commands);
|
|
10352
9934
|
if (declined)
|
|
10353
9935
|
return { previewUrl: null, skipReason: declined };
|
|
10354
|
-
|
|
9936
|
+
log28.info(this.tag, `Starting dev server on port ${port}...`);
|
|
10355
9937
|
const launch = devServerLaunch({
|
|
10356
9938
|
worktreePath: cwd,
|
|
10357
9939
|
port,
|
|
@@ -10392,14 +9974,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10392
9974
|
skipReason: launch.origin ? `${launch.origin}, and ${failure}` : failure
|
|
10393
9975
|
};
|
|
10394
9976
|
}
|
|
10395
|
-
|
|
9977
|
+
log28.info(this.tag, `Dev server ready on port ${port}`);
|
|
10396
9978
|
return { previewUrl: `http://localhost:${port}`, skipReason: null };
|
|
10397
9979
|
}
|
|
10398
9980
|
killDevServer() {
|
|
10399
9981
|
if (this.devServerProcess && !this.devServerProcess.killed) {
|
|
10400
9982
|
signalGroup(this.devServerProcess, "SIGTERM");
|
|
10401
9983
|
this.devServerProcess = null;
|
|
10402
|
-
|
|
9984
|
+
log28.debug(this.tag, "Killed dev server group");
|
|
10403
9985
|
}
|
|
10404
9986
|
if (this.devServerContainer) {
|
|
10405
9987
|
const name = this.devServerContainer;
|
|
@@ -10422,7 +10004,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10422
10004
|
try {
|
|
10423
10005
|
cleanupWorktree4(this.worktreePath);
|
|
10424
10006
|
} catch {
|
|
10425
|
-
|
|
10007
|
+
log28.warn(this.tag, "Failed to cleanup review worktree");
|
|
10426
10008
|
}
|
|
10427
10009
|
}
|
|
10428
10010
|
this.process = null;
|
|
@@ -10434,7 +10016,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10434
10016
|
this.lastSessionStats = null;
|
|
10435
10017
|
}
|
|
10436
10018
|
}
|
|
10437
|
-
var
|
|
10019
|
+
var TAG25 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
10438
10020
|
var init_review_worker = __esm(() => {
|
|
10439
10021
|
init_dist();
|
|
10440
10022
|
init_board_helpers();
|
|
@@ -10458,7 +10040,7 @@ var init_review_worker = __esm(() => {
|
|
|
10458
10040
|
|
|
10459
10041
|
// src/sleep-guard.ts
|
|
10460
10042
|
import { spawn } from "node:child_process";
|
|
10461
|
-
import { log as
|
|
10043
|
+
import { log as log29 } from "@gethmy/harness";
|
|
10462
10044
|
|
|
10463
10045
|
class SleepGuard {
|
|
10464
10046
|
platform;
|
|
@@ -10486,7 +10068,7 @@ class SleepGuard {
|
|
|
10486
10068
|
if (!this.child.killed)
|
|
10487
10069
|
this.child.kill("SIGTERM");
|
|
10488
10070
|
this.child = null;
|
|
10489
|
-
|
|
10071
|
+
log29.info(TAG26, "sleep assertion released");
|
|
10490
10072
|
}
|
|
10491
10073
|
}
|
|
10492
10074
|
start() {
|
|
@@ -10501,7 +10083,7 @@ class SleepGuard {
|
|
|
10501
10083
|
spawned = true;
|
|
10502
10084
|
});
|
|
10503
10085
|
child.on("error", (err) => {
|
|
10504
|
-
|
|
10086
|
+
log29.warn(TAG26, `caffeinate unavailable: ${err.message}`);
|
|
10505
10087
|
if (this.child === child)
|
|
10506
10088
|
this.child = null;
|
|
10507
10089
|
});
|
|
@@ -10514,23 +10096,23 @@ class SleepGuard {
|
|
|
10514
10096
|
});
|
|
10515
10097
|
child.unref();
|
|
10516
10098
|
this.child = child;
|
|
10517
|
-
|
|
10099
|
+
log29.info(TAG26, "sleep assertion acquired (caffeinate -i)");
|
|
10518
10100
|
} catch (err) {
|
|
10519
|
-
|
|
10101
|
+
log29.warn(TAG26, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
10520
10102
|
}
|
|
10521
10103
|
}
|
|
10522
10104
|
}
|
|
10523
|
-
var
|
|
10105
|
+
var TAG26 = "sleep-guard";
|
|
10524
10106
|
var init_sleep_guard = () => {};
|
|
10525
10107
|
|
|
10526
10108
|
// src/unblock.ts
|
|
10527
|
-
import { log as
|
|
10109
|
+
import { log as log30 } from "@gethmy/harness";
|
|
10528
10110
|
async function fetchBlocksLinks(client, cardId) {
|
|
10529
10111
|
try {
|
|
10530
10112
|
const { links } = await client.getCardLinks(cardId);
|
|
10531
10113
|
return links.filter((l) => l.link_type === "blocks");
|
|
10532
10114
|
} catch (err) {
|
|
10533
|
-
|
|
10115
|
+
log30.warn(TAG27, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10534
10116
|
return null;
|
|
10535
10117
|
}
|
|
10536
10118
|
}
|
|
@@ -10564,31 +10146,31 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
10564
10146
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
10565
10147
|
if (successors.length === 0)
|
|
10566
10148
|
return;
|
|
10567
|
-
|
|
10149
|
+
log30.info(TAG27, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
10568
10150
|
for (const link of successors) {
|
|
10569
10151
|
const successorId = link.target_card.id;
|
|
10570
10152
|
try {
|
|
10571
10153
|
const { card } = await deps.client.getCard(successorId);
|
|
10572
10154
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
10573
|
-
|
|
10155
|
+
log30.info(TAG27, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
10574
10156
|
await deps.client.updateCard(successorId, {
|
|
10575
10157
|
assignedAgentId: deps.agentId
|
|
10576
10158
|
});
|
|
10577
10159
|
} else {
|
|
10578
|
-
|
|
10160
|
+
log30.debug(TAG27, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
10579
10161
|
continue;
|
|
10580
10162
|
}
|
|
10581
10163
|
await deps.enqueue(successorId);
|
|
10582
10164
|
} catch (err) {
|
|
10583
|
-
|
|
10165
|
+
log30.warn(TAG27, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
10584
10166
|
}
|
|
10585
10167
|
}
|
|
10586
10168
|
}
|
|
10587
|
-
var
|
|
10169
|
+
var TAG27 = "unblock";
|
|
10588
10170
|
var init_unblock = () => {};
|
|
10589
10171
|
|
|
10590
10172
|
// src/cli-agent-runner.ts
|
|
10591
|
-
import { log as
|
|
10173
|
+
import { log as log31 } from "@gethmy/harness";
|
|
10592
10174
|
function truncateOutput(value) {
|
|
10593
10175
|
return value === undefined ? undefined : value.slice(0, MAX_OUTPUT_CHARS);
|
|
10594
10176
|
}
|
|
@@ -10753,7 +10335,7 @@ class CliAgentRunner {
|
|
|
10753
10335
|
return;
|
|
10754
10336
|
const draft = sanitizeRunEventDraft(body);
|
|
10755
10337
|
if (runEventPayloadExceedsLimit(draft.payload)) {
|
|
10756
|
-
|
|
10338
|
+
log31.warn(TAG28, `Dropping ${body.kind} event: payload exceeds ${MAX_RUN_EVENT_PAYLOAD_BYTES} bytes, or cannot be serialized`);
|
|
10757
10339
|
return;
|
|
10758
10340
|
}
|
|
10759
10341
|
this.buffer.push({ ...draft, createdAt: new Date().toISOString() });
|
|
@@ -10785,15 +10367,15 @@ class CliAgentRunner {
|
|
|
10785
10367
|
this.requeue(batch);
|
|
10786
10368
|
if (batch.length > 1) {
|
|
10787
10369
|
this.nextBatchSize = Math.ceil(batch.length / 2);
|
|
10788
|
-
|
|
10370
|
+
log31.warn(TAG28, `Flush of ${batch.length} run event(s) failed ${MAX_BATCH_ATTEMPTS}×; bisecting to ${this.nextBatchSize}: ${err}`);
|
|
10789
10371
|
return;
|
|
10790
10372
|
}
|
|
10791
10373
|
const dropped = this.buffer.shift();
|
|
10792
10374
|
this.nextBatchSize = MAX_BUFFER;
|
|
10793
|
-
|
|
10375
|
+
log31.warn(TAG28, `Dropping one ${dropped?.kind} event the API refused ${MAX_BATCH_ATTEMPTS}×: ${err}`);
|
|
10794
10376
|
return;
|
|
10795
10377
|
}
|
|
10796
|
-
|
|
10378
|
+
log31.warn(TAG28, `Failed to flush run events (attempt ${this.batchAttempts}/${MAX_BATCH_ATTEMPTS}): ${err}`);
|
|
10797
10379
|
this.requeue(batch);
|
|
10798
10380
|
} finally {
|
|
10799
10381
|
this.flushing = false;
|
|
@@ -10836,26 +10418,26 @@ function mapCost(cost) {
|
|
|
10836
10418
|
durationMs: cost.durationMs
|
|
10837
10419
|
};
|
|
10838
10420
|
}
|
|
10839
|
-
var
|
|
10421
|
+
var TAG28 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN = 8000, MAX_BATCH_ATTEMPTS = 3, FINAL_FLUSH_ATTEMPTS = 40;
|
|
10840
10422
|
var init_cli_agent_runner = __esm(() => {
|
|
10841
10423
|
init_dist();
|
|
10842
10424
|
});
|
|
10843
10425
|
|
|
10844
10426
|
// src/fanout.ts
|
|
10845
|
-
import { log as
|
|
10427
|
+
import { log as log32 } from "@gethmy/harness";
|
|
10846
10428
|
async function fetchLinks(client, cardId) {
|
|
10847
10429
|
try {
|
|
10848
10430
|
const { links } = await client.getCardLinks(cardId);
|
|
10849
10431
|
return links;
|
|
10850
10432
|
} catch (err) {
|
|
10851
|
-
|
|
10433
|
+
log32.warn(TAG29, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10852
10434
|
return null;
|
|
10853
10435
|
}
|
|
10854
10436
|
}
|
|
10855
10437
|
async function isFanoutChildOf(card, stage, client) {
|
|
10856
10438
|
const links = await fetchLinks(client, card.id);
|
|
10857
10439
|
if (links === null) {
|
|
10858
|
-
|
|
10440
|
+
log32.warn(TAG29, `#${card.short_id}: link read failed — treating as a fan-out child (fail closed, no recursive dispatch)`);
|
|
10859
10441
|
return true;
|
|
10860
10442
|
}
|
|
10861
10443
|
const parents = links.filter((l) => l.direction === "outgoing" && l.link_type === "is_part_of");
|
|
@@ -10942,7 +10524,7 @@ async function readStageHandoff(card, fromStage, deps) {
|
|
|
10942
10524
|
}
|
|
10943
10525
|
return null;
|
|
10944
10526
|
} catch (err) {
|
|
10945
|
-
|
|
10527
|
+
log32.warn(TAG29, `handoff read failed for #${card.short_id} stage "${fromStage}": ${err instanceof Error ? err.message : err}`);
|
|
10946
10528
|
return null;
|
|
10947
10529
|
}
|
|
10948
10530
|
}
|
|
@@ -11043,7 +10625,7 @@ async function dispatchWave(parent, stage, plan, items, deps) {
|
|
|
11043
10625
|
});
|
|
11044
10626
|
spawned = res.children ?? [];
|
|
11045
10627
|
} catch (err) {
|
|
11046
|
-
|
|
10628
|
+
log32.warn(TAG29, `child spawn failed for #${parent.short_id} stage "${stage.id}": ${err instanceof Error ? err.message : err}`);
|
|
11047
10629
|
return 0;
|
|
11048
10630
|
}
|
|
11049
10631
|
let created = 0;
|
|
@@ -11098,10 +10680,10 @@ async function seedChildHandoff(parent, stage, item, total, childId, deps) {
|
|
|
11098
10680
|
try {
|
|
11099
10681
|
await deps.client.addComment(childId, body, { commentType: "decision" });
|
|
11100
10682
|
} catch (err) {
|
|
11101
|
-
|
|
10683
|
+
log32.warn(TAG29, `seed handoff failed for child ${childId}: ${err instanceof Error ? err.message : err}`);
|
|
11102
10684
|
}
|
|
11103
10685
|
}
|
|
11104
|
-
var
|
|
10686
|
+
var TAG29 = "fanout";
|
|
11105
10687
|
var init_fanout = __esm(() => {
|
|
11106
10688
|
init_dist();
|
|
11107
10689
|
});
|
|
@@ -11336,7 +10918,7 @@ function runSizedPayload(input) {
|
|
|
11336
10918
|
}
|
|
11337
10919
|
|
|
11338
10920
|
// src/stage-advance.ts
|
|
11339
|
-
import { gateConfigErrorReason, log as
|
|
10921
|
+
import { gateConfigErrorReason, log as log33 } from "@gethmy/harness";
|
|
11340
10922
|
function endDispositionFor(outcome) {
|
|
11341
10923
|
return "endDisposition" in outcome && outcome.endDisposition ? outcome.endDisposition : { status: "completed" };
|
|
11342
10924
|
}
|
|
@@ -11367,7 +10949,7 @@ async function resolveStageColumnName(client, card, stage) {
|
|
|
11367
10949
|
const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
|
|
11368
10950
|
return match ? match.name : null;
|
|
11369
10951
|
} catch (err) {
|
|
11370
|
-
|
|
10952
|
+
log33.warn(TAG30, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11371
10953
|
return null;
|
|
11372
10954
|
}
|
|
11373
10955
|
}
|
|
@@ -11406,7 +10988,7 @@ async function holdGateMisconfigured(card, stage, detail, deps) {
|
|
|
11406
10988
|
});
|
|
11407
10989
|
} catch {}
|
|
11408
10990
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, { agentSessionId: deps.agentSessionId });
|
|
11409
|
-
|
|
10991
|
+
log33.info(TAG30, `#${card.short_id} GateMisconfigured: ${reason}`);
|
|
11410
10992
|
return { kind: "held_misconfigured", reason, endDisposition };
|
|
11411
10993
|
}
|
|
11412
10994
|
function firstErrorMessage(evaluation) {
|
|
@@ -11437,7 +11019,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11437
11019
|
evidence,
|
|
11438
11020
|
summary
|
|
11439
11021
|
});
|
|
11440
|
-
|
|
11022
|
+
log33.info(TAG30, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
|
|
11441
11023
|
if (decision === "exit") {
|
|
11442
11024
|
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
11443
11025
|
deps.sink?.recordLoopCompleted?.({
|
|
@@ -11482,7 +11064,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11482
11064
|
blockers: [reason],
|
|
11483
11065
|
agentSessionId: deps.agentSessionId
|
|
11484
11066
|
});
|
|
11485
|
-
|
|
11067
|
+
log33.info(TAG30, `#${card.short_id} LoopExhausted: ${reason}`);
|
|
11486
11068
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11487
11069
|
}
|
|
11488
11070
|
const guard = await guardStageReclaim(card, `converge-loop iteration of "${stage.name}"`, deps);
|
|
@@ -11507,7 +11089,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11507
11089
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11508
11090
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11509
11091
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11510
|
-
|
|
11092
|
+
log33.info(TAG30, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
|
|
11511
11093
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11512
11094
|
}
|
|
11513
11095
|
async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
|
|
@@ -11529,7 +11111,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
|
|
|
11529
11111
|
agentSessionId: deps.agentSessionId ?? undefined
|
|
11530
11112
|
});
|
|
11531
11113
|
} catch (err) {
|
|
11532
|
-
|
|
11114
|
+
log33.warn(TAG30, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11533
11115
|
}
|
|
11534
11116
|
}
|
|
11535
11117
|
async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
|
|
@@ -11557,7 +11139,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11557
11139
|
reason: "Playbook complete — final stage gate passed."
|
|
11558
11140
|
});
|
|
11559
11141
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11560
|
-
|
|
11142
|
+
log33.info(TAG30, `#${card.short_id} terminal stage "${stage.name}" passed — playbook complete (done left to the column)`);
|
|
11561
11143
|
return { kind: "completed_terminal" };
|
|
11562
11144
|
}
|
|
11563
11145
|
if (next.kind === "out_of_range") {
|
|
@@ -11589,7 +11171,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11589
11171
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11590
11172
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11591
11173
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11592
|
-
|
|
11174
|
+
log33.info(TAG30, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
|
|
11593
11175
|
if (next.stage.owner === "human") {
|
|
11594
11176
|
const reason = `Stage "${next.stage.name}" is yours: ${handoffText(next.stage)}`;
|
|
11595
11177
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
|
|
@@ -11626,7 +11208,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11626
11208
|
blockers: [reason],
|
|
11627
11209
|
agentSessionId: deps.agentSessionId
|
|
11628
11210
|
});
|
|
11629
|
-
|
|
11211
|
+
log33.info(TAG30, `#${card.short_id} GateUnmetExhausted: ${reason}`);
|
|
11630
11212
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11631
11213
|
}
|
|
11632
11214
|
const guard = await guardStageReclaim(card, `gate-unmet re-run of "${stage.name}"`, deps);
|
|
@@ -11649,7 +11231,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11649
11231
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11650
11232
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11651
11233
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11652
|
-
|
|
11234
|
+
log33.info(TAG30, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
|
|
11653
11235
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11654
11236
|
}
|
|
11655
11237
|
async function guardStageReclaim(card, what, deps) {
|
|
@@ -11675,10 +11257,10 @@ async function guardStageReclaim(card, what, deps) {
|
|
|
11675
11257
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11676
11258
|
released = stillOurs;
|
|
11677
11259
|
} catch (err) {
|
|
11678
|
-
|
|
11260
|
+
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" : ""}`);
|
|
11679
11261
|
}
|
|
11680
11262
|
}
|
|
11681
|
-
|
|
11263
|
+
log33.info(TAG30, `#${card.short_id} ${what} refused — ${detail} (${reason})${released ? "; released the daemon's claim so the stage router stops here" : ""}`);
|
|
11682
11264
|
try {
|
|
11683
11265
|
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.`, {
|
|
11684
11266
|
commentType: "decision",
|
|
@@ -11700,7 +11282,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11700
11282
|
try {
|
|
11701
11283
|
await runTransition(client, card, { removeLabels: [AGENT_LABEL] }, { store: stateStore, runId });
|
|
11702
11284
|
} catch (err) {
|
|
11703
|
-
|
|
11285
|
+
log33.warn(TAG30, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11704
11286
|
}
|
|
11705
11287
|
return {
|
|
11706
11288
|
status: opts.endStatus ?? "paused",
|
|
@@ -11709,7 +11291,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11709
11291
|
failureSummary: reason.slice(0, 300)
|
|
11710
11292
|
};
|
|
11711
11293
|
}
|
|
11712
|
-
var
|
|
11294
|
+
var TAG30 = "stage-advance", AGENT_LABEL = "agent";
|
|
11713
11295
|
var init_stage_advance = __esm(() => {
|
|
11714
11296
|
init_dist();
|
|
11715
11297
|
init_handback();
|
|
@@ -11785,7 +11367,7 @@ import {
|
|
|
11785
11367
|
GIT_NO_HOOKS as GIT_NO_HOOKS8,
|
|
11786
11368
|
implementRunContainment,
|
|
11787
11369
|
implementRunContainmentCliArgs as implementRunContainmentCliArgs2,
|
|
11788
|
-
log as
|
|
11370
|
+
log as log34,
|
|
11789
11371
|
makeBranchName,
|
|
11790
11372
|
normalizeGateSpec,
|
|
11791
11373
|
pushBranch as pushBranch3,
|
|
@@ -11991,11 +11573,11 @@ class Worker {
|
|
|
11991
11573
|
sessionId: this.sessionId
|
|
11992
11574
|
});
|
|
11993
11575
|
} catch (err) {
|
|
11994
|
-
|
|
11576
|
+
log34.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
11995
11577
|
}
|
|
11996
11578
|
}
|
|
11997
11579
|
get tag() {
|
|
11998
|
-
return `${
|
|
11580
|
+
return `${TAG31}:${this.id}`;
|
|
11999
11581
|
}
|
|
12000
11582
|
get isIdle() {
|
|
12001
11583
|
return this.state === "idle";
|
|
@@ -12057,12 +11639,12 @@ class Worker {
|
|
|
12057
11639
|
}
|
|
12058
11640
|
if (!crossedTurnCap(before, after, cap))
|
|
12059
11641
|
return;
|
|
12060
|
-
|
|
11642
|
+
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.`);
|
|
12061
11643
|
try {
|
|
12062
11644
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
12063
11645
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), { commentType: "blocker", agentSessionId: this.sessionId ?? undefined });
|
|
12064
11646
|
} catch (err) {
|
|
12065
|
-
|
|
11647
|
+
log34.warn(TAG31, `Could not post the turn-cap notice for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
12066
11648
|
}
|
|
12067
11649
|
}
|
|
12068
11650
|
async run(card, column, labels, subtasks) {
|
|
@@ -12117,7 +11699,7 @@ class Worker {
|
|
|
12117
11699
|
resumeReason: null
|
|
12118
11700
|
});
|
|
12119
11701
|
} catch (err) {
|
|
12120
|
-
|
|
11702
|
+
log34.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
12121
11703
|
}
|
|
12122
11704
|
}
|
|
12123
11705
|
try {
|
|
@@ -12125,20 +11707,20 @@ class Worker {
|
|
|
12125
11707
|
if (!resuming) {
|
|
12126
11708
|
this.branchName = makeBranchName(card.short_id, card.title, this.config.worktree.failedBranchPrefix);
|
|
12127
11709
|
}
|
|
12128
|
-
|
|
11710
|
+
log34.info(this.tag, resuming ? `Resuming #${card.short_id} "${card.title}" with ${this.grantedTurns ?? "the default"} more turns` : `Preparing #${card.short_id} "${card.title}"`);
|
|
12129
11711
|
const attemptCount = await this.stateStore.incrementAttempt(card.id);
|
|
12130
11712
|
const isRework = attemptCount > 1;
|
|
12131
11713
|
const runStarts = this.stateStore.getCardRunStarts(card.id);
|
|
12132
11714
|
const runCap = this.config.budget.maxRunsPerCard;
|
|
12133
11715
|
if (crossedTurnCap(runStarts - 1, runStarts, runCap)) {
|
|
12134
|
-
|
|
11716
|
+
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.`);
|
|
12135
11717
|
try {
|
|
12136
11718
|
await this.client.addComment(card.id, buildRunCapComment(runStarts, runCap), {
|
|
12137
11719
|
commentType: "blocker",
|
|
12138
11720
|
agentSessionId: this.sessionId ?? undefined
|
|
12139
11721
|
});
|
|
12140
11722
|
} catch (err) {
|
|
12141
|
-
|
|
11723
|
+
log34.warn(this.tag, `Could not post the run-backstop notice for ${card.id}: ${err instanceof Error ? err.message : err}`);
|
|
12142
11724
|
}
|
|
12143
11725
|
}
|
|
12144
11726
|
const recordedBranch = recordedBranchForCard(card.description, card.short_id, [
|
|
@@ -12148,10 +11730,10 @@ class Worker {
|
|
|
12148
11730
|
const continuesPushedWork = isRework || recordedBranch !== null;
|
|
12149
11731
|
if (!resuming && recordedBranch) {
|
|
12150
11732
|
if (recordedBranch !== this.branchName) {
|
|
12151
|
-
|
|
11733
|
+
log34.info(this.tag, `Card records pushed work on ${recordedBranch} — continuing that branch instead of ${this.branchName}`);
|
|
12152
11734
|
this.branchName = recordedBranch;
|
|
12153
11735
|
} else if (!isRework) {
|
|
12154
|
-
|
|
11736
|
+
log34.info(this.tag, `Card records pushed work on ${this.branchName} — continuing that branch instead of rebuilding from ${this.config.worktree.baseBranch}`);
|
|
12155
11737
|
}
|
|
12156
11738
|
}
|
|
12157
11739
|
this.startHeartbeat();
|
|
@@ -12193,14 +11775,14 @@ class Worker {
|
|
|
12193
11775
|
awaitingDecisionUntil: null
|
|
12194
11776
|
});
|
|
12195
11777
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
12196
|
-
info: (message) =>
|
|
12197
|
-
warn: (message) =>
|
|
11778
|
+
info: (message) => log34.info(this.tag, message),
|
|
11779
|
+
warn: (message) => log34.warn(this.tag, message)
|
|
12198
11780
|
});
|
|
12199
11781
|
session = started.session;
|
|
12200
11782
|
} catch (err) {
|
|
12201
11783
|
if (isSessionConflict(err)) {
|
|
12202
11784
|
this.sessionConflict = true;
|
|
12203
|
-
|
|
11785
|
+
log34.info(this.tag, `Skipping #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12204
11786
|
await this.stateStore.decrementAttempt(card.id);
|
|
12205
11787
|
return;
|
|
12206
11788
|
}
|
|
@@ -12208,7 +11790,7 @@ class Worker {
|
|
|
12208
11790
|
}
|
|
12209
11791
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
12210
11792
|
if (!sid) {
|
|
12211
|
-
|
|
11793
|
+
log34.warn(TAG31, "startAgentSession returned no session id");
|
|
12212
11794
|
}
|
|
12213
11795
|
this.sessionId = sid;
|
|
12214
11796
|
}
|
|
@@ -12226,7 +11808,7 @@ class Worker {
|
|
|
12226
11808
|
if (!resuming) {
|
|
12227
11809
|
const moved = await moveCardAndAddLabel(this.client, card, IN_PROGRESS_COLUMN, "agent");
|
|
12228
11810
|
if (!moved) {
|
|
12229
|
-
|
|
11811
|
+
log34.warn(this.tag, `Card #${card.short_id} was NOT moved to "In Progress" — check API logs`);
|
|
12230
11812
|
}
|
|
12231
11813
|
}
|
|
12232
11814
|
if (this.aborted)
|
|
@@ -12253,10 +11835,10 @@ class Worker {
|
|
|
12253
11835
|
}).trim();
|
|
12254
11836
|
const target = resolveContinuationTarget(this.branchName, continueRequested, this.config.worktree.failedBranchPrefix, this.config.worktree.approvedBranchPrefix, (ref) => fetchExistingBranch(repoRoot, ref));
|
|
12255
11837
|
if (target.branchName !== this.branchName) {
|
|
12256
|
-
|
|
11838
|
+
log34.info(this.tag, `Branch ${this.branchName} is gone from origin but its approved rename ${target.branchName} exists — continuing that branch`);
|
|
12257
11839
|
this.branchName = target.branchName;
|
|
12258
11840
|
} else if (target.reason === "exists_on_origin") {
|
|
12259
|
-
|
|
11841
|
+
log34.info(this.tag, `Branch ${this.branchName} exists on origin with no card record — continuing it rather than resetting it`);
|
|
12260
11842
|
}
|
|
12261
11843
|
this.worktreePath = createWorktree(this.config.worktree.basePath, this.config.worktree.baseBranch, this.branchName, {
|
|
12262
11844
|
continueExisting: target.continueExisting,
|
|
@@ -12290,7 +11872,7 @@ class Worker {
|
|
|
12290
11872
|
if (this.aborted)
|
|
12291
11873
|
return;
|
|
12292
11874
|
if (parked) {
|
|
12293
|
-
|
|
11875
|
+
log34.info(this.tag, `#${card.short_id} parked for plan approval — ending run`);
|
|
12294
11876
|
return;
|
|
12295
11877
|
}
|
|
12296
11878
|
}
|
|
@@ -12311,7 +11893,7 @@ class Worker {
|
|
|
12311
11893
|
progressPercent: 10
|
|
12312
11894
|
});
|
|
12313
11895
|
this.timeoutTimer = setTimeout(() => {
|
|
12314
|
-
|
|
11896
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
12315
11897
|
this.timedOut = true;
|
|
12316
11898
|
this.cancel("timeout");
|
|
12317
11899
|
}, this.config.maxTimeout);
|
|
@@ -12338,7 +11920,7 @@ class Worker {
|
|
|
12338
11920
|
}
|
|
12339
11921
|
this.state = "verifying";
|
|
12340
11922
|
await this.recordPhase("verifying");
|
|
12341
|
-
|
|
11923
|
+
log34.info(this.tag, `Claude finished for #${card.short_id}, running verification & completion`);
|
|
12342
11924
|
await this.client.updateAgentProgress(card.id, {
|
|
12343
11925
|
agentIdentifier: this.sessionIdentifier,
|
|
12344
11926
|
agentName: AGENT_NAME,
|
|
@@ -12354,7 +11936,7 @@ class Worker {
|
|
|
12354
11936
|
const onBeforeWorktreeCleanup = stageRun ? async (worktreePath) => {
|
|
12355
11937
|
stageGateEvaluation = await this.collectStageGateEvidence(card, stageRun.stage, worktreePath, subtasks);
|
|
12356
11938
|
} : undefined;
|
|
12357
|
-
const completed = await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.sessionIdentifier, this.identity.agentId, this.lastSessionStats, this.
|
|
11939
|
+
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);
|
|
12358
11940
|
if (completed === "park") {
|
|
12359
11941
|
await this.parkForDecision(card, "max_turns");
|
|
12360
11942
|
return;
|
|
@@ -12373,7 +11955,7 @@ class Worker {
|
|
|
12373
11955
|
break;
|
|
12374
11956
|
case "reclaim_refused":
|
|
12375
11957
|
this.held = true;
|
|
12376
|
-
|
|
11958
|
+
log34.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
12377
11959
|
break;
|
|
12378
11960
|
case "advanced":
|
|
12379
11961
|
case "completed_terminal":
|
|
@@ -12411,7 +11993,7 @@ class Worker {
|
|
|
12411
11993
|
return;
|
|
12412
11994
|
}
|
|
12413
11995
|
this.state = "error";
|
|
12414
|
-
|
|
11996
|
+
log34.error(this.tag, `Error on #${card.short_id}: ${msg}`);
|
|
12415
11997
|
const apiError = errClass.kind !== null;
|
|
12416
11998
|
const baseError = err instanceof WorktreeBaseError;
|
|
12417
11999
|
const noBudgetBurn = apiError || baseError;
|
|
@@ -12429,7 +12011,7 @@ class Worker {
|
|
|
12429
12011
|
try {
|
|
12430
12012
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12431
12013
|
} catch {
|
|
12432
|
-
|
|
12014
|
+
log34.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12433
12015
|
}
|
|
12434
12016
|
this.worktreePath = null;
|
|
12435
12017
|
}
|
|
@@ -12454,7 +12036,7 @@ class Worker {
|
|
|
12454
12036
|
}
|
|
12455
12037
|
});
|
|
12456
12038
|
} catch (tErr) {
|
|
12457
|
-
|
|
12039
|
+
log34.error(this.tag, `error transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12458
12040
|
}
|
|
12459
12041
|
if (this.runId) {
|
|
12460
12042
|
try {
|
|
@@ -12490,7 +12072,7 @@ class Worker {
|
|
|
12490
12072
|
try {
|
|
12491
12073
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12492
12074
|
} catch {
|
|
12493
|
-
|
|
12075
|
+
log34.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12494
12076
|
}
|
|
12495
12077
|
this.worktreePath = null;
|
|
12496
12078
|
}
|
|
@@ -12513,7 +12095,7 @@ class Worker {
|
|
|
12513
12095
|
}
|
|
12514
12096
|
});
|
|
12515
12097
|
} catch (tErr) {
|
|
12516
|
-
|
|
12098
|
+
log34.error(this.tag, `timeout transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12517
12099
|
}
|
|
12518
12100
|
try {
|
|
12519
12101
|
await this.stateStore.endRun(this.runId, "failed", {
|
|
@@ -12527,15 +12109,15 @@ class Worker {
|
|
|
12527
12109
|
try {
|
|
12528
12110
|
await this.client.updateCard(card.id, { assignedAgentId: null });
|
|
12529
12111
|
} catch (err) {
|
|
12530
|
-
|
|
12112
|
+
log34.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
|
|
12531
12113
|
}
|
|
12532
12114
|
try {
|
|
12533
12115
|
await runTransition(this.client, card, { removeLabels: ["agent"] });
|
|
12534
12116
|
} catch (tErr) {
|
|
12535
|
-
|
|
12117
|
+
log34.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12536
12118
|
}
|
|
12537
12119
|
} else {
|
|
12538
|
-
|
|
12120
|
+
log34.info(this.tag, `cancel arrived after completion on #${card.short_id} — keeping assignment so review picks it up (#585)`);
|
|
12539
12121
|
}
|
|
12540
12122
|
try {
|
|
12541
12123
|
await this.stateStore.endRun(this.runId, "paused", {
|
|
@@ -12599,28 +12181,28 @@ class Worker {
|
|
|
12599
12181
|
};
|
|
12600
12182
|
const { pick, reason } = selectAutoPlaybook(subject, playbooks ?? []);
|
|
12601
12183
|
if (!pick) {
|
|
12602
|
-
|
|
12184
|
+
log34.info(this.tag, `No playbook auto-bound to #${card.short_id}: ${reason}`);
|
|
12603
12185
|
return card;
|
|
12604
12186
|
}
|
|
12605
12187
|
const undeclared = undeclaredGateMetricsIn(pick, declaredMetricSet(this.config));
|
|
12606
12188
|
if (undeclared.length > 0) {
|
|
12607
|
-
|
|
12189
|
+
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.");
|
|
12608
12190
|
return card;
|
|
12609
12191
|
}
|
|
12610
12192
|
const applyResult = await this.client.request("POST", `/cards/${card.id}/apply-playbook`, {
|
|
12611
12193
|
playbookId: pick.id
|
|
12612
12194
|
});
|
|
12613
|
-
|
|
12195
|
+
log34.info(this.tag, `Auto-bound #${card.short_id} to playbook "${pick.name}": ${reason}`);
|
|
12614
12196
|
try {
|
|
12615
12197
|
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.`);
|
|
12616
12198
|
} catch (commentErr) {
|
|
12617
|
-
|
|
12199
|
+
log34.warn(this.tag, `Auto-bind comment failed for #${card.short_id}: ${commentErr instanceof Error ? commentErr.message : String(commentErr)}`);
|
|
12618
12200
|
}
|
|
12619
12201
|
try {
|
|
12620
12202
|
const { card: fresh } = await this.client.getCard(card.id);
|
|
12621
12203
|
return fresh;
|
|
12622
12204
|
} catch (fetchErr) {
|
|
12623
|
-
|
|
12205
|
+
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)}`);
|
|
12624
12206
|
return {
|
|
12625
12207
|
...card,
|
|
12626
12208
|
playbook_id: applyResult.card.playbook_id,
|
|
@@ -12629,7 +12211,7 @@ class Worker {
|
|
|
12629
12211
|
};
|
|
12630
12212
|
}
|
|
12631
12213
|
} catch (err) {
|
|
12632
|
-
|
|
12214
|
+
log34.warn(this.tag, `Auto-bind playbook check failed for #${card.short_id}, continuing unbound: ${err instanceof Error ? err.message : String(err)}`);
|
|
12633
12215
|
return card;
|
|
12634
12216
|
}
|
|
12635
12217
|
}
|
|
@@ -12741,7 +12323,7 @@ class Worker {
|
|
|
12741
12323
|
});
|
|
12742
12324
|
} catch (err) {
|
|
12743
12325
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12744
|
-
|
|
12326
|
+
log34.warn(this.tag, `fan-out tick failed on #${card.short_id}: ${detail}`);
|
|
12745
12327
|
await this.holdStageCard(card, `Fan-out stage "${ctx.stage.name}" could not run: ${detail}`);
|
|
12746
12328
|
return;
|
|
12747
12329
|
}
|
|
@@ -12750,7 +12332,7 @@ class Worker {
|
|
|
12750
12332
|
case "waiting": {
|
|
12751
12333
|
const total = outcome.kind === "dispatched" ? outcome.total : outcome.total;
|
|
12752
12334
|
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.`;
|
|
12753
|
-
|
|
12335
|
+
log34.info(this.tag, `#${card.short_id} ${note}`);
|
|
12754
12336
|
await this.client.updateAgentProgress(card.id, {
|
|
12755
12337
|
agentIdentifier: "claude-code-stage",
|
|
12756
12338
|
agentName: "Harmony Agent",
|
|
@@ -12778,7 +12360,7 @@ class Worker {
|
|
|
12778
12360
|
if (advance.kind === "advanced" || advance.kind === "completed_terminal") {
|
|
12779
12361
|
this.held = false;
|
|
12780
12362
|
}
|
|
12781
|
-
|
|
12363
|
+
log34.info(this.tag, `#${card.short_id} ${summary} → ${advance.kind}`);
|
|
12782
12364
|
return;
|
|
12783
12365
|
}
|
|
12784
12366
|
case "halted": {
|
|
@@ -12798,7 +12380,7 @@ class Worker {
|
|
|
12798
12380
|
}
|
|
12799
12381
|
}
|
|
12800
12382
|
async holdStageCard(card, reason, wait = false) {
|
|
12801
|
-
|
|
12383
|
+
log34.info(this.tag, `Holding #${card.short_id}: ${reason}`);
|
|
12802
12384
|
await this.stateStore.decrementAttempt(card.id);
|
|
12803
12385
|
try {
|
|
12804
12386
|
await this.client.addComment(card.id, reason, {
|
|
@@ -12817,7 +12399,7 @@ class Worker {
|
|
|
12817
12399
|
}
|
|
12818
12400
|
});
|
|
12819
12401
|
} catch (tErr) {
|
|
12820
|
-
|
|
12402
|
+
log34.warn(this.tag, `hold transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12821
12403
|
}
|
|
12822
12404
|
if (this.runId) {
|
|
12823
12405
|
try {
|
|
@@ -12835,7 +12417,7 @@ class Worker {
|
|
|
12835
12417
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
12836
12418
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12837
12419
|
const until = computeDecisionDeadline(waitHours);
|
|
12838
|
-
|
|
12420
|
+
log34.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
12839
12421
|
try {
|
|
12840
12422
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
12841
12423
|
holderMessage,
|
|
@@ -12847,7 +12429,7 @@ class Worker {
|
|
|
12847
12429
|
agentSessionId: this.sessionId ?? undefined
|
|
12848
12430
|
});
|
|
12849
12431
|
} catch (commentErr) {
|
|
12850
|
-
|
|
12432
|
+
log34.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
12851
12433
|
}
|
|
12852
12434
|
if (this.runId) {
|
|
12853
12435
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -12858,7 +12440,7 @@ class Worker {
|
|
|
12858
12440
|
awaitingDecisionUntil: until
|
|
12859
12441
|
});
|
|
12860
12442
|
} catch (storeErr) {
|
|
12861
|
-
|
|
12443
|
+
log34.error(this.tag, `#${card.short_id} could not be re-parked after a resume conflict — the daemon has no hold on it: ${storeErr}`);
|
|
12862
12444
|
}
|
|
12863
12445
|
}
|
|
12864
12446
|
}
|
|
@@ -12870,7 +12452,7 @@ class Worker {
|
|
|
12870
12452
|
this.progressTracker = null;
|
|
12871
12453
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12872
12454
|
const until = computeDecisionDeadline(waitHours);
|
|
12873
|
-
|
|
12455
|
+
log34.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
12874
12456
|
const measured = this.worktreePath && this.runExpectsCommits ? measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha) : "unknown";
|
|
12875
12457
|
const hasCommits = measured === "unknown" ? undefined : measured === "committed";
|
|
12876
12458
|
if (hasCommits === false && !this.noCommitNoticeSettled) {
|
|
@@ -12900,7 +12482,7 @@ class Worker {
|
|
|
12900
12482
|
});
|
|
12901
12483
|
commentId = res?.comment?.id ?? null;
|
|
12902
12484
|
} catch (err) {
|
|
12903
|
-
|
|
12485
|
+
log34.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
12904
12486
|
}
|
|
12905
12487
|
const cardCostCents = this.stateStore.getCardCostCents(card.id) + Math.max(0, this.runCostCents - this.chargedCents);
|
|
12906
12488
|
const readout = [
|
|
@@ -12921,7 +12503,7 @@ class Worker {
|
|
|
12921
12503
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
12922
12504
|
});
|
|
12923
12505
|
} catch (err) {
|
|
12924
|
-
|
|
12506
|
+
log34.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
12925
12507
|
}
|
|
12926
12508
|
if (this.runId) {
|
|
12927
12509
|
try {
|
|
@@ -12932,7 +12514,7 @@ class Worker {
|
|
|
12932
12514
|
...this.runLedger()
|
|
12933
12515
|
});
|
|
12934
12516
|
} catch (err) {
|
|
12935
|
-
|
|
12517
|
+
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}`);
|
|
12936
12518
|
}
|
|
12937
12519
|
}
|
|
12938
12520
|
}
|
|
@@ -12963,11 +12545,11 @@ class Worker {
|
|
|
12963
12545
|
});
|
|
12964
12546
|
const gate = normalizeGateSpec(ctx.stage.gate);
|
|
12965
12547
|
const metricsPath = gate?.kind === "custom" ? writeMetricsFile(this.config.playbooks.metrics ?? {}) : null;
|
|
12966
|
-
|
|
12548
|
+
log34.info(this.tag, `Running stage "${ctx.stage.name}" (role ${ctx.role}) for #${card.short_id} under the harness motor`);
|
|
12967
12549
|
const motorAbort = new AbortController;
|
|
12968
12550
|
this.motorAbort = motorAbort;
|
|
12969
12551
|
this.timeoutTimer = setTimeout(() => {
|
|
12970
|
-
|
|
12552
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms) during the motor stage run, cancelling`);
|
|
12971
12553
|
this.timedOut = true;
|
|
12972
12554
|
this.cancel("timeout");
|
|
12973
12555
|
}, this.config.maxTimeout);
|
|
@@ -12978,12 +12560,12 @@ class Worker {
|
|
|
12978
12560
|
let motorRunSettled = false;
|
|
12979
12561
|
const onMotorLine = (line) => {
|
|
12980
12562
|
if (line.type !== "agent_event") {
|
|
12981
|
-
|
|
12563
|
+
log34.info(this.tag, `motor: ${line.type}`);
|
|
12982
12564
|
return;
|
|
12983
12565
|
}
|
|
12984
12566
|
if (motorRunSettled)
|
|
12985
12567
|
return;
|
|
12986
|
-
|
|
12568
|
+
log34.debug(this.tag, `motor: agent_event ${line.event.kind}`);
|
|
12987
12569
|
if (line.event.kind === "tool_started" && STAGE_DAEMON_OWNED_TOOLS.includes(line.event.payload.toolName)) {
|
|
12988
12570
|
return;
|
|
12989
12571
|
}
|
|
@@ -13003,7 +12585,7 @@ class Worker {
|
|
|
13003
12585
|
currentTask: motorTask,
|
|
13004
12586
|
progressPercent: MOTOR_RUN_PROGRESS_PERCENT
|
|
13005
12587
|
}).catch((err) => {
|
|
13006
|
-
|
|
12588
|
+
log34.warn(this.tag, `motor session heartbeat failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13007
12589
|
});
|
|
13008
12590
|
}, MOTOR_SESSION_HEARTBEAT_MS);
|
|
13009
12591
|
heartbeat.unref?.();
|
|
@@ -13077,7 +12659,7 @@ class Worker {
|
|
|
13077
12659
|
break;
|
|
13078
12660
|
case "reclaim_refused":
|
|
13079
12661
|
this.held = true;
|
|
13080
|
-
|
|
12662
|
+
log34.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
13081
12663
|
break;
|
|
13082
12664
|
case "advanced":
|
|
13083
12665
|
case "completed_terminal":
|
|
@@ -13099,7 +12681,7 @@ class Worker {
|
|
|
13099
12681
|
try {
|
|
13100
12682
|
pushBranch3(this.branchName, worktreePath);
|
|
13101
12683
|
} catch (err) {
|
|
13102
|
-
|
|
12684
|
+
log34.error(this.tag, `push after the motor stage "${ctx.stage.name}" failed for ${this.branchName}: ${err instanceof Error ? err.message : err}`);
|
|
13103
12685
|
}
|
|
13104
12686
|
}
|
|
13105
12687
|
}
|
|
@@ -13107,7 +12689,7 @@ class Worker {
|
|
|
13107
12689
|
if (completionColumn) {
|
|
13108
12690
|
await transferCardToCompletion({ client: this.client, tag: this.tag }, card, completionColumn, this.onCardCompleted);
|
|
13109
12691
|
} else {
|
|
13110
|
-
|
|
12692
|
+
log34.warn(this.tag, `completion.moveToColumn is empty — #${card.short_id} stays in its current column after the motor stage run`);
|
|
13111
12693
|
}
|
|
13112
12694
|
await this.closeoutMotorWorktree(card);
|
|
13113
12695
|
}
|
|
@@ -13118,7 +12700,7 @@ class Worker {
|
|
|
13118
12700
|
try {
|
|
13119
12701
|
await teardownWorktree2(this.client, card.id, worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
13120
12702
|
} catch {
|
|
13121
|
-
|
|
12703
|
+
log34.warn(this.tag, "Failed to cleanup worktree after the motor stage run");
|
|
13122
12704
|
}
|
|
13123
12705
|
this.worktreePath = null;
|
|
13124
12706
|
}
|
|
@@ -13175,7 +12757,7 @@ ${prompt}`;
|
|
|
13175
12757
|
});
|
|
13176
12758
|
return handoff ? renderInheritedHandoffSection(handoff) : "";
|
|
13177
12759
|
} catch (err) {
|
|
13178
|
-
|
|
12760
|
+
log34.warn(this.tag, `inherited-handoff fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13179
12761
|
return "";
|
|
13180
12762
|
}
|
|
13181
12763
|
}
|
|
@@ -13195,9 +12777,9 @@ ${prompt}`;
|
|
|
13195
12777
|
commentType: "decision",
|
|
13196
12778
|
agentSessionId: this.sessionId ?? undefined
|
|
13197
12779
|
});
|
|
13198
|
-
|
|
12780
|
+
log34.info(this.tag, `Wrote stage handoff for #${card.short_id} (stage "${stage.name}")`);
|
|
13199
12781
|
} catch (err) {
|
|
13200
|
-
|
|
12782
|
+
log34.warn(this.tag, `stage-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13201
12783
|
}
|
|
13202
12784
|
}
|
|
13203
12785
|
async collectStageGateEvidence(card, stage, worktreePath, subtasks) {
|
|
@@ -13209,12 +12791,12 @@ ${prompt}`;
|
|
|
13209
12791
|
return null;
|
|
13210
12792
|
}
|
|
13211
12793
|
if (gate.pendingEngine === true) {
|
|
13212
|
-
|
|
12794
|
+
log34.info(this.tag, `Stage "${stage.name}" gate "${gate.kind}" is advisory — skipping enforcement`);
|
|
13213
12795
|
return null;
|
|
13214
12796
|
}
|
|
13215
12797
|
const review = gate.kind === "review_passed" ? parseReviewOutput(this.lastRunText) : undefined;
|
|
13216
12798
|
if (review) {
|
|
13217
|
-
|
|
12799
|
+
log34.info(this.tag, `Review-gated stage "${stage.name}" verdict: ${review.verdict} (${review.findings.length} finding(s))`);
|
|
13218
12800
|
}
|
|
13219
12801
|
const registry = buildGateCollectorRegistry2({
|
|
13220
12802
|
build: {
|
|
@@ -13231,7 +12813,7 @@ ${prompt}`;
|
|
|
13231
12813
|
return [];
|
|
13232
12814
|
const criteria = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
13233
12815
|
if (criteria.length > 0) {
|
|
13234
|
-
|
|
12816
|
+
log34.info(this.tag, `Grading the artifact gate for #${card.short_id} against ${criteria.length} plan criteria instead of the stage's own rubric`);
|
|
13235
12817
|
}
|
|
13236
12818
|
return criteria;
|
|
13237
12819
|
})()
|
|
@@ -13253,10 +12835,10 @@ ${prompt}`;
|
|
|
13253
12835
|
const evaluation = gateEvaluate(gate, evidence);
|
|
13254
12836
|
const insert = toStageGateEvidenceInsert(context, evidence);
|
|
13255
12837
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, insert);
|
|
13256
|
-
|
|
12838
|
+
log34.info(this.tag, `Recorded ${gate.kind} gate evidence for #${card.short_id} stage "${stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
13257
12839
|
return evaluation;
|
|
13258
12840
|
} catch (err) {
|
|
13259
|
-
|
|
12841
|
+
log34.warn(this.tag, `stage-gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13260
12842
|
return null;
|
|
13261
12843
|
}
|
|
13262
12844
|
}
|
|
@@ -13274,7 +12856,7 @@ ${prompt}`;
|
|
|
13274
12856
|
agentSessionId: this.sessionId
|
|
13275
12857
|
});
|
|
13276
12858
|
} catch (err) {
|
|
13277
|
-
|
|
12859
|
+
log34.warn(this.tag, `stage advancement failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13278
12860
|
return { kind: "no_advance" };
|
|
13279
12861
|
}
|
|
13280
12862
|
}
|
|
@@ -13300,7 +12882,7 @@ ${prompt}`;
|
|
|
13300
12882
|
this.modelChoice = choice;
|
|
13301
12883
|
const { model, escalated, source } = choice;
|
|
13302
12884
|
if (source !== "policy" || escalated) {
|
|
13303
|
-
|
|
12885
|
+
log34.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${this.sizing?.tier ?? "none"})`);
|
|
13304
12886
|
}
|
|
13305
12887
|
return model;
|
|
13306
12888
|
}
|
|
@@ -13315,7 +12897,7 @@ ${prompt}`;
|
|
|
13315
12897
|
encoding: "utf-8"
|
|
13316
12898
|
}).trim();
|
|
13317
12899
|
} catch (err) {
|
|
13318
|
-
|
|
12900
|
+
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`);
|
|
13319
12901
|
return { status: "failed", reason: "spawn" };
|
|
13320
12902
|
}
|
|
13321
12903
|
const outcome = await sizeRun({
|
|
@@ -13329,21 +12911,22 @@ ${prompt}`;
|
|
|
13329
12911
|
scoreBands: claude.scoreBands
|
|
13330
12912
|
});
|
|
13331
12913
|
if (outcome.status === "sized") {
|
|
13332
|
-
|
|
12914
|
+
log34.info(this.tag, `Sized #${card.short_id}: complexity ${outcome.sizing.complexity}/10 -> ${outcome.sizing.tier}`);
|
|
13333
12915
|
} else if (outcome.status === "failed") {
|
|
13334
|
-
|
|
12916
|
+
log34.warn(this.tag, `Sizing #${card.short_id} failed (${outcome.reason}) — using the policy fallback`);
|
|
13335
12917
|
}
|
|
13336
12918
|
return outcome;
|
|
13337
12919
|
}
|
|
13338
12920
|
recordPromptAssembled(outcomes) {
|
|
13339
|
-
const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count
|
|
12921
|
+
const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count;
|
|
13340
12922
|
const failures = outcomes.flatMap((o) => o.failure ? [{ flavour: o.flavour, message: o.failure.slice(0, 300) }] : []);
|
|
12923
|
+
const episodeCount = countOf("past_episodes");
|
|
13341
12924
|
this.cliRunner?.record({
|
|
13342
12925
|
kind: "prompt_assembled",
|
|
13343
12926
|
source: "system",
|
|
13344
12927
|
payload: {
|
|
13345
|
-
episodeCount:
|
|
13346
|
-
referenceCount: countOf("reference"),
|
|
12928
|
+
...episodeCount === undefined ? {} : { episodeCount },
|
|
12929
|
+
referenceCount: countOf("reference") ?? 0,
|
|
13347
12930
|
...failures.length > 0 ? { recallFailures: failures } : {}
|
|
13348
12931
|
}
|
|
13349
12932
|
});
|
|
@@ -13364,7 +12947,7 @@ ${prompt}`;
|
|
|
13364
12947
|
const measured = measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha);
|
|
13365
12948
|
if (measured !== "none") {
|
|
13366
12949
|
if (measured === "unknown") {
|
|
13367
|
-
|
|
12950
|
+
log34.debug(this.tag, `no-commit check could not attribute this run's commits — staying quiet on ${this.branchName ?? "this branch"}`);
|
|
13368
12951
|
}
|
|
13369
12952
|
return;
|
|
13370
12953
|
}
|
|
@@ -13373,7 +12956,7 @@ ${prompt}`;
|
|
|
13373
12956
|
recordNoCommitsYet(trigger, numTurns) {
|
|
13374
12957
|
const turnBudget = this.effectiveMaxTurns;
|
|
13375
12958
|
const threshold = noCommitNoticeThreshold(turnBudget);
|
|
13376
|
-
|
|
12959
|
+
log34.warn(this.tag, `No commits yet on ${this.branchName ?? "this run's branch"} — ${this.runToolCalls} tool calls into a ${turnBudget}-turn budget (${trigger})`);
|
|
13377
12960
|
this.cliRunner?.record({
|
|
13378
12961
|
kind: "no_commits_yet",
|
|
13379
12962
|
source: "system",
|
|
@@ -13425,9 +13008,9 @@ ${prompt}`;
|
|
|
13425
13008
|
agentSessionId: this.sessionId ?? undefined
|
|
13426
13009
|
});
|
|
13427
13010
|
giveUpCommentId = res?.comment?.id ?? null;
|
|
13428
|
-
|
|
13011
|
+
log34.warn(this.tag, `gave up on ${cardId} after ${attempts} attempts`);
|
|
13429
13012
|
} catch (err) {
|
|
13430
|
-
|
|
13013
|
+
log34.warn(this.tag, `failed to post give-up comment for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13431
13014
|
}
|
|
13432
13015
|
if (this.config.budget.pause.enabled) {
|
|
13433
13016
|
const waitHours = this.config.budget.pause.waitHours;
|
|
@@ -13441,7 +13024,7 @@ ${prompt}`;
|
|
|
13441
13024
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
13442
13025
|
});
|
|
13443
13026
|
} catch (err) {
|
|
13444
|
-
|
|
13027
|
+
log34.warn(this.tag, `Failed to mark the attempt cap pending for ${cardId}: ${err}`);
|
|
13445
13028
|
}
|
|
13446
13029
|
try {
|
|
13447
13030
|
await this.stateStore.markAwaitingDecision(cardId, {
|
|
@@ -13450,19 +13033,19 @@ ${prompt}`;
|
|
|
13450
13033
|
agentIdentifier: this.sessionIdentifier
|
|
13451
13034
|
});
|
|
13452
13035
|
} catch (err) {
|
|
13453
|
-
|
|
13036
|
+
log34.warn(this.tag, `Failed to record the attempt-cap decision locally for ${cardId}: ${err}`);
|
|
13454
13037
|
}
|
|
13455
13038
|
}
|
|
13456
13039
|
}
|
|
13457
13040
|
}
|
|
13458
13041
|
} catch (err) {
|
|
13459
|
-
|
|
13042
|
+
log34.warn(this.tag, `recordOutcome(${outcome}) failed: ${err instanceof Error ? err.message : err}`);
|
|
13460
13043
|
}
|
|
13461
13044
|
}
|
|
13462
13045
|
async pause() {
|
|
13463
13046
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13464
13047
|
return;
|
|
13465
|
-
|
|
13048
|
+
log34.info(this.tag, `Pausing work on ${this.cardId}`);
|
|
13466
13049
|
signalGroup2(this.process, "SIGSTOP");
|
|
13467
13050
|
if (this.timeoutTimer) {
|
|
13468
13051
|
clearTimeout(this.timeoutTimer);
|
|
@@ -13476,17 +13059,17 @@ ${prompt}`;
|
|
|
13476
13059
|
status: "paused"
|
|
13477
13060
|
});
|
|
13478
13061
|
} catch {
|
|
13479
|
-
|
|
13062
|
+
log34.warn(this.tag, "Failed to update agent session to paused");
|
|
13480
13063
|
}
|
|
13481
13064
|
}
|
|
13482
13065
|
}
|
|
13483
13066
|
async resume() {
|
|
13484
13067
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13485
13068
|
return;
|
|
13486
|
-
|
|
13069
|
+
log34.info(this.tag, `Resuming work on ${this.cardId}`);
|
|
13487
13070
|
signalGroup2(this.process, "SIGCONT");
|
|
13488
13071
|
this.timeoutTimer = setTimeout(() => {
|
|
13489
|
-
|
|
13072
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
13490
13073
|
this.timedOut = true;
|
|
13491
13074
|
this.cancel("timeout");
|
|
13492
13075
|
}, this.config.maxTimeout);
|
|
@@ -13498,7 +13081,7 @@ ${prompt}`;
|
|
|
13498
13081
|
status: "working"
|
|
13499
13082
|
});
|
|
13500
13083
|
} catch {
|
|
13501
|
-
|
|
13084
|
+
log34.warn(this.tag, "Failed to update agent session to working");
|
|
13502
13085
|
}
|
|
13503
13086
|
}
|
|
13504
13087
|
}
|
|
@@ -13507,7 +13090,7 @@ ${prompt}`;
|
|
|
13507
13090
|
return;
|
|
13508
13091
|
this.aborted = true;
|
|
13509
13092
|
this.state = "cancelling";
|
|
13510
|
-
|
|
13093
|
+
log34.info(this.tag, `Cancelling work on ${this.cardId}`);
|
|
13511
13094
|
this.motorAbort?.abort();
|
|
13512
13095
|
if (this.sdkRunner) {
|
|
13513
13096
|
await this.sdkRunner.stop(this.timedOut ? "timeout" : "user_requested");
|
|
@@ -13525,14 +13108,14 @@ ${prompt}`;
|
|
|
13525
13108
|
...buildTokenPayload(stats)
|
|
13526
13109
|
});
|
|
13527
13110
|
} catch (err) {
|
|
13528
|
-
|
|
13111
|
+
log34.warn(this.tag, `endAgentSession after cancel failed: ${err instanceof Error ? err.message : err}`);
|
|
13529
13112
|
}
|
|
13530
13113
|
}
|
|
13531
13114
|
}
|
|
13532
13115
|
async runPlanningPhase(enriched) {
|
|
13533
13116
|
const planning = this.config.planning;
|
|
13534
13117
|
const { card } = enriched;
|
|
13535
|
-
|
|
13118
|
+
log34.info(this.tag, `Planning pass for #${card.short_id} (mode=${planning.mode}, model=${planning.model})`);
|
|
13536
13119
|
await this.client.updateAgentProgress(card.id, {
|
|
13537
13120
|
agentIdentifier: this.sessionIdentifier,
|
|
13538
13121
|
agentName: AGENT_NAME,
|
|
@@ -13545,7 +13128,7 @@ ${prompt}`;
|
|
|
13545
13128
|
let planTimedOut = false;
|
|
13546
13129
|
const planTimeout = setTimeout(() => {
|
|
13547
13130
|
planTimedOut = true;
|
|
13548
|
-
|
|
13131
|
+
log34.warn(this.tag, "Planning pass exceeded timeout — abandoning, implementing directly");
|
|
13549
13132
|
if (this.sdkRunner) {
|
|
13550
13133
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13551
13134
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13563,7 +13146,7 @@ ${prompt}`;
|
|
|
13563
13146
|
initialPhase: "planning"
|
|
13564
13147
|
});
|
|
13565
13148
|
} catch (err) {
|
|
13566
|
-
|
|
13149
|
+
log34.warn(this.tag, `Planning pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13567
13150
|
return false;
|
|
13568
13151
|
} finally {
|
|
13569
13152
|
clearTimeout(planTimeout);
|
|
@@ -13574,7 +13157,7 @@ ${prompt}`;
|
|
|
13574
13157
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13575
13158
|
const planText = stats?.lastAssistantText ?? "";
|
|
13576
13159
|
if (!planText.trim()) {
|
|
13577
|
-
|
|
13160
|
+
log34.warn(this.tag, `Planning pass for #${card.short_id} produced no text — implementing directly`);
|
|
13578
13161
|
return false;
|
|
13579
13162
|
}
|
|
13580
13163
|
const artifact = extractPlanArtifact(planText, card.title);
|
|
@@ -13595,9 +13178,9 @@ ${prompt}`;
|
|
|
13595
13178
|
});
|
|
13596
13179
|
planId = createdId;
|
|
13597
13180
|
}
|
|
13598
|
-
|
|
13181
|
+
log34.info(this.tag, `Stored plan ${planId ?? "(unlinked)"} for #${card.short_id} (${artifact.tasks.length} tasks)`);
|
|
13599
13182
|
} catch (err) {
|
|
13600
|
-
|
|
13183
|
+
log34.warn(this.tag, `Failed to store/link plan (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13601
13184
|
}
|
|
13602
13185
|
if (planning.mode === "gated" && planId) {
|
|
13603
13186
|
try {
|
|
@@ -13616,11 +13199,11 @@ ${prompt}`;
|
|
|
13616
13199
|
...buildTokenPayload(stats)
|
|
13617
13200
|
}
|
|
13618
13201
|
}, { store: this.stateStore, runId: this.runId ?? undefined });
|
|
13619
|
-
|
|
13202
|
+
log34.info(this.tag, `#${card.short_id} parked in "${planning.awaitingApprovalColumn}" for plan approval`);
|
|
13620
13203
|
this.lastSessionStats = undefined;
|
|
13621
13204
|
return true;
|
|
13622
13205
|
} catch (err) {
|
|
13623
|
-
|
|
13206
|
+
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}`);
|
|
13624
13207
|
}
|
|
13625
13208
|
}
|
|
13626
13209
|
if (planId && planning.postComment) {
|
|
@@ -13630,7 +13213,7 @@ ${prompt}`;
|
|
|
13630
13213
|
agentSessionId: this.sessionId ?? undefined
|
|
13631
13214
|
});
|
|
13632
13215
|
} catch (err) {
|
|
13633
|
-
|
|
13216
|
+
log34.warn(this.tag, `Failed to post advisory plan comment (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13634
13217
|
}
|
|
13635
13218
|
}
|
|
13636
13219
|
return false;
|
|
@@ -13640,10 +13223,10 @@ ${prompt}`;
|
|
|
13640
13223
|
const { card } = enriched;
|
|
13641
13224
|
const existing = await this.loadPinnedContract(card.id);
|
|
13642
13225
|
if (existing) {
|
|
13643
|
-
|
|
13226
|
+
log34.info(this.tag, `Contract already pinned for #${card.short_id} (${existing.assertions.length} assertions) — reusing`);
|
|
13644
13227
|
return;
|
|
13645
13228
|
}
|
|
13646
|
-
|
|
13229
|
+
log34.info(this.tag, `Contract pass for #${card.short_id} (model=${contractCfg.model})`);
|
|
13647
13230
|
await this.client.updateAgentProgress(card.id, {
|
|
13648
13231
|
agentIdentifier: this.sessionIdentifier,
|
|
13649
13232
|
agentName: AGENT_NAME,
|
|
@@ -13656,7 +13239,7 @@ ${prompt}`;
|
|
|
13656
13239
|
let contractTimedOut = false;
|
|
13657
13240
|
const contractTimeout = setTimeout(() => {
|
|
13658
13241
|
contractTimedOut = true;
|
|
13659
|
-
|
|
13242
|
+
log34.warn(this.tag, "Contract pass exceeded timeout — abandoning, implementing directly");
|
|
13660
13243
|
if (this.sdkRunner) {
|
|
13661
13244
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13662
13245
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13674,7 +13257,7 @@ ${prompt}`;
|
|
|
13674
13257
|
initialPhase: "planning"
|
|
13675
13258
|
});
|
|
13676
13259
|
} catch (err) {
|
|
13677
|
-
|
|
13260
|
+
log34.warn(this.tag, `Contract pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13678
13261
|
return;
|
|
13679
13262
|
} finally {
|
|
13680
13263
|
clearTimeout(contractTimeout);
|
|
@@ -13685,12 +13268,12 @@ ${prompt}`;
|
|
|
13685
13268
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13686
13269
|
const contractText = stats?.lastAssistantText ?? "";
|
|
13687
13270
|
if (!contractText.trim()) {
|
|
13688
|
-
|
|
13271
|
+
log34.warn(this.tag, `Contract pass for #${card.short_id} produced no text — implementing directly`);
|
|
13689
13272
|
return;
|
|
13690
13273
|
}
|
|
13691
13274
|
const contract = extractContract(contractText, card);
|
|
13692
13275
|
if (contract.assertions.length < contractCfg.minAssertions) {
|
|
13693
|
-
|
|
13276
|
+
log34.warn(this.tag, `Contract for #${card.short_id} had ${contract.assertions.length} assertion(s) (< ${contractCfg.minAssertions}) — not pinning, implementing directly`);
|
|
13694
13277
|
return;
|
|
13695
13278
|
}
|
|
13696
13279
|
try {
|
|
@@ -13698,9 +13281,9 @@ ${prompt}`;
|
|
|
13698
13281
|
commentType: "decision",
|
|
13699
13282
|
agentSessionId: this.sessionId ?? undefined
|
|
13700
13283
|
});
|
|
13701
|
-
|
|
13284
|
+
log34.info(this.tag, `Pinned acceptance contract for #${card.short_id} (${contract.assertions.length} assertions)`);
|
|
13702
13285
|
} catch (err) {
|
|
13703
|
-
|
|
13286
|
+
log34.warn(this.tag, `Failed to pin contract (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13704
13287
|
}
|
|
13705
13288
|
}
|
|
13706
13289
|
async loadPinnedContract(cardId) {
|
|
@@ -13710,7 +13293,7 @@ ${prompt}`;
|
|
|
13710
13293
|
return null;
|
|
13711
13294
|
return extractPinnedContract(comments, this.identity);
|
|
13712
13295
|
} catch (err) {
|
|
13713
|
-
|
|
13296
|
+
log34.warn(this.tag, `pinned-contract fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13714
13297
|
return null;
|
|
13715
13298
|
}
|
|
13716
13299
|
}
|
|
@@ -13723,13 +13306,13 @@ ${prompt}`;
|
|
|
13723
13306
|
const res = await this.client.getPendingUserMessages(this.cardId, this.sessionId, this.lastDrainedSeq);
|
|
13724
13307
|
messages = res.messages ?? [];
|
|
13725
13308
|
} catch (err) {
|
|
13726
|
-
|
|
13309
|
+
log34.warn(this.tag, `Failed to fetch steering messages (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13727
13310
|
return;
|
|
13728
13311
|
}
|
|
13729
13312
|
if (messages.length === 0)
|
|
13730
13313
|
return;
|
|
13731
13314
|
this.lastDrainedSeq = Math.max(this.lastDrainedSeq, ...messages.map((m) => m.seq));
|
|
13732
|
-
|
|
13315
|
+
log34.info(this.tag, `Steering #${card.short_id}: resuming with ${messages.length} queued message(s)`);
|
|
13733
13316
|
this.state = "running";
|
|
13734
13317
|
await this.recordPhase("running");
|
|
13735
13318
|
try {
|
|
@@ -13741,7 +13324,7 @@ ${prompt}`;
|
|
|
13741
13324
|
...this.activeRunSpawnOpts ?? {}
|
|
13742
13325
|
});
|
|
13743
13326
|
} catch (err) {
|
|
13744
|
-
|
|
13327
|
+
log34.warn(this.tag, `Steering resume failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13745
13328
|
return;
|
|
13746
13329
|
}
|
|
13747
13330
|
}
|
|
@@ -13776,10 +13359,10 @@ ${prompt}`;
|
|
|
13776
13359
|
"--",
|
|
13777
13360
|
prompt
|
|
13778
13361
|
];
|
|
13779
|
-
|
|
13362
|
+
log34.info(this.tag, `Spawning: claude ${args.slice(0, 4).join(" ")} ...`);
|
|
13780
13363
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13781
13364
|
if (runLog) {
|
|
13782
|
-
|
|
13365
|
+
log34.info(this.tag, `Run log: ${runLog.path}`);
|
|
13783
13366
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} started=${new Date().toISOString()}
|
|
13784
13367
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
13785
13368
|
|
|
@@ -13816,7 +13399,7 @@ ${prompt}`;
|
|
|
13816
13399
|
});
|
|
13817
13400
|
}
|
|
13818
13401
|
parser.on("parse_error", (msg) => {
|
|
13819
|
-
|
|
13402
|
+
log34.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
13820
13403
|
runLog?.stream.write(`
|
|
13821
13404
|
[parse_error] ${msg}
|
|
13822
13405
|
`);
|
|
@@ -13889,10 +13472,10 @@ ${prompt}`;
|
|
|
13889
13472
|
const disallowedTools = opts.disallowedTools ? opts.disallowedTools.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
13890
13473
|
const initialPhase = opts.initialPhase ?? "exploring";
|
|
13891
13474
|
const maxBudgetUsd = this.effectiveMaxBudgetUsd;
|
|
13892
|
-
|
|
13475
|
+
log34.info(this.tag, `Spawning Agent SDK runner (model=${model}, maxBudgetUsd=${maxBudgetUsd || "off"}, maxTurns=${maxTurns}${opts.resumeSessionId ? ", resume" : ""})`);
|
|
13893
13476
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13894
13477
|
if (runLog) {
|
|
13895
|
-
|
|
13478
|
+
log34.info(this.tag, `Run log: ${runLog.path}`);
|
|
13896
13479
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} runner=sdk started=${new Date().toISOString()}
|
|
13897
13480
|
` + `# model=${model} maxTurns=${maxTurns} <prompt:${prompt.length} chars>
|
|
13898
13481
|
|
|
@@ -14027,7 +13610,7 @@ ${prompt}`;
|
|
|
14027
13610
|
try {
|
|
14028
13611
|
await teardownWorktree2(this.client, this.cardId, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
14029
13612
|
} catch {
|
|
14030
|
-
|
|
13613
|
+
log34.warn(this.tag, "Failed to cleanup worktree");
|
|
14031
13614
|
}
|
|
14032
13615
|
}
|
|
14033
13616
|
this.process = null;
|
|
@@ -14046,7 +13629,7 @@ ${prompt}`;
|
|
|
14046
13629
|
this.chargedTokens = emptyTokens();
|
|
14047
13630
|
}
|
|
14048
13631
|
}
|
|
14049
|
-
var
|
|
13632
|
+
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;
|
|
14050
13633
|
var init_worker = __esm(() => {
|
|
14051
13634
|
init_dist();
|
|
14052
13635
|
init_board_helpers();
|
|
@@ -14077,7 +13660,7 @@ var init_worker = __esm(() => {
|
|
|
14077
13660
|
});
|
|
14078
13661
|
|
|
14079
13662
|
// src/workspace-model-cache.ts
|
|
14080
|
-
import { log as
|
|
13663
|
+
import { log as log35 } from "@gethmy/harness";
|
|
14081
13664
|
|
|
14082
13665
|
class WorkspaceModelCache {
|
|
14083
13666
|
fetcher;
|
|
@@ -14112,7 +13695,7 @@ class WorkspaceModelCache {
|
|
|
14112
13695
|
this.stale = false;
|
|
14113
13696
|
} catch (err) {
|
|
14114
13697
|
this.stale = true;
|
|
14115
|
-
|
|
13698
|
+
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)}`);
|
|
14116
13699
|
}
|
|
14117
13700
|
return this.value;
|
|
14118
13701
|
}
|
|
@@ -14123,8 +13706,14 @@ var init_workspace_model_cache = () => {};
|
|
|
14123
13706
|
import {
|
|
14124
13707
|
cooldownMsFor,
|
|
14125
13708
|
describeApiError as describeApiError3,
|
|
14126
|
-
log as
|
|
13709
|
+
log as log36
|
|
14127
13710
|
} from "@gethmy/harness";
|
|
13711
|
+
function httpStatusOf(err) {
|
|
13712
|
+
if (typeof err !== "object" || err === null)
|
|
13713
|
+
return;
|
|
13714
|
+
const status = own(err, "status");
|
|
13715
|
+
return typeof status === "number" ? status : undefined;
|
|
13716
|
+
}
|
|
14128
13717
|
async function routeBudgetDecision(d, run, actions, cardId) {
|
|
14129
13718
|
if (!run) {
|
|
14130
13719
|
if (!cardId)
|
|
@@ -14219,11 +13808,11 @@ class Pool {
|
|
|
14219
13808
|
}
|
|
14220
13809
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
14221
13810
|
if (card.done || card.archived_at) {
|
|
14222
|
-
|
|
13811
|
+
log36.debug(TAG32, `#${card.short_id} skipped — ${card.done ? "done" : "archived"} (finished work is never enqueued, #1102)`);
|
|
14223
13812
|
return;
|
|
14224
13813
|
}
|
|
14225
13814
|
if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
|
|
14226
|
-
|
|
13815
|
+
log36.debug(TAG32, `Card ${card.id} already queued, active, or reserved, skipping`);
|
|
14227
13816
|
return;
|
|
14228
13817
|
}
|
|
14229
13818
|
this.reservations.add(card.id);
|
|
@@ -14231,19 +13820,19 @@ class Pool {
|
|
|
14231
13820
|
try {
|
|
14232
13821
|
const account = this.accountGuard?.check();
|
|
14233
13822
|
if (account && !account.running) {
|
|
14234
|
-
|
|
13823
|
+
log36.debug(TAG32, `#${card.short_id} held — ${account.detail}`);
|
|
14235
13824
|
await this.emitWaiting(card.id, account.detail);
|
|
14236
13825
|
return;
|
|
14237
13826
|
}
|
|
14238
13827
|
if (mode === "implement") {
|
|
14239
13828
|
if (this.authPaused) {
|
|
14240
|
-
|
|
13829
|
+
log36.debug(TAG32, `#${card.short_id} held — agent paused (auth error)`);
|
|
14241
13830
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
14242
13831
|
return;
|
|
14243
13832
|
}
|
|
14244
13833
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
14245
13834
|
if (cooldownMs > 0) {
|
|
14246
|
-
|
|
13835
|
+
log36.debug(TAG32, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
14247
13836
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
14248
13837
|
return;
|
|
14249
13838
|
}
|
|
@@ -14252,19 +13841,19 @@ class Pool {
|
|
|
14252
13841
|
if (decision.reason === "daily_budget") {
|
|
14253
13842
|
await this.denyDailyBudget(card, decision.detail);
|
|
14254
13843
|
} else {
|
|
14255
|
-
|
|
13844
|
+
log36.debug(TAG32, `#${card.short_id} skipped (${decision.reason}): ${decision.detail}`);
|
|
14256
13845
|
}
|
|
14257
13846
|
return;
|
|
14258
13847
|
}
|
|
14259
13848
|
const { blockers, successors } = await getChainSignals(this.client, card, this.projectId);
|
|
14260
13849
|
chainSuccessors = successors;
|
|
14261
13850
|
if (blockers === null) {
|
|
14262
|
-
|
|
13851
|
+
log36.warn(TAG32, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
14263
13852
|
return;
|
|
14264
13853
|
}
|
|
14265
13854
|
if (blockers.length > 0) {
|
|
14266
13855
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
14267
|
-
|
|
13856
|
+
log36.info(TAG32, `#${card.short_id} blocked by ${list} — waiting`);
|
|
14268
13857
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
14269
13858
|
return;
|
|
14270
13859
|
}
|
|
@@ -14296,9 +13885,9 @@ class Pool {
|
|
|
14296
13885
|
const reason = `Daily budget reached — waiting for reset (${detail})`;
|
|
14297
13886
|
const line = `#${card.short_id} skipped (daily_budget): ${detail}`;
|
|
14298
13887
|
if (this.lastWaitingEmit.get(card.id) === reason)
|
|
14299
|
-
|
|
13888
|
+
log36.debug(TAG32, line);
|
|
14300
13889
|
else
|
|
14301
|
-
|
|
13890
|
+
log36.warn(TAG32, line);
|
|
14302
13891
|
await this.emitWaiting(card.id, reason);
|
|
14303
13892
|
}
|
|
14304
13893
|
lastWaitingEmit = new Map;
|
|
@@ -14315,7 +13904,7 @@ class Pool {
|
|
|
14315
13904
|
});
|
|
14316
13905
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
14317
13906
|
} catch (err) {
|
|
14318
|
-
|
|
13907
|
+
log36.debug(TAG32, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14319
13908
|
}
|
|
14320
13909
|
}
|
|
14321
13910
|
noteApiError(err) {
|
|
@@ -14323,13 +13912,13 @@ class Pool {
|
|
|
14323
13912
|
return;
|
|
14324
13913
|
if (err.kind === "spend_limit") {
|
|
14325
13914
|
this.accountGuard?.halt("spend_limit").catch((hErr) => {
|
|
14326
|
-
|
|
13915
|
+
log36.error(TAG32, `Failed to latch the account halt: ${hErr}`);
|
|
14327
13916
|
});
|
|
14328
13917
|
return;
|
|
14329
13918
|
}
|
|
14330
13919
|
if (err.kind === "auth") {
|
|
14331
13920
|
if (!this.authPaused) {
|
|
14332
|
-
|
|
13921
|
+
log36.error(TAG32, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
14333
13922
|
}
|
|
14334
13923
|
this.authPaused = true;
|
|
14335
13924
|
return;
|
|
@@ -14338,7 +13927,7 @@ class Pool {
|
|
|
14338
13927
|
const until = Date.now() + cooldownMs;
|
|
14339
13928
|
if (until > this.apiCooldownUntil) {
|
|
14340
13929
|
this.apiCooldownUntil = until;
|
|
14341
|
-
|
|
13930
|
+
log36.warn(TAG32, `${describeApiError3(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
14342
13931
|
}
|
|
14343
13932
|
}
|
|
14344
13933
|
apiCooldownRemainingMs() {
|
|
@@ -14352,13 +13941,13 @@ class Pool {
|
|
|
14352
13941
|
const removed = queue.remove(cardId);
|
|
14353
13942
|
if (removed) {
|
|
14354
13943
|
this.cardDataCache.delete(cardId);
|
|
14355
|
-
|
|
13944
|
+
log36.info(TAG32, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
14356
13945
|
return;
|
|
14357
13946
|
}
|
|
14358
13947
|
}
|
|
14359
13948
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
14360
13949
|
if (worker) {
|
|
14361
|
-
|
|
13950
|
+
log36.info(TAG32, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
14362
13951
|
await worker.cancel("unassigned");
|
|
14363
13952
|
}
|
|
14364
13953
|
}
|
|
@@ -14415,10 +14004,10 @@ class Pool {
|
|
|
14415
14004
|
}
|
|
14416
14005
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
14417
14006
|
if (!worker) {
|
|
14418
|
-
|
|
14007
|
+
log36.debug(TAG32, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
14419
14008
|
return;
|
|
14420
14009
|
}
|
|
14421
|
-
|
|
14010
|
+
log36.info(TAG32, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
14422
14011
|
switch (command) {
|
|
14423
14012
|
case "pause":
|
|
14424
14013
|
await worker.pause();
|
|
@@ -14466,7 +14055,7 @@ class Pool {
|
|
|
14466
14055
|
};
|
|
14467
14056
|
}
|
|
14468
14057
|
async shutdown() {
|
|
14469
|
-
|
|
14058
|
+
log36.info(TAG32, "Shutting down pool...");
|
|
14470
14059
|
this.shuttingDown = true;
|
|
14471
14060
|
const active = [
|
|
14472
14061
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -14474,7 +14063,7 @@ class Pool {
|
|
|
14474
14063
|
];
|
|
14475
14064
|
await Promise.all(active.map((w) => w.cancel("shutdown")));
|
|
14476
14065
|
this.sleepGuard.stop();
|
|
14477
|
-
|
|
14066
|
+
log36.info(TAG32, "Pool shutdown complete");
|
|
14478
14067
|
}
|
|
14479
14068
|
async drainBudgetDecisions(cardId) {
|
|
14480
14069
|
const targets = cardId ? [cardId] : [
|
|
@@ -14505,7 +14094,11 @@ class Pool {
|
|
|
14505
14094
|
try {
|
|
14506
14095
|
({ decisions } = await this.client.getBudgetDecisions(cardId, new Date(sinceMs).toISOString()));
|
|
14507
14096
|
} catch (err) {
|
|
14508
|
-
|
|
14097
|
+
if (httpStatusOf(err) === 404) {
|
|
14098
|
+
await this.forgetDeletedCard(cardId, run);
|
|
14099
|
+
return;
|
|
14100
|
+
}
|
|
14101
|
+
log36.warn(TAG32, `getBudgetDecisions failed for ${cardId}: ${err}`);
|
|
14509
14102
|
return;
|
|
14510
14103
|
}
|
|
14511
14104
|
if (decisions.length > 0) {
|
|
@@ -14529,6 +14122,25 @@ class Pool {
|
|
|
14529
14122
|
this.draining.delete(cardId);
|
|
14530
14123
|
}
|
|
14531
14124
|
}
|
|
14125
|
+
async forgetDeletedCard(cardId, run) {
|
|
14126
|
+
if (run) {
|
|
14127
|
+
try {
|
|
14128
|
+
await this.stateStore.endRun(run.runId, "failed", {
|
|
14129
|
+
errorMessage: "card deleted"
|
|
14130
|
+
});
|
|
14131
|
+
} catch (err) {
|
|
14132
|
+
log36.warn(TAG32, `Failed to end the parked run for the deleted card ${cardId}: ${err}`);
|
|
14133
|
+
}
|
|
14134
|
+
}
|
|
14135
|
+
try {
|
|
14136
|
+
const dropped = await this.stateStore.forgetCard(cardId);
|
|
14137
|
+
if (dropped) {
|
|
14138
|
+
log36.info(TAG32, `Card ${cardId} no longer exists on the server — dropped from the local ledger; its budget question can never be answered`);
|
|
14139
|
+
}
|
|
14140
|
+
} catch (err) {
|
|
14141
|
+
log36.warn(TAG32, `Failed to drop the deleted card ${cardId}: ${err}`);
|
|
14142
|
+
}
|
|
14143
|
+
}
|
|
14532
14144
|
async releaseExpiredPark(run) {
|
|
14533
14145
|
try {
|
|
14534
14146
|
await this.client.addComment(run.cardId, formatExpiredParkComment({
|
|
@@ -14540,7 +14152,7 @@ class Pool {
|
|
|
14540
14152
|
...run.blockerCommentId ? { replyToId: run.blockerCommentId } : {}
|
|
14541
14153
|
});
|
|
14542
14154
|
} catch (err) {
|
|
14543
|
-
|
|
14155
|
+
log36.warn(TAG32, `Failed to post the expired-park closing comment for ${run.cardId}: ${err}`);
|
|
14544
14156
|
}
|
|
14545
14157
|
try {
|
|
14546
14158
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14554,14 +14166,14 @@ class Pool {
|
|
|
14554
14166
|
});
|
|
14555
14167
|
}
|
|
14556
14168
|
} catch (err) {
|
|
14557
|
-
|
|
14169
|
+
log36.error(TAG32, `Failed to move #${run.cardShortId} after an expired park: ${err}`);
|
|
14558
14170
|
}
|
|
14559
14171
|
try {
|
|
14560
14172
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14561
14173
|
errorMessage: "budget decision expired"
|
|
14562
14174
|
});
|
|
14563
14175
|
} catch (err) {
|
|
14564
|
-
|
|
14176
|
+
log36.warn(TAG32, `Failed to release the expired park for ${run.cardId}: ${err}`);
|
|
14565
14177
|
}
|
|
14566
14178
|
}
|
|
14567
14179
|
async releaseExpiredAttemptCap(cardId, blockerCommentId) {
|
|
@@ -14571,7 +14183,7 @@ class Pool {
|
|
|
14571
14183
|
...blockerCommentId ? { replyToId: blockerCommentId } : {}
|
|
14572
14184
|
});
|
|
14573
14185
|
} catch (err) {
|
|
14574
|
-
|
|
14186
|
+
log36.warn(TAG32, `Failed to post the expired attempt-cap note for ${cardId}: ${err}`);
|
|
14575
14187
|
}
|
|
14576
14188
|
try {
|
|
14577
14189
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14580,7 +14192,7 @@ class Pool {
|
|
|
14580
14192
|
failureSummary: "The attempt-budget decision expired with no answer. Reassign the card to grant a fresh attempt."
|
|
14581
14193
|
});
|
|
14582
14194
|
} catch (err) {
|
|
14583
|
-
|
|
14195
|
+
log36.warn(TAG32, `Failed to end the expired attempt-cap session for ${cardId}: ${err}`);
|
|
14584
14196
|
}
|
|
14585
14197
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14586
14198
|
}
|
|
@@ -14591,16 +14203,16 @@ class Pool {
|
|
|
14591
14203
|
}
|
|
14592
14204
|
const account = this.accountGuard?.check();
|
|
14593
14205
|
if (account && !account.running) {
|
|
14594
|
-
|
|
14206
|
+
log36.debug(TAG32, `#${run.cardShortId}: granted continue held — ${account.detail}`);
|
|
14595
14207
|
return;
|
|
14596
14208
|
}
|
|
14597
14209
|
const first = !this.adoptAnnounced.has(run.runId);
|
|
14598
14210
|
this.adoptAnnounced.add(run.runId);
|
|
14599
14211
|
const line = `#${run.cardShortId}: granted continue never reached a worker — re-enqueueing (${run.pipeline})`;
|
|
14600
14212
|
if (first)
|
|
14601
|
-
|
|
14213
|
+
log36.warn(TAG32, line);
|
|
14602
14214
|
else
|
|
14603
|
-
|
|
14215
|
+
log36.debug(TAG32, line);
|
|
14604
14216
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14605
14217
|
}
|
|
14606
14218
|
adoptAnnounced = new Set;
|
|
@@ -14619,7 +14231,7 @@ class Pool {
|
|
|
14619
14231
|
async continueRun(run, opts) {
|
|
14620
14232
|
const turns = this.budget.checkCardTurns(run.cardId);
|
|
14621
14233
|
if (!turns.allow) {
|
|
14622
|
-
|
|
14234
|
+
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.`);
|
|
14623
14235
|
try {
|
|
14624
14236
|
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.
|
|
14625
14237
|
|
|
@@ -14628,14 +14240,14 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14628
14240
|
agentSessionId: run.sessionId ?? undefined
|
|
14629
14241
|
});
|
|
14630
14242
|
} catch (err) {
|
|
14631
|
-
|
|
14243
|
+
log36.warn(TAG32, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14632
14244
|
}
|
|
14633
14245
|
await this.stopRun(run, "Continue refused: the card is at its per-card turn cap.");
|
|
14634
14246
|
return;
|
|
14635
14247
|
}
|
|
14636
14248
|
const runsSpent = this.budget.checkCardRuns(run.cardId);
|
|
14637
14249
|
if (!runsSpent.allow) {
|
|
14638
|
-
|
|
14250
|
+
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.`);
|
|
14639
14251
|
try {
|
|
14640
14252
|
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.
|
|
14641
14253
|
|
|
@@ -14644,7 +14256,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14644
14256
|
agentSessionId: run.sessionId ?? undefined
|
|
14645
14257
|
});
|
|
14646
14258
|
} catch (err) {
|
|
14647
|
-
|
|
14259
|
+
log36.warn(TAG32, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14648
14260
|
}
|
|
14649
14261
|
await this.stopRun(run, "Continue refused: the card is at its per-card run backstop.");
|
|
14650
14262
|
return;
|
|
@@ -14668,7 +14280,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14668
14280
|
awaitingDecisionUntil: null
|
|
14669
14281
|
});
|
|
14670
14282
|
} catch (err) {
|
|
14671
|
-
|
|
14283
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14672
14284
|
}
|
|
14673
14285
|
if (run.blockerCommentId) {
|
|
14674
14286
|
try {
|
|
@@ -14676,7 +14288,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14676
14288
|
resolve: true
|
|
14677
14289
|
});
|
|
14678
14290
|
} catch (err) {
|
|
14679
|
-
|
|
14291
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14680
14292
|
}
|
|
14681
14293
|
}
|
|
14682
14294
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
@@ -14700,7 +14312,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14700
14312
|
awaitingDecisionUntil: null
|
|
14701
14313
|
});
|
|
14702
14314
|
} catch (err) {
|
|
14703
|
-
|
|
14315
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14704
14316
|
}
|
|
14705
14317
|
if (run.blockerCommentId) {
|
|
14706
14318
|
try {
|
|
@@ -14708,13 +14320,13 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14708
14320
|
resolve: true
|
|
14709
14321
|
});
|
|
14710
14322
|
} catch (err) {
|
|
14711
|
-
|
|
14323
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14712
14324
|
}
|
|
14713
14325
|
}
|
|
14714
14326
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14715
14327
|
released += 1;
|
|
14716
14328
|
} catch (err) {
|
|
14717
|
-
|
|
14329
|
+
log36.error(TAG32, `Failed to release the account-limit park on ${run.cardId}: ${err}`);
|
|
14718
14330
|
}
|
|
14719
14331
|
}
|
|
14720
14332
|
return released;
|
|
@@ -14726,7 +14338,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14726
14338
|
resolve: true
|
|
14727
14339
|
});
|
|
14728
14340
|
} catch (err) {
|
|
14729
|
-
|
|
14341
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14730
14342
|
}
|
|
14731
14343
|
}
|
|
14732
14344
|
try {
|
|
@@ -14735,7 +14347,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14735
14347
|
awaitingDecisionUntil: null
|
|
14736
14348
|
});
|
|
14737
14349
|
} catch (err) {
|
|
14738
|
-
|
|
14350
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14739
14351
|
}
|
|
14740
14352
|
try {
|
|
14741
14353
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14757,26 +14369,26 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14757
14369
|
await this.client.endAgentSession(run.cardId, endSession);
|
|
14758
14370
|
}
|
|
14759
14371
|
} catch (err) {
|
|
14760
|
-
|
|
14372
|
+
log36.error(TAG32, `Failed to hand #${run.cardShortId} back after a stop: ${err}`);
|
|
14761
14373
|
}
|
|
14762
14374
|
try {
|
|
14763
14375
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14764
14376
|
errorMessage: "budget_decision_stop"
|
|
14765
14377
|
});
|
|
14766
14378
|
} catch (err) {
|
|
14767
|
-
|
|
14379
|
+
log36.warn(TAG32, `Failed to end the local run record for ${run.cardId}: ${err}`);
|
|
14768
14380
|
}
|
|
14769
14381
|
}
|
|
14770
14382
|
async grantAttempt(cardId) {
|
|
14771
14383
|
const turns = this.budget.checkCardTurns(cardId);
|
|
14772
14384
|
if (!turns.allow) {
|
|
14773
|
-
|
|
14385
|
+
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.`);
|
|
14774
14386
|
try {
|
|
14775
14387
|
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.
|
|
14776
14388
|
|
|
14777
14389
|
Reassign the card — that clears the turn count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14778
14390
|
} catch (err) {
|
|
14779
|
-
|
|
14391
|
+
log36.warn(TAG32, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14780
14392
|
}
|
|
14781
14393
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14782
14394
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card turn cap.");
|
|
@@ -14784,13 +14396,13 @@ Reassign the card — that clears the turn count and starts it fresh — or rais
|
|
|
14784
14396
|
}
|
|
14785
14397
|
const runsSpent = this.budget.checkCardRuns(cardId);
|
|
14786
14398
|
if (!runsSpent.allow) {
|
|
14787
|
-
|
|
14399
|
+
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.`);
|
|
14788
14400
|
try {
|
|
14789
14401
|
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.
|
|
14790
14402
|
|
|
14791
14403
|
Reassign the card — that clears the run count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14792
14404
|
} catch (err) {
|
|
14793
|
-
|
|
14405
|
+
log36.warn(TAG32, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14794
14406
|
}
|
|
14795
14407
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14796
14408
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card run backstop.");
|
|
@@ -14805,7 +14417,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14805
14417
|
awaitingDecisionUntil: null
|
|
14806
14418
|
});
|
|
14807
14419
|
} catch (err) {
|
|
14808
|
-
|
|
14420
|
+
log36.warn(TAG32, `Failed to clear the attempt-cap decision deadline for ${cardId}: ${err}`);
|
|
14809
14421
|
}
|
|
14810
14422
|
await this.enqueueCard(cardId, "implement");
|
|
14811
14423
|
}
|
|
@@ -14816,7 +14428,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14816
14428
|
try {
|
|
14817
14429
|
await this.client.updateComment(blockerCommentId, { resolve: true });
|
|
14818
14430
|
} catch (err) {
|
|
14819
|
-
|
|
14431
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14820
14432
|
}
|
|
14821
14433
|
}
|
|
14822
14434
|
try {
|
|
@@ -14825,7 +14437,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14825
14437
|
awaitingDecisionUntil: null
|
|
14826
14438
|
});
|
|
14827
14439
|
} catch (err) {
|
|
14828
|
-
|
|
14440
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${cardId}: ${err}`);
|
|
14829
14441
|
}
|
|
14830
14442
|
try {
|
|
14831
14443
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14834,7 +14446,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14834
14446
|
failureSummary
|
|
14835
14447
|
});
|
|
14836
14448
|
} catch (err) {
|
|
14837
|
-
|
|
14449
|
+
log36.warn(TAG32, `Failed to end the attempt-cap session for ${cardId}: ${err}`);
|
|
14838
14450
|
}
|
|
14839
14451
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14840
14452
|
}
|
|
@@ -14865,12 +14477,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14865
14477
|
const subtasks = card.subtasks ?? [];
|
|
14866
14478
|
await this.enqueue(card, column, cardLabels, subtasks, mode);
|
|
14867
14479
|
} catch (err) {
|
|
14868
|
-
|
|
14480
|
+
log36.error(TAG32, `Failed to re-enqueue ${cardId} after a budget decision: ${err}`);
|
|
14869
14481
|
}
|
|
14870
14482
|
}
|
|
14871
14483
|
async retireUnroutableRuns(shortId, cardId, reason = "column not found on this board") {
|
|
14872
14484
|
const unended = this.stateStore.getRunsForCard(cardId).filter((r) => r.endedAt === null);
|
|
14873
|
-
|
|
14485
|
+
log36.warn(TAG32, `#${shortId}: ${reason} — retiring ${unended.length} run record(s) instead of re-enqueueing`);
|
|
14874
14486
|
for (const run of unended) {
|
|
14875
14487
|
await this.stateStore.endRun(run.runId, "orphaned", {
|
|
14876
14488
|
errorMessage: `${reason} at re-enqueue — retired as unroutable (#1057/#1102)`
|
|
@@ -14883,12 +14495,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14883
14495
|
if (this.shuttingDown)
|
|
14884
14496
|
return false;
|
|
14885
14497
|
if (this.accountGuard && !this.accountGuard.check().running) {
|
|
14886
|
-
|
|
14498
|
+
log36.debug(TAG32, `${label} dispatch held — the account is halted`);
|
|
14887
14499
|
return false;
|
|
14888
14500
|
}
|
|
14889
14501
|
const idle = workers.find((w) => w.isIdle);
|
|
14890
14502
|
if (!idle) {
|
|
14891
|
-
|
|
14503
|
+
log36.debug(TAG32, `No idle ${label} workers (queue: ${queue.length})`);
|
|
14892
14504
|
return false;
|
|
14893
14505
|
}
|
|
14894
14506
|
const next = queue.dequeue();
|
|
@@ -14896,18 +14508,18 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14896
14508
|
return false;
|
|
14897
14509
|
const data = this.cardDataCache.get(next.cardId);
|
|
14898
14510
|
if (!data) {
|
|
14899
|
-
|
|
14511
|
+
log36.warn(TAG32, `No cached data for card ${next.cardId}, skipping`);
|
|
14900
14512
|
return false;
|
|
14901
14513
|
}
|
|
14902
14514
|
this.cardDataCache.delete(next.cardId);
|
|
14903
14515
|
this.lastWaitingEmit.delete(next.cardId);
|
|
14904
|
-
|
|
14516
|
+
log36.info(TAG32, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
14905
14517
|
this.sleepGuard.acquire();
|
|
14906
14518
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
14907
14519
|
return true;
|
|
14908
14520
|
}
|
|
14909
14521
|
}
|
|
14910
|
-
var
|
|
14522
|
+
var TAG32 = "pool";
|
|
14911
14523
|
var init_pool = __esm(() => {
|
|
14912
14524
|
init_dist();
|
|
14913
14525
|
init_board_helpers();
|
|
@@ -14941,7 +14553,7 @@ import {
|
|
|
14941
14553
|
writeFileSync as writeFileSync4
|
|
14942
14554
|
} from "node:fs";
|
|
14943
14555
|
import { dirname as dirname5 } from "node:path";
|
|
14944
|
-
import { log as
|
|
14556
|
+
import { log as log37 } from "@gethmy/harness";
|
|
14945
14557
|
function defaultRegistryPath() {
|
|
14946
14558
|
return portRegistryPath();
|
|
14947
14559
|
}
|
|
@@ -14956,7 +14568,7 @@ function load(path) {
|
|
|
14956
14568
|
}
|
|
14957
14569
|
return emptyRegistry();
|
|
14958
14570
|
} catch (err) {
|
|
14959
|
-
|
|
14571
|
+
log37.warn(TAG33, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
14960
14572
|
return emptyRegistry();
|
|
14961
14573
|
}
|
|
14962
14574
|
}
|
|
@@ -14977,7 +14589,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
14977
14589
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
14978
14590
|
save(path, registry);
|
|
14979
14591
|
} catch (err) {
|
|
14980
|
-
|
|
14592
|
+
log37.warn(TAG33, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
14981
14593
|
}
|
|
14982
14594
|
}
|
|
14983
14595
|
function daemonBaseUrl(bindAddr, port) {
|
|
@@ -14997,17 +14609,17 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
14997
14609
|
delete registry[projectId];
|
|
14998
14610
|
save(path, registry);
|
|
14999
14611
|
} catch (err) {
|
|
15000
|
-
|
|
14612
|
+
log37.warn(TAG33, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
15001
14613
|
}
|
|
15002
14614
|
}
|
|
15003
|
-
var
|
|
14615
|
+
var TAG33 = "port-registry";
|
|
15004
14616
|
var init_port_registry = __esm(() => {
|
|
15005
14617
|
init_dist();
|
|
15006
14618
|
init_paths();
|
|
15007
14619
|
});
|
|
15008
14620
|
|
|
15009
14621
|
// src/recovery.ts
|
|
15010
|
-
import { log as
|
|
14622
|
+
import { log as log38, teardownWorktree as teardownWorktree3 } from "@gethmy/harness";
|
|
15011
14623
|
function isProcessAlive(pid, currentPid) {
|
|
15012
14624
|
if (pid === currentPid)
|
|
15013
14625
|
return true;
|
|
@@ -15023,7 +14635,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
15023
14635
|
const { card } = await client.getCard(cardId);
|
|
15024
14636
|
return card;
|
|
15025
14637
|
} catch (err) {
|
|
15026
|
-
|
|
14638
|
+
log38.warn(TAG34, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15027
14639
|
return null;
|
|
15028
14640
|
}
|
|
15029
14641
|
}
|
|
@@ -15033,7 +14645,7 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
15033
14645
|
return [];
|
|
15034
14646
|
}
|
|
15035
14647
|
const outcomes = [];
|
|
15036
|
-
|
|
14648
|
+
log38.info(TAG34, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
15037
14649
|
for (const run of active) {
|
|
15038
14650
|
const outcome = {
|
|
15039
14651
|
runId: run.runId,
|
|
@@ -15045,16 +14657,16 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
15045
14657
|
};
|
|
15046
14658
|
outcomes.push(outcome);
|
|
15047
14659
|
if (isBudgetHeldRun(run)) {
|
|
15048
|
-
|
|
14660
|
+
log38.info(TAG34, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15049
14661
|
outcome.actions.push("skipped: held for a human budget decision");
|
|
15050
14662
|
continue;
|
|
15051
14663
|
}
|
|
15052
14664
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
15053
|
-
|
|
14665
|
+
log38.warn(TAG34, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
15054
14666
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
15055
14667
|
continue;
|
|
15056
14668
|
}
|
|
15057
|
-
|
|
14669
|
+
log38.info(TAG34, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
15058
14670
|
await recoverRun(run, store, client, config, outcome, {
|
|
15059
14671
|
rollbackAttempt: true,
|
|
15060
14672
|
agentId: opts.agentId
|
|
@@ -15075,7 +14687,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15075
14687
|
} catch (err) {
|
|
15076
14688
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15077
14689
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
15078
|
-
|
|
14690
|
+
log38.warn(TAG34, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
15079
14691
|
}
|
|
15080
14692
|
const card = await fetchCardSafely(client, run.cardId);
|
|
15081
14693
|
if (card) {
|
|
@@ -15135,9 +14747,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15135
14747
|
outcome.errors.push(`decrementAttempt: ${msg}`);
|
|
15136
14748
|
}
|
|
15137
14749
|
}
|
|
15138
|
-
|
|
14750
|
+
log38.info(TAG34, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
15139
14751
|
}
|
|
15140
|
-
var
|
|
14752
|
+
var TAG34 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
15141
14753
|
var init_recovery = __esm(() => {
|
|
15142
14754
|
init_board_helpers();
|
|
15143
14755
|
init_handback();
|
|
@@ -15159,18 +14771,18 @@ var init_repo_dirs = __esm(() => {
|
|
|
15159
14771
|
});
|
|
15160
14772
|
|
|
15161
14773
|
// src/claim.ts
|
|
15162
|
-
import { log as
|
|
14774
|
+
import { log as log39 } from "@gethmy/harness";
|
|
15163
14775
|
async function claimUnassignedCard(client, cardId, agentId, opts) {
|
|
15164
14776
|
try {
|
|
15165
14777
|
const { claimed } = opts ? await client.claimCard(cardId, agentId, opts) : await client.claimCard(cardId, agentId);
|
|
15166
|
-
|
|
14778
|
+
log39.debug(TAG35, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
15167
14779
|
return claimed;
|
|
15168
14780
|
} catch (err) {
|
|
15169
|
-
|
|
14781
|
+
log39.error(TAG35, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
15170
14782
|
return false;
|
|
15171
14783
|
}
|
|
15172
14784
|
}
|
|
15173
|
-
var
|
|
14785
|
+
var TAG35 = "claim";
|
|
15174
14786
|
var init_claim = () => {};
|
|
15175
14787
|
|
|
15176
14788
|
// src/strand-recovery.ts
|
|
@@ -15178,7 +14790,7 @@ var exports_strand_recovery = {};
|
|
|
15178
14790
|
__export(exports_strand_recovery, {
|
|
15179
14791
|
reclaimPreReviewStrands: () => reclaimPreReviewStrands
|
|
15180
14792
|
});
|
|
15181
|
-
import { log as
|
|
14793
|
+
import { log as log40, resolvePrUrl as resolvePrUrl2 } from "@gethmy/harness";
|
|
15182
14794
|
async function reclaimPreReviewStrands(opts) {
|
|
15183
14795
|
const {
|
|
15184
14796
|
client,
|
|
@@ -15222,22 +14834,22 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15222
14834
|
continue;
|
|
15223
14835
|
const won = await claimUnassignedCard(client, card.id, agentId);
|
|
15224
14836
|
if (!won) {
|
|
15225
|
-
|
|
14837
|
+
log40.debug(TAG36, `#${card.short_id} — lost the review claim race, skipping`);
|
|
15226
14838
|
continue;
|
|
15227
14839
|
}
|
|
15228
|
-
|
|
14840
|
+
log40.warn(TAG36, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
15229
14841
|
reclaimed.push(card.id);
|
|
15230
14842
|
if (opts.onClaimed) {
|
|
15231
14843
|
try {
|
|
15232
14844
|
await opts.onClaimed(card);
|
|
15233
14845
|
} catch (err) {
|
|
15234
|
-
|
|
14846
|
+
log40.error(TAG36, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
15235
14847
|
}
|
|
15236
14848
|
}
|
|
15237
14849
|
}
|
|
15238
14850
|
return reclaimed;
|
|
15239
14851
|
}
|
|
15240
|
-
var
|
|
14852
|
+
var TAG36 = "strand-recovery";
|
|
15241
14853
|
var init_strand_recovery = __esm(() => {
|
|
15242
14854
|
init_board_helpers();
|
|
15243
14855
|
init_claim();
|
|
@@ -15403,7 +15015,7 @@ var init_scope = __esm(() => {
|
|
|
15403
15015
|
});
|
|
15404
15016
|
|
|
15405
15017
|
// src/sweep.ts
|
|
15406
|
-
import { log as
|
|
15018
|
+
import { log as log41 } from "@gethmy/harness";
|
|
15407
15019
|
function rankSweepCandidates(opts) {
|
|
15408
15020
|
const {
|
|
15409
15021
|
agentId,
|
|
@@ -15522,10 +15134,10 @@ async function sweepForCard(opts) {
|
|
|
15522
15134
|
let { candidates, skipped, scope } = rankSweepCandidates(opts);
|
|
15523
15135
|
const cohesionCounts = opts.ranking.cohesionWeight !== 0;
|
|
15524
15136
|
if (cohesionCounts) {
|
|
15525
|
-
|
|
15137
|
+
log41.debug(TAG37, describeActiveScope(scope.active));
|
|
15526
15138
|
}
|
|
15527
15139
|
if (candidates.length === 0) {
|
|
15528
|
-
|
|
15140
|
+
log41.debug(TAG37, `no claimable card (${skipped.length} skipped in a pickup column)`);
|
|
15529
15141
|
return { claimed: null, skipped, probed: 0 };
|
|
15530
15142
|
}
|
|
15531
15143
|
const probeWindow = candidates.slice(0, maxProbes);
|
|
@@ -15541,12 +15153,12 @@ async function sweepForCard(opts) {
|
|
|
15541
15153
|
chainNeighbors: chainNeighbors(chain2)
|
|
15542
15154
|
}));
|
|
15543
15155
|
} else if (chainTermsCount && candidates.length > 1) {
|
|
15544
|
-
|
|
15156
|
+
log41.debug(TAG37, `chain signals not ranked — maxProbesPerTick=${maxProbes} leaves nothing to reorder among ${candidates.length} candidates`);
|
|
15545
15157
|
}
|
|
15546
15158
|
let probed = 0;
|
|
15547
15159
|
for (const candidate of candidates) {
|
|
15548
15160
|
if (probed >= maxProbes) {
|
|
15549
|
-
|
|
15161
|
+
log41.debug(TAG37, `probe budget ${maxProbes} spent — ${candidates.length - probed} candidate(s) deferred to the next tick`);
|
|
15550
15162
|
break;
|
|
15551
15163
|
}
|
|
15552
15164
|
probed++;
|
|
@@ -15624,10 +15236,10 @@ async function sweepForCard(opts) {
|
|
|
15624
15236
|
continue;
|
|
15625
15237
|
}
|
|
15626
15238
|
verified.card.assigned_agent_id = agentId;
|
|
15627
|
-
|
|
15239
|
+
log41.info(TAG37, claimLine(verified, probed, candidates, skipped, cohesionCounts ? scope.active : null));
|
|
15628
15240
|
return { claimed: verified, skipped, probed };
|
|
15629
15241
|
}
|
|
15630
|
-
|
|
15242
|
+
log41.debug(TAG37, `claimed nothing this sweep (${probed} probed, ${skipped.length} skipped)`);
|
|
15631
15243
|
return { claimed: null, skipped, probed };
|
|
15632
15244
|
}
|
|
15633
15245
|
function claimLine(claimed, rank, candidates, skipped, scope) {
|
|
@@ -15648,7 +15260,7 @@ async function refetchCard(client, cardId) {
|
|
|
15648
15260
|
const { card } = await client.getCard(cardId);
|
|
15649
15261
|
return card ?? null;
|
|
15650
15262
|
} catch (err) {
|
|
15651
|
-
|
|
15263
|
+
log41.warn(TAG37, `re-read failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15652
15264
|
return null;
|
|
15653
15265
|
}
|
|
15654
15266
|
}
|
|
@@ -15657,11 +15269,11 @@ async function hasLiveSession(client, cardId) {
|
|
|
15657
15269
|
const { session } = await client.getAgentSession(cardId);
|
|
15658
15270
|
return session != null;
|
|
15659
15271
|
} catch (err) {
|
|
15660
|
-
|
|
15272
|
+
log41.warn(TAG37, `session lookup failed for ${cardId} — treating as busy: ${err instanceof Error ? err.message : err}`);
|
|
15661
15273
|
return true;
|
|
15662
15274
|
}
|
|
15663
15275
|
}
|
|
15664
|
-
var
|
|
15276
|
+
var TAG37 = "sweep";
|
|
15665
15277
|
var init_sweep = __esm(() => {
|
|
15666
15278
|
init_board_helpers();
|
|
15667
15279
|
init_claim();
|
|
@@ -15672,7 +15284,7 @@ var init_sweep = __esm(() => {
|
|
|
15672
15284
|
});
|
|
15673
15285
|
|
|
15674
15286
|
// src/sweep-commands.ts
|
|
15675
|
-
import { log as
|
|
15287
|
+
import { log as log42 } from "@gethmy/harness";
|
|
15676
15288
|
function parseSweepCommands(payload) {
|
|
15677
15289
|
if (!payload || typeof payload !== "object")
|
|
15678
15290
|
return [];
|
|
@@ -15699,14 +15311,14 @@ function parseSweepCommands(payload) {
|
|
|
15699
15311
|
async function claimSweepCommands(client, agentId, projectId, daemonId) {
|
|
15700
15312
|
const request = client.request?.bind(client);
|
|
15701
15313
|
if (typeof request !== "function") {
|
|
15702
|
-
|
|
15314
|
+
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");
|
|
15703
15315
|
return [];
|
|
15704
15316
|
}
|
|
15705
15317
|
try {
|
|
15706
15318
|
const res = await request("POST", `/agents/${encodeURIComponent(agentId)}/sweep-commands/claim`, { projectId, daemonId });
|
|
15707
15319
|
return parseSweepCommands(res);
|
|
15708
15320
|
} catch (err) {
|
|
15709
|
-
|
|
15321
|
+
log42.warn(TAG38, `could not claim sweep commands: ${err instanceof Error ? err.message : err}`);
|
|
15710
15322
|
return [];
|
|
15711
15323
|
}
|
|
15712
15324
|
}
|
|
@@ -15719,15 +15331,15 @@ async function applySweepCommands(commands, actions) {
|
|
|
15719
15331
|
await actions.resume();
|
|
15720
15332
|
}
|
|
15721
15333
|
applied = command.verb;
|
|
15722
|
-
|
|
15334
|
+
log42.info(TAG38, `applied "${command.verb}" from the board (${command.id})`);
|
|
15723
15335
|
}
|
|
15724
15336
|
return applied;
|
|
15725
15337
|
}
|
|
15726
|
-
var
|
|
15338
|
+
var TAG38 = "sweep-cmd";
|
|
15727
15339
|
var init_sweep_commands = () => {};
|
|
15728
15340
|
|
|
15729
15341
|
// src/sweep-guard.ts
|
|
15730
|
-
import { log as
|
|
15342
|
+
import { log as log43 } from "@gethmy/harness";
|
|
15731
15343
|
|
|
15732
15344
|
class SweepGuard {
|
|
15733
15345
|
config;
|
|
@@ -15783,7 +15395,7 @@ class SweepGuard {
|
|
|
15783
15395
|
await this.store.haltSweep(reason);
|
|
15784
15396
|
if (!already) {
|
|
15785
15397
|
const claimed = this.store.getSweep().claimed;
|
|
15786
|
-
|
|
15398
|
+
log43.info(TAG39, `claiming stopped (${reason}) after ${claimed} claimed`);
|
|
15787
15399
|
}
|
|
15788
15400
|
return this.snapshot();
|
|
15789
15401
|
}
|
|
@@ -15792,7 +15404,7 @@ class SweepGuard {
|
|
|
15792
15404
|
const was = before.haltReason ? this.describeStop(before.haltReason) : null;
|
|
15793
15405
|
await this.store.resumeSweep();
|
|
15794
15406
|
if (was)
|
|
15795
|
-
|
|
15407
|
+
log43.info(TAG39, `claiming resumed by the operator — was stopped: ${was}`);
|
|
15796
15408
|
return this.snapshot();
|
|
15797
15409
|
}
|
|
15798
15410
|
snapshot() {
|
|
@@ -15854,11 +15466,11 @@ function sweepBannerLine(config) {
|
|
|
15854
15466
|
const authors = extra ? `you + ${extra} trusted author${extra === 1 ? "" : "s"}` : "you only";
|
|
15855
15467
|
return `Sweep ON — claims ${scope} in ${config.pickupColumns.join(", ")}, ` + `authored by ${authors}; caps: ${describeCaps(sweep, config.budget.dailyBudgetCents)}; ` + `${sweep.maxProbesPerTick} probes/tick`;
|
|
15856
15468
|
}
|
|
15857
|
-
var
|
|
15469
|
+
var TAG39 = "sweep";
|
|
15858
15470
|
var init_sweep_guard = () => {};
|
|
15859
15471
|
|
|
15860
15472
|
// src/reconcile.ts
|
|
15861
|
-
import { detectGitProvider as detectGitProvider5, log as
|
|
15473
|
+
import { detectGitProvider as detectGitProvider5, log as log44 } from "@gethmy/harness";
|
|
15862
15474
|
function daemonProcessLabels(approvedLabel) {
|
|
15863
15475
|
const names = ["agent", "agent-recovered", NEED_REVIEW_LABEL, approvedLabel];
|
|
15864
15476
|
return new Set(names.filter(Boolean).map((n) => n.toLowerCase()));
|
|
@@ -15921,7 +15533,7 @@ class Reconciler {
|
|
|
15921
15533
|
clearInterval(this.timer);
|
|
15922
15534
|
this.timer = null;
|
|
15923
15535
|
}
|
|
15924
|
-
|
|
15536
|
+
log44.info(TAG40, "Heartbeat stopped");
|
|
15925
15537
|
}
|
|
15926
15538
|
async recoverStaleRuns() {
|
|
15927
15539
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -15932,7 +15544,7 @@ class Reconciler {
|
|
|
15932
15544
|
const pool = this.pool;
|
|
15933
15545
|
for (const run of active) {
|
|
15934
15546
|
if (isBudgetHeldRun(run)) {
|
|
15935
|
-
|
|
15547
|
+
log44.info(TAG40, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15936
15548
|
continue;
|
|
15937
15549
|
}
|
|
15938
15550
|
const foreignDaemon = run.daemonPid !== process.pid;
|
|
@@ -15942,7 +15554,7 @@ class Reconciler {
|
|
|
15942
15554
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
15943
15555
|
continue;
|
|
15944
15556
|
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`;
|
|
15945
|
-
|
|
15557
|
+
log44.warn(TAG40, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
15946
15558
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
15947
15559
|
runId: run.runId,
|
|
15948
15560
|
cardId: run.cardId,
|
|
@@ -15969,11 +15581,11 @@ class Reconciler {
|
|
|
15969
15581
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
15970
15582
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
15971
15583
|
continue;
|
|
15972
|
-
|
|
15584
|
+
log44.warn(TAG40, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
15973
15585
|
try {
|
|
15974
15586
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
15975
15587
|
} catch (err) {
|
|
15976
|
-
|
|
15588
|
+
log44.error(TAG40, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
15977
15589
|
}
|
|
15978
15590
|
}
|
|
15979
15591
|
}
|
|
@@ -16005,7 +15617,7 @@ class Reconciler {
|
|
|
16005
15617
|
return;
|
|
16006
15618
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
16007
15619
|
const subtasks = card.subtasks ?? [];
|
|
16008
|
-
|
|
15620
|
+
log44.info(TAG40, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
16009
15621
|
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
16010
15622
|
}
|
|
16011
15623
|
});
|
|
@@ -16029,11 +15641,11 @@ class Reconciler {
|
|
|
16029
15641
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
16030
15642
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
16031
15643
|
continue;
|
|
16032
|
-
|
|
15644
|
+
log44.warn(TAG40, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
16033
15645
|
try {
|
|
16034
15646
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
16035
15647
|
} catch (err) {
|
|
16036
|
-
|
|
15648
|
+
log44.error(TAG40, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
16037
15649
|
}
|
|
16038
15650
|
}
|
|
16039
15651
|
}
|
|
@@ -16068,7 +15680,7 @@ class Reconciler {
|
|
|
16068
15680
|
}
|
|
16069
15681
|
this.lastSweepStopReported = null;
|
|
16070
15682
|
if (!this.pool.hasFreeImplementSlot()) {
|
|
16071
|
-
|
|
15683
|
+
log44.debug(TAG40, "sweep skipped — no free implement slot");
|
|
16072
15684
|
return;
|
|
16073
15685
|
}
|
|
16074
15686
|
const { claimed } = await sweepForCard({
|
|
@@ -16087,9 +15699,9 @@ class Reconciler {
|
|
|
16087
15699
|
reportSweepStopped(reason, detail) {
|
|
16088
15700
|
const line = `sweep stopped claiming — ${detail}`;
|
|
16089
15701
|
if (this.lastSweepStopReported === reason)
|
|
16090
|
-
|
|
15702
|
+
log44.debug(TAG40, line);
|
|
16091
15703
|
else
|
|
16092
|
-
|
|
15704
|
+
log44.warn(TAG40, line);
|
|
16093
15705
|
this.lastSweepStopReported = reason;
|
|
16094
15706
|
}
|
|
16095
15707
|
setSweepPresenceSink(sink) {
|
|
@@ -16127,7 +15739,7 @@ class Reconciler {
|
|
|
16127
15739
|
return;
|
|
16128
15740
|
this.lastSweepNudgeAt = now;
|
|
16129
15741
|
this.drainSweepCommands().catch((err) => {
|
|
16130
|
-
|
|
15742
|
+
log44.error(TAG40, `sweep command nudge failed: ${err instanceof Error ? err.message : err}`);
|
|
16131
15743
|
});
|
|
16132
15744
|
}
|
|
16133
15745
|
resolveRepoDirectories() {
|
|
@@ -16136,9 +15748,9 @@ class Reconciler {
|
|
|
16136
15748
|
const dirs = readRepoDirectories(process.cwd());
|
|
16137
15749
|
this.repoDirectories = dirs;
|
|
16138
15750
|
if (dirs.size === 0) {
|
|
16139
|
-
|
|
15751
|
+
log44.warn(TAG40, `Could not read top-level directories of ${process.cwd()} — sweep cohesion runs without its path signal`);
|
|
16140
15752
|
} else {
|
|
16141
|
-
|
|
15753
|
+
log44.debug(TAG40, `Path signal allows ${dirs.size} top-level directories`);
|
|
16142
15754
|
}
|
|
16143
15755
|
return dirs;
|
|
16144
15756
|
}
|
|
@@ -16174,7 +15786,7 @@ class Reconciler {
|
|
|
16174
15786
|
if (accountHalt !== this.lastAccountHoldReported) {
|
|
16175
15787
|
if (accountHalt) {
|
|
16176
15788
|
const unqueued = assignedCards.filter((c) => !knownCardIds.has(c.id)).length;
|
|
16177
|
-
|
|
15789
|
+
log44.warn(TAG40, `Account halted (${accountHalt}) — ${unqueued} assigned card(s) not queued; nothing is dispatched until \`harmony-agent resume\``);
|
|
16178
15790
|
}
|
|
16179
15791
|
this.lastAccountHoldReported = accountHalt;
|
|
16180
15792
|
}
|
|
@@ -16190,24 +15802,24 @@ class Reconciler {
|
|
|
16190
15802
|
const subtasks = card.subtasks ?? [];
|
|
16191
15803
|
const mode = route.mode;
|
|
16192
15804
|
if (route.stage) {
|
|
16193
|
-
|
|
15805
|
+
log44.info(TAG40, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
|
|
16194
15806
|
}
|
|
16195
15807
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
16196
|
-
|
|
15808
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
16197
15809
|
continue;
|
|
16198
15810
|
}
|
|
16199
15811
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
16200
|
-
|
|
15812
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
16201
15813
|
continue;
|
|
16202
15814
|
}
|
|
16203
15815
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
16204
|
-
|
|
15816
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
|
|
16205
15817
|
continue;
|
|
16206
15818
|
}
|
|
16207
15819
|
if (accountHalt) {
|
|
16208
|
-
|
|
15820
|
+
log44.debug(TAG40, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — holding, account halted (${accountHalt})`);
|
|
16209
15821
|
} else {
|
|
16210
|
-
|
|
15822
|
+
log44.info(TAG40, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
16211
15823
|
}
|
|
16212
15824
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
16213
15825
|
}
|
|
@@ -16218,13 +15830,13 @@ class Reconciler {
|
|
|
16218
15830
|
try {
|
|
16219
15831
|
await this.pool.drainBudgetDecisions();
|
|
16220
15832
|
} catch (err) {
|
|
16221
|
-
|
|
15833
|
+
log44.error(TAG40, `budget decisions were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16222
15834
|
}
|
|
16223
15835
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
16224
15836
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
16225
15837
|
for (const knownId of knownCardIds) {
|
|
16226
15838
|
if (!allAgentCardIds.has(knownId)) {
|
|
16227
|
-
|
|
15839
|
+
log44.info(TAG40, `Missed unassign: ${knownId} — removing`);
|
|
16228
15840
|
await this.pool.removeCard(knownId);
|
|
16229
15841
|
}
|
|
16230
15842
|
}
|
|
@@ -16232,20 +15844,20 @@ class Reconciler {
|
|
|
16232
15844
|
try {
|
|
16233
15845
|
await this.drainSweepCommands();
|
|
16234
15846
|
} catch (err) {
|
|
16235
|
-
|
|
15847
|
+
log44.error(TAG40, `sweep commands were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16236
15848
|
}
|
|
16237
15849
|
try {
|
|
16238
15850
|
await this.sweepForWork(cards, columns, labelMap, pickupConfig);
|
|
16239
15851
|
} catch (err) {
|
|
16240
|
-
|
|
15852
|
+
log44.error(TAG40, `sweep failed this tick: ${err instanceof Error ? err.message : err}`);
|
|
16241
15853
|
}
|
|
16242
|
-
|
|
15854
|
+
log44.debug(TAG40, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
16243
15855
|
} catch (err) {
|
|
16244
|
-
|
|
15856
|
+
log44.error(TAG40, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
16245
15857
|
}
|
|
16246
15858
|
}
|
|
16247
15859
|
}
|
|
16248
|
-
var
|
|
15860
|
+
var TAG40 = "reconcile", SWEEP_NUDGE_COOLDOWN_MS = 15000;
|
|
16249
15861
|
var init_reconcile = __esm(() => {
|
|
16250
15862
|
init_board_helpers();
|
|
16251
15863
|
init_recovery();
|
|
@@ -16265,7 +15877,7 @@ __export(exports_startup_banner, {
|
|
|
16265
15877
|
createStartupBanner: () => createStartupBanner,
|
|
16266
15878
|
contextSourceLine: () => contextSourceLine
|
|
16267
15879
|
});
|
|
16268
|
-
import { isPretty, log as
|
|
15880
|
+
import { isPretty, log as log45 } from "@gethmy/harness";
|
|
16269
15881
|
function createStartupBanner(config, version) {
|
|
16270
15882
|
return isPretty() ? prettyBanner(config, version) : jsonBanner(config, version);
|
|
16271
15883
|
}
|
|
@@ -16290,7 +15902,7 @@ function prettyBanner(config, version) {
|
|
|
16290
15902
|
checks.push({ kind: "ok", message });
|
|
16291
15903
|
},
|
|
16292
15904
|
warn(message) {
|
|
16293
|
-
|
|
15905
|
+
log45.warn(TAG41, message);
|
|
16294
15906
|
checks.push({ kind: "warn", message: message.split(`
|
|
16295
15907
|
`, 1)[0] });
|
|
16296
15908
|
},
|
|
@@ -16315,26 +15927,26 @@ function prettyBanner(config, version) {
|
|
|
16315
15927
|
};
|
|
16316
15928
|
}
|
|
16317
15929
|
function jsonBanner(config, version) {
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
15930
|
+
log45.info(TAG41, `Harmony Agent Daemon v${version} starting...`);
|
|
15931
|
+
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
15932
|
+
log45.info(TAG41, `Config: ${contextSourceLine(config)}`);
|
|
16321
15933
|
if (config.agent.review.enabled) {
|
|
16322
|
-
|
|
15934
|
+
log45.info(TAG41, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
16323
15935
|
}
|
|
16324
15936
|
let failed = false;
|
|
16325
15937
|
return {
|
|
16326
15938
|
setProjectName(_name) {},
|
|
16327
15939
|
setGitProvider(provider) {
|
|
16328
|
-
|
|
15940
|
+
log45.info(TAG41, `Git provider: ${provider}`);
|
|
16329
15941
|
},
|
|
16330
15942
|
setHttpPort(port) {
|
|
16331
|
-
|
|
15943
|
+
log45.info(TAG41, `HTTP server on port ${port}`);
|
|
16332
15944
|
},
|
|
16333
15945
|
check(message) {
|
|
16334
|
-
|
|
15946
|
+
log45.info(TAG41, message);
|
|
16335
15947
|
},
|
|
16336
15948
|
warn(message) {
|
|
16337
|
-
|
|
15949
|
+
log45.warn(TAG41, message);
|
|
16338
15950
|
},
|
|
16339
15951
|
fail() {
|
|
16340
15952
|
failed = true;
|
|
@@ -16342,7 +15954,7 @@ function jsonBanner(config, version) {
|
|
|
16342
15954
|
async ready(message) {
|
|
16343
15955
|
if (failed)
|
|
16344
15956
|
return;
|
|
16345
|
-
|
|
15957
|
+
log45.info(TAG41, message);
|
|
16346
15958
|
}
|
|
16347
15959
|
};
|
|
16348
15960
|
}
|
|
@@ -16435,7 +16047,7 @@ function cyan(s) {
|
|
|
16435
16047
|
function yellow(s) {
|
|
16436
16048
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
16437
16049
|
}
|
|
16438
|
-
var
|
|
16050
|
+
var TAG41 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
16439
16051
|
var init_startup_banner = __esm(() => {
|
|
16440
16052
|
init_port_registry();
|
|
16441
16053
|
ANSI = {
|
|
@@ -16456,7 +16068,7 @@ import {
|
|
|
16456
16068
|
writeFileSync as writeFileSync5
|
|
16457
16069
|
} from "node:fs";
|
|
16458
16070
|
import { dirname as dirname6, resolve as resolve2, sep as sep2 } from "node:path";
|
|
16459
|
-
import { log as
|
|
16071
|
+
import { log as log46 } from "@gethmy/harness";
|
|
16460
16072
|
function migrationMarkerPath(targetPath) {
|
|
16461
16073
|
return `${targetPath}.migrated`;
|
|
16462
16074
|
}
|
|
@@ -16530,7 +16142,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16530
16142
|
throw new Error("not an object");
|
|
16531
16143
|
legacy = parsed;
|
|
16532
16144
|
} catch (err) {
|
|
16533
|
-
|
|
16145
|
+
log46.warn(TAG42, `legacy state file unreadable — skipping the split this start; it retries on the next (${err instanceof Error ? err.message : err})`);
|
|
16534
16146
|
return NO_MIGRATION;
|
|
16535
16147
|
}
|
|
16536
16148
|
const runs = Array.isArray(legacy.runs) ? legacy.runs : [];
|
|
@@ -16552,13 +16164,13 @@ function migrateLegacyStateFile(opts) {
|
|
|
16552
16164
|
const legacyVersion = typeof legacy.version === "number" ? legacy.version : SCHEMA_VERSION;
|
|
16553
16165
|
const existing = existsSync5(targetPath) ? readTargetFile(targetPath) : null;
|
|
16554
16166
|
if (existsSync5(targetPath) && !existing) {
|
|
16555
|
-
|
|
16167
|
+
log46.warn(TAG42, "existing project state file unreadable — the split overwrites it");
|
|
16556
16168
|
}
|
|
16557
16169
|
let target;
|
|
16558
16170
|
if (existing) {
|
|
16559
16171
|
const existingVersion = typeof existing.version === "number" ? existing.version : SCHEMA_VERSION;
|
|
16560
16172
|
if (existingVersion !== legacyVersion) {
|
|
16561
|
-
|
|
16173
|
+
log46.warn(TAG42, `legacy state is schema v${legacyVersion} but the project store is v${existingVersion} — skipping the split`);
|
|
16562
16174
|
return NO_MIGRATION;
|
|
16563
16175
|
}
|
|
16564
16176
|
const existingRuns = Array.isArray(existing.runs) ? existing.runs : [];
|
|
@@ -16604,7 +16216,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16604
16216
|
if (!existsSync5(backupPath))
|
|
16605
16217
|
copyFileSync(legacyPath, backupPath);
|
|
16606
16218
|
} catch (err) {
|
|
16607
|
-
|
|
16219
|
+
log46.warn(TAG42, `could not back up the legacy state file (continuing): ${err instanceof Error ? err.message : err}`);
|
|
16608
16220
|
}
|
|
16609
16221
|
const remainingRuns = runs.filter((r) => !ownRunIds.has(r.runId));
|
|
16610
16222
|
const remainingCards = cards.filter((c) => !ownCardIds.has(c.cardId));
|
|
@@ -16628,10 +16240,10 @@ function migrateLegacyStateFile(opts) {
|
|
|
16628
16240
|
copiedIndeterminateCards: indeterminateCards.length,
|
|
16629
16241
|
foreignUnendedRuns: remainingRuns.filter((r) => r.endedAt === null).length
|
|
16630
16242
|
};
|
|
16631
|
-
|
|
16243
|
+
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`);
|
|
16632
16244
|
return result;
|
|
16633
16245
|
}
|
|
16634
|
-
var
|
|
16246
|
+
var TAG42 = "state-migration", NO_MIGRATION;
|
|
16635
16247
|
var init_state_migration = __esm(() => {
|
|
16636
16248
|
init_dist();
|
|
16637
16249
|
init_state_store();
|
|
@@ -16738,7 +16350,7 @@ var init_stream_parser_selftest = __esm(() => {
|
|
|
16738
16350
|
|
|
16739
16351
|
// src/watcher.ts
|
|
16740
16352
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16741
|
-
import { isPretty as isPretty2, log as
|
|
16353
|
+
import { isPretty as isPretty2, log as log47 } from "@gethmy/harness";
|
|
16742
16354
|
import { createClient } from "@supabase/supabase-js";
|
|
16743
16355
|
function toPresenceColumns(names) {
|
|
16744
16356
|
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));
|
|
@@ -16799,7 +16411,7 @@ class Watcher {
|
|
|
16799
16411
|
const stillHeld = this.supabase?.getChannels().some((c) => c.topic === old.topic) ?? false;
|
|
16800
16412
|
if (!stillHeld)
|
|
16801
16413
|
return true;
|
|
16802
|
-
|
|
16414
|
+
log47.warn(TAG43, `removeChannel(${old.topic}) returned "${status}" and the client still holds that topic — retrying the removal`);
|
|
16803
16415
|
return false;
|
|
16804
16416
|
}
|
|
16805
16417
|
constructor(credentials, projectId, identity, routing, onCardBroadcast, onAgentCommand, onSweepCommandNudge) {
|
|
@@ -16813,7 +16425,7 @@ class Watcher {
|
|
|
16813
16425
|
}
|
|
16814
16426
|
async start() {
|
|
16815
16427
|
if (!isPretty2()) {
|
|
16816
|
-
|
|
16428
|
+
log47.info(TAG43, "Connecting to Supabase realtime (broadcast)...");
|
|
16817
16429
|
}
|
|
16818
16430
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
16819
16431
|
this.subscribeBroadcast();
|
|
@@ -16851,7 +16463,7 @@ class Watcher {
|
|
|
16851
16463
|
status = `threw (${String(err)})`;
|
|
16852
16464
|
}
|
|
16853
16465
|
if (status !== "ok") {
|
|
16854
|
-
|
|
16466
|
+
log47.debug(TAG43, `Presence sweep update returned "${status}"`);
|
|
16855
16467
|
}
|
|
16856
16468
|
})();
|
|
16857
16469
|
}
|
|
@@ -16862,7 +16474,7 @@ class Watcher {
|
|
|
16862
16474
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
16863
16475
|
this.presenceChannel = presenceChannel;
|
|
16864
16476
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
16865
|
-
|
|
16477
|
+
log47.debug(TAG43, "Presence sync");
|
|
16866
16478
|
}).subscribe(async (status) => {
|
|
16867
16479
|
if (gen !== this.presenceGen)
|
|
16868
16480
|
return;
|
|
@@ -16878,13 +16490,13 @@ class Watcher {
|
|
|
16878
16490
|
if (trackStatus !== "ok") {
|
|
16879
16491
|
this.presenceTracked = false;
|
|
16880
16492
|
if (!this.stopping) {
|
|
16881
|
-
|
|
16493
|
+
log47.warn(TAG43, `Presence track returned "${trackStatus}" — scheduling reconnect`);
|
|
16882
16494
|
this.schedulePresenceReconnect();
|
|
16883
16495
|
}
|
|
16884
16496
|
return;
|
|
16885
16497
|
}
|
|
16886
16498
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16887
|
-
|
|
16499
|
+
log47.info(TAG43, "Presence tracked on board-presence channel");
|
|
16888
16500
|
}
|
|
16889
16501
|
this.presenceTracked = true;
|
|
16890
16502
|
this.presenceReconnectAttempts = 0;
|
|
@@ -16892,7 +16504,7 @@ class Watcher {
|
|
|
16892
16504
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16893
16505
|
this.presenceTracked = false;
|
|
16894
16506
|
if (!this.stopping) {
|
|
16895
|
-
|
|
16507
|
+
log47.warn(TAG43, `Presence subscription ${status} — scheduling reconnect`);
|
|
16896
16508
|
this.schedulePresenceReconnect();
|
|
16897
16509
|
}
|
|
16898
16510
|
}
|
|
@@ -16911,7 +16523,7 @@ class Watcher {
|
|
|
16911
16523
|
async reconnectPresence() {
|
|
16912
16524
|
if (this.stopping || !this.supabase)
|
|
16913
16525
|
return;
|
|
16914
|
-
|
|
16526
|
+
log47.warn(TAG43, `Reconnecting presence subscription (attempt ${this.presenceReconnectAttempts})`);
|
|
16915
16527
|
if (this.presenceChannel) {
|
|
16916
16528
|
const old = this.presenceChannel;
|
|
16917
16529
|
this.presenceChannel = null;
|
|
@@ -16934,13 +16546,13 @@ class Watcher {
|
|
|
16934
16546
|
return;
|
|
16935
16547
|
const gen = ++this.broadcastGen;
|
|
16936
16548
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
16937
|
-
|
|
16549
|
+
log47.debug(TAG43, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
16938
16550
|
this.onCardBroadcast({
|
|
16939
16551
|
event: "card_update",
|
|
16940
16552
|
payload: msg.payload ?? {}
|
|
16941
16553
|
});
|
|
16942
16554
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
16943
|
-
|
|
16555
|
+
log47.debug(TAG43, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
16944
16556
|
this.onCardBroadcast({
|
|
16945
16557
|
event: "card_created",
|
|
16946
16558
|
payload: msg.payload ?? {}
|
|
@@ -16950,11 +16562,11 @@ class Watcher {
|
|
|
16950
16562
|
const cardId = payload.card_id;
|
|
16951
16563
|
const command = payload.command;
|
|
16952
16564
|
if (cardId && command) {
|
|
16953
|
-
|
|
16565
|
+
log47.info(TAG43, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
16954
16566
|
this.onAgentCommand?.({ cardId, command });
|
|
16955
16567
|
}
|
|
16956
16568
|
}).on("broadcast", { event: "sweep_command" }, () => {
|
|
16957
|
-
|
|
16569
|
+
log47.debug(TAG43, "Broadcast: sweep_command — draining");
|
|
16958
16570
|
this.onSweepCommandNudge?.();
|
|
16959
16571
|
}).subscribe((status) => {
|
|
16960
16572
|
if (gen !== this.broadcastGen)
|
|
@@ -16963,13 +16575,13 @@ class Watcher {
|
|
|
16963
16575
|
this.connected = true;
|
|
16964
16576
|
this.reconnectAttempts = 0;
|
|
16965
16577
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16966
|
-
|
|
16578
|
+
log47.info(TAG43, "Broadcast subscription active");
|
|
16967
16579
|
}
|
|
16968
16580
|
this.maybeResolveReady();
|
|
16969
16581
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16970
16582
|
this.connected = false;
|
|
16971
16583
|
if (!this.stopping) {
|
|
16972
|
-
|
|
16584
|
+
log47.warn(TAG43, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
16973
16585
|
this.scheduleReconnect();
|
|
16974
16586
|
}
|
|
16975
16587
|
}
|
|
@@ -16988,7 +16600,7 @@ class Watcher {
|
|
|
16988
16600
|
async reconnectBroadcast() {
|
|
16989
16601
|
if (this.stopping || !this.supabase)
|
|
16990
16602
|
return;
|
|
16991
|
-
|
|
16603
|
+
log47.warn(TAG43, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
16992
16604
|
if (this.channel) {
|
|
16993
16605
|
const old = this.channel;
|
|
16994
16606
|
this.channel = null;
|
|
@@ -17030,10 +16642,10 @@ class Watcher {
|
|
|
17030
16642
|
}
|
|
17031
16643
|
this.connected = false;
|
|
17032
16644
|
this.presenceTracked = false;
|
|
17033
|
-
|
|
16645
|
+
log47.info(TAG43, "Broadcast subscription stopped");
|
|
17034
16646
|
}
|
|
17035
16647
|
}
|
|
17036
|
-
var
|
|
16648
|
+
var TAG43 = "watcher", MAX_PRESENCE_COLUMNS = 24, MAX_PRESENCE_COLUMN_LENGTH = 120;
|
|
17037
16649
|
var init_watcher = __esm(() => {
|
|
17038
16650
|
init_sweep_guard();
|
|
17039
16651
|
});
|
|
@@ -17307,7 +16919,7 @@ __export(exports_run_gc, {
|
|
|
17307
16919
|
pruneRunLogs: () => pruneRunLogs
|
|
17308
16920
|
});
|
|
17309
16921
|
import { unlinkSync as unlinkSync2 } from "node:fs";
|
|
17310
|
-
import { log as
|
|
16922
|
+
import { log as log48 } from "@gethmy/harness";
|
|
17311
16923
|
function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
17312
16924
|
let deleted = 0;
|
|
17313
16925
|
let bytesFreed = 0;
|
|
@@ -17321,7 +16933,7 @@ function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
|
17321
16933
|
deleted++;
|
|
17322
16934
|
bytesFreed += file.sizeBytes;
|
|
17323
16935
|
} catch (err) {
|
|
17324
|
-
|
|
16936
|
+
log48.debug(TAG44, `Could not remove ${file.path}: ${err instanceof Error ? err.message : err}`);
|
|
17325
16937
|
}
|
|
17326
16938
|
}
|
|
17327
16939
|
return { deleted, bytesFreed };
|
|
@@ -17344,7 +16956,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17344
16956
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
17345
16957
|
}
|
|
17346
16958
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
17347
|
-
|
|
16959
|
+
log48.info(TAG44, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
17348
16960
|
}
|
|
17349
16961
|
return {
|
|
17350
16962
|
runRecordsPurged,
|
|
@@ -17352,7 +16964,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17352
16964
|
logBytesFreed: logs.bytesFreed
|
|
17353
16965
|
};
|
|
17354
16966
|
}
|
|
17355
|
-
var
|
|
16967
|
+
var TAG44 = "gc", DAY_MS2 = 86400000;
|
|
17356
16968
|
var init_run_gc = __esm(() => {
|
|
17357
16969
|
init_paths();
|
|
17358
16970
|
init_run_stats();
|
|
@@ -17369,7 +16981,7 @@ __export(exports_worktree_gc, {
|
|
|
17369
16981
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
17370
16982
|
import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
17371
16983
|
import { resolve as resolve3 } from "node:path";
|
|
17372
|
-
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as
|
|
16984
|
+
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as log49 } from "@gethmy/harness";
|
|
17373
16985
|
function isTransientGitNetworkError(message) {
|
|
17374
16986
|
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
17375
16987
|
}
|
|
@@ -17482,10 +17094,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
17482
17094
|
});
|
|
17483
17095
|
} catch {}
|
|
17484
17096
|
if (result.removed.length > 0) {
|
|
17485
|
-
|
|
17097
|
+
log49.info(TAG45, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
17486
17098
|
}
|
|
17487
17099
|
if (result.errors.length > 0) {
|
|
17488
|
-
|
|
17100
|
+
log49.warn(TAG45, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
17489
17101
|
}
|
|
17490
17102
|
return result;
|
|
17491
17103
|
}
|
|
@@ -17515,7 +17127,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17515
17127
|
} catch (err) {
|
|
17516
17128
|
const detail = gitErrorDetail2(err);
|
|
17517
17129
|
if (isTransientGitNetworkError(detail)) {
|
|
17518
|
-
|
|
17130
|
+
log49.debug(TAG45, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
17519
17131
|
return result;
|
|
17520
17132
|
}
|
|
17521
17133
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -17555,7 +17167,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17555
17167
|
continue;
|
|
17556
17168
|
}
|
|
17557
17169
|
if (clock() > sweepDeadline) {
|
|
17558
|
-
|
|
17170
|
+
log49.debug(TAG45, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
17559
17171
|
break;
|
|
17560
17172
|
}
|
|
17561
17173
|
try {
|
|
@@ -17568,17 +17180,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17568
17180
|
} catch (err) {
|
|
17569
17181
|
const detail = gitErrorDetail2(err);
|
|
17570
17182
|
if (isTransientGitNetworkError(detail)) {
|
|
17571
|
-
|
|
17183
|
+
log49.debug(TAG45, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
17572
17184
|
break;
|
|
17573
17185
|
}
|
|
17574
17186
|
result.errors.push({ ref, error: detail });
|
|
17575
17187
|
}
|
|
17576
17188
|
}
|
|
17577
17189
|
if (result.removed.length > 0) {
|
|
17578
|
-
|
|
17190
|
+
log49.info(TAG45, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
17579
17191
|
}
|
|
17580
17192
|
if (result.errors.length > 0) {
|
|
17581
|
-
|
|
17193
|
+
log49.warn(TAG45, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
17582
17194
|
}
|
|
17583
17195
|
return result;
|
|
17584
17196
|
}
|
|
@@ -17611,18 +17223,18 @@ class WorktreeGc {
|
|
|
17611
17223
|
try {
|
|
17612
17224
|
runWorktreeGc(this.basePath, this.store);
|
|
17613
17225
|
} catch (err) {
|
|
17614
|
-
|
|
17226
|
+
log49.warn(TAG45, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17615
17227
|
}
|
|
17616
17228
|
if (this.remoteOpts) {
|
|
17617
17229
|
try {
|
|
17618
17230
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
17619
17231
|
} catch (err) {
|
|
17620
|
-
|
|
17232
|
+
log49.warn(TAG45, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17621
17233
|
}
|
|
17622
17234
|
}
|
|
17623
17235
|
if (this.retentionOpts) {
|
|
17624
17236
|
runRetentionGc(this.store, this.retentionOpts).catch((err) => {
|
|
17625
|
-
|
|
17237
|
+
log49.warn(TAG45, `Retention GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17626
17238
|
});
|
|
17627
17239
|
}
|
|
17628
17240
|
}
|
|
@@ -17636,7 +17248,7 @@ function getRepoRoot2() {
|
|
|
17636
17248
|
return null;
|
|
17637
17249
|
}
|
|
17638
17250
|
}
|
|
17639
|
-
var
|
|
17251
|
+
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;
|
|
17640
17252
|
var init_worktree_gc = __esm(() => {
|
|
17641
17253
|
init_run_gc();
|
|
17642
17254
|
GIT_NETWORK_EXEC = {
|
|
@@ -17677,7 +17289,7 @@ import { resolve as resolve4 } from "node:path";
|
|
|
17677
17289
|
import {
|
|
17678
17290
|
detectGitProvider as detectGitProvider6,
|
|
17679
17291
|
GIT_NO_HOOKS as GIT_NO_HOOKS10,
|
|
17680
|
-
log as
|
|
17292
|
+
log as log50,
|
|
17681
17293
|
validateGitProviderCli
|
|
17682
17294
|
} from "@gethmy/harness";
|
|
17683
17295
|
async function validatePrerequisites(config, banner) {
|
|
@@ -17754,7 +17366,7 @@ async function main() {
|
|
|
17754
17366
|
} catch (err) {
|
|
17755
17367
|
if (err instanceof ConfigValidationError) {
|
|
17756
17368
|
banner.fail();
|
|
17757
|
-
|
|
17369
|
+
log50.error(TAG46, err.message);
|
|
17758
17370
|
process.exit(1);
|
|
17759
17371
|
}
|
|
17760
17372
|
throw err;
|
|
@@ -17772,7 +17384,7 @@ async function main() {
|
|
|
17772
17384
|
} catch (err) {
|
|
17773
17385
|
if (err instanceof ConfigValidationError) {
|
|
17774
17386
|
banner.fail();
|
|
17775
|
-
|
|
17387
|
+
log50.error(TAG46, err.message);
|
|
17776
17388
|
process.exit(1);
|
|
17777
17389
|
}
|
|
17778
17390
|
throw err;
|
|
@@ -17796,7 +17408,7 @@ async function main() {
|
|
|
17796
17408
|
try {
|
|
17797
17409
|
await client.reportAgentConfig(config.workspaceId, agentId, effectiveModelReport(config.agent));
|
|
17798
17410
|
} catch (err) {
|
|
17799
|
-
|
|
17411
|
+
log50.warn(TAG46, `Could not report model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
17800
17412
|
}
|
|
17801
17413
|
migrateConfigDir();
|
|
17802
17414
|
try {
|
|
@@ -17806,7 +17418,7 @@ async function main() {
|
|
|
17806
17418
|
worktreePrefix: resolve4(config.repoRoot, config.agent.worktree.basePath)
|
|
17807
17419
|
});
|
|
17808
17420
|
} catch (err) {
|
|
17809
|
-
|
|
17421
|
+
log50.error(TAG46, `legacy state split failed — starting with the project store as-is: ${err instanceof Error ? err.message : err}`);
|
|
17810
17422
|
}
|
|
17811
17423
|
const stateStore = StateStore.open({ projectId: config.projectId });
|
|
17812
17424
|
const daemonId = randomUUID4();
|
|
@@ -17957,7 +17569,7 @@ async function main() {
|
|
|
17957
17569
|
if (shuttingDown)
|
|
17958
17570
|
return;
|
|
17959
17571
|
shuttingDown = true;
|
|
17960
|
-
|
|
17572
|
+
log50.info(TAG46, `Received ${signal}, shutting down gracefully...`);
|
|
17961
17573
|
reconciler.stop();
|
|
17962
17574
|
mergeMonitor?.stop();
|
|
17963
17575
|
worktreeGc.stop();
|
|
@@ -17968,19 +17580,19 @@ async function main() {
|
|
|
17968
17580
|
}
|
|
17969
17581
|
await watcher.stop();
|
|
17970
17582
|
await pool.shutdown();
|
|
17971
|
-
|
|
17583
|
+
log50.info(TAG46, "Daemon stopped.");
|
|
17972
17584
|
process.exit(exitCode);
|
|
17973
17585
|
};
|
|
17974
17586
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
17975
17587
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
17976
17588
|
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
17977
17589
|
process.on("uncaughtException", (err) => {
|
|
17978
|
-
|
|
17590
|
+
log50.error(TAG46, `Uncaught exception: ${err.message}`);
|
|
17979
17591
|
exitCode = 1;
|
|
17980
17592
|
shutdown("uncaughtException");
|
|
17981
17593
|
});
|
|
17982
17594
|
process.on("unhandledRejection", (reason) => {
|
|
17983
|
-
|
|
17595
|
+
log50.error(TAG46, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
17984
17596
|
exitCode = 1;
|
|
17985
17597
|
shutdown("unhandledRejection");
|
|
17986
17598
|
});
|
|
@@ -18040,33 +17652,33 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
18040
17652
|
if (assignedAgentId === undefined)
|
|
18041
17653
|
return;
|
|
18042
17654
|
if (assignedAgentId === agentId) {
|
|
18043
|
-
|
|
17655
|
+
log50.info(TAG46, `Broadcast: card ${cardId} assigned to agent`);
|
|
18044
17656
|
try {
|
|
18045
17657
|
await pool.resetAttemptsForReassign(cardId);
|
|
18046
17658
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
18047
17659
|
} catch (err) {
|
|
18048
|
-
|
|
17660
|
+
log50.error(TAG46, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
18049
17661
|
}
|
|
18050
17662
|
} else if (pool.isCardKnown(cardId)) {
|
|
18051
|
-
|
|
17663
|
+
log50.info(TAG46, `Broadcast: card ${cardId} unassigned from agent`);
|
|
18052
17664
|
await pool.removeCard(cardId);
|
|
18053
17665
|
}
|
|
18054
17666
|
}
|
|
18055
17667
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
18056
17668
|
const { card } = await client.getCard(cardId);
|
|
18057
17669
|
if (card.assigned_agent_id !== agentId) {
|
|
18058
|
-
|
|
17670
|
+
log50.debug(TAG46, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
18059
17671
|
return;
|
|
18060
17672
|
}
|
|
18061
17673
|
if (card.done || card.archived_at) {
|
|
18062
|
-
|
|
17674
|
+
log50.debug(TAG46, `Card ${cardId} is finished — skipping`);
|
|
18063
17675
|
return;
|
|
18064
17676
|
}
|
|
18065
17677
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
18066
17678
|
const columns = board.columns;
|
|
18067
17679
|
const column = columns.find((c) => c.id === card.column_id);
|
|
18068
17680
|
if (!column) {
|
|
18069
|
-
|
|
17681
|
+
log50.warn(TAG46, `Column not found for card ${cardId}`);
|
|
18070
17682
|
return;
|
|
18071
17683
|
}
|
|
18072
17684
|
const route = classifyPickup(card, column.name, {
|
|
@@ -18075,31 +17687,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
18075
17687
|
playbooks: config.agent.playbooks
|
|
18076
17688
|
});
|
|
18077
17689
|
if (!route) {
|
|
18078
|
-
|
|
17690
|
+
log50.info(TAG46, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
18079
17691
|
return;
|
|
18080
17692
|
}
|
|
18081
17693
|
if (route.stage) {
|
|
18082
|
-
|
|
17694
|
+
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`);
|
|
18083
17695
|
}
|
|
18084
17696
|
const mode = route.mode;
|
|
18085
17697
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
18086
17698
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
18087
17699
|
const subtasks = card.subtasks ?? [];
|
|
18088
17700
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
18089
|
-
|
|
17701
|
+
log50.debug(TAG46, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
18090
17702
|
return;
|
|
18091
17703
|
}
|
|
18092
17704
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
18093
|
-
|
|
17705
|
+
log50.debug(TAG46, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
|
|
18094
17706
|
return;
|
|
18095
17707
|
}
|
|
18096
17708
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
18097
|
-
|
|
17709
|
+
log50.info(TAG46, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
|
|
18098
17710
|
return;
|
|
18099
17711
|
}
|
|
18100
17712
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
18101
17713
|
}
|
|
18102
|
-
var
|
|
17714
|
+
var TAG46 = "daemon", BASE_REMOTE = "origin", PKG_VERSION;
|
|
18103
17715
|
var init_src = __esm(() => {
|
|
18104
17716
|
init_account_guard();
|
|
18105
17717
|
init_account_probe();
|