@astranova-live/cli 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/astra.js +1492 -1081
- package/package.json +1 -1
package/dist/astra.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/astra.ts
|
|
4
|
-
import
|
|
4
|
+
import process4 from "process";
|
|
5
5
|
import React5 from "react";
|
|
6
6
|
import { render } from "ink";
|
|
7
7
|
import { execFileSync } from "child_process";
|
|
@@ -62,6 +62,15 @@ function pendingClaimPath(agentName) {
|
|
|
62
62
|
function epochBudgetPath(agentName) {
|
|
63
63
|
return path.join(agentDir(agentName), "epoch_budget.json");
|
|
64
64
|
}
|
|
65
|
+
function strategyPath(agentName) {
|
|
66
|
+
return path.join(agentDir(agentName), "strategy.md");
|
|
67
|
+
}
|
|
68
|
+
function autopilotLogPath(agentName) {
|
|
69
|
+
return path.join(agentDir(agentName), "autopilot.log");
|
|
70
|
+
}
|
|
71
|
+
function daemonPidPath(agentName) {
|
|
72
|
+
return path.join(agentDir(agentName), "daemon.pid");
|
|
73
|
+
}
|
|
65
74
|
function cachePath(fileName) {
|
|
66
75
|
return path.join(_root(), ".cache", fileName);
|
|
67
76
|
}
|
|
@@ -103,10 +112,11 @@ var ConfigSchema = z.object({
|
|
|
103
112
|
preferences: z.object({
|
|
104
113
|
theme: z.enum(["dark", "light"]).default("dark")
|
|
105
114
|
}).default({}),
|
|
115
|
+
// Kept as optional for backward compat — autopilot config is now per-agent in state.json.
|
|
106
116
|
autopilot: z.object({
|
|
107
117
|
mode: z.enum(["off", "semi", "full"]).default("off"),
|
|
108
118
|
intervalMs: z.number().min(6e4).max(36e5).default(3e5)
|
|
109
|
-
}).
|
|
119
|
+
}).optional()
|
|
110
120
|
});
|
|
111
121
|
var CredentialsSchema = z.object({
|
|
112
122
|
agent_name: z.string(),
|
|
@@ -130,11 +140,16 @@ var RegisterResponseSchema = z.object({
|
|
|
130
140
|
api_key: z.string(),
|
|
131
141
|
verification_code: z.string()
|
|
132
142
|
});
|
|
143
|
+
var AgentAutopilotSchema = z.object({
|
|
144
|
+
mode: z.enum(["off", "semi", "full"]).default("off"),
|
|
145
|
+
intervalMs: z.number().min(6e4).max(36e5).default(3e5)
|
|
146
|
+
});
|
|
133
147
|
var AgentStateSchema = z.object({
|
|
134
148
|
status: z.string().default("unknown"),
|
|
135
149
|
journeyStage: z.enum(["fresh", "pending", "verified", "trading", "wallet_ready", "full"]).default("fresh"),
|
|
136
150
|
createdAt: z.string().default(() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
137
|
-
verificationCode: z.string().optional()
|
|
151
|
+
verificationCode: z.string().optional(),
|
|
152
|
+
autopilot: AgentAutopilotSchema.optional()
|
|
138
153
|
});
|
|
139
154
|
var StateSchema = z.object({
|
|
140
155
|
activeAgent: z.string(),
|
|
@@ -172,13 +187,19 @@ function saveConfig(config) {
|
|
|
172
187
|
writeFileSecure(configPath(), JSON.stringify(config, null, 2));
|
|
173
188
|
}
|
|
174
189
|
function loadAutopilotConfig() {
|
|
190
|
+
const agentName = getActiveAgent();
|
|
191
|
+
if (agentName) {
|
|
192
|
+
const state = loadState();
|
|
193
|
+
const agentAutopilot = state?.agents[agentName]?.autopilot;
|
|
194
|
+
if (agentAutopilot) return agentAutopilot;
|
|
195
|
+
}
|
|
175
196
|
const config = loadConfig();
|
|
176
197
|
return config?.autopilot ?? { mode: "off", intervalMs: 3e5 };
|
|
177
198
|
}
|
|
178
199
|
function saveAutopilotConfig(autopilot) {
|
|
179
|
-
const
|
|
180
|
-
if (!
|
|
181
|
-
|
|
200
|
+
const agentName = getActiveAgent();
|
|
201
|
+
if (!agentName) return;
|
|
202
|
+
updateAgentState(agentName, { autopilot });
|
|
182
203
|
}
|
|
183
204
|
function loadState() {
|
|
184
205
|
return readJsonFile(statePath(), StateSchema);
|
|
@@ -291,6 +312,59 @@ function clearPendingClaim(agentName) {
|
|
|
291
312
|
fs2.unlinkSync(filePath);
|
|
292
313
|
}
|
|
293
314
|
}
|
|
315
|
+
function appendAutopilotLog(agentName, entry) {
|
|
316
|
+
try {
|
|
317
|
+
ensureDir(agentDir(agentName));
|
|
318
|
+
fs2.appendFileSync(autopilotLogPath(agentName), JSON.stringify(entry) + "\n", {
|
|
319
|
+
encoding: "utf-8"
|
|
320
|
+
});
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function loadAutopilotLogSince(agentName, since) {
|
|
325
|
+
const filePath = autopilotLogPath(agentName);
|
|
326
|
+
if (!fs2.existsSync(filePath)) return [];
|
|
327
|
+
try {
|
|
328
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
329
|
+
const entries = [];
|
|
330
|
+
for (const line of raw.split("\n")) {
|
|
331
|
+
if (!line.trim()) continue;
|
|
332
|
+
try {
|
|
333
|
+
const entry = JSON.parse(line);
|
|
334
|
+
if (!since || new Date(entry.ts) > since) {
|
|
335
|
+
entries.push(entry);
|
|
336
|
+
}
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return entries;
|
|
341
|
+
} catch {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function loadDaemonPid(agentName) {
|
|
346
|
+
const filePath = daemonPidPath(agentName);
|
|
347
|
+
if (!fs2.existsSync(filePath)) return null;
|
|
348
|
+
try {
|
|
349
|
+
const pid = parseInt(fs2.readFileSync(filePath, "utf-8").trim(), 10);
|
|
350
|
+
return isNaN(pid) ? null : pid;
|
|
351
|
+
} catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function saveDaemonPid(agentName, pid) {
|
|
356
|
+
ensureDir(agentDir(agentName));
|
|
357
|
+
writeFileSecure(daemonPidPath(agentName), String(pid));
|
|
358
|
+
}
|
|
359
|
+
function clearDaemonPid(agentName) {
|
|
360
|
+
const filePath = daemonPidPath(agentName);
|
|
361
|
+
if (fs2.existsSync(filePath)) {
|
|
362
|
+
try {
|
|
363
|
+
fs2.unlinkSync(filePath);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
294
368
|
function listAgents() {
|
|
295
369
|
const agentsDir = path2.join(getRoot(), "agents");
|
|
296
370
|
if (!fs2.existsSync(agentsDir)) {
|
|
@@ -1390,772 +1464,300 @@ function loadMemory(agentName) {
|
|
|
1390
1464
|
}
|
|
1391
1465
|
}
|
|
1392
1466
|
|
|
1393
|
-
// src/
|
|
1394
|
-
import
|
|
1395
|
-
import {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
return
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1467
|
+
// src/tools/strategy.ts
|
|
1468
|
+
import fs6 from "fs";
|
|
1469
|
+
import { tool as tool2 } from "ai";
|
|
1470
|
+
import { z as z3 } from "zod";
|
|
1471
|
+
var MAX_STRATEGY_CHARS = 4e3;
|
|
1472
|
+
var writeStrategyTool = tool2({
|
|
1473
|
+
description: "Save the agent's trading strategy to disk. Call this after completing the guided strategy creation conversation. Replaces the entire strategy file. Max 4000 characters.",
|
|
1474
|
+
parameters: z3.object({
|
|
1475
|
+
content: z3.string().describe(
|
|
1476
|
+
"The complete trading strategy in markdown format. Replaces the entire strategy file. Max 4000 characters. Use clear sections: approach, buy conditions, sell conditions, position sizing, risk limits."
|
|
1477
|
+
)
|
|
1478
|
+
}),
|
|
1479
|
+
execute: async ({ content }) => {
|
|
1480
|
+
const agentName = getActiveAgent();
|
|
1481
|
+
if (!agentName) return { error: "No active agent." };
|
|
1482
|
+
if (content.length > MAX_STRATEGY_CHARS) {
|
|
1483
|
+
return {
|
|
1484
|
+
error: `Strategy content too long (${content.length} chars). Maximum is ${MAX_STRATEGY_CHARS} characters. Condense it.`
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
try {
|
|
1488
|
+
ensureDir(agentDir(agentName));
|
|
1489
|
+
fs6.writeFileSync(strategyPath(agentName), content, { encoding: "utf-8", mode: 384 });
|
|
1490
|
+
return { ok: true, chars: content.length };
|
|
1491
|
+
} catch (err) {
|
|
1492
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1493
|
+
return { error: `Failed to save strategy: ${msg}` };
|
|
1494
|
+
}
|
|
1414
1495
|
}
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
// src/ui/StatusBar.tsx
|
|
1429
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1430
|
-
var POLL_INTERVAL_MS = 6e4;
|
|
1431
|
-
var StatusBar = React.memo(function StatusBar2({
|
|
1432
|
-
agentName,
|
|
1433
|
-
journeyStage,
|
|
1434
|
-
autopilotMode = "off",
|
|
1435
|
-
autopilotIntervalMs = 3e5,
|
|
1436
|
-
onEpochChange
|
|
1437
|
-
}) {
|
|
1438
|
-
const [data, setData] = useState({ market: null, portfolio: null });
|
|
1439
|
-
const mounted = useRef(true);
|
|
1440
|
-
const canFetchData = journeyStage !== "fresh" && journeyStage !== "pending";
|
|
1441
|
-
const poll = useCallback(async () => {
|
|
1442
|
-
const [marketRes, portfolioRes] = await Promise.all([
|
|
1443
|
-
fetchMarket(agentName),
|
|
1444
|
-
fetchPortfolio(agentName)
|
|
1445
|
-
]);
|
|
1446
|
-
if (!mounted.current) return;
|
|
1447
|
-
setData((prev) => ({
|
|
1448
|
-
market: marketRes ?? prev.market,
|
|
1449
|
-
portfolio: portfolioRes ?? prev.portfolio
|
|
1450
|
-
}));
|
|
1451
|
-
if (marketRes && onEpochChange) {
|
|
1452
|
-
onEpochChange(marketRes.epochId);
|
|
1496
|
+
});
|
|
1497
|
+
var readStrategyTool = tool2({
|
|
1498
|
+
description: "Read the agent's current trading strategy from disk. Use when the user asks about their strategy, or to show the strategy before editing it.",
|
|
1499
|
+
parameters: z3.object({}),
|
|
1500
|
+
execute: async () => {
|
|
1501
|
+
const agentName = getActiveAgent();
|
|
1502
|
+
if (!agentName) return { error: "No active agent." };
|
|
1503
|
+
const filePath = strategyPath(agentName);
|
|
1504
|
+
if (!fs6.existsSync(filePath)) {
|
|
1505
|
+
return {
|
|
1506
|
+
error: "No trading strategy found for this agent. Guide the user through strategy creation \u2014 suggest `/strategy setup`."
|
|
1507
|
+
};
|
|
1453
1508
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
clearInterval(interval);
|
|
1463
|
-
};
|
|
1464
|
-
}, [canFetchData, poll]);
|
|
1465
|
-
const { market, portfolio } = data;
|
|
1466
|
-
const apActive = autopilotMode !== "off";
|
|
1467
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [
|
|
1468
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1469
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "#00ff00", children: "AstraNova" }),
|
|
1470
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1471
|
-
/* @__PURE__ */ jsx(Text, { color: "#ff8800", children: agentName }),
|
|
1472
|
-
canFetchData && market && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1473
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1474
|
-
/* @__PURE__ */ jsx(Text, { color: "#ffff00", children: "$NOVA " }),
|
|
1475
|
-
/* @__PURE__ */ jsx(Text, { color: "white", children: formatPrice(market.price) }),
|
|
1476
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1477
|
-
/* @__PURE__ */ jsx(Text, { color: moodColor(market.mood), children: market.mood })
|
|
1478
|
-
] }),
|
|
1479
|
-
canFetchData && portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1480
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1481
|
-
/* @__PURE__ */ jsxs(Text, { color: "#00ffff", children: [
|
|
1482
|
-
formatNum(portfolio.cash),
|
|
1483
|
-
" $SIM"
|
|
1484
|
-
] }),
|
|
1485
|
-
portfolio.tokens > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1486
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1487
|
-
/* @__PURE__ */ jsxs(Text, { color: "#ff00ff", children: [
|
|
1488
|
-
formatNum(portfolio.tokens),
|
|
1489
|
-
" $NOVA"
|
|
1490
|
-
] })
|
|
1491
|
-
] }),
|
|
1492
|
-
portfolio.pnl !== 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1493
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1494
|
-
/* @__PURE__ */ jsxs(Text, { color: portfolio.pnl >= 0 ? "#00ff00" : "#ff4444", children: [
|
|
1495
|
-
"P&L ",
|
|
1496
|
-
portfolio.pnl >= 0 ? "+" : "",
|
|
1497
|
-
formatNum(portfolio.pnl),
|
|
1498
|
-
" (",
|
|
1499
|
-
portfolio.pnlPct >= 0 ? "+" : "",
|
|
1500
|
-
portfolio.pnlPct.toFixed(1),
|
|
1501
|
-
"%)"
|
|
1502
|
-
] })
|
|
1503
|
-
] }),
|
|
1504
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1505
|
-
/* @__PURE__ */ jsxs(Text, { color: "#e2f902", children: [
|
|
1506
|
-
"Val ",
|
|
1507
|
-
formatNum(portfolio.portfolioValue)
|
|
1508
|
-
] })
|
|
1509
|
-
] }),
|
|
1510
|
-
!canFetchData && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1511
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1512
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "pending verification" })
|
|
1513
|
-
] }),
|
|
1514
|
-
canFetchData && !market && !portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1515
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1516
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
|
|
1517
|
-
] })
|
|
1518
|
-
] }),
|
|
1519
|
-
apActive && /* @__PURE__ */ jsxs(Text, { color: "#00ff00", children: [
|
|
1520
|
-
"AP: \u25CF ",
|
|
1521
|
-
autopilotMode.toUpperCase(),
|
|
1522
|
-
" ",
|
|
1523
|
-
formatInterval(autopilotIntervalMs)
|
|
1524
|
-
] })
|
|
1525
|
-
] }) });
|
|
1509
|
+
try {
|
|
1510
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
1511
|
+
return { ok: true, content };
|
|
1512
|
+
} catch (err) {
|
|
1513
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1514
|
+
return { error: `Failed to read strategy: ${msg}` };
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1526
1517
|
});
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
if (
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
if (Math.abs(n) >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1535
|
-
if (Math.abs(n) >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
1536
|
-
return n.toLocaleString(void 0, { maximumFractionDigits: 0 });
|
|
1537
|
-
}
|
|
1538
|
-
function moodColor(mood) {
|
|
1539
|
-
switch (mood) {
|
|
1540
|
-
case "euphoria":
|
|
1541
|
-
case "bullish":
|
|
1542
|
-
return "#00ff00";
|
|
1543
|
-
case "fear":
|
|
1544
|
-
case "bearish":
|
|
1545
|
-
return "#ff4444";
|
|
1546
|
-
case "crab":
|
|
1547
|
-
return "#ffff00";
|
|
1548
|
-
default:
|
|
1549
|
-
return "white";
|
|
1518
|
+
function loadStrategy(agentName) {
|
|
1519
|
+
const filePath = strategyPath(agentName);
|
|
1520
|
+
if (!fs6.existsSync(filePath)) return "";
|
|
1521
|
+
try {
|
|
1522
|
+
return fs6.readFileSync(filePath, "utf-8");
|
|
1523
|
+
} catch {
|
|
1524
|
+
return "";
|
|
1550
1525
|
}
|
|
1551
1526
|
}
|
|
1552
|
-
async function fetchMarket(agentName) {
|
|
1553
|
-
const result = await apiCall("GET", "/api/v1/market/state", void 0, agentName);
|
|
1554
|
-
if (!result.ok) return null;
|
|
1555
|
-
const d = result.data;
|
|
1556
|
-
const m = d.market ?? d;
|
|
1557
|
-
return {
|
|
1558
|
-
price: m.price ?? 0,
|
|
1559
|
-
mood: m.mood ?? "",
|
|
1560
|
-
epochId: m.epoch?.global ?? 0
|
|
1561
|
-
};
|
|
1562
|
-
}
|
|
1563
|
-
async function fetchPortfolio(agentName) {
|
|
1564
|
-
const result = await apiCall(
|
|
1565
|
-
"GET",
|
|
1566
|
-
"/api/v1/portfolio",
|
|
1567
|
-
void 0,
|
|
1568
|
-
agentName
|
|
1569
|
-
);
|
|
1570
|
-
if (!result.ok) return null;
|
|
1571
|
-
const d = result.data;
|
|
1572
|
-
const p = d.portfolio ?? d;
|
|
1573
|
-
const cash = p.cash ?? 0;
|
|
1574
|
-
const tokens = p.tokens ?? 0;
|
|
1575
|
-
const currentPrice = p.currentPrice ?? 0;
|
|
1576
|
-
return {
|
|
1577
|
-
cash,
|
|
1578
|
-
tokens,
|
|
1579
|
-
portfolioValue: p.portfolioValue ?? cash + tokens * currentPrice,
|
|
1580
|
-
pnl: p.pnl ?? 0,
|
|
1581
|
-
pnlPct: p.pnlPct ?? 0
|
|
1582
|
-
};
|
|
1583
|
-
}
|
|
1584
1527
|
|
|
1585
|
-
// src/
|
|
1586
|
-
import
|
|
1528
|
+
// src/daemon/autopilot-worker.ts
|
|
1529
|
+
import process3 from "process";
|
|
1587
1530
|
|
|
1588
|
-
// src/
|
|
1589
|
-
import {
|
|
1531
|
+
// src/agent/loop.ts
|
|
1532
|
+
import { streamText, generateText } from "ai";
|
|
1533
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
1590
1534
|
|
|
1591
|
-
// src/
|
|
1592
|
-
import {
|
|
1593
|
-
import {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
const pnlPct = data.pnlPct ?? 0;
|
|
1601
|
-
const earned = data.totalEarned ? Number(data.totalEarned) / 1e9 : 0;
|
|
1602
|
-
const claimable = data.claimable ? Number(data.claimable) / 1e9 : 0;
|
|
1603
|
-
const pnlColor = pnl >= 0 ? "#00ff00" : "#ff4444";
|
|
1604
|
-
const pnlSign = pnl >= 0 ? "+" : "";
|
|
1605
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
|
|
1606
|
-
/* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#00ffff", children: "Portfolio Overview" }) }),
|
|
1607
|
-
/* @__PURE__ */ jsx2(Row, { label: "$SIM Balance", value: formatNum2(cash), color: "#00ffff" }),
|
|
1608
|
-
/* @__PURE__ */ jsx2(Row, { label: "$NOVA Holdings", value: tokens > 0 ? formatNum2(tokens) : "\u2014", color: "#ff00ff" }),
|
|
1609
|
-
/* @__PURE__ */ jsx2(Row, { label: "$NOVA Price", value: price > 0 ? formatPrice2(price) : "\u2014", color: "#ffff00" }),
|
|
1610
|
-
/* @__PURE__ */ jsx2(Row, { label: "Portfolio Value", value: formatNum2(value), color: "white" }),
|
|
1611
|
-
/* @__PURE__ */ jsx2(Row, { label: "P&L", value: `${pnlSign}${formatNum2(pnl)} (${pnlSign}${pnlPct.toFixed(1)}%)`, color: pnlColor }),
|
|
1612
|
-
(data.hasWallet !== void 0 || data.walletLocal !== void 0) && /* @__PURE__ */ jsx2(
|
|
1613
|
-
Row,
|
|
1614
|
-
{
|
|
1615
|
-
label: "Wallet",
|
|
1616
|
-
value: data.hasWallet ? "registered" : data.walletLocal ? "needs registration" : "not set",
|
|
1617
|
-
color: data.hasWallet ? "#00ff00" : data.walletLocal ? "#ffff00" : "gray"
|
|
1618
|
-
}
|
|
1619
|
-
),
|
|
1620
|
-
(earned > 0 || claimable > 0) && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1621
|
-
/* @__PURE__ */ jsx2(Row, { label: "$ASTRA Earned", value: earned > 0 ? formatAstra(earned) : "\u2014", color: "#ffff00" }),
|
|
1622
|
-
/* @__PURE__ */ jsx2(Row, { label: "Claimable", value: claimable > 0 ? formatAstra(claimable) : "\u2014", color: claimable > 0 ? "#00ff00" : "gray" })
|
|
1623
|
-
] })
|
|
1624
|
-
] });
|
|
1625
|
-
}
|
|
1626
|
-
function Row({ label, value, color }) {
|
|
1627
|
-
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1628
|
-
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1629
|
-
label,
|
|
1630
|
-
": "
|
|
1631
|
-
] }),
|
|
1632
|
-
/* @__PURE__ */ jsx2(Text2, { color, bold: true, children: value })
|
|
1633
|
-
] });
|
|
1634
|
-
}
|
|
1635
|
-
function formatPrice2(price) {
|
|
1636
|
-
if (price >= 1) return `$${price.toFixed(2)}`;
|
|
1637
|
-
if (price >= 0.01) return `$${price.toFixed(4)}`;
|
|
1638
|
-
return `$${price.toFixed(6)}`;
|
|
1535
|
+
// src/agent/provider.ts
|
|
1536
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
1537
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
1538
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
1539
|
+
function isCodexOAuth() {
|
|
1540
|
+
const override = process.env.ASTRA_PROVIDER;
|
|
1541
|
+
if (override) return override === "openai-oauth";
|
|
1542
|
+
const config = loadConfig();
|
|
1543
|
+
return config?.provider === "openai-oauth";
|
|
1639
1544
|
}
|
|
1640
|
-
function
|
|
1641
|
-
|
|
1642
|
-
if (
|
|
1643
|
-
|
|
1545
|
+
function isOpenAIResponses() {
|
|
1546
|
+
const override = process.env.ASTRA_PROVIDER;
|
|
1547
|
+
if (override) return override === "openai";
|
|
1548
|
+
const config = loadConfig();
|
|
1549
|
+
return config?.provider === "openai";
|
|
1644
1550
|
}
|
|
1645
|
-
function
|
|
1646
|
-
|
|
1647
|
-
if (
|
|
1648
|
-
|
|
1551
|
+
function getOpenAIApiKey() {
|
|
1552
|
+
const envKey = process.env.ASTRA_API_KEY;
|
|
1553
|
+
if (envKey) return envKey;
|
|
1554
|
+
const config = loadConfig();
|
|
1555
|
+
if (config?.auth?.type === "api-key" && config.auth.apiKey) {
|
|
1556
|
+
return config.auth.apiKey;
|
|
1557
|
+
}
|
|
1558
|
+
throw new Error(
|
|
1559
|
+
"OpenAI API key not found. Set ASTRA_API_KEY or re-run onboarding."
|
|
1560
|
+
);
|
|
1649
1561
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
const epoch = data.epochAstra ? Number(data.epochAstra) / 1e9 : 0;
|
|
1657
|
-
const bonus = data.bonusAstra ? Number(data.bonusAstra) / 1e9 : 0;
|
|
1658
|
-
const statusColor = data.claimStatus === "claimable" ? "#00ff00" : data.claimStatus === "sent" ? "#00ffff" : "#ffff00";
|
|
1659
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
|
|
1660
|
-
/* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
1661
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "#ffff00", children: "$ASTRA Rewards" }),
|
|
1662
|
-
data.seasonId && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1663
|
-
" ",
|
|
1664
|
-
data.seasonId
|
|
1665
|
-
] })
|
|
1666
|
-
] }),
|
|
1667
|
-
/* @__PURE__ */ jsx3(Row2, { label: "Total Earned", value: formatAstra2(total), color: "#ffff00" }),
|
|
1668
|
-
/* @__PURE__ */ jsx3(Row2, { label: "Epoch Rewards", value: formatAstra2(epoch), color: "#00ffff" }),
|
|
1669
|
-
/* @__PURE__ */ jsx3(Row2, { label: "Season Bonus", value: formatAstra2(bonus), color: "#ff00ff" }),
|
|
1670
|
-
/* @__PURE__ */ jsx3(Row2, { label: "Status", value: data.claimStatus ?? "\u2014", color: statusColor }),
|
|
1671
|
-
/* @__PURE__ */ jsx3(Row2, { label: "Epochs Rewarded", value: data.epochsRewarded?.toString() ?? "\u2014", color: "white" }),
|
|
1672
|
-
data.bestEpochPnl !== void 0 && data.bestEpochPnl > 0 && /* @__PURE__ */ jsx3(Row2, { label: "Best Epoch P&L", value: `+${data.bestEpochPnl.toFixed(2)}`, color: "#00ff00" }),
|
|
1673
|
-
data.txSignature && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
1674
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tx: " }),
|
|
1675
|
-
/* @__PURE__ */ jsx3(Text3, { color: "#00ffff", children: data.txSignature })
|
|
1676
|
-
] })
|
|
1677
|
-
] });
|
|
1562
|
+
async function getCodexAccessToken() {
|
|
1563
|
+
const config = loadConfig();
|
|
1564
|
+
if (!config || config.auth.type !== "oauth" || !config.auth.oauth) {
|
|
1565
|
+
throw new Error("Codex OAuth not configured. Re-run onboarding.");
|
|
1566
|
+
}
|
|
1567
|
+
return ensureFreshToken(config);
|
|
1678
1568
|
}
|
|
1679
|
-
function
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
"
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1569
|
+
async function getModel() {
|
|
1570
|
+
const config = loadConfig();
|
|
1571
|
+
if (!config) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
"No config found. Run the onboarding wizard first (delete ~/.config/astranova/config.json to re-run)."
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
const providerOverride = process.env.ASTRA_PROVIDER;
|
|
1577
|
+
const apiKeyOverride = process.env.ASTRA_API_KEY;
|
|
1578
|
+
const modelOverride = process.env.ASTRA_MODEL;
|
|
1579
|
+
if (providerOverride) {
|
|
1580
|
+
if (!apiKeyOverride) {
|
|
1581
|
+
throw new Error(
|
|
1582
|
+
`ASTRA_PROVIDER=${providerOverride} is set but ASTRA_API_KEY is missing.
|
|
1583
|
+
Export your API key: export ASTRA_API_KEY=sk-...`
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
return createModelFromConfig({
|
|
1587
|
+
...config,
|
|
1588
|
+
provider: providerOverride,
|
|
1589
|
+
model: modelOverride ?? config.model,
|
|
1590
|
+
auth: { type: "api-key", apiKey: apiKeyOverride }
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
return createModelFromConfig(config);
|
|
1687
1594
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
if (
|
|
1691
|
-
if (
|
|
1692
|
-
|
|
1595
|
+
async function ensureFreshToken(config) {
|
|
1596
|
+
const oauth = config.auth.oauth;
|
|
1597
|
+
if (!oauth) throw new Error("OAuth config missing");
|
|
1598
|
+
if (!isTokenExpired(oauth.expiresAt)) return oauth.accessToken;
|
|
1599
|
+
try {
|
|
1600
|
+
const tokens = await refreshTokens({
|
|
1601
|
+
refreshToken: oauth.refreshToken,
|
|
1602
|
+
clientId: oauth.clientId
|
|
1603
|
+
});
|
|
1604
|
+
const updatedConfig = {
|
|
1605
|
+
...config,
|
|
1606
|
+
auth: {
|
|
1607
|
+
...config.auth,
|
|
1608
|
+
oauth: {
|
|
1609
|
+
...oauth,
|
|
1610
|
+
accessToken: tokens.accessToken,
|
|
1611
|
+
refreshToken: tokens.refreshToken,
|
|
1612
|
+
expiresAt: tokens.expiresAt
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
saveConfig(updatedConfig);
|
|
1617
|
+
return tokens.accessToken;
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1620
|
+
throw new Error(
|
|
1621
|
+
`OAuth token refresh failed: ${message}
|
|
1622
|
+
Please re-run onboarding to log in again (delete ~/.config/astranova/config.json).`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1693
1625
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
let i = 0;
|
|
1701
|
-
while (i < lines.length) {
|
|
1702
|
-
const line = lines[i];
|
|
1703
|
-
if (line.trimStart().startsWith(":::portfolio") || line.trimStart().startsWith(":::rewards")) {
|
|
1704
|
-
const cardType = line.trimStart().startsWith(":::portfolio") ? "portfolio" : "rewards";
|
|
1705
|
-
const jsonLines = [];
|
|
1706
|
-
i++;
|
|
1707
|
-
while (i < lines.length && !lines[i].trimStart().startsWith(":::")) {
|
|
1708
|
-
jsonLines.push(lines[i]);
|
|
1709
|
-
i++;
|
|
1626
|
+
function createModelFromConfig(config) {
|
|
1627
|
+
const { provider, model, auth } = config;
|
|
1628
|
+
switch (provider) {
|
|
1629
|
+
case "claude": {
|
|
1630
|
+
if (auth.type !== "api-key" || !auth.apiKey) {
|
|
1631
|
+
throw new Error("Claude requires an API key. Re-run onboarding to set one up.");
|
|
1710
1632
|
}
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
} else {
|
|
1718
|
-
const data = JSON.parse(raw);
|
|
1719
|
-
elements.push(/* @__PURE__ */ jsx4(RewardsCard, { data }, elements.length));
|
|
1720
|
-
}
|
|
1721
|
-
} catch {
|
|
1722
|
-
elements.push(
|
|
1723
|
-
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: jsonLines.join("\n") }) }, elements.length)
|
|
1724
|
-
);
|
|
1633
|
+
const anthropic = createAnthropic({ apiKey: auth.apiKey });
|
|
1634
|
+
return anthropic(model);
|
|
1635
|
+
}
|
|
1636
|
+
case "openai": {
|
|
1637
|
+
if (auth.type !== "api-key" || !auth.apiKey) {
|
|
1638
|
+
throw new Error("OpenAI requires an API key. Re-run onboarding to set one up.");
|
|
1725
1639
|
}
|
|
1726
|
-
|
|
1640
|
+
const openai = createOpenAI({ apiKey: auth.apiKey });
|
|
1641
|
+
return openai(model);
|
|
1727
1642
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
while (i < lines.length && !lines[i].trimStart().startsWith("```")) {
|
|
1732
|
-
codeLines.push(lines[i]);
|
|
1733
|
-
i++;
|
|
1643
|
+
case "google": {
|
|
1644
|
+
if (auth.type !== "api-key" || !auth.apiKey) {
|
|
1645
|
+
throw new Error("Gemini requires an API key. Re-run onboarding to set one up.");
|
|
1734
1646
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, marginY: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "gray", children: codeLines.join("\n") }) }, elements.length)
|
|
1738
|
-
);
|
|
1739
|
-
continue;
|
|
1647
|
+
const google = createGoogleGenerativeAI({ apiKey: auth.apiKey });
|
|
1648
|
+
return google(model);
|
|
1740
1649
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1650
|
+
case "openai-oauth":
|
|
1651
|
+
throw new Error("Codex OAuth uses custom provider. This is a bug \u2014 please report.");
|
|
1652
|
+
case "ollama":
|
|
1653
|
+
throw new Error(
|
|
1654
|
+
"Ollama support is coming soon. Please use Claude or ChatGPT/Codex.\nTo switch, delete ~/.config/astranova/config.json and re-run astra."
|
|
1744
1655
|
);
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
const headerMatch = /^(#{1,4})\s+(.+)$/.exec(line);
|
|
1749
|
-
if (headerMatch) {
|
|
1750
|
-
elements.push(
|
|
1751
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: elements.length > 0 ? 1 : 0, children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: headerMatch[2] }) }, elements.length)
|
|
1752
|
-
);
|
|
1753
|
-
i++;
|
|
1754
|
-
continue;
|
|
1755
|
-
}
|
|
1756
|
-
const bulletMatch = /^(\s*)[-*]\s+(.+)$/.exec(line);
|
|
1757
|
-
if (bulletMatch) {
|
|
1758
|
-
const indent = Math.floor((bulletMatch[1]?.length ?? 0) / 2);
|
|
1759
|
-
elements.push(
|
|
1760
|
-
/* @__PURE__ */ jsxs4(Box4, { marginLeft: indent * 2, children: [
|
|
1761
|
-
/* @__PURE__ */ jsx4(Text4, { children: " \u25CF " }),
|
|
1762
|
-
renderInline(bulletMatch[2])
|
|
1763
|
-
] }, elements.length)
|
|
1764
|
-
);
|
|
1765
|
-
i++;
|
|
1766
|
-
continue;
|
|
1767
|
-
}
|
|
1768
|
-
const numMatch = /^(\s*)(\d+)[.)]\s+(.+)$/.exec(line);
|
|
1769
|
-
if (numMatch) {
|
|
1770
|
-
const indent = Math.floor((numMatch[1]?.length ?? 0) / 2);
|
|
1771
|
-
elements.push(
|
|
1772
|
-
/* @__PURE__ */ jsxs4(Box4, { marginLeft: indent * 2, children: [
|
|
1773
|
-
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1774
|
-
" ",
|
|
1775
|
-
numMatch[2],
|
|
1776
|
-
". "
|
|
1777
|
-
] }),
|
|
1778
|
-
renderInline(numMatch[3])
|
|
1779
|
-
] }, elements.length)
|
|
1780
|
-
);
|
|
1781
|
-
i++;
|
|
1782
|
-
continue;
|
|
1783
|
-
}
|
|
1784
|
-
if (line.trim() === "") {
|
|
1785
|
-
elements.push(/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { children: " " }) }, elements.length));
|
|
1786
|
-
i++;
|
|
1787
|
-
continue;
|
|
1788
|
-
}
|
|
1789
|
-
elements.push(
|
|
1790
|
-
/* @__PURE__ */ jsx4(Box4, { flexWrap: "wrap", children: renderInline(line) }, elements.length)
|
|
1791
|
-
);
|
|
1792
|
-
i++;
|
|
1656
|
+
default:
|
|
1657
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
1793
1658
|
}
|
|
1794
|
-
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: elements });
|
|
1795
1659
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
node: /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: codeMatch[1] }, key++)
|
|
1816
|
-
});
|
|
1817
|
-
}
|
|
1818
|
-
const italicMatch = /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/.exec(remaining);
|
|
1819
|
-
if (italicMatch) {
|
|
1820
|
-
candidates.push({
|
|
1821
|
-
index: italicMatch.index,
|
|
1822
|
-
length: italicMatch[0].length,
|
|
1823
|
-
node: /* @__PURE__ */ jsx4(Text4, { italic: true, children: italicMatch[1] ?? italicMatch[2] }, key++)
|
|
1824
|
-
});
|
|
1825
|
-
}
|
|
1826
|
-
candidates.sort((a, b) => a.index - b.index);
|
|
1827
|
-
const pick = candidates[0];
|
|
1828
|
-
if (!pick) {
|
|
1829
|
-
parts.push(/* @__PURE__ */ jsx4(Text4, { children: remaining }, key++));
|
|
1830
|
-
break;
|
|
1831
|
-
}
|
|
1832
|
-
if (pick.index > 0) {
|
|
1833
|
-
parts.push(/* @__PURE__ */ jsx4(Text4, { children: remaining.slice(0, pick.index) }, key++));
|
|
1834
|
-
}
|
|
1835
|
-
parts.push(pick.node);
|
|
1836
|
-
remaining = remaining.slice(pick.index + pick.length);
|
|
1660
|
+
|
|
1661
|
+
// src/agent/system-prompt.ts
|
|
1662
|
+
function buildSystemPrompt(skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, memoryContent) {
|
|
1663
|
+
const stage = profile.journeyStage ?? "full";
|
|
1664
|
+
const isPending = stage === "fresh" || stage === "pending";
|
|
1665
|
+
const parts = [
|
|
1666
|
+
ROLE_DESCRIPTION,
|
|
1667
|
+
"",
|
|
1668
|
+
"---",
|
|
1669
|
+
"",
|
|
1670
|
+
TOOL_OVERRIDES,
|
|
1671
|
+
"",
|
|
1672
|
+
"---",
|
|
1673
|
+
""
|
|
1674
|
+
];
|
|
1675
|
+
if (skillContext) {
|
|
1676
|
+
parts.push("## AstraNova API Instructions", "");
|
|
1677
|
+
parts.push(skillContext);
|
|
1678
|
+
parts.push("", "---", "");
|
|
1837
1679
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1680
|
+
if (onboardingContext && isPending) {
|
|
1681
|
+
parts.push("## Onboarding Guide", "");
|
|
1682
|
+
parts.push(onboardingContext);
|
|
1683
|
+
parts.push("", "---", "");
|
|
1684
|
+
}
|
|
1685
|
+
if (tradingContext && !isPending) {
|
|
1686
|
+
parts.push("## Trading Guide", "");
|
|
1687
|
+
parts.push(tradingContext);
|
|
1688
|
+
parts.push("", "---", "");
|
|
1689
|
+
}
|
|
1690
|
+
if (rewardsContext && (stage === "wallet_ready" || stage === "full")) {
|
|
1691
|
+
parts.push("## Rewards Guide", "");
|
|
1692
|
+
parts.push(rewardsContext);
|
|
1693
|
+
parts.push("", "---", "");
|
|
1694
|
+
}
|
|
1695
|
+
if (apiContext) {
|
|
1696
|
+
parts.push("## API Reference", "");
|
|
1697
|
+
parts.push(apiContext);
|
|
1698
|
+
parts.push("", "---", "");
|
|
1699
|
+
}
|
|
1700
|
+
parts.push(DOCS_AWARENESS);
|
|
1701
|
+
parts.push("", "---", "");
|
|
1702
|
+
parts.push("## Current Agent State", "");
|
|
1703
|
+
parts.push(`Agent: ${profile.agentName}`);
|
|
1704
|
+
parts.push(`Status: ${profile.status ?? "unknown"}`);
|
|
1705
|
+
parts.push(`Journey Stage: ${stage}`);
|
|
1706
|
+
if (profile.simBalance !== void 0) {
|
|
1707
|
+
parts.push(`$SIM Balance: ${profile.simBalance.toLocaleString()}`);
|
|
1708
|
+
}
|
|
1709
|
+
if (profile.novaHoldings !== void 0) {
|
|
1710
|
+
parts.push(`$NOVA Holdings: ${profile.novaHoldings.toLocaleString()}`);
|
|
1711
|
+
}
|
|
1712
|
+
parts.push(`Wallet: ${profile.walletAddress ?? "not set"}`);
|
|
1713
|
+
parts.push(`Wallet Local: ${profile.walletLocal ? "yes" : "no"}`);
|
|
1714
|
+
if (profile.verificationCode) {
|
|
1715
|
+
parts.push(`Verification Code: ${profile.verificationCode}`);
|
|
1716
|
+
}
|
|
1717
|
+
if (profile.season !== void 0) {
|
|
1718
|
+
parts.push(`Season: ${profile.season}`);
|
|
1719
|
+
}
|
|
1720
|
+
const apMode = profile.autopilotMode ?? "off";
|
|
1721
|
+
if (apMode !== "off") {
|
|
1722
|
+
parts.push("", "---", "");
|
|
1723
|
+
parts.push(`## AUTOPILOT: ${apMode.toUpperCase()}`);
|
|
1724
|
+
parts.push("");
|
|
1725
|
+
parts.push(
|
|
1726
|
+
`Autopilot is active. When you receive an AUTOPILOT CHECK trigger, your strategy will be included in that message \u2014 execute per those instructions. Do NOT ask the user for confirmation or trade size during autopilot checks.`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
if (memoryContent && memoryContent.trim()) {
|
|
1730
|
+
parts.push("", "---", "");
|
|
1731
|
+
parts.push("## Agent Memory (persistent across sessions)", "");
|
|
1732
|
+
parts.push(memoryContent.trim());
|
|
1733
|
+
parts.push("");
|
|
1734
|
+
parts.push(`Use the \`update_memory\` tool to update this memory when you learn important facts about the user, their preferences, or trading patterns. Replace the entire content each time \u2014 keep only what matters.
|
|
1840
1735
|
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "wrap", children: msg.content }) })
|
|
1856
|
-
] }, i);
|
|
1857
|
-
}
|
|
1858
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
1859
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, color: msg.role === "user" ? "#00ff00" : "#00ffff", children: msg.role === "user" ? " You" : " Agent" }),
|
|
1860
|
-
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(MarkdownText, { children: msg.content }) : /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: msg.content }) })
|
|
1861
|
-
] }, i);
|
|
1862
|
-
}),
|
|
1863
|
-
streamingText !== void 0 && streamingText.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
1864
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "#00ffff", children: " Agent" }),
|
|
1865
|
-
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(MarkdownText, { children: streamingText }) })
|
|
1866
|
-
] })
|
|
1867
|
-
] });
|
|
1736
|
+
Specific triggers to save memory:
|
|
1737
|
+
- User mentions their trading style ("I like to hold long", "I always sell at 5%") \u2192 save it
|
|
1738
|
+
- User mentions preferred position size or risk tolerance \u2192 save it
|
|
1739
|
+
- User mentions their goals ("I want to be top 10 on the leaderboard") \u2192 save it
|
|
1740
|
+
- User reacts positively or negatively to a suggestion \u2192 note what they prefer`);
|
|
1741
|
+
} else {
|
|
1742
|
+
parts.push("", "---", "");
|
|
1743
|
+
parts.push("## Agent Memory");
|
|
1744
|
+
parts.push("");
|
|
1745
|
+
parts.push("No persistent memory saved yet. Use the `update_memory` tool to save important facts about the user (preferences, trading style, goals) that should persist across sessions. Max 2000 characters.");
|
|
1746
|
+
}
|
|
1747
|
+
parts.push("", "---", "");
|
|
1748
|
+
parts.push(buildJourneyGuidance(stage, profile));
|
|
1749
|
+
return parts.join("\n");
|
|
1868
1750
|
}
|
|
1751
|
+
function buildJourneyGuidance(stage, profile) {
|
|
1752
|
+
switch (stage) {
|
|
1753
|
+
case "fresh":
|
|
1754
|
+
case "pending":
|
|
1755
|
+
return buildVerificationGuidance(profile);
|
|
1756
|
+
case "verified": {
|
|
1757
|
+
const boardIntro = profile.boardPosted ? "" : `Before anything else, every agent gets one entrance message on the AstraNova board (max 280 chars). Suggest 3-5 creative options based on "${profile.agentName}". Use api_call POST /api/v1/board with {"message":"<chosen-message>"}. If the API returns 409, say "Looks like you already made your entrance!" and move on.
|
|
1869
1758
|
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
1873
|
-
import TextInput from "ink-text-input";
|
|
1874
|
-
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1875
|
-
function Input({
|
|
1876
|
-
isActive,
|
|
1877
|
-
onSubmit
|
|
1878
|
-
}) {
|
|
1879
|
-
const [value, setValue] = useState2("");
|
|
1880
|
-
const handleSubmit = (submitted) => {
|
|
1881
|
-
const trimmed = submitted.trim();
|
|
1882
|
-
if (!trimmed) return;
|
|
1883
|
-
onSubmit(trimmed);
|
|
1884
|
-
setValue("");
|
|
1885
|
-
};
|
|
1886
|
-
return /* @__PURE__ */ jsxs6(Box6, { width: "100%", paddingX: 2, paddingY: 1, children: [
|
|
1887
|
-
/* @__PURE__ */ jsx6(Text6, { color: isActive ? "yellow" : "gray", bold: true, children: "\u276F\u276F " }),
|
|
1888
|
-
/* @__PURE__ */ jsx6(
|
|
1889
|
-
TextInput,
|
|
1890
|
-
{
|
|
1891
|
-
value,
|
|
1892
|
-
onChange: setValue,
|
|
1893
|
-
onSubmit: handleSubmit,
|
|
1894
|
-
focus: isActive,
|
|
1895
|
-
showCursor: isActive,
|
|
1896
|
-
placeholder: ""
|
|
1897
|
-
}
|
|
1898
|
-
)
|
|
1899
|
-
] });
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
// src/ui/Spinner.tsx
|
|
1903
|
-
import { useState as useState3, useEffect as useEffect2 } from "react";
|
|
1904
|
-
import { Text as Text7 } from "ink";
|
|
1905
|
-
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1906
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1907
|
-
var INTERVAL_MS = 80;
|
|
1908
|
-
function Spinner({ label }) {
|
|
1909
|
-
const [frame, setFrame] = useState3(0);
|
|
1910
|
-
useEffect2(() => {
|
|
1911
|
-
const timer = setInterval(() => {
|
|
1912
|
-
setFrame((prev) => (prev + 1) % FRAMES.length);
|
|
1913
|
-
}, INTERVAL_MS);
|
|
1914
|
-
return () => clearInterval(timer);
|
|
1915
|
-
}, []);
|
|
1916
|
-
return /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1917
|
-
FRAMES[frame],
|
|
1918
|
-
" ",
|
|
1919
|
-
label ?? "Thinking..."
|
|
1920
|
-
] });
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
// src/agent/loop.ts
|
|
1924
|
-
import { streamText, generateText } from "ai";
|
|
1925
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
1926
|
-
|
|
1927
|
-
// src/agent/provider.ts
|
|
1928
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
1929
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
1930
|
-
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
1931
|
-
function isCodexOAuth() {
|
|
1932
|
-
const override = process.env.ASTRA_PROVIDER;
|
|
1933
|
-
if (override) return override === "openai-oauth";
|
|
1934
|
-
const config = loadConfig();
|
|
1935
|
-
return config?.provider === "openai-oauth";
|
|
1936
|
-
}
|
|
1937
|
-
function isOpenAIResponses() {
|
|
1938
|
-
const override = process.env.ASTRA_PROVIDER;
|
|
1939
|
-
if (override) return override === "openai";
|
|
1940
|
-
const config = loadConfig();
|
|
1941
|
-
return config?.provider === "openai";
|
|
1942
|
-
}
|
|
1943
|
-
function getOpenAIApiKey() {
|
|
1944
|
-
const envKey = process.env.ASTRA_API_KEY;
|
|
1945
|
-
if (envKey) return envKey;
|
|
1946
|
-
const config = loadConfig();
|
|
1947
|
-
if (config?.auth?.type === "api-key" && config.auth.apiKey) {
|
|
1948
|
-
return config.auth.apiKey;
|
|
1949
|
-
}
|
|
1950
|
-
throw new Error(
|
|
1951
|
-
"OpenAI API key not found. Set ASTRA_API_KEY or re-run onboarding."
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
|
-
async function getCodexAccessToken() {
|
|
1955
|
-
const config = loadConfig();
|
|
1956
|
-
if (!config || config.auth.type !== "oauth" || !config.auth.oauth) {
|
|
1957
|
-
throw new Error("Codex OAuth not configured. Re-run onboarding.");
|
|
1958
|
-
}
|
|
1959
|
-
return ensureFreshToken(config);
|
|
1960
|
-
}
|
|
1961
|
-
async function getModel() {
|
|
1962
|
-
const config = loadConfig();
|
|
1963
|
-
if (!config) {
|
|
1964
|
-
throw new Error(
|
|
1965
|
-
"No config found. Run the onboarding wizard first (delete ~/.config/astranova/config.json to re-run)."
|
|
1966
|
-
);
|
|
1967
|
-
}
|
|
1968
|
-
const providerOverride = process.env.ASTRA_PROVIDER;
|
|
1969
|
-
const apiKeyOverride = process.env.ASTRA_API_KEY;
|
|
1970
|
-
const modelOverride = process.env.ASTRA_MODEL;
|
|
1971
|
-
if (providerOverride) {
|
|
1972
|
-
if (!apiKeyOverride) {
|
|
1973
|
-
throw new Error(
|
|
1974
|
-
`ASTRA_PROVIDER=${providerOverride} is set but ASTRA_API_KEY is missing.
|
|
1975
|
-
Export your API key: export ASTRA_API_KEY=sk-...`
|
|
1976
|
-
);
|
|
1977
|
-
}
|
|
1978
|
-
return createModelFromConfig({
|
|
1979
|
-
...config,
|
|
1980
|
-
provider: providerOverride,
|
|
1981
|
-
model: modelOverride ?? config.model,
|
|
1982
|
-
auth: { type: "api-key", apiKey: apiKeyOverride }
|
|
1983
|
-
});
|
|
1984
|
-
}
|
|
1985
|
-
return createModelFromConfig(config);
|
|
1986
|
-
}
|
|
1987
|
-
async function ensureFreshToken(config) {
|
|
1988
|
-
const oauth = config.auth.oauth;
|
|
1989
|
-
if (!oauth) throw new Error("OAuth config missing");
|
|
1990
|
-
if (!isTokenExpired(oauth.expiresAt)) return oauth.accessToken;
|
|
1991
|
-
try {
|
|
1992
|
-
const tokens = await refreshTokens({
|
|
1993
|
-
refreshToken: oauth.refreshToken,
|
|
1994
|
-
clientId: oauth.clientId
|
|
1995
|
-
});
|
|
1996
|
-
const updatedConfig = {
|
|
1997
|
-
...config,
|
|
1998
|
-
auth: {
|
|
1999
|
-
...config.auth,
|
|
2000
|
-
oauth: {
|
|
2001
|
-
...oauth,
|
|
2002
|
-
accessToken: tokens.accessToken,
|
|
2003
|
-
refreshToken: tokens.refreshToken,
|
|
2004
|
-
expiresAt: tokens.expiresAt
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
};
|
|
2008
|
-
saveConfig(updatedConfig);
|
|
2009
|
-
return tokens.accessToken;
|
|
2010
|
-
} catch (error) {
|
|
2011
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2012
|
-
throw new Error(
|
|
2013
|
-
`OAuth token refresh failed: ${message}
|
|
2014
|
-
Please re-run onboarding to log in again (delete ~/.config/astranova/config.json).`
|
|
2015
|
-
);
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
function createModelFromConfig(config) {
|
|
2019
|
-
const { provider, model, auth } = config;
|
|
2020
|
-
switch (provider) {
|
|
2021
|
-
case "claude": {
|
|
2022
|
-
if (auth.type !== "api-key" || !auth.apiKey) {
|
|
2023
|
-
throw new Error("Claude requires an API key. Re-run onboarding to set one up.");
|
|
2024
|
-
}
|
|
2025
|
-
const anthropic = createAnthropic({ apiKey: auth.apiKey });
|
|
2026
|
-
return anthropic(model);
|
|
2027
|
-
}
|
|
2028
|
-
case "openai": {
|
|
2029
|
-
if (auth.type !== "api-key" || !auth.apiKey) {
|
|
2030
|
-
throw new Error("OpenAI requires an API key. Re-run onboarding to set one up.");
|
|
2031
|
-
}
|
|
2032
|
-
const openai = createOpenAI({ apiKey: auth.apiKey });
|
|
2033
|
-
return openai(model);
|
|
2034
|
-
}
|
|
2035
|
-
case "google": {
|
|
2036
|
-
if (auth.type !== "api-key" || !auth.apiKey) {
|
|
2037
|
-
throw new Error("Gemini requires an API key. Re-run onboarding to set one up.");
|
|
2038
|
-
}
|
|
2039
|
-
const google = createGoogleGenerativeAI({ apiKey: auth.apiKey });
|
|
2040
|
-
return google(model);
|
|
2041
|
-
}
|
|
2042
|
-
case "openai-oauth":
|
|
2043
|
-
throw new Error("Codex OAuth uses custom provider. This is a bug \u2014 please report.");
|
|
2044
|
-
case "ollama":
|
|
2045
|
-
throw new Error(
|
|
2046
|
-
"Ollama support is coming soon. Please use Claude or ChatGPT/Codex.\nTo switch, delete ~/.config/astranova/config.json and re-run astra."
|
|
2047
|
-
);
|
|
2048
|
-
default:
|
|
2049
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
// src/agent/system-prompt.ts
|
|
2054
|
-
function buildSystemPrompt(skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, memoryContent) {
|
|
2055
|
-
const stage = profile.journeyStage ?? "full";
|
|
2056
|
-
const isPending = stage === "fresh" || stage === "pending";
|
|
2057
|
-
const parts = [
|
|
2058
|
-
ROLE_DESCRIPTION,
|
|
2059
|
-
"",
|
|
2060
|
-
"---",
|
|
2061
|
-
"",
|
|
2062
|
-
TOOL_OVERRIDES,
|
|
2063
|
-
"",
|
|
2064
|
-
"---",
|
|
2065
|
-
""
|
|
2066
|
-
];
|
|
2067
|
-
if (skillContext) {
|
|
2068
|
-
parts.push("## AstraNova API Instructions", "");
|
|
2069
|
-
parts.push(skillContext);
|
|
2070
|
-
parts.push("", "---", "");
|
|
2071
|
-
}
|
|
2072
|
-
if (onboardingContext && isPending) {
|
|
2073
|
-
parts.push("## Onboarding Guide", "");
|
|
2074
|
-
parts.push(onboardingContext);
|
|
2075
|
-
parts.push("", "---", "");
|
|
2076
|
-
}
|
|
2077
|
-
if (tradingContext && !isPending) {
|
|
2078
|
-
parts.push("## Trading Guide", "");
|
|
2079
|
-
parts.push(tradingContext);
|
|
2080
|
-
parts.push("", "---", "");
|
|
2081
|
-
}
|
|
2082
|
-
if (rewardsContext && (stage === "wallet_ready" || stage === "full")) {
|
|
2083
|
-
parts.push("## Rewards Guide", "");
|
|
2084
|
-
parts.push(rewardsContext);
|
|
2085
|
-
parts.push("", "---", "");
|
|
2086
|
-
}
|
|
2087
|
-
if (apiContext) {
|
|
2088
|
-
parts.push("## API Reference", "");
|
|
2089
|
-
parts.push(apiContext);
|
|
2090
|
-
parts.push("", "---", "");
|
|
2091
|
-
}
|
|
2092
|
-
parts.push(DOCS_AWARENESS);
|
|
2093
|
-
parts.push("", "---", "");
|
|
2094
|
-
parts.push("## Current Agent State", "");
|
|
2095
|
-
parts.push(`Agent: ${profile.agentName}`);
|
|
2096
|
-
parts.push(`Status: ${profile.status ?? "unknown"}`);
|
|
2097
|
-
parts.push(`Journey Stage: ${stage}`);
|
|
2098
|
-
if (profile.simBalance !== void 0) {
|
|
2099
|
-
parts.push(`$SIM Balance: ${profile.simBalance.toLocaleString()}`);
|
|
2100
|
-
}
|
|
2101
|
-
if (profile.novaHoldings !== void 0) {
|
|
2102
|
-
parts.push(`$NOVA Holdings: ${profile.novaHoldings.toLocaleString()}`);
|
|
2103
|
-
}
|
|
2104
|
-
parts.push(`Wallet: ${profile.walletAddress ?? "not set"}`);
|
|
2105
|
-
parts.push(`Wallet Local: ${profile.walletLocal ? "yes" : "no"}`);
|
|
2106
|
-
if (profile.verificationCode) {
|
|
2107
|
-
parts.push(`Verification Code: ${profile.verificationCode}`);
|
|
2108
|
-
}
|
|
2109
|
-
if (profile.season !== void 0) {
|
|
2110
|
-
parts.push(`Season: ${profile.season}`);
|
|
2111
|
-
}
|
|
2112
|
-
const apMode = profile.autopilotMode ?? "off";
|
|
2113
|
-
if (apMode !== "off") {
|
|
2114
|
-
parts.push("", "---", "");
|
|
2115
|
-
parts.push(`## AUTOPILOT: ${apMode.toUpperCase()}`);
|
|
2116
|
-
parts.push("");
|
|
2117
|
-
parts.push(`On AUTOPILOT CHECK triggers:`);
|
|
2118
|
-
parts.push(`- GET /api/v1/market/state + GET /api/v1/portfolio`);
|
|
2119
|
-
parts.push(`- Apply strategy from memory.md (default: momentum + balance)`);
|
|
2120
|
-
if (apMode === "semi") {
|
|
2121
|
-
parts.push(`- SEMI: propose trade, wait for explicit user approval`);
|
|
2122
|
-
} else {
|
|
2123
|
-
parts.push(`- FULL: execute if signal clear; skip if uncertain`);
|
|
2124
|
-
}
|
|
2125
|
-
parts.push(`- Max 2-3 lines. No action = "Market checked \u2014 holding."`);
|
|
2126
|
-
}
|
|
2127
|
-
if (memoryContent && memoryContent.trim()) {
|
|
2128
|
-
parts.push("", "---", "");
|
|
2129
|
-
parts.push("## Agent Memory (persistent across sessions)", "");
|
|
2130
|
-
parts.push(memoryContent.trim());
|
|
2131
|
-
parts.push("");
|
|
2132
|
-
parts.push(`Use the \`update_memory\` tool to update this memory when you learn important facts about the user, their preferences, or trading patterns. Replace the entire content each time \u2014 keep only what matters.
|
|
2133
|
-
|
|
2134
|
-
Specific triggers to save memory:
|
|
2135
|
-
- User mentions their trading style ("I like to hold long", "I always sell at 5%") \u2192 save it
|
|
2136
|
-
- User mentions preferred position size or risk tolerance \u2192 save it
|
|
2137
|
-
- User mentions their goals ("I want to be top 10 on the leaderboard") \u2192 save it
|
|
2138
|
-
- User reacts positively or negatively to a suggestion \u2192 note what they prefer`);
|
|
2139
|
-
} else {
|
|
2140
|
-
parts.push("", "---", "");
|
|
2141
|
-
parts.push("## Agent Memory");
|
|
2142
|
-
parts.push("");
|
|
2143
|
-
parts.push("No persistent memory saved yet. Use the `update_memory` tool to save important facts about the user (preferences, trading style, goals) that should persist across sessions. Max 2000 characters.");
|
|
2144
|
-
}
|
|
2145
|
-
parts.push("", "---", "");
|
|
2146
|
-
parts.push(buildJourneyGuidance(stage, profile));
|
|
2147
|
-
return parts.join("\n");
|
|
2148
|
-
}
|
|
2149
|
-
function buildJourneyGuidance(stage, profile) {
|
|
2150
|
-
switch (stage) {
|
|
2151
|
-
case "fresh":
|
|
2152
|
-
case "pending":
|
|
2153
|
-
return buildVerificationGuidance(profile);
|
|
2154
|
-
case "verified": {
|
|
2155
|
-
const boardIntro = profile.boardPosted ? "" : `Before anything else, every agent gets one entrance message on the AstraNova board (max 280 chars). Suggest 3-5 creative options based on "${profile.agentName}". Use api_call POST /api/v1/board with {"message":"<chosen-message>"}. If the API returns 409, say "Looks like you already made your entrance!" and move on.
|
|
2156
|
-
|
|
2157
|
-
`;
|
|
2158
|
-
return `## Your Opening Message
|
|
1759
|
+
`;
|
|
1760
|
+
return `## Your Opening Message
|
|
2159
1761
|
|
|
2160
1762
|
Hey \u2014 welcome! This is exciting, "${profile.agentName}" is verified and ready to go.
|
|
2161
1763
|
|
|
@@ -2172,7 +1774,9 @@ If they trade, pull their portfolio afterwards using the card format and add a b
|
|
|
2172
1774
|
- Don't mention wallets yet \u2014 that comes later after they've traded a bit.
|
|
2173
1775
|
- If they ask what they can do, give them 2-3 quick options: "Check the market, make a trade, or look at the board."`;
|
|
2174
1776
|
}
|
|
2175
|
-
case "trading":
|
|
1777
|
+
case "trading": {
|
|
1778
|
+
const strategyNudge = !profile.hasStrategy ? `- If it comes up naturally, mention autopilot once: "By the way \u2014 I can trade on autopilot for you if you set up a strategy. Want to try?" Only mention once per session. If they say no or ignore it, drop it.
|
|
1779
|
+
` : "";
|
|
2176
1780
|
return `## Your Opening Message
|
|
2177
1781
|
|
|
2178
1782
|
Welcome back! Greet the user casually: "Hey! Want to see what the market's been up to?"
|
|
@@ -2186,13 +1790,14 @@ Wait for their response. Don't auto-pull data unless they say yes or ask for som
|
|
|
2186
1790
|
- After the user makes their 3rd trade OR after you've shown them the portfolio at least once, casually mention wallet setup once: "By the way \u2014 setting up a wallet takes about a minute and means your $ASTRA rewards are claimable the moment the season ends. Want to do it now?"
|
|
2187
1791
|
- If portfolio shows claimable rewards (rewards.claimable > "0"), always mention wallet setup regardless of trade count: "You've got $ASTRA rewards ready to claim \u2014 want to set up a wallet so you can collect them?"
|
|
2188
1792
|
- Only mention wallet setup once per session. If they say no or ignore it, drop it.
|
|
2189
|
-
|
|
1793
|
+
${strategyNudge}
|
|
2190
1794
|
**Wallet setup:** If they say yes, run the full wallet flow automatically (create \u2192 challenge \u2192 sign \u2192 register \u2192 verify) \u2014 tell them what you're doing along the way but don't stop to ask at each step.
|
|
2191
1795
|
|
|
2192
1796
|
**Conversation style:**
|
|
2193
1797
|
- Like a friend who's also trading. Casual, helpful, never pushy.
|
|
2194
1798
|
- Suggest things, wait for their input. Don't dump multiple suggestions at once.
|
|
2195
1799
|
- If they just want to trade, help them trade. If they want to chat, chat.`;
|
|
1800
|
+
}
|
|
2196
1801
|
case "wallet_ready":
|
|
2197
1802
|
return `## Your Opening Message
|
|
2198
1803
|
|
|
@@ -2536,39 +2141,39 @@ You have access to tools for interacting with the AstraNova Agent API, reading/w
|
|
|
2536
2141
|
- WALLET RULE: Before ANY wallet operation (create, register, show address), ALWAYS call \`read_config\` with \`key: "wallet"\` first to check if a wallet already exists locally. If it returns a publicKey, the wallet EXISTS \u2014 do NOT create a new one. If the API shows \`hasWallet: false\` but a local wallet exists, it means the wallet was created but not yet registered \u2014 skip to the challenge/verify step to register the existing wallet.`;
|
|
2537
2142
|
|
|
2538
2143
|
// src/tools/api.ts
|
|
2539
|
-
import { tool as
|
|
2144
|
+
import { tool as tool3 } from "ai";
|
|
2540
2145
|
|
|
2541
2146
|
// src/tools/schemas.ts
|
|
2542
|
-
import { z as
|
|
2543
|
-
var apiCallSchema =
|
|
2544
|
-
method:
|
|
2545
|
-
path:
|
|
2546
|
-
body:
|
|
2147
|
+
import { z as z4 } from "zod";
|
|
2148
|
+
var apiCallSchema = z4.object({
|
|
2149
|
+
method: z4.enum(["GET", "POST", "PUT", "PATCH"]),
|
|
2150
|
+
path: z4.string().describe("API path, e.g. /api/v1/agents/me"),
|
|
2151
|
+
body: z4.record(z4.unknown()).optional().describe("JSON body for POST/PUT/PATCH requests")
|
|
2547
2152
|
}).passthrough();
|
|
2548
|
-
var readConfigSchema =
|
|
2549
|
-
key:
|
|
2550
|
-
agentName:
|
|
2153
|
+
var readConfigSchema = z4.object({
|
|
2154
|
+
key: z4.enum(["profile", "wallet", "all_agents", "settings"]),
|
|
2155
|
+
agentName: z4.string().optional().describe("Agent name. Uses the active agent if not specified.")
|
|
2551
2156
|
});
|
|
2552
|
-
var writeConfigSchema =
|
|
2553
|
-
agentName:
|
|
2554
|
-
data:
|
|
2555
|
-
file:
|
|
2157
|
+
var writeConfigSchema = z4.object({
|
|
2158
|
+
agentName: z4.string().describe("Agent name to write config for"),
|
|
2159
|
+
data: z4.record(z4.unknown()).describe("Data to write"),
|
|
2160
|
+
file: z4.enum(["credentials", "settings", "profile"]).describe("Which config file to write")
|
|
2556
2161
|
});
|
|
2557
|
-
var createWalletSchema =
|
|
2558
|
-
agentName:
|
|
2162
|
+
var createWalletSchema = z4.object({
|
|
2163
|
+
agentName: z4.string().describe("Agent name to associate the wallet with")
|
|
2559
2164
|
});
|
|
2560
|
-
var signChallengeSchema =
|
|
2561
|
-
challenge:
|
|
2165
|
+
var signChallengeSchema = z4.object({
|
|
2166
|
+
challenge: z4.string().describe("The challenge string received from the wallet registration API")
|
|
2562
2167
|
});
|
|
2563
|
-
var signAndSendTransactionSchema =
|
|
2564
|
-
transaction:
|
|
2168
|
+
var signAndSendTransactionSchema = z4.object({
|
|
2169
|
+
transaction: z4.string().describe("Base64-encoded partially-signed transaction from the API")
|
|
2565
2170
|
});
|
|
2566
|
-
var registerAgentSchema =
|
|
2567
|
-
name:
|
|
2568
|
-
description:
|
|
2171
|
+
var registerAgentSchema = z4.object({
|
|
2172
|
+
name: z4.string().describe("Agent name (2-32 chars, lowercase, letters/numbers/hyphens/underscores)"),
|
|
2173
|
+
description: z4.string().describe("Short agent description \u2014 personality-driven, a few words")
|
|
2569
2174
|
});
|
|
2570
|
-
var switchAgentSchema =
|
|
2571
|
-
agentName:
|
|
2175
|
+
var switchAgentSchema = z4.object({
|
|
2176
|
+
agentName: z4.string().describe("Name of the agent to switch to")
|
|
2572
2177
|
});
|
|
2573
2178
|
|
|
2574
2179
|
// src/tools/api.ts
|
|
@@ -2600,7 +2205,7 @@ function resolveBody(body, rest, method) {
|
|
|
2600
2205
|
}
|
|
2601
2206
|
return void 0;
|
|
2602
2207
|
}
|
|
2603
|
-
var apiCallTool =
|
|
2208
|
+
var apiCallTool = tool3({
|
|
2604
2209
|
description: "Call the AstraNova Agent API. Use this for all API interactions \u2014 registration, trading, market data, portfolio, rewards, board posts, and verification. For POST/PUT/PATCH requests, put the request payload in the 'body' parameter as a JSON object.",
|
|
2605
2210
|
parameters: apiCallSchema,
|
|
2606
2211
|
execute: async (args) => {
|
|
@@ -2700,11 +2305,11 @@ var apiCallTool = tool2({
|
|
|
2700
2305
|
});
|
|
2701
2306
|
|
|
2702
2307
|
// src/tools/config.ts
|
|
2703
|
-
import { tool as
|
|
2704
|
-
import
|
|
2308
|
+
import { tool as tool4 } from "ai";
|
|
2309
|
+
import fs7 from "fs";
|
|
2705
2310
|
import path5 from "path";
|
|
2706
2311
|
import crypto2 from "crypto";
|
|
2707
|
-
var readConfigTool =
|
|
2312
|
+
var readConfigTool = tool4({
|
|
2708
2313
|
description: "Read local AstraNova configuration or credentials. Returns public information only \u2014 private keys and API keys are never included.",
|
|
2709
2314
|
parameters: readConfigSchema,
|
|
2710
2315
|
execute: async ({ key, agentName }) => {
|
|
@@ -2759,7 +2364,7 @@ var readConfigTool = tool3({
|
|
|
2759
2364
|
}
|
|
2760
2365
|
}
|
|
2761
2366
|
});
|
|
2762
|
-
var writeConfigTool =
|
|
2367
|
+
var writeConfigTool = tool4({
|
|
2763
2368
|
description: "Write local AstraNova configuration. Used to save agent credentials after registration or update profile data. Cannot write wallet files \u2014 use wallet tools instead.",
|
|
2764
2369
|
parameters: writeConfigSchema,
|
|
2765
2370
|
execute: async ({ agentName, data, file }) => {
|
|
@@ -2781,18 +2386,18 @@ var writeConfigTool = tool3({
|
|
|
2781
2386
|
});
|
|
2782
2387
|
} else {
|
|
2783
2388
|
const tmpPath = path5.join(dir, `.tmp-${crypto2.randomBytes(6).toString("hex")}`);
|
|
2784
|
-
|
|
2389
|
+
fs7.writeFileSync(tmpPath, JSON.stringify(data, null, 2), {
|
|
2785
2390
|
encoding: "utf-8",
|
|
2786
2391
|
mode: 384
|
|
2787
2392
|
});
|
|
2788
|
-
|
|
2393
|
+
fs7.renameSync(tmpPath, filePath);
|
|
2789
2394
|
}
|
|
2790
2395
|
return { success: true, file: `${file}.json`, agent: agentName };
|
|
2791
2396
|
}
|
|
2792
2397
|
});
|
|
2793
2398
|
|
|
2794
2399
|
// src/tools/wallet.ts
|
|
2795
|
-
import { tool as
|
|
2400
|
+
import { tool as tool5 } from "ai";
|
|
2796
2401
|
import {
|
|
2797
2402
|
Keypair,
|
|
2798
2403
|
Connection,
|
|
@@ -2801,7 +2406,7 @@ import {
|
|
|
2801
2406
|
import nacl from "tweetnacl";
|
|
2802
2407
|
import bs58 from "bs58";
|
|
2803
2408
|
var SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
2804
|
-
var createWalletTool =
|
|
2409
|
+
var createWalletTool = tool5({
|
|
2805
2410
|
description: "Generate a new Solana wallet keypair. Saves the keypair locally and returns the public key. The secret key is stored securely and never exposed.",
|
|
2806
2411
|
parameters: createWalletSchema,
|
|
2807
2412
|
execute: async ({ agentName }) => {
|
|
@@ -2824,7 +2429,7 @@ var createWalletTool = tool4({
|
|
|
2824
2429
|
};
|
|
2825
2430
|
}
|
|
2826
2431
|
});
|
|
2827
|
-
var signChallengeTool =
|
|
2432
|
+
var signChallengeTool = tool5({
|
|
2828
2433
|
description: "Sign a challenge string with the agent's wallet secret key. Returns a base58-encoded signature for wallet registration. The secret key is never exposed.",
|
|
2829
2434
|
parameters: signChallengeSchema,
|
|
2830
2435
|
execute: async ({ challenge }) => {
|
|
@@ -2865,7 +2470,7 @@ var signChallengeTool = tool4({
|
|
|
2865
2470
|
}
|
|
2866
2471
|
}
|
|
2867
2472
|
});
|
|
2868
|
-
var signAndSendTransactionTool =
|
|
2473
|
+
var signAndSendTransactionTool = tool5({
|
|
2869
2474
|
description: "Co-sign and submit a partially-signed Solana transaction (base64). Used for claiming $ASTRA rewards. Returns the transaction signature.",
|
|
2870
2475
|
parameters: signAndSendTransactionSchema,
|
|
2871
2476
|
execute: async ({ transaction: txBase64 }) => {
|
|
@@ -2919,16 +2524,59 @@ var signAndSendTransactionTool = tool4({
|
|
|
2919
2524
|
});
|
|
2920
2525
|
|
|
2921
2526
|
// src/tools/agent-management.ts
|
|
2922
|
-
import { tool as
|
|
2923
|
-
import { z as
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2527
|
+
import { tool as tool6 } from "ai";
|
|
2528
|
+
import { z as z5 } from "zod";
|
|
2529
|
+
|
|
2530
|
+
// src/daemon/daemon-manager.ts
|
|
2531
|
+
import { spawn } from "child_process";
|
|
2532
|
+
import process2 from "process";
|
|
2533
|
+
function isProcessAlive(pid) {
|
|
2534
|
+
try {
|
|
2535
|
+
process2.kill(pid, 0);
|
|
2536
|
+
return true;
|
|
2537
|
+
} catch {
|
|
2538
|
+
return false;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
function isDaemonRunning(agentName) {
|
|
2542
|
+
const pid = loadDaemonPid(agentName);
|
|
2543
|
+
if (pid === null) return false;
|
|
2544
|
+
if (isProcessAlive(pid)) return true;
|
|
2545
|
+
clearDaemonPid(agentName);
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
function startDaemon(agentName) {
|
|
2549
|
+
if (isDaemonRunning(agentName)) return;
|
|
2550
|
+
const child = spawn(process2.execPath, [process2.argv[1], "--daemon"], {
|
|
2551
|
+
detached: true,
|
|
2552
|
+
stdio: "ignore",
|
|
2553
|
+
env: { ...process2.env }
|
|
2554
|
+
});
|
|
2555
|
+
child.unref();
|
|
2556
|
+
if (child.pid) {
|
|
2557
|
+
saveDaemonPid(agentName, child.pid);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
function stopDaemon(agentName) {
|
|
2561
|
+
const pid = loadDaemonPid(agentName);
|
|
2562
|
+
if (pid !== null && isProcessAlive(pid)) {
|
|
2563
|
+
try {
|
|
2564
|
+
process2.kill(pid, "SIGTERM");
|
|
2565
|
+
} catch {
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
clearDaemonPid(agentName);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// src/tools/agent-management.ts
|
|
2572
|
+
var registerAgentTool = tool6({
|
|
2573
|
+
description: "Register a new AstraNova agent. Calls the API, saves credentials locally, and sets the new agent as active. The CLI will need to restart after this to load the new agent's context.",
|
|
2574
|
+
parameters: registerAgentSchema,
|
|
2575
|
+
execute: async ({ name, description }) => {
|
|
2576
|
+
if (!/^[a-z0-9_-]{2,32}$/.test(name)) {
|
|
2577
|
+
return {
|
|
2578
|
+
error: "Invalid agent name. Must be 2-32 chars, lowercase letters, numbers, hyphens, or underscores."
|
|
2579
|
+
};
|
|
2932
2580
|
}
|
|
2933
2581
|
const result = await apiCall("POST", "/api/v1/agents/register", { name, description });
|
|
2934
2582
|
if (!result.ok) {
|
|
@@ -2943,6 +2591,10 @@ var registerAgentTool = tool5({
|
|
|
2943
2591
|
if (!data.api_key) {
|
|
2944
2592
|
return { error: "Registration response missing api_key. Something went wrong." };
|
|
2945
2593
|
}
|
|
2594
|
+
const currentAgent = getActiveAgent();
|
|
2595
|
+
if (currentAgent) {
|
|
2596
|
+
stopDaemon(currentAgent);
|
|
2597
|
+
}
|
|
2946
2598
|
saveCredentials(name, {
|
|
2947
2599
|
agent_name: name,
|
|
2948
2600
|
api_key: data.api_key,
|
|
@@ -2966,7 +2618,7 @@ var registerAgentTool = tool5({
|
|
|
2966
2618
|
};
|
|
2967
2619
|
}
|
|
2968
2620
|
});
|
|
2969
|
-
var switchAgentTool =
|
|
2621
|
+
var switchAgentTool = tool6({
|
|
2970
2622
|
description: "Switch to a different registered agent. Updates the active agent. The CLI will need to restart to load the new agent's context.",
|
|
2971
2623
|
parameters: switchAgentSchema,
|
|
2972
2624
|
execute: async ({ agentName }) => {
|
|
@@ -2986,6 +2638,9 @@ var switchAgentTool = tool5({
|
|
|
2986
2638
|
agentName
|
|
2987
2639
|
};
|
|
2988
2640
|
}
|
|
2641
|
+
if (currentAgent) {
|
|
2642
|
+
stopDaemon(currentAgent);
|
|
2643
|
+
}
|
|
2989
2644
|
setActiveAgent(agentName);
|
|
2990
2645
|
requestRestart();
|
|
2991
2646
|
return {
|
|
@@ -2997,9 +2652,9 @@ var switchAgentTool = tool5({
|
|
|
2997
2652
|
};
|
|
2998
2653
|
}
|
|
2999
2654
|
});
|
|
3000
|
-
var listAgentsTool =
|
|
2655
|
+
var listAgentsTool = tool6({
|
|
3001
2656
|
description: "List all AstraNova agents registered on this machine, showing which one is active.",
|
|
3002
|
-
parameters:
|
|
2657
|
+
parameters: z5.object({}),
|
|
3003
2658
|
execute: async () => {
|
|
3004
2659
|
const agents = listAgents();
|
|
3005
2660
|
const active = getActiveAgent();
|
|
@@ -3030,7 +2685,9 @@ var astraTools = {
|
|
|
3030
2685
|
register_agent: registerAgentTool,
|
|
3031
2686
|
switch_agent: switchAgentTool,
|
|
3032
2687
|
list_agents: listAgentsTool,
|
|
3033
|
-
update_memory: updateMemoryTool
|
|
2688
|
+
update_memory: updateMemoryTool,
|
|
2689
|
+
read_strategy: readStrategyTool,
|
|
2690
|
+
write_strategy: writeStrategyTool
|
|
3034
2691
|
};
|
|
3035
2692
|
|
|
3036
2693
|
// src/agent/codex-provider.ts
|
|
@@ -3263,7 +2920,7 @@ function convertToolsForCodex(tools) {
|
|
|
3263
2920
|
}
|
|
3264
2921
|
|
|
3265
2922
|
// src/utils/audit.ts
|
|
3266
|
-
import
|
|
2923
|
+
import fs8 from "fs";
|
|
3267
2924
|
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
3268
2925
|
"secretKey",
|
|
3269
2926
|
"secret_key",
|
|
@@ -3297,12 +2954,12 @@ function sanitize(obj) {
|
|
|
3297
2954
|
function writeAuditEntry(entry) {
|
|
3298
2955
|
const logPath = auditLogPath();
|
|
3299
2956
|
try {
|
|
3300
|
-
if (
|
|
3301
|
-
const stat =
|
|
2957
|
+
if (fs8.existsSync(logPath)) {
|
|
2958
|
+
const stat = fs8.statSync(logPath);
|
|
3302
2959
|
if (stat.size > MAX_LOG_SIZE_BYTES) {
|
|
3303
2960
|
const backupPath = logPath.replace(".log", ".old.log");
|
|
3304
|
-
if (
|
|
3305
|
-
|
|
2961
|
+
if (fs8.existsSync(backupPath)) fs8.unlinkSync(backupPath);
|
|
2962
|
+
fs8.renameSync(logPath, backupPath);
|
|
3306
2963
|
}
|
|
3307
2964
|
}
|
|
3308
2965
|
const line = JSON.stringify({
|
|
@@ -3310,7 +2967,7 @@ function writeAuditEntry(entry) {
|
|
|
3310
2967
|
args: sanitize(entry.args),
|
|
3311
2968
|
result: truncateResult(sanitize(entry.result))
|
|
3312
2969
|
});
|
|
3313
|
-
|
|
2970
|
+
fs8.appendFileSync(logPath, line + "\n", { encoding: "utf-8" });
|
|
3314
2971
|
} catch {
|
|
3315
2972
|
}
|
|
3316
2973
|
}
|
|
@@ -3659,260 +3316,902 @@ async function runResponsesApiTurn(messages, systemPrompt, callbacks, turnConfig
|
|
|
3659
3316
|
});
|
|
3660
3317
|
}
|
|
3661
3318
|
}
|
|
3662
|
-
responseMessages.push({ role: "assistant", content: assistantContent });
|
|
3663
|
-
if (toolResultParts.length > 0) {
|
|
3664
|
-
responseMessages.push({ role: "tool", content: toolResultParts });
|
|
3665
|
-
}
|
|
3666
|
-
accessToken = await getAccessToken();
|
|
3667
|
-
result = await callCodexWithRetry({
|
|
3668
|
-
accessToken,
|
|
3669
|
-
model,
|
|
3670
|
-
instructions: systemPrompt,
|
|
3671
|
-
input: codexInput,
|
|
3672
|
-
tools: codexTools,
|
|
3673
|
-
callbacks,
|
|
3674
|
-
baseUrl,
|
|
3675
|
-
onTokenExpired
|
|
3676
|
-
});
|
|
3677
|
-
finalText = result.text;
|
|
3678
|
-
if (result.incomplete) {
|
|
3679
|
-
debugLog3(`Step ${steps} incomplete: ${result.incompleteReason}`);
|
|
3680
|
-
if (result.toolCalls.length > 0) {
|
|
3681
|
-
debugLog3(`Discarding ${result.toolCalls.length} tool calls from incomplete response`);
|
|
3682
|
-
result = { ...result, toolCalls: [] };
|
|
3319
|
+
responseMessages.push({ role: "assistant", content: assistantContent });
|
|
3320
|
+
if (toolResultParts.length > 0) {
|
|
3321
|
+
responseMessages.push({ role: "tool", content: toolResultParts });
|
|
3322
|
+
}
|
|
3323
|
+
accessToken = await getAccessToken();
|
|
3324
|
+
result = await callCodexWithRetry({
|
|
3325
|
+
accessToken,
|
|
3326
|
+
model,
|
|
3327
|
+
instructions: systemPrompt,
|
|
3328
|
+
input: codexInput,
|
|
3329
|
+
tools: codexTools,
|
|
3330
|
+
callbacks,
|
|
3331
|
+
baseUrl,
|
|
3332
|
+
onTokenExpired
|
|
3333
|
+
});
|
|
3334
|
+
finalText = result.text;
|
|
3335
|
+
if (result.incomplete) {
|
|
3336
|
+
debugLog3(`Step ${steps} incomplete: ${result.incompleteReason}`);
|
|
3337
|
+
if (result.toolCalls.length > 0) {
|
|
3338
|
+
debugLog3(`Discarding ${result.toolCalls.length} tool calls from incomplete response`);
|
|
3339
|
+
result = { ...result, toolCalls: [] };
|
|
3340
|
+
}
|
|
3341
|
+
finalText += "\n\n(Response was truncated \u2014 try a shorter message or start a new session.)";
|
|
3342
|
+
}
|
|
3343
|
+
debugLog3(`Step ${steps}: text=${result.text.length}chars, toolCalls=${result.toolCalls.map((tc) => tc.name).join(",") || "none"}`);
|
|
3344
|
+
}
|
|
3345
|
+
debugLog3(`Responses loop done after ${steps} steps, finalText=${finalText.length}chars`);
|
|
3346
|
+
let text3 = finalText;
|
|
3347
|
+
if (!text3 && steps > 0) {
|
|
3348
|
+
debugLog3("Tools ran but no summary text \u2014 nudging model for a summary");
|
|
3349
|
+
codexInput.push({ role: "user", content: "Please summarize what just happened." });
|
|
3350
|
+
accessToken = await getAccessToken();
|
|
3351
|
+
const summaryRetry = await callCodexWithRetry({
|
|
3352
|
+
accessToken,
|
|
3353
|
+
model,
|
|
3354
|
+
instructions: systemPrompt,
|
|
3355
|
+
input: codexInput,
|
|
3356
|
+
tools: codexTools,
|
|
3357
|
+
callbacks,
|
|
3358
|
+
baseUrl,
|
|
3359
|
+
onTokenExpired
|
|
3360
|
+
});
|
|
3361
|
+
text3 = summaryRetry.text;
|
|
3362
|
+
}
|
|
3363
|
+
if (!text3 && steps > 0) {
|
|
3364
|
+
text3 = "I ran the requested action but the model returned no summary. Please try asking again.";
|
|
3365
|
+
} else if (!text3) {
|
|
3366
|
+
text3 = "(No response from model)";
|
|
3367
|
+
}
|
|
3368
|
+
responseMessages.push({ role: "assistant", content: text3 });
|
|
3369
|
+
return { text: text3, responseMessages };
|
|
3370
|
+
}
|
|
3371
|
+
async function runSdkTurn(messages, systemPrompt, callbacks) {
|
|
3372
|
+
debugLog3(`SDK turn starting \u2014 getting model...`);
|
|
3373
|
+
const model = await getModel();
|
|
3374
|
+
debugLog3(`Model ready: ${model.modelId ?? "unknown"} \u2014 calling streamText...`);
|
|
3375
|
+
const abortController = new AbortController();
|
|
3376
|
+
let idleTimer;
|
|
3377
|
+
let timedOutBy;
|
|
3378
|
+
const overallTimer = setTimeout(() => {
|
|
3379
|
+
debugLog3("SDK turn timeout \u2014 aborting after overall timeout");
|
|
3380
|
+
timedOutBy = "overall";
|
|
3381
|
+
abortController.abort();
|
|
3382
|
+
}, TURN_TIMEOUT_MS);
|
|
3383
|
+
const resetIdleTimer = () => {
|
|
3384
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3385
|
+
idleTimer = setTimeout(() => {
|
|
3386
|
+
debugLog3("SDK turn idle timeout \u2014 no data for 30s, aborting");
|
|
3387
|
+
timedOutBy = "idle";
|
|
3388
|
+
abortController.abort();
|
|
3389
|
+
}, IDLE_TIMEOUT_MS);
|
|
3390
|
+
};
|
|
3391
|
+
try {
|
|
3392
|
+
const result = streamText({
|
|
3393
|
+
model,
|
|
3394
|
+
system: systemPrompt,
|
|
3395
|
+
messages,
|
|
3396
|
+
tools: astraTools,
|
|
3397
|
+
maxSteps: 10,
|
|
3398
|
+
temperature: 0.7,
|
|
3399
|
+
abortSignal: abortController.signal,
|
|
3400
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
3401
|
+
resetIdleTimer();
|
|
3402
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
3403
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
3404
|
+
const tc = toolCalls[i];
|
|
3405
|
+
const tr = toolResults?.[i];
|
|
3406
|
+
callbacks.onToolCallEnd?.(tc.toolName);
|
|
3407
|
+
writeAuditEntry({
|
|
3408
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3409
|
+
tool: tc.toolName,
|
|
3410
|
+
args: tc.args,
|
|
3411
|
+
result: tr?.result,
|
|
3412
|
+
ok: !tr?.result?.error,
|
|
3413
|
+
durationMs: 0
|
|
3414
|
+
// not available from SDK callback
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
});
|
|
3420
|
+
const abortPromise = new Promise((_, reject) => {
|
|
3421
|
+
abortController.signal.addEventListener("abort", () => {
|
|
3422
|
+
const label = timedOutBy === "idle" ? `No response for ${IDLE_TIMEOUT_MS / 1e3}s` : `Response timed out after ${TURN_TIMEOUT_MS / 1e3}s`;
|
|
3423
|
+
reject(new Error(`${label}. Please try again.`));
|
|
3424
|
+
});
|
|
3425
|
+
});
|
|
3426
|
+
debugLog3("streamText created \u2014 consuming textStream...");
|
|
3427
|
+
resetIdleTimer();
|
|
3428
|
+
await Promise.race([
|
|
3429
|
+
(async () => {
|
|
3430
|
+
for await (const chunk of result.textStream) {
|
|
3431
|
+
resetIdleTimer();
|
|
3432
|
+
callbacks.onTextChunk(chunk);
|
|
3433
|
+
}
|
|
3434
|
+
})(),
|
|
3435
|
+
abortPromise
|
|
3436
|
+
]);
|
|
3437
|
+
debugLog3("textStream consumed \u2014 awaiting response...");
|
|
3438
|
+
const response = await Promise.race([result.response, abortPromise]);
|
|
3439
|
+
const text3 = await Promise.race([result.text, abortPromise]);
|
|
3440
|
+
debugLog3(`SDK turn done \u2014 text=${text3.length}chars, messages=${response.messages.length}`);
|
|
3441
|
+
return {
|
|
3442
|
+
text: text3 || "(No response from LLM)",
|
|
3443
|
+
responseMessages: response.messages
|
|
3444
|
+
};
|
|
3445
|
+
} catch (error) {
|
|
3446
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3447
|
+
debugLog3(`SDK turn error: ${message}`);
|
|
3448
|
+
throw error;
|
|
3449
|
+
} finally {
|
|
3450
|
+
clearTimeout(overallTimer);
|
|
3451
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
function convertToCodexInput(messages) {
|
|
3455
|
+
const codexInput = [];
|
|
3456
|
+
for (const m of messages) {
|
|
3457
|
+
if (m.role === "user" || m.role === "assistant") {
|
|
3458
|
+
if (m.role === "assistant" && Array.isArray(m.content)) {
|
|
3459
|
+
const textParts = m.content.filter(
|
|
3460
|
+
(p) => p.type === "text"
|
|
3461
|
+
);
|
|
3462
|
+
if (textParts.length > 0) {
|
|
3463
|
+
codexInput.push({
|
|
3464
|
+
role: "assistant",
|
|
3465
|
+
content: textParts.map((p) => p.text).join("")
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
for (const part of m.content) {
|
|
3469
|
+
if (part.type === "tool-call") {
|
|
3470
|
+
const tc = part;
|
|
3471
|
+
const fcId = tc.toolCallId.startsWith("fc_") ? tc.toolCallId : `fc_${tc.toolCallId}`;
|
|
3472
|
+
codexInput.push({
|
|
3473
|
+
type: "function_call",
|
|
3474
|
+
id: fcId,
|
|
3475
|
+
call_id: tc.toolCallId,
|
|
3476
|
+
name: tc.toolName,
|
|
3477
|
+
arguments: JSON.stringify(tc.args)
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
} else {
|
|
3482
|
+
codexInput.push({
|
|
3483
|
+
role: m.role,
|
|
3484
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
} else if (m.role === "tool") {
|
|
3488
|
+
if (Array.isArray(m.content)) {
|
|
3489
|
+
for (const part of m.content) {
|
|
3490
|
+
if (part.type === "tool-result") {
|
|
3491
|
+
const tr = part;
|
|
3492
|
+
codexInput.push({
|
|
3493
|
+
type: "function_call_output",
|
|
3494
|
+
call_id: tr.toolCallId,
|
|
3495
|
+
output: JSON.stringify(tr.result)
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
return codexInput;
|
|
3503
|
+
}
|
|
3504
|
+
async function summarizeForCompaction(messages, systemPrompt, provider, modelName) {
|
|
3505
|
+
const summaryInstruction = `${systemPrompt}
|
|
3506
|
+
|
|
3507
|
+
---
|
|
3508
|
+
|
|
3509
|
+
${COMPACTION_PROMPT}`;
|
|
3510
|
+
if (provider === "openai-oauth") {
|
|
3511
|
+
const accessToken = await getCodexAccessToken();
|
|
3512
|
+
const codexInput = convertToCodexInput(messages);
|
|
3513
|
+
const result2 = await callCodex({
|
|
3514
|
+
accessToken,
|
|
3515
|
+
model: modelName ?? "gpt-5.3-codex",
|
|
3516
|
+
instructions: summaryInstruction,
|
|
3517
|
+
input: codexInput,
|
|
3518
|
+
timeoutMs: 6e4
|
|
3519
|
+
});
|
|
3520
|
+
return result2.text || "No summary generated.";
|
|
3521
|
+
}
|
|
3522
|
+
if (provider === "openai") {
|
|
3523
|
+
const apiKey = getOpenAIApiKey();
|
|
3524
|
+
const codexInput = convertToCodexInput(messages);
|
|
3525
|
+
const result2 = await callCodex({
|
|
3526
|
+
accessToken: apiKey,
|
|
3527
|
+
model: modelName ?? "gpt-4o-mini",
|
|
3528
|
+
instructions: summaryInstruction,
|
|
3529
|
+
input: codexInput,
|
|
3530
|
+
timeoutMs: 6e4,
|
|
3531
|
+
baseUrl: OPENAI_RESPONSES_URL
|
|
3532
|
+
});
|
|
3533
|
+
return result2.text || "No summary generated.";
|
|
3534
|
+
}
|
|
3535
|
+
const model = await getModel();
|
|
3536
|
+
const result = await generateText({
|
|
3537
|
+
model,
|
|
3538
|
+
system: summaryInstruction,
|
|
3539
|
+
messages,
|
|
3540
|
+
temperature: 0.3
|
|
3541
|
+
});
|
|
3542
|
+
return result.text || "No summary generated.";
|
|
3543
|
+
}
|
|
3544
|
+
var EMPTY_SENTINELS = [
|
|
3545
|
+
"(No response from model)",
|
|
3546
|
+
"(No response from LLM)",
|
|
3547
|
+
"I ran the requested action but the model returned no summary."
|
|
3548
|
+
];
|
|
3549
|
+
function isEmptyResponse(result) {
|
|
3550
|
+
if (!result.text) return true;
|
|
3551
|
+
const t = result.text.trim();
|
|
3552
|
+
return EMPTY_SENTINELS.some((s) => t === s || t.startsWith(s));
|
|
3553
|
+
}
|
|
3554
|
+
function extractJsonSchema(toolDef) {
|
|
3555
|
+
const t = toolDef;
|
|
3556
|
+
if (!t.parameters) return {};
|
|
3557
|
+
const params = t.parameters;
|
|
3558
|
+
if (params._def) {
|
|
3559
|
+
try {
|
|
3560
|
+
return zodToJsonSchema(params);
|
|
3561
|
+
} catch (e) {
|
|
3562
|
+
process.stderr.write(
|
|
3563
|
+
`Warning: Failed to convert tool schema to JSON Schema: ${e instanceof Error ? e.message : "unknown error"}
|
|
3564
|
+
`
|
|
3565
|
+
);
|
|
3566
|
+
return {};
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
if (typeof t.parameters === "object") {
|
|
3570
|
+
return t.parameters;
|
|
3571
|
+
}
|
|
3572
|
+
return {};
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
// src/autopilot/scheduler.ts
|
|
3576
|
+
var EPOCH_BUDGET = 10;
|
|
3577
|
+
var BUDGET_BUFFER = 2;
|
|
3578
|
+
var SEMI_TRIGGER_BASE = "AUTOPILOT CHECK (SEMI): Analyze market and execute a trade based on your strategy. Do NOT ask for confirmation \u2014 execute autonomously and report the result in 2-3 lines. If conditions are not met, say 'Market checked \u2014 holding.' with a brief reason.";
|
|
3579
|
+
var FULL_TRIGGER_BASE = "AUTOPILOT CHECK (FULL): Analyze market and execute a trade based on your strategy. Do NOT ask for confirmation. Do NOT ask about trade size \u2014 use position sizing from your strategy. Execute immediately if conditions are met, skip if uncertain. Keep response to 2-3 lines max.";
|
|
3580
|
+
var STRATEGY_RUN_BASE = "STRATEGY RUN: Check the market against your strategy and execute a trade if conditions are met. Do NOT ask for confirmation. Report what you did (or why you held) in 2-3 lines.";
|
|
3581
|
+
function buildAutopilotTrigger(mode, strategyContent) {
|
|
3582
|
+
const base = mode === "semi" ? SEMI_TRIGGER_BASE : mode === "full" ? FULL_TRIGGER_BASE : null;
|
|
3583
|
+
if (!base) return null;
|
|
3584
|
+
if (!strategyContent?.trim()) return base;
|
|
3585
|
+
return `${base}
|
|
3586
|
+
|
|
3587
|
+
## Your Strategy
|
|
3588
|
+
${strategyContent.trim()}`;
|
|
3589
|
+
}
|
|
3590
|
+
function buildStrategyRunTrigger(strategyContent) {
|
|
3591
|
+
if (!strategyContent?.trim()) return STRATEGY_RUN_BASE;
|
|
3592
|
+
return `${STRATEGY_RUN_BASE}
|
|
3593
|
+
|
|
3594
|
+
## Your Strategy
|
|
3595
|
+
${strategyContent.trim()}`;
|
|
3596
|
+
}
|
|
3597
|
+
function formatInterval(ms) {
|
|
3598
|
+
const minutes = Math.round(ms / 6e4);
|
|
3599
|
+
return `${minutes}m`;
|
|
3600
|
+
}
|
|
3601
|
+
function parseInterval(input) {
|
|
3602
|
+
const match = input.match(/^(\d+)m$/);
|
|
3603
|
+
if (!match) return null;
|
|
3604
|
+
const minutes = parseInt(match[1], 10);
|
|
3605
|
+
if (minutes < 1 || minutes > 60) return null;
|
|
3606
|
+
return minutes * 6e4;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
// src/daemon/autopilot-worker.ts
|
|
3610
|
+
var DEBUG4 = !!process3.env.ASTRA_DEBUG;
|
|
3611
|
+
function debugLog4(msg) {
|
|
3612
|
+
if (DEBUG4) process3.stderr.write(`[astra-daemon] ${msg}
|
|
3613
|
+
`);
|
|
3614
|
+
}
|
|
3615
|
+
async function runDaemon() {
|
|
3616
|
+
const agentName = getActiveAgent();
|
|
3617
|
+
if (!agentName) {
|
|
3618
|
+
process3.stderr.write("[astra-daemon] No active agent \u2014 exiting.\n");
|
|
3619
|
+
process3.exit(1);
|
|
3620
|
+
}
|
|
3621
|
+
const autopilotConfig = loadAutopilotConfig();
|
|
3622
|
+
if (autopilotConfig.mode !== "full") {
|
|
3623
|
+
process3.stderr.write("[astra-daemon] Autopilot mode is not full \u2014 exiting.\n");
|
|
3624
|
+
process3.exit(0);
|
|
3625
|
+
}
|
|
3626
|
+
debugLog4(`Starting for agent "${agentName}", interval ${autopilotConfig.intervalMs}ms`);
|
|
3627
|
+
const [skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext] = await Promise.all([
|
|
3628
|
+
getSkillContext(),
|
|
3629
|
+
fetchRemoteContext("TRADING.md").then((c) => c ?? ""),
|
|
3630
|
+
fetchRemoteContext("WALLET.md").then((c) => c ?? ""),
|
|
3631
|
+
fetchRemoteContext("REWARDS.md").then((c) => c ?? ""),
|
|
3632
|
+
fetchRemoteContext("ONBOARDING.md").then((c) => c ?? ""),
|
|
3633
|
+
fetchRemoteContext("API.md").then((c) => c ?? "")
|
|
3634
|
+
]);
|
|
3635
|
+
let intervalHandle = null;
|
|
3636
|
+
let isRunning = false;
|
|
3637
|
+
process3.on("SIGTERM", () => {
|
|
3638
|
+
debugLog4("Received SIGTERM \u2014 shutting down.");
|
|
3639
|
+
if (intervalHandle) clearInterval(intervalHandle);
|
|
3640
|
+
process3.exit(0);
|
|
3641
|
+
});
|
|
3642
|
+
async function runTick() {
|
|
3643
|
+
if (isRunning) {
|
|
3644
|
+
debugLog4("Tick skipped \u2014 previous turn still running.");
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
const strategy = loadStrategy(agentName);
|
|
3648
|
+
if (!strategy) {
|
|
3649
|
+
debugLog4("No strategy found \u2014 skipping tick.");
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
const budget = loadEpochBudget(agentName);
|
|
3653
|
+
const tradeCount = budget?.callCount ?? 0;
|
|
3654
|
+
if (tradeCount >= EPOCH_BUDGET - BUDGET_BUFFER) {
|
|
3655
|
+
debugLog4(`Epoch budget reached (${tradeCount}/${EPOCH_BUDGET}) \u2014 skipping tick.`);
|
|
3656
|
+
appendAutopilotLog(agentName, {
|
|
3657
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3658
|
+
action: `Budget reached (${tradeCount}/${EPOCH_BUDGET}) \u2014 skipping until next epoch`
|
|
3659
|
+
});
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
const trigger = buildAutopilotTrigger("full", strategy);
|
|
3663
|
+
if (!trigger) return;
|
|
3664
|
+
const state = loadState();
|
|
3665
|
+
const profile = {
|
|
3666
|
+
agentName,
|
|
3667
|
+
autopilotMode: "full",
|
|
3668
|
+
hasStrategy: true,
|
|
3669
|
+
journeyStage: state?.agents[agentName]?.journeyStage ?? "full"
|
|
3670
|
+
};
|
|
3671
|
+
const memoryContent = loadMemory(agentName);
|
|
3672
|
+
const coreMessages = [{ role: "user", content: trigger }];
|
|
3673
|
+
isRunning = true;
|
|
3674
|
+
try {
|
|
3675
|
+
debugLog4("Running autopilot turn...");
|
|
3676
|
+
const result = await runAgentTurn(
|
|
3677
|
+
coreMessages,
|
|
3678
|
+
skillContext,
|
|
3679
|
+
tradingContext,
|
|
3680
|
+
walletContext,
|
|
3681
|
+
rewardsContext,
|
|
3682
|
+
onboardingContext,
|
|
3683
|
+
apiContext,
|
|
3684
|
+
profile,
|
|
3685
|
+
{ onTextChunk: () => {
|
|
3686
|
+
}, onToolCallStart: () => {
|
|
3687
|
+
}, onToolCallEnd: () => {
|
|
3688
|
+
} },
|
|
3689
|
+
memoryContent
|
|
3690
|
+
);
|
|
3691
|
+
const responseText = result.text.trim();
|
|
3692
|
+
const summary = responseText.split("\n")[0].slice(0, 120) || "checked \u2192 no response";
|
|
3693
|
+
debugLog4(`Turn complete: ${summary}`);
|
|
3694
|
+
appendAutopilotLog(agentName, {
|
|
3695
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3696
|
+
action: summary
|
|
3697
|
+
});
|
|
3698
|
+
} catch (err) {
|
|
3699
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3700
|
+
debugLog4(`Turn error: ${msg}`);
|
|
3701
|
+
appendAutopilotLog(agentName, {
|
|
3702
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3703
|
+
action: `error: ${msg.slice(0, 100)}`
|
|
3704
|
+
});
|
|
3705
|
+
} finally {
|
|
3706
|
+
isRunning = false;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
await runTick();
|
|
3710
|
+
intervalHandle = setInterval(() => {
|
|
3711
|
+
void runTick();
|
|
3712
|
+
}, autopilotConfig.intervalMs);
|
|
3713
|
+
debugLog4("Daemon running. Waiting for ticks...");
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
// src/ui/App.tsx
|
|
3717
|
+
import { useState as useState4, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
3718
|
+
import { Box as Box7, Text as Text8, useApp, useInput } from "ink";
|
|
3719
|
+
|
|
3720
|
+
// src/ui/StatusBar.tsx
|
|
3721
|
+
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
3722
|
+
import { Box, Text } from "ink";
|
|
3723
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3724
|
+
var POLL_INTERVAL_MS = 6e4;
|
|
3725
|
+
var StatusBar = React.memo(function StatusBar2({
|
|
3726
|
+
agentName,
|
|
3727
|
+
journeyStage,
|
|
3728
|
+
autopilotMode = "off",
|
|
3729
|
+
autopilotIntervalMs = 3e5,
|
|
3730
|
+
onEpochChange
|
|
3731
|
+
}) {
|
|
3732
|
+
const [data, setData] = useState({ market: null, portfolio: null });
|
|
3733
|
+
const mounted = useRef(true);
|
|
3734
|
+
const canFetchData = journeyStage !== "fresh" && journeyStage !== "pending";
|
|
3735
|
+
const poll = useCallback(async () => {
|
|
3736
|
+
const [marketRes, portfolioRes] = await Promise.all([
|
|
3737
|
+
fetchMarket(agentName),
|
|
3738
|
+
fetchPortfolio(agentName)
|
|
3739
|
+
]);
|
|
3740
|
+
if (!mounted.current) return;
|
|
3741
|
+
setData((prev) => ({
|
|
3742
|
+
market: marketRes ?? prev.market,
|
|
3743
|
+
portfolio: portfolioRes ?? prev.portfolio
|
|
3744
|
+
}));
|
|
3745
|
+
if (marketRes && onEpochChange) {
|
|
3746
|
+
onEpochChange(marketRes.epochId);
|
|
3747
|
+
}
|
|
3748
|
+
}, [agentName, onEpochChange]);
|
|
3749
|
+
useEffect(() => {
|
|
3750
|
+
mounted.current = true;
|
|
3751
|
+
if (!canFetchData) return;
|
|
3752
|
+
void poll();
|
|
3753
|
+
const interval = setInterval(() => void poll(), POLL_INTERVAL_MS);
|
|
3754
|
+
return () => {
|
|
3755
|
+
mounted.current = false;
|
|
3756
|
+
clearInterval(interval);
|
|
3757
|
+
};
|
|
3758
|
+
}, [canFetchData, poll]);
|
|
3759
|
+
const { market, portfolio } = data;
|
|
3760
|
+
const apActive = autopilotMode !== "off";
|
|
3761
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [
|
|
3762
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
3763
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "#00ff00", children: "AstraNova" }),
|
|
3764
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3765
|
+
/* @__PURE__ */ jsx(Text, { color: "#ff8800", children: agentName }),
|
|
3766
|
+
canFetchData && market && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3767
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3768
|
+
/* @__PURE__ */ jsx(Text, { color: "#ffff00", children: "$NOVA " }),
|
|
3769
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: formatPrice(market.price) }),
|
|
3770
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3771
|
+
/* @__PURE__ */ jsx(Text, { color: moodColor(market.mood), children: market.mood })
|
|
3772
|
+
] }),
|
|
3773
|
+
canFetchData && portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3774
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3775
|
+
/* @__PURE__ */ jsxs(Text, { color: "#00ffff", children: [
|
|
3776
|
+
formatNum(portfolio.cash),
|
|
3777
|
+
" $SIM"
|
|
3778
|
+
] }),
|
|
3779
|
+
portfolio.tokens > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3780
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3781
|
+
/* @__PURE__ */ jsxs(Text, { color: "#ff00ff", children: [
|
|
3782
|
+
formatNum(portfolio.tokens),
|
|
3783
|
+
" $NOVA"
|
|
3784
|
+
] })
|
|
3785
|
+
] }),
|
|
3786
|
+
portfolio.pnl !== 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3787
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3788
|
+
/* @__PURE__ */ jsxs(Text, { color: portfolio.pnl >= 0 ? "#00ff00" : "#ff4444", children: [
|
|
3789
|
+
"P&L ",
|
|
3790
|
+
portfolio.pnl >= 0 ? "+" : "",
|
|
3791
|
+
formatNum(portfolio.pnl),
|
|
3792
|
+
" (",
|
|
3793
|
+
portfolio.pnlPct >= 0 ? "+" : "",
|
|
3794
|
+
portfolio.pnlPct.toFixed(1),
|
|
3795
|
+
"%)"
|
|
3796
|
+
] })
|
|
3797
|
+
] }),
|
|
3798
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3799
|
+
/* @__PURE__ */ jsxs(Text, { color: "#e2f902", children: [
|
|
3800
|
+
"Net ",
|
|
3801
|
+
formatNum(portfolio.portfolioValue)
|
|
3802
|
+
] })
|
|
3803
|
+
] }),
|
|
3804
|
+
!canFetchData && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3805
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3806
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "pending verification" })
|
|
3807
|
+
] }),
|
|
3808
|
+
canFetchData && !market && !portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3809
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
3810
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
|
|
3811
|
+
] })
|
|
3812
|
+
] }),
|
|
3813
|
+
apActive && /* @__PURE__ */ jsxs(Text, { color: "#00ff00", children: [
|
|
3814
|
+
"AP: \u25CF ",
|
|
3815
|
+
autopilotMode.toUpperCase(),
|
|
3816
|
+
" ",
|
|
3817
|
+
formatInterval(autopilotIntervalMs)
|
|
3818
|
+
] })
|
|
3819
|
+
] }) });
|
|
3820
|
+
});
|
|
3821
|
+
var StatusBar_default = StatusBar;
|
|
3822
|
+
function formatPrice(price) {
|
|
3823
|
+
if (price >= 1) return price.toFixed(2);
|
|
3824
|
+
if (price >= 0.01) return price.toFixed(4);
|
|
3825
|
+
return price.toFixed(6);
|
|
3826
|
+
}
|
|
3827
|
+
function formatNum(n) {
|
|
3828
|
+
if (Math.abs(n) >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
3829
|
+
if (Math.abs(n) >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
3830
|
+
return n.toLocaleString(void 0, { maximumFractionDigits: 0 });
|
|
3831
|
+
}
|
|
3832
|
+
function moodColor(mood) {
|
|
3833
|
+
switch (mood) {
|
|
3834
|
+
case "euphoria":
|
|
3835
|
+
case "bullish":
|
|
3836
|
+
return "#00ff00";
|
|
3837
|
+
case "fear":
|
|
3838
|
+
case "bearish":
|
|
3839
|
+
return "#ff4444";
|
|
3840
|
+
case "crab":
|
|
3841
|
+
return "#ffff00";
|
|
3842
|
+
default:
|
|
3843
|
+
return "white";
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
async function fetchMarket(agentName) {
|
|
3847
|
+
const result = await apiCall("GET", "/api/v1/market/state", void 0, agentName);
|
|
3848
|
+
if (!result.ok) return null;
|
|
3849
|
+
const d = result.data;
|
|
3850
|
+
const m = d.market ?? d;
|
|
3851
|
+
return {
|
|
3852
|
+
price: m.price ?? 0,
|
|
3853
|
+
mood: m.mood ?? "",
|
|
3854
|
+
epochId: m.epoch?.global ?? 0
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
async function fetchPortfolio(agentName) {
|
|
3858
|
+
const result = await apiCall(
|
|
3859
|
+
"GET",
|
|
3860
|
+
"/api/v1/portfolio",
|
|
3861
|
+
void 0,
|
|
3862
|
+
agentName
|
|
3863
|
+
);
|
|
3864
|
+
if (!result.ok) return null;
|
|
3865
|
+
const d = result.data;
|
|
3866
|
+
const p = d.portfolio ?? d;
|
|
3867
|
+
const cash = p.cash ?? 0;
|
|
3868
|
+
const tokens = p.tokens ?? 0;
|
|
3869
|
+
const currentPrice = p.currentPrice ?? 0;
|
|
3870
|
+
return {
|
|
3871
|
+
cash,
|
|
3872
|
+
tokens,
|
|
3873
|
+
portfolioValue: p.portfolioValue ?? cash + tokens * currentPrice,
|
|
3874
|
+
pnl: p.pnl ?? 0,
|
|
3875
|
+
pnlPct: p.pnlPct ?? 0
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
// src/ui/ChatView.tsx
|
|
3880
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
3881
|
+
|
|
3882
|
+
// src/ui/MarkdownText.tsx
|
|
3883
|
+
import { Text as Text4, Box as Box4 } from "ink";
|
|
3884
|
+
|
|
3885
|
+
// src/ui/PortfolioCard.tsx
|
|
3886
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
3887
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
3888
|
+
function PortfolioCard({ data }) {
|
|
3889
|
+
const price = data.currentPrice ?? 0;
|
|
3890
|
+
const cash = data.cash ?? 0;
|
|
3891
|
+
const tokens = data.tokens ?? 0;
|
|
3892
|
+
const value = data.portfolioValue ?? cash + tokens * price;
|
|
3893
|
+
const pnl = data.pnl ?? 0;
|
|
3894
|
+
const pnlPct = data.pnlPct ?? 0;
|
|
3895
|
+
const earned = data.totalEarned ? Number(data.totalEarned) / 1e9 : 0;
|
|
3896
|
+
const claimable = data.claimable ? Number(data.claimable) / 1e9 : 0;
|
|
3897
|
+
const pnlColor = pnl >= 0 ? "#00ff00" : "#ff4444";
|
|
3898
|
+
const pnlSign = pnl >= 0 ? "+" : "";
|
|
3899
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
|
|
3900
|
+
/* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#00ffff", children: "Portfolio Overview" }) }),
|
|
3901
|
+
/* @__PURE__ */ jsx2(Row, { label: "$SIM Balance", value: formatNum2(cash), color: "#00ffff" }),
|
|
3902
|
+
/* @__PURE__ */ jsx2(Row, { label: "$NOVA Holdings", value: tokens > 0 ? formatNum2(tokens) : "\u2014", color: "#ff00ff" }),
|
|
3903
|
+
/* @__PURE__ */ jsx2(Row, { label: "$NOVA Price", value: price > 0 ? formatPrice2(price) : "\u2014", color: "#ffff00" }),
|
|
3904
|
+
/* @__PURE__ */ jsx2(Row, { label: "Portfolio Value", value: formatNum2(value), color: "white" }),
|
|
3905
|
+
/* @__PURE__ */ jsx2(Row, { label: "P&L", value: `${pnlSign}${formatNum2(pnl)} (${pnlSign}${pnlPct.toFixed(1)}%)`, color: pnlColor }),
|
|
3906
|
+
(data.hasWallet !== void 0 || data.walletLocal !== void 0) && /* @__PURE__ */ jsx2(
|
|
3907
|
+
Row,
|
|
3908
|
+
{
|
|
3909
|
+
label: "Wallet",
|
|
3910
|
+
value: data.hasWallet ? "registered" : data.walletLocal ? "needs registration" : "not set",
|
|
3911
|
+
color: data.hasWallet ? "#00ff00" : data.walletLocal ? "#ffff00" : "gray"
|
|
3912
|
+
}
|
|
3913
|
+
),
|
|
3914
|
+
(earned > 0 || claimable > 0) && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
3915
|
+
/* @__PURE__ */ jsx2(Row, { label: "$ASTRA Earned", value: earned > 0 ? formatAstra(earned) : "\u2014", color: "#ffff00" }),
|
|
3916
|
+
/* @__PURE__ */ jsx2(Row, { label: "Claimable", value: claimable > 0 ? formatAstra(claimable) : "\u2014", color: claimable > 0 ? "#00ff00" : "gray" })
|
|
3917
|
+
] })
|
|
3918
|
+
] });
|
|
3919
|
+
}
|
|
3920
|
+
function Row({ label, value, color }) {
|
|
3921
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
3922
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
3923
|
+
label,
|
|
3924
|
+
": "
|
|
3925
|
+
] }),
|
|
3926
|
+
/* @__PURE__ */ jsx2(Text2, { color, bold: true, children: value })
|
|
3927
|
+
] });
|
|
3928
|
+
}
|
|
3929
|
+
function formatPrice2(price) {
|
|
3930
|
+
if (price >= 1) return `$${price.toFixed(2)}`;
|
|
3931
|
+
if (price >= 0.01) return `$${price.toFixed(4)}`;
|
|
3932
|
+
return `$${price.toFixed(6)}`;
|
|
3933
|
+
}
|
|
3934
|
+
function formatNum2(n) {
|
|
3935
|
+
if (Math.abs(n) >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
|
|
3936
|
+
if (Math.abs(n) >= 1e4) return `${(n / 1e3).toFixed(1)}K`;
|
|
3937
|
+
return n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
3938
|
+
}
|
|
3939
|
+
function formatAstra(n) {
|
|
3940
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M ASTRA`;
|
|
3941
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(2)}K ASTRA`;
|
|
3942
|
+
return `${n.toFixed(2)} ASTRA`;
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
// src/ui/RewardsCard.tsx
|
|
3946
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
3947
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3948
|
+
function RewardsCard({ data }) {
|
|
3949
|
+
const total = data.totalAstra ? Number(data.totalAstra) / 1e9 : 0;
|
|
3950
|
+
const epoch = data.epochAstra ? Number(data.epochAstra) / 1e9 : 0;
|
|
3951
|
+
const bonus = data.bonusAstra ? Number(data.bonusAstra) / 1e9 : 0;
|
|
3952
|
+
const statusColor = data.claimStatus === "claimable" ? "#00ff00" : data.claimStatus === "sent" ? "#00ffff" : "#ffff00";
|
|
3953
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
|
|
3954
|
+
/* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
3955
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "#ffff00", children: "$ASTRA Rewards" }),
|
|
3956
|
+
data.seasonId && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
3957
|
+
" ",
|
|
3958
|
+
data.seasonId
|
|
3959
|
+
] })
|
|
3960
|
+
] }),
|
|
3961
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Total Earned", value: formatAstra2(total), color: "#ffff00" }),
|
|
3962
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Epoch Rewards", value: formatAstra2(epoch), color: "#00ffff" }),
|
|
3963
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Season Bonus", value: formatAstra2(bonus), color: "#ff00ff" }),
|
|
3964
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Status", value: data.claimStatus ?? "\u2014", color: statusColor }),
|
|
3965
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Epochs Rewarded", value: data.epochsRewarded?.toString() ?? "\u2014", color: "white" }),
|
|
3966
|
+
data.bestEpochPnl !== void 0 && data.bestEpochPnl > 0 && /* @__PURE__ */ jsx3(Row2, { label: "Best Epoch P&L", value: `+${data.bestEpochPnl.toFixed(2)}`, color: "#00ff00" }),
|
|
3967
|
+
data.txSignature && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
3968
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tx: " }),
|
|
3969
|
+
/* @__PURE__ */ jsx3(Text3, { color: "#00ffff", children: data.txSignature })
|
|
3970
|
+
] })
|
|
3971
|
+
] });
|
|
3972
|
+
}
|
|
3973
|
+
function Row2({ label, value, color }) {
|
|
3974
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3975
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
3976
|
+
label,
|
|
3977
|
+
": "
|
|
3978
|
+
] }),
|
|
3979
|
+
/* @__PURE__ */ jsx3(Text3, { color, bold: true, children: value })
|
|
3980
|
+
] });
|
|
3981
|
+
}
|
|
3982
|
+
function formatAstra2(n) {
|
|
3983
|
+
if (n === 0) return "\u2014";
|
|
3984
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M ASTRA`;
|
|
3985
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(2)}K ASTRA`;
|
|
3986
|
+
return `${n.toFixed(4)} ASTRA`;
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
// src/ui/MarkdownText.tsx
|
|
3990
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3991
|
+
function MarkdownText({ children }) {
|
|
3992
|
+
const lines = children.split("\n");
|
|
3993
|
+
const elements = [];
|
|
3994
|
+
let i = 0;
|
|
3995
|
+
while (i < lines.length) {
|
|
3996
|
+
const line = lines[i];
|
|
3997
|
+
if (line.trimStart().startsWith(":::portfolio") || line.trimStart().startsWith(":::rewards")) {
|
|
3998
|
+
const cardType = line.trimStart().startsWith(":::portfolio") ? "portfolio" : "rewards";
|
|
3999
|
+
const jsonLines = [];
|
|
4000
|
+
i++;
|
|
4001
|
+
while (i < lines.length && !lines[i].trimStart().startsWith(":::")) {
|
|
4002
|
+
jsonLines.push(lines[i]);
|
|
4003
|
+
i++;
|
|
4004
|
+
}
|
|
4005
|
+
i++;
|
|
4006
|
+
try {
|
|
4007
|
+
const raw = jsonLines.join("\n");
|
|
4008
|
+
if (cardType === "portfolio") {
|
|
4009
|
+
const data = JSON.parse(raw);
|
|
4010
|
+
elements.push(/* @__PURE__ */ jsx4(PortfolioCard, { data }, elements.length));
|
|
4011
|
+
} else {
|
|
4012
|
+
const data = JSON.parse(raw);
|
|
4013
|
+
elements.push(/* @__PURE__ */ jsx4(RewardsCard, { data }, elements.length));
|
|
4014
|
+
}
|
|
4015
|
+
} catch {
|
|
4016
|
+
elements.push(
|
|
4017
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: jsonLines.join("\n") }) }, elements.length)
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
4020
|
+
continue;
|
|
4021
|
+
}
|
|
4022
|
+
if (line.trimStart().startsWith("```")) {
|
|
4023
|
+
const codeLines = [];
|
|
4024
|
+
i++;
|
|
4025
|
+
while (i < lines.length && !lines[i].trimStart().startsWith("```")) {
|
|
4026
|
+
codeLines.push(lines[i]);
|
|
4027
|
+
i++;
|
|
3683
4028
|
}
|
|
3684
|
-
|
|
4029
|
+
i++;
|
|
4030
|
+
elements.push(
|
|
4031
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, marginY: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "gray", children: codeLines.join("\n") }) }, elements.length)
|
|
4032
|
+
);
|
|
4033
|
+
continue;
|
|
3685
4034
|
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
4035
|
+
if (/^---+$/.test(line.trim())) {
|
|
4036
|
+
elements.push(
|
|
4037
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(40) }) }, elements.length)
|
|
4038
|
+
);
|
|
4039
|
+
i++;
|
|
4040
|
+
continue;
|
|
4041
|
+
}
|
|
4042
|
+
const headerMatch = /^(#{1,4})\s+(.+)$/.exec(line);
|
|
4043
|
+
if (headerMatch) {
|
|
4044
|
+
elements.push(
|
|
4045
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: elements.length > 0 ? 1 : 0, children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: headerMatch[2] }) }, elements.length)
|
|
4046
|
+
);
|
|
4047
|
+
i++;
|
|
4048
|
+
continue;
|
|
4049
|
+
}
|
|
4050
|
+
const bulletMatch = /^(\s*)[-*]\s+(.+)$/.exec(line);
|
|
4051
|
+
if (bulletMatch) {
|
|
4052
|
+
const indent = Math.floor((bulletMatch[1]?.length ?? 0) / 2);
|
|
4053
|
+
elements.push(
|
|
4054
|
+
/* @__PURE__ */ jsxs4(Box4, { marginLeft: indent * 2, children: [
|
|
4055
|
+
/* @__PURE__ */ jsx4(Text4, { children: " \u25CF " }),
|
|
4056
|
+
renderInline(bulletMatch[2])
|
|
4057
|
+
] }, elements.length)
|
|
4058
|
+
);
|
|
4059
|
+
i++;
|
|
4060
|
+
continue;
|
|
4061
|
+
}
|
|
4062
|
+
const numMatch = /^(\s*)(\d+)[.)]\s+(.+)$/.exec(line);
|
|
4063
|
+
if (numMatch) {
|
|
4064
|
+
const indent = Math.floor((numMatch[1]?.length ?? 0) / 2);
|
|
4065
|
+
elements.push(
|
|
4066
|
+
/* @__PURE__ */ jsxs4(Box4, { marginLeft: indent * 2, children: [
|
|
4067
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
4068
|
+
" ",
|
|
4069
|
+
numMatch[2],
|
|
4070
|
+
". "
|
|
4071
|
+
] }),
|
|
4072
|
+
renderInline(numMatch[3])
|
|
4073
|
+
] }, elements.length)
|
|
4074
|
+
);
|
|
4075
|
+
i++;
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
if (line.trim() === "") {
|
|
4079
|
+
elements.push(/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { children: " " }) }, elements.length));
|
|
4080
|
+
i++;
|
|
4081
|
+
continue;
|
|
4082
|
+
}
|
|
4083
|
+
elements.push(
|
|
4084
|
+
/* @__PURE__ */ jsx4(Box4, { flexWrap: "wrap", children: renderInline(line) }, elements.length)
|
|
4085
|
+
);
|
|
4086
|
+
i++;
|
|
3710
4087
|
}
|
|
3711
|
-
|
|
3712
|
-
return { text: text3, responseMessages };
|
|
4088
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: elements });
|
|
3713
4089
|
}
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
const resetIdleTimer = () => {
|
|
3727
|
-
if (idleTimer) clearTimeout(idleTimer);
|
|
3728
|
-
idleTimer = setTimeout(() => {
|
|
3729
|
-
debugLog3("SDK turn idle timeout \u2014 no data for 30s, aborting");
|
|
3730
|
-
timedOutBy = "idle";
|
|
3731
|
-
abortController.abort();
|
|
3732
|
-
}, IDLE_TIMEOUT_MS);
|
|
3733
|
-
};
|
|
3734
|
-
try {
|
|
3735
|
-
const result = streamText({
|
|
3736
|
-
model,
|
|
3737
|
-
system: systemPrompt,
|
|
3738
|
-
messages,
|
|
3739
|
-
tools: astraTools,
|
|
3740
|
-
maxSteps: 10,
|
|
3741
|
-
temperature: 0.7,
|
|
3742
|
-
abortSignal: abortController.signal,
|
|
3743
|
-
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
3744
|
-
resetIdleTimer();
|
|
3745
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
3746
|
-
for (let i = 0; i < toolCalls.length; i++) {
|
|
3747
|
-
const tc = toolCalls[i];
|
|
3748
|
-
const tr = toolResults?.[i];
|
|
3749
|
-
callbacks.onToolCallEnd?.(tc.toolName);
|
|
3750
|
-
writeAuditEntry({
|
|
3751
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3752
|
-
tool: tc.toolName,
|
|
3753
|
-
args: tc.args,
|
|
3754
|
-
result: tr?.result,
|
|
3755
|
-
ok: !tr?.result?.error,
|
|
3756
|
-
durationMs: 0
|
|
3757
|
-
// not available from SDK callback
|
|
3758
|
-
});
|
|
3759
|
-
}
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
});
|
|
3763
|
-
const abortPromise = new Promise((_, reject) => {
|
|
3764
|
-
abortController.signal.addEventListener("abort", () => {
|
|
3765
|
-
const label = timedOutBy === "idle" ? `No response for ${IDLE_TIMEOUT_MS / 1e3}s` : `Response timed out after ${TURN_TIMEOUT_MS / 1e3}s`;
|
|
3766
|
-
reject(new Error(`${label}. Please try again.`));
|
|
4090
|
+
function renderInline(text3) {
|
|
4091
|
+
const parts = [];
|
|
4092
|
+
let remaining = text3;
|
|
4093
|
+
let key = 0;
|
|
4094
|
+
while (remaining.length > 0) {
|
|
4095
|
+
const candidates = [];
|
|
4096
|
+
const boldMatch = /\*\*(.+?)\*\*|__(.+?)__/.exec(remaining);
|
|
4097
|
+
if (boldMatch) {
|
|
4098
|
+
candidates.push({
|
|
4099
|
+
index: boldMatch.index,
|
|
4100
|
+
length: boldMatch[0].length,
|
|
4101
|
+
node: /* @__PURE__ */ jsx4(Text4, { bold: true, children: boldMatch[1] ?? boldMatch[2] }, key++)
|
|
3767
4102
|
});
|
|
3768
|
-
}
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
4103
|
+
}
|
|
4104
|
+
const codeMatch = /`([^`]+?)`/.exec(remaining);
|
|
4105
|
+
if (codeMatch) {
|
|
4106
|
+
candidates.push({
|
|
4107
|
+
index: codeMatch.index,
|
|
4108
|
+
length: codeMatch[0].length,
|
|
4109
|
+
node: /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: codeMatch[1] }, key++)
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
const italicMatch = /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/.exec(remaining);
|
|
4113
|
+
if (italicMatch) {
|
|
4114
|
+
candidates.push({
|
|
4115
|
+
index: italicMatch.index,
|
|
4116
|
+
length: italicMatch[0].length,
|
|
4117
|
+
node: /* @__PURE__ */ jsx4(Text4, { italic: true, children: italicMatch[1] ?? italicMatch[2] }, key++)
|
|
4118
|
+
});
|
|
4119
|
+
}
|
|
4120
|
+
candidates.sort((a, b) => a.index - b.index);
|
|
4121
|
+
const pick = candidates[0];
|
|
4122
|
+
if (!pick) {
|
|
4123
|
+
parts.push(/* @__PURE__ */ jsx4(Text4, { children: remaining }, key++));
|
|
4124
|
+
break;
|
|
4125
|
+
}
|
|
4126
|
+
if (pick.index > 0) {
|
|
4127
|
+
parts.push(/* @__PURE__ */ jsx4(Text4, { children: remaining.slice(0, pick.index) }, key++));
|
|
4128
|
+
}
|
|
4129
|
+
parts.push(pick.node);
|
|
4130
|
+
remaining = remaining.slice(pick.index + pick.length);
|
|
3795
4131
|
}
|
|
4132
|
+
return /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: parts });
|
|
3796
4133
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
content: textParts.map((p) => p.text).join("")
|
|
3809
|
-
});
|
|
3810
|
-
}
|
|
3811
|
-
for (const part of m.content) {
|
|
3812
|
-
if (part.type === "tool-call") {
|
|
3813
|
-
const tc = part;
|
|
3814
|
-
const fcId = tc.toolCallId.startsWith("fc_") ? tc.toolCallId : `fc_${tc.toolCallId}`;
|
|
3815
|
-
codexInput.push({
|
|
3816
|
-
type: "function_call",
|
|
3817
|
-
id: fcId,
|
|
3818
|
-
call_id: tc.toolCallId,
|
|
3819
|
-
name: tc.toolName,
|
|
3820
|
-
arguments: JSON.stringify(tc.args)
|
|
3821
|
-
});
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
} else {
|
|
3825
|
-
codexInput.push({
|
|
3826
|
-
role: m.role,
|
|
3827
|
-
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
3828
|
-
});
|
|
4134
|
+
|
|
4135
|
+
// src/ui/ChatView.tsx
|
|
4136
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
4137
|
+
function ChatView({
|
|
4138
|
+
messages,
|
|
4139
|
+
streamingText
|
|
4140
|
+
}) {
|
|
4141
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", paddingX: 1, children: [
|
|
4142
|
+
messages.map((msg, i) => {
|
|
4143
|
+
if (msg.role === "log") {
|
|
4144
|
+
return /* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: msg.content }) }, i);
|
|
3829
4145
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
codexInput.push({
|
|
3836
|
-
type: "function_call_output",
|
|
3837
|
-
call_id: tr.toolCallId,
|
|
3838
|
-
output: JSON.stringify(tr.result)
|
|
3839
|
-
});
|
|
3840
|
-
}
|
|
3841
|
-
}
|
|
4146
|
+
if (msg.role === "autopilot") {
|
|
4147
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
4148
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "#ff00ff", children: " Autopilot" }),
|
|
4149
|
+
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "wrap", children: msg.content }) })
|
|
4150
|
+
] }, i);
|
|
3842
4151
|
}
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
4152
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
4153
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: msg.role === "user" ? "#00ff00" : "#00ffff", children: msg.role === "user" ? " You" : " Agent" }),
|
|
4154
|
+
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(MarkdownText, { children: msg.content }) : /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: msg.content }) })
|
|
4155
|
+
] }, i);
|
|
4156
|
+
}),
|
|
4157
|
+
streamingText !== void 0 && streamingText.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
4158
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "#00ffff", children: " Agent" }),
|
|
4159
|
+
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(MarkdownText, { children: streamingText }) })
|
|
4160
|
+
] })
|
|
4161
|
+
] });
|
|
3846
4162
|
}
|
|
3847
|
-
async function summarizeForCompaction(messages, systemPrompt, provider, modelName) {
|
|
3848
|
-
const summaryInstruction = `${systemPrompt}
|
|
3849
|
-
|
|
3850
|
-
---
|
|
3851
4163
|
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
messages,
|
|
3883
|
-
temperature: 0.3
|
|
3884
|
-
});
|
|
3885
|
-
return result.text || "No summary generated.";
|
|
3886
|
-
}
|
|
3887
|
-
var EMPTY_SENTINELS = [
|
|
3888
|
-
"(No response from model)",
|
|
3889
|
-
"(No response from LLM)",
|
|
3890
|
-
"I ran the requested action but the model returned no summary."
|
|
3891
|
-
];
|
|
3892
|
-
function isEmptyResponse(result) {
|
|
3893
|
-
if (!result.text) return true;
|
|
3894
|
-
const t = result.text.trim();
|
|
3895
|
-
return EMPTY_SENTINELS.some((s) => t === s || t.startsWith(s));
|
|
4164
|
+
// src/ui/Input.tsx
|
|
4165
|
+
import { useState as useState2 } from "react";
|
|
4166
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
4167
|
+
import TextInput from "ink-text-input";
|
|
4168
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4169
|
+
function Input({
|
|
4170
|
+
isActive,
|
|
4171
|
+
onSubmit
|
|
4172
|
+
}) {
|
|
4173
|
+
const [value, setValue] = useState2("");
|
|
4174
|
+
const handleSubmit = (submitted) => {
|
|
4175
|
+
const trimmed = submitted.trim();
|
|
4176
|
+
if (!trimmed) return;
|
|
4177
|
+
onSubmit(trimmed);
|
|
4178
|
+
setValue("");
|
|
4179
|
+
};
|
|
4180
|
+
return /* @__PURE__ */ jsxs6(Box6, { width: "100%", paddingX: 2, paddingY: 1, children: [
|
|
4181
|
+
/* @__PURE__ */ jsx6(Text6, { color: isActive ? "yellow" : "gray", bold: true, children: "\u276F\u276F " }),
|
|
4182
|
+
/* @__PURE__ */ jsx6(
|
|
4183
|
+
TextInput,
|
|
4184
|
+
{
|
|
4185
|
+
value,
|
|
4186
|
+
onChange: setValue,
|
|
4187
|
+
onSubmit: handleSubmit,
|
|
4188
|
+
focus: isActive,
|
|
4189
|
+
showCursor: isActive,
|
|
4190
|
+
placeholder: ""
|
|
4191
|
+
}
|
|
4192
|
+
)
|
|
4193
|
+
] });
|
|
3896
4194
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
);
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
}
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4195
|
+
|
|
4196
|
+
// src/ui/Spinner.tsx
|
|
4197
|
+
import { useState as useState3, useEffect as useEffect2 } from "react";
|
|
4198
|
+
import { Text as Text7 } from "ink";
|
|
4199
|
+
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4200
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4201
|
+
var INTERVAL_MS = 80;
|
|
4202
|
+
function Spinner({ label }) {
|
|
4203
|
+
const [frame, setFrame] = useState3(0);
|
|
4204
|
+
useEffect2(() => {
|
|
4205
|
+
const timer = setInterval(() => {
|
|
4206
|
+
setFrame((prev) => (prev + 1) % FRAMES.length);
|
|
4207
|
+
}, INTERVAL_MS);
|
|
4208
|
+
return () => clearInterval(timer);
|
|
4209
|
+
}, []);
|
|
4210
|
+
return /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4211
|
+
FRAMES[frame],
|
|
4212
|
+
" ",
|
|
4213
|
+
label ?? "Thinking..."
|
|
4214
|
+
] });
|
|
3916
4215
|
}
|
|
3917
4216
|
|
|
3918
4217
|
// src/ui/App.tsx
|
|
@@ -3931,6 +4230,7 @@ function App({
|
|
|
3931
4230
|
initialCoreMessages,
|
|
3932
4231
|
initialChatMessages,
|
|
3933
4232
|
initialAutopilotConfig,
|
|
4233
|
+
initialPendingTrades = 0,
|
|
3934
4234
|
debug
|
|
3935
4235
|
}) {
|
|
3936
4236
|
const { exit } = useApp();
|
|
@@ -3959,6 +4259,18 @@ function App({
|
|
|
3959
4259
|
epochCallCountRef.current = saved.callCount;
|
|
3960
4260
|
}
|
|
3961
4261
|
}, [agentName]);
|
|
4262
|
+
useEffect3(() => {
|
|
4263
|
+
if (initialPendingTrades > 0) {
|
|
4264
|
+
const count = initialPendingTrades;
|
|
4265
|
+
setChatMessages((prev) => [
|
|
4266
|
+
...prev,
|
|
4267
|
+
{
|
|
4268
|
+
role: "assistant",
|
|
4269
|
+
content: `Hey! Autopilot made **${count}** trade${count > 1 ? "s" : ""} while you were away. Type \`/auto report\` to see what happened.`
|
|
4270
|
+
}
|
|
4271
|
+
]);
|
|
4272
|
+
}
|
|
4273
|
+
}, []);
|
|
3962
4274
|
const coreMessagesRef = useRef2(coreMessages);
|
|
3963
4275
|
useEffect3(() => {
|
|
3964
4276
|
coreMessagesRef.current = coreMessages;
|
|
@@ -3986,7 +4298,7 @@ function App({
|
|
|
3986
4298
|
setChatMessages((prev) => [...prev, { role: "log", content: text3 }]);
|
|
3987
4299
|
}, []);
|
|
3988
4300
|
const runAutopilotTurn = useCallback2(
|
|
3989
|
-
async (triggerMsg) => {
|
|
4301
|
+
async (triggerMsg, displayMode = "log") => {
|
|
3990
4302
|
const currentCore = coreMessagesRef.current;
|
|
3991
4303
|
const newCoreMessages = [
|
|
3992
4304
|
...currentCore,
|
|
@@ -4004,11 +4316,17 @@ function App({
|
|
|
4004
4316
|
apiContext,
|
|
4005
4317
|
{ ...profile, autopilotMode },
|
|
4006
4318
|
{
|
|
4007
|
-
onTextChunk: () => {
|
|
4319
|
+
onTextChunk: displayMode === "chat" ? (chunk) => {
|
|
4320
|
+
setStreamingText((prev) => (prev ?? "") + chunk);
|
|
4321
|
+
} : () => {
|
|
4008
4322
|
},
|
|
4009
|
-
onToolCallStart: () => {
|
|
4323
|
+
onToolCallStart: displayMode === "chat" ? (name) => {
|
|
4324
|
+
setToolName(name);
|
|
4325
|
+
} : () => {
|
|
4010
4326
|
},
|
|
4011
|
-
onToolCallEnd: () => {
|
|
4327
|
+
onToolCallEnd: displayMode === "chat" ? () => {
|
|
4328
|
+
setToolName(void 0);
|
|
4329
|
+
} : () => {
|
|
4012
4330
|
}
|
|
4013
4331
|
},
|
|
4014
4332
|
memoryContent
|
|
@@ -4017,12 +4335,16 @@ function App({
|
|
|
4017
4335
|
const updatedCore = [...baseCoreMessages, ...result.responseMessages];
|
|
4018
4336
|
setCoreMessages(updatedCore);
|
|
4019
4337
|
const responseText = result.text.trim();
|
|
4020
|
-
if (
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4338
|
+
if (displayMode === "chat") {
|
|
4339
|
+
setStreamingText(void 0);
|
|
4340
|
+
setChatMessages((prev) => [
|
|
4341
|
+
...prev,
|
|
4342
|
+
{ role: "assistant", content: responseText || "Market checked \u2014 holding." }
|
|
4343
|
+
]);
|
|
4024
4344
|
} else {
|
|
4025
|
-
|
|
4345
|
+
const summary = responseText.split("\n")[0].slice(0, 120) || "checked \u2192 no response";
|
|
4346
|
+
addLogEntry(summary);
|
|
4347
|
+
appendAutopilotLog(agentName, { ts: (/* @__PURE__ */ new Date()).toISOString(), action: summary });
|
|
4026
4348
|
}
|
|
4027
4349
|
const updatedChat = chatMessagesRef.current;
|
|
4028
4350
|
saveSession({
|
|
@@ -4034,9 +4356,15 @@ function App({
|
|
|
4034
4356
|
});
|
|
4035
4357
|
} catch (error) {
|
|
4036
4358
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4037
|
-
|
|
4359
|
+
if (displayMode === "chat") {
|
|
4360
|
+
setStreamingText(void 0);
|
|
4361
|
+
setChatMessages((prev) => [...prev, { role: "assistant", content: `Autopilot error: ${message}` }]);
|
|
4362
|
+
} else {
|
|
4363
|
+
addLogEntry("error", message);
|
|
4364
|
+
}
|
|
4038
4365
|
} finally {
|
|
4039
4366
|
setIsLoading(false);
|
|
4367
|
+
if (displayMode === "chat") setToolName(void 0);
|
|
4040
4368
|
}
|
|
4041
4369
|
},
|
|
4042
4370
|
[skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry]
|
|
@@ -4051,12 +4379,13 @@ function App({
|
|
|
4051
4379
|
addLogEntry("Budget reached \u2014 skipping until next epoch");
|
|
4052
4380
|
return;
|
|
4053
4381
|
}
|
|
4054
|
-
const
|
|
4382
|
+
const strategy = loadStrategy(agentName);
|
|
4383
|
+
const trigger = buildAutopilotTrigger(autopilotMode, strategy);
|
|
4055
4384
|
if (!trigger) return;
|
|
4056
4385
|
if (autopilotMode === "semi") {
|
|
4057
|
-
void
|
|
4386
|
+
void runAutopilotTurn(trigger, "chat");
|
|
4058
4387
|
} else {
|
|
4059
|
-
void runAutopilotTurn(trigger);
|
|
4388
|
+
void runAutopilotTurn(trigger, "log");
|
|
4060
4389
|
}
|
|
4061
4390
|
}, autopilotIntervalMs);
|
|
4062
4391
|
return () => clearInterval(interval);
|
|
@@ -4101,28 +4430,61 @@ Epoch Budget: ${budgetLabel} calls used` }
|
|
|
4101
4430
|
return;
|
|
4102
4431
|
}
|
|
4103
4432
|
if (sub === "on" || sub === "semi") {
|
|
4433
|
+
stopDaemon(agentName);
|
|
4104
4434
|
setAutopilotMode("semi");
|
|
4105
4435
|
saveAutopilotConfig({ mode: "semi", intervalMs: autopilotIntervalMs });
|
|
4436
|
+
const hasStrat = !!loadStrategy(agentName);
|
|
4106
4437
|
setChatMessages((prev) => [
|
|
4107
4438
|
...prev,
|
|
4108
4439
|
{ role: "user", content: userText },
|
|
4109
|
-
{ role: "assistant", content: `Autopilot enabled: **SEMI** mode (every ${formatInterval(autopilotIntervalMs)}). I'll
|
|
4440
|
+
{ role: "assistant", content: `Autopilot enabled: **SEMI** mode (every ${formatInterval(autopilotIntervalMs)}). I'll execute trades automatically based on your strategy.${!hasStrat ? "\n\n\u26A0\uFE0F No strategy set yet \u2014 I'll use general momentum signals. Use `/strategy setup` to create one." : ""}` }
|
|
4110
4441
|
]);
|
|
4111
4442
|
addLogEntry("Mode set to SEMI");
|
|
4112
4443
|
return;
|
|
4113
4444
|
}
|
|
4114
4445
|
if (sub === "full") {
|
|
4446
|
+
const strategy = loadStrategy(agentName);
|
|
4447
|
+
if (!strategy) {
|
|
4448
|
+
setChatMessages((prev) => [
|
|
4449
|
+
...prev,
|
|
4450
|
+
{ role: "user", content: userText },
|
|
4451
|
+
{ role: "assistant", content: "You need a trading strategy before enabling full autopilot. Use `/strategy setup` to create one." }
|
|
4452
|
+
]);
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4115
4455
|
setAutopilotMode("full");
|
|
4116
4456
|
saveAutopilotConfig({ mode: "full", intervalMs: autopilotIntervalMs });
|
|
4457
|
+
startDaemon(agentName);
|
|
4117
4458
|
setChatMessages((prev) => [
|
|
4118
4459
|
...prev,
|
|
4119
4460
|
{ role: "user", content: userText },
|
|
4120
|
-
{ role: "assistant", content: `Autopilot enabled: **FULL** mode (every ${formatInterval(autopilotIntervalMs)}). I'll execute trades autonomously \u2014
|
|
4461
|
+
{ role: "assistant", content: `Autopilot enabled: **FULL** mode (every ${formatInterval(autopilotIntervalMs)}). I'll execute trades autonomously based on your strategy \u2014 even when you close the app.` }
|
|
4121
4462
|
]);
|
|
4122
|
-
addLogEntry("Mode set to FULL");
|
|
4463
|
+
addLogEntry("Mode set to FULL \u2014 daemon started");
|
|
4464
|
+
return;
|
|
4465
|
+
}
|
|
4466
|
+
if (sub === "report") {
|
|
4467
|
+
const entries = loadAutopilotLogSince(agentName, null);
|
|
4468
|
+
if (entries.length === 0) {
|
|
4469
|
+
setChatMessages((prev) => [
|
|
4470
|
+
...prev,
|
|
4471
|
+
{ role: "user", content: userText },
|
|
4472
|
+
{ role: "assistant", content: "No autopilot trades logged yet." }
|
|
4473
|
+
]);
|
|
4474
|
+
} else {
|
|
4475
|
+
const lines = entries.slice(-20).map((e) => `\`${e.ts.slice(11, 16)}\` ${e.action}`);
|
|
4476
|
+
setChatMessages((prev) => [
|
|
4477
|
+
...prev,
|
|
4478
|
+
{ role: "user", content: userText },
|
|
4479
|
+
{ role: "assistant", content: `**Autopilot log** (last ${lines.length} entries):
|
|
4480
|
+
|
|
4481
|
+
${lines.join("\n")}` }
|
|
4482
|
+
]);
|
|
4483
|
+
}
|
|
4123
4484
|
return;
|
|
4124
4485
|
}
|
|
4125
4486
|
if (sub === "off") {
|
|
4487
|
+
stopDaemon(agentName);
|
|
4126
4488
|
setAutopilotMode("off");
|
|
4127
4489
|
saveAutopilotConfig({ mode: "off", intervalMs: autopilotIntervalMs });
|
|
4128
4490
|
setChatMessages((prev) => [
|
|
@@ -4154,6 +4516,37 @@ Epoch Budget: ${budgetLabel} calls used` }
|
|
|
4154
4516
|
]);
|
|
4155
4517
|
return;
|
|
4156
4518
|
}
|
|
4519
|
+
if (cmd === "/strategy") {
|
|
4520
|
+
const sub = parts[1]?.toLowerCase();
|
|
4521
|
+
if (sub === "status") {
|
|
4522
|
+
const strategy = loadStrategy(agentName);
|
|
4523
|
+
setChatMessages((prev) => [
|
|
4524
|
+
...prev,
|
|
4525
|
+
{ role: "user", content: userText },
|
|
4526
|
+
strategy ? { role: "assistant", content: `**Your current strategy:**
|
|
4527
|
+
|
|
4528
|
+
${strategy}` } : { role: "assistant", content: "No strategy set up yet. Use `/strategy setup` to create one." }
|
|
4529
|
+
]);
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
if (sub === "setup") {
|
|
4533
|
+
const existing = loadStrategy(agentName);
|
|
4534
|
+
userText = existing ? `I want to review and update my trading strategy. Here it is:
|
|
4535
|
+
|
|
4536
|
+
${existing}
|
|
4537
|
+
|
|
4538
|
+
Let's go through it and improve or replace it.` : "I want to create a trading strategy. Please guide me through the options.";
|
|
4539
|
+
} else if (!sub) {
|
|
4540
|
+
const strategy = loadStrategy(agentName);
|
|
4541
|
+
if (strategy) {
|
|
4542
|
+
const trigger = buildStrategyRunTrigger(strategy);
|
|
4543
|
+
setChatMessages((prev) => [...prev, { role: "user", content: "/strategy" }]);
|
|
4544
|
+
void runAutopilotTurn(trigger, "chat");
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4547
|
+
userText = "I want to create a trading strategy. Please guide me through the options.";
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4157
4550
|
if (cmd === "/help" || cmd === "/?") {
|
|
4158
4551
|
setChatMessages((prev) => [
|
|
4159
4552
|
...prev,
|
|
@@ -4172,13 +4565,17 @@ Epoch Budget: ${budgetLabel} calls used` }
|
|
|
4172
4565
|
" `/buy <amt>` \u2014 Buy $NOVA (e.g. `/buy 500`)",
|
|
4173
4566
|
" `/sell <amt>`\u2014 Sell $NOVA (e.g. `/sell 200`)",
|
|
4174
4567
|
"",
|
|
4175
|
-
"**Autopilot**",
|
|
4568
|
+
"**Strategy & Autopilot**",
|
|
4176
4569
|
"",
|
|
4177
|
-
" `/
|
|
4178
|
-
" `/
|
|
4179
|
-
" `/
|
|
4180
|
-
" `/auto
|
|
4181
|
-
" `/auto
|
|
4570
|
+
" `/strategy` \u2014 Run strategy once (or create if none)",
|
|
4571
|
+
" `/strategy setup` \u2014 Create or edit your strategy",
|
|
4572
|
+
" `/strategy status` \u2014 View current strategy",
|
|
4573
|
+
" `/auto on` \u2014 Enable semi-auto mode",
|
|
4574
|
+
" `/auto full` \u2014 Enable full-auto mode (requires strategy)",
|
|
4575
|
+
" `/auto off` \u2014 Disable autopilot",
|
|
4576
|
+
" `/auto 5m` \u2014 Set interval (1m-60m)",
|
|
4577
|
+
" `/auto status` \u2014 Show autopilot status",
|
|
4578
|
+
" `/auto report` \u2014 Show autopilot trade log",
|
|
4182
4579
|
"",
|
|
4183
4580
|
"**System**",
|
|
4184
4581
|
"",
|
|
@@ -4326,7 +4723,7 @@ ${stack}
|
|
|
4326
4723
|
}
|
|
4327
4724
|
) }),
|
|
4328
4725
|
/* @__PURE__ */ jsxs8(Box7, { flexShrink: 0, width: "100%", paddingX: 2, marginTop: 1, justifyContent: "space-between", children: [
|
|
4329
|
-
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /exit" }),
|
|
4726
|
+
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /strategy \xB7 /exit" }),
|
|
4330
4727
|
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/auto on\xB7off\xB7set \xB7 Ctrl+C quit" })
|
|
4331
4728
|
] })
|
|
4332
4729
|
] });
|
|
@@ -4348,14 +4745,19 @@ function detectJourneyStage(params) {
|
|
|
4348
4745
|
}
|
|
4349
4746
|
async function main() {
|
|
4350
4747
|
ensureBaseStructure();
|
|
4748
|
+
const args = process4.argv.slice(2);
|
|
4749
|
+
const shouldContinue = args.includes("--continue") || args.includes("-c");
|
|
4750
|
+
const debug = args.includes("--debug") || args.includes("-d");
|
|
4751
|
+
const isDaemonMode = args.includes("--daemon");
|
|
4752
|
+
if (isDaemonMode) {
|
|
4753
|
+
await runDaemon();
|
|
4754
|
+
return;
|
|
4755
|
+
}
|
|
4351
4756
|
if (isRestartRequested()) {
|
|
4352
4757
|
clearRestartFlag();
|
|
4353
4758
|
}
|
|
4354
|
-
const args = process2.argv.slice(2);
|
|
4355
|
-
const shouldContinue = args.includes("--continue") || args.includes("-c");
|
|
4356
|
-
const debug = args.includes("--debug") || args.includes("-d");
|
|
4357
4759
|
if (debug) {
|
|
4358
|
-
|
|
4760
|
+
process4.env.ASTRA_DEBUG = "1";
|
|
4359
4761
|
}
|
|
4360
4762
|
const isReturning = isConfigured();
|
|
4361
4763
|
let onboardingResult = null;
|
|
@@ -4363,7 +4765,7 @@ async function main() {
|
|
|
4363
4765
|
onboardingResult = await runOnboarding();
|
|
4364
4766
|
if (!onboardingResult) {
|
|
4365
4767
|
console.error("Onboarding failed. Please try again.");
|
|
4366
|
-
|
|
4768
|
+
process4.exit(1);
|
|
4367
4769
|
}
|
|
4368
4770
|
}
|
|
4369
4771
|
const config = loadConfig();
|
|
@@ -4371,21 +4773,21 @@ async function main() {
|
|
|
4371
4773
|
console.error(
|
|
4372
4774
|
"No config found. Delete ~/.config/astranova/config.json and re-run to start fresh."
|
|
4373
4775
|
);
|
|
4374
|
-
|
|
4776
|
+
process4.exit(1);
|
|
4375
4777
|
}
|
|
4376
4778
|
const agentName = getActiveAgent();
|
|
4377
4779
|
if (!agentName) {
|
|
4378
4780
|
console.error(
|
|
4379
4781
|
"No active agent found. Delete ~/.config/astranova/ and re-run to start fresh."
|
|
4380
4782
|
);
|
|
4381
|
-
|
|
4783
|
+
process4.exit(1);
|
|
4382
4784
|
}
|
|
4383
4785
|
const credentials = loadCredentials(agentName);
|
|
4384
4786
|
if (!credentials) {
|
|
4385
4787
|
console.error(
|
|
4386
4788
|
`No credentials found for agent "${agentName}". Delete ~/.config/astranova/ and re-run to start fresh.`
|
|
4387
4789
|
);
|
|
4388
|
-
|
|
4790
|
+
process4.exit(1);
|
|
4389
4791
|
}
|
|
4390
4792
|
let apiStatus = null;
|
|
4391
4793
|
if (isReturning) {
|
|
@@ -4421,6 +4823,7 @@ async function main() {
|
|
|
4421
4823
|
journeyStage: stage,
|
|
4422
4824
|
verificationCode: onboardingResult?.verificationCode ?? apiStatus?.verificationCode
|
|
4423
4825
|
});
|
|
4826
|
+
const hasStrategy = !!loadStrategy(agentName);
|
|
4424
4827
|
const profile = {
|
|
4425
4828
|
agentName,
|
|
4426
4829
|
status: apiStatus?.status ?? (isNewAgent ? "pending_verification" : "active"),
|
|
@@ -4430,7 +4833,8 @@ async function main() {
|
|
|
4430
4833
|
verificationCode: onboardingResult?.verificationCode ?? apiStatus?.verificationCode,
|
|
4431
4834
|
isNewAgent,
|
|
4432
4835
|
boardPosted,
|
|
4433
|
-
journeyStage: stage
|
|
4836
|
+
journeyStage: stage,
|
|
4837
|
+
hasStrategy
|
|
4434
4838
|
};
|
|
4435
4839
|
pruneOldSessions(agentName);
|
|
4436
4840
|
const memoryContent = loadMemory(agentName);
|
|
@@ -4452,6 +4856,12 @@ async function main() {
|
|
|
4452
4856
|
}
|
|
4453
4857
|
}
|
|
4454
4858
|
const initialAutopilotConfig = loadAutopilotConfig();
|
|
4859
|
+
const lastSessionAt = shouldContinue ? (() => {
|
|
4860
|
+
const s = loadLatestSession(agentName);
|
|
4861
|
+
return s ? new Date(s.updatedAt) : null;
|
|
4862
|
+
})() : null;
|
|
4863
|
+
const pendingTrades = loadAutopilotLogSince(agentName, lastSessionAt);
|
|
4864
|
+
const initialPendingTrades = pendingTrades.length;
|
|
4455
4865
|
const { waitUntilExit } = render(
|
|
4456
4866
|
React5.createElement(App, {
|
|
4457
4867
|
agentName,
|
|
@@ -4467,6 +4877,7 @@ async function main() {
|
|
|
4467
4877
|
initialCoreMessages,
|
|
4468
4878
|
initialChatMessages,
|
|
4469
4879
|
initialAutopilotConfig,
|
|
4880
|
+
initialPendingTrades,
|
|
4470
4881
|
debug
|
|
4471
4882
|
})
|
|
4472
4883
|
);
|
|
@@ -4478,18 +4889,18 @@ async function main() {
|
|
|
4478
4889
|
Restarting as ${newAgent}...
|
|
4479
4890
|
`);
|
|
4480
4891
|
try {
|
|
4481
|
-
execFileSync(
|
|
4892
|
+
execFileSync(process4.execPath, process4.argv.slice(1), {
|
|
4482
4893
|
stdio: "inherit",
|
|
4483
|
-
env:
|
|
4894
|
+
env: process4.env
|
|
4484
4895
|
});
|
|
4485
4896
|
} catch {
|
|
4486
4897
|
}
|
|
4487
|
-
|
|
4898
|
+
process4.exit(0);
|
|
4488
4899
|
}
|
|
4489
4900
|
}
|
|
4490
4901
|
main().catch((error) => {
|
|
4491
4902
|
const message = error instanceof Error ? error.message : String(error);
|
|
4492
4903
|
console.error(`Fatal: ${message}`);
|
|
4493
|
-
|
|
4904
|
+
process4.exit(1);
|
|
4494
4905
|
});
|
|
4495
4906
|
//# sourceMappingURL=astra.js.map
|