@gethmy/agent 1.11.2 → 1.12.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 +80 -32
  2. package/dist/index.js +80 -32
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -390,7 +390,7 @@ var init_types = __esm(() => {
390
390
  pickupColumns: ["To Do"],
391
391
  priorityLabels: { urgent: 100, critical: 90, bug: 50 },
392
392
  columnBoost: true,
393
- runner: "cli",
393
+ runner: "sdk",
394
394
  completion: {
395
395
  createPR: false,
396
396
  moveToColumn: "Review",
@@ -565,7 +565,7 @@ function loadDaemonConfig() {
565
565
  }
566
566
  };
567
567
  if (agent.runner !== "cli" && agent.runner !== "sdk") {
568
- agent.runner = "cli";
568
+ agent.runner = DEFAULT_AGENT_CONFIG.runner;
569
569
  }
570
570
  return {
571
571
  apiKey,
@@ -6227,6 +6227,15 @@ class Worker {
6227
6227
  ...this.runLedger()
6228
6228
  });
6229
6229
  } catch {}
6230
+ const stopCost = this.lastSessionStats?.cost;
6231
+ if (stopCost) {
6232
+ const cents = Math.round(stopCost.totalCostUsd * 100);
6233
+ if (cents > 0) {
6234
+ try {
6235
+ await this.stateStore.addCost(card.id, cents);
6236
+ } catch {}
6237
+ }
6238
+ }
6230
6239
  } else if (this.runId && this.verificationFailed) {
6231
6240
  try {
6232
6241
  await this.stateStore.endRun(this.runId, "paused", {
@@ -6709,7 +6718,7 @@ class Worker {
6709
6718
  clearTimeout(this.timeoutTimer);
6710
6719
  this.timeoutTimer = null;
6711
6720
  }
6712
- if (this.worktreePath && (this.state === "error" || this.timedOut)) {
6721
+ if (this.worktreePath && (this.state === "error" || this.timedOut || this.aborted)) {
6713
6722
  try {
6714
6723
  cleanupWorktree(this.worktreePath, this.branchName ?? undefined);
6715
6724
  } catch {
@@ -7681,6 +7690,10 @@ class Watcher {
7681
7690
  readyPromise = new Promise((resolve3) => {
7682
7691
  this.readyResolve = resolve3;
7683
7692
  });
7693
+ stopping = false;
7694
+ reconnectTimer = null;
7695
+ reconnectAttempts = 0;
7696
+ broadcastGen = 0;
7684
7697
  get isConnected() {
7685
7698
  return this.connected;
7686
7699
  }
@@ -7709,7 +7722,34 @@ class Watcher {
7709
7722
  }
7710
7723
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
7711
7724
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
7712
- const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
7725
+ this.subscribeBroadcast();
7726
+ presenceChannel.on("presence", { event: "sync" }, () => {
7727
+ log.debug(TAG32, "Presence sync");
7728
+ }).subscribe(async (status) => {
7729
+ if (status === "SUBSCRIBED") {
7730
+ await presenceChannel.track({
7731
+ daemonId: this.daemonId,
7732
+ startedAt: new Date().toISOString(),
7733
+ userId: this.identity.userId,
7734
+ agentId: this.identity.agentId,
7735
+ userEmail: this.identity.userEmail,
7736
+ agentIdentifier: this.identity.agentIdentifier,
7737
+ agentName: this.identity.agentName
7738
+ });
7739
+ if (!isPretty() || !this.suppressStartupLogs) {
7740
+ log.info(TAG32, "Presence tracked on board-presence channel");
7741
+ }
7742
+ this.presenceTracked = true;
7743
+ this.maybeResolveReady();
7744
+ }
7745
+ });
7746
+ this.presenceChannel = presenceChannel;
7747
+ }
7748
+ subscribeBroadcast() {
7749
+ if (!this.supabase)
7750
+ return;
7751
+ const gen = ++this.broadcastGen;
7752
+ this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
7713
7753
  log.debug(TAG32, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
7714
7754
  this.onCardBroadcast({
7715
7755
  event: "card_update",
@@ -7730,46 +7770,54 @@ class Watcher {
7730
7770
  this.onAgentCommand?.({ cardId, command });
7731
7771
  }
7732
7772
  }).subscribe((status) => {
7773
+ if (gen !== this.broadcastGen)
7774
+ return;
7733
7775
  if (status === "SUBSCRIBED") {
7734
7776
  this.connected = true;
7777
+ this.reconnectAttempts = 0;
7735
7778
  if (!isPretty() || !this.suppressStartupLogs) {
7736
7779
  log.info(TAG32, "Broadcast subscription active");
7737
7780
  }
7738
7781
  this.maybeResolveReady();
7739
- } else if (status === "CHANNEL_ERROR") {
7740
- this.connected = false;
7741
- log.error(TAG32, "Broadcast channel error — will rely on reconciliation");
7742
- } else if (status === "TIMED_OUT") {
7743
- this.connected = false;
7744
- log.warn(TAG32, "Broadcast subscription timed out — retrying...");
7745
- } else if (status === "CLOSED") {
7782
+ } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
7746
7783
  this.connected = false;
7747
- }
7748
- });
7749
- this.channel = channel;
7750
- presenceChannel.on("presence", { event: "sync" }, () => {
7751
- log.debug(TAG32, "Presence sync");
7752
- }).subscribe(async (status) => {
7753
- if (status === "SUBSCRIBED") {
7754
- await presenceChannel.track({
7755
- daemonId: this.daemonId,
7756
- startedAt: new Date().toISOString(),
7757
- userId: this.identity.userId,
7758
- agentId: this.identity.agentId,
7759
- userEmail: this.identity.userEmail,
7760
- agentIdentifier: this.identity.agentIdentifier,
7761
- agentName: this.identity.agentName
7762
- });
7763
- if (!isPretty() || !this.suppressStartupLogs) {
7764
- log.info(TAG32, "Presence tracked on board-presence channel");
7784
+ if (!this.stopping) {
7785
+ log.warn(TAG32, `Broadcast subscription ${status} — scheduling reconnect`);
7786
+ this.scheduleReconnect();
7765
7787
  }
7766
- this.presenceTracked = true;
7767
- this.maybeResolveReady();
7768
7788
  }
7769
7789
  });
7770
- this.presenceChannel = presenceChannel;
7790
+ }
7791
+ scheduleReconnect() {
7792
+ if (this.stopping || this.reconnectTimer)
7793
+ return;
7794
+ const delay = Math.min(30000, 1000 * 2 ** this.reconnectAttempts);
7795
+ this.reconnectAttempts++;
7796
+ this.reconnectTimer = setTimeout(() => {
7797
+ this.reconnectTimer = null;
7798
+ this.reconnectBroadcast();
7799
+ }, delay);
7800
+ }
7801
+ async reconnectBroadcast() {
7802
+ if (this.stopping || !this.supabase)
7803
+ return;
7804
+ log.warn(TAG32, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
7805
+ if (this.channel) {
7806
+ const old = this.channel;
7807
+ this.channel = null;
7808
+ this.broadcastGen++;
7809
+ try {
7810
+ await this.supabase.removeChannel(old);
7811
+ } catch {}
7812
+ }
7813
+ this.subscribeBroadcast();
7771
7814
  }
7772
7815
  async stop() {
7816
+ this.stopping = true;
7817
+ if (this.reconnectTimer) {
7818
+ clearTimeout(this.reconnectTimer);
7819
+ this.reconnectTimer = null;
7820
+ }
7773
7821
  if (this.presenceChannel) {
7774
7822
  await this.supabase?.removeChannel(this.presenceChannel);
7775
7823
  this.presenceChannel = null;
package/dist/index.js CHANGED
@@ -389,7 +389,7 @@ var init_types = __esm(() => {
389
389
  pickupColumns: ["To Do"],
390
390
  priorityLabels: { urgent: 100, critical: 90, bug: 50 },
391
391
  columnBoost: true,
392
- runner: "cli",
392
+ runner: "sdk",
393
393
  completion: {
394
394
  createPR: false,
395
395
  moveToColumn: "Review",
@@ -564,7 +564,7 @@ function loadDaemonConfig() {
564
564
  }
565
565
  };
566
566
  if (agent.runner !== "cli" && agent.runner !== "sdk") {
567
- agent.runner = "cli";
567
+ agent.runner = DEFAULT_AGENT_CONFIG.runner;
568
568
  }
569
569
  return {
570
570
  apiKey,
@@ -6226,6 +6226,15 @@ class Worker {
6226
6226
  ...this.runLedger()
6227
6227
  });
6228
6228
  } catch {}
6229
+ const stopCost = this.lastSessionStats?.cost;
6230
+ if (stopCost) {
6231
+ const cents = Math.round(stopCost.totalCostUsd * 100);
6232
+ if (cents > 0) {
6233
+ try {
6234
+ await this.stateStore.addCost(card.id, cents);
6235
+ } catch {}
6236
+ }
6237
+ }
6229
6238
  } else if (this.runId && this.verificationFailed) {
6230
6239
  try {
6231
6240
  await this.stateStore.endRun(this.runId, "paused", {
@@ -6708,7 +6717,7 @@ class Worker {
6708
6717
  clearTimeout(this.timeoutTimer);
6709
6718
  this.timeoutTimer = null;
6710
6719
  }
6711
- if (this.worktreePath && (this.state === "error" || this.timedOut)) {
6720
+ if (this.worktreePath && (this.state === "error" || this.timedOut || this.aborted)) {
6712
6721
  try {
6713
6722
  cleanupWorktree(this.worktreePath, this.branchName ?? undefined);
6714
6723
  } catch {
@@ -7680,6 +7689,10 @@ class Watcher {
7680
7689
  readyPromise = new Promise((resolve3) => {
7681
7690
  this.readyResolve = resolve3;
7682
7691
  });
7692
+ stopping = false;
7693
+ reconnectTimer = null;
7694
+ reconnectAttempts = 0;
7695
+ broadcastGen = 0;
7683
7696
  get isConnected() {
7684
7697
  return this.connected;
7685
7698
  }
@@ -7708,7 +7721,34 @@ class Watcher {
7708
7721
  }
7709
7722
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
7710
7723
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
7711
- const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
7724
+ this.subscribeBroadcast();
7725
+ presenceChannel.on("presence", { event: "sync" }, () => {
7726
+ log.debug(TAG32, "Presence sync");
7727
+ }).subscribe(async (status) => {
7728
+ if (status === "SUBSCRIBED") {
7729
+ await presenceChannel.track({
7730
+ daemonId: this.daemonId,
7731
+ startedAt: new Date().toISOString(),
7732
+ userId: this.identity.userId,
7733
+ agentId: this.identity.agentId,
7734
+ userEmail: this.identity.userEmail,
7735
+ agentIdentifier: this.identity.agentIdentifier,
7736
+ agentName: this.identity.agentName
7737
+ });
7738
+ if (!isPretty() || !this.suppressStartupLogs) {
7739
+ log.info(TAG32, "Presence tracked on board-presence channel");
7740
+ }
7741
+ this.presenceTracked = true;
7742
+ this.maybeResolveReady();
7743
+ }
7744
+ });
7745
+ this.presenceChannel = presenceChannel;
7746
+ }
7747
+ subscribeBroadcast() {
7748
+ if (!this.supabase)
7749
+ return;
7750
+ const gen = ++this.broadcastGen;
7751
+ this.channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
7712
7752
  log.debug(TAG32, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
7713
7753
  this.onCardBroadcast({
7714
7754
  event: "card_update",
@@ -7729,46 +7769,54 @@ class Watcher {
7729
7769
  this.onAgentCommand?.({ cardId, command });
7730
7770
  }
7731
7771
  }).subscribe((status) => {
7772
+ if (gen !== this.broadcastGen)
7773
+ return;
7732
7774
  if (status === "SUBSCRIBED") {
7733
7775
  this.connected = true;
7776
+ this.reconnectAttempts = 0;
7734
7777
  if (!isPretty() || !this.suppressStartupLogs) {
7735
7778
  log.info(TAG32, "Broadcast subscription active");
7736
7779
  }
7737
7780
  this.maybeResolveReady();
7738
- } else if (status === "CHANNEL_ERROR") {
7739
- this.connected = false;
7740
- log.error(TAG32, "Broadcast channel error — will rely on reconciliation");
7741
- } else if (status === "TIMED_OUT") {
7742
- this.connected = false;
7743
- log.warn(TAG32, "Broadcast subscription timed out — retrying...");
7744
- } else if (status === "CLOSED") {
7781
+ } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
7745
7782
  this.connected = false;
7746
- }
7747
- });
7748
- this.channel = channel;
7749
- presenceChannel.on("presence", { event: "sync" }, () => {
7750
- log.debug(TAG32, "Presence sync");
7751
- }).subscribe(async (status) => {
7752
- if (status === "SUBSCRIBED") {
7753
- await presenceChannel.track({
7754
- daemonId: this.daemonId,
7755
- startedAt: new Date().toISOString(),
7756
- userId: this.identity.userId,
7757
- agentId: this.identity.agentId,
7758
- userEmail: this.identity.userEmail,
7759
- agentIdentifier: this.identity.agentIdentifier,
7760
- agentName: this.identity.agentName
7761
- });
7762
- if (!isPretty() || !this.suppressStartupLogs) {
7763
- log.info(TAG32, "Presence tracked on board-presence channel");
7783
+ if (!this.stopping) {
7784
+ log.warn(TAG32, `Broadcast subscription ${status} — scheduling reconnect`);
7785
+ this.scheduleReconnect();
7764
7786
  }
7765
- this.presenceTracked = true;
7766
- this.maybeResolveReady();
7767
7787
  }
7768
7788
  });
7769
- this.presenceChannel = presenceChannel;
7789
+ }
7790
+ scheduleReconnect() {
7791
+ if (this.stopping || this.reconnectTimer)
7792
+ return;
7793
+ const delay = Math.min(30000, 1000 * 2 ** this.reconnectAttempts);
7794
+ this.reconnectAttempts++;
7795
+ this.reconnectTimer = setTimeout(() => {
7796
+ this.reconnectTimer = null;
7797
+ this.reconnectBroadcast();
7798
+ }, delay);
7799
+ }
7800
+ async reconnectBroadcast() {
7801
+ if (this.stopping || !this.supabase)
7802
+ return;
7803
+ log.warn(TAG32, `Reconnecting broadcast subscription (attempt ${this.reconnectAttempts})`);
7804
+ if (this.channel) {
7805
+ const old = this.channel;
7806
+ this.channel = null;
7807
+ this.broadcastGen++;
7808
+ try {
7809
+ await this.supabase.removeChannel(old);
7810
+ } catch {}
7811
+ }
7812
+ this.subscribeBroadcast();
7770
7813
  }
7771
7814
  async stop() {
7815
+ this.stopping = true;
7816
+ if (this.reconnectTimer) {
7817
+ clearTimeout(this.reconnectTimer);
7818
+ this.reconnectTimer = null;
7819
+ }
7772
7820
  if (this.presenceChannel) {
7773
7821
  await this.supabase?.removeChannel(this.presenceChannel);
7774
7822
  this.presenceChannel = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/agent",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
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",