nyxora 26.6.26 → 26.6.28

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 (79) hide show
  1. package/README.md +23 -3
  2. package/bin/nyxora.mjs +16 -0
  3. package/dist/packages/core/src/agent/llmProvider.js +10 -3
  4. package/dist/packages/core/src/agent/web3Agent.js +1 -1
  5. package/dist/packages/core/src/config/marketConfigManager.js +2 -1
  6. package/dist/packages/core/src/config/parser.js +0 -5
  7. package/dist/packages/core/src/gateway/WebSocketManager.js +2 -1
  8. package/dist/packages/core/src/gateway/server.js +72 -12
  9. package/dist/packages/core/src/memory/episodic.js +7 -2
  10. package/dist/packages/core/src/memory/logger.js +16 -6
  11. package/dist/packages/core/src/memory/reflection.js +15 -11
  12. package/dist/packages/core/src/memory/validator.js +1 -12
  13. package/dist/packages/core/src/utils/httpClient.js +22 -5
  14. package/dist/packages/core/src/web3/aggregator/defiRouter.js +4 -2
  15. package/dist/packages/core/src/web3/aggregator/providerRegistry.js +8 -1
  16. package/dist/packages/core/src/web3/aggregator/providers/KyberSwapProvider.js +8 -4
  17. package/dist/packages/core/src/web3/aggregator/providers/LifiProvider.js +14 -2
  18. package/dist/packages/core/src/web3/aggregator/providers/OneInchProvider.js +3 -1
  19. package/dist/packages/core/src/web3/aggregator/providers/OpBridgeProvider.js +10 -2
  20. package/dist/packages/core/src/web3/aggregator/providers/OpenOceanProvider.js +63 -7
  21. package/dist/packages/core/src/web3/aggregator/providers/RelayProvider.js +20 -9
  22. package/dist/packages/core/src/web3/aggregator/providers/ZeroXProvider.js +40 -12
  23. package/dist/packages/core/src/web3/aggregator/routeSelector.js +25 -14
  24. package/dist/packages/core/src/web3/skills/bridgeToken.js +47 -3
  25. package/dist/packages/core/src/web3/skills/createMarketWatchAgent.js +26 -0
  26. package/dist/packages/core/src/web3/skills/getTxHistory.js +1 -1
  27. package/dist/packages/core/src/web3/skills/provideLiquidity.js +13 -7
  28. package/dist/packages/core/src/web3/skills/swapToken.js +47 -4
  29. package/dist/packages/core/src/web3/utils/marketEngine.js +8 -3
  30. package/dist/packages/core/src/web3/utils/rpcEngine.js +7 -1
  31. package/dist/packages/core/src/web3/utils/tokens.js +12 -12
  32. package/dist/packages/core/src/web3/utils/vaultClient.js +6 -0
  33. package/dist/packages/policy/src/server.js +24 -11
  34. package/package.json +1 -1
  35. package/packages/core/package.json +1 -1
  36. package/packages/core/src/agent/llmProvider.ts +14 -3
  37. package/packages/core/src/agent/web3Agent.ts +1 -1
  38. package/packages/core/src/config/marketConfigManager.ts +2 -2
  39. package/packages/core/src/config/parser.ts +3 -4
  40. package/packages/core/src/gateway/WebSocketManager.ts +2 -1
  41. package/packages/core/src/gateway/server.ts +72 -12
  42. package/packages/core/src/memory/episodic.ts +9 -2
  43. package/packages/core/src/memory/logger.ts +17 -6
  44. package/packages/core/src/memory/reflection.ts +18 -12
  45. package/packages/core/src/memory/validator.ts +1 -12
  46. package/packages/core/src/utils/httpClient.ts +22 -5
  47. package/packages/core/src/web3/aggregator/defiRouter.ts +6 -2
  48. package/packages/core/src/web3/aggregator/providerRegistry.ts +7 -1
  49. package/packages/core/src/web3/aggregator/providers/KyberSwapProvider.ts +9 -4
  50. package/packages/core/src/web3/aggregator/providers/LifiProvider.ts +16 -2
  51. package/packages/core/src/web3/aggregator/providers/OneInchProvider.ts +3 -2
  52. package/packages/core/src/web3/aggregator/providers/OpBridgeProvider.ts +11 -2
  53. package/packages/core/src/web3/aggregator/providers/OpenOceanProvider.ts +66 -7
  54. package/packages/core/src/web3/aggregator/providers/RelayProvider.ts +25 -10
  55. package/packages/core/src/web3/aggregator/providers/ZeroXProvider.ts +45 -12
  56. package/packages/core/src/web3/aggregator/routeSelector.ts +29 -16
  57. package/packages/core/src/web3/aggregator/types.ts +3 -0
  58. package/packages/core/src/web3/skills/bridgeToken.ts +19 -4
  59. package/packages/core/src/web3/skills/createMarketWatchAgent.ts +24 -0
  60. package/packages/core/src/web3/skills/getTxHistory.ts +1 -1
  61. package/packages/core/src/web3/skills/provideLiquidity.ts +14 -7
  62. package/packages/core/src/web3/skills/swapToken.ts +17 -5
  63. package/packages/core/src/web3/utils/marketEngine.ts +7 -3
  64. package/packages/core/src/web3/utils/rpcEngine.ts +5 -1
  65. package/packages/core/src/web3/utils/tokens.ts +12 -12
  66. package/packages/core/src/web3/utils/vaultClient.ts +7 -0
  67. package/packages/dashboard/dist/assets/index-BoFANMsj.js +16 -0
  68. package/packages/dashboard/dist/assets/index-K1CmXmAE.css +1 -0
  69. package/packages/dashboard/dist/index.html +2 -2
  70. package/packages/dashboard/package.json +1 -1
  71. package/packages/mcp-server/dist/server.js +17 -5
  72. package/packages/mcp-server/package.json +2 -2
  73. package/packages/mcp-server/src/server.ts +20 -5
  74. package/packages/policy/package.json +1 -1
  75. package/packages/policy/src/server.ts +28 -13
  76. package/packages/signer/package.json +1 -1
  77. package/dist/packages/core/src/test_security.js +0 -43
  78. package/packages/dashboard/dist/assets/index-C_WmWSch.js +0 -16
  79. package/packages/dashboard/dist/assets/index-CjZWf1Ei.css +0 -1
package/README.md CHANGED
@@ -97,8 +97,21 @@ To dive deeper into the technical details of our Zero-Knowledge security archite
97
97
 
98
98
  ## 🚀 Quick Start & Installation
99
99
 
100
- ### Option 1: Global Installation (Recommended)
101
- Nyxora can be installed globally via NPM, allowing you to use the `nyxora` CLI command from anywhere on your machine.
100
+ ### Option 1: One-Line Installation (Recommended)
101
+ The fastest way to install Nyxora is via our smart installation wrapper. This script automatically prepares Node.js (if missing) and securely fetches the Nyxora daemon directly from the NPM Registry.
102
+
103
+ **Linux & macOS:**
104
+ ```bash
105
+ curl -fsSL https://nyxoraai.github.io/Nyxora/install.sh | bash
106
+ ```
107
+
108
+ **Windows (PowerShell):**
109
+ ```powershell
110
+ iwr -useb https://nyxoraai.github.io/Nyxora/install.ps1 | iex
111
+ ```
112
+
113
+ ### Option 2: Global Installation (NPM)
114
+ If you already have Node.js installed, you can natively install Nyxora globally via NPM, allowing you to use the `nyxora` CLI command from anywhere on your machine.
102
115
 
103
116
  ```bash
104
117
  # Install globally
@@ -124,7 +137,7 @@ cd Nyxora
124
137
  # 1. Install Dependencies
125
138
  npm install
126
139
 
127
- # 2. Build the Dashboard UI
140
+ # 2. Build the Core, MCP Server, and Dashboard UI
128
141
  npm run build
129
142
 
130
143
  # 3. Interactive Setup Wizard
@@ -138,6 +151,13 @@ npm start
138
151
 
139
152
  > **⚠️ IMPORTANT:** Whenever you re-run `nyxora setup` or manually edit the config files, you **must restart the server** for the changes to take effect.
140
153
 
154
+ ### 🧹 Uninstallation & Reset
155
+ If you ever need to securely wipe the AI's episodic memory, delete your API keys, and completely remove Nyxora's configuration from your operating system, simply run:
156
+ ```bash
157
+ nyxora uninstall
158
+ ```
159
+ This acts as a master reset switch to return your environment to a clean state.
160
+
141
161
  ---
142
162
 
143
163
  ## ⚖️ Terms of Service
package/bin/nyxora.mjs CHANGED
@@ -350,6 +350,20 @@ async function unlock() {
350
350
  }
351
351
  }
352
352
 
353
+ async function serveMcp() {
354
+ const compiledMcp = path.join(projectRoot, 'packages/mcp-server/dist/server.js');
355
+ const useCompiled = fs.existsSync(compiledMcp);
356
+ const cmd = useCompiled ? 'node' : 'npx';
357
+ const args = useCompiled ? [compiledMcp] : ['ts-node', '-T', 'packages/mcp-server/src/server.ts'];
358
+ const child = spawn(cmd, args, {
359
+ cwd: projectRoot,
360
+ stdio: 'inherit',
361
+ env: { ...process.env, TS_NODE_CACHE: 'false' }
362
+ });
363
+
364
+ await new Promise(resolve => child.on('close', resolve));
365
+ }
366
+
353
367
  async function main() {
354
368
  switch(command) {
355
369
  case 'doctor': await runDoctor(); break;
@@ -366,6 +380,7 @@ async function main() {
366
380
  case 'unlock': await unlock(); break;
367
381
  case 'clean-logs': await cleanLogs(); break;
368
382
  case 'autostart': await autostart(process.argv[3]); break;
383
+ case 'mcp': await serveMcp(); break;
369
384
  case '-v':
370
385
  case '--v':
371
386
  case '--version':
@@ -383,6 +398,7 @@ Commands:
383
398
  start Start the Nyxora background daemon
384
399
  stop Stop the running daemon
385
400
  restart Restart the daemon
401
+ mcp Start the MCP Server directly (for testing/debug)
386
402
  chat Chat interactively with the AI in terminal
387
403
  setup Run the interactive Setup Wizard
388
404
  dashboard Open the dashboard in your browser
@@ -12,7 +12,8 @@ class OpenAIAdapter {
12
12
  message: {
13
13
  content: response.choices[0].message.content,
14
14
  tool_calls: response.choices[0].message.tool_calls
15
- }
15
+ },
16
+ usage: response.usage ? { total_tokens: response.usage.total_tokens } : undefined
16
17
  };
17
18
  }
18
19
  }
@@ -124,7 +125,8 @@ class AnthropicAdapter {
124
125
  message: {
125
126
  content: contentStr,
126
127
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined
127
- }
128
+ },
129
+ usage: response.usage ? { total_tokens: response.usage.input_tokens + response.usage.output_tokens } : undefined
128
130
  };
129
131
  }
130
132
  }
@@ -254,11 +256,16 @@ class GeminiAdapter {
254
256
  }
255
257
  }
256
258
  }
259
+ let totalTokens = 0;
260
+ if (data.usageMetadata && data.usageMetadata.totalTokenCount) {
261
+ totalTokens = data.usageMetadata.totalTokenCount;
262
+ }
257
263
  return {
258
264
  message: {
259
265
  content: contentStr,
260
266
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined
261
- }
267
+ },
268
+ usage: totalTokens > 0 ? { total_tokens: totalTokens } : undefined
262
269
  };
263
270
  }
264
271
  }
@@ -69,7 +69,7 @@ async function processWeb3Intent(input, role = 'user', onProgress, sessionId) {
69
69
  const context = 'web3';
70
70
  const response = await (0, llmUtils_1.executeWithRetry)(async (client) => {
71
71
  // Debug log to find out why Gemini 400 error happens
72
- console.log(`[LLM Debug] Sending ${messages.length} messages to LLM.`);
72
+ // console.log(`[LLM Debug] Sending ${messages.length} messages to LLM.`);
73
73
  return await client.chat({
74
74
  model: config.llm.model,
75
75
  temperature: config.llm.temperature,
@@ -8,8 +8,8 @@ exports.saveMarketKeys = saveMarketKeys;
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const yaml_1 = __importDefault(require("yaml"));
10
10
  const paths_1 = require("./paths");
11
- const MARKET_KEYS_FILE = (0, paths_1.getPath)('market_keys.yaml');
12
11
  function loadMarketKeys() {
12
+ const MARKET_KEYS_FILE = (0, paths_1.getPath)('market_keys.yaml');
13
13
  if (!fs_1.default.existsSync(MARKET_KEYS_FILE)) {
14
14
  return {};
15
15
  }
@@ -34,6 +34,7 @@ function saveMarketKeys(keys) {
34
34
  delete updated[k];
35
35
  }
36
36
  }
37
+ const MARKET_KEYS_FILE = (0, paths_1.getPath)('market_keys.yaml');
37
38
  fs_1.default.writeFileSync(MARKET_KEYS_FILE, yaml_1.default.stringify(updated), 'utf8');
38
39
  }
39
40
  catch (error) {
@@ -150,11 +150,6 @@ function loadConfig() {
150
150
  needsSave = true;
151
151
  parsed.integrations.telegram.bot_token = decryptDataSync(parsed.integrations.telegram.bot_token);
152
152
  }
153
- if (parsed.web3?.explorer_api_key) {
154
- if (parsed.web3.explorer_api_key.startsWith('ENC:'))
155
- needsSave = true;
156
- parsed.web3.explorer_api_key = decryptDataSync(parsed.web3.explorer_api_key);
157
- }
158
153
  // Auto-migration logic: move llm.credentials to root credentials
159
154
  if (parsed.llm && parsed.llm.credentials) {
160
155
  if (!parsed.credentials) {
@@ -26,7 +26,8 @@ class WebSocketManager {
26
26
  return;
27
27
  }
28
28
  // Validate Auth Token during Upgrade Handshake
29
- if (!(0, state_1.validateToken)(token)) {
29
+ const validation = (0, state_1.validateToken)(token);
30
+ if (!validation.valid) {
30
31
  socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
31
32
  socket.destroy();
32
33
  return;
@@ -183,7 +183,7 @@ app.post('/api/upload-google-credentials', (req, res) => {
183
183
  app.get('/api/history', (req, res) => {
184
184
  try {
185
185
  const sessionId = req.query.session_id;
186
- const history = reasoning_1.logger.getHistory(sessionId);
186
+ const history = reasoning_1.logger.getHistory(sessionId, 1000);
187
187
  // Filter out internal system prompt for the frontend
188
188
  const cleanHistory = history.filter((msg) => msg.role !== 'system');
189
189
  res.json(cleanHistory);
@@ -211,6 +211,18 @@ app.get('/api/sessions', (req, res) => {
211
211
  res.status(500).json({ error: error.message });
212
212
  }
213
213
  });
214
+ app.get('/api/sessions/search', (req, res) => {
215
+ try {
216
+ const q = req.query.q;
217
+ if (!q) {
218
+ return res.json(reasoning_1.logger.getSessions());
219
+ }
220
+ res.json(reasoning_1.logger.searchSessions(q));
221
+ }
222
+ catch (error) {
223
+ res.status(500).json({ error: error.message });
224
+ }
225
+ });
214
226
  app.post('/api/sessions', (req, res) => {
215
227
  try {
216
228
  const { title } = req.body;
@@ -297,16 +309,20 @@ app.get('/api/defi-keys', (req, res) => {
297
309
  const { aggregatorRegistry } = require('../web3/aggregator/providerRegistry');
298
310
  const providers = aggregatorRegistry.getAllProviders();
299
311
  const requirements = [];
312
+ const seenKeys = new Set();
300
313
  for (const provider of providers) {
301
314
  if (provider.manifest.requiredApiKeys) {
302
315
  for (const req of provider.manifest.requiredApiKeys) {
303
- requirements.push({
304
- id: req.id,
305
- label: req.label,
306
- required: req.required,
307
- docsUrl: req.docsUrl,
308
- configured: !!(keys[req.envKey] || keys[req.id])
309
- });
316
+ if (!seenKeys.has(req.id)) {
317
+ seenKeys.add(req.id);
318
+ requirements.push({
319
+ id: req.id,
320
+ label: req.label,
321
+ required: req.required,
322
+ docsUrl: req.docsUrl,
323
+ configured: !!keys[req.id]
324
+ });
325
+ }
310
326
  }
311
327
  }
312
328
  }
@@ -325,6 +341,17 @@ app.post('/api/defi-keys', (req, res) => {
325
341
  res.status(500).json({ error: error.message });
326
342
  }
327
343
  });
344
+ app.delete('/api/defi-keys/:id', (req, res) => {
345
+ try {
346
+ const keys = (0, defiConfigManager_1.loadDefiKeys)();
347
+ delete keys[req.params.id];
348
+ (0, defiConfigManager_1.saveDefiKeys)(keys);
349
+ res.json({ success: true });
350
+ }
351
+ catch (error) {
352
+ res.status(500).json({ error: error.message });
353
+ }
354
+ });
328
355
  app.get('/api/market-keys', (req, res) => {
329
356
  try {
330
357
  const keys = (0, marketConfigManager_1.loadMarketKeys)();
@@ -834,12 +861,15 @@ app.get('/api/portfolio', async (req, res) => {
834
861
  // --- Memory Triggers ---
835
862
  let messageCounter = 0;
836
863
  let idleTimer = null;
837
- function resetIdleTimer() {
864
+ function resetIdleTimer(sessionId) {
838
865
  if (idleTimer)
839
866
  clearTimeout(idleTimer);
840
867
  idleTimer = setTimeout(() => {
841
868
  console.log('[Memory] Idle trigger activated. Running Reflection Engine...');
842
- reflection_1.ReflectionEngine.runReflection();
869
+ reflection_1.ReflectionEngine.runReflection(sessionId).then(() => {
870
+ const { PromotionEngine } = require('../memory/promotionEngine');
871
+ PromotionEngine.runPromotionAndDecay();
872
+ }).catch(console.error);
843
873
  }, 3 * 60 * 1000); // 3 minutes idle
844
874
  }
845
875
  app.post('/api/v1/trade', async (req, res) => {
@@ -867,13 +897,16 @@ app.post('/api/chat', async (req, res) => {
867
897
  // Process input (this will automatically add to memory)
868
898
  const response = await (0, reasoning_1.processUserInput)(message, 'user', undefined, session_id);
869
899
  // Memory Triggers
870
- resetIdleTimer();
900
+ resetIdleTimer(session_id);
871
901
  messageCounter++;
872
902
  if (messageCounter >= 5) {
873
903
  console.log('[Memory] N-Message threshold reached. Running Reflection Engine...');
874
904
  messageCounter = 0;
875
905
  // Run asynchronously
876
- reflection_1.ReflectionEngine.runReflection();
906
+ reflection_1.ReflectionEngine.runReflection(session_id).then(() => {
907
+ const { PromotionEngine } = require('../memory/promotionEngine');
908
+ PromotionEngine.runPromotionAndDecay();
909
+ }).catch(console.error);
877
910
  }
878
911
  res.json({ response });
879
912
  }
@@ -891,6 +924,17 @@ app.get('/api/memory', (req, res) => {
891
924
  res.status(500).json({ error: error.message });
892
925
  }
893
926
  });
927
+ app.delete('/api/memory/all', (req, res) => {
928
+ try {
929
+ episodic_1.episodicDB.clearAllMemories();
930
+ const { PromotionEngine } = require('../memory/promotionEngine');
931
+ PromotionEngine.runPromotionAndDecay();
932
+ res.json({ success: true, message: "Episodic memory wiped completely." });
933
+ }
934
+ catch (error) {
935
+ res.status(500).json({ error: error.message });
936
+ }
937
+ });
894
938
  app.delete('/api/memory/:id', (req, res) => {
895
939
  try {
896
940
  const id = parseInt(req.params.id, 10);
@@ -1025,6 +1069,22 @@ async function startServer() {
1025
1069
  (0, bridgeWatcher_1.startBridgeWatcher)();
1026
1070
  // Start Event Listener for Limit Orders (V3)
1027
1071
  eventListener_1.eventListener.start();
1072
+ // Resume Market Watch tasks
1073
+ try {
1074
+ const fs = require('fs');
1075
+ const path = require('path');
1076
+ const { getAppDir } = require('../config/paths');
1077
+ const tasksPath = path.join(getAppDir(), 'market_tasks.json');
1078
+ if (fs.existsSync(tasksPath)) {
1079
+ const tasks = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
1080
+ if (tasks.length > 0) {
1081
+ console.log(`[Market Watch] Resuming ${tasks.length} background watch tasks...`);
1082
+ }
1083
+ }
1084
+ }
1085
+ catch (e) {
1086
+ console.error('[Market Watch] Failed to resume tasks:', e);
1087
+ }
1028
1088
  });
1029
1089
  server.on('error', (e) => {
1030
1090
  if (e.code === 'EADDRINUSE') {
@@ -36,16 +36,17 @@ class EpisodicMemoryDB {
36
36
  addCandidateFact(fact, confidenceScore = 0.5, category = 'general', ruleType = 'observation') {
37
37
  // Upsert logic
38
38
  const existing = this.db.prepare('SELECT id, occurrences, confidence FROM episodic_memories WHERE fact = ?').get(fact);
39
+ const safeScore = Math.min(1.0, confidenceScore);
39
40
  if (existing) {
40
41
  // Increment occurrences, boost confidence slightly up to max 1.0
41
42
  const newOccurrences = existing.occurrences + 1;
42
- const newConfidence = Math.min(1.0, existing.confidence + (confidenceScore * 0.2)); // Dampened boost
43
+ const newConfidence = Math.min(1.0, existing.confidence + (safeScore * 0.2)); // Dampened boost
43
44
  const stmt = this.db.prepare('UPDATE episodic_memories SET occurrences = ?, confidence = ?, rule_type = ?, lastSeen = CURRENT_TIMESTAMP WHERE id = ?');
44
45
  stmt.run(newOccurrences, newConfidence, ruleType, existing.id);
45
46
  }
46
47
  else {
47
48
  const stmt = this.db.prepare('INSERT INTO episodic_memories (fact, confidence, category, rule_type) VALUES (?, ?, ?, ?)');
48
- stmt.run(fact, confidenceScore, category, ruleType);
49
+ stmt.run(fact, safeScore, category, ruleType);
49
50
  }
50
51
  }
51
52
  getMemories() {
@@ -64,6 +65,10 @@ class EpisodicMemoryDB {
64
65
  `);
65
66
  stmt.run(minConfidence, daysOld);
66
67
  }
68
+ clearAllMemories() {
69
+ const stmt = this.db.prepare('DELETE FROM episodic_memories');
70
+ stmt.run();
71
+ }
67
72
  close() {
68
73
  try {
69
74
  this.db.close();
@@ -150,19 +150,29 @@ class Logger {
150
150
  renameSession(sessionId, newTitle) {
151
151
  this.db.prepare('UPDATE sessions SET title = ? WHERE id = ?').run(newTitle, sessionId);
152
152
  }
153
- getHistory(sessionId) {
153
+ searchSessions(query) {
154
+ const term = `%${query}%`;
155
+ return this.db.prepare(`
156
+ SELECT DISTINCT s.*
157
+ FROM sessions s
158
+ LEFT JOIN messages m ON s.id = m.session_id
159
+ WHERE s.title LIKE ? OR m.content LIKE ?
160
+ ORDER BY s.timestamp DESC
161
+ `).all(term, term);
162
+ }
163
+ getHistory(sessionId, limit = 40) {
154
164
  let rows;
155
165
  // Phase 2: Sliding Window Algorithm (LLM Context Limit)
156
- // Fetch only the last 40 messages, then order them chronologically
166
+ // Fetch only the last X messages, then order them chronologically
157
167
  if (sessionId) {
158
168
  rows = this.db.prepare(`
159
169
  SELECT * FROM (
160
170
  SELECT role, content, name, tool_call_id, tool_calls, session_id, id
161
171
  FROM messages
162
172
  WHERE session_id = ?
163
- ORDER BY id DESC LIMIT 40
173
+ ORDER BY id DESC LIMIT ?
164
174
  ) ORDER BY id ASC
165
- `).all(sessionId);
175
+ `).all(sessionId, limit);
166
176
  }
167
177
  else {
168
178
  rows = this.db.prepare(`
@@ -170,9 +180,9 @@ class Logger {
170
180
  SELECT role, content, name, tool_call_id, tool_calls, session_id, id
171
181
  FROM messages
172
182
  WHERE session_id IS NULL
173
- ORDER BY id DESC LIMIT 40
183
+ ORDER BY id DESC LIMIT ?
174
184
  ) ORDER BY id ASC
175
- `).all();
185
+ `).all(limit);
176
186
  }
177
187
  return rows.map((row) => {
178
188
  const entry = {
@@ -7,24 +7,27 @@ const logger_1 = require("./logger");
7
7
  const validator_1 = require("./validator");
8
8
  const episodic_1 = require("./episodic");
9
9
  class ReflectionEngine {
10
- static async runReflection() {
10
+ static async runReflection(sessionId) {
11
11
  try {
12
12
  // 1. Get recent session history
13
- const history = logger_1.logger.getHistory();
14
- if (history.length === 0)
13
+ const history = logger_1.logger.getHistory(sessionId);
14
+ if (history.length === 0) {
15
+ console.log('[ReflectionEngine] History is empty. Aborting reflection.');
15
16
  return;
16
- // Extract just the user and assistant text
17
+ }
18
+ // Extract just the user and assistant text, ignoring tool messages
17
19
  const recentChat = history
20
+ .filter(msg => msg.role !== 'tool')
18
21
  .map(msg => `[${msg.role}]: ${msg.content}`)
19
22
  .join('\n');
20
23
  const config = (0, parser_1.loadConfig)();
21
24
  const model = config.llm?.model || 'gpt-4o';
22
- const openai = await (0, llmUtils_1.getOpenAI)();
25
+ const llm = await (0, llmUtils_1.getLLMClient)();
23
26
  // 2. Build the heavily constrained System Prompt
24
27
  const systemPrompt = `
25
28
  You are the Self-Reflection Engine for a Web3 AI Agent.
26
29
  Your job is to analyze the following recent conversation and extract user habits, preferences, or corrections.
27
- You MUST output ONLY valid JSON in the exact format specified.
30
+ You MUST output ONLY valid JSON in the exact format specified. Do not include markdown code blocks around the JSON.
28
31
 
29
32
  CRITICAL RULES:
30
33
  1. DO NOT extract or remember any Private Keys, Seed Phrases, Mnemonic Words, Passwords, API Keys, or Session Tokens.
@@ -48,19 +51,20 @@ Return a JSON object with an array "memories":
48
51
  - rule_type "permanent": A strict reprimand or absolute preference (e.g., "Never use Ethereum!").
49
52
  `;
50
53
  // 3. Query LLM
51
- const response = await openai.chat.completions.create({
54
+ const response = await llm.chat({
52
55
  model: model,
53
56
  messages: [
54
57
  { role: 'system', content: systemPrompt },
55
58
  { role: 'user', content: recentChat }
56
59
  ],
57
- response_format: { type: 'json_object' },
58
60
  temperature: 0.1
59
61
  });
60
- const content = response.choices[0]?.message?.content;
62
+ const content = response.message?.content;
61
63
  if (!content)
62
64
  return;
63
- const data = JSON.parse(content);
65
+ // Strip markdown codeblocks if LLM incorrectly formatted it
66
+ const cleanContent = content.replace(/```json/gi, '').replace(/```/g, '').trim();
67
+ const data = JSON.parse(cleanContent);
64
68
  const memories = data.memories || [];
65
69
  // 4. Validate and Store
66
70
  let addedCount = 0;
@@ -74,7 +78,7 @@ Return a JSON object with an array "memories":
74
78
  // Fast-Track Override Logic
75
79
  let confidence = 0.5; // default for observation
76
80
  if (mem.rule_type === 'permanent')
77
- confidence = 2.0; // Fast-track override
81
+ confidence = 1.0; // Fast-track override
78
82
  if (mem.rule_type === 'temporary')
79
83
  confidence = 0.8;
80
84
  episodic_1.episodicDB.addCandidateFact(safeFact, confidence, mem.category || 'general', mem.rule_type || 'observation');
@@ -16,18 +16,7 @@ class MemoryValidator {
16
16
  if (privateKeyRegex.test(text)) {
17
17
  throw new Error('SECURITY VIOLATION: Potential Private Key detected in memory extraction.');
18
18
  }
19
- // 2. Check for typical 12-24 word BIP-39 Mnemonic patterns.
20
- // We do a rough heuristic: 12-24 lowercase words separated by spaces in a single block.
21
- // This is hard to do perfectly with regex, so we look for sequences of 12+ words
22
- const words = text.toLowerCase().match(/\b[a-z]+\b/g) || [];
23
- if (words.length >= 12) {
24
- // Very basic check: are there exactly 12 or 24 words in a continuous string?
25
- const mnemonicRegex = /\b([a-z]+(?:\s+[a-z]+){11,23})\b/g;
26
- if (mnemonicRegex.test(text.toLowerCase())) {
27
- // Log it as suspicious, but we'll reject it to be safe if it looks like a seed phrase
28
- throw new Error('SECURITY VIOLATION: Potential Seed Phrase detected in memory extraction.');
29
- }
30
- }
19
+ // Removed overly aggressive 12-word mnemonic check to avoid false positives in conversational languages
31
20
  // 3. Check for API/Bot Tokens (e.g., Telegram token format: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11)
32
21
  const telegramTokenRegex = /\b\d{8,10}:[a-zA-Z0-9_-]{35}\b/g;
33
22
  if (telegramTokenRegex.test(text)) {
@@ -26,16 +26,29 @@ async function safeFetch(url, options = {}) {
26
26
  for (let attempt = 0; attempt <= retries; attempt++) {
27
27
  const controller = new AbortController();
28
28
  const id = setTimeout(() => controller.abort(), timeoutMs);
29
+ const externalSignal = fetchOptions.signal;
30
+ const onExternalAbort = () => controller.abort();
31
+ if (externalSignal) {
32
+ if (externalSignal.aborted)
33
+ controller.abort();
34
+ else
35
+ externalSignal.addEventListener('abort', onExternalAbort);
36
+ }
37
+ const cleanup = () => {
38
+ clearTimeout(id);
39
+ if (externalSignal)
40
+ externalSignal.removeEventListener('abort', onExternalAbort);
41
+ };
29
42
  try {
30
43
  const res = await fetch(url, {
31
44
  ...fetchOptions,
32
45
  headers: finalHeaders,
33
46
  signal: controller.signal
34
47
  });
35
- clearTimeout(id);
48
+ cleanup();
36
49
  // Handle Rate Limits specifically with a forced retry if attempts remain
37
50
  if (res.status === 429) {
38
- if (attempt < retries) {
51
+ if (attempt < retries && !controller.signal.aborted) {
39
52
  // Coingecko and others often need longer backoff, exponentially increase
40
53
  await delay(retryDelayMs * (attempt + 1) * 2);
41
54
  continue;
@@ -44,7 +57,7 @@ async function safeFetch(url, options = {}) {
44
57
  }
45
58
  // Handle Server Errors with retry
46
59
  if (res.status >= 500) {
47
- if (attempt < retries) {
60
+ if (attempt < retries && !controller.signal.aborted) {
48
61
  await delay(retryDelayMs * (attempt + 1));
49
62
  continue;
50
63
  }
@@ -53,8 +66,12 @@ async function safeFetch(url, options = {}) {
53
66
  return res;
54
67
  }
55
68
  catch (error) {
56
- clearTimeout(id);
69
+ cleanup();
57
70
  lastError = error;
71
+ // Do not retry if the external aggregator specifically aborted us
72
+ if (externalSignal && externalSignal.aborted) {
73
+ throw new HttpError(499, 'Client Closed Request', '');
74
+ }
58
75
  // AbortError is a timeout
59
76
  if (error.name === 'AbortError') {
60
77
  if (attempt < retries) {
@@ -64,7 +81,7 @@ async function safeFetch(url, options = {}) {
64
81
  throw new HttpError(408, 'Request Timeout', '');
65
82
  }
66
83
  // Network errors (like fetch failed)
67
- if (attempt < retries) {
84
+ if (attempt < retries && !controller.signal.aborted) {
68
85
  await delay(retryDelayMs * (attempt + 1));
69
86
  continue;
70
87
  }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.routeTransaction = routeTransaction;
4
4
  const routeSelector_1 = require("./routeSelector");
5
- async function routeTransaction(fromChain, toChain, fromToken, toToken, amountInWei, userAddress, slippageTolerance = "auto") {
5
+ async function routeTransaction(fromChain, toChain, fromToken, toToken, amountInWei, amountFormatted, userAddress, slippageTolerance = "auto", providerName) {
6
6
  fromChain = String(fromChain || "");
7
7
  toChain = String(toChain || "");
8
8
  if (!fromChain || !toChain) {
@@ -35,8 +35,10 @@ async function routeTransaction(fromChain, toChain, fromToken, toToken, amountIn
35
35
  fromToken,
36
36
  toToken,
37
37
  amountInWei,
38
+ amountFormatted,
38
39
  userAddress,
39
- slippageTolerance
40
+ slippageTolerance,
41
+ preferredProvider: providerName && providerName !== "auto" ? providerName : undefined
40
42
  };
41
43
  console.log(`[DeFi Router] Routing transaction via Extensible Provider Runtime...`);
42
44
  return await (0, routeSelector_1.fetchBestRoute)(request, "best_output");
@@ -91,7 +91,14 @@ class AggregatorRegistry {
91
91
  fs_1.default.mkdirSync(providersDir, { recursive: true });
92
92
  return;
93
93
  }
94
- const files = fs_1.default.readdirSync(providersDir).filter(f => f.endsWith('.ts') || f.endsWith('.js'));
94
+ const isCompiled = __dirname.includes('dist') || __dirname.includes('build') || process.env.NODE_ENV === 'production';
95
+ const files = fs_1.default.readdirSync(providersDir).filter(f => {
96
+ if (f.endsWith('.d.ts'))
97
+ return false;
98
+ if (isCompiled)
99
+ return f.endsWith('.js');
100
+ return f.endsWith('.ts') || f.endsWith('.js');
101
+ });
95
102
  for (const file of files) {
96
103
  const fullPath = path_1.default.join(providersDir, file);
97
104
  try {
@@ -38,7 +38,7 @@ class KyberSwapProvider {
38
38
  const slipParam = request.slippageTolerance === "auto" ? 50 : (request.slippageTolerance * 100);
39
39
  // Phase 1: Route
40
40
  const routeUrl = `https://aggregator-api.kyberswap.com/${chainName}/api/v1/routes?tokenIn=${request.fromToken}&tokenOut=${request.toToken}&amountIn=${request.amountInWei}`;
41
- const routeRes = await (0, httpClient_1.safeFetch)(routeUrl, { signal: context.abortSignal });
41
+ const routeRes = await (0, httpClient_1.safeFetch)(routeUrl, { signal: context.abortSignal, retries: 0 });
42
42
  if (!routeRes.ok)
43
43
  throw new Error(`KyberSwap Route Error: ${await routeRes.text()}`);
44
44
  const routeData = await routeRes.json();
@@ -55,13 +55,17 @@ class KyberSwapProvider {
55
55
  method: 'POST',
56
56
  headers: { 'Content-Type': 'application/json' },
57
57
  body: JSON.stringify(buildPayload),
58
- signal: context.abortSignal
58
+ signal: context.abortSignal,
59
+ retries: 0
59
60
  });
60
61
  if (!buildRes.ok)
61
62
  throw new Error(`KyberSwap Build Error: ${await buildRes.text()}`);
62
63
  const buildData = await buildRes.json();
63
- const isNative = request.fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ||
64
- request.fromToken === '0x0000000000000000000000000000000000000000';
64
+ // KyberSwap returns calldata in 'data' field, and router address in 'routerAddress'
65
+ if (!buildData.data || !buildData.data.routerAddress || !buildData.data.data) {
66
+ throw new Error(`KyberSwap build response missing required fields (routerAddress or data). Got: ${JSON.stringify(Object.keys(buildData.data || {}))}`);
67
+ }
68
+ const isNative = request.fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
65
69
  return {
66
70
  provider: this.manifest.name,
67
71
  routeId: `kyber-${crypto_1.default.randomUUID()}`,
@@ -44,17 +44,29 @@ class LifiProvider {
44
44
  async getQuote(request, context) {
45
45
  const key = context.apiKeys['lifi_key'];
46
46
  const slipParam = request.slippageTolerance === "auto" ? 0.005 : (request.slippageTolerance / 100);
47
- const url = `https://li.quest/v1/quote?fromChain=${request.fromChain}&toChain=${request.toChain}&fromToken=${request.fromToken}&toToken=${request.toToken}&fromAmount=${request.amountInWei}&fromAddress=${request.userAddress}&slippage=${slipParam}`;
47
+ const fromChainId = CHAIN_IDS[request.fromChain];
48
+ const toChainId = CHAIN_IDS[request.toChain];
49
+ const url = `https://li.quest/v1/quote?fromChain=${fromChainId}&toChain=${toChainId}&fromToken=${request.fromToken}&toToken=${request.toToken}&fromAmount=${request.amountInWei}&fromAddress=${request.userAddress}&slippage=${slipParam}`;
48
50
  const headers = {};
49
51
  if (key)
50
52
  headers['x-lifi-api-key'] = key;
51
53
  const res = await (0, httpClient_1.safeFetch)(url, {
52
54
  headers,
53
- signal: context.abortSignal
55
+ signal: context.abortSignal,
56
+ retries: 0
54
57
  });
55
58
  if (!res.ok)
56
59
  throw new Error(`LI.FI API Error: ${await res.text()}`);
57
60
  const data = await res.json();
61
+ // LiFi can return HTTP 200 with an 'errors' array if a tool fails
62
+ if (data.errors && data.errors.length > 0) {
63
+ const err = data.errors[0];
64
+ throw new Error(`LI.FI route error (${err.code || 'UNKNOWN'}): ${err.message}`);
65
+ }
66
+ // transactionRequest may be null if LiFi found no executable route
67
+ if (!data.transactionRequest || !data.transactionRequest.to) {
68
+ throw new Error(`[LifiProvider] No valid transactionRequest returned — no executable route found`);
69
+ }
58
70
  return {
59
71
  provider: this.manifest.name,
60
72
  routeId: `lifi-${crypto_1.default.randomUUID()}`,