@astranova-live/cli 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/astra.js +435 -131
- package/package.json +1 -1
package/dist/astra.js
CHANGED
|
@@ -59,6 +59,9 @@ function memoryPath(agentName) {
|
|
|
59
59
|
function pendingClaimPath(agentName) {
|
|
60
60
|
return path.join(agentDir(agentName), "pending_claim.json");
|
|
61
61
|
}
|
|
62
|
+
function epochBudgetPath(agentName) {
|
|
63
|
+
return path.join(agentDir(agentName), "epoch_budget.json");
|
|
64
|
+
}
|
|
62
65
|
function cachePath(fileName) {
|
|
63
66
|
return path.join(_root(), ".cache", fileName);
|
|
64
67
|
}
|
|
@@ -99,7 +102,11 @@ var ConfigSchema = z.object({
|
|
|
99
102
|
apiBase: z.string().default("https://agents.astranova.live"),
|
|
100
103
|
preferences: z.object({
|
|
101
104
|
theme: z.enum(["dark", "light"]).default("dark")
|
|
102
|
-
}).default({})
|
|
105
|
+
}).default({}),
|
|
106
|
+
autopilot: z.object({
|
|
107
|
+
mode: z.enum(["off", "semi", "full"]).default("off"),
|
|
108
|
+
intervalMs: z.number().min(6e4).max(36e5).default(3e5)
|
|
109
|
+
}).default({ mode: "off", intervalMs: 3e5 })
|
|
103
110
|
});
|
|
104
111
|
var CredentialsSchema = z.object({
|
|
105
112
|
agent_name: z.string(),
|
|
@@ -164,6 +171,15 @@ function saveConfig(config) {
|
|
|
164
171
|
ensureBaseStructure();
|
|
165
172
|
writeFileSecure(configPath(), JSON.stringify(config, null, 2));
|
|
166
173
|
}
|
|
174
|
+
function loadAutopilotConfig() {
|
|
175
|
+
const config = loadConfig();
|
|
176
|
+
return config?.autopilot ?? { mode: "off", intervalMs: 3e5 };
|
|
177
|
+
}
|
|
178
|
+
function saveAutopilotConfig(autopilot) {
|
|
179
|
+
const config = loadConfig();
|
|
180
|
+
if (!config) return;
|
|
181
|
+
saveConfig({ ...config, autopilot });
|
|
182
|
+
}
|
|
167
183
|
function loadState() {
|
|
168
184
|
return readJsonFile(statePath(), StateSchema);
|
|
169
185
|
}
|
|
@@ -253,6 +269,22 @@ function loadPendingClaim(agentName) {
|
|
|
253
269
|
return null;
|
|
254
270
|
}
|
|
255
271
|
}
|
|
272
|
+
function loadEpochBudget(agentName) {
|
|
273
|
+
const filePath = epochBudgetPath(agentName);
|
|
274
|
+
if (!fs2.existsSync(filePath)) return null;
|
|
275
|
+
try {
|
|
276
|
+
return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function saveEpochBudget(agentName, data) {
|
|
282
|
+
try {
|
|
283
|
+
ensureDir(agentDir(agentName));
|
|
284
|
+
writeFileSecure(epochBudgetPath(agentName), JSON.stringify(data));
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
256
288
|
function clearPendingClaim(agentName) {
|
|
257
289
|
const filePath = pendingClaimPath(agentName);
|
|
258
290
|
if (fs2.existsSync(filePath)) {
|
|
@@ -1054,7 +1086,8 @@ async function runOnboarding() {
|
|
|
1054
1086
|
model,
|
|
1055
1087
|
auth,
|
|
1056
1088
|
apiBase: "https://agents.astranova.live",
|
|
1057
|
-
preferences: { theme: "dark" }
|
|
1089
|
+
preferences: { theme: "dark" },
|
|
1090
|
+
autopilot: { mode: "off", intervalMs: 3e5 }
|
|
1058
1091
|
};
|
|
1059
1092
|
saveConfig(config);
|
|
1060
1093
|
clack3.log.success(`Provider set to ${provider} (${model})`);
|
|
@@ -1358,17 +1391,50 @@ function loadMemory(agentName) {
|
|
|
1358
1391
|
}
|
|
1359
1392
|
|
|
1360
1393
|
// src/ui/App.tsx
|
|
1361
|
-
import { useState as useState4, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
1394
|
+
import { useState as useState4, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
1362
1395
|
import { Box as Box7, Text as Text8, useApp, useInput } from "ink";
|
|
1363
1396
|
|
|
1364
1397
|
// src/ui/StatusBar.tsx
|
|
1365
1398
|
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
1366
1399
|
import { Box, Text } from "ink";
|
|
1400
|
+
|
|
1401
|
+
// src/autopilot/scheduler.ts
|
|
1402
|
+
var CALLS_PER_AUTOPILOT_TURN = 3;
|
|
1403
|
+
var EPOCH_BUDGET = 10;
|
|
1404
|
+
var BUDGET_BUFFER = 2;
|
|
1405
|
+
var SEMI_TRIGGER_MSG = "AUTOPILOT CHECK: Analyze market and propose a trade if signal is clear. Ask me to confirm before executing.";
|
|
1406
|
+
var FULL_TRIGGER_MSG = "AUTOPILOT CHECK: Analyze market and execute a trade if signal is clear. If uncertain, skip. Keep response to 2-3 lines max.";
|
|
1407
|
+
function buildAutopilotTrigger(mode) {
|
|
1408
|
+
switch (mode) {
|
|
1409
|
+
case "semi":
|
|
1410
|
+
return SEMI_TRIGGER_MSG;
|
|
1411
|
+
case "full":
|
|
1412
|
+
return FULL_TRIGGER_MSG;
|
|
1413
|
+
case "off":
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function formatInterval(ms) {
|
|
1418
|
+
const minutes = Math.round(ms / 6e4);
|
|
1419
|
+
return `${minutes}m`;
|
|
1420
|
+
}
|
|
1421
|
+
function parseInterval(input) {
|
|
1422
|
+
const match = input.match(/^(\d+)m$/);
|
|
1423
|
+
if (!match) return null;
|
|
1424
|
+
const minutes = parseInt(match[1], 10);
|
|
1425
|
+
if (minutes < 1 || minutes > 60) return null;
|
|
1426
|
+
return minutes * 6e4;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/ui/StatusBar.tsx
|
|
1367
1430
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1368
|
-
var POLL_INTERVAL_MS =
|
|
1431
|
+
var POLL_INTERVAL_MS = 6e4;
|
|
1369
1432
|
var StatusBar = React.memo(function StatusBar2({
|
|
1370
1433
|
agentName,
|
|
1371
|
-
journeyStage
|
|
1434
|
+
journeyStage,
|
|
1435
|
+
autopilotMode = "off",
|
|
1436
|
+
autopilotIntervalMs = 3e5,
|
|
1437
|
+
onEpochChange
|
|
1372
1438
|
}) {
|
|
1373
1439
|
const [data, setData] = useState({ market: null, portfolio: null });
|
|
1374
1440
|
const mounted = useRef(true);
|
|
@@ -1383,7 +1449,10 @@ var StatusBar = React.memo(function StatusBar2({
|
|
|
1383
1449
|
market: marketRes ?? prev.market,
|
|
1384
1450
|
portfolio: portfolioRes ?? prev.portfolio
|
|
1385
1451
|
}));
|
|
1386
|
-
|
|
1452
|
+
if (marketRes && onEpochChange) {
|
|
1453
|
+
onEpochChange(marketRes.epochId);
|
|
1454
|
+
}
|
|
1455
|
+
}, [agentName, onEpochChange]);
|
|
1387
1456
|
useEffect(() => {
|
|
1388
1457
|
mounted.current = true;
|
|
1389
1458
|
if (!canFetchData) return;
|
|
@@ -1395,50 +1464,64 @@ var StatusBar = React.memo(function StatusBar2({
|
|
|
1395
1464
|
};
|
|
1396
1465
|
}, [canFetchData, poll]);
|
|
1397
1466
|
const { market, portfolio } = data;
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
/* @__PURE__ */
|
|
1401
|
-
|
|
1402
|
-
canFetchData && market && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1403
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1404
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "$NOVA " }),
|
|
1405
|
-
/* @__PURE__ */ jsx(Text, { color: "white", children: formatPrice(market.price) }),
|
|
1406
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1407
|
-
/* @__PURE__ */ jsx(Text, { color: moodColor(market.mood), children: market.mood })
|
|
1408
|
-
] }),
|
|
1409
|
-
canFetchData && portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1467
|
+
const apActive = autopilotMode !== "off";
|
|
1468
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [
|
|
1469
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1470
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "#00ff00", children: "AstraNova" }),
|
|
1410
1471
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1411
|
-
/* @__PURE__ */
|
|
1412
|
-
|
|
1413
|
-
"
|
|
1472
|
+
/* @__PURE__ */ jsx(Text, { color: "#ff8800", children: agentName }),
|
|
1473
|
+
canFetchData && market && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1474
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1475
|
+
/* @__PURE__ */ jsx(Text, { color: "#ffff00", children: "$NOVA " }),
|
|
1476
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: formatPrice(market.price) }),
|
|
1477
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1478
|
+
/* @__PURE__ */ jsx(Text, { color: moodColor(market.mood), children: market.mood })
|
|
1414
1479
|
] }),
|
|
1415
|
-
|
|
1480
|
+
canFetchData && portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1416
1481
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1417
|
-
/* @__PURE__ */ jsxs(Text, { color: "
|
|
1418
|
-
formatNum(portfolio.
|
|
1419
|
-
" $
|
|
1482
|
+
/* @__PURE__ */ jsxs(Text, { color: "#00ffff", children: [
|
|
1483
|
+
formatNum(portfolio.cash),
|
|
1484
|
+
" $SIM"
|
|
1485
|
+
] }),
|
|
1486
|
+
portfolio.tokens > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1487
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1488
|
+
/* @__PURE__ */ jsxs(Text, { color: "#ff00ff", children: [
|
|
1489
|
+
formatNum(portfolio.tokens),
|
|
1490
|
+
" $NOVA"
|
|
1491
|
+
] })
|
|
1492
|
+
] }),
|
|
1493
|
+
portfolio.pnl !== 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1494
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1495
|
+
/* @__PURE__ */ jsxs(Text, { color: portfolio.pnl >= 0 ? "#00ff00" : "#ff4444", children: [
|
|
1496
|
+
"P&L ",
|
|
1497
|
+
portfolio.pnl >= 0 ? "+" : "",
|
|
1498
|
+
formatNum(portfolio.pnl),
|
|
1499
|
+
" (",
|
|
1500
|
+
portfolio.pnlPct >= 0 ? "+" : "",
|
|
1501
|
+
portfolio.pnlPct.toFixed(1),
|
|
1502
|
+
"%)"
|
|
1503
|
+
] })
|
|
1504
|
+
] }),
|
|
1505
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1506
|
+
/* @__PURE__ */ jsxs(Text, { color: "#e2f902", children: [
|
|
1507
|
+
"Val ",
|
|
1508
|
+
formatNum(portfolio.portfolioValue)
|
|
1420
1509
|
] })
|
|
1421
1510
|
] }),
|
|
1422
|
-
|
|
1511
|
+
!canFetchData && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1423
1512
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1424
|
-
/* @__PURE__ */
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
portfolio.pnlPct >= 0 ? "+" : "",
|
|
1430
|
-
portfolio.pnlPct.toFixed(1),
|
|
1431
|
-
"%)"
|
|
1432
|
-
] })
|
|
1513
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "pending verification" })
|
|
1514
|
+
] }),
|
|
1515
|
+
canFetchData && !market && !portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1516
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1517
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
|
|
1433
1518
|
] })
|
|
1434
1519
|
] }),
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
|
|
1441
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
|
|
1520
|
+
apActive && /* @__PURE__ */ jsxs(Text, { color: "#00ff00", children: [
|
|
1521
|
+
"AP: \u25CF ",
|
|
1522
|
+
autopilotMode.toUpperCase(),
|
|
1523
|
+
" ",
|
|
1524
|
+
formatInterval(autopilotIntervalMs)
|
|
1442
1525
|
] })
|
|
1443
1526
|
] }) });
|
|
1444
1527
|
});
|
|
@@ -1457,12 +1540,12 @@ function moodColor(mood) {
|
|
|
1457
1540
|
switch (mood) {
|
|
1458
1541
|
case "euphoria":
|
|
1459
1542
|
case "bullish":
|
|
1460
|
-
return "
|
|
1543
|
+
return "#00ff00";
|
|
1461
1544
|
case "fear":
|
|
1462
1545
|
case "bearish":
|
|
1463
|
-
return "
|
|
1546
|
+
return "#ff4444";
|
|
1464
1547
|
case "crab":
|
|
1465
|
-
return "
|
|
1548
|
+
return "#ffff00";
|
|
1466
1549
|
default:
|
|
1467
1550
|
return "white";
|
|
1468
1551
|
}
|
|
@@ -1474,7 +1557,8 @@ async function fetchMarket(agentName) {
|
|
|
1474
1557
|
const m = d.market ?? d;
|
|
1475
1558
|
return {
|
|
1476
1559
|
price: m.price ?? 0,
|
|
1477
|
-
mood: m.mood ?? ""
|
|
1560
|
+
mood: m.mood ?? "",
|
|
1561
|
+
epochId: m.epoch?.global ?? 0
|
|
1478
1562
|
};
|
|
1479
1563
|
}
|
|
1480
1564
|
async function fetchPortfolio(agentName) {
|
|
@@ -1487,9 +1571,13 @@ async function fetchPortfolio(agentName) {
|
|
|
1487
1571
|
if (!result.ok) return null;
|
|
1488
1572
|
const d = result.data;
|
|
1489
1573
|
const p = d.portfolio ?? d;
|
|
1574
|
+
const cash = p.cash ?? 0;
|
|
1575
|
+
const tokens = p.tokens ?? 0;
|
|
1576
|
+
const currentPrice = p.currentPrice ?? 0;
|
|
1490
1577
|
return {
|
|
1491
|
-
cash
|
|
1492
|
-
tokens
|
|
1578
|
+
cash,
|
|
1579
|
+
tokens,
|
|
1580
|
+
portfolioValue: p.portfolioValue ?? cash + tokens * currentPrice,
|
|
1493
1581
|
pnl: p.pnl ?? 0,
|
|
1494
1582
|
pnlPct: p.pnlPct ?? 0
|
|
1495
1583
|
};
|
|
@@ -1513,35 +1601,26 @@ function PortfolioCard({ data }) {
|
|
|
1513
1601
|
const pnlPct = data.pnlPct ?? 0;
|
|
1514
1602
|
const earned = data.totalEarned ? Number(data.totalEarned) / 1e9 : 0;
|
|
1515
1603
|
const claimable = data.claimable ? Number(data.claimable) / 1e9 : 0;
|
|
1516
|
-
const pnlColor = pnl >= 0 ? "
|
|
1604
|
+
const pnlColor = pnl >= 0 ? "#00ff00" : "#ff4444";
|
|
1517
1605
|
const pnlSign = pnl >= 0 ? "+" : "";
|
|
1518
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column",
|
|
1519
|
-
/* @__PURE__ */ jsx2(Box2, {
|
|
1520
|
-
/* @__PURE__ */
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
value: data.hasWallet ? "registered" : data.walletLocal ? "needs registration" : "not set",
|
|
1534
|
-
color: data.hasWallet ? "green" : data.walletLocal ? "yellow" : "gray"
|
|
1535
|
-
}
|
|
1536
|
-
)
|
|
1537
|
-
] })
|
|
1538
|
-
] }),
|
|
1606
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
|
|
1607
|
+
/* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#00ffff", children: "Portfolio Overview" }) }),
|
|
1608
|
+
/* @__PURE__ */ jsx2(Row, { label: "$SIM Balance", value: formatNum2(cash), color: "#00ffff" }),
|
|
1609
|
+
/* @__PURE__ */ jsx2(Row, { label: "$NOVA Holdings", value: tokens > 0 ? formatNum2(tokens) : "\u2014", color: "#ff00ff" }),
|
|
1610
|
+
/* @__PURE__ */ jsx2(Row, { label: "$NOVA Price", value: price > 0 ? formatPrice2(price) : "\u2014", color: "#ffff00" }),
|
|
1611
|
+
/* @__PURE__ */ jsx2(Row, { label: "Portfolio Value", value: formatNum2(value), color: "white" }),
|
|
1612
|
+
/* @__PURE__ */ jsx2(Row, { label: "P&L", value: `${pnlSign}${formatNum2(pnl)} (${pnlSign}${pnlPct.toFixed(1)}%)`, color: pnlColor }),
|
|
1613
|
+
(data.hasWallet !== void 0 || data.walletLocal !== void 0) && /* @__PURE__ */ jsx2(
|
|
1614
|
+
Row,
|
|
1615
|
+
{
|
|
1616
|
+
label: "Wallet",
|
|
1617
|
+
value: data.hasWallet ? "registered" : data.walletLocal ? "needs registration" : "not set",
|
|
1618
|
+
color: data.hasWallet ? "#00ff00" : data.walletLocal ? "#ffff00" : "gray"
|
|
1619
|
+
}
|
|
1620
|
+
),
|
|
1539
1621
|
(earned > 0 || claimable > 0) && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1540
|
-
/* @__PURE__ */ jsx2(
|
|
1541
|
-
/* @__PURE__ */
|
|
1542
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: "50%", children: /* @__PURE__ */ jsx2(Row, { label: "$ASTRA Earned", value: earned > 0 ? formatAstra(earned) : "\u2014", color: "yellow" }) }),
|
|
1543
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: "50%", children: /* @__PURE__ */ jsx2(Row, { label: "Claimable", value: claimable > 0 ? formatAstra(claimable) : "\u2014", color: claimable > 0 ? "green" : "gray" }) })
|
|
1544
|
-
] })
|
|
1622
|
+
/* @__PURE__ */ jsx2(Row, { label: "$ASTRA Earned", value: earned > 0 ? formatAstra(earned) : "\u2014", color: "#ffff00" }),
|
|
1623
|
+
/* @__PURE__ */ jsx2(Row, { label: "Claimable", value: claimable > 0 ? formatAstra(claimable) : "\u2014", color: claimable > 0 ? "#00ff00" : "gray" })
|
|
1545
1624
|
] })
|
|
1546
1625
|
] });
|
|
1547
1626
|
}
|
|
@@ -1572,38 +1651,29 @@ function formatAstra(n) {
|
|
|
1572
1651
|
|
|
1573
1652
|
// src/ui/RewardsCard.tsx
|
|
1574
1653
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
1575
|
-
import {
|
|
1654
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1576
1655
|
function RewardsCard({ data }) {
|
|
1577
1656
|
const total = data.totalAstra ? Number(data.totalAstra) / 1e9 : 0;
|
|
1578
1657
|
const epoch = data.epochAstra ? Number(data.epochAstra) / 1e9 : 0;
|
|
1579
1658
|
const bonus = data.bonusAstra ? Number(data.bonusAstra) / 1e9 : 0;
|
|
1580
|
-
const statusColor = data.claimStatus === "claimable" ? "
|
|
1581
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column",
|
|
1582
|
-
/* @__PURE__ */ jsxs3(Box3, {
|
|
1583
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "
|
|
1659
|
+
const statusColor = data.claimStatus === "claimable" ? "#00ff00" : data.claimStatus === "sent" ? "#00ffff" : "#ffff00";
|
|
1660
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
|
|
1661
|
+
/* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
1662
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "#ffff00", children: "$ASTRA Rewards" }),
|
|
1584
1663
|
data.seasonId && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1585
|
-
"
|
|
1664
|
+
" ",
|
|
1586
1665
|
data.seasonId
|
|
1587
1666
|
] })
|
|
1588
1667
|
] }),
|
|
1589
|
-
/* @__PURE__ */
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
data.bestEpochPnl !== void 0 && data.bestEpochPnl > 0 && /* @__PURE__ */ jsx3(Row2, { label: "Best Epoch P&L", value: `+${data.bestEpochPnl.toFixed(2)}`, color: "green" })
|
|
1599
|
-
] })
|
|
1600
|
-
] }),
|
|
1601
|
-
data.txSignature && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1602
|
-
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(36) }) }),
|
|
1603
|
-
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1604
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tx: " }),
|
|
1605
|
-
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: data.txSignature })
|
|
1606
|
-
] })
|
|
1668
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Total Earned", value: formatAstra2(total), color: "#ffff00" }),
|
|
1669
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Epoch Rewards", value: formatAstra2(epoch), color: "#00ffff" }),
|
|
1670
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Season Bonus", value: formatAstra2(bonus), color: "#ff00ff" }),
|
|
1671
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Status", value: data.claimStatus ?? "\u2014", color: statusColor }),
|
|
1672
|
+
/* @__PURE__ */ jsx3(Row2, { label: "Epochs Rewarded", value: data.epochsRewarded?.toString() ?? "\u2014", color: "white" }),
|
|
1673
|
+
data.bestEpochPnl !== void 0 && data.bestEpochPnl > 0 && /* @__PURE__ */ jsx3(Row2, { label: "Best Epoch P&L", value: `+${data.bestEpochPnl.toFixed(2)}`, color: "#00ff00" }),
|
|
1674
|
+
data.txSignature && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
1675
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tx: " }),
|
|
1676
|
+
/* @__PURE__ */ jsx3(Text3, { color: "#00ffff", children: data.txSignature })
|
|
1607
1677
|
] })
|
|
1608
1678
|
] });
|
|
1609
1679
|
}
|
|
@@ -1776,12 +1846,23 @@ function ChatView({
|
|
|
1776
1846
|
streamingText
|
|
1777
1847
|
}) {
|
|
1778
1848
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", paddingX: 1, children: [
|
|
1779
|
-
messages.map((msg, i) =>
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1849
|
+
messages.map((msg, i) => {
|
|
1850
|
+
if (msg.role === "log") {
|
|
1851
|
+
return /* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: msg.content }) }, i);
|
|
1852
|
+
}
|
|
1853
|
+
if (msg.role === "autopilot") {
|
|
1854
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
1855
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "#ff00ff", children: " Autopilot" }),
|
|
1856
|
+
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "wrap", children: msg.content }) })
|
|
1857
|
+
] }, i);
|
|
1858
|
+
}
|
|
1859
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
1860
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: msg.role === "user" ? "#00ff00" : "#00ffff", children: msg.role === "user" ? " You" : " Agent" }),
|
|
1861
|
+
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(MarkdownText, { children: msg.content }) : /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: msg.content }) })
|
|
1862
|
+
] }, i);
|
|
1863
|
+
}),
|
|
1783
1864
|
streamingText !== void 0 && streamingText.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
1784
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "
|
|
1865
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "#00ffff", children: " Agent" }),
|
|
1785
1866
|
/* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(MarkdownText, { children: streamingText }) })
|
|
1786
1867
|
] })
|
|
1787
1868
|
] });
|
|
@@ -1803,8 +1884,8 @@ function Input({
|
|
|
1803
1884
|
onSubmit(trimmed);
|
|
1804
1885
|
setValue("");
|
|
1805
1886
|
};
|
|
1806
|
-
return /* @__PURE__ */
|
|
1807
|
-
/* @__PURE__ */ jsx6(Text6, { color: isActive ? "yellow" : "gray", bold: true, children: "\
|
|
1887
|
+
return /* @__PURE__ */ jsxs6(Box6, { width: "100%", paddingX: 2, paddingY: 1, children: [
|
|
1888
|
+
/* @__PURE__ */ jsx6(Text6, { color: isActive ? "yellow" : "gray", bold: true, children: "\u276F\u276F " }),
|
|
1808
1889
|
/* @__PURE__ */ jsx6(
|
|
1809
1890
|
TextInput,
|
|
1810
1891
|
{
|
|
@@ -1813,10 +1894,10 @@ function Input({
|
|
|
1813
1894
|
onSubmit: handleSubmit,
|
|
1814
1895
|
focus: isActive,
|
|
1815
1896
|
showCursor: isActive,
|
|
1816
|
-
placeholder:
|
|
1897
|
+
placeholder: ""
|
|
1817
1898
|
}
|
|
1818
1899
|
)
|
|
1819
|
-
] })
|
|
1900
|
+
] });
|
|
1820
1901
|
}
|
|
1821
1902
|
|
|
1822
1903
|
// src/ui/Spinner.tsx
|
|
@@ -2029,6 +2110,21 @@ function buildSystemPrompt(skillContext, tradingContext, walletContext, rewardsC
|
|
|
2029
2110
|
if (profile.season !== void 0) {
|
|
2030
2111
|
parts.push(`Season: ${profile.season}`);
|
|
2031
2112
|
}
|
|
2113
|
+
const apMode = profile.autopilotMode ?? "off";
|
|
2114
|
+
if (apMode !== "off") {
|
|
2115
|
+
parts.push("", "---", "");
|
|
2116
|
+
parts.push(`## AUTOPILOT: ${apMode.toUpperCase()}`);
|
|
2117
|
+
parts.push("");
|
|
2118
|
+
parts.push(`On AUTOPILOT CHECK triggers:`);
|
|
2119
|
+
parts.push(`- GET /api/v1/market/state + GET /api/v1/portfolio`);
|
|
2120
|
+
parts.push(`- Apply strategy from memory.md (default: momentum + balance)`);
|
|
2121
|
+
if (apMode === "semi") {
|
|
2122
|
+
parts.push(`- SEMI: propose trade, wait for explicit user approval`);
|
|
2123
|
+
} else {
|
|
2124
|
+
parts.push(`- FULL: execute if signal clear; skip if uncertain`);
|
|
2125
|
+
}
|
|
2126
|
+
parts.push(`- Max 2-3 lines. No action = "Market checked \u2014 holding."`);
|
|
2127
|
+
}
|
|
2032
2128
|
if (memoryContent && memoryContent.trim()) {
|
|
2033
2129
|
parts.push("", "---", "");
|
|
2034
2130
|
parts.push("## Agent Memory (persistent across sessions)", "");
|
|
@@ -2257,6 +2353,9 @@ CRITICAL: When the user says "setup wallet" or "create wallet", you MUST execute
|
|
|
2257
2353
|
|
|
2258
2354
|
The entire flow (steps 1-6) should happen in one continuous sequence of tool calls. Only stop to talk to the user at the end with the final result.
|
|
2259
2355
|
|
|
2356
|
+
After the wallet is registered, ALWAYS include this funding reminder in your final message:
|
|
2357
|
+
"Your wallet is set up! One thing before you can claim rewards \u2014 you'll need a tiny bit of SOL to cover the transaction fee. **0.01 SOL (~$1)** is more than enough and covers hundreds of transactions. Send it to your wallet address: \`<walletAddress>\`. Once it's funded, just say 'claim rewards' and I'll handle the rest."
|
|
2358
|
+
|
|
2260
2359
|
### Rich display \u2014 Portfolio Card:
|
|
2261
2360
|
When showing portfolio data, wrap the raw JSON from the API in a special block so the terminal renders a styled card.
|
|
2262
2361
|
|
|
@@ -2341,7 +2440,9 @@ When \`txSignature\` is present and \`claimStatus\` is "sent", the reward has be
|
|
|
2341
2440
|
If the user asks "where is my wallet?" or similar, tell them the wallet is stored at \`~/.config/astranova/agents/<agent-name>/wallet.json\`. Remind them to never share the file \u2014 it contains their private key. To check their public key, use \`read_config\` with \`key: "wallet"\`.
|
|
2342
2441
|
|
|
2343
2442
|
### Reward claim flow (use tools, NOT scripts):
|
|
2344
|
-
1. \`api_call
|
|
2443
|
+
IMPORTANT: Before step 1, determine the seasonId from context (portfolio response, rewards response, or anything seen in this conversation). Do NOT ask the user for the seasonId \u2014 use what you already know. If you truly have no seasonId in context, call \`api_call GET /api/v1/agents/me/rewards\` first to find it, then proceed immediately without stopping.
|
|
2444
|
+
|
|
2445
|
+
1. \`api_call POST /api/v1/agents/me/rewards/claim\` with \`{"seasonId":"<season-from-context>"}\`
|
|
2345
2446
|
\u2192 Returns: \`{"success":true,"totalAmount":"...","rewardCount":N,"expiresAt":"...","transaction":"<base64>"}\`
|
|
2346
2447
|
\u2192 The \`transaction\` field is the base64-encoded partially-signed Solana transaction.
|
|
2347
2448
|
\u2192 NOTE: The transaction expires in 10 minutes (see \`expiresAt\`). Complete step 2 quickly.
|
|
@@ -2423,7 +2524,7 @@ You have access to tools for interacting with the AstraNova Agent API, reading/w
|
|
|
2423
2524
|
- NEVER display or reference private keys. Wallet operations return public keys only.
|
|
2424
2525
|
- When the user asks to trade, verify, or claim rewards, use the appropriate API calls IMMEDIATELY.
|
|
2425
2526
|
- TRADE RULE: You MUST call the api_call tool to execute ANY trade. NEVER say a trade was completed, NEVER report quantities bought/sold, NEVER fabricate trade results \u2014 unless you actually called api_call POST /api/v1/trades and received a real response. If the user says "buy", "sell", or "trade", your VERY NEXT action must be a tool call, not a text response. A trade that was not executed via api_call DID NOT HAPPEN. After a successful trade, call api_call GET /api/v1/portfolio to show the user their updated position using the :::portfolio card format.
|
|
2426
|
-
- CLAIM RULE: Claiming rewards requires THREE sequential tool calls \u2014 you MUST execute ALL THREE in a single turn without stopping. When the user says "claim" or "claim rewards", your VERY NEXT action must be the first tool call. Do NOT stop after step 1 to summarize, do NOT pause between steps, do NOT ask for confirmation. Execute all three steps back-to-back: (1) api_call POST /api/v1/agents/me/rewards/claim \u2192 returns a base64 transaction, (2) sign_and_send_transaction with that base64 \u2192 returns a real txSignature, (3) api_call POST /api/v1/agents/me/rewards/confirm with the txSignature. Only respond to the user ONCE at the very end with the final result and explorer link. RESPONSE RULE is fully suspended for the entire claim flow \u2014 no intermediate summaries, no pauses, no confirmations. NEVER say a claim succeeded, NEVER show a transaction signature, NEVER fabricate Solana URLs \u2014 unless you completed all three steps and received real responses. If ANY step fails, tell the user which step failed and why. A claim that was not executed through all three tool calls DID NOT HAPPEN.
|
|
2527
|
+
- CLAIM RULE: Claiming rewards requires THREE sequential tool calls \u2014 you MUST execute ALL THREE in a single turn without stopping. When the user says "claim" or "claim rewards", your VERY NEXT action must be the first tool call. Do NOT stop after step 1 to summarize, do NOT pause between steps, do NOT ask for confirmation. Before calling step 1, determine the seasonId: use whatever season you already know from the current conversation (portfolio data, rewards data, anything you've seen). Do NOT ask the user for the seasonId \u2014 if you have it from context, use it. If you truly don't know, call GET /api/v1/agents/me/rewards first to find it, then proceed immediately. Execute all three steps back-to-back: (1) api_call POST /api/v1/agents/me/rewards/claim with {"seasonId":"<season>"} \u2192 returns a base64 transaction, (2) sign_and_send_transaction with that base64 \u2192 returns a real txSignature, (3) api_call POST /api/v1/agents/me/rewards/confirm with the txSignature and seasonId. Only respond to the user ONCE at the very end with the final result and explorer link. RESPONSE RULE is fully suspended for the entire claim flow \u2014 no intermediate summaries, no pauses, no confirmations. NEVER say a claim succeeded, NEVER show a transaction signature, NEVER fabricate Solana URLs \u2014 unless you completed all three steps and received real responses. If ANY step fails, tell the user which step failed and why. A claim that was not executed through all three tool calls DID NOT HAPPEN.
|
|
2427
2528
|
- WALLET SETUP RULE: When the user says "setup wallet", "create wallet", "set up my wallet" or anything similar, your VERY NEXT action must be a tool call \u2014 NOT a text response. START by calling \`read_config\` with \`key: "wallet"\` immediately. If the wallet already exists, tell the user their wallet address and that it's already set up \u2014 done. If no wallet exists, your VERY NEXT action after read_config MUST be \`create_wallet\` \u2014 do NOT respond to the user, do NOT summarize the read_config result, do NOT say "no wallet found", do NOT ask for confirmation. Just call \`create_wallet\` immediately. Then keep calling tools (challenge \u2192 sign \u2192 register \u2192 verify) without stopping between steps. Only respond once at the very end with the final result. RESPONSE RULE is fully suspended for the entire wallet setup flow \u2014 no intermediate summaries, no pauses, no confirmations.
|
|
2428
2529
|
- NO HALLUCINATION RULE: You must NEVER fabricate tool results. If you did not call a tool, you do not have its result. Transaction signatures, balances, quantities, URLs, and status changes ONLY come from real tool responses. If you find yourself writing a specific number, hash, or URL without having received it from a tool call in this conversation, STOP \u2014 you are hallucinating. Call the tool instead.
|
|
2429
2530
|
- RESPONSE RULE: After EVERY tool call, you MUST respond with a text summary of the result. NEVER return an empty response after a tool call. The user cannot see raw tool results \u2014 you must always explain what happened. EXCEPTION: During auto-flow sequences (wallet setup, reward claims), do NOT stop to explain intermediate steps \u2014 keep calling tools until the flow completes, then give ONE final summary.
|
|
@@ -3471,6 +3572,7 @@ async function runResponsesApiTurn(messages, systemPrompt, callbacks, turnConfig
|
|
|
3471
3572
|
toolResultParts.push({
|
|
3472
3573
|
type: "tool-result",
|
|
3473
3574
|
toolCallId: tc.callId,
|
|
3575
|
+
toolName: tc.name,
|
|
3474
3576
|
result: errorResult
|
|
3475
3577
|
});
|
|
3476
3578
|
codexInput.push({
|
|
@@ -3506,6 +3608,7 @@ async function runResponsesApiTurn(messages, systemPrompt, callbacks, turnConfig
|
|
|
3506
3608
|
toolResultParts.push({
|
|
3507
3609
|
type: "tool-result",
|
|
3508
3610
|
toolCallId: tc.callId,
|
|
3611
|
+
toolName: tc.name,
|
|
3509
3612
|
result: toolResult
|
|
3510
3613
|
});
|
|
3511
3614
|
codexInput.push({
|
|
@@ -3536,6 +3639,7 @@ async function runResponsesApiTurn(messages, systemPrompt, callbacks, turnConfig
|
|
|
3536
3639
|
toolResultParts.push({
|
|
3537
3640
|
type: "tool-result",
|
|
3538
3641
|
toolCallId: tc.callId,
|
|
3642
|
+
toolName: tc.name,
|
|
3539
3643
|
result: errorResult
|
|
3540
3644
|
});
|
|
3541
3645
|
codexInput.push({
|
|
@@ -3823,6 +3927,7 @@ function App({
|
|
|
3823
3927
|
memoryContent,
|
|
3824
3928
|
initialCoreMessages,
|
|
3825
3929
|
initialChatMessages,
|
|
3930
|
+
initialAutopilotConfig,
|
|
3826
3931
|
debug
|
|
3827
3932
|
}) {
|
|
3828
3933
|
const { exit } = useApp();
|
|
@@ -3833,20 +3938,131 @@ function App({
|
|
|
3833
3938
|
initialCoreMessages ?? []
|
|
3834
3939
|
);
|
|
3835
3940
|
const providerRef = useRef2(loadConfig()?.provider ?? "unknown");
|
|
3836
|
-
const [streamingText, setStreamingText] = useState4(
|
|
3837
|
-
void 0
|
|
3838
|
-
);
|
|
3941
|
+
const [streamingText, setStreamingText] = useState4(void 0);
|
|
3839
3942
|
const [isLoading, setIsLoading] = useState4(false);
|
|
3943
|
+
const isLoadingRef = useRef2(false);
|
|
3944
|
+
useEffect3(() => {
|
|
3945
|
+
isLoadingRef.current = isLoading;
|
|
3946
|
+
}, [isLoading]);
|
|
3840
3947
|
const [toolName, setToolName] = useState4(void 0);
|
|
3948
|
+
const [autopilotMode, setAutopilotMode] = useState4(initialAutopilotConfig?.mode ?? "off");
|
|
3949
|
+
const [autopilotIntervalMs, setAutopilotIntervalMs] = useState4(initialAutopilotConfig?.intervalMs ?? 3e5);
|
|
3950
|
+
const epochCallCountRef = useRef2(0);
|
|
3951
|
+
const epochIdRef = useRef2(null);
|
|
3952
|
+
useEffect3(() => {
|
|
3953
|
+
const saved = loadEpochBudget(agentName);
|
|
3954
|
+
if (saved) {
|
|
3955
|
+
epochIdRef.current = saved.epochId;
|
|
3956
|
+
epochCallCountRef.current = saved.callCount;
|
|
3957
|
+
}
|
|
3958
|
+
}, [agentName]);
|
|
3959
|
+
const coreMessagesRef = useRef2(coreMessages);
|
|
3960
|
+
useEffect3(() => {
|
|
3961
|
+
coreMessagesRef.current = coreMessages;
|
|
3962
|
+
}, [coreMessages]);
|
|
3963
|
+
const chatMessagesRef = useRef2(chatMessages);
|
|
3964
|
+
useEffect3(() => {
|
|
3965
|
+
chatMessagesRef.current = chatMessages;
|
|
3966
|
+
}, [chatMessages]);
|
|
3967
|
+
const sendMessageRef = useRef2(async () => {
|
|
3968
|
+
});
|
|
3841
3969
|
useInput((_input, key) => {
|
|
3842
3970
|
if (key.ctrl && _input === "c") {
|
|
3843
3971
|
exit();
|
|
3844
3972
|
}
|
|
3845
3973
|
});
|
|
3974
|
+
const handleEpochChange = useCallback2((newEpoch) => {
|
|
3975
|
+
if (epochIdRef.current === newEpoch) return;
|
|
3976
|
+
epochIdRef.current = newEpoch;
|
|
3977
|
+
epochCallCountRef.current = 0;
|
|
3978
|
+
saveEpochBudget(agentName, { epochId: newEpoch, callCount: 0 });
|
|
3979
|
+
}, [agentName]);
|
|
3980
|
+
const addLogEntry = useCallback2((action, detail) => {
|
|
3981
|
+
const time = formatTime(/* @__PURE__ */ new Date());
|
|
3982
|
+
const text3 = detail ? `\u27F3 ${time} ${action} \u2014 ${detail}` : `\u27F3 ${time} ${action}`;
|
|
3983
|
+
setChatMessages((prev) => [...prev, { role: "log", content: text3 }]);
|
|
3984
|
+
}, []);
|
|
3985
|
+
const runAutopilotTurn = useCallback2(
|
|
3986
|
+
async (triggerMsg) => {
|
|
3987
|
+
const currentCore = coreMessagesRef.current;
|
|
3988
|
+
const newCoreMessages = [
|
|
3989
|
+
...currentCore,
|
|
3990
|
+
{ role: "user", content: triggerMsg }
|
|
3991
|
+
];
|
|
3992
|
+
setIsLoading(true);
|
|
3993
|
+
try {
|
|
3994
|
+
const result = await runAgentTurn(
|
|
3995
|
+
newCoreMessages,
|
|
3996
|
+
skillContext,
|
|
3997
|
+
tradingContext,
|
|
3998
|
+
walletContext,
|
|
3999
|
+
rewardsContext,
|
|
4000
|
+
onboardingContext,
|
|
4001
|
+
apiContext,
|
|
4002
|
+
{ ...profile, autopilotMode },
|
|
4003
|
+
{
|
|
4004
|
+
onTextChunk: () => {
|
|
4005
|
+
},
|
|
4006
|
+
onToolCallStart: () => {
|
|
4007
|
+
},
|
|
4008
|
+
onToolCallEnd: () => {
|
|
4009
|
+
}
|
|
4010
|
+
},
|
|
4011
|
+
memoryContent
|
|
4012
|
+
);
|
|
4013
|
+
const baseCoreMessages = result.compactedMessages ?? newCoreMessages;
|
|
4014
|
+
const updatedCore = [...baseCoreMessages, ...result.responseMessages];
|
|
4015
|
+
setCoreMessages(updatedCore);
|
|
4016
|
+
const responseText = result.text.trim();
|
|
4017
|
+
if (responseText) {
|
|
4018
|
+
const firstLine = responseText.split("\n")[0].slice(0, 80);
|
|
4019
|
+
const detail = responseText.length > 80 ? responseText.slice(0, 120) : void 0;
|
|
4020
|
+
addLogEntry(firstLine, detail !== firstLine ? detail : void 0);
|
|
4021
|
+
} else {
|
|
4022
|
+
addLogEntry("checked \u2192 no response");
|
|
4023
|
+
}
|
|
4024
|
+
const updatedChat = chatMessagesRef.current;
|
|
4025
|
+
saveSession({
|
|
4026
|
+
agentName,
|
|
4027
|
+
provider: providerRef.current,
|
|
4028
|
+
sessionId,
|
|
4029
|
+
coreMessages: updatedCore,
|
|
4030
|
+
chatMessages: updatedChat
|
|
4031
|
+
});
|
|
4032
|
+
} catch (error) {
|
|
4033
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4034
|
+
addLogEntry("error", message);
|
|
4035
|
+
} finally {
|
|
4036
|
+
setIsLoading(false);
|
|
4037
|
+
}
|
|
4038
|
+
},
|
|
4039
|
+
[skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry]
|
|
4040
|
+
);
|
|
4041
|
+
useEffect3(() => {
|
|
4042
|
+
if (autopilotMode === "off") return;
|
|
4043
|
+
const interval = setInterval(() => {
|
|
4044
|
+
if (isLoadingRef.current) return;
|
|
4045
|
+
if (epochCallCountRef.current + CALLS_PER_AUTOPILOT_TURN > EPOCH_BUDGET - BUDGET_BUFFER) {
|
|
4046
|
+
addLogEntry("Budget reached \u2014 skipping until next epoch");
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
epochCallCountRef.current += CALLS_PER_AUTOPILOT_TURN;
|
|
4050
|
+
saveEpochBudget(agentName, { epochId: epochIdRef.current ?? 0, callCount: epochCallCountRef.current });
|
|
4051
|
+
const trigger = buildAutopilotTrigger(autopilotMode);
|
|
4052
|
+
if (!trigger) return;
|
|
4053
|
+
if (autopilotMode === "semi") {
|
|
4054
|
+
void sendMessageRef.current(trigger, "autopilot");
|
|
4055
|
+
} else {
|
|
4056
|
+
void runAutopilotTurn(trigger);
|
|
4057
|
+
}
|
|
4058
|
+
}, autopilotIntervalMs);
|
|
4059
|
+
return () => clearInterval(interval);
|
|
4060
|
+
}, [autopilotMode, autopilotIntervalMs, addLogEntry, runAutopilotTurn]);
|
|
3846
4061
|
const sendMessage = useCallback2(
|
|
3847
|
-
async (userText) => {
|
|
4062
|
+
async (userText, displayRole = "user") => {
|
|
3848
4063
|
if (userText.startsWith("/")) {
|
|
3849
|
-
const
|
|
4064
|
+
const parts = userText.trim().split(/\s+/);
|
|
4065
|
+
const cmd = parts[0].toLowerCase();
|
|
3850
4066
|
if (cmd === "/exit" || cmd === "/quit" || cmd === "/q") {
|
|
3851
4067
|
exit();
|
|
3852
4068
|
return;
|
|
@@ -3863,6 +4079,77 @@ function App({
|
|
|
3863
4079
|
]);
|
|
3864
4080
|
return;
|
|
3865
4081
|
}
|
|
4082
|
+
if (cmd === "/auto") {
|
|
4083
|
+
const sub = parts[1]?.toLowerCase();
|
|
4084
|
+
if (!sub || sub === "status") {
|
|
4085
|
+
const modeLabel = autopilotMode.toUpperCase();
|
|
4086
|
+
const intervalLabel = formatInterval(autopilotIntervalMs);
|
|
4087
|
+
const budgetLabel = `${epochCallCountRef.current}/${EPOCH_BUDGET}`;
|
|
4088
|
+
setChatMessages((prev) => [
|
|
4089
|
+
...prev,
|
|
4090
|
+
{ role: "user", content: userText },
|
|
4091
|
+
{ role: "assistant", content: `**Autopilot Status**
|
|
4092
|
+
|
|
4093
|
+
Mode: ${modeLabel}
|
|
4094
|
+
Interval: ${intervalLabel}
|
|
4095
|
+
Epoch Budget: ${budgetLabel} calls used` }
|
|
4096
|
+
]);
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
if (sub === "on" || sub === "semi") {
|
|
4100
|
+
setAutopilotMode("semi");
|
|
4101
|
+
saveAutopilotConfig({ mode: "semi", intervalMs: autopilotIntervalMs });
|
|
4102
|
+
setChatMessages((prev) => [
|
|
4103
|
+
...prev,
|
|
4104
|
+
{ role: "user", content: userText },
|
|
4105
|
+
{ role: "assistant", content: `Autopilot enabled: **SEMI** mode (every ${formatInterval(autopilotIntervalMs)}). I'll propose trades for your approval.` }
|
|
4106
|
+
]);
|
|
4107
|
+
addLogEntry("Mode set to SEMI");
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
if (sub === "full") {
|
|
4111
|
+
setAutopilotMode("full");
|
|
4112
|
+
saveAutopilotConfig({ mode: "full", intervalMs: autopilotIntervalMs });
|
|
4113
|
+
setChatMessages((prev) => [
|
|
4114
|
+
...prev,
|
|
4115
|
+
{ role: "user", content: userText },
|
|
4116
|
+
{ role: "assistant", content: `Autopilot enabled: **FULL** mode (every ${formatInterval(autopilotIntervalMs)}). I'll execute trades autonomously \u2014 check the log panel.` }
|
|
4117
|
+
]);
|
|
4118
|
+
addLogEntry("Mode set to FULL");
|
|
4119
|
+
return;
|
|
4120
|
+
}
|
|
4121
|
+
if (sub === "off") {
|
|
4122
|
+
setAutopilotMode("off");
|
|
4123
|
+
saveAutopilotConfig({ mode: "off", intervalMs: autopilotIntervalMs });
|
|
4124
|
+
setChatMessages((prev) => [
|
|
4125
|
+
...prev,
|
|
4126
|
+
{ role: "user", content: userText },
|
|
4127
|
+
{ role: "assistant", content: "Autopilot disabled." }
|
|
4128
|
+
]);
|
|
4129
|
+
addLogEntry("Autopilot OFF");
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
4132
|
+
const parsed = parseInterval(sub);
|
|
4133
|
+
if (parsed !== null) {
|
|
4134
|
+
setAutopilotIntervalMs(parsed);
|
|
4135
|
+
const newMode = autopilotMode === "off" ? "semi" : autopilotMode;
|
|
4136
|
+
setAutopilotMode(newMode);
|
|
4137
|
+
saveAutopilotConfig({ mode: newMode, intervalMs: parsed });
|
|
4138
|
+
setChatMessages((prev) => [
|
|
4139
|
+
...prev,
|
|
4140
|
+
{ role: "user", content: userText },
|
|
4141
|
+
{ role: "assistant", content: `Autopilot interval set to **${formatInterval(parsed)}**. Mode: ${newMode.toUpperCase()}.` }
|
|
4142
|
+
]);
|
|
4143
|
+
addLogEntry(`Interval set to ${formatInterval(parsed)}`);
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
setChatMessages((prev) => [
|
|
4147
|
+
...prev,
|
|
4148
|
+
{ role: "user", content: userText },
|
|
4149
|
+
{ role: "assistant", content: "Usage: `/auto on` \xB7 `/auto full` \xB7 `/auto off` \xB7 `/auto 5m` \xB7 `/auto status`" }
|
|
4150
|
+
]);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
3866
4153
|
if (cmd === "/help" || cmd === "/?") {
|
|
3867
4154
|
setChatMessages((prev) => [
|
|
3868
4155
|
...prev,
|
|
@@ -3881,13 +4168,13 @@ function App({
|
|
|
3881
4168
|
" `/buy <amt>` \u2014 Buy $NOVA (e.g. `/buy 500`)",
|
|
3882
4169
|
" `/sell <amt>`\u2014 Sell $NOVA (e.g. `/sell 200`)",
|
|
3883
4170
|
"",
|
|
3884
|
-
"**
|
|
4171
|
+
"**Autopilot**",
|
|
3885
4172
|
"",
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
4173
|
+
" `/auto on` \u2014 Enable semi-auto mode",
|
|
4174
|
+
" `/auto full` \u2014 Enable full-auto mode",
|
|
4175
|
+
" `/auto off` \u2014 Disable autopilot",
|
|
4176
|
+
" `/auto 5m` \u2014 Set interval (1m-60m)",
|
|
4177
|
+
" `/auto status` \u2014 Show autopilot status",
|
|
3891
4178
|
"",
|
|
3892
4179
|
"**System**",
|
|
3893
4180
|
"",
|
|
@@ -3907,8 +4194,8 @@ function App({
|
|
|
3907
4194
|
"/trades": "Show my recent trade history.",
|
|
3908
4195
|
"/board": "Show recent posts from the board.",
|
|
3909
4196
|
"/wallet": "Check my wallet status \u2014 do I have one set up?",
|
|
3910
|
-
"/buy": `Buy ${
|
|
3911
|
-
"/sell": `Sell ${
|
|
4197
|
+
"/buy": `Buy ${parts.slice(1).join(" ") || "some"} $NOVA.`,
|
|
4198
|
+
"/sell": `Sell ${parts.slice(1).join(" ") || "some"} $NOVA.`
|
|
3912
4199
|
};
|
|
3913
4200
|
if (shortcuts[cmd]) {
|
|
3914
4201
|
userText = shortcuts[cmd];
|
|
@@ -3923,7 +4210,7 @@ function App({
|
|
|
3923
4210
|
}
|
|
3924
4211
|
setChatMessages((prev) => [
|
|
3925
4212
|
...prev,
|
|
3926
|
-
{ role:
|
|
4213
|
+
{ role: displayRole, content: userText }
|
|
3927
4214
|
]);
|
|
3928
4215
|
const newCoreMessages = [
|
|
3929
4216
|
...coreMessages,
|
|
@@ -3941,7 +4228,7 @@ function App({
|
|
|
3941
4228
|
rewardsContext,
|
|
3942
4229
|
onboardingContext,
|
|
3943
4230
|
apiContext,
|
|
3944
|
-
profile,
|
|
4231
|
+
{ ...profile, autopilotMode },
|
|
3945
4232
|
{
|
|
3946
4233
|
onTextChunk: (chunk) => {
|
|
3947
4234
|
setStreamingText((prev) => (prev ?? "") + chunk);
|
|
@@ -3964,13 +4251,13 @@ function App({
|
|
|
3964
4251
|
updatedChat = [
|
|
3965
4252
|
marker,
|
|
3966
4253
|
...recentChat,
|
|
3967
|
-
{ role:
|
|
4254
|
+
{ role: displayRole, content: userText },
|
|
3968
4255
|
{ role: "assistant", content: result.text }
|
|
3969
4256
|
];
|
|
3970
4257
|
} else {
|
|
3971
4258
|
updatedChat = [
|
|
3972
4259
|
...chatMessages,
|
|
3973
|
-
{ role:
|
|
4260
|
+
{ role: displayRole, content: userText },
|
|
3974
4261
|
{ role: "assistant", content: result.text }
|
|
3975
4262
|
];
|
|
3976
4263
|
}
|
|
@@ -4008,8 +4295,11 @@ ${stack}
|
|
|
4008
4295
|
setToolName(void 0);
|
|
4009
4296
|
}
|
|
4010
4297
|
},
|
|
4011
|
-
[coreMessages, chatMessages, skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, agentName, sessionId]
|
|
4298
|
+
[coreMessages, chatMessages, skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry]
|
|
4012
4299
|
);
|
|
4300
|
+
useEffect3(() => {
|
|
4301
|
+
sendMessageRef.current = sendMessage;
|
|
4302
|
+
}, [sendMessage]);
|
|
4013
4303
|
const handleSubmit = useCallback2(
|
|
4014
4304
|
(userText) => {
|
|
4015
4305
|
void sendMessage(userText);
|
|
@@ -4021,13 +4311,25 @@ ${stack}
|
|
|
4021
4311
|
isLoading && toolName && /* @__PURE__ */ jsx7(Spinner, { label: `Calling ${toolName}...` }),
|
|
4022
4312
|
isLoading && !toolName && /* @__PURE__ */ jsx7(Spinner, { label: streamingText ? "Thinking..." : void 0 }),
|
|
4023
4313
|
/* @__PURE__ */ jsx7(Box7, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx7(Input, { isActive: !isLoading, onSubmit: handleSubmit }) }),
|
|
4024
|
-
/* @__PURE__ */ jsx7(Box7, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx7(
|
|
4025
|
-
|
|
4314
|
+
/* @__PURE__ */ jsx7(Box7, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx7(
|
|
4315
|
+
StatusBar_default,
|
|
4316
|
+
{
|
|
4317
|
+
agentName,
|
|
4318
|
+
journeyStage: profile.journeyStage ?? "full",
|
|
4319
|
+
autopilotMode,
|
|
4320
|
+
autopilotIntervalMs,
|
|
4321
|
+
onEpochChange: handleEpochChange
|
|
4322
|
+
}
|
|
4323
|
+
) }),
|
|
4324
|
+
/* @__PURE__ */ jsxs8(Box7, { flexShrink: 0, width: "100%", paddingX: 2, marginTop: 1, justifyContent: "space-between", children: [
|
|
4026
4325
|
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /exit" }),
|
|
4027
|
-
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "Ctrl+C quit" })
|
|
4326
|
+
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/auto on\xB7off\xB7set \xB7 Ctrl+C quit" })
|
|
4028
4327
|
] })
|
|
4029
4328
|
] });
|
|
4030
4329
|
}
|
|
4330
|
+
function formatTime(d) {
|
|
4331
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
4332
|
+
}
|
|
4031
4333
|
|
|
4032
4334
|
// src/bin/astra.ts
|
|
4033
4335
|
function detectJourneyStage(params) {
|
|
@@ -4145,6 +4447,7 @@ async function main() {
|
|
|
4145
4447
|
console.log(" No previous session found. Starting fresh.\n");
|
|
4146
4448
|
}
|
|
4147
4449
|
}
|
|
4450
|
+
const initialAutopilotConfig = loadAutopilotConfig();
|
|
4148
4451
|
const { waitUntilExit } = render(
|
|
4149
4452
|
React5.createElement(App, {
|
|
4150
4453
|
agentName,
|
|
@@ -4159,6 +4462,7 @@ async function main() {
|
|
|
4159
4462
|
memoryContent,
|
|
4160
4463
|
initialCoreMessages,
|
|
4161
4464
|
initialChatMessages,
|
|
4465
|
+
initialAutopilotConfig,
|
|
4162
4466
|
debug
|
|
4163
4467
|
})
|
|
4164
4468
|
);
|