@gethmy/agent 1.47.3 → 1.47.4
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 +723 -576
- package/dist/index.js +694 -548
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -5673,6 +5673,8 @@ function decideAutoMergeAction(input) {
|
|
|
5673
5673
|
if (headSha && reviewedSha !== headSha)
|
|
5674
5674
|
return "rereview";
|
|
5675
5675
|
}
|
|
5676
|
+
if (input.mergeConflict)
|
|
5677
|
+
return "stamp-conflict";
|
|
5676
5678
|
const independent = config.independentReview;
|
|
5677
5679
|
if (independent.enabled) {
|
|
5678
5680
|
const state = input.independentReview ?? "absent";
|
|
@@ -5741,6 +5743,9 @@ async function stampFindingsExhausted(client, card, maxRounds) {
|
|
|
5741
5743
|
async function stampReviewIncomplete(client, card) {
|
|
5742
5744
|
await stampForHumanOnce(client, card, "Independent review did not complete (no verdict)");
|
|
5743
5745
|
}
|
|
5746
|
+
async function stampMergeConflict(client, card) {
|
|
5747
|
+
await stampForHumanOnce(client, card, "Merge conflict with the base branch");
|
|
5748
|
+
}
|
|
5744
5749
|
async function removeApprovedLabel(client, card, resolvedLabels, approvedLabel) {
|
|
5745
5750
|
const name = approvedLabel.toLowerCase();
|
|
5746
5751
|
const obj = resolvedLabels.find((l) => l.name.toLowerCase() === name);
|
|
@@ -5761,7 +5766,14 @@ async function attemptAutoMerge(deps) {
|
|
|
5761
5766
|
return "foreign-pr";
|
|
5762
5767
|
}
|
|
5763
5768
|
const independent = autoMerge.independentReview;
|
|
5764
|
-
const {
|
|
5769
|
+
const {
|
|
5770
|
+
ciStatus,
|
|
5771
|
+
headSha,
|
|
5772
|
+
baseRefName,
|
|
5773
|
+
mergeConflict,
|
|
5774
|
+
independentReview,
|
|
5775
|
+
reviewVerdict
|
|
5776
|
+
} = await getPrStatus(prUrl, cwd, provider, independent.enabled ? independent.checkName : undefined, independent.enabled ? independent.verdict.checkName : undefined);
|
|
5765
5777
|
deps.onPrRead?.({ headSha, baseRefName });
|
|
5766
5778
|
if (driver === "interactive" && baseRefName !== config.worktree.baseBranch) {
|
|
5767
5779
|
if (baseRefName !== null) {
|
|
@@ -5795,7 +5807,8 @@ async function attemptAutoMerge(deps) {
|
|
|
5795
5807
|
independentReview,
|
|
5796
5808
|
reviewVerdict,
|
|
5797
5809
|
reviewRequestedSha,
|
|
5798
|
-
blockingVerdictRounds
|
|
5810
|
+
blockingVerdictRounds,
|
|
5811
|
+
mergeConflict
|
|
5799
5812
|
});
|
|
5800
5813
|
switch (action) {
|
|
5801
5814
|
case "wait":
|
|
@@ -5815,6 +5828,10 @@ async function attemptAutoMerge(deps) {
|
|
|
5815
5828
|
log13.debug(TAG13, `#${card.short_id} waiting (ci=${ciStatus}, review=${independentReview}, verdict=${reviewVerdict})`);
|
|
5816
5829
|
}
|
|
5817
5830
|
return action;
|
|
5831
|
+
case "stamp-conflict":
|
|
5832
|
+
log13.info(TAG13, `#${card.short_id} PR conflicts with its base — flagging for human. Resolve: gh pr checkout <n> && git fetch origin ${baseRefName ?? "main"} && git merge origin/${baseRefName ?? "main"}`);
|
|
5833
|
+
await stampMergeConflict(client, card);
|
|
5834
|
+
return action;
|
|
5818
5835
|
case "stamp-failure":
|
|
5819
5836
|
log13.info(TAG13, `#${card.short_id} CI failed — flagging for human`);
|
|
5820
5837
|
await stampCiFailure(client, card);
|
|
@@ -5975,6 +5992,28 @@ var init_merge_complete = __esm(() => {
|
|
|
5975
5992
|
execFileAsync = promisify(execFile);
|
|
5976
5993
|
});
|
|
5977
5994
|
|
|
5995
|
+
// src/pr-state-sync.ts
|
|
5996
|
+
import {
|
|
5997
|
+
log as log15,
|
|
5998
|
+
readPullRequestSnapshot
|
|
5999
|
+
} from "@gethmy/harness";
|
|
6000
|
+
async function syncPullRequestState(deps) {
|
|
6001
|
+
try {
|
|
6002
|
+
const snapshot = await readPullRequestSnapshot(deps.prUrl, deps.cwd, deps.provider);
|
|
6003
|
+
if (!snapshot)
|
|
6004
|
+
return;
|
|
6005
|
+
await deps.client.upsertCardPullRequest(deps.cardId, {
|
|
6006
|
+
url: deps.prUrl,
|
|
6007
|
+
state: snapshot.state,
|
|
6008
|
+
ci_status: snapshot.ciStatus
|
|
6009
|
+
});
|
|
6010
|
+
} catch (err) {
|
|
6011
|
+
log15.warn(TAG15, `Could not write PR state for ${deps.prUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
var TAG15 = "pr-state-sync";
|
|
6015
|
+
var init_pr_state_sync = () => {};
|
|
6016
|
+
|
|
5978
6017
|
// src/merge-monitor.ts
|
|
5979
6018
|
var exports_merge_monitor = {};
|
|
5980
6019
|
__export(exports_merge_monitor, {
|
|
@@ -5983,7 +6022,7 @@ __export(exports_merge_monitor, {
|
|
|
5983
6022
|
import {
|
|
5984
6023
|
checkPrMergeStatus,
|
|
5985
6024
|
detectGitProvider as detectGitProvider2,
|
|
5986
|
-
log as
|
|
6025
|
+
log as log16,
|
|
5987
6026
|
resolvePrUrl
|
|
5988
6027
|
} from "@gethmy/harness";
|
|
5989
6028
|
|
|
@@ -6017,7 +6056,7 @@ class MergeMonitor {
|
|
|
6017
6056
|
clearTimeout(this.timer);
|
|
6018
6057
|
this.timer = null;
|
|
6019
6058
|
}
|
|
6020
|
-
|
|
6059
|
+
log16.info(TAG16, "Merge monitor stopped");
|
|
6021
6060
|
}
|
|
6022
6061
|
async runOnce() {
|
|
6023
6062
|
await this.tick();
|
|
@@ -6053,21 +6092,28 @@ class MergeMonitor {
|
|
|
6053
6092
|
}
|
|
6054
6093
|
}
|
|
6055
6094
|
if (candidatesWithLabels.length === 0) {
|
|
6056
|
-
|
|
6095
|
+
log16.debug(TAG16, "No Ready to Merge cards found");
|
|
6057
6096
|
return;
|
|
6058
6097
|
}
|
|
6059
6098
|
const batch = candidatesWithLabels.slice(0, 5);
|
|
6060
|
-
|
|
6099
|
+
log16.debug(TAG16, `Checking ${batch.length} Ready to Merge card(s)`);
|
|
6061
6100
|
const results = await Promise.allSettled(batch.map(async ({ card, labels }) => {
|
|
6062
6101
|
const branchName = extractBranchFromDescription(card.description);
|
|
6063
6102
|
const prUrl = resolvePrUrl(card.description ?? null, branchName, this.cwd, this.provider);
|
|
6064
6103
|
if (!prUrl) {
|
|
6065
|
-
|
|
6104
|
+
log16.debug(TAG16, `#${card.short_id} has no resolvable PR — skipping`);
|
|
6066
6105
|
return;
|
|
6067
6106
|
}
|
|
6068
6107
|
const state = await checkPrMergeStatus(prUrl, this.cwd, this.provider);
|
|
6108
|
+
await syncPullRequestState({
|
|
6109
|
+
client: this.client,
|
|
6110
|
+
cardId: card.id,
|
|
6111
|
+
prUrl,
|
|
6112
|
+
cwd: this.cwd,
|
|
6113
|
+
provider: this.provider
|
|
6114
|
+
});
|
|
6069
6115
|
if (state === "merged") {
|
|
6070
|
-
|
|
6116
|
+
log16.info(TAG16, `#${card.short_id} PR merged — completing`);
|
|
6071
6117
|
await this.completeMergedCard(card, labels);
|
|
6072
6118
|
} else if (state === "open") {
|
|
6073
6119
|
await attemptAutoMerge({
|
|
@@ -6081,16 +6127,16 @@ class MergeMonitor {
|
|
|
6081
6127
|
...this.repairContext ? { repair: this.repairContext } : {}
|
|
6082
6128
|
});
|
|
6083
6129
|
} else {
|
|
6084
|
-
|
|
6130
|
+
log16.debug(TAG16, `#${card.short_id} PR state: ${state}`);
|
|
6085
6131
|
}
|
|
6086
6132
|
}));
|
|
6087
6133
|
for (const r of results) {
|
|
6088
6134
|
if (r.status === "rejected") {
|
|
6089
|
-
|
|
6135
|
+
log16.warn(TAG16, `Card processing failed: ${r.reason}`);
|
|
6090
6136
|
}
|
|
6091
6137
|
}
|
|
6092
6138
|
} catch (err) {
|
|
6093
|
-
|
|
6139
|
+
log16.error(TAG16, `Tick failed: ${err instanceof Error ? err.message : err}`);
|
|
6094
6140
|
}
|
|
6095
6141
|
}
|
|
6096
6142
|
async completeMergedCard(card, resolvedLabels) {
|
|
@@ -6104,11 +6150,12 @@ class MergeMonitor {
|
|
|
6104
6150
|
});
|
|
6105
6151
|
}
|
|
6106
6152
|
}
|
|
6107
|
-
var
|
|
6153
|
+
var TAG16 = "merge-monitor";
|
|
6108
6154
|
var init_merge_monitor = __esm(() => {
|
|
6109
6155
|
init_auto_merge();
|
|
6110
6156
|
init_board_helpers();
|
|
6111
6157
|
init_merge_complete();
|
|
6158
|
+
init_pr_state_sync();
|
|
6112
6159
|
init_review_worktree();
|
|
6113
6160
|
});
|
|
6114
6161
|
|
|
@@ -6301,7 +6348,7 @@ __export(exports_plan_criteria, {
|
|
|
6301
6348
|
criteriaSettledBy: () => criteriaSettledBy,
|
|
6302
6349
|
CRITERION_COMPLETED: () => CRITERION_COMPLETED
|
|
6303
6350
|
});
|
|
6304
|
-
import { log as
|
|
6351
|
+
import { log as log17 } from "@gethmy/harness";
|
|
6305
6352
|
function criteriaSettledBy(tasks, cardId) {
|
|
6306
6353
|
if (!cardId || !Array.isArray(tasks))
|
|
6307
6354
|
return [];
|
|
@@ -6362,7 +6409,7 @@ async function settlePlanCriteria(client, tag, card) {
|
|
|
6362
6409
|
const settled = card.plan_id ? criteriaSettledBy(payload?.tasks, card.id) : [];
|
|
6363
6410
|
const divergent = divergentCriteria(payload, card.id, card.plan_id);
|
|
6364
6411
|
if (divergent.length > 0) {
|
|
6365
|
-
|
|
6412
|
+
log17.warn(tag, describeDivergence(card.short_id, card.plan_id, divergent));
|
|
6366
6413
|
}
|
|
6367
6414
|
const settleGroup = async (criteria, planIdOf, describe2) => {
|
|
6368
6415
|
if (criteria.length === 0)
|
|
@@ -6373,15 +6420,15 @@ async function settlePlanCriteria(client, tag, card) {
|
|
|
6373
6420
|
status: CRITERION_COMPLETED
|
|
6374
6421
|
});
|
|
6375
6422
|
}
|
|
6376
|
-
|
|
6423
|
+
log17.info(tag, describe2());
|
|
6377
6424
|
} catch (err) {
|
|
6378
|
-
|
|
6425
|
+
log17.warn(tag, `plan criteria write-back failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6379
6426
|
}
|
|
6380
6427
|
};
|
|
6381
6428
|
await settleGroup(settled, () => card.plan_id, () => describeSettled(card.short_id, settled));
|
|
6382
6429
|
await settleGroup(divergent, (criterion) => criterion.plan_id, () => `${describeSettled(card.short_id, divergent)} (from the criterion side)`);
|
|
6383
6430
|
} catch (err) {
|
|
6384
|
-
|
|
6431
|
+
log17.warn(tag, `plan criteria write-back failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6385
6432
|
}
|
|
6386
6433
|
}
|
|
6387
6434
|
async function fetchAcceptanceCriteria(client, tag, card) {
|
|
@@ -6390,12 +6437,12 @@ async function fetchAcceptanceCriteria(client, tag, card) {
|
|
|
6390
6437
|
try {
|
|
6391
6438
|
const plan = await client.getPlanByCardId(card.id);
|
|
6392
6439
|
if (!plan?.plan) {
|
|
6393
|
-
|
|
6440
|
+
log17.warn(tag, `#${card.short_id} names plan ${card.plan_id}, but the plan read returned no plan — grading the card text alone; check that the card's plan_id still resolves`);
|
|
6394
6441
|
return [];
|
|
6395
6442
|
}
|
|
6396
6443
|
return normalizeAcceptanceCriteria(plan.tasks, card.id);
|
|
6397
6444
|
} catch (err) {
|
|
6398
|
-
|
|
6445
|
+
log17.warn(tag, `plan criteria fetch failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6399
6446
|
return [];
|
|
6400
6447
|
}
|
|
6401
6448
|
}
|
|
@@ -6502,7 +6549,7 @@ var init_budget_pause = __esm(() => {
|
|
|
6502
6549
|
});
|
|
6503
6550
|
|
|
6504
6551
|
// src/queue.ts
|
|
6505
|
-
import { log as
|
|
6552
|
+
import { log as log18 } from "@gethmy/harness";
|
|
6506
6553
|
function scoreCardBreakdown(config, card, column, labels, signals = {}) {
|
|
6507
6554
|
const w = config.ranking;
|
|
6508
6555
|
const priority = w.priorityWeight * (PRIORITY_STEP[card.priority ?? "medium"] ?? PRIORITY_STEP.medium);
|
|
@@ -6563,7 +6610,7 @@ class PriorityQueue {
|
|
|
6563
6610
|
enqueue(card, column, labels, mode = "implement", signals = {}) {
|
|
6564
6611
|
const existing = this.items.findIndex((i) => i.cardId === card.id);
|
|
6565
6612
|
if (existing !== -1) {
|
|
6566
|
-
|
|
6613
|
+
log18.debug(TAG17, `Card #${card.short_id} already queued, updating priority`);
|
|
6567
6614
|
this.items.splice(existing, 1);
|
|
6568
6615
|
}
|
|
6569
6616
|
const breakdown = scoreCardBreakdown(this.config, card, column, labels, signals);
|
|
@@ -6584,7 +6631,7 @@ class PriorityQueue {
|
|
|
6584
6631
|
}
|
|
6585
6632
|
}
|
|
6586
6633
|
this.items.splice(insertIdx, 0, item);
|
|
6587
|
-
|
|
6634
|
+
log18.info(TAG17, `Enqueued #${card.short_id} "${card.title}" (${formatBreakdown(breakdown)}, pos=${insertIdx}, queue=${this.items.length})`);
|
|
6588
6635
|
}
|
|
6589
6636
|
dequeue() {
|
|
6590
6637
|
return this.items.shift() ?? null;
|
|
@@ -6594,7 +6641,7 @@ class PriorityQueue {
|
|
|
6594
6641
|
if (idx === -1)
|
|
6595
6642
|
return null;
|
|
6596
6643
|
const [item] = this.items.splice(idx, 1);
|
|
6597
|
-
|
|
6644
|
+
log18.info(TAG17, `Removed #${item.shortId} from queue`);
|
|
6598
6645
|
return item;
|
|
6599
6646
|
}
|
|
6600
6647
|
has(cardId) {
|
|
@@ -6613,7 +6660,7 @@ class PriorityQueue {
|
|
|
6613
6660
|
return this.items.slice();
|
|
6614
6661
|
}
|
|
6615
6662
|
}
|
|
6616
|
-
var
|
|
6663
|
+
var TAG17 = "queue", MS_PER_DAY = 86400000, PRIORITY_STEP;
|
|
6617
6664
|
var init_queue = __esm(() => {
|
|
6618
6665
|
init_src();
|
|
6619
6666
|
PRIORITY_STEP = {
|
|
@@ -6625,7 +6672,7 @@ var init_queue = __esm(() => {
|
|
|
6625
6672
|
});
|
|
6626
6673
|
|
|
6627
6674
|
// src/reported-config-cache.ts
|
|
6628
|
-
import { log as
|
|
6675
|
+
import { log as log19 } from "@gethmy/harness";
|
|
6629
6676
|
|
|
6630
6677
|
class ReportedConfigCache {
|
|
6631
6678
|
client;
|
|
@@ -6642,27 +6689,27 @@ class ReportedConfigCache {
|
|
|
6642
6689
|
await this.client.reportAgentConfig(workspaceId, agentId, report);
|
|
6643
6690
|
this.last = serialized;
|
|
6644
6691
|
} catch (err) {
|
|
6645
|
-
|
|
6692
|
+
log19.warn(TAG18, `Could not report the resolved model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
6646
6693
|
}
|
|
6647
6694
|
}
|
|
6648
6695
|
}
|
|
6649
|
-
var
|
|
6696
|
+
var TAG18 = "reported-config";
|
|
6650
6697
|
var init_reported_config_cache = () => {};
|
|
6651
6698
|
|
|
6652
6699
|
// src/run-closeout.ts
|
|
6653
|
-
import { log as
|
|
6700
|
+
import { log as log20 } from "@gethmy/harness";
|
|
6654
6701
|
async function transferCardToCompletion(deps, card, moveToColumn, onPromoted) {
|
|
6655
6702
|
await moveCardToColumn(deps.client, card, moveToColumn);
|
|
6656
6703
|
try {
|
|
6657
6704
|
await releaseAssignedAgent(deps.client, card.id);
|
|
6658
6705
|
} catch (err) {
|
|
6659
|
-
|
|
6706
|
+
log20.warn(deps.tag, `assignment release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6660
6707
|
}
|
|
6661
6708
|
if (onPromoted) {
|
|
6662
6709
|
try {
|
|
6663
6710
|
await onPromoted(card);
|
|
6664
6711
|
} catch (err) {
|
|
6665
|
-
|
|
6712
|
+
log20.warn(deps.tag, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6666
6713
|
}
|
|
6667
6714
|
}
|
|
6668
6715
|
}
|
|
@@ -6676,7 +6723,7 @@ async function endRunSession(deps, card, disposition, extraPayload, onError) {
|
|
|
6676
6723
|
} catch (err) {
|
|
6677
6724
|
if (onError === "throw")
|
|
6678
6725
|
throw err;
|
|
6679
|
-
|
|
6726
|
+
log20.error(deps.tag, `endAgentSession after the run failed on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6680
6727
|
}
|
|
6681
6728
|
}
|
|
6682
6729
|
var init_run_closeout = __esm(() => {
|
|
@@ -6692,7 +6739,7 @@ import {
|
|
|
6692
6739
|
extractPrUrl as extractPrUrl2,
|
|
6693
6740
|
GIT_NO_HOOKS as GIT_NO_HOOKS6,
|
|
6694
6741
|
getBranchWebUrl,
|
|
6695
|
-
log as
|
|
6742
|
+
log as log21,
|
|
6696
6743
|
pushBranch,
|
|
6697
6744
|
reportFindings,
|
|
6698
6745
|
runFormatFix,
|
|
@@ -6739,24 +6786,24 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6739
6786
|
if (!hasCommits) {
|
|
6740
6787
|
const { maxTurnsExhausted, failureSummary } = describeNoCommitFailure(sessionStats?.cost?.numTurns ?? 0, effectiveMaxTurns ?? config.claude.maxTurns);
|
|
6741
6788
|
if (noCommitOutcome(maxTurnsExhausted, config.budget.pause.enabled) === "park") {
|
|
6742
|
-
|
|
6789
|
+
log21.warn(TAG19, `No commits on branch ${branchName} — ${failureSummary}; parking for a decision`);
|
|
6743
6790
|
return "park";
|
|
6744
6791
|
}
|
|
6745
6792
|
if (expectsCommits === false) {
|
|
6746
|
-
|
|
6793
|
+
log21.info(TAG19, `No commits on branch ${branchName} — this stage's deliverable is not a diff; its gate decides the run`);
|
|
6747
6794
|
if (onBeforeWorktreeCleanup) {
|
|
6748
6795
|
try {
|
|
6749
6796
|
await onBeforeWorktreeCleanup(worktreePath);
|
|
6750
6797
|
} catch (err) {
|
|
6751
|
-
|
|
6798
|
+
log21.warn(TAG19, `onBeforeWorktreeCleanup hook failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6752
6799
|
}
|
|
6753
6800
|
} else {
|
|
6754
|
-
await endRunSession({ client, tag:
|
|
6801
|
+
await endRunSession({ client, tag: TAG19 }, card, { status: "completed" }, buildTokenPayload(sessionStats), "throw");
|
|
6755
6802
|
}
|
|
6756
6803
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6757
6804
|
return true;
|
|
6758
6805
|
}
|
|
6759
|
-
|
|
6806
|
+
log21.warn(TAG19, `No commits on branch ${branchName} — ${failureSummary}; counting as a failed attempt`);
|
|
6760
6807
|
const noCommitHandback = await guardedHandback(client, card.id, {
|
|
6761
6808
|
agentId,
|
|
6762
6809
|
workingColumnId: card.column_id
|
|
@@ -6773,13 +6820,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6773
6820
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6774
6821
|
return false;
|
|
6775
6822
|
}
|
|
6776
|
-
|
|
6823
|
+
log21.info(TAG19, `Pushing branch ${branchName} (pre-verify)...`);
|
|
6777
6824
|
let lastPushedSha = null;
|
|
6778
6825
|
try {
|
|
6779
6826
|
pushBranch(branchName, worktreePath);
|
|
6780
6827
|
lastPushedSha = readHeadSha(worktreePath);
|
|
6781
6828
|
} catch (err) {
|
|
6782
|
-
|
|
6829
|
+
log21.error(TAG19, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
6783
6830
|
}
|
|
6784
6831
|
const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
|
|
6785
6832
|
if (lastPushedSha) {
|
|
@@ -6798,7 +6845,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6798
6845
|
let autoFixAttempts = 0;
|
|
6799
6846
|
if (!result.passed && config.verification.autoFix) {
|
|
6800
6847
|
for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
|
|
6801
|
-
|
|
6848
|
+
log21.info(TAG19, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
|
|
6802
6849
|
await client.updateAgentProgress(card.id, {
|
|
6803
6850
|
agentIdentifier: sessionIdentifier,
|
|
6804
6851
|
agentName: AGENT_NAME,
|
|
@@ -6815,14 +6862,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6815
6862
|
result = await runVerification(worktreePath, config, workerId);
|
|
6816
6863
|
autoFixAttempts = attempt + 1;
|
|
6817
6864
|
if (result.passed) {
|
|
6818
|
-
|
|
6865
|
+
log21.info(TAG19, `Auto-fix succeeded on attempt ${attempt + 1}`);
|
|
6819
6866
|
const sha = readHeadSha(worktreePath);
|
|
6820
6867
|
if (sha && sha !== lastPushedSha) {
|
|
6821
6868
|
try {
|
|
6822
6869
|
pushBranch(branchName, worktreePath);
|
|
6823
6870
|
lastPushedSha = sha;
|
|
6824
6871
|
} catch (err) {
|
|
6825
|
-
|
|
6872
|
+
log21.warn(TAG19, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
6826
6873
|
}
|
|
6827
6874
|
}
|
|
6828
6875
|
break;
|
|
@@ -6830,14 +6877,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6830
6877
|
}
|
|
6831
6878
|
}
|
|
6832
6879
|
if (!result.passed) {
|
|
6833
|
-
|
|
6880
|
+
log21.warn(TAG19, `Verification failed for #${card.short_id} — reporting findings`);
|
|
6834
6881
|
const failSha = readHeadSha(worktreePath);
|
|
6835
6882
|
if (failSha && failSha !== lastPushedSha) {
|
|
6836
6883
|
try {
|
|
6837
6884
|
pushBranch(branchName, worktreePath);
|
|
6838
6885
|
lastPushedSha = failSha;
|
|
6839
6886
|
} catch (err) {
|
|
6840
|
-
|
|
6887
|
+
log21.warn(TAG19, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
6841
6888
|
}
|
|
6842
6889
|
}
|
|
6843
6890
|
const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
|
|
@@ -6848,7 +6895,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6848
6895
|
recoveryBranch: branchName
|
|
6849
6896
|
});
|
|
6850
6897
|
} catch (err) {
|
|
6851
|
-
|
|
6898
|
+
log21.debug(TAG19, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
6852
6899
|
}
|
|
6853
6900
|
await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
|
|
6854
6901
|
const verifyHandback = await guardedHandback(client, card.id, {
|
|
@@ -6868,15 +6915,24 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6868
6915
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6869
6916
|
return false;
|
|
6870
6917
|
}
|
|
6871
|
-
|
|
6918
|
+
log21.info(TAG19, `Verification passed for #${card.short_id}`);
|
|
6872
6919
|
}
|
|
6873
6920
|
let prUrl = null;
|
|
6874
6921
|
if (config.completion.createPR) {
|
|
6875
6922
|
const provider = detectGitProvider3(worktreePath);
|
|
6876
6923
|
prUrl = createPullRequest(card, branchName, worktreePath, config, provider);
|
|
6924
|
+
if (prUrl) {
|
|
6925
|
+
await syncPullRequestState({
|
|
6926
|
+
client,
|
|
6927
|
+
cardId: card.id,
|
|
6928
|
+
prUrl,
|
|
6929
|
+
cwd: worktreePath,
|
|
6930
|
+
provider
|
|
6931
|
+
});
|
|
6932
|
+
}
|
|
6877
6933
|
}
|
|
6878
6934
|
if (config.completion.moveToColumn) {
|
|
6879
|
-
await transferCardToCompletion({ client, tag:
|
|
6935
|
+
await transferCardToCompletion({ client, tag: TAG19 }, card, config.completion.moveToColumn, onMovedToCompletion);
|
|
6880
6936
|
}
|
|
6881
6937
|
if (config.completion.postSummary) {
|
|
6882
6938
|
await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch, sessionStats);
|
|
@@ -6885,14 +6941,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
6885
6941
|
try {
|
|
6886
6942
|
await onBeforeWorktreeCleanup(worktreePath);
|
|
6887
6943
|
} catch (err) {
|
|
6888
|
-
|
|
6944
|
+
log21.warn(TAG19, `onBeforeWorktreeCleanup hook failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6889
6945
|
}
|
|
6890
6946
|
}
|
|
6891
6947
|
if (!onBeforeWorktreeCleanup) {
|
|
6892
|
-
await endRunSession({ client, tag:
|
|
6948
|
+
await endRunSession({ client, tag: TAG19 }, card, { status: "completed" }, buildTokenPayload(sessionStats), "throw");
|
|
6893
6949
|
}
|
|
6894
6950
|
await teardownWorktree(client, card.id, worktreePath, branchName, agentSessionId);
|
|
6895
|
-
|
|
6951
|
+
log21.info(TAG19, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
|
|
6896
6952
|
return true;
|
|
6897
6953
|
}
|
|
6898
6954
|
function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
@@ -6934,7 +6990,7 @@ function commitUncommittedChanges(worktreePath, card) {
|
|
|
6934
6990
|
encoding: "utf-8"
|
|
6935
6991
|
}).trim();
|
|
6936
6992
|
} catch (err) {
|
|
6937
|
-
|
|
6993
|
+
log21.warn(TAG19, `git status failed in ${worktreePath}: ${err instanceof Error ? err.message : err}`);
|
|
6938
6994
|
return false;
|
|
6939
6995
|
}
|
|
6940
6996
|
if (status.length === 0)
|
|
@@ -6950,10 +7006,10 @@ function commitUncommittedChanges(worktreePath, card) {
|
|
|
6950
7006
|
cwd: worktreePath,
|
|
6951
7007
|
encoding: "utf-8"
|
|
6952
7008
|
});
|
|
6953
|
-
|
|
7009
|
+
log21.warn(TAG19, `Auto-committed uncommitted worktree changes for #${card.short_id} — agent ended without committing`);
|
|
6954
7010
|
return true;
|
|
6955
7011
|
} catch (err) {
|
|
6956
|
-
|
|
7012
|
+
log21.error(TAG19, `auto-commit failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6957
7013
|
return false;
|
|
6958
7014
|
}
|
|
6959
7015
|
}
|
|
@@ -7002,9 +7058,9 @@ async function recordBranchProvenance(client, card, branchName) {
|
|
|
7002
7058
|
${BRANCH_PROVENANCE_MARKER} (pushed — a later run continues it)
|
|
7003
7059
|
Branch: \`${branchName}\``;
|
|
7004
7060
|
await client.updateCard(card.id, { description: description + block });
|
|
7005
|
-
|
|
7061
|
+
log21.info(TAG19, `Recorded branch provenance on #${card.short_id}`);
|
|
7006
7062
|
} catch (err) {
|
|
7007
|
-
|
|
7063
|
+
log21.warn(TAG19, `Failed to record branch provenance on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7008
7064
|
}
|
|
7009
7065
|
}
|
|
7010
7066
|
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
|
|
@@ -7058,24 +7114,25 @@ ${commitLog}
|
|
|
7058
7114
|
description: baseDesc + parts.join(`
|
|
7059
7115
|
`)
|
|
7060
7116
|
});
|
|
7061
|
-
|
|
7117
|
+
log21.info(TAG19, `Posted completion summary to #${card.short_id}`);
|
|
7062
7118
|
} catch (err) {
|
|
7063
|
-
|
|
7119
|
+
log21.error(TAG19, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
|
|
7064
7120
|
}
|
|
7065
7121
|
}
|
|
7066
|
-
var
|
|
7122
|
+
var TAG19 = "completion", SUMMARY_MARKER = `---
|
|
7067
7123
|
**Agent completed**`, BRANCH_PROVENANCE_MARKER = `---
|
|
7068
7124
|
**Agent branch**`;
|
|
7069
7125
|
var init_completion = __esm(() => {
|
|
7070
7126
|
init_src();
|
|
7071
7127
|
init_board_helpers();
|
|
7072
7128
|
init_handback();
|
|
7129
|
+
init_pr_state_sync();
|
|
7073
7130
|
init_run_closeout();
|
|
7074
7131
|
init_types();
|
|
7075
7132
|
});
|
|
7076
7133
|
|
|
7077
7134
|
// src/progress-tracker.ts
|
|
7078
|
-
import { log as
|
|
7135
|
+
import { log as log22 } from "@gethmy/harness";
|
|
7079
7136
|
function truncate2(str, max) {
|
|
7080
7137
|
return str.length > max ? `${str.slice(0, max - 3)}...` : str;
|
|
7081
7138
|
}
|
|
@@ -7194,7 +7251,7 @@ class ProgressTracker {
|
|
|
7194
7251
|
}
|
|
7195
7252
|
onToolStart(name, input) {
|
|
7196
7253
|
this.toolCallCount++;
|
|
7197
|
-
|
|
7254
|
+
log22.debug(TAG20, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
|
|
7198
7255
|
const filePath = this.extractString(input, "file_path");
|
|
7199
7256
|
if (filePath) {
|
|
7200
7257
|
if (EDIT_TOOLS.has(name)) {
|
|
@@ -7261,7 +7318,7 @@ class ProgressTracker {
|
|
|
7261
7318
|
transitionTo(newPhase) {
|
|
7262
7319
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
7263
7320
|
return;
|
|
7264
|
-
|
|
7321
|
+
log22.info(TAG20, `Phase: ${this.phase} → ${newPhase}`);
|
|
7265
7322
|
const previousPhase = this.phase;
|
|
7266
7323
|
this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
|
|
7267
7324
|
this.phase = newPhase;
|
|
@@ -7366,7 +7423,7 @@ class ProgressTracker {
|
|
|
7366
7423
|
}
|
|
7367
7424
|
sendUpdate(currentTask) {
|
|
7368
7425
|
this.lastUpdateAt = Date.now();
|
|
7369
|
-
|
|
7426
|
+
log22.debug(TAG20, `Progress: ${this.progress}% — ${currentTask}`);
|
|
7370
7427
|
this.client.updateAgentProgress(this.cardId, {
|
|
7371
7428
|
agentIdentifier: this.sessionIdentifier,
|
|
7372
7429
|
agentName: AGENT_NAME,
|
|
@@ -7383,7 +7440,7 @@ class ProgressTracker {
|
|
|
7383
7440
|
modelName: this.lastCost?.modelName ?? this.requestedModel ?? undefined,
|
|
7384
7441
|
numTurns: this.lastCost?.numTurns ?? 0
|
|
7385
7442
|
}).catch((err) => {
|
|
7386
|
-
|
|
7443
|
+
log22.warn(TAG20, `Failed to send progress update: ${err}`);
|
|
7387
7444
|
});
|
|
7388
7445
|
if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
|
|
7389
7446
|
this.lastEmittedProgress = this.progress;
|
|
@@ -7414,7 +7471,7 @@ class ProgressTracker {
|
|
|
7414
7471
|
return null;
|
|
7415
7472
|
}
|
|
7416
7473
|
}
|
|
7417
|
-
var
|
|
7474
|
+
var TAG20 = "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;
|
|
7418
7475
|
var init_progress_tracker = __esm(() => {
|
|
7419
7476
|
init_types();
|
|
7420
7477
|
SENTENCE_SPLIT = /\.\s|\n/;
|
|
@@ -7448,7 +7505,7 @@ var init_progress_tracker = __esm(() => {
|
|
|
7448
7505
|
});
|
|
7449
7506
|
|
|
7450
7507
|
// src/prompt.ts
|
|
7451
|
-
import { log as
|
|
7508
|
+
import { log as log23 } from "@gethmy/harness";
|
|
7452
7509
|
import {
|
|
7453
7510
|
buildMemoryQuery,
|
|
7454
7511
|
RERANK_MAX_CANDIDATES,
|
|
@@ -7499,11 +7556,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
7499
7556
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
7500
7557
|
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.`
|
|
7501
7558
|
});
|
|
7502
|
-
|
|
7559
|
+
log23.info(TAG21, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
7503
7560
|
return result.prompt + referenceSection;
|
|
7504
7561
|
} catch (err) {
|
|
7505
7562
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7506
|
-
|
|
7563
|
+
log23.warn(TAG21, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
7507
7564
|
onPromptFallback?.();
|
|
7508
7565
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
7509
7566
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + referenceSection;
|
|
@@ -7525,7 +7582,7 @@ ${untrustedDataBlock(section, {
|
|
|
7525
7582
|
purpose: "discussion to take into account"
|
|
7526
7583
|
})}` : "";
|
|
7527
7584
|
} catch (err) {
|
|
7528
|
-
|
|
7585
|
+
log23.warn(TAG21, "comment-thread fetch failed", {
|
|
7529
7586
|
event: "comment_fetch_failed",
|
|
7530
7587
|
error: err instanceof Error ? err.message : String(err)
|
|
7531
7588
|
});
|
|
@@ -7567,7 +7624,7 @@ ${bullets}`, {
|
|
|
7567
7624
|
})}`;
|
|
7568
7625
|
} catch (err) {
|
|
7569
7626
|
const error = err instanceof Error ? err.message : String(err);
|
|
7570
|
-
|
|
7627
|
+
log23.warn(TAG21, "reference recall failed", {
|
|
7571
7628
|
event: "reference_recall_failed",
|
|
7572
7629
|
error
|
|
7573
7630
|
});
|
|
@@ -7609,7 +7666,7 @@ ${subtaskStr}
|
|
|
7609
7666
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
7610
7667
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
7611
7668
|
}
|
|
7612
|
-
var
|
|
7669
|
+
var TAG21 = "prompt", RESUME_CONTINUATION = `Continue from where you stopped.
|
|
7613
7670
|
|
|
7614
7671
|
This is the same session. The task you were given, the work you have already done,
|
|
7615
7672
|
and everything you read are above in this conversation. None of it has changed, and
|
|
@@ -7634,7 +7691,7 @@ import {
|
|
|
7634
7691
|
extractPrUrl as extractPrUrl3,
|
|
7635
7692
|
getBranchWebUrl as getBranchWebUrl2,
|
|
7636
7693
|
getHeadSha,
|
|
7637
|
-
log as
|
|
7694
|
+
log as log24,
|
|
7638
7695
|
pushBranch as pushBranch2,
|
|
7639
7696
|
renameRemoteBranch,
|
|
7640
7697
|
sanitizeCiText as sanitizeCiText2,
|
|
@@ -7753,6 +7810,20 @@ function extractResult(parsed) {
|
|
|
7753
7810
|
findings
|
|
7754
7811
|
};
|
|
7755
7812
|
}
|
|
7813
|
+
function parseStructuredReview(structured) {
|
|
7814
|
+
try {
|
|
7815
|
+
const parsed = JSON.parse(structured);
|
|
7816
|
+
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
7817
|
+
return extractResult(parsed);
|
|
7818
|
+
}
|
|
7819
|
+
} catch {}
|
|
7820
|
+
log24.warn(TAG22, "Review spawn returned no schema-valid verdict — returning error verdict (card stays in Review)");
|
|
7821
|
+
return {
|
|
7822
|
+
verdict: "error",
|
|
7823
|
+
summary: structured.slice(0, 500),
|
|
7824
|
+
findings: []
|
|
7825
|
+
};
|
|
7826
|
+
}
|
|
7756
7827
|
function parseReviewOutput(stdout) {
|
|
7757
7828
|
const fencedBlocks = [...stdout.matchAll(/```json\s*([\s\S]*?)```/g)];
|
|
7758
7829
|
for (let i = fencedBlocks.length - 1;i >= 0; i--) {
|
|
@@ -7760,7 +7831,7 @@ function parseReviewOutput(stdout) {
|
|
|
7760
7831
|
try {
|
|
7761
7832
|
const parsed = JSON.parse(raw);
|
|
7762
7833
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
7763
|
-
|
|
7834
|
+
log24.debug(TAG22, "Parsed review output from fenced JSON block");
|
|
7764
7835
|
return extractResult(parsed);
|
|
7765
7836
|
}
|
|
7766
7837
|
} catch {}
|
|
@@ -7786,21 +7857,21 @@ function parseReviewOutput(stdout) {
|
|
|
7786
7857
|
try {
|
|
7787
7858
|
const parsed = JSON.parse(candidates[i]);
|
|
7788
7859
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
7789
|
-
|
|
7860
|
+
log24.debug(TAG22, "Parsed review output from raw JSON object");
|
|
7790
7861
|
return extractResult(parsed);
|
|
7791
7862
|
}
|
|
7792
7863
|
} catch {}
|
|
7793
7864
|
}
|
|
7794
7865
|
const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
|
|
7795
7866
|
if (verdictMatch) {
|
|
7796
|
-
|
|
7867
|
+
log24.warn(TAG22, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
|
|
7797
7868
|
return {
|
|
7798
7869
|
verdict: verdictMatch[1].toLowerCase(),
|
|
7799
7870
|
summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
|
|
7800
7871
|
findings: []
|
|
7801
7872
|
};
|
|
7802
7873
|
}
|
|
7803
|
-
|
|
7874
|
+
log24.warn(TAG22, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
|
|
7804
7875
|
return {
|
|
7805
7876
|
verdict: "error",
|
|
7806
7877
|
summary: stdout.slice(0, 500),
|
|
@@ -7836,7 +7907,7 @@ async function postReviewComment(client, card, commentType, body, agentSessionId
|
|
|
7836
7907
|
agentSessionId: agentSessionId ?? undefined
|
|
7837
7908
|
});
|
|
7838
7909
|
} catch (err) {
|
|
7839
|
-
|
|
7910
|
+
log24.error(TAG22, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7840
7911
|
}
|
|
7841
7912
|
}
|
|
7842
7913
|
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, agentSessionId, stateStore, agentId, resolvedFromPrUrl, visualLensSkipReason) {
|
|
@@ -7860,7 +7931,7 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
7860
7931
|
};
|
|
7861
7932
|
}
|
|
7862
7933
|
if (!handbackVerdict.proceed) {
|
|
7863
|
-
|
|
7934
|
+
log24.info(TAG22, `#${card.short_id}: review outcome will not move the card — ${handbackVerdict.detail} (${handbackVerdict.reason})`);
|
|
7864
7935
|
}
|
|
7865
7936
|
const lensLine = visualLensSkipReason ? `Visual QA did not run — ${sanitizeCiText2(visualLensSkipReason, REVIEW_FAILURE_REASON_MAX)}. Anything that needs a rendered page is ungraded.` : "";
|
|
7866
7937
|
const LOOPING_REFUSALS = new Set(["unreadable", "released", "done"]);
|
|
@@ -7870,19 +7941,19 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
7870
7941
|
}
|
|
7871
7942
|
try {
|
|
7872
7943
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
7873
|
-
|
|
7944
|
+
log24.warn(TAG22, `#${card.short_id} labelled "${NEED_REVIEW_LABEL}" (${handbackVerdict.reason}) so the daemon stops re-claiming and re-reviewing it`);
|
|
7874
7945
|
} catch (err) {
|
|
7875
|
-
|
|
7946
|
+
log24.warn(TAG22, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
7876
7947
|
}
|
|
7877
7948
|
}
|
|
7878
7949
|
const currentCycle = getReviewCycle(freshDesc) + 1;
|
|
7879
7950
|
const maxCycles = config.review.maxReviewCycles;
|
|
7880
7951
|
if (result.verdict === "error") {
|
|
7881
|
-
|
|
7952
|
+
log24.warn(TAG22, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
|
|
7882
7953
|
try {
|
|
7883
7954
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
7884
7955
|
} catch (err) {
|
|
7885
|
-
|
|
7956
|
+
log24.warn(TAG22, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
7886
7957
|
}
|
|
7887
7958
|
if (config.review.postFindings) {
|
|
7888
7959
|
const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
|
|
@@ -7925,12 +7996,21 @@ ${runLogTail}
|
|
|
7925
7996
|
renameRemoteBranch(branchName, newRef, worktreePath);
|
|
7926
7997
|
approvedBranch = newRef;
|
|
7927
7998
|
} catch (err) {
|
|
7928
|
-
|
|
7999
|
+
log24.warn(TAG22, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
|
|
7929
8000
|
}
|
|
7930
8001
|
}
|
|
7931
8002
|
if (config.review.createPR && approvedBranch) {
|
|
7932
8003
|
const provider = detectGitProvider4(worktreePath);
|
|
7933
8004
|
prUrl = createPullRequest2(card, approvedBranch, worktreePath, config, provider, resolvedFromPrUrl ?? null);
|
|
8005
|
+
if (prUrl) {
|
|
8006
|
+
await syncPullRequestState({
|
|
8007
|
+
client,
|
|
8008
|
+
cardId: card.id,
|
|
8009
|
+
prUrl,
|
|
8010
|
+
cwd: worktreePath,
|
|
8011
|
+
provider
|
|
8012
|
+
});
|
|
8013
|
+
}
|
|
7934
8014
|
}
|
|
7935
8015
|
}
|
|
7936
8016
|
await addLabelByName(client, card, config.review.approvedLabel, config.review.approvedLabelColor);
|
|
@@ -7958,14 +8038,14 @@ ${runLogTail}
|
|
|
7958
8038
|
await client.updateCard(card.id, { description: desc });
|
|
7959
8039
|
}
|
|
7960
8040
|
} catch (err) {
|
|
7961
|
-
|
|
8041
|
+
log24.warn(TAG22, `Failed to persist PR URL / branch rename to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
|
|
7962
8042
|
}
|
|
7963
8043
|
}
|
|
7964
8044
|
if (branchName) {
|
|
7965
8045
|
try {
|
|
7966
8046
|
await persistReviewedSha(client, card, worktreePath);
|
|
7967
8047
|
} catch (err) {
|
|
7968
|
-
|
|
8048
|
+
log24.warn(TAG22, `Failed to persist Reviewed-SHA to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
7969
8049
|
}
|
|
7970
8050
|
}
|
|
7971
8051
|
if (config.review.postFindings) {
|
|
@@ -7988,7 +8068,7 @@ ${runLogTail}
|
|
|
7988
8068
|
progressPercent: 100,
|
|
7989
8069
|
...buildTokenPayload(sessionStats)
|
|
7990
8070
|
});
|
|
7991
|
-
|
|
8071
|
+
log24.info(TAG22, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
|
|
7992
8072
|
} else {
|
|
7993
8073
|
const reworkFindings = result.findings.filter((f) => f.relatedToDiff !== false);
|
|
7994
8074
|
const criticalFindings = reworkFindings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
|
|
@@ -7996,7 +8076,7 @@ ${runLogTail}
|
|
|
7996
8076
|
const linkedFindings = [...criticalFindings, ...majorFindings];
|
|
7997
8077
|
const minorFindings = reworkFindings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
|
|
7998
8078
|
if (currentCycle >= maxCycles) {
|
|
7999
|
-
|
|
8079
|
+
log24.warn(TAG22, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
|
|
8000
8080
|
if (handbackVerdict.proceed) {
|
|
8001
8081
|
await moveCardToColumn(client, freshCard ?? card, config.review.moveToColumn);
|
|
8002
8082
|
} else {
|
|
@@ -8024,7 +8104,7 @@ ${runLogTail}
|
|
|
8024
8104
|
try {
|
|
8025
8105
|
await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
|
|
8026
8106
|
} catch (err) {
|
|
8027
|
-
|
|
8107
|
+
log24.error(TAG22, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
|
|
8028
8108
|
}
|
|
8029
8109
|
}));
|
|
8030
8110
|
if (linkedFindings.length > 0) {
|
|
@@ -8036,7 +8116,7 @@ ${runLogTail}
|
|
|
8036
8116
|
try {
|
|
8037
8117
|
await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
|
|
8038
8118
|
} catch (err) {
|
|
8039
|
-
|
|
8119
|
+
log24.error(TAG22, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
8040
8120
|
}
|
|
8041
8121
|
}));
|
|
8042
8122
|
if (handbackVerdict.proceed) {
|
|
@@ -8045,7 +8125,7 @@ ${runLogTail}
|
|
|
8045
8125
|
try {
|
|
8046
8126
|
await client.updateCard(card.id, { description: updatedDesc });
|
|
8047
8127
|
} catch (err) {
|
|
8048
|
-
|
|
8128
|
+
log24.error(TAG22, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
|
|
8049
8129
|
}
|
|
8050
8130
|
}
|
|
8051
8131
|
const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
|
|
@@ -8064,9 +8144,9 @@ ${runLogTail}
|
|
|
8064
8144
|
if (handbackVerdict.proceed && config.planning.enabled && card.plan_id) {
|
|
8065
8145
|
try {
|
|
8066
8146
|
await client.updateCard(card.id, { needsPlanRefresh: true });
|
|
8067
|
-
|
|
8147
|
+
log24.info(TAG22, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
|
|
8068
8148
|
} catch (err) {
|
|
8069
|
-
|
|
8149
|
+
log24.warn(TAG22, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
8070
8150
|
}
|
|
8071
8151
|
}
|
|
8072
8152
|
if (handbackVerdict.proceed) {
|
|
@@ -8084,10 +8164,10 @@ ${runLogTail}
|
|
|
8084
8164
|
recoveryBranch
|
|
8085
8165
|
});
|
|
8086
8166
|
} catch (err) {
|
|
8087
|
-
|
|
8167
|
+
log24.debug(TAG22, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
8088
8168
|
}
|
|
8089
8169
|
if (recoveryBranch) {
|
|
8090
|
-
|
|
8170
|
+
log24.info(TAG22, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
|
|
8091
8171
|
}
|
|
8092
8172
|
await client.endAgentSession(card.id, {
|
|
8093
8173
|
status: "failed",
|
|
@@ -8096,19 +8176,20 @@ ${runLogTail}
|
|
|
8096
8176
|
recoveryBranch,
|
|
8097
8177
|
...buildTokenPayload(sessionStats)
|
|
8098
8178
|
});
|
|
8099
|
-
|
|
8179
|
+
log24.info(TAG22, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
|
|
8100
8180
|
}
|
|
8101
8181
|
if (branchName) {
|
|
8102
8182
|
cleanupWorktree3(worktreePath, branchName);
|
|
8103
8183
|
}
|
|
8104
8184
|
}
|
|
8105
|
-
var
|
|
8185
|
+
var TAG22 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
|
|
8106
8186
|
**Review:`, RUN_LOG_TAIL_BYTES = 2048;
|
|
8107
8187
|
var init_review_completion = __esm(() => {
|
|
8108
8188
|
init_src();
|
|
8109
8189
|
init_board_helpers();
|
|
8110
8190
|
init_completion();
|
|
8111
8191
|
init_handback();
|
|
8192
|
+
init_pr_state_sync();
|
|
8112
8193
|
init_types();
|
|
8113
8194
|
});
|
|
8114
8195
|
|
|
@@ -8250,7 +8331,7 @@ Set \`relatedToDiff\` on every finding — only diff-caused findings gate the ve
|
|
|
8250
8331
|
${step3Body}
|
|
8251
8332
|
|
|
8252
8333
|
### Step 4: Output
|
|
8253
|
-
After completing all steps,
|
|
8334
|
+
After completing all steps, answer with the structured verdict. Its fields:
|
|
8254
8335
|
|
|
8255
8336
|
\`\`\`json
|
|
8256
8337
|
${REVIEW_VERDICT_SCHEMA}
|
|
@@ -8291,9 +8372,9 @@ ${serverSection}
|
|
|
8291
8372
|
## Finish with the verdict
|
|
8292
8373
|
|
|
8293
8374
|
Only this turn's output is read. A verdict you already wrote earlier in this session
|
|
8294
|
-
does NOT count — you must
|
|
8375
|
+
does NOT count — you must answer with the structured verdict again here, or this review produces nothing.
|
|
8295
8376
|
|
|
8296
|
-
When the review is complete,
|
|
8377
|
+
When the review is complete, answer with the structured verdict. Its fields:
|
|
8297
8378
|
|
|
8298
8379
|
\`\`\`json
|
|
8299
8380
|
${REVIEW_VERDICT_SCHEMA}
|
|
@@ -8325,6 +8406,61 @@ var init_review_prompt = __esm(() => {
|
|
|
8325
8406
|
init_types();
|
|
8326
8407
|
});
|
|
8327
8408
|
|
|
8409
|
+
// src/review-verdict-schema.ts
|
|
8410
|
+
var REVIEW_VERDICT_JSON_SCHEMA;
|
|
8411
|
+
var init_review_verdict_schema = __esm(() => {
|
|
8412
|
+
REVIEW_VERDICT_JSON_SCHEMA = {
|
|
8413
|
+
type: "object",
|
|
8414
|
+
properties: {
|
|
8415
|
+
verdict: { type: "string", enum: ["approved", "rejected"] },
|
|
8416
|
+
summary: { type: "string" },
|
|
8417
|
+
scopeCheck: {
|
|
8418
|
+
type: "object",
|
|
8419
|
+
properties: {
|
|
8420
|
+
status: { type: "string", enum: ["clean", "drift", "missing"] },
|
|
8421
|
+
notes: { type: "string" }
|
|
8422
|
+
},
|
|
8423
|
+
required: ["status"],
|
|
8424
|
+
additionalProperties: false
|
|
8425
|
+
},
|
|
8426
|
+
acceptanceChecks: {
|
|
8427
|
+
type: "array",
|
|
8428
|
+
items: {
|
|
8429
|
+
type: "object",
|
|
8430
|
+
properties: {
|
|
8431
|
+
criterion: { type: "string" },
|
|
8432
|
+
status: {
|
|
8433
|
+
type: "string",
|
|
8434
|
+
enum: ["pass", "partial", "fail", "unverifiable"]
|
|
8435
|
+
},
|
|
8436
|
+
evidence: { type: "string" }
|
|
8437
|
+
},
|
|
8438
|
+
required: ["criterion", "status"],
|
|
8439
|
+
additionalProperties: false
|
|
8440
|
+
}
|
|
8441
|
+
},
|
|
8442
|
+
findings: {
|
|
8443
|
+
type: "array",
|
|
8444
|
+
items: {
|
|
8445
|
+
type: "object",
|
|
8446
|
+
properties: {
|
|
8447
|
+
severity: { type: "string", enum: ["critical", "major", "minor"] },
|
|
8448
|
+
category: { type: "string" },
|
|
8449
|
+
title: { type: "string" },
|
|
8450
|
+
description: { type: "string" },
|
|
8451
|
+
location: { type: "string" },
|
|
8452
|
+
relatedToDiff: { type: "boolean" }
|
|
8453
|
+
},
|
|
8454
|
+
required: ["severity", "title", "description", "relatedToDiff"],
|
|
8455
|
+
additionalProperties: false
|
|
8456
|
+
}
|
|
8457
|
+
}
|
|
8458
|
+
},
|
|
8459
|
+
required: ["verdict", "summary", "acceptanceChecks", "findings"],
|
|
8460
|
+
additionalProperties: false
|
|
8461
|
+
};
|
|
8462
|
+
});
|
|
8463
|
+
|
|
8328
8464
|
// src/run-log.ts
|
|
8329
8465
|
import {
|
|
8330
8466
|
appendFileSync,
|
|
@@ -8332,7 +8468,7 @@ import {
|
|
|
8332
8468
|
mkdirSync as mkdirSync3
|
|
8333
8469
|
} from "node:fs";
|
|
8334
8470
|
import { join as join5 } from "node:path";
|
|
8335
|
-
import { log as
|
|
8471
|
+
import { log as log25 } from "@gethmy/harness";
|
|
8336
8472
|
function runLogPath(runId, shortId) {
|
|
8337
8473
|
return join5(runsDir(), `${runId}-card-${shortId}.log`);
|
|
8338
8474
|
}
|
|
@@ -8346,7 +8482,7 @@ function appendRunLog(tag, runId, shortId, line) {
|
|
|
8346
8482
|
`, "utf-8");
|
|
8347
8483
|
return path;
|
|
8348
8484
|
} catch (err) {
|
|
8349
|
-
|
|
8485
|
+
log25.warn(tag, `Failed to append to run log: ${err instanceof Error ? err.message : err}`);
|
|
8350
8486
|
return null;
|
|
8351
8487
|
}
|
|
8352
8488
|
}
|
|
@@ -8358,11 +8494,11 @@ function openRunLog(tag, runId, shortId) {
|
|
|
8358
8494
|
const path = runLogPath(runId, shortId);
|
|
8359
8495
|
const stream = createWriteStream(path, { flags: "a" });
|
|
8360
8496
|
stream.on("error", (err) => {
|
|
8361
|
-
|
|
8497
|
+
log25.warn(tag, `Run log ${path} failed: ${err.message}`);
|
|
8362
8498
|
});
|
|
8363
8499
|
return { path, stream };
|
|
8364
8500
|
} catch (err) {
|
|
8365
|
-
|
|
8501
|
+
log25.warn(tag, `Failed to open run log: ${err instanceof Error ? err.message : err}`);
|
|
8366
8502
|
return null;
|
|
8367
8503
|
}
|
|
8368
8504
|
}
|
|
@@ -8399,7 +8535,7 @@ function isOwnNoticeConflict(err) {
|
|
|
8399
8535
|
const driver = sessionConflictDriver(err);
|
|
8400
8536
|
return driver === null || driver === NOTICE_DRIVER;
|
|
8401
8537
|
}
|
|
8402
|
-
async function startSessionYieldingNotice(start, client, cardId,
|
|
8538
|
+
async function startSessionYieldingNotice(start, client, cardId, log26) {
|
|
8403
8539
|
try {
|
|
8404
8540
|
return await start();
|
|
8405
8541
|
} catch (err) {
|
|
@@ -8408,12 +8544,12 @@ async function startSessionYieldingNotice(start, client, cardId, log25) {
|
|
|
8408
8544
|
try {
|
|
8409
8545
|
const { session } = await client.getAgentSession(cardId);
|
|
8410
8546
|
if (!session) {
|
|
8411
|
-
|
|
8547
|
+
log26.info(`The session that refused the start is already gone — retrying: ${err instanceof Error ? err.message : String(err)}`);
|
|
8412
8548
|
} else if (!isPoolNotice(session)) {
|
|
8413
|
-
|
|
8549
|
+
log26.warn("The session that refused the start is not a pool notice on a re-read — leaving it alone");
|
|
8414
8550
|
throw err;
|
|
8415
8551
|
} else {
|
|
8416
|
-
|
|
8552
|
+
log26.info(`Ending this pool's own notice on the card and retrying the session start: ${err instanceof Error ? err.message : String(err)}`);
|
|
8417
8553
|
await client.endAgentSession(cardId, {
|
|
8418
8554
|
status: "failed",
|
|
8419
8555
|
failureReason: "stale",
|
|
@@ -8423,7 +8559,7 @@ async function startSessionYieldingNotice(start, client, cardId, log25) {
|
|
|
8423
8559
|
} catch (endErr) {
|
|
8424
8560
|
if (endErr === err)
|
|
8425
8561
|
throw err;
|
|
8426
|
-
|
|
8562
|
+
log26.warn(`Could not yield this pool's own notice: ${endErr instanceof Error ? endErr.message : String(endErr)}`);
|
|
8427
8563
|
throw err;
|
|
8428
8564
|
}
|
|
8429
8565
|
return await start();
|
|
@@ -8460,7 +8596,7 @@ import {
|
|
|
8460
8596
|
writeFileSync as writeFileSync2
|
|
8461
8597
|
} from "node:fs";
|
|
8462
8598
|
import { dirname as dirname2, join as join6 } from "node:path";
|
|
8463
|
-
import { log as
|
|
8599
|
+
import { log as log26 } from "@gethmy/harness";
|
|
8464
8600
|
function emptyTokens() {
|
|
8465
8601
|
return cloneWithoutPrototype({
|
|
8466
8602
|
inputTokens: 0,
|
|
@@ -8599,7 +8735,7 @@ class StateStore {
|
|
|
8599
8735
|
const raw = readFileSync6(this.path, "utf-8");
|
|
8600
8736
|
const parsed = parseJsonWithoutPrototypes(raw);
|
|
8601
8737
|
if (parsed?.version !== SCHEMA_VERSION) {
|
|
8602
|
-
|
|
8738
|
+
log26.warn(TAG23, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — migrating (preserving card budget/attempts, dropping in-flight runs)`);
|
|
8603
8739
|
return cloneWithoutPrototype({
|
|
8604
8740
|
version: SCHEMA_VERSION,
|
|
8605
8741
|
daemonId: null,
|
|
@@ -8624,7 +8760,7 @@ class StateStore {
|
|
|
8624
8760
|
account: parsed.account ?? emptyAccount()
|
|
8625
8761
|
});
|
|
8626
8762
|
} catch (err) {
|
|
8627
|
-
|
|
8763
|
+
log26.error(TAG23, `failed to read state file: ${err instanceof Error ? err.message : err}`);
|
|
8628
8764
|
return emptyState();
|
|
8629
8765
|
}
|
|
8630
8766
|
}
|
|
@@ -9021,7 +9157,7 @@ class StateStore {
|
|
|
9021
9157
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
9022
9158
|
}
|
|
9023
9159
|
}
|
|
9024
|
-
var
|
|
9160
|
+
var TAG23 = "state-store", SCHEMA_VERSION = 1;
|
|
9025
9161
|
var init_state_store = __esm(() => {
|
|
9026
9162
|
init_src();
|
|
9027
9163
|
init_paths();
|
|
@@ -9029,7 +9165,7 @@ var init_state_store = __esm(() => {
|
|
|
9029
9165
|
|
|
9030
9166
|
// src/stream-parser.ts
|
|
9031
9167
|
import { EventEmitter } from "node:events";
|
|
9032
|
-
import { log as
|
|
9168
|
+
import { log as log27 } from "@gethmy/harness";
|
|
9033
9169
|
function normalizeToolResultContent(raw) {
|
|
9034
9170
|
if (raw == null)
|
|
9035
9171
|
return;
|
|
@@ -9050,7 +9186,7 @@ function normalizeToolResultContent(raw) {
|
|
|
9050
9186
|
return String(raw);
|
|
9051
9187
|
}
|
|
9052
9188
|
}
|
|
9053
|
-
var
|
|
9189
|
+
var TAG24 = "stream-parser", StreamParser;
|
|
9054
9190
|
var init_stream_parser = __esm(() => {
|
|
9055
9191
|
StreamParser = class StreamParser extends EventEmitter {
|
|
9056
9192
|
buffer = "";
|
|
@@ -9097,14 +9233,14 @@ var init_stream_parser = __esm(() => {
|
|
|
9097
9233
|
try {
|
|
9098
9234
|
msg = JSON.parse(line);
|
|
9099
9235
|
} catch {
|
|
9100
|
-
|
|
9236
|
+
log27.debug(TAG24, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
9101
9237
|
return;
|
|
9102
9238
|
}
|
|
9103
9239
|
try {
|
|
9104
9240
|
this.handleMessage(msg);
|
|
9105
9241
|
} catch (err) {
|
|
9106
9242
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9107
|
-
|
|
9243
|
+
log27.warn(TAG24, `Error handling stream event: ${errMsg}`);
|
|
9108
9244
|
this.emit("parse_error", errMsg);
|
|
9109
9245
|
}
|
|
9110
9246
|
}
|
|
@@ -9171,6 +9307,9 @@ var init_stream_parser = __esm(() => {
|
|
|
9171
9307
|
modelName: this.observedModel ?? (typeof msg.model === "string" ? msg.model : undefined)
|
|
9172
9308
|
});
|
|
9173
9309
|
}
|
|
9310
|
+
if (msg.structured_output !== undefined) {
|
|
9311
|
+
this.emit("structured_output", msg.structured_output);
|
|
9312
|
+
}
|
|
9174
9313
|
this.emit("result", msg.stop_reason ?? msg.subtype ?? "unknown");
|
|
9175
9314
|
break;
|
|
9176
9315
|
}
|
|
@@ -9180,7 +9319,7 @@ var init_stream_parser = __esm(() => {
|
|
|
9180
9319
|
});
|
|
9181
9320
|
|
|
9182
9321
|
// src/transitions.ts
|
|
9183
|
-
import { log as
|
|
9322
|
+
import { log as log28 } from "@gethmy/harness";
|
|
9184
9323
|
async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
9185
9324
|
let lastErr;
|
|
9186
9325
|
for (let i = 0;i < attempts; i++) {
|
|
@@ -9191,7 +9330,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
9191
9330
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
9192
9331
|
if (i < attempts - 1) {
|
|
9193
9332
|
const wait = backoffMs * 2 ** i;
|
|
9194
|
-
|
|
9333
|
+
log28.warn(TAG25, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
9195
9334
|
await new Promise((r) => setTimeout(r, wait));
|
|
9196
9335
|
}
|
|
9197
9336
|
}
|
|
@@ -9215,10 +9354,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9215
9354
|
if (opts.strictColumn) {
|
|
9216
9355
|
throw new TransitionError("move", 1, msg);
|
|
9217
9356
|
}
|
|
9218
|
-
|
|
9357
|
+
log28.warn(TAG25, `#${shortId}: ${msg} — skipping move`);
|
|
9219
9358
|
} else if (card.column_id !== target.id) {
|
|
9220
9359
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
9221
|
-
|
|
9360
|
+
log28.info(TAG25, `#${shortId} → "${target.name}"`);
|
|
9222
9361
|
card.column_id = target.id;
|
|
9223
9362
|
moveLanded = true;
|
|
9224
9363
|
} else {
|
|
@@ -9237,7 +9376,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9237
9376
|
continue;
|
|
9238
9377
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
9239
9378
|
existing.add(labelId);
|
|
9240
|
-
|
|
9379
|
+
log28.info(TAG25, `#${shortId} +label "${name}"`);
|
|
9241
9380
|
}
|
|
9242
9381
|
card.labelIds = Array.from(existing);
|
|
9243
9382
|
}
|
|
@@ -9249,23 +9388,23 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
9249
9388
|
continue;
|
|
9250
9389
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
9251
9390
|
existing.delete(match.id);
|
|
9252
|
-
|
|
9391
|
+
log28.info(TAG25, `#${shortId} -label "${name}"`);
|
|
9253
9392
|
}
|
|
9254
9393
|
card.labelIds = Array.from(existing);
|
|
9255
9394
|
}
|
|
9256
9395
|
if (plan.updateCard) {
|
|
9257
9396
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
9258
|
-
|
|
9397
|
+
log28.info(TAG25, `#${shortId} updated`);
|
|
9259
9398
|
}
|
|
9260
9399
|
if (plan.endSession) {
|
|
9261
9400
|
const endResult = await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
9262
9401
|
result.endSession = endResult;
|
|
9263
|
-
|
|
9402
|
+
log28.info(TAG25, `#${shortId} session ended (${plan.endSession.status})`);
|
|
9264
9403
|
}
|
|
9265
9404
|
if (plan.assignAgent !== undefined) {
|
|
9266
9405
|
const assignedAgentId = plan.assignAgent;
|
|
9267
9406
|
await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
|
|
9268
|
-
|
|
9407
|
+
log28.info(TAG25, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
|
|
9269
9408
|
}
|
|
9270
9409
|
if (opts.store && opts.runId) {
|
|
9271
9410
|
try {
|
|
@@ -9279,11 +9418,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
9279
9418
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
9280
9419
|
return result?.label?.id ?? null;
|
|
9281
9420
|
} catch (err) {
|
|
9282
|
-
|
|
9421
|
+
log28.warn(TAG25, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
9283
9422
|
return null;
|
|
9284
9423
|
}
|
|
9285
9424
|
}
|
|
9286
|
-
var
|
|
9425
|
+
var TAG25 = "transition", TransitionError;
|
|
9287
9426
|
var init_transitions = __esm(() => {
|
|
9288
9427
|
TransitionError = class TransitionError extends Error {
|
|
9289
9428
|
step;
|
|
@@ -9313,7 +9452,7 @@ import {
|
|
|
9313
9452
|
formatDiffSummary,
|
|
9314
9453
|
GIT_NO_HOOKS as GIT_NO_HOOKS7,
|
|
9315
9454
|
implementRunContainmentCliArgs,
|
|
9316
|
-
log as
|
|
9455
|
+
log as log29,
|
|
9317
9456
|
probeDevServer,
|
|
9318
9457
|
removeSandboxContainer,
|
|
9319
9458
|
resolveStageGate,
|
|
@@ -9409,11 +9548,11 @@ class ReviewWorker {
|
|
|
9409
9548
|
cliSessionId: this.cliSessionId
|
|
9410
9549
|
});
|
|
9411
9550
|
} catch (err) {
|
|
9412
|
-
|
|
9551
|
+
log29.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
9413
9552
|
}
|
|
9414
9553
|
}
|
|
9415
9554
|
get tag() {
|
|
9416
|
-
return `${
|
|
9555
|
+
return `${TAG26}:${this.id}`;
|
|
9417
9556
|
}
|
|
9418
9557
|
get isIdle() {
|
|
9419
9558
|
return this.state === "idle";
|
|
@@ -9451,7 +9590,7 @@ class ReviewWorker {
|
|
|
9451
9590
|
try {
|
|
9452
9591
|
const after = await this.stateStore.addTurns(cardId, numTurns);
|
|
9453
9592
|
if (crossedTurnCap(before, after, cap)) {
|
|
9454
|
-
|
|
9593
|
+
log29.warn(TAG26, `#${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.`);
|
|
9455
9594
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
9456
9595
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), {
|
|
9457
9596
|
commentType: "blocker",
|
|
@@ -9493,12 +9632,12 @@ class ReviewWorker {
|
|
|
9493
9632
|
resumeMessage: null
|
|
9494
9633
|
});
|
|
9495
9634
|
} catch (err) {
|
|
9496
|
-
|
|
9635
|
+
log29.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
9497
9636
|
}
|
|
9498
9637
|
}
|
|
9499
9638
|
try {
|
|
9500
9639
|
this.state = "preparing";
|
|
9501
|
-
|
|
9640
|
+
log29.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}"`);
|
|
9502
9641
|
this.startHeartbeat();
|
|
9503
9642
|
if (!resuming) {
|
|
9504
9643
|
await this.stateStore.insertRun({
|
|
@@ -9526,7 +9665,7 @@ class ReviewWorker {
|
|
|
9526
9665
|
const resolution = await resolveReviewBranch(card.description, repoRoot);
|
|
9527
9666
|
if (resolution.kind !== "branch") {
|
|
9528
9667
|
const why = resolution.kind === "skip" ? resolution.reason : "no branch or PR reference";
|
|
9529
|
-
|
|
9668
|
+
log29.info(this.tag, `#${card.short_id} not auto-reviewable (${why}) — marking for human review (staying in Review)`);
|
|
9530
9669
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9531
9670
|
return;
|
|
9532
9671
|
}
|
|
@@ -9537,13 +9676,13 @@ class ReviewWorker {
|
|
|
9537
9676
|
reviewedSha: extractReviewedSha2(card.description ?? null)
|
|
9538
9677
|
});
|
|
9539
9678
|
if (continuity.kind === "skip") {
|
|
9540
|
-
|
|
9679
|
+
log29.warn(this.tag, `#${card.short_id} refusing the PR-derived re-review (${continuity.reason}) — marking for human review (staying in Review)`);
|
|
9541
9680
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9542
9681
|
return;
|
|
9543
9682
|
}
|
|
9544
9683
|
}
|
|
9545
9684
|
this.branchName = resolution.branch;
|
|
9546
|
-
|
|
9685
|
+
log29.info(this.tag, `Review branch: ${this.branchName}`);
|
|
9547
9686
|
let reviewSession;
|
|
9548
9687
|
try {
|
|
9549
9688
|
const startSession = () => this.client.startAgentSession(card.id, {
|
|
@@ -9559,14 +9698,14 @@ class ReviewWorker {
|
|
|
9559
9698
|
awaitingDecisionUntil: null
|
|
9560
9699
|
});
|
|
9561
9700
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
9562
|
-
info: (message) =>
|
|
9563
|
-
warn: (message) =>
|
|
9701
|
+
info: (message) => log29.info(this.tag, message),
|
|
9702
|
+
warn: (message) => log29.warn(this.tag, message)
|
|
9564
9703
|
});
|
|
9565
9704
|
reviewSession = started.session;
|
|
9566
9705
|
} catch (err) {
|
|
9567
9706
|
if (isSessionConflict(err)) {
|
|
9568
9707
|
this.sessionConflict = true;
|
|
9569
|
-
|
|
9708
|
+
log29.info(this.tag, `Skipping review of #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9570
9709
|
return;
|
|
9571
9710
|
}
|
|
9572
9711
|
throw err;
|
|
@@ -9587,7 +9726,7 @@ class ReviewWorker {
|
|
|
9587
9726
|
const cwd = this.worktreePath;
|
|
9588
9727
|
const devServer = await this.startDevServer(card, cwd, port);
|
|
9589
9728
|
if (devServer.skipReason) {
|
|
9590
|
-
|
|
9729
|
+
log29.warn(this.tag, `Reviewing #${card.short_id} without the visual lens — ${devServer.skipReason}`);
|
|
9591
9730
|
}
|
|
9592
9731
|
await this.client.updateAgentProgress(card.id, {
|
|
9593
9732
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9625,10 +9764,10 @@ class ReviewWorker {
|
|
|
9625
9764
|
pinnedContract = extractPinnedContract(comments, this.identity);
|
|
9626
9765
|
}
|
|
9627
9766
|
} catch (err) {
|
|
9628
|
-
|
|
9767
|
+
log29.warn(this.tag, `pinned-contract fetch failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9629
9768
|
}
|
|
9630
9769
|
if (pinnedContract) {
|
|
9631
|
-
|
|
9770
|
+
log29.info(this.tag, `Grading pinned contract for #${card.short_id} (${pinnedContract.assertions.length} criteria)`);
|
|
9632
9771
|
}
|
|
9633
9772
|
}
|
|
9634
9773
|
const systemPrompt = buildReviewSystemPrompt();
|
|
@@ -9643,7 +9782,7 @@ class ReviewWorker {
|
|
|
9643
9782
|
} else {
|
|
9644
9783
|
const planCriteria2 = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
9645
9784
|
if (planCriteria2.length > 0) {
|
|
9646
|
-
|
|
9785
|
+
log29.info(this.tag, `Grading ${planCriteria2.length} plan criteria for #${card.short_id}`);
|
|
9647
9786
|
}
|
|
9648
9787
|
userPrompt = buildReviewUserPrompt(enriched, this.branchName, cwd, previewUrl, diffSummary, this.config.worktree.baseBranch, undefined, pinnedContract, planCriteria2, devServer.skipReason);
|
|
9649
9788
|
if (resuming && this.resumeMessage) {
|
|
@@ -9660,7 +9799,7 @@ ${userPrompt}`;
|
|
|
9660
9799
|
contextIncluded: { source: "review-knowledge", mode: "review" }
|
|
9661
9800
|
});
|
|
9662
9801
|
} catch (err) {
|
|
9663
|
-
|
|
9802
|
+
log29.warn(this.tag, `prompt_history persistence skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
9664
9803
|
}
|
|
9665
9804
|
await this.client.updateAgentProgress(card.id, {
|
|
9666
9805
|
agentIdentifier: this.sessionIdentifier,
|
|
@@ -9670,7 +9809,7 @@ ${userPrompt}`;
|
|
|
9670
9809
|
progressPercent: 20
|
|
9671
9810
|
});
|
|
9672
9811
|
this.timeoutTimer = setTimeout(() => {
|
|
9673
|
-
|
|
9812
|
+
log29.warn(this.tag, `Review timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
9674
9813
|
this.timedOut = true;
|
|
9675
9814
|
this.cancel("timeout");
|
|
9676
9815
|
}, this.config.review.maxTimeout);
|
|
@@ -9692,10 +9831,10 @@ ${userPrompt}`;
|
|
|
9692
9831
|
}
|
|
9693
9832
|
this.state = "completing";
|
|
9694
9833
|
await this.recordPhase("completing");
|
|
9695
|
-
|
|
9834
|
+
log29.info(this.tag, `Claude review finished for #${card.short_id}`);
|
|
9696
9835
|
this.killDevServer();
|
|
9697
|
-
const result =
|
|
9698
|
-
|
|
9836
|
+
const result = parseStructuredReview(stdout);
|
|
9837
|
+
log29.info(this.tag, `Review verdict: ${result.verdict} (${result.findings.length} finding(s))`);
|
|
9699
9838
|
await this.client.updateAgentProgress(card.id, {
|
|
9700
9839
|
agentIdentifier: this.sessionIdentifier,
|
|
9701
9840
|
agentName: `${AGENT_NAME} (Review)`,
|
|
@@ -9717,7 +9856,7 @@ ${userPrompt}`;
|
|
|
9717
9856
|
this.state = "error";
|
|
9718
9857
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9719
9858
|
this.lastErrorMessage = msg;
|
|
9720
|
-
|
|
9859
|
+
log29.error(this.tag, `Error reviewing #${card.short_id}: ${msg}`);
|
|
9721
9860
|
this.lastRunLogPath = appendRunLog(this.tag, this.runId, card.short_id, `[${new Date().toISOString()}] review failed before completion: ${msg}`) ?? this.lastRunLogPath;
|
|
9722
9861
|
const reviewHandback = await guardedHandback(this.client, card.id, {
|
|
9723
9862
|
agentId: this.identity.agentId,
|
|
@@ -9733,7 +9872,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9733
9872
|
agentSessionId: this.sessionId ?? undefined
|
|
9734
9873
|
});
|
|
9735
9874
|
} catch (cErr) {
|
|
9736
|
-
|
|
9875
|
+
log29.warn(this.tag, `Failed to post the failure reason to #${card.short_id}: ${cErr instanceof Error ? cErr.message : cErr}`);
|
|
9737
9876
|
}
|
|
9738
9877
|
}
|
|
9739
9878
|
try {
|
|
@@ -9745,21 +9884,21 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9745
9884
|
}
|
|
9746
9885
|
});
|
|
9747
9886
|
} catch (tErr) {
|
|
9748
|
-
|
|
9887
|
+
log29.error(this.tag, `endAgentSession unrecoverable on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
9749
9888
|
}
|
|
9750
9889
|
if (reviewHandback.verdict.proceed) {
|
|
9751
9890
|
try {
|
|
9752
9891
|
await moveCardToColumn(this.client, reviewHandback.card ?? card, this.config.review.failColumn);
|
|
9753
|
-
|
|
9892
|
+
log29.info(this.tag, `Moved #${card.short_id} to "${this.config.review.failColumn}" after error`);
|
|
9754
9893
|
} catch {
|
|
9755
|
-
|
|
9894
|
+
log29.warn(this.tag, "Failed to move card to fail column after error");
|
|
9756
9895
|
}
|
|
9757
9896
|
} else if (reviewHandback.verdict.reason === "unreadable" || reviewHandback.verdict.reason === "released" || reviewHandback.verdict.reason === "done") {
|
|
9758
9897
|
try {
|
|
9759
9898
|
await addLabelByName(this.client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
9760
|
-
|
|
9899
|
+
log29.warn(this.tag, `#${card.short_id} could not be re-read — labelled "${NEED_REVIEW_LABEL}" so reconcile stops re-enqueueing the review`);
|
|
9761
9900
|
} catch {
|
|
9762
|
-
|
|
9901
|
+
log29.warn(this.tag, `Failed to add "${NEED_REVIEW_LABEL}" label after an unreadable card`);
|
|
9763
9902
|
}
|
|
9764
9903
|
}
|
|
9765
9904
|
if (this.runId) {
|
|
@@ -9803,7 +9942,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9803
9942
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
9804
9943
|
const waitHours = this.config.budget.pause.waitHours;
|
|
9805
9944
|
const until = computeDecisionDeadline(waitHours);
|
|
9806
|
-
|
|
9945
|
+
log29.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
9807
9946
|
try {
|
|
9808
9947
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
9809
9948
|
holderMessage,
|
|
@@ -9815,7 +9954,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9815
9954
|
agentSessionId: this.sessionId ?? undefined
|
|
9816
9955
|
});
|
|
9817
9956
|
} catch (commentErr) {
|
|
9818
|
-
|
|
9957
|
+
log29.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
9819
9958
|
}
|
|
9820
9959
|
if (this.runId) {
|
|
9821
9960
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -9826,7 +9965,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9826
9965
|
awaitingDecisionUntil: until
|
|
9827
9966
|
});
|
|
9828
9967
|
} catch (storeErr) {
|
|
9829
|
-
|
|
9968
|
+
log29.error(this.tag, `#${card.short_id} could not be re-parked after a resume conflict — the daemon has no hold on it: ${storeErr}`);
|
|
9830
9969
|
}
|
|
9831
9970
|
}
|
|
9832
9971
|
}
|
|
@@ -9839,7 +9978,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9839
9978
|
this.progressTracker = null;
|
|
9840
9979
|
const waitHours = this.config.budget.pause.waitHours;
|
|
9841
9980
|
const until = computeDecisionDeadline(waitHours);
|
|
9842
|
-
|
|
9981
|
+
log29.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
9843
9982
|
const body = formatBudgetComment({
|
|
9844
9983
|
trigger,
|
|
9845
9984
|
numTurns: stats?.cost?.numTurns ?? 0,
|
|
@@ -9861,7 +10000,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9861
10000
|
});
|
|
9862
10001
|
commentId = res?.comment?.id ?? null;
|
|
9863
10002
|
} catch (err) {
|
|
9864
|
-
|
|
10003
|
+
log29.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
9865
10004
|
}
|
|
9866
10005
|
try {
|
|
9867
10006
|
await this.client.updateAgentProgress(card.id, {
|
|
@@ -9872,7 +10011,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9872
10011
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
9873
10012
|
});
|
|
9874
10013
|
} catch (err) {
|
|
9875
|
-
|
|
10014
|
+
log29.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
9876
10015
|
}
|
|
9877
10016
|
if (this.runId) {
|
|
9878
10017
|
try {
|
|
@@ -9883,14 +10022,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9883
10022
|
...this.endLedger()
|
|
9884
10023
|
});
|
|
9885
10024
|
} catch (err) {
|
|
9886
|
-
|
|
10025
|
+
log29.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}`);
|
|
9887
10026
|
}
|
|
9888
10027
|
}
|
|
9889
10028
|
}
|
|
9890
10029
|
async pause() {
|
|
9891
10030
|
if (!this.isActive || !this.process || this.process.killed)
|
|
9892
10031
|
return;
|
|
9893
|
-
|
|
10032
|
+
log29.info(this.tag, `Pausing review on ${this.cardId}`);
|
|
9894
10033
|
signalGroup(this.process, "SIGSTOP");
|
|
9895
10034
|
if (this.timeoutTimer) {
|
|
9896
10035
|
clearTimeout(this.timeoutTimer);
|
|
@@ -9904,17 +10043,17 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9904
10043
|
status: "paused"
|
|
9905
10044
|
});
|
|
9906
10045
|
} catch {
|
|
9907
|
-
|
|
10046
|
+
log29.warn(this.tag, "Failed to update agent session to paused");
|
|
9908
10047
|
}
|
|
9909
10048
|
}
|
|
9910
10049
|
}
|
|
9911
10050
|
async resume() {
|
|
9912
10051
|
if (!this.isActive || !this.process || this.process.killed)
|
|
9913
10052
|
return;
|
|
9914
|
-
|
|
10053
|
+
log29.info(this.tag, `Resuming review on ${this.cardId}`);
|
|
9915
10054
|
signalGroup(this.process, "SIGCONT");
|
|
9916
10055
|
this.timeoutTimer = setTimeout(() => {
|
|
9917
|
-
|
|
10056
|
+
log29.warn(this.tag, `Timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
9918
10057
|
this.timedOut = true;
|
|
9919
10058
|
this.cancel("timeout");
|
|
9920
10059
|
}, this.config.review.maxTimeout);
|
|
@@ -9926,7 +10065,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9926
10065
|
status: "working"
|
|
9927
10066
|
});
|
|
9928
10067
|
} catch {
|
|
9929
|
-
|
|
10068
|
+
log29.warn(this.tag, "Failed to update agent session to working");
|
|
9930
10069
|
}
|
|
9931
10070
|
}
|
|
9932
10071
|
}
|
|
@@ -9935,7 +10074,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9935
10074
|
return;
|
|
9936
10075
|
this.aborted = true;
|
|
9937
10076
|
this.state = "cancelling";
|
|
9938
|
-
|
|
10077
|
+
log29.info(this.tag, `Cancelling review on ${this.cardId}`);
|
|
9939
10078
|
const snapshotStats = this.lastSessionStats ?? this.progressTracker?.stats;
|
|
9940
10079
|
if (this.progressTracker) {
|
|
9941
10080
|
this.progressTracker?.stop();
|
|
@@ -9976,6 +10115,8 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9976
10115
|
String(effectiveMaxTurns),
|
|
9977
10116
|
"--allowedTools",
|
|
9978
10117
|
"Bash(readonly),Read,Glob,Grep,Agent,mcp__harmony__*",
|
|
10118
|
+
"--json-schema",
|
|
10119
|
+
JSON.stringify(REVIEW_VERDICT_JSON_SCHEMA),
|
|
9979
10120
|
...opts.resumeSessionId ? ["--resume", opts.resumeSessionId] : [],
|
|
9980
10121
|
...systemPrompt ? ["--append-system-prompt", systemPrompt] : [],
|
|
9981
10122
|
...this.config.claude.additionalArgs,
|
|
@@ -9988,11 +10129,11 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
9988
10129
|
"--",
|
|
9989
10130
|
prompt
|
|
9990
10131
|
];
|
|
9991
|
-
|
|
10132
|
+
log29.info(this.tag, `Spawning review: claude ${args.slice(0, 5).join(" ")} ...`);
|
|
9992
10133
|
const runLog = openRunLog(this.tag, this.runId, shortId);
|
|
9993
10134
|
this.lastRunLogPath = runLog?.path ?? null;
|
|
9994
10135
|
if (runLog) {
|
|
9995
|
-
|
|
10136
|
+
log29.info(this.tag, `Run log: ${runLog.path}`);
|
|
9996
10137
|
runLog.stream.write(`# run=${this.runId} card=#${shortId} pipeline=review started=${new Date().toISOString()}
|
|
9997
10138
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
9998
10139
|
|
|
@@ -10006,12 +10147,16 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10006
10147
|
const parser = new StreamParser;
|
|
10007
10148
|
tracker.attach(parser);
|
|
10008
10149
|
const textChunks = [];
|
|
10150
|
+
let structuredOutput;
|
|
10151
|
+
parser.on("structured_output", (output) => {
|
|
10152
|
+
structuredOutput = output;
|
|
10153
|
+
});
|
|
10009
10154
|
parser.on("text", (content) => {
|
|
10010
10155
|
textChunks.push(content);
|
|
10011
10156
|
this.captureCliSessionId(parser.sessionId);
|
|
10012
10157
|
});
|
|
10013
10158
|
parser.on("parse_error", (msg) => {
|
|
10014
|
-
|
|
10159
|
+
log29.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
10015
10160
|
runLog?.stream.write(`
|
|
10016
10161
|
[parse_error] ${msg}
|
|
10017
10162
|
`);
|
|
@@ -10038,7 +10183,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10038
10183
|
this.process?.on("close", (code) => {
|
|
10039
10184
|
this.process = null;
|
|
10040
10185
|
this.captureCliSessionId(parser.sessionId);
|
|
10041
|
-
const stdout = textChunks.join("");
|
|
10186
|
+
const stdout = structuredOutput !== undefined ? JSON.stringify(structuredOutput) : textChunks.join("");
|
|
10042
10187
|
const stats = tracker.stats;
|
|
10043
10188
|
if (runLog) {
|
|
10044
10189
|
runLog.stream.write(`
|
|
@@ -10090,16 +10235,16 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10090
10235
|
const evidence = await collectGateEvidence(registry, context);
|
|
10091
10236
|
const evaluation = gateEvaluate(resolved.gate, evidence);
|
|
10092
10237
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, toStageGateEvidenceInsert(context, evidence));
|
|
10093
|
-
|
|
10238
|
+
log29.info(this.tag, `Recorded review_passed gate evidence for #${card.short_id} stage "${resolved.stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
10094
10239
|
} catch (err) {
|
|
10095
|
-
|
|
10240
|
+
log29.warn(this.tag, `review gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
10096
10241
|
}
|
|
10097
10242
|
}
|
|
10098
10243
|
async startDevServer(card, cwd, port) {
|
|
10099
10244
|
const declined = devServerSkipReason(cwd, this.config.commands);
|
|
10100
10245
|
if (declined)
|
|
10101
10246
|
return { previewUrl: null, skipReason: declined };
|
|
10102
|
-
|
|
10247
|
+
log29.info(this.tag, `Starting dev server on port ${port}...`);
|
|
10103
10248
|
const launch = devServerLaunch({
|
|
10104
10249
|
worktreePath: cwd,
|
|
10105
10250
|
port,
|
|
@@ -10140,14 +10285,14 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10140
10285
|
skipReason: launch.origin ? `${launch.origin}, and ${failure}` : failure
|
|
10141
10286
|
};
|
|
10142
10287
|
}
|
|
10143
|
-
|
|
10288
|
+
log29.info(this.tag, `Dev server ready on port ${port}`);
|
|
10144
10289
|
return { previewUrl: `http://localhost:${port}`, skipReason: null };
|
|
10145
10290
|
}
|
|
10146
10291
|
killDevServer() {
|
|
10147
10292
|
if (this.devServerProcess && !this.devServerProcess.killed) {
|
|
10148
10293
|
signalGroup(this.devServerProcess, "SIGTERM");
|
|
10149
10294
|
this.devServerProcess = null;
|
|
10150
|
-
|
|
10295
|
+
log29.debug(this.tag, "Killed dev server group");
|
|
10151
10296
|
}
|
|
10152
10297
|
if (this.devServerContainer) {
|
|
10153
10298
|
const name = this.devServerContainer;
|
|
@@ -10170,7 +10315,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10170
10315
|
try {
|
|
10171
10316
|
cleanupWorktree4(this.worktreePath);
|
|
10172
10317
|
} catch {
|
|
10173
|
-
|
|
10318
|
+
log29.warn(this.tag, "Failed to cleanup review worktree");
|
|
10174
10319
|
}
|
|
10175
10320
|
}
|
|
10176
10321
|
this.process = null;
|
|
@@ -10182,7 +10327,7 @@ No verdict was produced, so this card has not been reviewed. The run's log is at
|
|
|
10182
10327
|
this.lastSessionStats = null;
|
|
10183
10328
|
}
|
|
10184
10329
|
}
|
|
10185
|
-
var
|
|
10330
|
+
var TAG26 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
10186
10331
|
var init_review_worker = __esm(() => {
|
|
10187
10332
|
init_src();
|
|
10188
10333
|
init_board_helpers();
|
|
@@ -10195,6 +10340,7 @@ var init_review_worker = __esm(() => {
|
|
|
10195
10340
|
init_prompt();
|
|
10196
10341
|
init_review_completion();
|
|
10197
10342
|
init_review_prompt();
|
|
10343
|
+
init_review_verdict_schema();
|
|
10198
10344
|
init_review_worktree();
|
|
10199
10345
|
init_run_log();
|
|
10200
10346
|
init_session_conflict();
|
|
@@ -10206,7 +10352,7 @@ var init_review_worker = __esm(() => {
|
|
|
10206
10352
|
|
|
10207
10353
|
// src/sleep-guard.ts
|
|
10208
10354
|
import { spawn } from "node:child_process";
|
|
10209
|
-
import { log as
|
|
10355
|
+
import { log as log30 } from "@gethmy/harness";
|
|
10210
10356
|
|
|
10211
10357
|
class SleepGuard {
|
|
10212
10358
|
platform;
|
|
@@ -10234,7 +10380,7 @@ class SleepGuard {
|
|
|
10234
10380
|
if (!this.child.killed)
|
|
10235
10381
|
this.child.kill("SIGTERM");
|
|
10236
10382
|
this.child = null;
|
|
10237
|
-
|
|
10383
|
+
log30.info(TAG27, "sleep assertion released");
|
|
10238
10384
|
}
|
|
10239
10385
|
}
|
|
10240
10386
|
start() {
|
|
@@ -10249,7 +10395,7 @@ class SleepGuard {
|
|
|
10249
10395
|
spawned = true;
|
|
10250
10396
|
});
|
|
10251
10397
|
child.on("error", (err) => {
|
|
10252
|
-
|
|
10398
|
+
log30.warn(TAG27, `caffeinate unavailable: ${err.message}`);
|
|
10253
10399
|
if (this.child === child)
|
|
10254
10400
|
this.child = null;
|
|
10255
10401
|
});
|
|
@@ -10262,13 +10408,13 @@ class SleepGuard {
|
|
|
10262
10408
|
});
|
|
10263
10409
|
child.unref();
|
|
10264
10410
|
this.child = child;
|
|
10265
|
-
|
|
10411
|
+
log30.info(TAG27, "sleep assertion acquired (caffeinate -i)");
|
|
10266
10412
|
} catch (err) {
|
|
10267
|
-
|
|
10413
|
+
log30.warn(TAG27, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
10268
10414
|
}
|
|
10269
10415
|
}
|
|
10270
10416
|
}
|
|
10271
|
-
var
|
|
10417
|
+
var TAG27 = "sleep-guard";
|
|
10272
10418
|
var init_sleep_guard = () => {};
|
|
10273
10419
|
|
|
10274
10420
|
// src/stage-hold.ts
|
|
@@ -10293,13 +10439,13 @@ function stageHoldUntil(holdMinutes, nowMs) {
|
|
|
10293
10439
|
}
|
|
10294
10440
|
|
|
10295
10441
|
// src/unblock.ts
|
|
10296
|
-
import { log as
|
|
10442
|
+
import { log as log31 } from "@gethmy/harness";
|
|
10297
10443
|
async function fetchBlocksLinks(client, cardId) {
|
|
10298
10444
|
try {
|
|
10299
10445
|
const { links } = await client.getCardLinks(cardId);
|
|
10300
10446
|
return links.filter((l) => l.link_type === "blocks");
|
|
10301
10447
|
} catch (err) {
|
|
10302
|
-
|
|
10448
|
+
log31.warn(TAG28, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10303
10449
|
return null;
|
|
10304
10450
|
}
|
|
10305
10451
|
}
|
|
@@ -10333,31 +10479,31 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
10333
10479
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
10334
10480
|
if (successors.length === 0)
|
|
10335
10481
|
return;
|
|
10336
|
-
|
|
10482
|
+
log31.info(TAG28, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
10337
10483
|
for (const link of successors) {
|
|
10338
10484
|
const successorId = link.target_card.id;
|
|
10339
10485
|
try {
|
|
10340
10486
|
const { card } = await deps.client.getCard(successorId);
|
|
10341
10487
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
10342
|
-
|
|
10488
|
+
log31.info(TAG28, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
10343
10489
|
await deps.client.updateCard(successorId, {
|
|
10344
10490
|
assignedAgentId: deps.agentId
|
|
10345
10491
|
});
|
|
10346
10492
|
} else {
|
|
10347
|
-
|
|
10493
|
+
log31.debug(TAG28, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
10348
10494
|
continue;
|
|
10349
10495
|
}
|
|
10350
10496
|
await deps.enqueue(successorId);
|
|
10351
10497
|
} catch (err) {
|
|
10352
|
-
|
|
10498
|
+
log31.warn(TAG28, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
10353
10499
|
}
|
|
10354
10500
|
}
|
|
10355
10501
|
}
|
|
10356
|
-
var
|
|
10502
|
+
var TAG28 = "unblock";
|
|
10357
10503
|
var init_unblock = () => {};
|
|
10358
10504
|
|
|
10359
10505
|
// src/cli-agent-runner.ts
|
|
10360
|
-
import { log as
|
|
10506
|
+
import { log as log32 } from "@gethmy/harness";
|
|
10361
10507
|
function truncateOutput(value) {
|
|
10362
10508
|
return value === undefined ? undefined : value.slice(0, MAX_OUTPUT_CHARS);
|
|
10363
10509
|
}
|
|
@@ -10522,7 +10668,7 @@ class CliAgentRunner {
|
|
|
10522
10668
|
return;
|
|
10523
10669
|
const draft = sanitizeRunEventDraft(body);
|
|
10524
10670
|
if (runEventPayloadExceedsLimit(draft.payload)) {
|
|
10525
|
-
|
|
10671
|
+
log32.warn(TAG29, `Dropping ${body.kind} event: payload exceeds ${MAX_RUN_EVENT_PAYLOAD_BYTES} bytes, or cannot be serialized`);
|
|
10526
10672
|
return;
|
|
10527
10673
|
}
|
|
10528
10674
|
this.buffer.push({ ...draft, createdAt: new Date().toISOString() });
|
|
@@ -10554,15 +10700,15 @@ class CliAgentRunner {
|
|
|
10554
10700
|
this.requeue(batch);
|
|
10555
10701
|
if (batch.length > 1) {
|
|
10556
10702
|
this.nextBatchSize = Math.ceil(batch.length / 2);
|
|
10557
|
-
|
|
10703
|
+
log32.warn(TAG29, `Flush of ${batch.length} run event(s) failed ${MAX_BATCH_ATTEMPTS}×; bisecting to ${this.nextBatchSize}: ${err}`);
|
|
10558
10704
|
return;
|
|
10559
10705
|
}
|
|
10560
10706
|
const dropped = this.buffer.shift();
|
|
10561
10707
|
this.nextBatchSize = MAX_BUFFER;
|
|
10562
|
-
|
|
10708
|
+
log32.warn(TAG29, `Dropping one ${dropped?.kind} event the API refused ${MAX_BATCH_ATTEMPTS}×: ${err}`);
|
|
10563
10709
|
return;
|
|
10564
10710
|
}
|
|
10565
|
-
|
|
10711
|
+
log32.warn(TAG29, `Failed to flush run events (attempt ${this.batchAttempts}/${MAX_BATCH_ATTEMPTS}): ${err}`);
|
|
10566
10712
|
this.requeue(batch);
|
|
10567
10713
|
} finally {
|
|
10568
10714
|
this.flushing = false;
|
|
@@ -10605,26 +10751,26 @@ function mapCost(cost) {
|
|
|
10605
10751
|
durationMs: cost.durationMs
|
|
10606
10752
|
};
|
|
10607
10753
|
}
|
|
10608
|
-
var
|
|
10754
|
+
var TAG29 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN = 8000, MAX_BATCH_ATTEMPTS = 3, FINAL_FLUSH_ATTEMPTS = 40;
|
|
10609
10755
|
var init_cli_agent_runner = __esm(() => {
|
|
10610
10756
|
init_src();
|
|
10611
10757
|
});
|
|
10612
10758
|
|
|
10613
10759
|
// src/fanout.ts
|
|
10614
|
-
import { log as
|
|
10760
|
+
import { log as log33 } from "@gethmy/harness";
|
|
10615
10761
|
async function fetchLinks(client, cardId) {
|
|
10616
10762
|
try {
|
|
10617
10763
|
const { links } = await client.getCardLinks(cardId);
|
|
10618
10764
|
return links;
|
|
10619
10765
|
} catch (err) {
|
|
10620
|
-
|
|
10766
|
+
log33.warn(TAG30, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
10621
10767
|
return null;
|
|
10622
10768
|
}
|
|
10623
10769
|
}
|
|
10624
10770
|
async function isFanoutChildOf(card, stage, client) {
|
|
10625
10771
|
const links = await fetchLinks(client, card.id);
|
|
10626
10772
|
if (links === null) {
|
|
10627
|
-
|
|
10773
|
+
log33.warn(TAG30, `#${card.short_id}: link read failed — treating as a fan-out child (fail closed, no recursive dispatch)`);
|
|
10628
10774
|
return true;
|
|
10629
10775
|
}
|
|
10630
10776
|
const parents = links.filter((l) => l.direction === "outgoing" && l.link_type === "is_part_of");
|
|
@@ -10711,7 +10857,7 @@ async function readStageHandoff(card, fromStage, deps) {
|
|
|
10711
10857
|
}
|
|
10712
10858
|
return null;
|
|
10713
10859
|
} catch (err) {
|
|
10714
|
-
|
|
10860
|
+
log33.warn(TAG30, `handoff read failed for #${card.short_id} stage "${fromStage}": ${err instanceof Error ? err.message : err}`);
|
|
10715
10861
|
return null;
|
|
10716
10862
|
}
|
|
10717
10863
|
}
|
|
@@ -10812,7 +10958,7 @@ async function dispatchWave(parent, stage, plan, items, deps) {
|
|
|
10812
10958
|
});
|
|
10813
10959
|
spawned = res.children ?? [];
|
|
10814
10960
|
} catch (err) {
|
|
10815
|
-
|
|
10961
|
+
log33.warn(TAG30, `child spawn failed for #${parent.short_id} stage "${stage.id}": ${err instanceof Error ? err.message : err}`);
|
|
10816
10962
|
return 0;
|
|
10817
10963
|
}
|
|
10818
10964
|
let created = 0;
|
|
@@ -10867,10 +11013,10 @@ async function seedChildHandoff(parent, stage, item, total, childId, deps) {
|
|
|
10867
11013
|
try {
|
|
10868
11014
|
await deps.client.addComment(childId, body, { commentType: "decision" });
|
|
10869
11015
|
} catch (err) {
|
|
10870
|
-
|
|
11016
|
+
log33.warn(TAG30, `seed handoff failed for child ${childId}: ${err instanceof Error ? err.message : err}`);
|
|
10871
11017
|
}
|
|
10872
11018
|
}
|
|
10873
|
-
var
|
|
11019
|
+
var TAG30 = "fanout";
|
|
10874
11020
|
var init_fanout = __esm(() => {
|
|
10875
11021
|
init_src();
|
|
10876
11022
|
});
|
|
@@ -11147,7 +11293,7 @@ function runSizedPayload(input) {
|
|
|
11147
11293
|
}
|
|
11148
11294
|
|
|
11149
11295
|
// src/stage-advance.ts
|
|
11150
|
-
import { gateConfigErrorReason, log as
|
|
11296
|
+
import { gateConfigErrorReason, log as log34 } from "@gethmy/harness";
|
|
11151
11297
|
function endDispositionFor(outcome) {
|
|
11152
11298
|
return "endDisposition" in outcome && outcome.endDisposition ? outcome.endDisposition : { status: "completed" };
|
|
11153
11299
|
}
|
|
@@ -11178,7 +11324,7 @@ async function resolveStageColumnName(client, card, stage) {
|
|
|
11178
11324
|
const match = board.columns.find((c) => c.id === target || c.name.toLowerCase() === target.toLowerCase());
|
|
11179
11325
|
return match ? match.name : null;
|
|
11180
11326
|
} catch (err) {
|
|
11181
|
-
|
|
11327
|
+
log34.warn(TAG31, `board fetch failed resolving stage column for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11182
11328
|
return null;
|
|
11183
11329
|
}
|
|
11184
11330
|
}
|
|
@@ -11217,7 +11363,7 @@ async function holdGateMisconfigured(card, stage, detail, deps) {
|
|
|
11217
11363
|
});
|
|
11218
11364
|
} catch {}
|
|
11219
11365
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, { agentSessionId: deps.agentSessionId });
|
|
11220
|
-
|
|
11366
|
+
log34.info(TAG31, `#${card.short_id} GateMisconfigured: ${reason}`);
|
|
11221
11367
|
return { kind: "held_misconfigured", reason, endDisposition };
|
|
11222
11368
|
}
|
|
11223
11369
|
function firstErrorMessage(evaluation) {
|
|
@@ -11248,7 +11394,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11248
11394
|
evidence,
|
|
11249
11395
|
summary
|
|
11250
11396
|
});
|
|
11251
|
-
|
|
11397
|
+
log34.info(TAG31, `#${card.short_id} converge loop "${stage.name}": ${summary} → ${decision}`);
|
|
11252
11398
|
if (decision === "exit") {
|
|
11253
11399
|
await deps.stateStore.resetLoopIterations(card.id).catch(() => {});
|
|
11254
11400
|
deps.sink?.recordLoopCompleted?.({
|
|
@@ -11293,7 +11439,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11293
11439
|
blockers: [reason],
|
|
11294
11440
|
agentSessionId: deps.agentSessionId
|
|
11295
11441
|
});
|
|
11296
|
-
|
|
11442
|
+
log34.info(TAG31, `#${card.short_id} LoopExhausted: ${reason}`);
|
|
11297
11443
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11298
11444
|
}
|
|
11299
11445
|
const guard = await guardStageReclaim(card, `converge-loop iteration of "${stage.name}"`, deps);
|
|
@@ -11318,7 +11464,7 @@ async function advanceConvergeLoop(card, stage, stageIndex, def, evaluation, loo
|
|
|
11318
11464
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11319
11465
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11320
11466
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11321
|
-
|
|
11467
|
+
log34.info(TAG31, `#${card.short_id} converge loop "${stage.name}" — requeued to "${toColumn}" for iteration ${iteration + 1}/${maxIterations}`);
|
|
11322
11468
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11323
11469
|
}
|
|
11324
11470
|
async function writeIterationHandoff(card, stage, iteration, maxIterations, evaluation, deps) {
|
|
@@ -11340,7 +11486,7 @@ ${findings.map((f) => `- [${f.level}] ${f.message}`).join(`
|
|
|
11340
11486
|
agentSessionId: deps.agentSessionId ?? undefined
|
|
11341
11487
|
});
|
|
11342
11488
|
} catch (err) {
|
|
11343
|
-
|
|
11489
|
+
log34.warn(TAG31, `iteration-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11344
11490
|
}
|
|
11345
11491
|
}
|
|
11346
11492
|
async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps) {
|
|
@@ -11368,7 +11514,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11368
11514
|
reason: "Playbook complete — final stage gate passed."
|
|
11369
11515
|
});
|
|
11370
11516
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11371
|
-
|
|
11517
|
+
log34.info(TAG31, `#${card.short_id} terminal stage "${stage.name}" passed — playbook complete (done left to the column)`);
|
|
11372
11518
|
return { kind: "completed_terminal" };
|
|
11373
11519
|
}
|
|
11374
11520
|
if (next.kind === "out_of_range") {
|
|
@@ -11400,7 +11546,7 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
11400
11546
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11401
11547
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11402
11548
|
deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
|
|
11403
|
-
|
|
11549
|
+
log34.info(TAG31, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
|
|
11404
11550
|
if (next.stage.owner === "human") {
|
|
11405
11551
|
const reason = `Stage "${next.stage.name}" is yours: ${handoffText(next.stage)}`;
|
|
11406
11552
|
const endDisposition = await holdForHuman(deps.client, card, reason, deps.runId, deps.stateStore, {
|
|
@@ -11437,7 +11583,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11437
11583
|
blockers: [reason],
|
|
11438
11584
|
agentSessionId: deps.agentSessionId
|
|
11439
11585
|
});
|
|
11440
|
-
|
|
11586
|
+
log34.info(TAG31, `#${card.short_id} GateUnmetExhausted: ${reason}`);
|
|
11441
11587
|
return { kind: "held_gate_unmet", reason, endDisposition };
|
|
11442
11588
|
}
|
|
11443
11589
|
const guard = await guardStageReclaim(card, `gate-unmet re-run of "${stage.name}"`, deps);
|
|
@@ -11460,7 +11606,7 @@ async function handleGateUnmet(card, stage, summary, deps) {
|
|
|
11460
11606
|
addLabels: [{ name: AGENT_LABEL }],
|
|
11461
11607
|
...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
11462
11608
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11463
|
-
|
|
11609
|
+
log34.info(TAG31, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
|
|
11464
11610
|
return { kind: "requeued_gate_unmet", toColumn };
|
|
11465
11611
|
}
|
|
11466
11612
|
async function guardStageReclaim(card, what, deps) {
|
|
@@ -11486,10 +11632,10 @@ async function guardStageReclaim(card, what, deps) {
|
|
|
11486
11632
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
11487
11633
|
released = stillOurs;
|
|
11488
11634
|
} catch (err) {
|
|
11489
|
-
|
|
11635
|
+
log34.warn(TAG31, `#${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" : ""}`);
|
|
11490
11636
|
}
|
|
11491
11637
|
}
|
|
11492
|
-
|
|
11638
|
+
log34.info(TAG31, `#${card.short_id} ${what} refused — ${detail} (${reason})${released ? "; released the daemon's claim so the stage router stops here" : ""}`);
|
|
11493
11639
|
try {
|
|
11494
11640
|
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.`, {
|
|
11495
11641
|
commentType: "decision",
|
|
@@ -11511,7 +11657,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11511
11657
|
try {
|
|
11512
11658
|
await runTransition(client, card, { removeLabels: [AGENT_LABEL] }, { store: stateStore, runId });
|
|
11513
11659
|
} catch (err) {
|
|
11514
|
-
|
|
11660
|
+
log34.warn(TAG31, `hold transition failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
11515
11661
|
}
|
|
11516
11662
|
return {
|
|
11517
11663
|
status: opts.endStatus ?? "paused",
|
|
@@ -11520,7 +11666,7 @@ async function holdForHuman(client, card, reason, runId, stateStore, opts = {})
|
|
|
11520
11666
|
failureSummary: reason.slice(0, 300)
|
|
11521
11667
|
};
|
|
11522
11668
|
}
|
|
11523
|
-
var
|
|
11669
|
+
var TAG31 = "stage-advance", AGENT_LABEL = "agent";
|
|
11524
11670
|
var init_stage_advance = __esm(() => {
|
|
11525
11671
|
init_src();
|
|
11526
11672
|
init_handback();
|
|
@@ -11628,7 +11774,7 @@ import {
|
|
|
11628
11774
|
GIT_NO_HOOKS as GIT_NO_HOOKS8,
|
|
11629
11775
|
implementRunContainment,
|
|
11630
11776
|
implementRunContainmentCliArgs as implementRunContainmentCliArgs2,
|
|
11631
|
-
log as
|
|
11777
|
+
log as log35,
|
|
11632
11778
|
makeBranchName,
|
|
11633
11779
|
normalizeGateSpec,
|
|
11634
11780
|
pushBranch as pushBranch3,
|
|
@@ -11834,11 +11980,11 @@ class Worker {
|
|
|
11834
11980
|
sessionId: this.sessionId
|
|
11835
11981
|
});
|
|
11836
11982
|
} catch (err) {
|
|
11837
|
-
|
|
11983
|
+
log35.warn(this.tag, `state store updateRun failed: ${err instanceof Error ? err.message : err}`);
|
|
11838
11984
|
}
|
|
11839
11985
|
}
|
|
11840
11986
|
get tag() {
|
|
11841
|
-
return `${
|
|
11987
|
+
return `${TAG32}:${this.id}`;
|
|
11842
11988
|
}
|
|
11843
11989
|
get isIdle() {
|
|
11844
11990
|
return this.state === "idle";
|
|
@@ -11900,12 +12046,12 @@ class Worker {
|
|
|
11900
12046
|
}
|
|
11901
12047
|
if (!crossedTurnCap(before, after, cap))
|
|
11902
12048
|
return;
|
|
11903
|
-
|
|
12049
|
+
log35.warn(TAG32, `#${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.`);
|
|
11904
12050
|
try {
|
|
11905
12051
|
const legs = this.stateStore.getRun(this.runId ?? "")?.legs?.length || 1;
|
|
11906
12052
|
await this.client.addComment(cardId, buildTurnCapComment(after, cap, legs), { commentType: "blocker", agentSessionId: this.sessionId ?? undefined });
|
|
11907
12053
|
} catch (err) {
|
|
11908
|
-
|
|
12054
|
+
log35.warn(TAG32, `Could not post the turn-cap notice for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
11909
12055
|
}
|
|
11910
12056
|
}
|
|
11911
12057
|
async run(card, column, labels, subtasks) {
|
|
@@ -11960,7 +12106,7 @@ class Worker {
|
|
|
11960
12106
|
resumeReason: null
|
|
11961
12107
|
});
|
|
11962
12108
|
} catch (err) {
|
|
11963
|
-
|
|
12109
|
+
log35.warn(this.tag, `Failed to clear the consumed grant: ${err}`);
|
|
11964
12110
|
}
|
|
11965
12111
|
}
|
|
11966
12112
|
try {
|
|
@@ -11968,20 +12114,20 @@ class Worker {
|
|
|
11968
12114
|
if (!resuming) {
|
|
11969
12115
|
this.branchName = makeBranchName(card.short_id, card.title, this.config.worktree.failedBranchPrefix);
|
|
11970
12116
|
}
|
|
11971
|
-
|
|
12117
|
+
log35.info(this.tag, resuming ? `Resuming #${card.short_id} "${card.title}" with ${this.grantedTurns ?? "the default"} more turns` : `Preparing #${card.short_id} "${card.title}"`);
|
|
11972
12118
|
const attemptCount = await this.stateStore.incrementAttempt(card.id);
|
|
11973
12119
|
const isRework = attemptCount > 1;
|
|
11974
12120
|
const runStarts = this.stateStore.getCardRunStarts(card.id);
|
|
11975
12121
|
const runCap = this.config.budget.maxRunsPerCard;
|
|
11976
12122
|
if (crossedTurnCap(runStarts - 1, runStarts, runCap)) {
|
|
11977
|
-
|
|
12123
|
+
log35.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.`);
|
|
11978
12124
|
try {
|
|
11979
12125
|
await this.client.addComment(card.id, buildRunCapComment(runStarts, runCap), {
|
|
11980
12126
|
commentType: "blocker",
|
|
11981
12127
|
agentSessionId: this.sessionId ?? undefined
|
|
11982
12128
|
});
|
|
11983
12129
|
} catch (err) {
|
|
11984
|
-
|
|
12130
|
+
log35.warn(this.tag, `Could not post the run-backstop notice for ${card.id}: ${err instanceof Error ? err.message : err}`);
|
|
11985
12131
|
}
|
|
11986
12132
|
}
|
|
11987
12133
|
const recordedBranch = recordedBranchForCard(card.description, card.short_id, [
|
|
@@ -11991,10 +12137,10 @@ class Worker {
|
|
|
11991
12137
|
const continuesPushedWork = isRework || recordedBranch !== null;
|
|
11992
12138
|
if (!resuming && recordedBranch) {
|
|
11993
12139
|
if (recordedBranch !== this.branchName) {
|
|
11994
|
-
|
|
12140
|
+
log35.info(this.tag, `Card records pushed work on ${recordedBranch} — continuing that branch instead of ${this.branchName}`);
|
|
11995
12141
|
this.branchName = recordedBranch;
|
|
11996
12142
|
} else if (!isRework) {
|
|
11997
|
-
|
|
12143
|
+
log35.info(this.tag, `Card records pushed work on ${this.branchName} — continuing that branch instead of rebuilding from ${this.config.worktree.baseBranch}`);
|
|
11998
12144
|
}
|
|
11999
12145
|
}
|
|
12000
12146
|
this.startHeartbeat();
|
|
@@ -12036,14 +12182,14 @@ class Worker {
|
|
|
12036
12182
|
awaitingDecisionUntil: null
|
|
12037
12183
|
});
|
|
12038
12184
|
const started = await startSessionYieldingNotice(startSession, this.client, card.id, {
|
|
12039
|
-
info: (message) =>
|
|
12040
|
-
warn: (message) =>
|
|
12185
|
+
info: (message) => log35.info(this.tag, message),
|
|
12186
|
+
warn: (message) => log35.warn(this.tag, message)
|
|
12041
12187
|
});
|
|
12042
12188
|
session = started.session;
|
|
12043
12189
|
} catch (err) {
|
|
12044
12190
|
if (isSessionConflict(err)) {
|
|
12045
12191
|
this.sessionConflict = true;
|
|
12046
|
-
|
|
12192
|
+
log35.info(this.tag, `Skipping #${card.short_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12047
12193
|
await this.stateStore.decrementAttempt(card.id);
|
|
12048
12194
|
return;
|
|
12049
12195
|
}
|
|
@@ -12051,7 +12197,7 @@ class Worker {
|
|
|
12051
12197
|
}
|
|
12052
12198
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
12053
12199
|
if (!sid) {
|
|
12054
|
-
|
|
12200
|
+
log35.warn(TAG32, "startAgentSession returned no session id");
|
|
12055
12201
|
}
|
|
12056
12202
|
this.sessionId = sid;
|
|
12057
12203
|
}
|
|
@@ -12069,7 +12215,7 @@ class Worker {
|
|
|
12069
12215
|
if (!resuming) {
|
|
12070
12216
|
const moved = await moveCardAndAddLabel(this.client, card, IN_PROGRESS_COLUMN, "agent");
|
|
12071
12217
|
if (!moved) {
|
|
12072
|
-
|
|
12218
|
+
log35.warn(this.tag, `Card #${card.short_id} was NOT moved to "In Progress" — check API logs`);
|
|
12073
12219
|
}
|
|
12074
12220
|
}
|
|
12075
12221
|
if (this.aborted)
|
|
@@ -12096,10 +12242,10 @@ class Worker {
|
|
|
12096
12242
|
}).trim();
|
|
12097
12243
|
const target = resolveContinuationTarget(this.branchName, continueRequested, this.config.worktree.failedBranchPrefix, this.config.worktree.approvedBranchPrefix, (ref) => fetchExistingBranch(repoRoot, ref));
|
|
12098
12244
|
if (target.branchName !== this.branchName) {
|
|
12099
|
-
|
|
12245
|
+
log35.info(this.tag, `Branch ${this.branchName} is gone from origin but its approved rename ${target.branchName} exists — continuing that branch`);
|
|
12100
12246
|
this.branchName = target.branchName;
|
|
12101
12247
|
} else if (target.reason === "exists_on_origin") {
|
|
12102
|
-
|
|
12248
|
+
log35.info(this.tag, `Branch ${this.branchName} exists on origin with no card record — continuing it rather than resetting it`);
|
|
12103
12249
|
}
|
|
12104
12250
|
this.worktreePath = createWorktree(this.config.worktree.basePath, this.config.worktree.baseBranch, this.branchName, {
|
|
12105
12251
|
continueExisting: target.continueExisting,
|
|
@@ -12133,7 +12279,7 @@ class Worker {
|
|
|
12133
12279
|
if (this.aborted)
|
|
12134
12280
|
return;
|
|
12135
12281
|
if (parked) {
|
|
12136
|
-
|
|
12282
|
+
log35.info(this.tag, `#${card.short_id} parked for plan approval — ending run`);
|
|
12137
12283
|
return;
|
|
12138
12284
|
}
|
|
12139
12285
|
}
|
|
@@ -12154,7 +12300,7 @@ class Worker {
|
|
|
12154
12300
|
progressPercent: 10
|
|
12155
12301
|
});
|
|
12156
12302
|
this.timeoutTimer = setTimeout(() => {
|
|
12157
|
-
|
|
12303
|
+
log35.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
12158
12304
|
this.timedOut = true;
|
|
12159
12305
|
this.cancel("timeout");
|
|
12160
12306
|
}, this.config.maxTimeout);
|
|
@@ -12181,7 +12327,7 @@ class Worker {
|
|
|
12181
12327
|
}
|
|
12182
12328
|
this.state = "verifying";
|
|
12183
12329
|
await this.recordPhase("verifying");
|
|
12184
|
-
|
|
12330
|
+
log35.info(this.tag, `Claude finished for #${card.short_id}, running verification & completion`);
|
|
12185
12331
|
await this.client.updateAgentProgress(card.id, {
|
|
12186
12332
|
agentIdentifier: this.sessionIdentifier,
|
|
12187
12333
|
agentName: AGENT_NAME,
|
|
@@ -12216,7 +12362,7 @@ class Worker {
|
|
|
12216
12362
|
break;
|
|
12217
12363
|
case "reclaim_refused":
|
|
12218
12364
|
this.held = true;
|
|
12219
|
-
|
|
12365
|
+
log35.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
12220
12366
|
break;
|
|
12221
12367
|
case "advanced":
|
|
12222
12368
|
case "completed_terminal":
|
|
@@ -12254,7 +12400,7 @@ class Worker {
|
|
|
12254
12400
|
return;
|
|
12255
12401
|
}
|
|
12256
12402
|
this.state = "error";
|
|
12257
|
-
|
|
12403
|
+
log35.error(this.tag, `Error on #${card.short_id}: ${msg}`);
|
|
12258
12404
|
const apiError = errClass.kind !== null;
|
|
12259
12405
|
const baseError = err instanceof WorktreeBaseError;
|
|
12260
12406
|
const noBudgetBurn = apiError || baseError;
|
|
@@ -12272,7 +12418,7 @@ class Worker {
|
|
|
12272
12418
|
try {
|
|
12273
12419
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12274
12420
|
} catch {
|
|
12275
|
-
|
|
12421
|
+
log35.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12276
12422
|
}
|
|
12277
12423
|
this.worktreePath = null;
|
|
12278
12424
|
}
|
|
@@ -12297,7 +12443,7 @@ class Worker {
|
|
|
12297
12443
|
}
|
|
12298
12444
|
});
|
|
12299
12445
|
} catch (tErr) {
|
|
12300
|
-
|
|
12446
|
+
log35.error(this.tag, `error transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12301
12447
|
}
|
|
12302
12448
|
if (this.runId) {
|
|
12303
12449
|
try {
|
|
@@ -12333,7 +12479,7 @@ class Worker {
|
|
|
12333
12479
|
try {
|
|
12334
12480
|
await teardownWorktree2(this.client, card.id, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12335
12481
|
} catch {
|
|
12336
|
-
|
|
12482
|
+
log35.warn(this.tag, "Failed to cleanup worktree before requeue");
|
|
12337
12483
|
}
|
|
12338
12484
|
this.worktreePath = null;
|
|
12339
12485
|
}
|
|
@@ -12356,7 +12502,7 @@ class Worker {
|
|
|
12356
12502
|
}
|
|
12357
12503
|
});
|
|
12358
12504
|
} catch (tErr) {
|
|
12359
|
-
|
|
12505
|
+
log35.error(this.tag, `timeout transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12360
12506
|
}
|
|
12361
12507
|
try {
|
|
12362
12508
|
await this.stateStore.endRun(this.runId, "failed", {
|
|
@@ -12370,15 +12516,15 @@ class Worker {
|
|
|
12370
12516
|
try {
|
|
12371
12517
|
await this.client.updateCard(card.id, { assignedAgentId: null });
|
|
12372
12518
|
} catch (err) {
|
|
12373
|
-
|
|
12519
|
+
log35.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
|
|
12374
12520
|
}
|
|
12375
12521
|
try {
|
|
12376
12522
|
await runTransition(this.client, card, { removeLabels: ["agent"] });
|
|
12377
12523
|
} catch (tErr) {
|
|
12378
|
-
|
|
12524
|
+
log35.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12379
12525
|
}
|
|
12380
12526
|
} else {
|
|
12381
|
-
|
|
12527
|
+
log35.info(this.tag, `cancel arrived after completion on #${card.short_id} — keeping assignment so review picks it up (#585)`);
|
|
12382
12528
|
}
|
|
12383
12529
|
try {
|
|
12384
12530
|
await this.stateStore.endRun(this.runId, "paused", {
|
|
@@ -12442,28 +12588,28 @@ class Worker {
|
|
|
12442
12588
|
};
|
|
12443
12589
|
const { pick, reason } = selectAutoPlaybook(subject, playbooks ?? []);
|
|
12444
12590
|
if (!pick) {
|
|
12445
|
-
|
|
12591
|
+
log35.info(this.tag, `No playbook auto-bound to #${card.short_id}: ${reason}`);
|
|
12446
12592
|
return card;
|
|
12447
12593
|
}
|
|
12448
12594
|
const undeclared = undeclaredGateMetricsIn(pick, declaredMetricSet(this.config));
|
|
12449
12595
|
if (undeclared.length > 0) {
|
|
12450
|
-
|
|
12596
|
+
log35.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.");
|
|
12451
12597
|
return card;
|
|
12452
12598
|
}
|
|
12453
12599
|
const applyResult = await this.client.request("POST", `/cards/${card.id}/apply-playbook`, {
|
|
12454
12600
|
playbookId: pick.id
|
|
12455
12601
|
});
|
|
12456
|
-
|
|
12602
|
+
log35.info(this.tag, `Auto-bound #${card.short_id} to playbook "${pick.name}": ${reason}`);
|
|
12457
12603
|
try {
|
|
12458
12604
|
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.`);
|
|
12459
12605
|
} catch (commentErr) {
|
|
12460
|
-
|
|
12606
|
+
log35.warn(this.tag, `Auto-bind comment failed for #${card.short_id}: ${commentErr instanceof Error ? commentErr.message : String(commentErr)}`);
|
|
12461
12607
|
}
|
|
12462
12608
|
try {
|
|
12463
12609
|
const { card: fresh } = await this.client.getCard(card.id);
|
|
12464
12610
|
return fresh;
|
|
12465
12611
|
} catch (fetchErr) {
|
|
12466
|
-
|
|
12612
|
+
log35.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)}`);
|
|
12467
12613
|
return {
|
|
12468
12614
|
...card,
|
|
12469
12615
|
playbook_id: applyResult.card.playbook_id,
|
|
@@ -12472,7 +12618,7 @@ class Worker {
|
|
|
12472
12618
|
};
|
|
12473
12619
|
}
|
|
12474
12620
|
} catch (err) {
|
|
12475
|
-
|
|
12621
|
+
log35.warn(this.tag, `Auto-bind playbook check failed for #${card.short_id}, continuing unbound: ${err instanceof Error ? err.message : String(err)}`);
|
|
12476
12622
|
return card;
|
|
12477
12623
|
}
|
|
12478
12624
|
}
|
|
@@ -12584,7 +12730,7 @@ class Worker {
|
|
|
12584
12730
|
});
|
|
12585
12731
|
} catch (err) {
|
|
12586
12732
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12587
|
-
|
|
12733
|
+
log35.warn(this.tag, `fan-out tick failed on #${card.short_id}: ${detail}`);
|
|
12588
12734
|
await this.holdStageCard(card, `Fan-out stage "${ctx.stage.name}" could not run: ${detail}`);
|
|
12589
12735
|
return;
|
|
12590
12736
|
}
|
|
@@ -12593,7 +12739,7 @@ class Worker {
|
|
|
12593
12739
|
case "waiting": {
|
|
12594
12740
|
const total = outcome.kind === "dispatched" ? outcome.total : outcome.total;
|
|
12595
12741
|
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.`;
|
|
12596
|
-
|
|
12742
|
+
log35.info(this.tag, `#${card.short_id} ${note}`);
|
|
12597
12743
|
await this.client.updateAgentProgress(card.id, {
|
|
12598
12744
|
agentIdentifier: "claude-code-stage",
|
|
12599
12745
|
agentName: "Harmony Agent",
|
|
@@ -12621,7 +12767,7 @@ class Worker {
|
|
|
12621
12767
|
if (advance.kind === "advanced" || advance.kind === "completed_terminal") {
|
|
12622
12768
|
this.held = false;
|
|
12623
12769
|
}
|
|
12624
|
-
|
|
12770
|
+
log35.info(this.tag, `#${card.short_id} ${summary} → ${advance.kind}`);
|
|
12625
12771
|
return;
|
|
12626
12772
|
}
|
|
12627
12773
|
case "halted": {
|
|
@@ -12641,7 +12787,7 @@ class Worker {
|
|
|
12641
12787
|
}
|
|
12642
12788
|
}
|
|
12643
12789
|
async holdStageCard(card, reason, wait = false, armCooldown = false) {
|
|
12644
|
-
|
|
12790
|
+
log35.info(this.tag, `Holding #${card.short_id}: ${reason}`);
|
|
12645
12791
|
await this.stateStore.decrementAttempt(card.id);
|
|
12646
12792
|
let announced = false;
|
|
12647
12793
|
const holdStage = armCooldown ? card.current_stage : null;
|
|
@@ -12654,11 +12800,11 @@ class Worker {
|
|
|
12654
12800
|
});
|
|
12655
12801
|
announced = recorded.announced;
|
|
12656
12802
|
} catch (err) {
|
|
12657
|
-
|
|
12803
|
+
log35.warn(this.tag, `stage hold not recorded on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
12658
12804
|
}
|
|
12659
12805
|
}
|
|
12660
12806
|
if (announced) {
|
|
12661
|
-
|
|
12807
|
+
log35.debug(this.tag, `#${card.short_id} already carries this blocker — not reposting`);
|
|
12662
12808
|
} else {
|
|
12663
12809
|
try {
|
|
12664
12810
|
await this.client.addComment(card.id, reason, {
|
|
@@ -12680,7 +12826,7 @@ class Worker {
|
|
|
12680
12826
|
}
|
|
12681
12827
|
});
|
|
12682
12828
|
} catch (tErr) {
|
|
12683
|
-
|
|
12829
|
+
log35.warn(this.tag, `hold transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
12684
12830
|
}
|
|
12685
12831
|
if (this.runId) {
|
|
12686
12832
|
try {
|
|
@@ -12698,7 +12844,7 @@ class Worker {
|
|
|
12698
12844
|
const holderMessage = err instanceof Error ? err.message : String(err);
|
|
12699
12845
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12700
12846
|
const until = computeDecisionDeadline(waitHours);
|
|
12701
|
-
|
|
12847
|
+
log35.warn(this.tag, `#${card.short_id} stays parked — the resume could not reclaim its session: ${holderMessage}`);
|
|
12702
12848
|
try {
|
|
12703
12849
|
await this.client.addComment(card.id, formatResumeConflictComment({
|
|
12704
12850
|
holderMessage,
|
|
@@ -12710,7 +12856,7 @@ class Worker {
|
|
|
12710
12856
|
agentSessionId: this.sessionId ?? undefined
|
|
12711
12857
|
});
|
|
12712
12858
|
} catch (commentErr) {
|
|
12713
|
-
|
|
12859
|
+
log35.warn(this.tag, `Failed to post the resume-conflict note for #${card.short_id}: ${commentErr}`);
|
|
12714
12860
|
}
|
|
12715
12861
|
if (this.runId) {
|
|
12716
12862
|
const run = this.stateStore.getRun(this.runId);
|
|
@@ -12721,7 +12867,7 @@ class Worker {
|
|
|
12721
12867
|
awaitingDecisionUntil: until
|
|
12722
12868
|
});
|
|
12723
12869
|
} catch (storeErr) {
|
|
12724
|
-
|
|
12870
|
+
log35.error(this.tag, `#${card.short_id} could not be re-parked after a resume conflict — the daemon has no hold on it: ${storeErr}`);
|
|
12725
12871
|
}
|
|
12726
12872
|
}
|
|
12727
12873
|
}
|
|
@@ -12733,7 +12879,7 @@ class Worker {
|
|
|
12733
12879
|
this.progressTracker = null;
|
|
12734
12880
|
const waitHours = this.config.budget.pause.waitHours;
|
|
12735
12881
|
const until = computeDecisionDeadline(waitHours);
|
|
12736
|
-
|
|
12882
|
+
log35.warn(this.tag, `#${card.short_id} parked (${trigger}) — awaiting a human decision for ${waitHours}h`);
|
|
12737
12883
|
const measured = this.worktreePath && this.runExpectsCommits ? measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha) : "unknown";
|
|
12738
12884
|
const hasCommits = measured === "unknown" ? undefined : measured === "committed";
|
|
12739
12885
|
if (hasCommits === false && !this.noCommitNoticeSettled) {
|
|
@@ -12763,7 +12909,7 @@ class Worker {
|
|
|
12763
12909
|
});
|
|
12764
12910
|
commentId = res?.comment?.id ?? null;
|
|
12765
12911
|
} catch (err) {
|
|
12766
|
-
|
|
12912
|
+
log35.warn(this.tag, `Failed to post the budget-pause comment: ${err}`);
|
|
12767
12913
|
}
|
|
12768
12914
|
const cardCostCents = this.stateStore.getCardCostCents(card.id) + Math.max(0, this.runCostCents - this.chargedCents);
|
|
12769
12915
|
const readout = [
|
|
@@ -12784,7 +12930,7 @@ class Worker {
|
|
|
12784
12930
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
12785
12931
|
});
|
|
12786
12932
|
} catch (err) {
|
|
12787
|
-
|
|
12933
|
+
log35.warn(this.tag, `Failed to mark the session blocked: ${err}`);
|
|
12788
12934
|
}
|
|
12789
12935
|
if (this.runId) {
|
|
12790
12936
|
try {
|
|
@@ -12795,7 +12941,7 @@ class Worker {
|
|
|
12795
12941
|
...this.runLedger()
|
|
12796
12942
|
});
|
|
12797
12943
|
} catch (err) {
|
|
12798
|
-
|
|
12944
|
+
log35.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}`);
|
|
12799
12945
|
}
|
|
12800
12946
|
}
|
|
12801
12947
|
}
|
|
@@ -12826,11 +12972,11 @@ class Worker {
|
|
|
12826
12972
|
});
|
|
12827
12973
|
const gate = normalizeGateSpec(ctx.stage.gate);
|
|
12828
12974
|
const metricsPath = gate?.kind === "custom" ? writeMetricsFile(this.config.playbooks.metrics ?? {}) : null;
|
|
12829
|
-
|
|
12975
|
+
log35.info(this.tag, `Running stage "${ctx.stage.name}" (role ${ctx.role}) for #${card.short_id} under the harness motor`);
|
|
12830
12976
|
const motorAbort = new AbortController;
|
|
12831
12977
|
this.motorAbort = motorAbort;
|
|
12832
12978
|
this.timeoutTimer = setTimeout(() => {
|
|
12833
|
-
|
|
12979
|
+
log35.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms) during the motor stage run, cancelling`);
|
|
12834
12980
|
this.timedOut = true;
|
|
12835
12981
|
this.cancel("timeout");
|
|
12836
12982
|
}, this.config.maxTimeout);
|
|
@@ -12841,12 +12987,12 @@ class Worker {
|
|
|
12841
12987
|
let motorRunSettled = false;
|
|
12842
12988
|
const onMotorLine = (line) => {
|
|
12843
12989
|
if (line.type !== "agent_event") {
|
|
12844
|
-
|
|
12990
|
+
log35.info(this.tag, `motor: ${line.type}`);
|
|
12845
12991
|
return;
|
|
12846
12992
|
}
|
|
12847
12993
|
if (motorRunSettled)
|
|
12848
12994
|
return;
|
|
12849
|
-
|
|
12995
|
+
log35.debug(this.tag, `motor: agent_event ${line.event.kind}`);
|
|
12850
12996
|
if (line.event.kind === "tool_started" && STAGE_DAEMON_OWNED_TOOLS.includes(line.event.payload.toolName)) {
|
|
12851
12997
|
return;
|
|
12852
12998
|
}
|
|
@@ -12866,7 +13012,7 @@ class Worker {
|
|
|
12866
13012
|
currentTask: motorTask,
|
|
12867
13013
|
progressPercent: MOTOR_RUN_PROGRESS_PERCENT
|
|
12868
13014
|
}).catch((err) => {
|
|
12869
|
-
|
|
13015
|
+
log35.warn(this.tag, `motor session heartbeat failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
12870
13016
|
});
|
|
12871
13017
|
}, MOTOR_SESSION_HEARTBEAT_MS);
|
|
12872
13018
|
heartbeat.unref?.();
|
|
@@ -12940,7 +13086,7 @@ class Worker {
|
|
|
12940
13086
|
break;
|
|
12941
13087
|
case "reclaim_refused":
|
|
12942
13088
|
this.held = true;
|
|
12943
|
-
|
|
13089
|
+
log35.info(this.tag, `#${card.short_id} stage reclaim refused (${outcome.reason})${outcome.released ? " — released the daemon's claim" : ""}`);
|
|
12944
13090
|
break;
|
|
12945
13091
|
case "advanced":
|
|
12946
13092
|
case "completed_terminal":
|
|
@@ -12964,7 +13110,7 @@ class Worker {
|
|
|
12964
13110
|
pushBranch3(this.branchName, worktreePath);
|
|
12965
13111
|
pushed = true;
|
|
12966
13112
|
} catch (err) {
|
|
12967
|
-
|
|
13113
|
+
log35.error(this.tag, `push after the motor stage "${ctx.stage.name}" failed for ${this.branchName}: ${err instanceof Error ? err.message : err}`);
|
|
12968
13114
|
}
|
|
12969
13115
|
if (pushed) {
|
|
12970
13116
|
await recordBranchProvenance(this.client, card, this.branchName);
|
|
@@ -12976,7 +13122,7 @@ class Worker {
|
|
|
12976
13122
|
if (completionColumn) {
|
|
12977
13123
|
await transferCardToCompletion({ client: this.client, tag: this.tag }, card, completionColumn, this.onCardCompleted);
|
|
12978
13124
|
} else {
|
|
12979
|
-
|
|
13125
|
+
log35.warn(this.tag, `completion.moveToColumn is empty — #${card.short_id} stays in its current column after the motor stage run`);
|
|
12980
13126
|
}
|
|
12981
13127
|
await this.closeoutMotorWorktree(card);
|
|
12982
13128
|
}
|
|
@@ -12987,7 +13133,7 @@ class Worker {
|
|
|
12987
13133
|
try {
|
|
12988
13134
|
await teardownWorktree2(this.client, card.id, worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
12989
13135
|
} catch {
|
|
12990
|
-
|
|
13136
|
+
log35.warn(this.tag, "Failed to cleanup worktree after the motor stage run");
|
|
12991
13137
|
}
|
|
12992
13138
|
this.worktreePath = null;
|
|
12993
13139
|
}
|
|
@@ -13047,7 +13193,7 @@ ${prompt}`;
|
|
|
13047
13193
|
});
|
|
13048
13194
|
return handoff ? renderInheritedHandoffSection(handoff) : "";
|
|
13049
13195
|
} catch (err) {
|
|
13050
|
-
|
|
13196
|
+
log35.warn(this.tag, `inherited-handoff fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13051
13197
|
return "";
|
|
13052
13198
|
}
|
|
13053
13199
|
}
|
|
@@ -13067,9 +13213,9 @@ ${prompt}`;
|
|
|
13067
13213
|
commentType: "decision",
|
|
13068
13214
|
agentSessionId: this.sessionId ?? undefined
|
|
13069
13215
|
});
|
|
13070
|
-
|
|
13216
|
+
log35.info(this.tag, `Wrote stage handoff for #${card.short_id} (stage "${stage.name}")`);
|
|
13071
13217
|
} catch (err) {
|
|
13072
|
-
|
|
13218
|
+
log35.warn(this.tag, `stage-handoff write failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13073
13219
|
}
|
|
13074
13220
|
}
|
|
13075
13221
|
async collectStageGateEvidence(card, stage, worktreePath, subtasks) {
|
|
@@ -13081,12 +13227,12 @@ ${prompt}`;
|
|
|
13081
13227
|
return null;
|
|
13082
13228
|
}
|
|
13083
13229
|
if (gate.pendingEngine === true) {
|
|
13084
|
-
|
|
13230
|
+
log35.info(this.tag, `Stage "${stage.name}" gate "${gate.kind}" is advisory — skipping enforcement`);
|
|
13085
13231
|
return null;
|
|
13086
13232
|
}
|
|
13087
13233
|
const review = gate.kind === "review_passed" ? parseReviewOutput(this.lastRunText) : undefined;
|
|
13088
13234
|
if (review) {
|
|
13089
|
-
|
|
13235
|
+
log35.info(this.tag, `Review-gated stage "${stage.name}" verdict: ${review.verdict} (${review.findings.length} finding(s))`);
|
|
13090
13236
|
}
|
|
13091
13237
|
const registry = buildGateCollectorRegistry2({
|
|
13092
13238
|
build: {
|
|
@@ -13103,7 +13249,7 @@ ${prompt}`;
|
|
|
13103
13249
|
return [];
|
|
13104
13250
|
const criteria = await fetchAcceptanceCriteria(this.client, this.tag, card);
|
|
13105
13251
|
if (criteria.length > 0) {
|
|
13106
|
-
|
|
13252
|
+
log35.info(this.tag, `Grading the artifact gate for #${card.short_id} against ${criteria.length} plan criteria instead of the stage's own rubric`);
|
|
13107
13253
|
}
|
|
13108
13254
|
return criteria;
|
|
13109
13255
|
})()
|
|
@@ -13125,10 +13271,10 @@ ${prompt}`;
|
|
|
13125
13271
|
const evaluation = gateEvaluate(gate, evidence);
|
|
13126
13272
|
const insert = toStageGateEvidenceInsert(context, evidence);
|
|
13127
13273
|
await this.client.request("POST", `/cards/${encodeURIComponent(card.id)}/stage-gate-evidence`, insert);
|
|
13128
|
-
|
|
13274
|
+
log35.info(this.tag, `Recorded ${gate.kind} gate evidence for #${card.short_id} stage "${stage.name}": result=${evidence.result} passed=${evaluation.passed}`);
|
|
13129
13275
|
return evaluation;
|
|
13130
13276
|
} catch (err) {
|
|
13131
|
-
|
|
13277
|
+
log35.warn(this.tag, `stage-gate evidence collection failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13132
13278
|
return null;
|
|
13133
13279
|
}
|
|
13134
13280
|
}
|
|
@@ -13146,7 +13292,7 @@ ${prompt}`;
|
|
|
13146
13292
|
agentSessionId: this.sessionId
|
|
13147
13293
|
});
|
|
13148
13294
|
} catch (err) {
|
|
13149
|
-
|
|
13295
|
+
log35.warn(this.tag, `stage advancement failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
13150
13296
|
return { kind: "no_advance" };
|
|
13151
13297
|
}
|
|
13152
13298
|
}
|
|
@@ -13172,7 +13318,7 @@ ${prompt}`;
|
|
|
13172
13318
|
this.modelChoice = choice;
|
|
13173
13319
|
const { model, escalated, source } = choice;
|
|
13174
13320
|
if (source !== "policy" || escalated) {
|
|
13175
|
-
|
|
13321
|
+
log35.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${this.sizing?.tier ?? "none"})`);
|
|
13176
13322
|
}
|
|
13177
13323
|
return model;
|
|
13178
13324
|
}
|
|
@@ -13191,7 +13337,7 @@ ${prompt}`;
|
|
|
13191
13337
|
encoding: "utf-8"
|
|
13192
13338
|
}).trim();
|
|
13193
13339
|
} catch (err) {
|
|
13194
|
-
|
|
13340
|
+
log35.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`);
|
|
13195
13341
|
return { status: "failed", reason: "spawn" };
|
|
13196
13342
|
}
|
|
13197
13343
|
const outcome = await sizeRun({
|
|
@@ -13205,9 +13351,9 @@ ${prompt}`;
|
|
|
13205
13351
|
scoreBands: claude.scoreBands
|
|
13206
13352
|
});
|
|
13207
13353
|
if (outcome.status === "sized") {
|
|
13208
|
-
|
|
13354
|
+
log35.info(this.tag, `Sized #${card.short_id}: complexity ${outcome.sizing.complexity}/10 -> ${outcome.sizing.tier}`);
|
|
13209
13355
|
} else if (outcome.status === "failed") {
|
|
13210
|
-
|
|
13356
|
+
log35.warn(this.tag, `Sizing #${card.short_id} failed (${outcome.reason}) — using the policy fallback`);
|
|
13211
13357
|
}
|
|
13212
13358
|
return outcome;
|
|
13213
13359
|
}
|
|
@@ -13223,14 +13369,14 @@ ${prompt}`;
|
|
|
13223
13369
|
]);
|
|
13224
13370
|
const outcome = decideJevSizing(response);
|
|
13225
13371
|
if (outcome.status === "failed") {
|
|
13226
|
-
|
|
13372
|
+
log35.warn(this.tag, `Sizing #${card.short_id} failed (${outcome.reason}): the sizing route answered with no usable tier — using the policy fallback`);
|
|
13227
13373
|
return outcome;
|
|
13228
13374
|
}
|
|
13229
|
-
|
|
13375
|
+
log35.info(this.tag, `Sized #${card.short_id} with ${describeJevSizing(response, outcome.sizing.tier)}`);
|
|
13230
13376
|
return outcome;
|
|
13231
13377
|
} catch (err) {
|
|
13232
13378
|
const reason = err instanceof JevSizingDeadline ? "timeout" : "spawn";
|
|
13233
|
-
|
|
13379
|
+
log35.warn(this.tag, `Sizing #${card.short_id} failed (${reason}): ${err instanceof Error ? err.message : String(err)} — using the policy fallback`);
|
|
13234
13380
|
return { status: "failed", reason };
|
|
13235
13381
|
} finally {
|
|
13236
13382
|
if (deadline)
|
|
@@ -13271,7 +13417,7 @@ ${prompt}`;
|
|
|
13271
13417
|
const measured = measureRunCommits(this.worktreePath, this.config.worktree.baseBranch, this.runBaselineSha);
|
|
13272
13418
|
if (measured !== "none") {
|
|
13273
13419
|
if (measured === "unknown") {
|
|
13274
|
-
|
|
13420
|
+
log35.debug(this.tag, `no-commit check could not attribute this run's commits — staying quiet on ${this.branchName ?? "this branch"}`);
|
|
13275
13421
|
}
|
|
13276
13422
|
return;
|
|
13277
13423
|
}
|
|
@@ -13280,7 +13426,7 @@ ${prompt}`;
|
|
|
13280
13426
|
recordNoCommitsYet(trigger, numTurns) {
|
|
13281
13427
|
const turnBudget = this.effectiveMaxTurns;
|
|
13282
13428
|
const threshold = noCommitNoticeThreshold(turnBudget);
|
|
13283
|
-
|
|
13429
|
+
log35.warn(this.tag, `No commits yet on ${this.branchName ?? "this run's branch"} — ${this.runToolCalls} tool calls into a ${turnBudget}-turn budget (${trigger})`);
|
|
13284
13430
|
this.cliRunner?.record({
|
|
13285
13431
|
kind: "no_commits_yet",
|
|
13286
13432
|
source: "system",
|
|
@@ -13334,9 +13480,9 @@ ${prompt}`;
|
|
|
13334
13480
|
agentSessionId: this.sessionId ?? undefined
|
|
13335
13481
|
});
|
|
13336
13482
|
giveUpCommentId = res?.comment?.id ?? null;
|
|
13337
|
-
|
|
13483
|
+
log35.warn(this.tag, `gave up on ${cardId} after ${attempts} attempts`);
|
|
13338
13484
|
} catch (err) {
|
|
13339
|
-
|
|
13485
|
+
log35.warn(this.tag, `failed to post give-up comment for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13340
13486
|
}
|
|
13341
13487
|
if (this.config.budget.pause.enabled) {
|
|
13342
13488
|
const waitHours = this.config.budget.pause.waitHours;
|
|
@@ -13350,7 +13496,7 @@ ${prompt}`;
|
|
|
13350
13496
|
awaitingDecisionUntil: new Date(until).toISOString()
|
|
13351
13497
|
});
|
|
13352
13498
|
} catch (err) {
|
|
13353
|
-
|
|
13499
|
+
log35.warn(this.tag, `Failed to mark the attempt cap pending for ${cardId}: ${err}`);
|
|
13354
13500
|
}
|
|
13355
13501
|
try {
|
|
13356
13502
|
await this.stateStore.markAwaitingDecision(cardId, {
|
|
@@ -13359,19 +13505,19 @@ ${prompt}`;
|
|
|
13359
13505
|
agentIdentifier: this.sessionIdentifier
|
|
13360
13506
|
});
|
|
13361
13507
|
} catch (err) {
|
|
13362
|
-
|
|
13508
|
+
log35.warn(this.tag, `Failed to record the attempt-cap decision locally for ${cardId}: ${err}`);
|
|
13363
13509
|
}
|
|
13364
13510
|
}
|
|
13365
13511
|
}
|
|
13366
13512
|
}
|
|
13367
13513
|
} catch (err) {
|
|
13368
|
-
|
|
13514
|
+
log35.warn(this.tag, `recordOutcome(${outcome}) failed: ${err instanceof Error ? err.message : err}`);
|
|
13369
13515
|
}
|
|
13370
13516
|
}
|
|
13371
13517
|
async pause() {
|
|
13372
13518
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13373
13519
|
return;
|
|
13374
|
-
|
|
13520
|
+
log35.info(this.tag, `Pausing work on ${this.cardId}`);
|
|
13375
13521
|
signalGroup2(this.process, "SIGSTOP");
|
|
13376
13522
|
if (this.timeoutTimer) {
|
|
13377
13523
|
clearTimeout(this.timeoutTimer);
|
|
@@ -13385,17 +13531,17 @@ ${prompt}`;
|
|
|
13385
13531
|
status: "paused"
|
|
13386
13532
|
});
|
|
13387
13533
|
} catch {
|
|
13388
|
-
|
|
13534
|
+
log35.warn(this.tag, "Failed to update agent session to paused");
|
|
13389
13535
|
}
|
|
13390
13536
|
}
|
|
13391
13537
|
}
|
|
13392
13538
|
async resume() {
|
|
13393
13539
|
if (!this.isActive || !this.process || this.process.killed)
|
|
13394
13540
|
return;
|
|
13395
|
-
|
|
13541
|
+
log35.info(this.tag, `Resuming work on ${this.cardId}`);
|
|
13396
13542
|
signalGroup2(this.process, "SIGCONT");
|
|
13397
13543
|
this.timeoutTimer = setTimeout(() => {
|
|
13398
|
-
|
|
13544
|
+
log35.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
13399
13545
|
this.timedOut = true;
|
|
13400
13546
|
this.cancel("timeout");
|
|
13401
13547
|
}, this.config.maxTimeout);
|
|
@@ -13407,7 +13553,7 @@ ${prompt}`;
|
|
|
13407
13553
|
status: "working"
|
|
13408
13554
|
});
|
|
13409
13555
|
} catch {
|
|
13410
|
-
|
|
13556
|
+
log35.warn(this.tag, "Failed to update agent session to working");
|
|
13411
13557
|
}
|
|
13412
13558
|
}
|
|
13413
13559
|
}
|
|
@@ -13416,7 +13562,7 @@ ${prompt}`;
|
|
|
13416
13562
|
return;
|
|
13417
13563
|
this.aborted = true;
|
|
13418
13564
|
this.state = "cancelling";
|
|
13419
|
-
|
|
13565
|
+
log35.info(this.tag, `Cancelling work on ${this.cardId}`);
|
|
13420
13566
|
this.motorAbort?.abort();
|
|
13421
13567
|
if (this.sdkRunner) {
|
|
13422
13568
|
await this.sdkRunner.stop(this.timedOut ? "timeout" : "user_requested");
|
|
@@ -13434,14 +13580,14 @@ ${prompt}`;
|
|
|
13434
13580
|
...buildTokenPayload(stats)
|
|
13435
13581
|
});
|
|
13436
13582
|
} catch (err) {
|
|
13437
|
-
|
|
13583
|
+
log35.warn(this.tag, `endAgentSession after cancel failed: ${err instanceof Error ? err.message : err}`);
|
|
13438
13584
|
}
|
|
13439
13585
|
}
|
|
13440
13586
|
}
|
|
13441
13587
|
async runPlanningPhase(enriched) {
|
|
13442
13588
|
const planning = this.config.planning;
|
|
13443
13589
|
const { card } = enriched;
|
|
13444
|
-
|
|
13590
|
+
log35.info(this.tag, `Planning pass for #${card.short_id} (mode=${planning.mode}, model=${planning.model})`);
|
|
13445
13591
|
await this.client.updateAgentProgress(card.id, {
|
|
13446
13592
|
agentIdentifier: this.sessionIdentifier,
|
|
13447
13593
|
agentName: AGENT_NAME,
|
|
@@ -13454,7 +13600,7 @@ ${prompt}`;
|
|
|
13454
13600
|
let planTimedOut = false;
|
|
13455
13601
|
const planTimeout = setTimeout(() => {
|
|
13456
13602
|
planTimedOut = true;
|
|
13457
|
-
|
|
13603
|
+
log35.warn(this.tag, "Planning pass exceeded timeout — abandoning, implementing directly");
|
|
13458
13604
|
if (this.sdkRunner) {
|
|
13459
13605
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13460
13606
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13472,7 +13618,7 @@ ${prompt}`;
|
|
|
13472
13618
|
initialPhase: "planning"
|
|
13473
13619
|
});
|
|
13474
13620
|
} catch (err) {
|
|
13475
|
-
|
|
13621
|
+
log35.warn(this.tag, `Planning pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13476
13622
|
return false;
|
|
13477
13623
|
} finally {
|
|
13478
13624
|
clearTimeout(planTimeout);
|
|
@@ -13483,7 +13629,7 @@ ${prompt}`;
|
|
|
13483
13629
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13484
13630
|
const planText = stats?.lastAssistantText ?? "";
|
|
13485
13631
|
if (!planText.trim()) {
|
|
13486
|
-
|
|
13632
|
+
log35.warn(this.tag, `Planning pass for #${card.short_id} produced no text — implementing directly`);
|
|
13487
13633
|
return false;
|
|
13488
13634
|
}
|
|
13489
13635
|
const artifact = extractPlanArtifact(planText, card.title);
|
|
@@ -13504,9 +13650,9 @@ ${prompt}`;
|
|
|
13504
13650
|
});
|
|
13505
13651
|
planId = createdId;
|
|
13506
13652
|
}
|
|
13507
|
-
|
|
13653
|
+
log35.info(this.tag, `Stored plan ${planId ?? "(unlinked)"} for #${card.short_id} (${artifact.tasks.length} tasks)`);
|
|
13508
13654
|
} catch (err) {
|
|
13509
|
-
|
|
13655
|
+
log35.warn(this.tag, `Failed to store/link plan (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13510
13656
|
}
|
|
13511
13657
|
if (planning.mode === "gated" && planId) {
|
|
13512
13658
|
try {
|
|
@@ -13525,11 +13671,11 @@ ${prompt}`;
|
|
|
13525
13671
|
...buildTokenPayload(stats)
|
|
13526
13672
|
}
|
|
13527
13673
|
}, { store: this.stateStore, runId: this.runId ?? undefined });
|
|
13528
|
-
|
|
13674
|
+
log35.info(this.tag, `#${card.short_id} parked in "${planning.awaitingApprovalColumn}" for plan approval`);
|
|
13529
13675
|
this.lastSessionStats = undefined;
|
|
13530
13676
|
return true;
|
|
13531
13677
|
} catch (err) {
|
|
13532
|
-
|
|
13678
|
+
log35.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}`);
|
|
13533
13679
|
}
|
|
13534
13680
|
}
|
|
13535
13681
|
if (planId && planning.postComment) {
|
|
@@ -13539,7 +13685,7 @@ ${prompt}`;
|
|
|
13539
13685
|
agentSessionId: this.sessionId ?? undefined
|
|
13540
13686
|
});
|
|
13541
13687
|
} catch (err) {
|
|
13542
|
-
|
|
13688
|
+
log35.warn(this.tag, `Failed to post advisory plan comment (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13543
13689
|
}
|
|
13544
13690
|
}
|
|
13545
13691
|
return false;
|
|
@@ -13549,10 +13695,10 @@ ${prompt}`;
|
|
|
13549
13695
|
const { card } = enriched;
|
|
13550
13696
|
const existing = await this.loadPinnedContract(card.id);
|
|
13551
13697
|
if (existing) {
|
|
13552
|
-
|
|
13698
|
+
log35.info(this.tag, `Contract already pinned for #${card.short_id} (${existing.assertions.length} assertions) — reusing`);
|
|
13553
13699
|
return;
|
|
13554
13700
|
}
|
|
13555
|
-
|
|
13701
|
+
log35.info(this.tag, `Contract pass for #${card.short_id} (model=${contractCfg.model})`);
|
|
13556
13702
|
await this.client.updateAgentProgress(card.id, {
|
|
13557
13703
|
agentIdentifier: this.sessionIdentifier,
|
|
13558
13704
|
agentName: AGENT_NAME,
|
|
@@ -13565,7 +13711,7 @@ ${prompt}`;
|
|
|
13565
13711
|
let contractTimedOut = false;
|
|
13566
13712
|
const contractTimeout = setTimeout(() => {
|
|
13567
13713
|
contractTimedOut = true;
|
|
13568
|
-
|
|
13714
|
+
log35.warn(this.tag, "Contract pass exceeded timeout — abandoning, implementing directly");
|
|
13569
13715
|
if (this.sdkRunner) {
|
|
13570
13716
|
this.sdkRunner.stop("timeout").catch(() => {});
|
|
13571
13717
|
} else if (this.process && !this.process.killed) {
|
|
@@ -13583,7 +13729,7 @@ ${prompt}`;
|
|
|
13583
13729
|
initialPhase: "planning"
|
|
13584
13730
|
});
|
|
13585
13731
|
} catch (err) {
|
|
13586
|
-
|
|
13732
|
+
log35.warn(this.tag, `Contract pass failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13587
13733
|
return;
|
|
13588
13734
|
} finally {
|
|
13589
13735
|
clearTimeout(contractTimeout);
|
|
@@ -13594,12 +13740,12 @@ ${prompt}`;
|
|
|
13594
13740
|
await this.chargeDailyLedger(card.id, card.short_id);
|
|
13595
13741
|
const contractText = stats?.lastAssistantText ?? "";
|
|
13596
13742
|
if (!contractText.trim()) {
|
|
13597
|
-
|
|
13743
|
+
log35.warn(this.tag, `Contract pass for #${card.short_id} produced no text — implementing directly`);
|
|
13598
13744
|
return;
|
|
13599
13745
|
}
|
|
13600
13746
|
const contract = extractContract(contractText, card);
|
|
13601
13747
|
if (contract.assertions.length < contractCfg.minAssertions) {
|
|
13602
|
-
|
|
13748
|
+
log35.warn(this.tag, `Contract for #${card.short_id} had ${contract.assertions.length} assertion(s) (< ${contractCfg.minAssertions}) — not pinning, implementing directly`);
|
|
13603
13749
|
return;
|
|
13604
13750
|
}
|
|
13605
13751
|
try {
|
|
@@ -13607,9 +13753,9 @@ ${prompt}`;
|
|
|
13607
13753
|
commentType: "decision",
|
|
13608
13754
|
agentSessionId: this.sessionId ?? undefined
|
|
13609
13755
|
});
|
|
13610
|
-
|
|
13756
|
+
log35.info(this.tag, `Pinned acceptance contract for #${card.short_id} (${contract.assertions.length} assertions)`);
|
|
13611
13757
|
} catch (err) {
|
|
13612
|
-
|
|
13758
|
+
log35.warn(this.tag, `Failed to pin contract (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13613
13759
|
}
|
|
13614
13760
|
}
|
|
13615
13761
|
async loadPinnedContract(cardId) {
|
|
@@ -13619,7 +13765,7 @@ ${prompt}`;
|
|
|
13619
13765
|
return null;
|
|
13620
13766
|
return extractPinnedContract(comments, this.identity);
|
|
13621
13767
|
} catch (err) {
|
|
13622
|
-
|
|
13768
|
+
log35.warn(this.tag, `pinned-contract fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
13623
13769
|
return null;
|
|
13624
13770
|
}
|
|
13625
13771
|
}
|
|
@@ -13632,13 +13778,13 @@ ${prompt}`;
|
|
|
13632
13778
|
const res = await this.client.getPendingUserMessages(this.cardId, this.sessionId, this.lastDrainedSeq);
|
|
13633
13779
|
messages = res.messages ?? [];
|
|
13634
13780
|
} catch (err) {
|
|
13635
|
-
|
|
13781
|
+
log35.warn(this.tag, `Failed to fetch steering messages (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13636
13782
|
return;
|
|
13637
13783
|
}
|
|
13638
13784
|
if (messages.length === 0)
|
|
13639
13785
|
return;
|
|
13640
13786
|
this.lastDrainedSeq = Math.max(this.lastDrainedSeq, ...messages.map((m) => m.seq));
|
|
13641
|
-
|
|
13787
|
+
log35.info(this.tag, `Steering #${card.short_id}: resuming with ${messages.length} queued message(s)`);
|
|
13642
13788
|
this.state = "running";
|
|
13643
13789
|
await this.recordPhase("running");
|
|
13644
13790
|
try {
|
|
@@ -13650,7 +13796,7 @@ ${prompt}`;
|
|
|
13650
13796
|
...this.activeRunSpawnOpts ?? {}
|
|
13651
13797
|
});
|
|
13652
13798
|
} catch (err) {
|
|
13653
|
-
|
|
13799
|
+
log35.warn(this.tag, `Steering resume failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
13654
13800
|
return;
|
|
13655
13801
|
}
|
|
13656
13802
|
}
|
|
@@ -13685,10 +13831,10 @@ ${prompt}`;
|
|
|
13685
13831
|
"--",
|
|
13686
13832
|
prompt
|
|
13687
13833
|
];
|
|
13688
|
-
|
|
13834
|
+
log35.info(this.tag, `Spawning: claude ${args.slice(0, 4).join(" ")} ...`);
|
|
13689
13835
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13690
13836
|
if (runLog) {
|
|
13691
|
-
|
|
13837
|
+
log35.info(this.tag, `Run log: ${runLog.path}`);
|
|
13692
13838
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} started=${new Date().toISOString()}
|
|
13693
13839
|
` + `# args: ${args.slice(0, -2).join(" ")} -- <prompt:${prompt.length} chars>
|
|
13694
13840
|
|
|
@@ -13725,7 +13871,7 @@ ${prompt}`;
|
|
|
13725
13871
|
});
|
|
13726
13872
|
}
|
|
13727
13873
|
parser.on("parse_error", (msg) => {
|
|
13728
|
-
|
|
13874
|
+
log35.debug(this.tag, `Stream parse error (non-fatal): ${msg}`);
|
|
13729
13875
|
runLog?.stream.write(`
|
|
13730
13876
|
[parse_error] ${msg}
|
|
13731
13877
|
`);
|
|
@@ -13798,10 +13944,10 @@ ${prompt}`;
|
|
|
13798
13944
|
const disallowedTools = opts.disallowedTools ? opts.disallowedTools.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
13799
13945
|
const initialPhase = opts.initialPhase ?? "exploring";
|
|
13800
13946
|
const maxBudgetUsd = this.effectiveMaxBudgetUsd;
|
|
13801
|
-
|
|
13947
|
+
log35.info(this.tag, `Spawning Agent SDK runner (model=${model}, maxBudgetUsd=${maxBudgetUsd || "off"}, maxTurns=${maxTurns}${opts.resumeSessionId ? ", resume" : ""})`);
|
|
13802
13948
|
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
13803
13949
|
if (runLog) {
|
|
13804
|
-
|
|
13950
|
+
log35.info(this.tag, `Run log: ${runLog.path}`);
|
|
13805
13951
|
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} runner=sdk started=${new Date().toISOString()}
|
|
13806
13952
|
` + `# model=${model} maxTurns=${maxTurns} <prompt:${prompt.length} chars>
|
|
13807
13953
|
|
|
@@ -13936,7 +14082,7 @@ ${prompt}`;
|
|
|
13936
14082
|
try {
|
|
13937
14083
|
await teardownWorktree2(this.client, this.cardId, this.worktreePath, this.branchName ?? undefined, this.sessionId);
|
|
13938
14084
|
} catch {
|
|
13939
|
-
|
|
14085
|
+
log35.warn(this.tag, "Failed to cleanup worktree");
|
|
13940
14086
|
}
|
|
13941
14087
|
}
|
|
13942
14088
|
this.process = null;
|
|
@@ -13955,7 +14101,7 @@ ${prompt}`;
|
|
|
13955
14101
|
this.chargedTokens = emptyTokens();
|
|
13956
14102
|
}
|
|
13957
14103
|
}
|
|
13958
|
-
var
|
|
14104
|
+
var TAG32 = "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;
|
|
13959
14105
|
var init_worker = __esm(() => {
|
|
13960
14106
|
init_src();
|
|
13961
14107
|
init_board_helpers();
|
|
@@ -13987,7 +14133,7 @@ var init_worker = __esm(() => {
|
|
|
13987
14133
|
});
|
|
13988
14134
|
|
|
13989
14135
|
// src/workspace-model-cache.ts
|
|
13990
|
-
import { log as
|
|
14136
|
+
import { log as log36 } from "@gethmy/harness";
|
|
13991
14137
|
|
|
13992
14138
|
class WorkspaceModelCache {
|
|
13993
14139
|
fetcher;
|
|
@@ -14022,7 +14168,7 @@ class WorkspaceModelCache {
|
|
|
14022
14168
|
this.stale = false;
|
|
14023
14169
|
} catch (err) {
|
|
14024
14170
|
this.stale = true;
|
|
14025
|
-
|
|
14171
|
+
log36.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)}`);
|
|
14026
14172
|
}
|
|
14027
14173
|
return this.value;
|
|
14028
14174
|
}
|
|
@@ -14033,7 +14179,7 @@ var init_workspace_model_cache = () => {};
|
|
|
14033
14179
|
import {
|
|
14034
14180
|
cooldownMsFor,
|
|
14035
14181
|
describeApiError as describeApiError3,
|
|
14036
|
-
log as
|
|
14182
|
+
log as log37
|
|
14037
14183
|
} from "@gethmy/harness";
|
|
14038
14184
|
function httpStatusOf(err) {
|
|
14039
14185
|
if (typeof err !== "object" || err === null)
|
|
@@ -14126,11 +14272,11 @@ class Pool {
|
|
|
14126
14272
|
}
|
|
14127
14273
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
14128
14274
|
if (card.done || card.archived_at) {
|
|
14129
|
-
|
|
14275
|
+
log37.debug(TAG33, `#${card.short_id} skipped — ${card.done ? "done" : "archived"} (finished work is never enqueued, #1102)`);
|
|
14130
14276
|
return;
|
|
14131
14277
|
}
|
|
14132
14278
|
if (this.isCardKnown(card.id) || this.reservations.has(card.id)) {
|
|
14133
|
-
|
|
14279
|
+
log37.debug(TAG33, `Card ${card.id} already queued, active, or reserved, skipping`);
|
|
14134
14280
|
return;
|
|
14135
14281
|
}
|
|
14136
14282
|
this.reservations.add(card.id);
|
|
@@ -14138,25 +14284,25 @@ class Pool {
|
|
|
14138
14284
|
try {
|
|
14139
14285
|
const account = this.accountGuard?.check();
|
|
14140
14286
|
if (account && !account.running) {
|
|
14141
|
-
|
|
14287
|
+
log37.debug(TAG33, `#${card.short_id} held — ${account.detail}`);
|
|
14142
14288
|
await this.emitWaiting(card.id, account.detail);
|
|
14143
14289
|
return;
|
|
14144
14290
|
}
|
|
14145
14291
|
if (mode === "implement") {
|
|
14146
14292
|
if (this.authPaused) {
|
|
14147
|
-
|
|
14293
|
+
log37.debug(TAG33, `#${card.short_id} held — agent paused (auth error)`);
|
|
14148
14294
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
14149
14295
|
return;
|
|
14150
14296
|
}
|
|
14151
14297
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
14152
14298
|
if (cooldownMs > 0) {
|
|
14153
|
-
|
|
14299
|
+
log37.debug(TAG33, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
14154
14300
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
14155
14301
|
return;
|
|
14156
14302
|
}
|
|
14157
14303
|
const stageHold = classifyStageHold(card, this.stateStore.getStageHold(card.id), Date.now());
|
|
14158
14304
|
if (stageHold.kind === "holding") {
|
|
14159
|
-
|
|
14305
|
+
log37.debug(TAG33, `#${card.short_id} held — ${stageHold.detail}`);
|
|
14160
14306
|
return;
|
|
14161
14307
|
}
|
|
14162
14308
|
if (stageHold.kind === "stale") {
|
|
@@ -14167,19 +14313,19 @@ class Pool {
|
|
|
14167
14313
|
if (decision.reason === "daily_budget") {
|
|
14168
14314
|
await this.denyDailyBudget(card, decision.detail);
|
|
14169
14315
|
} else {
|
|
14170
|
-
|
|
14316
|
+
log37.debug(TAG33, `#${card.short_id} skipped (${decision.reason}): ${decision.detail}`);
|
|
14171
14317
|
}
|
|
14172
14318
|
return;
|
|
14173
14319
|
}
|
|
14174
14320
|
const { blockers, successors } = await getChainSignals(this.client, card, this.projectId);
|
|
14175
14321
|
chainSuccessors = successors;
|
|
14176
14322
|
if (blockers === null) {
|
|
14177
|
-
|
|
14323
|
+
log37.warn(TAG33, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
14178
14324
|
return;
|
|
14179
14325
|
}
|
|
14180
14326
|
if (blockers.length > 0) {
|
|
14181
14327
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
14182
|
-
|
|
14328
|
+
log37.info(TAG33, `#${card.short_id} blocked by ${list} — waiting`);
|
|
14183
14329
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
14184
14330
|
return;
|
|
14185
14331
|
}
|
|
@@ -14211,9 +14357,9 @@ class Pool {
|
|
|
14211
14357
|
const reason = `Daily budget reached — waiting for reset (${detail})`;
|
|
14212
14358
|
const line = `#${card.short_id} skipped (daily_budget): ${detail}`;
|
|
14213
14359
|
if (this.lastWaitingEmit.get(card.id) === reason)
|
|
14214
|
-
|
|
14360
|
+
log37.debug(TAG33, line);
|
|
14215
14361
|
else
|
|
14216
|
-
|
|
14362
|
+
log37.warn(TAG33, line);
|
|
14217
14363
|
await this.emitWaiting(card.id, reason);
|
|
14218
14364
|
}
|
|
14219
14365
|
lastWaitingEmit = new Map;
|
|
@@ -14230,7 +14376,7 @@ class Pool {
|
|
|
14230
14376
|
});
|
|
14231
14377
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
14232
14378
|
} catch (err) {
|
|
14233
|
-
|
|
14379
|
+
log37.debug(TAG33, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14234
14380
|
}
|
|
14235
14381
|
}
|
|
14236
14382
|
noteApiError(err) {
|
|
@@ -14238,13 +14384,13 @@ class Pool {
|
|
|
14238
14384
|
return;
|
|
14239
14385
|
if (err.kind === "spend_limit") {
|
|
14240
14386
|
this.accountGuard?.halt("spend_limit").catch((hErr) => {
|
|
14241
|
-
|
|
14387
|
+
log37.error(TAG33, `Failed to latch the account halt: ${hErr}`);
|
|
14242
14388
|
});
|
|
14243
14389
|
return;
|
|
14244
14390
|
}
|
|
14245
14391
|
if (err.kind === "auth") {
|
|
14246
14392
|
if (!this.authPaused) {
|
|
14247
|
-
|
|
14393
|
+
log37.error(TAG33, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
14248
14394
|
}
|
|
14249
14395
|
this.authPaused = true;
|
|
14250
14396
|
return;
|
|
@@ -14253,7 +14399,7 @@ class Pool {
|
|
|
14253
14399
|
const until = Date.now() + cooldownMs;
|
|
14254
14400
|
if (until > this.apiCooldownUntil) {
|
|
14255
14401
|
this.apiCooldownUntil = until;
|
|
14256
|
-
|
|
14402
|
+
log37.warn(TAG33, `${describeApiError3(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
14257
14403
|
}
|
|
14258
14404
|
}
|
|
14259
14405
|
apiCooldownRemainingMs() {
|
|
@@ -14267,13 +14413,13 @@ class Pool {
|
|
|
14267
14413
|
const removed = queue.remove(cardId);
|
|
14268
14414
|
if (removed) {
|
|
14269
14415
|
this.cardDataCache.delete(cardId);
|
|
14270
|
-
|
|
14416
|
+
log37.info(TAG33, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
14271
14417
|
return;
|
|
14272
14418
|
}
|
|
14273
14419
|
}
|
|
14274
14420
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
14275
14421
|
if (worker) {
|
|
14276
|
-
|
|
14422
|
+
log37.info(TAG33, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
14277
14423
|
await worker.cancel("unassigned");
|
|
14278
14424
|
}
|
|
14279
14425
|
}
|
|
@@ -14330,10 +14476,10 @@ class Pool {
|
|
|
14330
14476
|
}
|
|
14331
14477
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
14332
14478
|
if (!worker) {
|
|
14333
|
-
|
|
14479
|
+
log37.debug(TAG33, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
14334
14480
|
return;
|
|
14335
14481
|
}
|
|
14336
|
-
|
|
14482
|
+
log37.info(TAG33, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
14337
14483
|
switch (command) {
|
|
14338
14484
|
case "pause":
|
|
14339
14485
|
await worker.pause();
|
|
@@ -14381,7 +14527,7 @@ class Pool {
|
|
|
14381
14527
|
};
|
|
14382
14528
|
}
|
|
14383
14529
|
async shutdown() {
|
|
14384
|
-
|
|
14530
|
+
log37.info(TAG33, "Shutting down pool...");
|
|
14385
14531
|
this.shuttingDown = true;
|
|
14386
14532
|
const active = [
|
|
14387
14533
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -14389,7 +14535,7 @@ class Pool {
|
|
|
14389
14535
|
];
|
|
14390
14536
|
await Promise.all(active.map((w) => w.cancel("shutdown")));
|
|
14391
14537
|
this.sleepGuard.stop();
|
|
14392
|
-
|
|
14538
|
+
log37.info(TAG33, "Pool shutdown complete");
|
|
14393
14539
|
}
|
|
14394
14540
|
async drainBudgetDecisions(cardId) {
|
|
14395
14541
|
const targets = cardId ? [cardId] : [
|
|
@@ -14424,7 +14570,7 @@ class Pool {
|
|
|
14424
14570
|
await this.forgetDeletedCard(cardId, run);
|
|
14425
14571
|
return;
|
|
14426
14572
|
}
|
|
14427
|
-
|
|
14573
|
+
log37.warn(TAG33, `getBudgetDecisions failed for ${cardId}: ${err}`);
|
|
14428
14574
|
return;
|
|
14429
14575
|
}
|
|
14430
14576
|
if (decisions.length > 0) {
|
|
@@ -14455,16 +14601,16 @@ class Pool {
|
|
|
14455
14601
|
errorMessage: "card deleted"
|
|
14456
14602
|
});
|
|
14457
14603
|
} catch (err) {
|
|
14458
|
-
|
|
14604
|
+
log37.warn(TAG33, `Failed to end the parked run for the deleted card ${cardId}: ${err}`);
|
|
14459
14605
|
}
|
|
14460
14606
|
}
|
|
14461
14607
|
try {
|
|
14462
14608
|
const dropped = await this.stateStore.forgetCard(cardId);
|
|
14463
14609
|
if (dropped) {
|
|
14464
|
-
|
|
14610
|
+
log37.info(TAG33, `Card ${cardId} no longer exists on the server — dropped from the local ledger; its budget question can never be answered`);
|
|
14465
14611
|
}
|
|
14466
14612
|
} catch (err) {
|
|
14467
|
-
|
|
14613
|
+
log37.warn(TAG33, `Failed to drop the deleted card ${cardId}: ${err}`);
|
|
14468
14614
|
}
|
|
14469
14615
|
}
|
|
14470
14616
|
async releaseExpiredPark(run) {
|
|
@@ -14478,7 +14624,7 @@ class Pool {
|
|
|
14478
14624
|
...run.blockerCommentId ? { replyToId: run.blockerCommentId } : {}
|
|
14479
14625
|
});
|
|
14480
14626
|
} catch (err) {
|
|
14481
|
-
|
|
14627
|
+
log37.warn(TAG33, `Failed to post the expired-park closing comment for ${run.cardId}: ${err}`);
|
|
14482
14628
|
}
|
|
14483
14629
|
try {
|
|
14484
14630
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14492,14 +14638,14 @@ class Pool {
|
|
|
14492
14638
|
});
|
|
14493
14639
|
}
|
|
14494
14640
|
} catch (err) {
|
|
14495
|
-
|
|
14641
|
+
log37.error(TAG33, `Failed to move #${run.cardShortId} after an expired park: ${err}`);
|
|
14496
14642
|
}
|
|
14497
14643
|
try {
|
|
14498
14644
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14499
14645
|
errorMessage: "budget decision expired"
|
|
14500
14646
|
});
|
|
14501
14647
|
} catch (err) {
|
|
14502
|
-
|
|
14648
|
+
log37.warn(TAG33, `Failed to release the expired park for ${run.cardId}: ${err}`);
|
|
14503
14649
|
}
|
|
14504
14650
|
}
|
|
14505
14651
|
async releaseExpiredAttemptCap(cardId, blockerCommentId) {
|
|
@@ -14509,7 +14655,7 @@ class Pool {
|
|
|
14509
14655
|
...blockerCommentId ? { replyToId: blockerCommentId } : {}
|
|
14510
14656
|
});
|
|
14511
14657
|
} catch (err) {
|
|
14512
|
-
|
|
14658
|
+
log37.warn(TAG33, `Failed to post the expired attempt-cap note for ${cardId}: ${err}`);
|
|
14513
14659
|
}
|
|
14514
14660
|
try {
|
|
14515
14661
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14518,7 +14664,7 @@ class Pool {
|
|
|
14518
14664
|
failureSummary: "The attempt-budget decision expired with no answer. Reassign the card to grant a fresh attempt."
|
|
14519
14665
|
});
|
|
14520
14666
|
} catch (err) {
|
|
14521
|
-
|
|
14667
|
+
log37.warn(TAG33, `Failed to end the expired attempt-cap session for ${cardId}: ${err}`);
|
|
14522
14668
|
}
|
|
14523
14669
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14524
14670
|
}
|
|
@@ -14529,16 +14675,16 @@ class Pool {
|
|
|
14529
14675
|
}
|
|
14530
14676
|
const account = this.accountGuard?.check();
|
|
14531
14677
|
if (account && !account.running) {
|
|
14532
|
-
|
|
14678
|
+
log37.debug(TAG33, `#${run.cardShortId}: granted continue held — ${account.detail}`);
|
|
14533
14679
|
return;
|
|
14534
14680
|
}
|
|
14535
14681
|
const first = !this.adoptAnnounced.has(run.runId);
|
|
14536
14682
|
this.adoptAnnounced.add(run.runId);
|
|
14537
14683
|
const line = `#${run.cardShortId}: granted continue never reached a worker — re-enqueueing (${run.pipeline})`;
|
|
14538
14684
|
if (first)
|
|
14539
|
-
|
|
14685
|
+
log37.warn(TAG33, line);
|
|
14540
14686
|
else
|
|
14541
|
-
|
|
14687
|
+
log37.debug(TAG33, line);
|
|
14542
14688
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14543
14689
|
}
|
|
14544
14690
|
adoptAnnounced = new Set;
|
|
@@ -14557,7 +14703,7 @@ class Pool {
|
|
|
14557
14703
|
async continueRun(run, opts) {
|
|
14558
14704
|
const turns = this.budget.checkCardTurns(run.cardId);
|
|
14559
14705
|
if (!turns.allow) {
|
|
14560
|
-
|
|
14706
|
+
log37.warn(TAG33, `Refusing a Continue on ${run.cardId} — ${turns.detail}. Handing the card back: reassign it to clear the count, or raise budget.maxTurnsPerCard.`);
|
|
14561
14707
|
try {
|
|
14562
14708
|
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.
|
|
14563
14709
|
|
|
@@ -14566,14 +14712,14 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14566
14712
|
agentSessionId: run.sessionId ?? undefined
|
|
14567
14713
|
});
|
|
14568
14714
|
} catch (err) {
|
|
14569
|
-
|
|
14715
|
+
log37.warn(TAG33, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14570
14716
|
}
|
|
14571
14717
|
await this.stopRun(run, "Continue refused: the card is at its per-card turn cap.");
|
|
14572
14718
|
return;
|
|
14573
14719
|
}
|
|
14574
14720
|
const runsSpent = this.budget.checkCardRuns(run.cardId);
|
|
14575
14721
|
if (!runsSpent.allow) {
|
|
14576
|
-
|
|
14722
|
+
log37.warn(TAG33, `Refusing a Continue on ${run.cardId} — ${runsSpent.detail}. Handing the card back: reassign it to clear the count, or raise budget.maxRunsPerCard.`);
|
|
14577
14723
|
try {
|
|
14578
14724
|
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.
|
|
14579
14725
|
|
|
@@ -14582,7 +14728,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14582
14728
|
agentSessionId: run.sessionId ?? undefined
|
|
14583
14729
|
});
|
|
14584
14730
|
} catch (err) {
|
|
14585
|
-
|
|
14731
|
+
log37.warn(TAG33, `Could not explain the refused Continue on ${run.cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14586
14732
|
}
|
|
14587
14733
|
await this.stopRun(run, "Continue refused: the card is at its per-card run backstop.");
|
|
14588
14734
|
return;
|
|
@@ -14606,7 +14752,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14606
14752
|
awaitingDecisionUntil: null
|
|
14607
14753
|
});
|
|
14608
14754
|
} catch (err) {
|
|
14609
|
-
|
|
14755
|
+
log37.warn(TAG33, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14610
14756
|
}
|
|
14611
14757
|
if (run.blockerCommentId) {
|
|
14612
14758
|
try {
|
|
@@ -14614,7 +14760,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14614
14760
|
resolve: true
|
|
14615
14761
|
});
|
|
14616
14762
|
} catch (err) {
|
|
14617
|
-
|
|
14763
|
+
log37.warn(TAG33, `Failed to resolve the blocker comment: ${err}`);
|
|
14618
14764
|
}
|
|
14619
14765
|
}
|
|
14620
14766
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
@@ -14638,7 +14784,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14638
14784
|
awaitingDecisionUntil: null
|
|
14639
14785
|
});
|
|
14640
14786
|
} catch (err) {
|
|
14641
|
-
|
|
14787
|
+
log37.warn(TAG33, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14642
14788
|
}
|
|
14643
14789
|
if (run.blockerCommentId) {
|
|
14644
14790
|
try {
|
|
@@ -14646,13 +14792,13 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14646
14792
|
resolve: true
|
|
14647
14793
|
});
|
|
14648
14794
|
} catch (err) {
|
|
14649
|
-
|
|
14795
|
+
log37.warn(TAG33, `Failed to resolve the blocker comment: ${err}`);
|
|
14650
14796
|
}
|
|
14651
14797
|
}
|
|
14652
14798
|
await this.enqueueCard(run.cardId, run.pipeline);
|
|
14653
14799
|
released += 1;
|
|
14654
14800
|
} catch (err) {
|
|
14655
|
-
|
|
14801
|
+
log37.error(TAG33, `Failed to release the account-limit park on ${run.cardId}: ${err}`);
|
|
14656
14802
|
}
|
|
14657
14803
|
}
|
|
14658
14804
|
return released;
|
|
@@ -14664,7 +14810,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14664
14810
|
resolve: true
|
|
14665
14811
|
});
|
|
14666
14812
|
} catch (err) {
|
|
14667
|
-
|
|
14813
|
+
log37.warn(TAG33, `Failed to resolve the blocker comment: ${err}`);
|
|
14668
14814
|
}
|
|
14669
14815
|
}
|
|
14670
14816
|
try {
|
|
@@ -14673,7 +14819,7 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14673
14819
|
awaitingDecisionUntil: null
|
|
14674
14820
|
});
|
|
14675
14821
|
} catch (err) {
|
|
14676
|
-
|
|
14822
|
+
log37.warn(TAG33, `Failed to clear the decision deadline for ${run.cardId}: ${err}`);
|
|
14677
14823
|
}
|
|
14678
14824
|
try {
|
|
14679
14825
|
const { verdict, card } = await guardedHandback(this.client, run.cardId, {
|
|
@@ -14695,26 +14841,26 @@ I am handing the card back rather than holding it parked. Reassign it — that c
|
|
|
14695
14841
|
await this.client.endAgentSession(run.cardId, endSession);
|
|
14696
14842
|
}
|
|
14697
14843
|
} catch (err) {
|
|
14698
|
-
|
|
14844
|
+
log37.error(TAG33, `Failed to hand #${run.cardShortId} back after a stop: ${err}`);
|
|
14699
14845
|
}
|
|
14700
14846
|
try {
|
|
14701
14847
|
await this.stateStore.endRun(run.runId, "failed", {
|
|
14702
14848
|
errorMessage: "budget_decision_stop"
|
|
14703
14849
|
});
|
|
14704
14850
|
} catch (err) {
|
|
14705
|
-
|
|
14851
|
+
log37.warn(TAG33, `Failed to end the local run record for ${run.cardId}: ${err}`);
|
|
14706
14852
|
}
|
|
14707
14853
|
}
|
|
14708
14854
|
async grantAttempt(cardId) {
|
|
14709
14855
|
const turns = this.budget.checkCardTurns(cardId);
|
|
14710
14856
|
if (!turns.allow) {
|
|
14711
|
-
|
|
14857
|
+
log37.warn(TAG33, `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.`);
|
|
14712
14858
|
try {
|
|
14713
14859
|
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.
|
|
14714
14860
|
|
|
14715
14861
|
Reassign the card — that clears the turn count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14716
14862
|
} catch (err) {
|
|
14717
|
-
|
|
14863
|
+
log37.warn(TAG33, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14718
14864
|
}
|
|
14719
14865
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14720
14866
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card turn cap.");
|
|
@@ -14722,13 +14868,13 @@ Reassign the card — that clears the turn count and starts it fresh — or rais
|
|
|
14722
14868
|
}
|
|
14723
14869
|
const runsSpent = this.budget.checkCardRuns(cardId);
|
|
14724
14870
|
if (!runsSpent.allow) {
|
|
14725
|
-
|
|
14871
|
+
log37.warn(TAG33, `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.`);
|
|
14726
14872
|
try {
|
|
14727
14873
|
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.
|
|
14728
14874
|
|
|
14729
14875
|
Reassign the card — that clears the run count and starts it fresh — or raise the cap in the daemon config.`, { commentType: "blocker" });
|
|
14730
14876
|
} catch (err) {
|
|
14731
|
-
|
|
14877
|
+
log37.warn(TAG33, `Could not explain the capped attempt grant on ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14732
14878
|
}
|
|
14733
14879
|
await this.stateStore.resetAttempts(cardId, { clearTurns: false });
|
|
14734
14880
|
await this.stopAttemptCap(cardId, "Attempt budget cleared, but the card is at its per-card run backstop.");
|
|
@@ -14743,7 +14889,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14743
14889
|
awaitingDecisionUntil: null
|
|
14744
14890
|
});
|
|
14745
14891
|
} catch (err) {
|
|
14746
|
-
|
|
14892
|
+
log37.warn(TAG33, `Failed to clear the attempt-cap decision deadline for ${cardId}: ${err}`);
|
|
14747
14893
|
}
|
|
14748
14894
|
await this.enqueueCard(cardId, "implement");
|
|
14749
14895
|
}
|
|
@@ -14754,7 +14900,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14754
14900
|
try {
|
|
14755
14901
|
await this.client.updateComment(blockerCommentId, { resolve: true });
|
|
14756
14902
|
} catch (err) {
|
|
14757
|
-
|
|
14903
|
+
log37.warn(TAG33, `Failed to resolve the blocker comment: ${err}`);
|
|
14758
14904
|
}
|
|
14759
14905
|
}
|
|
14760
14906
|
try {
|
|
@@ -14763,7 +14909,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14763
14909
|
awaitingDecisionUntil: null
|
|
14764
14910
|
});
|
|
14765
14911
|
} catch (err) {
|
|
14766
|
-
|
|
14912
|
+
log37.warn(TAG33, `Failed to clear the decision deadline for ${cardId}: ${err}`);
|
|
14767
14913
|
}
|
|
14768
14914
|
try {
|
|
14769
14915
|
await this.client.endAgentSession(cardId, {
|
|
@@ -14772,7 +14918,7 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14772
14918
|
failureSummary
|
|
14773
14919
|
});
|
|
14774
14920
|
} catch (err) {
|
|
14775
|
-
|
|
14921
|
+
log37.warn(TAG33, `Failed to end the attempt-cap session for ${cardId}: ${err}`);
|
|
14776
14922
|
}
|
|
14777
14923
|
await this.stateStore.clearAwaitingDecision(cardId);
|
|
14778
14924
|
}
|
|
@@ -14803,12 +14949,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14803
14949
|
const subtasks = card.subtasks ?? [];
|
|
14804
14950
|
await this.enqueue(card, column, cardLabels, subtasks, mode);
|
|
14805
14951
|
} catch (err) {
|
|
14806
|
-
|
|
14952
|
+
log37.error(TAG33, `Failed to re-enqueue ${cardId} after a budget decision: ${err}`);
|
|
14807
14953
|
}
|
|
14808
14954
|
}
|
|
14809
14955
|
async retireUnroutableRuns(shortId, cardId, reason = "column not found on this board") {
|
|
14810
14956
|
const unended = this.stateStore.getRunsForCard(cardId).filter((r) => r.endedAt === null);
|
|
14811
|
-
|
|
14957
|
+
log37.warn(TAG33, `#${shortId}: ${reason} — retiring ${unended.length} run record(s) instead of re-enqueueing`);
|
|
14812
14958
|
for (const run of unended) {
|
|
14813
14959
|
await this.stateStore.endRun(run.runId, "orphaned", {
|
|
14814
14960
|
errorMessage: `${reason} at re-enqueue — retired as unroutable (#1057/#1102)`
|
|
@@ -14821,12 +14967,12 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14821
14967
|
if (this.shuttingDown)
|
|
14822
14968
|
return false;
|
|
14823
14969
|
if (this.accountGuard && !this.accountGuard.check().running) {
|
|
14824
|
-
|
|
14970
|
+
log37.debug(TAG33, `${label} dispatch held — the account is halted`);
|
|
14825
14971
|
return false;
|
|
14826
14972
|
}
|
|
14827
14973
|
const idle = workers.find((w) => w.isIdle);
|
|
14828
14974
|
if (!idle) {
|
|
14829
|
-
|
|
14975
|
+
log37.debug(TAG33, `No idle ${label} workers (queue: ${queue.length})`);
|
|
14830
14976
|
return false;
|
|
14831
14977
|
}
|
|
14832
14978
|
const next = queue.dequeue();
|
|
@@ -14834,18 +14980,18 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
14834
14980
|
return false;
|
|
14835
14981
|
const data = this.cardDataCache.get(next.cardId);
|
|
14836
14982
|
if (!data) {
|
|
14837
|
-
|
|
14983
|
+
log37.warn(TAG33, `No cached data for card ${next.cardId}, skipping`);
|
|
14838
14984
|
return false;
|
|
14839
14985
|
}
|
|
14840
14986
|
this.cardDataCache.delete(next.cardId);
|
|
14841
14987
|
this.lastWaitingEmit.delete(next.cardId);
|
|
14842
|
-
|
|
14988
|
+
log37.info(TAG33, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
14843
14989
|
this.sleepGuard.acquire();
|
|
14844
14990
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
14845
14991
|
return true;
|
|
14846
14992
|
}
|
|
14847
14993
|
}
|
|
14848
|
-
var
|
|
14994
|
+
var TAG33 = "pool";
|
|
14849
14995
|
var init_pool = __esm(() => {
|
|
14850
14996
|
init_src();
|
|
14851
14997
|
init_board_helpers();
|
|
@@ -14880,7 +15026,7 @@ import {
|
|
|
14880
15026
|
writeFileSync as writeFileSync4
|
|
14881
15027
|
} from "node:fs";
|
|
14882
15028
|
import { dirname as dirname4 } from "node:path";
|
|
14883
|
-
import { log as
|
|
15029
|
+
import { log as log38 } from "@gethmy/harness";
|
|
14884
15030
|
function defaultRegistryPath() {
|
|
14885
15031
|
return portRegistryPath();
|
|
14886
15032
|
}
|
|
@@ -14895,7 +15041,7 @@ function load(path) {
|
|
|
14895
15041
|
}
|
|
14896
15042
|
return emptyRegistry();
|
|
14897
15043
|
} catch (err) {
|
|
14898
|
-
|
|
15044
|
+
log38.warn(TAG34, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
14899
15045
|
return emptyRegistry();
|
|
14900
15046
|
}
|
|
14901
15047
|
}
|
|
@@ -14916,7 +15062,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
14916
15062
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
14917
15063
|
save(path, registry);
|
|
14918
15064
|
} catch (err) {
|
|
14919
|
-
|
|
15065
|
+
log38.warn(TAG34, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
14920
15066
|
}
|
|
14921
15067
|
}
|
|
14922
15068
|
function daemonBaseUrl(bindAddr, port) {
|
|
@@ -14936,17 +15082,17 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
14936
15082
|
delete registry[projectId];
|
|
14937
15083
|
save(path, registry);
|
|
14938
15084
|
} catch (err) {
|
|
14939
|
-
|
|
15085
|
+
log38.warn(TAG34, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
14940
15086
|
}
|
|
14941
15087
|
}
|
|
14942
|
-
var
|
|
15088
|
+
var TAG34 = "port-registry";
|
|
14943
15089
|
var init_port_registry = __esm(() => {
|
|
14944
15090
|
init_src();
|
|
14945
15091
|
init_paths();
|
|
14946
15092
|
});
|
|
14947
15093
|
|
|
14948
15094
|
// src/recovery.ts
|
|
14949
|
-
import { log as
|
|
15095
|
+
import { log as log39, teardownWorktree as teardownWorktree3 } from "@gethmy/harness";
|
|
14950
15096
|
function isProcessAlive(pid, currentPid) {
|
|
14951
15097
|
if (pid === currentPid)
|
|
14952
15098
|
return true;
|
|
@@ -14962,7 +15108,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
14962
15108
|
const { card } = await client.getCard(cardId);
|
|
14963
15109
|
return card;
|
|
14964
15110
|
} catch (err) {
|
|
14965
|
-
|
|
15111
|
+
log39.warn(TAG35, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
14966
15112
|
return null;
|
|
14967
15113
|
}
|
|
14968
15114
|
}
|
|
@@ -14972,7 +15118,7 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
14972
15118
|
return [];
|
|
14973
15119
|
}
|
|
14974
15120
|
const outcomes = [];
|
|
14975
|
-
|
|
15121
|
+
log39.info(TAG35, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
14976
15122
|
for (const run of active) {
|
|
14977
15123
|
const outcome = {
|
|
14978
15124
|
runId: run.runId,
|
|
@@ -14984,16 +15130,16 @@ async function recoverOrphans(store, client, config, opts = {}) {
|
|
|
14984
15130
|
};
|
|
14985
15131
|
outcomes.push(outcome);
|
|
14986
15132
|
if (isBudgetHeldRun(run)) {
|
|
14987
|
-
|
|
15133
|
+
log39.info(TAG35, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
14988
15134
|
outcome.actions.push("skipped: held for a human budget decision");
|
|
14989
15135
|
continue;
|
|
14990
15136
|
}
|
|
14991
15137
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
14992
|
-
|
|
15138
|
+
log39.warn(TAG35, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
14993
15139
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
14994
15140
|
continue;
|
|
14995
15141
|
}
|
|
14996
|
-
|
|
15142
|
+
log39.info(TAG35, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
14997
15143
|
await recoverRun(run, store, client, config, outcome, {
|
|
14998
15144
|
rollbackAttempt: true,
|
|
14999
15145
|
agentId: opts.agentId
|
|
@@ -15014,7 +15160,7 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15014
15160
|
} catch (err) {
|
|
15015
15161
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15016
15162
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
15017
|
-
|
|
15163
|
+
log39.warn(TAG35, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
15018
15164
|
}
|
|
15019
15165
|
const card = await fetchCardSafely(client, run.cardId);
|
|
15020
15166
|
if (card) {
|
|
@@ -15074,9 +15220,9 @@ async function recoverRun(run, store, client, config, outcome, opts = {}) {
|
|
|
15074
15220
|
outcome.errors.push(`decrementAttempt: ${msg}`);
|
|
15075
15221
|
}
|
|
15076
15222
|
}
|
|
15077
|
-
|
|
15223
|
+
log39.info(TAG35, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
15078
15224
|
}
|
|
15079
|
-
var
|
|
15225
|
+
var TAG35 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
15080
15226
|
var init_recovery = __esm(() => {
|
|
15081
15227
|
init_board_helpers();
|
|
15082
15228
|
init_handback();
|
|
@@ -15105,18 +15251,18 @@ function isTrustedCardAuthor(card, trustedAuthors) {
|
|
|
15105
15251
|
}
|
|
15106
15252
|
|
|
15107
15253
|
// src/claim.ts
|
|
15108
|
-
import { log as
|
|
15254
|
+
import { log as log40 } from "@gethmy/harness";
|
|
15109
15255
|
async function claimUnassignedCard(client, cardId, agentId, opts) {
|
|
15110
15256
|
try {
|
|
15111
15257
|
const { claimed } = opts ? await client.claimCard(cardId, agentId, opts) : await client.claimCard(cardId, agentId);
|
|
15112
|
-
|
|
15258
|
+
log40.debug(TAG36, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
15113
15259
|
return claimed;
|
|
15114
15260
|
} catch (err) {
|
|
15115
|
-
|
|
15261
|
+
log40.error(TAG36, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
15116
15262
|
return false;
|
|
15117
15263
|
}
|
|
15118
15264
|
}
|
|
15119
|
-
var
|
|
15265
|
+
var TAG36 = "claim";
|
|
15120
15266
|
var init_claim = () => {};
|
|
15121
15267
|
|
|
15122
15268
|
// src/strand-recovery.ts
|
|
@@ -15124,7 +15270,7 @@ var exports_strand_recovery = {};
|
|
|
15124
15270
|
__export(exports_strand_recovery, {
|
|
15125
15271
|
reclaimPreReviewStrands: () => reclaimPreReviewStrands
|
|
15126
15272
|
});
|
|
15127
|
-
import { log as
|
|
15273
|
+
import { log as log41, resolvePrUrl as resolvePrUrl2 } from "@gethmy/harness";
|
|
15128
15274
|
async function reclaimPreReviewStrands(opts) {
|
|
15129
15275
|
const {
|
|
15130
15276
|
client,
|
|
@@ -15160,7 +15306,7 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15160
15306
|
continue;
|
|
15161
15307
|
const daemonBranch = daemonBranches.get(card.id);
|
|
15162
15308
|
if (!isTrustedCardAuthor(card, trustedAuthors) && daemonBranch !== branch) {
|
|
15163
|
-
|
|
15309
|
+
log41.debug(TAG37, `#${card.short_id} - skipped: ${card.source != null ? `card was filed externally (source: ${card.source})` : `author ${card.created_by ?? "(none)"} is not trusted`}, and ${daemonBranch ? `the card records ${branch} while this daemon pushed ${daemonBranch}` : "this daemon has pushed no branch for it"}`);
|
|
15164
15310
|
continue;
|
|
15165
15311
|
}
|
|
15166
15312
|
const labels = resolveCardLabels(card, labelMap);
|
|
@@ -15176,22 +15322,22 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15176
15322
|
continue;
|
|
15177
15323
|
const won = await claimUnassignedCard(client, card.id, agentId);
|
|
15178
15324
|
if (!won) {
|
|
15179
|
-
|
|
15325
|
+
log41.debug(TAG37, `#${card.short_id} — lost the review claim race, skipping`);
|
|
15180
15326
|
continue;
|
|
15181
15327
|
}
|
|
15182
|
-
|
|
15328
|
+
log41.warn(TAG37, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
15183
15329
|
reclaimed.push(card.id);
|
|
15184
15330
|
if (opts.onClaimed) {
|
|
15185
15331
|
try {
|
|
15186
15332
|
await opts.onClaimed(card);
|
|
15187
15333
|
} catch (err) {
|
|
15188
|
-
|
|
15334
|
+
log41.error(TAG37, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
15189
15335
|
}
|
|
15190
15336
|
}
|
|
15191
15337
|
}
|
|
15192
15338
|
return reclaimed;
|
|
15193
15339
|
}
|
|
15194
|
-
var
|
|
15340
|
+
var TAG37 = "strand-recovery";
|
|
15195
15341
|
var init_strand_recovery = __esm(() => {
|
|
15196
15342
|
init_src();
|
|
15197
15343
|
init_board_helpers();
|
|
@@ -15357,7 +15503,7 @@ var init_scope = __esm(() => {
|
|
|
15357
15503
|
});
|
|
15358
15504
|
|
|
15359
15505
|
// src/sweep.ts
|
|
15360
|
-
import { log as
|
|
15506
|
+
import { log as log42 } from "@gethmy/harness";
|
|
15361
15507
|
function rankSweepCandidates(opts) {
|
|
15362
15508
|
const {
|
|
15363
15509
|
agentId,
|
|
@@ -15476,10 +15622,10 @@ async function sweepForCard(opts) {
|
|
|
15476
15622
|
let { candidates, skipped, scope } = rankSweepCandidates(opts);
|
|
15477
15623
|
const cohesionCounts = opts.ranking.cohesionWeight !== 0;
|
|
15478
15624
|
if (cohesionCounts) {
|
|
15479
|
-
|
|
15625
|
+
log42.debug(TAG38, describeActiveScope(scope.active));
|
|
15480
15626
|
}
|
|
15481
15627
|
if (candidates.length === 0) {
|
|
15482
|
-
|
|
15628
|
+
log42.debug(TAG38, `no claimable card (${skipped.length} skipped in a pickup column)`);
|
|
15483
15629
|
return { claimed: null, skipped, probed: 0 };
|
|
15484
15630
|
}
|
|
15485
15631
|
const probeWindow = candidates.slice(0, maxProbes);
|
|
@@ -15495,12 +15641,12 @@ async function sweepForCard(opts) {
|
|
|
15495
15641
|
chainNeighbors: chainNeighbors(chain2)
|
|
15496
15642
|
}));
|
|
15497
15643
|
} else if (chainTermsCount && candidates.length > 1) {
|
|
15498
|
-
|
|
15644
|
+
log42.debug(TAG38, `chain signals not ranked — maxProbesPerTick=${maxProbes} leaves nothing to reorder among ${candidates.length} candidates`);
|
|
15499
15645
|
}
|
|
15500
15646
|
let probed = 0;
|
|
15501
15647
|
for (const candidate of candidates) {
|
|
15502
15648
|
if (probed >= maxProbes) {
|
|
15503
|
-
|
|
15649
|
+
log42.debug(TAG38, `probe budget ${maxProbes} spent — ${candidates.length - probed} candidate(s) deferred to the next tick`);
|
|
15504
15650
|
break;
|
|
15505
15651
|
}
|
|
15506
15652
|
probed++;
|
|
@@ -15578,10 +15724,10 @@ async function sweepForCard(opts) {
|
|
|
15578
15724
|
continue;
|
|
15579
15725
|
}
|
|
15580
15726
|
verified.card.assigned_agent_id = agentId;
|
|
15581
|
-
|
|
15727
|
+
log42.info(TAG38, claimLine(verified, probed, candidates, skipped, cohesionCounts ? scope.active : null));
|
|
15582
15728
|
return { claimed: verified, skipped, probed };
|
|
15583
15729
|
}
|
|
15584
|
-
|
|
15730
|
+
log42.debug(TAG38, `claimed nothing this sweep (${probed} probed, ${skipped.length} skipped)`);
|
|
15585
15731
|
return { claimed: null, skipped, probed };
|
|
15586
15732
|
}
|
|
15587
15733
|
function claimLine(claimed, rank, candidates, skipped, scope) {
|
|
@@ -15602,7 +15748,7 @@ async function refetchCard(client, cardId) {
|
|
|
15602
15748
|
const { card } = await client.getCard(cardId);
|
|
15603
15749
|
return card ?? null;
|
|
15604
15750
|
} catch (err) {
|
|
15605
|
-
|
|
15751
|
+
log42.warn(TAG38, `re-read failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
15606
15752
|
return null;
|
|
15607
15753
|
}
|
|
15608
15754
|
}
|
|
@@ -15611,11 +15757,11 @@ async function hasLiveSession(client, cardId) {
|
|
|
15611
15757
|
const { session } = await client.getAgentSession(cardId);
|
|
15612
15758
|
return session != null;
|
|
15613
15759
|
} catch (err) {
|
|
15614
|
-
|
|
15760
|
+
log42.warn(TAG38, `session lookup failed for ${cardId} — treating as busy: ${err instanceof Error ? err.message : err}`);
|
|
15615
15761
|
return true;
|
|
15616
15762
|
}
|
|
15617
15763
|
}
|
|
15618
|
-
var
|
|
15764
|
+
var TAG38 = "sweep";
|
|
15619
15765
|
var init_sweep = __esm(() => {
|
|
15620
15766
|
init_board_helpers();
|
|
15621
15767
|
init_claim();
|
|
@@ -15626,7 +15772,7 @@ var init_sweep = __esm(() => {
|
|
|
15626
15772
|
});
|
|
15627
15773
|
|
|
15628
15774
|
// src/sweep-commands.ts
|
|
15629
|
-
import { log as
|
|
15775
|
+
import { log as log43 } from "@gethmy/harness";
|
|
15630
15776
|
function parseSweepCommands(payload) {
|
|
15631
15777
|
if (!payload || typeof payload !== "object")
|
|
15632
15778
|
return [];
|
|
@@ -15653,14 +15799,14 @@ function parseSweepCommands(payload) {
|
|
|
15653
15799
|
async function claimSweepCommands(client, agentId, projectId, daemonId) {
|
|
15654
15800
|
const request = client.request?.bind(client);
|
|
15655
15801
|
if (typeof request !== "function") {
|
|
15656
|
-
|
|
15802
|
+
log43.warn(TAG39, "installed @gethmy/mcp has no generic request method — board sweep commands cannot be claimed; use `harmony-agent sweep stop|resume` until it is upgraded");
|
|
15657
15803
|
return [];
|
|
15658
15804
|
}
|
|
15659
15805
|
try {
|
|
15660
15806
|
const res = await request("POST", `/agents/${encodeURIComponent(agentId)}/sweep-commands/claim`, { projectId, daemonId });
|
|
15661
15807
|
return parseSweepCommands(res);
|
|
15662
15808
|
} catch (err) {
|
|
15663
|
-
|
|
15809
|
+
log43.warn(TAG39, `could not claim sweep commands: ${err instanceof Error ? err.message : err}`);
|
|
15664
15810
|
return [];
|
|
15665
15811
|
}
|
|
15666
15812
|
}
|
|
@@ -15673,15 +15819,15 @@ async function applySweepCommands(commands, actions) {
|
|
|
15673
15819
|
await actions.resume();
|
|
15674
15820
|
}
|
|
15675
15821
|
applied = command.verb;
|
|
15676
|
-
|
|
15822
|
+
log43.info(TAG39, `applied "${command.verb}" from the board (${command.id})`);
|
|
15677
15823
|
}
|
|
15678
15824
|
return applied;
|
|
15679
15825
|
}
|
|
15680
|
-
var
|
|
15826
|
+
var TAG39 = "sweep-cmd";
|
|
15681
15827
|
var init_sweep_commands = () => {};
|
|
15682
15828
|
|
|
15683
15829
|
// src/sweep-guard.ts
|
|
15684
|
-
import { log as
|
|
15830
|
+
import { log as log44 } from "@gethmy/harness";
|
|
15685
15831
|
|
|
15686
15832
|
class SweepGuard {
|
|
15687
15833
|
config;
|
|
@@ -15737,7 +15883,7 @@ class SweepGuard {
|
|
|
15737
15883
|
await this.store.haltSweep(reason);
|
|
15738
15884
|
if (!already) {
|
|
15739
15885
|
const claimed = this.store.getSweep().claimed;
|
|
15740
|
-
|
|
15886
|
+
log44.info(TAG40, `claiming stopped (${reason}) after ${claimed} claimed`);
|
|
15741
15887
|
}
|
|
15742
15888
|
return this.snapshot();
|
|
15743
15889
|
}
|
|
@@ -15746,7 +15892,7 @@ class SweepGuard {
|
|
|
15746
15892
|
const was = before.haltReason ? this.describeStop(before.haltReason) : null;
|
|
15747
15893
|
await this.store.resumeSweep();
|
|
15748
15894
|
if (was)
|
|
15749
|
-
|
|
15895
|
+
log44.info(TAG40, `claiming resumed by the operator — was stopped: ${was}`);
|
|
15750
15896
|
return this.snapshot();
|
|
15751
15897
|
}
|
|
15752
15898
|
snapshot() {
|
|
@@ -15808,11 +15954,11 @@ function sweepBannerLine(config) {
|
|
|
15808
15954
|
const authors = extra ? `you + ${extra} trusted author${extra === 1 ? "" : "s"}` : "you only";
|
|
15809
15955
|
return `Sweep ON — claims ${scope} in ${config.pickupColumns.join(", ")}, ` + `authored by ${authors}; caps: ${describeCaps(sweep, config.budget.dailyBudgetCents)}; ` + `${sweep.maxProbesPerTick} probes/tick`;
|
|
15810
15956
|
}
|
|
15811
|
-
var
|
|
15957
|
+
var TAG40 = "sweep";
|
|
15812
15958
|
var init_sweep_guard = () => {};
|
|
15813
15959
|
|
|
15814
15960
|
// src/reconcile.ts
|
|
15815
|
-
import { detectGitProvider as detectGitProvider5, log as
|
|
15961
|
+
import { detectGitProvider as detectGitProvider5, log as log45 } from "@gethmy/harness";
|
|
15816
15962
|
function daemonProcessLabels(approvedLabel) {
|
|
15817
15963
|
const names = ["agent", "agent-recovered", NEED_REVIEW_LABEL, approvedLabel];
|
|
15818
15964
|
return new Set(names.filter(Boolean).map((n) => n.toLowerCase()));
|
|
@@ -15875,7 +16021,7 @@ class Reconciler {
|
|
|
15875
16021
|
clearInterval(this.timer);
|
|
15876
16022
|
this.timer = null;
|
|
15877
16023
|
}
|
|
15878
|
-
|
|
16024
|
+
log45.info(TAG41, "Heartbeat stopped");
|
|
15879
16025
|
}
|
|
15880
16026
|
async recoverStaleRuns() {
|
|
15881
16027
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -15886,7 +16032,7 @@ class Reconciler {
|
|
|
15886
16032
|
const pool = this.pool;
|
|
15887
16033
|
for (const run of active) {
|
|
15888
16034
|
if (isBudgetHeldRun(run)) {
|
|
15889
|
-
|
|
16035
|
+
log45.info(TAG41, `run ${run.runId} (#${run.cardShortId}) is held for a human budget decision — leaving it`);
|
|
15890
16036
|
continue;
|
|
15891
16037
|
}
|
|
15892
16038
|
const foreignDaemon = run.daemonPid !== process.pid;
|
|
@@ -15896,7 +16042,7 @@ class Reconciler {
|
|
|
15896
16042
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
15897
16043
|
continue;
|
|
15898
16044
|
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`;
|
|
15899
|
-
|
|
16045
|
+
log45.warn(TAG41, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
15900
16046
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
15901
16047
|
runId: run.runId,
|
|
15902
16048
|
cardId: run.cardId,
|
|
@@ -15923,11 +16069,11 @@ class Reconciler {
|
|
|
15923
16069
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
15924
16070
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
15925
16071
|
continue;
|
|
15926
|
-
|
|
16072
|
+
log45.warn(TAG41, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
15927
16073
|
try {
|
|
15928
16074
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
15929
16075
|
} catch (err) {
|
|
15930
|
-
|
|
16076
|
+
log45.error(TAG41, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
15931
16077
|
}
|
|
15932
16078
|
}
|
|
15933
16079
|
}
|
|
@@ -15980,7 +16126,7 @@ class Reconciler {
|
|
|
15980
16126
|
return;
|
|
15981
16127
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
15982
16128
|
const subtasks = card.subtasks ?? [];
|
|
15983
|
-
|
|
16129
|
+
log45.info(TAG41, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
15984
16130
|
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
15985
16131
|
}
|
|
15986
16132
|
});
|
|
@@ -16004,11 +16150,11 @@ class Reconciler {
|
|
|
16004
16150
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
16005
16151
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
16006
16152
|
continue;
|
|
16007
|
-
|
|
16153
|
+
log45.warn(TAG41, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
16008
16154
|
try {
|
|
16009
16155
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
16010
16156
|
} catch (err) {
|
|
16011
|
-
|
|
16157
|
+
log45.error(TAG41, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
16012
16158
|
}
|
|
16013
16159
|
}
|
|
16014
16160
|
}
|
|
@@ -16043,7 +16189,7 @@ class Reconciler {
|
|
|
16043
16189
|
}
|
|
16044
16190
|
this.lastSweepStopReported = null;
|
|
16045
16191
|
if (!this.pool.hasFreeImplementSlot()) {
|
|
16046
|
-
|
|
16192
|
+
log45.debug(TAG41, "sweep skipped — no free implement slot");
|
|
16047
16193
|
return;
|
|
16048
16194
|
}
|
|
16049
16195
|
const { claimed } = await sweepForCard({
|
|
@@ -16062,9 +16208,9 @@ class Reconciler {
|
|
|
16062
16208
|
reportSweepStopped(reason, detail) {
|
|
16063
16209
|
const line = `sweep stopped claiming — ${detail}`;
|
|
16064
16210
|
if (this.lastSweepStopReported === reason)
|
|
16065
|
-
|
|
16211
|
+
log45.debug(TAG41, line);
|
|
16066
16212
|
else
|
|
16067
|
-
|
|
16213
|
+
log45.warn(TAG41, line);
|
|
16068
16214
|
this.lastSweepStopReported = reason;
|
|
16069
16215
|
}
|
|
16070
16216
|
setSweepPresenceSink(sink) {
|
|
@@ -16102,7 +16248,7 @@ class Reconciler {
|
|
|
16102
16248
|
return;
|
|
16103
16249
|
this.lastSweepNudgeAt = now;
|
|
16104
16250
|
this.drainSweepCommands().catch((err) => {
|
|
16105
|
-
|
|
16251
|
+
log45.error(TAG41, `sweep command nudge failed: ${err instanceof Error ? err.message : err}`);
|
|
16106
16252
|
});
|
|
16107
16253
|
}
|
|
16108
16254
|
resolveRepoDirectories() {
|
|
@@ -16111,9 +16257,9 @@ class Reconciler {
|
|
|
16111
16257
|
const dirs = readRepoDirectories(process.cwd());
|
|
16112
16258
|
this.repoDirectories = dirs;
|
|
16113
16259
|
if (dirs.size === 0) {
|
|
16114
|
-
|
|
16260
|
+
log45.warn(TAG41, `Could not read top-level directories of ${process.cwd()} — sweep cohesion runs without its path signal`);
|
|
16115
16261
|
} else {
|
|
16116
|
-
|
|
16262
|
+
log45.debug(TAG41, `Path signal allows ${dirs.size} top-level directories`);
|
|
16117
16263
|
}
|
|
16118
16264
|
return dirs;
|
|
16119
16265
|
}
|
|
@@ -16149,7 +16295,7 @@ class Reconciler {
|
|
|
16149
16295
|
if (accountHalt !== this.lastAccountHoldReported) {
|
|
16150
16296
|
if (accountHalt) {
|
|
16151
16297
|
const unqueued = assignedCards.filter((c) => !knownCardIds.has(c.id)).length;
|
|
16152
|
-
|
|
16298
|
+
log45.warn(TAG41, `Account halted (${accountHalt}) — ${unqueued} assigned card(s) not queued; nothing is dispatched until \`harmony-agent resume\``);
|
|
16153
16299
|
}
|
|
16154
16300
|
this.lastAccountHoldReported = accountHalt;
|
|
16155
16301
|
}
|
|
@@ -16165,24 +16311,24 @@ class Reconciler {
|
|
|
16165
16311
|
const subtasks = card.subtasks ?? [];
|
|
16166
16312
|
const mode = route.mode;
|
|
16167
16313
|
if (route.stage) {
|
|
16168
|
-
|
|
16314
|
+
log45.info(TAG41, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
|
|
16169
16315
|
}
|
|
16170
16316
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
16171
|
-
|
|
16317
|
+
log45.debug(TAG41, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
16172
16318
|
continue;
|
|
16173
16319
|
}
|
|
16174
16320
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
16175
|
-
|
|
16321
|
+
log45.debug(TAG41, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
16176
16322
|
continue;
|
|
16177
16323
|
}
|
|
16178
16324
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
16179
|
-
|
|
16325
|
+
log45.debug(TAG41, `Skipping #${card.short_id} — no branch or PR reference (not qualified for auto-review)`);
|
|
16180
16326
|
continue;
|
|
16181
16327
|
}
|
|
16182
16328
|
if (accountHalt) {
|
|
16183
|
-
|
|
16329
|
+
log45.debug(TAG41, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — holding, account halted (${accountHalt})`);
|
|
16184
16330
|
} else {
|
|
16185
|
-
|
|
16331
|
+
log45.info(TAG41, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
16186
16332
|
}
|
|
16187
16333
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
16188
16334
|
}
|
|
@@ -16193,13 +16339,13 @@ class Reconciler {
|
|
|
16193
16339
|
try {
|
|
16194
16340
|
await this.pool.drainBudgetDecisions();
|
|
16195
16341
|
} catch (err) {
|
|
16196
|
-
|
|
16342
|
+
log45.error(TAG41, `budget decisions were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16197
16343
|
}
|
|
16198
16344
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
16199
16345
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
16200
16346
|
for (const knownId of knownCardIds) {
|
|
16201
16347
|
if (!allAgentCardIds.has(knownId)) {
|
|
16202
|
-
|
|
16348
|
+
log45.info(TAG41, `Missed unassign: ${knownId} — removing`);
|
|
16203
16349
|
await this.pool.removeCard(knownId);
|
|
16204
16350
|
}
|
|
16205
16351
|
}
|
|
@@ -16207,20 +16353,20 @@ class Reconciler {
|
|
|
16207
16353
|
try {
|
|
16208
16354
|
await this.drainSweepCommands();
|
|
16209
16355
|
} catch (err) {
|
|
16210
|
-
|
|
16356
|
+
log45.error(TAG41, `sweep commands were not drained this tick: ${err instanceof Error ? err.message : err}`);
|
|
16211
16357
|
}
|
|
16212
16358
|
try {
|
|
16213
16359
|
await this.sweepForWork(cards, columns, labelMap, pickupConfig);
|
|
16214
16360
|
} catch (err) {
|
|
16215
|
-
|
|
16361
|
+
log45.error(TAG41, `sweep failed this tick: ${err instanceof Error ? err.message : err}`);
|
|
16216
16362
|
}
|
|
16217
|
-
|
|
16363
|
+
log45.debug(TAG41, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
16218
16364
|
} catch (err) {
|
|
16219
|
-
|
|
16365
|
+
log45.error(TAG41, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
16220
16366
|
}
|
|
16221
16367
|
}
|
|
16222
16368
|
}
|
|
16223
|
-
var
|
|
16369
|
+
var TAG41 = "reconcile", SWEEP_NUDGE_COOLDOWN_MS = 15000;
|
|
16224
16370
|
var init_reconcile = __esm(() => {
|
|
16225
16371
|
init_board_helpers();
|
|
16226
16372
|
init_recovery();
|
|
@@ -16339,7 +16485,7 @@ __export(exports_startup_banner, {
|
|
|
16339
16485
|
createStartupBanner: () => createStartupBanner,
|
|
16340
16486
|
contextSourceLine: () => contextSourceLine
|
|
16341
16487
|
});
|
|
16342
|
-
import { isPretty, log as
|
|
16488
|
+
import { isPretty, log as log46 } from "@gethmy/harness";
|
|
16343
16489
|
function createStartupBanner(config, version) {
|
|
16344
16490
|
return isPretty() ? prettyBanner(config, version) : jsonBanner(config, version);
|
|
16345
16491
|
}
|
|
@@ -16368,7 +16514,7 @@ function prettyBanner(config, version) {
|
|
|
16368
16514
|
checks.push({ kind: "ok", message });
|
|
16369
16515
|
},
|
|
16370
16516
|
warn(message) {
|
|
16371
|
-
|
|
16517
|
+
log46.warn(TAG42, message);
|
|
16372
16518
|
checks.push({ kind: "warn", message: message.split(`
|
|
16373
16519
|
`, 1)[0] });
|
|
16374
16520
|
},
|
|
@@ -16394,31 +16540,31 @@ function prettyBanner(config, version) {
|
|
|
16394
16540
|
};
|
|
16395
16541
|
}
|
|
16396
16542
|
function jsonBanner(config, version) {
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16543
|
+
log46.info(TAG42, `Harmony Agent Daemon v${version} starting...`);
|
|
16544
|
+
log46.info(TAG42, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
16545
|
+
log46.info(TAG42, `Config: ${contextSourceLine(config)}`);
|
|
16400
16546
|
if (config.agent.review.enabled) {
|
|
16401
|
-
|
|
16547
|
+
log46.info(TAG42, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
16402
16548
|
}
|
|
16403
16549
|
let failed = false;
|
|
16404
16550
|
let modelLogged = false;
|
|
16405
16551
|
return {
|
|
16406
16552
|
setProjectName(_name) {},
|
|
16407
16553
|
setGitProvider(provider) {
|
|
16408
|
-
|
|
16554
|
+
log46.info(TAG42, `Git provider: ${provider}`);
|
|
16409
16555
|
},
|
|
16410
16556
|
setHttpPort(port) {
|
|
16411
|
-
|
|
16557
|
+
log46.info(TAG42, `HTTP server on port ${port}`);
|
|
16412
16558
|
},
|
|
16413
16559
|
setModel(resolved) {
|
|
16414
16560
|
modelLogged = true;
|
|
16415
|
-
|
|
16561
|
+
log46.info(TAG42, `Model: ${startupModelLine(resolved)}`);
|
|
16416
16562
|
},
|
|
16417
16563
|
check(message) {
|
|
16418
|
-
|
|
16564
|
+
log46.info(TAG42, message);
|
|
16419
16565
|
},
|
|
16420
16566
|
warn(message) {
|
|
16421
|
-
|
|
16567
|
+
log46.warn(TAG42, message);
|
|
16422
16568
|
},
|
|
16423
16569
|
fail() {
|
|
16424
16570
|
failed = true;
|
|
@@ -16427,8 +16573,8 @@ function jsonBanner(config, version) {
|
|
|
16427
16573
|
if (failed)
|
|
16428
16574
|
return;
|
|
16429
16575
|
if (!modelLogged)
|
|
16430
|
-
|
|
16431
|
-
|
|
16576
|
+
log46.info(TAG42, `Model: ${localModelLine(config)}`);
|
|
16577
|
+
log46.info(TAG42, message);
|
|
16432
16578
|
}
|
|
16433
16579
|
};
|
|
16434
16580
|
}
|
|
@@ -16524,7 +16670,7 @@ function cyan(s) {
|
|
|
16524
16670
|
function yellow(s) {
|
|
16525
16671
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
16526
16672
|
}
|
|
16527
|
-
var
|
|
16673
|
+
var TAG42 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
16528
16674
|
var init_startup_banner = __esm(() => {
|
|
16529
16675
|
init_port_registry();
|
|
16530
16676
|
init_startup_model();
|
|
@@ -16546,7 +16692,7 @@ import {
|
|
|
16546
16692
|
writeFileSync as writeFileSync5
|
|
16547
16693
|
} from "node:fs";
|
|
16548
16694
|
import { dirname as dirname5, resolve as resolve2, sep as sep2 } from "node:path";
|
|
16549
|
-
import { log as
|
|
16695
|
+
import { log as log47 } from "@gethmy/harness";
|
|
16550
16696
|
function migrationMarkerPath(targetPath) {
|
|
16551
16697
|
return `${targetPath}.migrated`;
|
|
16552
16698
|
}
|
|
@@ -16620,7 +16766,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16620
16766
|
throw new Error("not an object");
|
|
16621
16767
|
legacy = parsed;
|
|
16622
16768
|
} catch (err) {
|
|
16623
|
-
|
|
16769
|
+
log47.warn(TAG43, `legacy state file unreadable — skipping the split this start; it retries on the next (${err instanceof Error ? err.message : err})`);
|
|
16624
16770
|
return NO_MIGRATION;
|
|
16625
16771
|
}
|
|
16626
16772
|
const runs = Array.isArray(legacy.runs) ? legacy.runs : [];
|
|
@@ -16642,13 +16788,13 @@ function migrateLegacyStateFile(opts) {
|
|
|
16642
16788
|
const legacyVersion = typeof legacy.version === "number" ? legacy.version : SCHEMA_VERSION;
|
|
16643
16789
|
const existing = existsSync5(targetPath) ? readTargetFile(targetPath) : null;
|
|
16644
16790
|
if (existsSync5(targetPath) && !existing) {
|
|
16645
|
-
|
|
16791
|
+
log47.warn(TAG43, "existing project state file unreadable — the split overwrites it");
|
|
16646
16792
|
}
|
|
16647
16793
|
let target;
|
|
16648
16794
|
if (existing) {
|
|
16649
16795
|
const existingVersion = typeof existing.version === "number" ? existing.version : SCHEMA_VERSION;
|
|
16650
16796
|
if (existingVersion !== legacyVersion) {
|
|
16651
|
-
|
|
16797
|
+
log47.warn(TAG43, `legacy state is schema v${legacyVersion} but the project store is v${existingVersion} — skipping the split`);
|
|
16652
16798
|
return NO_MIGRATION;
|
|
16653
16799
|
}
|
|
16654
16800
|
const existingRuns = Array.isArray(existing.runs) ? existing.runs : [];
|
|
@@ -16694,7 +16840,7 @@ function migrateLegacyStateFile(opts) {
|
|
|
16694
16840
|
if (!existsSync5(backupPath))
|
|
16695
16841
|
copyFileSync(legacyPath, backupPath);
|
|
16696
16842
|
} catch (err) {
|
|
16697
|
-
|
|
16843
|
+
log47.warn(TAG43, `could not back up the legacy state file (continuing): ${err instanceof Error ? err.message : err}`);
|
|
16698
16844
|
}
|
|
16699
16845
|
const remainingRuns = runs.filter((r) => !ownRunIds.has(r.runId));
|
|
16700
16846
|
const remainingCards = cards.filter((c) => !ownCardIds.has(c.cardId));
|
|
@@ -16718,10 +16864,10 @@ function migrateLegacyStateFile(opts) {
|
|
|
16718
16864
|
copiedIndeterminateCards: indeterminateCards.length,
|
|
16719
16865
|
foreignUnendedRuns: remainingRuns.filter((r) => r.endedAt === null).length
|
|
16720
16866
|
};
|
|
16721
|
-
|
|
16867
|
+
log47.info(TAG43, `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`);
|
|
16722
16868
|
return result;
|
|
16723
16869
|
}
|
|
16724
|
-
var
|
|
16870
|
+
var TAG43 = "state-migration", NO_MIGRATION;
|
|
16725
16871
|
var init_state_migration = __esm(() => {
|
|
16726
16872
|
init_src();
|
|
16727
16873
|
init_state_store();
|
|
@@ -16828,7 +16974,7 @@ var init_stream_parser_selftest = __esm(() => {
|
|
|
16828
16974
|
|
|
16829
16975
|
// src/watcher.ts
|
|
16830
16976
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16831
|
-
import { isPretty as isPretty2, log as
|
|
16977
|
+
import { isPretty as isPretty2, log as log48 } from "@gethmy/harness";
|
|
16832
16978
|
import { createClient } from "@supabase/supabase-js";
|
|
16833
16979
|
function toPresenceColumns(names) {
|
|
16834
16980
|
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));
|
|
@@ -16889,7 +17035,7 @@ class Watcher {
|
|
|
16889
17035
|
const stillHeld = this.supabase?.getChannels().some((c) => c.topic === old.topic) ?? false;
|
|
16890
17036
|
if (!stillHeld)
|
|
16891
17037
|
return true;
|
|
16892
|
-
|
|
17038
|
+
log48.warn(TAG44, `removeChannel(${old.topic}) returned "${status}" and the client still holds that topic — retrying the removal`);
|
|
16893
17039
|
return false;
|
|
16894
17040
|
}
|
|
16895
17041
|
constructor(credentials, projectId, identity, routing, onCardBroadcast, onAgentCommand, onSweepCommandNudge) {
|
|
@@ -16903,7 +17049,7 @@ class Watcher {
|
|
|
16903
17049
|
}
|
|
16904
17050
|
async start() {
|
|
16905
17051
|
if (!isPretty2()) {
|
|
16906
|
-
|
|
17052
|
+
log48.info(TAG44, "Connecting to Supabase realtime (broadcast)...");
|
|
16907
17053
|
}
|
|
16908
17054
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
16909
17055
|
this.subscribeBroadcast();
|
|
@@ -16941,7 +17087,7 @@ class Watcher {
|
|
|
16941
17087
|
status = `threw (${String(err)})`;
|
|
16942
17088
|
}
|
|
16943
17089
|
if (status !== "ok") {
|
|
16944
|
-
|
|
17090
|
+
log48.debug(TAG44, `Presence sweep update returned "${status}"`);
|
|
16945
17091
|
}
|
|
16946
17092
|
})();
|
|
16947
17093
|
}
|
|
@@ -16952,7 +17098,7 @@ class Watcher {
|
|
|
16952
17098
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
16953
17099
|
this.presenceChannel = presenceChannel;
|
|
16954
17100
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
16955
|
-
|
|
17101
|
+
log48.debug(TAG44, "Presence sync");
|
|
16956
17102
|
}).subscribe(async (status) => {
|
|
16957
17103
|
if (gen !== this.presenceGen)
|
|
16958
17104
|
return;
|
|
@@ -16968,13 +17114,13 @@ class Watcher {
|
|
|
16968
17114
|
if (trackStatus !== "ok") {
|
|
16969
17115
|
this.presenceTracked = false;
|
|
16970
17116
|
if (!this.stopping) {
|
|
16971
|
-
|
|
17117
|
+
log48.warn(TAG44, `Presence track returned "${trackStatus}" — scheduling reconnect`);
|
|
16972
17118
|
this.schedulePresenceReconnect();
|
|
16973
17119
|
}
|
|
16974
17120
|
return;
|
|
16975
17121
|
}
|
|
16976
17122
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
16977
|
-
|
|
17123
|
+
log48.info(TAG44, "Presence tracked on board-presence channel");
|
|
16978
17124
|
}
|
|
16979
17125
|
this.presenceTracked = true;
|
|
16980
17126
|
this.presenceReconnectAttempts = 0;
|
|
@@ -16982,7 +17128,7 @@ class Watcher {
|
|
|
16982
17128
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
16983
17129
|
this.presenceTracked = false;
|
|
16984
17130
|
if (!this.stopping) {
|
|
16985
|
-
|
|
17131
|
+
log48.warn(TAG44, `Presence subscription ${status} — scheduling reconnect`);
|
|
16986
17132
|
this.schedulePresenceReconnect();
|
|
16987
17133
|
}
|
|
16988
17134
|
}
|
|
@@ -17001,7 +17147,7 @@ class Watcher {
|
|
|
17001
17147
|
async reconnectPresence() {
|
|
17002
17148
|
if (this.stopping || !this.supabase)
|
|
17003
17149
|
return;
|
|
17004
|
-
|
|
17150
|
+
log48.warn(TAG44, `Reconnecting presence subscription (attempt ${this.presenceReconnectAttempts})`);
|
|
17005
17151
|
if (this.presenceChannel) {
|
|
17006
17152
|
const old = this.presenceChannel;
|
|
17007
17153
|
this.presenceChannel = null;
|
|
@@ -17024,13 +17170,13 @@ class Watcher {
|
|
|
17024
17170
|
return;
|
|
17025
17171
|
const gen = ++this.broadcastGen;
|
|
17026
17172
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
17027
|
-
|
|
17173
|
+
log48.debug(TAG44, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
17028
17174
|
this.onCardBroadcast({
|
|
17029
17175
|
event: "card_update",
|
|
17030
17176
|
payload: msg.payload ?? {}
|
|
17031
17177
|
});
|
|
17032
17178
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
17033
|
-
|
|
17179
|
+
log48.debug(TAG44, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
17034
17180
|
this.onCardBroadcast({
|
|
17035
17181
|
event: "card_created",
|
|
17036
17182
|
payload: msg.payload ?? {}
|
|
@@ -17040,11 +17186,11 @@ class Watcher {
|
|
|
17040
17186
|
const cardId = payload.card_id;
|
|
17041
17187
|
const command = payload.command;
|
|
17042
17188
|
if (cardId && command) {
|
|
17043
|
-
|
|
17189
|
+
log48.info(TAG44, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
17044
17190
|
this.onAgentCommand?.({ cardId, command });
|
|
17045
17191
|
}
|
|
17046
17192
|
}).on("broadcast", { event: "sweep_command" }, () => {
|
|
17047
|
-
|
|
17193
|
+
log48.debug(TAG44, "Broadcast: sweep_command — draining");
|
|
17048
17194
|
this.onSweepCommandNudge?.();
|
|
17049
17195
|
}).subscribe((status) => {
|
|
17050
17196
|
if (gen !== this.broadcastGen)
|
|
@@ -17053,13 +17199,13 @@ class Watcher {
|
|
|
17053
17199
|
this.connected = true;
|
|
17054
17200
|
this.reconnectAttempts = 0;
|
|
17055
17201
|
if (!isPretty2() || !this.suppressStartupLogs) {
|
|
17056
|
-
|
|
17202
|
+
log48.info(TAG44, "Broadcast subscription active");
|
|
17057
17203
|
}
|
|
17058
17204
|
this.maybeResolveReady();
|
|
17059
17205
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
17060
17206
|
this.connected = false;
|
|
17061
17207
|
if (!this.stopping) {
|
|
17062
|
-
|
|
17208
|
+
log48.warn(TAG44, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
17063
17209
|
this.scheduleReconnect();
|
|
17064
17210
|
}
|
|
17065
17211
|
}
|
|
@@ -17078,7 +17224,7 @@ class Watcher {
|
|
|
17078
17224
|
async reconnectBroadcast() {
|
|
17079
17225
|
if (this.stopping || !this.supabase)
|
|
17080
17226
|
return;
|
|
17081
|
-
|
|
17227
|
+
log48.warn(TAG44, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
17082
17228
|
if (this.channel) {
|
|
17083
17229
|
const old = this.channel;
|
|
17084
17230
|
this.channel = null;
|
|
@@ -17120,10 +17266,10 @@ class Watcher {
|
|
|
17120
17266
|
}
|
|
17121
17267
|
this.connected = false;
|
|
17122
17268
|
this.presenceTracked = false;
|
|
17123
|
-
|
|
17269
|
+
log48.info(TAG44, "Broadcast subscription stopped");
|
|
17124
17270
|
}
|
|
17125
17271
|
}
|
|
17126
|
-
var
|
|
17272
|
+
var TAG44 = "watcher", MAX_PRESENCE_COLUMNS = 24, MAX_PRESENCE_COLUMN_LENGTH = 120;
|
|
17127
17273
|
var init_watcher = __esm(() => {
|
|
17128
17274
|
init_sweep_guard();
|
|
17129
17275
|
});
|
|
@@ -17397,7 +17543,7 @@ __export(exports_run_gc, {
|
|
|
17397
17543
|
pruneRunLogs: () => pruneRunLogs
|
|
17398
17544
|
});
|
|
17399
17545
|
import { unlinkSync as unlinkSync2 } from "node:fs";
|
|
17400
|
-
import { log as
|
|
17546
|
+
import { log as log49 } from "@gethmy/harness";
|
|
17401
17547
|
function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
17402
17548
|
let deleted = 0;
|
|
17403
17549
|
let bytesFreed = 0;
|
|
@@ -17411,7 +17557,7 @@ function pruneRunLogs(dir, beforeMs, keepRunIds = new Set) {
|
|
|
17411
17557
|
deleted++;
|
|
17412
17558
|
bytesFreed += file.sizeBytes;
|
|
17413
17559
|
} catch (err) {
|
|
17414
|
-
|
|
17560
|
+
log49.debug(TAG45, `Could not remove ${file.path}: ${err instanceof Error ? err.message : err}`);
|
|
17415
17561
|
}
|
|
17416
17562
|
}
|
|
17417
17563
|
return { deleted, bytesFreed };
|
|
@@ -17434,7 +17580,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17434
17580
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
17435
17581
|
}
|
|
17436
17582
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
17437
|
-
|
|
17583
|
+
log49.info(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
17438
17584
|
}
|
|
17439
17585
|
return {
|
|
17440
17586
|
runRecordsPurged,
|
|
@@ -17442,7 +17588,7 @@ async function runRetentionGc(store, opts) {
|
|
|
17442
17588
|
logBytesFreed: logs.bytesFreed
|
|
17443
17589
|
};
|
|
17444
17590
|
}
|
|
17445
|
-
var
|
|
17591
|
+
var TAG45 = "gc", DAY_MS2 = 86400000;
|
|
17446
17592
|
var init_run_gc = __esm(() => {
|
|
17447
17593
|
init_paths();
|
|
17448
17594
|
init_run_stats();
|
|
@@ -17459,7 +17605,7 @@ __export(exports_worktree_gc, {
|
|
|
17459
17605
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
17460
17606
|
import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
17461
17607
|
import { resolve as resolve3 } from "node:path";
|
|
17462
|
-
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as
|
|
17608
|
+
import { cleanupWorktree as cleanupWorktree5, GIT_NO_HOOKS as GIT_NO_HOOKS9, log as log50 } from "@gethmy/harness";
|
|
17463
17609
|
function isTransientGitNetworkError(message) {
|
|
17464
17610
|
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
17465
17611
|
}
|
|
@@ -17572,10 +17718,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
17572
17718
|
});
|
|
17573
17719
|
} catch {}
|
|
17574
17720
|
if (result.removed.length > 0) {
|
|
17575
|
-
|
|
17721
|
+
log50.info(TAG46, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
17576
17722
|
}
|
|
17577
17723
|
if (result.errors.length > 0) {
|
|
17578
|
-
|
|
17724
|
+
log50.warn(TAG46, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
17579
17725
|
}
|
|
17580
17726
|
return result;
|
|
17581
17727
|
}
|
|
@@ -17605,7 +17751,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17605
17751
|
} catch (err) {
|
|
17606
17752
|
const detail = gitErrorDetail2(err);
|
|
17607
17753
|
if (isTransientGitNetworkError(detail)) {
|
|
17608
|
-
|
|
17754
|
+
log50.debug(TAG46, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
17609
17755
|
return result;
|
|
17610
17756
|
}
|
|
17611
17757
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -17645,7 +17791,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17645
17791
|
continue;
|
|
17646
17792
|
}
|
|
17647
17793
|
if (clock() > sweepDeadline) {
|
|
17648
|
-
|
|
17794
|
+
log50.debug(TAG46, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
17649
17795
|
break;
|
|
17650
17796
|
}
|
|
17651
17797
|
try {
|
|
@@ -17658,17 +17804,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
17658
17804
|
} catch (err) {
|
|
17659
17805
|
const detail = gitErrorDetail2(err);
|
|
17660
17806
|
if (isTransientGitNetworkError(detail)) {
|
|
17661
|
-
|
|
17807
|
+
log50.debug(TAG46, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
17662
17808
|
break;
|
|
17663
17809
|
}
|
|
17664
17810
|
result.errors.push({ ref, error: detail });
|
|
17665
17811
|
}
|
|
17666
17812
|
}
|
|
17667
17813
|
if (result.removed.length > 0) {
|
|
17668
|
-
|
|
17814
|
+
log50.info(TAG46, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
17669
17815
|
}
|
|
17670
17816
|
if (result.errors.length > 0) {
|
|
17671
|
-
|
|
17817
|
+
log50.warn(TAG46, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
17672
17818
|
}
|
|
17673
17819
|
return result;
|
|
17674
17820
|
}
|
|
@@ -17701,18 +17847,18 @@ class WorktreeGc {
|
|
|
17701
17847
|
try {
|
|
17702
17848
|
runWorktreeGc(this.basePath, this.store);
|
|
17703
17849
|
} catch (err) {
|
|
17704
|
-
|
|
17850
|
+
log50.warn(TAG46, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17705
17851
|
}
|
|
17706
17852
|
if (this.remoteOpts) {
|
|
17707
17853
|
try {
|
|
17708
17854
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
17709
17855
|
} catch (err) {
|
|
17710
|
-
|
|
17856
|
+
log50.warn(TAG46, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17711
17857
|
}
|
|
17712
17858
|
}
|
|
17713
17859
|
if (this.retentionOpts) {
|
|
17714
17860
|
runRetentionGc(this.store, this.retentionOpts).catch((err) => {
|
|
17715
|
-
|
|
17861
|
+
log50.warn(TAG46, `Retention GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
17716
17862
|
});
|
|
17717
17863
|
}
|
|
17718
17864
|
}
|
|
@@ -17726,7 +17872,7 @@ function getRepoRoot2() {
|
|
|
17726
17872
|
return null;
|
|
17727
17873
|
}
|
|
17728
17874
|
}
|
|
17729
|
-
var
|
|
17875
|
+
var TAG46 = "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;
|
|
17730
17876
|
var init_worktree_gc = __esm(() => {
|
|
17731
17877
|
init_run_gc();
|
|
17732
17878
|
GIT_NETWORK_EXEC = {
|
|
@@ -17767,7 +17913,7 @@ import {
|
|
|
17767
17913
|
claudeVersion,
|
|
17768
17914
|
detectGitProvider as detectGitProvider6,
|
|
17769
17915
|
GIT_NO_HOOKS as GIT_NO_HOOKS10,
|
|
17770
|
-
log as
|
|
17916
|
+
log as log51,
|
|
17771
17917
|
validateGitProviderCli
|
|
17772
17918
|
} from "@gethmy/harness";
|
|
17773
17919
|
async function validatePrerequisites(config, banner) {
|
|
@@ -17841,7 +17987,7 @@ async function main() {
|
|
|
17841
17987
|
} catch (err) {
|
|
17842
17988
|
if (err instanceof ConfigValidationError) {
|
|
17843
17989
|
banner.fail();
|
|
17844
|
-
|
|
17990
|
+
log51.error(TAG47, err.message);
|
|
17845
17991
|
process.exit(1);
|
|
17846
17992
|
}
|
|
17847
17993
|
throw err;
|
|
@@ -17859,7 +18005,7 @@ async function main() {
|
|
|
17859
18005
|
} catch (err) {
|
|
17860
18006
|
if (err instanceof ConfigValidationError) {
|
|
17861
18007
|
banner.fail();
|
|
17862
|
-
|
|
18008
|
+
log51.error(TAG47, err.message);
|
|
17863
18009
|
process.exit(1);
|
|
17864
18010
|
}
|
|
17865
18011
|
throw err;
|
|
@@ -17883,7 +18029,7 @@ async function main() {
|
|
|
17883
18029
|
try {
|
|
17884
18030
|
await client.reportAgentConfig(config.workspaceId, agentId, effectiveModelReport(config.agent));
|
|
17885
18031
|
} catch (err) {
|
|
17886
|
-
|
|
18032
|
+
log51.warn(TAG47, `Could not report model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
17887
18033
|
}
|
|
17888
18034
|
const startupModel = await resolveStartupModel(config, client);
|
|
17889
18035
|
banner.setModel(startupModel);
|
|
@@ -17898,7 +18044,7 @@ async function main() {
|
|
|
17898
18044
|
worktreePrefix: resolve4(config.repoRoot, config.agent.worktree.basePath)
|
|
17899
18045
|
});
|
|
17900
18046
|
} catch (err) {
|
|
17901
|
-
|
|
18047
|
+
log51.error(TAG47, `legacy state split failed — starting with the project store as-is: ${err instanceof Error ? err.message : err}`);
|
|
17902
18048
|
}
|
|
17903
18049
|
const stateStore = StateStore.open({ projectId: config.projectId });
|
|
17904
18050
|
const daemonId = randomUUID4();
|
|
@@ -18049,7 +18195,7 @@ async function main() {
|
|
|
18049
18195
|
if (shuttingDown)
|
|
18050
18196
|
return;
|
|
18051
18197
|
shuttingDown = true;
|
|
18052
|
-
|
|
18198
|
+
log51.info(TAG47, `Received ${signal}, shutting down gracefully...`);
|
|
18053
18199
|
reconciler.stop();
|
|
18054
18200
|
mergeMonitor?.stop();
|
|
18055
18201
|
worktreeGc.stop();
|
|
@@ -18060,19 +18206,19 @@ async function main() {
|
|
|
18060
18206
|
}
|
|
18061
18207
|
await watcher.stop();
|
|
18062
18208
|
await pool.shutdown();
|
|
18063
|
-
|
|
18209
|
+
log51.info(TAG47, "Daemon stopped.");
|
|
18064
18210
|
process.exit(exitCode);
|
|
18065
18211
|
};
|
|
18066
18212
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
18067
18213
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
18068
18214
|
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
18069
18215
|
process.on("uncaughtException", (err) => {
|
|
18070
|
-
|
|
18216
|
+
log51.error(TAG47, `Uncaught exception: ${err.message}`);
|
|
18071
18217
|
exitCode = 1;
|
|
18072
18218
|
shutdown("uncaughtException");
|
|
18073
18219
|
});
|
|
18074
18220
|
process.on("unhandledRejection", (reason) => {
|
|
18075
|
-
|
|
18221
|
+
log51.error(TAG47, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
18076
18222
|
exitCode = 1;
|
|
18077
18223
|
shutdown("unhandledRejection");
|
|
18078
18224
|
});
|
|
@@ -18132,33 +18278,33 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
18132
18278
|
if (assignedAgentId === undefined)
|
|
18133
18279
|
return;
|
|
18134
18280
|
if (assignedAgentId === agentId) {
|
|
18135
|
-
|
|
18281
|
+
log51.info(TAG47, `Broadcast: card ${cardId} assigned to agent`);
|
|
18136
18282
|
try {
|
|
18137
18283
|
await pool.resetAttemptsForReassign(cardId);
|
|
18138
18284
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
18139
18285
|
} catch (err) {
|
|
18140
|
-
|
|
18286
|
+
log51.error(TAG47, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
18141
18287
|
}
|
|
18142
18288
|
} else if (pool.isCardKnown(cardId)) {
|
|
18143
|
-
|
|
18289
|
+
log51.info(TAG47, `Broadcast: card ${cardId} unassigned from agent`);
|
|
18144
18290
|
await pool.removeCard(cardId);
|
|
18145
18291
|
}
|
|
18146
18292
|
}
|
|
18147
18293
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
18148
18294
|
const { card } = await client.getCard(cardId);
|
|
18149
18295
|
if (card.assigned_agent_id !== agentId) {
|
|
18150
|
-
|
|
18296
|
+
log51.debug(TAG47, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
18151
18297
|
return;
|
|
18152
18298
|
}
|
|
18153
18299
|
if (card.done || card.archived_at) {
|
|
18154
|
-
|
|
18300
|
+
log51.debug(TAG47, `Card ${cardId} is finished — skipping`);
|
|
18155
18301
|
return;
|
|
18156
18302
|
}
|
|
18157
18303
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
18158
18304
|
const columns = board.columns;
|
|
18159
18305
|
const column = columns.find((c) => c.id === card.column_id);
|
|
18160
18306
|
if (!column) {
|
|
18161
|
-
|
|
18307
|
+
log51.warn(TAG47, `Column not found for card ${cardId}`);
|
|
18162
18308
|
return;
|
|
18163
18309
|
}
|
|
18164
18310
|
const route = classifyPickup(card, column.name, {
|
|
@@ -18167,31 +18313,31 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
18167
18313
|
playbooks: config.agent.playbooks
|
|
18168
18314
|
});
|
|
18169
18315
|
if (!route) {
|
|
18170
|
-
|
|
18316
|
+
log51.info(TAG47, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
18171
18317
|
return;
|
|
18172
18318
|
}
|
|
18173
18319
|
if (route.stage) {
|
|
18174
|
-
|
|
18320
|
+
log51.info(TAG47, `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`);
|
|
18175
18321
|
}
|
|
18176
18322
|
const mode = route.mode;
|
|
18177
18323
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
18178
18324
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
18179
18325
|
const subtasks = card.subtasks ?? [];
|
|
18180
18326
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
18181
|
-
|
|
18327
|
+
log51.debug(TAG47, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
18182
18328
|
return;
|
|
18183
18329
|
}
|
|
18184
18330
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
18185
|
-
|
|
18331
|
+
log51.debug(TAG47, `Card #${card.short_id} has "${NEED_REVIEW_LABEL}" label (needs human) — skipping review`);
|
|
18186
18332
|
return;
|
|
18187
18333
|
}
|
|
18188
18334
|
if (mode === "review" && !qualifiesForAutoReview(card.description)) {
|
|
18189
|
-
|
|
18335
|
+
log51.info(TAG47, `Card #${card.short_id} has no branch or PR reference — skipping auto-review`);
|
|
18190
18336
|
return;
|
|
18191
18337
|
}
|
|
18192
18338
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
18193
18339
|
}
|
|
18194
|
-
var
|
|
18340
|
+
var TAG47 = "daemon", BASE_REMOTE = "origin", PKG_VERSION;
|
|
18195
18341
|
var init_src2 = __esm(() => {
|
|
18196
18342
|
init_account_guard();
|
|
18197
18343
|
init_account_probe();
|
|
@@ -18317,22 +18463,22 @@ function realOrResolved(path) {
|
|
|
18317
18463
|
var init_local_pin_migration = () => {};
|
|
18318
18464
|
|
|
18319
18465
|
// src/head-anchor.ts
|
|
18320
|
-
import { log as
|
|
18466
|
+
import { log as log52 } from "@gethmy/harness";
|
|
18321
18467
|
function sealProvenHead(sha, provenance) {
|
|
18322
18468
|
if (!/^[0-9a-f]{40}$/.test(sha)) {
|
|
18323
|
-
|
|
18469
|
+
log52.warn(TAG48, `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`);
|
|
18324
18470
|
return;
|
|
18325
18471
|
}
|
|
18326
18472
|
return sha;
|
|
18327
18473
|
}
|
|
18328
|
-
var
|
|
18474
|
+
var TAG48 = "head-anchor";
|
|
18329
18475
|
var init_head_anchor = () => {};
|
|
18330
18476
|
|
|
18331
18477
|
// src/merge-cleanup.ts
|
|
18332
18478
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
18333
18479
|
import { existsSync as existsSync7, lstatSync as lstatSync3, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
|
18334
18480
|
import { basename, dirname as dirname7, isAbsolute, resolve as resolve6, sep as sep4 } from "node:path";
|
|
18335
|
-
import { cleanupWorktree as cleanupWorktree6, GIT_NO_HOOKS as GIT_NO_HOOKS12, log as
|
|
18481
|
+
import { cleanupWorktree as cleanupWorktree6, GIT_NO_HOOKS as GIT_NO_HOOKS12, log as log53 } from "@gethmy/harness";
|
|
18336
18482
|
function canonicalize(p) {
|
|
18337
18483
|
const original = isAbsolute(p) ? p : `${process.cwd()}${sep4}${p}`;
|
|
18338
18484
|
try {
|
|
@@ -18407,7 +18553,7 @@ function linkedWorktreePaths(cwd) {
|
|
|
18407
18553
|
if (main2 !== null && records.some((r) => r.path === main2)) {
|
|
18408
18554
|
return records.filter((r) => r.path !== main2).map((r) => r.path);
|
|
18409
18555
|
}
|
|
18410
|
-
|
|
18556
|
+
log53.warn(TAG49, `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.`);
|
|
18411
18557
|
return records.slice(1).map((r) => r.path);
|
|
18412
18558
|
}
|
|
18413
18559
|
function sameDirectoryIgnoringCase(asked, registered) {
|
|
@@ -18450,18 +18596,18 @@ function cleanupMergedWorktree(args) {
|
|
|
18450
18596
|
}
|
|
18451
18597
|
if (registration.kind !== "registered") {
|
|
18452
18598
|
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.`;
|
|
18453
|
-
|
|
18599
|
+
log53.warn(TAG49, reason);
|
|
18454
18600
|
return { removed: false, reason };
|
|
18455
18601
|
}
|
|
18456
18602
|
if (cwdIsInsideWorktree(args.cwd, args.worktreePath) || cwdIsInsideWorktree(process.cwd(), args.worktreePath)) {
|
|
18457
18603
|
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.`;
|
|
18458
|
-
|
|
18604
|
+
log53.warn(TAG49, reason);
|
|
18459
18605
|
return { removed: false, reason };
|
|
18460
18606
|
}
|
|
18461
18607
|
cleanupWorktree6(args.worktreePath, args.branchName ?? undefined);
|
|
18462
18608
|
return { removed: !existsSync7(args.worktreePath) };
|
|
18463
18609
|
}
|
|
18464
|
-
var
|
|
18610
|
+
var TAG49 = "merge-cleanup";
|
|
18465
18611
|
var init_merge_cleanup = () => {};
|
|
18466
18612
|
|
|
18467
18613
|
// src/merge-command.ts
|
|
@@ -18476,7 +18622,7 @@ import { resolve as resolve7 } from "node:path";
|
|
|
18476
18622
|
import { promisify as promisify2 } from "node:util";
|
|
18477
18623
|
import {
|
|
18478
18624
|
GIT_NO_HOOKS as GIT_NO_HOOKS13,
|
|
18479
|
-
log as
|
|
18625
|
+
log as log54,
|
|
18480
18626
|
resolvePrHeadBranch as resolvePrHeadBranch3
|
|
18481
18627
|
} from "@gethmy/harness";
|
|
18482
18628
|
function buildFindingsBlock(items) {
|
|
@@ -18503,7 +18649,7 @@ async function gitRevParse(what, at) {
|
|
|
18503
18649
|
async function cwdIsRepositoryTopLevel(cwd) {
|
|
18504
18650
|
const top = await gitRevParse("--show-toplevel", cwd);
|
|
18505
18651
|
if (!top || realpathOr(top) !== realpathOr(cwd)) {
|
|
18506
|
-
|
|
18652
|
+
log54.warn(TAG50, `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.`);
|
|
18507
18653
|
return false;
|
|
18508
18654
|
}
|
|
18509
18655
|
const gitDir = await gitRevParse("--git-dir", cwd);
|
|
@@ -18515,7 +18661,7 @@ async function cwdIsRepositoryTopLevel(cwd) {
|
|
|
18515
18661
|
}
|
|
18516
18662
|
if (isRegisteredWorktree(cwd, cwd))
|
|
18517
18663
|
return true;
|
|
18518
|
-
|
|
18664
|
+
log54.warn(TAG50, `not reading a local head from ${cwd} — git does not list it as a worktree of its own repository.`);
|
|
18519
18665
|
return false;
|
|
18520
18666
|
}
|
|
18521
18667
|
function realpathOr(p) {
|
|
@@ -18540,7 +18686,7 @@ async function readValidatedWorktreeHead(deps) {
|
|
|
18540
18686
|
const registration = classifyWorktreeRegistration(worktreePath, cwd);
|
|
18541
18687
|
if (registration.kind !== "registered") {
|
|
18542
18688
|
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.`;
|
|
18543
|
-
|
|
18689
|
+
log54.warn(TAG50, `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.`);
|
|
18544
18690
|
return { sha: undefined, detail };
|
|
18545
18691
|
}
|
|
18546
18692
|
}
|
|
@@ -18554,7 +18700,7 @@ async function readValidatedWorktreeHead(deps) {
|
|
|
18554
18700
|
const raw = await gitRevParse("HEAD", readAt);
|
|
18555
18701
|
const sha = raw ? sealProvenHead(raw, worktreePath ? "registered-worktree" : "repository-top-level") : undefined;
|
|
18556
18702
|
if (!sha) {
|
|
18557
|
-
|
|
18703
|
+
log54.warn(TAG50, `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`);
|
|
18558
18704
|
return {
|
|
18559
18705
|
sha: undefined,
|
|
18560
18706
|
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.`
|
|
@@ -18563,14 +18709,14 @@ async function readValidatedWorktreeHead(deps) {
|
|
|
18563
18709
|
if (worktreePath) {
|
|
18564
18710
|
const resolved = await resolvePrHeadBranch3(deps.prUrl, cwd, deps.provider);
|
|
18565
18711
|
if (resolved.kind === "skip") {
|
|
18566
|
-
|
|
18712
|
+
log54.warn(TAG50, `not corroborating ${worktreePath} against the head branch of ${deps.prUrl}: ${resolved.reason}. The anchor stands on the head sha alone.`);
|
|
18567
18713
|
} else {
|
|
18568
18714
|
const branch = await checkedOutBranch(worktreePath);
|
|
18569
18715
|
if (branch === null) {
|
|
18570
|
-
|
|
18716
|
+
log54.warn(TAG50, `${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.`);
|
|
18571
18717
|
} else if (branch !== resolved.branch) {
|
|
18572
18718
|
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.`;
|
|
18573
|
-
|
|
18719
|
+
log54.warn(TAG50, `refusing the anchor from ${worktreePath}.${detail}`);
|
|
18574
18720
|
return { sha: undefined, detail };
|
|
18575
18721
|
}
|
|
18576
18722
|
}
|
|
@@ -18620,7 +18766,7 @@ async function runMergeCommand(deps) {
|
|
|
18620
18766
|
});
|
|
18621
18767
|
} catch (err) {
|
|
18622
18768
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18623
|
-
|
|
18769
|
+
log54.warn(TAG50, `merge attempt failed: ${detail}`);
|
|
18624
18770
|
deps.emit({
|
|
18625
18771
|
type: "parked",
|
|
18626
18772
|
reason: `the merge attempt failed: ${detail}`
|
|
@@ -18641,7 +18787,7 @@ async function runMergeCommand(deps) {
|
|
|
18641
18787
|
if (outcome === "report-findings") {
|
|
18642
18788
|
const reported = findings[0];
|
|
18643
18789
|
if (!reported) {
|
|
18644
|
-
|
|
18790
|
+
log54.warn(TAG50, "the merge reported findings but none arrived through the callback — parking rather than reporting an empty list");
|
|
18645
18791
|
deps.emit({
|
|
18646
18792
|
type: "parked",
|
|
18647
18793
|
reason: "the independent review posted blocking findings, but they could not be reported — the card stays in Review for a person"
|
|
@@ -18691,7 +18837,7 @@ async function branchRefGone(branchName, cwd) {
|
|
|
18691
18837
|
const stderr = String(failure.stderr ?? "").trim();
|
|
18692
18838
|
if (failure.code === 1 && !stderr)
|
|
18693
18839
|
return true;
|
|
18694
|
-
|
|
18840
|
+
log54.warn(TAG50, `could not determine whether ${branchName} still exists (code=${String(failure.code)}${stderr ? `, ${stderr}` : ""}) — reporting it as possibly present`);
|
|
18695
18841
|
return false;
|
|
18696
18842
|
}
|
|
18697
18843
|
}
|
|
@@ -18702,7 +18848,7 @@ async function finish(deps) {
|
|
|
18702
18848
|
const resolved = await resolvePrHeadBranch3(deps.prUrl, deps.cwd, deps.provider);
|
|
18703
18849
|
const branch = resolved.kind === "branch" ? resolved.branch : null;
|
|
18704
18850
|
if (resolved.kind === "skip") {
|
|
18705
|
-
|
|
18851
|
+
log54.warn(TAG50, `no head branch for ${deps.prUrl}: ${resolved.reason}`);
|
|
18706
18852
|
}
|
|
18707
18853
|
const out = cleanupMergedWorktree({
|
|
18708
18854
|
worktreePath,
|
|
@@ -18711,12 +18857,12 @@ async function finish(deps) {
|
|
|
18711
18857
|
});
|
|
18712
18858
|
if (!out.removed) {
|
|
18713
18859
|
const reason = out.reason ?? "the worktree directory still exists after cleanup";
|
|
18714
|
-
|
|
18860
|
+
log54.warn(TAG50, `cleanup refused for ${worktreePath}: ${reason}`);
|
|
18715
18861
|
deps.emit({ type: "cleanup_refused", worktree: worktreePath, reason });
|
|
18716
18862
|
} else {
|
|
18717
18863
|
const branchGone = branch ? await branchRefGone(branch, deps.cwd) : true;
|
|
18718
18864
|
if (branch && !branchGone) {
|
|
18719
|
-
|
|
18865
|
+
log54.warn(TAG50, `local branch ${branch} may still exist after cleanup`);
|
|
18720
18866
|
}
|
|
18721
18867
|
deps.emit({
|
|
18722
18868
|
type: "cleaned",
|
|
@@ -18727,7 +18873,7 @@ async function finish(deps) {
|
|
|
18727
18873
|
}
|
|
18728
18874
|
} catch (err) {
|
|
18729
18875
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18730
|
-
|
|
18876
|
+
log54.warn(TAG50, `worktree cleanup failed for ${worktreePath}: ${detail}`);
|
|
18731
18877
|
deps.emit({
|
|
18732
18878
|
type: "cleanup_refused",
|
|
18733
18879
|
worktree: worktreePath,
|
|
@@ -18745,16 +18891,16 @@ async function finish(deps) {
|
|
|
18745
18891
|
});
|
|
18746
18892
|
if (!out.moved) {
|
|
18747
18893
|
const reason = out.reason ?? "the card could not be moved out of Review";
|
|
18748
|
-
|
|
18894
|
+
log54.warn(TAG50, `board not updated after merge: ${reason}`);
|
|
18749
18895
|
deps.emit({ type: "board_incomplete", reason });
|
|
18750
18896
|
}
|
|
18751
18897
|
} catch (err) {
|
|
18752
18898
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18753
|
-
|
|
18899
|
+
log54.warn(TAG50, `board not updated after merge: ${detail}`);
|
|
18754
18900
|
deps.emit({ type: "board_incomplete", reason: detail });
|
|
18755
18901
|
}
|
|
18756
18902
|
}
|
|
18757
|
-
var
|
|
18903
|
+
var TAG50 = "merge-command", POLL_INTERVAL_MS = 30000, execFileAsync2, KEEP_WAITING, PARK_REASONS;
|
|
18758
18904
|
var init_merge_command = __esm(() => {
|
|
18759
18905
|
init_src();
|
|
18760
18906
|
init_auto_merge();
|
|
@@ -18770,6 +18916,7 @@ var init_merge_command = __esm(() => {
|
|
|
18770
18916
|
"stamp-failure": "CI is red — the card stays in Review for a person",
|
|
18771
18917
|
"stamp-findings-exhausted": "the findings loop spent its rounds and the independent review still objects — the findings were read and acted on, so this needs a person rather than another push",
|
|
18772
18918
|
"stamp-review-findings": "the independent review posted blocking findings — nothing merges over an unread finding",
|
|
18919
|
+
"stamp-conflict": "the pull request conflicts with its base — merge the base branch into it and push",
|
|
18773
18920
|
"stamp-review-incomplete": "the independent review never reached a verdict (truncated or errored run)",
|
|
18774
18921
|
rereview: "the branch moved since it was reviewed — the merge needs a fresh review",
|
|
18775
18922
|
repair: "CI needs repair, which the interactive driver does not do",
|
|
@@ -18898,7 +19045,7 @@ init_package_version();
|
|
|
18898
19045
|
import { readFileSync as readFileSync11, realpathSync as realpathSync4 } from "node:fs";
|
|
18899
19046
|
import { dirname as dirname8, join as join10 } from "node:path";
|
|
18900
19047
|
import { fileURLToPath } from "node:url";
|
|
18901
|
-
import { log as
|
|
19048
|
+
import { log as log55 } from "@gethmy/harness";
|
|
18902
19049
|
var USAGE = `
|
|
18903
19050
|
Harmony Agent — push-based daemon + ops toolkit.
|
|
18904
19051
|
|
|
@@ -19773,7 +19920,7 @@ if (isMainModule()) {
|
|
|
19773
19920
|
if (code !== 0)
|
|
19774
19921
|
process.exit(code);
|
|
19775
19922
|
}).catch((err) => {
|
|
19776
|
-
|
|
19923
|
+
log55.error("cli", err instanceof Error ? err.message : String(err));
|
|
19777
19924
|
process.exit(1);
|
|
19778
19925
|
});
|
|
19779
19926
|
}
|