@gethmy/agent 1.7.2 → 1.8.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 +207 -90
- package/dist/index.js +207 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1162,6 +1162,7 @@ class MergeMonitor {
|
|
|
1162
1162
|
running = false;
|
|
1163
1163
|
provider;
|
|
1164
1164
|
cwd;
|
|
1165
|
+
onCardCompleted = null;
|
|
1165
1166
|
constructor(client, projectId, config, intervalMs = 60000) {
|
|
1166
1167
|
this.client = client;
|
|
1167
1168
|
this.projectId = projectId;
|
|
@@ -1282,6 +1283,13 @@ class MergeMonitor {
|
|
|
1282
1283
|
log.info(TAG7, `Deleted local branch ${branchName}`);
|
|
1283
1284
|
} catch {}
|
|
1284
1285
|
}
|
|
1286
|
+
if (this.onCardCompleted) {
|
|
1287
|
+
try {
|
|
1288
|
+
await this.onCardCompleted(card);
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
log.warn(TAG7, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1285
1293
|
log.info(TAG7, `#${card.short_id} completed (merged)`);
|
|
1286
1294
|
}
|
|
1287
1295
|
}
|
|
@@ -2028,7 +2036,7 @@ function buildTokenPayload(stats) {
|
|
|
2028
2036
|
modelName: stats.cost.modelName
|
|
2029
2037
|
};
|
|
2030
2038
|
}
|
|
2031
|
-
async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionStats, workspaceId, agentSessionId, stateStore) {
|
|
2039
|
+
async function runCompletion(client, card, branchName, worktreePath, config, workerId, sessionStats, workspaceId, agentSessionId, stateStore, onMovedToCompletion) {
|
|
2032
2040
|
let verificationResult = {
|
|
2033
2041
|
passed: true,
|
|
2034
2042
|
buildErrors: [],
|
|
@@ -2137,6 +2145,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2137
2145
|
}
|
|
2138
2146
|
if (config.completion.moveToColumn) {
|
|
2139
2147
|
await moveCardToColumn(client, card, config.completion.moveToColumn);
|
|
2148
|
+
if (onMovedToCompletion) {
|
|
2149
|
+
try {
|
|
2150
|
+
await onMovedToCompletion(card);
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
log.warn(TAG12, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2140
2155
|
}
|
|
2141
2156
|
if (config.completion.postSummary) {
|
|
2142
2157
|
await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch, sessionStats);
|
|
@@ -4248,9 +4263,66 @@ var init_review_worker = __esm(() => {
|
|
|
4248
4263
|
init_worktree();
|
|
4249
4264
|
});
|
|
4250
4265
|
|
|
4266
|
+
// src/unblock.ts
|
|
4267
|
+
async function fetchBlocksLinks(client, cardId) {
|
|
4268
|
+
try {
|
|
4269
|
+
const { links } = await client.getCardLinks(cardId);
|
|
4270
|
+
return links.filter((l) => l.link_type === "blocks");
|
|
4271
|
+
} catch (err) {
|
|
4272
|
+
log.warn(TAG20, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
4273
|
+
return null;
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
function isBlockerResolved(blocker, columns) {
|
|
4277
|
+
if (blocker.done)
|
|
4278
|
+
return true;
|
|
4279
|
+
const blockerColumn = columns.find((c) => c.id === blocker.column_id);
|
|
4280
|
+
return blockerColumn?.mark_cards_done === true;
|
|
4281
|
+
}
|
|
4282
|
+
async function getUnresolvedBlockers(client, card, projectId) {
|
|
4283
|
+
const links = await fetchBlocksLinks(client, card.id);
|
|
4284
|
+
if (!links)
|
|
4285
|
+
return null;
|
|
4286
|
+
const incoming = links.filter((l) => l.direction === "incoming");
|
|
4287
|
+
if (incoming.length === 0)
|
|
4288
|
+
return [];
|
|
4289
|
+
const board = await client.getBoard(projectId, { summary: true });
|
|
4290
|
+
const columns = board.columns ?? [];
|
|
4291
|
+
return incoming.filter((l) => !isBlockerResolved(l.target_card, columns)).map((l) => ({
|
|
4292
|
+
cardId: l.target_card.id,
|
|
4293
|
+
shortId: l.target_card.short_id,
|
|
4294
|
+
title: l.target_card.title
|
|
4295
|
+
}));
|
|
4296
|
+
}
|
|
4297
|
+
async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
4298
|
+
const links = await fetchBlocksLinks(deps.client, completedCard.id);
|
|
4299
|
+
if (!links)
|
|
4300
|
+
return;
|
|
4301
|
+
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
4302
|
+
if (successors.length === 0)
|
|
4303
|
+
return;
|
|
4304
|
+
log.info(TAG20, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
4305
|
+
for (const link of successors) {
|
|
4306
|
+
const successorId = link.target_card.id;
|
|
4307
|
+
try {
|
|
4308
|
+
const { card } = await deps.client.getCard(successorId);
|
|
4309
|
+
if (card.assignee_id !== deps.agentUserId) {
|
|
4310
|
+
log.debug(TAG20, `successor #${card.short_id} not assigned to agent — skipping promotion`);
|
|
4311
|
+
continue;
|
|
4312
|
+
}
|
|
4313
|
+
await deps.enqueue(successorId);
|
|
4314
|
+
} catch (err) {
|
|
4315
|
+
log.warn(TAG20, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
var TAG20 = "unblock";
|
|
4320
|
+
var init_unblock = __esm(() => {
|
|
4321
|
+
init_log();
|
|
4322
|
+
});
|
|
4323
|
+
|
|
4251
4324
|
// ../harmony-shared/dist/cardLinks.js
|
|
4252
4325
|
var init_cardLinks = () => {};
|
|
4253
|
-
|
|
4254
4326
|
// ../harmony-shared/dist/commentSerializer.js
|
|
4255
4327
|
function sanitizeHeaderField(value) {
|
|
4256
4328
|
return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
|
|
@@ -4388,11 +4460,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
4388
4460
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
4389
4461
|
When finished, call harmony_end_agent_session with status="completed".`
|
|
4390
4462
|
});
|
|
4391
|
-
log.info(
|
|
4463
|
+
log.info(TAG21, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
4392
4464
|
return result.prompt + pastEpisodesSection;
|
|
4393
4465
|
} catch (err) {
|
|
4394
4466
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4395
|
-
log.warn(
|
|
4467
|
+
log.warn(TAG21, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
4396
4468
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
4397
4469
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
|
|
4398
4470
|
}
|
|
@@ -4410,7 +4482,7 @@ async function renderCommentsSection(client, cardId) {
|
|
|
4410
4482
|
|
|
4411
4483
|
${section}` : "";
|
|
4412
4484
|
} catch (err) {
|
|
4413
|
-
log.warn(
|
|
4485
|
+
log.warn(TAG21, "comment-thread fetch failed", {
|
|
4414
4486
|
event: "comment_fetch_failed",
|
|
4415
4487
|
error: err instanceof Error ? err.message : String(err)
|
|
4416
4488
|
});
|
|
@@ -4460,7 +4532,7 @@ ${description}`.trim();
|
|
|
4460
4532
|
## Similar past tasks
|
|
4461
4533
|
${bullets}`;
|
|
4462
4534
|
} catch (err) {
|
|
4463
|
-
log.warn(
|
|
4535
|
+
log.warn(TAG21, "past-episodes recall failed", {
|
|
4464
4536
|
event: "episode_recall_failed",
|
|
4465
4537
|
error: err instanceof Error ? err.message : String(err)
|
|
4466
4538
|
});
|
|
@@ -4501,7 +4573,7 @@ ${subtaskStr}
|
|
|
4501
4573
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
4502
4574
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
4503
4575
|
}
|
|
4504
|
-
var
|
|
4576
|
+
var TAG21 = "prompt";
|
|
4505
4577
|
var init_prompt = __esm(() => {
|
|
4506
4578
|
init_dist();
|
|
4507
4579
|
init_log();
|
|
@@ -4515,6 +4587,7 @@ class Worker {
|
|
|
4515
4587
|
workspaceId;
|
|
4516
4588
|
projectId;
|
|
4517
4589
|
stateStore;
|
|
4590
|
+
onCardCompleted;
|
|
4518
4591
|
id;
|
|
4519
4592
|
state = "idle";
|
|
4520
4593
|
cardId = null;
|
|
@@ -4530,13 +4603,14 @@ class Worker {
|
|
|
4530
4603
|
verificationFailed = false;
|
|
4531
4604
|
sessionId = null;
|
|
4532
4605
|
runId = null;
|
|
4533
|
-
constructor(id, config, client, _userEmail, onDone, workspaceId, projectId, stateStore) {
|
|
4606
|
+
constructor(id, config, client, _userEmail, onDone, workspaceId, projectId, stateStore, onCardCompleted) {
|
|
4534
4607
|
this.config = config;
|
|
4535
4608
|
this.client = client;
|
|
4536
4609
|
this.onDone = onDone;
|
|
4537
4610
|
this.workspaceId = workspaceId;
|
|
4538
4611
|
this.projectId = projectId;
|
|
4539
4612
|
this.stateStore = stateStore;
|
|
4613
|
+
this.onCardCompleted = onCardCompleted;
|
|
4540
4614
|
this.id = id;
|
|
4541
4615
|
}
|
|
4542
4616
|
startHeartbeat() {
|
|
@@ -4571,7 +4645,7 @@ class Worker {
|
|
|
4571
4645
|
}
|
|
4572
4646
|
}
|
|
4573
4647
|
get tag() {
|
|
4574
|
-
return `${
|
|
4648
|
+
return `${TAG22}:${this.id}`;
|
|
4575
4649
|
}
|
|
4576
4650
|
get isIdle() {
|
|
4577
4651
|
return this.state === "idle";
|
|
@@ -4617,7 +4691,7 @@ class Worker {
|
|
|
4617
4691
|
});
|
|
4618
4692
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
4619
4693
|
if (!sid) {
|
|
4620
|
-
log.warn(
|
|
4694
|
+
log.warn(TAG22, "startAgentSession returned no session id");
|
|
4621
4695
|
}
|
|
4622
4696
|
this.sessionId = sid;
|
|
4623
4697
|
await this.recordPhase("preparing");
|
|
@@ -4666,7 +4740,7 @@ class Worker {
|
|
|
4666
4740
|
});
|
|
4667
4741
|
this.state = "completing";
|
|
4668
4742
|
await this.recordPhase("completing");
|
|
4669
|
-
const completed = await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.lastSessionStats, this.workspaceId, this.sessionId, this.stateStore);
|
|
4743
|
+
const completed = await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.lastSessionStats, this.workspaceId, this.sessionId, this.stateStore, this.onCardCompleted);
|
|
4670
4744
|
this.verificationFailed = !completed;
|
|
4671
4745
|
} catch (err) {
|
|
4672
4746
|
this.state = "error";
|
|
@@ -4911,7 +4985,7 @@ class Worker {
|
|
|
4911
4985
|
this.sessionId = null;
|
|
4912
4986
|
}
|
|
4913
4987
|
}
|
|
4914
|
-
var
|
|
4988
|
+
var TAG22 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4;
|
|
4915
4989
|
var init_worker = __esm(() => {
|
|
4916
4990
|
init_board_helpers();
|
|
4917
4991
|
init_completion();
|
|
@@ -4930,14 +5004,17 @@ var init_worker = __esm(() => {
|
|
|
4930
5004
|
// src/pool.ts
|
|
4931
5005
|
class Pool {
|
|
4932
5006
|
client;
|
|
5007
|
+
projectId;
|
|
4933
5008
|
stateStore;
|
|
4934
5009
|
implWorkers = [];
|
|
4935
5010
|
reviewWorkers = [];
|
|
4936
5011
|
implQueue;
|
|
4937
5012
|
reviewQueue;
|
|
4938
5013
|
budget;
|
|
5014
|
+
onCardCompleted = null;
|
|
4939
5015
|
constructor(config, client, userEmail, workspaceId, projectId, stateStore) {
|
|
4940
5016
|
this.client = client;
|
|
5017
|
+
this.projectId = projectId;
|
|
4941
5018
|
this.stateStore = stateStore;
|
|
4942
5019
|
this.implQueue = new PriorityQueue(config);
|
|
4943
5020
|
this.reviewQueue = new PriorityQueue(config);
|
|
@@ -4945,7 +5022,9 @@ class Pool {
|
|
|
4945
5022
|
for (let i = 0;i < config.poolSize; i++) {
|
|
4946
5023
|
this.implWorkers.push(new Worker(i, config, client, userEmail, () => {
|
|
4947
5024
|
this.tryDispatchFor(this.implWorkers, this.implQueue, "impl");
|
|
4948
|
-
}, workspaceId, projectId, stateStore)
|
|
5025
|
+
}, workspaceId, projectId, stateStore, async (completedCard) => {
|
|
5026
|
+
await this.onCardCompleted?.(completedCard);
|
|
5027
|
+
}));
|
|
4949
5028
|
}
|
|
4950
5029
|
if (config.review.enabled) {
|
|
4951
5030
|
for (let i = 0;i < config.review.poolSize; i++) {
|
|
@@ -4958,20 +5037,31 @@ class Pool {
|
|
|
4958
5037
|
}
|
|
4959
5038
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
4960
5039
|
if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
|
|
4961
|
-
log.debug(
|
|
5040
|
+
log.debug(TAG23, `Card ${card.id} already queued or active, skipping`);
|
|
4962
5041
|
return;
|
|
4963
5042
|
}
|
|
4964
5043
|
if (mode === "implement") {
|
|
4965
5044
|
const decision = this.budget.check(card.id);
|
|
4966
5045
|
if (!decision.allow) {
|
|
4967
5046
|
if (decision.reason === "daily_budget") {
|
|
4968
|
-
log.warn(
|
|
5047
|
+
log.warn(TAG23, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
4969
5048
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
4970
5049
|
} else {
|
|
4971
|
-
log.debug(
|
|
5050
|
+
log.debug(TAG23, `#${card.short_id} gave up: ${decision.detail}`);
|
|
4972
5051
|
}
|
|
4973
5052
|
return;
|
|
4974
5053
|
}
|
|
5054
|
+
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
5055
|
+
if (blockers === null) {
|
|
5056
|
+
log.warn(TAG23, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
5057
|
+
return;
|
|
5058
|
+
}
|
|
5059
|
+
if (blockers.length > 0) {
|
|
5060
|
+
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
5061
|
+
log.info(TAG23, `#${card.short_id} blocked by ${list} — waiting`);
|
|
5062
|
+
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
4975
5065
|
}
|
|
4976
5066
|
const queue = mode === "review" ? this.reviewQueue : this.implQueue;
|
|
4977
5067
|
queue.enqueue(card, column, labels, mode);
|
|
@@ -4984,7 +5074,10 @@ class Pool {
|
|
|
4984
5074
|
await this.emitWaiting(card.id, position > 0 ? `Queued (${position}/${total}) — waiting for ${mode} worker` : `Queued — waiting for ${mode} worker`);
|
|
4985
5075
|
}
|
|
4986
5076
|
}
|
|
5077
|
+
lastWaitingEmit = new Map;
|
|
4987
5078
|
async emitWaiting(cardId, currentTask) {
|
|
5079
|
+
if (this.lastWaitingEmit.get(cardId) === currentTask)
|
|
5080
|
+
return;
|
|
4988
5081
|
try {
|
|
4989
5082
|
await this.client.updateAgentProgress(cardId, {
|
|
4990
5083
|
agentIdentifier: agentIdentifier(0),
|
|
@@ -4992,23 +5085,25 @@ class Pool {
|
|
|
4992
5085
|
status: "waiting",
|
|
4993
5086
|
currentTask
|
|
4994
5087
|
});
|
|
5088
|
+
this.lastWaitingEmit.set(cardId, currentTask);
|
|
4995
5089
|
} catch (err) {
|
|
4996
|
-
log.debug(
|
|
5090
|
+
log.debug(TAG23, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
4997
5091
|
}
|
|
4998
5092
|
}
|
|
4999
5093
|
async removeCard(cardId) {
|
|
5000
5094
|
await this.stateStore.resetAttempts(cardId);
|
|
5095
|
+
this.lastWaitingEmit.delete(cardId);
|
|
5001
5096
|
for (const queue of [this.implQueue, this.reviewQueue]) {
|
|
5002
5097
|
const removed = queue.remove(cardId);
|
|
5003
5098
|
if (removed) {
|
|
5004
5099
|
this.cardDataCache.delete(cardId);
|
|
5005
|
-
log.info(
|
|
5100
|
+
log.info(TAG23, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
5006
5101
|
return;
|
|
5007
5102
|
}
|
|
5008
5103
|
}
|
|
5009
5104
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
5010
5105
|
if (worker) {
|
|
5011
|
-
log.info(
|
|
5106
|
+
log.info(TAG23, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
5012
5107
|
await worker.cancel();
|
|
5013
5108
|
}
|
|
5014
5109
|
}
|
|
@@ -5036,10 +5131,10 @@ class Pool {
|
|
|
5036
5131
|
async handleAgentCommand(cardId, command) {
|
|
5037
5132
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
5038
5133
|
if (!worker) {
|
|
5039
|
-
log.debug(
|
|
5134
|
+
log.debug(TAG23, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
5040
5135
|
return;
|
|
5041
5136
|
}
|
|
5042
|
-
log.info(
|
|
5137
|
+
log.info(TAG23, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
5043
5138
|
switch (command) {
|
|
5044
5139
|
case "pause":
|
|
5045
5140
|
await worker.pause();
|
|
@@ -5085,19 +5180,19 @@ class Pool {
|
|
|
5085
5180
|
};
|
|
5086
5181
|
}
|
|
5087
5182
|
async shutdown() {
|
|
5088
|
-
log.info(
|
|
5183
|
+
log.info(TAG23, "Shutting down pool...");
|
|
5089
5184
|
const active = [
|
|
5090
5185
|
...this.implWorkers.filter((w) => w.isActive),
|
|
5091
5186
|
...this.reviewWorkers.filter((w) => w.isActive)
|
|
5092
5187
|
];
|
|
5093
5188
|
await Promise.all(active.map((w) => w.cancel()));
|
|
5094
|
-
log.info(
|
|
5189
|
+
log.info(TAG23, "Pool shutdown complete");
|
|
5095
5190
|
}
|
|
5096
5191
|
cardDataCache = new Map;
|
|
5097
5192
|
tryDispatchFor(workers, queue, label) {
|
|
5098
5193
|
const idle = workers.find((w) => w.isIdle);
|
|
5099
5194
|
if (!idle) {
|
|
5100
|
-
log.debug(
|
|
5195
|
+
log.debug(TAG23, `No idle ${label} workers (queue: ${queue.length})`);
|
|
5101
5196
|
return false;
|
|
5102
5197
|
}
|
|
5103
5198
|
const next = queue.dequeue();
|
|
@@ -5105,21 +5200,23 @@ class Pool {
|
|
|
5105
5200
|
return false;
|
|
5106
5201
|
const data = this.cardDataCache.get(next.cardId);
|
|
5107
5202
|
if (!data) {
|
|
5108
|
-
log.warn(
|
|
5203
|
+
log.warn(TAG23, `No cached data for card ${next.cardId}, skipping`);
|
|
5109
5204
|
return false;
|
|
5110
5205
|
}
|
|
5111
5206
|
this.cardDataCache.delete(next.cardId);
|
|
5112
|
-
|
|
5207
|
+
this.lastWaitingEmit.delete(next.cardId);
|
|
5208
|
+
log.info(TAG23, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
5113
5209
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
5114
5210
|
return true;
|
|
5115
5211
|
}
|
|
5116
5212
|
}
|
|
5117
|
-
var
|
|
5213
|
+
var TAG23 = "pool";
|
|
5118
5214
|
var init_pool = __esm(() => {
|
|
5119
5215
|
init_log();
|
|
5120
5216
|
init_queue();
|
|
5121
5217
|
init_review_worker();
|
|
5122
5218
|
init_types();
|
|
5219
|
+
init_unblock();
|
|
5123
5220
|
init_worker();
|
|
5124
5221
|
});
|
|
5125
5222
|
|
|
@@ -5139,7 +5236,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
5139
5236
|
const { card } = await client.getCard(cardId);
|
|
5140
5237
|
return card;
|
|
5141
5238
|
} catch (err) {
|
|
5142
|
-
log.warn(
|
|
5239
|
+
log.warn(TAG24, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
5143
5240
|
return null;
|
|
5144
5241
|
}
|
|
5145
5242
|
}
|
|
@@ -5149,7 +5246,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
5149
5246
|
return [];
|
|
5150
5247
|
}
|
|
5151
5248
|
const outcomes = [];
|
|
5152
|
-
log.info(
|
|
5249
|
+
log.info(TAG24, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
5153
5250
|
for (const run of active) {
|
|
5154
5251
|
const outcome = {
|
|
5155
5252
|
runId: run.runId,
|
|
@@ -5161,11 +5258,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
5161
5258
|
};
|
|
5162
5259
|
outcomes.push(outcome);
|
|
5163
5260
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
5164
|
-
log.warn(
|
|
5261
|
+
log.warn(TAG24, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
5165
5262
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
5166
5263
|
continue;
|
|
5167
5264
|
}
|
|
5168
|
-
log.info(
|
|
5265
|
+
log.info(TAG24, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
5169
5266
|
await recoverRun(run, store, client, config, outcome);
|
|
5170
5267
|
}
|
|
5171
5268
|
return outcomes;
|
|
@@ -5183,7 +5280,7 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
5183
5280
|
} catch (err) {
|
|
5184
5281
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5185
5282
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
5186
|
-
log.warn(
|
|
5283
|
+
log.warn(TAG24, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
5187
5284
|
}
|
|
5188
5285
|
const card = await fetchCardSafely(client, run.cardId);
|
|
5189
5286
|
if (card) {
|
|
@@ -5224,9 +5321,9 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
5224
5321
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5225
5322
|
outcome.errors.push(`endRun: ${msg}`);
|
|
5226
5323
|
}
|
|
5227
|
-
log.info(
|
|
5324
|
+
log.info(TAG24, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
5228
5325
|
}
|
|
5229
|
-
var
|
|
5326
|
+
var TAG24 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
5230
5327
|
var init_recovery = __esm(() => {
|
|
5231
5328
|
init_board_helpers();
|
|
5232
5329
|
init_log();
|
|
@@ -5274,7 +5371,7 @@ class Reconciler {
|
|
|
5274
5371
|
clearInterval(this.timer);
|
|
5275
5372
|
this.timer = null;
|
|
5276
5373
|
}
|
|
5277
|
-
log.info(
|
|
5374
|
+
log.info(TAG25, "Heartbeat stopped");
|
|
5278
5375
|
}
|
|
5279
5376
|
async recoverStaleRuns() {
|
|
5280
5377
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -5291,7 +5388,7 @@ class Reconciler {
|
|
|
5291
5388
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
5292
5389
|
continue;
|
|
5293
5390
|
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`;
|
|
5294
|
-
log.warn(
|
|
5391
|
+
log.warn(TAG25, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
5295
5392
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
5296
5393
|
runId: run.runId,
|
|
5297
5394
|
cardId: run.cardId,
|
|
@@ -5327,18 +5424,18 @@ class Reconciler {
|
|
|
5327
5424
|
const subtasks = card.subtasks ?? [];
|
|
5328
5425
|
const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
|
|
5329
5426
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
5330
|
-
log.debug(
|
|
5427
|
+
log.debug(TAG25, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
5331
5428
|
continue;
|
|
5332
5429
|
}
|
|
5333
5430
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
5334
|
-
log.debug(
|
|
5431
|
+
log.debug(TAG25, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
5335
5432
|
continue;
|
|
5336
5433
|
}
|
|
5337
5434
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
5338
|
-
log.debug(
|
|
5435
|
+
log.debug(TAG25, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
5339
5436
|
continue;
|
|
5340
5437
|
}
|
|
5341
|
-
log.info(
|
|
5438
|
+
log.info(TAG25, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
5342
5439
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
5343
5440
|
}
|
|
5344
5441
|
}
|
|
@@ -5347,17 +5444,17 @@ class Reconciler {
|
|
|
5347
5444
|
}
|
|
5348
5445
|
for (const knownId of knownCardIds) {
|
|
5349
5446
|
if (!allAgentCardIds.has(knownId)) {
|
|
5350
|
-
log.info(
|
|
5447
|
+
log.info(TAG25, `Missed unassign: ${knownId} — removing`);
|
|
5351
5448
|
await this.pool.removeCard(knownId);
|
|
5352
5449
|
}
|
|
5353
5450
|
}
|
|
5354
|
-
log.debug(
|
|
5451
|
+
log.debug(TAG25, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
5355
5452
|
} catch (err) {
|
|
5356
|
-
log.error(
|
|
5453
|
+
log.error(TAG25, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
5357
5454
|
}
|
|
5358
5455
|
}
|
|
5359
5456
|
}
|
|
5360
|
-
var
|
|
5457
|
+
var TAG25 = "reconcile";
|
|
5361
5458
|
var init_reconcile = __esm(() => {
|
|
5362
5459
|
init_board_helpers();
|
|
5363
5460
|
init_log();
|
|
@@ -5388,7 +5485,12 @@ function prettyBanner(config, version) {
|
|
|
5388
5485
|
gitProvider = provider;
|
|
5389
5486
|
},
|
|
5390
5487
|
check(message) {
|
|
5391
|
-
checks.push(message);
|
|
5488
|
+
checks.push({ kind: "ok", message });
|
|
5489
|
+
},
|
|
5490
|
+
warn(message) {
|
|
5491
|
+
log.warn(TAG26, message);
|
|
5492
|
+
checks.push({ kind: "warn", message: message.split(`
|
|
5493
|
+
`, 1)[0] });
|
|
5392
5494
|
},
|
|
5393
5495
|
fail() {
|
|
5394
5496
|
failed = true;
|
|
@@ -5410,19 +5512,22 @@ function prettyBanner(config, version) {
|
|
|
5410
5512
|
};
|
|
5411
5513
|
}
|
|
5412
5514
|
function jsonBanner(config, version) {
|
|
5413
|
-
log.info(
|
|
5414
|
-
log.info(
|
|
5515
|
+
log.info(TAG26, `Harmony Agent Daemon v${version} starting...`);
|
|
5516
|
+
log.info(TAG26, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
5415
5517
|
if (config.agent.review.enabled) {
|
|
5416
|
-
log.info(
|
|
5518
|
+
log.info(TAG26, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
5417
5519
|
}
|
|
5418
5520
|
let failed = false;
|
|
5419
5521
|
return {
|
|
5420
5522
|
setProjectName(_name) {},
|
|
5421
5523
|
setGitProvider(provider) {
|
|
5422
|
-
log.info(
|
|
5524
|
+
log.info(TAG26, `Git provider: ${provider}`);
|
|
5423
5525
|
},
|
|
5424
5526
|
check(message) {
|
|
5425
|
-
log.info(
|
|
5527
|
+
log.info(TAG26, message);
|
|
5528
|
+
},
|
|
5529
|
+
warn(message) {
|
|
5530
|
+
log.warn(TAG26, message);
|
|
5426
5531
|
},
|
|
5427
5532
|
fail() {
|
|
5428
5533
|
failed = true;
|
|
@@ -5430,7 +5535,7 @@ function jsonBanner(config, version) {
|
|
|
5430
5535
|
async ready(message) {
|
|
5431
5536
|
if (failed)
|
|
5432
5537
|
return;
|
|
5433
|
-
log.info(
|
|
5538
|
+
log.info(TAG26, message);
|
|
5434
5539
|
}
|
|
5435
5540
|
};
|
|
5436
5541
|
}
|
|
@@ -5444,8 +5549,8 @@ function renderPretty(input) {
|
|
|
5444
5549
|
lines.push(` ${dim(row.label.padEnd(9))} ${row.value}`);
|
|
5445
5550
|
}
|
|
5446
5551
|
lines.push("");
|
|
5447
|
-
for (const
|
|
5448
|
-
lines.push(` ${cyan("✓")} ${
|
|
5552
|
+
for (const row of checks) {
|
|
5553
|
+
lines.push(row.kind === "warn" ? ` ${yellow("⚠")} ${row.message}` : ` ${cyan("✓")} ${row.message}`);
|
|
5449
5554
|
}
|
|
5450
5555
|
lines.push("");
|
|
5451
5556
|
lines.push(`${cyan("▶")} ${cyan("Ready")} — ${readyMessage}`);
|
|
@@ -5494,13 +5599,17 @@ function dim(s) {
|
|
|
5494
5599
|
function cyan(s) {
|
|
5495
5600
|
return `${ANSI.cyan}${s}${ANSI.reset}`;
|
|
5496
5601
|
}
|
|
5497
|
-
|
|
5602
|
+
function yellow(s) {
|
|
5603
|
+
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
5604
|
+
}
|
|
5605
|
+
var TAG26 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
5498
5606
|
var init_startup_banner = __esm(() => {
|
|
5499
5607
|
init_log();
|
|
5500
5608
|
ANSI = {
|
|
5501
5609
|
reset: "\x1B[0m",
|
|
5502
5610
|
dim: "\x1B[2m",
|
|
5503
|
-
cyan: "\x1B[36m"
|
|
5611
|
+
cyan: "\x1B[36m",
|
|
5612
|
+
yellow: "\x1B[33m"
|
|
5504
5613
|
};
|
|
5505
5614
|
});
|
|
5506
5615
|
|
|
@@ -5640,18 +5749,18 @@ class Watcher {
|
|
|
5640
5749
|
}
|
|
5641
5750
|
async start() {
|
|
5642
5751
|
if (!isPretty()) {
|
|
5643
|
-
log.info(
|
|
5752
|
+
log.info(TAG27, "Connecting to Supabase realtime (broadcast)...");
|
|
5644
5753
|
}
|
|
5645
5754
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
5646
5755
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
5647
5756
|
const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
5648
|
-
log.debug(
|
|
5757
|
+
log.debug(TAG27, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
5649
5758
|
this.onCardBroadcast({
|
|
5650
5759
|
event: "card_update",
|
|
5651
5760
|
payload: msg.payload ?? {}
|
|
5652
5761
|
});
|
|
5653
5762
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
5654
|
-
log.debug(
|
|
5763
|
+
log.debug(TAG27, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
5655
5764
|
this.onCardBroadcast({
|
|
5656
5765
|
event: "card_created",
|
|
5657
5766
|
payload: msg.payload ?? {}
|
|
@@ -5661,29 +5770,29 @@ class Watcher {
|
|
|
5661
5770
|
const cardId = payload.card_id;
|
|
5662
5771
|
const command = payload.command;
|
|
5663
5772
|
if (cardId && command) {
|
|
5664
|
-
log.info(
|
|
5773
|
+
log.info(TAG27, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
5665
5774
|
this.onAgentCommand?.({ cardId, command });
|
|
5666
5775
|
}
|
|
5667
5776
|
}).subscribe((status) => {
|
|
5668
5777
|
if (status === "SUBSCRIBED") {
|
|
5669
5778
|
this.connected = true;
|
|
5670
5779
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
5671
|
-
log.info(
|
|
5780
|
+
log.info(TAG27, "Broadcast subscription active");
|
|
5672
5781
|
}
|
|
5673
5782
|
this.maybeResolveReady();
|
|
5674
5783
|
} else if (status === "CHANNEL_ERROR") {
|
|
5675
5784
|
this.connected = false;
|
|
5676
|
-
log.error(
|
|
5785
|
+
log.error(TAG27, "Broadcast channel error — will rely on reconciliation");
|
|
5677
5786
|
} else if (status === "TIMED_OUT") {
|
|
5678
5787
|
this.connected = false;
|
|
5679
|
-
log.warn(
|
|
5788
|
+
log.warn(TAG27, "Broadcast subscription timed out — retrying...");
|
|
5680
5789
|
} else if (status === "CLOSED") {
|
|
5681
5790
|
this.connected = false;
|
|
5682
5791
|
}
|
|
5683
5792
|
});
|
|
5684
5793
|
this.channel = channel;
|
|
5685
5794
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
5686
|
-
log.debug(
|
|
5795
|
+
log.debug(TAG27, "Presence sync");
|
|
5687
5796
|
}).subscribe(async (status) => {
|
|
5688
5797
|
if (status === "SUBSCRIBED") {
|
|
5689
5798
|
await presenceChannel.track({
|
|
@@ -5695,7 +5804,7 @@ class Watcher {
|
|
|
5695
5804
|
agentName: this.identity.agentName
|
|
5696
5805
|
});
|
|
5697
5806
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
5698
|
-
log.info(
|
|
5807
|
+
log.info(TAG27, "Presence tracked on board-presence channel");
|
|
5699
5808
|
}
|
|
5700
5809
|
this.presenceTracked = true;
|
|
5701
5810
|
this.maybeResolveReady();
|
|
@@ -5717,10 +5826,10 @@ class Watcher {
|
|
|
5717
5826
|
this.supabase = null;
|
|
5718
5827
|
}
|
|
5719
5828
|
this.connected = false;
|
|
5720
|
-
log.info(
|
|
5829
|
+
log.info(TAG27, "Broadcast subscription stopped");
|
|
5721
5830
|
}
|
|
5722
5831
|
}
|
|
5723
|
-
var
|
|
5832
|
+
var TAG27 = "watcher";
|
|
5724
5833
|
var init_watcher = __esm(() => {
|
|
5725
5834
|
init_log();
|
|
5726
5835
|
});
|
|
@@ -5795,10 +5904,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
5795
5904
|
});
|
|
5796
5905
|
} catch {}
|
|
5797
5906
|
if (result.removed.length > 0) {
|
|
5798
|
-
log.info(
|
|
5907
|
+
log.info(TAG28, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
5799
5908
|
}
|
|
5800
5909
|
if (result.errors.length > 0) {
|
|
5801
|
-
log.warn(
|
|
5910
|
+
log.warn(TAG28, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
5802
5911
|
}
|
|
5803
5912
|
return result;
|
|
5804
5913
|
}
|
|
@@ -5875,10 +5984,10 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
5875
5984
|
}
|
|
5876
5985
|
}
|
|
5877
5986
|
if (result.removed.length > 0) {
|
|
5878
|
-
log.info(
|
|
5987
|
+
log.info(TAG28, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
5879
5988
|
}
|
|
5880
5989
|
if (result.errors.length > 0) {
|
|
5881
|
-
log.warn(
|
|
5990
|
+
log.warn(TAG28, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
5882
5991
|
}
|
|
5883
5992
|
return result;
|
|
5884
5993
|
}
|
|
@@ -5909,13 +6018,13 @@ class WorktreeGc {
|
|
|
5909
6018
|
try {
|
|
5910
6019
|
runWorktreeGc(this.basePath, this.store);
|
|
5911
6020
|
} catch (err) {
|
|
5912
|
-
log.warn(
|
|
6021
|
+
log.warn(TAG28, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
5913
6022
|
}
|
|
5914
6023
|
if (this.remoteOpts) {
|
|
5915
6024
|
try {
|
|
5916
6025
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
5917
6026
|
} catch (err) {
|
|
5918
|
-
log.warn(
|
|
6027
|
+
log.warn(TAG28, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
5919
6028
|
}
|
|
5920
6029
|
}
|
|
5921
6030
|
}
|
|
@@ -5929,7 +6038,7 @@ function getRepoRoot2() {
|
|
|
5929
6038
|
return null;
|
|
5930
6039
|
}
|
|
5931
6040
|
}
|
|
5932
|
-
var
|
|
6041
|
+
var TAG28 = "worktree-gc";
|
|
5933
6042
|
var init_worktree_gc = __esm(() => {
|
|
5934
6043
|
init_log();
|
|
5935
6044
|
init_worktree();
|
|
@@ -5965,8 +6074,7 @@ async function validatePrerequisites(config, banner) {
|
|
|
5965
6074
|
encoding: "utf-8"
|
|
5966
6075
|
}).trim();
|
|
5967
6076
|
if (status) {
|
|
5968
|
-
banner.
|
|
5969
|
-
log.warn(TAG28, `Working directory has uncommitted changes:
|
|
6077
|
+
banner.warn(`Working directory has uncommitted changes:
|
|
5970
6078
|
${status}`);
|
|
5971
6079
|
}
|
|
5972
6080
|
execFileSync10("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
|
|
@@ -6012,7 +6120,7 @@ async function main() {
|
|
|
6012
6120
|
} catch (err) {
|
|
6013
6121
|
if (err instanceof ConfigValidationError) {
|
|
6014
6122
|
banner.fail();
|
|
6015
|
-
log.error(
|
|
6123
|
+
log.error(TAG29, err.message);
|
|
6016
6124
|
process.exit(1);
|
|
6017
6125
|
}
|
|
6018
6126
|
throw err;
|
|
@@ -6030,12 +6138,21 @@ async function main() {
|
|
|
6030
6138
|
const realtimeCreds = await fetchRealtimeCredentials(client);
|
|
6031
6139
|
banner.check("Realtime credentials");
|
|
6032
6140
|
const pool = new Pool(config.agent, client, config.userEmail, config.workspaceId, config.projectId, stateStore);
|
|
6141
|
+
const promoteSuccessors = async (completedCard) => {
|
|
6142
|
+
await promoteUnblockedSuccessors(completedCard, {
|
|
6143
|
+
client,
|
|
6144
|
+
agentUserId,
|
|
6145
|
+
enqueue: (cardId) => tryEnqueueCard(cardId, client, pool, config)
|
|
6146
|
+
});
|
|
6147
|
+
};
|
|
6148
|
+
pool.onCardCompleted = promoteSuccessors;
|
|
6033
6149
|
const reviewColumns = config.agent.review.enabled ? config.agent.review.pickupColumns : [];
|
|
6034
6150
|
const approvedLabel = config.agent.review.enabled ? config.agent.review.approvedLabel : "";
|
|
6035
6151
|
const reconciler = new Reconciler(client, pool, config.projectId, agentUserId, config.agent.pickupColumns, reviewColumns, approvedLabel, config.agent.timing.reconcileIntervalMs, stateStore, config.agent);
|
|
6036
6152
|
let mergeMonitor = null;
|
|
6037
6153
|
if (config.agent.review.enabled && config.agent.review.mergeMonitor) {
|
|
6038
6154
|
mergeMonitor = new MergeMonitor(client, config.projectId, config.agent);
|
|
6155
|
+
mergeMonitor.onCardCompleted = promoteSuccessors;
|
|
6039
6156
|
}
|
|
6040
6157
|
const worktreeGc = new WorktreeGc(config.agent.worktree.basePath, stateStore, config.agent.timing.worktreeGcIntervalMs, config.agent.worktree.failedAttemptRetentionDays > 0 ? {
|
|
6041
6158
|
prefix: config.agent.worktree.failedBranchPrefix,
|
|
@@ -6104,25 +6221,25 @@ async function main() {
|
|
|
6104
6221
|
if (shuttingDown)
|
|
6105
6222
|
return;
|
|
6106
6223
|
shuttingDown = true;
|
|
6107
|
-
log.info(
|
|
6224
|
+
log.info(TAG29, `Received ${signal}, shutting down gracefully...`);
|
|
6108
6225
|
reconciler.stop();
|
|
6109
6226
|
mergeMonitor?.stop();
|
|
6110
6227
|
worktreeGc.stop();
|
|
6111
6228
|
await httpServer?.stop();
|
|
6112
6229
|
await watcher.stop();
|
|
6113
6230
|
await pool.shutdown();
|
|
6114
|
-
log.info(
|
|
6231
|
+
log.info(TAG29, "Daemon stopped.");
|
|
6115
6232
|
process.exit(exitCode);
|
|
6116
6233
|
};
|
|
6117
6234
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
6118
6235
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
6119
6236
|
process.on("uncaughtException", (err) => {
|
|
6120
|
-
log.error(
|
|
6237
|
+
log.error(TAG29, `Uncaught exception: ${err.message}`);
|
|
6121
6238
|
exitCode = 1;
|
|
6122
6239
|
shutdown("uncaughtException");
|
|
6123
6240
|
});
|
|
6124
6241
|
process.on("unhandledRejection", (reason) => {
|
|
6125
|
-
log.error(
|
|
6242
|
+
log.error(TAG29, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
6126
6243
|
exitCode = 1;
|
|
6127
6244
|
shutdown("unhandledRejection");
|
|
6128
6245
|
});
|
|
@@ -6134,8 +6251,7 @@ async function main() {
|
|
|
6134
6251
|
try {
|
|
6135
6252
|
await httpServer.start();
|
|
6136
6253
|
} catch (err) {
|
|
6137
|
-
banner.
|
|
6138
|
-
log.warn(TAG28, `HTTP server failed to bind: ${err instanceof Error ? err.message : err}`);
|
|
6254
|
+
banner.warn(`HTTP server failed to bind: ${err instanceof Error ? err.message : err}`);
|
|
6139
6255
|
}
|
|
6140
6256
|
}
|
|
6141
6257
|
const reviewCount = config.agent.review.enabled ? config.agent.review.poolSize : 0;
|
|
@@ -6170,14 +6286,14 @@ async function handleBroadcast(event, client, pool, config, agentUserId) {
|
|
|
6170
6286
|
if (assigneeId === undefined)
|
|
6171
6287
|
return;
|
|
6172
6288
|
if (assigneeId === agentUserId) {
|
|
6173
|
-
log.info(
|
|
6289
|
+
log.info(TAG29, `Broadcast: card ${cardId} assigned to agent`);
|
|
6174
6290
|
try {
|
|
6175
6291
|
await tryEnqueueCard(cardId, client, pool, config);
|
|
6176
6292
|
} catch (err) {
|
|
6177
|
-
log.error(
|
|
6293
|
+
log.error(TAG29, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
6178
6294
|
}
|
|
6179
6295
|
} else if (pool.isCardKnown(cardId)) {
|
|
6180
|
-
log.info(
|
|
6296
|
+
log.info(TAG29, `Broadcast: card ${cardId} unassigned from agent`);
|
|
6181
6297
|
await pool.removeCard(cardId);
|
|
6182
6298
|
}
|
|
6183
6299
|
}
|
|
@@ -6187,13 +6303,13 @@ async function tryEnqueueCard(cardId, client, pool, config) {
|
|
|
6187
6303
|
const columns = board.columns;
|
|
6188
6304
|
const column = columns.find((c) => c.id === card.column_id);
|
|
6189
6305
|
if (!column) {
|
|
6190
|
-
log.warn(
|
|
6306
|
+
log.warn(TAG29, `Column not found for card ${cardId}`);
|
|
6191
6307
|
return;
|
|
6192
6308
|
}
|
|
6193
6309
|
const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
6194
6310
|
const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
6195
6311
|
if (!isPickupColumn && !isReviewColumn) {
|
|
6196
|
-
log.info(
|
|
6312
|
+
log.info(TAG29, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
|
|
6197
6313
|
return;
|
|
6198
6314
|
}
|
|
6199
6315
|
const mode = isReviewColumn ? "review" : "implement";
|
|
@@ -6201,16 +6317,16 @@ async function tryEnqueueCard(cardId, client, pool, config) {
|
|
|
6201
6317
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
6202
6318
|
const subtasks = card.subtasks ?? [];
|
|
6203
6319
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
6204
|
-
log.debug(
|
|
6320
|
+
log.debug(TAG29, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
6205
6321
|
return;
|
|
6206
6322
|
}
|
|
6207
6323
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
6208
|
-
log.info(
|
|
6324
|
+
log.info(TAG29, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
6209
6325
|
return;
|
|
6210
6326
|
}
|
|
6211
6327
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
6212
6328
|
}
|
|
6213
|
-
var
|
|
6329
|
+
var TAG29 = "daemon", PKG_VERSION;
|
|
6214
6330
|
var init_src = __esm(() => {
|
|
6215
6331
|
init_board_helpers();
|
|
6216
6332
|
init_config();
|
|
@@ -6227,6 +6343,7 @@ var init_src = __esm(() => {
|
|
|
6227
6343
|
init_state_store();
|
|
6228
6344
|
init_stream_parser_selftest();
|
|
6229
6345
|
init_types();
|
|
6346
|
+
init_unblock();
|
|
6230
6347
|
init_watcher();
|
|
6231
6348
|
init_worktree_gc();
|
|
6232
6349
|
({ version: PKG_VERSION } = createRequire2(import.meta.url)("../package.json"));
|