@gethmy/agent 1.14.1 → 1.14.3

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
@@ -2311,6 +2311,12 @@ class MergeMonitor {
2311
2311
  log.warn(TAG7, `Failed to update card: ${err instanceof Error ? err.message : err}`);
2312
2312
  }
2313
2313
  }
2314
+ try {
2315
+ await this.client.updateCard(card.id, { assignedAgentId: null });
2316
+ log.info(TAG7, `Cleared agent assignment on #${card.short_id}`);
2317
+ } catch (err) {
2318
+ log.warn(TAG7, `Failed to clear agent assignment on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2319
+ }
2314
2320
  const branchName = extractBranchFromDescription(card.description);
2315
2321
  if (branchName) {
2316
2322
  try {
@@ -4604,7 +4610,7 @@ class ProgressTracker {
4604
4610
  this.transitionTo("testing");
4605
4611
  }
4606
4612
  } else if (name === "mcp__harmony__harmony_end_agent_session") {
4607
- this.transitionTo("finishing");
4613
+ this.enterFinishingAndStop();
4608
4614
  }
4609
4615
  if (name === "mcp__harmony__harmony_toggle_subtask" && this.subtaskMode) {
4610
4616
  const val = this.extractString(input, "completed");
@@ -4663,6 +4669,14 @@ class ProgressTracker {
4663
4669
  this.lastAction = "";
4664
4670
  this.scheduleUpdate(PHASES[newPhase].label);
4665
4671
  }
4672
+ enterFinishingAndStop() {
4673
+ if (PHASE_ORDER.finishing > PHASE_ORDER[this.phase]) {
4674
+ this.runEventSink?.recordPhaseChanged("finishing", this.phase);
4675
+ this.phase = "finishing";
4676
+ this.progress = Math.max(this.progress, PHASES.finishing.min);
4677
+ }
4678
+ this.stop();
4679
+ }
4666
4680
  incrementProgress() {
4667
4681
  const config = PHASES[this.phase];
4668
4682
  const range = config.max - config.min;
@@ -5837,6 +5851,11 @@ async function runTransition(client, card, plan, opts = {}) {
5837
5851
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
5838
5852
  log.info(TAG22, `#${shortId} session ended (${plan.endSession.status})`);
5839
5853
  }
5854
+ if (plan.assignAgent !== undefined) {
5855
+ const assignedAgentId = plan.assignAgent;
5856
+ await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
5857
+ log.info(TAG22, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
5858
+ }
5840
5859
  if (opts.store && opts.runId) {
5841
5860
  try {
5842
5861
  await opts.store.heartbeat(opts.runId);
@@ -6972,7 +6991,8 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
6972
6991
  });
6973
6992
  await runTransition(deps.client, card, {
6974
6993
  move: { columnName: toColumn },
6975
- addLabels: [{ name: AGENT_LABEL }]
6994
+ addLabels: [{ name: AGENT_LABEL }],
6995
+ ...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
6976
6996
  }, { store: deps.stateStore, runId: deps.runId });
6977
6997
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
6978
6998
  log.info(TAG28, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
@@ -7001,7 +7021,11 @@ async function handleGateUnmet(card, stage, summary, deps) {
7001
7021
  try {
7002
7022
  await deps.client.addComment(card.id, `Stage gate unmet — re-running "${stage.name}". ${summary}.`, { commentType: "progress" });
7003
7023
  } catch {}
7004
- await runTransition(deps.client, card, { move: { columnName: toColumn }, addLabels: [{ name: AGENT_LABEL }] }, { store: deps.stateStore, runId: deps.runId });
7024
+ await runTransition(deps.client, card, {
7025
+ move: { columnName: toColumn },
7026
+ addLabels: [{ name: AGENT_LABEL }],
7027
+ ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
7028
+ }, { store: deps.stateStore, runId: deps.runId });
7005
7029
  log.info(TAG28, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
7006
7030
  return { kind: "requeued_gate_unmet", toColumn };
7007
7031
  }
@@ -7101,6 +7125,7 @@ class Worker {
7101
7125
  timedOut = false;
7102
7126
  verificationFailed = false;
7103
7127
  held = false;
7128
+ completionStarted = false;
7104
7129
  sessionId = null;
7105
7130
  runId = null;
7106
7131
  cliSessionId = null;
@@ -7172,6 +7197,7 @@ class Worker {
7172
7197
  this.timedOut = false;
7173
7198
  this.verificationFailed = false;
7174
7199
  this.held = false;
7200
+ this.completionStarted = false;
7175
7201
  this.runCostCents = 0;
7176
7202
  this.runTurns = 0;
7177
7203
  this.cliSessionId = null;
@@ -7311,6 +7337,7 @@ class Worker {
7311
7337
  progressPercent: 75
7312
7338
  });
7313
7339
  this.state = "completing";
7340
+ this.completionStarted = true;
7314
7341
  await this.recordPhase("completing");
7315
7342
  let stageGateEvaluation = null;
7316
7343
  const stageRun = stageCtx.kind === "run" ? stageCtx : null;
@@ -7662,6 +7689,7 @@ class Worker {
7662
7689
  return await advanceStageOnGate(card, stage, stageIndex, def, evaluation, {
7663
7690
  client: this.client,
7664
7691
  stateStore: this.stateStore,
7692
+ agentId: this.agentId,
7665
7693
  maxAttempts: this.config.budget.maxAttemptsPerCard,
7666
7694
  fallbackColumn: this.config.pickupColumns[0] ?? "To Do",
7667
7695
  sink: this.cliRunner,
@@ -7764,7 +7792,7 @@ class Worker {
7764
7792
  sigtermTimeoutMs: CANCEL_SIGTERM_TIMEOUT2
7765
7793
  });
7766
7794
  }
7767
- if (this.cardId && !this.timedOut) {
7795
+ if (this.cardId && !this.timedOut && !this.completionStarted) {
7768
7796
  try {
7769
7797
  const stats = this.lastSessionStats ?? this.progressTracker?.stats;
7770
7798
  await this.client.endAgentSession(this.cardId, {
package/dist/index.js CHANGED
@@ -2310,6 +2310,12 @@ class MergeMonitor {
2310
2310
  log.warn(TAG7, `Failed to update card: ${err instanceof Error ? err.message : err}`);
2311
2311
  }
2312
2312
  }
2313
+ try {
2314
+ await this.client.updateCard(card.id, { assignedAgentId: null });
2315
+ log.info(TAG7, `Cleared agent assignment on #${card.short_id}`);
2316
+ } catch (err) {
2317
+ log.warn(TAG7, `Failed to clear agent assignment on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2318
+ }
2313
2319
  const branchName = extractBranchFromDescription(card.description);
2314
2320
  if (branchName) {
2315
2321
  try {
@@ -4603,7 +4609,7 @@ class ProgressTracker {
4603
4609
  this.transitionTo("testing");
4604
4610
  }
4605
4611
  } else if (name === "mcp__harmony__harmony_end_agent_session") {
4606
- this.transitionTo("finishing");
4612
+ this.enterFinishingAndStop();
4607
4613
  }
4608
4614
  if (name === "mcp__harmony__harmony_toggle_subtask" && this.subtaskMode) {
4609
4615
  const val = this.extractString(input, "completed");
@@ -4662,6 +4668,14 @@ class ProgressTracker {
4662
4668
  this.lastAction = "";
4663
4669
  this.scheduleUpdate(PHASES[newPhase].label);
4664
4670
  }
4671
+ enterFinishingAndStop() {
4672
+ if (PHASE_ORDER.finishing > PHASE_ORDER[this.phase]) {
4673
+ this.runEventSink?.recordPhaseChanged("finishing", this.phase);
4674
+ this.phase = "finishing";
4675
+ this.progress = Math.max(this.progress, PHASES.finishing.min);
4676
+ }
4677
+ this.stop();
4678
+ }
4665
4679
  incrementProgress() {
4666
4680
  const config = PHASES[this.phase];
4667
4681
  const range = config.max - config.min;
@@ -5836,6 +5850,11 @@ async function runTransition(client, card, plan, opts = {}) {
5836
5850
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
5837
5851
  log.info(TAG22, `#${shortId} session ended (${plan.endSession.status})`);
5838
5852
  }
5853
+ if (plan.assignAgent !== undefined) {
5854
+ const assignedAgentId = plan.assignAgent;
5855
+ await withRetry("assignAgent", shortId, () => client.updateCard(card.id, { assignedAgentId }), attempts, backoffMs);
5856
+ log.info(TAG22, assignedAgentId ? `#${shortId} assigned → agent ${assignedAgentId}` : `#${shortId} unassigned`);
5857
+ }
5839
5858
  if (opts.store && opts.runId) {
5840
5859
  try {
5841
5860
  await opts.store.heartbeat(opts.runId);
@@ -6971,7 +6990,8 @@ async function advanceStageOnGate(card, stage, stageIndex, def, evaluation, deps
6971
6990
  });
6972
6991
  await runTransition(deps.client, card, {
6973
6992
  move: { columnName: toColumn },
6974
- addLabels: [{ name: AGENT_LABEL }]
6993
+ addLabels: [{ name: AGENT_LABEL }],
6994
+ ...isAgentRunnableOwner(next.stage.owner) ? { assignAgent: deps.agentId } : {}
6975
6995
  }, { store: deps.stateStore, runId: deps.runId });
6976
6996
  deps.stateStore.recordOutcome(card.id, "success").catch(() => {});
6977
6997
  log.info(TAG28, `#${card.short_id} advanced "${stage.name}" → "${next.stage.name}" (column "${toColumn}")`);
@@ -7000,7 +7020,11 @@ async function handleGateUnmet(card, stage, summary, deps) {
7000
7020
  try {
7001
7021
  await deps.client.addComment(card.id, `Stage gate unmet — re-running "${stage.name}". ${summary}.`, { commentType: "progress" });
7002
7022
  } catch {}
7003
- await runTransition(deps.client, card, { move: { columnName: toColumn }, addLabels: [{ name: AGENT_LABEL }] }, { store: deps.stateStore, runId: deps.runId });
7023
+ await runTransition(deps.client, card, {
7024
+ move: { columnName: toColumn },
7025
+ addLabels: [{ name: AGENT_LABEL }],
7026
+ ...isAgentRunnableOwner(stage.owner) ? { assignAgent: deps.agentId } : {}
7027
+ }, { store: deps.stateStore, runId: deps.runId });
7004
7028
  log.info(TAG28, `#${card.short_id} gate unmet for "${stage.name}" — requeued to "${toColumn}" for re-run (attempt ${attempts}/${deps.maxAttempts})`);
7005
7029
  return { kind: "requeued_gate_unmet", toColumn };
7006
7030
  }
@@ -7100,6 +7124,7 @@ class Worker {
7100
7124
  timedOut = false;
7101
7125
  verificationFailed = false;
7102
7126
  held = false;
7127
+ completionStarted = false;
7103
7128
  sessionId = null;
7104
7129
  runId = null;
7105
7130
  cliSessionId = null;
@@ -7171,6 +7196,7 @@ class Worker {
7171
7196
  this.timedOut = false;
7172
7197
  this.verificationFailed = false;
7173
7198
  this.held = false;
7199
+ this.completionStarted = false;
7174
7200
  this.runCostCents = 0;
7175
7201
  this.runTurns = 0;
7176
7202
  this.cliSessionId = null;
@@ -7310,6 +7336,7 @@ class Worker {
7310
7336
  progressPercent: 75
7311
7337
  });
7312
7338
  this.state = "completing";
7339
+ this.completionStarted = true;
7313
7340
  await this.recordPhase("completing");
7314
7341
  let stageGateEvaluation = null;
7315
7342
  const stageRun = stageCtx.kind === "run" ? stageCtx : null;
@@ -7661,6 +7688,7 @@ class Worker {
7661
7688
  return await advanceStageOnGate(card, stage, stageIndex, def, evaluation, {
7662
7689
  client: this.client,
7663
7690
  stateStore: this.stateStore,
7691
+ agentId: this.agentId,
7664
7692
  maxAttempts: this.config.budget.maxAttemptsPerCard,
7665
7693
  fallbackColumn: this.config.pickupColumns[0] ?? "To Do",
7666
7694
  sink: this.cliRunner,
@@ -7763,7 +7791,7 @@ class Worker {
7763
7791
  sigtermTimeoutMs: CANCEL_SIGTERM_TIMEOUT2
7764
7792
  });
7765
7793
  }
7766
- if (this.cardId && !this.timedOut) {
7794
+ if (this.cardId && !this.timedOut && !this.completionStarted) {
7767
7795
  try {
7768
7796
  const stats = this.lastSessionStats ?? this.progressTracker?.stats;
7769
7797
  await this.client.endAgentSession(this.cardId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/agent",
3
- "version": "1.14.1",
3
+ "version": "1.14.3",
4
4
  "description": "Push-based agent daemon for Harmony — watches board assignments and spawns Claude CLI workers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",