@gethmy/agent 1.8.0 → 1.8.1
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 +187 -90
- package/dist/index.js +187 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3754,6 +3754,7 @@ class ReviewWorker {
|
|
|
3754
3754
|
progressTracker = null;
|
|
3755
3755
|
lastSessionStats = null;
|
|
3756
3756
|
aborted = false;
|
|
3757
|
+
timedOut = false;
|
|
3757
3758
|
runId = null;
|
|
3758
3759
|
lastRunLogPath = null;
|
|
3759
3760
|
sessionId = null;
|
|
@@ -3809,6 +3810,7 @@ class ReviewWorker {
|
|
|
3809
3810
|
}
|
|
3810
3811
|
async run(card, column, labels, subtasks) {
|
|
3811
3812
|
this.aborted = false;
|
|
3813
|
+
this.timedOut = false;
|
|
3812
3814
|
this.cardId = card.id;
|
|
3813
3815
|
this.startedAt = Date.now();
|
|
3814
3816
|
this.runId = newRunId();
|
|
@@ -3946,6 +3948,7 @@ class ReviewWorker {
|
|
|
3946
3948
|
});
|
|
3947
3949
|
this.timeoutTimer = setTimeout(() => {
|
|
3948
3950
|
log.warn(this.tag, `Review timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
3951
|
+
this.timedOut = true;
|
|
3949
3952
|
this.cancel();
|
|
3950
3953
|
}, this.config.review.maxTimeout);
|
|
3951
3954
|
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks);
|
|
@@ -4012,7 +4015,7 @@ class ReviewWorker {
|
|
|
4012
4015
|
try {
|
|
4013
4016
|
const run = this.stateStore.getRun(this.runId);
|
|
4014
4017
|
if (run && run.endedAt === null) {
|
|
4015
|
-
const status = this.state === "error" || this.aborted ? "paused" : "completed";
|
|
4018
|
+
const status = this.timedOut ? "failed" : this.state === "error" || this.aborted ? "paused" : "completed";
|
|
4016
4019
|
await this.stateStore.endRun(this.runId, status);
|
|
4017
4020
|
}
|
|
4018
4021
|
} catch {}
|
|
@@ -4050,6 +4053,7 @@ class ReviewWorker {
|
|
|
4050
4053
|
signalGroup(this.process, "SIGCONT");
|
|
4051
4054
|
this.timeoutTimer = setTimeout(() => {
|
|
4052
4055
|
log.warn(this.tag, `Timeout reached (${this.config.review.maxTimeout}ms), cancelling`);
|
|
4056
|
+
this.timedOut = true;
|
|
4053
4057
|
this.cancel();
|
|
4054
4058
|
}, this.config.review.maxTimeout);
|
|
4055
4059
|
if (this.cardId) {
|
|
@@ -4085,7 +4089,11 @@ class ReviewWorker {
|
|
|
4085
4089
|
if (this.cardId) {
|
|
4086
4090
|
try {
|
|
4087
4091
|
await this.client.endAgentSession(this.cardId, {
|
|
4088
|
-
status: "paused",
|
|
4092
|
+
status: this.timedOut ? "failed" : "paused",
|
|
4093
|
+
...this.timedOut ? {
|
|
4094
|
+
failureReason: "timeout",
|
|
4095
|
+
failureSummary: `Review exceeded the ${Math.round(this.config.review.maxTimeout / 60000)} min timeout`
|
|
4096
|
+
} : {},
|
|
4089
4097
|
...buildTokenPayload(snapshotStats)
|
|
4090
4098
|
});
|
|
4091
4099
|
} catch {}
|
|
@@ -4263,13 +4271,74 @@ var init_review_worker = __esm(() => {
|
|
|
4263
4271
|
init_worktree();
|
|
4264
4272
|
});
|
|
4265
4273
|
|
|
4274
|
+
// src/sleep-guard.ts
|
|
4275
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
4276
|
+
|
|
4277
|
+
class SleepGuard {
|
|
4278
|
+
platform;
|
|
4279
|
+
child = null;
|
|
4280
|
+
holds = 0;
|
|
4281
|
+
constructor(platform = process.platform) {
|
|
4282
|
+
this.platform = platform;
|
|
4283
|
+
}
|
|
4284
|
+
get active() {
|
|
4285
|
+
return this.child !== null;
|
|
4286
|
+
}
|
|
4287
|
+
acquire() {
|
|
4288
|
+
this.holds++;
|
|
4289
|
+
if (this.holds === 1)
|
|
4290
|
+
this.start();
|
|
4291
|
+
}
|
|
4292
|
+
release() {
|
|
4293
|
+
this.holds = Math.max(0, this.holds - 1);
|
|
4294
|
+
if (this.holds === 0)
|
|
4295
|
+
this.stop();
|
|
4296
|
+
}
|
|
4297
|
+
stop() {
|
|
4298
|
+
this.holds = 0;
|
|
4299
|
+
if (this.child) {
|
|
4300
|
+
if (!this.child.killed)
|
|
4301
|
+
this.child.kill("SIGTERM");
|
|
4302
|
+
this.child = null;
|
|
4303
|
+
log.info(TAG20, "sleep assertion released");
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
start() {
|
|
4307
|
+
if (this.platform !== "darwin" || this.child)
|
|
4308
|
+
return;
|
|
4309
|
+
try {
|
|
4310
|
+
const child = spawn3("caffeinate", ["-i", "-w", String(process.pid)], {
|
|
4311
|
+
stdio: "ignore"
|
|
4312
|
+
});
|
|
4313
|
+
child.on("error", (err) => {
|
|
4314
|
+
log.warn(TAG20, `caffeinate unavailable: ${err.message}`);
|
|
4315
|
+
if (this.child === child)
|
|
4316
|
+
this.child = null;
|
|
4317
|
+
});
|
|
4318
|
+
child.on("exit", () => {
|
|
4319
|
+
if (this.child === child)
|
|
4320
|
+
this.child = null;
|
|
4321
|
+
});
|
|
4322
|
+
child.unref();
|
|
4323
|
+
this.child = child;
|
|
4324
|
+
log.info(TAG20, "sleep assertion acquired (caffeinate -i)");
|
|
4325
|
+
} catch (err) {
|
|
4326
|
+
log.warn(TAG20, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
var TAG20 = "sleep-guard";
|
|
4331
|
+
var init_sleep_guard = __esm(() => {
|
|
4332
|
+
init_log();
|
|
4333
|
+
});
|
|
4334
|
+
|
|
4266
4335
|
// src/unblock.ts
|
|
4267
4336
|
async function fetchBlocksLinks(client, cardId) {
|
|
4268
4337
|
try {
|
|
4269
4338
|
const { links } = await client.getCardLinks(cardId);
|
|
4270
4339
|
return links.filter((l) => l.link_type === "blocks");
|
|
4271
4340
|
} catch (err) {
|
|
4272
|
-
log.warn(
|
|
4341
|
+
log.warn(TAG21, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
4273
4342
|
return null;
|
|
4274
4343
|
}
|
|
4275
4344
|
}
|
|
@@ -4301,22 +4370,22 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
4301
4370
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
4302
4371
|
if (successors.length === 0)
|
|
4303
4372
|
return;
|
|
4304
|
-
log.info(
|
|
4373
|
+
log.info(TAG21, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
4305
4374
|
for (const link of successors) {
|
|
4306
4375
|
const successorId = link.target_card.id;
|
|
4307
4376
|
try {
|
|
4308
4377
|
const { card } = await deps.client.getCard(successorId);
|
|
4309
4378
|
if (card.assignee_id !== deps.agentUserId) {
|
|
4310
|
-
log.debug(
|
|
4379
|
+
log.debug(TAG21, `successor #${card.short_id} not assigned to agent — skipping promotion`);
|
|
4311
4380
|
continue;
|
|
4312
4381
|
}
|
|
4313
4382
|
await deps.enqueue(successorId);
|
|
4314
4383
|
} catch (err) {
|
|
4315
|
-
log.warn(
|
|
4384
|
+
log.warn(TAG21, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
4316
4385
|
}
|
|
4317
4386
|
}
|
|
4318
4387
|
}
|
|
4319
|
-
var
|
|
4388
|
+
var TAG21 = "unblock";
|
|
4320
4389
|
var init_unblock = __esm(() => {
|
|
4321
4390
|
init_log();
|
|
4322
4391
|
});
|
|
@@ -4460,11 +4529,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
4460
4529
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
4461
4530
|
When finished, call harmony_end_agent_session with status="completed".`
|
|
4462
4531
|
});
|
|
4463
|
-
log.info(
|
|
4532
|
+
log.info(TAG22, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
4464
4533
|
return result.prompt + pastEpisodesSection;
|
|
4465
4534
|
} catch (err) {
|
|
4466
4535
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4467
|
-
log.warn(
|
|
4536
|
+
log.warn(TAG22, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
4468
4537
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
4469
4538
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
|
|
4470
4539
|
}
|
|
@@ -4482,7 +4551,7 @@ async function renderCommentsSection(client, cardId) {
|
|
|
4482
4551
|
|
|
4483
4552
|
${section}` : "";
|
|
4484
4553
|
} catch (err) {
|
|
4485
|
-
log.warn(
|
|
4554
|
+
log.warn(TAG22, "comment-thread fetch failed", {
|
|
4486
4555
|
event: "comment_fetch_failed",
|
|
4487
4556
|
error: err instanceof Error ? err.message : String(err)
|
|
4488
4557
|
});
|
|
@@ -4532,7 +4601,7 @@ ${description}`.trim();
|
|
|
4532
4601
|
## Similar past tasks
|
|
4533
4602
|
${bullets}`;
|
|
4534
4603
|
} catch (err) {
|
|
4535
|
-
log.warn(
|
|
4604
|
+
log.warn(TAG22, "past-episodes recall failed", {
|
|
4536
4605
|
event: "episode_recall_failed",
|
|
4537
4606
|
error: err instanceof Error ? err.message : String(err)
|
|
4538
4607
|
});
|
|
@@ -4573,7 +4642,7 @@ ${subtaskStr}
|
|
|
4573
4642
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
4574
4643
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
4575
4644
|
}
|
|
4576
|
-
var
|
|
4645
|
+
var TAG22 = "prompt";
|
|
4577
4646
|
var init_prompt = __esm(() => {
|
|
4578
4647
|
init_dist();
|
|
4579
4648
|
init_log();
|
|
@@ -4600,6 +4669,7 @@ class Worker {
|
|
|
4600
4669
|
progressTracker = null;
|
|
4601
4670
|
lastSessionStats;
|
|
4602
4671
|
aborted = false;
|
|
4672
|
+
timedOut = false;
|
|
4603
4673
|
verificationFailed = false;
|
|
4604
4674
|
sessionId = null;
|
|
4605
4675
|
runId = null;
|
|
@@ -4645,7 +4715,7 @@ class Worker {
|
|
|
4645
4715
|
}
|
|
4646
4716
|
}
|
|
4647
4717
|
get tag() {
|
|
4648
|
-
return `${
|
|
4718
|
+
return `${TAG23}:${this.id}`;
|
|
4649
4719
|
}
|
|
4650
4720
|
get isIdle() {
|
|
4651
4721
|
return this.state === "idle";
|
|
@@ -4655,6 +4725,7 @@ class Worker {
|
|
|
4655
4725
|
}
|
|
4656
4726
|
async run(card, column, labels, subtasks) {
|
|
4657
4727
|
this.aborted = false;
|
|
4728
|
+
this.timedOut = false;
|
|
4658
4729
|
this.verificationFailed = false;
|
|
4659
4730
|
this.cardId = card.id;
|
|
4660
4731
|
this.startedAt = Date.now();
|
|
@@ -4691,7 +4762,7 @@ class Worker {
|
|
|
4691
4762
|
});
|
|
4692
4763
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
4693
4764
|
if (!sid) {
|
|
4694
|
-
log.warn(
|
|
4765
|
+
log.warn(TAG23, "startAgentSession returned no session id");
|
|
4695
4766
|
}
|
|
4696
4767
|
this.sessionId = sid;
|
|
4697
4768
|
await this.recordPhase("preparing");
|
|
@@ -4723,6 +4794,7 @@ class Worker {
|
|
|
4723
4794
|
});
|
|
4724
4795
|
this.timeoutTimer = setTimeout(() => {
|
|
4725
4796
|
log.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
4797
|
+
this.timedOut = true;
|
|
4726
4798
|
this.cancel();
|
|
4727
4799
|
}, this.config.maxTimeout);
|
|
4728
4800
|
await this.spawnClaude(prompt, card, subtasks);
|
|
@@ -4769,6 +4841,24 @@ class Worker {
|
|
|
4769
4841
|
await this.stateStore.endRun(this.runId, "completed");
|
|
4770
4842
|
} catch {}
|
|
4771
4843
|
await this.recordOutcome(card.id, "success");
|
|
4844
|
+
} else if (this.runId && this.timedOut) {
|
|
4845
|
+
try {
|
|
4846
|
+
await runTransition(this.client, card, {
|
|
4847
|
+
move: { columnName: this.config.pickupColumns[0] ?? "To Do" },
|
|
4848
|
+
endSession: {
|
|
4849
|
+
status: "failed",
|
|
4850
|
+
failureReason: "timeout",
|
|
4851
|
+
failureSummary: `Run exceeded the ${Math.round(this.config.maxTimeout / 60000)} min timeout`,
|
|
4852
|
+
...buildTokenPayload(this.lastSessionStats)
|
|
4853
|
+
}
|
|
4854
|
+
});
|
|
4855
|
+
} catch (tErr) {
|
|
4856
|
+
log.error(this.tag, `timeout transition failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
4857
|
+
}
|
|
4858
|
+
try {
|
|
4859
|
+
await this.stateStore.endRun(this.runId, "failed", "timeout");
|
|
4860
|
+
} catch {}
|
|
4861
|
+
await this.recordOutcome(card.id, "failure");
|
|
4772
4862
|
} else if (this.runId && this.aborted) {
|
|
4773
4863
|
try {
|
|
4774
4864
|
await this.stateStore.endRun(this.runId, "paused", "cancelled");
|
|
@@ -4839,6 +4929,7 @@ class Worker {
|
|
|
4839
4929
|
signalGroup(this.process, "SIGCONT");
|
|
4840
4930
|
this.timeoutTimer = setTimeout(() => {
|
|
4841
4931
|
log.warn(this.tag, `Timeout reached (${this.config.maxTimeout}ms), cancelling`);
|
|
4932
|
+
this.timedOut = true;
|
|
4842
4933
|
this.cancel();
|
|
4843
4934
|
}, this.config.maxTimeout);
|
|
4844
4935
|
if (this.cardId) {
|
|
@@ -4865,7 +4956,7 @@ class Worker {
|
|
|
4865
4956
|
sigtermTimeoutMs: CANCEL_SIGTERM_TIMEOUT2
|
|
4866
4957
|
});
|
|
4867
4958
|
}
|
|
4868
|
-
if (this.cardId) {
|
|
4959
|
+
if (this.cardId && !this.timedOut) {
|
|
4869
4960
|
try {
|
|
4870
4961
|
const stats = this.lastSessionStats ?? this.progressTracker?.stats;
|
|
4871
4962
|
await this.client.endAgentSession(this.cardId, {
|
|
@@ -4969,7 +5060,7 @@ class Worker {
|
|
|
4969
5060
|
clearTimeout(this.timeoutTimer);
|
|
4970
5061
|
this.timeoutTimer = null;
|
|
4971
5062
|
}
|
|
4972
|
-
if (this.worktreePath && this.state === "error") {
|
|
5063
|
+
if (this.worktreePath && (this.state === "error" || this.timedOut)) {
|
|
4973
5064
|
try {
|
|
4974
5065
|
cleanupWorktree(this.worktreePath, this.branchName ?? undefined);
|
|
4975
5066
|
} catch {
|
|
@@ -4985,7 +5076,7 @@ class Worker {
|
|
|
4985
5076
|
this.sessionId = null;
|
|
4986
5077
|
}
|
|
4987
5078
|
}
|
|
4988
|
-
var
|
|
5079
|
+
var TAG23 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4;
|
|
4989
5080
|
var init_worker = __esm(() => {
|
|
4990
5081
|
init_board_helpers();
|
|
4991
5082
|
init_completion();
|
|
@@ -5011,6 +5102,7 @@ class Pool {
|
|
|
5011
5102
|
implQueue;
|
|
5012
5103
|
reviewQueue;
|
|
5013
5104
|
budget;
|
|
5105
|
+
sleepGuard = new SleepGuard;
|
|
5014
5106
|
onCardCompleted = null;
|
|
5015
5107
|
constructor(config, client, userEmail, workspaceId, projectId, stateStore) {
|
|
5016
5108
|
this.client = client;
|
|
@@ -5022,6 +5114,7 @@ class Pool {
|
|
|
5022
5114
|
for (let i = 0;i < config.poolSize; i++) {
|
|
5023
5115
|
this.implWorkers.push(new Worker(i, config, client, userEmail, () => {
|
|
5024
5116
|
this.tryDispatchFor(this.implWorkers, this.implQueue, "impl");
|
|
5117
|
+
this.sleepGuard.release();
|
|
5025
5118
|
}, workspaceId, projectId, stateStore, async (completedCard) => {
|
|
5026
5119
|
await this.onCardCompleted?.(completedCard);
|
|
5027
5120
|
}));
|
|
@@ -5031,34 +5124,35 @@ class Pool {
|
|
|
5031
5124
|
const reviewWorkerId = config.poolSize + i;
|
|
5032
5125
|
this.reviewWorkers.push(new ReviewWorker(reviewWorkerId, config, client, userEmail, () => {
|
|
5033
5126
|
this.tryDispatchFor(this.reviewWorkers, this.reviewQueue, "review");
|
|
5127
|
+
this.sleepGuard.release();
|
|
5034
5128
|
}, stateStore, workspaceId, projectId));
|
|
5035
5129
|
}
|
|
5036
5130
|
}
|
|
5037
5131
|
}
|
|
5038
5132
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
5039
5133
|
if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
|
|
5040
|
-
log.debug(
|
|
5134
|
+
log.debug(TAG24, `Card ${card.id} already queued or active, skipping`);
|
|
5041
5135
|
return;
|
|
5042
5136
|
}
|
|
5043
5137
|
if (mode === "implement") {
|
|
5044
5138
|
const decision = this.budget.check(card.id);
|
|
5045
5139
|
if (!decision.allow) {
|
|
5046
5140
|
if (decision.reason === "daily_budget") {
|
|
5047
|
-
log.warn(
|
|
5141
|
+
log.warn(TAG24, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
5048
5142
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
5049
5143
|
} else {
|
|
5050
|
-
log.debug(
|
|
5144
|
+
log.debug(TAG24, `#${card.short_id} gave up: ${decision.detail}`);
|
|
5051
5145
|
}
|
|
5052
5146
|
return;
|
|
5053
5147
|
}
|
|
5054
5148
|
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
5055
5149
|
if (blockers === null) {
|
|
5056
|
-
log.warn(
|
|
5150
|
+
log.warn(TAG24, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
5057
5151
|
return;
|
|
5058
5152
|
}
|
|
5059
5153
|
if (blockers.length > 0) {
|
|
5060
5154
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
5061
|
-
log.info(
|
|
5155
|
+
log.info(TAG24, `#${card.short_id} blocked by ${list} — waiting`);
|
|
5062
5156
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
5063
5157
|
return;
|
|
5064
5158
|
}
|
|
@@ -5087,7 +5181,7 @@ class Pool {
|
|
|
5087
5181
|
});
|
|
5088
5182
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
5089
5183
|
} catch (err) {
|
|
5090
|
-
log.debug(
|
|
5184
|
+
log.debug(TAG24, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
5091
5185
|
}
|
|
5092
5186
|
}
|
|
5093
5187
|
async removeCard(cardId) {
|
|
@@ -5097,13 +5191,13 @@ class Pool {
|
|
|
5097
5191
|
const removed = queue.remove(cardId);
|
|
5098
5192
|
if (removed) {
|
|
5099
5193
|
this.cardDataCache.delete(cardId);
|
|
5100
|
-
log.info(
|
|
5194
|
+
log.info(TAG24, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
5101
5195
|
return;
|
|
5102
5196
|
}
|
|
5103
5197
|
}
|
|
5104
5198
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
5105
5199
|
if (worker) {
|
|
5106
|
-
log.info(
|
|
5200
|
+
log.info(TAG24, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
5107
5201
|
await worker.cancel();
|
|
5108
5202
|
}
|
|
5109
5203
|
}
|
|
@@ -5131,10 +5225,10 @@ class Pool {
|
|
|
5131
5225
|
async handleAgentCommand(cardId, command) {
|
|
5132
5226
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
5133
5227
|
if (!worker) {
|
|
5134
|
-
log.debug(
|
|
5228
|
+
log.debug(TAG24, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
5135
5229
|
return;
|
|
5136
5230
|
}
|
|
5137
|
-
log.info(
|
|
5231
|
+
log.info(TAG24, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
5138
5232
|
switch (command) {
|
|
5139
5233
|
case "pause":
|
|
5140
5234
|
await worker.pause();
|
|
@@ -5180,19 +5274,20 @@ class Pool {
|
|
|
5180
5274
|
};
|
|
5181
5275
|
}
|
|
5182
5276
|
async shutdown() {
|
|
5183
|
-
log.info(
|
|
5277
|
+
log.info(TAG24, "Shutting down pool...");
|
|
5184
5278
|
const active = [
|
|
5185
5279
|
...this.implWorkers.filter((w) => w.isActive),
|
|
5186
5280
|
...this.reviewWorkers.filter((w) => w.isActive)
|
|
5187
5281
|
];
|
|
5188
5282
|
await Promise.all(active.map((w) => w.cancel()));
|
|
5189
|
-
|
|
5283
|
+
this.sleepGuard.stop();
|
|
5284
|
+
log.info(TAG24, "Pool shutdown complete");
|
|
5190
5285
|
}
|
|
5191
5286
|
cardDataCache = new Map;
|
|
5192
5287
|
tryDispatchFor(workers, queue, label) {
|
|
5193
5288
|
const idle = workers.find((w) => w.isIdle);
|
|
5194
5289
|
if (!idle) {
|
|
5195
|
-
log.debug(
|
|
5290
|
+
log.debug(TAG24, `No idle ${label} workers (queue: ${queue.length})`);
|
|
5196
5291
|
return false;
|
|
5197
5292
|
}
|
|
5198
5293
|
const next = queue.dequeue();
|
|
@@ -5200,21 +5295,23 @@ class Pool {
|
|
|
5200
5295
|
return false;
|
|
5201
5296
|
const data = this.cardDataCache.get(next.cardId);
|
|
5202
5297
|
if (!data) {
|
|
5203
|
-
log.warn(
|
|
5298
|
+
log.warn(TAG24, `No cached data for card ${next.cardId}, skipping`);
|
|
5204
5299
|
return false;
|
|
5205
5300
|
}
|
|
5206
5301
|
this.cardDataCache.delete(next.cardId);
|
|
5207
5302
|
this.lastWaitingEmit.delete(next.cardId);
|
|
5208
|
-
log.info(
|
|
5303
|
+
log.info(TAG24, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
5304
|
+
this.sleepGuard.acquire();
|
|
5209
5305
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
5210
5306
|
return true;
|
|
5211
5307
|
}
|
|
5212
5308
|
}
|
|
5213
|
-
var
|
|
5309
|
+
var TAG24 = "pool";
|
|
5214
5310
|
var init_pool = __esm(() => {
|
|
5215
5311
|
init_log();
|
|
5216
5312
|
init_queue();
|
|
5217
5313
|
init_review_worker();
|
|
5314
|
+
init_sleep_guard();
|
|
5218
5315
|
init_types();
|
|
5219
5316
|
init_unblock();
|
|
5220
5317
|
init_worker();
|
|
@@ -5236,7 +5333,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
5236
5333
|
const { card } = await client.getCard(cardId);
|
|
5237
5334
|
return card;
|
|
5238
5335
|
} catch (err) {
|
|
5239
|
-
log.warn(
|
|
5336
|
+
log.warn(TAG25, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
5240
5337
|
return null;
|
|
5241
5338
|
}
|
|
5242
5339
|
}
|
|
@@ -5246,7 +5343,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
5246
5343
|
return [];
|
|
5247
5344
|
}
|
|
5248
5345
|
const outcomes = [];
|
|
5249
|
-
log.info(
|
|
5346
|
+
log.info(TAG25, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
5250
5347
|
for (const run of active) {
|
|
5251
5348
|
const outcome = {
|
|
5252
5349
|
runId: run.runId,
|
|
@@ -5258,11 +5355,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
5258
5355
|
};
|
|
5259
5356
|
outcomes.push(outcome);
|
|
5260
5357
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
5261
|
-
log.warn(
|
|
5358
|
+
log.warn(TAG25, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
5262
5359
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
5263
5360
|
continue;
|
|
5264
5361
|
}
|
|
5265
|
-
log.info(
|
|
5362
|
+
log.info(TAG25, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
5266
5363
|
await recoverRun(run, store, client, config, outcome);
|
|
5267
5364
|
}
|
|
5268
5365
|
return outcomes;
|
|
@@ -5280,7 +5377,7 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
5280
5377
|
} catch (err) {
|
|
5281
5378
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5282
5379
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
5283
|
-
log.warn(
|
|
5380
|
+
log.warn(TAG25, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
5284
5381
|
}
|
|
5285
5382
|
const card = await fetchCardSafely(client, run.cardId);
|
|
5286
5383
|
if (card) {
|
|
@@ -5321,9 +5418,9 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
5321
5418
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5322
5419
|
outcome.errors.push(`endRun: ${msg}`);
|
|
5323
5420
|
}
|
|
5324
|
-
log.info(
|
|
5421
|
+
log.info(TAG25, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
5325
5422
|
}
|
|
5326
|
-
var
|
|
5423
|
+
var TAG25 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
5327
5424
|
var init_recovery = __esm(() => {
|
|
5328
5425
|
init_board_helpers();
|
|
5329
5426
|
init_log();
|
|
@@ -5371,7 +5468,7 @@ class Reconciler {
|
|
|
5371
5468
|
clearInterval(this.timer);
|
|
5372
5469
|
this.timer = null;
|
|
5373
5470
|
}
|
|
5374
|
-
log.info(
|
|
5471
|
+
log.info(TAG26, "Heartbeat stopped");
|
|
5375
5472
|
}
|
|
5376
5473
|
async recoverStaleRuns() {
|
|
5377
5474
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -5388,7 +5485,7 @@ class Reconciler {
|
|
|
5388
5485
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
5389
5486
|
continue;
|
|
5390
5487
|
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`;
|
|
5391
|
-
log.warn(
|
|
5488
|
+
log.warn(TAG26, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
5392
5489
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
5393
5490
|
runId: run.runId,
|
|
5394
5491
|
cardId: run.cardId,
|
|
@@ -5424,18 +5521,18 @@ class Reconciler {
|
|
|
5424
5521
|
const subtasks = card.subtasks ?? [];
|
|
5425
5522
|
const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
|
|
5426
5523
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
5427
|
-
log.debug(
|
|
5524
|
+
log.debug(TAG26, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
5428
5525
|
continue;
|
|
5429
5526
|
}
|
|
5430
5527
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
5431
|
-
log.debug(
|
|
5528
|
+
log.debug(TAG26, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
5432
5529
|
continue;
|
|
5433
5530
|
}
|
|
5434
5531
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
5435
|
-
log.debug(
|
|
5532
|
+
log.debug(TAG26, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
5436
5533
|
continue;
|
|
5437
5534
|
}
|
|
5438
|
-
log.info(
|
|
5535
|
+
log.info(TAG26, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
5439
5536
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
5440
5537
|
}
|
|
5441
5538
|
}
|
|
@@ -5444,17 +5541,17 @@ class Reconciler {
|
|
|
5444
5541
|
}
|
|
5445
5542
|
for (const knownId of knownCardIds) {
|
|
5446
5543
|
if (!allAgentCardIds.has(knownId)) {
|
|
5447
|
-
log.info(
|
|
5544
|
+
log.info(TAG26, `Missed unassign: ${knownId} — removing`);
|
|
5448
5545
|
await this.pool.removeCard(knownId);
|
|
5449
5546
|
}
|
|
5450
5547
|
}
|
|
5451
|
-
log.debug(
|
|
5548
|
+
log.debug(TAG26, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
5452
5549
|
} catch (err) {
|
|
5453
|
-
log.error(
|
|
5550
|
+
log.error(TAG26, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
5454
5551
|
}
|
|
5455
5552
|
}
|
|
5456
5553
|
}
|
|
5457
|
-
var
|
|
5554
|
+
var TAG26 = "reconcile";
|
|
5458
5555
|
var init_reconcile = __esm(() => {
|
|
5459
5556
|
init_board_helpers();
|
|
5460
5557
|
init_log();
|
|
@@ -5488,7 +5585,7 @@ function prettyBanner(config, version) {
|
|
|
5488
5585
|
checks.push({ kind: "ok", message });
|
|
5489
5586
|
},
|
|
5490
5587
|
warn(message) {
|
|
5491
|
-
log.warn(
|
|
5588
|
+
log.warn(TAG27, message);
|
|
5492
5589
|
checks.push({ kind: "warn", message: message.split(`
|
|
5493
5590
|
`, 1)[0] });
|
|
5494
5591
|
},
|
|
@@ -5512,22 +5609,22 @@ function prettyBanner(config, version) {
|
|
|
5512
5609
|
};
|
|
5513
5610
|
}
|
|
5514
5611
|
function jsonBanner(config, version) {
|
|
5515
|
-
log.info(
|
|
5516
|
-
log.info(
|
|
5612
|
+
log.info(TAG27, `Harmony Agent Daemon v${version} starting...`);
|
|
5613
|
+
log.info(TAG27, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
5517
5614
|
if (config.agent.review.enabled) {
|
|
5518
|
-
log.info(
|
|
5615
|
+
log.info(TAG27, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
5519
5616
|
}
|
|
5520
5617
|
let failed = false;
|
|
5521
5618
|
return {
|
|
5522
5619
|
setProjectName(_name) {},
|
|
5523
5620
|
setGitProvider(provider) {
|
|
5524
|
-
log.info(
|
|
5621
|
+
log.info(TAG27, `Git provider: ${provider}`);
|
|
5525
5622
|
},
|
|
5526
5623
|
check(message) {
|
|
5527
|
-
log.info(
|
|
5624
|
+
log.info(TAG27, message);
|
|
5528
5625
|
},
|
|
5529
5626
|
warn(message) {
|
|
5530
|
-
log.warn(
|
|
5627
|
+
log.warn(TAG27, message);
|
|
5531
5628
|
},
|
|
5532
5629
|
fail() {
|
|
5533
5630
|
failed = true;
|
|
@@ -5535,7 +5632,7 @@ function jsonBanner(config, version) {
|
|
|
5535
5632
|
async ready(message) {
|
|
5536
5633
|
if (failed)
|
|
5537
5634
|
return;
|
|
5538
|
-
log.info(
|
|
5635
|
+
log.info(TAG27, message);
|
|
5539
5636
|
}
|
|
5540
5637
|
};
|
|
5541
5638
|
}
|
|
@@ -5602,7 +5699,7 @@ function cyan(s) {
|
|
|
5602
5699
|
function yellow(s) {
|
|
5603
5700
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
5604
5701
|
}
|
|
5605
|
-
var
|
|
5702
|
+
var TAG27 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
5606
5703
|
var init_startup_banner = __esm(() => {
|
|
5607
5704
|
init_log();
|
|
5608
5705
|
ANSI = {
|
|
@@ -5749,18 +5846,18 @@ class Watcher {
|
|
|
5749
5846
|
}
|
|
5750
5847
|
async start() {
|
|
5751
5848
|
if (!isPretty()) {
|
|
5752
|
-
log.info(
|
|
5849
|
+
log.info(TAG28, "Connecting to Supabase realtime (broadcast)...");
|
|
5753
5850
|
}
|
|
5754
5851
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
5755
5852
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
5756
5853
|
const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
5757
|
-
log.debug(
|
|
5854
|
+
log.debug(TAG28, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
5758
5855
|
this.onCardBroadcast({
|
|
5759
5856
|
event: "card_update",
|
|
5760
5857
|
payload: msg.payload ?? {}
|
|
5761
5858
|
});
|
|
5762
5859
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
5763
|
-
log.debug(
|
|
5860
|
+
log.debug(TAG28, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
5764
5861
|
this.onCardBroadcast({
|
|
5765
5862
|
event: "card_created",
|
|
5766
5863
|
payload: msg.payload ?? {}
|
|
@@ -5770,29 +5867,29 @@ class Watcher {
|
|
|
5770
5867
|
const cardId = payload.card_id;
|
|
5771
5868
|
const command = payload.command;
|
|
5772
5869
|
if (cardId && command) {
|
|
5773
|
-
log.info(
|
|
5870
|
+
log.info(TAG28, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
5774
5871
|
this.onAgentCommand?.({ cardId, command });
|
|
5775
5872
|
}
|
|
5776
5873
|
}).subscribe((status) => {
|
|
5777
5874
|
if (status === "SUBSCRIBED") {
|
|
5778
5875
|
this.connected = true;
|
|
5779
5876
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
5780
|
-
log.info(
|
|
5877
|
+
log.info(TAG28, "Broadcast subscription active");
|
|
5781
5878
|
}
|
|
5782
5879
|
this.maybeResolveReady();
|
|
5783
5880
|
} else if (status === "CHANNEL_ERROR") {
|
|
5784
5881
|
this.connected = false;
|
|
5785
|
-
log.error(
|
|
5882
|
+
log.error(TAG28, "Broadcast channel error — will rely on reconciliation");
|
|
5786
5883
|
} else if (status === "TIMED_OUT") {
|
|
5787
5884
|
this.connected = false;
|
|
5788
|
-
log.warn(
|
|
5885
|
+
log.warn(TAG28, "Broadcast subscription timed out — retrying...");
|
|
5789
5886
|
} else if (status === "CLOSED") {
|
|
5790
5887
|
this.connected = false;
|
|
5791
5888
|
}
|
|
5792
5889
|
});
|
|
5793
5890
|
this.channel = channel;
|
|
5794
5891
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
5795
|
-
log.debug(
|
|
5892
|
+
log.debug(TAG28, "Presence sync");
|
|
5796
5893
|
}).subscribe(async (status) => {
|
|
5797
5894
|
if (status === "SUBSCRIBED") {
|
|
5798
5895
|
await presenceChannel.track({
|
|
@@ -5804,7 +5901,7 @@ class Watcher {
|
|
|
5804
5901
|
agentName: this.identity.agentName
|
|
5805
5902
|
});
|
|
5806
5903
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
5807
|
-
log.info(
|
|
5904
|
+
log.info(TAG28, "Presence tracked on board-presence channel");
|
|
5808
5905
|
}
|
|
5809
5906
|
this.presenceTracked = true;
|
|
5810
5907
|
this.maybeResolveReady();
|
|
@@ -5826,10 +5923,10 @@ class Watcher {
|
|
|
5826
5923
|
this.supabase = null;
|
|
5827
5924
|
}
|
|
5828
5925
|
this.connected = false;
|
|
5829
|
-
log.info(
|
|
5926
|
+
log.info(TAG28, "Broadcast subscription stopped");
|
|
5830
5927
|
}
|
|
5831
5928
|
}
|
|
5832
|
-
var
|
|
5929
|
+
var TAG28 = "watcher";
|
|
5833
5930
|
var init_watcher = __esm(() => {
|
|
5834
5931
|
init_log();
|
|
5835
5932
|
});
|
|
@@ -5904,10 +6001,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
5904
6001
|
});
|
|
5905
6002
|
} catch {}
|
|
5906
6003
|
if (result.removed.length > 0) {
|
|
5907
|
-
log.info(
|
|
6004
|
+
log.info(TAG29, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
5908
6005
|
}
|
|
5909
6006
|
if (result.errors.length > 0) {
|
|
5910
|
-
log.warn(
|
|
6007
|
+
log.warn(TAG29, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
5911
6008
|
}
|
|
5912
6009
|
return result;
|
|
5913
6010
|
}
|
|
@@ -5984,10 +6081,10 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
5984
6081
|
}
|
|
5985
6082
|
}
|
|
5986
6083
|
if (result.removed.length > 0) {
|
|
5987
|
-
log.info(
|
|
6084
|
+
log.info(TAG29, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
5988
6085
|
}
|
|
5989
6086
|
if (result.errors.length > 0) {
|
|
5990
|
-
log.warn(
|
|
6087
|
+
log.warn(TAG29, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
5991
6088
|
}
|
|
5992
6089
|
return result;
|
|
5993
6090
|
}
|
|
@@ -6018,13 +6115,13 @@ class WorktreeGc {
|
|
|
6018
6115
|
try {
|
|
6019
6116
|
runWorktreeGc(this.basePath, this.store);
|
|
6020
6117
|
} catch (err) {
|
|
6021
|
-
log.warn(
|
|
6118
|
+
log.warn(TAG29, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
6022
6119
|
}
|
|
6023
6120
|
if (this.remoteOpts) {
|
|
6024
6121
|
try {
|
|
6025
6122
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
6026
6123
|
} catch (err) {
|
|
6027
|
-
log.warn(
|
|
6124
|
+
log.warn(TAG29, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
6028
6125
|
}
|
|
6029
6126
|
}
|
|
6030
6127
|
}
|
|
@@ -6038,7 +6135,7 @@ function getRepoRoot2() {
|
|
|
6038
6135
|
return null;
|
|
6039
6136
|
}
|
|
6040
6137
|
}
|
|
6041
|
-
var
|
|
6138
|
+
var TAG29 = "worktree-gc";
|
|
6042
6139
|
var init_worktree_gc = __esm(() => {
|
|
6043
6140
|
init_log();
|
|
6044
6141
|
init_worktree();
|
|
@@ -6120,7 +6217,7 @@ async function main() {
|
|
|
6120
6217
|
} catch (err) {
|
|
6121
6218
|
if (err instanceof ConfigValidationError) {
|
|
6122
6219
|
banner.fail();
|
|
6123
|
-
log.error(
|
|
6220
|
+
log.error(TAG30, err.message);
|
|
6124
6221
|
process.exit(1);
|
|
6125
6222
|
}
|
|
6126
6223
|
throw err;
|
|
@@ -6221,25 +6318,25 @@ async function main() {
|
|
|
6221
6318
|
if (shuttingDown)
|
|
6222
6319
|
return;
|
|
6223
6320
|
shuttingDown = true;
|
|
6224
|
-
log.info(
|
|
6321
|
+
log.info(TAG30, `Received ${signal}, shutting down gracefully...`);
|
|
6225
6322
|
reconciler.stop();
|
|
6226
6323
|
mergeMonitor?.stop();
|
|
6227
6324
|
worktreeGc.stop();
|
|
6228
6325
|
await httpServer?.stop();
|
|
6229
6326
|
await watcher.stop();
|
|
6230
6327
|
await pool.shutdown();
|
|
6231
|
-
log.info(
|
|
6328
|
+
log.info(TAG30, "Daemon stopped.");
|
|
6232
6329
|
process.exit(exitCode);
|
|
6233
6330
|
};
|
|
6234
6331
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
6235
6332
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
6236
6333
|
process.on("uncaughtException", (err) => {
|
|
6237
|
-
log.error(
|
|
6334
|
+
log.error(TAG30, `Uncaught exception: ${err.message}`);
|
|
6238
6335
|
exitCode = 1;
|
|
6239
6336
|
shutdown("uncaughtException");
|
|
6240
6337
|
});
|
|
6241
6338
|
process.on("unhandledRejection", (reason) => {
|
|
6242
|
-
log.error(
|
|
6339
|
+
log.error(TAG30, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
6243
6340
|
exitCode = 1;
|
|
6244
6341
|
shutdown("unhandledRejection");
|
|
6245
6342
|
});
|
|
@@ -6286,14 +6383,14 @@ async function handleBroadcast(event, client, pool, config, agentUserId) {
|
|
|
6286
6383
|
if (assigneeId === undefined)
|
|
6287
6384
|
return;
|
|
6288
6385
|
if (assigneeId === agentUserId) {
|
|
6289
|
-
log.info(
|
|
6386
|
+
log.info(TAG30, `Broadcast: card ${cardId} assigned to agent`);
|
|
6290
6387
|
try {
|
|
6291
6388
|
await tryEnqueueCard(cardId, client, pool, config);
|
|
6292
6389
|
} catch (err) {
|
|
6293
|
-
log.error(
|
|
6390
|
+
log.error(TAG30, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
6294
6391
|
}
|
|
6295
6392
|
} else if (pool.isCardKnown(cardId)) {
|
|
6296
|
-
log.info(
|
|
6393
|
+
log.info(TAG30, `Broadcast: card ${cardId} unassigned from agent`);
|
|
6297
6394
|
await pool.removeCard(cardId);
|
|
6298
6395
|
}
|
|
6299
6396
|
}
|
|
@@ -6303,13 +6400,13 @@ async function tryEnqueueCard(cardId, client, pool, config) {
|
|
|
6303
6400
|
const columns = board.columns;
|
|
6304
6401
|
const column = columns.find((c) => c.id === card.column_id);
|
|
6305
6402
|
if (!column) {
|
|
6306
|
-
log.warn(
|
|
6403
|
+
log.warn(TAG30, `Column not found for card ${cardId}`);
|
|
6307
6404
|
return;
|
|
6308
6405
|
}
|
|
6309
6406
|
const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
6310
6407
|
const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
6311
6408
|
if (!isPickupColumn && !isReviewColumn) {
|
|
6312
|
-
log.info(
|
|
6409
|
+
log.info(TAG30, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
|
|
6313
6410
|
return;
|
|
6314
6411
|
}
|
|
6315
6412
|
const mode = isReviewColumn ? "review" : "implement";
|
|
@@ -6317,16 +6414,16 @@ async function tryEnqueueCard(cardId, client, pool, config) {
|
|
|
6317
6414
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
6318
6415
|
const subtasks = card.subtasks ?? [];
|
|
6319
6416
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
6320
|
-
log.debug(
|
|
6417
|
+
log.debug(TAG30, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
6321
6418
|
return;
|
|
6322
6419
|
}
|
|
6323
6420
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
6324
|
-
log.info(
|
|
6421
|
+
log.info(TAG30, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
6325
6422
|
return;
|
|
6326
6423
|
}
|
|
6327
6424
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
6328
6425
|
}
|
|
6329
|
-
var
|
|
6426
|
+
var TAG30 = "daemon", PKG_VERSION;
|
|
6330
6427
|
var init_src = __esm(() => {
|
|
6331
6428
|
init_board_helpers();
|
|
6332
6429
|
init_config();
|