@songsid/agend 2.1.1 → 2.1.2-beta.10

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 (52) hide show
  1. package/dist/backend/antigravity.js +4 -2
  2. package/dist/backend/antigravity.js.map +1 -1
  3. package/dist/backend/codex.d.ts +16 -0
  4. package/dist/backend/codex.js +226 -56
  5. package/dist/backend/codex.js.map +1 -1
  6. package/dist/backend/grok.js +20 -0
  7. package/dist/backend/grok.js.map +1 -1
  8. package/dist/backend/kiro.js +6 -2
  9. package/dist/backend/kiro.js.map +1 -1
  10. package/dist/backend/types.d.ts +7 -0
  11. package/dist/backend/types.js.map +1 -1
  12. package/dist/channel/adapters/discord.js +4 -5
  13. package/dist/channel/adapters/discord.js.map +1 -1
  14. package/dist/channel/adapters/telegram.js +50 -15
  15. package/dist/channel/adapters/telegram.js.map +1 -1
  16. package/dist/channel/ipc-bridge.d.ts +8 -0
  17. package/dist/channel/ipc-bridge.js +41 -14
  18. package/dist/channel/ipc-bridge.js.map +1 -1
  19. package/dist/channel/mcp-server.js +5 -0
  20. package/dist/channel/mcp-server.js.map +1 -1
  21. package/dist/channel/message-queue.d.ts +1 -0
  22. package/dist/channel/message-queue.js +49 -17
  23. package/dist/channel/message-queue.js.map +1 -1
  24. package/dist/cli.js +3 -2
  25. package/dist/cli.js.map +1 -1
  26. package/dist/daemon.d.ts +11 -8
  27. package/dist/daemon.js +56 -16
  28. package/dist/daemon.js.map +1 -1
  29. package/dist/fleet-manager.d.ts +7 -1
  30. package/dist/fleet-manager.js +58 -9
  31. package/dist/fleet-manager.js.map +1 -1
  32. package/dist/instance-lifecycle.d.ts +2 -1
  33. package/dist/instance-lifecycle.js +6 -2
  34. package/dist/instance-lifecycle.js.map +1 -1
  35. package/dist/instructions.d.ts +5 -0
  36. package/dist/instructions.js +9 -11
  37. package/dist/instructions.js.map +1 -1
  38. package/dist/logger.js +14 -0
  39. package/dist/logger.js.map +1 -1
  40. package/dist/outbound-handlers.d.ts +9 -0
  41. package/dist/outbound-handlers.js +46 -26
  42. package/dist/outbound-handlers.js.map +1 -1
  43. package/dist/topic-commands.d.ts +1 -1
  44. package/dist/topic-commands.js +11 -2
  45. package/dist/topic-commands.js.map +1 -1
  46. package/dist/tui-glyphs.d.ts +21 -0
  47. package/dist/tui-glyphs.js +22 -0
  48. package/dist/tui-glyphs.js.map +1 -0
  49. package/dist/view-api.d.ts +3 -0
  50. package/dist/view-api.js +6 -2
  51. package/dist/view-api.js.map +1 -1
  52. package/package.json +1 -1
@@ -163,6 +163,10 @@ export class FleetManager {
163
163
  failoverActive = new Map(); // instance → current failover model
164
164
  // IPC reconnect: tracks instances being intentionally stopped (skip reconnect)
165
165
  ipcStoppingInstances = new Set();
166
+ /** Coalesce concurrent connection attempts for the same daemon socket. */
167
+ ipcConnectInFlight = new Map();
168
+ /** At most one reconnect/backoff loop may exist per instance. */
169
+ ipcReconnectInFlight = new Map();
166
170
  // Adapter restart: prevents re-entrant restart attempts
167
171
  adapterRestarting = new Set();
168
172
  // Adapter isolation: track state per adapter for retry + visibility
@@ -651,7 +655,7 @@ export class FleetManager {
651
655
  await new Promise(resolve => setTimeout(resolve, 250));
652
656
  await this.startClassicInstance(instanceName, this.classicChannels.getBackendByInstance(instanceName, this.fleetConfig?.defaults?.backend), this.classicChannels.getPreTaskCommand(channel.channelId, channel.adapterId), this.classicChannels.getModel(channel.channelId, channel.adapterId, this.fleetConfig?.defaults?.model), this.classicChannels.getAutoPauseAfter(channel.channelId, channel.adapterId, this.fleetConfig?.defaults?.auto_pause_after));
653
657
  }
654
- async startInstance(name, config, topicMode) {
658
+ async startInstance(name, config, topicMode, kind = "fleet-topic") {
655
659
  if (this.lifecycle.isPaused(name)) {
656
660
  this.logger.info({ name }, "Persisted paused instance — skipping startup");
657
661
  return;
@@ -667,7 +671,11 @@ export class FleetManager {
667
671
  this.ensureGeneralInstructions(config.working_directory, config.backend);
668
672
  }
669
673
  this.instanceProcessStatus.delete(name);
670
- await this.lifecycle.start(name, config, topicMode);
674
+ await this.lifecycle.start(name, config, topicMode, {
675
+ kind,
676
+ backend: config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code",
677
+ model: this.resolveInstanceModel(name).display,
678
+ });
671
679
  // Auto-connect IPC — daemon.start() ensures socket is ready before resolving
672
680
  await this.connectIpcToInstance(name);
673
681
  }
@@ -978,6 +986,11 @@ export class FleetManager {
978
986
  // Rotate fleet.log daily too (besides the startup size check above), so a
979
987
  // long-running fleet doesn't accumulate an unbounded log.
980
988
  rotateLogIfNeeded(join(this.dataDir, "fleet.log"));
989
+ // Instance output.log is pipe-pane (TUI ANSI). Daemon health ticks also
990
+ // rotate it; this is the daily safety net for idle/stopped instances.
991
+ for (const name of Object.keys(this.fleetConfig?.instances ?? {})) {
992
+ rotateLogIfNeeded(join(this.dataDir, "instances", name, "output.log"));
993
+ }
981
994
  }, () => {
982
995
  const instances = Object.keys(this.fleetConfig?.instances ?? {});
983
996
  const costMap = new Map();
@@ -1931,19 +1944,38 @@ export class FleetManager {
1931
1944
  this.logger.info({ adapterId, type: channelConfig.type }, "Additional adapter started");
1932
1945
  }
1933
1946
  /** Connect IPC to a single instance with all handlers */
1934
- async connectIpcToInstance(name) {
1947
+ connectIpcToInstance(name) {
1948
+ const inFlight = this.ipcConnectInFlight.get(name);
1949
+ if (inFlight)
1950
+ return inFlight;
1951
+ const connection = this.connectIpcToInstanceInternal(name)
1952
+ .finally(() => {
1953
+ if (this.ipcConnectInFlight.get(name) === connection) {
1954
+ this.ipcConnectInFlight.delete(name);
1955
+ }
1956
+ });
1957
+ this.ipcConnectInFlight.set(name, connection);
1958
+ return connection;
1959
+ }
1960
+ async connectIpcToInstanceInternal(name) {
1935
1961
  // Close existing client to prevent socket leak on reconnect
1936
1962
  const existing = this.instanceIpcClients.get(name);
1937
1963
  if (existing) {
1938
- this.ipcStoppingInstances.add(name);
1964
+ // Remove application listeners before destroying the socket. Even if a
1965
+ // future regression creates two clients, the replaced one cannot keep
1966
+ // handling fleet_outbound messages as an orphan.
1967
+ existing.removeAllListeners();
1939
1968
  try {
1940
- existing.close();
1969
+ await existing.close();
1941
1970
  }
1942
1971
  catch (err) {
1943
1972
  this.logger.debug({ err, name }, "IPC client close failed (likely already closed)");
1944
1973
  }
1945
- this.instanceIpcClients.delete(name);
1946
- this.ipcStoppingInstances.delete(name);
1974
+ finally {
1975
+ if (this.instanceIpcClients.get(name) === existing) {
1976
+ this.instanceIpcClients.delete(name);
1977
+ }
1978
+ }
1947
1979
  }
1948
1980
  const sockPath = join(this.getInstanceDir(name), "channel.sock");
1949
1981
  if (!existsSync(sockPath))
@@ -2021,6 +2053,10 @@ export class FleetManager {
2021
2053
  }
2022
2054
  // Auto-reconnect on disconnect (unless intentionally stopping)
2023
2055
  ipc.on("disconnect", () => {
2056
+ // A delayed event from a replaced/stale client must never delete the
2057
+ // current connection or start another reconnect loop.
2058
+ if (this.instanceIpcClients.get(name) !== ipc)
2059
+ return;
2024
2060
  this.instanceIpcClients.delete(name);
2025
2061
  if (this.ipcStoppingInstances.has(name))
2026
2062
  return;
@@ -2032,7 +2068,20 @@ export class FleetManager {
2032
2068
  }
2033
2069
  }
2034
2070
  /** Attempt IPC reconnection with exponential backoff */
2035
- async ipcReconnect(name) {
2071
+ ipcReconnect(name) {
2072
+ const inFlight = this.ipcReconnectInFlight.get(name);
2073
+ if (inFlight)
2074
+ return inFlight;
2075
+ const reconnect = this.runIpcReconnect(name)
2076
+ .finally(() => {
2077
+ if (this.ipcReconnectInFlight.get(name) === reconnect) {
2078
+ this.ipcReconnectInFlight.delete(name);
2079
+ }
2080
+ });
2081
+ this.ipcReconnectInFlight.set(name, reconnect);
2082
+ return reconnect;
2083
+ }
2084
+ async runIpcReconnect(name) {
2036
2085
  for (let attempt = 1;; attempt++) {
2037
2086
  if (this.ipcStoppingInstances.has(name) || !this.daemons.has(name))
2038
2087
  return;
@@ -4771,7 +4820,7 @@ When users create specialized instances, suggest these configurations:
4771
4820
  ...(preTaskCommand ? { pre_task_command: preTaskCommand } : {}),
4772
4821
  };
4773
4822
  const topicMode = this.fleetConfig?.channel?.mode === "topic";
4774
- await this.startInstance(instanceName, config, topicMode);
4823
+ await this.startInstance(instanceName, config, topicMode, "classic");
4775
4824
  }
4776
4825
  /** Handle /start slash command — register classic channel */
4777
4826
  async handleClassicStart(channelId, channelName, userId, guildId, adapterId, backend) {