nyxora 26.6.21 → 26.6.23
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 +3 -4
- package/bin/nyxora.mjs +14 -2
- package/dist/packages/core/src/agent/cronManager.js +107 -0
- package/dist/packages/core/src/agent/reasoning.js +85 -22
- package/dist/packages/core/src/agent/transactionManager.js +2 -2
- package/dist/packages/core/src/agent/updateIdentity.js +71 -0
- package/dist/packages/core/src/config/paths.js +5 -20
- package/dist/packages/core/src/gateway/WebSocketManager.js +98 -0
- package/dist/packages/core/src/gateway/chat.js +38 -0
- package/dist/packages/core/src/gateway/cli.js +20 -20
- package/dist/packages/core/src/gateway/server.js +63 -8
- package/dist/packages/core/src/gateway/setup.js +9 -6
- package/dist/packages/core/src/gateway/telegram.js +43 -0
- package/dist/packages/core/src/gateway/tracker.js +63 -0
- package/dist/packages/core/src/memory/logger.js +1 -1
- package/dist/packages/core/src/system/skills/cancelTask.js +40 -0
- package/dist/packages/core/src/system/skills/editFile.js +5 -0
- package/dist/packages/core/src/system/skills/scheduleTask.js +39 -0
- package/dist/packages/core/src/system/skills/searchWeb.js +61 -8
- package/dist/packages/core/src/system/skills/writeFile.js +5 -0
- package/dist/packages/core/src/web3/skills/getPrice.js +1 -1
- package/dist/packages/core/src/web3/utils/vaultClient.js +79 -29
- package/dist/packages/policy/src/server.js +29 -1
- package/package.json +7 -1
- package/packages/core/package.json +8 -1
- package/packages/core/src/agent/cronManager.ts +87 -0
- package/packages/core/src/agent/reasoning.ts +91 -21
- package/packages/core/src/agent/transactionManager.ts +2 -1
- package/packages/core/src/agent/updateIdentity.ts +68 -0
- package/packages/core/src/config/parser.ts +1 -1
- package/packages/core/src/config/paths.ts +5 -23
- package/packages/core/src/gateway/WebSocketManager.ts +114 -0
- package/packages/core/src/gateway/chat.ts +40 -1
- package/packages/core/src/gateway/cli.ts +10 -10
- package/packages/core/src/gateway/server.ts +66 -7
- package/packages/core/src/gateway/setup.ts +8 -5
- package/packages/core/src/gateway/telegram.ts +49 -0
- package/packages/core/src/gateway/tracker.ts +61 -0
- package/packages/core/src/memory/logger.ts +1 -1
- package/packages/core/src/system/skills/cancelTask.ts +38 -0
- package/packages/core/src/system/skills/editFile.ts +7 -0
- package/packages/core/src/system/skills/scheduleTask.ts +38 -0
- package/packages/core/src/system/skills/searchWeb.ts +56 -8
- package/packages/core/src/system/skills/writeFile.ts +7 -0
- package/packages/core/src/web3/skills/getPrice.ts +1 -1
- package/packages/core/src/web3/utils/vaultClient.ts +86 -26
- package/packages/dashboard/dist/assets/index-CjZWf1Ei.css +1 -0
- package/packages/dashboard/dist/assets/index-CmWZofn_.js +16 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/dist/routers/0x.png +0 -0
- package/packages/dashboard/dist/routers/1inch.png +0 -0
- package/packages/dashboard/dist/routers/cmc.png +0 -0
- package/packages/dashboard/dist/routers/kyberswap.png +0 -0
- package/packages/dashboard/dist/routers/lifi.png +0 -0
- package/packages/dashboard/dist/routers/openocean.png +0 -0
- package/packages/dashboard/dist/routers/relay.png +0 -0
- package/packages/dashboard/dist/routers/zerion.png +0 -0
- package/packages/dashboard/package.json +1 -1
- package/packages/mcp-server/package.json +1 -1
- package/packages/policy/package.json +4 -3
- package/packages/policy/src/server.ts +29 -1
- package/packages/signer/package.json +1 -1
- package/packages/dashboard/dist/assets/index-CQNHWZtN.css +0 -1
- package/packages/dashboard/dist/assets/index-Di9x08yk.js +0 -16
|
@@ -2,30 +2,12 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
|
|
5
|
-
let isGlobalModeCache: boolean | null = null;
|
|
6
|
-
|
|
7
5
|
export function getAppDir(): string {
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
const localConfig = path.join(process.cwd(), 'config.yaml');
|
|
12
|
-
|
|
13
|
-
if (fs.existsSync(localEnv) || fs.existsSync(localConfig)) {
|
|
14
|
-
isGlobalModeCache = false; // Local manual mode
|
|
15
|
-
} else {
|
|
16
|
-
isGlobalModeCache = true; // Global CLI mode
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (isGlobalModeCache) {
|
|
21
|
-
const globalDir = path.join(os.homedir(), '.nyxora');
|
|
22
|
-
if (!fs.existsSync(globalDir)) {
|
|
23
|
-
fs.mkdirSync(globalDir, { recursive: true });
|
|
24
|
-
}
|
|
25
|
-
return globalDir;
|
|
6
|
+
const globalDir = path.join(os.homedir(), '.nyxora');
|
|
7
|
+
if (!fs.existsSync(globalDir)) {
|
|
8
|
+
fs.mkdirSync(globalDir, { recursive: true });
|
|
26
9
|
}
|
|
27
|
-
|
|
28
|
-
return process.cwd();
|
|
10
|
+
return globalDir;
|
|
29
11
|
}
|
|
30
12
|
|
|
31
13
|
function ensureDir(dir: string) {
|
|
@@ -47,7 +29,7 @@ export function getPath(filename: string): string {
|
|
|
47
29
|
subDir = 'config';
|
|
48
30
|
} else if (lowerFile.endsWith('.token') || lowerFile.includes('vault') || lowerFile.includes('credentials')) {
|
|
49
31
|
subDir = 'auth';
|
|
50
|
-
} else if (lowerFile.endsWith('.log') || lowerFile.includes('pid')) {
|
|
32
|
+
} else if (lowerFile.endsWith('.log') || lowerFile.includes('pid') || lowerFile.includes('tracker')) {
|
|
51
33
|
subDir = 'run';
|
|
52
34
|
}
|
|
53
35
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import { validateToken } from '../utils/state';
|
|
4
|
+
import { IncomingMessage } from 'http';
|
|
5
|
+
|
|
6
|
+
interface BufferedLog {
|
|
7
|
+
timestamp: number;
|
|
8
|
+
message: string;
|
|
9
|
+
level: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class WebSocketManager {
|
|
13
|
+
private wss: WebSocketServer;
|
|
14
|
+
private clients: Map<string, WebSocket> = new Map();
|
|
15
|
+
private ringBuffer: Map<string, { logs: BufferedLog[]; timeout: NodeJS.Timeout }> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor(server: http.Server) {
|
|
18
|
+
this.wss = new WebSocketServer({ noServer: true });
|
|
19
|
+
|
|
20
|
+
server.on('upgrade', async (request: IncomingMessage, socket, head) => {
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(request.url || '', `http://${request.headers.host}`);
|
|
23
|
+
if (url.pathname !== '/ws/stream') {
|
|
24
|
+
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
|
25
|
+
socket.destroy();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const traceId = url.searchParams.get('traceId');
|
|
30
|
+
const token = url.searchParams.get('token');
|
|
31
|
+
|
|
32
|
+
if (!traceId || !token) {
|
|
33
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
34
|
+
socket.destroy();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate Auth Token during Upgrade Handshake
|
|
39
|
+
if (!validateToken(token)) {
|
|
40
|
+
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
41
|
+
socket.destroy();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
46
|
+
this.wss.emit('connection', ws, request, traceId);
|
|
47
|
+
});
|
|
48
|
+
} catch (e) {
|
|
49
|
+
socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
|
|
50
|
+
socket.destroy();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.wss.on('connection', (ws: WebSocket, request: IncomingMessage, traceId: string) => {
|
|
55
|
+
this.clients.set(traceId, ws);
|
|
56
|
+
|
|
57
|
+
ws.on('close', () => {
|
|
58
|
+
this.clients.delete(traceId);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Flush Ring Buffer if it exists
|
|
62
|
+
const buffer = this.ringBuffer.get(traceId);
|
|
63
|
+
if (buffer) {
|
|
64
|
+
clearTimeout(buffer.timeout);
|
|
65
|
+
for (const log of buffer.logs) {
|
|
66
|
+
ws.send(JSON.stringify(log));
|
|
67
|
+
}
|
|
68
|
+
this.ringBuffer.delete(traceId);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public broadcast(traceId: string, message: string, level: string = 'info') {
|
|
74
|
+
const payload = { timestamp: Date.now(), message, level };
|
|
75
|
+
const ws = this.clients.get(traceId);
|
|
76
|
+
|
|
77
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
78
|
+
// Client is connected, send directly
|
|
79
|
+
ws.send(JSON.stringify(payload));
|
|
80
|
+
} else {
|
|
81
|
+
// Client not connected yet, buffer the log (Anti-Race Condition)
|
|
82
|
+
let buffer = this.ringBuffer.get(traceId);
|
|
83
|
+
if (!buffer) {
|
|
84
|
+
buffer = {
|
|
85
|
+
logs: [],
|
|
86
|
+
timeout: setTimeout(() => {
|
|
87
|
+
// Drop buffer after 5 seconds if client never connects
|
|
88
|
+
this.ringBuffer.delete(traceId);
|
|
89
|
+
}, 5000)
|
|
90
|
+
};
|
|
91
|
+
this.ringBuffer.set(traceId, buffer);
|
|
92
|
+
}
|
|
93
|
+
buffer.logs.push(payload);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public broadcastAll(message: string, level: string = 'info') {
|
|
98
|
+
const payload = { timestamp: Date.now(), message, level };
|
|
99
|
+
const payloadStr = JSON.stringify(payload);
|
|
100
|
+
for (const [_, ws] of this.clients) {
|
|
101
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
102
|
+
ws.send(payloadStr);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Global instance to be initialized in server.ts
|
|
109
|
+
export let wsManager: WebSocketManager | null = null;
|
|
110
|
+
|
|
111
|
+
export function initWebSocket(server: http.Server) {
|
|
112
|
+
wsManager = new WebSocketManager(server);
|
|
113
|
+
return wsManager;
|
|
114
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { intro, text, spinner, isCancel, cancel } from '@clack/prompts';
|
|
1
|
+
import { intro, text, spinner, isCancel, cancel, confirm } from '@clack/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { getPath } from '../config/paths';
|
|
@@ -77,6 +77,45 @@ export async function chatInteractive() {
|
|
|
77
77
|
finalReply = finalReply.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
78
78
|
|
|
79
79
|
console.log(finalReply + '\n');
|
|
80
|
+
|
|
81
|
+
// Check for pending transactions
|
|
82
|
+
try {
|
|
83
|
+
const txRes = await fetch('http://localhost:3000/api/transactions', {
|
|
84
|
+
headers: { 'x-nyxora-token': token }
|
|
85
|
+
});
|
|
86
|
+
if (txRes.ok) {
|
|
87
|
+
const txs = await txRes.json();
|
|
88
|
+
for (const tx of txs) {
|
|
89
|
+
const isApproved = await confirm({
|
|
90
|
+
message: pc.yellow(`Approve Transaction [${tx.type.toUpperCase()}] on ${tx.chainName.toUpperCase()}?`),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (isCancel(isApproved) || !isApproved) {
|
|
94
|
+
await fetch(`http://localhost:3000/api/transactions/${tx.id}/reject`, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'Content-Type': 'application/json', 'x-nyxora-token': token },
|
|
97
|
+
body: JSON.stringify({ nonce: tx.nonce, sessionId: 'cli-chat' })
|
|
98
|
+
});
|
|
99
|
+
console.log(pc.red(`Transaction rejected.\n`));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const appRes = await fetch(`http://localhost:3000/api/transactions/${tx.id}/approve`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json', 'x-nyxora-token': token },
|
|
106
|
+
body: JSON.stringify({ nonce: tx.nonce, sessionId: 'cli-chat' })
|
|
107
|
+
});
|
|
108
|
+
const appData = await appRes.json();
|
|
109
|
+
if (appData.success) {
|
|
110
|
+
console.log(pc.green(`Transaction approved! Processing in background...\n`));
|
|
111
|
+
} else {
|
|
112
|
+
console.log(pc.red(`Failed to approve: ${appData.error}\n`));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
// silently ignore fetch errors for tx polling
|
|
118
|
+
}
|
|
80
119
|
} catch (error) {
|
|
81
120
|
s.stop(pc.red('Connection failed.'));
|
|
82
121
|
console.log(pc.red(`Is the daemon running? (http://localhost:3000)`));
|
|
@@ -18,7 +18,6 @@ import { saveApiKeys } from '../config/parser';
|
|
|
18
18
|
async function main() {
|
|
19
19
|
// 1. Determine configuration directory
|
|
20
20
|
const appDir = getAppDir();
|
|
21
|
-
const isGlobalMode = appDir !== process.cwd();
|
|
22
21
|
|
|
23
22
|
console.log(`================================`);
|
|
24
23
|
console.log(`🤖 Nyxora CLI Agent Booting Up...`);
|
|
@@ -191,9 +190,8 @@ console.log(`================================`);
|
|
|
191
190
|
process.exit(0);
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
// 2. Setup boilerplate files
|
|
193
|
+
// 2. Setup boilerplate files since we enforce global mode
|
|
195
194
|
let isFirstBoot = false;
|
|
196
|
-
if (isGlobalMode) {
|
|
197
195
|
const globalConfigPath = getPath('config.yaml');
|
|
198
196
|
const globalUserMdPath = getPath('user.md');
|
|
199
197
|
const globalIdentityMdPath = getPath('IDENTITY.md');
|
|
@@ -201,7 +199,10 @@ console.log(`================================`);
|
|
|
201
199
|
// Copy default config.yaml
|
|
202
200
|
if (!fs.existsSync(globalConfigPath)) {
|
|
203
201
|
isFirstBoot = true;
|
|
204
|
-
|
|
202
|
+
let exampleConfigPath = path.resolve(__dirname, '../../../config.yaml'); // Dev
|
|
203
|
+
if (!fs.existsSync(exampleConfigPath)) {
|
|
204
|
+
exampleConfigPath = path.resolve(__dirname, '../../../../../config.yaml'); // Compiled
|
|
205
|
+
}
|
|
205
206
|
if (fs.existsSync(exampleConfigPath)) {
|
|
206
207
|
fs.copyFileSync(exampleConfigPath, globalConfigPath);
|
|
207
208
|
} else {
|
|
@@ -209,13 +210,12 @@ console.log(`================================`);
|
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
if (!fs.existsSync(globalUserMdPath)) {
|
|
214
|
+
fs.writeFileSync(globalUserMdPath, 'Write custom instructions, special rules, user profiles, or the persona you want for Nyxora AI in this file.\n');
|
|
215
|
+
}
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
217
|
+
if (!fs.existsSync(globalIdentityMdPath)) {
|
|
218
|
+
fs.writeFileSync(globalIdentityMdPath, 'You are a Web3 AI assistant named Nyxora.\n');
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
if (isFirstBoot) {
|
|
@@ -17,9 +17,11 @@ import rateLimit from 'express-rate-limit';
|
|
|
17
17
|
import http from 'http';
|
|
18
18
|
import jwt from 'jsonwebtoken';
|
|
19
19
|
import crypto from 'crypto';
|
|
20
|
+
import os from 'os';
|
|
20
21
|
import { getPath } from '../config/paths';
|
|
21
22
|
import { validateToken, getSessionToken } from '../utils/state';
|
|
22
23
|
|
|
24
|
+
import { initWebSocket } from './WebSocketManager';
|
|
23
25
|
import fs from 'fs';
|
|
24
26
|
import yaml from 'yaml';
|
|
25
27
|
import { processUserInput, logger } from '../agent/reasoning';
|
|
@@ -29,6 +31,7 @@ import { getPublicClient, SUPPORTED_CHAIN_NAMES, getAddress } from '../web3/conf
|
|
|
29
31
|
import { TOKEN_MAP, ERC20_ABI } from '../web3/utils/tokens';
|
|
30
32
|
import { Tracker } from './tracker';
|
|
31
33
|
import { txManager } from '../agent/transactionManager';
|
|
34
|
+
import multer from 'multer';
|
|
32
35
|
|
|
33
36
|
import { executeTransfer, transferToolDefinition } from '../web3/skills/transfer';
|
|
34
37
|
import { executeSwap, swapTokenToolDefinition } from '../web3/skills/swapToken';
|
|
@@ -68,6 +71,9 @@ import { xManagerToolDefinition } from '../system/skills/xManager';
|
|
|
68
71
|
import { notionWorkspaceToolDefinition } from '../system/skills/notionWorkspace';
|
|
69
72
|
import { audioTranscribeToolDefinition } from '../system/skills/audioTranscribe';
|
|
70
73
|
import { summarizeTextToolDefinition } from '../system/skills/summarizeText';
|
|
74
|
+
import { scheduleTaskDefinition } from '../system/skills/scheduleTask';
|
|
75
|
+
import { cancelTaskDefinition } from '../system/skills/cancelTask';
|
|
76
|
+
import { cronManager } from '../agent/cronManager';
|
|
71
77
|
import { updateSecurityPolicyToolDefinition } from '../system/skills/updateSecurityPolicy';
|
|
72
78
|
import { writeLocalFileToolDefinition } from '../system/skills/writeFile';
|
|
73
79
|
import { generateExcelToolDefinition } from '../system/skills/generateExcel';
|
|
@@ -160,13 +166,11 @@ app.use('/api', (req, res, next) => {
|
|
|
160
166
|
});
|
|
161
167
|
|
|
162
168
|
// Serve Static Dashboard
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
rootDir = nextDir;
|
|
169
|
+
// __dirname is packages/core/dist/gateway (compiled) OR packages/core/src/gateway (dev)
|
|
170
|
+
let dashboardPath = path.join(__dirname, '..', '..', '..', 'dashboard', 'dist'); // Dev
|
|
171
|
+
if (!fs.existsSync(dashboardPath)) {
|
|
172
|
+
dashboardPath = path.join(__dirname, '..', '..', '..', '..', '..', 'packages', 'dashboard', 'dist'); // Compiled
|
|
168
173
|
}
|
|
169
|
-
const dashboardPath = path.join(rootDir, 'packages', 'dashboard', 'dist');
|
|
170
174
|
app.use(express.static(dashboardPath));
|
|
171
175
|
|
|
172
176
|
app.get('/', (req, res) => {
|
|
@@ -181,6 +185,31 @@ app.get('/tos', (req, res) => {
|
|
|
181
185
|
res.send(generateTosHtml());
|
|
182
186
|
});
|
|
183
187
|
|
|
188
|
+
const storage = multer.diskStorage({
|
|
189
|
+
destination: function (req, file, cb) {
|
|
190
|
+
const docsDir = path.join(os.homedir(), '.nyxora', 'docs');
|
|
191
|
+
if (!fs.existsSync(docsDir)) fs.mkdirSync(docsDir, { recursive: true });
|
|
192
|
+
cb(null, docsDir);
|
|
193
|
+
},
|
|
194
|
+
filename: function (req, file, cb) {
|
|
195
|
+
const safeName = file.originalname.replace(/[^a-zA-Z0-9.\-_]/g, '_');
|
|
196
|
+
cb(null, `${Date.now()}-${safeName}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const upload = multer({ storage });
|
|
200
|
+
|
|
201
|
+
app.post('/api/upload', upload.single('file'), (req, res) => {
|
|
202
|
+
try {
|
|
203
|
+
if (!req.file) {
|
|
204
|
+
return res.status(400).json({ error: 'No file uploaded' });
|
|
205
|
+
}
|
|
206
|
+
return res.json({ filePath: req.file.path });
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error('[Upload] Error:', err);
|
|
209
|
+
return res.status(500).json({ error: 'Failed to save file' });
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
184
213
|
app.post('/api/upload-google-credentials', (req, res) => {
|
|
185
214
|
try {
|
|
186
215
|
const credentials = req.body.credentials;
|
|
@@ -380,7 +409,9 @@ const systemSkills = [
|
|
|
380
409
|
xManagerToolDefinition,
|
|
381
410
|
notionWorkspaceToolDefinition,
|
|
382
411
|
audioTranscribeToolDefinition,
|
|
383
|
-
summarizeTextToolDefinition
|
|
412
|
+
summarizeTextToolDefinition,
|
|
413
|
+
scheduleTaskDefinition,
|
|
414
|
+
cancelTaskDefinition
|
|
384
415
|
];
|
|
385
416
|
|
|
386
417
|
app.get('/api/stats', (req, res) => {
|
|
@@ -400,6 +431,13 @@ app.get('/api/logs', (req, res) => {
|
|
|
400
431
|
res.json(Tracker.getLogs());
|
|
401
432
|
});
|
|
402
433
|
|
|
434
|
+
app.get('/api/cron', (req, res) => {
|
|
435
|
+
res.json({
|
|
436
|
+
activeJobs: cronManager.getActiveJobsCount(),
|
|
437
|
+
jobs: cronManager.getJobs()
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
403
441
|
app.get('/api/skills', (req, res) => {
|
|
404
442
|
const skillsWithStatus = allSkills.map(skill => ({
|
|
405
443
|
...skill,
|
|
@@ -873,6 +911,24 @@ function resetIdleTimer() {
|
|
|
873
911
|
}, 3 * 60 * 1000); // 3 minutes idle
|
|
874
912
|
}
|
|
875
913
|
|
|
914
|
+
app.post('/api/v1/trade', async (req, res) => {
|
|
915
|
+
try {
|
|
916
|
+
const { message, session_id } = req.body;
|
|
917
|
+
if (!message) return res.status(400).json({ error: 'Message is required' });
|
|
918
|
+
|
|
919
|
+
const traceId = crypto.randomBytes(8).toString('hex');
|
|
920
|
+
|
|
921
|
+
// Asynchronous background execution
|
|
922
|
+
processUserInput(message, session_id || traceId).catch(err => {
|
|
923
|
+
console.error(`[TradeAPI] Error:`, err);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
res.status(200).json({ status: 'processing', traceId });
|
|
927
|
+
} catch (error: any) {
|
|
928
|
+
res.status(500).json({ error: error.message });
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
876
932
|
app.post('/api/chat', async (req, res) => {
|
|
877
933
|
try {
|
|
878
934
|
const { message, session_id } = req.body;
|
|
@@ -1032,6 +1088,9 @@ export function startServer() {
|
|
|
1032
1088
|
const server = app.listen(PORT, '127.0.0.1', () => {
|
|
1033
1089
|
console.log(`🤖 Nyxora API Server running on port ${PORT}`);
|
|
1034
1090
|
|
|
1091
|
+
// Initialize WebSocket Manager
|
|
1092
|
+
initWebSocket(server);
|
|
1093
|
+
|
|
1035
1094
|
// Start the Telegram bot listener
|
|
1036
1095
|
startTelegramBot();
|
|
1037
1096
|
|
|
@@ -288,7 +288,7 @@ Provider: ${config.llm.provider}`;
|
|
|
288
288
|
{ value: 'gitManager', label: 'Git Operations (Commit/Push/Pull)' },
|
|
289
289
|
{ value: 'updateSecurityPolicy', label: 'Update policy.yaml rules', hint: 'safeguard' },
|
|
290
290
|
{ value: 'browseWeb', label: 'Browse & Scrape Webpages' },
|
|
291
|
-
{ value: 'searchWeb', label: 'Smart Web Search (Tavily/Brave)', hint: '
|
|
291
|
+
{ value: 'searchWeb', label: 'Smart Web Search (Tavily/Brave/DuckDuckGo)', hint: 'Optional API Key' },
|
|
292
292
|
{ value: 'googleWorkspace', label: 'Google Workspace (Gmail, Docs, Sheets, Forms)', hint: 'Requires OAuth' },
|
|
293
293
|
{ value: 'notionWorkspace', label: 'Notion Integration' },
|
|
294
294
|
{ value: 'xManager', label: 'X/Twitter Management' },
|
|
@@ -320,14 +320,17 @@ Provider: ${config.llm.provider}`;
|
|
|
320
320
|
options: [
|
|
321
321
|
{ value: 'tavily', label: 'Tavily Search (Built for AI - 1000 free/mo)' },
|
|
322
322
|
{ value: 'brave', label: 'Brave Search (Privacy focused - 2000 free/mo)' },
|
|
323
|
+
{ value: 'duckduckgo', label: 'DuckDuckGo (Free & Built-in)' },
|
|
323
324
|
],
|
|
324
325
|
});
|
|
325
326
|
if (isCancel(searchProvider)) return process.exit(0);
|
|
326
327
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
if (searchProvider !== 'duckduckgo') {
|
|
329
|
+
searchApiKey = (await password({
|
|
330
|
+
message: `Enter API Key for ${searchProvider} (Get it free at ${searchProvider === 'tavily' ? 'tavily.com' : 'search.brave.com'}):`,
|
|
331
|
+
})) as string;
|
|
332
|
+
if (isCancel(searchApiKey)) return process.exit(0);
|
|
333
|
+
}
|
|
331
334
|
}
|
|
332
335
|
|
|
333
336
|
const setupTelegram = activeChannels.includes('telegram');
|
|
@@ -12,6 +12,9 @@ import { executeApprove, executeAaveSupply, executeVaultDeposit, executeUniv3Min
|
|
|
12
12
|
import { executeRevokeApproval } from '../web3/skills/revokeApprovals';
|
|
13
13
|
import { formatTransactionSuccess, formatTransactionError } from '../utils/formatter';
|
|
14
14
|
import pc from 'picocolors';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import os from 'os';
|
|
15
18
|
|
|
16
19
|
let globalBotInstance: Telegraf | null = null;
|
|
17
20
|
|
|
@@ -178,6 +181,52 @@ export function startTelegramBot() {
|
|
|
178
181
|
}
|
|
179
182
|
});
|
|
180
183
|
|
|
184
|
+
bot.on('document', async (ctx) => {
|
|
185
|
+
const doc = ctx.message.document;
|
|
186
|
+
const caption = ctx.message.caption || '';
|
|
187
|
+
console.log(`[Telegram] Received document from ${ctx.from?.first_name || 'User'}: ${doc.file_name}`);
|
|
188
|
+
|
|
189
|
+
await ctx.sendChatAction('typing');
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const fileLink = await ctx.telegram.getFileLink(doc.file_id);
|
|
193
|
+
const docsDir = path.join(os.homedir(), '.nyxora', 'docs');
|
|
194
|
+
if (!fs.existsSync(docsDir)) fs.mkdirSync(docsDir, { recursive: true });
|
|
195
|
+
|
|
196
|
+
const safeName = (doc.file_name || 'telegram_doc').replace(/[^a-zA-Z0-9.\-_]/g, '_');
|
|
197
|
+
const localFilePath = path.join(docsDir, `${Date.now()}-${safeName}`);
|
|
198
|
+
|
|
199
|
+
const res = await fetch(fileLink.toString());
|
|
200
|
+
const buffer = await res.arrayBuffer();
|
|
201
|
+
fs.writeFileSync(localFilePath, Buffer.from(buffer));
|
|
202
|
+
|
|
203
|
+
const prompt = `Tolong analisis dokumen ini: ${localFilePath}\n\n${caption}`;
|
|
204
|
+
|
|
205
|
+
let progressMsgId: number | null = null;
|
|
206
|
+
const onProgress = async (progressText: string) => {
|
|
207
|
+
try {
|
|
208
|
+
if (!progressMsgId) {
|
|
209
|
+
const sent = await ctx.reply(`<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML' });
|
|
210
|
+
progressMsgId = sent.message_id;
|
|
211
|
+
} else {
|
|
212
|
+
await ctx.telegram.editMessageText(ctx.chat.id, progressMsgId, undefined, `<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML' });
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const response = await processUserInput(prompt, 'user', onProgress, ctx.chat?.id.toString());
|
|
218
|
+
|
|
219
|
+
if (progressMsgId) {
|
|
220
|
+
await ctx.telegram.deleteMessage(ctx.chat.id, progressMsgId).catch(() => {});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML' });
|
|
224
|
+
} catch (error: any) {
|
|
225
|
+
console.error('[Telegram] Error processing document:', error);
|
|
226
|
+
await ctx.reply('❌ Sorry, I failed to download or analyze the document.');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
181
230
|
// Handle callbacks
|
|
182
231
|
bot.action(/^approve_(.+)$/, async (ctx) => {
|
|
183
232
|
const txId = ctx.match[1];
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { wsManager } from './WebSocketManager';
|
|
4
|
+
import { getPath } from '../config/paths';
|
|
5
|
+
|
|
1
6
|
interface Stats {
|
|
2
7
|
cost: number;
|
|
3
8
|
tokens: number;
|
|
@@ -26,6 +31,53 @@ const eventLogs: EventLog[] = [];
|
|
|
26
31
|
const gatewayLogs: GatewayLog[] = [];
|
|
27
32
|
const MAX_LOGS = 100;
|
|
28
33
|
|
|
34
|
+
let trackerFile = '';
|
|
35
|
+
try {
|
|
36
|
+
trackerFile = getPath('tracker.json');
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// Fallback
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadState() {
|
|
42
|
+
if (!trackerFile) return;
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(trackerFile)) {
|
|
45
|
+
const data = JSON.parse(fs.readFileSync(trackerFile, 'utf8'));
|
|
46
|
+
if (data.stats) Object.assign(stats, data.stats);
|
|
47
|
+
if (data.eventLogs && Array.isArray(data.eventLogs)) {
|
|
48
|
+
eventLogs.splice(0, eventLogs.length, ...data.eventLogs);
|
|
49
|
+
}
|
|
50
|
+
if (data.gatewayLogs && Array.isArray(data.gatewayLogs)) {
|
|
51
|
+
gatewayLogs.splice(0, gatewayLogs.length, ...data.gatewayLogs);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let savePending = false;
|
|
58
|
+
function saveState() {
|
|
59
|
+
if (!trackerFile) return;
|
|
60
|
+
if (savePending) return;
|
|
61
|
+
savePending = true;
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
flushState();
|
|
64
|
+
savePending = false;
|
|
65
|
+
}, 1000);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function flushState() {
|
|
69
|
+
if (!trackerFile) return;
|
|
70
|
+
try {
|
|
71
|
+
fs.writeFileSync(trackerFile, JSON.stringify({ stats, eventLogs, gatewayLogs }));
|
|
72
|
+
} catch (e) {}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
process.on('exit', flushState);
|
|
76
|
+
process.on('SIGTERM', () => { flushState(); process.exit(0); });
|
|
77
|
+
process.on('SIGINT', () => { flushState(); process.exit(0); });
|
|
78
|
+
|
|
79
|
+
loadState();
|
|
80
|
+
|
|
29
81
|
function formatTime(): string {
|
|
30
82
|
const now = new Date();
|
|
31
83
|
return now.toTimeString().split(' ')[0]; // Returns HH:MM:SS
|
|
@@ -41,10 +93,12 @@ export const Tracker = {
|
|
|
41
93
|
else if (provider === 'gemini') rate = 0.00001;
|
|
42
94
|
|
|
43
95
|
stats.cost += (amount * rate);
|
|
96
|
+
saveState();
|
|
44
97
|
},
|
|
45
98
|
|
|
46
99
|
addMessage: () => {
|
|
47
100
|
stats.messages += 1;
|
|
101
|
+
saveState();
|
|
48
102
|
},
|
|
49
103
|
|
|
50
104
|
getStats: () => {
|
|
@@ -54,11 +108,18 @@ export const Tracker = {
|
|
|
54
108
|
addEvent: (event: string, meta: any = {}) => {
|
|
55
109
|
eventLogs.unshift({ timestamp: formatTime(), event, meta });
|
|
56
110
|
if (eventLogs.length > MAX_LOGS) eventLogs.pop();
|
|
111
|
+
saveState();
|
|
57
112
|
},
|
|
58
113
|
|
|
59
114
|
addGatewayLog: (message: string, meta?: any) => {
|
|
60
115
|
gatewayLogs.unshift({ timestamp: formatTime(), message, meta });
|
|
61
116
|
if (gatewayLogs.length > MAX_LOGS) gatewayLogs.pop();
|
|
117
|
+
saveState();
|
|
118
|
+
|
|
119
|
+
// Broadcast terminal logs to Dashboard via WebSocket
|
|
120
|
+
if (wsManager) {
|
|
121
|
+
wsManager.broadcastAll(`[${formatTime()}] ${message}`, meta?.level || 'info');
|
|
122
|
+
}
|
|
62
123
|
},
|
|
63
124
|
|
|
64
125
|
getLogs: () => {
|
|
@@ -204,7 +204,7 @@ export class Logger {
|
|
|
204
204
|
SELECT * FROM (
|
|
205
205
|
SELECT role, content, name, tool_call_id, tool_calls, session_id, id
|
|
206
206
|
FROM messages
|
|
207
|
-
WHERE session_id IS NULL
|
|
207
|
+
WHERE session_id IS NULL
|
|
208
208
|
ORDER BY id DESC LIMIT 40
|
|
209
209
|
) ORDER BY id ASC
|
|
210
210
|
`).all();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cronManager } from '../../agent/cronManager';
|
|
2
|
+
|
|
3
|
+
export const cancelTaskDefinition = {
|
|
4
|
+
type: "function",
|
|
5
|
+
function: {
|
|
6
|
+
name: "cancel_task",
|
|
7
|
+
description: "Cancel a scheduled background task (cron job). Use this when the user asks you to stop monitoring or cancel a previously scheduled task.",
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
jobId: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "The unique Job ID of the scheduled task to cancel."
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
required: ["jobId"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function executeCancelTask(args: any): Promise<string> {
|
|
22
|
+
const { jobId } = args;
|
|
23
|
+
|
|
24
|
+
if (!jobId) {
|
|
25
|
+
return "Error: Missing required parameter jobId.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const success = cronManager.removeJob(jobId);
|
|
30
|
+
if (success) {
|
|
31
|
+
return `Success! I have cancelled the scheduled background task with Job ID: ${jobId}.`;
|
|
32
|
+
} else {
|
|
33
|
+
return `Failed to cancel task. No active task found with Job ID: ${jobId}. You can check active jobs by asking me what tasks are currently running.`;
|
|
34
|
+
}
|
|
35
|
+
} catch (error: any) {
|
|
36
|
+
return `Failed to cancel task: ${error.message}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -4,6 +4,13 @@ import path from 'path';
|
|
|
4
4
|
export function editLocalFile(filePath: string, searchString: string, replacementString: string): string {
|
|
5
5
|
try {
|
|
6
6
|
const absolutePath = path.resolve(filePath);
|
|
7
|
+
|
|
8
|
+
// Security Firewall: Block modification of core configuration files
|
|
9
|
+
const basename = path.basename(absolutePath);
|
|
10
|
+
if (['config.yaml', 'rpc_key.yaml', 'policy.yaml'].includes(basename)) {
|
|
11
|
+
return `Error: Access Denied. You are strictly forbidden from modifying core configuration files directly. If you need to update your agent name, use the update_identity tool instead.`;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
if (!fs.existsSync(absolutePath)) {
|
|
8
15
|
return `Error: File not found at ${absolutePath}`;
|
|
9
16
|
}
|