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
|
@@ -9,6 +9,8 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
11
|
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const http_1 = __importDefault(require("http"));
|
|
13
|
+
const msgpackr_1 = require("msgpackr");
|
|
12
14
|
function getInternalToken() {
|
|
13
15
|
try {
|
|
14
16
|
const tokenPath = path_1.default.join(os_1.default.homedir(), '.nyxora', 'auth', 'runtime.token');
|
|
@@ -21,19 +23,43 @@ function getInternalToken() {
|
|
|
21
23
|
}
|
|
22
24
|
async function getAddress() {
|
|
23
25
|
const token = getInternalToken();
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const POLICY_SOCKET = '/tmp/nyxora-policy.sock';
|
|
28
|
+
const options = {
|
|
29
|
+
socketPath: fs_1.default.existsSync(POLICY_SOCKET) ? POLICY_SOCKET : undefined,
|
|
30
|
+
host: fs_1.default.existsSync(POLICY_SOCKET) ? undefined : '127.0.0.1',
|
|
31
|
+
port: fs_1.default.existsSync(POLICY_SOCKET) ? undefined : (process.env.POLICY_PORT || 3001),
|
|
32
|
+
path: '/address',
|
|
33
|
+
method: 'GET',
|
|
34
|
+
headers: {
|
|
35
|
+
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const req = http_1.default.request(options, (res) => {
|
|
39
|
+
let data = '';
|
|
40
|
+
res.on('data', chunk => data += chunk);
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
try {
|
|
43
|
+
if (res.statusCode !== 200) {
|
|
44
|
+
reject(new Error(data));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const parsed = JSON.parse(data);
|
|
48
|
+
resolve(parsed.address);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
reject(new Error(`Failed to get address from vault: ${e.message}`));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
28
54
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
55
|
+
req.on('error', (error) => {
|
|
56
|
+
reject(new Error(`Failed to get address from vault: ${error.message}`));
|
|
57
|
+
});
|
|
58
|
+
req.setTimeout(30000, () => {
|
|
59
|
+
req.destroy(new Error('Timeout'));
|
|
60
|
+
});
|
|
61
|
+
req.end();
|
|
62
|
+
});
|
|
37
63
|
}
|
|
38
64
|
async function submitTransaction(txPayload) {
|
|
39
65
|
const token = getInternalToken();
|
|
@@ -45,25 +71,49 @@ async function submitTransaction(txPayload) {
|
|
|
45
71
|
const secret = getInternalToken() || '';
|
|
46
72
|
txPayload.internalSignature = crypto_1.default.createHmac('sha256', secret).update(txPayload.chainName + amountWei).digest('hex');
|
|
47
73
|
}
|
|
48
|
-
|
|
49
|
-
const
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const POLICY_SOCKET = '/tmp/nyxora-policy.sock';
|
|
76
|
+
const payloadBuffer = (0, msgpackr_1.pack)(txPayload);
|
|
77
|
+
const options = {
|
|
78
|
+
socketPath: fs_1.default.existsSync(POLICY_SOCKET) ? POLICY_SOCKET : undefined,
|
|
79
|
+
host: fs_1.default.existsSync(POLICY_SOCKET) ? undefined : '127.0.0.1',
|
|
80
|
+
port: fs_1.default.existsSync(POLICY_SOCKET) ? undefined : (process.env.POLICY_PORT || 3001),
|
|
81
|
+
path: '/request-tx',
|
|
50
82
|
method: 'POST',
|
|
51
83
|
headers: {
|
|
52
|
-
'Content-Type': 'application/
|
|
84
|
+
'Content-Type': 'application/msgpack',
|
|
85
|
+
'Content-Length': payloadBuffer.length,
|
|
53
86
|
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const req = http_1.default.request(options, (res) => {
|
|
90
|
+
let data = '';
|
|
91
|
+
res.on('data', chunk => data += chunk);
|
|
92
|
+
res.on('end', () => {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(data);
|
|
95
|
+
if (res.statusCode !== 200) {
|
|
96
|
+
reject(new Error(parsed.error || 'Failed to submit transaction'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (parsed.status === 'pending') {
|
|
100
|
+
resolve(`Pending (ID: ${parsed.txId})`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
resolve(parsed.signedHash || parsed.hash || parsed.txHash || parsed.status || 'Transaction executed successfully (No Hash returned)');
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
reject(new Error(`Failed to parse vault response: ${e.message}`));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
57
109
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new Error(`Transaction submission failed: ${error.message}`);
|
|
68
|
-
}
|
|
110
|
+
req.on('error', (error) => {
|
|
111
|
+
reject(new Error(`Transaction submission failed: ${error.message}`));
|
|
112
|
+
});
|
|
113
|
+
req.setTimeout(30000, () => {
|
|
114
|
+
req.destroy(new Error('Timeout'));
|
|
115
|
+
});
|
|
116
|
+
req.write(payloadBuffer);
|
|
117
|
+
req.end();
|
|
118
|
+
});
|
|
69
119
|
}
|
|
@@ -15,6 +15,7 @@ const zod_1 = require("zod");
|
|
|
15
15
|
const path_1 = __importDefault(require("path"));
|
|
16
16
|
const crypto_1 = __importDefault(require("crypto"));
|
|
17
17
|
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const msgpackr_1 = require("msgpackr");
|
|
18
19
|
process.on('unhandledRejection', (reason, promise) => {
|
|
19
20
|
console.error('[Anti-Crash] Unhandled Rejection at:', promise, 'reason:', reason);
|
|
20
21
|
});
|
|
@@ -35,6 +36,25 @@ catch (e) {
|
|
|
35
36
|
const SIGNER_SOCKET = process.env.SIGNER_SOCKET_PATH || '/tmp/nyxora-signer.sock';
|
|
36
37
|
const app = (0, express_1.default)();
|
|
37
38
|
app.use(express_1.default.json());
|
|
39
|
+
// MessagePack Parser Middleware for Hyper-Optimized IPC
|
|
40
|
+
app.use((req, res, next) => {
|
|
41
|
+
if (req.headers['content-type'] === 'application/msgpack') {
|
|
42
|
+
let chunks = [];
|
|
43
|
+
req.on('data', chunk => chunks.push(chunk));
|
|
44
|
+
req.on('end', () => {
|
|
45
|
+
try {
|
|
46
|
+
req.body = (0, msgpackr_1.unpack)(Buffer.concat(chunks));
|
|
47
|
+
next();
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
res.status(400).json({ error: 'Invalid MessagePack payload' });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
38
58
|
const TxRequestSchema = zod_1.z.object({
|
|
39
59
|
type: zod_1.z.enum(['transfer', 'swap', 'bridge', 'mint', 'custom']),
|
|
40
60
|
chainName: zod_1.z.string(),
|
|
@@ -237,7 +257,8 @@ app.post('/approve-tx/:id', (req, res) => {
|
|
|
237
257
|
signerReq.write(requestPayload);
|
|
238
258
|
signerReq.end();
|
|
239
259
|
});
|
|
240
|
-
const
|
|
260
|
+
const POLICY_SOCKET = '/tmp/nyxora-policy.sock';
|
|
261
|
+
const server = app.listen(Number(PORT), '127.0.0.1', () => {
|
|
241
262
|
console.log(`[Policy Engine] Listening on 127.0.0.1:${PORT} (Secured Local Loopback)`);
|
|
242
263
|
});
|
|
243
264
|
server.on('error', (e) => {
|
|
@@ -250,3 +271,10 @@ server.on('error', (e) => {
|
|
|
250
271
|
process.exit(1);
|
|
251
272
|
}
|
|
252
273
|
});
|
|
274
|
+
// Start UDS Server for Hyper-Optimized IPC
|
|
275
|
+
const udsServer = http_1.default.createServer(app);
|
|
276
|
+
if (fs_1.default.existsSync(POLICY_SOCKET))
|
|
277
|
+
fs_1.default.unlinkSync(POLICY_SOCKET);
|
|
278
|
+
udsServer.listen(POLICY_SOCKET, () => {
|
|
279
|
+
console.log(`[Policy Engine] Listening on UDS ${POLICY_SOCKET} (Hyper-Optimized IPC)`);
|
|
280
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyxora",
|
|
3
|
-
"version": "26.6.
|
|
3
|
+
"version": "26.6.23",
|
|
4
4
|
"description": "Your Personal Web3 Assistant",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web3",
|
|
@@ -60,11 +60,14 @@
|
|
|
60
60
|
"dotenv": "^17.4.2",
|
|
61
61
|
"duck-duck-scrape": "^2.2.7",
|
|
62
62
|
"express": "^5.2.1",
|
|
63
|
+
"msgpackr": "^2.0.4",
|
|
63
64
|
"express-rate-limit": "^7.5.0",
|
|
64
65
|
"helmet": "^8.0.0",
|
|
65
66
|
"isolated-vm": "^6.1.2",
|
|
66
67
|
"jsonwebtoken": "^9.0.2",
|
|
67
68
|
"mammoth": "^1.6.0",
|
|
69
|
+
"multer": "^2.2.0",
|
|
70
|
+
"node-cron": "^4.4.1",
|
|
68
71
|
"open": "^11.0.0",
|
|
69
72
|
"openai": "^6.39.0",
|
|
70
73
|
"pdf-parse": "^2.4.5",
|
|
@@ -77,6 +80,7 @@
|
|
|
77
80
|
"typescript": "^6.0.3",
|
|
78
81
|
"viem": "^2.51.0",
|
|
79
82
|
"write-excel-file": "^4.1.1",
|
|
83
|
+
"ws": "^8.21.0",
|
|
80
84
|
"yaml": "^2.9.0",
|
|
81
85
|
"zod": "^3.23.8"
|
|
82
86
|
},
|
|
@@ -96,7 +100,9 @@
|
|
|
96
100
|
},
|
|
97
101
|
"devDependencies": {
|
|
98
102
|
"@types/jsonwebtoken": "^9.0.5",
|
|
103
|
+
"@types/ws": "^8.5.10",
|
|
99
104
|
"@types/node": "^25.9.1",
|
|
105
|
+
"@types/node-cron": "^3.0.11",
|
|
100
106
|
"concurrently": "^10.0.3",
|
|
101
107
|
"oxc-minify": "^0.135.0",
|
|
102
108
|
"vitepress": "^2.0.0-alpha.17"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyxora-agent-core",
|
|
3
|
-
"version": "26.6.
|
|
3
|
+
"version": "26.6.23",
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "src/gateway/server.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"express-rate-limit": "^7.5.0",
|
|
15
15
|
"helmet": "^8.0.0",
|
|
16
16
|
"mammoth": "^1.12.0",
|
|
17
|
+
"msgpackr": "^2.0.4",
|
|
18
|
+
"multer": "^2.2.0",
|
|
19
|
+
"node-cron": "^4.4.1",
|
|
17
20
|
"open": "^11.0.0",
|
|
18
21
|
"openai": "^6.39.0",
|
|
19
22
|
"pdf-parse": "^2.4.5",
|
|
@@ -22,12 +25,16 @@
|
|
|
22
25
|
"telegraf": "^4.16.3",
|
|
23
26
|
"twitter-api-v2": "^1.29.0",
|
|
24
27
|
"write-excel-file": "^4.1.1",
|
|
28
|
+
"ws": "^8.21.0",
|
|
25
29
|
"yaml": "^2.9.0",
|
|
26
30
|
"zod": "^3.25.76"
|
|
27
31
|
},
|
|
28
32
|
"devDependencies": {
|
|
33
|
+
"@types/multer": "^2.1.0",
|
|
29
34
|
"@types/node": "^25.9.2",
|
|
35
|
+
"@types/node-cron": "^3.0.11",
|
|
30
36
|
"@types/pdf-parse": "^1.1.5",
|
|
37
|
+
"@types/ws": "^8.18.1",
|
|
31
38
|
"vitest": "^4.1.8"
|
|
32
39
|
},
|
|
33
40
|
"scripts": {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as cron from 'node-cron';
|
|
2
|
+
import { loadConfig } from '../config/parser';
|
|
3
|
+
import { sendPushNotification } from '../gateway/telegram';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
|
|
7
|
+
export interface CronJob {
|
|
8
|
+
id: string;
|
|
9
|
+
expression: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
task: cron.ScheduledTask;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class CronManager {
|
|
16
|
+
private jobs: Map<string, CronJob> = new Map();
|
|
17
|
+
|
|
18
|
+
public addJob(expression: string, prompt: string): string {
|
|
19
|
+
const id = randomUUID();
|
|
20
|
+
|
|
21
|
+
// Validate expression
|
|
22
|
+
if (!cron.validate(expression)) {
|
|
23
|
+
throw new Error(`Invalid cron expression: ${expression}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const task = cron.schedule(expression, async () => {
|
|
27
|
+
console.log(pc.cyan(`[Cron] Executing job ${id}: "${prompt}"`));
|
|
28
|
+
try {
|
|
29
|
+
// Dynamically import processUserInput to avoid circular dependencies
|
|
30
|
+
const { processUserInput } = await import('./reasoning');
|
|
31
|
+
|
|
32
|
+
// Execute the prompt as a background system task
|
|
33
|
+
const response = await processUserInput(prompt, 'system', undefined, `cron-${id}`);
|
|
34
|
+
|
|
35
|
+
// Push notification to Telegram if configured
|
|
36
|
+
const config = loadConfig();
|
|
37
|
+
if (config.integrations?.telegram?.enabled && config.integrations?.telegram?.authorized_chat_id) {
|
|
38
|
+
const message = `🤖 *AI Scheduled Report*\n\n${response}`;
|
|
39
|
+
await sendPushNotification(config.integrations.telegram.authorized_chat_id, message);
|
|
40
|
+
}
|
|
41
|
+
} catch (err: any) {
|
|
42
|
+
console.error(pc.red(`[Cron] Failed to execute job ${id}:`), err);
|
|
43
|
+
const config = loadConfig();
|
|
44
|
+
if (config.integrations?.telegram?.enabled && config.integrations?.telegram?.authorized_chat_id) {
|
|
45
|
+
await sendPushNotification(config.integrations.telegram.authorized_chat_id, `⚠️ *Cron Job Error*\n\nPrompt: ${prompt}\nError: ${err.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.jobs.set(id, {
|
|
51
|
+
id,
|
|
52
|
+
expression,
|
|
53
|
+
prompt,
|
|
54
|
+
task,
|
|
55
|
+
createdAt: Date.now()
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(pc.green(`[Cron] Scheduled new job ${id} with expression '${expression}'`));
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public removeJob(id: string): boolean {
|
|
63
|
+
const job = this.jobs.get(id);
|
|
64
|
+
if (job) {
|
|
65
|
+
job.task.stop();
|
|
66
|
+
this.jobs.delete(id);
|
|
67
|
+
console.log(pc.yellow(`[Cron] Removed job ${id}`));
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public getJobs(): Omit<CronJob, 'task'>[] {
|
|
74
|
+
return Array.from(this.jobs.values()).map(job => ({
|
|
75
|
+
id: job.id,
|
|
76
|
+
expression: job.expression,
|
|
77
|
+
prompt: job.prompt,
|
|
78
|
+
createdAt: job.createdAt
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public getActiveJobsCount(): number {
|
|
83
|
+
return this.jobs.size;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const cronManager = new CronManager();
|
|
@@ -29,6 +29,7 @@ import { getTxHistoryToolDefinition, getTxHistory } from '../web3/skills/getTxHi
|
|
|
29
29
|
import { createLimitOrderToolDefinition, createLimitOrder } from '../web3/skills/createLimitOrder';
|
|
30
30
|
|
|
31
31
|
import { updateProfileToolDefinition, updateProfile } from './updateProfile';
|
|
32
|
+
import { updateIdentityToolDefinition, updateIdentity } from './updateIdentity';
|
|
32
33
|
import { updateSecurityPolicyToolDefinition, updateSecurityPolicy } from '../system/skills/updateSecurityPolicy';
|
|
33
34
|
import { analyzeDocumentToolDefinition, analyzeDocument } from '../system/skills/analyzeDocument';
|
|
34
35
|
import { readLocalFileToolDefinition, readLocalFile } from '../system/skills/readFile';
|
|
@@ -44,6 +45,8 @@ import { xManagerToolDefinition, manageTwitter } from '../system/skills/xManager
|
|
|
44
45
|
import { notionWorkspaceToolDefinition, manageNotion } from '../system/skills/notionWorkspace';
|
|
45
46
|
import { audioTranscribeToolDefinition, transcribeAudio } from '../system/skills/audioTranscribe';
|
|
46
47
|
import { summarizeTextToolDefinition, summarizeText } from '../system/skills/summarizeText';
|
|
48
|
+
import { scheduleTaskDefinition, executeScheduleTask } from '../system/skills/scheduleTask';
|
|
49
|
+
import { cancelTaskDefinition, executeCancelTask } from '../system/skills/cancelTask';
|
|
47
50
|
import {
|
|
48
51
|
readGmailInbox,
|
|
49
52
|
listCalendarEvents,
|
|
@@ -167,35 +170,64 @@ CRITICAL RULE 11: ADAPTIVE RESPONSE RULE. You must process Web3 data (portfolio,
|
|
|
167
170
|
CRITICAL RULE 13: WALLET CONTEXT CACHING. Portfolio data in chat history is potentially stale. Do not use cached data for transactional planning; refresh the balance via tools first.
|
|
168
171
|
CRITICAL RULE 14: TRANSACTION EXECUTION. For ALL state-changing transactions (swap, bridge, transfer, stake), do NOT ask for verbal confirmation. Execute the tool IMMEDIATELY. The tool itself will trigger a secure popup in the user's dashboard UI for final approval.
|
|
169
172
|
CRITICAL RULE 17: MINIMIZE UNNECESSARY TOOL CALLS. Do not call tools if the answer exists in recent verified context and freshness is not strictly required. Use history to save latency.
|
|
173
|
+
CRITICAL RULE 19: GET_PRICE USAGE. Use get_price ONLY when the user explicitly asks for a simple price check (e.g. 'harga', 'price'). Do NOT use this for 'analysis', 'market analysis', or 'analisis pasar'.
|
|
170
174
|
|
|
171
175
|
[ANTI-HALLUCINATION PROTOCOL]
|
|
172
176
|
CRITICAL RULE 6: NETWORK SAFETY VALIDATION. If a request implies cross-chain or mainnet/testnet mixing, or the token symbol is ambiguous (USDC vs USDC.e), YOU MUST NOT GUESS. Ask for confirmation.
|
|
173
177
|
CRITICAL RULE 7: TOOL CONFIDENCE & HALUCINATION PREVENTION. NEVER fabricate blockchain data. If a tool fails or data is missing, state it explicitly. Do not estimate balances, prices, APY, or gas.
|
|
178
|
+
CRITICAL RULE 18: AMOUNT PRECISION. When displaying crypto amounts, use exactly 6 decimal places for precision, or round to 2 decimals only if the value is significantly large (>$10,000). Never abbreviate unless the number is >$1,000,000.
|
|
174
179
|
CRITICAL RULE 9: DEFI CONFIGURATION FALLBACK. If a tool fails due to Rate Limits, Unauthorized, or Missing API Keys, instruct the user to visit the "DeFi Configuration 🔑" menu in the dashboard.
|
|
175
180
|
CRITICAL RULE 12: SMART SLIPPAGE AWARENESS. For low-liquidity assets, warn the user that default slippage might not be enough. NEVER invent specific slippage percentage numbers.
|
|
176
181
|
CRITICAL RULE 16: CAPABILITY HONESTY. NEVER claim a capability not available through installed tools. If asked for an unsupported action, state honestly that the skill is missing.
|
|
177
|
-
CRITICAL RULE 19: MARKET CONFIDENCE SCORE. When analyzing market data, token security, or preparing trades, you MUST explicitly declare a 'Confidence Score (0-100%)' INSIDE your <think> block. If your score is below 40%, you must firmly WARN the user and advise against the trade in your final response
|
|
182
|
+
CRITICAL RULE 19: MARKET CONFIDENCE SCORE. When analyzing market data, token security, or preparing trades, you MUST explicitly declare a 'Confidence Score (0-100%)' INSIDE your <think> block. If your score is below 40%, you must firmly WARN the user and advise against the trade in your final response.
|
|
183
|
+
CRITICAL RULE 20: CRON JOBS VS LIMIT ORDERS. STRICT RULE: Do NOT use schedule_task for price-based trading triggers or buying/selling at a specific price level. Use create_limit_order. STRICT RULE: Do NOT use create_limit_order for time-based recurring tasks (e.g. 'every 5 minutes', 'every Monday'). Use schedule_task.
|
|
184
|
+
CRITICAL RULE 21: CONFIGURATION SECURITY. You are STRICTLY FORBIDDEN from modifying config.yaml, rpc_key.yaml, or policy.yaml using OS skills or terminal commands (like sed, echo, nano). If you need to change your name, use the update_identity tool.`;
|
|
178
185
|
|
|
179
|
-
|
|
186
|
+
const identityMdPath = getPath('IDENTITY.md');
|
|
187
|
+
const userMdPath = getPath('user.md');
|
|
188
|
+
|
|
189
|
+
let isFirstTime = false;
|
|
180
190
|
try {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
const identityContent = fs.existsSync(identityMdPath) ? fs.readFileSync(identityMdPath, 'utf8').trim() : '';
|
|
192
|
+
const userContent = fs.existsSync(userMdPath) ? fs.readFileSync(userMdPath, 'utf8').trim() : '';
|
|
193
|
+
|
|
194
|
+
// Check if files are empty or contain the default installation text
|
|
195
|
+
const isIdentityDefault = !identityContent || identityContent.includes('You are a Web3 AI assistant named Nyxora.');
|
|
196
|
+
const isUserDefault = !userContent || userContent.includes('Write custom instructions, special rules, user profiles');
|
|
197
|
+
|
|
198
|
+
isFirstTime = isIdentityDefault && isUserDefault;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
isFirstTime = true;
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
if (isFirstTime) {
|
|
204
|
+
basePrompt += `\n\n[ONBOARDING MODE]
|
|
205
|
+
This is your VERY FIRST interaction with the user. You MUST warmly welcome them to Nyxora and ask for 4 things to initialize your setup:
|
|
206
|
+
1. Their Name
|
|
207
|
+
2. What they want to name YOU (the AI Agent)
|
|
208
|
+
3. Their Hobbies or Job (so you can tailor your conversation context)
|
|
209
|
+
4. Your Persona/Character (e.g., professional, sarcastic, JARVIS, anime waifu)
|
|
210
|
+
Do NOT perform any web3 tasks or generic answers until they provide all 4 details. Once they answer, use 'update_profile' to save their name and hobbies/job to user.md, and use 'update_identity' (making sure to provide the 'agentName' parameter!) to save your new name and persona to IDENTITY.md.`;
|
|
211
|
+
} else {
|
|
212
|
+
// Read IDENTITY.md for core AI persona
|
|
213
|
+
try {
|
|
214
|
+
if (fs.existsSync(identityMdPath)) {
|
|
215
|
+
const identityInstructions = fs.readFileSync(identityMdPath, 'utf8');
|
|
216
|
+
basePrompt += `\n\n--- CORE IDENTITY & PERSONA ---\n${identityInstructions}`;
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Failed to read IDENTITY.md:', error);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Read user.md for custom instructions
|
|
223
|
+
try {
|
|
224
|
+
if (fs.existsSync(userMdPath)) {
|
|
225
|
+
const customInstructions = fs.readFileSync(userMdPath, 'utf8');
|
|
226
|
+
basePrompt += `\n\n--- CUSTOM USER INSTRUCTIONS ---\n${customInstructions}`;
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('Failed to read user.md:', error);
|
|
196
230
|
}
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error('Failed to read user.md:', error);
|
|
199
231
|
}
|
|
200
232
|
|
|
201
233
|
// Read policy.yaml for NLP security constraints
|
|
@@ -254,7 +286,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
254
286
|
const history = logger.getHistory(sessionId);
|
|
255
287
|
|
|
256
288
|
// Format messages for OpenAI
|
|
257
|
-
|
|
289
|
+
let messages: any[] = [
|
|
258
290
|
{ role: 'system', content: getSystemPrompt() },
|
|
259
291
|
...history
|
|
260
292
|
.filter(m => !(m.role === 'tool' && !m.tool_call_id))
|
|
@@ -269,6 +301,11 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
269
301
|
})
|
|
270
302
|
];
|
|
271
303
|
|
|
304
|
+
// Remove orphaned tool responses (truncated by the 40-message limit) at the start of the history window
|
|
305
|
+
while (messages.length > 1 && messages[1].role === 'tool') {
|
|
306
|
+
messages.splice(1, 1);
|
|
307
|
+
}
|
|
308
|
+
|
|
272
309
|
try {
|
|
273
310
|
const lowerInput = input.toLowerCase();
|
|
274
311
|
const hasWeb3Keyword = /swap|transfer|price|token|crypto|bridge|wallet|balance|portfolio|buy|sell|send|receive|address|market|limit|mint|nft/i.test(lowerInput);
|
|
@@ -299,7 +336,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
299
336
|
createLimitOrderToolDefinition
|
|
300
337
|
);
|
|
301
338
|
}
|
|
302
|
-
const SYSTEM_TOOLS = [updateProfileToolDefinition, updateSecurityPolicyToolDefinition, analyzeDocumentToolDefinition, readLocalFileToolDefinition, writeLocalFileToolDefinition, generateExcelToolDefinition, runTerminalCommandToolDefinition, browseWebsiteToolDefinition, searchWebToolDefinition, editLocalFileToolDefinition, gitManagerToolDefinition, xManagerToolDefinition, notionWorkspaceToolDefinition, audioTranscribeToolDefinition, summarizeTextToolDefinition];
|
|
339
|
+
const SYSTEM_TOOLS = [updateProfileToolDefinition, updateIdentityToolDefinition, updateSecurityPolicyToolDefinition, analyzeDocumentToolDefinition, readLocalFileToolDefinition, writeLocalFileToolDefinition, generateExcelToolDefinition, runTerminalCommandToolDefinition, browseWebsiteToolDefinition, searchWebToolDefinition, editLocalFileToolDefinition, gitManagerToolDefinition, xManagerToolDefinition, notionWorkspaceToolDefinition, audioTranscribeToolDefinition, summarizeTextToolDefinition, scheduleTaskDefinition, cancelTaskDefinition];
|
|
303
340
|
const GOOGLE_TOOLS = [readGmailInboxToolDefinition, listCalendarEventsToolDefinition, appendRowToSheetsToolDefinition, readGoogleDocsToolDefinition, readGoogleFormResponsesToolDefinition];
|
|
304
341
|
|
|
305
342
|
let activeTools: any[] = [];
|
|
@@ -499,6 +536,10 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
499
536
|
result = updateProfile(args.content, args.mode);
|
|
500
537
|
break;
|
|
501
538
|
}
|
|
539
|
+
case 'update_identity': {
|
|
540
|
+
result = updateIdentity(args.content, args.mode);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
502
543
|
case 'update_security_policy': {
|
|
503
544
|
result = await updateSecurityPolicy(args.policy, args.action || 'add');
|
|
504
545
|
break;
|
|
@@ -555,6 +596,14 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
555
596
|
result = await searchWeb(args.query, args.depth);
|
|
556
597
|
break;
|
|
557
598
|
}
|
|
599
|
+
case 'schedule_task': {
|
|
600
|
+
result = await executeScheduleTask(args);
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
case 'cancel_task': {
|
|
604
|
+
result = await executeCancelTask(args);
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
558
607
|
|
|
559
608
|
case 'read_gmail_inbox': {
|
|
560
609
|
result = await readGmailInbox(args.maxResults);
|
|
@@ -642,12 +691,33 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
642
691
|
}
|
|
643
692
|
Tracker.addEvent('llm.final_response', { provider: config.llm.provider });
|
|
644
693
|
|
|
645
|
-
|
|
694
|
+
let finalContent = secondResponse.choices[0].message.content || "";
|
|
695
|
+
|
|
696
|
+
// Clean up orphaned <think> blocks that forgot to output </think>
|
|
697
|
+
finalContent = finalContent.replace(/<thought>[\s\S]*?<\/thought>\n?/gi, '');
|
|
698
|
+
finalContent = finalContent.replace(/<think>[\s\S]*?<\/think>\n?/gi, '');
|
|
699
|
+
if (finalContent.includes('<think>')) {
|
|
700
|
+
finalContent = finalContent.replace(/<think>[\s\S]*?\n\n/i, '');
|
|
701
|
+
finalContent = finalContent.replace(/<think>[\s\S]*$/i, '');
|
|
702
|
+
}
|
|
703
|
+
finalContent = finalContent.trim();
|
|
704
|
+
|
|
646
705
|
logger.addEntry({ role: 'assistant', content: finalContent }, sessionId);
|
|
647
706
|
return finalContent;
|
|
648
707
|
}
|
|
649
708
|
|
|
650
|
-
|
|
709
|
+
let finalContent = responseMessage.content || "No response generated.";
|
|
710
|
+
|
|
711
|
+
// Clean up orphaned <think> blocks that forgot to output </think>
|
|
712
|
+
finalContent = finalContent.replace(/<thought>[\s\S]*?<\/thought>\n?/gi, '');
|
|
713
|
+
finalContent = finalContent.replace(/<think>[\s\S]*?<\/think>\n?/gi, '');
|
|
714
|
+
if (finalContent.includes('<think>')) {
|
|
715
|
+
finalContent = finalContent.replace(/<think>[\s\S]*?\n\n/i, '');
|
|
716
|
+
finalContent = finalContent.replace(/<think>[\s\S]*$/i, '');
|
|
717
|
+
}
|
|
718
|
+
finalContent = finalContent.trim();
|
|
719
|
+
|
|
720
|
+
return finalContent;
|
|
651
721
|
} catch (error: any) {
|
|
652
722
|
console.error("LLM Error:", error);
|
|
653
723
|
const errorMsg = '⚠️ All models are temporarily rate-limited. Please try again in a few minutes.';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { getPath } from '../config/paths';
|
|
4
5
|
|
|
5
6
|
export type TransactionType = 'transfer' | 'swap' | 'bridge' | 'mint' | 'custom' | 'approve' | 'revokeApproval' | 'aaveSupply' | 'vaultDeposit' | 'univ3Mint' | 'limit_order';
|
|
6
7
|
|
|
@@ -35,7 +36,7 @@ class TransactionManager {
|
|
|
35
36
|
private dbPath: string;
|
|
36
37
|
|
|
37
38
|
constructor() {
|
|
38
|
-
this.dbPath =
|
|
39
|
+
this.dbPath = getPath('.nyxora_withdrawals.json');
|
|
39
40
|
this.loadWithdrawals();
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { getPath } from '../config/paths';
|
|
3
|
+
import { loadConfig, saveConfig } from '../config/parser';
|
|
4
|
+
|
|
5
|
+
export function updateIdentity(content: string, mode: 'append' | 'replace', agentName?: string): string {
|
|
6
|
+
try {
|
|
7
|
+
const identityMdPath = getPath('IDENTITY.md');
|
|
8
|
+
|
|
9
|
+
if (mode === 'replace') {
|
|
10
|
+
fs.writeFileSync(identityMdPath, content, 'utf8');
|
|
11
|
+
|
|
12
|
+
let msg = "Identity replaced successfully. New IDENTITY.md has been saved.";
|
|
13
|
+
if (agentName) {
|
|
14
|
+
const config = loadConfig();
|
|
15
|
+
config.agent.name = agentName;
|
|
16
|
+
saveConfig(config);
|
|
17
|
+
msg += ` Agent Name updated to '${agentName}' in config.`;
|
|
18
|
+
}
|
|
19
|
+
return msg;
|
|
20
|
+
} else {
|
|
21
|
+
let existingContent = "";
|
|
22
|
+
if (fs.existsSync(identityMdPath)) {
|
|
23
|
+
existingContent = fs.readFileSync(identityMdPath, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const newContent = existingContent + "\n" + content;
|
|
27
|
+
fs.writeFileSync(identityMdPath, newContent, 'utf8');
|
|
28
|
+
|
|
29
|
+
let msg = "Identity appended successfully. New instructions added to IDENTITY.md.";
|
|
30
|
+
if (agentName) {
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
config.agent.name = agentName;
|
|
33
|
+
saveConfig(config);
|
|
34
|
+
msg += ` Agent Name updated to '${agentName}' in config.`;
|
|
35
|
+
}
|
|
36
|
+
return msg;
|
|
37
|
+
}
|
|
38
|
+
} catch (error: any) {
|
|
39
|
+
return `Failed to update identity: ${error.message}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const updateIdentityToolDefinition = {
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: "update_identity",
|
|
47
|
+
description: "Updates or rewrites the IDENTITY.md file. Use this when the user sets or changes your AI name, persona, or core character instructions.",
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
content: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "The content to write or append to IDENTITY.md",
|
|
54
|
+
},
|
|
55
|
+
mode: {
|
|
56
|
+
type: "string",
|
|
57
|
+
enum: ["append", "replace"],
|
|
58
|
+
description: "Whether to append the content to the existing file or replace the entire file.",
|
|
59
|
+
},
|
|
60
|
+
agentName: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "The short display name of the AI agent (e.g. Hinata, Nyxora). MUST be provided if the user assigns you a new name.",
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
required: ["content", "mode"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -115,7 +115,7 @@ export interface NyxoraConfig {
|
|
|
115
115
|
credentials?: any; // Deprecated, kept for parsing during migration
|
|
116
116
|
};
|
|
117
117
|
web_search?: {
|
|
118
|
-
provider: 'tavily' | 'brave' | 'mesh';
|
|
118
|
+
provider: 'tavily' | 'brave' | 'duckduckgo' | 'mesh';
|
|
119
119
|
enabled: boolean;
|
|
120
120
|
};
|
|
121
121
|
credentials?: {
|