@exagent/agent 0.1.1 → 0.1.2

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/dist/index.js CHANGED
@@ -34,14 +34,21 @@ __export(index_exports, {
34
34
  AgentRuntime: () => AgentRuntime,
35
35
  AnthropicAdapter: () => AnthropicAdapter,
36
36
  BaseLLMAdapter: () => BaseLLMAdapter,
37
+ DeepSeekAdapter: () => DeepSeekAdapter,
38
+ GoogleAdapter: () => GoogleAdapter,
39
+ GroqAdapter: () => GroqAdapter,
37
40
  LLMConfigSchema: () => LLMConfigSchema,
38
41
  LLMProviderSchema: () => LLMProviderSchema,
39
42
  MarketDataService: () => MarketDataService,
43
+ MistralAdapter: () => MistralAdapter,
40
44
  OllamaAdapter: () => OllamaAdapter,
41
45
  OpenAIAdapter: () => OpenAIAdapter,
46
+ RelayClient: () => RelayClient,
47
+ RelayConfigSchema: () => RelayConfigSchema,
42
48
  RiskManager: () => RiskManager,
43
49
  RiskUniverseSchema: () => RiskUniverseSchema,
44
50
  STRATEGY_TEMPLATES: () => STRATEGY_TEMPLATES,
51
+ TogetherAdapter: () => TogetherAdapter,
45
52
  TradeExecutor: () => TradeExecutor,
46
53
  TradingConfigSchema: () => TradingConfigSchema,
47
54
  VaultConfigSchema: () => VaultConfigSchema,
@@ -49,9 +56,12 @@ __export(index_exports, {
49
56
  VaultPolicySchema: () => VaultPolicySchema,
50
57
  createLLMAdapter: () => createLLMAdapter,
51
58
  createSampleConfig: () => createSampleConfig,
59
+ decryptEnvFile: () => decryptEnvFile,
60
+ encryptEnvFile: () => encryptEnvFile,
52
61
  getAllStrategyTemplates: () => getAllStrategyTemplates,
53
62
  getStrategyTemplate: () => getStrategyTemplate,
54
63
  loadConfig: () => loadConfig,
64
+ loadSecureEnv: () => loadSecureEnv,
55
65
  loadStrategy: () => loadStrategy,
56
66
  validateConfig: () => validateConfig,
57
67
  validateStrategy: () => validateStrategy
@@ -60,6 +70,8 @@ module.exports = __toCommonJS(index_exports);
60
70
 
61
71
  // src/runtime.ts
62
72
  var import_sdk = require("@exagent/sdk");
73
+ var import_viem2 = require("viem");
74
+ var import_chains2 = require("viem/chains");
63
75
 
64
76
  // src/llm/openai.ts
65
77
  var import_openai = __toESM(require("openai"));
@@ -104,7 +116,7 @@ var OpenAIAdapter = class extends BaseLLMAdapter {
104
116
  async chat(messages) {
105
117
  try {
106
118
  const response = await this.client.chat.completions.create({
107
- model: this.config.model || "gpt-4-turbo-preview",
119
+ model: this.config.model || "gpt-4.1",
108
120
  messages: messages.map((m) => ({
109
121
  role: m.role,
110
122
  content: m.content
@@ -149,7 +161,7 @@ var AnthropicAdapter = class extends BaseLLMAdapter {
149
161
  const systemMessage = messages.find((m) => m.role === "system");
150
162
  const chatMessages = messages.filter((m) => m.role !== "system");
151
163
  const body = {
152
- model: this.config.model || "claude-3-opus-20240229",
164
+ model: this.config.model || "claude-opus-4-5-20251101",
153
165
  max_tokens: this.config.maxTokens || 4096,
154
166
  temperature: this.config.temperature,
155
167
  system: systemMessage?.content,
@@ -186,6 +198,256 @@ var AnthropicAdapter = class extends BaseLLMAdapter {
186
198
  }
187
199
  };
188
200
 
201
+ // src/llm/google.ts
202
+ var GoogleAdapter = class extends BaseLLMAdapter {
203
+ apiKey;
204
+ baseUrl;
205
+ constructor(config) {
206
+ super(config);
207
+ if (!config.apiKey) {
208
+ throw new Error("Google AI API key required");
209
+ }
210
+ this.apiKey = config.apiKey;
211
+ this.baseUrl = config.endpoint || "https://generativelanguage.googleapis.com/v1beta";
212
+ }
213
+ async chat(messages) {
214
+ const model = this.config.model || "gemini-2.5-flash";
215
+ const systemMessage = messages.find((m) => m.role === "system");
216
+ const chatMessages = messages.filter((m) => m.role !== "system");
217
+ const contents = chatMessages.map((m) => ({
218
+ role: m.role === "assistant" ? "model" : "user",
219
+ parts: [{ text: m.content }]
220
+ }));
221
+ const body = {
222
+ contents,
223
+ generationConfig: {
224
+ temperature: this.config.temperature,
225
+ maxOutputTokens: this.config.maxTokens || 4096
226
+ }
227
+ };
228
+ if (systemMessage) {
229
+ body.systemInstruction = {
230
+ parts: [{ text: systemMessage.content }]
231
+ };
232
+ }
233
+ const url = `${this.baseUrl}/models/${model}:generateContent?key=${this.apiKey}`;
234
+ const response = await fetch(url, {
235
+ method: "POST",
236
+ headers: {
237
+ "Content-Type": "application/json"
238
+ },
239
+ body: JSON.stringify(body)
240
+ });
241
+ if (!response.ok) {
242
+ const error = await response.text();
243
+ throw new Error(`Google AI API error: ${response.status} - ${error}`);
244
+ }
245
+ const data = await response.json();
246
+ const candidate = data.candidates?.[0];
247
+ if (!candidate?.content?.parts) {
248
+ throw new Error("No response from Google AI");
249
+ }
250
+ const content = candidate.content.parts.map((part) => part.text || "").join("");
251
+ const usageMetadata = data.usageMetadata;
252
+ return {
253
+ content,
254
+ usage: usageMetadata ? {
255
+ promptTokens: usageMetadata.promptTokenCount || 0,
256
+ completionTokens: usageMetadata.candidatesTokenCount || 0,
257
+ totalTokens: usageMetadata.totalTokenCount || 0
258
+ } : void 0
259
+ };
260
+ }
261
+ };
262
+
263
+ // src/llm/deepseek.ts
264
+ var import_openai2 = __toESM(require("openai"));
265
+ var DeepSeekAdapter = class extends BaseLLMAdapter {
266
+ client;
267
+ constructor(config) {
268
+ super(config);
269
+ if (!config.apiKey) {
270
+ throw new Error("DeepSeek API key required");
271
+ }
272
+ this.client = new import_openai2.default({
273
+ apiKey: config.apiKey,
274
+ baseURL: config.endpoint || "https://api.deepseek.com/v1"
275
+ });
276
+ }
277
+ async chat(messages) {
278
+ try {
279
+ const response = await this.client.chat.completions.create({
280
+ model: this.config.model || "deepseek-chat",
281
+ messages: messages.map((m) => ({
282
+ role: m.role,
283
+ content: m.content
284
+ })),
285
+ temperature: this.config.temperature,
286
+ max_tokens: this.config.maxTokens
287
+ });
288
+ const choice = response.choices[0];
289
+ if (!choice || !choice.message) {
290
+ throw new Error("No response from DeepSeek");
291
+ }
292
+ return {
293
+ content: choice.message.content || "",
294
+ usage: response.usage ? {
295
+ promptTokens: response.usage.prompt_tokens,
296
+ completionTokens: response.usage.completion_tokens,
297
+ totalTokens: response.usage.total_tokens
298
+ } : void 0
299
+ };
300
+ } catch (error) {
301
+ if (error instanceof import_openai2.default.APIError) {
302
+ throw new Error(`DeepSeek API error: ${error.message}`);
303
+ }
304
+ throw error;
305
+ }
306
+ }
307
+ };
308
+
309
+ // src/llm/mistral.ts
310
+ var MistralAdapter = class extends BaseLLMAdapter {
311
+ apiKey;
312
+ baseUrl;
313
+ constructor(config) {
314
+ super(config);
315
+ if (!config.apiKey) {
316
+ throw new Error("Mistral API key required");
317
+ }
318
+ this.apiKey = config.apiKey;
319
+ this.baseUrl = config.endpoint || "https://api.mistral.ai/v1";
320
+ }
321
+ async chat(messages) {
322
+ const body = {
323
+ model: this.config.model || "mistral-large-latest",
324
+ messages: messages.map((m) => ({
325
+ role: m.role,
326
+ content: m.content
327
+ })),
328
+ temperature: this.config.temperature,
329
+ max_tokens: this.config.maxTokens
330
+ };
331
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
332
+ method: "POST",
333
+ headers: {
334
+ "Content-Type": "application/json",
335
+ Authorization: `Bearer ${this.apiKey}`
336
+ },
337
+ body: JSON.stringify(body)
338
+ });
339
+ if (!response.ok) {
340
+ const error = await response.text();
341
+ throw new Error(`Mistral API error: ${response.status} - ${error}`);
342
+ }
343
+ const data = await response.json();
344
+ const choice = data.choices?.[0];
345
+ if (!choice || !choice.message) {
346
+ throw new Error("No response from Mistral");
347
+ }
348
+ return {
349
+ content: choice.message.content || "",
350
+ usage: data.usage ? {
351
+ promptTokens: data.usage.prompt_tokens,
352
+ completionTokens: data.usage.completion_tokens,
353
+ totalTokens: data.usage.total_tokens
354
+ } : void 0
355
+ };
356
+ }
357
+ };
358
+
359
+ // src/llm/groq.ts
360
+ var import_openai3 = __toESM(require("openai"));
361
+ var GroqAdapter = class extends BaseLLMAdapter {
362
+ client;
363
+ constructor(config) {
364
+ super(config);
365
+ if (!config.apiKey) {
366
+ throw new Error("Groq API key required");
367
+ }
368
+ this.client = new import_openai3.default({
369
+ apiKey: config.apiKey,
370
+ baseURL: config.endpoint || "https://api.groq.com/openai/v1"
371
+ });
372
+ }
373
+ async chat(messages) {
374
+ try {
375
+ const response = await this.client.chat.completions.create({
376
+ model: this.config.model || "llama-3.1-70b-versatile",
377
+ messages: messages.map((m) => ({
378
+ role: m.role,
379
+ content: m.content
380
+ })),
381
+ temperature: this.config.temperature,
382
+ max_tokens: this.config.maxTokens
383
+ });
384
+ const choice = response.choices[0];
385
+ if (!choice || !choice.message) {
386
+ throw new Error("No response from Groq");
387
+ }
388
+ return {
389
+ content: choice.message.content || "",
390
+ usage: response.usage ? {
391
+ promptTokens: response.usage.prompt_tokens,
392
+ completionTokens: response.usage.completion_tokens,
393
+ totalTokens: response.usage.total_tokens
394
+ } : void 0
395
+ };
396
+ } catch (error) {
397
+ if (error instanceof import_openai3.default.APIError) {
398
+ throw new Error(`Groq API error: ${error.message}`);
399
+ }
400
+ throw error;
401
+ }
402
+ }
403
+ };
404
+
405
+ // src/llm/together.ts
406
+ var import_openai4 = __toESM(require("openai"));
407
+ var TogetherAdapter = class extends BaseLLMAdapter {
408
+ client;
409
+ constructor(config) {
410
+ super(config);
411
+ if (!config.apiKey) {
412
+ throw new Error("Together AI API key required");
413
+ }
414
+ this.client = new import_openai4.default({
415
+ apiKey: config.apiKey,
416
+ baseURL: config.endpoint || "https://api.together.xyz/v1"
417
+ });
418
+ }
419
+ async chat(messages) {
420
+ try {
421
+ const response = await this.client.chat.completions.create({
422
+ model: this.config.model || "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
423
+ messages: messages.map((m) => ({
424
+ role: m.role,
425
+ content: m.content
426
+ })),
427
+ temperature: this.config.temperature,
428
+ max_tokens: this.config.maxTokens
429
+ });
430
+ const choice = response.choices[0];
431
+ if (!choice || !choice.message) {
432
+ throw new Error("No response from Together AI");
433
+ }
434
+ return {
435
+ content: choice.message.content || "",
436
+ usage: response.usage ? {
437
+ promptTokens: response.usage.prompt_tokens,
438
+ completionTokens: response.usage.completion_tokens,
439
+ totalTokens: response.usage.total_tokens
440
+ } : void 0
441
+ };
442
+ } catch (error) {
443
+ if (error instanceof import_openai4.default.APIError) {
444
+ throw new Error(`Together AI API error: ${error.message}`);
445
+ }
446
+ throw error;
447
+ }
448
+ }
449
+ };
450
+
189
451
  // src/llm/ollama.ts
190
452
  var OllamaAdapter = class extends BaseLLMAdapter {
191
453
  baseUrl;
@@ -218,7 +480,7 @@ var OllamaAdapter = class extends BaseLLMAdapter {
218
480
  }
219
481
  async chat(messages) {
220
482
  const body = {
221
- model: this.config.model || "mistral",
483
+ model: this.config.model || "llama3.2",
222
484
  messages: messages.map((m) => ({
223
485
  role: m.role,
224
486
  content: m.content
@@ -253,7 +515,7 @@ var OllamaAdapter = class extends BaseLLMAdapter {
253
515
  getMetadata() {
254
516
  return {
255
517
  provider: "ollama",
256
- model: this.config.model || "mistral",
518
+ model: this.config.model || "llama3.2",
257
519
  isLocal: true
258
520
  };
259
521
  }
@@ -266,6 +528,16 @@ async function createLLMAdapter(config) {
266
528
  return new OpenAIAdapter(config);
267
529
  case "anthropic":
268
530
  return new AnthropicAdapter(config);
531
+ case "google":
532
+ return new GoogleAdapter(config);
533
+ case "deepseek":
534
+ return new DeepSeekAdapter(config);
535
+ case "mistral":
536
+ return new MistralAdapter(config);
537
+ case "groq":
538
+ return new GroqAdapter(config);
539
+ case "together":
540
+ return new TogetherAdapter(config);
269
541
  case "ollama":
270
542
  const adapter = new OllamaAdapter(config);
271
543
  await adapter.healthCheck();
@@ -319,7 +591,7 @@ async function loadStrategy(strategyPath) {
319
591
  console.log("No custom strategy found, using default (hold) strategy");
320
592
  return defaultStrategy;
321
593
  }
322
- async function loadTypeScriptModule(path) {
594
+ async function loadTypeScriptModule(path2) {
323
595
  try {
324
596
  const tsxPath = require.resolve("tsx");
325
597
  const { pathToFileURL } = await import("url");
@@ -330,7 +602,7 @@ async function loadTypeScriptModule(path) {
330
602
  "--import",
331
603
  "tsx/esm",
332
604
  "-e",
333
- `import('${pathToFileURL(path).href}').then(m => console.log(JSON.stringify({ exports: Object.keys(m) }))).catch(e => console.error('ERROR:', e.message))`
605
+ `import('${pathToFileURL(path2).href}').then(m => console.log(JSON.stringify({ exports: Object.keys(m) }))).catch(e => console.error('ERROR:', e.message))`
334
606
  ],
335
607
  {
336
608
  cwd: process.cwd(),
@@ -353,7 +625,7 @@ async function loadTypeScriptModule(path) {
353
625
  const tsx = await import("tsx/esm/api");
354
626
  const unregister = tsx.register();
355
627
  try {
356
- const module2 = await import(path);
628
+ const module2 = await import(path2);
357
629
  return module2;
358
630
  } finally {
359
631
  unregister();
@@ -1153,7 +1425,268 @@ var VaultManager = class {
1153
1425
  }
1154
1426
  };
1155
1427
 
1428
+ // src/relay.ts
1429
+ var import_ws = __toESM(require("ws"));
1430
+ var import_accounts2 = require("viem/accounts");
1431
+ var RelayClient = class {
1432
+ config;
1433
+ ws = null;
1434
+ authenticated = false;
1435
+ reconnectAttempts = 0;
1436
+ maxReconnectAttempts = 50;
1437
+ reconnectTimer = null;
1438
+ heartbeatTimer = null;
1439
+ stopped = false;
1440
+ constructor(config) {
1441
+ this.config = config;
1442
+ }
1443
+ /**
1444
+ * Connect to the relay server
1445
+ */
1446
+ async connect() {
1447
+ if (this.stopped) return;
1448
+ const wsUrl = this.config.relay.apiUrl.replace(/^https?:\/\//, (m) => m.includes("https") ? "wss://" : "ws://").replace(/\/$/, "") + "/ws/agent";
1449
+ return new Promise((resolve, reject) => {
1450
+ try {
1451
+ this.ws = new import_ws.default(wsUrl);
1452
+ } catch (error) {
1453
+ console.error("Relay: Failed to create WebSocket:", error);
1454
+ this.scheduleReconnect();
1455
+ reject(error);
1456
+ return;
1457
+ }
1458
+ const connectTimeout = setTimeout(() => {
1459
+ if (!this.authenticated) {
1460
+ console.error("Relay: Connection timeout");
1461
+ this.ws?.close();
1462
+ this.scheduleReconnect();
1463
+ reject(new Error("Connection timeout"));
1464
+ }
1465
+ }, 15e3);
1466
+ this.ws.on("open", async () => {
1467
+ console.log("Relay: Connected, authenticating...");
1468
+ this.reconnectAttempts = 0;
1469
+ try {
1470
+ await this.authenticate();
1471
+ } catch (error) {
1472
+ console.error("Relay: Authentication failed:", error);
1473
+ this.ws?.close();
1474
+ clearTimeout(connectTimeout);
1475
+ reject(error);
1476
+ }
1477
+ });
1478
+ this.ws.on("message", (raw) => {
1479
+ try {
1480
+ const data = JSON.parse(raw.toString());
1481
+ this.handleMessage(data);
1482
+ if (data.type === "auth_success") {
1483
+ clearTimeout(connectTimeout);
1484
+ this.authenticated = true;
1485
+ this.startHeartbeat();
1486
+ console.log("Relay: Authenticated successfully");
1487
+ resolve();
1488
+ } else if (data.type === "auth_error") {
1489
+ clearTimeout(connectTimeout);
1490
+ console.error(`Relay: Auth rejected: ${data.message}`);
1491
+ reject(new Error(data.message));
1492
+ }
1493
+ } catch {
1494
+ }
1495
+ });
1496
+ this.ws.on("close", (code, reason) => {
1497
+ clearTimeout(connectTimeout);
1498
+ this.authenticated = false;
1499
+ this.stopHeartbeat();
1500
+ if (!this.stopped) {
1501
+ console.log(`Relay: Disconnected (${code}: ${reason.toString() || "unknown"})`);
1502
+ this.scheduleReconnect();
1503
+ }
1504
+ });
1505
+ this.ws.on("error", (error) => {
1506
+ if (!this.stopped) {
1507
+ console.error("Relay: WebSocket error:", error.message);
1508
+ }
1509
+ });
1510
+ });
1511
+ }
1512
+ /**
1513
+ * Authenticate with the relay server using wallet signature
1514
+ */
1515
+ async authenticate() {
1516
+ const account = (0, import_accounts2.privateKeyToAccount)(this.config.privateKey);
1517
+ const timestamp = Math.floor(Date.now() / 1e3);
1518
+ const message = `ExagentRelay:${this.config.agentId}:${timestamp}`;
1519
+ const signature = await (0, import_accounts2.signMessage)({
1520
+ message,
1521
+ privateKey: this.config.privateKey
1522
+ });
1523
+ this.send({
1524
+ type: "auth",
1525
+ agentId: this.config.agentId,
1526
+ wallet: account.address,
1527
+ timestamp,
1528
+ signature
1529
+ });
1530
+ }
1531
+ /**
1532
+ * Handle incoming messages from the relay server
1533
+ */
1534
+ handleMessage(data) {
1535
+ switch (data.type) {
1536
+ case "command":
1537
+ if (data.command && this.config.onCommand) {
1538
+ this.config.onCommand(data.command);
1539
+ }
1540
+ break;
1541
+ case "auth_success":
1542
+ case "auth_error":
1543
+ break;
1544
+ case "error":
1545
+ console.error(`Relay: Server error: ${data.message}`);
1546
+ break;
1547
+ }
1548
+ }
1549
+ /**
1550
+ * Send a status heartbeat
1551
+ */
1552
+ sendHeartbeat(status) {
1553
+ if (!this.authenticated) return;
1554
+ this.send({
1555
+ type: "heartbeat",
1556
+ agentId: this.config.agentId,
1557
+ status
1558
+ });
1559
+ }
1560
+ /**
1561
+ * Send a status update (outside of regular heartbeat)
1562
+ */
1563
+ sendStatusUpdate(status) {
1564
+ if (!this.authenticated) return;
1565
+ this.send({
1566
+ type: "status_update",
1567
+ agentId: this.config.agentId,
1568
+ status
1569
+ });
1570
+ }
1571
+ /**
1572
+ * Send a message to the command center
1573
+ */
1574
+ sendMessage(messageType, level, title, body, data) {
1575
+ if (!this.authenticated) return;
1576
+ this.send({
1577
+ type: "message",
1578
+ agentId: this.config.agentId,
1579
+ messageType,
1580
+ level,
1581
+ title,
1582
+ body,
1583
+ data
1584
+ });
1585
+ }
1586
+ /**
1587
+ * Send a command execution result
1588
+ */
1589
+ sendCommandResult(commandId, success, result) {
1590
+ if (!this.authenticated) return;
1591
+ this.send({
1592
+ type: "command_result",
1593
+ agentId: this.config.agentId,
1594
+ commandId,
1595
+ success,
1596
+ result
1597
+ });
1598
+ }
1599
+ /**
1600
+ * Start the heartbeat timer
1601
+ */
1602
+ startHeartbeat() {
1603
+ this.stopHeartbeat();
1604
+ const interval = this.config.relay.heartbeatIntervalMs || 3e4;
1605
+ this.heartbeatTimer = setInterval(() => {
1606
+ if (this.ws?.readyState === import_ws.default.OPEN) {
1607
+ this.ws.ping();
1608
+ }
1609
+ }, interval);
1610
+ }
1611
+ /**
1612
+ * Stop the heartbeat timer
1613
+ */
1614
+ stopHeartbeat() {
1615
+ if (this.heartbeatTimer) {
1616
+ clearInterval(this.heartbeatTimer);
1617
+ this.heartbeatTimer = null;
1618
+ }
1619
+ }
1620
+ /**
1621
+ * Schedule a reconnection with exponential backoff
1622
+ */
1623
+ scheduleReconnect() {
1624
+ if (this.stopped) return;
1625
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1626
+ console.error("Relay: Max reconnection attempts reached. Giving up.");
1627
+ return;
1628
+ }
1629
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
1630
+ this.reconnectAttempts++;
1631
+ console.log(
1632
+ `Relay: Reconnecting in ${delay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
1633
+ );
1634
+ this.reconnectTimer = setTimeout(() => {
1635
+ this.connect().catch(() => {
1636
+ });
1637
+ }, delay);
1638
+ }
1639
+ /**
1640
+ * Send a JSON message to the WebSocket
1641
+ */
1642
+ send(data) {
1643
+ if (this.ws?.readyState === import_ws.default.OPEN) {
1644
+ this.ws.send(JSON.stringify(data));
1645
+ }
1646
+ }
1647
+ /**
1648
+ * Check if connected and authenticated
1649
+ */
1650
+ get isConnected() {
1651
+ return this.authenticated && this.ws?.readyState === import_ws.default.OPEN;
1652
+ }
1653
+ /**
1654
+ * Disconnect and stop reconnecting
1655
+ */
1656
+ disconnect() {
1657
+ this.stopped = true;
1658
+ this.stopHeartbeat();
1659
+ if (this.reconnectTimer) {
1660
+ clearTimeout(this.reconnectTimer);
1661
+ this.reconnectTimer = null;
1662
+ }
1663
+ if (this.ws) {
1664
+ this.ws.close(1e3, "Agent shutting down");
1665
+ this.ws = null;
1666
+ }
1667
+ this.authenticated = false;
1668
+ console.log("Relay: Disconnected");
1669
+ }
1670
+ };
1671
+
1672
+ // src/browser-open.ts
1673
+ var import_child_process2 = require("child_process");
1674
+ function openBrowser(url) {
1675
+ const platform = process.platform;
1676
+ try {
1677
+ if (platform === "darwin") {
1678
+ (0, import_child_process2.exec)(`open "${url}"`);
1679
+ } else if (platform === "win32") {
1680
+ (0, import_child_process2.exec)(`start "" "${url}"`);
1681
+ } else {
1682
+ (0, import_child_process2.exec)(`xdg-open "${url}"`);
1683
+ }
1684
+ } catch {
1685
+ }
1686
+ }
1687
+
1156
1688
  // src/runtime.ts
1689
+ var FUNDS_LOW_THRESHOLD = 5e-3;
1157
1690
  var AgentRuntime = class {
1158
1691
  config;
1159
1692
  client;
@@ -1163,9 +1696,14 @@ var AgentRuntime = class {
1163
1696
  riskManager;
1164
1697
  marketData;
1165
1698
  vaultManager;
1699
+ relay = null;
1166
1700
  isRunning = false;
1701
+ mode = "idle";
1167
1702
  configHash;
1168
1703
  lastVaultCheck = 0;
1704
+ cycleCount = 0;
1705
+ lastCycleAt = 0;
1706
+ processAlive = true;
1169
1707
  VAULT_CHECK_INTERVAL = 3e5;
1170
1708
  // Check vault status every 5 minutes
1171
1709
  constructor(config) {
@@ -1197,8 +1735,44 @@ var AgentRuntime = class {
1197
1735
  this.riskManager = new RiskManager(this.config.trading);
1198
1736
  this.marketData = new MarketDataService(this.getRpcUrl());
1199
1737
  await this.initializeVaultManager();
1738
+ await this.initializeRelay();
1200
1739
  console.log("Agent initialized successfully");
1201
1740
  }
1741
+ /**
1742
+ * Initialize the relay client for command center connectivity
1743
+ */
1744
+ async initializeRelay() {
1745
+ const relayConfig = this.config.relay;
1746
+ const relayEnabled = process.env.EXAGENT_RELAY_ENABLED !== "false";
1747
+ if (!relayConfig?.enabled || !relayEnabled) {
1748
+ console.log("Relay: Disabled");
1749
+ return;
1750
+ }
1751
+ const apiUrl = process.env.EXAGENT_API_URL || relayConfig.apiUrl;
1752
+ if (!apiUrl) {
1753
+ console.log("Relay: No API URL configured, skipping");
1754
+ return;
1755
+ }
1756
+ this.relay = new RelayClient({
1757
+ agentId: String(this.config.agentId),
1758
+ privateKey: this.config.privateKey,
1759
+ relay: {
1760
+ ...relayConfig,
1761
+ apiUrl
1762
+ },
1763
+ onCommand: (cmd) => this.handleCommand(cmd)
1764
+ });
1765
+ try {
1766
+ await this.relay.connect();
1767
+ console.log("Relay: Connected to command center");
1768
+ this.sendRelayStatus();
1769
+ } catch (error) {
1770
+ console.warn(
1771
+ "Relay: Failed to connect (agent will work locally):",
1772
+ error instanceof Error ? error.message : error
1773
+ );
1774
+ }
1775
+ }
1202
1776
  /**
1203
1777
  * Initialize the vault manager based on config
1204
1778
  */
@@ -1228,7 +1802,9 @@ var AgentRuntime = class {
1228
1802
  }
1229
1803
  }
1230
1804
  /**
1231
- * Ensure the current wallet is linked to the agent
1805
+ * Ensure the current wallet is linked to the agent.
1806
+ * If the trading wallet differs from the owner, enters a recovery loop
1807
+ * that waits for the owner to link it from the website.
1232
1808
  */
1233
1809
  async ensureWalletLinked() {
1234
1810
  const agentId = BigInt(this.config.agentId);
@@ -1238,9 +1814,31 @@ var AgentRuntime = class {
1238
1814
  console.log("Wallet not linked, linking now...");
1239
1815
  const agent = await this.client.registry.getAgent(agentId);
1240
1816
  if (agent?.owner.toLowerCase() !== address.toLowerCase()) {
1241
- throw new Error(
1242
- `Cannot link wallet: ${address} is not the owner of agent ${this.config.agentId}. Owner is ${agent?.owner}`
1243
- );
1817
+ const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
1818
+ console.log("");
1819
+ console.log("=== WALLET LINKING REQUIRED ===");
1820
+ console.log("");
1821
+ console.log(` Agent owner: ${agent?.owner}`);
1822
+ console.log(` Trading wallet: ${address}`);
1823
+ console.log("");
1824
+ console.log(" Your trading wallet needs to be linked to your agent.");
1825
+ console.log(" Opening the command center in your browser...");
1826
+ console.log(` ${ccUrl}`);
1827
+ console.log("");
1828
+ openBrowser(ccUrl);
1829
+ console.log(" Waiting for wallet to be linked... (checking every 15s)");
1830
+ console.log(" Press Ctrl+C to exit.");
1831
+ console.log("");
1832
+ while (true) {
1833
+ await this.sleep(15e3);
1834
+ const linked = await this.client.registry.isLinkedWallet(agentId, address);
1835
+ if (linked) {
1836
+ console.log(" Wallet linked! Continuing setup...");
1837
+ console.log("");
1838
+ return;
1839
+ }
1840
+ process.stdout.write(".");
1841
+ }
1244
1842
  }
1245
1843
  await this.client.registry.linkOwnWallet(agentId);
1246
1844
  console.log("Wallet linked successfully");
@@ -1249,8 +1847,9 @@ var AgentRuntime = class {
1249
1847
  }
1250
1848
  }
1251
1849
  /**
1252
- * Sync the LLM config hash to chain for epoch tracking
1253
- * This ensures trades are attributed to the correct config epoch
1850
+ * Sync the LLM config hash to chain for epoch tracking.
1851
+ * If the wallet has insufficient gas, enters a recovery loop
1852
+ * that waits for the user to fund the wallet.
1254
1853
  */
1255
1854
  async syncConfigHash() {
1256
1855
  const agentId = BigInt(this.config.agentId);
@@ -1260,9 +1859,50 @@ var AgentRuntime = class {
1260
1859
  const onChainHash = await this.client.registry.getConfigHash(agentId);
1261
1860
  if (onChainHash !== this.configHash) {
1262
1861
  console.log("Config changed, updating on-chain...");
1263
- await this.client.registry.updateConfig(agentId, this.configHash);
1264
- const newEpoch = await this.client.registry.getCurrentEpoch(agentId);
1265
- console.log(`Config updated, new epoch started: ${newEpoch}`);
1862
+ try {
1863
+ await this.client.registry.updateConfig(agentId, this.configHash);
1864
+ const newEpoch = await this.client.registry.getCurrentEpoch(agentId);
1865
+ console.log(`Config updated, new epoch started: ${newEpoch}`);
1866
+ } catch (error) {
1867
+ const message = error instanceof Error ? error.message : String(error);
1868
+ if (message.includes("insufficient funds") || message.includes("gas") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
1869
+ const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
1870
+ const chain = this.config.network === "mainnet" ? import_chains2.base : import_chains2.baseSepolia;
1871
+ const publicClientInstance = (0, import_viem2.createPublicClient)({
1872
+ chain,
1873
+ transport: (0, import_viem2.http)(this.getRpcUrl())
1874
+ });
1875
+ console.log("");
1876
+ console.log("=== ETH NEEDED FOR GAS ===");
1877
+ console.log("");
1878
+ console.log(` Wallet: ${this.client.address}`);
1879
+ console.log(" Your wallet needs ETH to pay for transaction gas.");
1880
+ console.log(" Opening the command center to fund your wallet...");
1881
+ console.log(` ${ccUrl}`);
1882
+ console.log("");
1883
+ openBrowser(ccUrl);
1884
+ console.log(" Waiting for ETH... (checking every 15s)");
1885
+ console.log(" Press Ctrl+C to exit.");
1886
+ console.log("");
1887
+ while (true) {
1888
+ await this.sleep(15e3);
1889
+ const balance = await publicClientInstance.getBalance({
1890
+ address: this.client.address
1891
+ });
1892
+ if (balance > BigInt(0)) {
1893
+ console.log(" ETH detected! Retrying config update...");
1894
+ console.log("");
1895
+ await this.client.registry.updateConfig(agentId, this.configHash);
1896
+ const newEpoch = await this.client.registry.getCurrentEpoch(agentId);
1897
+ console.log(`Config updated, new epoch started: ${newEpoch}`);
1898
+ return;
1899
+ }
1900
+ process.stdout.write(".");
1901
+ }
1902
+ } else {
1903
+ throw error;
1904
+ }
1905
+ }
1266
1906
  } else {
1267
1907
  const currentEpoch = await this.client.registry.getCurrentEpoch(agentId);
1268
1908
  console.log(`Config hash matches on-chain (epoch ${currentEpoch})`);
@@ -1275,48 +1915,301 @@ var AgentRuntime = class {
1275
1915
  return this.configHash;
1276
1916
  }
1277
1917
  /**
1278
- * Start the trading loop
1918
+ * Start the agent in daemon mode.
1919
+ * The agent enters idle mode and waits for commands from the command center.
1920
+ * Trading begins only when a start_trading command is received.
1921
+ *
1922
+ * If relay is not configured, falls back to immediate trading mode.
1279
1923
  */
1280
1924
  async run() {
1281
- if (this.isRunning) {
1282
- throw new Error("Agent is already running");
1925
+ this.processAlive = true;
1926
+ if (this.relay) {
1927
+ console.log("");
1928
+ console.log("Agent is in IDLE mode. Waiting for commands from command center.");
1929
+ console.log("Visit https://exagent.io to start trading from the dashboard.");
1930
+ console.log("");
1931
+ this.mode = "idle";
1932
+ this.sendRelayStatus();
1933
+ this.relay.sendMessage(
1934
+ "system",
1935
+ "success",
1936
+ "Agent Connected",
1937
+ `${this.config.name} is online and waiting for commands.`,
1938
+ { wallet: this.client.address }
1939
+ );
1940
+ while (this.processAlive) {
1941
+ if (this.mode === "trading" && this.isRunning) {
1942
+ try {
1943
+ await this.runCycle();
1944
+ } catch (error) {
1945
+ const message = error instanceof Error ? error.message : String(error);
1946
+ console.error("Error in trading cycle:", message);
1947
+ this.relay?.sendMessage(
1948
+ "system",
1949
+ "error",
1950
+ "Cycle Error",
1951
+ message
1952
+ );
1953
+ }
1954
+ await this.sleep(this.config.trading.tradingIntervalMs);
1955
+ } else {
1956
+ this.sendRelayStatus();
1957
+ await this.sleep(3e4);
1958
+ }
1959
+ }
1960
+ } else {
1961
+ if (this.isRunning) {
1962
+ throw new Error("Agent is already running");
1963
+ }
1964
+ this.isRunning = true;
1965
+ this.mode = "trading";
1966
+ console.log("Starting trading loop...");
1967
+ console.log(`Interval: ${this.config.trading.tradingIntervalMs}ms`);
1968
+ while (this.isRunning) {
1969
+ try {
1970
+ await this.runCycle();
1971
+ } catch (error) {
1972
+ console.error("Error in trading cycle:", error);
1973
+ }
1974
+ await this.sleep(this.config.trading.tradingIntervalMs);
1975
+ }
1283
1976
  }
1284
- this.isRunning = true;
1285
- console.log("Starting trading loop...");
1286
- console.log(`Interval: ${this.config.trading.tradingIntervalMs}ms`);
1287
- while (this.isRunning) {
1288
- try {
1289
- await this.runCycle();
1290
- } catch (error) {
1291
- console.error("Error in trading cycle:", error);
1977
+ }
1978
+ /**
1979
+ * Handle a command from the command center
1980
+ */
1981
+ async handleCommand(cmd) {
1982
+ console.log(`Command received: ${cmd.type}`);
1983
+ try {
1984
+ switch (cmd.type) {
1985
+ case "start_trading":
1986
+ if (this.mode === "trading") {
1987
+ this.relay?.sendCommandResult(cmd.id, true, "Already trading");
1988
+ return;
1989
+ }
1990
+ this.mode = "trading";
1991
+ this.isRunning = true;
1992
+ console.log("Trading started via command center");
1993
+ this.relay?.sendCommandResult(cmd.id, true, "Trading started");
1994
+ this.relay?.sendMessage(
1995
+ "system",
1996
+ "success",
1997
+ "Trading Started",
1998
+ "Agent is now actively trading."
1999
+ );
2000
+ this.sendRelayStatus();
2001
+ break;
2002
+ case "stop_trading":
2003
+ if (this.mode === "idle") {
2004
+ this.relay?.sendCommandResult(cmd.id, true, "Already idle");
2005
+ return;
2006
+ }
2007
+ this.mode = "idle";
2008
+ this.isRunning = false;
2009
+ console.log("Trading stopped via command center");
2010
+ this.relay?.sendCommandResult(cmd.id, true, "Trading stopped");
2011
+ this.relay?.sendMessage(
2012
+ "system",
2013
+ "info",
2014
+ "Trading Stopped",
2015
+ "Agent is now idle. Send start_trading to resume."
2016
+ );
2017
+ this.sendRelayStatus();
2018
+ break;
2019
+ case "update_risk_params": {
2020
+ const params = cmd.params || {};
2021
+ if (params.maxPositionSizeBps !== void 0) {
2022
+ this.config.trading.maxPositionSizeBps = Number(params.maxPositionSizeBps);
2023
+ }
2024
+ if (params.maxDailyLossBps !== void 0) {
2025
+ this.config.trading.maxDailyLossBps = Number(params.maxDailyLossBps);
2026
+ }
2027
+ this.riskManager = new RiskManager(this.config.trading);
2028
+ console.log("Risk params updated via command center");
2029
+ this.relay?.sendCommandResult(cmd.id, true, "Risk params updated");
2030
+ this.relay?.sendMessage(
2031
+ "config_updated",
2032
+ "info",
2033
+ "Risk Parameters Updated",
2034
+ `Max position: ${this.config.trading.maxPositionSizeBps / 100}%, Max daily loss: ${this.config.trading.maxDailyLossBps / 100}%`
2035
+ );
2036
+ break;
2037
+ }
2038
+ case "update_trading_interval": {
2039
+ const intervalMs = Number(cmd.params?.intervalMs);
2040
+ if (intervalMs && intervalMs >= 1e3) {
2041
+ this.config.trading.tradingIntervalMs = intervalMs;
2042
+ console.log(`Trading interval updated to ${intervalMs}ms`);
2043
+ this.relay?.sendCommandResult(cmd.id, true, `Interval set to ${intervalMs}ms`);
2044
+ } else {
2045
+ this.relay?.sendCommandResult(cmd.id, false, "Invalid interval (minimum 1000ms)");
2046
+ }
2047
+ break;
2048
+ }
2049
+ case "create_vault": {
2050
+ const result = await this.createVault();
2051
+ this.relay?.sendCommandResult(
2052
+ cmd.id,
2053
+ result.success,
2054
+ result.success ? `Vault created: ${result.vaultAddress}` : result.error
2055
+ );
2056
+ if (result.success) {
2057
+ this.relay?.sendMessage(
2058
+ "vault_created",
2059
+ "success",
2060
+ "Vault Created",
2061
+ `Vault deployed at ${result.vaultAddress}`,
2062
+ { vaultAddress: result.vaultAddress }
2063
+ );
2064
+ }
2065
+ break;
2066
+ }
2067
+ case "refresh_status":
2068
+ this.sendRelayStatus();
2069
+ this.relay?.sendCommandResult(cmd.id, true, "Status refreshed");
2070
+ break;
2071
+ case "shutdown":
2072
+ console.log("Shutdown requested via command center");
2073
+ this.relay?.sendCommandResult(cmd.id, true, "Shutting down");
2074
+ this.relay?.sendMessage(
2075
+ "system",
2076
+ "info",
2077
+ "Shutting Down",
2078
+ "Agent is shutting down. Restart manually to reconnect."
2079
+ );
2080
+ await this.sleep(1e3);
2081
+ this.stop();
2082
+ break;
2083
+ default:
2084
+ console.warn(`Unknown command: ${cmd.type}`);
2085
+ this.relay?.sendCommandResult(cmd.id, false, `Unknown command: ${cmd.type}`);
1292
2086
  }
1293
- await this.sleep(this.config.trading.tradingIntervalMs);
2087
+ } catch (error) {
2088
+ const message = error instanceof Error ? error.message : String(error);
2089
+ console.error(`Command ${cmd.type} failed:`, message);
2090
+ this.relay?.sendCommandResult(cmd.id, false, message);
1294
2091
  }
1295
2092
  }
2093
+ /**
2094
+ * Send current status to the relay
2095
+ */
2096
+ sendRelayStatus() {
2097
+ if (!this.relay) return;
2098
+ const vaultConfig = this.config.vault || { policy: "disabled" };
2099
+ const status = {
2100
+ mode: this.mode,
2101
+ agentId: String(this.config.agentId),
2102
+ wallet: this.client?.address,
2103
+ cycleCount: this.cycleCount,
2104
+ lastCycleAt: this.lastCycleAt,
2105
+ tradingIntervalMs: this.config.trading.tradingIntervalMs,
2106
+ llm: {
2107
+ provider: this.config.llm.provider,
2108
+ model: this.config.llm.model || "default"
2109
+ },
2110
+ risk: this.riskManager?.getStatus() || {
2111
+ dailyPnL: 0,
2112
+ dailyLossLimit: 0,
2113
+ isLimitHit: false
2114
+ },
2115
+ vault: {
2116
+ policy: vaultConfig.policy,
2117
+ hasVault: false,
2118
+ vaultAddress: null
2119
+ }
2120
+ };
2121
+ this.relay.sendHeartbeat(status);
2122
+ }
1296
2123
  /**
1297
2124
  * Run a single trading cycle
1298
2125
  */
1299
2126
  async runCycle() {
1300
2127
  console.log(`
1301
2128
  --- Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
2129
+ this.cycleCount++;
2130
+ this.lastCycleAt = Date.now();
1302
2131
  await this.checkVaultAutoCreation();
1303
2132
  const tokens = this.config.allowedTokens || this.getDefaultTokens();
1304
2133
  const marketData = await this.marketData.fetchMarketData(this.client.address, tokens);
1305
2134
  console.log(`Portfolio value: $${marketData.portfolioValue.toFixed(2)}`);
1306
- const signals = await this.strategy(marketData, this.llm, this.config);
2135
+ this.checkFundsLow(marketData);
2136
+ let signals;
2137
+ try {
2138
+ signals = await this.strategy(marketData, this.llm, this.config);
2139
+ } catch (error) {
2140
+ const message = error instanceof Error ? error.message : String(error);
2141
+ console.error("LLM/strategy error:", message);
2142
+ this.relay?.sendMessage(
2143
+ "llm_error",
2144
+ "error",
2145
+ "Strategy Error",
2146
+ message
2147
+ );
2148
+ return;
2149
+ }
1307
2150
  console.log(`Strategy generated ${signals.length} signals`);
1308
2151
  const filteredSignals = this.riskManager.filterSignals(signals, marketData);
1309
2152
  console.log(`${filteredSignals.length} signals passed risk checks`);
2153
+ if (this.riskManager.getStatus().isLimitHit) {
2154
+ this.relay?.sendMessage(
2155
+ "risk_limit_hit",
2156
+ "warning",
2157
+ "Risk Limit Hit",
2158
+ `Daily loss limit reached: ${this.riskManager.getStatus().dailyPnL.toFixed(2)}`
2159
+ );
2160
+ }
1310
2161
  if (filteredSignals.length > 0) {
1311
2162
  const results = await this.executor.executeAll(filteredSignals);
1312
2163
  for (const result of results) {
1313
2164
  if (result.success) {
1314
2165
  console.log(`Trade executed: ${result.signal.action} - ${result.txHash}`);
2166
+ this.relay?.sendMessage(
2167
+ "trade_executed",
2168
+ "success",
2169
+ "Trade Executed",
2170
+ `${result.signal.action.toUpperCase()}: ${result.signal.reasoning || "No reason provided"}`,
2171
+ {
2172
+ action: result.signal.action,
2173
+ txHash: result.txHash,
2174
+ tokenIn: result.signal.tokenIn,
2175
+ tokenOut: result.signal.tokenOut
2176
+ }
2177
+ );
1315
2178
  } else {
1316
2179
  console.warn(`Trade failed: ${result.error}`);
2180
+ this.relay?.sendMessage(
2181
+ "trade_failed",
2182
+ "error",
2183
+ "Trade Failed",
2184
+ result.error || "Unknown error",
2185
+ { action: result.signal.action }
2186
+ );
1317
2187
  }
1318
2188
  }
1319
2189
  }
2190
+ this.sendRelayStatus();
2191
+ }
2192
+ /**
2193
+ * Check if ETH balance is below threshold and notify
2194
+ */
2195
+ checkFundsLow(marketData) {
2196
+ if (!this.relay) return;
2197
+ const wethAddress = "0x4200000000000000000000000000000000000006";
2198
+ const ethBalance = marketData.balances[wethAddress] || BigInt(0);
2199
+ const ethAmount = Number(ethBalance) / 1e18;
2200
+ if (ethAmount < FUNDS_LOW_THRESHOLD) {
2201
+ this.relay.sendMessage(
2202
+ "funds_low",
2203
+ "warning",
2204
+ "Low Funds",
2205
+ `ETH balance is ${ethAmount.toFixed(6)} ETH. Fund your trading wallet to continue trading.`,
2206
+ {
2207
+ ethBalance: ethAmount.toFixed(6),
2208
+ wallet: this.client.address,
2209
+ threshold: FUNDS_LOW_THRESHOLD
2210
+ }
2211
+ );
2212
+ }
1320
2213
  }
1321
2214
  /**
1322
2215
  * Check for vault auto-creation based on policy
@@ -1330,7 +2223,14 @@ var AgentRuntime = class {
1330
2223
  const result = await this.vaultManager.checkAndAutoCreateVault();
1331
2224
  switch (result.action) {
1332
2225
  case "created":
1333
- console.log(`\u{1F389} Vault created automatically: ${result.vaultAddress}`);
2226
+ console.log(`Vault created automatically: ${result.vaultAddress}`);
2227
+ this.relay?.sendMessage(
2228
+ "vault_created",
2229
+ "success",
2230
+ "Vault Auto-Created",
2231
+ `Vault deployed at ${result.vaultAddress}`,
2232
+ { vaultAddress: result.vaultAddress }
2233
+ );
1334
2234
  break;
1335
2235
  case "already_exists":
1336
2236
  break;
@@ -1342,11 +2242,16 @@ var AgentRuntime = class {
1342
2242
  }
1343
2243
  }
1344
2244
  /**
1345
- * Stop the trading loop
2245
+ * Stop the agent process completely
1346
2246
  */
1347
2247
  stop() {
1348
2248
  console.log("Stopping agent...");
1349
2249
  this.isRunning = false;
2250
+ this.processAlive = false;
2251
+ this.mode = "idle";
2252
+ if (this.relay) {
2253
+ this.relay.disconnect();
2254
+ }
1350
2255
  }
1351
2256
  /**
1352
2257
  * Get RPC URL based on network
@@ -1384,6 +2289,7 @@ var AgentRuntime = class {
1384
2289
  const vaultConfig = this.config.vault || { policy: "disabled" };
1385
2290
  return {
1386
2291
  isRunning: this.isRunning,
2292
+ mode: this.mode,
1387
2293
  agentId: Number(this.config.agentId),
1388
2294
  wallet: this.client?.address || "not initialized",
1389
2295
  llm: {
@@ -1397,7 +2303,11 @@ var AgentRuntime = class {
1397
2303
  hasVault: false,
1398
2304
  // Updated async via getVaultStatus
1399
2305
  vaultAddress: null
1400
- }
2306
+ },
2307
+ relay: {
2308
+ connected: this.relay?.isConnected || false
2309
+ },
2310
+ cycleCount: this.cycleCount
1401
2311
  };
1402
2312
  }
1403
2313
  /**
@@ -1476,6 +2386,11 @@ var VaultConfigSchema = import_zod.z.object({
1476
2386
  var WalletConfigSchema = import_zod.z.object({
1477
2387
  setup: WalletSetupSchema.default("provide")
1478
2388
  }).optional();
2389
+ var RelayConfigSchema = import_zod.z.object({
2390
+ enabled: import_zod.z.boolean().default(false),
2391
+ apiUrl: import_zod.z.string().url(),
2392
+ heartbeatIntervalMs: import_zod.z.number().min(5e3).default(3e4)
2393
+ }).optional();
1479
2394
  var AgentConfigSchema = import_zod.z.object({
1480
2395
  // Identity (from on-chain registration)
1481
2396
  agentId: import_zod.z.union([import_zod.z.number().positive(), import_zod.z.string()]),
@@ -1491,6 +2406,8 @@ var AgentConfigSchema = import_zod.z.object({
1491
2406
  trading: TradingConfigSchema.default({}),
1492
2407
  // Vault configuration (copy trading)
1493
2408
  vault: VaultConfigSchema.default({}),
2409
+ // Relay configuration (command center)
2410
+ relay: RelayConfigSchema,
1494
2411
  // Allowed tokens (addresses)
1495
2412
  allowedTokens: import_zod.z.array(import_zod.z.string()).optional()
1496
2413
  });
@@ -1519,6 +2436,21 @@ function loadConfig(configPath) {
1519
2436
  if (process.env.ANTHROPIC_API_KEY && config.llm.provider === "anthropic") {
1520
2437
  llmConfig.apiKey = process.env.ANTHROPIC_API_KEY;
1521
2438
  }
2439
+ if (process.env.GOOGLE_AI_API_KEY && config.llm.provider === "google") {
2440
+ llmConfig.apiKey = process.env.GOOGLE_AI_API_KEY;
2441
+ }
2442
+ if (process.env.DEEPSEEK_API_KEY && config.llm.provider === "deepseek") {
2443
+ llmConfig.apiKey = process.env.DEEPSEEK_API_KEY;
2444
+ }
2445
+ if (process.env.MISTRAL_API_KEY && config.llm.provider === "mistral") {
2446
+ llmConfig.apiKey = process.env.MISTRAL_API_KEY;
2447
+ }
2448
+ if (process.env.GROQ_API_KEY && config.llm.provider === "groq") {
2449
+ llmConfig.apiKey = process.env.GROQ_API_KEY;
2450
+ }
2451
+ if (process.env.TOGETHER_API_KEY && config.llm.provider === "together") {
2452
+ llmConfig.apiKey = process.env.TOGETHER_API_KEY;
2453
+ }
1522
2454
  if (process.env.EXAGENT_LLM_URL) {
1523
2455
  llmConfig.endpoint = process.env.EXAGENT_LLM_URL;
1524
2456
  }
@@ -1557,7 +2489,7 @@ function createSampleConfig(agentId, name) {
1557
2489
  network: "testnet",
1558
2490
  llm: {
1559
2491
  provider: "openai",
1560
- model: "gpt-4-turbo-preview",
2492
+ model: "gpt-4.1",
1561
2493
  temperature: 0.7,
1562
2494
  maxTokens: 4096
1563
2495
  },
@@ -1577,20 +2509,211 @@ function createSampleConfig(agentId, name) {
1577
2509
  }
1578
2510
  };
1579
2511
  }
2512
+
2513
+ // src/secure-env.ts
2514
+ var crypto = __toESM(require("crypto"));
2515
+ var fs = __toESM(require("fs"));
2516
+ var path = __toESM(require("path"));
2517
+ var ALGORITHM = "aes-256-gcm";
2518
+ var PBKDF2_ITERATIONS = 1e5;
2519
+ var SALT_LENGTH = 32;
2520
+ var IV_LENGTH = 16;
2521
+ var KEY_LENGTH = 32;
2522
+ var SENSITIVE_PATTERNS = [
2523
+ /PRIVATE_KEY$/i,
2524
+ /_API_KEY$/i,
2525
+ /API_KEY$/i,
2526
+ /_SECRET$/i,
2527
+ /^OPENAI_API_KEY$/i,
2528
+ /^ANTHROPIC_API_KEY$/i,
2529
+ /^GOOGLE_AI_API_KEY$/i,
2530
+ /^DEEPSEEK_API_KEY$/i,
2531
+ /^MISTRAL_API_KEY$/i,
2532
+ /^GROQ_API_KEY$/i,
2533
+ /^TOGETHER_API_KEY$/i
2534
+ ];
2535
+ function isSensitiveKey(key) {
2536
+ return SENSITIVE_PATTERNS.some((pattern) => pattern.test(key));
2537
+ }
2538
+ function deriveKey(passphrase, salt) {
2539
+ return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
2540
+ }
2541
+ function encryptValue(value, key) {
2542
+ const iv = crypto.randomBytes(IV_LENGTH);
2543
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
2544
+ let encrypted = cipher.update(value, "utf8", "hex");
2545
+ encrypted += cipher.final("hex");
2546
+ const tag = cipher.getAuthTag();
2547
+ return {
2548
+ iv: iv.toString("hex"),
2549
+ encrypted,
2550
+ tag: tag.toString("hex")
2551
+ };
2552
+ }
2553
+ function decryptValue(encrypted, key, iv, tag) {
2554
+ const decipher = crypto.createDecipheriv(
2555
+ ALGORITHM,
2556
+ key,
2557
+ Buffer.from(iv, "hex")
2558
+ );
2559
+ decipher.setAuthTag(Buffer.from(tag, "hex"));
2560
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
2561
+ decrypted += decipher.final("utf8");
2562
+ return decrypted;
2563
+ }
2564
+ function parseEnvFile(content) {
2565
+ const entries = [];
2566
+ for (const line of content.split("\n")) {
2567
+ const trimmed = line.trim();
2568
+ if (!trimmed || trimmed.startsWith("#")) continue;
2569
+ const eqIndex = trimmed.indexOf("=");
2570
+ if (eqIndex === -1) continue;
2571
+ const key = trimmed.slice(0, eqIndex).trim();
2572
+ let value = trimmed.slice(eqIndex + 1).trim();
2573
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2574
+ value = value.slice(1, -1);
2575
+ }
2576
+ if (key && value) {
2577
+ entries.push({ key, value });
2578
+ }
2579
+ }
2580
+ return entries;
2581
+ }
2582
+ function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
2583
+ if (!fs.existsSync(envPath)) {
2584
+ throw new Error(`File not found: ${envPath}`);
2585
+ }
2586
+ const content = fs.readFileSync(envPath, "utf-8");
2587
+ const entries = parseEnvFile(content);
2588
+ if (entries.length === 0) {
2589
+ throw new Error("No environment variables found in file");
2590
+ }
2591
+ const salt = crypto.randomBytes(SALT_LENGTH);
2592
+ const key = deriveKey(passphrase, salt);
2593
+ const encryptedEntries = entries.map(({ key: envKey, value }) => {
2594
+ if (isSensitiveKey(envKey)) {
2595
+ const { iv, encrypted, tag } = encryptValue(value, key);
2596
+ return {
2597
+ key: envKey,
2598
+ value: encrypted,
2599
+ encrypted: true,
2600
+ iv,
2601
+ tag
2602
+ };
2603
+ }
2604
+ return {
2605
+ key: envKey,
2606
+ value,
2607
+ encrypted: false
2608
+ };
2609
+ });
2610
+ const encryptedEnv = {
2611
+ version: 1,
2612
+ salt: salt.toString("hex"),
2613
+ entries: encryptedEntries
2614
+ };
2615
+ const encPath = envPath + ".enc";
2616
+ fs.writeFileSync(encPath, JSON.stringify(encryptedEnv, null, 2), { mode: 384 });
2617
+ if (deleteOriginal) {
2618
+ fs.unlinkSync(envPath);
2619
+ }
2620
+ const sensitiveCount = encryptedEntries.filter((e) => e.encrypted).length;
2621
+ const plainCount = encryptedEntries.filter((e) => !e.encrypted).length;
2622
+ console.log(
2623
+ `Encrypted ${sensitiveCount} sensitive values (${plainCount} non-sensitive kept as plaintext)`
2624
+ );
2625
+ return encPath;
2626
+ }
2627
+ function decryptEnvFile(encPath, passphrase) {
2628
+ if (!fs.existsSync(encPath)) {
2629
+ throw new Error(`Encrypted env file not found: ${encPath}`);
2630
+ }
2631
+ const content = JSON.parse(fs.readFileSync(encPath, "utf-8"));
2632
+ if (content.version !== 1) {
2633
+ throw new Error(`Unsupported encrypted env version: ${content.version}`);
2634
+ }
2635
+ const salt = Buffer.from(content.salt, "hex");
2636
+ const key = deriveKey(passphrase, salt);
2637
+ const result = {};
2638
+ for (const entry of content.entries) {
2639
+ if (entry.encrypted) {
2640
+ if (!entry.iv || !entry.tag) {
2641
+ throw new Error(`Missing encryption metadata for ${entry.key}`);
2642
+ }
2643
+ try {
2644
+ result[entry.key] = decryptValue(entry.value, key, entry.iv, entry.tag);
2645
+ } catch {
2646
+ throw new Error(
2647
+ `Failed to decrypt ${entry.key}. Wrong passphrase or corrupted data.`
2648
+ );
2649
+ }
2650
+ } else {
2651
+ result[entry.key] = entry.value;
2652
+ }
2653
+ }
2654
+ return result;
2655
+ }
2656
+ function loadSecureEnv(basePath, passphrase) {
2657
+ const encPath = path.join(basePath, ".env.enc");
2658
+ const envPath = path.join(basePath, ".env");
2659
+ if (fs.existsSync(encPath)) {
2660
+ if (!passphrase) {
2661
+ passphrase = process.env.EXAGENT_PASSPHRASE;
2662
+ }
2663
+ if (!passphrase) {
2664
+ console.warn("");
2665
+ console.warn("WARNING: Found .env.enc but no passphrase provided.");
2666
+ console.warn(" Set EXAGENT_PASSPHRASE environment variable or");
2667
+ console.warn(" pass --passphrase when running the agent.");
2668
+ console.warn(" Falling back to plaintext .env file.");
2669
+ console.warn("");
2670
+ } else {
2671
+ const vars = decryptEnvFile(encPath, passphrase);
2672
+ for (const [key, value] of Object.entries(vars)) {
2673
+ process.env[key] = value;
2674
+ }
2675
+ return true;
2676
+ }
2677
+ }
2678
+ if (fs.existsSync(envPath)) {
2679
+ const content = fs.readFileSync(envPath, "utf-8");
2680
+ const entries = parseEnvFile(content);
2681
+ const sensitiveKeys = entries.filter(({ key }) => isSensitiveKey(key)).map(({ key }) => key);
2682
+ if (sensitiveKeys.length > 0) {
2683
+ console.warn("");
2684
+ console.warn("WARNING: Sensitive values stored in plaintext .env file:");
2685
+ for (const key of sensitiveKeys) {
2686
+ console.warn(` - ${key}`);
2687
+ }
2688
+ console.warn("");
2689
+ console.warn(' Run "npx @exagent/agent encrypt" to secure your keys.');
2690
+ console.warn("");
2691
+ }
2692
+ return false;
2693
+ }
2694
+ return false;
2695
+ }
1580
2696
  // Annotate the CommonJS export names for ESM import in node:
1581
2697
  0 && (module.exports = {
1582
2698
  AgentConfigSchema,
1583
2699
  AgentRuntime,
1584
2700
  AnthropicAdapter,
1585
2701
  BaseLLMAdapter,
2702
+ DeepSeekAdapter,
2703
+ GoogleAdapter,
2704
+ GroqAdapter,
1586
2705
  LLMConfigSchema,
1587
2706
  LLMProviderSchema,
1588
2707
  MarketDataService,
2708
+ MistralAdapter,
1589
2709
  OllamaAdapter,
1590
2710
  OpenAIAdapter,
2711
+ RelayClient,
2712
+ RelayConfigSchema,
1591
2713
  RiskManager,
1592
2714
  RiskUniverseSchema,
1593
2715
  STRATEGY_TEMPLATES,
2716
+ TogetherAdapter,
1594
2717
  TradeExecutor,
1595
2718
  TradingConfigSchema,
1596
2719
  VaultConfigSchema,
@@ -1598,9 +2721,12 @@ function createSampleConfig(agentId, name) {
1598
2721
  VaultPolicySchema,
1599
2722
  createLLMAdapter,
1600
2723
  createSampleConfig,
2724
+ decryptEnvFile,
2725
+ encryptEnvFile,
1601
2726
  getAllStrategyTemplates,
1602
2727
  getStrategyTemplate,
1603
2728
  loadConfig,
2729
+ loadSecureEnv,
1604
2730
  loadStrategy,
1605
2731
  validateConfig,
1606
2732
  validateStrategy