@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.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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
},
|
|
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.
|
|
199
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
955
|
+
currentInterval = BigInt(
|
|
956
|
+
await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
|
|
957
|
+
);
|
|
775
958
|
} catch (error) {
|
|
776
959
|
const errorMessage = error.message || "";
|
|
777
|
-
console.warn(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1073
|
+
const currentIntervalNow = BigInt(
|
|
1074
|
+
await this.router.getComputeSubscriptionInterval(sub.subscriptionId)
|
|
1075
|
+
);
|
|
876
1076
|
if (currentIntervalNow !== interval && currentIntervalNow !== interval - 1n) {
|
|
877
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|