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.
Files changed (64) hide show
  1. package/README.md +3 -4
  2. package/bin/nyxora.mjs +14 -2
  3. package/dist/packages/core/src/agent/cronManager.js +107 -0
  4. package/dist/packages/core/src/agent/reasoning.js +85 -22
  5. package/dist/packages/core/src/agent/transactionManager.js +2 -2
  6. package/dist/packages/core/src/agent/updateIdentity.js +71 -0
  7. package/dist/packages/core/src/config/paths.js +5 -20
  8. package/dist/packages/core/src/gateway/WebSocketManager.js +98 -0
  9. package/dist/packages/core/src/gateway/chat.js +38 -0
  10. package/dist/packages/core/src/gateway/cli.js +20 -20
  11. package/dist/packages/core/src/gateway/server.js +63 -8
  12. package/dist/packages/core/src/gateway/setup.js +9 -6
  13. package/dist/packages/core/src/gateway/telegram.js +43 -0
  14. package/dist/packages/core/src/gateway/tracker.js +63 -0
  15. package/dist/packages/core/src/memory/logger.js +1 -1
  16. package/dist/packages/core/src/system/skills/cancelTask.js +40 -0
  17. package/dist/packages/core/src/system/skills/editFile.js +5 -0
  18. package/dist/packages/core/src/system/skills/scheduleTask.js +39 -0
  19. package/dist/packages/core/src/system/skills/searchWeb.js +61 -8
  20. package/dist/packages/core/src/system/skills/writeFile.js +5 -0
  21. package/dist/packages/core/src/web3/skills/getPrice.js +1 -1
  22. package/dist/packages/core/src/web3/utils/vaultClient.js +79 -29
  23. package/dist/packages/policy/src/server.js +29 -1
  24. package/package.json +7 -1
  25. package/packages/core/package.json +8 -1
  26. package/packages/core/src/agent/cronManager.ts +87 -0
  27. package/packages/core/src/agent/reasoning.ts +91 -21
  28. package/packages/core/src/agent/transactionManager.ts +2 -1
  29. package/packages/core/src/agent/updateIdentity.ts +68 -0
  30. package/packages/core/src/config/parser.ts +1 -1
  31. package/packages/core/src/config/paths.ts +5 -23
  32. package/packages/core/src/gateway/WebSocketManager.ts +114 -0
  33. package/packages/core/src/gateway/chat.ts +40 -1
  34. package/packages/core/src/gateway/cli.ts +10 -10
  35. package/packages/core/src/gateway/server.ts +66 -7
  36. package/packages/core/src/gateway/setup.ts +8 -5
  37. package/packages/core/src/gateway/telegram.ts +49 -0
  38. package/packages/core/src/gateway/tracker.ts +61 -0
  39. package/packages/core/src/memory/logger.ts +1 -1
  40. package/packages/core/src/system/skills/cancelTask.ts +38 -0
  41. package/packages/core/src/system/skills/editFile.ts +7 -0
  42. package/packages/core/src/system/skills/scheduleTask.ts +38 -0
  43. package/packages/core/src/system/skills/searchWeb.ts +56 -8
  44. package/packages/core/src/system/skills/writeFile.ts +7 -0
  45. package/packages/core/src/web3/skills/getPrice.ts +1 -1
  46. package/packages/core/src/web3/utils/vaultClient.ts +86 -26
  47. package/packages/dashboard/dist/assets/index-CjZWf1Ei.css +1 -0
  48. package/packages/dashboard/dist/assets/index-CmWZofn_.js +16 -0
  49. package/packages/dashboard/dist/index.html +2 -2
  50. package/packages/dashboard/dist/routers/0x.png +0 -0
  51. package/packages/dashboard/dist/routers/1inch.png +0 -0
  52. package/packages/dashboard/dist/routers/cmc.png +0 -0
  53. package/packages/dashboard/dist/routers/kyberswap.png +0 -0
  54. package/packages/dashboard/dist/routers/lifi.png +0 -0
  55. package/packages/dashboard/dist/routers/openocean.png +0 -0
  56. package/packages/dashboard/dist/routers/relay.png +0 -0
  57. package/packages/dashboard/dist/routers/zerion.png +0 -0
  58. package/packages/dashboard/package.json +1 -1
  59. package/packages/mcp-server/package.json +1 -1
  60. package/packages/policy/package.json +4 -3
  61. package/packages/policy/src/server.ts +29 -1
  62. package/packages/signer/package.json +1 -1
  63. package/packages/dashboard/dist/assets/index-CQNHWZtN.css +0 -1
  64. 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
- try {
25
- const res = await fetch(`http://127.0.0.1:${process.env.POLICY_PORT || 3001}/address`, {
26
- headers: token ? { 'Authorization': `Bearer ${token}` } : {},
27
- signal: AbortSignal.timeout(30000)
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
- if (!res.ok)
30
- throw new Error(await res.text());
31
- const data = await res.json();
32
- return data.address;
33
- }
34
- catch (error) {
35
- throw new Error(`Failed to get address from vault: ${error.message}`);
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
- try {
49
- const res = await fetch(`http://127.0.0.1:${process.env.POLICY_PORT || 3001}/request-tx`, {
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/json',
84
+ 'Content-Type': 'application/msgpack',
85
+ 'Content-Length': payloadBuffer.length,
53
86
  ...(token ? { 'Authorization': `Bearer ${token}` } : {})
54
- },
55
- body: JSON.stringify(txPayload),
56
- signal: AbortSignal.timeout(30000)
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
- const data = await res.json();
59
- if (!res.ok)
60
- throw new Error(data.error || 'Failed to submit transaction');
61
- if (data.status === 'pending') {
62
- return `Pending (ID: ${data.txId})`;
63
- }
64
- return data.signedHash || data.hash || data.txHash || data.status || 'Transaction executed successfully (No Hash returned)';
65
- }
66
- catch (error) {
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 server = app.listen(PORT, '127.0.0.1', () => {
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.21",
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.21",
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
- // Read IDENTITY.md for core AI persona
186
+ const identityMdPath = getPath('IDENTITY.md');
187
+ const userMdPath = getPath('user.md');
188
+
189
+ let isFirstTime = false;
180
190
  try {
181
- const identityMdPath = getPath('IDENTITY.md');
182
- if (fs.existsSync(identityMdPath)) {
183
- const identityInstructions = fs.readFileSync(identityMdPath, 'utf8');
184
- basePrompt += `\n\n--- CORE IDENTITY & PERSONA ---\n${identityInstructions}`;
185
- }
186
- } catch (error) {
187
- console.error('Failed to read IDENTITY.md:', error);
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
- // Read user.md for custom instructions
191
- try {
192
- const userMdPath = getPath('user.md');
193
- if (fs.existsSync(userMdPath)) {
194
- const customInstructions = fs.readFileSync(userMdPath, 'utf8');
195
- basePrompt += `\n\n--- CUSTOM USER INSTRUCTIONS ---\n${customInstructions}`;
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
- const messages: any[] = [
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
- const finalContent = secondResponse.choices[0].message.content || "";
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
- return responseMessage.content || "No response generated.";
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 = path.join(process.cwd(), '.nyxora_withdrawals.json');
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?: {