@aiaiaichain/agent 0.1.4 → 0.1.5

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.
Files changed (58) hide show
  1. package/dist/cli.js +218 -5
  2. package/dist/core/ChainConfig.js +1 -1
  3. package/dist/core/SystemMonitor.d.ts +1 -4
  4. package/dist/core/SystemMonitor.js +20 -46
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.js +11 -0
  7. package/dist/models/ModelRegistry.js +12 -4
  8. package/dist/runner/AgentRunner.d.ts +2 -0
  9. package/dist/runner/AgentRunner.js +18 -1
  10. package/dist/runner/ModelClient.js +109 -48
  11. package/dist/session/SessionManager.d.ts +1 -0
  12. package/dist/session/SessionManager.js +8 -2
  13. package/dist/session/SessionStore.js +8 -3
  14. package/dist/tools/CrossTools.js +13 -5
  15. package/dist/tools/MarketSentiment.js +22 -13
  16. package/dist/tools/NewsSentiment.js +9 -3
  17. package/dist/tools/PriceFeed.js +11 -4
  18. package/dist/tools/TechnicalAnalysis.js +2 -1
  19. package/dist/tools/TokenCalendar.d.ts +24 -0
  20. package/dist/tools/TokenCalendar.js +81 -0
  21. package/dist/tools/TokenSecurityScanner.d.ts +22 -0
  22. package/dist/tools/TokenSecurityScanner.js +102 -0
  23. package/dist/tools/TransactionSim.d.ts +17 -0
  24. package/dist/tools/TransactionSim.js +78 -0
  25. package/dist/tui/App.js +143 -21
  26. package/dist/tui/REPL.js +2 -2
  27. package/dist/tui/Sparkline.d.ts +21 -0
  28. package/dist/tui/Sparkline.js +44 -0
  29. package/dist/tui/ThemePresets.d.ts +25 -0
  30. package/dist/tui/ThemePresets.js +117 -0
  31. package/dist/util/clipboard.d.ts +9 -0
  32. package/dist/util/clipboard.js +26 -0
  33. package/dist/util/commandSuggest.d.ts +7 -0
  34. package/dist/util/commandSuggest.js +44 -0
  35. package/dist/util/confirmation.d.ts +6 -0
  36. package/dist/util/confirmation.js +16 -0
  37. package/dist/util/errorHandler.d.ts +3 -0
  38. package/dist/util/errorHandler.js +28 -0
  39. package/dist/util/logger.d.ts +11 -0
  40. package/dist/util/logger.js +43 -0
  41. package/dist/util/processManager.d.ts +5 -0
  42. package/dist/util/processManager.js +39 -0
  43. package/dist/util/resilientFetch.d.ts +21 -0
  44. package/dist/util/resilientFetch.js +94 -0
  45. package/dist/util/responseCache.d.ts +27 -0
  46. package/dist/util/responseCache.js +54 -0
  47. package/dist/util/safeLog.d.ts +4 -5
  48. package/dist/util/safeLog.js +68 -30
  49. package/dist/util/scheduler.d.ts +14 -0
  50. package/dist/util/scheduler.js +75 -0
  51. package/dist/util/webhooks.d.ts +9 -0
  52. package/dist/util/webhooks.js +75 -0
  53. package/dist/wallet/ActionFeed.js +12 -4
  54. package/dist/wallet/AgentWallet.d.ts +1 -0
  55. package/dist/wallet/AgentWallet.js +30 -5
  56. package/dist/wallet/ProfitTracker.d.ts +30 -0
  57. package/dist/wallet/ProfitTracker.js +93 -0
  58. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@ import { loadExtension } from "./loader.js";
15
15
  import { App } from "./tui/App.js";
16
16
  import { T } from "./tui/theme.js";
17
17
  import { wireNotify, safeLog } from "./util/safeLog.js";
18
+ import { logger } from "./util/logger.js";
18
19
  import { modelRegistry } from "./models/ModelRegistry.js";
19
20
  import { CostTracker } from "./models/CostTracker.js";
20
21
  import { AgentRunner } from "./runner/AgentRunner.js";
@@ -26,6 +27,7 @@ import { agentWallet, DEPOSIT_WALLET, ACTION_WALLET, SIGNER } from "./wallet/Age
26
27
  import { PROVIDERS, getConfigured, getUnconfigured } from "./providers/ProviderRegistry.js";
27
28
  import { env } from "./core/EnvLoader.js";
28
29
  import { gmgnHelp, gmgnStatus, saveGmgnApiKey } from "./tools/GmgnIntegration.js";
30
+ import { scanTokenSecurityTool } from "./tools/TokenSecurityScanner.js";
29
31
  import { sessionStore } from "./session/SessionStore.js";
30
32
  const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), ".aiaiai");
31
33
  const envPath = join(AIAIAI_HOME, ".env");
@@ -79,6 +81,10 @@ if (subcmd === "--help" || subcmd === "-h" || subcmd === "help") {
79
81
  console.log(" aiaiaichain deposit Show deposit instructions");
80
82
  console.log(" aiaiaichain burn Show burn instructions");
81
83
  console.log(" aiaiaichain actions Recent agent actions");
84
+ console.log(" aiaiaichain pnl Profit & Loss report");
85
+ console.log(" aiaiaichain health System health & diagnostics");
86
+ console.log(" aiaiaichain doctor Comprehensive provider check");
87
+ console.log(" aiaiaichain scan <address> Token security scan");
82
88
  console.log(" aiaiaichain --help This help");
83
89
  console.log(" aiaiaichain --version Show version");
84
90
  console.log("");
@@ -106,7 +112,22 @@ if (subcmd === "setup") {
106
112
  console.log(T.muted(` Home: ${AIAIAI_HOME}`));
107
113
  console.log(T.accent("\n Run `aiaiaichain` to start the agent.\n"));
108
114
  process.exit(0);
109
- })().catch(e => { console.error(T.error(`Setup error: ${e.message}`)); process.exit(1); });
115
+ })().catch(e => {
116
+ const msg = e instanceof Error ? e.message : String(e);
117
+ if (msg.includes('ENOENT')) {
118
+ console.error(T.error(`Setup error: Cannot write to ~/.aiaiai/.env — check permissions.`));
119
+ }
120
+ else if (msg.includes('EACCES')) {
121
+ console.error(T.error(`Setup error: Permission denied — try running with correct user.`));
122
+ }
123
+ else if (msg.includes('API') || msg.includes('401')) {
124
+ console.error(T.error(`Setup error: API key invalid — check your provider key.`));
125
+ }
126
+ else {
127
+ console.error(T.error(`Setup error: ${msg}`));
128
+ }
129
+ process.exit(1);
130
+ });
110
131
  await new Promise(() => { });
111
132
  }
112
133
  if (subcmd === "config") {
@@ -205,6 +226,39 @@ if (subcmd === "fees") {
205
226
  })();
206
227
  await new Promise(() => { });
207
228
  }
229
+ if (subcmd === "pnl" || subcmd === "pl" || subcmd === "profit") {
230
+ (async () => {
231
+ const { getPLSummary, getTradeHistory, exportCSV } = await import("./wallet/ProfitTracker.js");
232
+ const { priceFeed } = await import("./tools/PriceFeed.js");
233
+ const price = await priceFeed.getAiaiaiPrice();
234
+ const summary = getPLSummary(parseFloat(price.priceUsd ?? "0"));
235
+ const history = getTradeHistory();
236
+ console.log(T.accent("\n Profit & Loss — $AIAIAI\n"));
237
+ console.log(` Avg Cost: $${summary.avgCostBasis.toFixed(8)}`);
238
+ console.log(` Total Spent: ${summary.totalSpent.toFixed(4)} SOL`);
239
+ console.log(` Total Received: ${summary.totalReceived.toFixed(4)} SOL`);
240
+ console.log(` Realized P&L: ${summary.realizedPL >= 0 ? "+" : ""}${summary.realizedPL.toFixed(4)} SOL`);
241
+ console.log(` Unrealized P&L: ${summary.unrealizedPL >= 0 ? "+" : ""}${summary.unrealizedPL.toFixed(4)} SOL`);
242
+ console.log(` ROI: ${summary.roi >= 0 ? "+" : ""}${summary.roi.toFixed(2)}%`);
243
+ console.log(` Trades: ${history.length}`);
244
+ if (history.length > 0) {
245
+ console.log(T.muted("\n Recent trades:"));
246
+ history.slice(-5).forEach(t => {
247
+ const date = new Date(t.timestamp).toLocaleDateString();
248
+ console.log(` ${date} ${t.type} ${t.tokenAmount.toFixed(0)} tokens @ ${t.pricePerToken.toFixed(8)} SOL`);
249
+ });
250
+ }
251
+ try {
252
+ const csvPath = exportCSV();
253
+ console.log(T.success(`\n CSV exported: ${csvPath}`));
254
+ }
255
+ catch (error) {
256
+ logger.warn('CLI', 'CSV export failed', { error: error.message });
257
+ }
258
+ process.exit(0);
259
+ })();
260
+ await new Promise(() => { });
261
+ }
208
262
  if (subcmd === "keys" || subcmd === "providers") {
209
263
  (async () => {
210
264
  const readline = await import("node:readline");
@@ -323,7 +377,9 @@ if (subcmd === "gmgn" || subcmd === "gmgnhelp") {
323
377
  gmgnEnv[m[1]] = m[2];
324
378
  }
325
379
  }
326
- catch { /* no gmgn env */ }
380
+ catch {
381
+ logger.debug('CLI', 'No GMGN env file found (optional)');
382
+ }
327
383
  const output = execSync(fullArgs.join(" "), {
328
384
  env: { ...process.env, ...gmgnEnv },
329
385
  encoding: "utf-8",
@@ -338,6 +394,25 @@ if (subcmd === "gmgn" || subcmd === "gmgnhelp") {
338
394
  }
339
395
  process.exit(0);
340
396
  }
397
+ if (subcmd === "scan" || subcmd === "security") {
398
+ (async () => {
399
+ const tokenAddress = args[1];
400
+ if (!tokenAddress) {
401
+ console.log(T.error(" Usage: aiaiai scan <token-address>"));
402
+ process.exit(1);
403
+ }
404
+ console.log(T.accent("\n \uD83D\uDD0D Scanning token...\n"));
405
+ const result = await scanTokenSecurityTool("", { address: tokenAddress });
406
+ if (result.isError) {
407
+ console.log(result.content[0].text);
408
+ process.exit(1);
409
+ }
410
+ console.log(result.content[0].text);
411
+ console.log("");
412
+ process.exit(0);
413
+ })().catch(e => { console.error(T.error("Scan error: " + e.message)); process.exit(1); });
414
+ await new Promise(() => { });
415
+ }
341
416
  if (subcmd === "update" || subcmd === "upgrade") {
342
417
  (async () => {
343
418
  const pkgName = "@aiaiaichain/agent";
@@ -368,9 +443,12 @@ if (subcmd === "update" || subcmd === "upgrade") {
368
443
  changelog = `Git: ${pkgData.gitHead.slice(0, 8)}`;
369
444
  }
370
445
  }
371
- catch { /* no changelog available */ }
446
+ catch {
447
+ logger.debug('CLI', 'No changelog available (optional)');
448
+ }
372
449
  }
373
- catch {
450
+ catch (error) {
451
+ logger.warn('CLI', 'Could not reach npm registry', { error: error.message });
374
452
  console.log(T.error(" Could not reach npm registry. Check your connection."));
375
453
  process.exit(1);
376
454
  }
@@ -414,6 +492,119 @@ if (subcmd === "update" || subcmd === "upgrade") {
414
492
  })().catch(e => { console.error(T.error(`Error: ${e.message}`)); process.exit(1); });
415
493
  await new Promise(() => { });
416
494
  }
495
+ if (subcmd === "health" || subcmd === "doctor") {
496
+ (async () => {
497
+ console.log(T.accent("\n \u2705 AIAIAI Health Check\n"));
498
+ let healthy = true;
499
+ // Check env file
500
+ console.log(T.muted(" Config:"));
501
+ if (existsSync(envPath)) {
502
+ console.log(T.success(" .env found"));
503
+ }
504
+ else {
505
+ console.log(T.warn(" .env not found \u2014 run: aiaiai setup"));
506
+ healthy = false;
507
+ }
508
+ // Check API key
509
+ console.log(T.muted(" API Key:"));
510
+ if (process.env.OPENROUTER_API_KEY || env.get("OPENROUTER_API_KEY")) {
511
+ console.log(T.success(" OPENROUTER_API_KEY set"));
512
+ }
513
+ else {
514
+ console.log(T.warn(" OPENROUTER_API_KEY not set"));
515
+ healthy = false;
516
+ }
517
+ // Check RPC
518
+ console.log(T.muted(" Solana RPC:"));
519
+ try {
520
+ const { resilientFetch } = await import("./util/resilientFetch.js");
521
+ const rpcUrl = process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
522
+ const res = await resilientFetch(rpcUrl, {
523
+ timeout: 10_000,
524
+ retries: 0,
525
+ method: "POST",
526
+ headers: { "Content-Type": "application/json" },
527
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getHealth", params: [] }),
528
+ });
529
+ const json = await res.json();
530
+ if (json.result === "ok") {
531
+ console.log(T.success(" RPC healthy: " + rpcUrl));
532
+ }
533
+ else {
534
+ console.log(T.warn(" RPC responded but unhealthy"));
535
+ healthy = false;
536
+ }
537
+ }
538
+ catch (e) {
539
+ console.log(T.error(" RPC unreachable: " + e.message));
540
+ healthy = false;
541
+ }
542
+ // Check disk writable
543
+ console.log(T.muted(" Disk:"));
544
+ try {
545
+ const { writeFileSync, unlinkSync } = await import("node:fs");
546
+ const testFile = join(AIAIAI_HOME, ".health-test");
547
+ writeFileSync(testFile, "test", "utf-8");
548
+ unlinkSync(testFile);
549
+ console.log(T.success(" writable"));
550
+ }
551
+ catch (error) {
552
+ logger.warn('CLI', 'Disk not writable', { error: error.message });
553
+ console.log(T.error(" not writable"));
554
+ healthy = false;
555
+ }
556
+ // ── Extras (doctor mode) ──────────────────────────────────────────
557
+ console.log(T.muted("\n Extras:"));
558
+ // Theme
559
+ try {
560
+ const { getCurrentTheme } = await import("./tui/ThemePresets.js");
561
+ console.log(T.success(` Theme: ${getCurrentTheme()} (run aiaiai to change)`));
562
+ }
563
+ catch {
564
+ console.log(T.muted(" Theme: default"));
565
+ }
566
+ // Clipboard
567
+ try {
568
+ const { clipboardSupported } = await import("./util/clipboard.js");
569
+ console.log(T.muted(` Clipboard: ${clipboardSupported()}`));
570
+ }
571
+ catch {
572
+ console.log(T.muted(" Clipboard: not available"));
573
+ }
574
+ // Webhooks
575
+ try {
576
+ const { hasWebhooks } = await import("./util/webhooks.js");
577
+ console.log(hasWebhooks() ? T.success(" Webhooks: configured") : T.muted(" Webhooks: not configured (set WEBHOOK_DISCORD or WEBHOOK_TELEGRAM in .env)"));
578
+ }
579
+ catch {
580
+ console.log(T.muted(" Webhooks: not available"));
581
+ }
582
+ // Provider key check
583
+ console.log(T.muted(" Provider Keys:"));
584
+ const providers = [
585
+ { name: "OpenRouter", key: "OPENROUTER_API_KEY" },
586
+ { name: "Anthropic", key: "ANTHROPIC_API_KEY" },
587
+ { name: "OpenAI", key: "OPENAI_API_KEY" },
588
+ { name: "Google", key: "GOOGLE_API_KEY" },
589
+ { name: "DeepSeek", key: "DEEPSEEK_API_KEY" },
590
+ { name: "Groq", key: "GROQ_API_KEY" },
591
+ { name: "GMGN", key: "GMGN_API_KEY" },
592
+ ];
593
+ for (const p of providers) {
594
+ const has = process.env[p.key] || env.get(p.key);
595
+ console.log(has ? T.success(` ${p.name}: ✓`) : T.dim(` ${p.name}: ✗ (optional)`));
596
+ }
597
+ console.log("");
598
+ if (healthy) {
599
+ console.log(T.success(" All systems operational.\n"));
600
+ }
601
+ else {
602
+ console.log(T.warn(" Some checks failed. Review above for details.\n"));
603
+ }
604
+ process.exit(healthy ? 0 : 1);
605
+ })().catch(e => { console.error(T.error("Health check failed: " + e.message)); process.exit(2); });
606
+ await new Promise(() => { });
607
+ }
417
608
  if (subcmd === "sessions") {
418
609
  const sessions = sessionStore.list();
419
610
  if (sessions.length === 0) {
@@ -461,7 +652,29 @@ for (let i = 0; i < args.length; i++) {
461
652
  }
462
653
  let systemPrompt = SYSTEM_PROMPT;
463
654
  if (promptPath && existsSync(promptPath)) {
464
- systemPrompt = readFileSync(promptPath, "utf-8");
655
+ let rawPrompt = readFileSync(promptPath, "utf-8");
656
+ // Validate prompt file size
657
+ if (rawPrompt.length > 10_000) {
658
+ console.error(T.error("Prompt file too large (max 10KB)."));
659
+ process.exit(1);
660
+ }
661
+ // Strip control characters
662
+ rawPrompt = rawPrompt.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
663
+ // Basic prompt injection detection
664
+ const dangerousPatterns = [
665
+ /ignore (all |previous |above|prior) instructions/i,
666
+ /you are now /i,
667
+ /new instructions/i,
668
+ /override (system|safety)/i,
669
+ ];
670
+ for (const pattern of dangerousPatterns) {
671
+ if (pattern.test(rawPrompt)) {
672
+ console.error(T.error("Prompt file contains suspicious patterns. Use a simpler prompt."));
673
+ process.exit(1);
674
+ }
675
+ }
676
+ systemPrompt = rawPrompt;
677
+ console.log(T.muted(" Custom prompt loaded: " + promptPath));
465
678
  }
466
679
  // ── Headless mode ─────────────────────────────────────────────────────────
467
680
  const headlessIdx = args.indexOf("--headless");
@@ -7,7 +7,7 @@ export const CHAINS = {
7
7
  name: 'solana',
8
8
  symbol: 'SOL',
9
9
  chainId: 101,
10
- rpcUrl: 'https://api.mainnet-beta.solana.com',
10
+ rpcUrl: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
11
11
  explorerUrl: 'https://solscan.io',
12
12
  enabled: true,
13
13
  primary: true,
@@ -15,9 +15,7 @@ export interface SystemStats {
15
15
  }
16
16
  export declare class SystemMonitor {
17
17
  private apiCallCount;
18
- private lastNetStats;
19
- constructor();
20
- private initNetStats;
18
+ private prevCpuTimes;
21
19
  /** Track an API call */
22
20
  trackApiCall(): void;
23
21
  /** Get current system stats */
@@ -25,7 +23,6 @@ export declare class SystemMonitor {
25
23
  private getCpuPercent;
26
24
  private getRamPercent;
27
25
  private getNetStats;
28
- private getRawNetStats;
29
26
  private formatBytes;
30
27
  private formatUptime;
31
28
  /** Reset API call counter (call every minute) */
@@ -2,20 +2,11 @@
2
2
  * SystemMonitor — tracks CPU, RAM, network, and agent stats.
3
3
  * Uses Node.js built-in modules only (no external deps).
4
4
  */
5
- import { cpus, totalmem, freemem, uptime, networkInterfaces } from 'node:os';
5
+ import { cpus, totalmem, freemem, uptime } from 'node:os';
6
+ import { logger } from '../util/logger.js';
6
7
  export class SystemMonitor {
7
8
  apiCallCount = 0;
8
- lastNetStats = { rx: 0, tx: 0, time: Date.now() };
9
- constructor() {
10
- this.initNetStats();
11
- }
12
- initNetStats() {
13
- try {
14
- const stats = this.getRawNetStats();
15
- this.lastNetStats = { rx: stats.rx, tx: stats.tx, time: Date.now() };
16
- }
17
- catch { /* non-fatal */ }
18
- }
9
+ prevCpuTimes = { idle: 0, total: 0 };
19
10
  /** Track an API call */
20
11
  trackApiCall() {
21
12
  this.apiCallCount++;
@@ -45,9 +36,21 @@ export class SystemMonitor {
45
36
  }
46
37
  const idle = totalIdle / cpuInfo.length;
47
38
  const total = totalTick / cpuInfo.length;
48
- return Math.round((1 - idle / total) * 100);
39
+ // Delta-based: compare against previous sample
40
+ const nowIdle = idle, nowTotal = total;
41
+ if (this.prevCpuTimes.total > 0) {
42
+ const idleDelta = nowIdle - this.prevCpuTimes.idle;
43
+ const totalDelta = nowTotal - this.prevCpuTimes.total;
44
+ this.prevCpuTimes = { idle: nowIdle, total: nowTotal };
45
+ if (totalDelta <= 0)
46
+ return 0;
47
+ return Math.round((1 - idleDelta / totalDelta) * 100);
48
+ }
49
+ this.prevCpuTimes = { idle: nowIdle, total: nowTotal };
50
+ return 0;
49
51
  }
50
- catch {
52
+ catch (error) {
53
+ logger.debug('SystemMonitor', 'CPU measurement failed', { error: error.message });
51
54
  return 0;
52
55
  }
53
56
  }
@@ -56,38 +59,9 @@ export class SystemMonitor {
56
59
  return Math.round((used / totalmem()) * 100);
57
60
  }
58
61
  getNetStats() {
59
- try {
60
- const current = this.getRawNetStats();
61
- const elapsed = (Date.now() - this.lastNetStats.time) / 1000;
62
- if (elapsed < 1)
63
- return { netUp: '0 B/s', netDown: '0 B/s' };
64
- const rxPerSec = Math.max(0, (current.rx - this.lastNetStats.rx) / elapsed);
65
- const txPerSec = Math.max(0, (current.tx - this.lastNetStats.tx) / elapsed);
66
- this.lastNetStats = { rx: current.rx, tx: current.tx, time: Date.now() };
67
- return {
68
- netUp: this.formatBytes(txPerSec) + '/s',
69
- netDown: this.formatBytes(rxPerSec) + '/s',
70
- };
71
- }
72
- catch {
73
- return { netUp: 'N/A', netDown: 'N/A' };
74
- }
75
- }
76
- getRawNetStats() {
77
- let rx = 0, tx = 0;
78
- const interfaces = networkInterfaces();
79
- for (const name of Object.keys(interfaces)) {
80
- const iface = interfaces[name];
81
- if (!iface)
82
- continue;
83
- for (const info of iface) {
84
- if (info.internal)
85
- continue;
86
- // Node.js doesn't expose per-interface bytes by default
87
- // This is a best-effort approximation
88
- }
89
- }
90
- return { rx, tx };
62
+ // Node.js doesn't expose per-interface byte counters without native addons.
63
+ // Return N/A rather than pretending zeros are real data.
64
+ return { netUp: 'N/A', netDown: 'N/A' };
91
65
  }
92
66
  formatBytes(bytes) {
93
67
  if (bytes === 0)
package/dist/index.d.ts CHANGED
@@ -48,4 +48,14 @@ export { AgentDir } from "./core/AgentDir.js";
48
48
  export { EnvLoader, env } from "./core/EnvLoader.js";
49
49
  export { CHAINS, getPrimaryChain, getEnabledChains, getChain, getSupportedChains } from "./core/ChainConfig.js";
50
50
  export type { ChainInfo } from "./core/ChainConfig.js";
51
+ export { resilientFetch, isReachable } from "./util/resilientFetch.js";
52
+ export { logger } from "./util/logger.js";
53
+ export { getCached, setCache, getOrCompute, clearCache, cacheStats } from "./util/responseCache.js";
54
+ export { addTask, removeTask, pauseTask, resumeTask, pauseAll, resumeAll, startScheduler } from "./util/scheduler.js";
55
+ export { requireConfirmation, isNonInteractive } from "./util/confirmation.js";
56
+ export { ProcessManager } from "./util/processManager.js";
57
+ export { recordTrade, getPLSummary, getTradeHistory, exportCSV } from "./wallet/ProfitTracker.js";
58
+ export type { Trade, ProfitState, PLSummary } from "./wallet/ProfitTracker.js";
59
+ export { scanTokenSecurityTool } from "./tools/TokenSecurityScanner.js";
60
+ export type { TokenSecurityResult } from "./tools/TokenSecurityScanner.js";
51
61
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -50,4 +50,15 @@ export { MCPServer } from "./mcp/server.js";
50
50
  export { AgentDir } from "./core/AgentDir.js";
51
51
  export { EnvLoader, env } from "./core/EnvLoader.js";
52
52
  export { CHAINS, getPrimaryChain, getEnabledChains, getChain, getSupportedChains } from "./core/ChainConfig.js";
53
+ // Utilities
54
+ export { resilientFetch, isReachable } from "./util/resilientFetch.js";
55
+ export { logger } from "./util/logger.js";
56
+ export { getCached, setCache, getOrCompute, clearCache, cacheStats } from "./util/responseCache.js";
57
+ export { addTask, removeTask, pauseTask, resumeTask, pauseAll, resumeAll, startScheduler } from "./util/scheduler.js";
58
+ export { requireConfirmation, isNonInteractive } from "./util/confirmation.js";
59
+ export { ProcessManager } from "./util/processManager.js";
60
+ // Profit Tracker
61
+ export { recordTrade, getPLSummary, getTradeHistory, exportCSV } from "./wallet/ProfitTracker.js";
62
+ // Token Security
63
+ export { scanTokenSecurityTool } from "./tools/TokenSecurityScanner.js";
53
64
  //# sourceMappingURL=index.js.map
@@ -4,6 +4,8 @@
4
4
  */
5
5
  import { env } from "../core/EnvLoader.js";
6
6
  import { Type } from "@sinclair/typebox";
7
+ import { logger } from "../util/logger.js";
8
+ import { resilientFetch } from "../util/resilientFetch.js";
7
9
  const PROVIDERS_WITH_MODELS = [
8
10
  { id: "openrouter", envVar: "OPENROUTER_API_KEY", baseUrl: "https://openrouter.ai/api/v1/models", mapper: (m) => ({ id: m.id, name: m.name, context: m.context_length ?? 131072, max: m.top_provider?.max_completion_tokens ?? 4096 }) },
9
11
  { id: "anthropic", envVar: "ANTHROPIC_API_KEY", baseUrl: "https://api.anthropic.com/v1/models", mapper: (m) => ({ id: m.id, name: m.name, context: m.context_length ?? 200000, max: m.top_provider?.max_completion_tokens ?? 8192 }) },
@@ -56,9 +58,11 @@ export class ModelRegistry {
56
58
  if (provider.id === "google") {
57
59
  url = `${provider.baseUrl}?key=${apiKey}`;
58
60
  }
59
- const res = await fetch(url, { headers });
60
- if (!res.ok)
61
+ const res = await resilientFetch(url, { timeout: 15_000, retries: 1, headers });
62
+ if (!res.ok) {
63
+ logger.warn('ModelRegistry', `Provider ${provider.id} returned ${res.status}`);
61
64
  continue;
65
+ }
62
66
  const data = await res.json();
63
67
  const items = data.data ?? data.models ?? data;
64
68
  if (!Array.isArray(items))
@@ -80,10 +84,14 @@ export class ModelRegistry {
80
84
  this.providerModels.set(provider.id, []);
81
85
  this.providerModels.get(provider.id).push(model);
82
86
  }
83
- catch { /* skip bad entries */ }
87
+ catch (error) {
88
+ logger.debug('ModelRegistry', 'Skipped bad model entry', { error: error.message });
89
+ }
84
90
  }
85
91
  }
86
- catch { /* skip this provider */ }
92
+ catch (error) {
93
+ logger.warn('ModelRegistry', 'Skipped provider', { error: error.message });
94
+ }
87
95
  }
88
96
  this.buildTiers();
89
97
  this.initialized = true;
@@ -44,6 +44,8 @@ export declare class AgentRunner {
44
44
  private contextStore?;
45
45
  private abortController;
46
46
  private _pendingApproval?;
47
+ private _toolRetries;
48
+ private _maxToolRetries;
47
49
  constructor(registry: Registry, session: SessionManager, onEvent: (event: RunnerEvent) => void, sessionCtx: SessionContext, effectLevel: string, modelReg: ModelRegistry, costTracker?: CostTracker, goalManager?: GoalManager, contextStore?: ContextStore);
48
50
  setEffectLevel(level: string): void;
49
51
  run(input: string): Promise<void>;
@@ -2,6 +2,7 @@
2
2
  * AgentRunner — main agent loop. Handles tool calling, streaming, and message processing.
3
3
  * The AI responds with text or tool calls; we parse, dispatch, and feed results back.
4
4
  */
5
+ import { logger } from "../util/logger.js";
5
6
  import { ToolDispatcher } from "./ToolDispatcher.js";
6
7
  import { ModelClient, resolveModelConfig } from "./ModelClient.js";
7
8
  export class AgentRunner {
@@ -17,6 +18,8 @@ export class AgentRunner {
17
18
  contextStore;
18
19
  abortController = null;
19
20
  _pendingApproval;
21
+ _toolRetries = new Map();
22
+ _maxToolRetries = 2;
20
23
  constructor(registry, session, onEvent, sessionCtx, effectLevel, modelReg, costTracker, goalManager, contextStore) {
21
24
  this.registry = registry;
22
25
  this.session = session;
@@ -53,7 +56,9 @@ export class AgentRunner {
53
56
  try {
54
57
  args = JSON.parse(toolCallMatch[3]);
55
58
  }
56
- catch { /* use empty args */ }
59
+ catch {
60
+ logger.warn('AgentRunner', 'Failed to parse tool call args from stream', { toolCall: toolCallMatch[0].slice(0, 100) });
61
+ }
57
62
  fullResponse = fullResponse.replace(toolCallMatch[0], "").trim();
58
63
  toolCallBuffer = "";
59
64
  if (toolName !== "noop") {
@@ -130,6 +135,18 @@ export class AgentRunner {
130
135
  result: resultText,
131
136
  isError: result.isError ?? false,
132
137
  });
138
+ // Break retry loop: don't feed back if tool has failed too many times
139
+ if (result.isError) {
140
+ const retries = (this._toolRetries.get(toolName) ?? 0) + 1;
141
+ this._toolRetries.set(toolName, retries);
142
+ if (retries > this._maxToolRetries) {
143
+ this._toolRetries.delete(toolName);
144
+ return; // Stop feeding errors — prevent loop
145
+ }
146
+ }
147
+ else {
148
+ this._toolRetries.delete(toolName);
149
+ }
133
150
  // Feed the result back to the model for a follow-up
134
151
  const toolMessages = [
135
152
  ...messages.slice(-5),