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