@noosphere/agent-core 0.1.0-alpha.10 → 0.1.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -304,54 +304,69 @@ var ContainerManager = class {
304
304
  this.containerPorts = /* @__PURE__ */ new Map();
305
305
  this.docker = new import_dockerode.default();
306
306
  }
307
- async runContainer(container, input, timeout = 3e5) {
307
+ async runContainer(container, input, timeout = 3e5, connectionRetries = 5, connectionRetryDelayMs = 3e3) {
308
308
  const startTime = Date.now();
309
+ const port = container.port ? parseInt(container.port) : 8081;
310
+ const containerHost = process.env.DOCKER_NETWORK ? `noosphere-${container.name}` : "localhost";
311
+ const url = `http://${containerHost}:${port}/computation`;
312
+ let requestBody;
309
313
  try {
310
- const port = container.port ? parseInt(container.port) : 8081;
311
- const containerHost = process.env.DOCKER_NETWORK ? `noosphere-${container.name}` : "localhost";
312
- const url = `http://${containerHost}:${port}/computation`;
313
- let requestBody;
314
+ const parsedInput = JSON.parse(input);
315
+ requestBody = { input, ...parsedInput };
316
+ } catch {
317
+ requestBody = { input };
318
+ }
319
+ let lastError;
320
+ for (let attempt = 1; attempt <= connectionRetries; attempt++) {
314
321
  try {
315
- const parsedInput = JSON.parse(input);
316
- requestBody = { input, ...parsedInput };
317
- } catch {
318
- requestBody = { input };
319
- }
320
- const response = await import_axios.default.post(url, requestBody, {
321
- timeout,
322
- headers: {
323
- "Content-Type": "application/json"
322
+ const response = await import_axios.default.post(url, requestBody, {
323
+ timeout,
324
+ headers: {
325
+ "Content-Type": "application/json"
326
+ }
327
+ });
328
+ const executionTime2 = Date.now() - startTime;
329
+ let output;
330
+ if (typeof response.data === "string") {
331
+ output = response.data;
332
+ } else if (response.data.output !== void 0) {
333
+ output = typeof response.data.output === "string" ? response.data.output : JSON.stringify(response.data.output);
334
+ } else {
335
+ output = JSON.stringify(response.data);
324
336
  }
325
- });
326
- const executionTime = Date.now() - startTime;
327
- let output;
328
- if (typeof response.data === "string") {
329
- output = response.data;
330
- } else if (response.data.output !== void 0) {
331
- output = typeof response.data.output === "string" ? response.data.output : JSON.stringify(response.data.output);
332
- } else {
333
- output = JSON.stringify(response.data);
334
- }
335
- return {
336
- output,
337
- exitCode: 0,
338
- executionTime
339
- };
340
- } catch (error) {
341
- const executionTime = Date.now() - startTime;
342
- if (error.response) {
343
- throw new Error(
344
- `Container HTTP error ${error.response.status}: ${JSON.stringify(error.response.data)}`
345
- );
346
- } else if (error.code === "ECONNREFUSED") {
347
- throw new Error(
348
- `Cannot connect to container (port ${container.port || 8081}). Is it running?`
349
- );
350
- } else if (error.code === "ETIMEDOUT" || error.code === "ECONNABORTED") {
351
- throw new Error(`Container execution timeout after ${timeout}ms`);
337
+ return {
338
+ output,
339
+ exitCode: 0,
340
+ executionTime: executionTime2
341
+ };
342
+ } catch (error) {
343
+ lastError = error;
344
+ if (error.code === "ECONNREFUSED") {
345
+ if (attempt < connectionRetries) {
346
+ console.log(` \u23F3 Container not ready (attempt ${attempt}/${connectionRetries}), retrying in ${connectionRetryDelayMs / 1e3}s...`);
347
+ await this.sleep(connectionRetryDelayMs);
348
+ continue;
349
+ }
350
+ }
351
+ break;
352
352
  }
353
- throw error;
354
353
  }
354
+ const executionTime = Date.now() - startTime;
355
+ if (lastError.response) {
356
+ throw new Error(
357
+ `Container HTTP error ${lastError.response.status}: ${JSON.stringify(lastError.response.data)}`
358
+ );
359
+ } else if (lastError.code === "ECONNREFUSED") {
360
+ throw new Error(
361
+ `Cannot connect to container (port ${container.port || 8081}) after ${connectionRetries} attempts. Is it running?`
362
+ );
363
+ } else if (lastError.code === "ETIMEDOUT" || lastError.code === "ECONNABORTED") {
364
+ throw new Error(`Container execution timeout after ${timeout}ms`);
365
+ }
366
+ throw lastError;
367
+ }
368
+ sleep(ms) {
369
+ return new Promise((resolve) => setTimeout(resolve, ms));
355
370
  }
356
371
  async collectContainerResult(dockerContainer, workDir, startTime) {
357
372
  try {
@@ -797,7 +812,13 @@ var SchedulerService = class extends import_events2.EventEmitter {
797
812
  try {
798
813
  currentInterval = BigInt(await this.router.getComputeSubscriptionInterval(sub.subscriptionId));
799
814
  } catch (error) {
800
- console.warn(` Could not get interval from router for subscription ${subId}:`, error.message);
815
+ const errorMessage = error.message || "";
816
+ console.warn(` Could not get interval from router for subscription ${subId}:`, errorMessage);
817
+ if (errorMessage.includes("SubscriptionNotFound")) {
818
+ console.log(` Subscription ${subId} not found (cancelled), untracking...`);
819
+ this.untrackSubscription(sub.subscriptionId);
820
+ continue;
821
+ }
801
822
  const intervalsSinceActive = BigInt(currentBlockTime) - sub.activeAt;
802
823
  currentInterval = intervalsSinceActive / sub.intervalSeconds + 1n;
803
824
  }
@@ -1406,7 +1427,6 @@ var ConfigLoader = class {
1406
1427
 
1407
1428
  // src/NoosphereAgent.ts
1408
1429
  var NoosphereAgent = class _NoosphereAgent {
1409
- // Deduplication: track requests being processed
1410
1430
  constructor(options) {
1411
1431
  this.options = options;
1412
1432
  this.isRunning = false;
@@ -1464,6 +1484,12 @@ var NoosphereAgent = class _NoosphereAgent {
1464
1484
  if (!this.getContainer && (!this.containers || this.containers.size === 0)) {
1465
1485
  console.warn("\u26A0\uFE0F No container source provided. Agent will not be able to execute requests.");
1466
1486
  }
1487
+ this.maxRetries = options.maxRetries ?? 3;
1488
+ this.retryIntervalMs = options.retryIntervalMs ?? 3e4;
1489
+ this.containerTimeout = options.containerConfig?.timeout ?? 3e5;
1490
+ this.containerConnectionRetries = options.containerConfig?.connectionRetries ?? 5;
1491
+ this.containerConnectionRetryDelayMs = options.containerConfig?.connectionRetryDelayMs ?? 3e3;
1492
+ this.healthCheckIntervalMs = options.healthCheckIntervalMs ?? 3e5;
1467
1493
  }
1468
1494
  /**
1469
1495
  * Initialize NoosphereAgent from config.json (RECOMMENDED)
@@ -1602,10 +1628,100 @@ var NoosphereAgent = class _NoosphereAgent {
1602
1628
  await this.handleRequest(data.requestStartedEvent);
1603
1629
  }
1604
1630
  });
1631
+ if (this.options.getRetryableEvents && this.options.resetEventForRetry) {
1632
+ this.startRetryTimer();
1633
+ }
1634
+ this.startHealthCheck();
1605
1635
  this.isRunning = true;
1606
1636
  console.log("\u2713 Noosphere Agent is running");
1607
1637
  console.log("Listening for requests...");
1608
1638
  }
1639
+ /**
1640
+ * Start the retry timer for failed requests
1641
+ */
1642
+ startRetryTimer() {
1643
+ if (this.retryTimer) {
1644
+ clearInterval(this.retryTimer);
1645
+ }
1646
+ console.log(`\u{1F504} Retry mechanism enabled: max ${this.maxRetries} retries, check every ${this.retryIntervalMs / 1e3}s`);
1647
+ this.retryTimer = setInterval(async () => {
1648
+ await this.processRetries();
1649
+ }, this.retryIntervalMs);
1650
+ }
1651
+ /**
1652
+ * Start the health check timer for registry validation
1653
+ */
1654
+ startHealthCheck() {
1655
+ if (this.healthCheckTimer) {
1656
+ clearInterval(this.healthCheckTimer);
1657
+ }
1658
+ console.log(`\u{1F3E5} Health check enabled: check every ${this.healthCheckIntervalMs / 1e3}s`);
1659
+ this.healthCheckTimer = setInterval(async () => {
1660
+ await this.performHealthCheck();
1661
+ }, this.healthCheckIntervalMs);
1662
+ }
1663
+ /**
1664
+ * Perform health check - verify registry has containers and reload if necessary
1665
+ */
1666
+ async performHealthCheck() {
1667
+ const stats = this.registryManager.getStats();
1668
+ if (stats.totalContainers === 0) {
1669
+ console.warn("\u26A0\uFE0F Health check: 0 containers detected, attempting registry reload...");
1670
+ try {
1671
+ await this.registryManager.reload();
1672
+ const newStats = this.registryManager.getStats();
1673
+ if (newStats.totalContainers > 0) {
1674
+ console.log(`\u2713 Health check: Registry recovered - ${newStats.totalContainers} containers loaded`);
1675
+ } else {
1676
+ console.error("\u274C Health check: Registry reload failed - still 0 containers");
1677
+ }
1678
+ } catch (error) {
1679
+ console.error("\u274C Health check: Registry reload error:", error.message);
1680
+ }
1681
+ }
1682
+ }
1683
+ /**
1684
+ * Process retryable failed events (with throttling to avoid rate limits)
1685
+ */
1686
+ async processRetries() {
1687
+ if (!this.options.getRetryableEvents || !this.options.resetEventForRetry) {
1688
+ return;
1689
+ }
1690
+ const retryableEvents = this.options.getRetryableEvents(this.maxRetries);
1691
+ if (retryableEvents.length === 0) {
1692
+ return;
1693
+ }
1694
+ const event = retryableEvents[0];
1695
+ if (this.processingRequests.has(event.requestId)) {
1696
+ return;
1697
+ }
1698
+ console.log(`\u{1F504} Retrying request ${event.requestId.slice(0, 10)}... (attempt ${event.retryCount + 1}/${this.maxRetries}, ${retryableEvents.length} remaining)`);
1699
+ this.options.resetEventForRetry(event.requestId);
1700
+ const container = this.getContainerMetadata(event.containerId);
1701
+ if (!container) {
1702
+ console.log(` \u26A0\uFE0F Container ${event.containerId.slice(0, 10)}... no longer supported, skipping retry`);
1703
+ return;
1704
+ }
1705
+ const retryEvent = {
1706
+ requestId: event.requestId,
1707
+ subscriptionId: BigInt(event.subscriptionId),
1708
+ interval: event.interval,
1709
+ containerId: event.containerId,
1710
+ redundancy: 1,
1711
+ useDeliveryInbox: false,
1712
+ feeAmount: BigInt(0),
1713
+ feeToken: "0x0000000000000000000000000000000000000000",
1714
+ walletAddress: "0x0000000000000000000000000000000000000000",
1715
+ verifier: "0x0000000000000000000000000000000000000000",
1716
+ coordinator: this.config.coordinatorAddress,
1717
+ blockNumber: 0
1718
+ };
1719
+ try {
1720
+ await this.handleRequest(retryEvent);
1721
+ } catch (error) {
1722
+ console.log(` \u274C Retry failed for ${event.requestId.slice(0, 10)}...: ${error.message}`);
1723
+ }
1724
+ }
1609
1725
  /**
1610
1726
  * Convert registry ContainerMetadata to agent-core ContainerMetadata
1611
1727
  */
@@ -1750,8 +1866,9 @@ var NoosphereAgent = class _NoosphereAgent {
1750
1866
  const result = await this.containerManager.runContainer(
1751
1867
  container,
1752
1868
  inputData,
1753
- 3e5
1754
- // 5 min timeout
1869
+ this.containerTimeout,
1870
+ this.containerConnectionRetries,
1871
+ this.containerConnectionRetryDelayMs
1755
1872
  );
1756
1873
  if (result.exitCode !== 0) {
1757
1874
  console.error(` \u274C Container execution failed with exit code ${result.exitCode}`);
@@ -1842,6 +1959,14 @@ var NoosphereAgent = class _NoosphereAgent {
1842
1959
  }
1843
1960
  async stop() {
1844
1961
  console.log("Stopping Noosphere Agent...");
1962
+ if (this.retryTimer) {
1963
+ clearInterval(this.retryTimer);
1964
+ this.retryTimer = void 0;
1965
+ }
1966
+ if (this.healthCheckTimer) {
1967
+ clearInterval(this.healthCheckTimer);
1968
+ this.healthCheckTimer = void 0;
1969
+ }
1845
1970
  await this.eventMonitor.stop();
1846
1971
  this.scheduler.stop();
1847
1972
  await this.containerManager.cleanup();