nyxora 1.7.0 → 1.7.1

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 (53) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/nyxora.mjs +8 -0
  3. package/package.json +1 -2
  4. package/packages/core/package.json +1 -1
  5. package/packages/core/src/agent/limitOrderManager.ts +2 -2
  6. package/packages/core/src/agent/reasoning.ts +12 -10
  7. package/packages/core/src/config/parser.ts +99 -9
  8. package/packages/core/src/gateway/cli.ts +28 -2
  9. package/packages/core/src/gateway/setup.ts +44 -9
  10. package/packages/core/src/gateway/telegram.ts +3 -1
  11. package/packages/core/src/system/skills/searchWeb.ts +187 -21
  12. package/packages/core/src/web3/config.ts +7 -1
  13. package/packages/core/src/web3/skills/bridgeToken.ts +4 -3
  14. package/packages/core/src/web3/skills/checkAddress.ts +2 -2
  15. package/packages/core/src/web3/skills/checkPortfolio.ts +2 -2
  16. package/packages/core/src/web3/skills/checkSecurity.ts +3 -2
  17. package/packages/core/src/web3/skills/customTx.ts +2 -2
  18. package/packages/core/src/web3/skills/getBalance.ts +3 -3
  19. package/packages/core/src/web3/skills/marketAnalysis.ts +2 -2
  20. package/packages/core/src/web3/skills/mintNft.ts +2 -2
  21. package/packages/core/src/web3/skills/swapToken.ts +4 -3
  22. package/packages/core/src/web3/skills/transfer.ts +2 -2
  23. package/packages/core/src/web3/utils/tokens.ts +8 -0
  24. package/packages/dashboard/dist/assets/{index-24OeXn-k.css → index-BSk4CLkG.css} +1 -1
  25. package/packages/dashboard/dist/assets/{index-DQtaOlOl.js → index-Dc3Tu0Te.js} +20 -20
  26. package/packages/dashboard/dist/index.html +2 -2
  27. package/packages/dashboard/package.json +1 -1
  28. package/packages/mcp-server/package.json +1 -1
  29. package/packages/policy/package.json +1 -1
  30. package/packages/signer/package.json +1 -1
  31. package/packages/dashboard/public/favicon.svg +0 -10
  32. package/packages/dashboard/public/icons.svg +0 -24
  33. package/packages/dashboard/src/App.css +0 -184
  34. package/packages/dashboard/src/App.tsx +0 -585
  35. package/packages/dashboard/src/BalanceWidget.tsx +0 -65
  36. package/packages/dashboard/src/MarketWidget.tsx +0 -73
  37. package/packages/dashboard/src/NetworkSelector.tsx +0 -64
  38. package/packages/dashboard/src/NyxoraLogo.tsx +0 -25
  39. package/packages/dashboard/src/OsSkills.tsx +0 -352
  40. package/packages/dashboard/src/Overview.tsx +0 -157
  41. package/packages/dashboard/src/PendingTransactions.tsx +0 -75
  42. package/packages/dashboard/src/Settings.tsx +0 -338
  43. package/packages/dashboard/src/Skills.tsx +0 -200
  44. package/packages/dashboard/src/SwapWidget.tsx +0 -141
  45. package/packages/dashboard/src/TransactionWidget.tsx +0 -95
  46. package/packages/dashboard/src/assets/hero.png +0 -0
  47. package/packages/dashboard/src/assets/react.svg +0 -1
  48. package/packages/dashboard/src/assets/vite.svg +0 -1
  49. package/packages/dashboard/src/components/PillSelect.tsx +0 -65
  50. package/packages/dashboard/src/index.css +0 -807
  51. package/packages/dashboard/src/main.tsx +0 -10
  52. package/packages/dashboard/src/overview.css +0 -304
  53. package/packages/dashboard/src/utils/api.ts +0 -31
@@ -1,43 +1,209 @@
1
- import { search, SafeSearchType } from 'duck-duck-scrape';
1
+ import { loadConfig, loadApiKeys } from '../../config/parser';
2
2
 
3
- export async function searchWeb(query: string): Promise<string> {
4
- try {
5
- const searchResults = await search(query, {
6
- safeSearch: SafeSearchType.MODERATE
7
- });
3
+ interface SearchQueryResult {
4
+ title: string;
5
+ url: string;
6
+ content: string;
7
+ }
8
+
9
+ const SEARXNG_INSTANCES = [
10
+ 'https://search.mdosch.de',
11
+ 'https://searx.tiekoetter.com',
12
+ 'https://paulgo.io',
13
+ 'https://searx.be',
14
+ 'https://searx.fmac.network'
15
+ ];
16
+
17
+ async function searchTavily(query: string, apiKey: string, depth: number = 1): Promise<SearchQueryResult[]> {
18
+ const searchDepth = depth > 1 ? 'advanced' : 'basic';
19
+ const maxResults = depth > 1 ? 15 : 8;
20
+ const res = await fetch('https://api.tavily.com/search', {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/json' },
23
+ body: JSON.stringify({ api_key: apiKey, query, search_depth: searchDepth, max_results: maxResults })
24
+ });
25
+
26
+ if (!res.ok) {
27
+ const status = res.status;
28
+ throw new Error(`[Tavily Error] Status: ${status}`);
29
+ }
30
+
31
+ const json = await res.json();
32
+ if (!json.results) return [];
33
+
34
+ return json.results.map((r: any) => ({
35
+ title: r.title,
36
+ url: r.url,
37
+ content: r.content
38
+ }));
39
+ }
8
40
 
9
- if (!searchResults.results || searchResults.results.length === 0) {
10
- return "No results found for your query.";
41
+ async function searchBrave(query: string, apiKey: string, depth: number = 1): Promise<SearchQueryResult[]> {
42
+ const q = encodeURIComponent(query);
43
+ const count = depth > 1 ? 15 : 8;
44
+ const res = await fetch(`https://api.search.brave.com/res/v1/web/search?q=${q}&count=${count}`, {
45
+ headers: {
46
+ 'Accept': 'application/json',
47
+ 'X-Subscription-Token': apiKey
11
48
  }
49
+ });
50
+
51
+ if (!res.ok) {
52
+ const status = res.status;
53
+ throw new Error(`[Brave Error] Status: ${status}`);
54
+ }
55
+
56
+ const json = await res.json();
57
+ if (!json.web || !json.web.results) return [];
58
+
59
+ return json.web.results.map((r: any) => ({
60
+ title: r.title,
61
+ url: r.url,
62
+ content: r.description || r.title
63
+ }));
64
+ }
12
65
 
13
- // Limit to top 8 results
14
- const topResults = searchResults.results.slice(0, 8);
15
-
16
- let responseText = `Search Results for "${query}":\n\n`;
17
-
18
- topResults.forEach((result, index) => {
19
- responseText += `${index + 1}. ${result.title}\n`;
20
- responseText += `URL: ${result.url}\n`;
21
- responseText += `Snippet: ${result.description}\n\n`;
22
- });
66
+ async function searchSearxng(query: string, depth: number = 1): Promise<SearchQueryResult[]> {
67
+ const q = encodeURIComponent(query);
68
+ const maxResults = depth > 1 ? 15 : 8;
69
+ for (const url of SEARXNG_INSTANCES) {
70
+ try {
71
+ const res = await fetch(`${url}/search?q=${q}&format=json`, {
72
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' },
73
+ signal: AbortSignal.timeout(4000)
74
+ });
75
+
76
+ if (res.ok) {
77
+ const json = await res.json();
78
+ if (!json.results || json.results.length === 0) continue;
79
+
80
+ return json.results.slice(0, maxResults).map((r: any) => ({
81
+ title: r.title || 'No title',
82
+ url: r.url || '#',
83
+ content: r.content || r.snippet || r.description || 'No description available'
84
+ }));
85
+ }
86
+ } catch (e) {
87
+ continue;
88
+ }
89
+ }
90
+ throw new Error('[SearXNG Error] All decentralized instances failed.');
91
+ }
92
+
93
+ const searchCache = new Map<string, {data: SearchQueryResult[], timestamp: number}>();
94
+
95
+ export async function searchWeb(query: string, depth: number = 1): Promise<string> {
96
+ // Auto-inject current year for time-sensitive queries
97
+ const lowerQuery = query.toLowerCase();
98
+ const currentYear = new Date().getFullYear().toString();
99
+ let finalQuery = query;
100
+
101
+ if ((lowerQuery.includes('hari ini') || lowerQuery.includes('sekarang') || lowerQuery.includes('today') || lowerQuery.includes('saat ini') || lowerQuery.includes('terbaru')) && !lowerQuery.includes(currentYear)) {
102
+ finalQuery = `${query} ${currentYear}`;
103
+ console.log(`[WebSearch] Auto-injected current year: "${finalQuery}"`);
104
+ }
23
105
 
106
+ const cacheKey = `${finalQuery.trim().toLowerCase()}_depth_${depth}`;
107
+ const cached = searchCache.get(cacheKey);
108
+ if (cached && (Date.now() - cached.timestamp < 300000)) {
109
+ console.log(`[WebSearch] Returning cached results for: "${finalQuery}" (Depth: ${depth})`);
110
+ let responseText = `Search Results for "${query}" (Cached):\n\n`;
111
+ cached.data.forEach((r, index) => {
112
+ responseText += `${index + 1}. ${r.title}\n`;
113
+ responseText += `URL: ${r.url}\n`;
114
+ responseText += `Snippet: ${r.content}\n\n`;
115
+ });
24
116
  return responseText.trim();
25
- } catch (error: any) {
26
- return `Failed to search the web: ${error.message}`;
27
117
  }
118
+
119
+ const config = loadConfig();
120
+ const provider = config.web_search?.provider || 'mesh';
121
+ const vaultKeys = await loadApiKeys();
122
+ const creds = Object.keys(vaultKeys).length > 0 ? vaultKeys : (config.credentials || {});
123
+
124
+ let results: SearchQueryResult[] = [];
125
+
126
+ try {
127
+ if (provider === 'tavily' && creds.tavily_key) {
128
+ try {
129
+ results = await searchTavily(finalQuery, creds.tavily_key, depth);
130
+ } catch (e: any) {
131
+ if (e.message.includes('401') || e.message.includes('429')) {
132
+ console.warn('[WebSearch] Primary provider (Tavily) failed with 429/401. Switching to backup provider (Brave Search)...');
133
+ if (creds.brave_key) {
134
+ try {
135
+ results = await searchBrave(finalQuery, creds.brave_key, depth);
136
+ } catch (e2: any) {
137
+ console.warn('[WebSearch] Backup provider (Brave) failed. Falling back to SearXNG Mesh...');
138
+ results = await searchSearxng(finalQuery, depth);
139
+ }
140
+ } else {
141
+ console.warn('[WebSearch] No backup provider found. Falling back to SearXNG Mesh...');
142
+ results = await searchSearxng(finalQuery, depth);
143
+ }
144
+ } else {
145
+ throw e;
146
+ }
147
+ }
148
+ } else if (provider === 'brave' && creds.brave_key) {
149
+ try {
150
+ results = await searchBrave(finalQuery, creds.brave_key, depth);
151
+ } catch (e: any) {
152
+ if (e.message.includes('403') || e.message.includes('429')) {
153
+ console.warn('[WebSearch] Primary provider (Brave) failed with 429/403. Switching to backup provider (Tavily)...');
154
+ if (creds.tavily_key) {
155
+ try {
156
+ results = await searchTavily(finalQuery, creds.tavily_key, depth);
157
+ } catch (e2: any) {
158
+ console.warn('[WebSearch] Backup provider (Tavily) failed. Falling back to SearXNG Mesh...');
159
+ results = await searchSearxng(finalQuery, depth);
160
+ }
161
+ } else {
162
+ console.warn('[WebSearch] No backup provider found. Falling back to SearXNG Mesh...');
163
+ results = await searchSearxng(finalQuery, depth);
164
+ }
165
+ } else {
166
+ throw e;
167
+ }
168
+ }
169
+ } else {
170
+ results = await searchSearxng(finalQuery, depth);
171
+ }
172
+ } catch (e: any) {
173
+ return `Failed to search the web: ${e.message}`;
174
+ }
175
+
176
+ if (results.length > 0) {
177
+ searchCache.set(cacheKey, { data: results, timestamp: Date.now() });
178
+ } else {
179
+ return `Search Results for "${query}": No results found.`;
180
+ }
181
+
182
+ let responseText = `Search Results for "${query}":\n\n`;
183
+ results.forEach((r, index) => {
184
+ responseText += `${index + 1}. ${r.title}\n`;
185
+ responseText += `URL: ${r.url}\n`;
186
+ responseText += `Snippet: ${r.content}\n\n`;
187
+ });
188
+
189
+ return responseText.trim();
28
190
  }
29
191
 
30
192
  export const searchWebToolDefinition = {
31
193
  type: "function",
32
194
  function: {
33
195
  name: "search_web",
34
- description: "Searches the internet for information using a search engine. Returns top titles, snippets, and URLs. Use this to find current events, documentation, or general facts.",
196
+ description: "Searches the internet for information using a search engine. Returns top titles, snippets, and URLs. Use this to find current events, documentation, or general facts. If the user asks for deep/comprehensive research, pass depth: 2 or 3.",
35
197
  parameters: {
36
198
  type: "object",
37
199
  properties: {
38
200
  query: {
39
201
  type: "string",
40
202
  description: "The search query to look up.",
203
+ },
204
+ depth: {
205
+ type: "number",
206
+ description: "Depth of the search (1 for basic, 2 or 3 for deep comprehensive research). Default is 1.",
41
207
  }
42
208
  },
43
209
  required: ["query"],
@@ -1,5 +1,5 @@
1
1
  import { createPublicClient, http, fallback, PublicClient, Transport } from 'viem';
2
- import { mainnet, base, bsc, arbitrum, optimism, sepolia } from 'viem/chains';
2
+ import { mainnet, base, bsc, arbitrum, optimism, sepolia, polygon } from 'viem/chains';
3
3
  import { loadConfig } from '../config/parser';
4
4
 
5
5
  export const supportedChains = {
@@ -9,8 +9,10 @@ export const supportedChains = {
9
9
  arbitrum: arbitrum,
10
10
  optimism: optimism,
11
11
  sepolia: sepolia,
12
+ polygon: polygon,
12
13
  };
13
14
 
15
+ export const SUPPORTED_CHAIN_NAMES = Object.keys(supportedChains);
14
16
  export type ChainName = keyof typeof supportedChains;
15
17
 
16
18
  export function getPublicClient(chainName: ChainName): PublicClient {
@@ -53,6 +55,10 @@ export function getPublicClient(chainName: ChainName): PublicClient {
53
55
  } else if (chainName === 'sepolia') {
54
56
  transports.push(http('https://ethereum-sepolia-rpc.publicnode.com', { timeout: 5000 }));
55
57
  transports.push(http('https://rpc.sepolia.org', { timeout: 5000 }));
58
+ } else if (chainName === 'polygon') {
59
+ transports.push(http('https://polygon-rpc.publicnode.com', { timeout: 5000 }));
60
+ transports.push(http('https://polygon.llamarpc.com', { timeout: 5000 }));
61
+ transports.push(http('https://polygon-rpc.com', { timeout: 5000 }));
56
62
  }
57
63
  }
58
64
 
@@ -1,5 +1,5 @@
1
1
  import { parseUnits, formatUnits } from 'viem';
2
- import { getPublicClient, getAddress, ChainName } from '../config';
2
+ import { getPublicClient, getAddress, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { txManager } from '../../agent/transactionManager';
4
4
  import { resolveToken, ERC20_ABI } from '../utils/tokens';
5
5
 
@@ -10,6 +10,7 @@ const CHAIN_IDS: Record<ChainName, number> = {
10
10
  arbitrum: 42161,
11
11
  optimism: 10,
12
12
  sepolia: 11155111,
13
+ polygon: 137,
13
14
  };
14
15
 
15
16
  async function getLifiQuote(fromChainId: number, toChainId: number, fromToken: string, toToken: string, amountWei: string, userAddress: string, slippage: number) {
@@ -210,12 +211,12 @@ export const bridgeTokenToolDefinition = {
210
211
  properties: {
211
212
  fromChainName: {
212
213
  type: "string",
213
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
214
+ enum: SUPPORTED_CHAIN_NAMES,
214
215
  description: "The source blockchain network",
215
216
  },
216
217
  toChainName: {
217
218
  type: "string",
218
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
219
+ enum: SUPPORTED_CHAIN_NAMES,
219
220
  description: "The destination blockchain network",
220
221
  },
221
222
  fromToken: {
@@ -1,5 +1,5 @@
1
1
  import { isAddress } from 'viem';
2
- import { getPublicClient, ChainName } from '../config';
2
+ import { getPublicClient, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
 
4
4
  export async function checkAddress(chainName: ChainName, address: string): Promise<string> {
5
5
  try {
@@ -40,7 +40,7 @@ export const checkAddressToolDefinition = {
40
40
  properties: {
41
41
  chainName: {
42
42
  type: "string",
43
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
43
+ enum: SUPPORTED_CHAIN_NAMES,
44
44
  description: "The name of the blockchain to check the address on."
45
45
  },
46
46
  address: {
@@ -1,5 +1,5 @@
1
1
  import { formatEther, formatUnits } from 'viem';
2
- import { getPublicClient, ChainName } from '../config';
2
+ import { getPublicClient, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { TOKEN_MAP, ERC20_ABI } from '../utils/tokens';
4
4
 
5
5
  const portfolioCache: Record<string, { data: string, timestamp: number }> = {};
@@ -181,7 +181,7 @@ export const checkPortfolioToolDefinition = {
181
181
  properties: {
182
182
  chainName: {
183
183
  type: "string",
184
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
184
+ enum: SUPPORTED_CHAIN_NAMES,
185
185
  description: "The blockchain network",
186
186
  },
187
187
  address: {
@@ -1,4 +1,4 @@
1
- import { ChainName } from '../config';
1
+ import { ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
2
2
 
3
3
  const CHAIN_IDS: Record<ChainName, number> = {
4
4
  ethereum: 1,
@@ -7,6 +7,7 @@ const CHAIN_IDS: Record<ChainName, number> = {
7
7
  arbitrum: 42161,
8
8
  optimism: 10,
9
9
  sepolia: 11155111,
10
+ polygon: 137,
10
11
  };
11
12
 
12
13
  export async function checkTokenSecurity(chainName: ChainName, contractAddress: string): Promise<string> {
@@ -57,7 +58,7 @@ export const checkSecurityToolDefinition = {
57
58
  properties: {
58
59
  chainName: {
59
60
  type: "string",
60
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
61
+ enum: SUPPORTED_CHAIN_NAMES,
61
62
  description: "The blockchain network",
62
63
  },
63
64
  contractAddress: {
@@ -1,5 +1,5 @@
1
1
  import { parseEther } from 'viem';
2
- import { getPublicClient, getAddress, ChainName } from '../config';
2
+ import { getPublicClient, getAddress, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { txManager } from '../../agent/transactionManager';
4
4
 
5
5
  export async function prepareCustomTx(
@@ -91,7 +91,7 @@ export const customTxToolDefinition = {
91
91
  properties: {
92
92
  chainName: {
93
93
  type: "string",
94
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
94
+ enum: SUPPORTED_CHAIN_NAMES,
95
95
  description: "The blockchain network",
96
96
  },
97
97
  toAddress: {
@@ -1,5 +1,5 @@
1
1
  import { formatEther, formatUnits } from 'viem';
2
- import { getPublicClient, ChainName } from '../config';
2
+ import { getPublicClient, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { ERC20_ABI, resolveToken } from '../utils/tokens';
4
4
  import { saveTokenToWhitelist } from '../../utils/userWhitelistManager';
5
5
 
@@ -72,8 +72,8 @@ export const getBalanceToolDefinition = {
72
72
  properties: {
73
73
  chainName: {
74
74
  type: "string",
75
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
76
- description: "The name of the blockchain to check."
75
+ enum: SUPPORTED_CHAIN_NAMES,
76
+ description: "The blockchain network"
77
77
  },
78
78
  address: {
79
79
  type: "string",
@@ -1,4 +1,4 @@
1
- import { ChainName } from '../config';
1
+ import { ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
2
2
  import { resolveToken } from '../utils/tokens';
3
3
 
4
4
  export async function analyzeMarket(chainName: ChainName, tokenAddressOrSymbol: string): Promise<string> {
@@ -85,7 +85,7 @@ export const marketAnalysisToolDefinition = {
85
85
  properties: {
86
86
  chainName: {
87
87
  type: "string",
88
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
88
+ enum: SUPPORTED_CHAIN_NAMES,
89
89
  description: "The blockchain network",
90
90
  },
91
91
  tokenAddressOrSymbol: {
@@ -1,5 +1,5 @@
1
1
  import { parseAbi, parseEther } from 'viem';
2
- import { getPublicClient, getAddress, ChainName } from '../config';
2
+ import { getPublicClient, getAddress, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { txManager } from '../../agent/transactionManager';
4
4
 
5
5
  export async function prepareMintNft(
@@ -120,7 +120,7 @@ export const mintNftToolDefinition = {
120
120
  properties: {
121
121
  chainName: {
122
122
  type: "string",
123
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
123
+ enum: SUPPORTED_CHAIN_NAMES,
124
124
  description: "The blockchain network",
125
125
  },
126
126
  contractAddress: {
@@ -1,17 +1,18 @@
1
1
  import { parseUnits, formatUnits } from 'viem';
2
- import { getPublicClient, getAddress, ChainName } from '../config';
2
+ import { getPublicClient, getAddress, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { txManager } from '../../agent/transactionManager';
4
4
  import { resolveToken, ERC20_ABI } from '../utils/tokens';
5
5
  import { saveTokenToWhitelist } from '../../utils/userWhitelistManager';
6
6
  import crypto from 'crypto';
7
7
 
8
- const CHAIN_IDS: Record<ChainName, number> = {
8
+ const CHAIN_IDS: Record<string, number> = {
9
9
  ethereum: 1,
10
10
  base: 8453,
11
11
  bsc: 56,
12
12
  arbitrum: 42161,
13
13
  optimism: 10,
14
14
  sepolia: 11155111,
15
+ polygon: 137,
15
16
  };
16
17
 
17
18
  async function getLifiQuote(fromChainId: number, toChainId: number, fromToken: string, toToken: string, amountWei: string, userAddress: string, slippage: number) {
@@ -231,7 +232,7 @@ export const swapTokenToolDefinition = {
231
232
  properties: {
232
233
  chainName: {
233
234
  type: "string",
234
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
235
+ enum: SUPPORTED_CHAIN_NAMES,
235
236
  description: "The blockchain network",
236
237
  },
237
238
  fromToken: {
@@ -1,5 +1,5 @@
1
1
  import { parseEther, parseUnits } from 'viem';
2
- import { getPublicClient, getAddress, ChainName } from '../config';
2
+ import { getPublicClient, getAddress, ChainName, SUPPORTED_CHAIN_NAMES } from '../config';
3
3
  import { txManager } from '../../agent/transactionManager';
4
4
  import { resolveToken, ERC20_ABI } from '../utils/tokens';
5
5
 
@@ -120,7 +120,7 @@ export const transferToolDefinition = {
120
120
  properties: {
121
121
  chainName: {
122
122
  type: "string",
123
- enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
123
+ enum: SUPPORTED_CHAIN_NAMES,
124
124
  description: "The name of the blockchain to execute the transfer on."
125
125
  },
126
126
  toAddress: {
@@ -91,6 +91,14 @@ export const TOKEN_MAP: Record<ChainName, Record<string, `0x${string}`>> = {
91
91
  WETH: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
92
92
  USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // Circle Faucet Sepolia USDC
93
93
  USDT: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0" // Common Sepolia USDT
94
+ },
95
+ polygon: {
96
+ MATIC: "0x0000000000000000000000000000000000000000",
97
+ POL: "0x0000000000000000000000000000000000000000",
98
+ WMATIC: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
99
+ WPOL: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
100
+ USDC: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
101
+ USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
94
102
  }
95
103
  };
96
104
 
@@ -1 +1 @@
1
- :root{--bg-color: #3b4252;--bg-secondary: #434c5e;--bg-sidebar: #2e3440;--text-primary: #eceff4;--text-secondary: #d8dee9;--accent: #88c0d0;--accent-hover: #81a1c1;--glass-bg: rgba(46, 52, 64, .7);--glass-border: rgba(216, 222, 233, .1);--chat-user: #88c0d0;--chat-user-text: #2e3440;--chat-agent: #2e3440;--tool-bg: #4c566a;--success: #a3be8c;--danger: #bf616a}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background-color:var(--bg-color);color:var(--text-primary);height:100vh;overflow:hidden}#root{display:flex;height:100vh;width:100vw}.sidebar{width:280px;background-color:var(--bg-sidebar);border-right:1px solid var(--glass-border);display:flex;flex-direction:column;box-shadow:10px 0 30px -10px #00000080;z-index:100}.agent-identity-card{padding:24px;display:flex;align-items:center;gap:16px;border-bottom:1px solid rgba(255,255,255,.05);margin-bottom:8px;background:linear-gradient(180deg,rgba(59,130,246,.05) 0%,transparent 100%)}.agent-avatar{display:flex;justify-content:center;align-items:center}.agent-info{display:flex;flex-direction:column;gap:4px}.agent-name{font-size:1.1rem;font-weight:700;color:#fff;letter-spacing:-.025em}.agent-status{display:flex;align-items:center;gap:6px;font-size:.75rem;color:#4ade80;font-weight:600;letter-spacing:.05em}.status-dot{width:8px;height:8px;background-color:#4ade80;border-radius:50%;box-shadow:0 0 8px #4ade80;animation:pulse-green 2s infinite}@keyframes pulse-green{0%{transform:scale(.95);box-shadow:0 0 #4ade80b3}70%{transform:scale(1);box-shadow:0 0 0 6px #4ade8000}to{transform:scale(.95);box-shadow:0 0 #4ade8000}}.sidebar-scroll-area{flex:1;overflow-y:auto;padding-bottom:24px}.sidebar-scroll-area::-webkit-scrollbar{width:4px}.sidebar-scroll-area::-webkit-scrollbar-thumb{background:#ffffff0d}.sidebar-section{padding:24px 24px 8px;font-size:.75rem;color:#8b9bb4;font-weight:500}.sidebar-nav{display:flex;flex-direction:column;gap:4px;padding:0 16px}.nav-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:8px;color:#94a3b8;font-size:.8rem;font-weight:500;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);border:1px solid transparent}.nav-icon{transition:transform .25s cubic-bezier(.4,0,.2,1)}.nav-item:hover{background-color:#ffffff08;color:#fff;transform:translate(4px)}.nav-item:hover .nav-icon{transform:scale(1.1);color:#fff}.sessions-list{gap:0px!important;justify-content:flex-start!important;flex:none!important;height:max-content!important}.session-item{justify-content:space-between;padding:4px 10px!important;margin-bottom:2px!important;margin-top:0!important;min-height:28px!important;height:max-content!important;flex:none!important}.delete-session-btn{background:transparent;border:none;color:#bf616a;cursor:pointer;opacity:0;transition:opacity .2s ease,transform .2s ease;display:flex;align-items:center;justify-content:center;padding:4px;border-radius:6px}.delete-session-btn:hover{background:#bf616a33;transform:scale(1.1)}.session-item:hover .delete-session-btn{opacity:1}.nav-item.active{background:linear-gradient(90deg,rgba(59,130,246,.15) 0%,transparent 100%);color:#fff;border-left:3px solid var(--accent);border-radius:4px 12px 12px 4px;font-weight:600}.nav-item.active .nav-icon{color:var(--accent)}.main-content{flex:1;display:flex;flex-direction:column;background-image:radial-gradient(at 0% 0%,rgba(59,130,246,.1) 0px,transparent 50%),radial-gradient(at 100% 100%,rgba(139,92,246,.1) 0px,transparent 50%)}.topbar{height:64px;border-bottom:1px solid var(--glass-border);display:flex;align-items:center;justify-content:space-between;padding:0 24px;background:var(--bg-color)}.topbar-left{display:flex;align-items:center;gap:16px;font-size:.95rem;font-weight:500;color:var(--text-secondary)}.topbar-right{display:flex;align-items:center;gap:12px}.custom-network-selector{position:relative}.network-selector-pill{display:flex;align-items:center;gap:8px;background:#88c0d0;border:none;color:#000;font-weight:600;font-size:.85rem;border-radius:9999px;padding:8px 16px;cursor:pointer;transition:all .2s;outline:none;font-family:inherit}.network-selector-pill:hover{opacity:.9;box-shadow:0 0 10px #88c0d066}.network-selector-pill:focus-visible{box-shadow:0 0 0 2px #88c0d080}.network-icon{flex-shrink:0}.network-chevron{opacity:.7;margin-left:4px}.network-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;background:var(--bg-secondary);border:1px solid var(--glass-border);border-radius:12px;padding:6px;list-style:none;margin:0;min-width:180px;box-shadow:0 8px 24px #0006;z-index:100;animation:dropdownSlideIn .2s ease-out}@keyframes dropdownSlideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.network-dropdown-item{padding:10px 14px;border-radius:8px;cursor:pointer;font-size:.85rem;color:#e5e7eb;transition:background .1s}.network-dropdown-item:hover{background:#ffffff0d}.network-dropdown-item.active{background:#88c0d01a;color:#88c0d0;font-weight:600}.workspace-container{display:flex;height:calc(100vh - 64px);width:100%}.chat-wrapper{display:flex;flex-direction:column;padding:24px 0;height:100%}.resizer{width:6px;background:var(--glass-border);cursor:col-resize;transition:background .2s;z-index:10}.resizer:hover,.resizer:active{background:var(--accent)}.canvas-panel{flex:1;display:flex;flex-direction:column;background:var(--bg-sidebar);padding:32px;overflow-y:auto;position:relative;background-image:radial-gradient(at 50% 50%,rgba(59,130,246,.05) 0px,transparent 80%)}.canvas-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:32px;border-bottom:1px solid rgba(255,255,255,.05);padding-bottom:16px}.canvas-title{font-family:monospace;color:#94a3b8;font-size:.85rem;display:flex;align-items:center;gap:8px;text-transform:uppercase;letter-spacing:.05em}.canvas-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#475569;gap:16px}.chat-container{flex:1;overflow-y:auto;padding:0 24px;display:flex;flex-direction:column;gap:20px}.chat-container::-webkit-scrollbar{width:6px}.chat-container::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:4px}.message-wrapper{display:flex;flex-direction:column;max-width:85%;animation:fadeIn .3s ease-out forwards}.message-wrapper.agent{align-self:flex-start;flex-direction:row;align-items:flex-end;gap:8px}.message-wrapper.user{align-self:flex-end;flex-direction:row-reverse;align-items:flex-end;gap:8px}.copy-btn{background:transparent;border:none;color:var(--text-secondary);cursor:pointer;padding:6px;border-radius:6px;opacity:0;transition:opacity .2s,background .2s;display:flex;align-items:center;justify-content:center}.copy-btn:hover{background:var(--bg-secondary);color:var(--text-primary)}.message-wrapper:hover .copy-btn{opacity:1}.message-bubble{padding:14px 18px;border-radius:18px;line-height:1.6;font-size:.95rem;box-shadow:0 4px 6px -1px #0000001a;white-space:pre-wrap}.user .message-bubble{background-color:var(--chat-user);color:var(--chat-user-text);border-bottom-right-radius:4px}.agent .message-bubble{background-color:var(--chat-agent);border:1px solid var(--glass-border);border-bottom-left-radius:4px;color:var(--text-primary)}.tool-call{font-size:.85rem;color:var(--text-secondary);background:var(--tool-bg);padding:10px 14px;border-radius:12px;margin-top:10px;border:1px solid var(--glass-border);display:flex;align-items:center;gap:8px}.tool-call code{color:var(--accent);font-family:monospace}.input-area{padding:20px 24px 0}.input-form{display:flex;gap:12px;background:var(--bg-secondary);border:1px solid var(--glass-border);border-radius:16px;padding:8px;transition:all .2s}.input-form:focus-within{border-color:var(--accent);box-shadow:0 0 0 2px #3b82f633}.chat-input{flex:1;background:transparent;border:none;padding:12px 16px;color:#fff;font-size:.95rem;font-family:inherit;outline:none}.send-button{background:var(--accent);color:#fff;border:none;width:44px;height:44px;border-radius:12px;display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .2s}.voice-button{background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--glass-border);width:44px;height:44px;border-radius:12px;display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .2s}.voice-button:hover{color:#fff;border-color:#ef4444}.voice-button.listening{background:#ef444433;color:#ef4444;border-color:#ef4444;animation:pulse-red 1.5s infinite}.voice-button.active-mode{border-color:#3b82f6;color:#3b82f6}.voice-button.speaking{background:#3b82f633;color:#3b82f6;border-color:#3b82f6;animation:pulse-blue 1.5s infinite}@keyframes pulse-red{0%{box-shadow:0 0 #ef444466}70%{box-shadow:0 0 0 10px #ef444400}to{box-shadow:0 0 #ef444400}}@keyframes pulse-blue{0%{box-shadow:0 0 #3b82f666}70%{box-shadow:0 0 0 10px #3b82f600}to{box-shadow:0 0 #3b82f600}}.send-button:hover:not(:disabled){background:var(--accent-hover);transform:scale(1.05)}.send-button:disabled{opacity:.5;cursor:not-allowed}.typing-indicator{display:flex;gap:4px;padding:14px 18px;background-color:var(--chat-agent);border:1px solid var(--glass-border);border-radius:18px 18px 18px 4px;width:fit-content;align-self:flex-start}.dot{width:6px;height:6px;background:var(--text-secondary);border-radius:50%;animation:bounce 1.4s infinite ease-in-out both}.dot:nth-child(1){animation-delay:-.32s}.dot:nth-child(2){animation-delay:-.16s}@keyframes bounce{0%,80%,to{transform:scale(0)}40%{transform:scale(1)}}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.prompt-suggestions{display:flex;gap:12px;margin-bottom:12px;overflow-x:auto}.prompt-suggestions button{background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--glass-border);padding:8px 16px;border-radius:20px;font-size:.85rem;cursor:pointer;white-space:nowrap;transition:all .2s}.prompt-suggestions button:hover{border-color:var(--accent);color:var(--text-primary)}.trending-tokens{display:flex;align-items:center;gap:12px;margin-top:12px;font-size:.8rem;color:var(--text-secondary)}.token-tag{background:var(--accent);color:var(--bg-color);padding:4px 10px;border-radius:12px;font-weight:600;cursor:pointer;transition:transform .2s,filter .2s}.token-tag:hover{transform:translateY(-2px);filter:brightness(1.2)}.nord-label{display:block;font-size:.75rem;font-weight:700;color:#81a1c1;text-transform:uppercase;margin-bottom:6px;letter-spacing:.05em}.nord-pill-input{width:100%;background-color:#2e3440b3;border:1px solid rgba(216,222,233,.1);color:#88c0d0;padding:10px 20px;border-radius:9999px;font-size:.9rem;font-weight:600;outline:none;transition:all .2s ease}.nord-pill-input:focus{border-color:#88c0d0;box-shadow:0 0 10px #88c0d033}.nord-pill-input::placeholder{color:#4c566a}.nord-input{width:100%;background-color:#2e3440;border:1px solid #434c5e;color:#eceff4;padding:10px 14px;border-radius:6px;font-size:.9rem;outline:none;transition:all .2s ease}.nord-input:focus{border-color:#88c0d0;box-shadow:0 0 0 2px #88c0d033}.nord-input::placeholder{color:#4c566a}.nord-slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:6px;background:#4c566a;border-radius:3px;outline:none;margin-top:10px}.nord-slider::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:#eceff4;cursor:pointer;border:2px solid #88c0d0;transition:all .2s ease}.nord-slider::-webkit-slider-thumb:hover{transform:scale(1.1);background:#fff}.nord-slider::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:#eceff4;cursor:pointer;border:2px solid #88c0d0;transition:all .2s ease}.nord-btn-primary{background-color:#81a1c1;color:#2e3440;border:none;border-radius:6px;padding:10px 20px;font-size:.9rem;font-weight:600;cursor:pointer;transition:background-color .2s ease;display:flex;align-items:center;justify-content:center}.nord-btn-primary:hover{background-color:#88c0d0}.nord-btn-primary:disabled{background-color:#4c566a;color:#8fbcbb;cursor:not-allowed}.nord-panel-header{display:flex;align-items:center;gap:10px;margin-bottom:20px;border-bottom:1px solid rgba(216,222,233,.05);padding-bottom:12px}.nord-panel-header h3{margin:0;font-size:1.1rem;color:#eceff4;font-weight:600}.overview-container{padding:24px;overflow-y:auto;height:calc(100vh - 64px);color:#fff;font-family:Inter,sans-serif;max-width:1200px;margin:0 auto}.overview-container::-webkit-scrollbar{width:6px}.overview-container::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:4px}.overview-header h1{font-size:1.5rem;font-weight:600;margin-bottom:4px}.overview-header p{color:var(--text-secondary);font-size:.9rem;margin-bottom:24px}.panel{background:#14182099;border:1px solid rgba(255,255,255,.05);border-radius:12px;padding:20px;margin-bottom:24px}.panel-header h3{font-size:1.1rem;font-weight:600;margin-bottom:4px}.panel-header p{color:var(--text-secondary);font-size:.85rem;margin-bottom:16px}.form-group{margin-bottom:16px}.form-row{display:flex;gap:16px}.flex-1{flex:1}label{display:block;font-size:.75rem;text-transform:uppercase;color:var(--text-secondary);margin-bottom:8px;letter-spacing:.05em}input,select{width:100%;background:#0000004d;border:1px solid rgba(255,255,255,.1);padding:10px 12px;border-radius:8px;color:#fff;font-size:.9rem;font-family:monospace}input:read-only{color:var(--text-secondary)}.input-with-icon{position:relative;display:flex;align-items:center}.input-with-icon svg{position:absolute;right:12px;color:var(--text-secondary);cursor:pointer}.form-actions{display:flex;align-items:center;gap:12px;margin-top:20px}.btn-primary{background:#3b82f6;color:#fff;border:none;padding:8px 16px;border-radius:6px;font-size:.9rem;cursor:pointer}.btn-secondary{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.2);padding:8px 16px;border-radius:6px;font-size:.9rem;cursor:pointer}.action-hint{color:var(--text-secondary);font-size:.85rem;margin-left:8px}.snapshot-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}.stat-val{font-size:1.5rem;font-weight:700;margin-bottom:4px}.text-green{color:#22c55e}.stat-block p{color:var(--text-secondary);font-size:.8rem;margin-top:8px;line-height:1.4}.metrics-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:32px}.metric-card{background:#14182099;border:1px solid rgba(255,255,255,.05);border-radius:12px;padding:16px}.metric-val{font-size:1.5rem;font-weight:700;margin-bottom:4px}.metric-sub{color:var(--text-secondary);font-size:.75rem}.section-title{font-size:.75rem;text-transform:uppercase;color:var(--text-secondary);margin-bottom:12px;letter-spacing:.05em}.session-item{display:flex;justify-content:space-between;padding:16px 20px;margin-bottom:24px}.text-secondary{color:var(--text-secondary)}.attention-panel{background:#eab3081a;border:1px solid rgba(234,179,8,.2);border-radius:12px;padding:16px 20px;margin-bottom:24px;display:flex;flex-direction:column}.attention-header{display:flex;align-items:center;gap:8px;color:#eab308;margin-bottom:8px}.attention-header h4{font-size:1rem;font-weight:600}.attention-content p{font-size:.95rem;margin-bottom:4px}.attention-content span{font-size:.85rem}.logs-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.log-panel{background:#0a0c10cc;border:1px solid rgba(255,255,255,.05);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;height:300px}.log-header{padding:12px 16px;background:#ffffff0d;border-bottom:1px solid rgba(255,255,255,.05);font-size:.85rem;font-weight:600}.badge{background:#ffffff1a;padding:2px 6px;border-radius:10px;font-size:.7rem;margin-left:8px}.log-content{padding:12px 16px;overflow-y:auto;display:flex;flex-direction:column;gap:4px}.log-content code,.log-json{font-family:Consolas,Monaco,monospace;font-size:.75rem;color:#a3a3a3;line-height:1.4;word-break:break-all}.log-row{display:flex;gap:12px;font-family:Consolas,Monaco,monospace;font-size:.75rem;margin-bottom:4px}.log-time{color:#fb923c;min-width:60px}.log-msg{color:#d1d5db}.log-meta{color:#6b7280}.gateway-row{margin-bottom:2px}
1
+ :root{--bg-color: #3b4252;--bg-secondary: #434c5e;--bg-sidebar: #2e3440;--text-primary: #eceff4;--text-secondary: #d8dee9;--accent: #88c0d0;--accent-hover: #81a1c1;--glass-bg: rgba(46, 52, 64, .7);--glass-border: rgba(216, 222, 233, .1);--chat-user: #88c0d0;--chat-user-text: #2e3440;--chat-agent: #2e3440;--tool-bg: #4c566a;--success: #a3be8c;--danger: #bf616a}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background-color:var(--bg-color);color:var(--text-primary);height:100vh;overflow:hidden}#root{display:flex;height:100vh;width:100vw}.sidebar{width:280px;background-color:var(--bg-sidebar);border-right:1px solid var(--glass-border);display:flex;flex-direction:column;box-shadow:10px 0 30px -10px #00000080;z-index:100}.agent-identity-card{padding:24px;display:flex;align-items:center;gap:16px;border-bottom:1px solid rgba(255,255,255,.05);margin-bottom:8px;background:linear-gradient(180deg,rgba(59,130,246,.05) 0%,transparent 100%)}.agent-avatar{display:flex;justify-content:center;align-items:center}.agent-info{display:flex;flex-direction:column;gap:4px}.agent-name{font-size:1.1rem;font-weight:700;color:#fff;letter-spacing:-.025em}.agent-status{display:flex;align-items:center;gap:6px;font-size:.75rem;color:#4ade80;font-weight:600;letter-spacing:.05em}.status-dot{width:8px;height:8px;background-color:#4ade80;border-radius:50%;box-shadow:0 0 8px #4ade80;animation:pulse-green 2s infinite}@keyframes pulse-green{0%{transform:scale(.95);box-shadow:0 0 #4ade80b3}70%{transform:scale(1);box-shadow:0 0 0 6px #4ade8000}to{transform:scale(.95);box-shadow:0 0 #4ade8000}}.sidebar-scroll-area{flex:1;overflow-y:auto;padding-bottom:24px}.sidebar-scroll-area::-webkit-scrollbar{width:4px}.sidebar-scroll-area::-webkit-scrollbar-thumb{background:#ffffff0d}.sidebar-section{padding:24px 24px 8px;font-size:.75rem;color:#8b9bb4;font-weight:500}.sidebar-nav{display:flex;flex-direction:column;gap:4px;padding:0 16px}.nav-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:8px;color:#94a3b8;font-size:.8rem;font-weight:500;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);border:1px solid transparent}.nav-icon{transition:transform .25s cubic-bezier(.4,0,.2,1)}.nav-item:hover{background-color:#ffffff08;color:#fff;transform:translate(4px)}.nav-item:hover .nav-icon{transform:scale(1.1);color:#fff}.sessions-list{gap:0px!important;justify-content:flex-start!important;flex:none!important;height:max-content!important}.session-item{justify-content:space-between;padding:4px 10px!important;margin-bottom:2px!important;margin-top:0!important;min-height:28px!important;height:max-content!important;flex:none!important}.delete-session-btn{background:transparent;border:none;color:#bf616a;cursor:pointer;opacity:0;transition:opacity .2s ease,transform .2s ease;display:flex;align-items:center;justify-content:center;padding:4px;border-radius:6px}.delete-session-btn:hover{background:#bf616a33;transform:scale(1.1)}.session-item:hover .delete-session-btn{opacity:1}.nav-item.active{background:linear-gradient(90deg,rgba(59,130,246,.15) 0%,transparent 100%);color:#fff;border-left:3px solid var(--accent);border-radius:4px 12px 12px 4px;font-weight:600}.nav-item.active .nav-icon{color:var(--accent)}.main-content{flex:1;display:flex;flex-direction:column;background-image:radial-gradient(at 0% 0%,rgba(59,130,246,.1) 0px,transparent 50%),radial-gradient(at 100% 100%,rgba(139,92,246,.1) 0px,transparent 50%)}.topbar{height:64px;border-bottom:1px solid var(--glass-border);display:flex;align-items:center;justify-content:space-between;padding:0 24px;background:var(--bg-color)}.topbar-left{display:flex;align-items:center;gap:16px;font-size:.95rem;font-weight:500;color:var(--text-secondary)}.topbar-right{display:flex;align-items:center;gap:12px}.custom-network-selector{position:relative}.network-selector-pill{display:flex;align-items:center;gap:8px;background:#88c0d0;border:none;color:#000;font-weight:600;font-size:.85rem;border-radius:9999px;padding:8px 16px;cursor:pointer;transition:all .2s;outline:none;font-family:inherit}.network-selector-pill:hover{opacity:.9;box-shadow:0 0 10px #88c0d066}.network-selector-pill:focus-visible{box-shadow:0 0 0 2px #88c0d080}.network-icon{flex-shrink:0}.network-chevron{opacity:.7;margin-left:4px}.network-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;background:var(--bg-secondary);border:1px solid var(--glass-border);border-radius:12px;padding:6px;list-style:none;margin:0;min-width:180px;box-shadow:0 8px 24px #0006;z-index:100;animation:dropdownSlideIn .2s ease-out}@keyframes dropdownSlideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.network-dropdown-item{padding:10px 14px;border-radius:8px;cursor:pointer;font-size:.85rem;color:#e5e7eb;transition:background .1s}.network-dropdown-item:hover{background:#ffffff0d}.network-dropdown-item.active{background:#88c0d01a;color:#88c0d0;font-weight:600}.workspace-container{display:flex;height:calc(100vh - 64px);width:100%}.chat-wrapper{display:flex;flex-direction:column;padding:24px 0;height:100%}.resizer{width:6px;background:var(--glass-border);cursor:col-resize;transition:background .2s;z-index:10}.resizer:hover,.resizer:active{background:var(--accent)}.canvas-panel{flex:1;display:flex;flex-direction:column;background:var(--bg-sidebar);padding:32px;overflow-y:auto;position:relative;background-image:radial-gradient(at 50% 50%,rgba(59,130,246,.05) 0px,transparent 80%)}.canvas-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:32px;border-bottom:1px solid rgba(255,255,255,.05);padding-bottom:16px}.canvas-title{font-family:monospace;color:#94a3b8;font-size:.85rem;display:flex;align-items:center;gap:8px;text-transform:uppercase;letter-spacing:.05em}.canvas-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#475569;gap:16px}.chat-container{flex:1;overflow-y:auto;padding:0 24px;display:flex;flex-direction:column;gap:20px}.chat-container::-webkit-scrollbar{width:6px}.chat-container::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:4px}.message-wrapper{display:flex;flex-direction:column;max-width:85%;animation:fadeIn .3s ease-out forwards}.message-wrapper.agent{align-self:flex-start;flex-direction:row;align-items:flex-end;gap:8px}.message-wrapper.user{align-self:flex-end;flex-direction:row-reverse;align-items:flex-end;gap:8px}.copy-btn{background:transparent;border:none;color:var(--text-secondary);cursor:pointer;padding:6px;border-radius:6px;opacity:0;transition:opacity .2s,background .2s;display:flex;align-items:center;justify-content:center}.copy-btn:hover{background:var(--bg-secondary);color:var(--text-primary)}.message-wrapper:hover .copy-btn{opacity:1}.message-bubble{padding:14px 18px;border-radius:18px;line-height:1.6;font-size:.95rem;box-shadow:0 4px 6px -1px #0000001a;white-space:pre-wrap}.user .message-bubble{background-color:var(--chat-user);color:var(--chat-user-text);border-bottom-right-radius:4px}.agent .message-bubble{background-color:var(--chat-agent);border:1px solid var(--glass-border);border-bottom-left-radius:4px;color:var(--text-primary)}.tool-call{font-size:.85rem;color:var(--text-secondary);background:var(--tool-bg);padding:10px 14px;border-radius:12px;margin-top:10px;border:1px solid var(--glass-border);display:flex;align-items:center;gap:8px}.tool-call code{color:var(--accent);font-family:monospace}.input-area{padding:20px 24px 0}.input-form{display:flex;gap:12px;background:var(--bg-secondary);border:1px solid var(--glass-border);border-radius:16px;padding:8px;transition:all .2s}.input-form:focus-within{border-color:var(--accent);box-shadow:0 0 0 2px #3b82f633}.chat-input{flex:1;background:transparent;border:none;padding:12px 16px;color:#fff;font-size:.95rem;font-family:inherit;outline:none}.send-button{background:var(--accent);color:#fff;border:none;width:44px;height:44px;border-radius:12px;display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .2s}.voice-button{background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--glass-border);width:44px;height:44px;border-radius:12px;display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .2s}.voice-button:hover{color:#fff;border-color:#ef4444}.voice-button.listening{background:#ef444433;color:#ef4444;border-color:#ef4444;animation:pulse-red 1.5s infinite}.voice-button.active-mode{border-color:#3b82f6;color:#3b82f6}.voice-button.speaking{background:#3b82f633;color:#3b82f6;border-color:#3b82f6;animation:pulse-blue 1.5s infinite}@keyframes pulse-red{0%{box-shadow:0 0 #ef444466}70%{box-shadow:0 0 0 10px #ef444400}to{box-shadow:0 0 #ef444400}}@keyframes pulse-blue{0%{box-shadow:0 0 #3b82f666}70%{box-shadow:0 0 0 10px #3b82f600}to{box-shadow:0 0 #3b82f600}}.send-button:hover:not(:disabled){background:var(--accent-hover);transform:scale(1.05)}.send-button:disabled{opacity:.5;cursor:not-allowed}.typing-indicator{display:flex;gap:4px;padding:14px 18px;background-color:var(--chat-agent);border:1px solid var(--glass-border);border-radius:18px 18px 18px 4px;width:fit-content;align-self:flex-start}.dot{width:6px;height:6px;background:var(--text-secondary);border-radius:50%;animation:bounce 1.4s infinite ease-in-out both}.dot:nth-child(1){animation-delay:-.32s}.dot:nth-child(2){animation-delay:-.16s}@keyframes bounce{0%,80%,to{transform:scale(0)}40%{transform:scale(1)}}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.prompt-suggestions{display:flex;gap:12px;margin-bottom:12px;overflow-x:auto}.prompt-suggestions button{background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--glass-border);padding:8px 16px;border-radius:20px;font-size:.85rem;cursor:pointer;white-space:nowrap;transition:all .2s}.prompt-suggestions button:hover{border-color:var(--accent);color:var(--text-primary)}.trending-tokens{display:flex;align-items:center;gap:12px;margin-top:12px;font-size:.8rem;color:var(--text-secondary)}.token-tag{background:var(--accent);color:var(--bg-color);padding:4px 10px;border-radius:12px;font-weight:600;cursor:pointer;transition:transform .2s,filter .2s}.token-tag:hover{transform:translateY(-2px);filter:brightness(1.2)}.nord-label{display:block;font-size:.75rem;font-weight:700;color:#81a1c1;text-transform:uppercase;margin-bottom:6px;letter-spacing:.05em}.nord-pill-input{width:100%;background-color:#2e3440b3;border:1px solid rgba(216,222,233,.1);color:#88c0d0;padding:10px 20px;border-radius:9999px;font-size:.9rem;font-weight:600;outline:none;transition:all .2s ease}.nord-pill-input:focus{border-color:#88c0d0;box-shadow:0 0 10px #88c0d033}.nord-pill-input::placeholder{color:#4c566a}.nord-input{width:100%;background-color:#2e3440;border:1px solid #434c5e;color:#eceff4;padding:10px 14px;border-radius:6px;font-size:.9rem;outline:none;transition:all .2s ease}.nord-input:focus{border-color:#88c0d0;box-shadow:0 0 0 2px #88c0d033}.nord-input::placeholder{color:#4c566a}.nord-slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:6px;background:#4c566a;border-radius:3px;outline:none;margin-top:10px}.nord-slider::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:#eceff4;cursor:pointer;border:2px solid #88c0d0;transition:all .2s ease}.nord-slider::-webkit-slider-thumb:hover{transform:scale(1.1);background:#fff}.nord-slider::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:#eceff4;cursor:pointer;border:2px solid #88c0d0;transition:all .2s ease}.nord-btn-primary{background-color:#81a1c1;color:#2e3440;border:none;border-radius:6px;padding:10px 20px;font-size:.9rem;font-weight:600;cursor:pointer;transition:background-color .2s ease;display:flex;align-items:center;justify-content:center}.nord-btn-primary:hover{background-color:#88c0d0}.nord-btn-primary:disabled{background-color:#4c566a;color:#8fbcbb;cursor:not-allowed}.nord-panel-header{display:flex;align-items:center;gap:10px;margin-bottom:20px;border-bottom:1px solid rgba(216,222,233,.05);padding-bottom:12px}.nord-panel-header h3{margin:0;font-size:1.1rem;color:#eceff4;font-weight:600}.overview-container{padding:24px;overflow-y:auto;height:calc(100vh - 64px);color:#fff;font-family:Inter,sans-serif;max-width:1200px;margin:0 auto}.overview-container::-webkit-scrollbar{width:6px}.overview-container::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:4px}.overview-header h1{font-size:1.5rem;font-weight:600;margin-bottom:4px}.overview-header p{color:var(--text-secondary);font-size:.9rem;margin-bottom:24px}.panel{background:#14182099;border:1px solid rgba(255,255,255,.05);border-radius:12px;padding:20px;margin-bottom:24px}.panel-header h3{font-size:1.1rem;font-weight:600;margin-bottom:4px}.panel-header p{color:var(--text-secondary);font-size:.85rem;margin-bottom:16px}.form-group{margin-bottom:16px}.form-row{display:flex;gap:16px}.flex-1{flex:1}label{display:block;font-size:.75rem;text-transform:uppercase;color:var(--text-secondary);margin-bottom:8px;letter-spacing:.05em}input,select{width:100%;background:#0000004d;border:1px solid rgba(255,255,255,.1);padding:10px 12px;border-radius:8px;color:#fff;font-size:.9rem;font-family:monospace}input:read-only{color:var(--text-secondary)}.input-with-icon{position:relative;display:flex;align-items:center}.input-with-icon svg{position:absolute;right:12px;color:var(--text-secondary);cursor:pointer}.form-actions{display:flex;align-items:center;gap:12px;margin-top:20px}.btn-primary{background:#3b82f6;color:#fff;border:none;padding:8px 16px;border-radius:6px;font-size:.9rem;cursor:pointer}.btn-secondary{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.2);padding:8px 16px;border-radius:6px;font-size:.9rem;cursor:pointer}.action-hint{color:var(--text-secondary);font-size:.85rem;margin-left:8px}.snapshot-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}.stat-val{font-size:1.5rem;font-weight:700;margin-bottom:4px}.text-green{color:#22c55e}.stat-block p{color:var(--text-secondary);font-size:.8rem;margin-top:8px;line-height:1.4}.metrics-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:32px}.metric-card{background:#14182099;border:1px solid rgba(255,255,255,.05);border-radius:12px;padding:16px}.metric-val{font-size:1.5rem;font-weight:700;margin-bottom:4px}.metric-sub{color:var(--text-secondary);font-size:.75rem}.section-title{font-size:.75rem;text-transform:uppercase;color:var(--text-secondary);margin-bottom:12px;letter-spacing:.05em}.session-item{display:flex;justify-content:space-between;padding:16px 20px;margin-bottom:24px}.text-secondary{color:var(--text-secondary)}.attention-panel{background:#eab3081a;border:1px solid rgba(234,179,8,.2);border-radius:12px;padding:16px 20px;margin-bottom:24px;display:flex;flex-direction:column}.attention-header{display:flex;align-items:center;gap:8px;color:#eab308;margin-bottom:8px}.attention-header h4{font-size:1rem;font-weight:600}.attention-content p{font-size:.95rem;margin-bottom:4px}.attention-content span{font-size:.85rem}.logs-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.log-panel{background:#0a0c10cc;border:1px solid rgba(255,255,255,.05);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;height:300px}.log-header{padding:12px 16px;background:#ffffff0d;border-bottom:1px solid rgba(255,255,255,.05);font-size:.85rem;font-weight:600}.badge{background:#ffffff1a;padding:2px 6px;border-radius:10px;font-size:.7rem;margin-left:8px}.log-content{padding:12px 16px;overflow-y:auto;overflow-x:auto;display:flex;flex-direction:column;gap:4px}.log-content::-webkit-scrollbar{width:6px;height:6px}.log-content::-webkit-scrollbar-thumb{background:#434c5e;border-radius:4px}.log-content::-webkit-scrollbar-thumb:hover{background:#4c566a}.log-content::-webkit-scrollbar-corner{background:transparent}.log-content code,.log-json{font-family:Consolas,Monaco,monospace;font-size:.75rem;color:#a3a3a3;line-height:1.4;word-break:break-all}.log-row{display:flex;gap:12px;font-family:Consolas,Monaco,monospace;font-size:.75rem;margin-bottom:4px}.log-time{color:#fb923c;min-width:60px}.log-msg{color:#d1d5db}.log-meta{color:#6b7280}.gateway-row{margin-bottom:2px}