@gethmy/agent 1.41.0 → 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +639 -997
- package/dist/index.js +541 -966
- package/package.json +3 -3
package/dist/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
|
}
|
|
@@ -9273,7 +8847,7 @@ class StateStore {
|
|
|
9273
8847
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
9274
8848
|
}
|
|
9275
8849
|
}
|
|
9276
|
-
var
|
|
8850
|
+
var TAG22 = "state-store", SCHEMA_VERSION = 1;
|
|
9277
8851
|
var init_state_store = __esm(() => {
|
|
9278
8852
|
init_dist();
|
|
9279
8853
|
init_paths();
|
|
@@ -9281,7 +8855,7 @@ var init_state_store = __esm(() => {
|
|
|
9281
8855
|
|
|
9282
8856
|
// src/stream-parser.ts
|
|
9283
8857
|
import { EventEmitter } from "node:events";
|
|
9284
|
-
import { log as
|
|
8858
|
+
import { log as log26 } from "@gethmy/harness";
|
|
9285
8859
|
function normalizeToolResultContent(raw) {
|
|
9286
8860
|
if (raw == null)
|
|
9287
8861
|
return;
|
|
@@ -9302,7 +8876,7 @@ function normalizeToolResultContent(raw) {
|
|
|
9302
8876
|
return String(raw);
|
|
9303
8877
|
}
|
|
9304
8878
|
}
|
|
9305
|
-
var
|
|
8879
|
+
var TAG23 = "stream-parser", StreamParser;
|
|
9306
8880
|
var init_stream_parser = __esm(() => {
|
|
9307
8881
|
StreamParser = class StreamParser extends EventEmitter {
|
|
9308
8882
|
buffer = "";
|
|
@@ -9349,14 +8923,14 @@ var init_stream_parser = __esm(() => {
|
|
|
9349
8923
|
try {
|
|
9350
8924
|
msg = JSON.parse(line);
|
|
9351
8925
|
} catch {
|
|
9352
|
-
|
|
8926
|
+
log26.debug(TAG23, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
9353
8927
|
return;
|
|
9354
8928
|
}
|
|
9355
8929
|
try {
|
|
9356
8930
|
this.handleMessage(msg);
|
|
9357
8931
|
} catch (err) {
|
|
9358
8932
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9359
|
-
|
|
8933
|
+
log26.warn(TAG23, `Error handling stream event: ${errMsg}`);
|
|
9360
8934
|
this.emit("parse_error", errMsg);
|
|
9361
8935
|
}
|
|
9362
8936
|
}
|
|
@@ -9432,7 +9006,7 @@ var init_stream_parser = __esm(() => {
|
|
|
9432
9006
|
});
|
|
9433
9007
|
|
|
9434
9008
|
// src/transitions.ts
|
|
9435
|
-
import { log as
|
|
9009
|
+
import { log as log27 } from "@gethmy/harness";
|
|
9436
9010
|
async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
9437
9011
|
let lastErr;
|
|
9438
9012
|
for (let i = 0;i < attempts; i++) {
|
|
@@ -9443,7 +9017,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
9443
9017
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
9444
9018
|
if (i < attempts - 1) {
|
|
9445
9019
|
const wait = backoffMs * 2 ** i;
|
|
9446
|
-
|
|
9020
|
+
log27.warn(TAG24, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
9447
9021
|
await new Promise((r) => setTimeout(r, wait));
|
|
9448
9022
|
}
|
|
9449
9023
|
}
|
|
@@ -9467,10 +9041,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9467
9041
|
if (opts.strictColumn) {
|
|
9468
9042
|
throw new TransitionError("move", 1, msg);
|
|
9469
9043
|
}
|
|
9470
|
-
|
|
9044
|
+
log27.warn(TAG24, `#${shortId}: ${msg} — skipping move`);
|
|
9471
9045
|
} else if (card.column_id !== target.id) {
|
|
9472
9046
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
9473
|
-
|
|
9047
|
+
log27.info(TAG24, `#${shortId} → "${target.name}"`);
|
|
9474
9048
|
card.column_id = target.id;
|
|
9475
9049
|
moveLanded = true;
|
|
9476
9050
|
} else {
|
|
@@ -9489,7 +9063,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9489
9063
|
continue;
|
|
9490
9064
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
9491
9065
|
existing.add(labelId);
|
|
9492
|
-
|
|
9066
|
+
log27.info(TAG24, `#${shortId} +label "${name}"`);
|
|
9493
9067
|
}
|
|
9494
9068
|
card.labelIds = Array.from(existing);
|
|
9495
9069
|
}
|
|
@@ -9501,23 +9075,23 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9501
9075
|
continue;
|
|
9502
9076
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
9503
9077
|
existing.delete(match.id);
|
|
9504
|
-
|
|
9078
|
+
log27.info(TAG24, `#${shortId} -label "${name}"`);
|
|
9505
9079
|
}
|
|
9506
9080
|
card.labelIds = Array.from(existing);
|
|
9507
9081
|
}
|
|
9508
9082
|
if (plan.updateCard) {
|
|
9509
9083
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
9510
|
-
|
|
9084
|
+
log27.info(TAG24, `#${shortId} updated`);
|
|
9511
9085
|
}
|
|
9512
9086
|
if (plan.endSession) {
|
|
9513
9087
|
const endResult = await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
9514
9088
|
result.endSession = endResult;
|
|
9515
|
-
|
|
9089
|
+
log27.info(TAG24, `#${shortId} session ended (${plan.endSession.status})`);
|
|
9516
9090
|
}
|
|
9517
9091
|
if (plan.assignAgent !== undefined) {
|
|
9518
9092
|
const assignedAgentId = plan.assignAgent;
|
|
9519
9093
|
await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
|
|
9520
|
-
|
|
9094
|
+
log27.info(TAG24, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
|
|
9521
9095
|
}
|
|
9522
9096
|
if (opts.store && opts.runId) {
|
|
9523
9097
|
try {
|
|
@@ -9531,11 +9105,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
9531
9105
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
9532
9106
|
return result?.label?.id ?? null;
|
|
9533
9107
|
} catch (err) {
|
|
9534
|
-
|
|
9108
|
+
log27.warn(TAG24, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
9535
9109
|
return null;
|
|
9536
9110
|
}
|
|
9537
9111
|
}
|
|
9538
|
-
var
|
|
9112
|
+
var TAG24 = "transition", TransitionError;
|
|
9539
9113
|
var init_transitions = __esm(() => {
|
|
9540
9114
|
TransitionError = class TransitionError extends Error {
|
|
9541
9115
|
step;
|
|
@@ -9565,7 +9139,7 @@ import {
|
|
|
9565
9139
|
formatDiffSummary,
|
|
9566
9140
|
GIT_NO_HOOKS as GIT_NO_HOOKS7,
|
|
9567
9141
|
implementRunContainmentCliArgs,
|
|
9568
|
-
log as
|
|
9142
|
+
log as log28,
|
|
9569
9143
|
probeDevServer,
|
|
9570
9144
|
removeSandboxContainer,
|
|
9571
9145
|
resolveStageGate,
|
|
@@ -9661,11 +9235,11 @@ class ReviewWorker {
|
|
|
9661
9235
|
cliSessionId: this.cliSessionId
|
|
9662
9236
|
});
|
|
9663
9237
|
} catch (err) {
|
|
9664
|
-
|
|
9238
|
+
log28.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
9665
9239
|
}
|
|
9666
9240
|
}
|
|
9667
9241
|
get tag() {
|
|
9668
|
-
return `${
|
|
9242
|
+
return `${TAG25}:${this.id}`;
|
|
9669
9243
|
}
|
|
9670
9244
|
get isIdle() {
|
|
9671
9245
|
return this.state === "idle";
|
|
@@ -9703,7 +9277,7 @@ class ReviewWorker {
|
|
|
9703
9277
|
try {
|
|
9704
9278
|
const after = await this.stateStore.addTurns(cardId, numTurns);
|
|
9705
9279
|
if (crossedTurnCap(before, after, cap)) {
|
|
9706
|
-
|
|
9280
|
+
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
9281
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
9708
9282
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), {
|
|
9709
9283
|
commentType: "blocker",
|
|
@@ -9745,12 +9319,12 @@ class ReviewWorker {
|
|
|
9745
9319
|
resumeMessage: null
|
|
9746
9320
|
});
|
|
9747
9321
|
} catch (err) {
|
|
9748
|
-
|
|
9322
|
+
log28.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
9749
9323
|
}
|
|
9750
9324
|
}
|
|
9751
9325
|
try {
|
|
9752
9326
|
this.state = "preparing";
|
|
9753
|
-
|
|
9327
|
+
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
9328
|
this.startHeartbeat();
|
|
9755
9329
|
if (!resuming) {
|
|
9756
9330
|
await this.stateStore.insertRun({
|
|
@@ -9778,7 +9352,7 @@ class ReviewWorker {
|
|
|
9778
9352
|
const resolution = await resolveReviewBranch(card.description, repoRoot);
|
|
9779
9353
|
if (resolution.kind !== "branch") {
|
|
9780
9354
|
const why = resolution.kind === "skip" ? resolution.reason : "no branch or PR reference";
|
|
9781
|
-
|
|
9355
|
+
log28.info(this.tag, `#${card.short_id} not auto-reviewable (${why}) — marking for human review (staying in Review)`);
|
|
9782
9356
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9783
9357
|
return;
|
|
9784
9358
|
}
|
|
@@ -9789,13 +9363,13 @@ class ReviewWorker {
|
|
|
9789
9363
|
reviewedSha: extractReviewedSha2(card.description ?? null)
|
|
9790
9364
|
});
|
|
9791
9365
|
if (continuity.kind === "skip") {
|
|
9792
|
-
|
|
9366
|
+
log28.warn(this.tag, `#${card.short_id} refusing the PR-derived re-review (${continuity.reason}) — marking for human review (staying in Review)`);
|
|
9793
9367
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9794
9368
|
return;
|
|
9795
9369
|
}
|
|
9796
9370
|
}
|
|
9797
9371
|
this.branchName = resolution.branch;
|
|
9798
|
-
|
|
9372
|
+
log28.info(this.tag, `Review branch: ${this.branchName}`);
|
|
9799
9373
|
let reviewSession;
|
|
9800
9374
|
try {
|
|
9801
9375
|
const startSession = () => this.client.startAgentSession(card.id, {
|
|
@@ -9811,14 +9385,14 @@ class ReviewWorker {
|
|
|
9811
9385
|
awaitingDecisionUntil: null
|
|
9812
9386
|
});
|
|
9813
9387
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
9814
|
-
info: (message) =>
|
|
9815
|
-
warn: (message) =>
|
|
9388
|
+
info: (message) => log28.info(this.tag, message),
|
|
9389
|
+
warn: (message) => log28.warn(this.tag, message)
|
|
9816
9390
|
});
|
|
9817
9391
|
reviewSession = started.session;
|
|
9818
9392
|
} catch (err) {
|
|
9819
9393
|
if (isSessionConflict(err)) {
|
|
9820
9394
|
this.sessionConflict = true;
|
|
9821
|
-
|
|
9395
|
+
log28.info(this.tag, `Skipping review of #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9822
9396
|
return;
|
|
9823
9397
|
}
|
|
9824
9398
|
throw err;
|
|
@@ -9839,7 +9413,7 @@ class ReviewWorker {
|
|
|
9839
9413
|
const cwd = this.worktreePath;
|
|
9840
9414
|
const devServer = await this.startDevServer(card, cwd, port);
|
|
9841
9415
|
if (devServer.skipReason) {
|
|
9842
|
-
|
|
9416
|
+
log28.warn(this.tag, `Reviewing #${card.short_id} without the visual lens — ${devServer.skipReason}`);
|
|
9843
9417
|
}
|
|
9844
9418
|
await this.client.updateAgentProgress(card.id, {
|
|
9845
9419
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9877,10 +9451,10 @@ class ReviewWorker {
|
|
|
9877
9451
|
pinnedContract = extractPinnedContract(comments, this.identity);
|
|
9878
9452
|
}
|
|
9879
9453
|
} catch (err) {
|
|
9880
|
-
|
|
9454
|
+
log28.warn(this.tag, `pinned-contract fetch failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9881
9455
|
}
|
|
9882
9456
|
if (pinnedContract) {
|
|
9883
|
-
|
|
9457
|
+
log28.info(this.tag, `Grading pinned contract for #${card.short_id} (${pinnedContract.assertions.length} criteria)`);
|
|
9884
9458
|
}
|
|
9885
9459
|
}
|
|
9886
9460
|
const systemPrompt = buildReviewSystemPrompt();
|
|
@@ -9895,7 +9469,7 @@ class ReviewWorker {
|
|
|
9895
9469
|
} else {
|
|
9896
9470
|
const planCriteria2 = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
9897
9471
|
if (planCriteria2.length > 0) {
|
|
9898
|
-
|
|
9472
|
+
log28.info(this.tag, `Grading ${planCriteria2.length} plan criteria for #${card.short_id}`);
|
|
9899
9473
|
}
|
|
9900
9474
|
userPrompt = buildReviewUserPrompt(enriched, this.branchName, cwd, previewUrl, diffSummary, this.config.worktree.baseBranch, undefined, pinnedContract, planCriteria2, devServer.skipReason);
|
|
9901
9475
|
if (resuming && this.resumeMessage) {
|
|
@@ -9912,7 +9486,7 @@ ${userPrompt}`;
|
|
|
9912
9486
|
contextIncluded: { source: "review-knowledge", mode: "review" }
|
|
9913
9487
|
});
|
|
9914
9488
|
} catch (err) {
|
|
9915
|
-
|
|
9489
|
+
log28.warn(this.tag, `prompt_history persistence skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
9916
9490
|
}
|
|
9917
9491
|
await this.client.updateAgentProgress(card.id, {
|
|
9918
9492
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9922,7 +9496,7 @@ ${userPrompt}`;
|
|
|
9922
9496
|
progressPercent: 20
|
|
9923
9497
|
});
|
|
9924
9498
|
this.timeoutTimer = setTimeout(() => {
|
|
9925
|
-
|
|
9499
|
+
log28.warn(this.tag, `Review timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
9926
9500
|
this.timedOut = true;
|
|
9927
9501
|
this.cancel("timeout");
|
|
9928
9502
|
}, this.config.review.maxTimeout);
|
|
@@ -9944,10 +9518,10 @@ ${userPrompt}`;
|
|
|
9944
9518
|
}
|
|
9945
9519
|
this.state = "completing";
|
|
9946
9520
|
await this.recordPhase("completing");
|
|
9947
|
-
|
|
9521
|
+
log28.info(this.tag, `Claude review finished for #${card.short_id}`);
|
|
9948
9522
|
this.killDevServer();
|
|
9949
9523
|
const result = parseReviewOutput(stdout);
|
|
9950
|
-
|
|
9524
|
+
log28.info(this.tag, `Review verdict: ${result.verdict} (${result.findings.length} finding(s))`);
|
|
9951
9525
|
await this.client.updateAgentProgress(card.id, {
|
|
9952
9526
|
agentIdentifier: this.sessionIdentifier,
|
|
9953
9527
|
agentName: `${AGENT_NAME} (Review)`,
|
|
@@ -9955,7 +9529,7 @@ ${userPrompt}`;
|
|
|
9955
9529
|
currentTask: `Processing ${result.verdict} verdict`,
|
|
9956
9530
|
progressPercent: 80
|
|
9957
9531
|
});
|
|
9958
|
-
await runReviewCompletion(this.client, card, result, this.config, cwd, this.branchName, sessionStats, this.lastRunLogPath, this.
|
|
9532
|
+
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
9533
|
await this.collectReviewGate(card, result);
|
|
9960
9534
|
} catch (err) {
|
|
9961
9535
|
if (err instanceof BudgetPauseError) {
|
|
@@ -9969,7 +9543,7 @@ ${userPrompt}`;
|
|
|
9969
9543
|
this.state = "error";
|
|
9970
9544
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9971
9545
|
this.lastErrorMessage = msg;
|
|
9972
|
-
|
|
9546
|
+
log28.error(this.tag, `Error reviewing #${card.short_id}: ${msg}`);
|
|
9973
9547
|
this.lastRunLogPath = appendRunLog(this.tag, this.runId, card.short_id, `[${new Date().toISOString()}] review failed before completion: ${msg}`) ?? this.lastRunLogPath;
|
|
9974
9548
|
const reviewHandback = await guardedHandback(this.client, card.id, {
|
|
9975
9549
|
agentId: this.identity.agentId,
|
|
@@ -9985,7 +9559,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9985
9559
|
agentSessionId: this.sessionId ?? undefined
|
|
9986
9560
|
});
|
|
9987
9561
|
} catch (cErr) {
|
|
9988
|
-
|
|
9562
|
+
log28.warn(this.tag, `Failed to post the failure reason to #${card.short_id}: ${cErr instanceof Error ? cErr.message : cErr}`);
|
|
9989
9563
|
}
|
|
9990
9564
|
}
|
|
9991
9565
|
try {
|
|
@@ -9997,21 +9571,21 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9997
9571
|
}
|
|
9998
9572
|
});
|
|
9999
9573
|
} catch (tErr) {
|
|
10000
|
-
|
|
9574
|
+
log28.error(this.tag, `endAgentSession unrecoverable on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
10001
9575
|
}
|
|
10002
9576
|
if (reviewHandback.verdict.proceed) {
|
|
10003
9577
|
try {
|
|
10004
9578
|
await moveCardToColumn(this.client, reviewHandback.card ?? card, this.config.review.failColumn);
|
|
10005
|
-
|
|
9579
|
+
log28.info(this.tag, `Moved #${card.short_id} to "${this.config.review.failColumn}" after error`);
|
|
10006
9580
|
} catch {
|
|
10007
|
-
|
|
9581
|
+
log28.warn(this.tag, "Failed to move card to fail column after error");
|
|
10008
9582
|
}
|
|
10009
9583
|
} else if (reviewHandback.verdict.reason === "unreadable" || reviewHandback.verdict.reason === "released" || reviewHandback.verdict.reason === "done") {
|
|
10010
9584
|
try {
|
|
10011
9585
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
10012
|
-
|
|
9586
|
+
log28.warn(this.tag, `#${card.short_id} could not be re-read — labelled "${NEED_REVIEW_LABEL}" so reconcile stops re-enqueueing the review`);
|
|
10013
9587
|
} catch {
|
|
10014
|
-
|
|
9588
|
+
log28.warn(this.tag, `Failed to add "${NEED_REVIEW_LABEL}" label after an unreadable card`);
|
|
10015
9589
|
}
|
|
10016
9590
|
}
|
|
10017
9591
|
if (this.runId) {
|
|
@@ -10055,7 +9629,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10055
9629
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
10056
9630
|
const waitHours = this.config.budget.pause.waitHours;
|
|
10057
9631
|
const until = computeDecisionDeadline(waitHours);
|
|
10058
|
-
|
|
9632
|
+
log28.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
10059
9633
|
try {
|
|
10060
9634
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
10061
9635
|
holderMessage,
|
|
@@ -10067,7 +9641,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10067
9641
|
agentSessionId: this.sessionId ?? undefined
|
|
10068
9642
|
});
|
|
10069
9643
|
} catch (commentErr) {
|
|
10070
|
-
|
|
9644
|
+
log28.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
10071
9645
|
}
|
|
10072
9646
|
if (this.runId) {
|
|
10073
9647
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -10078,7 +9652,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10078
9652
|
awaitingDecisionUntil: until
|
|
10079
9653
|
});
|
|
10080
9654
|
} catch (storeErr) {
|
|
10081
|
-
|
|
9655
|
+
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
9656
|
}
|
|
10083
9657
|
}
|
|
10084
9658
|
}
|
|
@@ -10091,7 +9665,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10091
9665
|
this.progressTracker = null;
|
|
10092
9666
|
const waitHours = this.config.budget.pause.waitHours;
|
|
10093
9667
|
const until = computeDecisionDeadline(waitHours);
|
|
10094
|
-
|
|
9668
|
+
log28.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
10095
9669
|
const body = formatBudgetComment({
|
|
10096
9670
|
trigger,
|
|
10097
9671
|
numTurns: stats?.cost?.numTurns ?? 0,
|
|
@@ -10113,7 +9687,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10113
9687
|
});
|
|
10114
9688
|
commentId = res?.comment?.id ?? null;
|
|
10115
9689
|
} catch (err) {
|
|
10116
|
-
|
|
9690
|
+
log28.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
10117
9691
|
}
|
|
10118
9692
|
try {
|
|
10119
9693
|
await this.client.updateAgentProgress(card.id, {
|
|
@@ -10124,7 +9698,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10124
9698
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
10125
9699
|
});
|
|
10126
9700
|
} catch (err) {
|
|
10127
|
-
|
|
9701
|
+
log28.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
10128
9702
|
}
|
|
10129
9703
|
if (this.runId) {
|
|
10130
9704
|
try {
|
|
@@ -10135,14 +9709,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10135
9709
|
...this.endLedger()
|
|
10136
9710
|
});
|
|
10137
9711
|
} catch (err) {
|
|
10138
|
-
|
|
9712
|
+
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
9713
|
}
|
|
10140
9714
|
}
|
|
10141
9715
|
}
|
|
10142
9716
|
async pause() {
|
|
10143
9717
|
if (!this.isActive || !this.process || this.process.killed)
|
|
10144
9718
|
return;
|
|
10145
|
-
|
|
9719
|
+
log28.info(this.tag, `Pausing review on ${this.cardId}`);
|
|
10146
9720
|
signalGroup(this.process, "SIGSTOP");
|
|
10147
9721
|
if (this.timeoutTimer) {
|
|
10148
9722
|
clearTimeout(this.timeoutTimer);
|
|
@@ -10156,17 +9730,17 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10156
9730
|
status: "paused"
|
|
10157
9731
|
});
|
|
10158
9732
|
} catch {
|
|
10159
|
-
|
|
9733
|
+
log28.warn(this.tag, "Failed to update agent session to paused");
|
|
10160
9734
|
}
|
|
10161
9735
|
}
|
|
10162
9736
|
}
|
|
10163
9737
|
async resume() {
|
|
10164
9738
|
if (!this.isActive || !this.process || this.process.killed)
|
|
10165
9739
|
return;
|
|
10166
|
-
|
|
9740
|
+
log28.info(this.tag, `Resuming review on ${this.cardId}`);
|
|
10167
9741
|
signalGroup(this.process, "SIGCONT");
|
|
10168
9742
|
this.timeoutTimer = setTimeout(() => {
|
|
10169
|
-
|
|
9743
|
+
log28.warn(this.tag, `Timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
10170
9744
|
this.timedOut = true;
|
|
10171
9745
|
this.cancel("timeout");
|
|
10172
9746
|
}, this.config.review.maxTimeout);
|
|
@@ -10178,7 +9752,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10178
9752
|
status: "working"
|
|
10179
9753
|
});
|
|
10180
9754
|
} catch {
|
|
10181
|
-
|
|
9755
|
+
log28.warn(this.tag, "Failed to update agent session to working");
|
|
10182
9756
|
}
|
|
10183
9757
|
}
|
|
10184
9758
|
}
|
|
@@ -10187,7 +9761,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10187
9761
|
return;
|
|
10188
9762
|
this.aborted = true;
|
|
10189
9763
|
this.state = "cancelling";
|
|
10190
|
-
|
|
9764
|
+
log28.info(this.tag, `Cancelling review on ${this.cardId}`);
|
|
10191
9765
|
const snapshotStats = this.lastSessionStats ?? this.progressTracker?.stats;
|
|
10192
9766
|
if (this.progressTracker) {
|
|
10193
9767
|
this.progressTracker?.stop();
|
|
@@ -10240,11 +9814,11 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10240
9814
|
"--",
|
|
10241
9815
|
prompt
|
|
10242
9816
|
];
|
|
10243
|
-
|
|
9817
|
+
log28.info(this.tag, `Spawning review: claude ${args.slice(0, 5).join(" ")} ...`);
|
|
10244
9818
|
const runLog = openRunLog(this.tag, this.runId, shortId);
|
|
10245
9819
|
this.lastRunLogPath = runLog?.path ?? null;
|
|
10246
9820
|
if (runLog) {
|
|
10247
|
-
|
|
9821
|
+
log28.info(this.tag, `Run log: ${runLog.path}`);
|
|
10248
9822
|
runLog.stream.write(`# run=${this.runId} card=#${shortId} pipeline=review started=${new Date().toISOString()}
|
|
10249
9823
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
10250
9824
|
|
|
@@ -10263,7 +9837,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10263
9837
|
this.captureCliSessionId(parser.sessionId);
|
|
10264
9838
|
});
|
|
10265
9839
|
parser.on("parse_error", (msg) => {
|
|
10266
|
-
|
|
9840
|
+
log28.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
10267
9841
|
runLog?.stream.write(`
|
|
10268
9842
|
[parse_error] ${msg}
|
|
10269
9843
|
`);
|
|
@@ -10342,16 +9916,16 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10342
9916
|
const evidence = await collectGateEvidence(registry, context);
|
|
10343
9917
|
const evaluation = gateEvaluate(resolved.gate, evidence);
|
|
10344
9918
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, toStageGateEvidenceInsert(context, evidence));
|
|
10345
|
-
|
|
9919
|
+
log28.info(this.tag, `Recorded review_passed gate evidence for #${card.short_id} stage "${resolved.stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
10346
9920
|
} catch (err) {
|
|
10347
|
-
|
|
9921
|
+
log28.warn(this.tag, `review gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
10348
9922
|
}
|
|
10349
9923
|
}
|
|
10350
9924
|
async startDevServer(card, cwd, port) {
|
|
10351
9925
|
const declined = devServerSkipReason(cwd, this.config.commands);
|
|
10352
9926
|
if (declined)
|
|
10353
9927
|
return { previewUrl: null, skipReason: declined };
|
|
10354
|
-
|
|
9928
|
+
log28.info(this.tag, `Starting dev server on port ${port}...`);
|
|
10355
9929
|
const launch = devServerLaunch({
|
|
10356
9930
|
worktreePath: cwd,
|
|
10357
9931
|
port,
|
|
@@ -10392,14 +9966,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10392
9966
|
skipReason: launch.origin ? `${launch.origin}, and ${failure}` : failure
|
|
10393
9967
|
};
|
|
10394
9968
|
}
|
|
10395
|
-
|
|
9969
|
+
log28.info(this.tag, `Dev server ready on port ${port}`);
|
|
10396
9970
|
return { previewUrl: `http://localhost:${port}`, skipReason: null };
|
|
10397
9971
|
}
|
|
10398
9972
|
killDevServer() {
|
|
10399
9973
|
if (this.devServerProcess && !this.devServerProcess.killed) {
|
|
10400
9974
|
signalGroup(this.devServerProcess, "SIGTERM");
|
|
10401
9975
|
this.devServerProcess = null;
|
|
10402
|
-
|
|
9976
|
+
log28.debug(this.tag, "Killed dev server group");
|
|
10403
9977
|
}
|
|
10404
9978
|
if (this.devServerContainer) {
|
|
10405
9979
|
const name = this.devServerContainer;
|
|
@@ -10422,7 +9996,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10422
9996
|
try {
|
|
10423
9997
|
cleanupWorktree4(this.worktreePath);
|
|
10424
9998
|
} catch {
|
|
10425
|
-
|
|
9999
|
+
log28.warn(this.tag, "Failed to cleanup review worktree");
|
|
10426
10000
|
}
|
|
10427
10001
|
}
|
|
10428
10002
|
this.process = null;
|
|
@@ -10434,7 +10008,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10434
10008
|
this.lastSessionStats = null;
|
|
10435
10009
|
}
|
|
10436
10010
|
}
|
|
10437
|
-
var
|
|
10011
|
+
var TAG25 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
10438
10012
|
var init_review_worker = __esm(() => {
|
|
10439
10013
|
init_dist();
|
|
10440
10014
|
init_board_helpers();
|
|
@@ -10458,7 +10032,7 @@ var init_review_worker = __esm(() => {
|
|
|
10458
10032
|
|
|
10459
10033
|
// src/sleep-guard.ts
|
|
10460
10034
|
import { spawn } from "node:child_process";
|
|
10461
|
-
import { log as
|
|
10035
|
+
import { log as log29 } from "@gethmy/harness";
|
|
10462
10036
|
|
|
10463
10037
|
class SleepGuard {
|
|
10464
10038
|
platform;
|
|
@@ -10486,7 +10060,7 @@ class SleepGuard {
|
|
|
10486
10060
|
if (!this.child.killed)
|
|
10487
10061
|
this.child.kill("SIGTERM");
|
|
10488
10062
|
this.child = null;
|
|
10489
|
-
|
|
10063
|
+
log29.info(TAG26, "sleep assertion released");
|
|
10490
10064
|
}
|
|
10491
10065
|
}
|
|
10492
10066
|
start() {
|
|
@@ -10501,7 +10075,7 @@ class SleepGuard {
|
|
|
10501
10075
|
spawned = true;
|
|
10502
10076
|
});
|
|
10503
10077
|
child.on("error", (err) => {
|
|
10504
|
-
|
|
10078
|
+
log29.warn(TAG26, `caffeinate unavailable: ${err.message}`);
|
|
10505
10079
|
if (this.child === child)
|
|
10506
10080
|
this.child = null;
|
|
10507
10081
|
});
|
|
@@ -10514,23 +10088,23 @@ class SleepGuard {
|
|
|
10514
10088
|
});
|
|
10515
10089
|
child.unref();
|
|
10516
10090
|
this.child = child;
|
|
10517
|
-
|
|
10091
|
+
log29.info(TAG26, "sleep assertion acquired (caffeinate -i)");
|
|
10518
10092
|
} catch (err) {
|
|
10519
|
-
|
|
10093
|
+
log29.warn(TAG26, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
10520
10094
|
}
|
|
10521
10095
|
}
|
|
10522
10096
|
}
|
|
10523
|
-
var
|
|
10097
|
+
var TAG26 = "sleep-guard";
|
|
10524
10098
|
var init_sleep_guard = () => {};
|
|
10525
10099
|
|
|
10526
10100
|
// src/unblock.ts
|
|
10527
|
-
import { log as
|
|
10101
|
+
import { log as log30 } from "@gethmy/harness";
|
|
10528
10102
|
async function fetchBlocksLinks(client, cardId) {
|
|
10529
10103
|
try {
|
|
10530
10104
|
const { links } = await client.getCardLinks(cardId);
|
|
10531
10105
|
return links.filter((l) => l.link_type === "blocks");
|
|
10532
10106
|
} catch (err) {
|
|
10533
|
-
|
|
10107
|
+
log30.warn(TAG27, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10534
10108
|
return null;
|
|
10535
10109
|
}
|
|
10536
10110
|
}
|
|
@@ -10564,31 +10138,31 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
10564
10138
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
10565
10139
|
if (successors.length === 0)
|
|
10566
10140
|
return;
|
|
10567
|
-
|
|
10141
|
+
log30.info(TAG27, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
10568
10142
|
for (const link of successors) {
|
|
10569
10143
|
const successorId = link.target_card.id;
|
|
10570
10144
|
try {
|
|
10571
10145
|
const { card } = await deps.client.getCard(successorId);
|
|
10572
10146
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
10573
|
-
|
|
10147
|
+
log30.info(TAG27, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
10574
10148
|
await deps.client.updateCard(successorId, {
|
|
10575
10149
|
assignedAgentId: deps.agentId
|
|
10576
10150
|
});
|
|
10577
10151
|
} else {
|
|
10578
|
-
|
|
10152
|
+
log30.debug(TAG27, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
10579
10153
|
continue;
|
|
10580
10154
|
}
|
|
10581
10155
|
await deps.enqueue(successorId);
|
|
10582
10156
|
} catch (err) {
|
|
10583
|
-
|
|
10157
|
+
log30.warn(TAG27, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
10584
10158
|
}
|
|
10585
10159
|
}
|
|
10586
10160
|
}
|
|
10587
|
-
var
|
|
10161
|
+
var TAG27 = "unblock";
|
|
10588
10162
|
var init_unblock = () => {};
|
|
10589
10163
|
|
|
10590
10164
|
// src/cli-agent-runner.ts
|
|
10591
|
-
import { log as
|
|
10165
|
+
import { log as log31 } from "@gethmy/harness";
|
|
10592
10166
|
function truncateOutput(value) {
|
|
10593
10167
|
return value === undefined ? undefined : value.slice(0, MAX_OUTPUT_CHARS);
|
|
10594
10168
|
}
|
|
@@ -10753,7 +10327,7 @@ class CliAgentRunner {
|
|
|
10753
10327
|
return;
|
|
10754
10328
|
const draft = sanitizeRunEventDraft(body);
|
|
10755
10329
|
if (runEventPayloadExceedsLimit(draft.payload)) {
|
|
10756
|
-
|
|
10330
|
+
log31.warn(TAG28, `Dropping ${body.kind} event: payload exceeds ${MAX_RUN_EVENT_PAYLOAD_BYTES} bytes, or cannot be serialized`);
|
|
10757
10331
|
return;
|
|
10758
10332
|
}
|
|
10759
10333
|
this.buffer.push({ ...draft, createdAt: new Date().toISOString() });
|
|
@@ -10785,15 +10359,15 @@ class CliAgentRunner {
|
|
|
10785
10359
|
this.requeue(batch);
|
|
10786
10360
|
if (batch.length > 1) {
|
|
10787
10361
|
this.nextBatchSize = Math.ceil(batch.length / 2);
|
|
10788
|
-
|
|
10362
|
+
log31.warn(TAG28, `Flush of ${batch.length} run event(s) failed ${MAX_BATCH_ATTEMPTS}×; bisecting to ${this.nextBatchSize}: ${err}`);
|
|
10789
10363
|
return;
|
|
10790
10364
|
}
|
|
10791
10365
|
const dropped = this.buffer.shift();
|
|
10792
10366
|
this.nextBatchSize = MAX_BUFFER;
|
|
10793
|
-
|
|
10367
|
+
log31.warn(TAG28, `Dropping one ${dropped?.kind} event the API refused ${MAX_BATCH_ATTEMPTS}×: ${err}`);
|
|
10794
10368
|
return;
|
|
10795
10369
|
}
|
|
10796
|
-
|
|
10370
|
+
log31.warn(TAG28, `Failed to flush run events (attempt ${this.batchAttempts}/${MAX_BATCH_ATTEMPTS}): ${err}`);
|
|
10797
10371
|
this.requeue(batch);
|
|
10798
10372
|
} finally {
|
|
10799
10373
|
this.flushing = false;
|
|
@@ -10836,26 +10410,26 @@ function mapCost(cost) {
|
|
|
10836
10410
|
durationMs: cost.durationMs
|
|
10837
10411
|
};
|
|
10838
10412
|
}
|
|
10839
|
-
var
|
|
10413
|
+
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
10414
|
var init_cli_agent_runner = __esm(() => {
|
|
10841
10415
|
init_dist();
|
|
10842
10416
|
});
|
|
10843
10417
|
|
|
10844
10418
|
// src/fanout.ts
|
|
10845
|
-
import { log as
|
|
10419
|
+
import { log as log32 } from "@gethmy/harness";
|
|
10846
10420
|
async function fetchLinks(client, cardId) {
|
|
10847
10421
|
try {
|
|
10848
10422
|
const { links } = await client.getCardLinks(cardId);
|
|
10849
10423
|
return links;
|
|
10850
10424
|
} catch (err) {
|
|
10851
|
-
|
|
10425
|
+
log32.warn(TAG29, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10852
10426
|
return null;
|
|
10853
10427
|
}
|
|
10854
10428
|
}
|
|
10855
10429
|
async function isFanoutChildOf(card, stage, client) {
|
|
10856
10430
|
const links = await fetchLinks(client, card.id);
|
|
10857
10431
|
if (links === null) {
|
|
10858
|
-
|
|
10432
|
+
log32.warn(TAG29, `#${card.short_id}: link read failed — treating as a fan-out child (fail closed, no recursive dispatch)`);
|
|
10859
10433
|
return true;
|
|
10860
10434
|
}
|
|
10861
10435
|
const parents = links.filter((l) => l.direction === "outgoing" && l.link_type === "is_part_of");
|
|
@@ -10942,7 +10516,7 @@ async function readStageHandoff(card, fromStage, deps) {
|
|
|
10942
10516
|
}
|
|
10943
10517
|
return null;
|
|
10944
10518
|
} catch (err) {
|
|
10945
|
-
|
|
10519
|
+
log32.warn(TAG29, `handoff read failed for #${card.short_id} stage "${fromStage}": ${err instanceof Error ? err.message : err}`);
|
|
10946
10520
|
return null;
|
|
10947
10521
|
}
|
|
10948
10522
|
}
|
|
@@ -11043,7 +10617,7 @@ async function dispatchWave(parent, stage, plan, items, deps) {
|
|
|
11043
10617
|
});
|
|
11044
10618
|
spawned = res.children ?? [];
|
|
11045
10619
|
} catch (err) {
|
|
11046
|
-
|
|
10620
|
+
log32.warn(TAG29, `child spawn failed for #${parent.short_id} stage "${stage.id}": ${err instanceof Error ? err.message : err}`);
|
|
11047
10621
|
return 0;
|
|
11048
10622
|
}
|
|
11049
10623
|
let created = 0;
|
|
@@ -11098,10 +10672,10 @@ async function seedChildHandoff(parent, stage, item, total, childId, deps) {
|
|
|
11098
10672
|
try {
|
|
11099
10673
|
await deps.client.addComment(childId, body, { commentType: "decision" });
|
|
11100
10674
|
} catch (err) {
|
|
11101
|
-
|
|
10675
|
+
log32.warn(TAG29, `seed handoff failed for child ${childId}: ${err instanceof Error ? err.message : err}`);
|
|
11102
10676
|
}
|
|
11103
10677
|
}
|
|
11104
|
-
var
|
|
10678
|
+
var TAG29 = "fanout";
|
|
11105
10679
|
var init_fanout = __esm(() => {
|
|
11106
10680
|
init_dist();
|
|
11107
10681
|
});
|
|
@@ -11336,7 +10910,7 @@ function runSizedPayload(input) {
|
|
|
11336
10910
|
}
|
|
11337
10911
|
|
|
11338
10912
|
// src/stage-advance.ts
|
|
11339
|
-
import { gateConfigErrorReason, log as
|
|
10913
|
+
import { gateConfigErrorReason, log as log33 } from "@gethmy/harness";
|
|
11340
10914
|
function endDispositionFor(outcome) {
|
|
11341
10915
|
return "endDisposition" in outcome && outcome.endDisposition ? outcome.endDisposition : { status: "completed" };
|
|
11342
10916
|
}
|
|
@@ -11367,7 +10941,7 @@ async function resolveStageColumnName(client, card, stage) {
|
|
|
11367
10941
|
const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
|
|
11368
10942
|
return match ? match.name : null;
|
|
11369
10943
|
} catch (err) {
|
|
11370
|
-
|
|
10944
|
+
log33.warn(TAG30, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11371
10945
|
return null;
|
|
11372
10946
|
}
|
|
11373
10947
|
}
|
|
@@ -11406,7 +10980,7 @@ async function holdGateMisconfigured(card, stage, detail, deps) {
|
|
|
11406
10980
|
});
|
|
11407
10981
|
} catch {}
|
|
11408
10982
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, { agentSessionId: deps.agentSessionId });
|
|
11409
|
-
|
|
10983
|
+
log33.info(TAG30, `#${card.short_id} GateMisconfigured: ${reason}`);
|
|
11410
10984
|
return { kind: "held_misconfigured", reason, endDisposition };
|
|
11411
10985
|
}
|
|
11412
10986
|
function firstErrorMessage(evaluation) {
|
|
@@ -11437,7 +11011,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11437
11011
|
evidence,
|
|
11438
11012
|
summary
|
|
11439
11013
|
});
|
|
11440
|
-
|
|
11014
|
+
log33.info(TAG30, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
|
|
11441
11015
|
if (decision === "exit") {
|
|
11442
11016
|
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
11443
11017
|
deps.sink?.recordLoopCompleted?.({
|
|
@@ -11482,7 +11056,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11482
11056
|
blockers: [reason],
|
|
11483
11057
|
agentSessionId: deps.agentSessionId
|
|
11484
11058
|
});
|
|
11485
|
-
|
|
11059
|
+
log33.info(TAG30, `#${card.short_id} LoopExhausted: ${reason}`);
|
|
11486
11060
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11487
11061
|
}
|
|
11488
11062
|
const guard = await guardStageReclaim(card, `converge-loop iteration of "${stage.name}"`, deps);
|
|
@@ -11507,7 +11081,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11507
11081
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11508
11082
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11509
11083
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11510
|
-
|
|
11084
|
+
log33.info(TAG30, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
|
|
11511
11085
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11512
11086
|
}
|
|
11513
11087
|
async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
|
|
@@ -11529,7 +11103,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
|
|
|
11529
11103
|
agentSessionId: deps.agentSessionId ?? undefined
|
|
11530
11104
|
});
|
|
11531
11105
|
} catch (err) {
|
|
11532
|
-
|
|
11106
|
+
log33.warn(TAG30, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11533
11107
|
}
|
|
11534
11108
|
}
|
|
11535
11109
|
async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
|
|
@@ -11557,7 +11131,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11557
11131
|
reason: "Playbook complete — final stage gate passed."
|
|
11558
11132
|
});
|
|
11559
11133
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11560
|
-
|
|
11134
|
+
log33.info(TAG30, `#${card.short_id} terminal stage "${stage.name}" passed — playbook complete (done left to the column)`);
|
|
11561
11135
|
return { kind: "completed_terminal" };
|
|
11562
11136
|
}
|
|
11563
11137
|
if (next.kind === "out_of_range") {
|
|
@@ -11589,7 +11163,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11589
11163
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11590
11164
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11591
11165
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11592
|
-
|
|
11166
|
+
log33.info(TAG30, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
|
|
11593
11167
|
if (next.stage.owner === "human") {
|
|
11594
11168
|
const reason = `Stage "${next.stage.name}" is yours: ${handoffText(next.stage)}`;
|
|
11595
11169
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
|
|
@@ -11626,7 +11200,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11626
11200
|
blockers: [reason],
|
|
11627
11201
|
agentSessionId: deps.agentSessionId
|
|
11628
11202
|
});
|
|
11629
|
-
|
|
11203
|
+
log33.info(TAG30, `#${card.short_id} GateUnmetExhausted: ${reason}`);
|
|
11630
11204
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11631
11205
|
}
|
|
11632
11206
|
const guard = await guardStageReclaim(card, `gate-unmet re-run of "${stage.name}"`, deps);
|
|
@@ -11649,7 +11223,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11649
11223
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11650
11224
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11651
11225
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11652
|
-
|
|
11226
|
+
log33.info(TAG30, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
|
|
11653
11227
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11654
11228
|
}
|
|
11655
11229
|
async function guardStageReclaim(card, what, deps) {
|
|
@@ -11675,10 +11249,10 @@ async function guardStageReclaim(card, what, deps) {
|
|
|
11675
11249
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11676
11250
|
released = stillOurs;
|
|
11677
11251
|
} catch (err) {
|
|
11678
|
-
|
|
11252
|
+
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
11253
|
}
|
|
11680
11254
|
}
|
|
11681
|
-
|
|
11255
|
+
log33.info(TAG30, `#${card.short_id} ${what} refused — ${detail} (${reason})${released ? "; released the daemon's claim so the stage router stops here" : ""}`);
|
|
11682
11256
|
try {
|
|
11683
11257
|
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
11258
|
commentType: "decision",
|
|
@@ -11700,7 +11274,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11700
11274
|
try {
|
|
11701
11275
|
await runTransition(client, card, { removeLabels: [AGENT_LABEL] }, { store: stateStore, runId });
|
|
11702
11276
|
} catch (err) {
|
|
11703
|
-
|
|
11277
|
+
log33.warn(TAG30, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11704
11278
|
}
|
|
11705
11279
|
return {
|
|
11706
11280
|
status: opts.endStatus ?? "paused",
|
|
@@ -11709,7 +11283,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11709
11283
|
failureSummary: reason.slice(0, 300)
|
|
11710
11284
|
};
|
|
11711
11285
|
}
|
|
11712
|
-
var
|
|
11286
|
+
var TAG30 = "stage-advance", AGENT_LABEL = "agent";
|
|
11713
11287
|
var init_stage_advance = __esm(() => {
|
|
11714
11288
|
init_dist();
|
|
11715
11289
|
init_handback();
|
|
@@ -11785,7 +11359,7 @@ import {
|
|
|
11785
11359
|
GIT_NO_HOOKS as GIT_NO_HOOKS8,
|
|
11786
11360
|
implementRunContainment,
|
|
11787
11361
|
implementRunContainmentCliArgs as implementRunContainmentCliArgs2,
|
|
11788
|
-
log as
|
|
11362
|
+
log as log34,
|
|
11789
11363
|
makeBranchName,
|
|
11790
11364
|
normalizeGateSpec,
|
|
11791
11365
|
pushBranch as pushBranch3,
|
|
@@ -11991,11 +11565,11 @@ class Worker {
|
|
|
11991
11565
|
sessionId: this.sessionId
|
|
11992
11566
|
});
|
|
11993
11567
|
} catch (err) {
|
|
11994
|
-
|
|
11568
|
+
log34.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
11995
11569
|
}
|
|
11996
11570
|
}
|
|
11997
11571
|
get tag() {
|
|
11998
|
-
return `${
|
|
11572
|
+
return `${TAG31}:${this.id}`;
|
|
11999
11573
|
}
|
|
12000
11574
|
get isIdle() {
|
|
12001
11575
|
return this.state === "idle";
|
|
@@ -12057,12 +11631,12 @@ class Worker {
|
|
|
12057
11631
|
}
|
|
12058
11632
|
if (!crossedTurnCap(before, after, cap))
|
|
12059
11633
|
return;
|
|
12060
|
-
|
|
11634
|
+
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
11635
|
try {
|
|
12062
11636
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
12063
11637
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), { commentType: "blocker", agentSessionId: this.sessionId ?? undefined });
|
|
12064
11638
|
} catch (err) {
|
|
12065
|
-
|
|
11639
|
+
log34.warn(TAG31, `Could not post the turn-cap notice for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
12066
11640
|
}
|
|
12067
11641
|
}
|
|
12068
11642
|
async run(card, column, labels, subtasks) {
|
|
@@ -12117,7 +11691,7 @@ class Worker {
|
|
|
12117
11691
|
resumeReason: null
|
|
12118
11692
|
});
|
|
12119
11693
|
} catch (err) {
|
|
12120
|
-
|
|
11694
|
+
log34.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
12121
11695
|
}
|
|
12122
11696
|
}
|
|
12123
11697
|
try {
|
|
@@ -12125,20 +11699,20 @@ class Worker {
|
|
|
12125
11699
|
if (!resuming) {
|
|
12126
11700
|
this.branchName = makeBranchName(card.short_id, card.title, this.config.worktree.failedBranchPrefix);
|
|
12127
11701
|
}
|
|
12128
|
-
|
|
11702
|
+
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
11703
|
const attemptCount = await this.stateStore.incrementAttempt(card.id);
|
|
12130
11704
|
const isRework = attemptCount > 1;
|
|
12131
11705
|
const runStarts = this.stateStore.getCardRunStarts(card.id);
|
|
12132
11706
|
const runCap = this.config.budget.maxRunsPerCard;
|
|
12133
11707
|
if (crossedTurnCap(runStarts - 1, runStarts, runCap)) {
|
|
12134
|
-
|
|
11708
|
+
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
11709
|
try {
|
|
12136
11710
|
await this.client.addComment(card.id, buildRunCapComment(runStarts, runCap), {
|
|
12137
11711
|
commentType: "blocker",
|
|
12138
11712
|
agentSessionId: this.sessionId ?? undefined
|
|
12139
11713
|
});
|
|
12140
11714
|
} catch (err) {
|
|
12141
|
-
|
|
11715
|
+
log34.warn(this.tag, `Could not post the run-backstop notice for ${card.id}: ${err instanceof Error ? err.message : err}`);
|
|
12142
11716
|
}
|
|
12143
11717
|
}
|
|
12144
11718
|
const recordedBranch = recordedBranchForCard(card.description, card.short_id, [
|
|
@@ -12148,10 +11722,10 @@ class Worker {
|
|
|
12148
11722
|
const continuesPushedWork = isRework || recordedBranch !== null;
|
|
12149
11723
|
if (!resuming && recordedBranch) {
|
|
12150
11724
|
if (recordedBranch !== this.branchName) {
|
|
12151
|
-
|
|
11725
|
+
log34.info(this.tag, `Card records pushed work on ${recordedBranch} — continuing that branch instead of ${this.branchName}`);
|
|
12152
11726
|
this.branchName = recordedBranch;
|
|
12153
11727
|
} else if (!isRework) {
|
|
12154
|
-
|
|
11728
|
+
log34.info(this.tag, `Card records pushed work on ${this.branchName} — continuing that branch instead of rebuilding from ${this.config.worktree.baseBranch}`);
|
|
12155
11729
|
}
|
|
12156
11730
|
}
|
|
12157
11731
|
this.startHeartbeat();
|
|
@@ -12193,14 +11767,14 @@ class Worker {
|
|
|
12193
11767
|
awaitingDecisionUntil: null
|
|
12194
11768
|
});
|
|
12195
11769
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
12196
|
-
info: (message) =>
|
|
12197
|
-
warn: (message) =>
|
|
11770
|
+
info: (message) => log34.info(this.tag, message),
|
|
11771
|
+
warn: (message) => log34.warn(this.tag, message)
|
|
12198
11772
|
});
|
|
12199
11773
|
session = started.session;
|
|
12200
11774
|
} catch (err) {
|
|
12201
11775
|
if (isSessionConflict(err)) {
|
|
12202
11776
|
this.sessionConflict = true;
|
|
12203
|
-
|
|
11777
|
+
log34.info(this.tag, `Skipping #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12204
11778
|
await this.stateStore.decrementAttempt(card.id);
|
|
12205
11779
|
return;
|
|
12206
11780
|
}
|
|
@@ -12208,7 +11782,7 @@ class Worker {
|
|
|
12208
11782
|
}
|
|
12209
11783
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
12210
11784
|
if (!sid) {
|
|
12211
|
-
|
|
11785
|
+
log34.warn(TAG31, "startAgentSession returned no session id");
|
|
12212
11786
|
}
|
|
12213
11787
|
this.sessionId = sid;
|
|
12214
11788
|
}
|
|
@@ -12226,7 +11800,7 @@ class Worker {
|
|
|
12226
11800
|
if (!resuming) {
|
|
12227
11801
|
const moved = await moveCardAndAddLabel(this.client, card, IN_PROGRESS_COLUMN, "agent");
|
|
12228
11802
|
if (!moved) {
|
|
12229
|
-
|
|
11803
|
+
log34.warn(this.tag, `Card #${card.short_id} was NOT moved to "In Progress" — check API logs`);
|
|
12230
11804
|
}
|
|
12231
11805
|
}
|
|
12232
11806
|
if (this.aborted)
|
|
@@ -12253,10 +11827,10 @@ class Worker {
|
|
|
12253
11827
|
}).trim();
|
|
12254
11828
|
const target = resolveContinuationTarget(this.branchName, continueRequested, this.config.worktree.failedBranchPrefix, this.config.worktree.approvedBranchPrefix, (ref) => fetchExistingBranch(repoRoot, ref));
|
|
12255
11829
|
if (target.branchName !== this.branchName) {
|
|
12256
|
-
|
|
11830
|
+
log34.info(this.tag, `Branch ${this.branchName} is gone from origin but its approved rename ${target.branchName} exists — continuing that branch`);
|
|
12257
11831
|
this.branchName = target.branchName;
|
|
12258
11832
|
} else if (target.reason === "exists_on_origin") {
|
|
12259
|
-
|
|
11833
|
+
log34.info(this.tag, `Branch ${this.branchName} exists on origin with no card record — continuing it rather than resetting it`);
|
|
12260
11834
|
}
|
|
12261
11835
|
this.worktreePath = createWorktree(this.config.worktree.basePath, this.config.worktree.baseBranch, this.branchName, {
|
|
12262
11836
|
continueExisting: target.continueExisting,
|
|
@@ -12290,7 +11864,7 @@ class Worker {
|
|
|
12290
11864
|
if (this.aborted)
|
|
12291
11865
|
return;
|
|
12292
11866
|
if (parked) {
|
|
12293
|
-
|
|
11867
|
+
log34.info(this.tag, `#${card.short_id} parked for plan approval — ending run`);
|
|
12294
11868
|
return;
|
|
12295
11869
|
}
|
|
12296
11870
|
}
|
|
@@ -12311,7 +11885,7 @@ class Worker {
|
|
|
12311
11885
|
progressPercent: 10
|
|
12312
11886
|
});
|
|
12313
11887
|
this.timeoutTimer = setTimeout(() => {
|
|
12314
|
-
|
|
11888
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
12315
11889
|
this.timedOut = true;
|
|
12316
11890
|
this.cancel("timeout");
|
|
12317
11891
|
}, this.config.maxTimeout);
|
|
@@ -12338,7 +11912,7 @@ class Worker {
|
|
|
12338
11912
|
}
|
|
12339
11913
|
this.state = "verifying";
|
|
12340
11914
|
await this.recordPhase("verifying");
|
|
12341
|
-
|
|
11915
|
+
log34.info(this.tag, `Claude finished for #${card.short_id}, running verification & completion`);
|
|
12342
11916
|
await this.client.updateAgentProgress(card.id, {
|
|
12343
11917
|
agentIdentifier: this.sessionIdentifier,
|
|
12344
11918
|
agentName: AGENT_NAME,
|
|
@@ -12354,7 +11928,7 @@ class Worker {
|
|
|
12354
11928
|
const onBeforeWorktreeCleanup = stageRun ? async (worktreePath) => {
|
|
12355
11929
|
stageGateEvaluation = await this.collectStageGateEvidence(card, stageRun.stage, worktreePath, subtasks);
|
|
12356
11930
|
} : 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.
|
|
11931
|
+
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
11932
|
if (completed === "park") {
|
|
12359
11933
|
await this.parkForDecision(card, "max_turns");
|
|
12360
11934
|
return;
|
|
@@ -12373,7 +11947,7 @@ class Worker {
|
|
|
12373
11947
|
break;
|
|
12374
11948
|
case "reclaim_refused":
|
|
12375
11949
|
this.held = true;
|
|
12376
|
-
|
|
11950
|
+
log34.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
12377
11951
|
break;
|
|
12378
11952
|
case "advanced":
|
|
12379
11953
|
case "completed_terminal":
|
|
@@ -12411,7 +11985,7 @@ class Worker {
|
|
|
12411
11985
|
return;
|
|
12412
11986
|
}
|
|
12413
11987
|
this.state = "error";
|
|
12414
|
-
|
|
11988
|
+
log34.error(this.tag, `Error on #${card.short_id}: ${msg}`);
|
|
12415
11989
|
const apiError = errClass.kind !== null;
|
|
12416
11990
|
const baseError = err instanceof WorktreeBaseError;
|
|
12417
11991
|
const noBudgetBurn = apiError || baseError;
|
|
@@ -12429,7 +12003,7 @@ class Worker {
|
|
|
12429
12003
|
try {
|
|
12430
12004
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12431
12005
|
} catch {
|
|
12432
|
-
|
|
12006
|
+
log34.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12433
12007
|
}
|
|
12434
12008
|
this.worktreePath = null;
|
|
12435
12009
|
}
|
|
@@ -12454,7 +12028,7 @@ class Worker {
|
|
|
12454
12028
|
}
|
|
12455
12029
|
});
|
|
12456
12030
|
} catch (tErr) {
|
|
12457
|
-
|
|
12031
|
+
log34.error(this.tag, `error transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12458
12032
|
}
|
|
12459
12033
|
if (this.runId) {
|
|
12460
12034
|
try {
|
|
@@ -12490,7 +12064,7 @@ class Worker {
|
|
|
12490
12064
|
try {
|
|
12491
12065
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12492
12066
|
} catch {
|
|
12493
|
-
|
|
12067
|
+
log34.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12494
12068
|
}
|
|
12495
12069
|
this.worktreePath = null;
|
|
12496
12070
|
}
|
|
@@ -12513,7 +12087,7 @@ class Worker {
|
|
|
12513
12087
|
}
|
|
12514
12088
|
});
|
|
12515
12089
|
} catch (tErr) {
|
|
12516
|
-
|
|
12090
|
+
log34.error(this.tag, `timeout transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12517
12091
|
}
|
|
12518
12092
|
try {
|
|
12519
12093
|
await this.stateStore.endRun(this.runId, "failed", {
|
|
@@ -12527,15 +12101,15 @@ class Worker {
|
|
|
12527
12101
|
try {
|
|
12528
12102
|
await this.client.updateCard(card.id, { assignedAgentId: null });
|
|
12529
12103
|
} catch (err) {
|
|
12530
|
-
|
|
12104
|
+
log34.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
|
|
12531
12105
|
}
|
|
12532
12106
|
try {
|
|
12533
12107
|
await runTransition(this.client, card, { removeLabels: ["agent"] });
|
|
12534
12108
|
} catch (tErr) {
|
|
12535
|
-
|
|
12109
|
+
log34.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12536
12110
|
}
|
|
12537
12111
|
} else {
|
|
12538
|
-
|
|
12112
|
+
log34.info(this.tag, `cancel arrived after completion on #${card.short_id} — keeping assignment so review picks it up (#585)`);
|
|
12539
12113
|
}
|
|
12540
12114
|
try {
|
|
12541
12115
|
await this.stateStore.endRun(this.runId, "paused", {
|
|
@@ -12599,28 +12173,28 @@ class Worker {
|
|
|
12599
12173
|
};
|
|
12600
12174
|
const { pick, reason } = selectAutoPlaybook(subject, playbooks ?? []);
|
|
12601
12175
|
if (!pick) {
|
|
12602
|
-
|
|
12176
|
+
log34.info(this.tag, `No playbook auto-bound to #${card.short_id}: ${reason}`);
|
|
12603
12177
|
return card;
|
|
12604
12178
|
}
|
|
12605
12179
|
const undeclared = undeclaredGateMetricsIn(pick, declaredMetricSet(this.config));
|
|
12606
12180
|
if (undeclared.length > 0) {
|
|
12607
|
-
|
|
12181
|
+
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
12182
|
return card;
|
|
12609
12183
|
}
|
|
12610
12184
|
const applyResult = await this.client.request("POST", `/cards/${card.id}/apply-playbook`, {
|
|
12611
12185
|
playbookId: pick.id
|
|
12612
12186
|
});
|
|
12613
|
-
|
|
12187
|
+
log34.info(this.tag, `Auto-bound #${card.short_id} to playbook "${pick.name}": ${reason}`);
|
|
12614
12188
|
try {
|
|
12615
12189
|
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
12190
|
} catch (commentErr) {
|
|
12617
|
-
|
|
12191
|
+
log34.warn(this.tag, `Auto-bind comment failed for #${card.short_id}: ${commentErr instanceof Error ? commentErr.message : String(commentErr)}`);
|
|
12618
12192
|
}
|
|
12619
12193
|
try {
|
|
12620
12194
|
const { card: fresh } = await this.client.getCard(card.id);
|
|
12621
12195
|
return fresh;
|
|
12622
12196
|
} catch (fetchErr) {
|
|
12623
|
-
|
|
12197
|
+
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
12198
|
return {
|
|
12625
12199
|
...card,
|
|
12626
12200
|
playbook_id: applyResult.card.playbook_id,
|
|
@@ -12629,7 +12203,7 @@ class Worker {
|
|
|
12629
12203
|
};
|
|
12630
12204
|
}
|
|
12631
12205
|
} catch (err) {
|
|
12632
|
-
|
|
12206
|
+
log34.warn(this.tag, `Auto-bind playbook check failed for #${card.short_id}, continuing unbound: ${err instanceof Error ? err.message : String(err)}`);
|
|
12633
12207
|
return card;
|
|
12634
12208
|
}
|
|
12635
12209
|
}
|
|
@@ -12741,7 +12315,7 @@ class Worker {
|
|
|
12741
12315
|
});
|
|
12742
12316
|
} catch (err) {
|
|
12743
12317
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12744
|
-
|
|
12318
|
+
log34.warn(this.tag, `fan-out tick failed on #${card.short_id}: ${detail}`);
|
|
12745
12319
|
await this.holdStageCard(card, `Fan-out stage "${ctx.stage.name}" could not run: ${detail}`);
|
|
12746
12320
|
return;
|
|
12747
12321
|
}
|
|
@@ -12750,7 +12324,7 @@ class Worker {
|
|
|
12750
12324
|
case "waiting": {
|
|
12751
12325
|
const total = outcome.kind === "dispatched" ? outcome.total : outcome.total;
|
|
12752
12326
|
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
|
-
|
|
12327
|
+
log34.info(this.tag, `#${card.short_id} ${note}`);
|
|
12754
12328
|
await this.client.updateAgentProgress(card.id, {
|
|
12755
12329
|
agentIdentifier: "claude-code-stage",
|
|
12756
12330
|
agentName: "Harmony Agent",
|
|
@@ -12778,7 +12352,7 @@ class Worker {
|
|
|
12778
12352
|
if (advance.kind === "advanced" || advance.kind === "completed_terminal") {
|
|
12779
12353
|
this.held = false;
|
|
12780
12354
|
}
|
|
12781
|
-
|
|
12355
|
+
log34.info(this.tag, `#${card.short_id} ${summary} → ${advance.kind}`);
|
|
12782
12356
|
return;
|
|
12783
12357
|
}
|
|
12784
12358
|
case "halted": {
|
|
@@ -12798,7 +12372,7 @@ class Worker {
|
|
|
12798
12372
|
}
|
|
12799
12373
|
}
|
|
12800
12374
|
async holdStageCard(card, reason, wait = false) {
|
|
12801
|
-
|
|
12375
|
+
log34.info(this.tag, `Holding #${card.short_id}: ${reason}`);
|
|
12802
12376
|
await this.stateStore.decrementAttempt(card.id);
|
|
12803
12377
|
try {
|
|
12804
12378
|
await this.client.addComment(card.id, reason, {
|
|
@@ -12817,7 +12391,7 @@ class Worker {
|
|
|
12817
12391
|
}
|
|
12818
12392
|
});
|
|
12819
12393
|
} catch (tErr) {
|
|
12820
|
-
|
|
12394
|
+
log34.warn(this.tag, `hold transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12821
12395
|
}
|
|
12822
12396
|
if (this.runId) {
|
|
12823
12397
|
try {
|
|
@@ -12835,7 +12409,7 @@ class Worker {
|
|
|
12835
12409
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
12836
12410
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12837
12411
|
const until = computeDecisionDeadline(waitHours);
|
|
12838
|
-
|
|
12412
|
+
log34.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
12839
12413
|
try {
|
|
12840
12414
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
12841
12415
|
holderMessage,
|
|
@@ -12847,7 +12421,7 @@ class Worker {
|
|
|
12847
12421
|
agentSessionId: this.sessionId ?? undefined
|
|
12848
12422
|
});
|
|
12849
12423
|
} catch (commentErr) {
|
|
12850
|
-
|
|
12424
|
+
log34.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
12851
12425
|
}
|
|
12852
12426
|
if (this.runId) {
|
|
12853
12427
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -12858,7 +12432,7 @@ class Worker {
|
|
|
12858
12432
|
awaitingDecisionUntil: until
|
|
12859
12433
|
});
|
|
12860
12434
|
} catch (storeErr) {
|
|
12861
|
-
|
|
12435
|
+
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
12436
|
}
|
|
12863
12437
|
}
|
|
12864
12438
|
}
|
|
@@ -12870,7 +12444,7 @@ class Worker {
|
|
|
12870
12444
|
this.progressTracker = null;
|
|
12871
12445
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12872
12446
|
const until = computeDecisionDeadline(waitHours);
|
|
12873
|
-
|
|
12447
|
+
log34.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
12874
12448
|
const measured = this.worktreePath && this.runExpectsCommits ? measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha) : "unknown";
|
|
12875
12449
|
const hasCommits = measured === "unknown" ? undefined : measured === "committed";
|
|
12876
12450
|
if (hasCommits === false && !this.noCommitNoticeSettled) {
|
|
@@ -12900,7 +12474,7 @@ class Worker {
|
|
|
12900
12474
|
});
|
|
12901
12475
|
commentId = res?.comment?.id ?? null;
|
|
12902
12476
|
} catch (err) {
|
|
12903
|
-
|
|
12477
|
+
log34.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
12904
12478
|
}
|
|
12905
12479
|
const cardCostCents = this.stateStore.getCardCostCents(card.id) + Math.max(0, this.runCostCents - this.chargedCents);
|
|
12906
12480
|
const readout = [
|
|
@@ -12921,7 +12495,7 @@ class Worker {
|
|
|
12921
12495
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
12922
12496
|
});
|
|
12923
12497
|
} catch (err) {
|
|
12924
|
-
|
|
12498
|
+
log34.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
12925
12499
|
}
|
|
12926
12500
|
if (this.runId) {
|
|
12927
12501
|
try {
|
|
@@ -12932,7 +12506,7 @@ class Worker {
|
|
|
12932
12506
|
...this.runLedger()
|
|
12933
12507
|
});
|
|
12934
12508
|
} catch (err) {
|
|
12935
|
-
|
|
12509
|
+
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
12510
|
}
|
|
12937
12511
|
}
|
|
12938
12512
|
}
|
|
@@ -12963,11 +12537,11 @@ class Worker {
|
|
|
12963
12537
|
});
|
|
12964
12538
|
const gate = normalizeGateSpec(ctx.stage.gate);
|
|
12965
12539
|
const metricsPath = gate?.kind === "custom" ? writeMetricsFile(this.config.playbooks.metrics ?? {}) : null;
|
|
12966
|
-
|
|
12540
|
+
log34.info(this.tag, `Running stage "${ctx.stage.name}" (role ${ctx.role}) for #${card.short_id} under the harness motor`);
|
|
12967
12541
|
const motorAbort = new AbortController;
|
|
12968
12542
|
this.motorAbort = motorAbort;
|
|
12969
12543
|
this.timeoutTimer = setTimeout(() => {
|
|
12970
|
-
|
|
12544
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms) during the motor stage run, cancelling`);
|
|
12971
12545
|
this.timedOut = true;
|
|
12972
12546
|
this.cancel("timeout");
|
|
12973
12547
|
}, this.config.maxTimeout);
|
|
@@ -12978,12 +12552,12 @@ class Worker {
|
|
|
12978
12552
|
let motorRunSettled = false;
|
|
12979
12553
|
const onMotorLine = (line) => {
|
|
12980
12554
|
if (line.type !== "agent_event") {
|
|
12981
|
-
|
|
12555
|
+
log34.info(this.tag, `motor: ${line.type}`);
|
|
12982
12556
|
return;
|
|
12983
12557
|
}
|
|
12984
12558
|
if (motorRunSettled)
|
|
12985
12559
|
return;
|
|
12986
|
-
|
|
12560
|
+
log34.debug(this.tag, `motor: agent_event ${line.event.kind}`);
|
|
12987
12561
|
if (line.event.kind === "tool_started" && STAGE_DAEMON_OWNED_TOOLS.includes(line.event.payload.toolName)) {
|
|
12988
12562
|
return;
|
|
12989
12563
|
}
|
|
@@ -13003,7 +12577,7 @@ class Worker {
|
|
|
13003
12577
|
currentTask: motorTask,
|
|
13004
12578
|
progressPercent: MOTOR_RUN_PROGRESS_PERCENT
|
|
13005
12579
|
}).catch((err) => {
|
|
13006
|
-
|
|
12580
|
+
log34.warn(this.tag, `motor session heartbeat failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13007
12581
|
});
|
|
13008
12582
|
}, MOTOR_SESSION_HEARTBEAT_MS);
|
|
13009
12583
|
heartbeat.unref?.();
|
|
@@ -13077,7 +12651,7 @@ class Worker {
|
|
|
13077
12651
|
break;
|
|
13078
12652
|
case "reclaim_refused":
|
|
13079
12653
|
this.held = true;
|
|
13080
|
-
|
|
12654
|
+
log34.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
13081
12655
|
break;
|
|
13082
12656
|
case "advanced":
|
|
13083
12657
|
case "completed_terminal":
|
|
@@ -13099,7 +12673,7 @@ class Worker {
|
|
|
13099
12673
|
try {
|
|
13100
12674
|
pushBranch3(this.branchName, worktreePath);
|
|
13101
12675
|
} catch (err) {
|
|
13102
|
-
|
|
12676
|
+
log34.error(this.tag, `push after the motor stage "${ctx.stage.name}" failed for ${this.branchName}: ${err instanceof Error ? err.message : err}`);
|
|
13103
12677
|
}
|
|
13104
12678
|
}
|
|
13105
12679
|
}
|
|
@@ -13107,7 +12681,7 @@ class Worker {
|
|
|
13107
12681
|
if (completionColumn) {
|
|
13108
12682
|
await transferCardToCompletion({ client: this.client, tag: this.tag }, card, completionColumn, this.onCardCompleted);
|
|
13109
12683
|
} else {
|
|
13110
|
-
|
|
12684
|
+
log34.warn(this.tag, `completion.moveToColumn is empty — #${card.short_id} stays in its current column after the motor stage run`);
|
|
13111
12685
|
}
|
|
13112
12686
|
await this.closeoutMotorWorktree(card);
|
|
13113
12687
|
}
|
|
@@ -13118,7 +12692,7 @@ class Worker {
|
|
|
13118
12692
|
try {
|
|
13119
12693
|
await teardownWorktree2(this.client, card.id, worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
13120
12694
|
} catch {
|
|
13121
|
-
|
|
12695
|
+
log34.warn(this.tag, "Failed to cleanup worktree after the motor stage run");
|
|
13122
12696
|
}
|
|
13123
12697
|
this.worktreePath = null;
|
|
13124
12698
|
}
|
|
@@ -13175,7 +12749,7 @@ ${prompt}`;
|
|
|
13175
12749
|
});
|
|
13176
12750
|
return handoff ? renderInheritedHandoffSection(handoff) : "";
|
|
13177
12751
|
} catch (err) {
|
|
13178
|
-
|
|
12752
|
+
log34.warn(this.tag, `inherited-handoff fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13179
12753
|
return "";
|
|
13180
12754
|
}
|
|
13181
12755
|
}
|
|
@@ -13195,9 +12769,9 @@ ${prompt}`;
|
|
|
13195
12769
|
commentType: "decision",
|
|
13196
12770
|
agentSessionId: this.sessionId ?? undefined
|
|
13197
12771
|
});
|
|
13198
|
-
|
|
12772
|
+
log34.info(this.tag, `Wrote stage handoff for #${card.short_id} (stage "${stage.name}")`);
|
|
13199
12773
|
} catch (err) {
|
|
13200
|
-
|
|
12774
|
+
log34.warn(this.tag, `stage-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13201
12775
|
}
|
|
13202
12776
|
}
|
|
13203
12777
|
async collectStageGateEvidence(card, stage, worktreePath, subtasks) {
|
|
@@ -13209,12 +12783,12 @@ ${prompt}`;
|
|
|
13209
12783
|
return null;
|
|
13210
12784
|
}
|
|
13211
12785
|
if (gate.pendingEngine === true) {
|
|
13212
|
-
|
|
12786
|
+
log34.info(this.tag, `Stage "${stage.name}" gate "${gate.kind}" is advisory — skipping enforcement`);
|
|
13213
12787
|
return null;
|
|
13214
12788
|
}
|
|
13215
12789
|
const review = gate.kind === "review_passed" ? parseReviewOutput(this.lastRunText) : undefined;
|
|
13216
12790
|
if (review) {
|
|
13217
|
-
|
|
12791
|
+
log34.info(this.tag, `Review-gated stage "${stage.name}" verdict: ${review.verdict} (${review.findings.length} finding(s))`);
|
|
13218
12792
|
}
|
|
13219
12793
|
const registry = buildGateCollectorRegistry2({
|
|
13220
12794
|
build: {
|
|
@@ -13231,7 +12805,7 @@ ${prompt}`;
|
|
|
13231
12805
|
return [];
|
|
13232
12806
|
const criteria = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
13233
12807
|
if (criteria.length > 0) {
|
|
13234
|
-
|
|
12808
|
+
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
12809
|
}
|
|
13236
12810
|
return criteria;
|
|
13237
12811
|
})()
|
|
@@ -13253,10 +12827,10 @@ ${prompt}`;
|
|
|
13253
12827
|
const evaluation = gateEvaluate(gate, evidence);
|
|
13254
12828
|
const insert = toStageGateEvidenceInsert(context, evidence);
|
|
13255
12829
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, insert);
|
|
13256
|
-
|
|
12830
|
+
log34.info(this.tag, `Recorded ${gate.kind} gate evidence for #${card.short_id} stage "${stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
13257
12831
|
return evaluation;
|
|
13258
12832
|
} catch (err) {
|
|
13259
|
-
|
|
12833
|
+
log34.warn(this.tag, `stage-gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13260
12834
|
return null;
|
|
13261
12835
|
}
|
|
13262
12836
|
}
|
|
@@ -13274,7 +12848,7 @@ ${prompt}`;
|
|
|
13274
12848
|
agentSessionId: this.sessionId
|
|
13275
12849
|
});
|
|
13276
12850
|
} catch (err) {
|
|
13277
|
-
|
|
12851
|
+
log34.warn(this.tag, `stage advancement failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13278
12852
|
return { kind: "no_advance" };
|
|
13279
12853
|
}
|
|
13280
12854
|
}
|
|
@@ -13300,7 +12874,7 @@ ${prompt}`;
|
|
|
13300
12874
|
this.modelChoice = choice;
|
|
13301
12875
|
const { model, escalated, source } = choice;
|
|
13302
12876
|
if (source !== "policy" || escalated) {
|
|
13303
|
-
|
|
12877
|
+
log34.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${this.sizing?.tier ?? "none"})`);
|
|
13304
12878
|
}
|
|
13305
12879
|
return model;
|
|
13306
12880
|
}
|
|
@@ -13315,7 +12889,7 @@ ${prompt}`;
|
|
|
13315
12889
|
encoding: "utf-8"
|
|
13316
12890
|
}).trim();
|
|
13317
12891
|
} catch (err) {
|
|
13318
|
-
|
|
12892
|
+
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
12893
|
return { status: "failed", reason: "spawn" };
|
|
13320
12894
|
}
|
|
13321
12895
|
const outcome = await sizeRun({
|
|
@@ -13329,21 +12903,22 @@ ${prompt}`;
|
|
|
13329
12903
|
scoreBands: claude.scoreBands
|
|
13330
12904
|
});
|
|
13331
12905
|
if (outcome.status === "sized") {
|
|
13332
|
-
|
|
12906
|
+
log34.info(this.tag, `Sized #${card.short_id}: complexity ${outcome.sizing.complexity}/10 -> ${outcome.sizing.tier}`);
|
|
13333
12907
|
} else if (outcome.status === "failed") {
|
|
13334
|
-
|
|
12908
|
+
log34.warn(this.tag, `Sizing #${card.short_id} failed (${outcome.reason}) — using the policy fallback`);
|
|
13335
12909
|
}
|
|
13336
12910
|
return outcome;
|
|
13337
12911
|
}
|
|
13338
12912
|
recordPromptAssembled(outcomes) {
|
|
13339
|
-
const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count
|
|
12913
|
+
const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count;
|
|
13340
12914
|
const failures = outcomes.flatMap((o) => o.failure ? [{ flavour: o.flavour, message: o.failure.slice(0, 300) }] : []);
|
|
12915
|
+
const episodeCount = countOf("past_episodes");
|
|
13341
12916
|
this.cliRunner?.record({
|
|
13342
12917
|
kind: "prompt_assembled",
|
|
13343
12918
|
source: "system",
|
|
13344
12919
|
payload: {
|
|
13345
|
-
episodeCount:
|
|
13346
|
-
referenceCount: countOf("reference"),
|
|
12920
|
+
...episodeCount === undefined ? {} : { episodeCount },
|
|
12921
|
+
referenceCount: countOf("reference") ?? 0,
|
|
13347
12922
|
...failures.length > 0 ? { recallFailures: failures } : {}
|
|
13348
12923
|
}
|
|
13349
12924
|
});
|
|
@@ -13364,7 +12939,7 @@ ${prompt}`;
|
|
|
13364
12939
|
const measured = measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha);
|
|
13365
12940
|
if (measured !== "none") {
|
|
13366
12941
|
if (measured === "unknown") {
|
|
13367
|
-
|
|
12942
|
+
log34.debug(this.tag, `no-commit check could not attribute this run's commits — staying quiet on ${this.branchName ?? "this branch"}`);
|
|
13368
12943
|
}
|
|
13369
12944
|
return;
|
|
13370
12945
|
}
|
|
@@ -13373,7 +12948,7 @@ ${prompt}`;
|
|
|
13373
12948
|
recordNoCommitsYet(trigger, numTurns) {
|
|
13374
12949
|
const turnBudget = this.effectiveMaxTurns;
|
|
13375
12950
|
const threshold = noCommitNoticeThreshold(turnBudget);
|
|
13376
|
-
|
|
12951
|
+
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
12952
|
this.cliRunner?.record({
|
|
13378
12953
|
kind: "no_commits_yet",
|
|
13379
12954
|
source: "system",
|
|
@@ -13425,9 +13000,9 @@ ${prompt}`;
|
|
|
13425
13000
|
agentSessionId: this.sessionId ?? undefined
|
|
13426
13001
|
});
|
|
13427
13002
|
giveUpCommentId = res?.comment?.id ?? null;
|
|
13428
|
-
|
|
13003
|
+
log34.warn(this.tag, `gave up on ${cardId} after ${attempts} attempts`);
|
|
13429
13004
|
} catch (err) {
|
|
13430
|
-
|
|
13005
|
+
log34.warn(this.tag, `failed to post give-up comment for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13431
13006
|
}
|
|
13432
13007
|
if (this.config.budget.pause.enabled) {
|
|
13433
13008
|
const waitHours = this.config.budget.pause.waitHours;
|
|
@@ -13441,7 +13016,7 @@ ${prompt}`;
|
|
|
13441
13016
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
13442
13017
|
});
|
|
13443
13018
|
} catch (err) {
|
|
13444
|
-
|
|
13019
|
+
log34.warn(this.tag, `Failed to mark the attempt cap pending for ${cardId}: ${err}`);
|
|
13445
13020
|
}
|
|
13446
13021
|
try {
|
|
13447
13022
|
await this.stateStore.markAwaitingDecision(cardId, {
|
|
@@ -13450,19 +13025,19 @@ ${prompt}`;
|
|
|
13450
13025
|
agentIdentifier: this.sessionIdentifier
|
|
13451
13026
|
});
|
|
13452
13027
|
} catch (err) {
|
|
13453
|
-
|
|
13028
|
+
log34.warn(this.tag, `Failed to record the attempt-cap decision locally for ${cardId}: ${err}`);
|
|
13454
13029
|
}
|
|
13455
13030
|
}
|
|
13456
13031
|
}
|
|
13457
13032
|
}
|
|
13458
13033
|
} catch (err) {
|
|
13459
|
-
|
|
13034
|
+
log34.warn(this.tag, `recordOutcome(${outcome}) failed: ${err instanceof Error ? err.message : err}`);
|
|
13460
13035
|
}
|
|
13461
13036
|
}
|
|
13462
13037
|
async pause() {
|
|
13463
13038
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13464
13039
|
return;
|
|
13465
|
-
|
|
13040
|
+
log34.info(this.tag, `Pausing work on ${this.cardId}`);
|
|
13466
13041
|
signalGroup2(this.process, "SIGSTOP");
|
|
13467
13042
|
if (this.timeoutTimer) {
|
|
13468
13043
|
clearTimeout(this.timeoutTimer);
|
|
@@ -13476,17 +13051,17 @@ ${prompt}`;
|
|
|
13476
13051
|
status: "paused"
|
|
13477
13052
|
});
|
|
13478
13053
|
} catch {
|
|
13479
|
-
|
|
13054
|
+
log34.warn(this.tag, "Failed to update agent session to paused");
|
|
13480
13055
|
}
|
|
13481
13056
|
}
|
|
13482
13057
|
}
|
|
13483
13058
|
async resume() {
|
|
13484
13059
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13485
13060
|
return;
|
|
13486
|
-
|
|
13061
|
+
log34.info(this.tag, `Resuming work on ${this.cardId}`);
|
|
13487
13062
|
signalGroup2(this.process, "SIGCONT");
|
|
13488
13063
|
this.timeoutTimer = setTimeout(() => {
|
|
13489
|
-
|
|
13064
|
+
log34.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
13490
13065
|
this.timedOut = true;
|
|
13491
13066
|
this.cancel("timeout");
|
|
13492
13067
|
}, this.config.maxTimeout);
|
|
@@ -13498,7 +13073,7 @@ ${prompt}`;
|
|
|
13498
13073
|
status: "working"
|
|
13499
13074
|
});
|
|
13500
13075
|
} catch {
|
|
13501
|
-
|
|
13076
|
+
log34.warn(this.tag, "Failed to update agent session to working");
|
|
13502
13077
|
}
|
|
13503
13078
|
}
|
|
13504
13079
|
}
|
|
@@ -13507,7 +13082,7 @@ ${prompt}`;
|
|
|
13507
13082
|
return;
|
|
13508
13083
|
this.aborted = true;
|
|
13509
13084
|
this.state = "cancelling";
|
|
13510
|
-
|
|
13085
|
+
log34.info(this.tag, `Cancelling work on ${this.cardId}`);
|
|
13511
13086
|
this.motorAbort?.abort();
|
|
13512
13087
|
if (this.sdkRunner) {
|
|
13513
13088
|
await this.sdkRunner.stop(this.timedOut ? "timeout" : "user_requested");
|
|
@@ -13525,14 +13100,14 @@ ${prompt}`;
|
|
|
13525
13100
|
...buildTokenPayload(stats)
|
|
13526
13101
|
});
|
|
13527
13102
|
} catch (err) {
|
|
13528
|
-
|
|
13103
|
+
log34.warn(this.tag, `endAgentSession after cancel failed: ${err instanceof Error ? err.message : err}`);
|
|
13529
13104
|
}
|
|
13530
13105
|
}
|
|
13531
13106
|
}
|
|
13532
13107
|
async runPlanningPhase(enriched) {
|
|
13533
13108
|
const planning = this.config.planning;
|
|
13534
13109
|
const { card } = enriched;
|
|
13535
|
-
|
|
13110
|
+
log34.info(this.tag, `Planning pass for #${card.short_id} (mode=${planning.mode}, model=${planning.model})`);
|
|
13536
13111
|
await this.client.updateAgentProgress(card.id, {
|
|
13537
13112
|
agentIdentifier: this.sessionIdentifier,
|
|
13538
13113
|
agentName: AGENT_NAME,
|
|
@@ -13545,7 +13120,7 @@ ${prompt}`;
|
|
|
13545
13120
|
let planTimedOut = false;
|
|
13546
13121
|
const planTimeout = setTimeout(() => {
|
|
13547
13122
|
planTimedOut = true;
|
|
13548
|
-
|
|
13123
|
+
log34.warn(this.tag, "Planning pass exceeded timeout — abandoning, implementing directly");
|
|
13549
13124
|
if (this.sdkRunner) {
|
|
13550
13125
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13551
13126
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13563,7 +13138,7 @@ ${prompt}`;
|
|
|
13563
13138
|
initialPhase: "planning"
|
|
13564
13139
|
});
|
|
13565
13140
|
} catch (err) {
|
|
13566
|
-
|
|
13141
|
+
log34.warn(this.tag, `Planning pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13567
13142
|
return false;
|
|
13568
13143
|
} finally {
|
|
13569
13144
|
clearTimeout(planTimeout);
|
|
@@ -13574,7 +13149,7 @@ ${prompt}`;
|
|
|
13574
13149
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13575
13150
|
const planText = stats?.lastAssistantText ?? "";
|
|
13576
13151
|
if (!planText.trim()) {
|
|
13577
|
-
|
|
13152
|
+
log34.warn(this.tag, `Planning pass for #${card.short_id} produced no text — implementing directly`);
|
|
13578
13153
|
return false;
|
|
13579
13154
|
}
|
|
13580
13155
|
const artifact = extractPlanArtifact(planText, card.title);
|
|
@@ -13595,9 +13170,9 @@ ${prompt}`;
|
|
|
13595
13170
|
});
|
|
13596
13171
|
planId = createdId;
|
|
13597
13172
|
}
|
|
13598
|
-
|
|
13173
|
+
log34.info(this.tag, `Stored plan ${planId ?? "(unlinked)"} for #${card.short_id} (${artifact.tasks.length} tasks)`);
|
|
13599
13174
|
} catch (err) {
|
|
13600
|
-
|
|
13175
|
+
log34.warn(this.tag, `Failed to store/link plan (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13601
13176
|
}
|
|
13602
13177
|
if (planning.mode === "gated" && planId) {
|
|
13603
13178
|
try {
|
|
@@ -13616,11 +13191,11 @@ ${prompt}`;
|
|
|
13616
13191
|
...buildTokenPayload(stats)
|
|
13617
13192
|
}
|
|
13618
13193
|
}, { store: this.stateStore, runId: this.runId ?? undefined });
|
|
13619
|
-
|
|
13194
|
+
log34.info(this.tag, `#${card.short_id} parked in "${planning.awaitingApprovalColumn}" for plan approval`);
|
|
13620
13195
|
this.lastSessionStats = undefined;
|
|
13621
13196
|
return true;
|
|
13622
13197
|
} catch (err) {
|
|
13623
|
-
|
|
13198
|
+
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
13199
|
}
|
|
13625
13200
|
}
|
|
13626
13201
|
if (planId && planning.postComment) {
|
|
@@ -13630,7 +13205,7 @@ ${prompt}`;
|
|
|
13630
13205
|
agentSessionId: this.sessionId ?? undefined
|
|
13631
13206
|
});
|
|
13632
13207
|
} catch (err) {
|
|
13633
|
-
|
|
13208
|
+
log34.warn(this.tag, `Failed to post advisory plan comment (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13634
13209
|
}
|
|
13635
13210
|
}
|
|
13636
13211
|
return false;
|
|
@@ -13640,10 +13215,10 @@ ${prompt}`;
|
|
|
13640
13215
|
const { card } = enriched;
|
|
13641
13216
|
const existing = await this.loadPinnedContract(card.id);
|
|
13642
13217
|
if (existing) {
|
|
13643
|
-
|
|
13218
|
+
log34.info(this.tag, `Contract already pinned for #${card.short_id} (${existing.assertions.length} assertions) — reusing`);
|
|
13644
13219
|
return;
|
|
13645
13220
|
}
|
|
13646
|
-
|
|
13221
|
+
log34.info(this.tag, `Contract pass for #${card.short_id} (model=${contractCfg.model})`);
|
|
13647
13222
|
await this.client.updateAgentProgress(card.id, {
|
|
13648
13223
|
agentIdentifier: this.sessionIdentifier,
|
|
13649
13224
|
agentName: AGENT_NAME,
|
|
@@ -13656,7 +13231,7 @@ ${prompt}`;
|
|
|
13656
13231
|
let contractTimedOut = false;
|
|
13657
13232
|
const contractTimeout = setTimeout(() => {
|
|
13658
13233
|
contractTimedOut = true;
|
|
13659
|
-
|
|
13234
|
+
log34.warn(this.tag, "Contract pass exceeded timeout — abandoning, implementing directly");
|
|
13660
13235
|
if (this.sdkRunner) {
|
|
13661
13236
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13662
13237
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13674,7 +13249,7 @@ ${prompt}`;
|
|
|
13674
13249
|
initialPhase: "planning"
|
|
13675
13250
|
});
|
|
13676
13251
|
} catch (err) {
|
|
13677
|
-
|
|
13252
|
+
log34.warn(this.tag, `Contract pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13678
13253
|
return;
|
|
13679
13254
|
} finally {
|
|
13680
13255
|
clearTimeout(contractTimeout);
|
|
@@ -13685,12 +13260,12 @@ ${prompt}`;
|
|
|
13685
13260
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13686
13261
|
const contractText = stats?.lastAssistantText ?? "";
|
|
13687
13262
|
if (!contractText.trim()) {
|
|
13688
|
-
|
|
13263
|
+
log34.warn(this.tag, `Contract pass for #${card.short_id} produced no text — implementing directly`);
|
|
13689
13264
|
return;
|
|
13690
13265
|
}
|
|
13691
13266
|
const contract = extractContract(contractText, card);
|
|
13692
13267
|
if (contract.assertions.length < contractCfg.minAssertions) {
|
|
13693
|
-
|
|
13268
|
+
log34.warn(this.tag, `Contract for #${card.short_id} had ${contract.assertions.length} assertion(s) (< ${contractCfg.minAssertions}) — not pinning, implementing directly`);
|
|
13694
13269
|
return;
|
|
13695
13270
|
}
|
|
13696
13271
|
try {
|
|
@@ -13698,9 +13273,9 @@ ${prompt}`;
|
|
|
13698
13273
|
commentType: "decision",
|
|
13699
13274
|
agentSessionId: this.sessionId ?? undefined
|
|
13700
13275
|
});
|
|
13701
|
-
|
|
13276
|
+
log34.info(this.tag, `Pinned acceptance contract for #${card.short_id} (${contract.assertions.length} assertions)`);
|
|
13702
13277
|
} catch (err) {
|
|
13703
|
-
|
|
13278
|
+
log34.warn(this.tag, `Failed to pin contract (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13704
13279
|
}
|
|
13705
13280
|
}
|
|
13706
13281
|
async loadPinnedContract(cardId) {
|
|
@@ -13710,7 +13285,7 @@ ${prompt}`;
|
|
|
13710
13285
|
return null;
|
|
13711
13286
|
return extractPinnedContract(comments, this.identity);
|
|
13712
13287
|
} catch (err) {
|
|
13713
|
-
|
|
13288
|
+
log34.warn(this.tag, `pinned-contract fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13714
13289
|
return null;
|
|
13715
13290
|
}
|
|
13716
13291
|
}
|
|
@@ -13723,13 +13298,13 @@ ${prompt}`;
|
|
|
13723
13298
|
const res = await this.client.getPendingUserMessages(this.cardId, this.sessionId, this.lastDrainedSeq);
|
|
13724
13299
|
messages = res.messages ?? [];
|
|
13725
13300
|
} catch (err) {
|
|
13726
|
-
|
|
13301
|
+
log34.warn(this.tag, `Failed to fetch steering messages (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13727
13302
|
return;
|
|
13728
13303
|
}
|
|
13729
13304
|
if (messages.length === 0)
|
|
13730
13305
|
return;
|
|
13731
13306
|
this.lastDrainedSeq = Math.max(this.lastDrainedSeq, ...messages.map((m) => m.seq));
|
|
13732
|
-
|
|
13307
|
+
log34.info(this.tag, `Steering #${card.short_id}: resuming with ${messages.length} queued message(s)`);
|
|
13733
13308
|
this.state = "running";
|
|
13734
13309
|
await this.recordPhase("running");
|
|
13735
13310
|
try {
|
|
@@ -13741,7 +13316,7 @@ ${prompt}`;
|
|
|
13741
13316
|
...this.activeRunSpawnOpts ?? {}
|
|
13742
13317
|
});
|
|
13743
13318
|
} catch (err) {
|
|
13744
|
-
|
|
13319
|
+
log34.warn(this.tag, `Steering resume failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13745
13320
|
return;
|
|
13746
13321
|
}
|
|
13747
13322
|
}
|
|
@@ -13776,10 +13351,10 @@ ${prompt}`;
|
|
|
13776
13351
|
"--",
|
|
13777
13352
|
prompt
|
|
13778
13353
|
];
|
|
13779
|
-
|
|
13354
|
+
log34.info(this.tag, `Spawning: claude ${args.slice(0, 4).join(" ")} ...`);
|
|
13780
13355
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13781
13356
|
if (runLog) {
|
|
13782
|
-
|
|
13357
|
+
log34.info(this.tag, `Run log: ${runLog.path}`);
|
|
13783
13358
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} started=${new Date().toISOString()}
|
|
13784
13359
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
13785
13360
|
|
|
@@ -13816,7 +13391,7 @@ ${prompt}`;
|
|
|
13816
13391
|
});
|
|
13817
13392
|
}
|
|
13818
13393
|
parser.on("parse_error", (msg) => {
|
|
13819
|
-
|
|
13394
|
+
log34.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
13820
13395
|
runLog?.stream.write(`
|
|
13821
13396
|
[parse_error] ${msg}
|
|
13822
13397
|
`);
|
|
@@ -13889,10 +13464,10 @@ ${prompt}`;
|
|
|
13889
13464
|
const disallowedTools = opts.disallowedTools ? opts.disallowedTools.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
13890
13465
|
const initialPhase = opts.initialPhase ?? "exploring";
|
|
13891
13466
|
const maxBudgetUsd = this.effectiveMaxBudgetUsd;
|
|
13892
|
-
|
|
13467
|
+
log34.info(this.tag, `Spawning Agent SDK runner (model=${model}, maxBudgetUsd=${maxBudgetUsd || "off"}, maxTurns=${maxTurns}${opts.resumeSessionId ? ", resume" : ""})`);
|
|
13893
13468
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13894
13469
|
if (runLog) {
|
|
13895
|
-
|
|
13470
|
+
log34.info(this.tag, `Run log: ${runLog.path}`);
|
|
13896
13471
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} runner=sdk started=${new Date().toISOString()}
|
|
13897
13472
|
` + `# model=${model} maxTurns=${maxTurns} <prompt:${prompt.length} chars>
|
|
13898
13473
|
|
|
@@ -14027,7 +13602,7 @@ ${prompt}`;
|
|
|
14027
13602
|
try {
|
|
14028
13603
|
await teardownWorktree2(this.client, this.cardId, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
14029
13604
|
} catch {
|
|
14030
|
-
|
|
13605
|
+
log34.warn(this.tag, "Failed to cleanup worktree");
|
|
14031
13606
|
}
|
|
14032
13607
|
}
|
|
14033
13608
|
this.process = null;
|
|
@@ -14046,7 +13621,7 @@ ${prompt}`;
|
|
|
14046
13621
|
this.chargedTokens = emptyTokens();
|
|
14047
13622
|
}
|
|
14048
13623
|
}
|
|
14049
|
-
var
|
|
13624
|
+
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
13625
|
var init_worker = __esm(() => {
|
|
14051
13626
|
init_dist();
|
|
14052
13627
|
init_board_helpers();
|
|
@@ -14077,7 +13652,7 @@ var init_worker = __esm(() => {
|
|
|
14077
13652
|
});
|
|
14078
13653
|
|
|
14079
13654
|
// src/workspace-model-cache.ts
|
|
14080
|
-
import { log as
|
|
13655
|
+
import { log as log35 } from "@gethmy/harness";
|
|
14081
13656
|
|
|
14082
13657
|
class WorkspaceModelCache {
|
|
14083
13658
|
fetcher;
|
|
@@ -14112,7 +13687,7 @@ class WorkspaceModelCache {
|
|
|
14112
13687
|
this.stale = false;
|
|
14113
13688
|
} catch (err) {
|
|
14114
13689
|
this.stale = true;
|
|
14115
|
-
|
|
13690
|
+
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
13691
|
}
|
|
14117
13692
|
return this.value;
|
|
14118
13693
|
}
|
|
@@ -14123,7 +13698,7 @@ var init_workspace_model_cache = () => {};
|
|
|
14123
13698
|
import {
|
|
14124
13699
|
cooldownMsFor,
|
|
14125
13700
|
describeApiError as describeApiError3,
|
|
14126
|
-
log as
|
|
13701
|
+
log as log36
|
|
14127
13702
|
} from "@gethmy/harness";
|
|
14128
13703
|
async function routeBudgetDecision(d, run, actions, cardId) {
|
|
14129
13704
|
if (!run) {
|
|
@@ -14219,11 +13794,11 @@ class Pool {
|
|
|
14219
13794
|
}
|
|
14220
13795
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
14221
13796
|
if (card.done || card.archived_at) {
|
|
14222
|
-
|
|
13797
|
+
log36.debug(TAG32, `#${card.short_id} skipped — ${card.done ? "done" : "archived"} (finished work is never enqueued, #1102)`);
|
|
14223
13798
|
return;
|
|
14224
13799
|
}
|
|
14225
13800
|
if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
|
|
14226
|
-
|
|
13801
|
+
log36.debug(TAG32, `Card ${card.id} already queued, active, or reserved, skipping`);
|
|
14227
13802
|
return;
|
|
14228
13803
|
}
|
|
14229
13804
|
this.reservations.add(card.id);
|
|
@@ -14231,19 +13806,19 @@ class Pool {
|
|
|
14231
13806
|
try {
|
|
14232
13807
|
const account = this.accountGuard?.check();
|
|
14233
13808
|
if (account && !account.running) {
|
|
14234
|
-
|
|
13809
|
+
log36.debug(TAG32, `#${card.short_id} held — ${account.detail}`);
|
|
14235
13810
|
await this.emitWaiting(card.id, account.detail);
|
|
14236
13811
|
return;
|
|
14237
13812
|
}
|
|
14238
13813
|
if (mode === "implement") {
|
|
14239
13814
|
if (this.authPaused) {
|
|
14240
|
-
|
|
13815
|
+
log36.debug(TAG32, `#${card.short_id} held — agent paused (auth error)`);
|
|
14241
13816
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
14242
13817
|
return;
|
|
14243
13818
|
}
|
|
14244
13819
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
14245
13820
|
if (cooldownMs > 0) {
|
|
14246
|
-
|
|
13821
|
+
log36.debug(TAG32, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
14247
13822
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
14248
13823
|
return;
|
|
14249
13824
|
}
|
|
@@ -14252,19 +13827,19 @@ class Pool {
|
|
|
14252
13827
|
if (decision.reason === "daily_budget") {
|
|
14253
13828
|
await this.denyDailyBudget(card, decision.detail);
|
|
14254
13829
|
} else {
|
|
14255
|
-
|
|
13830
|
+
log36.debug(TAG32, `#${card.short_id} skipped (${decision.reason}): ${decision.detail}`);
|
|
14256
13831
|
}
|
|
14257
13832
|
return;
|
|
14258
13833
|
}
|
|
14259
13834
|
const { blockers, successors } = await getChainSignals(this.client, card, this.projectId);
|
|
14260
13835
|
chainSuccessors = successors;
|
|
14261
13836
|
if (blockers === null) {
|
|
14262
|
-
|
|
13837
|
+
log36.warn(TAG32, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
14263
13838
|
return;
|
|
14264
13839
|
}
|
|
14265
13840
|
if (blockers.length > 0) {
|
|
14266
13841
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
14267
|
-
|
|
13842
|
+
log36.info(TAG32, `#${card.short_id} blocked by ${list} — waiting`);
|
|
14268
13843
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
14269
13844
|
return;
|
|
14270
13845
|
}
|
|
@@ -14296,9 +13871,9 @@ class Pool {
|
|
|
14296
13871
|
const reason = `Daily budget reached — waiting for reset (${detail})`;
|
|
14297
13872
|
const line = `#${card.short_id} skipped (daily_budget): ${detail}`;
|
|
14298
13873
|
if (this.lastWaitingEmit.get(card.id) === reason)
|
|
14299
|
-
|
|
13874
|
+
log36.debug(TAG32, line);
|
|
14300
13875
|
else
|
|
14301
|
-
|
|
13876
|
+
log36.warn(TAG32, line);
|
|
14302
13877
|
await this.emitWaiting(card.id, reason);
|
|
14303
13878
|
}
|
|
14304
13879
|
lastWaitingEmit = new Map;
|
|
@@ -14315,7 +13890,7 @@ class Pool {
|
|
|
14315
13890
|
});
|
|
14316
13891
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
14317
13892
|
} catch (err) {
|
|
14318
|
-
|
|
13893
|
+
log36.debug(TAG32, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14319
13894
|
}
|
|
14320
13895
|
}
|
|
14321
13896
|
noteApiError(err) {
|
|
@@ -14323,13 +13898,13 @@ class Pool {
|
|
|
14323
13898
|
return;
|
|
14324
13899
|
if (err.kind === "spend_limit") {
|
|
14325
13900
|
this.accountGuard?.halt("spend_limit").catch((hErr) => {
|
|
14326
|
-
|
|
13901
|
+
log36.error(TAG32, `Failed to latch the account halt: ${hErr}`);
|
|
14327
13902
|
});
|
|
14328
13903
|
return;
|
|
14329
13904
|
}
|
|
14330
13905
|
if (err.kind === "auth") {
|
|
14331
13906
|
if (!this.authPaused) {
|
|
14332
|
-
|
|
13907
|
+
log36.error(TAG32, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
14333
13908
|
}
|
|
14334
13909
|
this.authPaused = true;
|
|
14335
13910
|
return;
|
|
@@ -14338,7 +13913,7 @@ class Pool {
|
|
|
14338
13913
|
const until = Date.now() + cooldownMs;
|
|
14339
13914
|
if (until > this.apiCooldownUntil) {
|
|
14340
13915
|
this.apiCooldownUntil = until;
|
|
14341
|
-
|
|
13916
|
+
log36.warn(TAG32, `${describeApiError3(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
14342
13917
|
}
|
|
14343
13918
|
}
|
|
14344
13919
|
apiCooldownRemainingMs() {
|
|
@@ -14352,13 +13927,13 @@ class Pool {
|
|
|
14352
13927
|
const removed = queue.remove(cardId);
|
|
14353
13928
|
if (removed) {
|
|
14354
13929
|
this.cardDataCache.delete(cardId);
|
|
14355
|
-
|
|
13930
|
+
log36.info(TAG32, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
14356
13931
|
return;
|
|
14357
13932
|
}
|
|
14358
13933
|
}
|
|
14359
13934
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
14360
13935
|
if (worker) {
|
|
14361
|
-
|
|
13936
|
+
log36.info(TAG32, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
14362
13937
|
await worker.cancel("unassigned");
|
|
14363
13938
|
}
|
|
14364
13939
|
}
|
|
@@ -14415,10 +13990,10 @@ class Pool {
|
|
|
14415
13990
|
}
|
|
14416
13991
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
14417
13992
|
if (!worker) {
|
|
14418
|
-
|
|
13993
|
+
log36.debug(TAG32, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
14419
13994
|
return;
|
|
14420
13995
|
}
|
|
14421
|
-
|
|
13996
|
+
log36.info(TAG32, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
14422
13997
|
switch (command) {
|
|
14423
13998
|
case "pause":
|
|
14424
13999
|
await worker.pause();
|
|
@@ -14466,7 +14041,7 @@ class Pool {
|
|
|
14466
14041
|
};
|
|
14467
14042
|
}
|
|
14468
14043
|
async shutdown() {
|
|
14469
|
-
|
|
14044
|
+
log36.info(TAG32, "Shutting down pool...");
|
|
14470
14045
|
this.shuttingDown = true;
|
|
14471
14046
|
const active = [
|
|
14472
14047
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -14474,7 +14049,7 @@ class Pool {
|
|
|
14474
14049
|
];
|
|
14475
14050
|
await Promise.all(active.map((w) => w.cancel("shutdown")));
|
|
14476
14051
|
this.sleepGuard.stop();
|
|
14477
|
-
|
|
14052
|
+
log36.info(TAG32, "Pool shutdown complete");
|
|
14478
14053
|
}
|
|
14479
14054
|
async drainBudgetDecisions(cardId) {
|
|
14480
14055
|
const targets = cardId ? [cardId] : [
|
|
@@ -14505,7 +14080,7 @@ class Pool {
|
|
|
14505
14080
|
try {
|
|
14506
14081
|
({ decisions } = await this.client.getBudgetDecisions(cardId, new Date(sinceMs).toISOString()));
|
|
14507
14082
|
} catch (err) {
|
|
14508
|
-
|
|
14083
|
+
log36.warn(TAG32, `getBudgetDecisions failed for ${cardId}: ${err}`);
|
|
14509
14084
|
return;
|
|
14510
14085
|
}
|
|
14511
14086
|
if (decisions.length > 0) {
|
|
@@ -14540,7 +14115,7 @@ class Pool {
|
|
|
14540
14115
|
...run.blockerCommentId ? { replyToId: run.blockerCommentId } : {}
|
|
14541
14116
|
});
|
|
14542
14117
|
} catch (err) {
|
|
14543
|
-
|
|
14118
|
+
log36.warn(TAG32, `Failed to post the expired-park closing comment for ${run.cardId}: ${err}`);
|
|
14544
14119
|
}
|
|
14545
14120
|
try {
|
|
14546
14121
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14554,14 +14129,14 @@ class Pool {
|
|
|
14554
14129
|
});
|
|
14555
14130
|
}
|
|
14556
14131
|
} catch (err) {
|
|
14557
|
-
|
|
14132
|
+
log36.error(TAG32, `Failed to move #${run.cardShortId} after an expired park: ${err}`);
|
|
14558
14133
|
}
|
|
14559
14134
|
try {
|
|
14560
14135
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14561
14136
|
errorMessage: "budget decision expired"
|
|
14562
14137
|
});
|
|
14563
14138
|
} catch (err) {
|
|
14564
|
-
|
|
14139
|
+
log36.warn(TAG32, `Failed to release the expired park for ${run.cardId}: ${err}`);
|
|
14565
14140
|
}
|
|
14566
14141
|
}
|
|
14567
14142
|
async releaseExpiredAttemptCap(cardId, blockerCommentId) {
|
|
@@ -14571,7 +14146,7 @@ class Pool {
|
|
|
14571
14146
|
...blockerCommentId ? { replyToId: blockerCommentId } : {}
|
|
14572
14147
|
});
|
|
14573
14148
|
} catch (err) {
|
|
14574
|
-
|
|
14149
|
+
log36.warn(TAG32, `Failed to post the expired attempt-cap note for ${cardId}: ${err}`);
|
|
14575
14150
|
}
|
|
14576
14151
|
try {
|
|
14577
14152
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14580,7 +14155,7 @@ class Pool {
|
|
|
14580
14155
|
failureSummary: "The attempt-budget decision expired with no answer. Reassign the card to grant a fresh attempt."
|
|
14581
14156
|
});
|
|
14582
14157
|
} catch (err) {
|
|
14583
|
-
|
|
14158
|
+
log36.warn(TAG32, `Failed to end the expired attempt-cap session for ${cardId}: ${err}`);
|
|
14584
14159
|
}
|
|
14585
14160
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14586
14161
|
}
|
|
@@ -14591,16 +14166,16 @@ class Pool {
|
|
|
14591
14166
|
}
|
|
14592
14167
|
const account = this.accountGuard?.check();
|
|
14593
14168
|
if (account && !account.running) {
|
|
14594
|
-
|
|
14169
|
+
log36.debug(TAG32, `#${run.cardShortId}: granted continue held — ${account.detail}`);
|
|
14595
14170
|
return;
|
|
14596
14171
|
}
|
|
14597
14172
|
const first = !this.adoptAnnounced.has(run.runId);
|
|
14598
14173
|
this.adoptAnnounced.add(run.runId);
|
|
14599
14174
|
const line = `#${run.cardShortId}: granted continue never reached a worker — re-enqueueing (${run.pipeline})`;
|
|
14600
14175
|
if (first)
|
|
14601
|
-
|
|
14176
|
+
log36.warn(TAG32, line);
|
|
14602
14177
|
else
|
|
14603
|
-
|
|
14178
|
+
log36.debug(TAG32, line);
|
|
14604
14179
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14605
14180
|
}
|
|
14606
14181
|
adoptAnnounced = new Set;
|
|
@@ -14619,7 +14194,7 @@ class Pool {
|
|
|
14619
14194
|
async continueRun(run, opts) {
|
|
14620
14195
|
const turns = this.budget.checkCardTurns(run.cardId);
|
|
14621
14196
|
if (!turns.allow) {
|
|
14622
|
-
|
|
14197
|
+
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
14198
|
try {
|
|
14624
14199
|
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
14200
|
|
|
@@ -14628,14 +14203,14 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14628
14203
|
agentSessionId: run.sessionId ?? undefined
|
|
14629
14204
|
});
|
|
14630
14205
|
} catch (err) {
|
|
14631
|
-
|
|
14206
|
+
log36.warn(TAG32, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14632
14207
|
}
|
|
14633
14208
|
await this.stopRun(run, "Continue refused: the card is at its per-card turn cap.");
|
|
14634
14209
|
return;
|
|
14635
14210
|
}
|
|
14636
14211
|
const runsSpent = this.budget.checkCardRuns(run.cardId);
|
|
14637
14212
|
if (!runsSpent.allow) {
|
|
14638
|
-
|
|
14213
|
+
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
14214
|
try {
|
|
14640
14215
|
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
14216
|
|
|
@@ -14644,7 +14219,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14644
14219
|
agentSessionId: run.sessionId ?? undefined
|
|
14645
14220
|
});
|
|
14646
14221
|
} catch (err) {
|
|
14647
|
-
|
|
14222
|
+
log36.warn(TAG32, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14648
14223
|
}
|
|
14649
14224
|
await this.stopRun(run, "Continue refused: the card is at its per-card run backstop.");
|
|
14650
14225
|
return;
|
|
@@ -14668,7 +14243,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14668
14243
|
awaitingDecisionUntil: null
|
|
14669
14244
|
});
|
|
14670
14245
|
} catch (err) {
|
|
14671
|
-
|
|
14246
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14672
14247
|
}
|
|
14673
14248
|
if (run.blockerCommentId) {
|
|
14674
14249
|
try {
|
|
@@ -14676,7 +14251,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14676
14251
|
resolve: true
|
|
14677
14252
|
});
|
|
14678
14253
|
} catch (err) {
|
|
14679
|
-
|
|
14254
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14680
14255
|
}
|
|
14681
14256
|
}
|
|
14682
14257
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
@@ -14700,7 +14275,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14700
14275
|
awaitingDecisionUntil: null
|
|
14701
14276
|
});
|
|
14702
14277
|
} catch (err) {
|
|
14703
|
-
|
|
14278
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14704
14279
|
}
|
|
14705
14280
|
if (run.blockerCommentId) {
|
|
14706
14281
|
try {
|
|
@@ -14708,13 +14283,13 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14708
14283
|
resolve: true
|
|
14709
14284
|
});
|
|
14710
14285
|
} catch (err) {
|
|
14711
|
-
|
|
14286
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14712
14287
|
}
|
|
14713
14288
|
}
|
|
14714
14289
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14715
14290
|
released += 1;
|
|
14716
14291
|
} catch (err) {
|
|
14717
|
-
|
|
14292
|
+
log36.error(TAG32, `Failed to release the account-limit park on ${run.cardId}: ${err}`);
|
|
14718
14293
|
}
|
|
14719
14294
|
}
|
|
14720
14295
|
return released;
|
|
@@ -14726,7 +14301,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14726
14301
|
resolve: true
|
|
14727
14302
|
});
|
|
14728
14303
|
} catch (err) {
|
|
14729
|
-
|
|
14304
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14730
14305
|
}
|
|
14731
14306
|
}
|
|
14732
14307
|
try {
|
|
@@ -14735,7 +14310,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14735
14310
|
awaitingDecisionUntil: null
|
|
14736
14311
|
});
|
|
14737
14312
|
} catch (err) {
|
|
14738
|
-
|
|
14313
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14739
14314
|
}
|
|
14740
14315
|
try {
|
|
14741
14316
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14757,26 +14332,26 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14757
14332
|
await this.client.endAgentSession(run.cardId, endSession);
|
|
14758
14333
|
}
|
|
14759
14334
|
} catch (err) {
|
|
14760
|
-
|
|
14335
|
+
log36.error(TAG32, `Failed to hand #${run.cardShortId} back after a stop: ${err}`);
|
|
14761
14336
|
}
|
|
14762
14337
|
try {
|
|
14763
14338
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14764
14339
|
errorMessage: "budget_decision_stop"
|
|
14765
14340
|
});
|
|
14766
14341
|
} catch (err) {
|
|
14767
|
-
|
|
14342
|
+
log36.warn(TAG32, `Failed to end the local run record for ${run.cardId}: ${err}`);
|
|
14768
14343
|
}
|
|
14769
14344
|
}
|
|
14770
14345
|
async grantAttempt(cardId) {
|
|
14771
14346
|
const turns = this.budget.checkCardTurns(cardId);
|
|
14772
14347
|
if (!turns.allow) {
|
|
14773
|
-
|
|
14348
|
+
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
14349
|
try {
|
|
14775
14350
|
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
14351
|
|
|
14777
14352
|
Reassign the card — that clears the turn count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14778
14353
|
} catch (err) {
|
|
14779
|
-
|
|
14354
|
+
log36.warn(TAG32, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14780
14355
|
}
|
|
14781
14356
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14782
14357
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card turn cap.");
|
|
@@ -14784,13 +14359,13 @@ Reassign the card — that clears the turn count and starts it fresh — or rais
|
|
|
14784
14359
|
}
|
|
14785
14360
|
const runsSpent = this.budget.checkCardRuns(cardId);
|
|
14786
14361
|
if (!runsSpent.allow) {
|
|
14787
|
-
|
|
14362
|
+
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
14363
|
try {
|
|
14789
14364
|
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
14365
|
|
|
14791
14366
|
Reassign the card — that clears the run count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14792
14367
|
} catch (err) {
|
|
14793
|
-
|
|
14368
|
+
log36.warn(TAG32, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14794
14369
|
}
|
|
14795
14370
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14796
14371
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card run backstop.");
|
|
@@ -14805,7 +14380,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14805
14380
|
awaitingDecisionUntil: null
|
|
14806
14381
|
});
|
|
14807
14382
|
} catch (err) {
|
|
14808
|
-
|
|
14383
|
+
log36.warn(TAG32, `Failed to clear the attempt-cap decision deadline for ${cardId}: ${err}`);
|
|
14809
14384
|
}
|
|
14810
14385
|
await this.enqueueCard(cardId, "implement");
|
|
14811
14386
|
}
|
|
@@ -14816,7 +14391,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14816
14391
|
try {
|
|
14817
14392
|
await this.client.updateComment(blockerCommentId, { resolve: true });
|
|
14818
14393
|
} catch (err) {
|
|
14819
|
-
|
|
14394
|
+
log36.warn(TAG32, `Failed to resolve the blocker comment: ${err}`);
|
|
14820
14395
|
}
|
|
14821
14396
|
}
|
|
14822
14397
|
try {
|
|
@@ -14825,7 +14400,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14825
14400
|
awaitingDecisionUntil: null
|
|
14826
14401
|
});
|
|
14827
14402
|
} catch (err) {
|
|
14828
|
-
|
|
14403
|
+
log36.warn(TAG32, `Failed to clear the decision deadline for ${cardId}: ${err}`);
|
|
14829
14404
|
}
|
|
14830
14405
|
try {
|
|
14831
14406
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14834,7 +14409,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14834
14409
|
failureSummary
|
|
14835
14410
|
});
|
|
14836
14411
|
} catch (err) {
|
|
14837
|
-
|
|
14412
|
+
log36.warn(TAG32, `Failed to end the attempt-cap session for ${cardId}: ${err}`);
|
|
14838
14413
|
}
|
|
14839
14414
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14840
14415
|
}
|
|
@@ -14865,12 +14440,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14865
14440
|
const subtasks = card.subtasks ?? [];
|
|
14866
14441
|
await this.enqueue(card, column, cardLabels, subtasks, mode);
|
|
14867
14442
|
} catch (err) {
|
|
14868
|
-
|
|
14443
|
+
log36.error(TAG32, `Failed to re-enqueue ${cardId} after a budget decision: ${err}`);
|
|
14869
14444
|
}
|
|
14870
14445
|
}
|
|
14871
14446
|
async retireUnroutableRuns(shortId, cardId, reason = "column not found on this board") {
|
|
14872
14447
|
const unended = this.stateStore.getRunsForCard(cardId).filter((r) => r.endedAt === null);
|
|
14873
|
-
|
|
14448
|
+
log36.warn(TAG32, `#${shortId}: ${reason} — retiring ${unended.length} run record(s) instead of re-enqueueing`);
|
|
14874
14449
|
for (const run of unended) {
|
|
14875
14450
|
await this.stateStore.endRun(run.runId, "orphaned", {
|
|
14876
14451
|
errorMessage: `${reason} at re-enqueue — retired as unroutable (#1057/#1102)`
|
|
@@ -14883,12 +14458,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14883
14458
|
if (this.shuttingDown)
|
|
14884
14459
|
return false;
|
|
14885
14460
|
if (this.accountGuard && !this.accountGuard.check().running) {
|
|
14886
|
-
|
|
14461
|
+
log36.debug(TAG32, `${label} dispatch held — the account is halted`);
|
|
14887
14462
|
return false;
|
|
14888
14463
|
}
|
|
14889
14464
|
const idle = workers.find((w) => w.isIdle);
|
|
14890
14465
|
if (!idle) {
|
|
14891
|
-
|
|
14466
|
+
log36.debug(TAG32, `No idle ${label} workers (queue: ${queue.length})`);
|
|
14892
14467
|
return false;
|
|
14893
14468
|
}
|
|
14894
14469
|
const next = queue.dequeue();
|
|
@@ -14896,18 +14471,18 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14896
14471
|
return false;
|
|
14897
14472
|
const data = this.cardDataCache.get(next.cardId);
|
|
14898
14473
|
if (!data) {
|
|
14899
|
-
|
|
14474
|
+
log36.warn(TAG32, `No cached data for card ${next.cardId}, skipping`);
|
|
14900
14475
|
return false;
|
|
14901
14476
|
}
|
|
14902
14477
|
this.cardDataCache.delete(next.cardId);
|
|
14903
14478
|
this.lastWaitingEmit.delete(next.cardId);
|
|
14904
|
-
|
|
14479
|
+
log36.info(TAG32, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
14905
14480
|
this.sleepGuard.acquire();
|
|
14906
14481
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
14907
14482
|
return true;
|
|
14908
14483
|
}
|
|
14909
14484
|
}
|
|
14910
|
-
var
|
|
14485
|
+
var TAG32 = "pool";
|
|
14911
14486
|
var init_pool = __esm(() => {
|
|
14912
14487
|
init_dist();
|
|
14913
14488
|
init_board_helpers();
|
|
@@ -14941,7 +14516,7 @@ import {
|
|
|
14941
14516
|
writeFileSync as writeFileSync4
|
|
14942
14517
|
} from "node:fs";
|
|
14943
14518
|
import { dirname as dirname5 } from "node:path";
|
|
14944
|
-
import { log as
|
|
14519
|
+
import { log as log37 } from "@gethmy/harness";
|
|
14945
14520
|
function defaultRegistryPath() {
|
|
14946
14521
|
return portRegistryPath();
|
|
14947
14522
|
}
|
|
@@ -14956,7 +14531,7 @@ function load(path) {
|
|
|
14956
14531
|
}
|
|
14957
14532
|
return emptyRegistry();
|
|
14958
14533
|
} catch (err) {
|
|
14959
|
-
|
|
14534
|
+
log37.warn(TAG33, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
14960
14535
|
return emptyRegistry();
|
|
14961
14536
|
}
|
|
14962
14537
|
}
|
|
@@ -14977,7 +14552,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
14977
14552
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
14978
14553
|
save(path, registry);
|
|
14979
14554
|
} catch (err) {
|
|
14980
|
-
|
|
14555
|
+
log37.warn(TAG33, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
14981
14556
|
}
|
|
14982
14557
|
}
|
|
14983
14558
|
function daemonBaseUrl(bindAddr, port) {
|
|
@@ -14997,17 +14572,17 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
14997
14572
|
delete registry[projectId];
|
|
14998
14573
|
save(path, registry);
|
|
14999
14574
|
} catch (err) {
|
|
15000
|
-
|
|
14575
|
+
log37.warn(TAG33, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
15001
14576
|
}
|
|
15002
14577
|
}
|
|
15003
|
-
var
|
|
14578
|
+
var TAG33 = "port-registry";
|
|
15004
14579
|
var init_port_registry = __esm(() => {
|
|
15005
14580
|
init_dist();
|
|
15006
14581
|
init_paths();
|
|
15007
14582
|
});
|
|
15008
14583
|
|
|
15009
14584
|
// src/recovery.ts
|
|
15010
|
-
import { log as
|
|
14585
|
+
import { log as log38, teardownWorktree as teardownWorktree3 } from "@gethmy/harness";
|
|
15011
14586
|
function isProcessAlive(pid, currentPid) {
|
|
15012
14587
|
if (pid === currentPid)
|
|
15013
14588
|
return true;
|
|
@@ -15023,7 +14598,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
15023
14598
|
const { card } = await client.getCard(cardId);
|
|
15024
14599
|
return card;
|
|
15025
14600
|
} catch (err) {
|
|
15026
|
-
|
|
14601
|
+
log38.warn(TAG34, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15027
14602
|
return null;
|
|
15028
14603
|
}
|
|
15029
14604
|
}
|
|
@@ -15033,7 +14608,7 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
15033
14608
|
return [];
|
|
15034
14609
|
}
|
|
15035
14610
|
const outcomes = [];
|
|
15036
|
-
|
|
14611
|
+
log38.info(TAG34, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
15037
14612
|
for (const run of active) {
|
|
15038
14613
|
const outcome = {
|
|
15039
14614
|
runId: run.runId,
|
|
@@ -15045,16 +14620,16 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
15045
14620
|
};
|
|
15046
14621
|
outcomes.push(outcome);
|
|
15047
14622
|
if (isBudgetHeldRun(run)) {
|
|
15048
|
-
|
|
14623
|
+
log38.info(TAG34, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15049
14624
|
outcome.actions.push("skipped: held for a human budget decision");
|
|
15050
14625
|
continue;
|
|
15051
14626
|
}
|
|
15052
14627
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
15053
|
-
|
|
14628
|
+
log38.warn(TAG34, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
15054
14629
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
15055
14630
|
continue;
|
|
15056
14631
|
}
|
|
15057
|
-
|
|
14632
|
+
log38.info(TAG34, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
15058
14633
|
await recoverRun(run, store, client, config, outcome, {
|
|
15059
14634
|
rollbackAttempt: true,
|
|
15060
14635
|
agentId: opts.agentId
|
|
@@ -15075,7 +14650,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15075
14650
|
} catch (err) {
|
|
15076
14651
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15077
14652
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
15078
|
-
|
|
14653
|
+
log38.warn(TAG34, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
15079
14654
|
}
|
|
15080
14655
|
const card = await fetchCardSafely(client, run.cardId);
|
|
15081
14656
|
if (card) {
|
|
@@ -15135,9 +14710,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15135
14710
|
outcome.errors.push(`decrementAttempt: ${msg}`);
|
|
15136
14711
|
}
|
|
15137
14712
|
}
|
|
15138
|
-
|
|
14713
|
+
log38.info(TAG34, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
15139
14714
|
}
|
|
15140
|
-
var
|
|
14715
|
+
var TAG34 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
15141
14716
|
var init_recovery = __esm(() => {
|
|
15142
14717
|
init_board_helpers();
|
|
15143
14718
|
init_handback();
|
|
@@ -15159,18 +14734,18 @@ var init_repo_dirs = __esm(() => {
|
|
|
15159
14734
|
});
|
|
15160
14735
|
|
|
15161
14736
|
// src/claim.ts
|
|
15162
|
-
import { log as
|
|
14737
|
+
import { log as log39 } from "@gethmy/harness";
|
|
15163
14738
|
async function claimUnassignedCard(client, cardId, agentId, opts) {
|
|
15164
14739
|
try {
|
|
15165
14740
|
const { claimed } = opts ? await client.claimCard(cardId, agentId, opts) : await client.claimCard(cardId, agentId);
|
|
15166
|
-
|
|
14741
|
+
log39.debug(TAG35, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
15167
14742
|
return claimed;
|
|
15168
14743
|
} catch (err) {
|
|
15169
|
-
|
|
14744
|
+
log39.error(TAG35, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
15170
14745
|
return false;
|
|
15171
14746
|
}
|
|
15172
14747
|
}
|
|
15173
|
-
var
|
|
14748
|
+
var TAG35 = "claim";
|
|
15174
14749
|
var init_claim = () => {};
|
|
15175
14750
|
|
|
15176
14751
|
// src/strand-recovery.ts
|
|
@@ -15178,7 +14753,7 @@ var exports_strand_recovery = {};
|
|
|
15178
14753
|
__export(exports_strand_recovery, {
|
|
15179
14754
|
reclaimPreReviewStrands: () => reclaimPreReviewStrands
|
|
15180
14755
|
});
|
|
15181
|
-
import { log as
|
|
14756
|
+
import { log as log40, resolvePrUrl as resolvePrUrl2 } from "@gethmy/harness";
|
|
15182
14757
|
async function reclaimPreReviewStrands(opts) {
|
|
15183
14758
|
const {
|
|
15184
14759
|
client,
|
|
@@ -15222,22 +14797,22 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15222
14797
|
continue;
|
|
15223
14798
|
const won = await claimUnassignedCard(client, card.id, agentId);
|
|
15224
14799
|
if (!won) {
|
|
15225
|
-
|
|
14800
|
+
log40.debug(TAG36, `#${card.short_id} — lost the review claim race, skipping`);
|
|
15226
14801
|
continue;
|
|
15227
14802
|
}
|
|
15228
|
-
|
|
14803
|
+
log40.warn(TAG36, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
15229
14804
|
reclaimed.push(card.id);
|
|
15230
14805
|
if (opts.onClaimed) {
|
|
15231
14806
|
try {
|
|
15232
14807
|
await opts.onClaimed(card);
|
|
15233
14808
|
} catch (err) {
|
|
15234
|
-
|
|
14809
|
+
log40.error(TAG36, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
15235
14810
|
}
|
|
15236
14811
|
}
|
|
15237
14812
|
}
|
|
15238
14813
|
return reclaimed;
|
|
15239
14814
|
}
|
|
15240
|
-
var
|
|
14815
|
+
var TAG36 = "strand-recovery";
|
|
15241
14816
|
var init_strand_recovery = __esm(() => {
|
|
15242
14817
|
init_board_helpers();
|
|
15243
14818
|
init_claim();
|
|
@@ -15403,7 +14978,7 @@ var init_scope = __esm(() => {
|
|
|
15403
14978
|
});
|
|
15404
14979
|
|
|
15405
14980
|
// src/sweep.ts
|
|
15406
|
-
import { log as
|
|
14981
|
+
import { log as log41 } from "@gethmy/harness";
|
|
15407
14982
|
function rankSweepCandidates(opts) {
|
|
15408
14983
|
const {
|
|
15409
14984
|
agentId,
|
|
@@ -15522,10 +15097,10 @@ async function sweepForCard(opts) {
|
|
|
15522
15097
|
let { candidates, skipped, scope } = rankSweepCandidates(opts);
|
|
15523
15098
|
const cohesionCounts = opts.ranking.cohesionWeight !== 0;
|
|
15524
15099
|
if (cohesionCounts) {
|
|
15525
|
-
|
|
15100
|
+
log41.debug(TAG37, describeActiveScope(scope.active));
|
|
15526
15101
|
}
|
|
15527
15102
|
if (candidates.length === 0) {
|
|
15528
|
-
|
|
15103
|
+
log41.debug(TAG37, `no claimable card (${skipped.length} skipped in a pickup column)`);
|
|
15529
15104
|
return { claimed: null, skipped, probed: 0 };
|
|
15530
15105
|
}
|
|
15531
15106
|
const probeWindow = candidates.slice(0, maxProbes);
|
|
@@ -15541,12 +15116,12 @@ async function sweepForCard(opts) {
|
|
|
15541
15116
|
chainNeighbors: chainNeighbors(chain2)
|
|
15542
15117
|
}));
|
|
15543
15118
|
} else if (chainTermsCount && candidates.length > 1) {
|
|
15544
|
-
|
|
15119
|
+
log41.debug(TAG37, `chain signals not ranked — maxProbesPerTick=${maxProbes} leaves nothing to reorder among ${candidates.length} candidates`);
|
|
15545
15120
|
}
|
|
15546
15121
|
let probed = 0;
|
|
15547
15122
|
for (const candidate of candidates) {
|
|
15548
15123
|
if (probed >= maxProbes) {
|
|
15549
|
-
|
|
15124
|
+
log41.debug(TAG37, `probe budget ${maxProbes} spent — ${candidates.length - probed} candidate(s) deferred to the next tick`);
|
|
15550
15125
|
break;
|
|
15551
15126
|
}
|
|
15552
15127
|
probed++;
|
|
@@ -15624,10 +15199,10 @@ async function sweepForCard(opts) {
|
|
|
15624
15199
|
continue;
|
|
15625
15200
|
}
|
|
15626
15201
|
verified.card.assigned_agent_id = agentId;
|
|
15627
|
-
|
|
15202
|
+
log41.info(TAG37, claimLine(verified, probed, candidates, skipped, cohesionCounts ? scope.active : null));
|
|
15628
15203
|
return { claimed: verified, skipped, probed };
|
|
15629
15204
|
}
|
|
15630
|
-
|
|
15205
|
+
log41.debug(TAG37, `claimed nothing this sweep (${probed} probed, ${skipped.length} skipped)`);
|
|
15631
15206
|
return { claimed: null, skipped, probed };
|
|
15632
15207
|
}
|
|
15633
15208
|
function claimLine(claimed, rank, candidates, skipped, scope) {
|
|
@@ -15648,7 +15223,7 @@ async function refetchCard(client, cardId) {
|
|
|
15648
15223
|
const { card } = await client.getCard(cardId);
|
|
15649
15224
|
return card ?? null;
|
|
15650
15225
|
} catch (err) {
|
|
15651
|
-
|
|
15226
|
+
log41.warn(TAG37, `re-read failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15652
15227
|
return null;
|
|
15653
15228
|
}
|
|
15654
15229
|
}
|
|
@@ -15657,11 +15232,11 @@ async function hasLiveSession(client, cardId) {
|
|
|
15657
15232
|
const { session } = await client.getAgentSession(cardId);
|
|
15658
15233
|
return session != null;
|
|
15659
15234
|
} catch (err) {
|
|
15660
|
-
|
|
15235
|
+
log41.warn(TAG37, `session lookup failed for ${cardId} — treating as busy: ${err instanceof Error ? err.message : err}`);
|
|
15661
15236
|
return true;
|
|
15662
15237
|
}
|
|
15663
15238
|
}
|
|
15664
|
-
var
|
|
15239
|
+
var TAG37 = "sweep";
|
|
15665
15240
|
var init_sweep = __esm(() => {
|
|
15666
15241
|
init_board_helpers();
|
|
15667
15242
|
init_claim();
|
|
@@ -15672,7 +15247,7 @@ var init_sweep = __esm(() => {
|
|
|
15672
15247
|
});
|
|
15673
15248
|
|
|
15674
15249
|
// src/sweep-commands.ts
|
|
15675
|
-
import { log as
|
|
15250
|
+
import { log as log42 } from "@gethmy/harness";
|
|
15676
15251
|
function parseSweepCommands(payload) {
|
|
15677
15252
|
if (!payload || typeof payload !== "object")
|
|
15678
15253
|
return [];
|
|
@@ -15699,14 +15274,14 @@ function parseSweepCommands(payload) {
|
|
|
15699
15274
|
async function claimSweepCommands(client, agentId, projectId, daemonId) {
|
|
15700
15275
|
const request = client.request?.bind(client);
|
|
15701
15276
|
if (typeof request !== "function") {
|
|
15702
|
-
|
|
15277
|
+
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
15278
|
return [];
|
|
15704
15279
|
}
|
|
15705
15280
|
try {
|
|
15706
15281
|
const res = await request("POST", `/agents/${encodeURIComponent(agentId)}/sweep-commands/claim`, { projectId, daemonId });
|
|
15707
15282
|
return parseSweepCommands(res);
|
|
15708
15283
|
} catch (err) {
|
|
15709
|
-
|
|
15284
|
+
log42.warn(TAG38, `could not claim sweep commands: ${err instanceof Error ? err.message : err}`);
|
|
15710
15285
|
return [];
|
|
15711
15286
|
}
|
|
15712
15287
|
}
|
|
@@ -15719,15 +15294,15 @@ async function applySweepCommands(commands, actions) {
|
|
|
15719
15294
|
await actions.resume();
|
|
15720
15295
|
}
|
|
15721
15296
|
applied = command.verb;
|
|
15722
|
-
|
|
15297
|
+
log42.info(TAG38, `applied "${command.verb}" from the board (${command.id})`);
|
|
15723
15298
|
}
|
|
15724
15299
|
return applied;
|
|
15725
15300
|
}
|
|
15726
|
-
var
|
|
15301
|
+
var TAG38 = "sweep-cmd";
|
|
15727
15302
|
var init_sweep_commands = () => {};
|
|
15728
15303
|
|
|
15729
15304
|
// src/sweep-guard.ts
|
|
15730
|
-
import { log as
|
|
15305
|
+
import { log as log43 } from "@gethmy/harness";
|
|
15731
15306
|
|
|
15732
15307
|
class SweepGuard {
|
|
15733
15308
|
config;
|
|
@@ -15783,7 +15358,7 @@ class SweepGuard {
|
|
|
15783
15358
|
await this.store.haltSweep(reason);
|
|
15784
15359
|
if (!already) {
|
|
15785
15360
|
const claimed = this.store.getSweep().claimed;
|
|
15786
|
-
|
|
15361
|
+
log43.info(TAG39, `claiming stopped (${reason}) after ${claimed} claimed`);
|
|
15787
15362
|
}
|
|
15788
15363
|
return this.snapshot();
|
|
15789
15364
|
}
|
|
@@ -15792,7 +15367,7 @@ class SweepGuard {
|
|
|
15792
15367
|
const was = before.haltReason ? this.describeStop(before.haltReason) : null;
|
|
15793
15368
|
await this.store.resumeSweep();
|
|
15794
15369
|
if (was)
|
|
15795
|
-
|
|
15370
|
+
log43.info(TAG39, `claiming resumed by the operator — was stopped: ${was}`);
|
|
15796
15371
|
return this.snapshot();
|
|
15797
15372
|
}
|
|
15798
15373
|
snapshot() {
|
|
@@ -15854,11 +15429,11 @@ function sweepBannerLine(config) {
|
|
|
15854
15429
|
const authors = extra ? `you + ${extra} trusted author${extra === 1 ? "" : "s"}` : "you only";
|
|
15855
15430
|
return `Sweep ON — claims ${scope} in ${config.pickupColumns.join(", ")}, ` + `authored by ${authors}; caps: ${describeCaps(sweep, config.budget.dailyBudgetCents)}; ` + `${sweep.maxProbesPerTick} probes/tick`;
|
|
15856
15431
|
}
|
|
15857
|
-
var
|
|
15432
|
+
var TAG39 = "sweep";
|
|
15858
15433
|
var init_sweep_guard = () => {};
|
|
15859
15434
|
|
|
15860
15435
|
// src/reconcile.ts
|
|
15861
|
-
import { detectGitProvider as detectGitProvider5, log as
|
|
15436
|
+
import { detectGitProvider as detectGitProvider5, log as log44 } from "@gethmy/harness";
|
|
15862
15437
|
function daemonProcessLabels(approvedLabel) {
|
|
15863
15438
|
const names = ["agent", "agent-recovered", NEED_REVIEW_LABEL, approvedLabel];
|
|
15864
15439
|
return new Set(names.filter(Boolean).map((n) => n.toLowerCase()));
|
|
@@ -15921,7 +15496,7 @@ class Reconciler {
|
|
|
15921
15496
|
clearInterval(this.timer);
|
|
15922
15497
|
this.timer = null;
|
|
15923
15498
|
}
|
|
15924
|
-
|
|
15499
|
+
log44.info(TAG40, "Heartbeat stopped");
|
|
15925
15500
|
}
|
|
15926
15501
|
async recoverStaleRuns() {
|
|
15927
15502
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -15932,7 +15507,7 @@ class Reconciler {
|
|
|
15932
15507
|
const pool = this.pool;
|
|
15933
15508
|
for (const run of active) {
|
|
15934
15509
|
if (isBudgetHeldRun(run)) {
|
|
15935
|
-
|
|
15510
|
+
log44.info(TAG40, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15936
15511
|
continue;
|
|
15937
15512
|
}
|
|
15938
15513
|
const foreignDaemon = run.daemonPid !== process.pid;
|
|
@@ -15942,7 +15517,7 @@ class Reconciler {
|
|
|
15942
15517
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
15943
15518
|
continue;
|
|
15944
15519
|
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
|
-
|
|
15520
|
+
log44.warn(TAG40, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
15946
15521
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
15947
15522
|
runId: run.runId,
|
|
15948
15523
|
cardId: run.cardId,
|
|
@@ -15969,11 +15544,11 @@ class Reconciler {
|
|
|
15969
15544
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
15970
15545
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
15971
15546
|
continue;
|
|
15972
|
-
|
|
15547
|
+
log44.warn(TAG40, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
15973
15548
|
try {
|
|
15974
15549
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
15975
15550
|
} catch (err) {
|
|
15976
|
-
|
|
15551
|
+
log44.error(TAG40, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
15977
15552
|
}
|
|
15978
15553
|
}
|
|
15979
15554
|
}
|
|
@@ -16005,7 +15580,7 @@ class Reconciler {
|
|
|
16005
15580
|
return;
|
|
16006
15581
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
16007
15582
|
const subtasks = card.subtasks ?? [];
|
|
16008
|
-
|
|
15583
|
+
log44.info(TAG40, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
16009
15584
|
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
16010
15585
|
}
|
|
16011
15586
|
});
|
|
@@ -16029,11 +15604,11 @@ class Reconciler {
|
|
|
16029
15604
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
16030
15605
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
16031
15606
|
continue;
|
|
16032
|
-
|
|
15607
|
+
log44.warn(TAG40, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
16033
15608
|
try {
|
|
16034
15609
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
16035
15610
|
} catch (err) {
|
|
16036
|
-
|
|
15611
|
+
log44.error(TAG40, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
16037
15612
|
}
|
|
16038
15613
|
}
|
|
16039
15614
|
}
|
|
@@ -16068,7 +15643,7 @@ class Reconciler {
|
|
|
16068
15643
|
}
|
|
16069
15644
|
this.lastSweepStopReported = null;
|
|
16070
15645
|
if (!this.pool.hasFreeImplementSlot()) {
|
|
16071
|
-
|
|
15646
|
+
log44.debug(TAG40, "sweep skipped — no free implement slot");
|
|
16072
15647
|
return;
|
|
16073
15648
|
}
|
|
16074
15649
|
const { claimed } = await sweepForCard({
|
|
@@ -16087,9 +15662,9 @@ class Reconciler {
|
|
|
16087
15662
|
reportSweepStopped(reason, detail) {
|
|
16088
15663
|
const line = `sweep stopped claiming — ${detail}`;
|
|
16089
15664
|
if (this.lastSweepStopReported === reason)
|
|
16090
|
-
|
|
15665
|
+
log44.debug(TAG40, line);
|
|
16091
15666
|
else
|
|
16092
|
-
|
|
15667
|
+
log44.warn(TAG40, line);
|
|
16093
15668
|
this.lastSweepStopReported = reason;
|
|
16094
15669
|
}
|
|
16095
15670
|
setSweepPresenceSink(sink) {
|
|
@@ -16127,7 +15702,7 @@ class Reconciler {
|
|
|
16127
15702
|
return;
|
|
16128
15703
|
this.lastSweepNudgeAt = now;
|
|
16129
15704
|
this.drainSweepCommands().catch((err) => {
|
|
16130
|
-
|
|
15705
|
+
log44.error(TAG40, `sweep command nudge failed: ${err instanceof Error ? err.message : err}`);
|
|
16131
15706
|
});
|
|
16132
15707
|
}
|
|
16133
15708
|
resolveRepoDirectories() {
|
|
@@ -16136,9 +15711,9 @@ class Reconciler {
|
|
|
16136
15711
|
const dirs = readRepoDirectories(process.cwd());
|
|
16137
15712
|
this.repoDirectories = dirs;
|
|
16138
15713
|
if (dirs.size === 0) {
|
|
16139
|
-
|
|
15714
|
+
log44.warn(TAG40, `Could not read top-level directories of ${process.cwd()} — sweep cohesion runs without its path signal`);
|
|
16140
15715
|
} else {
|
|
16141
|
-
|
|
15716
|
+
log44.debug(TAG40, `Path signal allows ${dirs.size} top-level directories`);
|
|
16142
15717
|
}
|
|
16143
15718
|
return dirs;
|
|
16144
15719
|
}
|
|
@@ -16174,7 +15749,7 @@ class Reconciler {
|
|
|
16174
15749
|
if (accountHalt !== this.lastAccountHoldReported) {
|
|
16175
15750
|
if (accountHalt) {
|
|
16176
15751
|
const unqueued = assignedCards.filter((c) => !knownCardIds.has(c.id)).length;
|
|
16177
|
-
|
|
15752
|
+
log44.warn(TAG40, `Account halted (${accountHalt}) — ${unqueued} assigned card(s) not queued; nothing is dispatched until \`harmony-agent resume\``);
|
|
16178
15753
|
}
|
|
16179
15754
|
this.lastAccountHoldReported = accountHalt;
|
|
16180
15755
|
}
|
|
@@ -16190,24 +15765,24 @@ class Reconciler {
|
|
|
16190
15765
|
const subtasks = card.subtasks ?? [];
|
|
16191
15766
|
const mode = route.mode;
|
|
16192
15767
|
if (route.stage) {
|
|
16193
|
-
|
|
15768
|
+
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
15769
|
}
|
|
16195
15770
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
16196
|
-
|
|
15771
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
16197
15772
|
continue;
|
|
16198
15773
|
}
|
|
16199
15774
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
16200
|
-
|
|
15775
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
16201
15776
|
continue;
|
|
16202
15777
|
}
|
|
16203
15778
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
16204
|
-
|
|
15779
|
+
log44.debug(TAG40, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
|
|
16205
15780
|
continue;
|
|
16206
15781
|
}
|
|
16207
15782
|
if (accountHalt) {
|
|
16208
|
-
|
|
15783
|
+
log44.debug(TAG40, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — holding, account halted (${accountHalt})`);
|
|
16209
15784
|
} else {
|
|
16210
|
-
|
|
15785
|
+
log44.info(TAG40, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
16211
15786
|
}
|
|
16212
15787
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
16213
15788
|
}
|
|
@@ -16218,13 +15793,13 @@ class Reconciler {
|
|
|
16218
15793
|
try {
|
|
16219
15794
|
await this.pool.drainBudgetDecisions();
|
|
16220
15795
|
} catch (err) {
|
|
16221
|
-
|
|
15796
|
+
log44.error(TAG40, `budget decisions were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16222
15797
|
}
|
|
16223
15798
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
16224
15799
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
16225
15800
|
for (const knownId of knownCardIds) {
|
|
16226
15801
|
if (!allAgentCardIds.has(knownId)) {
|
|
16227
|
-
|
|
15802
|
+
log44.info(TAG40, `Missed unassign: ${knownId} — removing`);
|
|
16228
15803
|
await this.pool.removeCard(knownId);
|
|
16229
15804
|
}
|
|
16230
15805
|
}
|
|
@@ -16232,20 +15807,20 @@ class Reconciler {
|
|
|
16232
15807
|
try {
|
|
16233
15808
|
await this.drainSweepCommands();
|
|
16234
15809
|
} catch (err) {
|
|
16235
|
-
|
|
15810
|
+
log44.error(TAG40, `sweep commands were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16236
15811
|
}
|
|
16237
15812
|
try {
|
|
16238
15813
|
await this.sweepForWork(cards, columns, labelMap, pickupConfig);
|
|
16239
15814
|
} catch (err) {
|
|
16240
|
-
|
|
15815
|
+
log44.error(TAG40, `sweep failed this tick: ${err instanceof Error ? err.message : err}`);
|
|
16241
15816
|
}
|
|
16242
|
-
|
|
15817
|
+
log44.debug(TAG40, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
16243
15818
|
} catch (err) {
|
|
16244
|
-
|
|
15819
|
+
log44.error(TAG40, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
16245
15820
|
}
|
|
16246
15821
|
}
|
|
16247
15822
|
}
|
|
16248
|
-
var
|
|
15823
|
+
var TAG40 = "reconcile", SWEEP_NUDGE_COOLDOWN_MS = 15000;
|
|
16249
15824
|
var init_reconcile = __esm(() => {
|
|
16250
15825
|
init_board_helpers();
|
|
16251
15826
|
init_recovery();
|
|
@@ -16265,7 +15840,7 @@ __export(exports_startup_banner, {
|
|
|
16265
15840
|
createStartupBanner: () => createStartupBanner,
|
|
16266
15841
|
contextSourceLine: () => contextSourceLine
|
|
16267
15842
|
});
|
|
16268
|
-
import { isPretty, log as
|
|
15843
|
+
import { isPretty, log as log45 } from "@gethmy/harness";
|
|
16269
15844
|
function createStartupBanner(config, version) {
|
|
16270
15845
|
return isPretty() ? prettyBanner(config, version) : jsonBanner(config, version);
|
|
16271
15846
|
}
|
|
@@ -16290,7 +15865,7 @@ function prettyBanner(config, version) {
|
|
|
16290
15865
|
checks.push({ kind: "ok", message });
|
|
16291
15866
|
},
|
|
16292
15867
|
warn(message) {
|
|
16293
|
-
|
|
15868
|
+
log45.warn(TAG41, message);
|
|
16294
15869
|
checks.push({ kind: "warn", message: message.split(`
|
|
16295
15870
|
`, 1)[0] });
|
|
16296
15871
|
},
|
|
@@ -16315,26 +15890,26 @@ function prettyBanner(config, version) {
|
|
|
16315
15890
|
};
|
|
16316
15891
|
}
|
|
16317
15892
|
function jsonBanner(config, version) {
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
15893
|
+
log45.info(TAG41, `Harmony Agent Daemon v${version} starting...`);
|
|
15894
|
+
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
15895
|
+
log45.info(TAG41, `Config: ${contextSourceLine(config)}`);
|
|
16321
15896
|
if (config.agent.review.enabled) {
|
|
16322
|
-
|
|
15897
|
+
log45.info(TAG41, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
16323
15898
|
}
|
|
16324
15899
|
let failed = false;
|
|
16325
15900
|
return {
|
|
16326
15901
|
setProjectName(_name) {},
|
|
16327
15902
|
setGitProvider(provider) {
|
|
16328
|
-
|
|
15903
|
+
log45.info(TAG41, `Git provider: ${provider}`);
|
|
16329
15904
|
},
|
|
16330
15905
|
setHttpPort(port) {
|
|
16331
|
-
|
|
15906
|
+
log45.info(TAG41, `HTTP server on port ${port}`);
|
|
16332
15907
|
},
|
|
16333
15908
|
check(message) {
|
|
16334
|
-
|
|
15909
|
+
log45.info(TAG41, message);
|
|
16335
15910
|
},
|
|
16336
15911
|
warn(message) {
|
|
16337
|
-
|
|
15912
|
+
log45.warn(TAG41, message);
|
|
16338
15913
|
},
|
|
16339
15914
|
fail() {
|
|
16340
15915
|
failed = true;
|
|
@@ -16342,7 +15917,7 @@ function jsonBanner(config, version) {
|
|
|
16342
15917
|
async ready(message) {
|
|
16343
15918
|
if (failed)
|
|
16344
15919
|
return;
|
|
16345
|
-
|
|
15920
|
+
log45.info(TAG41, message);
|
|
16346
15921
|
}
|
|
16347
15922
|
};
|
|
16348
15923
|
}
|
|
@@ -16435,7 +16010,7 @@ function cyan(s) {
|
|
|
16435
16010
|
function yellow(s) {
|
|
16436
16011
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
16437
16012
|
}
|
|
16438
|
-
var
|
|
16013
|
+
var TAG41 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
16439
16014
|
var init_startup_banner = __esm(() => {
|
|
16440
16015
|
init_port_registry();
|
|
16441
16016
|
ANSI = {
|
|
@@ -16456,7 +16031,7 @@ import {
|
|
|
16456
16031
|
writeFileSync as writeFileSync5
|
|
16457
16032
|
} from "node:fs";
|
|
16458
16033
|
import { dirname as dirname6, resolve as resolve2, sep as sep2 } from "node:path";
|
|
16459
|
-
import { log as
|
|
16034
|
+
import { log as log46 } from "@gethmy/harness";
|
|
16460
16035
|
function migrationMarkerPath(targetPath) {
|
|
16461
16036
|
return `${targetPath}.migrated`;
|
|
16462
16037
|
}
|
|
@@ -16530,7 +16105,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16530
16105
|
throw new Error("not an object");
|
|
16531
16106
|
legacy = parsed;
|
|
16532
16107
|
} catch (err) {
|
|
16533
|
-
|
|
16108
|
+
log46.warn(TAG42, `legacy state file unreadable — skipping the split this start; it retries on the next (${err instanceof Error ? err.message : err})`);
|
|
16534
16109
|
return NO_MIGRATION;
|
|
16535
16110
|
}
|
|
16536
16111
|
const runs = Array.isArray(legacy.runs) ? legacy.runs : [];
|
|
@@ -16552,13 +16127,13 @@ function migrateLegacyStateFile(opts) {
|
|
|
16552
16127
|
const legacyVersion = typeof legacy.version === "number" ? legacy.version : SCHEMA_VERSION;
|
|
16553
16128
|
const existing = existsSync5(targetPath) ? readTargetFile(targetPath) : null;
|
|
16554
16129
|
if (existsSync5(targetPath) && !existing) {
|
|
16555
|
-
|
|
16130
|
+
log46.warn(TAG42, "existing project state file unreadable — the split overwrites it");
|
|
16556
16131
|
}
|
|
16557
16132
|
let target;
|
|
16558
16133
|
if (existing) {
|
|
16559
16134
|
const existingVersion = typeof existing.version === "number" ? existing.version : SCHEMA_VERSION;
|
|
16560
16135
|
if (existingVersion !== legacyVersion) {
|
|
16561
|
-
|
|
16136
|
+
log46.warn(TAG42, `legacy state is schema v${legacyVersion} but the project store is v${existingVersion} — skipping the split`);
|
|
16562
16137
|
return NO_MIGRATION;
|
|
16563
16138
|
}
|
|
16564
16139
|
const existingRuns = Array.isArray(existing.runs) ? existing.runs : [];
|
|
@@ -16604,7 +16179,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16604
16179
|
if (!existsSync5(backupPath))
|
|
16605
16180
|
copyFileSync(legacyPath, backupPath);
|
|
16606
16181
|
} catch (err) {
|
|
16607
|
-
|
|
16182
|
+
log46.warn(TAG42, `could not back up the legacy state file (continuing): ${err instanceof Error ? err.message : err}`);
|
|
16608
16183
|
}
|
|
16609
16184
|
const remainingRuns = runs.filter((r) => !ownRunIds.has(r.runId));
|
|
16610
16185
|
const remainingCards = cards.filter((c) => !ownCardIds.has(c.cardId));
|
|
@@ -16628,10 +16203,10 @@ function migrateLegacyStateFile(opts) {
|
|
|
16628
16203
|
copiedIndeterminateCards: indeterminateCards.length,
|
|
16629
16204
|
foreignUnendedRuns: remainingRuns.filter((r) => r.endedAt === null).length
|
|
16630
16205
|
};
|
|
16631
|
-
|
|
16206
|
+
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
16207
|
return result;
|
|
16633
16208
|
}
|
|
16634
|
-
var
|
|
16209
|
+
var TAG42 = "state-migration", NO_MIGRATION;
|
|
16635
16210
|
var init_state_migration = __esm(() => {
|
|
16636
16211
|
init_dist();
|
|
16637
16212
|
init_state_store();
|
|
@@ -16738,7 +16313,7 @@ var init_stream_parser_selftest = __esm(() => {
|
|
|
16738
16313
|
|
|
16739
16314
|
// src/watcher.ts
|
|
16740
16315
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16741
|
-
import { isPretty as isPretty2, log as
|
|
16316
|
+
import { isPretty as isPretty2, log as log47 } from "@gethmy/harness";
|
|
16742
16317
|
import { createClient } from "@supabase/supabase-js";
|
|
16743
16318
|
function toPresenceColumns(names) {
|
|
16744
16319
|
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 +16374,7 @@ class Watcher {
|
|
|
16799
16374
|
const stillHeld = this.supabase?.getChannels().some((c) => c.topic === old.topic) ?? false;
|
|
16800
16375
|
if (!stillHeld)
|
|
16801
16376
|
return true;
|
|
16802
|
-
|
|
16377
|
+
log47.warn(TAG43, `removeChannel(${old.topic}) returned "${status}" and the client still holds that topic — retrying the removal`);
|
|
16803
16378
|
return false;
|
|
16804
16379
|
}
|
|
16805
16380
|
constructor(credentials, projectId, identity, routing, onCardBroadcast, onAgentCommand, onSweepCommandNudge) {
|
|
@@ -16813,7 +16388,7 @@ class Watcher {
|
|
|
16813
16388
|
}
|
|
16814
16389
|
async start() {
|
|
16815
16390
|
if (!isPretty2()) {
|
|
16816
|
-
|
|
16391
|
+
log47.info(TAG43, "Connecting to Supabase realtime (broadcast)...");
|
|
16817
16392
|
}
|
|
16818
16393
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
16819
16394
|
this.subscribeBroadcast();
|
|
@@ -16851,7 +16426,7 @@ class Watcher {
|
|
|
16851
16426
|
status = `threw (${String(err)})`;
|
|
16852
16427
|
}
|
|
16853
16428
|
if (status !== "ok") {
|
|
16854
|
-
|
|
16429
|
+
log47.debug(TAG43, `Presence sweep update returned "${status}"`);
|
|
16855
16430
|
}
|
|
16856
16431
|
})();
|
|
16857
16432
|
}
|
|
@@ -16862,7 +16437,7 @@ class Watcher {
|
|
|
16862
16437
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
16863
16438
|
this.presenceChannel = presenceChannel;
|
|
16864
16439
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
16865
|
-
|
|
16440
|
+
log47.debug(TAG43, "Presence sync");
|
|
16866
16441
|
}).subscribe(async (status) => {
|
|
16867
16442
|
if (gen !== this.presenceGen)
|
|
16868
16443
|
return;
|
|
@@ -16878,13 +16453,13 @@ class Watcher {
|
|
|
16878
16453
|
if (trackStatus !== "ok") {
|
|
16879
16454
|
this.presenceTracked = false;
|
|
16880
16455
|
if (!this.stopping) {
|
|
16881
|
-
|
|
16456
|
+
log47.warn(TAG43, `Presence track returned "${trackStatus}" — scheduling reconnect`);
|
|
16882
16457
|
this.schedulePresenceReconnect();
|
|
16883
16458
|
}
|
|
16884
16459
|
return;
|
|
16885
16460
|
}
|
|
16886
16461
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16887
|
-
|
|
16462
|
+
log47.info(TAG43, "Presence tracked on board-presence channel");
|
|
16888
16463
|
}
|
|
16889
16464
|
this.presenceTracked = true;
|
|
16890
16465
|
this.presenceReconnectAttempts = 0;
|
|
@@ -16892,7 +16467,7 @@ class Watcher {
|
|
|
16892
16467
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16893
16468
|
this.presenceTracked = false;
|
|
16894
16469
|
if (!this.stopping) {
|
|
16895
|
-
|
|
16470
|
+
log47.warn(TAG43, `Presence subscription ${status} — scheduling reconnect`);
|
|
16896
16471
|
this.schedulePresenceReconnect();
|
|
16897
16472
|
}
|
|
16898
16473
|
}
|
|
@@ -16911,7 +16486,7 @@ class Watcher {
|
|
|
16911
16486
|
async reconnectPresence() {
|
|
16912
16487
|
if (this.stopping || !this.supabase)
|
|
16913
16488
|
return;
|
|
16914
|
-
|
|
16489
|
+
log47.warn(TAG43, `Reconnecting presence subscription (attempt ${this.presenceReconnectAttempts})`);
|
|
16915
16490
|
if (this.presenceChannel) {
|
|
16916
16491
|
const old = this.presenceChannel;
|
|
16917
16492
|
this.presenceChannel = null;
|
|
@@ -16934,13 +16509,13 @@ class Watcher {
|
|
|
16934
16509
|
return;
|
|
16935
16510
|
const gen = ++this.broadcastGen;
|
|
16936
16511
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
16937
|
-
|
|
16512
|
+
log47.debug(TAG43, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
16938
16513
|
this.onCardBroadcast({
|
|
16939
16514
|
event: "card_update",
|
|
16940
16515
|
payload: msg.payload ?? {}
|
|
16941
16516
|
});
|
|
16942
16517
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
16943
|
-
|
|
16518
|
+
log47.debug(TAG43, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
16944
16519
|
this.onCardBroadcast({
|
|
16945
16520
|
event: "card_created",
|
|
16946
16521
|
payload: msg.payload ?? {}
|
|
@@ -16950,11 +16525,11 @@ class Watcher {
|
|
|
16950
16525
|
const cardId = payload.card_id;
|
|
16951
16526
|
const command = payload.command;
|
|
16952
16527
|
if (cardId && command) {
|
|
16953
|
-
|
|
16528
|
+
log47.info(TAG43, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
16954
16529
|
this.onAgentCommand?.({ cardId, command });
|
|
16955
16530
|
}
|
|
16956
16531
|
}).on("broadcast", { event: "sweep_command" }, () => {
|
|
16957
|
-
|
|
16532
|
+
log47.debug(TAG43, "Broadcast: sweep_command — draining");
|
|
16958
16533
|
this.onSweepCommandNudge?.();
|
|
16959
16534
|
}).subscribe((status) => {
|
|
16960
16535
|
if (gen !== this.broadcastGen)
|
|
@@ -16963,13 +16538,13 @@ class Watcher {
|
|
|
16963
16538
|
this.connected = true;
|
|
16964
16539
|
this.reconnectAttempts = 0;
|
|
16965
16540
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16966
|
-
|
|
16541
|
+
log47.info(TAG43, "Broadcast subscription active");
|
|
16967
16542
|
}
|
|
16968
16543
|
this.maybeResolveReady();
|
|
16969
16544
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16970
16545
|
this.connected = false;
|
|
16971
16546
|
if (!this.stopping) {
|
|
16972
|
-
|
|
16547
|
+
log47.warn(TAG43, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
16973
16548
|
this.scheduleReconnect();
|
|
16974
16549
|
}
|
|
16975
16550
|
}
|
|
@@ -16988,7 +16563,7 @@ class Watcher {
|
|
|
16988
16563
|
async reconnectBroadcast() {
|
|
16989
16564
|
if (this.stopping || !this.supabase)
|
|
16990
16565
|
return;
|
|
16991
|
-
|
|
16566
|
+
log47.warn(TAG43, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
16992
16567
|
if (this.channel) {
|
|
16993
16568
|
const old = this.channel;
|
|
16994
16569
|
this.channel = null;
|
|
@@ -17030,10 +16605,10 @@ class Watcher {
|
|
|
17030
16605
|
}
|
|
17031
16606
|
this.connected = false;
|
|
17032
16607
|
this.presenceTracked = false;
|
|
17033
|
-
|
|
16608
|
+
log47.info(TAG43, "Broadcast subscription stopped");
|
|
17034
16609
|
}
|
|
17035
16610
|
}
|
|
17036
|
-
var
|
|
16611
|
+
var TAG43 = "watcher", MAX_PRESENCE_COLUMNS = 24, MAX_PRESENCE_COLUMN_LENGTH = 120;
|
|
17037
16612
|
var init_watcher = __esm(() => {
|
|
17038
16613
|
init_sweep_guard();
|
|
17039
16614
|
});
|
|
@@ -17307,7 +16882,7 @@ __export(exports_run_gc, {
|
|
|
17307
16882
|
pruneRunLogs: () => pruneRunLogs
|
|
17308
16883
|
});
|
|
17309
16884
|
import { unlinkSync as unlinkSync2 } from "node:fs";
|
|
17310
|
-
import { log as
|
|
16885
|
+
import { log as log48 } from "@gethmy/harness";
|
|
17311
16886
|
function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
17312
16887
|
let deleted = 0;
|
|
17313
16888
|
let bytesFreed = 0;
|
|
@@ -17321,7 +16896,7 @@ function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
|
17321
16896
|
deleted++;
|
|
17322
16897
|
bytesFreed += file.sizeBytes;
|
|
17323
16898
|
} catch (err) {
|
|
17324
|
-
|
|
16899
|
+
log48.debug(TAG44, `Could not remove ${file.path}: ${err instanceof Error ? err.message : err}`);
|
|
17325
16900
|
}
|
|
17326
16901
|
}
|
|
17327
16902
|
return { deleted, bytesFreed };
|
|
@@ -17344,7 +16919,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17344
16919
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
17345
16920
|
}
|
|
17346
16921
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
17347
|
-
|
|
16922
|
+
log48.info(TAG44, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
17348
16923
|
}
|
|
17349
16924
|
return {
|
|
17350
16925
|
runRecordsPurged,
|
|
@@ -17352,7 +16927,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17352
16927
|
logBytesFreed: logs.bytesFreed
|
|
17353
16928
|
};
|
|
17354
16929
|
}
|
|
17355
|
-
var
|
|
16930
|
+
var TAG44 = "gc", DAY_MS2 = 86400000;
|
|
17356
16931
|
var init_run_gc = __esm(() => {
|
|
17357
16932
|
init_paths();
|
|
17358
16933
|
init_run_stats();
|
|
@@ -17369,7 +16944,7 @@ __export(exports_worktree_gc, {
|
|
|
17369
16944
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
17370
16945
|
import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
17371
16946
|
import { resolve as resolve3 } from "node:path";
|
|
17372
|
-
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as
|
|
16947
|
+
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as log49 } from "@gethmy/harness";
|
|
17373
16948
|
function isTransientGitNetworkError(message) {
|
|
17374
16949
|
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
17375
16950
|
}
|
|
@@ -17482,10 +17057,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
17482
17057
|
});
|
|
17483
17058
|
} catch {}
|
|
17484
17059
|
if (result.removed.length > 0) {
|
|
17485
|
-
|
|
17060
|
+
log49.info(TAG45, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
17486
17061
|
}
|
|
17487
17062
|
if (result.errors.length > 0) {
|
|
17488
|
-
|
|
17063
|
+
log49.warn(TAG45, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
17489
17064
|
}
|
|
17490
17065
|
return result;
|
|
17491
17066
|
}
|
|
@@ -17515,7 +17090,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17515
17090
|
} catch (err) {
|
|
17516
17091
|
const detail = gitErrorDetail2(err);
|
|
17517
17092
|
if (isTransientGitNetworkError(detail)) {
|
|
17518
|
-
|
|
17093
|
+
log49.debug(TAG45, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
17519
17094
|
return result;
|
|
17520
17095
|
}
|
|
17521
17096
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -17555,7 +17130,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17555
17130
|
continue;
|
|
17556
17131
|
}
|
|
17557
17132
|
if (clock() > sweepDeadline) {
|
|
17558
|
-
|
|
17133
|
+
log49.debug(TAG45, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
17559
17134
|
break;
|
|
17560
17135
|
}
|
|
17561
17136
|
try {
|
|
@@ -17568,17 +17143,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17568
17143
|
} catch (err) {
|
|
17569
17144
|
const detail = gitErrorDetail2(err);
|
|
17570
17145
|
if (isTransientGitNetworkError(detail)) {
|
|
17571
|
-
|
|
17146
|
+
log49.debug(TAG45, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
17572
17147
|
break;
|
|
17573
17148
|
}
|
|
17574
17149
|
result.errors.push({ ref, error: detail });
|
|
17575
17150
|
}
|
|
17576
17151
|
}
|
|
17577
17152
|
if (result.removed.length > 0) {
|
|
17578
|
-
|
|
17153
|
+
log49.info(TAG45, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
17579
17154
|
}
|
|
17580
17155
|
if (result.errors.length > 0) {
|
|
17581
|
-
|
|
17156
|
+
log49.warn(TAG45, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
17582
17157
|
}
|
|
17583
17158
|
return result;
|
|
17584
17159
|
}
|
|
@@ -17611,18 +17186,18 @@ class WorktreeGc {
|
|
|
17611
17186
|
try {
|
|
17612
17187
|
runWorktreeGc(this.basePath, this.store);
|
|
17613
17188
|
} catch (err) {
|
|
17614
|
-
|
|
17189
|
+
log49.warn(TAG45, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17615
17190
|
}
|
|
17616
17191
|
if (this.remoteOpts) {
|
|
17617
17192
|
try {
|
|
17618
17193
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
17619
17194
|
} catch (err) {
|
|
17620
|
-
|
|
17195
|
+
log49.warn(TAG45, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17621
17196
|
}
|
|
17622
17197
|
}
|
|
17623
17198
|
if (this.retentionOpts) {
|
|
17624
17199
|
runRetentionGc(this.store, this.retentionOpts).catch((err) => {
|
|
17625
|
-
|
|
17200
|
+
log49.warn(TAG45, `Retention GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17626
17201
|
});
|
|
17627
17202
|
}
|
|
17628
17203
|
}
|
|
@@ -17636,7 +17211,7 @@ function getRepoRoot2() {
|
|
|
17636
17211
|
return null;
|
|
17637
17212
|
}
|
|
17638
17213
|
}
|
|
17639
|
-
var
|
|
17214
|
+
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
17215
|
var init_worktree_gc = __esm(() => {
|
|
17641
17216
|
init_run_gc();
|
|
17642
17217
|
GIT_NETWORK_EXEC = {
|
|
@@ -17677,7 +17252,7 @@ import { resolve as resolve4 } from "node:path";
|
|
|
17677
17252
|
import {
|
|
17678
17253
|
detectGitProvider as detectGitProvider6,
|
|
17679
17254
|
GIT_NO_HOOKS as GIT_NO_HOOKS10,
|
|
17680
|
-
log as
|
|
17255
|
+
log as log50,
|
|
17681
17256
|
validateGitProviderCli
|
|
17682
17257
|
} from "@gethmy/harness";
|
|
17683
17258
|
async function validatePrerequisites(config, banner) {
|
|
@@ -17754,7 +17329,7 @@ async function main() {
|
|
|
17754
17329
|
} catch (err) {
|
|
17755
17330
|
if (err instanceof ConfigValidationError) {
|
|
17756
17331
|
banner.fail();
|
|
17757
|
-
|
|
17332
|
+
log50.error(TAG46, err.message);
|
|
17758
17333
|
process.exit(1);
|
|
17759
17334
|
}
|
|
17760
17335
|
throw err;
|
|
@@ -17772,7 +17347,7 @@ async function main() {
|
|
|
17772
17347
|
} catch (err) {
|
|
17773
17348
|
if (err instanceof ConfigValidationError) {
|
|
17774
17349
|
banner.fail();
|
|
17775
|
-
|
|
17350
|
+
log50.error(TAG46, err.message);
|
|
17776
17351
|
process.exit(1);
|
|
17777
17352
|
}
|
|
17778
17353
|
throw err;
|
|
@@ -17796,7 +17371,7 @@ async function main() {
|
|
|
17796
17371
|
try {
|
|
17797
17372
|
await client.reportAgentConfig(config.workspaceId, agentId, effectiveModelReport(config.agent));
|
|
17798
17373
|
} catch (err) {
|
|
17799
|
-
|
|
17374
|
+
log50.warn(TAG46, `Could not report model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
17800
17375
|
}
|
|
17801
17376
|
migrateConfigDir();
|
|
17802
17377
|
try {
|
|
@@ -17806,7 +17381,7 @@ async function main() {
|
|
|
17806
17381
|
worktreePrefix: resolve4(config.repoRoot, config.agent.worktree.basePath)
|
|
17807
17382
|
});
|
|
17808
17383
|
} catch (err) {
|
|
17809
|
-
|
|
17384
|
+
log50.error(TAG46, `legacy state split failed — starting with the project store as-is: ${err instanceof Error ? err.message : err}`);
|
|
17810
17385
|
}
|
|
17811
17386
|
const stateStore = StateStore.open({ projectId: config.projectId });
|
|
17812
17387
|
const daemonId = randomUUID4();
|
|
@@ -17957,7 +17532,7 @@ async function main() {
|
|
|
17957
17532
|
if (shuttingDown)
|
|
17958
17533
|
return;
|
|
17959
17534
|
shuttingDown = true;
|
|
17960
|
-
|
|
17535
|
+
log50.info(TAG46, `Received ${signal}, shutting down gracefully...`);
|
|
17961
17536
|
reconciler.stop();
|
|
17962
17537
|
mergeMonitor?.stop();
|
|
17963
17538
|
worktreeGc.stop();
|
|
@@ -17968,19 +17543,19 @@ async function main() {
|
|
|
17968
17543
|
}
|
|
17969
17544
|
await watcher.stop();
|
|
17970
17545
|
await pool.shutdown();
|
|
17971
|
-
|
|
17546
|
+
log50.info(TAG46, "Daemon stopped.");
|
|
17972
17547
|
process.exit(exitCode);
|
|
17973
17548
|
};
|
|
17974
17549
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
17975
17550
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
17976
17551
|
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
17977
17552
|
process.on("uncaughtException", (err) => {
|
|
17978
|
-
|
|
17553
|
+
log50.error(TAG46, `Uncaught exception: ${err.message}`);
|
|
17979
17554
|
exitCode = 1;
|
|
17980
17555
|
shutdown("uncaughtException");
|
|
17981
17556
|
});
|
|
17982
17557
|
process.on("unhandledRejection", (reason) => {
|
|
17983
|
-
|
|
17558
|
+
log50.error(TAG46, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
17984
17559
|
exitCode = 1;
|
|
17985
17560
|
shutdown("unhandledRejection");
|
|
17986
17561
|
});
|
|
@@ -18040,33 +17615,33 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
18040
17615
|
if (assignedAgentId === undefined)
|
|
18041
17616
|
return;
|
|
18042
17617
|
if (assignedAgentId === agentId) {
|
|
18043
|
-
|
|
17618
|
+
log50.info(TAG46, `Broadcast: card ${cardId} assigned to agent`);
|
|
18044
17619
|
try {
|
|
18045
17620
|
await pool.resetAttemptsForReassign(cardId);
|
|
18046
17621
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
18047
17622
|
} catch (err) {
|
|
18048
|
-
|
|
17623
|
+
log50.error(TAG46, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
18049
17624
|
}
|
|
18050
17625
|
} else if (pool.isCardKnown(cardId)) {
|
|
18051
|
-
|
|
17626
|
+
log50.info(TAG46, `Broadcast: card ${cardId} unassigned from agent`);
|
|
18052
17627
|
await pool.removeCard(cardId);
|
|
18053
17628
|
}
|
|
18054
17629
|
}
|
|
18055
17630
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
18056
17631
|
const { card } = await client.getCard(cardId);
|
|
18057
17632
|
if (card.assigned_agent_id !== agentId) {
|
|
18058
|
-
|
|
17633
|
+
log50.debug(TAG46, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
18059
17634
|
return;
|
|
18060
17635
|
}
|
|
18061
17636
|
if (card.done || card.archived_at) {
|
|
18062
|
-
|
|
17637
|
+
log50.debug(TAG46, `Card ${cardId} is finished — skipping`);
|
|
18063
17638
|
return;
|
|
18064
17639
|
}
|
|
18065
17640
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
18066
17641
|
const columns = board.columns;
|
|
18067
17642
|
const column = columns.find((c) => c.id === card.column_id);
|
|
18068
17643
|
if (!column) {
|
|
18069
|
-
|
|
17644
|
+
log50.warn(TAG46, `Column not found for card ${cardId}`);
|
|
18070
17645
|
return;
|
|
18071
17646
|
}
|
|
18072
17647
|
const route = classifyPickup(card, column.name, {
|
|
@@ -18075,31 +17650,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
18075
17650
|
playbooks: config.agent.playbooks
|
|
18076
17651
|
});
|
|
18077
17652
|
if (!route) {
|
|
18078
|
-
|
|
17653
|
+
log50.info(TAG46, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
18079
17654
|
return;
|
|
18080
17655
|
}
|
|
18081
17656
|
if (route.stage) {
|
|
18082
|
-
|
|
17657
|
+
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
17658
|
}
|
|
18084
17659
|
const mode = route.mode;
|
|
18085
17660
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
18086
17661
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
18087
17662
|
const subtasks = card.subtasks ?? [];
|
|
18088
17663
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
18089
|
-
|
|
17664
|
+
log50.debug(TAG46, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
18090
17665
|
return;
|
|
18091
17666
|
}
|
|
18092
17667
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
18093
|
-
|
|
17668
|
+
log50.debug(TAG46, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
|
|
18094
17669
|
return;
|
|
18095
17670
|
}
|
|
18096
17671
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
18097
|
-
|
|
17672
|
+
log50.info(TAG46, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
|
|
18098
17673
|
return;
|
|
18099
17674
|
}
|
|
18100
17675
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
18101
17676
|
}
|
|
18102
|
-
var
|
|
17677
|
+
var TAG46 = "daemon", BASE_REMOTE = "origin", PKG_VERSION;
|
|
18103
17678
|
var init_src = __esm(() => {
|
|
18104
17679
|
init_account_guard();
|
|
18105
17680
|
init_account_probe();
|