@gethmy/agent 1.42.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7361,7 +7361,7 @@ function renderPreviousAttemptsSection(failures) {
7361
7361
  ].join(`
7362
7362
  `);
7363
7363
  }
7364
- async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId, onRecallOutcome) {
7364
+ async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId, onRecallOutcome, onPromptFallback) {
7365
7365
  const { card } = enriched;
7366
7366
  const referenceSection = await renderReferenceSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome);
7367
7367
  try {
@@ -7379,6 +7379,7 @@ The daemon owns the run lifecycle: once your work is committed it ends the agent
7379
7379
  } catch (err) {
7380
7380
  const msg = err instanceof Error ? err.message : String(err);
7381
7381
  log22.warn(TAG20, `Failed to generate prompt via API, using fallback: ${msg}`);
7382
+ onPromptFallback?.();
7382
7383
  const commentsSection = await renderCommentsSection(client, card.id);
7383
7384
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + referenceSection;
7384
7385
  }
@@ -7411,9 +7412,14 @@ async function renderReferenceSection(client, title, description, workspaceId, p
7411
7412
  const query = buildMemoryQuery(title, description);
7412
7413
  const { entities } = await client.searchMemoryEntities(workspaceId, query, {
7413
7414
  project_id: projectId,
7414
- limit: 5
7415
+ limit: 5,
7416
+ consumer: "agent-reference"
7417
+ });
7418
+ onOutcome?.({
7419
+ flavour: "reference",
7420
+ count: entities.length,
7421
+ candidateCount: entities.length
7415
7422
  });
7416
- onOutcome?.({ flavour: "reference", count: entities.length });
7417
7423
  if (entities.length === 0)
7418
7424
  return "";
7419
7425
  const bullets = entities.map((entity) => {
@@ -8612,6 +8618,14 @@ class StateStore {
8612
8618
  getCard(cardId) {
8613
8619
  return this.state.cards.find((c) => c.cardId === cardId) ?? null;
8614
8620
  }
8621
+ async forgetCard(cardId) {
8622
+ const before = this.state.cards.length;
8623
+ this.state.cards = this.state.cards.filter((c) => c.cardId !== cardId);
8624
+ if (this.state.cards.length === before)
8625
+ return false;
8626
+ await this.persist();
8627
+ return true;
8628
+ }
8615
8629
  async incrementAttempt(cardId) {
8616
8630
  const rec = this.ensureCard(cardId);
8617
8631
  rec.attempts += 1;
@@ -12671,11 +12685,16 @@ class Worker {
12671
12685
  if (worktreePath) {
12672
12686
  commitUncommittedChanges(worktreePath, card);
12673
12687
  if (this.branchName) {
12688
+ let pushed = false;
12674
12689
  try {
12675
12690
  pushBranch3(this.branchName, worktreePath);
12691
+ pushed = true;
12676
12692
  } catch (err) {
12677
12693
  log34.error(this.tag, `push after the motor stage "${ctx.stage.name}" failed for ${this.branchName}: ${err instanceof Error ? err.message : err}`);
12678
12694
  }
12695
+ if (pushed) {
12696
+ await recordBranchProvenance(this.client, card, this.branchName);
12697
+ }
12679
12698
  }
12680
12699
  }
12681
12700
  const completionColumn = this.config.completion.moveToColumn;
@@ -12699,8 +12718,11 @@ class Worker {
12699
12718
  }
12700
12719
  async buildFreshRunPrompt(enriched, card, stageCtx, continuesPushedWork, resuming) {
12701
12720
  const recallOutcomes = [];
12702
- const basePrompt = await buildPrompt(enriched, this.branchName, this.worktreePath, this.client, this.workspaceId, this.projectId, (outcome) => recallOutcomes.push(outcome));
12703
- this.recordPromptAssembled(recallOutcomes);
12721
+ let promptFallback = false;
12722
+ const basePrompt = await buildPrompt(enriched, this.branchName, this.worktreePath, this.client, this.workspaceId, this.projectId, (outcome) => recallOutcomes.push(outcome), () => {
12723
+ promptFallback = true;
12724
+ });
12725
+ this.recordPromptAssembled(recallOutcomes, promptFallback);
12704
12726
  let prompt = basePrompt;
12705
12727
  if (stageCtx.kind === "run") {
12706
12728
  const loop = getStageLoop(stageCtx.stage);
@@ -12910,16 +12932,20 @@ ${prompt}`;
12910
12932
  }
12911
12933
  return outcome;
12912
12934
  }
12913
- recordPromptAssembled(outcomes) {
12914
- const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count;
12935
+ recordPromptAssembled(outcomes, promptFallback = false) {
12936
+ const outcomeOf = (flavour) => outcomes.find((o) => o.flavour === flavour);
12937
+ const countOf = (flavour) => outcomeOf(flavour)?.count;
12915
12938
  const failures = outcomes.flatMap((o) => o.failure ? [{ flavour: o.flavour, message: o.failure.slice(0, 300) }] : []);
12916
12939
  const episodeCount = countOf("past_episodes");
12940
+ const candidateCount = outcomeOf("reference")?.candidateCount;
12917
12941
  this.cliRunner?.record({
12918
12942
  kind: "prompt_assembled",
12919
12943
  source: "system",
12920
12944
  payload: {
12921
12945
  ...episodeCount === undefined ? {} : { episodeCount },
12922
12946
  referenceCount: countOf("reference") ?? 0,
12947
+ ...candidateCount === undefined ? {} : { candidateCount },
12948
+ ...promptFallback ? { promptFallback: true } : {},
12923
12949
  ...failures.length > 0 ? { recallFailures: failures } : {}
12924
12950
  }
12925
12951
  });
@@ -13701,6 +13727,12 @@ import {
13701
13727
  describeApiError as describeApiError3,
13702
13728
  log as log36
13703
13729
  } from "@gethmy/harness";
13730
+ function httpStatusOf(err) {
13731
+ if (typeof err !== "object" || err === null)
13732
+ return;
13733
+ const status = own(err, "status");
13734
+ return typeof status === "number" ? status : undefined;
13735
+ }
13704
13736
  async function routeBudgetDecision(d, run, actions, cardId) {
13705
13737
  if (!run) {
13706
13738
  if (!cardId)
@@ -14081,6 +14113,10 @@ class Pool {
14081
14113
  try {
14082
14114
  ({ decisions } = await this.client.getBudgetDecisions(cardId, new Date(sinceMs).toISOString()));
14083
14115
  } catch (err) {
14116
+ if (httpStatusOf(err) === 404) {
14117
+ await this.forgetDeletedCard(cardId, run);
14118
+ return;
14119
+ }
14084
14120
  log36.warn(TAG32, `getBudgetDecisions failed for ${cardId}: ${err}`);
14085
14121
  return;
14086
14122
  }
@@ -14105,6 +14141,25 @@ class Pool {
14105
14141
  this.draining.delete(cardId);
14106
14142
  }
14107
14143
  }
14144
+ async forgetDeletedCard(cardId, run) {
14145
+ if (run) {
14146
+ try {
14147
+ await this.stateStore.endRun(run.runId, "failed", {
14148
+ errorMessage: "card deleted"
14149
+ });
14150
+ } catch (err) {
14151
+ log36.warn(TAG32, `Failed to end the parked run for the deleted card ${cardId}: ${err}`);
14152
+ }
14153
+ }
14154
+ try {
14155
+ const dropped = await this.stateStore.forgetCard(cardId);
14156
+ if (dropped) {
14157
+ log36.info(TAG32, `Card ${cardId} no longer exists on the server — dropped from the local ledger; its budget question can never be answered`);
14158
+ }
14159
+ } catch (err) {
14160
+ log36.warn(TAG32, `Failed to drop the deleted card ${cardId}: ${err}`);
14161
+ }
14162
+ }
14108
14163
  async releaseExpiredPark(run) {
14109
14164
  try {
14110
14165
  await this.client.addComment(run.cardId, formatExpiredParkComment({
package/dist/index.js CHANGED
@@ -7360,7 +7360,7 @@ function renderPreviousAttemptsSection(failures) {
7360
7360
  ].join(`
7361
7361
  `);
7362
7362
  }
7363
- async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId, onRecallOutcome) {
7363
+ async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId, onRecallOutcome, onPromptFallback) {
7364
7364
  const { card } = enriched;
7365
7365
  const referenceSection = await renderReferenceSection(client, card.title, card.description ?? "", workspaceId, projectId, onRecallOutcome);
7366
7366
  try {
@@ -7378,6 +7378,7 @@ The daemon owns the run lifecycle: once your work is committed it ends the agent
7378
7378
  } catch (err) {
7379
7379
  const msg = err instanceof Error ? err.message : String(err);
7380
7380
  log22.warn(TAG20, `Failed to generate prompt via API, using fallback: ${msg}`);
7381
+ onPromptFallback?.();
7381
7382
  const commentsSection = await renderCommentsSection(client, card.id);
7382
7383
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + referenceSection;
7383
7384
  }
@@ -7410,9 +7411,14 @@ async function renderReferenceSection(client, title, description, workspaceId, p
7410
7411
  const query = buildMemoryQuery(title, description);
7411
7412
  const { entities } = await client.searchMemoryEntities(workspaceId, query, {
7412
7413
  project_id: projectId,
7413
- limit: 5
7414
+ limit: 5,
7415
+ consumer: "agent-reference"
7416
+ });
7417
+ onOutcome?.({
7418
+ flavour: "reference",
7419
+ count: entities.length,
7420
+ candidateCount: entities.length
7414
7421
  });
7415
- onOutcome?.({ flavour: "reference", count: entities.length });
7416
7422
  if (entities.length === 0)
7417
7423
  return "";
7418
7424
  const bullets = entities.map((entity) => {
@@ -8611,6 +8617,14 @@ class StateStore {
8611
8617
  getCard(cardId) {
8612
8618
  return this.state.cards.find((c) => c.cardId === cardId) ?? null;
8613
8619
  }
8620
+ async forgetCard(cardId) {
8621
+ const before = this.state.cards.length;
8622
+ this.state.cards = this.state.cards.filter((c) => c.cardId !== cardId);
8623
+ if (this.state.cards.length === before)
8624
+ return false;
8625
+ await this.persist();
8626
+ return true;
8627
+ }
8614
8628
  async incrementAttempt(cardId) {
8615
8629
  const rec = this.ensureCard(cardId);
8616
8630
  rec.attempts += 1;
@@ -12670,11 +12684,16 @@ class Worker {
12670
12684
  if (worktreePath) {
12671
12685
  commitUncommittedChanges(worktreePath, card);
12672
12686
  if (this.branchName) {
12687
+ let pushed = false;
12673
12688
  try {
12674
12689
  pushBranch3(this.branchName, worktreePath);
12690
+ pushed = true;
12675
12691
  } catch (err) {
12676
12692
  log34.error(this.tag, `push after the motor stage "${ctx.stage.name}" failed for ${this.branchName}: ${err instanceof Error ? err.message : err}`);
12677
12693
  }
12694
+ if (pushed) {
12695
+ await recordBranchProvenance(this.client, card, this.branchName);
12696
+ }
12678
12697
  }
12679
12698
  }
12680
12699
  const completionColumn = this.config.completion.moveToColumn;
@@ -12698,8 +12717,11 @@ class Worker {
12698
12717
  }
12699
12718
  async buildFreshRunPrompt(enriched, card, stageCtx, continuesPushedWork, resuming) {
12700
12719
  const recallOutcomes = [];
12701
- const basePrompt = await buildPrompt(enriched, this.branchName, this.worktreePath, this.client, this.workspaceId, this.projectId, (outcome) => recallOutcomes.push(outcome));
12702
- this.recordPromptAssembled(recallOutcomes);
12720
+ let promptFallback = false;
12721
+ const basePrompt = await buildPrompt(enriched, this.branchName, this.worktreePath, this.client, this.workspaceId, this.projectId, (outcome) => recallOutcomes.push(outcome), () => {
12722
+ promptFallback = true;
12723
+ });
12724
+ this.recordPromptAssembled(recallOutcomes, promptFallback);
12703
12725
  let prompt = basePrompt;
12704
12726
  if (stageCtx.kind === "run") {
12705
12727
  const loop = getStageLoop(stageCtx.stage);
@@ -12909,16 +12931,20 @@ ${prompt}`;
12909
12931
  }
12910
12932
  return outcome;
12911
12933
  }
12912
- recordPromptAssembled(outcomes) {
12913
- const countOf = (flavour) => outcomes.find((o) => o.flavour === flavour)?.count;
12934
+ recordPromptAssembled(outcomes, promptFallback = false) {
12935
+ const outcomeOf = (flavour) => outcomes.find((o) => o.flavour === flavour);
12936
+ const countOf = (flavour) => outcomeOf(flavour)?.count;
12914
12937
  const failures = outcomes.flatMap((o) => o.failure ? [{ flavour: o.flavour, message: o.failure.slice(0, 300) }] : []);
12915
12938
  const episodeCount = countOf("past_episodes");
12939
+ const candidateCount = outcomeOf("reference")?.candidateCount;
12916
12940
  this.cliRunner?.record({
12917
12941
  kind: "prompt_assembled",
12918
12942
  source: "system",
12919
12943
  payload: {
12920
12944
  ...episodeCount === undefined ? {} : { episodeCount },
12921
12945
  referenceCount: countOf("reference") ?? 0,
12946
+ ...candidateCount === undefined ? {} : { candidateCount },
12947
+ ...promptFallback ? { promptFallback: true } : {},
12922
12948
  ...failures.length > 0 ? { recallFailures: failures } : {}
12923
12949
  }
12924
12950
  });
@@ -13700,6 +13726,12 @@ import {
13700
13726
  describeApiError as describeApiError3,
13701
13727
  log as log36
13702
13728
  } from "@gethmy/harness";
13729
+ function httpStatusOf(err) {
13730
+ if (typeof err !== "object" || err === null)
13731
+ return;
13732
+ const status = own(err, "status");
13733
+ return typeof status === "number" ? status : undefined;
13734
+ }
13703
13735
  async function routeBudgetDecision(d, run, actions, cardId) {
13704
13736
  if (!run) {
13705
13737
  if (!cardId)
@@ -14080,6 +14112,10 @@ class Pool {
14080
14112
  try {
14081
14113
  ({ decisions } = await this.client.getBudgetDecisions(cardId, new Date(sinceMs).toISOString()));
14082
14114
  } catch (err) {
14115
+ if (httpStatusOf(err) === 404) {
14116
+ await this.forgetDeletedCard(cardId, run);
14117
+ return;
14118
+ }
14083
14119
  log36.warn(TAG32, `getBudgetDecisions failed for ${cardId}: ${err}`);
14084
14120
  return;
14085
14121
  }
@@ -14104,6 +14140,25 @@ class Pool {
14104
14140
  this.draining.delete(cardId);
14105
14141
  }
14106
14142
  }
14143
+ async forgetDeletedCard(cardId, run) {
14144
+ if (run) {
14145
+ try {
14146
+ await this.stateStore.endRun(run.runId, "failed", {
14147
+ errorMessage: "card deleted"
14148
+ });
14149
+ } catch (err) {
14150
+ log36.warn(TAG32, `Failed to end the parked run for the deleted card ${cardId}: ${err}`);
14151
+ }
14152
+ }
14153
+ try {
14154
+ const dropped = await this.stateStore.forgetCard(cardId);
14155
+ if (dropped) {
14156
+ log36.info(TAG32, `Card ${cardId} no longer exists on the server — dropped from the local ledger; its budget question can never be answered`);
14157
+ }
14158
+ } catch (err) {
14159
+ log36.warn(TAG32, `Failed to drop the deleted card ${cardId}: ${err}`);
14160
+ }
14161
+ }
14107
14162
  async releaseExpiredPark(run) {
14108
14163
  try {
14109
14164
  await this.client.addComment(run.cardId, formatExpiredParkComment({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/agent",
3
- "version": "1.42.0",
3
+ "version": "1.44.0",
4
4
  "description": "Push-based agent daemon for Harmony — picks up assigned cards, builds them in isolated git worktrees, and opens PRs. It runs where you put it.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,8 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@anthropic-ai/claude-agent-sdk": "^0.3.178",
48
- "@gethmy/harness": "1.15.0",
49
- "@gethmy/mcp": "3.11.0",
48
+ "@gethmy/harness": "1.17.0",
49
+ "@gethmy/mcp": "3.13.0",
50
50
  "@supabase/supabase-js": "2.95.3"
51
51
  },
52
52
  "devDependencies": {