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