@gethmy/agent 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +145 -84
- package/dist/index.js +145 -84
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -105,6 +105,7 @@ var init_log = __esm(() => {
|
|
|
105
105
|
var exports_board_helpers = {};
|
|
106
106
|
__export(exports_board_helpers, {
|
|
107
107
|
resolveCardLabels: () => resolveCardLabels,
|
|
108
|
+
releaseAssignedAgent: () => releaseAssignedAgent,
|
|
108
109
|
moveCardToColumn: () => moveCardToColumn,
|
|
109
110
|
moveCardAndAddLabel: () => moveCardAndAddLabel,
|
|
110
111
|
hasLabel: () => hasLabel,
|
|
@@ -126,6 +127,9 @@ function resolveCardLabels(card, labelMap) {
|
|
|
126
127
|
function hasLabel(cardLabels, labelName) {
|
|
127
128
|
return cardLabels.some((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
128
129
|
}
|
|
130
|
+
async function releaseAssignedAgent(client, cardId) {
|
|
131
|
+
await client.updateCard(cardId, { assignedAgentId: null });
|
|
132
|
+
}
|
|
129
133
|
async function moveCardToColumn(client, card, targetColumnName) {
|
|
130
134
|
try {
|
|
131
135
|
const board = await client.getBoard(card.project_id);
|
|
@@ -3862,6 +3866,11 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
3862
3866
|
}
|
|
3863
3867
|
if (config.completion.moveToColumn) {
|
|
3864
3868
|
await moveCardToColumn(client, card, config.completion.moveToColumn);
|
|
3869
|
+
try {
|
|
3870
|
+
await releaseAssignedAgent(client, card.id);
|
|
3871
|
+
} catch (err) {
|
|
3872
|
+
log.warn(TAG15, `assignment release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
3873
|
+
}
|
|
3865
3874
|
if (onMovedToCompletion) {
|
|
3866
3875
|
try {
|
|
3867
3876
|
await onMovedToCompletion(card);
|
|
@@ -6190,6 +6199,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
6190
6199
|
const board = await withRetry("move", shortId, () => client.getBoard(card.project_id), attempts, backoffMs);
|
|
6191
6200
|
const columns = board.columns;
|
|
6192
6201
|
const labels = board.labels ?? [];
|
|
6202
|
+
let moveLanded = false;
|
|
6193
6203
|
if (plan.move) {
|
|
6194
6204
|
const target = columns.find((c) => c.name.toLowerCase() === plan.move.columnName.toLowerCase());
|
|
6195
6205
|
if (!target) {
|
|
@@ -6202,8 +6212,14 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
6202
6212
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
6203
6213
|
log.info(TAG23, `#${shortId} → "${target.name}"`);
|
|
6204
6214
|
card.column_id = target.id;
|
|
6215
|
+
moveLanded = true;
|
|
6216
|
+
} else {
|
|
6217
|
+
moveLanded = true;
|
|
6205
6218
|
}
|
|
6206
6219
|
}
|
|
6220
|
+
if (moveLanded && plan.onMoved) {
|
|
6221
|
+
await plan.onMoved();
|
|
6222
|
+
}
|
|
6207
6223
|
if (plan.addLabels?.length) {
|
|
6208
6224
|
const existing = new Set(card.labelIds ?? []);
|
|
6209
6225
|
for (const { name, color } of plan.addLabels) {
|
|
@@ -6439,7 +6455,15 @@ class ReviewWorker {
|
|
|
6439
6455
|
const cwd = this.worktreePath;
|
|
6440
6456
|
if (!localMode) {
|
|
6441
6457
|
log.info(this.tag, `Starting dev server on port ${port}...`);
|
|
6442
|
-
|
|
6458
|
+
const [devCmd, devArgs] = spawnRunArgs("dev", "--port", String(port));
|
|
6459
|
+
this.devServerProcess = spawnInGroup(devCmd, devArgs, {
|
|
6460
|
+
cwd,
|
|
6461
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6462
|
+
});
|
|
6463
|
+
let devServerSpawnError = null;
|
|
6464
|
+
this.devServerProcess.once("error", (err) => {
|
|
6465
|
+
devServerSpawnError = err;
|
|
6466
|
+
});
|
|
6443
6467
|
await this.client.updateAgentProgress(card.id, {
|
|
6444
6468
|
agentIdentifier: agentIdentifier(this.id),
|
|
6445
6469
|
agentName: `${AGENT_NAME} (Review)`,
|
|
@@ -6447,6 +6471,9 @@ class ReviewWorker {
|
|
|
6447
6471
|
currentTask: `Starting dev server on port ${port}…`,
|
|
6448
6472
|
progressPercent: 10
|
|
6449
6473
|
});
|
|
6474
|
+
if (devServerSpawnError) {
|
|
6475
|
+
throw new DevServerReadinessError(`dev server failed to start (${devCmd}): ${devServerSpawnError.message}`);
|
|
6476
|
+
}
|
|
6450
6477
|
await waitForDevServer(this.devServerProcess, 30000);
|
|
6451
6478
|
await probeDevServer(port);
|
|
6452
6479
|
log.info(this.tag, `Dev server ready on port ${port}`);
|
|
@@ -6701,7 +6728,7 @@ class ReviewWorker {
|
|
|
6701
6728
|
[parse_error] ${msg}
|
|
6702
6729
|
`);
|
|
6703
6730
|
});
|
|
6704
|
-
if (this.process
|
|
6731
|
+
if (this.process?.stdout) {
|
|
6705
6732
|
parser.attach(this.process.stdout);
|
|
6706
6733
|
if (runLog) {
|
|
6707
6734
|
this.process.stdout.on("data", (chunk) => {
|
|
@@ -6710,14 +6737,14 @@ class ReviewWorker {
|
|
|
6710
6737
|
}
|
|
6711
6738
|
}
|
|
6712
6739
|
let stderr = "";
|
|
6713
|
-
this.process
|
|
6740
|
+
this.process?.stderr?.on("data", (data) => {
|
|
6714
6741
|
stderr += data.toString();
|
|
6715
6742
|
runLog?.stream.write(`[stderr] ${data.toString()}`);
|
|
6716
6743
|
});
|
|
6717
|
-
this.process
|
|
6744
|
+
this.process?.on("error", (err) => {
|
|
6718
6745
|
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
6719
6746
|
});
|
|
6720
|
-
this.process
|
|
6747
|
+
this.process?.on("close", (code) => {
|
|
6721
6748
|
this.process = null;
|
|
6722
6749
|
const stdout = textChunks.join("");
|
|
6723
6750
|
const stats = tracker.stats;
|
|
@@ -6847,6 +6874,7 @@ var init_review_worker = __esm(() => {
|
|
|
6847
6874
|
init_gate_collectors();
|
|
6848
6875
|
init_git_diff_stat();
|
|
6849
6876
|
init_log();
|
|
6877
|
+
init_pm();
|
|
6850
6878
|
init_process_group();
|
|
6851
6879
|
init_progress_tracker();
|
|
6852
6880
|
init_review_completion();
|
|
@@ -7502,16 +7530,16 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
7502
7530
|
currentStage: next.stage.id,
|
|
7503
7531
|
done: false
|
|
7504
7532
|
});
|
|
7505
|
-
deps.sink?.recordPlaybookAdvanced({
|
|
7506
|
-
fromStageId: stage.id,
|
|
7507
|
-
fromStageName: stage.name,
|
|
7508
|
-
toStageId: next.stage.id,
|
|
7509
|
-
toStageName: next.stage.name,
|
|
7510
|
-
advancedBy: "system",
|
|
7511
|
-
reason: summary
|
|
7512
|
-
});
|
|
7513
7533
|
await runTransition(deps.client, card, {
|
|
7514
7534
|
move: { columnName: toColumn },
|
|
7535
|
+
onMoved: () => deps.sink?.recordPlaybookAdvanced({
|
|
7536
|
+
fromStageId: stage.id,
|
|
7537
|
+
fromStageName: stage.name,
|
|
7538
|
+
toStageId: next.stage.id,
|
|
7539
|
+
toStageName: next.stage.name,
|
|
7540
|
+
advancedBy: "system",
|
|
7541
|
+
reason: summary
|
|
7542
|
+
}),
|
|
7515
7543
|
addLabels: [{ name: AGENT_LABEL }],
|
|
7516
7544
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
7517
7545
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
@@ -9220,6 +9248,22 @@ var init_recovery = __esm(() => {
|
|
|
9220
9248
|
init_worktree();
|
|
9221
9249
|
});
|
|
9222
9250
|
|
|
9251
|
+
// src/claim.ts
|
|
9252
|
+
async function claimReviewCard(client, cardId, agentId) {
|
|
9253
|
+
try {
|
|
9254
|
+
const { claimed } = await client.claimCard(cardId, agentId);
|
|
9255
|
+
log.debug(TAG34, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
9256
|
+
return claimed;
|
|
9257
|
+
} catch (err) {
|
|
9258
|
+
log.error(TAG34, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
9259
|
+
return false;
|
|
9260
|
+
}
|
|
9261
|
+
}
|
|
9262
|
+
var TAG34 = "claim";
|
|
9263
|
+
var init_claim = __esm(() => {
|
|
9264
|
+
init_log();
|
|
9265
|
+
});
|
|
9266
|
+
|
|
9223
9267
|
// src/strand-recovery.ts
|
|
9224
9268
|
var exports_strand_recovery = {};
|
|
9225
9269
|
__export(exports_strand_recovery, {
|
|
@@ -9266,19 +9310,27 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
9266
9310
|
const prUrl = resolvePrUrl(card.description ?? null, branch, cwd, provider);
|
|
9267
9311
|
if (prUrl)
|
|
9268
9312
|
continue;
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
}
|
|
9274
|
-
|
|
9313
|
+
const won = await claimReviewCard(client, card.id, agentId);
|
|
9314
|
+
if (!won) {
|
|
9315
|
+
log.debug(TAG35, `#${card.short_id} — lost the review claim race, skipping`);
|
|
9316
|
+
continue;
|
|
9317
|
+
}
|
|
9318
|
+
log.warn(TAG35, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
9319
|
+
reclaimed.push(card.id);
|
|
9320
|
+
if (opts.onClaimed) {
|
|
9321
|
+
try {
|
|
9322
|
+
await opts.onClaimed(card);
|
|
9323
|
+
} catch (err) {
|
|
9324
|
+
log.error(TAG35, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
9325
|
+
}
|
|
9275
9326
|
}
|
|
9276
9327
|
}
|
|
9277
9328
|
return reclaimed;
|
|
9278
9329
|
}
|
|
9279
|
-
var
|
|
9330
|
+
var TAG35 = "strand-recovery";
|
|
9280
9331
|
var init_strand_recovery = __esm(() => {
|
|
9281
9332
|
init_board_helpers();
|
|
9333
|
+
init_claim();
|
|
9282
9334
|
init_git_pr();
|
|
9283
9335
|
init_log();
|
|
9284
9336
|
init_review_worktree();
|
|
@@ -9327,7 +9379,7 @@ class Reconciler {
|
|
|
9327
9379
|
clearInterval(this.timer);
|
|
9328
9380
|
this.timer = null;
|
|
9329
9381
|
}
|
|
9330
|
-
log.info(
|
|
9382
|
+
log.info(TAG36, "Heartbeat stopped");
|
|
9331
9383
|
}
|
|
9332
9384
|
async recoverStaleRuns() {
|
|
9333
9385
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -9344,7 +9396,7 @@ class Reconciler {
|
|
|
9344
9396
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
9345
9397
|
continue;
|
|
9346
9398
|
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`;
|
|
9347
|
-
log.warn(
|
|
9399
|
+
log.warn(TAG36, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
9348
9400
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
9349
9401
|
runId: run.runId,
|
|
9350
9402
|
cardId: run.cardId,
|
|
@@ -9371,11 +9423,11 @@ class Reconciler {
|
|
|
9371
9423
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
9372
9424
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
9373
9425
|
continue;
|
|
9374
|
-
log.warn(
|
|
9426
|
+
log.warn(TAG36, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
9375
9427
|
try {
|
|
9376
9428
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
9377
9429
|
} catch (err) {
|
|
9378
|
-
log.error(
|
|
9430
|
+
log.error(TAG36, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9379
9431
|
}
|
|
9380
9432
|
}
|
|
9381
9433
|
}
|
|
@@ -9397,10 +9449,19 @@ class Reconciler {
|
|
|
9397
9449
|
labelMap,
|
|
9398
9450
|
reviewColumns: this.reviewColumns,
|
|
9399
9451
|
approvedLabel: this.approvedLabel,
|
|
9400
|
-
graceMs:
|
|
9452
|
+
graceMs: 0,
|
|
9401
9453
|
knownCardIds,
|
|
9402
9454
|
cwd: process.cwd(),
|
|
9403
|
-
provider: this.gitProvider
|
|
9455
|
+
provider: this.gitProvider,
|
|
9456
|
+
onClaimed: async (card) => {
|
|
9457
|
+
const column = columns.find((c) => c.id === card.column_id);
|
|
9458
|
+
if (!column)
|
|
9459
|
+
return;
|
|
9460
|
+
const cardLabels = resolveCardLabels(card, labelMap);
|
|
9461
|
+
const subtasks = card.subtasks ?? [];
|
|
9462
|
+
log.info(TAG36, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
9463
|
+
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
9464
|
+
}
|
|
9404
9465
|
});
|
|
9405
9466
|
}
|
|
9406
9467
|
async releaseStalledApprovals(cards, columns, knownCardIds) {
|
|
@@ -9422,11 +9483,11 @@ class Reconciler {
|
|
|
9422
9483
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
9423
9484
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
9424
9485
|
continue;
|
|
9425
|
-
log.warn(
|
|
9486
|
+
log.warn(TAG36, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
9426
9487
|
try {
|
|
9427
9488
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
9428
9489
|
} catch (err) {
|
|
9429
|
-
log.error(
|
|
9490
|
+
log.error(TAG36, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9430
9491
|
}
|
|
9431
9492
|
}
|
|
9432
9493
|
}
|
|
@@ -9469,21 +9530,21 @@ class Reconciler {
|
|
|
9469
9530
|
const subtasks = card.subtasks ?? [];
|
|
9470
9531
|
const mode = route.mode;
|
|
9471
9532
|
if (route.stage) {
|
|
9472
|
-
log.info(
|
|
9533
|
+
log.info(TAG36, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
|
|
9473
9534
|
}
|
|
9474
9535
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
9475
|
-
log.debug(
|
|
9536
|
+
log.debug(TAG36, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
9476
9537
|
continue;
|
|
9477
9538
|
}
|
|
9478
9539
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
9479
|
-
log.debug(
|
|
9540
|
+
log.debug(TAG36, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
9480
9541
|
continue;
|
|
9481
9542
|
}
|
|
9482
9543
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
9483
|
-
log.debug(
|
|
9544
|
+
log.debug(TAG36, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
9484
9545
|
continue;
|
|
9485
9546
|
}
|
|
9486
|
-
log.info(
|
|
9547
|
+
log.info(TAG36, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
9487
9548
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
9488
9549
|
}
|
|
9489
9550
|
}
|
|
@@ -9494,18 +9555,18 @@ class Reconciler {
|
|
|
9494
9555
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
9495
9556
|
for (const knownId of knownCardIds) {
|
|
9496
9557
|
if (!allAgentCardIds.has(knownId)) {
|
|
9497
|
-
log.info(
|
|
9558
|
+
log.info(TAG36, `Missed unassign: ${knownId} — removing`);
|
|
9498
9559
|
await this.pool.removeCard(knownId);
|
|
9499
9560
|
}
|
|
9500
9561
|
}
|
|
9501
9562
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
9502
|
-
log.debug(
|
|
9563
|
+
log.debug(TAG36, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
9503
9564
|
} catch (err) {
|
|
9504
|
-
log.error(
|
|
9565
|
+
log.error(TAG36, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
9505
9566
|
}
|
|
9506
9567
|
}
|
|
9507
9568
|
}
|
|
9508
|
-
var
|
|
9569
|
+
var TAG36 = "reconcile";
|
|
9509
9570
|
var init_reconcile = __esm(() => {
|
|
9510
9571
|
init_board_helpers();
|
|
9511
9572
|
init_git_pr();
|
|
@@ -9545,7 +9606,7 @@ function prettyBanner(config, version) {
|
|
|
9545
9606
|
checks.push({ kind: "ok", message });
|
|
9546
9607
|
},
|
|
9547
9608
|
warn(message) {
|
|
9548
|
-
log.warn(
|
|
9609
|
+
log.warn(TAG37, message);
|
|
9549
9610
|
checks.push({ kind: "warn", message: message.split(`
|
|
9550
9611
|
`, 1)[0] });
|
|
9551
9612
|
},
|
|
@@ -9570,25 +9631,25 @@ function prettyBanner(config, version) {
|
|
|
9570
9631
|
};
|
|
9571
9632
|
}
|
|
9572
9633
|
function jsonBanner(config, version) {
|
|
9573
|
-
log.info(
|
|
9574
|
-
log.info(
|
|
9634
|
+
log.info(TAG37, `Harmony Agent Daemon v${version} starting...`);
|
|
9635
|
+
log.info(TAG37, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
9575
9636
|
if (config.agent.review.enabled) {
|
|
9576
|
-
log.info(
|
|
9637
|
+
log.info(TAG37, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
9577
9638
|
}
|
|
9578
9639
|
let failed = false;
|
|
9579
9640
|
return {
|
|
9580
9641
|
setProjectName(_name) {},
|
|
9581
9642
|
setGitProvider(provider) {
|
|
9582
|
-
log.info(
|
|
9643
|
+
log.info(TAG37, `Git provider: ${provider}`);
|
|
9583
9644
|
},
|
|
9584
9645
|
setHttpPort(port) {
|
|
9585
|
-
log.info(
|
|
9646
|
+
log.info(TAG37, `HTTP server on port ${port}`);
|
|
9586
9647
|
},
|
|
9587
9648
|
check(message) {
|
|
9588
|
-
log.info(
|
|
9649
|
+
log.info(TAG37, message);
|
|
9589
9650
|
},
|
|
9590
9651
|
warn(message) {
|
|
9591
|
-
log.warn(
|
|
9652
|
+
log.warn(TAG37, message);
|
|
9592
9653
|
},
|
|
9593
9654
|
fail() {
|
|
9594
9655
|
failed = true;
|
|
@@ -9596,7 +9657,7 @@ function jsonBanner(config, version) {
|
|
|
9596
9657
|
async ready(message) {
|
|
9597
9658
|
if (failed)
|
|
9598
9659
|
return;
|
|
9599
|
-
log.info(
|
|
9660
|
+
log.info(TAG37, message);
|
|
9600
9661
|
}
|
|
9601
9662
|
};
|
|
9602
9663
|
}
|
|
@@ -9677,7 +9738,7 @@ function cyan(s) {
|
|
|
9677
9738
|
function yellow(s) {
|
|
9678
9739
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
9679
9740
|
}
|
|
9680
|
-
var
|
|
9741
|
+
var TAG37 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
9681
9742
|
var init_startup_banner = __esm(() => {
|
|
9682
9743
|
init_log();
|
|
9683
9744
|
ANSI = {
|
|
@@ -9828,13 +9889,13 @@ class Watcher {
|
|
|
9828
9889
|
}
|
|
9829
9890
|
async start() {
|
|
9830
9891
|
if (!isPretty()) {
|
|
9831
|
-
log.info(
|
|
9892
|
+
log.info(TAG38, "Connecting to Supabase realtime (broadcast)...");
|
|
9832
9893
|
}
|
|
9833
9894
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
9834
9895
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
9835
9896
|
this.subscribeBroadcast();
|
|
9836
9897
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
9837
|
-
log.debug(
|
|
9898
|
+
log.debug(TAG38, "Presence sync");
|
|
9838
9899
|
}).subscribe(async (status) => {
|
|
9839
9900
|
if (status === "SUBSCRIBED") {
|
|
9840
9901
|
await presenceChannel.track({
|
|
@@ -9847,7 +9908,7 @@ class Watcher {
|
|
|
9847
9908
|
agentName: this.identity.agentName
|
|
9848
9909
|
});
|
|
9849
9910
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
9850
|
-
log.info(
|
|
9911
|
+
log.info(TAG38, "Presence tracked on board-presence channel");
|
|
9851
9912
|
}
|
|
9852
9913
|
this.presenceTracked = true;
|
|
9853
9914
|
this.maybeResolveReady();
|
|
@@ -9860,13 +9921,13 @@ class Watcher {
|
|
|
9860
9921
|
return;
|
|
9861
9922
|
const gen = ++this.broadcastGen;
|
|
9862
9923
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
9863
|
-
log.debug(
|
|
9924
|
+
log.debug(TAG38, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
9864
9925
|
this.onCardBroadcast({
|
|
9865
9926
|
event: "card_update",
|
|
9866
9927
|
payload: msg.payload ?? {}
|
|
9867
9928
|
});
|
|
9868
9929
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
9869
|
-
log.debug(
|
|
9930
|
+
log.debug(TAG38, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
9870
9931
|
this.onCardBroadcast({
|
|
9871
9932
|
event: "card_created",
|
|
9872
9933
|
payload: msg.payload ?? {}
|
|
@@ -9876,7 +9937,7 @@ class Watcher {
|
|
|
9876
9937
|
const cardId = payload.card_id;
|
|
9877
9938
|
const command = payload.command;
|
|
9878
9939
|
if (cardId && command) {
|
|
9879
|
-
log.info(
|
|
9940
|
+
log.info(TAG38, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
9880
9941
|
this.onAgentCommand?.({ cardId, command });
|
|
9881
9942
|
}
|
|
9882
9943
|
}).subscribe((status) => {
|
|
@@ -9886,13 +9947,13 @@ class Watcher {
|
|
|
9886
9947
|
this.connected = true;
|
|
9887
9948
|
this.reconnectAttempts = 0;
|
|
9888
9949
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
9889
|
-
log.info(
|
|
9950
|
+
log.info(TAG38, "Broadcast subscription active");
|
|
9890
9951
|
}
|
|
9891
9952
|
this.maybeResolveReady();
|
|
9892
9953
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
9893
9954
|
this.connected = false;
|
|
9894
9955
|
if (!this.stopping) {
|
|
9895
|
-
log.warn(
|
|
9956
|
+
log.warn(TAG38, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
9896
9957
|
this.scheduleReconnect();
|
|
9897
9958
|
}
|
|
9898
9959
|
}
|
|
@@ -9911,7 +9972,7 @@ class Watcher {
|
|
|
9911
9972
|
async reconnectBroadcast() {
|
|
9912
9973
|
if (this.stopping || !this.supabase)
|
|
9913
9974
|
return;
|
|
9914
|
-
log.warn(
|
|
9975
|
+
log.warn(TAG38, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
9915
9976
|
if (this.channel) {
|
|
9916
9977
|
const old = this.channel;
|
|
9917
9978
|
this.channel = null;
|
|
@@ -9941,10 +10002,10 @@ class Watcher {
|
|
|
9941
10002
|
this.supabase = null;
|
|
9942
10003
|
}
|
|
9943
10004
|
this.connected = false;
|
|
9944
|
-
log.info(
|
|
10005
|
+
log.info(TAG38, "Broadcast subscription stopped");
|
|
9945
10006
|
}
|
|
9946
10007
|
}
|
|
9947
|
-
var
|
|
10008
|
+
var TAG38 = "watcher";
|
|
9948
10009
|
var init_watcher = __esm(() => {
|
|
9949
10010
|
init_log();
|
|
9950
10011
|
});
|
|
@@ -10031,10 +10092,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
10031
10092
|
});
|
|
10032
10093
|
} catch {}
|
|
10033
10094
|
if (result.removed.length > 0) {
|
|
10034
|
-
log.info(
|
|
10095
|
+
log.info(TAG39, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
10035
10096
|
}
|
|
10036
10097
|
if (result.errors.length > 0) {
|
|
10037
|
-
log.warn(
|
|
10098
|
+
log.warn(TAG39, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
10038
10099
|
}
|
|
10039
10100
|
return result;
|
|
10040
10101
|
}
|
|
@@ -10064,7 +10125,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
10064
10125
|
} catch (err) {
|
|
10065
10126
|
const detail = gitErrorDetail2(err);
|
|
10066
10127
|
if (isTransientGitNetworkError(detail)) {
|
|
10067
|
-
log.debug(
|
|
10128
|
+
log.debug(TAG39, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
10068
10129
|
return result;
|
|
10069
10130
|
}
|
|
10070
10131
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -10103,7 +10164,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
10103
10164
|
continue;
|
|
10104
10165
|
}
|
|
10105
10166
|
if (clock() > sweepDeadline) {
|
|
10106
|
-
log.debug(
|
|
10167
|
+
log.debug(TAG39, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
10107
10168
|
break;
|
|
10108
10169
|
}
|
|
10109
10170
|
try {
|
|
@@ -10116,17 +10177,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
10116
10177
|
} catch (err) {
|
|
10117
10178
|
const detail = gitErrorDetail2(err);
|
|
10118
10179
|
if (isTransientGitNetworkError(detail)) {
|
|
10119
|
-
log.debug(
|
|
10180
|
+
log.debug(TAG39, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
10120
10181
|
break;
|
|
10121
10182
|
}
|
|
10122
10183
|
result.errors.push({ ref, error: detail });
|
|
10123
10184
|
}
|
|
10124
10185
|
}
|
|
10125
10186
|
if (result.removed.length > 0) {
|
|
10126
|
-
log.info(
|
|
10187
|
+
log.info(TAG39, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
10127
10188
|
}
|
|
10128
10189
|
if (result.errors.length > 0) {
|
|
10129
|
-
log.warn(
|
|
10190
|
+
log.warn(TAG39, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
10130
10191
|
}
|
|
10131
10192
|
return result;
|
|
10132
10193
|
}
|
|
@@ -10157,13 +10218,13 @@ class WorktreeGc {
|
|
|
10157
10218
|
try {
|
|
10158
10219
|
runWorktreeGc(this.basePath, this.store);
|
|
10159
10220
|
} catch (err) {
|
|
10160
|
-
log.warn(
|
|
10221
|
+
log.warn(TAG39, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
10161
10222
|
}
|
|
10162
10223
|
if (this.remoteOpts) {
|
|
10163
10224
|
try {
|
|
10164
10225
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
10165
10226
|
} catch (err) {
|
|
10166
|
-
log.warn(
|
|
10227
|
+
log.warn(TAG39, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
10167
10228
|
}
|
|
10168
10229
|
}
|
|
10169
10230
|
}
|
|
@@ -10177,7 +10238,7 @@ function getRepoRoot2() {
|
|
|
10177
10238
|
return null;
|
|
10178
10239
|
}
|
|
10179
10240
|
}
|
|
10180
|
-
var
|
|
10241
|
+
var TAG39 = "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;
|
|
10181
10242
|
var init_worktree_gc = __esm(() => {
|
|
10182
10243
|
init_log();
|
|
10183
10244
|
init_worktree();
|
|
@@ -10281,7 +10342,7 @@ async function main() {
|
|
|
10281
10342
|
} catch (err) {
|
|
10282
10343
|
if (err instanceof ConfigValidationError) {
|
|
10283
10344
|
banner.fail();
|
|
10284
|
-
log.error(
|
|
10345
|
+
log.error(TAG40, err.message);
|
|
10285
10346
|
process.exit(1);
|
|
10286
10347
|
}
|
|
10287
10348
|
throw err;
|
|
@@ -10291,7 +10352,7 @@ async function main() {
|
|
|
10291
10352
|
} catch (err) {
|
|
10292
10353
|
if (err instanceof ConfigValidationError) {
|
|
10293
10354
|
banner.fail();
|
|
10294
|
-
log.error(
|
|
10355
|
+
log.error(TAG40, err.message);
|
|
10295
10356
|
process.exit(1);
|
|
10296
10357
|
}
|
|
10297
10358
|
throw err;
|
|
@@ -10401,7 +10462,7 @@ async function main() {
|
|
|
10401
10462
|
if (shuttingDown)
|
|
10402
10463
|
return;
|
|
10403
10464
|
shuttingDown = true;
|
|
10404
|
-
log.info(
|
|
10465
|
+
log.info(TAG40, `Received ${signal}, shutting down gracefully...`);
|
|
10405
10466
|
reconciler.stop();
|
|
10406
10467
|
mergeMonitor?.stop();
|
|
10407
10468
|
worktreeGc.stop();
|
|
@@ -10411,18 +10472,18 @@ async function main() {
|
|
|
10411
10472
|
}
|
|
10412
10473
|
await watcher.stop();
|
|
10413
10474
|
await pool.shutdown();
|
|
10414
|
-
log.info(
|
|
10475
|
+
log.info(TAG40, "Daemon stopped.");
|
|
10415
10476
|
process.exit(exitCode);
|
|
10416
10477
|
};
|
|
10417
10478
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
10418
10479
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
10419
10480
|
process.on("uncaughtException", (err) => {
|
|
10420
|
-
log.error(
|
|
10481
|
+
log.error(TAG40, `Uncaught exception: ${err.message}`);
|
|
10421
10482
|
exitCode = 1;
|
|
10422
10483
|
shutdown("uncaughtException");
|
|
10423
10484
|
});
|
|
10424
10485
|
process.on("unhandledRejection", (reason) => {
|
|
10425
|
-
log.error(
|
|
10486
|
+
log.error(TAG40, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
10426
10487
|
exitCode = 1;
|
|
10427
10488
|
shutdown("unhandledRejection");
|
|
10428
10489
|
});
|
|
@@ -10475,29 +10536,29 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
10475
10536
|
if (assignedAgentId === undefined)
|
|
10476
10537
|
return;
|
|
10477
10538
|
if (assignedAgentId === agentId) {
|
|
10478
|
-
log.info(
|
|
10539
|
+
log.info(TAG40, `Broadcast: card ${cardId} assigned to agent`);
|
|
10479
10540
|
try {
|
|
10480
10541
|
await pool.resetAttemptsForReassign(cardId);
|
|
10481
10542
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
10482
10543
|
} catch (err) {
|
|
10483
|
-
log.error(
|
|
10544
|
+
log.error(TAG40, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
10484
10545
|
}
|
|
10485
10546
|
} else if (pool.isCardKnown(cardId)) {
|
|
10486
|
-
log.info(
|
|
10547
|
+
log.info(TAG40, `Broadcast: card ${cardId} unassigned from agent`);
|
|
10487
10548
|
await pool.removeCard(cardId);
|
|
10488
10549
|
}
|
|
10489
10550
|
}
|
|
10490
10551
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
10491
10552
|
const { card } = await client.getCard(cardId);
|
|
10492
10553
|
if (card.assigned_agent_id !== agentId) {
|
|
10493
|
-
log.debug(
|
|
10554
|
+
log.debug(TAG40, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
10494
10555
|
return;
|
|
10495
10556
|
}
|
|
10496
10557
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
10497
10558
|
const columns = board.columns;
|
|
10498
10559
|
const column = columns.find((c) => c.id === card.column_id);
|
|
10499
10560
|
if (!column) {
|
|
10500
|
-
log.warn(
|
|
10561
|
+
log.warn(TAG40, `Column not found for card ${cardId}`);
|
|
10501
10562
|
return;
|
|
10502
10563
|
}
|
|
10503
10564
|
const route = classifyPickup(card, column.name, {
|
|
@@ -10506,27 +10567,27 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
10506
10567
|
playbooks: config.agent.playbooks
|
|
10507
10568
|
});
|
|
10508
10569
|
if (!route) {
|
|
10509
|
-
log.info(
|
|
10570
|
+
log.info(TAG40, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
10510
10571
|
return;
|
|
10511
10572
|
}
|
|
10512
10573
|
if (route.stage) {
|
|
10513
|
-
log.info(
|
|
10574
|
+
log.info(TAG40, `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`);
|
|
10514
10575
|
}
|
|
10515
10576
|
const mode = route.mode;
|
|
10516
10577
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
10517
10578
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
10518
10579
|
const subtasks = card.subtasks ?? [];
|
|
10519
10580
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
10520
|
-
log.debug(
|
|
10581
|
+
log.debug(TAG40, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
10521
10582
|
return;
|
|
10522
10583
|
}
|
|
10523
10584
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
10524
|
-
log.info(
|
|
10585
|
+
log.info(TAG40, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
10525
10586
|
return;
|
|
10526
10587
|
}
|
|
10527
10588
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
10528
10589
|
}
|
|
10529
|
-
var
|
|
10590
|
+
var TAG40 = "daemon", PKG_VERSION;
|
|
10530
10591
|
var init_src = __esm(() => {
|
|
10531
10592
|
init_board_helpers();
|
|
10532
10593
|
init_config();
|
package/dist/index.js
CHANGED
|
@@ -104,6 +104,7 @@ var init_log = __esm(() => {
|
|
|
104
104
|
var exports_board_helpers = {};
|
|
105
105
|
__export(exports_board_helpers, {
|
|
106
106
|
resolveCardLabels: () => resolveCardLabels,
|
|
107
|
+
releaseAssignedAgent: () => releaseAssignedAgent,
|
|
107
108
|
moveCardToColumn: () => moveCardToColumn,
|
|
108
109
|
moveCardAndAddLabel: () => moveCardAndAddLabel,
|
|
109
110
|
hasLabel: () => hasLabel,
|
|
@@ -125,6 +126,9 @@ function resolveCardLabels(card, labelMap) {
|
|
|
125
126
|
function hasLabel(cardLabels, labelName) {
|
|
126
127
|
return cardLabels.some((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
127
128
|
}
|
|
129
|
+
async function releaseAssignedAgent(client, cardId) {
|
|
130
|
+
await client.updateCard(cardId, { assignedAgentId: null });
|
|
131
|
+
}
|
|
128
132
|
async function moveCardToColumn(client, card, targetColumnName) {
|
|
129
133
|
try {
|
|
130
134
|
const board = await client.getBoard(card.project_id);
|
|
@@ -3861,6 +3865,11 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
3861
3865
|
}
|
|
3862
3866
|
if (config.completion.moveToColumn) {
|
|
3863
3867
|
await moveCardToColumn(client, card, config.completion.moveToColumn);
|
|
3868
|
+
try {
|
|
3869
|
+
await releaseAssignedAgent(client, card.id);
|
|
3870
|
+
} catch (err) {
|
|
3871
|
+
log.warn(TAG15, `assignment release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
3872
|
+
}
|
|
3864
3873
|
if (onMovedToCompletion) {
|
|
3865
3874
|
try {
|
|
3866
3875
|
await onMovedToCompletion(card);
|
|
@@ -6189,6 +6198,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
6189
6198
|
const board = await withRetry("move", shortId, () => client.getBoard(card.project_id), attempts, backoffMs);
|
|
6190
6199
|
const columns = board.columns;
|
|
6191
6200
|
const labels = board.labels ?? [];
|
|
6201
|
+
let moveLanded = false;
|
|
6192
6202
|
if (plan.move) {
|
|
6193
6203
|
const target = columns.find((c) => c.name.toLowerCase() === plan.move.columnName.toLowerCase());
|
|
6194
6204
|
if (!target) {
|
|
@@ -6201,8 +6211,14 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
6201
6211
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
6202
6212
|
log.info(TAG23, `#${shortId} → "${target.name}"`);
|
|
6203
6213
|
card.column_id = target.id;
|
|
6214
|
+
moveLanded = true;
|
|
6215
|
+
} else {
|
|
6216
|
+
moveLanded = true;
|
|
6204
6217
|
}
|
|
6205
6218
|
}
|
|
6219
|
+
if (moveLanded && plan.onMoved) {
|
|
6220
|
+
await plan.onMoved();
|
|
6221
|
+
}
|
|
6206
6222
|
if (plan.addLabels?.length) {
|
|
6207
6223
|
const existing = new Set(card.labelIds ?? []);
|
|
6208
6224
|
for (const { name, color } of plan.addLabels) {
|
|
@@ -6438,7 +6454,15 @@ class ReviewWorker {
|
|
|
6438
6454
|
const cwd = this.worktreePath;
|
|
6439
6455
|
if (!localMode) {
|
|
6440
6456
|
log.info(this.tag, `Starting dev server on port ${port}...`);
|
|
6441
|
-
|
|
6457
|
+
const [devCmd, devArgs] = spawnRunArgs("dev", "--port", String(port));
|
|
6458
|
+
this.devServerProcess = spawnInGroup(devCmd, devArgs, {
|
|
6459
|
+
cwd,
|
|
6460
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6461
|
+
});
|
|
6462
|
+
let devServerSpawnError = null;
|
|
6463
|
+
this.devServerProcess.once("error", (err) => {
|
|
6464
|
+
devServerSpawnError = err;
|
|
6465
|
+
});
|
|
6442
6466
|
await this.client.updateAgentProgress(card.id, {
|
|
6443
6467
|
agentIdentifier: agentIdentifier(this.id),
|
|
6444
6468
|
agentName: `${AGENT_NAME} (Review)`,
|
|
@@ -6446,6 +6470,9 @@ class ReviewWorker {
|
|
|
6446
6470
|
currentTask: `Starting dev server on port ${port}…`,
|
|
6447
6471
|
progressPercent: 10
|
|
6448
6472
|
});
|
|
6473
|
+
if (devServerSpawnError) {
|
|
6474
|
+
throw new DevServerReadinessError(`dev server failed to start (${devCmd}): ${devServerSpawnError.message}`);
|
|
6475
|
+
}
|
|
6449
6476
|
await waitForDevServer(this.devServerProcess, 30000);
|
|
6450
6477
|
await probeDevServer(port);
|
|
6451
6478
|
log.info(this.tag, `Dev server ready on port ${port}`);
|
|
@@ -6700,7 +6727,7 @@ class ReviewWorker {
|
|
|
6700
6727
|
[parse_error] ${msg}
|
|
6701
6728
|
`);
|
|
6702
6729
|
});
|
|
6703
|
-
if (this.process
|
|
6730
|
+
if (this.process?.stdout) {
|
|
6704
6731
|
parser.attach(this.process.stdout);
|
|
6705
6732
|
if (runLog) {
|
|
6706
6733
|
this.process.stdout.on("data", (chunk) => {
|
|
@@ -6709,14 +6736,14 @@ class ReviewWorker {
|
|
|
6709
6736
|
}
|
|
6710
6737
|
}
|
|
6711
6738
|
let stderr = "";
|
|
6712
|
-
this.process
|
|
6739
|
+
this.process?.stderr?.on("data", (data) => {
|
|
6713
6740
|
stderr += data.toString();
|
|
6714
6741
|
runLog?.stream.write(`[stderr] ${data.toString()}`);
|
|
6715
6742
|
});
|
|
6716
|
-
this.process
|
|
6743
|
+
this.process?.on("error", (err) => {
|
|
6717
6744
|
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
6718
6745
|
});
|
|
6719
|
-
this.process
|
|
6746
|
+
this.process?.on("close", (code) => {
|
|
6720
6747
|
this.process = null;
|
|
6721
6748
|
const stdout = textChunks.join("");
|
|
6722
6749
|
const stats = tracker.stats;
|
|
@@ -6846,6 +6873,7 @@ var init_review_worker = __esm(() => {
|
|
|
6846
6873
|
init_gate_collectors();
|
|
6847
6874
|
init_git_diff_stat();
|
|
6848
6875
|
init_log();
|
|
6876
|
+
init_pm();
|
|
6849
6877
|
init_process_group();
|
|
6850
6878
|
init_progress_tracker();
|
|
6851
6879
|
init_review_completion();
|
|
@@ -7501,16 +7529,16 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
|
|
|
7501
7529
|
currentStage: next.stage.id,
|
|
7502
7530
|
done: false
|
|
7503
7531
|
});
|
|
7504
|
-
deps.sink?.recordPlaybookAdvanced({
|
|
7505
|
-
fromStageId: stage.id,
|
|
7506
|
-
fromStageName: stage.name,
|
|
7507
|
-
toStageId: next.stage.id,
|
|
7508
|
-
toStageName: next.stage.name,
|
|
7509
|
-
advancedBy: "system",
|
|
7510
|
-
reason: summary
|
|
7511
|
-
});
|
|
7512
7532
|
await runTransition(deps.client, card, {
|
|
7513
7533
|
move: { columnName: toColumn },
|
|
7534
|
+
onMoved: () => deps.sink?.recordPlaybookAdvanced({
|
|
7535
|
+
fromStageId: stage.id,
|
|
7536
|
+
fromStageName: stage.name,
|
|
7537
|
+
toStageId: next.stage.id,
|
|
7538
|
+
toStageName: next.stage.name,
|
|
7539
|
+
advancedBy: "system",
|
|
7540
|
+
reason: summary
|
|
7541
|
+
}),
|
|
7514
7542
|
addLabels: [{ name: AGENT_LABEL }],
|
|
7515
7543
|
...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
|
|
7516
7544
|
}, { store: deps.stateStore, runId: deps.runId });
|
|
@@ -9219,6 +9247,22 @@ var init_recovery = __esm(() => {
|
|
|
9219
9247
|
init_worktree();
|
|
9220
9248
|
});
|
|
9221
9249
|
|
|
9250
|
+
// src/claim.ts
|
|
9251
|
+
async function claimReviewCard(client, cardId, agentId) {
|
|
9252
|
+
try {
|
|
9253
|
+
const { claimed } = await client.claimCard(cardId, agentId);
|
|
9254
|
+
log.debug(TAG34, `claim ${cardId} → ${claimed ? "won" : "lost"}`);
|
|
9255
|
+
return claimed;
|
|
9256
|
+
} catch (err) {
|
|
9257
|
+
log.error(TAG34, `claim ${cardId} failed: ${err instanceof Error ? err.message : err}`);
|
|
9258
|
+
return false;
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
var TAG34 = "claim";
|
|
9262
|
+
var init_claim = __esm(() => {
|
|
9263
|
+
init_log();
|
|
9264
|
+
});
|
|
9265
|
+
|
|
9222
9266
|
// src/strand-recovery.ts
|
|
9223
9267
|
var exports_strand_recovery = {};
|
|
9224
9268
|
__export(exports_strand_recovery, {
|
|
@@ -9265,19 +9309,27 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
9265
9309
|
const prUrl = resolvePrUrl(card.description ?? null, branch, cwd, provider);
|
|
9266
9310
|
if (prUrl)
|
|
9267
9311
|
continue;
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
}
|
|
9273
|
-
|
|
9312
|
+
const won = await claimReviewCard(client, card.id, agentId);
|
|
9313
|
+
if (!won) {
|
|
9314
|
+
log.debug(TAG35, `#${card.short_id} — lost the review claim race, skipping`);
|
|
9315
|
+
continue;
|
|
9316
|
+
}
|
|
9317
|
+
log.warn(TAG35, `#${card.short_id} claimed for review (branch pushed, no PR, unowned)`);
|
|
9318
|
+
reclaimed.push(card.id);
|
|
9319
|
+
if (opts.onClaimed) {
|
|
9320
|
+
try {
|
|
9321
|
+
await opts.onClaimed(card);
|
|
9322
|
+
} catch (err) {
|
|
9323
|
+
log.error(TAG35, `onClaimed for #${card.short_id} failed: ${err instanceof Error ? err.message : err}`);
|
|
9324
|
+
}
|
|
9274
9325
|
}
|
|
9275
9326
|
}
|
|
9276
9327
|
return reclaimed;
|
|
9277
9328
|
}
|
|
9278
|
-
var
|
|
9329
|
+
var TAG35 = "strand-recovery";
|
|
9279
9330
|
var init_strand_recovery = __esm(() => {
|
|
9280
9331
|
init_board_helpers();
|
|
9332
|
+
init_claim();
|
|
9281
9333
|
init_git_pr();
|
|
9282
9334
|
init_log();
|
|
9283
9335
|
init_review_worktree();
|
|
@@ -9326,7 +9378,7 @@ class Reconciler {
|
|
|
9326
9378
|
clearInterval(this.timer);
|
|
9327
9379
|
this.timer = null;
|
|
9328
9380
|
}
|
|
9329
|
-
log.info(
|
|
9381
|
+
log.info(TAG36, "Heartbeat stopped");
|
|
9330
9382
|
}
|
|
9331
9383
|
async recoverStaleRuns() {
|
|
9332
9384
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -9343,7 +9395,7 @@ class Reconciler {
|
|
|
9343
9395
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
9344
9396
|
continue;
|
|
9345
9397
|
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`;
|
|
9346
|
-
log.warn(
|
|
9398
|
+
log.warn(TAG36, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
9347
9399
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
9348
9400
|
runId: run.runId,
|
|
9349
9401
|
cardId: run.cardId,
|
|
@@ -9370,11 +9422,11 @@ class Reconciler {
|
|
|
9370
9422
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
9371
9423
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
9372
9424
|
continue;
|
|
9373
|
-
log.warn(
|
|
9425
|
+
log.warn(TAG36, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
9374
9426
|
try {
|
|
9375
9427
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
9376
9428
|
} catch (err) {
|
|
9377
|
-
log.error(
|
|
9429
|
+
log.error(TAG36, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9378
9430
|
}
|
|
9379
9431
|
}
|
|
9380
9432
|
}
|
|
@@ -9396,10 +9448,19 @@ class Reconciler {
|
|
|
9396
9448
|
labelMap,
|
|
9397
9449
|
reviewColumns: this.reviewColumns,
|
|
9398
9450
|
approvedLabel: this.approvedLabel,
|
|
9399
|
-
graceMs:
|
|
9451
|
+
graceMs: 0,
|
|
9400
9452
|
knownCardIds,
|
|
9401
9453
|
cwd: process.cwd(),
|
|
9402
|
-
provider: this.gitProvider
|
|
9454
|
+
provider: this.gitProvider,
|
|
9455
|
+
onClaimed: async (card) => {
|
|
9456
|
+
const column = columns.find((c) => c.id === card.column_id);
|
|
9457
|
+
if (!column)
|
|
9458
|
+
return;
|
|
9459
|
+
const cardLabels = resolveCardLabels(card, labelMap);
|
|
9460
|
+
const subtasks = card.subtasks ?? [];
|
|
9461
|
+
log.info(TAG36, `Enqueuing claimed review card #${card.short_id} (agent-agnostic pickup)`);
|
|
9462
|
+
await this.pool.enqueue(card, column, cardLabels, subtasks, "review");
|
|
9463
|
+
}
|
|
9403
9464
|
});
|
|
9404
9465
|
}
|
|
9405
9466
|
async releaseStalledApprovals(cards, columns, knownCardIds) {
|
|
@@ -9421,11 +9482,11 @@ class Reconciler {
|
|
|
9421
9482
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
9422
9483
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
9423
9484
|
continue;
|
|
9424
|
-
log.warn(
|
|
9485
|
+
log.warn(TAG36, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
9425
9486
|
try {
|
|
9426
9487
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
9427
9488
|
} catch (err) {
|
|
9428
|
-
log.error(
|
|
9489
|
+
log.error(TAG36, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
9429
9490
|
}
|
|
9430
9491
|
}
|
|
9431
9492
|
}
|
|
@@ -9468,21 +9529,21 @@ class Reconciler {
|
|
|
9468
9529
|
const subtasks = card.subtasks ?? [];
|
|
9469
9530
|
const mode = route.mode;
|
|
9470
9531
|
if (route.stage) {
|
|
9471
|
-
log.info(
|
|
9532
|
+
log.info(TAG36, `Stage card #${card.short_id} (stage "${card.current_stage}") in "${column.name}" — routing to the stage executor (implement) regardless of column`);
|
|
9472
9533
|
}
|
|
9473
9534
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
9474
|
-
log.debug(
|
|
9535
|
+
log.debug(TAG36, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
9475
9536
|
continue;
|
|
9476
9537
|
}
|
|
9477
9538
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
9478
|
-
log.debug(
|
|
9539
|
+
log.debug(TAG36, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
9479
9540
|
continue;
|
|
9480
9541
|
}
|
|
9481
9542
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
9482
|
-
log.debug(
|
|
9543
|
+
log.debug(TAG36, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
9483
9544
|
continue;
|
|
9484
9545
|
}
|
|
9485
|
-
log.info(
|
|
9546
|
+
log.info(TAG36, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
9486
9547
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
9487
9548
|
}
|
|
9488
9549
|
}
|
|
@@ -9493,18 +9554,18 @@ class Reconciler {
|
|
|
9493
9554
|
await this.recoverStrandedReview(cards, columns, labelMap, knownCardIds);
|
|
9494
9555
|
for (const knownId of knownCardIds) {
|
|
9495
9556
|
if (!allAgentCardIds.has(knownId)) {
|
|
9496
|
-
log.info(
|
|
9557
|
+
log.info(TAG36, `Missed unassign: ${knownId} — removing`);
|
|
9497
9558
|
await this.pool.removeCard(knownId);
|
|
9498
9559
|
}
|
|
9499
9560
|
}
|
|
9500
9561
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
9501
|
-
log.debug(
|
|
9562
|
+
log.debug(TAG36, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
9502
9563
|
} catch (err) {
|
|
9503
|
-
log.error(
|
|
9564
|
+
log.error(TAG36, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
9504
9565
|
}
|
|
9505
9566
|
}
|
|
9506
9567
|
}
|
|
9507
|
-
var
|
|
9568
|
+
var TAG36 = "reconcile";
|
|
9508
9569
|
var init_reconcile = __esm(() => {
|
|
9509
9570
|
init_board_helpers();
|
|
9510
9571
|
init_git_pr();
|
|
@@ -9544,7 +9605,7 @@ function prettyBanner(config, version) {
|
|
|
9544
9605
|
checks.push({ kind: "ok", message });
|
|
9545
9606
|
},
|
|
9546
9607
|
warn(message) {
|
|
9547
|
-
log.warn(
|
|
9608
|
+
log.warn(TAG37, message);
|
|
9548
9609
|
checks.push({ kind: "warn", message: message.split(`
|
|
9549
9610
|
`, 1)[0] });
|
|
9550
9611
|
},
|
|
@@ -9569,25 +9630,25 @@ function prettyBanner(config, version) {
|
|
|
9569
9630
|
};
|
|
9570
9631
|
}
|
|
9571
9632
|
function jsonBanner(config, version) {
|
|
9572
|
-
log.info(
|
|
9573
|
-
log.info(
|
|
9633
|
+
log.info(TAG37, `Harmony Agent Daemon v${version} starting...`);
|
|
9634
|
+
log.info(TAG37, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
9574
9635
|
if (config.agent.review.enabled) {
|
|
9575
|
-
log.info(
|
|
9636
|
+
log.info(TAG37, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
9576
9637
|
}
|
|
9577
9638
|
let failed = false;
|
|
9578
9639
|
return {
|
|
9579
9640
|
setProjectName(_name) {},
|
|
9580
9641
|
setGitProvider(provider) {
|
|
9581
|
-
log.info(
|
|
9642
|
+
log.info(TAG37, `Git provider: ${provider}`);
|
|
9582
9643
|
},
|
|
9583
9644
|
setHttpPort(port) {
|
|
9584
|
-
log.info(
|
|
9645
|
+
log.info(TAG37, `HTTP server on port ${port}`);
|
|
9585
9646
|
},
|
|
9586
9647
|
check(message) {
|
|
9587
|
-
log.info(
|
|
9648
|
+
log.info(TAG37, message);
|
|
9588
9649
|
},
|
|
9589
9650
|
warn(message) {
|
|
9590
|
-
log.warn(
|
|
9651
|
+
log.warn(TAG37, message);
|
|
9591
9652
|
},
|
|
9592
9653
|
fail() {
|
|
9593
9654
|
failed = true;
|
|
@@ -9595,7 +9656,7 @@ function jsonBanner(config, version) {
|
|
|
9595
9656
|
async ready(message) {
|
|
9596
9657
|
if (failed)
|
|
9597
9658
|
return;
|
|
9598
|
-
log.info(
|
|
9659
|
+
log.info(TAG37, message);
|
|
9599
9660
|
}
|
|
9600
9661
|
};
|
|
9601
9662
|
}
|
|
@@ -9676,7 +9737,7 @@ function cyan(s) {
|
|
|
9676
9737
|
function yellow(s) {
|
|
9677
9738
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
9678
9739
|
}
|
|
9679
|
-
var
|
|
9740
|
+
var TAG37 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
9680
9741
|
var init_startup_banner = __esm(() => {
|
|
9681
9742
|
init_log();
|
|
9682
9743
|
ANSI = {
|
|
@@ -9827,13 +9888,13 @@ class Watcher {
|
|
|
9827
9888
|
}
|
|
9828
9889
|
async start() {
|
|
9829
9890
|
if (!isPretty()) {
|
|
9830
|
-
log.info(
|
|
9891
|
+
log.info(TAG38, "Connecting to Supabase realtime (broadcast)...");
|
|
9831
9892
|
}
|
|
9832
9893
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
9833
9894
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
9834
9895
|
this.subscribeBroadcast();
|
|
9835
9896
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
9836
|
-
log.debug(
|
|
9897
|
+
log.debug(TAG38, "Presence sync");
|
|
9837
9898
|
}).subscribe(async (status) => {
|
|
9838
9899
|
if (status === "SUBSCRIBED") {
|
|
9839
9900
|
await presenceChannel.track({
|
|
@@ -9846,7 +9907,7 @@ class Watcher {
|
|
|
9846
9907
|
agentName: this.identity.agentName
|
|
9847
9908
|
});
|
|
9848
9909
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
9849
|
-
log.info(
|
|
9910
|
+
log.info(TAG38, "Presence tracked on board-presence channel");
|
|
9850
9911
|
}
|
|
9851
9912
|
this.presenceTracked = true;
|
|
9852
9913
|
this.maybeResolveReady();
|
|
@@ -9859,13 +9920,13 @@ class Watcher {
|
|
|
9859
9920
|
return;
|
|
9860
9921
|
const gen = ++this.broadcastGen;
|
|
9861
9922
|
this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
9862
|
-
log.debug(
|
|
9923
|
+
log.debug(TAG38, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
9863
9924
|
this.onCardBroadcast({
|
|
9864
9925
|
event: "card_update",
|
|
9865
9926
|
payload: msg.payload ?? {}
|
|
9866
9927
|
});
|
|
9867
9928
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
9868
|
-
log.debug(
|
|
9929
|
+
log.debug(TAG38, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
9869
9930
|
this.onCardBroadcast({
|
|
9870
9931
|
event: "card_created",
|
|
9871
9932
|
payload: msg.payload ?? {}
|
|
@@ -9875,7 +9936,7 @@ class Watcher {
|
|
|
9875
9936
|
const cardId = payload.card_id;
|
|
9876
9937
|
const command = payload.command;
|
|
9877
9938
|
if (cardId && command) {
|
|
9878
|
-
log.info(
|
|
9939
|
+
log.info(TAG38, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
9879
9940
|
this.onAgentCommand?.({ cardId, command });
|
|
9880
9941
|
}
|
|
9881
9942
|
}).subscribe((status) => {
|
|
@@ -9885,13 +9946,13 @@ class Watcher {
|
|
|
9885
9946
|
this.connected = true;
|
|
9886
9947
|
this.reconnectAttempts = 0;
|
|
9887
9948
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
9888
|
-
log.info(
|
|
9949
|
+
log.info(TAG38, "Broadcast subscription active");
|
|
9889
9950
|
}
|
|
9890
9951
|
this.maybeResolveReady();
|
|
9891
9952
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
9892
9953
|
this.connected = false;
|
|
9893
9954
|
if (!this.stopping) {
|
|
9894
|
-
log.warn(
|
|
9955
|
+
log.warn(TAG38, `Broadcast subscription ${status} — scheduling reconnect`);
|
|
9895
9956
|
this.scheduleReconnect();
|
|
9896
9957
|
}
|
|
9897
9958
|
}
|
|
@@ -9910,7 +9971,7 @@ class Watcher {
|
|
|
9910
9971
|
async reconnectBroadcast() {
|
|
9911
9972
|
if (this.stopping || !this.supabase)
|
|
9912
9973
|
return;
|
|
9913
|
-
log.warn(
|
|
9974
|
+
log.warn(TAG38, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
|
|
9914
9975
|
if (this.channel) {
|
|
9915
9976
|
const old = this.channel;
|
|
9916
9977
|
this.channel = null;
|
|
@@ -9940,10 +10001,10 @@ class Watcher {
|
|
|
9940
10001
|
this.supabase = null;
|
|
9941
10002
|
}
|
|
9942
10003
|
this.connected = false;
|
|
9943
|
-
log.info(
|
|
10004
|
+
log.info(TAG38, "Broadcast subscription stopped");
|
|
9944
10005
|
}
|
|
9945
10006
|
}
|
|
9946
|
-
var
|
|
10007
|
+
var TAG38 = "watcher";
|
|
9947
10008
|
var init_watcher = __esm(() => {
|
|
9948
10009
|
init_log();
|
|
9949
10010
|
});
|
|
@@ -10030,10 +10091,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
10030
10091
|
});
|
|
10031
10092
|
} catch {}
|
|
10032
10093
|
if (result.removed.length > 0) {
|
|
10033
|
-
log.info(
|
|
10094
|
+
log.info(TAG39, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
10034
10095
|
}
|
|
10035
10096
|
if (result.errors.length > 0) {
|
|
10036
|
-
log.warn(
|
|
10097
|
+
log.warn(TAG39, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
10037
10098
|
}
|
|
10038
10099
|
return result;
|
|
10039
10100
|
}
|
|
@@ -10063,7 +10124,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
10063
10124
|
} catch (err) {
|
|
10064
10125
|
const detail = gitErrorDetail2(err);
|
|
10065
10126
|
if (isTransientGitNetworkError(detail)) {
|
|
10066
|
-
log.debug(
|
|
10127
|
+
log.debug(TAG39, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
10067
10128
|
return result;
|
|
10068
10129
|
}
|
|
10069
10130
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -10102,7 +10163,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
10102
10163
|
continue;
|
|
10103
10164
|
}
|
|
10104
10165
|
if (clock() > sweepDeadline) {
|
|
10105
|
-
log.debug(
|
|
10166
|
+
log.debug(TAG39, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
10106
10167
|
break;
|
|
10107
10168
|
}
|
|
10108
10169
|
try {
|
|
@@ -10115,17 +10176,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
10115
10176
|
} catch (err) {
|
|
10116
10177
|
const detail = gitErrorDetail2(err);
|
|
10117
10178
|
if (isTransientGitNetworkError(detail)) {
|
|
10118
|
-
log.debug(
|
|
10179
|
+
log.debug(TAG39, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
10119
10180
|
break;
|
|
10120
10181
|
}
|
|
10121
10182
|
result.errors.push({ ref, error: detail });
|
|
10122
10183
|
}
|
|
10123
10184
|
}
|
|
10124
10185
|
if (result.removed.length > 0) {
|
|
10125
|
-
log.info(
|
|
10186
|
+
log.info(TAG39, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
10126
10187
|
}
|
|
10127
10188
|
if (result.errors.length > 0) {
|
|
10128
|
-
log.warn(
|
|
10189
|
+
log.warn(TAG39, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
10129
10190
|
}
|
|
10130
10191
|
return result;
|
|
10131
10192
|
}
|
|
@@ -10156,13 +10217,13 @@ class WorktreeGc {
|
|
|
10156
10217
|
try {
|
|
10157
10218
|
runWorktreeGc(this.basePath, this.store);
|
|
10158
10219
|
} catch (err) {
|
|
10159
|
-
log.warn(
|
|
10220
|
+
log.warn(TAG39, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
10160
10221
|
}
|
|
10161
10222
|
if (this.remoteOpts) {
|
|
10162
10223
|
try {
|
|
10163
10224
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
10164
10225
|
} catch (err) {
|
|
10165
|
-
log.warn(
|
|
10226
|
+
log.warn(TAG39, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
10166
10227
|
}
|
|
10167
10228
|
}
|
|
10168
10229
|
}
|
|
@@ -10176,7 +10237,7 @@ function getRepoRoot2() {
|
|
|
10176
10237
|
return null;
|
|
10177
10238
|
}
|
|
10178
10239
|
}
|
|
10179
|
-
var
|
|
10240
|
+
var TAG39 = "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;
|
|
10180
10241
|
var init_worktree_gc = __esm(() => {
|
|
10181
10242
|
init_log();
|
|
10182
10243
|
init_worktree();
|
|
@@ -10280,7 +10341,7 @@ async function main() {
|
|
|
10280
10341
|
} catch (err) {
|
|
10281
10342
|
if (err instanceof ConfigValidationError) {
|
|
10282
10343
|
banner.fail();
|
|
10283
|
-
log.error(
|
|
10344
|
+
log.error(TAG40, err.message);
|
|
10284
10345
|
process.exit(1);
|
|
10285
10346
|
}
|
|
10286
10347
|
throw err;
|
|
@@ -10290,7 +10351,7 @@ async function main() {
|
|
|
10290
10351
|
} catch (err) {
|
|
10291
10352
|
if (err instanceof ConfigValidationError) {
|
|
10292
10353
|
banner.fail();
|
|
10293
|
-
log.error(
|
|
10354
|
+
log.error(TAG40, err.message);
|
|
10294
10355
|
process.exit(1);
|
|
10295
10356
|
}
|
|
10296
10357
|
throw err;
|
|
@@ -10400,7 +10461,7 @@ async function main() {
|
|
|
10400
10461
|
if (shuttingDown)
|
|
10401
10462
|
return;
|
|
10402
10463
|
shuttingDown = true;
|
|
10403
|
-
log.info(
|
|
10464
|
+
log.info(TAG40, `Received ${signal}, shutting down gracefully...`);
|
|
10404
10465
|
reconciler.stop();
|
|
10405
10466
|
mergeMonitor?.stop();
|
|
10406
10467
|
worktreeGc.stop();
|
|
@@ -10410,18 +10471,18 @@ async function main() {
|
|
|
10410
10471
|
}
|
|
10411
10472
|
await watcher.stop();
|
|
10412
10473
|
await pool.shutdown();
|
|
10413
|
-
log.info(
|
|
10474
|
+
log.info(TAG40, "Daemon stopped.");
|
|
10414
10475
|
process.exit(exitCode);
|
|
10415
10476
|
};
|
|
10416
10477
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
10417
10478
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
10418
10479
|
process.on("uncaughtException", (err) => {
|
|
10419
|
-
log.error(
|
|
10480
|
+
log.error(TAG40, `Uncaught exception: ${err.message}`);
|
|
10420
10481
|
exitCode = 1;
|
|
10421
10482
|
shutdown("uncaughtException");
|
|
10422
10483
|
});
|
|
10423
10484
|
process.on("unhandledRejection", (reason) => {
|
|
10424
|
-
log.error(
|
|
10485
|
+
log.error(TAG40, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
10425
10486
|
exitCode = 1;
|
|
10426
10487
|
shutdown("unhandledRejection");
|
|
10427
10488
|
});
|
|
@@ -10474,29 +10535,29 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
10474
10535
|
if (assignedAgentId === undefined)
|
|
10475
10536
|
return;
|
|
10476
10537
|
if (assignedAgentId === agentId) {
|
|
10477
|
-
log.info(
|
|
10538
|
+
log.info(TAG40, `Broadcast: card ${cardId} assigned to agent`);
|
|
10478
10539
|
try {
|
|
10479
10540
|
await pool.resetAttemptsForReassign(cardId);
|
|
10480
10541
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
10481
10542
|
} catch (err) {
|
|
10482
|
-
log.error(
|
|
10543
|
+
log.error(TAG40, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
10483
10544
|
}
|
|
10484
10545
|
} else if (pool.isCardKnown(cardId)) {
|
|
10485
|
-
log.info(
|
|
10546
|
+
log.info(TAG40, `Broadcast: card ${cardId} unassigned from agent`);
|
|
10486
10547
|
await pool.removeCard(cardId);
|
|
10487
10548
|
}
|
|
10488
10549
|
}
|
|
10489
10550
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
10490
10551
|
const { card } = await client.getCard(cardId);
|
|
10491
10552
|
if (card.assigned_agent_id !== agentId) {
|
|
10492
|
-
log.debug(
|
|
10553
|
+
log.debug(TAG40, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
10493
10554
|
return;
|
|
10494
10555
|
}
|
|
10495
10556
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
10496
10557
|
const columns = board.columns;
|
|
10497
10558
|
const column = columns.find((c) => c.id === card.column_id);
|
|
10498
10559
|
if (!column) {
|
|
10499
|
-
log.warn(
|
|
10560
|
+
log.warn(TAG40, `Column not found for card ${cardId}`);
|
|
10500
10561
|
return;
|
|
10501
10562
|
}
|
|
10502
10563
|
const route = classifyPickup(card, column.name, {
|
|
@@ -10505,27 +10566,27 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
10505
10566
|
playbooks: config.agent.playbooks
|
|
10506
10567
|
});
|
|
10507
10568
|
if (!route) {
|
|
10508
|
-
log.info(
|
|
10569
|
+
log.info(TAG40, `Card #${card.short_id} is in "${column.name}", not a pickup/review/stage column — skipping`);
|
|
10509
10570
|
return;
|
|
10510
10571
|
}
|
|
10511
10572
|
if (route.stage) {
|
|
10512
|
-
log.info(
|
|
10573
|
+
log.info(TAG40, `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`);
|
|
10513
10574
|
}
|
|
10514
10575
|
const mode = route.mode;
|
|
10515
10576
|
const labelMap = buildLabelMap(board.labels ?? []);
|
|
10516
10577
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
10517
10578
|
const subtasks = card.subtasks ?? [];
|
|
10518
10579
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
10519
|
-
log.debug(
|
|
10580
|
+
log.debug(TAG40, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
10520
10581
|
return;
|
|
10521
10582
|
}
|
|
10522
10583
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
10523
|
-
log.info(
|
|
10584
|
+
log.info(TAG40, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
10524
10585
|
return;
|
|
10525
10586
|
}
|
|
10526
10587
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
10527
10588
|
}
|
|
10528
|
-
var
|
|
10589
|
+
var TAG40 = "daemon", PKG_VERSION;
|
|
10529
10590
|
var init_src = __esm(() => {
|
|
10530
10591
|
init_board_helpers();
|
|
10531
10592
|
init_config();
|