@noosphere/agent-core 0.2.0-alpha.2 → 0.2.0-alpha.3

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/index.cjs CHANGED
@@ -61,6 +61,12 @@ module.exports = __toCommonJS(index_exports);
61
61
  // src/EventMonitor.ts
62
62
  var import_events = require("events");
63
63
  var import_ethers = require("ethers");
64
+ var DEFAULT_CONNECTION_CONFIG = {
65
+ wsConnectTimeoutMs: 1e4,
66
+ wsMaxConnectRetries: 3,
67
+ wsConnectRetryDelayMs: 5e3,
68
+ wsRecoveryIntervalMs: 6e4
69
+ };
64
70
  var EventMonitor = class extends import_events.EventEmitter {
65
71
  constructor(config, routerAbi, coordinatorAbi, options) {
66
72
  super();
@@ -72,24 +78,115 @@ var EventMonitor = class extends import_events.EventEmitter {
72
78
  this.isReconnecting = false;
73
79
  this.heartbeatInterval = null;
74
80
  this.lastEventTime = Date.now();
81
+ // Connection state management
82
+ this.connectionState = "INIT";
83
+ this.wsRecoveryInterval = null;
84
+ this.pollingInterval = null;
75
85
  this.lastProcessedBlock = config.deploymentBlock || 0;
76
86
  this.useWebSocket = false;
77
87
  this.checkpointCallbacks = options;
88
+ this.connectionConfig = {
89
+ ...DEFAULT_CONNECTION_CONFIG,
90
+ ...options?.connectionConfig
91
+ };
92
+ }
93
+ /**
94
+ * Get current connection state
95
+ */
96
+ getConnectionState() {
97
+ return this.connectionState;
98
+ }
99
+ /**
100
+ * Get connection mode string for health API
101
+ */
102
+ getConnectionMode() {
103
+ switch (this.connectionState) {
104
+ case "WS_ACTIVE":
105
+ return "websocket";
106
+ case "HTTP_FALLBACK":
107
+ return "http_polling";
108
+ default:
109
+ return "connecting";
110
+ }
78
111
  }
79
112
  async connect() {
80
- try {
81
- if (this.config.wsRpcUrl) {
82
- this.provider = new import_ethers.ethers.WebSocketProvider(this.config.wsRpcUrl);
83
- this.useWebSocket = true;
84
- console.log("\u2713 Connected via WebSocket (push-based events)");
85
- } else {
86
- throw new Error("WebSocket URL not provided");
113
+ const { wsMaxConnectRetries, wsConnectRetryDelayMs } = this.connectionConfig;
114
+ if (this.config.wsRpcUrl) {
115
+ this.connectionState = "WS_CONNECTING";
116
+ for (let attempt = 1; attempt <= wsMaxConnectRetries; attempt++) {
117
+ try {
118
+ console.log(`\u{1F50C} WebSocket connection attempt ${attempt}/${wsMaxConnectRetries}...`);
119
+ await this.connectWebSocketWithTimeout();
120
+ this.connectionState = "WS_ACTIVE";
121
+ this.useWebSocket = true;
122
+ console.log("\u2713 Connected via WebSocket (push-based events)");
123
+ this.initializeContracts();
124
+ return;
125
+ } catch (error) {
126
+ const errorMessage = error instanceof Error ? error.message : String(error);
127
+ console.warn(`\u26A0\uFE0F WebSocket attempt ${attempt}/${wsMaxConnectRetries} failed: ${errorMessage}`);
128
+ if (attempt < wsMaxConnectRetries) {
129
+ console.log(` Retrying in ${wsConnectRetryDelayMs / 1e3}s...`);
130
+ await this.sleep(wsConnectRetryDelayMs);
131
+ }
132
+ }
133
+ }
134
+ console.warn(`\u26A0\uFE0F All ${wsMaxConnectRetries} WebSocket attempts failed, falling back to HTTP polling`);
135
+ } else {
136
+ console.log("\u2139\uFE0F No WebSocket URL provided, using HTTP polling");
137
+ }
138
+ await this.connectHttp();
139
+ }
140
+ /**
141
+ * Connect via WebSocket with timeout
142
+ */
143
+ async connectWebSocketWithTimeout() {
144
+ const { wsConnectTimeoutMs } = this.connectionConfig;
145
+ return new Promise((resolve, reject) => {
146
+ const timeout = setTimeout(() => {
147
+ reject(new Error(`WebSocket connection timeout after ${wsConnectTimeoutMs}ms`));
148
+ }, wsConnectTimeoutMs);
149
+ try {
150
+ const wsProvider = new import_ethers.ethers.WebSocketProvider(this.config.wsRpcUrl);
151
+ wsProvider.getBlockNumber().then(() => {
152
+ clearTimeout(timeout);
153
+ this.provider = wsProvider;
154
+ resolve();
155
+ }).catch((error) => {
156
+ clearTimeout(timeout);
157
+ wsProvider.destroy().catch(() => {
158
+ });
159
+ reject(error);
160
+ });
161
+ } catch (error) {
162
+ clearTimeout(timeout);
163
+ reject(error);
87
164
  }
165
+ });
166
+ }
167
+ /**
168
+ * Connect via HTTP (fallback mode)
169
+ */
170
+ async connectHttp() {
171
+ this.connectionState = "HTTP_FALLBACK";
172
+ this.useWebSocket = false;
173
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this.config.rpcUrl);
174
+ try {
175
+ await this.provider.getBlockNumber();
176
+ console.log("\u2713 Connected via HTTP polling (fallback mode)");
88
177
  } catch (error) {
89
- console.warn("WebSocket unavailable, falling back to HTTP polling");
90
- this.provider = new import_ethers.ethers.JsonRpcProvider(this.config.rpcUrl);
91
- this.useWebSocket = false;
178
+ const errorMessage = error instanceof Error ? error.message : String(error);
179
+ throw new Error(`HTTP connection failed: ${errorMessage}`);
180
+ }
181
+ this.initializeContracts();
182
+ if (this.config.wsRpcUrl) {
183
+ this.startWsRecoveryLoop();
92
184
  }
185
+ }
186
+ /**
187
+ * Initialize contract instances
188
+ */
189
+ initializeContracts() {
93
190
  this.router = new import_ethers.ethers.Contract(this.config.routerAddress, this.routerAbi, this.provider);
94
191
  this.coordinator = new import_ethers.ethers.Contract(
95
192
  this.config.coordinatorAddress,
@@ -97,6 +194,63 @@ var EventMonitor = class extends import_events.EventEmitter {
97
194
  this.provider
98
195
  );
99
196
  }
197
+ /**
198
+ * Start WS recovery loop when in HTTP fallback mode
199
+ */
200
+ startWsRecoveryLoop() {
201
+ if (this.wsRecoveryInterval) {
202
+ clearInterval(this.wsRecoveryInterval);
203
+ }
204
+ const { wsRecoveryIntervalMs } = this.connectionConfig;
205
+ console.log(`\u{1F504} Starting WS recovery loop (every ${wsRecoveryIntervalMs / 1e3}s)`);
206
+ this.wsRecoveryInterval = setInterval(async () => {
207
+ if (this.connectionState !== "HTTP_FALLBACK") {
208
+ this.stopWsRecoveryLoop();
209
+ return;
210
+ }
211
+ console.log("\u{1F50C} Attempting WebSocket recovery...");
212
+ try {
213
+ await this.connectWebSocketWithTimeout();
214
+ console.log("\u2713 WebSocket connection recovered!");
215
+ this.stopPolling();
216
+ this.connectionState = "WS_ACTIVE";
217
+ this.useWebSocket = true;
218
+ this.initializeContracts();
219
+ await this.replayMissedEvents();
220
+ await this.startWebSocketListening();
221
+ this.stopWsRecoveryLoop();
222
+ this.emit("connectionRecovered", { mode: "websocket" });
223
+ } catch (error) {
224
+ const errorMessage = error instanceof Error ? error.message : String(error);
225
+ console.log(` WS recovery failed: ${errorMessage}, will retry in ${wsRecoveryIntervalMs / 1e3}s`);
226
+ }
227
+ }, wsRecoveryIntervalMs);
228
+ }
229
+ /**
230
+ * Stop WS recovery loop
231
+ */
232
+ stopWsRecoveryLoop() {
233
+ if (this.wsRecoveryInterval) {
234
+ clearInterval(this.wsRecoveryInterval);
235
+ this.wsRecoveryInterval = null;
236
+ console.log("\u{1F504} WS recovery loop stopped");
237
+ }
238
+ }
239
+ /**
240
+ * Stop HTTP polling
241
+ */
242
+ stopPolling() {
243
+ if (this.pollingInterval) {
244
+ clearInterval(this.pollingInterval);
245
+ this.pollingInterval = null;
246
+ }
247
+ }
248
+ /**
249
+ * Sleep helper
250
+ */
251
+ sleep(ms) {
252
+ return new Promise((resolve) => setTimeout(resolve, ms));
253
+ }
100
254
  async start() {
101
255
  if (this.checkpointCallbacks?.loadCheckpoint) {
102
256
  const checkpoint = this.checkpointCallbacks.loadCheckpoint();
@@ -168,7 +322,9 @@ var EventMonitor = class extends import_events.EventEmitter {
168
322
  const blockNumber = await this.provider.getBlockNumber();
169
323
  const timeSinceLastEvent = Date.now() - this.lastEventTime;
170
324
  if (timeSinceLastEvent > 18e4 && blockNumber > this.lastProcessedBlock + 5) {
171
- console.log(`\u26A0\uFE0F No events for ${Math.round(timeSinceLastEvent / 1e3)}s, checking for missed events...`);
325
+ console.log(
326
+ `\u26A0\uFE0F No events for ${Math.round(timeSinceLastEvent / 1e3)}s, checking for missed events...`
327
+ );
172
328
  await this.replayMissedEvents();
173
329
  }
174
330
  }
@@ -182,7 +338,9 @@ var EventMonitor = class extends import_events.EventEmitter {
182
338
  try {
183
339
  const currentBlock = await this.provider.getBlockNumber();
184
340
  if (currentBlock > this.lastProcessedBlock) {
185
- console.log(`\u{1F4E5} Replaying events from block ${this.lastProcessedBlock + 1} to ${currentBlock}`);
341
+ console.log(
342
+ `\u{1F4E5} Replaying events from block ${this.lastProcessedBlock + 1} to ${currentBlock}`
343
+ );
186
344
  await this.replayEvents(this.lastProcessedBlock + 1, currentBlock);
187
345
  this.lastEventTime = Date.now();
188
346
  }
@@ -193,6 +351,7 @@ var EventMonitor = class extends import_events.EventEmitter {
193
351
  handleDisconnect() {
194
352
  if (this.isReconnecting) return;
195
353
  this.isReconnecting = true;
354
+ this.connectionState = "WS_RECONNECTING";
196
355
  if (this.heartbeatInterval) {
197
356
  clearInterval(this.heartbeatInterval);
198
357
  this.heartbeatInterval = null;
@@ -203,9 +362,10 @@ var EventMonitor = class extends import_events.EventEmitter {
203
362
  }
204
363
  async startPolling() {
205
364
  console.log("Starting HTTP polling (fallback mode)...");
206
- const pollingInterval = this.config.pollingInterval || 12e3;
365
+ this.stopPolling();
366
+ const pollingIntervalMs = this.config.pollingInterval || 12e3;
207
367
  let lastBlock = await this.provider.getBlockNumber();
208
- setInterval(async () => {
368
+ this.pollingInterval = setInterval(async () => {
209
369
  try {
210
370
  const currentBlock = await this.provider.getBlockNumber();
211
371
  if (currentBlock > lastBlock) {
@@ -225,7 +385,7 @@ var EventMonitor = class extends import_events.EventEmitter {
225
385
  } catch (error) {
226
386
  console.error("Polling error:", error);
227
387
  }
228
- }, pollingInterval);
388
+ }, pollingIntervalMs);
229
389
  }
230
390
  async processEvent(event) {
231
391
  const commitment = event.args.commitment;
@@ -248,16 +408,22 @@ var EventMonitor = class extends import_events.EventEmitter {
248
408
  async reconnect() {
249
409
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
250
410
  console.error("Max reconnection attempts reached. Falling back to HTTP polling.");
251
- this.useWebSocket = false;
252
- await this.connect();
411
+ if (this.provider instanceof import_ethers.ethers.WebSocketProvider) {
412
+ try {
413
+ await this.provider.destroy();
414
+ } catch {
415
+ }
416
+ }
417
+ await this.connectHttp();
253
418
  await this.startPolling();
419
+ this.reconnectAttempts = 0;
254
420
  return;
255
421
  }
256
422
  const backoff = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 6e4);
257
423
  console.log(
258
424
  `\u{1F504} Reconnecting in ${backoff}ms (attempt ${this.reconnectAttempts + 1}/${this.maxReconnectAttempts})`
259
425
  );
260
- await new Promise((resolve) => setTimeout(resolve, backoff));
426
+ await this.sleep(backoff);
261
427
  try {
262
428
  if (this.coordinator) {
263
429
  this.coordinator.removeAllListeners();
@@ -268,14 +434,19 @@ var EventMonitor = class extends import_events.EventEmitter {
268
434
  } catch {
269
435
  }
270
436
  }
271
- await this.connect();
437
+ await this.connectWebSocketWithTimeout();
438
+ this.connectionState = "WS_ACTIVE";
439
+ this.useWebSocket = true;
440
+ this.initializeContracts();
272
441
  await this.replayMissedEvents();
273
442
  await this.startWebSocketListening();
274
443
  console.log("\u2713 Reconnected successfully");
275
444
  this.reconnectAttempts = 0;
276
445
  this.lastEventTime = Date.now();
446
+ this.emit("connectionRecovered", { mode: "websocket" });
277
447
  } catch (error) {
278
- console.error("Reconnection failed:", error);
448
+ const errorMessage = error instanceof Error ? error.message : String(error);
449
+ console.error(`Reconnection failed: ${errorMessage}`);
279
450
  this.reconnectAttempts++;
280
451
  await this.reconnect();
281
452
  }
@@ -294,6 +465,8 @@ var EventMonitor = class extends import_events.EventEmitter {
294
465
  clearInterval(this.heartbeatInterval);
295
466
  this.heartbeatInterval = null;
296
467
  }
468
+ this.stopWsRecoveryLoop();
469
+ this.stopPolling();
297
470
  if (this.router) {
298
471
  this.router.removeAllListeners();
299
472
  }
@@ -303,6 +476,7 @@ var EventMonitor = class extends import_events.EventEmitter {
303
476
  if (this.provider instanceof import_ethers.ethers.WebSocketProvider) {
304
477
  await this.provider.destroy();
305
478
  }
479
+ this.connectionState = "INIT";
306
480
  }
307
481
  };
308
482
 
@@ -357,7 +531,9 @@ var ContainerManager = class {
357
531
  lastError = error;
358
532
  if (error.code === "ECONNREFUSED") {
359
533
  if (attempt < connectionRetries) {
360
- console.log(` \u23F3 Container not ready (attempt ${attempt}/${connectionRetries}), retrying in ${connectionRetryDelayMs / 1e3}s...`);
534
+ console.log(
535
+ ` \u23F3 Container not ready (attempt ${attempt}/${connectionRetries}), retrying in ${connectionRetryDelayMs / 1e3}s...`
536
+ );
361
537
  await this.sleep(connectionRetryDelayMs);
362
538
  continue;
363
539
  }
@@ -536,7 +712,10 @@ var ContainerManager = class {
536
712
  try {
537
713
  await this.startPersistentContainer(id, container);
538
714
  } catch (error) {
539
- console.error(` \u274C Failed to start ${container.name || container.image}:`, error.message);
715
+ console.error(
716
+ ` \u274C Failed to start ${container.name || container.image}:`,
717
+ error.message
718
+ );
540
719
  }
541
720
  });
542
721
  await Promise.all(pullAndStartPromises);
@@ -819,15 +998,22 @@ var SchedulerService = class extends import_events2.EventEmitter {
819
998
  let currentInterval = 0n;
820
999
  try {
821
1000
  if (sub.intervalSeconds <= 0n) {
822
- console.warn(` Skipping subscription ${subId}: invalid intervalSeconds (${sub.intervalSeconds})`);
1001
+ console.warn(
1002
+ ` Skipping subscription ${subId}: invalid intervalSeconds (${sub.intervalSeconds})`
1003
+ );
823
1004
  this.untrackSubscription(sub.subscriptionId);
824
1005
  continue;
825
1006
  }
826
1007
  try {
827
- currentInterval = BigInt(await this.router.getComputeSubscriptionInterval(sub.subscriptionId));
1008
+ currentInterval = BigInt(
1009
+ await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
1010
+ );
828
1011
  } catch (error) {
829
1012
  const errorMessage = error.message || "";
830
- console.warn(` Could not get interval from router for subscription ${subId}:`, errorMessage);
1013
+ console.warn(
1014
+ ` Could not get interval from router for subscription ${subId}:`,
1015
+ errorMessage
1016
+ );
831
1017
  if (errorMessage.includes("SubscriptionNotFound")) {
832
1018
  console.log(` Subscription ${subId} not found (cancelled), untracking...`);
833
1019
  this.untrackSubscription(sub.subscriptionId);
@@ -836,7 +1022,9 @@ var SchedulerService = class extends import_events2.EventEmitter {
836
1022
  const intervalsSinceActive = BigInt(currentBlockTime) - sub.activeAt;
837
1023
  currentInterval = intervalsSinceActive / sub.intervalSeconds + 1n;
838
1024
  }
839
- console.log(` Subscription ${subId}: currentInterval=${currentInterval}, maxExecutions=${sub.maxExecutions}, activeAt=${sub.activeAt}`);
1025
+ console.log(
1026
+ ` Subscription ${subId}: currentInterval=${currentInterval}, maxExecutions=${sub.maxExecutions}, activeAt=${sub.activeAt}`
1027
+ );
840
1028
  if (!this.shouldProcess(sub, currentBlockTime)) {
841
1029
  continue;
842
1030
  }
@@ -863,12 +1051,16 @@ var SchedulerService = class extends import_events2.EventEmitter {
863
1051
  return false;
864
1052
  };
865
1053
  if (containsError(error, "Panic due to OVERFLOW") || containsError(error, "arithmetic underflow or overflow")) {
866
- console.log(` Interval ${currentInterval} for subscription ${subId} appears to be already executed (overflow), marking as committed`);
1054
+ console.log(
1055
+ ` Interval ${currentInterval} for subscription ${subId} appears to be already executed (overflow), marking as committed`
1056
+ );
867
1057
  const commitmentKey = `${subId}:${currentInterval}`;
868
1058
  this.addCommittedInterval(commitmentKey);
869
1059
  sub.currentInterval = currentInterval + 1n;
870
1060
  } else if (containsError(error, "0x3cdc51d3") || containsError(error, "NoNextInterval")) {
871
- console.log(` Subscription ${subId}: waiting for client to trigger interval 1 (NoNextInterval)`);
1061
+ console.log(
1062
+ ` Subscription ${subId}: waiting for client to trigger interval 1 (NoNextInterval)`
1063
+ );
872
1064
  } else if (containsError(error, "execution reverted") || containsError(error, "Transaction simulation failed")) {
873
1065
  console.log(` Subscription ${subId} appears to be cancelled or invalid, untracking...`);
874
1066
  this.untrackSubscription(sub.subscriptionId);
@@ -882,13 +1074,17 @@ var SchedulerService = class extends import_events2.EventEmitter {
882
1074
  shouldProcess(sub, currentBlockTime) {
883
1075
  const subId = sub.subscriptionId.toString();
884
1076
  if (BigInt(currentBlockTime) < sub.activeAt) {
885
- console.log(` Skip: not active yet (currentTime=${currentBlockTime}, activeAt=${sub.activeAt})`);
1077
+ console.log(
1078
+ ` Skip: not active yet (currentTime=${currentBlockTime}, activeAt=${sub.activeAt})`
1079
+ );
886
1080
  return false;
887
1081
  }
888
1082
  const intervalsSinceActive = BigInt(currentBlockTime) - sub.activeAt;
889
1083
  const currentInterval = intervalsSinceActive / sub.intervalSeconds + 1n;
890
1084
  if (sub.maxExecutions > 0n && currentInterval > sub.maxExecutions) {
891
- console.log(` Subscription ${subId} completed (interval ${currentInterval} > maxExecutions ${sub.maxExecutions}), untracking...`);
1085
+ console.log(
1086
+ ` Subscription ${subId} completed (interval ${currentInterval} > maxExecutions ${sub.maxExecutions}), untracking...`
1087
+ );
892
1088
  this.untrackSubscription(sub.subscriptionId);
893
1089
  return false;
894
1090
  }
@@ -898,7 +1094,9 @@ var SchedulerService = class extends import_events2.EventEmitter {
898
1094
  return false;
899
1095
  }
900
1096
  if (sub.txAttempts >= this.config.maxRetryAttempts) {
901
- console.log(` Skip: max retry attempts reached (${sub.txAttempts}/${this.config.maxRetryAttempts})`);
1097
+ console.log(
1098
+ ` Skip: max retry attempts reached (${sub.txAttempts}/${this.config.maxRetryAttempts})`
1099
+ );
902
1100
  return false;
903
1101
  }
904
1102
  return true;
@@ -925,9 +1123,13 @@ var SchedulerService = class extends import_events2.EventEmitter {
925
1123
  const runKey = `${sub.subscriptionId}:${interval}`;
926
1124
  try {
927
1125
  console.log(` Preparing interval ${interval} for subscription ${sub.subscriptionId}...`);
928
- const currentIntervalNow = BigInt(await this.router.getComputeSubscriptionInterval(sub.subscriptionId));
1126
+ const currentIntervalNow = BigInt(
1127
+ await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
1128
+ );
929
1129
  if (currentIntervalNow !== interval && currentIntervalNow !== interval - 1n) {
930
- console.log(` \u26A0\uFE0F Interval changed: expected ${interval}, blockchain is at ${currentIntervalNow}. Skipping.`);
1130
+ console.log(
1131
+ ` \u26A0\uFE0F Interval changed: expected ${interval}, blockchain is at ${currentIntervalNow}. Skipping.`
1132
+ );
931
1133
  return;
932
1134
  }
933
1135
  const tx = await this.coordinator.prepareNextInterval(
@@ -975,7 +1177,9 @@ var SchedulerService = class extends import_events2.EventEmitter {
975
1177
  const errorMessage = error.message || "";
976
1178
  const isNoNextIntervalError = errorMessage.includes("0x3cdc51d3") || errorMessage.includes("NoNextInterval");
977
1179
  if (isNoNextIntervalError) {
978
- console.log(` Subscription ${sub.subscriptionId}: NoNextInterval - waiting for client to trigger interval 1`);
1180
+ console.log(
1181
+ ` Subscription ${sub.subscriptionId}: NoNextInterval - waiting for client to trigger interval 1`
1182
+ );
979
1183
  sub.txAttempts = 0;
980
1184
  return;
981
1185
  }
@@ -1057,7 +1261,9 @@ var SchedulerService = class extends import_events2.EventEmitter {
1057
1261
  }
1058
1262
  if (!this.isSubscriptionActive(sub, blockTime)) {
1059
1263
  if (sub.intervalSeconds > 0) {
1060
- console.log(` Sub ${subscriptionId}: inactive (activeAt=${sub.activeAt}, now=${blockTime})`);
1264
+ console.log(
1265
+ ` Sub ${subscriptionId}: inactive (activeAt=${sub.activeAt}, now=${blockTime})`
1266
+ );
1061
1267
  }
1062
1268
  skippedInactive++;
1063
1269
  continue;
@@ -1074,7 +1280,9 @@ var SchedulerService = class extends import_events2.EventEmitter {
1074
1280
  }
1075
1281
  }
1076
1282
  }
1077
- console.log(` Sync stats: ${newSubscriptions} tracked, ${skippedEmpty} empty, ${skippedInactive} inactive, ${skippedContainers} unsupported containers, ${skippedOnDemand} on-demand`);
1283
+ console.log(
1284
+ ` Sync stats: ${newSubscriptions} tracked, ${skippedEmpty} empty, ${skippedInactive} inactive, ${skippedContainers} unsupported containers, ${skippedOnDemand} on-demand`
1285
+ );
1078
1286
  this.lastSyncedId = endId;
1079
1287
  if (newSubscriptions > 0) {
1080
1288
  console.log(`\u2713 Synced ${newSubscriptions} active subscriptions (ID ${startId} - ${endId})`);
@@ -1732,11 +1940,7 @@ var NoosphereAgent = class _NoosphereAgent {
1732
1940
  loadCheckpoint: options.loadCheckpoint,
1733
1941
  saveCheckpoint: options.saveCheckpoint
1734
1942
  });
1735
- this.router = new import_ethers5.ethers.Contract(
1736
- options.config.routerAddress,
1737
- routerAbi,
1738
- this.provider
1739
- );
1943
+ this.router = new import_ethers5.ethers.Contract(options.config.routerAddress, routerAbi, this.provider);
1740
1944
  this.coordinator = new import_ethers5.ethers.Contract(
1741
1945
  options.config.coordinatorAddress,
1742
1946
  coordinatorAbi,
@@ -1891,23 +2095,28 @@ var NoosphereAgent = class _NoosphereAgent {
1891
2095
  console.warn("\u26A0\uFE0F Failed to get SubscriptionBatchReader address:", error.message);
1892
2096
  }
1893
2097
  this.scheduler.start();
1894
- this.scheduler.on("commitment:success", async (data) => {
1895
- if (this.options.onCommitmentSuccess) {
1896
- this.options.onCommitmentSuccess({
1897
- subscriptionId: data.subscriptionId,
1898
- interval: data.interval,
1899
- txHash: data.txHash,
1900
- blockNumber: data.blockNumber,
1901
- gasUsed: data.gasUsed || "0",
1902
- gasPrice: data.gasPrice || "0",
1903
- gasCost: data.gasCost || "0"
1904
- });
1905
- }
1906
- if (data.requestStartedEvent) {
1907
- console.log(` \u{1F4E5} Processing RequestStarted from prepare receipt (fallback for missed WebSocket)`);
1908
- await this.handleRequest(data.requestStartedEvent);
2098
+ this.scheduler.on(
2099
+ "commitment:success",
2100
+ async (data) => {
2101
+ if (this.options.onCommitmentSuccess) {
2102
+ this.options.onCommitmentSuccess({
2103
+ subscriptionId: data.subscriptionId,
2104
+ interval: data.interval,
2105
+ txHash: data.txHash,
2106
+ blockNumber: data.blockNumber,
2107
+ gasUsed: data.gasUsed || "0",
2108
+ gasPrice: data.gasPrice || "0",
2109
+ gasCost: data.gasCost || "0"
2110
+ });
2111
+ }
2112
+ if (data.requestStartedEvent) {
2113
+ console.log(
2114
+ ` \u{1F4E5} Processing RequestStarted from prepare receipt (fallback for missed WebSocket)`
2115
+ );
2116
+ await this.handleRequest(data.requestStartedEvent);
2117
+ }
1909
2118
  }
1910
- });
2119
+ );
1911
2120
  if (this.options.getRetryableEvents && this.options.resetEventForRetry) {
1912
2121
  this.startRetryTimer();
1913
2122
  }
@@ -1923,7 +2132,9 @@ var NoosphereAgent = class _NoosphereAgent {
1923
2132
  if (this.retryTimer) {
1924
2133
  clearInterval(this.retryTimer);
1925
2134
  }
1926
- console.log(`\u{1F504} Retry mechanism enabled: max ${this.maxRetries} retries, check every ${this.retryIntervalMs / 1e3}s`);
2135
+ console.log(
2136
+ `\u{1F504} Retry mechanism enabled: max ${this.maxRetries} retries, check every ${this.retryIntervalMs / 1e3}s`
2137
+ );
1927
2138
  this.retryTimer = setInterval(async () => {
1928
2139
  await this.processRetries();
1929
2140
  }, this.retryIntervalMs);
@@ -1951,7 +2162,9 @@ var NoosphereAgent = class _NoosphereAgent {
1951
2162
  await this.registryManager.reload();
1952
2163
  const newStats = this.registryManager.getStats();
1953
2164
  if (newStats.totalContainers > 0) {
1954
- console.log(`\u2713 Health check: Registry recovered - ${newStats.totalContainers} containers loaded`);
2165
+ console.log(
2166
+ `\u2713 Health check: Registry recovered - ${newStats.totalContainers} containers loaded`
2167
+ );
1955
2168
  } else {
1956
2169
  console.error("\u274C Health check: Registry reload failed - still 0 containers");
1957
2170
  }
@@ -1975,11 +2188,15 @@ var NoosphereAgent = class _NoosphereAgent {
1975
2188
  if (this.processingRequests.has(event.requestId)) {
1976
2189
  return;
1977
2190
  }
1978
- console.log(`\u{1F504} Retrying request ${event.requestId.slice(0, 10)}... (attempt ${event.retryCount + 1}/${this.maxRetries}, ${retryableEvents.length} remaining)`);
2191
+ console.log(
2192
+ `\u{1F504} Retrying request ${event.requestId.slice(0, 10)}... (attempt ${event.retryCount + 1}/${this.maxRetries}, ${retryableEvents.length} remaining)`
2193
+ );
1979
2194
  this.options.resetEventForRetry(event.requestId);
1980
2195
  const container = this.getContainerMetadata(event.containerId);
1981
2196
  if (!container) {
1982
- console.log(` \u26A0\uFE0F Container ${event.containerId.slice(0, 10)}... no longer supported, skipping retry`);
2197
+ console.log(
2198
+ ` \u26A0\uFE0F Container ${event.containerId.slice(0, 10)}... no longer supported, skipping retry`
2199
+ );
1983
2200
  return;
1984
2201
  }
1985
2202
  const retryEvent = {
@@ -1999,7 +2216,9 @@ var NoosphereAgent = class _NoosphereAgent {
1999
2216
  try {
2000
2217
  await this.handleRequest(retryEvent);
2001
2218
  } catch (error) {
2002
- console.log(` \u274C Retry failed for ${event.requestId.slice(0, 10)}...: ${error.message}`);
2219
+ console.log(
2220
+ ` \u274C Retry failed for ${event.requestId.slice(0, 10)}...: ${error.message}`
2221
+ );
2003
2222
  }
2004
2223
  }
2005
2224
  /**
@@ -2061,7 +2280,9 @@ var NoosphereAgent = class _NoosphereAgent {
2061
2280
  console.log(` SubscriptionId: ${event.subscriptionId}`);
2062
2281
  console.log(` Interval: ${event.interval}`);
2063
2282
  console.log(` ContainerId: ${event.containerId.slice(0, 10)}...`);
2064
- console.log(` \u{1F4E6} Container: ${container.name} (${container.image}:${container.tag || "latest"})`);
2283
+ console.log(
2284
+ ` \u{1F4E6} Container: ${container.name} (${container.image}:${container.tag || "latest"})`
2285
+ );
2065
2286
  if (this.options.onRequestStarted) {
2066
2287
  this.options.onRequestStarted({
2067
2288
  requestId: event.requestId,
@@ -2077,13 +2298,18 @@ var NoosphereAgent = class _NoosphereAgent {
2077
2298
  });
2078
2299
  }
2079
2300
  try {
2080
- const currentInterval = await this.router.getComputeSubscriptionInterval(event.subscriptionId);
2301
+ const currentInterval = await this.router.getComputeSubscriptionInterval(
2302
+ event.subscriptionId
2303
+ );
2081
2304
  const eventInterval = Number(event.interval);
2082
2305
  const isOneTimeExecution = currentInterval === 4294967295n;
2083
2306
  if (!isOneTimeExecution && currentInterval > eventInterval + 2) {
2084
2307
  console.log(` \u23ED\uFE0F Skipping old interval ${eventInterval} (current: ${currentInterval})`);
2085
2308
  if (this.options.onRequestSkipped) {
2086
- this.options.onRequestSkipped(event.requestId, `Old interval ${eventInterval} (current: ${currentInterval})`);
2309
+ this.options.onRequestSkipped(
2310
+ event.requestId,
2311
+ `Old interval ${eventInterval} (current: ${currentInterval})`
2312
+ );
2087
2313
  }
2088
2314
  this.processingRequests.delete(event.requestId);
2089
2315
  return;
@@ -2102,7 +2328,10 @@ var NoosphereAgent = class _NoosphereAgent {
2102
2328
  if (currentCount >= event.redundancy) {
2103
2329
  console.log(` \u23ED\uFE0F Already fulfilled (${currentCount}/${event.redundancy}), skipping`);
2104
2330
  if (this.options.onRequestSkipped) {
2105
- this.options.onRequestSkipped(event.requestId, `Already fulfilled (${currentCount}/${event.redundancy})`);
2331
+ this.options.onRequestSkipped(
2332
+ event.requestId,
2333
+ `Already fulfilled (${currentCount}/${event.redundancy})`
2334
+ );
2106
2335
  }
2107
2336
  this.processingRequests.delete(event.requestId);
2108
2337
  return;
@@ -2112,7 +2341,10 @@ var NoosphereAgent = class _NoosphereAgent {
2112
2341
  if (!clientAddress || clientAddress === "0x0000000000000000000000000000000000000000") {
2113
2342
  console.error(` \u274C Invalid client address for subscription ${event.subscriptionId}`);
2114
2343
  if (this.options.onRequestFailed) {
2115
- this.options.onRequestFailed(event.requestId, `Invalid client address for subscription ${event.subscriptionId}`);
2344
+ this.options.onRequestFailed(
2345
+ event.requestId,
2346
+ `Invalid client address for subscription ${event.subscriptionId}`
2347
+ );
2116
2348
  }
2117
2349
  return;
2118
2350
  }
@@ -2164,7 +2396,10 @@ var NoosphereAgent = class _NoosphereAgent {
2164
2396
  const errorMessage = resolveError.message || String(resolveError);
2165
2397
  console.error(` \u274C Failed to resolve PayloadData:`, resolveError);
2166
2398
  if (this.options.onRequestFailed) {
2167
- this.options.onRequestFailed(event.requestId, `Failed to resolve PayloadData: ${errorMessage}`);
2399
+ this.options.onRequestFailed(
2400
+ event.requestId,
2401
+ `Failed to resolve PayloadData: ${errorMessage}`
2402
+ );
2168
2403
  }
2169
2404
  return;
2170
2405
  }
@@ -2186,7 +2421,10 @@ var NoosphereAgent = class _NoosphereAgent {
2186
2421
  console.error(` \u274C Container execution failed with exit code ${result.exitCode}`);
2187
2422
  console.error(` \u{1F4C4} Container output:`, result.output);
2188
2423
  if (this.options.onRequestFailed) {
2189
- this.options.onRequestFailed(event.requestId, `Container execution failed with exit code ${result.exitCode}`);
2424
+ this.options.onRequestFailed(
2425
+ event.requestId,
2426
+ `Container execution failed with exit code ${result.exitCode}`
2427
+ );
2190
2428
  }
2191
2429
  return;
2192
2430
  }
@@ -2306,6 +2544,20 @@ var NoosphereAgent = class _NoosphereAgent {
2306
2544
  getScheduler() {
2307
2545
  return this.scheduler;
2308
2546
  }
2547
+ /**
2548
+ * Get current connection state
2549
+ * @returns Connection state: 'INIT' | 'WS_CONNECTING' | 'WS_ACTIVE' | 'WS_RECONNECTING' | 'HTTP_FALLBACK'
2550
+ */
2551
+ getConnectionState() {
2552
+ return this.eventMonitor.getConnectionState();
2553
+ }
2554
+ /**
2555
+ * Get current connection mode
2556
+ * @returns Connection mode: 'websocket' | 'http_polling' | 'connecting'
2557
+ */
2558
+ getConnectionMode() {
2559
+ return this.eventMonitor.getConnectionMode();
2560
+ }
2309
2561
  };
2310
2562
 
2311
2563
  // src/types/index.ts