@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.
Files changed (3) hide show
  1. package/dist/cli.js +207 -90
  2. package/dist/index.js +207 -90
  3. 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(TAG20, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
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(TAG20, `Failed to generate prompt via API, using fallback: ${msg}`);
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(TAG20, "comment-thread fetch failed", {
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(TAG20, "past-episodes recall failed", {
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 TAG20 = "prompt";
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 `${TAG21}:${this.id}`;
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(TAG21, "startAgentSession returned no session id");
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 TAG21 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4;
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(TAG22, `Card ${card.id} already queued or active, skipping`);
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(TAG22, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
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(TAG22, `#${card.short_id} gave up: ${decision.detail}`);
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(TAG22, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
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(TAG22, `Removed #${removed.shortId} from ${removed.mode} queue`);
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(TAG22, `Cancelling worker ${worker.id} for card ${cardId}`);
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(TAG22, `No active worker for card ${cardId}, ignoring ${command}`);
5134
+ log.debug(TAG23, `No active worker for card ${cardId}, ignoring ${command}`);
5040
5135
  return;
5041
5136
  }
5042
- log.info(TAG22, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
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(TAG22, "Shutting down pool...");
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(TAG22, "Pool shutdown complete");
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(TAG22, `No idle ${label} workers (queue: ${queue.length})`);
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(TAG22, `No cached data for card ${next.cardId}, skipping`);
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
- log.info(TAG22, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
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 TAG22 = "pool";
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(TAG23, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
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(TAG23, `recovering ${active.length} orphan run(s) from prior daemon`);
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(TAG23, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
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(TAG23, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
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(TAG23, `endAgentSession failed for ${run.cardId}: ${msg}`);
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(TAG23, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
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 TAG23 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
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(TAG24, "Heartbeat stopped");
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(TAG24, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
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(TAG24, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
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(TAG24, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
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(TAG24, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
5435
+ log.debug(TAG25, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
5339
5436
  continue;
5340
5437
  }
5341
- log.info(TAG24, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
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(TAG24, `Missed unassign: ${knownId} — removing`);
5447
+ log.info(TAG25, `Missed unassign: ${knownId} — removing`);
5351
5448
  await this.pool.removeCard(knownId);
5352
5449
  }
5353
5450
  }
5354
- log.debug(TAG24, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
5451
+ log.debug(TAG25, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
5355
5452
  } catch (err) {
5356
- log.error(TAG24, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
5453
+ log.error(TAG25, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
5357
5454
  }
5358
5455
  }
5359
5456
  }
5360
- var TAG24 = "reconcile";
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(TAG25, `Harmony Agent Daemon v${version} starting...`);
5414
- log.info(TAG25, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
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(TAG25, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
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(TAG25, `Git provider: ${provider}`);
5524
+ log.info(TAG26, `Git provider: ${provider}`);
5423
5525
  },
5424
5526
  check(message) {
5425
- log.info(TAG25, message);
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(TAG25, message);
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 msg of checks) {
5448
- lines.push(` ${cyan("✓")} ${msg}`);
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
- var TAG25 = "daemon", RULE_WIDTH = 70, ANSI;
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(TAG26, "Connecting to Supabase realtime (broadcast)...");
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(TAG26, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
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(TAG26, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
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(TAG26, `Broadcast: agent_command ${command} for ${cardId}`);
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(TAG26, "Broadcast subscription active");
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(TAG26, "Broadcast channel error — will rely on reconciliation");
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(TAG26, "Broadcast subscription timed out — retrying...");
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(TAG26, "Presence sync");
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(TAG26, "Presence tracked on board-presence channel");
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(TAG26, "Broadcast subscription stopped");
5829
+ log.info(TAG27, "Broadcast subscription stopped");
5721
5830
  }
5722
5831
  }
5723
- var TAG26 = "watcher";
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(TAG27, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
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(TAG27, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
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(TAG27, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
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(TAG27, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
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(TAG27, `GC tick failed: ${err instanceof Error ? err.message : err}`);
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(TAG27, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
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 TAG27 = "worktree-gc";
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.fail();
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(TAG28, err.message);
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(TAG28, `Received ${signal}, shutting down gracefully...`);
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(TAG28, "Daemon stopped.");
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(TAG28, `Uncaught exception: ${err.message}`);
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(TAG28, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
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.fail();
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(TAG28, `Broadcast: card ${cardId} assigned to agent`);
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(TAG28, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
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(TAG28, `Broadcast: card ${cardId} unassigned from agent`);
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(TAG28, `Column not found for card ${cardId}`);
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(TAG28, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
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(TAG28, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
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(TAG28, `Card #${card.short_id} has no branch reference — skipping auto-review`);
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 TAG28 = "daemon", PKG_VERSION;
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"));