@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 +321 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +321 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
},
|
|
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.
|
|
252
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1008
|
+
currentInterval = BigInt(
|
|
1009
|
+
await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
|
|
1010
|
+
);
|
|
828
1011
|
} catch (error) {
|
|
829
1012
|
const errorMessage = error.message || "";
|
|
830
|
-
console.warn(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1126
|
+
const currentIntervalNow = BigInt(
|
|
1127
|
+
await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
|
|
1128
|
+
);
|
|
929
1129
|
if (currentIntervalNow !== interval && currentIntervalNow !== interval - 1n) {
|
|
930
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|