@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.js CHANGED
@@ -8,6 +8,12 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
  // src/EventMonitor.ts
9
9
  import { EventEmitter } from "events";
10
10
  import { ethers } from "ethers";
11
+ var DEFAULT_CONNECTION_CONFIG = {
12
+ wsConnectTimeoutMs: 1e4,
13
+ wsMaxConnectRetries: 3,
14
+ wsConnectRetryDelayMs: 5e3,
15
+ wsRecoveryIntervalMs: 6e4
16
+ };
11
17
  var EventMonitor = class extends EventEmitter {
12
18
  constructor(config, routerAbi, coordinatorAbi, options) {
13
19
  super();
@@ -19,24 +25,115 @@ var EventMonitor = class extends EventEmitter {
19
25
  this.isReconnecting = false;
20
26
  this.heartbeatInterval = null;
21
27
  this.lastEventTime = Date.now();
28
+ // Connection state management
29
+ this.connectionState = "INIT";
30
+ this.wsRecoveryInterval = null;
31
+ this.pollingInterval = null;
22
32
  this.lastProcessedBlock = config.deploymentBlock || 0;
23
33
  this.useWebSocket = false;
24
34
  this.checkpointCallbacks = options;
35
+ this.connectionConfig = {
36
+ ...DEFAULT_CONNECTION_CONFIG,
37
+ ...options?.connectionConfig
38
+ };
39
+ }
40
+ /**
41
+ * Get current connection state
42
+ */
43
+ getConnectionState() {
44
+ return this.connectionState;
45
+ }
46
+ /**
47
+ * Get connection mode string for health API
48
+ */
49
+ getConnectionMode() {
50
+ switch (this.connectionState) {
51
+ case "WS_ACTIVE":
52
+ return "websocket";
53
+ case "HTTP_FALLBACK":
54
+ return "http_polling";
55
+ default:
56
+ return "connecting";
57
+ }
25
58
  }
26
59
  async connect() {
27
- try {
28
- if (this.config.wsRpcUrl) {
29
- this.provider = new ethers.WebSocketProvider(this.config.wsRpcUrl);
30
- this.useWebSocket = true;
31
- console.log("\u2713 Connected via WebSocket (push-based events)");
32
- } else {
33
- throw new Error("WebSocket URL not provided");
60
+ const { wsMaxConnectRetries, wsConnectRetryDelayMs } = this.connectionConfig;
61
+ if (this.config.wsRpcUrl) {
62
+ this.connectionState = "WS_CONNECTING";
63
+ for (let attempt = 1; attempt <= wsMaxConnectRetries; attempt++) {
64
+ try {
65
+ console.log(`\u{1F50C} WebSocket connection attempt ${attempt}/${wsMaxConnectRetries}...`);
66
+ await this.connectWebSocketWithTimeout();
67
+ this.connectionState = "WS_ACTIVE";
68
+ this.useWebSocket = true;
69
+ console.log("\u2713 Connected via WebSocket (push-based events)");
70
+ this.initializeContracts();
71
+ return;
72
+ } catch (error) {
73
+ const errorMessage = error instanceof Error ? error.message : String(error);
74
+ console.warn(`\u26A0\uFE0F WebSocket attempt ${attempt}/${wsMaxConnectRetries} failed: ${errorMessage}`);
75
+ if (attempt < wsMaxConnectRetries) {
76
+ console.log(` Retrying in ${wsConnectRetryDelayMs / 1e3}s...`);
77
+ await this.sleep(wsConnectRetryDelayMs);
78
+ }
79
+ }
80
+ }
81
+ console.warn(`\u26A0\uFE0F All ${wsMaxConnectRetries} WebSocket attempts failed, falling back to HTTP polling`);
82
+ } else {
83
+ console.log("\u2139\uFE0F No WebSocket URL provided, using HTTP polling");
84
+ }
85
+ await this.connectHttp();
86
+ }
87
+ /**
88
+ * Connect via WebSocket with timeout
89
+ */
90
+ async connectWebSocketWithTimeout() {
91
+ const { wsConnectTimeoutMs } = this.connectionConfig;
92
+ return new Promise((resolve, reject) => {
93
+ const timeout = setTimeout(() => {
94
+ reject(new Error(`WebSocket connection timeout after ${wsConnectTimeoutMs}ms`));
95
+ }, wsConnectTimeoutMs);
96
+ try {
97
+ const wsProvider = new ethers.WebSocketProvider(this.config.wsRpcUrl);
98
+ wsProvider.getBlockNumber().then(() => {
99
+ clearTimeout(timeout);
100
+ this.provider = wsProvider;
101
+ resolve();
102
+ }).catch((error) => {
103
+ clearTimeout(timeout);
104
+ wsProvider.destroy().catch(() => {
105
+ });
106
+ reject(error);
107
+ });
108
+ } catch (error) {
109
+ clearTimeout(timeout);
110
+ reject(error);
34
111
  }
112
+ });
113
+ }
114
+ /**
115
+ * Connect via HTTP (fallback mode)
116
+ */
117
+ async connectHttp() {
118
+ this.connectionState = "HTTP_FALLBACK";
119
+ this.useWebSocket = false;
120
+ this.provider = new ethers.JsonRpcProvider(this.config.rpcUrl);
121
+ try {
122
+ await this.provider.getBlockNumber();
123
+ console.log("\u2713 Connected via HTTP polling (fallback mode)");
35
124
  } catch (error) {
36
- console.warn("WebSocket unavailable, falling back to HTTP polling");
37
- this.provider = new ethers.JsonRpcProvider(this.config.rpcUrl);
38
- this.useWebSocket = false;
125
+ const errorMessage = error instanceof Error ? error.message : String(error);
126
+ throw new Error(`HTTP connection failed: ${errorMessage}`);
127
+ }
128
+ this.initializeContracts();
129
+ if (this.config.wsRpcUrl) {
130
+ this.startWsRecoveryLoop();
39
131
  }
132
+ }
133
+ /**
134
+ * Initialize contract instances
135
+ */
136
+ initializeContracts() {
40
137
  this.router = new ethers.Contract(this.config.routerAddress, this.routerAbi, this.provider);
41
138
  this.coordinator = new ethers.Contract(
42
139
  this.config.coordinatorAddress,
@@ -44,6 +141,63 @@ var EventMonitor = class extends EventEmitter {
44
141
  this.provider
45
142
  );
46
143
  }
144
+ /**
145
+ * Start WS recovery loop when in HTTP fallback mode
146
+ */
147
+ startWsRecoveryLoop() {
148
+ if (this.wsRecoveryInterval) {
149
+ clearInterval(this.wsRecoveryInterval);
150
+ }
151
+ const { wsRecoveryIntervalMs } = this.connectionConfig;
152
+ console.log(`\u{1F504} Starting WS recovery loop (every ${wsRecoveryIntervalMs / 1e3}s)`);
153
+ this.wsRecoveryInterval = setInterval(async () => {
154
+ if (this.connectionState !== "HTTP_FALLBACK") {
155
+ this.stopWsRecoveryLoop();
156
+ return;
157
+ }
158
+ console.log("\u{1F50C} Attempting WebSocket recovery...");
159
+ try {
160
+ await this.connectWebSocketWithTimeout();
161
+ console.log("\u2713 WebSocket connection recovered!");
162
+ this.stopPolling();
163
+ this.connectionState = "WS_ACTIVE";
164
+ this.useWebSocket = true;
165
+ this.initializeContracts();
166
+ await this.replayMissedEvents();
167
+ await this.startWebSocketListening();
168
+ this.stopWsRecoveryLoop();
169
+ this.emit("connectionRecovered", { mode: "websocket" });
170
+ } catch (error) {
171
+ const errorMessage = error instanceof Error ? error.message : String(error);
172
+ console.log(` WS recovery failed: ${errorMessage}, will retry in ${wsRecoveryIntervalMs / 1e3}s`);
173
+ }
174
+ }, wsRecoveryIntervalMs);
175
+ }
176
+ /**
177
+ * Stop WS recovery loop
178
+ */
179
+ stopWsRecoveryLoop() {
180
+ if (this.wsRecoveryInterval) {
181
+ clearInterval(this.wsRecoveryInterval);
182
+ this.wsRecoveryInterval = null;
183
+ console.log("\u{1F504} WS recovery loop stopped");
184
+ }
185
+ }
186
+ /**
187
+ * Stop HTTP polling
188
+ */
189
+ stopPolling() {
190
+ if (this.pollingInterval) {
191
+ clearInterval(this.pollingInterval);
192
+ this.pollingInterval = null;
193
+ }
194
+ }
195
+ /**
196
+ * Sleep helper
197
+ */
198
+ sleep(ms) {
199
+ return new Promise((resolve) => setTimeout(resolve, ms));
200
+ }
47
201
  async start() {
48
202
  if (this.checkpointCallbacks?.loadCheckpoint) {
49
203
  const checkpoint = this.checkpointCallbacks.loadCheckpoint();
@@ -115,7 +269,9 @@ var EventMonitor = class extends EventEmitter {
115
269
  const blockNumber = await this.provider.getBlockNumber();
116
270
  const timeSinceLastEvent = Date.now() - this.lastEventTime;
117
271
  if (timeSinceLastEvent > 18e4 && blockNumber > this.lastProcessedBlock + 5) {
118
- console.log(`\u26A0\uFE0F No events for ${Math.round(timeSinceLastEvent / 1e3)}s, checking for missed events...`);
272
+ console.log(
273
+ `\u26A0\uFE0F No events for ${Math.round(timeSinceLastEvent / 1e3)}s, checking for missed events...`
274
+ );
119
275
  await this.replayMissedEvents();
120
276
  }
121
277
  }
@@ -129,7 +285,9 @@ var EventMonitor = class extends EventEmitter {
129
285
  try {
130
286
  const currentBlock = await this.provider.getBlockNumber();
131
287
  if (currentBlock > this.lastProcessedBlock) {
132
- console.log(`\u{1F4E5} Replaying events from block ${this.lastProcessedBlock + 1} to ${currentBlock}`);
288
+ console.log(
289
+ `\u{1F4E5} Replaying events from block ${this.lastProcessedBlock + 1} to ${currentBlock}`
290
+ );
133
291
  await this.replayEvents(this.lastProcessedBlock + 1, currentBlock);
134
292
  this.lastEventTime = Date.now();
135
293
  }
@@ -140,6 +298,7 @@ var EventMonitor = class extends EventEmitter {
140
298
  handleDisconnect() {
141
299
  if (this.isReconnecting) return;
142
300
  this.isReconnecting = true;
301
+ this.connectionState = "WS_RECONNECTING";
143
302
  if (this.heartbeatInterval) {
144
303
  clearInterval(this.heartbeatInterval);
145
304
  this.heartbeatInterval = null;
@@ -150,9 +309,10 @@ var EventMonitor = class extends EventEmitter {
150
309
  }
151
310
  async startPolling() {
152
311
  console.log("Starting HTTP polling (fallback mode)...");
153
- const pollingInterval = this.config.pollingInterval || 12e3;
312
+ this.stopPolling();
313
+ const pollingIntervalMs = this.config.pollingInterval || 12e3;
154
314
  let lastBlock = await this.provider.getBlockNumber();
155
- setInterval(async () => {
315
+ this.pollingInterval = setInterval(async () => {
156
316
  try {
157
317
  const currentBlock = await this.provider.getBlockNumber();
158
318
  if (currentBlock > lastBlock) {
@@ -172,7 +332,7 @@ var EventMonitor = class extends EventEmitter {
172
332
  } catch (error) {
173
333
  console.error("Polling error:", error);
174
334
  }
175
- }, pollingInterval);
335
+ }, pollingIntervalMs);
176
336
  }
177
337
  async processEvent(event) {
178
338
  const commitment = event.args.commitment;
@@ -195,16 +355,22 @@ var EventMonitor = class extends EventEmitter {
195
355
  async reconnect() {
196
356
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
197
357
  console.error("Max reconnection attempts reached. Falling back to HTTP polling.");
198
- this.useWebSocket = false;
199
- await this.connect();
358
+ if (this.provider instanceof ethers.WebSocketProvider) {
359
+ try {
360
+ await this.provider.destroy();
361
+ } catch {
362
+ }
363
+ }
364
+ await this.connectHttp();
200
365
  await this.startPolling();
366
+ this.reconnectAttempts = 0;
201
367
  return;
202
368
  }
203
369
  const backoff = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 6e4);
204
370
  console.log(
205
371
  `\u{1F504} Reconnecting in ${backoff}ms (attempt ${this.reconnectAttempts + 1}/${this.maxReconnectAttempts})`
206
372
  );
207
- await new Promise((resolve) => setTimeout(resolve, backoff));
373
+ await this.sleep(backoff);
208
374
  try {
209
375
  if (this.coordinator) {
210
376
  this.coordinator.removeAllListeners();
@@ -215,14 +381,19 @@ var EventMonitor = class extends EventEmitter {
215
381
  } catch {
216
382
  }
217
383
  }
218
- await this.connect();
384
+ await this.connectWebSocketWithTimeout();
385
+ this.connectionState = "WS_ACTIVE";
386
+ this.useWebSocket = true;
387
+ this.initializeContracts();
219
388
  await this.replayMissedEvents();
220
389
  await this.startWebSocketListening();
221
390
  console.log("\u2713 Reconnected successfully");
222
391
  this.reconnectAttempts = 0;
223
392
  this.lastEventTime = Date.now();
393
+ this.emit("connectionRecovered", { mode: "websocket" });
224
394
  } catch (error) {
225
- console.error("Reconnection failed:", error);
395
+ const errorMessage = error instanceof Error ? error.message : String(error);
396
+ console.error(`Reconnection failed: ${errorMessage}`);
226
397
  this.reconnectAttempts++;
227
398
  await this.reconnect();
228
399
  }
@@ -241,6 +412,8 @@ var EventMonitor = class extends EventEmitter {
241
412
  clearInterval(this.heartbeatInterval);
242
413
  this.heartbeatInterval = null;
243
414
  }
415
+ this.stopWsRecoveryLoop();
416
+ this.stopPolling();
244
417
  if (this.router) {
245
418
  this.router.removeAllListeners();
246
419
  }
@@ -250,6 +423,7 @@ var EventMonitor = class extends EventEmitter {
250
423
  if (this.provider instanceof ethers.WebSocketProvider) {
251
424
  await this.provider.destroy();
252
425
  }
426
+ this.connectionState = "INIT";
253
427
  }
254
428
  };
255
429
 
@@ -304,7 +478,9 @@ var ContainerManager = class {
304
478
  lastError = error;
305
479
  if (error.code === "ECONNREFUSED") {
306
480
  if (attempt < connectionRetries) {
307
- console.log(` \u23F3 Container not ready (attempt ${attempt}/${connectionRetries}), retrying in ${connectionRetryDelayMs / 1e3}s...`);
481
+ console.log(
482
+ ` \u23F3 Container not ready (attempt ${attempt}/${connectionRetries}), retrying in ${connectionRetryDelayMs / 1e3}s...`
483
+ );
308
484
  await this.sleep(connectionRetryDelayMs);
309
485
  continue;
310
486
  }
@@ -483,7 +659,10 @@ var ContainerManager = class {
483
659
  try {
484
660
  await this.startPersistentContainer(id, container);
485
661
  } catch (error) {
486
- console.error(` \u274C Failed to start ${container.name || container.image}:`, error.message);
662
+ console.error(
663
+ ` \u274C Failed to start ${container.name || container.image}:`,
664
+ error.message
665
+ );
487
666
  }
488
667
  });
489
668
  await Promise.all(pullAndStartPromises);
@@ -766,15 +945,22 @@ var SchedulerService = class extends EventEmitter2 {
766
945
  let currentInterval = 0n;
767
946
  try {
768
947
  if (sub.intervalSeconds <= 0n) {
769
- console.warn(` Skipping subscription ${subId}: invalid intervalSeconds (${sub.intervalSeconds})`);
948
+ console.warn(
949
+ ` Skipping subscription ${subId}: invalid intervalSeconds (${sub.intervalSeconds})`
950
+ );
770
951
  this.untrackSubscription(sub.subscriptionId);
771
952
  continue;
772
953
  }
773
954
  try {
774
- currentInterval = BigInt(await this.router.getComputeSubscriptionInterval(sub.subscriptionId));
955
+ currentInterval = BigInt(
956
+ await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
957
+ );
775
958
  } catch (error) {
776
959
  const errorMessage = error.message || "";
777
- console.warn(` Could not get interval from router for subscription ${subId}:`, errorMessage);
960
+ console.warn(
961
+ ` Could not get interval from router for subscription ${subId}:`,
962
+ errorMessage
963
+ );
778
964
  if (errorMessage.includes("SubscriptionNotFound")) {
779
965
  console.log(` Subscription ${subId} not found (cancelled), untracking...`);
780
966
  this.untrackSubscription(sub.subscriptionId);
@@ -783,7 +969,9 @@ var SchedulerService = class extends EventEmitter2 {
783
969
  const intervalsSinceActive = BigInt(currentBlockTime) - sub.activeAt;
784
970
  currentInterval = intervalsSinceActive / sub.intervalSeconds + 1n;
785
971
  }
786
- console.log(` Subscription ${subId}: currentInterval=${currentInterval}, maxExecutions=${sub.maxExecutions}, activeAt=${sub.activeAt}`);
972
+ console.log(
973
+ ` Subscription ${subId}: currentInterval=${currentInterval}, maxExecutions=${sub.maxExecutions}, activeAt=${sub.activeAt}`
974
+ );
787
975
  if (!this.shouldProcess(sub, currentBlockTime)) {
788
976
  continue;
789
977
  }
@@ -810,12 +998,16 @@ var SchedulerService = class extends EventEmitter2 {
810
998
  return false;
811
999
  };
812
1000
  if (containsError(error, "Panic due to OVERFLOW") || containsError(error, "arithmetic underflow or overflow")) {
813
- console.log(` Interval ${currentInterval} for subscription ${subId} appears to be already executed (overflow), marking as committed`);
1001
+ console.log(
1002
+ ` Interval ${currentInterval} for subscription ${subId} appears to be already executed (overflow), marking as committed`
1003
+ );
814
1004
  const commitmentKey = `${subId}:${currentInterval}`;
815
1005
  this.addCommittedInterval(commitmentKey);
816
1006
  sub.currentInterval = currentInterval + 1n;
817
1007
  } else if (containsError(error, "0x3cdc51d3") || containsError(error, "NoNextInterval")) {
818
- console.log(` Subscription ${subId}: waiting for client to trigger interval 1 (NoNextInterval)`);
1008
+ console.log(
1009
+ ` Subscription ${subId}: waiting for client to trigger interval 1 (NoNextInterval)`
1010
+ );
819
1011
  } else if (containsError(error, "execution reverted") || containsError(error, "Transaction simulation failed")) {
820
1012
  console.log(` Subscription ${subId} appears to be cancelled or invalid, untracking...`);
821
1013
  this.untrackSubscription(sub.subscriptionId);
@@ -829,13 +1021,17 @@ var SchedulerService = class extends EventEmitter2 {
829
1021
  shouldProcess(sub, currentBlockTime) {
830
1022
  const subId = sub.subscriptionId.toString();
831
1023
  if (BigInt(currentBlockTime) < sub.activeAt) {
832
- console.log(` Skip: not active yet (currentTime=${currentBlockTime}, activeAt=${sub.activeAt})`);
1024
+ console.log(
1025
+ ` Skip: not active yet (currentTime=${currentBlockTime}, activeAt=${sub.activeAt})`
1026
+ );
833
1027
  return false;
834
1028
  }
835
1029
  const intervalsSinceActive = BigInt(currentBlockTime) - sub.activeAt;
836
1030
  const currentInterval = intervalsSinceActive / sub.intervalSeconds + 1n;
837
1031
  if (sub.maxExecutions > 0n && currentInterval > sub.maxExecutions) {
838
- console.log(` Subscription ${subId} completed (interval ${currentInterval} > maxExecutions ${sub.maxExecutions}), untracking...`);
1032
+ console.log(
1033
+ ` Subscription ${subId} completed (interval ${currentInterval} > maxExecutions ${sub.maxExecutions}), untracking...`
1034
+ );
839
1035
  this.untrackSubscription(sub.subscriptionId);
840
1036
  return false;
841
1037
  }
@@ -845,7 +1041,9 @@ var SchedulerService = class extends EventEmitter2 {
845
1041
  return false;
846
1042
  }
847
1043
  if (sub.txAttempts >= this.config.maxRetryAttempts) {
848
- console.log(` Skip: max retry attempts reached (${sub.txAttempts}/${this.config.maxRetryAttempts})`);
1044
+ console.log(
1045
+ ` Skip: max retry attempts reached (${sub.txAttempts}/${this.config.maxRetryAttempts})`
1046
+ );
849
1047
  return false;
850
1048
  }
851
1049
  return true;
@@ -872,9 +1070,13 @@ var SchedulerService = class extends EventEmitter2 {
872
1070
  const runKey = `${sub.subscriptionId}:${interval}`;
873
1071
  try {
874
1072
  console.log(` Preparing interval ${interval} for subscription ${sub.subscriptionId}...`);
875
- const currentIntervalNow = BigInt(await this.router.getComputeSubscriptionInterval(sub.subscriptionId));
1073
+ const currentIntervalNow = BigInt(
1074
+ await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
1075
+ );
876
1076
  if (currentIntervalNow !== interval && currentIntervalNow !== interval - 1n) {
877
- console.log(` \u26A0\uFE0F Interval changed: expected ${interval}, blockchain is at ${currentIntervalNow}. Skipping.`);
1077
+ console.log(
1078
+ ` \u26A0\uFE0F Interval changed: expected ${interval}, blockchain is at ${currentIntervalNow}. Skipping.`
1079
+ );
878
1080
  return;
879
1081
  }
880
1082
  const tx = await this.coordinator.prepareNextInterval(
@@ -922,7 +1124,9 @@ var SchedulerService = class extends EventEmitter2 {
922
1124
  const errorMessage = error.message || "";
923
1125
  const isNoNextIntervalError = errorMessage.includes("0x3cdc51d3") || errorMessage.includes("NoNextInterval");
924
1126
  if (isNoNextIntervalError) {
925
- console.log(` Subscription ${sub.subscriptionId}: NoNextInterval - waiting for client to trigger interval 1`);
1127
+ console.log(
1128
+ ` Subscription ${sub.subscriptionId}: NoNextInterval - waiting for client to trigger interval 1`
1129
+ );
926
1130
  sub.txAttempts = 0;
927
1131
  return;
928
1132
  }
@@ -1004,7 +1208,9 @@ var SchedulerService = class extends EventEmitter2 {
1004
1208
  }
1005
1209
  if (!this.isSubscriptionActive(sub, blockTime)) {
1006
1210
  if (sub.intervalSeconds > 0) {
1007
- console.log(` Sub ${subscriptionId}: inactive (activeAt=${sub.activeAt}, now=${blockTime})`);
1211
+ console.log(
1212
+ ` Sub ${subscriptionId}: inactive (activeAt=${sub.activeAt}, now=${blockTime})`
1213
+ );
1008
1214
  }
1009
1215
  skippedInactive++;
1010
1216
  continue;
@@ -1021,7 +1227,9 @@ var SchedulerService = class extends EventEmitter2 {
1021
1227
  }
1022
1228
  }
1023
1229
  }
1024
- console.log(` Sync stats: ${newSubscriptions} tracked, ${skippedEmpty} empty, ${skippedInactive} inactive, ${skippedContainers} unsupported containers, ${skippedOnDemand} on-demand`);
1230
+ console.log(
1231
+ ` Sync stats: ${newSubscriptions} tracked, ${skippedEmpty} empty, ${skippedInactive} inactive, ${skippedContainers} unsupported containers, ${skippedOnDemand} on-demand`
1232
+ );
1025
1233
  this.lastSyncedId = endId;
1026
1234
  if (newSubscriptions > 0) {
1027
1235
  console.log(`\u2713 Synced ${newSubscriptions} active subscriptions (ID ${startId} - ${endId})`);
@@ -1696,11 +1904,7 @@ var NoosphereAgent = class _NoosphereAgent {
1696
1904
  loadCheckpoint: options.loadCheckpoint,
1697
1905
  saveCheckpoint: options.saveCheckpoint
1698
1906
  });
1699
- this.router = new ethers5.Contract(
1700
- options.config.routerAddress,
1701
- routerAbi,
1702
- this.provider
1703
- );
1907
+ this.router = new ethers5.Contract(options.config.routerAddress, routerAbi, this.provider);
1704
1908
  this.coordinator = new ethers5.Contract(
1705
1909
  options.config.coordinatorAddress,
1706
1910
  coordinatorAbi,
@@ -1855,23 +2059,28 @@ var NoosphereAgent = class _NoosphereAgent {
1855
2059
  console.warn("\u26A0\uFE0F Failed to get SubscriptionBatchReader address:", error.message);
1856
2060
  }
1857
2061
  this.scheduler.start();
1858
- this.scheduler.on("commitment:success", async (data) => {
1859
- if (this.options.onCommitmentSuccess) {
1860
- this.options.onCommitmentSuccess({
1861
- subscriptionId: data.subscriptionId,
1862
- interval: data.interval,
1863
- txHash: data.txHash,
1864
- blockNumber: data.blockNumber,
1865
- gasUsed: data.gasUsed || "0",
1866
- gasPrice: data.gasPrice || "0",
1867
- gasCost: data.gasCost || "0"
1868
- });
1869
- }
1870
- if (data.requestStartedEvent) {
1871
- console.log(` \u{1F4E5} Processing RequestStarted from prepare receipt (fallback for missed WebSocket)`);
1872
- await this.handleRequest(data.requestStartedEvent);
2062
+ this.scheduler.on(
2063
+ "commitment:success",
2064
+ async (data) => {
2065
+ if (this.options.onCommitmentSuccess) {
2066
+ this.options.onCommitmentSuccess({
2067
+ subscriptionId: data.subscriptionId,
2068
+ interval: data.interval,
2069
+ txHash: data.txHash,
2070
+ blockNumber: data.blockNumber,
2071
+ gasUsed: data.gasUsed || "0",
2072
+ gasPrice: data.gasPrice || "0",
2073
+ gasCost: data.gasCost || "0"
2074
+ });
2075
+ }
2076
+ if (data.requestStartedEvent) {
2077
+ console.log(
2078
+ ` \u{1F4E5} Processing RequestStarted from prepare receipt (fallback for missed WebSocket)`
2079
+ );
2080
+ await this.handleRequest(data.requestStartedEvent);
2081
+ }
1873
2082
  }
1874
- });
2083
+ );
1875
2084
  if (this.options.getRetryableEvents && this.options.resetEventForRetry) {
1876
2085
  this.startRetryTimer();
1877
2086
  }
@@ -1887,7 +2096,9 @@ var NoosphereAgent = class _NoosphereAgent {
1887
2096
  if (this.retryTimer) {
1888
2097
  clearInterval(this.retryTimer);
1889
2098
  }
1890
- console.log(`\u{1F504} Retry mechanism enabled: max ${this.maxRetries} retries, check every ${this.retryIntervalMs / 1e3}s`);
2099
+ console.log(
2100
+ `\u{1F504} Retry mechanism enabled: max ${this.maxRetries} retries, check every ${this.retryIntervalMs / 1e3}s`
2101
+ );
1891
2102
  this.retryTimer = setInterval(async () => {
1892
2103
  await this.processRetries();
1893
2104
  }, this.retryIntervalMs);
@@ -1915,7 +2126,9 @@ var NoosphereAgent = class _NoosphereAgent {
1915
2126
  await this.registryManager.reload();
1916
2127
  const newStats = this.registryManager.getStats();
1917
2128
  if (newStats.totalContainers > 0) {
1918
- console.log(`\u2713 Health check: Registry recovered - ${newStats.totalContainers} containers loaded`);
2129
+ console.log(
2130
+ `\u2713 Health check: Registry recovered - ${newStats.totalContainers} containers loaded`
2131
+ );
1919
2132
  } else {
1920
2133
  console.error("\u274C Health check: Registry reload failed - still 0 containers");
1921
2134
  }
@@ -1939,11 +2152,15 @@ var NoosphereAgent = class _NoosphereAgent {
1939
2152
  if (this.processingRequests.has(event.requestId)) {
1940
2153
  return;
1941
2154
  }
1942
- console.log(`\u{1F504} Retrying request ${event.requestId.slice(0, 10)}... (attempt ${event.retryCount + 1}/${this.maxRetries}, ${retryableEvents.length} remaining)`);
2155
+ console.log(
2156
+ `\u{1F504} Retrying request ${event.requestId.slice(0, 10)}... (attempt ${event.retryCount + 1}/${this.maxRetries}, ${retryableEvents.length} remaining)`
2157
+ );
1943
2158
  this.options.resetEventForRetry(event.requestId);
1944
2159
  const container = this.getContainerMetadata(event.containerId);
1945
2160
  if (!container) {
1946
- console.log(` \u26A0\uFE0F Container ${event.containerId.slice(0, 10)}... no longer supported, skipping retry`);
2161
+ console.log(
2162
+ ` \u26A0\uFE0F Container ${event.containerId.slice(0, 10)}... no longer supported, skipping retry`
2163
+ );
1947
2164
  return;
1948
2165
  }
1949
2166
  const retryEvent = {
@@ -1963,7 +2180,9 @@ var NoosphereAgent = class _NoosphereAgent {
1963
2180
  try {
1964
2181
  await this.handleRequest(retryEvent);
1965
2182
  } catch (error) {
1966
- console.log(` \u274C Retry failed for ${event.requestId.slice(0, 10)}...: ${error.message}`);
2183
+ console.log(
2184
+ ` \u274C Retry failed for ${event.requestId.slice(0, 10)}...: ${error.message}`
2185
+ );
1967
2186
  }
1968
2187
  }
1969
2188
  /**
@@ -2025,7 +2244,9 @@ var NoosphereAgent = class _NoosphereAgent {
2025
2244
  console.log(` SubscriptionId: ${event.subscriptionId}`);
2026
2245
  console.log(` Interval: ${event.interval}`);
2027
2246
  console.log(` ContainerId: ${event.containerId.slice(0, 10)}...`);
2028
- console.log(` \u{1F4E6} Container: ${container.name} (${container.image}:${container.tag || "latest"})`);
2247
+ console.log(
2248
+ ` \u{1F4E6} Container: ${container.name} (${container.image}:${container.tag || "latest"})`
2249
+ );
2029
2250
  if (this.options.onRequestStarted) {
2030
2251
  this.options.onRequestStarted({
2031
2252
  requestId: event.requestId,
@@ -2041,13 +2262,18 @@ var NoosphereAgent = class _NoosphereAgent {
2041
2262
  });
2042
2263
  }
2043
2264
  try {
2044
- const currentInterval = await this.router.getComputeSubscriptionInterval(event.subscriptionId);
2265
+ const currentInterval = await this.router.getComputeSubscriptionInterval(
2266
+ event.subscriptionId
2267
+ );
2045
2268
  const eventInterval = Number(event.interval);
2046
2269
  const isOneTimeExecution = currentInterval === 4294967295n;
2047
2270
  if (!isOneTimeExecution && currentInterval > eventInterval + 2) {
2048
2271
  console.log(` \u23ED\uFE0F Skipping old interval ${eventInterval} (current: ${currentInterval})`);
2049
2272
  if (this.options.onRequestSkipped) {
2050
- this.options.onRequestSkipped(event.requestId, `Old interval ${eventInterval} (current: ${currentInterval})`);
2273
+ this.options.onRequestSkipped(
2274
+ event.requestId,
2275
+ `Old interval ${eventInterval} (current: ${currentInterval})`
2276
+ );
2051
2277
  }
2052
2278
  this.processingRequests.delete(event.requestId);
2053
2279
  return;
@@ -2066,7 +2292,10 @@ var NoosphereAgent = class _NoosphereAgent {
2066
2292
  if (currentCount >= event.redundancy) {
2067
2293
  console.log(` \u23ED\uFE0F Already fulfilled (${currentCount}/${event.redundancy}), skipping`);
2068
2294
  if (this.options.onRequestSkipped) {
2069
- this.options.onRequestSkipped(event.requestId, `Already fulfilled (${currentCount}/${event.redundancy})`);
2295
+ this.options.onRequestSkipped(
2296
+ event.requestId,
2297
+ `Already fulfilled (${currentCount}/${event.redundancy})`
2298
+ );
2070
2299
  }
2071
2300
  this.processingRequests.delete(event.requestId);
2072
2301
  return;
@@ -2076,7 +2305,10 @@ var NoosphereAgent = class _NoosphereAgent {
2076
2305
  if (!clientAddress || clientAddress === "0x0000000000000000000000000000000000000000") {
2077
2306
  console.error(` \u274C Invalid client address for subscription ${event.subscriptionId}`);
2078
2307
  if (this.options.onRequestFailed) {
2079
- this.options.onRequestFailed(event.requestId, `Invalid client address for subscription ${event.subscriptionId}`);
2308
+ this.options.onRequestFailed(
2309
+ event.requestId,
2310
+ `Invalid client address for subscription ${event.subscriptionId}`
2311
+ );
2080
2312
  }
2081
2313
  return;
2082
2314
  }
@@ -2128,7 +2360,10 @@ var NoosphereAgent = class _NoosphereAgent {
2128
2360
  const errorMessage = resolveError.message || String(resolveError);
2129
2361
  console.error(` \u274C Failed to resolve PayloadData:`, resolveError);
2130
2362
  if (this.options.onRequestFailed) {
2131
- this.options.onRequestFailed(event.requestId, `Failed to resolve PayloadData: ${errorMessage}`);
2363
+ this.options.onRequestFailed(
2364
+ event.requestId,
2365
+ `Failed to resolve PayloadData: ${errorMessage}`
2366
+ );
2132
2367
  }
2133
2368
  return;
2134
2369
  }
@@ -2150,7 +2385,10 @@ var NoosphereAgent = class _NoosphereAgent {
2150
2385
  console.error(` \u274C Container execution failed with exit code ${result.exitCode}`);
2151
2386
  console.error(` \u{1F4C4} Container output:`, result.output);
2152
2387
  if (this.options.onRequestFailed) {
2153
- this.options.onRequestFailed(event.requestId, `Container execution failed with exit code ${result.exitCode}`);
2388
+ this.options.onRequestFailed(
2389
+ event.requestId,
2390
+ `Container execution failed with exit code ${result.exitCode}`
2391
+ );
2154
2392
  }
2155
2393
  return;
2156
2394
  }
@@ -2270,6 +2508,20 @@ var NoosphereAgent = class _NoosphereAgent {
2270
2508
  getScheduler() {
2271
2509
  return this.scheduler;
2272
2510
  }
2511
+ /**
2512
+ * Get current connection state
2513
+ * @returns Connection state: 'INIT' | 'WS_CONNECTING' | 'WS_ACTIVE' | 'WS_RECONNECTING' | 'HTTP_FALLBACK'
2514
+ */
2515
+ getConnectionState() {
2516
+ return this.eventMonitor.getConnectionState();
2517
+ }
2518
+ /**
2519
+ * Get current connection mode
2520
+ * @returns Connection mode: 'websocket' | 'http_polling' | 'connecting'
2521
+ */
2522
+ getConnectionMode() {
2523
+ return this.eventMonitor.getConnectionMode();
2524
+ }
2273
2525
  };
2274
2526
 
2275
2527
  // src/types/index.ts