@songsid/agend 2.3.0-beta.11 → 2.3.0-beta.12

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 (48) hide show
  1. package/dist/agent-whats-new.js +14 -9
  2. package/dist/agent-whats-new.js.map +1 -1
  3. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  4. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  5. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  6. package/dist/channel/adapters/discord.d.ts +2 -0
  7. package/dist/channel/adapters/discord.js +14 -4
  8. package/dist/channel/adapters/discord.js.map +1 -1
  9. package/dist/cli.js +12 -79
  10. package/dist/cli.js.map +1 -1
  11. package/dist/daemon.d.ts +3 -0
  12. package/dist/daemon.js +25 -0
  13. package/dist/daemon.js.map +1 -1
  14. package/dist/fleet-manager.d.ts +8 -1
  15. package/dist/fleet-manager.js +101 -33
  16. package/dist/fleet-manager.js.map +1 -1
  17. package/dist/fleet-process-handlers.d.ts +18 -0
  18. package/dist/fleet-process-handlers.js +69 -0
  19. package/dist/fleet-process-handlers.js.map +1 -0
  20. package/dist/general-knowledge/skills/web-appearance/SKILL.md +1 -1
  21. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +4 -0
  22. package/dist/instance-lifecycle.d.ts +2 -0
  23. package/dist/instance-lifecycle.js +11 -6
  24. package/dist/instance-lifecycle.js.map +1 -1
  25. package/dist/outbound-schemas.js +1 -1
  26. package/dist/outbound-schemas.js.map +1 -1
  27. package/dist/settings-api.d.ts +4 -0
  28. package/dist/settings-api.js +23 -0
  29. package/dist/settings-api.js.map +1 -1
  30. package/dist/ui/app-effects.js +13 -1
  31. package/dist/ui/chat-store.js +32 -10
  32. package/dist/ui/chat-strings.js +16 -0
  33. package/dist/ui/chat-thread.js +5 -2
  34. package/dist/ui/panel-chat.js +24 -3
  35. package/dist/ui/panel-settings.js +24 -0
  36. package/dist/ui/settings-strings.js +2 -0
  37. package/dist/ui/shared/effect-owner-source.js +8 -1
  38. package/dist/ui/shared/effect-registry.js +10 -2
  39. package/dist/ui/shared/owner-css.js +30 -1
  40. package/dist/web-api.d.ts +6 -2
  41. package/dist/web-api.js +50 -14
  42. package/dist/web-api.js.map +1 -1
  43. package/dist/web-owner-css-store.d.ts +43 -1
  44. package/dist/web-owner-css-store.js +65 -9
  45. package/dist/web-owner-css-store.js.map +1 -1
  46. package/dist/web-settings-tools.js +7 -4
  47. package/dist/web-settings-tools.js.map +1 -1
  48. package/package.json +1 -1
@@ -411,6 +411,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
411
411
  /** #2175: this boot's progress message, whose completion may still be owed by its background retry. */
412
412
  private startupProgress;
413
413
  private adapterState;
414
+ private adapterRetryInFlight;
414
415
  /** Web Settings secret rotation is deliberately separate from reconnect
415
416
  * recovery: a rotation must build a fresh provider client with the new token,
416
417
  * and stale callbacks from the old client must not win. */
@@ -1683,8 +1684,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1683
1684
  private sweepOrphanedWebUploads;
1684
1685
  /** Start the shared channel adapter(s) for topic mode */
1685
1686
  private startSharedAdapter;
1686
- /** Exponential backoff retry for a single failed adapter (background, non-blocking). */
1687
+ /** Keep trying while the fleet runs, with a five-minute cap even after a long outage. */
1687
1688
  private scheduleAdapterRetry;
1689
+ /** One login per connection, shared by the background timer and the operator's Retry now. */
1690
+ private attemptAdapterRetry;
1691
+ retryAdapterNow(adapterId: string): {
1692
+ queued: boolean;
1693
+ already_running?: boolean;
1694
+ };
1688
1695
  /** Notify admin about adapter failure (uses any available adapter). */
1689
1696
  private notifyAdapterFailure;
1690
1697
  /** Notify admin that a retried adapter reconnected. */
@@ -784,6 +784,7 @@ export class FleetManager {
784
784
  /** #2175: this boot's progress message, whose completion may still be owed by its background retry. */
785
785
  startupProgress = null;
786
786
  adapterState = new Map();
787
+ adapterRetryInFlight = new Map();
787
788
  /** Web Settings secret rotation is deliberately separate from reconnect
788
789
  * recovery: a rotation must build a fresh provider client with the new token,
789
790
  * and stale callbacks from the old client must not win. */
@@ -7537,48 +7538,109 @@ export class FleetManager {
7537
7538
  const err = results[i].reason;
7538
7539
  this.logger.error({ adapterId, err: err?.message ?? err }, "Adapter startup failed — scheduling background retry");
7539
7540
  this.adapterState.set(adapterId, { status: "retrying", retryCount: 0, lastError: err?.message ?? String(err) });
7540
- this.scheduleAdapterRetry(adapterId, channelConfigs[i], i === 0 ? fleet : undefined);
7541
+ this.scheduleAdapterRetry(adapterId);
7541
7542
  // Notify admin via whichever adapter is already up
7542
7543
  this.notifyAdapterFailure(adapterId, err?.message ?? String(err));
7543
7544
  }
7544
7545
  }
7545
7546
  }
7546
- /** Exponential backoff retry for a single failed adapter (background, non-blocking). */
7547
- scheduleAdapterRetry(adapterId, channelConfig, fleet) {
7548
- const MAX_RETRIES = 10;
7547
+ /** Keep trying while the fleet runs, with a five-minute cap even after a long outage. */
7548
+ scheduleAdapterRetry(adapterId) {
7549
7549
  const INITIAL_DELAY_MS = 5_000;
7550
7550
  const MAX_DELAY_MS = 5 * 60_000;
7551
7551
  const state = this.adapterState.get(adapterId);
7552
- if (!state || state.retryCount >= MAX_RETRIES) {
7553
- if (state) {
7554
- state.status = "failed";
7555
- this.logger.error({ adapterId, retries: state.retryCount }, "Adapter retry exhausted — giving up");
7556
- this.notifyAdapterFailure(adapterId, `Retry exhausted after ${state.retryCount} attempts. Check token/network and restart fleet.`);
7557
- }
7552
+ if (this.shuttingDown || !state || state.retryTimer || this.adapterRetryInFlight.has(adapterId))
7558
7553
  return;
7559
- }
7560
- const delay = Math.min(INITIAL_DELAY_MS * Math.pow(2, state.retryCount), MAX_DELAY_MS);
7554
+ const delay = Math.min(INITIAL_DELAY_MS * Math.pow(2, Math.min(state.retryCount, 6)), MAX_DELAY_MS);
7561
7555
  this.logger.info({ adapterId, attempt: state.retryCount + 1, delay_ms: delay }, "Scheduling adapter retry");
7562
- state.retryTimer = setTimeout(async () => {
7556
+ const timer = setTimeout(() => {
7557
+ const current = this.adapterState.get(adapterId);
7558
+ if (this.shuttingDown || current?.retryTimer !== timer)
7559
+ return;
7560
+ current.retryTimer = undefined;
7561
+ void this.attemptAdapterRetry(adapterId);
7562
+ }, delay);
7563
+ state.retryTimer = timer;
7564
+ timer.unref?.();
7565
+ }
7566
+ /** One login per connection, shared by the background timer and the operator's Retry now. */
7567
+ attemptAdapterRetry(adapterId) {
7568
+ const pending = this.adapterRetryInFlight.get(adapterId);
7569
+ if (pending)
7570
+ return pending;
7571
+ if (this.shuttingDown || !this.secureConnectionChannel(adapterId))
7572
+ return Promise.resolve();
7573
+ const attempt = Promise.resolve().then(async () => {
7574
+ const channel = this.secureConnectionChannel(adapterId);
7575
+ if (this.shuttingDown || !channel)
7576
+ return;
7577
+ const generation = this.settingsGeneration;
7578
+ const binding = JSON.stringify([channel, this.getPrimaryAdapterId()]);
7579
+ const token = process.env[channel.bot_token_env];
7580
+ const operation = { owner: Symbol("adapter-retry"), assert: () => {
7581
+ if (this.shuttingDown || generation !== this.settingsGeneration
7582
+ || JSON.stringify([this.secureConnectionChannel(adapterId), this.getPrimaryAdapterId()]) !== binding
7583
+ || process.env[channel.bot_token_env] !== token)
7584
+ throw new Error("adapter retry superseded");
7585
+ } };
7586
+ const state = this.adapterState.get(adapterId) ?? { status: "retrying", retryCount: 0 };
7587
+ state.status = "retrying";
7563
7588
  state.retryCount++;
7589
+ this.adapterState.set(adapterId, state);
7564
7590
  try {
7565
- if (fleet) {
7566
- await this.startSingleAdapter(fleet, channelConfig);
7567
- }
7568
- else {
7569
- await this.startAdditionalAdapter(channelConfig);
7570
- }
7571
- state.status = "connected";
7572
- state.lastError = undefined;
7591
+ if (this.getPrimaryAdapterId() === adapterId)
7592
+ await this.startSingleAdapter(this.fleetConfig, channel, undefined, operation);
7593
+ else
7594
+ await this.startAdditionalAdapter(channel, true, undefined, operation);
7595
+ if (this.shuttingDown || generation !== this.settingsGeneration)
7596
+ return;
7597
+ if (!this.adapters.has(adapterId))
7598
+ throw new Error("Adapter token is not configured");
7599
+ // Health events can replace the state object during login. Update its current owner.
7600
+ const current = this.adapterState.get(adapterId);
7601
+ current.status = "connected";
7602
+ current.lastError = undefined;
7573
7603
  this.logger.info({ adapterId, attempts: state.retryCount }, "Adapter reconnected on retry");
7574
7604
  this.notifyAdapterRecovery(adapterId, state.retryCount);
7575
7605
  }
7576
7606
  catch (err) {
7577
- state.lastError = err?.message ?? String(err);
7578
- this.logger.warn({ adapterId, attempt: state.retryCount, err: state.lastError }, "Adapter retry failed");
7579
- this.scheduleAdapterRetry(adapterId, channelConfig, fleet);
7607
+ if (this.shuttingDown || generation !== this.settingsGeneration)
7608
+ return;
7609
+ const current = this.adapterState.get(adapterId);
7610
+ current.status = "retrying";
7611
+ current.lastError = err?.message ?? String(err);
7612
+ this.logger.warn({ adapterId, attempt: state.retryCount, err: current.lastError }, "Adapter retry failed");
7580
7613
  }
7581
- }, delay);
7614
+ }).finally(() => {
7615
+ if (this.adapterRetryInFlight.get(adapterId) !== attempt)
7616
+ return;
7617
+ this.adapterRetryInFlight.delete(adapterId);
7618
+ const channel = this.secureConnectionChannel(adapterId);
7619
+ if (!this.shuttingDown && channel && this.adapterState.get(adapterId)?.status !== "connected") {
7620
+ this.scheduleAdapterRetry(adapterId);
7621
+ }
7622
+ });
7623
+ this.adapterRetryInFlight.set(adapterId, attempt);
7624
+ return attempt;
7625
+ }
7626
+ retryAdapterNow(adapterId) {
7627
+ if (this.shuttingDown)
7628
+ throw new Error("The fleet is stopping");
7629
+ if (!this.secureConnectionChannel(adapterId))
7630
+ throw new Error("Connection not found");
7631
+ if (this.adapterRetryInFlight.has(adapterId) || this.adapterRestarting.has(adapterId))
7632
+ return { queued: true, already_running: true };
7633
+ const state = this.adapterState.get(adapterId);
7634
+ if (state?.retryTimer) {
7635
+ clearTimeout(state.retryTimer);
7636
+ state.retryTimer = undefined;
7637
+ }
7638
+ const adapter = this.adapters.get(adapterId);
7639
+ if (adapter && this.connectionStatus(adapterId, state, this.worlds.get(adapterId)).status === "connected")
7640
+ return { queued: false };
7641
+ const run = adapter ? this.restartAdapter(adapter, adapterId) : this.attemptAdapterRetry(adapterId);
7642
+ void run.catch(err => this.logger.warn({ adapterId, err }, "Requested adapter retry failed"));
7643
+ return { queued: true };
7582
7644
  }
7583
7645
  /** Notify admin about adapter failure (uses any available adapter). */
7584
7646
  notifyAdapterFailure(adapterId, error) {
@@ -7633,6 +7695,7 @@ export class FleetManager {
7633
7695
  : snapshot.status === "stopped" ? "failed"
7634
7696
  : "retrying";
7635
7697
  this.adapterState.set(adapterId, {
7698
+ ...previous,
7636
7699
  status,
7637
7700
  retryCount: previous?.retryCount ?? 0,
7638
7701
  lastError: status === "connected" ? undefined : snapshot.lastReconnectReason ?? previous?.lastError,
@@ -9458,6 +9521,16 @@ export class FleetManager {
9458
9521
  respond(null, permitted.message);
9459
9522
  return;
9460
9523
  }
9524
+ // Fleet tools resolve their own targets. A missing chat world must not prevent local
9525
+ // delivery, inspection or receipt lookup; only channel tools need reply routing below.
9526
+ const handler = outboundHandlers.get(tool);
9527
+ if (handler && !usesChannelAddress(tool)) {
9528
+ this.eventLog?.logActivity("tool_call", senderSessionName ?? instanceName, this.summarizeToolCall(tool, args));
9529
+ await handler(this, args, respond, {
9530
+ instanceName, requestId, fleetRequestId, senderSessionName, operationId, sourceDaemonBootId,
9531
+ });
9532
+ return;
9533
+ }
9461
9534
  // A fleet with no chat platform at all is driven from the web dashboard alone: a reply has nowhere
9462
9535
  // else to go, and "retry shortly" would have the agent retry forever. It goes to the web chat.
9463
9536
  const webOnlyReply = tool === "reply" && this.worlds.size === 0 && this.isWebOnlyFleet();
@@ -9649,19 +9722,14 @@ export class FleetManager {
9649
9722
  if (routeToolCall(outAdapter, tool, args, threadId, respond)) {
9650
9723
  return;
9651
9724
  }
9652
- // Log tool calls for activity visualization
9653
- const senderLabel = senderSessionName ?? instanceName;
9654
- this.eventLog?.logActivity("tool_call", senderLabel, this.summarizeToolCall(tool, args));
9655
- // Dispatch fleet-specific tools via handler map
9656
- const handler = outboundHandlers.get(tool);
9657
9725
  if (handler) {
9726
+ this.eventLog?.logActivity("tool_call", senderSessionName ?? instanceName, this.summarizeToolCall(tool, args));
9658
9727
  await handler(this, args, respond, {
9659
9728
  instanceName, requestId, fleetRequestId, senderSessionName, operationId, sourceDaemonBootId,
9660
9729
  });
9661
9730
  }
9662
- else {
9731
+ else
9663
9732
  respond(null, `Unknown tool: ${tool}`);
9664
- }
9665
9733
  }
9666
9734
  /** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
9667
9735
  /** No chat platform is configured at all: the web dashboard is the fleet's only surface. */
@@ -20493,7 +20561,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20493
20561
  all = false;
20494
20562
  const message = err?.message ?? String(err);
20495
20563
  this.adapterState.set(id, { status: "retrying", retryCount: 0, lastError: message });
20496
- this.scheduleAdapterRetry(id, channel);
20564
+ this.scheduleAdapterRetry(id);
20497
20565
  }
20498
20566
  }
20499
20567
  this.classicChannels?.configureAdapters(this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : []));