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.
- package/README.md +23 -3
- package/bin/nyxora.mjs +16 -0
- package/dist/packages/core/src/agent/llmProvider.js +10 -3
- package/dist/packages/core/src/agent/web3Agent.js +1 -1
- package/dist/packages/core/src/config/marketConfigManager.js +2 -1
- package/dist/packages/core/src/config/parser.js +0 -5
- package/dist/packages/core/src/gateway/WebSocketManager.js +2 -1
- package/dist/packages/core/src/gateway/server.js +72 -12
- package/dist/packages/core/src/memory/episodic.js +7 -2
- package/dist/packages/core/src/memory/logger.js +16 -6
- package/dist/packages/core/src/memory/reflection.js +15 -11
- package/dist/packages/core/src/memory/validator.js +1 -12
- package/dist/packages/core/src/utils/httpClient.js +22 -5
- package/dist/packages/core/src/web3/aggregator/defiRouter.js +4 -2
- package/dist/packages/core/src/web3/aggregator/providerRegistry.js +8 -1
- package/dist/packages/core/src/web3/aggregator/providers/KyberSwapProvider.js +8 -4
- package/dist/packages/core/src/web3/aggregator/providers/LifiProvider.js +14 -2
- package/dist/packages/core/src/web3/aggregator/providers/OneInchProvider.js +3 -1
- package/dist/packages/core/src/web3/aggregator/providers/OpBridgeProvider.js +10 -2
- package/dist/packages/core/src/web3/aggregator/providers/OpenOceanProvider.js +63 -7
- package/dist/packages/core/src/web3/aggregator/providers/RelayProvider.js +20 -9
- package/dist/packages/core/src/web3/aggregator/providers/ZeroXProvider.js +40 -12
- package/dist/packages/core/src/web3/aggregator/routeSelector.js +25 -14
- package/dist/packages/core/src/web3/skills/bridgeToken.js +47 -3
- package/dist/packages/core/src/web3/skills/createMarketWatchAgent.js +26 -0
- package/dist/packages/core/src/web3/skills/getTxHistory.js +1 -1
- package/dist/packages/core/src/web3/skills/provideLiquidity.js +13 -7
- package/dist/packages/core/src/web3/skills/swapToken.js +47 -4
- package/dist/packages/core/src/web3/utils/marketEngine.js +8 -3
- package/dist/packages/core/src/web3/utils/rpcEngine.js +7 -1
- package/dist/packages/core/src/web3/utils/tokens.js +12 -12
- package/dist/packages/core/src/web3/utils/vaultClient.js +6 -0
- package/dist/packages/policy/src/server.js +24 -11
- package/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/llmProvider.ts +14 -3
- package/packages/core/src/agent/web3Agent.ts +1 -1
- package/packages/core/src/config/marketConfigManager.ts +2 -2
- package/packages/core/src/config/parser.ts +3 -4
- package/packages/core/src/gateway/WebSocketManager.ts +2 -1
- package/packages/core/src/gateway/server.ts +72 -12
- package/packages/core/src/memory/episodic.ts +9 -2
- package/packages/core/src/memory/logger.ts +17 -6
- package/packages/core/src/memory/reflection.ts +18 -12
- package/packages/core/src/memory/validator.ts +1 -12
- package/packages/core/src/utils/httpClient.ts +22 -5
- package/packages/core/src/web3/aggregator/defiRouter.ts +6 -2
- package/packages/core/src/web3/aggregator/providerRegistry.ts +7 -1
- package/packages/core/src/web3/aggregator/providers/KyberSwapProvider.ts +9 -4
- package/packages/core/src/web3/aggregator/providers/LifiProvider.ts +16 -2
- package/packages/core/src/web3/aggregator/providers/OneInchProvider.ts +3 -2
- package/packages/core/src/web3/aggregator/providers/OpBridgeProvider.ts +11 -2
- package/packages/core/src/web3/aggregator/providers/OpenOceanProvider.ts +66 -7
- package/packages/core/src/web3/aggregator/providers/RelayProvider.ts +25 -10
- package/packages/core/src/web3/aggregator/providers/ZeroXProvider.ts +45 -12
- package/packages/core/src/web3/aggregator/routeSelector.ts +29 -16
- package/packages/core/src/web3/aggregator/types.ts +3 -0
- package/packages/core/src/web3/skills/bridgeToken.ts +19 -4
- package/packages/core/src/web3/skills/createMarketWatchAgent.ts +24 -0
- package/packages/core/src/web3/skills/getTxHistory.ts +1 -1
- package/packages/core/src/web3/skills/provideLiquidity.ts +14 -7
- package/packages/core/src/web3/skills/swapToken.ts +17 -5
- package/packages/core/src/web3/utils/marketEngine.ts +7 -3
- package/packages/core/src/web3/utils/rpcEngine.ts +5 -1
- package/packages/core/src/web3/utils/tokens.ts +12 -12
- package/packages/core/src/web3/utils/vaultClient.ts +7 -0
- package/packages/dashboard/dist/assets/index-BoFANMsj.js +16 -0
- package/packages/dashboard/dist/assets/index-K1CmXmAE.css +1 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package.json +1 -1
- package/packages/mcp-server/dist/server.js +17 -5
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/server.ts +20 -5
- package/packages/policy/package.json +1 -1
- package/packages/policy/src/server.ts +28 -13
- package/packages/signer/package.json +1 -1
- package/dist/packages/core/src/test_security.js +0 -43
- package/packages/dashboard/dist/assets/index-C_WmWSch.js +0 -16
- 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:
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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 + (
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
62
|
+
const content = response.message?.content;
|
|
61
63
|
if (!content)
|
|
62
64
|
return;
|
|
63
|
-
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
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()}`,
|