@songsid/agend 2.1.4-beta.53 → 2.1.4-beta.54

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.
@@ -700,7 +700,17 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
700
700
  notifyFleetError(text: string): void;
701
701
  private static readonly FLEET_ERROR_THROTTLE_MS;
702
702
  private fleetErrorNotices;
703
- notifyInstanceTopic(instanceName: string, text: string, extraOpts?: import("./channel/types.js").SendOpts): void;
703
+ /**
704
+ * Post into an instance's topic. Returns whether a send was DISPATCHED — a
705
+ * target was resolved and sendText was called — so a caller that must not
706
+ * lose the message (notifyFleetError, which spends a throttle key) can tell
707
+ * that from silence. Existing callers ignore the result and are unaffected.
708
+ *
709
+ * Not a delivery guarantee: sendText is fire-and-forget, and a platform-side
710
+ * rejection surfaces only as a warn in its .catch. True delivery confirmation
711
+ * would have to make this async and change every caller.
712
+ */
713
+ notifyInstanceTopic(instanceName: string, text: string, extraOpts?: import("./channel/types.js").SendOpts): boolean;
704
714
  /**
705
715
  * Post decision buttons whose callback ids are `<prefix><nonce>:<action>`.
706
716
  * The entry is registered before the send and rolled back if the send
@@ -5441,14 +5441,6 @@ export class FleetManager {
5441
5441
  return;
5442
5442
  }
5443
5443
  const suppressed = seen?.suppressed ?? 0;
5444
- this.fleetErrorNotices.set(key, { at: now, suppressed: 0 });
5445
- // Bound the map: it is keyed by message text, and a message with a varying
5446
- // suffix (a path, an id) would otherwise grow it without limit.
5447
- if (this.fleetErrorNotices.size > 100) {
5448
- const oldest = this.fleetErrorNotices.keys().next().value;
5449
- if (oldest !== undefined)
5450
- this.fleetErrorNotices.delete(oldest);
5451
- }
5452
5444
  const body = suppressed > 0
5453
5445
  ? `${text}\n${t("fleet.error_suppressed", suppressed, Math.round(FleetManager.FLEET_ERROR_THROTTLE_MS / 60_000))}`
5454
5446
  : text;
@@ -5458,26 +5450,60 @@ export class FleetManager {
5458
5450
  // topic_id to post into it.
5459
5451
  const general = Object.entries(this.fleetConfig?.instances ?? {})
5460
5452
  .find(([, config]) => config.general_topic === true)?.[0];
5453
+ // "dispatched", not "delivered": see notifyInstanceTopic. A platform that
5454
+ // rejects the message afterwards still consumes the throttle window, which
5455
+ // is the pre-existing semantic and not something this change alters.
5456
+ let dispatched = false;
5461
5457
  if (general) {
5462
- this.notifyInstanceTopic(general, body);
5463
- return;
5458
+ dispatched = this.notifyInstanceTopic(general, body);
5464
5459
  }
5465
- // No General instance — fall back to the primary channel's group.
5466
- const channelCfg = this.getChannelConfig();
5467
- const groupId = channelCfg?.group_id;
5468
- if (this.adapter && groupId) {
5469
- this.adapter.sendText(String(groupId), body)
5470
- .catch(err => this.logger.warn({ err }, "Failed to send fleet error notification"));
5460
+ else {
5461
+ // No General instance — fall back to the primary channel's group.
5462
+ const groupId = this.getChannelConfig()?.group_id;
5463
+ if (this.adapter && groupId) {
5464
+ this.adapter.sendText(String(groupId), body)
5465
+ .catch(err => this.logger.warn({ err }, "Failed to send fleet error notification"));
5466
+ dispatched = true;
5467
+ }
5468
+ }
5469
+ if (!dispatched) {
5470
+ // Do NOT record the throttle key. It used to be claimed before delivery
5471
+ // was attempted, so a message nobody could receive — every fleet error
5472
+ // raised before adapters exist, which is when startup faults happen —
5473
+ // also suppressed the next ten minutes of identical ones. Leaving the key
5474
+ // unspent lets the next caller, once an adapter is up, actually get
5475
+ // through. The counter is not reset either: nothing was shown.
5476
+ this.logger.warn({ text: body }, "Fleet error could not be delivered (no adapter or no target yet) — not consuming the throttle window");
5471
5477
  return;
5472
5478
  }
5473
- this.logger.warn({ text: body }, "Fleet error had no notification target (no General instance, no adapter)");
5479
+ this.fleetErrorNotices.set(key, { at: now, suppressed: 0 });
5480
+ // Bound the map: it is keyed by message text, and a message with a varying
5481
+ // suffix (a path, an id) would otherwise grow it without limit.
5482
+ if (this.fleetErrorNotices.size > 100) {
5483
+ const oldest = this.fleetErrorNotices.keys().next().value;
5484
+ if (oldest !== undefined)
5485
+ this.fleetErrorNotices.delete(oldest);
5486
+ }
5474
5487
  }
5475
5488
  static FLEET_ERROR_THROTTLE_MS = 10 * 60_000;
5476
5489
  fleetErrorNotices = new Map();
5490
+ /**
5491
+ * Post into an instance's topic. Returns whether a send was DISPATCHED — a
5492
+ * target was resolved and sendText was called — so a caller that must not
5493
+ * lose the message (notifyFleetError, which spends a throttle key) can tell
5494
+ * that from silence. Existing callers ignore the result and are unaffected.
5495
+ *
5496
+ * Not a delivery guarantee: sendText is fire-and-forget, and a platform-side
5497
+ * rejection surfaces only as a warn in its .catch. True delivery confirmation
5498
+ * would have to make this async and change every caller.
5499
+ */
5477
5500
  notifyInstanceTopic(instanceName, text, extraOpts) {
5478
5501
  const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
5479
- if (!adapter)
5480
- return;
5502
+ if (!adapter) {
5503
+ // Early startup: adapters are created well after the fleet object exists.
5504
+ this.logger.warn({ instanceName }, "No adapter yet — instance topic notification not sent");
5505
+ return false;
5506
+ }
5481
5507
  const channelCfg = this.getChannelConfig(this.getInstanceAdapterId(instanceName));
5482
5508
  const groupId = channelCfg?.group_id;
5483
5509
  // Fleet topic instance
@@ -5485,20 +5511,23 @@ export class FleetManager {
5485
5511
  if (threadId != null && groupId) {
5486
5512
  adapter.sendText(String(groupId), text, { threadId: String(threadId), ...extraOpts })
5487
5513
  .catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send instance topic notification"));
5488
- return;
5514
+ return true;
5489
5515
  }
5490
5516
  // Classic instance: find its channelId from the classic manager
5491
5517
  const classicChatId = this.classicChannels?.getChannelIdByInstance(instanceName);
5492
5518
  if (classicChatId) {
5493
5519
  adapter.sendText(classicChatId, text, extraOpts)
5494
5520
  .catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send classic notification"));
5495
- return;
5521
+ return true;
5496
5522
  }
5497
5523
  // Fallback: send to group without threadId
5498
5524
  if (groupId) {
5499
5525
  adapter.sendText(String(groupId), text, extraOpts)
5500
5526
  .catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send notification (no topic)"));
5527
+ return true;
5501
5528
  }
5529
+ this.logger.warn({ instanceName }, "No group id — instance topic notification not sent");
5530
+ return false;
5502
5531
  }
5503
5532
  // ── Nonce-armed button prompts (hang / assist / exit / clear) ──
5504
5533
  //