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