nyxora 1.5.8 → 1.6.0
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 +4 -4
- package/package.json +14 -2
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/limitOrderManager.ts +193 -0
- package/packages/core/src/agent/reasoning.d.ts.map +1 -0
- package/packages/core/src/agent/reasoning.ts +466 -0
- package/packages/core/src/agent/transactionManager.ts +49 -0
- package/packages/core/src/agent/updateProfile.ts +47 -0
- package/packages/core/src/config/parser.d.ts.map +1 -0
- package/packages/core/src/config/parser.ts +106 -0
- package/packages/core/src/config/paths.ts +33 -0
- package/packages/core/src/gateway/cli.d.ts.map +1 -0
- package/packages/core/src/gateway/cli.ts +130 -0
- package/packages/core/src/gateway/server.ts +234 -0
- package/packages/core/src/gateway/setup.ts +276 -0
- package/packages/core/src/gateway/telegram.ts +157 -0
- package/packages/core/src/gateway/test.ts +16 -0
- package/packages/core/src/gateway/tracker.ts +70 -0
- package/packages/core/src/memory/logger.d.ts.map +1 -0
- package/packages/core/src/memory/logger.ts +121 -0
- package/packages/core/src/system/pluginManager.ts +91 -0
- package/packages/core/src/system/skills/browseWeb.ts +50 -0
- package/packages/core/src/system/skills/executeShell.ts +33 -0
- package/packages/core/src/system/skills/installSkill.ts +51 -0
- package/packages/core/src/system/skills/readFile.ts +33 -0
- package/packages/core/src/system/skills/updateSecurityPolicy.ts +55 -0
- package/packages/core/src/system/skills/writeFile.ts +40 -0
- package/packages/core/src/utils/formatter.ts +30 -0
- package/packages/core/src/utils/state.ts +10 -0
- package/packages/core/src/web3/config.d.ts.map +1 -0
- package/packages/core/src/web3/config.ts +43 -0
- package/packages/core/src/web3/skills/bridgeToken.ts +247 -0
- package/packages/core/src/web3/skills/checkAddress.ts +54 -0
- package/packages/core/src/web3/skills/checkPortfolio.ts +130 -0
- package/packages/core/src/web3/skills/checkSecurity.ts +71 -0
- package/packages/core/src/web3/skills/createWallet.ts +32 -0
- package/packages/core/src/web3/skills/customTx.ts +117 -0
- package/packages/core/src/web3/skills/getBalance.d.ts.map +1 -0
- package/packages/core/src/web3/skills/getBalance.ts +86 -0
- package/packages/core/src/web3/skills/getMyAddress.ts +26 -0
- package/packages/core/src/web3/skills/getPrice.ts +43 -0
- package/packages/core/src/web3/skills/marketAnalysis.ts +73 -0
- package/packages/core/src/web3/skills/mintNft.ts +146 -0
- package/packages/core/src/web3/skills/swapToken.ts +246 -0
- package/packages/core/src/web3/skills/transfer.ts +142 -0
- package/packages/core/src/web3/utils/tokens.ts +114 -0
- package/packages/dashboard/package.json +1 -1
- package/packages/dashboard/src/App.css +184 -0
- package/packages/dashboard/src/App.tsx +527 -0
- package/packages/dashboard/src/BalanceWidget.tsx +65 -0
- package/packages/dashboard/src/MarketWidget.tsx +73 -0
- package/packages/dashboard/src/Memory.tsx +110 -0
- package/packages/dashboard/src/Overview.tsx +157 -0
- package/packages/dashboard/src/PendingTransactions.tsx +75 -0
- package/packages/dashboard/src/Settings.tsx +230 -0
- package/packages/dashboard/src/Skills.tsx +78 -0
- package/packages/dashboard/src/SwapWidget.tsx +141 -0
- package/packages/dashboard/src/TransactionWidget.tsx +95 -0
- package/packages/dashboard/src/assets/hero.png +0 -0
- package/packages/dashboard/src/assets/react.svg +1 -0
- package/packages/dashboard/src/assets/vite.svg +1 -0
- package/packages/dashboard/src/index.css +509 -0
- package/packages/dashboard/src/main.tsx +10 -0
- package/packages/dashboard/src/overview.css +304 -0
- package/packages/dashboard/src/utils/api.ts +24 -0
- package/packages/policy/package.json +1 -1
- package/packages/policy/src/server.ts +195 -0
- package/packages/signer/package.json +1 -1
- package/packages/signer/src/crypto.ts +44 -0
- package/packages/signer/src/server.ts +126 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { OpenAI } from 'openai';
|
|
4
|
+
import { loadConfig } from '../config/parser';
|
|
5
|
+
import { Logger } from '../memory/logger';
|
|
6
|
+
import { Tracker } from '../gateway/tracker';
|
|
7
|
+
import { getBalanceToolDefinition, getBalance } from '../web3/skills/getBalance';
|
|
8
|
+
import { transferToolDefinition, prepareTransfer } from '../web3/skills/transfer';
|
|
9
|
+
import { getPriceToolDefinition, getPrice } from '../web3/skills/getPrice';
|
|
10
|
+
import { swapTokenToolDefinition, prepareSwapToken } from '../web3/skills/swapToken';
|
|
11
|
+
import { bridgeTokenToolDefinition, prepareBridgeToken } from '../web3/skills/bridgeToken';
|
|
12
|
+
import { mintNftToolDefinition, prepareMintNft } from '../web3/skills/mintNft';
|
|
13
|
+
import { customTxToolDefinition, prepareCustomTx } from '../web3/skills/customTx';
|
|
14
|
+
import { createWalletToolDefinition, createWallet } from '../web3/skills/createWallet';
|
|
15
|
+
import { checkSecurityToolDefinition, checkTokenSecurity } from '../web3/skills/checkSecurity';
|
|
16
|
+
import { marketAnalysisToolDefinition, analyzeMarket } from '../web3/skills/marketAnalysis';
|
|
17
|
+
import { checkPortfolioToolDefinition, checkPortfolio } from '../web3/skills/checkPortfolio';
|
|
18
|
+
import { checkAddressToolDefinition, checkAddress } from '../web3/skills/checkAddress';
|
|
19
|
+
import { getMyAddressToolDefinition, getMyAddress } from '../web3/skills/getMyAddress';
|
|
20
|
+
import { createLimitOrderToolDefinition, listLimitOrdersToolDefinition, cancelLimitOrderToolDefinition, limitOrderManager } from './limitOrderManager';
|
|
21
|
+
import { updateProfileToolDefinition, updateProfile } from './updateProfile';
|
|
22
|
+
import { updateSecurityPolicyToolDefinition, updateSecurityPolicy } from '../system/skills/updateSecurityPolicy';
|
|
23
|
+
import { readLocalFileToolDefinition, readLocalFile } from '../system/skills/readFile';
|
|
24
|
+
import { writeLocalFileToolDefinition, writeLocalFile } from '../system/skills/writeFile';
|
|
25
|
+
import { runTerminalCommandToolDefinition, runTerminalCommand } from '../system/skills/executeShell';
|
|
26
|
+
import { browseWebsiteToolDefinition, browseWebsite } from '../system/skills/browseWeb';
|
|
27
|
+
import { installExternalSkillToolDefinition, installExternalSkill } from '../system/skills/installSkill';
|
|
28
|
+
import { pluginManager } from '../system/pluginManager';
|
|
29
|
+
import { getPath } from '../config/paths';
|
|
30
|
+
import pc from 'picocolors';
|
|
31
|
+
|
|
32
|
+
export const logger = new Logger();
|
|
33
|
+
|
|
34
|
+
let currentKeyIndex = 0;
|
|
35
|
+
|
|
36
|
+
function getOpenAI(): OpenAI {
|
|
37
|
+
const config = loadConfig();
|
|
38
|
+
|
|
39
|
+
if (config.llm.provider === 'ollama') {
|
|
40
|
+
return new OpenAI({
|
|
41
|
+
baseURL: process.env.OLLAMA_BASE_URL ? `${process.env.OLLAMA_BASE_URL}/v1` : 'http://localhost:11434/v1',
|
|
42
|
+
apiKey: 'ollama', // API key is not required for local Ollama
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get API key from config (UI) or fallback to .env
|
|
47
|
+
let apiKey = '';
|
|
48
|
+
|
|
49
|
+
let configuredKeys = config.llm.api_keys;
|
|
50
|
+
if (typeof configuredKeys === 'string') {
|
|
51
|
+
configuredKeys = [configuredKeys];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (Array.isArray(configuredKeys) && configuredKeys.length > 0) {
|
|
55
|
+
// Filter out empty keys
|
|
56
|
+
const keys = configuredKeys.filter(k => typeof k === 'string' && k.trim() !== '');
|
|
57
|
+
if (keys.length > 0) {
|
|
58
|
+
currentKeyIndex = currentKeyIndex % keys.length;
|
|
59
|
+
apiKey = keys[currentKeyIndex];
|
|
60
|
+
console.log(`[LLM] Using rotated API Key (${currentKeyIndex + 1}/${keys.length}): ${apiKey.substring(0, 4)}...`);
|
|
61
|
+
currentKeyIndex++; // Increment for next request
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fallbacks if no valid keys found in config.llm.api_keys
|
|
66
|
+
if (!apiKey) {
|
|
67
|
+
if (config.llm.provider === 'gemini') {
|
|
68
|
+
apiKey = config.llm.credentials?.gemini_key || '';
|
|
69
|
+
} else if (config.llm.provider === 'openrouter') {
|
|
70
|
+
apiKey = config.llm.credentials?.openrouter_key || '';
|
|
71
|
+
} else {
|
|
72
|
+
apiKey = config.llm.credentials?.openai_key || '';
|
|
73
|
+
}
|
|
74
|
+
if (!apiKey) {
|
|
75
|
+
throw new Error(`No API Key found for ${config.llm.provider} in config.yaml. Please run 'nyxora setup' to configure it.`);
|
|
76
|
+
}
|
|
77
|
+
console.log(`[LLM] Using default API Key from config.yaml`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (config.llm.provider === 'gemini') {
|
|
81
|
+
return new OpenAI({
|
|
82
|
+
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/',
|
|
83
|
+
apiKey: apiKey,
|
|
84
|
+
});
|
|
85
|
+
} else if (config.llm.provider === 'openrouter') {
|
|
86
|
+
return new OpenAI({
|
|
87
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
88
|
+
apiKey: apiKey,
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
return new OpenAI({
|
|
92
|
+
apiKey: apiKey,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function executeWithRetry(
|
|
98
|
+
requestBuilder: (client: OpenAI) => Promise<any>,
|
|
99
|
+
maxRetries = 3
|
|
100
|
+
): Promise<any> {
|
|
101
|
+
let retries = 0;
|
|
102
|
+
|
|
103
|
+
while (retries <= maxRetries) {
|
|
104
|
+
try {
|
|
105
|
+
const client = getOpenAI();
|
|
106
|
+
return await requestBuilder(client);
|
|
107
|
+
} catch (error: any) {
|
|
108
|
+
const status = error?.status || error?.response?.status;
|
|
109
|
+
|
|
110
|
+
// 401 Unauthorized or 400 Bad Request - don't retry, it's fatal
|
|
111
|
+
if (status === 401 || status === 400) {
|
|
112
|
+
console.error(`[LLM] Fatal Error ${status}: ${error.message}. Aborting.`);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 429 Rate Limit - rotate provider/key immediately and retry
|
|
117
|
+
if (status === 429) {
|
|
118
|
+
console.warn(`[LLM] Rate Limit (429) hit. Rotating key...`);
|
|
119
|
+
// getOpenAI() automatically rotates to next key if available
|
|
120
|
+
retries++;
|
|
121
|
+
if (retries > maxRetries) throw error;
|
|
122
|
+
continue; // Try next key immediately
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 500, 502, 503, Timeout, Network error - Exponential Backoff
|
|
126
|
+
retries++;
|
|
127
|
+
if (retries > maxRetries) {
|
|
128
|
+
console.error(`[LLM] Max retries reached.`);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const delayMs = Math.pow(2, retries) * 1000; // 2s, 4s, 8s
|
|
133
|
+
console.warn(`[LLM] API Error (${status || error.message}). Retrying in ${delayMs}ms...`);
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getSystemPrompt() {
|
|
140
|
+
const config = loadConfig();
|
|
141
|
+
let basePrompt = `You are an autonomous Web3 agent operating on EVM chains.
|
|
142
|
+
You are equipped with a native wallet.
|
|
143
|
+
CRITICAL RULE: You must always reply in the exact same language that the user uses to talk to you. If the user speaks Indonesian, reply in Indonesian. If they speak English, reply in English.
|
|
144
|
+
CRITICAL RULE: If the user asks to check "my balance", "saldo saya", or anything about their own wallet, DO NOT ask them for an address. You must immediately call the get_balance tool and LEAVE THE ADDRESS PARAMETER EMPTY. The system will automatically use the injected private key wallet.
|
|
145
|
+
Always use the tools to interact with the blockchain.
|
|
146
|
+
If the user doesn't specify a chain, default to: ${config.agent.default_chain}.`;
|
|
147
|
+
|
|
148
|
+
// Read IDENTITY.md for core AI persona
|
|
149
|
+
try {
|
|
150
|
+
const identityMdPath = getPath('IDENTITY.md');
|
|
151
|
+
if (fs.existsSync(identityMdPath)) {
|
|
152
|
+
const identityInstructions = fs.readFileSync(identityMdPath, 'utf8');
|
|
153
|
+
basePrompt += `\n\n--- CORE IDENTITY & PERSONA ---\n${identityInstructions}`;
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Failed to read IDENTITY.md:', error);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Read user.md for custom instructions
|
|
160
|
+
try {
|
|
161
|
+
const userMdPath = getPath('user.md');
|
|
162
|
+
if (fs.existsSync(userMdPath)) {
|
|
163
|
+
const customInstructions = fs.readFileSync(userMdPath, 'utf8');
|
|
164
|
+
basePrompt += `\n\n--- CUSTOM USER INSTRUCTIONS ---\n${customInstructions}`;
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Failed to read user.md:', error);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read security_policy.md for NLP security constraints
|
|
171
|
+
try {
|
|
172
|
+
const policyPath = getPath('security_policy.md');
|
|
173
|
+
if (fs.existsSync(policyPath)) {
|
|
174
|
+
const securityInstructions = fs.readFileSync(policyPath, 'utf8');
|
|
175
|
+
basePrompt += `\n\n--- SECURITY POLICY (MANDATORY RULES) ---\n${securityInstructions}\n\nCRITICAL: If the user asks you to perform an action that violates the Security Policy above, YOU MUST NOT EXECUTE IT DIRECTLY. Instead, ask for their explicit permission first.`;
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Failed to read security_policy.md:', error);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return basePrompt;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function processUserInput(input: string, role: 'user' | 'system' = 'user', onProgress?: (msg: string) => void): Promise<string> {
|
|
185
|
+
const config = loadConfig();
|
|
186
|
+
// Add input to memory
|
|
187
|
+
logger.addEntry({ role, content: input });
|
|
188
|
+
|
|
189
|
+
const history = logger.getHistory();
|
|
190
|
+
|
|
191
|
+
// Format messages for OpenAI
|
|
192
|
+
const messages: any[] = [
|
|
193
|
+
{ role: 'system', content: getSystemPrompt() },
|
|
194
|
+
...history
|
|
195
|
+
.filter(m => !(m.role === 'tool' && !m.tool_call_id))
|
|
196
|
+
.map(m => {
|
|
197
|
+
let role = m.role;
|
|
198
|
+
if (role === 'system') role = 'user';
|
|
199
|
+
const msg: any = { role, content: m.content || "" };
|
|
200
|
+
if (m.name) msg.name = m.name;
|
|
201
|
+
if (m.tool_call_id) msg.tool_call_id = m.tool_call_id;
|
|
202
|
+
if (m.tool_calls) msg.tool_calls = m.tool_calls;
|
|
203
|
+
return msg;
|
|
204
|
+
})
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
if (config.llm.provider !== 'openai' && config.llm.provider !== 'ollama' && config.llm.provider !== 'gemini' && config.llm.provider !== 'openrouter') {
|
|
209
|
+
return `Provider ${config.llm.provider} is configured, but currently only OpenAI, OpenRouter, Ollama, and Gemini adapters are implemented.`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const response = await executeWithRetry(async (client) => {
|
|
213
|
+
return await client.chat.completions.create({
|
|
214
|
+
model: config.llm.model,
|
|
215
|
+
temperature: config.llm.temperature,
|
|
216
|
+
messages: messages,
|
|
217
|
+
tools: [
|
|
218
|
+
getBalanceToolDefinition as any,
|
|
219
|
+
transferToolDefinition as any,
|
|
220
|
+
getPriceToolDefinition as any,
|
|
221
|
+
swapTokenToolDefinition as any,
|
|
222
|
+
bridgeTokenToolDefinition as any,
|
|
223
|
+
mintNftToolDefinition as any,
|
|
224
|
+
customTxToolDefinition as any,
|
|
225
|
+
createWalletToolDefinition as any,
|
|
226
|
+
checkSecurityToolDefinition as any,
|
|
227
|
+
marketAnalysisToolDefinition as any,
|
|
228
|
+
checkPortfolioToolDefinition as any,
|
|
229
|
+
checkAddressToolDefinition as any,
|
|
230
|
+
getMyAddressToolDefinition as any,
|
|
231
|
+
createLimitOrderToolDefinition as any,
|
|
232
|
+
listLimitOrdersToolDefinition as any,
|
|
233
|
+
cancelLimitOrderToolDefinition as any,
|
|
234
|
+
updateProfileToolDefinition as any,
|
|
235
|
+
updateSecurityPolicyToolDefinition as any,
|
|
236
|
+
readLocalFileToolDefinition as any,
|
|
237
|
+
writeLocalFileToolDefinition as any,
|
|
238
|
+
runTerminalCommandToolDefinition as any,
|
|
239
|
+
browseWebsiteToolDefinition as any,
|
|
240
|
+
installExternalSkillToolDefinition as any,
|
|
241
|
+
...pluginManager.getToolDefinitions()
|
|
242
|
+
],
|
|
243
|
+
tool_choice: "auto",
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const responseMessage = response.choices[0].message;
|
|
248
|
+
|
|
249
|
+
// Log tracking
|
|
250
|
+
Tracker.addMessage();
|
|
251
|
+
if (response.usage?.total_tokens) {
|
|
252
|
+
Tracker.addTokens(response.usage.total_tokens, config.llm.provider);
|
|
253
|
+
}
|
|
254
|
+
Tracker.addEvent('llm.response', { provider: config.llm.provider, tool_calls: responseMessage.tool_calls?.length || 0 });
|
|
255
|
+
|
|
256
|
+
// Log assistant response
|
|
257
|
+
logger.addEntry({
|
|
258
|
+
role: 'assistant',
|
|
259
|
+
content: responseMessage.content || "",
|
|
260
|
+
tool_calls: responseMessage.tool_calls,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Check if the model wants to call a tool
|
|
264
|
+
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
265
|
+
for (const _toolCall of responseMessage.tool_calls) {
|
|
266
|
+
const toolCall = _toolCall as any;
|
|
267
|
+
let result = "";
|
|
268
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
269
|
+
const toolName = toolCall.function.name;
|
|
270
|
+
|
|
271
|
+
console.log(pc.yellow(`[⚡ Eksekusi Tool] AI memanggil ${toolName}...`));
|
|
272
|
+
if (onProgress) onProgress(`_⚡ Menjalankan alat: ${toolName}..._`);
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
switch (toolName) {
|
|
276
|
+
case 'get_balance': {
|
|
277
|
+
result = await getBalance(args.chainName, args.address, args.token);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case 'transfer_token':
|
|
281
|
+
case 'transfer_native': {
|
|
282
|
+
if (config.permissions?.web3?.allow_transfer === false) {
|
|
283
|
+
result = `[Security Blocked] Runtime Permission Denied: Web3 transfers are disabled. Update config.yaml to allow.`;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
result = await prepareTransfer(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 'get_price': {
|
|
290
|
+
result = await getPrice(args.coinId);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'swap_token': {
|
|
294
|
+
if (config.permissions?.web3?.allow_swap === false) {
|
|
295
|
+
result = `[Security Blocked] Runtime Permission Denied: Web3 swaps are disabled. Update config.yaml to allow.`;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
// Note: max_usd_per_tx validation would ideally be calculated here before prepareSwapToken
|
|
299
|
+
result = await prepareSwapToken(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'bridge_token': {
|
|
303
|
+
if (config.permissions?.web3?.allow_transfer === false) {
|
|
304
|
+
result = `[Security Blocked] Runtime Permission Denied: Web3 bridging (transfer) is disabled. Update config.yaml to allow.`;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
result = await prepareBridgeToken(args.fromChainName, args.toChainName, args.fromToken, args.toToken, args.amountStr, args.mode, args.providerName);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case 'mint_nft': {
|
|
311
|
+
result = await prepareMintNft(args.chainName, args.contractAddress, args.functionSignature, args.argsStr, args.valueEth);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case 'custom_tx': {
|
|
315
|
+
if (config.permissions?.web3?.allow_transfer === false) {
|
|
316
|
+
result = `[Security Blocked] Runtime Permission Denied: Custom transactions are blocked because transfers are disabled.`;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
result = await prepareCustomTx(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case 'create_wallet': {
|
|
323
|
+
result = await createWallet();
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case 'check_token_security': {
|
|
327
|
+
result = await checkTokenSecurity(args.chainName, args.contractAddress);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case 'analyze_market': {
|
|
331
|
+
result = await analyzeMarket(args.chainName, args.tokenAddressOrSymbol);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'check_portfolio': {
|
|
335
|
+
result = await checkPortfolio(args.chainName, args.address);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case 'check_address': {
|
|
339
|
+
result = await checkAddress(args.chainName, args.address);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case 'get_my_address': {
|
|
343
|
+
result = await getMyAddress();
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case 'create_limit_order': {
|
|
347
|
+
if (config.permissions?.web3?.allow_swap === false) {
|
|
348
|
+
result = `[Security Blocked] Runtime Permission Denied: Limit orders require swap permissions. Update config.yaml to allow.`;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
result = limitOrderManager.createOrder(args.chainName, args.fromToken, args.toToken, args.amountStr, args.targetPriceUsd, args.condition);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case 'list_limit_orders': {
|
|
355
|
+
result = limitOrderManager.listOrders();
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case 'cancel_limit_order': {
|
|
359
|
+
result = limitOrderManager.cancelOrder(args.id);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case 'update_profile': {
|
|
363
|
+
result = updateProfile(args.content, args.mode);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case 'update_security_policy': {
|
|
367
|
+
result = updateSecurityPolicy(args.rule, args.action);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case 'read_local_file': {
|
|
371
|
+
result = readLocalFile(args.filePath);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case 'write_local_file': {
|
|
375
|
+
if (config.permissions?.system?.allow_file_write === false) {
|
|
376
|
+
result = `[Security Blocked] Runtime Permission Denied: File writing is disabled. Update config.yaml to allow.`;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
result = writeLocalFile(args.filePath, args.content);
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
case 'run_terminal_command': {
|
|
383
|
+
if (config.permissions?.system?.allow_shell_execution === false) {
|
|
384
|
+
result = `[Security Blocked] Runtime Permission Denied: Shell execution is disabled. Update config.yaml to allow.`;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
result = await runTerminalCommand(args.command);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case 'browse_website': {
|
|
391
|
+
result = await browseWebsite(args.url);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case 'install_external_skill': {
|
|
395
|
+
result = await installExternalSkill(args.url);
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
default: {
|
|
399
|
+
const externalResult = await pluginManager.executeTool(toolName, args);
|
|
400
|
+
if (externalResult !== null) {
|
|
401
|
+
result = externalResult;
|
|
402
|
+
} else {
|
|
403
|
+
result = `Error: Tool ${toolName} is not implemented.`;
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (result.includes('[Security Blocked]') || result.startsWith('Error:')) {
|
|
410
|
+
console.log(pc.red(`[❌ Gagal] Tool ${toolName} mengembalikan error atau diblokir.`));
|
|
411
|
+
} else {
|
|
412
|
+
console.log(pc.green(`[✅ Sukses] Tool ${toolName} berhasil dieksekusi.`));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
} catch (toolError: any) {
|
|
416
|
+
result = `Error executing ${toolName}: ${toolError.message}`;
|
|
417
|
+
console.log(pc.red(`[❌ Error Crash] Eksekusi ${toolName} gagal total: ${toolError.message}`));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
logger.addEntry({
|
|
421
|
+
role: 'tool',
|
|
422
|
+
tool_call_id: toolCall.id,
|
|
423
|
+
name: toolName,
|
|
424
|
+
content: result,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Second call to get the final answer after tool execution
|
|
429
|
+
const secondMessages = [
|
|
430
|
+
{ role: 'system', content: getSystemPrompt() },
|
|
431
|
+
...logger.getHistory()
|
|
432
|
+
.filter(m => !(m.role === 'tool' && !m.tool_call_id))
|
|
433
|
+
.map(m => {
|
|
434
|
+
let role = m.role;
|
|
435
|
+
if (role === 'system') role = 'user';
|
|
436
|
+
const msg: any = { role, content: m.content || "" };
|
|
437
|
+
if (m.name) msg.name = m.name;
|
|
438
|
+
if (m.tool_call_id) msg.tool_call_id = m.tool_call_id;
|
|
439
|
+
if (m.tool_calls) msg.tool_calls = m.tool_calls;
|
|
440
|
+
return msg;
|
|
441
|
+
})
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
const secondResponse = await executeWithRetry(async (client) => {
|
|
445
|
+
return await client.chat.completions.create({
|
|
446
|
+
model: config.llm.model,
|
|
447
|
+
messages: secondMessages,
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (secondResponse.usage?.total_tokens) {
|
|
452
|
+
Tracker.addTokens(secondResponse.usage.total_tokens, config.llm.provider);
|
|
453
|
+
}
|
|
454
|
+
Tracker.addEvent('llm.final_response', { provider: config.llm.provider });
|
|
455
|
+
|
|
456
|
+
const finalContent = secondResponse.choices[0].message.content || "";
|
|
457
|
+
logger.addEntry({ role: 'assistant', content: finalContent });
|
|
458
|
+
return finalContent;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return responseMessage.content || "No response generated.";
|
|
462
|
+
} catch (error: any) {
|
|
463
|
+
console.error("LLM Error:", error);
|
|
464
|
+
return `Error connecting to AI Provider: ${error.message}`;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export type TransactionType = 'transfer' | 'swap' | 'bridge' | 'mint' | 'custom';
|
|
4
|
+
|
|
5
|
+
export interface PendingTransaction {
|
|
6
|
+
id: string;
|
|
7
|
+
type: TransactionType;
|
|
8
|
+
chainName: string;
|
|
9
|
+
details: any;
|
|
10
|
+
status: 'pending' | 'approved' | 'rejected' | 'executed' | 'failed';
|
|
11
|
+
result?: string;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class TransactionManager {
|
|
16
|
+
private transactions: Map<string, PendingTransaction> = new Map();
|
|
17
|
+
|
|
18
|
+
createPendingTransaction(type: TransactionType, chainName: string, details: any): PendingTransaction {
|
|
19
|
+
const id = crypto.randomUUID();
|
|
20
|
+
const tx: PendingTransaction = {
|
|
21
|
+
id,
|
|
22
|
+
type,
|
|
23
|
+
chainName,
|
|
24
|
+
details,
|
|
25
|
+
status: 'pending',
|
|
26
|
+
createdAt: Date.now(),
|
|
27
|
+
};
|
|
28
|
+
this.transactions.set(id, tx);
|
|
29
|
+
return tx;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getPending(): PendingTransaction[] {
|
|
33
|
+
return Array.from(this.transactions.values()).filter(t => t.status === 'pending');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getTransaction(id: string): PendingTransaction | undefined {
|
|
37
|
+
return this.transactions.get(id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
updateStatus(id: string, status: PendingTransaction['status'], result?: string) {
|
|
41
|
+
const tx = this.transactions.get(id);
|
|
42
|
+
if (tx) {
|
|
43
|
+
tx.status = status;
|
|
44
|
+
if (result) tx.result = result;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const txManager = new TransactionManager();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { getPath } from '../config/paths';
|
|
3
|
+
|
|
4
|
+
export function updateProfile(content: string, mode: 'append' | 'replace'): string {
|
|
5
|
+
try {
|
|
6
|
+
const userMdPath = getPath('user.md');
|
|
7
|
+
|
|
8
|
+
if (mode === 'replace') {
|
|
9
|
+
fs.writeFileSync(userMdPath, content, 'utf8');
|
|
10
|
+
return "Profile replaced successfully. New user.md has been saved.";
|
|
11
|
+
} else {
|
|
12
|
+
let existingContent = "";
|
|
13
|
+
if (fs.existsSync(userMdPath)) {
|
|
14
|
+
existingContent = fs.readFileSync(userMdPath, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const newContent = existingContent + "\n" + content;
|
|
18
|
+
fs.writeFileSync(userMdPath, newContent, 'utf8');
|
|
19
|
+
return "Profile appended successfully. New instructions added to user.md.";
|
|
20
|
+
}
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
return `Failed to update profile: ${error.message}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const updateProfileToolDefinition = {
|
|
27
|
+
type: "function",
|
|
28
|
+
function: {
|
|
29
|
+
name: "update_profile",
|
|
30
|
+
description: "Updates or rewrites the user.md file. Use this when the user asks you to remember something about them, change their persona, or update your instructions.",
|
|
31
|
+
parameters: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
content: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "The content to write or append to user.md",
|
|
37
|
+
},
|
|
38
|
+
mode: {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: ["append", "replace"],
|
|
41
|
+
description: "Whether to append the content to the existing file or replace the entire file.",
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ["content", "mode"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["parser.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;QAC5C,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,wBAAgB,UAAU,IAAI,aAAa,CAc1C"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import yaml from 'yaml';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getPath } from './paths';
|
|
5
|
+
|
|
6
|
+
export interface NyxoraConfig {
|
|
7
|
+
agent: {
|
|
8
|
+
name: string;
|
|
9
|
+
default_chain: string;
|
|
10
|
+
};
|
|
11
|
+
llm: {
|
|
12
|
+
provider: 'openai' | 'anthropic' | 'ollama' | 'gemini' | 'openrouter';
|
|
13
|
+
model: string;
|
|
14
|
+
temperature: number;
|
|
15
|
+
api_keys?: string[];
|
|
16
|
+
credentials?: {
|
|
17
|
+
openai_key?: string;
|
|
18
|
+
gemini_key?: string;
|
|
19
|
+
openrouter_key?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
memory: {
|
|
23
|
+
type: string;
|
|
24
|
+
path: string;
|
|
25
|
+
};
|
|
26
|
+
web3?: {
|
|
27
|
+
rpc_urls?: Record<string, string>;
|
|
28
|
+
};
|
|
29
|
+
integrations?: {
|
|
30
|
+
telegram?: {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
bot_token?: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
permissions?: {
|
|
36
|
+
web3?: {
|
|
37
|
+
allow_transfer?: boolean;
|
|
38
|
+
allow_swap?: boolean;
|
|
39
|
+
max_usd_per_tx?: number;
|
|
40
|
+
};
|
|
41
|
+
system?: {
|
|
42
|
+
allow_shell_execution?: boolean;
|
|
43
|
+
allow_file_write?: boolean;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function loadConfig(): NyxoraConfig {
|
|
49
|
+
const configPath = getPath('config.yaml');
|
|
50
|
+
try {
|
|
51
|
+
const file = fs.readFileSync(configPath, 'utf8');
|
|
52
|
+
const parsed = yaml.parse(file) as Partial<NyxoraConfig>;
|
|
53
|
+
|
|
54
|
+
// Merge with defaults
|
|
55
|
+
return {
|
|
56
|
+
agent: parsed.agent || { name: 'Nyxora-Default', default_chain: 'base' },
|
|
57
|
+
llm: parsed.llm || {
|
|
58
|
+
provider: 'openai',
|
|
59
|
+
model: 'gpt-4o-mini',
|
|
60
|
+
temperature: 0.2,
|
|
61
|
+
api_keys: [],
|
|
62
|
+
credentials: {}
|
|
63
|
+
},
|
|
64
|
+
memory: parsed.memory || { type: 'file', path: './memory.json' },
|
|
65
|
+
web3: parsed.web3 || { rpc_urls: {} },
|
|
66
|
+
integrations: parsed.integrations || {
|
|
67
|
+
telegram: { enabled: false }
|
|
68
|
+
},
|
|
69
|
+
permissions: parsed.permissions || {
|
|
70
|
+
web3: { allow_transfer: false, allow_swap: true, max_usd_per_tx: 50 },
|
|
71
|
+
system: { allow_shell_execution: false, allow_file_write: false }
|
|
72
|
+
}
|
|
73
|
+
} as NyxoraConfig;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Failed to load config.yaml. Using default configuration.', error);
|
|
76
|
+
return {
|
|
77
|
+
agent: { name: 'Nyxora-Default', default_chain: 'base' },
|
|
78
|
+
llm: {
|
|
79
|
+
provider: 'openai',
|
|
80
|
+
model: 'gpt-4o-mini',
|
|
81
|
+
temperature: 0.2,
|
|
82
|
+
api_keys: [],
|
|
83
|
+
credentials: {}
|
|
84
|
+
},
|
|
85
|
+
memory: { type: 'file', path: './memory.json' },
|
|
86
|
+
web3: { rpc_urls: {} },
|
|
87
|
+
integrations: {
|
|
88
|
+
telegram: { enabled: false }
|
|
89
|
+
},
|
|
90
|
+
permissions: {
|
|
91
|
+
web3: { allow_transfer: false, allow_swap: true, max_usd_per_tx: 50 },
|
|
92
|
+
system: { allow_shell_execution: false, allow_file_write: false }
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function saveConfig(newConfig: NyxoraConfig): void {
|
|
99
|
+
const configPath = getPath('config.yaml');
|
|
100
|
+
try {
|
|
101
|
+
const yamlStr = yaml.stringify(newConfig);
|
|
102
|
+
fs.writeFileSync(configPath, yamlStr, 'utf8');
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Failed to save config.yaml', error);
|
|
105
|
+
}
|
|
106
|
+
}
|