@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.
Files changed (3) hide show
  1. package/dist/cli.js +187 -90
  2. package/dist/index.js +187 -90
  3. 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(TAG20, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
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(TAG20, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
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(TAG20, `successor #${card.short_id} not assigned to agent — skipping promotion`);
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(TAG20, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
4384
+ log.warn(TAG21, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
4316
4385
  }
4317
4386
  }
4318
4387
  }
4319
- var TAG20 = "unblock";
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(TAG21, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
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(TAG21, `Failed to generate prompt via API, using fallback: ${msg}`);
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(TAG21, "comment-thread fetch failed", {
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(TAG21, "past-episodes recall failed", {
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 TAG21 = "prompt";
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 `${TAG22}:${this.id}`;
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(TAG22, "startAgentSession returned no session id");
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 TAG22 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4;
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(TAG23, `Card ${card.id} already queued or active, skipping`);
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(TAG23, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
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(TAG23, `#${card.short_id} gave up: ${decision.detail}`);
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(TAG23, `#${card.short_id} blocker check failed — deferring to next tick`);
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(TAG23, `#${card.short_id} blocked by ${list} — waiting`);
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(TAG23, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
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(TAG23, `Removed #${removed.shortId} from ${removed.mode} queue`);
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(TAG23, `Cancelling worker ${worker.id} for card ${cardId}`);
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(TAG23, `No active worker for card ${cardId}, ignoring ${command}`);
5228
+ log.debug(TAG24, `No active worker for card ${cardId}, ignoring ${command}`);
5135
5229
  return;
5136
5230
  }
5137
- log.info(TAG23, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
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(TAG23, "Shutting down pool...");
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
- log.info(TAG23, "Pool shutdown complete");
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(TAG23, `No idle ${label} workers (queue: ${queue.length})`);
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(TAG23, `No cached data for card ${next.cardId}, skipping`);
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(TAG23, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
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 TAG23 = "pool";
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(TAG24, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
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(TAG24, `recovering ${active.length} orphan run(s) from prior daemon`);
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(TAG24, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
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(TAG24, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
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(TAG24, `endAgentSession failed for ${run.cardId}: ${msg}`);
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(TAG24, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
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 TAG24 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
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(TAG25, "Heartbeat stopped");
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(TAG25, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
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(TAG25, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
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(TAG25, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
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(TAG25, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
5532
+ log.debug(TAG26, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
5436
5533
  continue;
5437
5534
  }
5438
- log.info(TAG25, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
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(TAG25, `Missed unassign: ${knownId} — removing`);
5544
+ log.info(TAG26, `Missed unassign: ${knownId} — removing`);
5448
5545
  await this.pool.removeCard(knownId);
5449
5546
  }
5450
5547
  }
5451
- log.debug(TAG25, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
5548
+ log.debug(TAG26, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
5452
5549
  } catch (err) {
5453
- log.error(TAG25, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
5550
+ log.error(TAG26, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
5454
5551
  }
5455
5552
  }
5456
5553
  }
5457
- var TAG25 = "reconcile";
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(TAG26, message);
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(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(", ")}`);
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(TAG26, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
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(TAG26, `Git provider: ${provider}`);
5621
+ log.info(TAG27, `Git provider: ${provider}`);
5525
5622
  },
5526
5623
  check(message) {
5527
- log.info(TAG26, message);
5624
+ log.info(TAG27, message);
5528
5625
  },
5529
5626
  warn(message) {
5530
- log.warn(TAG26, message);
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(TAG26, message);
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 TAG26 = "daemon", RULE_WIDTH = 70, ANSI;
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(TAG27, "Connecting to Supabase realtime (broadcast)...");
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(TAG27, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
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(TAG27, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
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(TAG27, `Broadcast: agent_command ${command} for ${cardId}`);
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(TAG27, "Broadcast subscription active");
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(TAG27, "Broadcast channel error — will rely on reconciliation");
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(TAG27, "Broadcast subscription timed out — retrying...");
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(TAG27, "Presence sync");
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(TAG27, "Presence tracked on board-presence channel");
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(TAG27, "Broadcast subscription stopped");
5926
+ log.info(TAG28, "Broadcast subscription stopped");
5830
5927
  }
5831
5928
  }
5832
- var TAG27 = "watcher";
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(TAG28, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
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(TAG28, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
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(TAG28, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
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(TAG28, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
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(TAG28, `GC tick failed: ${err instanceof Error ? err.message : err}`);
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(TAG28, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
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 TAG28 = "worktree-gc";
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(TAG29, err.message);
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(TAG29, `Received ${signal}, shutting down gracefully...`);
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(TAG29, "Daemon stopped.");
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(TAG29, `Uncaught exception: ${err.message}`);
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(TAG29, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
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(TAG29, `Broadcast: card ${cardId} assigned to agent`);
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(TAG29, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
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(TAG29, `Broadcast: card ${cardId} unassigned from agent`);
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(TAG29, `Column not found for card ${cardId}`);
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(TAG29, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
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(TAG29, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
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(TAG29, `Card #${card.short_id} has no branch reference — skipping auto-review`);
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 TAG29 = "daemon", PKG_VERSION;
6426
+ var TAG30 = "daemon", PKG_VERSION;
6330
6427
  var init_src = __esm(() => {
6331
6428
  init_board_helpers();
6332
6429
  init_config();