@songsid/agend 2.0.12 → 2.1.0-beta.1
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/backend/types.d.ts +12 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/daemon.d.ts +29 -1
- package/dist/daemon.js +148 -4
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-manager.d.ts +13 -0
- package/dist/fleet-manager.js +82 -9
- package/dist/fleet-manager.js.map +1 -1
- package/dist/topic-commands.js +12 -0
- package/dist/topic-commands.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
57
57
|
private failoverActive;
|
|
58
58
|
readonly ipcStoppingInstances: Set<string>;
|
|
59
59
|
private adapterRestarting;
|
|
60
|
+
private adapterState;
|
|
60
61
|
private collabInstances;
|
|
61
62
|
private healthServer;
|
|
62
63
|
private healthPortRetried;
|
|
@@ -137,6 +138,18 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
137
138
|
private rotateInboxes;
|
|
138
139
|
/** Start the shared channel adapter(s) for topic mode */
|
|
139
140
|
private startSharedAdapter;
|
|
141
|
+
/** Exponential backoff retry for a single failed adapter (background, non-blocking). */
|
|
142
|
+
private scheduleAdapterRetry;
|
|
143
|
+
/** Notify admin about adapter failure (uses any available adapter). */
|
|
144
|
+
private notifyAdapterFailure;
|
|
145
|
+
/** Notify admin that a retried adapter reconnected. */
|
|
146
|
+
private notifyAdapterRecovery;
|
|
147
|
+
/** Get adapter states for /status visibility. */
|
|
148
|
+
getAdapterStates(): Map<string, {
|
|
149
|
+
status: string;
|
|
150
|
+
retryCount: number;
|
|
151
|
+
lastError?: string;
|
|
152
|
+
}>;
|
|
140
153
|
/** Start the primary adapter (backward-compatible, sets this.adapter) */
|
|
141
154
|
private startSingleAdapter;
|
|
142
155
|
/** Start an additional (non-primary) adapter */
|
package/dist/fleet-manager.js
CHANGED
|
@@ -110,6 +110,8 @@ export class FleetManager {
|
|
|
110
110
|
ipcStoppingInstances = new Set();
|
|
111
111
|
// Adapter restart: prevents re-entrant restart attempts
|
|
112
112
|
adapterRestarting = new Set();
|
|
113
|
+
// Adapter isolation: track state per adapter for retry + visibility
|
|
114
|
+
adapterState = new Map();
|
|
113
115
|
collabInstances = new Set();
|
|
114
116
|
// Health endpoint
|
|
115
117
|
healthServer = null;
|
|
@@ -836,17 +838,81 @@ export class FleetManager {
|
|
|
836
838
|
const channelConfigs = fleet.channels ?? (fleet.channel ? [fleet.channel] : []);
|
|
837
839
|
if (channelConfigs.length === 0)
|
|
838
840
|
return;
|
|
839
|
-
// Start
|
|
840
|
-
await
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
//
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
841
|
+
// Start ALL adapters in parallel — any single failure doesn't block others.
|
|
842
|
+
const results = await Promise.allSettled(channelConfigs.map((cfg, i) => i === 0
|
|
843
|
+
? this.startSingleAdapter(fleet, cfg)
|
|
844
|
+
: this.startAdditionalAdapter(cfg)));
|
|
845
|
+
// Track state + schedule background retry for failures.
|
|
846
|
+
for (let i = 0; i < channelConfigs.length; i++) {
|
|
847
|
+
const adapterId = channelConfigs[i].id ?? channelConfigs[i].type;
|
|
848
|
+
if (results[i].status === "fulfilled") {
|
|
849
|
+
this.adapterState.set(adapterId, { status: "connected", retryCount: 0 });
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
const err = results[i].reason;
|
|
853
|
+
this.logger.error({ adapterId, err: err?.message ?? err }, "Adapter startup failed — scheduling background retry");
|
|
854
|
+
this.adapterState.set(adapterId, { status: "retrying", retryCount: 0, lastError: err?.message ?? String(err) });
|
|
855
|
+
this.scheduleAdapterRetry(adapterId, channelConfigs[i], i === 0 ? fleet : undefined);
|
|
856
|
+
// Notify admin via whichever adapter is already up
|
|
857
|
+
this.notifyAdapterFailure(adapterId, err?.message ?? String(err));
|
|
858
|
+
}
|
|
848
859
|
}
|
|
849
860
|
}
|
|
861
|
+
/** Exponential backoff retry for a single failed adapter (background, non-blocking). */
|
|
862
|
+
scheduleAdapterRetry(adapterId, channelConfig, fleet) {
|
|
863
|
+
const MAX_RETRIES = 10;
|
|
864
|
+
const INITIAL_DELAY_MS = 5_000;
|
|
865
|
+
const MAX_DELAY_MS = 5 * 60_000;
|
|
866
|
+
const state = this.adapterState.get(adapterId);
|
|
867
|
+
if (!state || state.retryCount >= MAX_RETRIES) {
|
|
868
|
+
if (state) {
|
|
869
|
+
state.status = "failed";
|
|
870
|
+
this.logger.error({ adapterId, retries: state.retryCount }, "Adapter retry exhausted — giving up");
|
|
871
|
+
this.notifyAdapterFailure(adapterId, `Retry exhausted after ${state.retryCount} attempts. Check token/network and restart fleet.`);
|
|
872
|
+
}
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const delay = Math.min(INITIAL_DELAY_MS * Math.pow(2, state.retryCount), MAX_DELAY_MS);
|
|
876
|
+
this.logger.info({ adapterId, attempt: state.retryCount + 1, delay_ms: delay }, "Scheduling adapter retry");
|
|
877
|
+
state.retryTimer = setTimeout(async () => {
|
|
878
|
+
state.retryCount++;
|
|
879
|
+
try {
|
|
880
|
+
if (fleet) {
|
|
881
|
+
await this.startSingleAdapter(fleet, channelConfig);
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
await this.startAdditionalAdapter(channelConfig);
|
|
885
|
+
}
|
|
886
|
+
state.status = "connected";
|
|
887
|
+
state.lastError = undefined;
|
|
888
|
+
this.logger.info({ adapterId, attempts: state.retryCount }, "Adapter reconnected on retry");
|
|
889
|
+
this.notifyAdapterRecovery(adapterId, state.retryCount);
|
|
890
|
+
}
|
|
891
|
+
catch (err) {
|
|
892
|
+
state.lastError = err?.message ?? String(err);
|
|
893
|
+
this.logger.warn({ adapterId, attempt: state.retryCount, err: state.lastError }, "Adapter retry failed");
|
|
894
|
+
this.scheduleAdapterRetry(adapterId, channelConfig, fleet);
|
|
895
|
+
}
|
|
896
|
+
}, delay);
|
|
897
|
+
}
|
|
898
|
+
/** Notify admin about adapter failure (uses any available adapter). */
|
|
899
|
+
notifyAdapterFailure(adapterId, error) {
|
|
900
|
+
const generalId = this.findGeneralInstance();
|
|
901
|
+
if (generalId) {
|
|
902
|
+
this.notifyInstanceTopic(generalId, `⚠️ Adapter "${adapterId}" failed to start: ${error}\nRetrying in background. Other adapters unaffected.`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
/** Notify admin that a retried adapter reconnected. */
|
|
906
|
+
notifyAdapterRecovery(adapterId, attempts) {
|
|
907
|
+
const generalId = this.findGeneralInstance();
|
|
908
|
+
if (generalId) {
|
|
909
|
+
this.notifyInstanceTopic(generalId, `✅ Adapter "${adapterId}" reconnected (after ${attempts} ${attempts === 1 ? "retry" : "retries"}).`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/** Get adapter states for /status visibility. */
|
|
913
|
+
getAdapterStates() {
|
|
914
|
+
return this.adapterState;
|
|
915
|
+
}
|
|
850
916
|
/** Start the primary adapter (backward-compatible, sets this.adapter) */
|
|
851
917
|
async startSingleAdapter(fleet, channelConfig) {
|
|
852
918
|
const botToken = process.env[channelConfig.bot_token_env];
|
|
@@ -3683,6 +3749,13 @@ When users create specialized instances, suggest these configurations:
|
|
|
3683
3749
|
clearInterval(this.watchdogTimer);
|
|
3684
3750
|
this.watchdogTimer = null;
|
|
3685
3751
|
}
|
|
3752
|
+
// Cancel adapter retry timers
|
|
3753
|
+
for (const state of this.adapterState.values()) {
|
|
3754
|
+
if (state.retryTimer) {
|
|
3755
|
+
clearTimeout(state.retryTimer);
|
|
3756
|
+
state.retryTimer = undefined;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3686
3759
|
this.clearStatuslineWatchers();
|
|
3687
3760
|
this.costGuard?.stop();
|
|
3688
3761
|
this.dailySummary?.stop();
|