@astra-cli/cli 1.2.6 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/astra.js +309 -206
  2. package/package.json +1 -1
package/dist/astra.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/bin/astra.ts
4
4
  import process4 from "process";
5
- import React5 from "react";
5
+ import React7 from "react";
6
6
  import { render } from "ink";
7
7
  import { execFileSync as execFileSync2 } from "child_process";
8
8
 
@@ -52,6 +52,33 @@ function getActiveManifest() {
52
52
  return _activeManifest;
53
53
  }
54
54
 
55
+ // src/ui/AppErrorBoundary.tsx
56
+ import React from "react";
57
+ import { Text, Box } from "ink";
58
+ import { jsx, jsxs } from "react/jsx-runtime";
59
+ var AppErrorBoundary = class extends React.Component {
60
+ state = { error: null };
61
+ static getDerivedStateFromError(error) {
62
+ return { error };
63
+ }
64
+ componentDidCatch(error) {
65
+ process.stderr.write(`[astra] Fatal render error: ${error.stack ?? error.message}
66
+ `);
67
+ }
68
+ render() {
69
+ if (this.state.error) {
70
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
71
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "Something went wrong" }),
72
+ /* @__PURE__ */ jsx(Text, { children: " " }),
73
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: this.state.error.message }),
74
+ /* @__PURE__ */ jsx(Text, { children: " " }),
75
+ /* @__PURE__ */ jsx(Text, { children: "Press Ctrl+C to exit. Your session has been saved." })
76
+ ] });
77
+ }
78
+ return this.props.children;
79
+ }
80
+ };
81
+
55
82
  // src/domain/astranova/manifest.ts
56
83
  var ASTRANOVA_MANIFEST = {
57
84
  name: "astranova",
@@ -452,8 +479,7 @@ function getActivePlugin() {
452
479
  return loadState()?.activePlugin ?? "astranova";
453
480
  }
454
481
  function setActivePlugin(name) {
455
- const state = loadState();
456
- if (!state) return;
482
+ const state = loadState() ?? { activePlugin: "astranova", activeAgents: {}, agents: {} };
457
483
  saveState({ ...state, activePlugin: name });
458
484
  }
459
485
  function loadPluginManifest(name) {
@@ -556,11 +582,24 @@ async function refreshTokens(params) {
556
582
  client_id: clientId,
557
583
  refresh_token: params.refreshToken
558
584
  });
559
- const response = await fetch(OPENAI_TOKEN_ENDPOINT, {
560
- method: "POST",
561
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
562
- body
563
- });
585
+ const controller = new AbortController();
586
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
587
+ let response;
588
+ try {
589
+ response = await fetch(OPENAI_TOKEN_ENDPOINT, {
590
+ method: "POST",
591
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
592
+ body,
593
+ signal: controller.signal
594
+ });
595
+ } catch (err) {
596
+ clearTimeout(timeoutId);
597
+ if (err instanceof Error && err.name === "AbortError") {
598
+ throw new Error("Token refresh timed out after 15s");
599
+ }
600
+ throw err;
601
+ }
602
+ clearTimeout(timeoutId);
564
603
  if (!response.ok) {
565
604
  const text3 = await response.text();
566
605
  throw new Error(`Token refresh failed (HTTP ${response.status}): ${text3.slice(0, 200)}`);
@@ -871,31 +910,38 @@ async function promptApiKey(provider) {
871
910
  openai: "sk-...",
872
911
  google: "AIza..."
873
912
  };
874
- const apiKey = await clack.text({
875
- message: `Enter your ${labels[provider] ?? "API key"}`,
876
- placeholder: placeholders[provider] ?? "your-api-key",
877
- validate(value) {
878
- if (!value || value.trim().length === 0) {
879
- return "API key is required";
913
+ const maxAttempts = 5;
914
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
915
+ const apiKey = await clack.text({
916
+ message: `Enter your ${labels[provider] ?? "API key"}`,
917
+ placeholder: placeholders[provider] ?? "your-api-key",
918
+ validate(value) {
919
+ if (!value || value.trim().length === 0) {
920
+ return "API key is required";
921
+ }
922
+ return void 0;
880
923
  }
881
- return void 0;
924
+ });
925
+ if (clack.isCancel(apiKey)) {
926
+ clack.cancel("Setup cancelled.");
927
+ process.exit(0);
882
928
  }
883
- });
884
- if (clack.isCancel(apiKey)) {
885
- clack.cancel("Setup cancelled.");
886
- process.exit(0);
887
- }
888
- const trimmed = apiKey.trim();
889
- const spinner5 = clack.spinner();
890
- spinner5.start("Validating API key...");
891
- const valid = await validateApiKey(provider, trimmed);
892
- if (!valid.ok) {
893
- spinner5.stop(`API key validation failed: ${valid.error}`);
894
- clack.log.error("Please check your key and try again.");
895
- return promptApiKey(provider);
929
+ const trimmed = apiKey.trim();
930
+ const spinner5 = clack.spinner();
931
+ spinner5.start("Validating API key...");
932
+ const valid = await validateApiKey(provider, trimmed);
933
+ if (!valid.ok) {
934
+ spinner5.stop(`API key validation failed: ${valid.error}`);
935
+ clack.log.error(
936
+ attempt < maxAttempts - 1 ? "Please check your key and try again." : "Too many failed attempts. Please restart setup."
937
+ );
938
+ continue;
939
+ }
940
+ spinner5.stop("API key validated.");
941
+ return trimmed;
896
942
  }
897
- spinner5.stop("API key validated.");
898
- return trimmed;
943
+ clack.cancel("Too many failed API key attempts.");
944
+ process.exit(1);
899
945
  }
900
946
  async function validateApiKey(provider, apiKey) {
901
947
  try {
@@ -1081,6 +1127,10 @@ async function registerAgent() {
1081
1127
  if (!parsed.success) {
1082
1128
  spinner5.stop("Registration failed.");
1083
1129
  clack2.log.error("Unexpected response from API. Please try again.");
1130
+ if (process.env.ASTRA_DEBUG) {
1131
+ process.stderr.write(`[astra] Schema validation: ${JSON.stringify(parsed.error.issues)}
1132
+ `);
1133
+ }
1084
1134
  continue;
1085
1135
  }
1086
1136
  const { agent } = parsed.data;
@@ -1459,11 +1509,11 @@ async function getCached(name, url, ttlMs) {
1459
1509
  return fallbackToStale(contentPath, name, url, response.status);
1460
1510
  }
1461
1511
  const content = await response.text();
1462
- fs3.writeFileSync(contentPath, content, "utf-8");
1512
+ fs3.writeFileSync(contentPath, content, { encoding: "utf-8", mode: 384 });
1463
1513
  fs3.writeFileSync(
1464
1514
  metaPath(name),
1465
1515
  JSON.stringify({ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), url }),
1466
- "utf-8"
1516
+ { encoding: "utf-8", mode: 384 }
1467
1517
  );
1468
1518
  return content;
1469
1519
  } catch {
@@ -1554,7 +1604,7 @@ function saveSession(params) {
1554
1604
  agentName: params.agentName,
1555
1605
  provider: params.provider,
1556
1606
  sessionId: params.sessionId,
1557
- createdAt: params.sessionId,
1607
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1558
1608
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1559
1609
  coreMessages: serializeMessages(params.coreMessages),
1560
1610
  chatMessages: params.chatMessages.slice(-MAX_MESSAGES)
@@ -2909,21 +2959,22 @@ var registerAgentTool = tool6({
2909
2959
  api_key: apiKey,
2910
2960
  api_base: getActiveManifest().apiBase
2911
2961
  });
2962
+ const verificationCode = data.verification_code ?? data.agent?.verification_code;
2912
2963
  setActiveAgent(name);
2913
2964
  updateAgentState(name, {
2914
2965
  status: "pending_verification",
2915
2966
  journeyStage: "fresh",
2916
- verificationCode: data.verification_code
2967
+ verificationCode
2917
2968
  });
2918
2969
  requestRestart();
2919
2970
  return {
2920
2971
  success: true,
2921
2972
  agentName: name,
2922
2973
  status: "pending_verification",
2923
- verificationCode: data.verification_code,
2974
+ verificationCode,
2924
2975
  simBalance: data.agent?.simBalance ?? 1e4,
2925
2976
  restartRequired: true,
2926
- message: `Agent "${name}" registered successfully! Verification code: ${data.verification_code}. Restarting to load the new agent...`
2977
+ message: `Agent "${name}" registered successfully!${verificationCode ? ` Verification code: ${verificationCode}.` : ""} Restarting to load the new agent...`
2927
2978
  };
2928
2979
  }
2929
2980
  });
@@ -3162,7 +3213,6 @@ async function parseSSEStream(body, callbacks, idleTimeoutMs = 45e3) {
3162
3213
  name: event.item.name,
3163
3214
  args: ""
3164
3215
  });
3165
- callbacks?.onToolCallStart?.(event.item.name);
3166
3216
  }
3167
3217
  break;
3168
3218
  case "response.function_call_arguments.delta":
@@ -3174,12 +3224,6 @@ async function parseSSEStream(body, callbacks, idleTimeoutMs = 45e3) {
3174
3224
  }
3175
3225
  break;
3176
3226
  case "response.function_call_arguments.done":
3177
- if (event.item_id) {
3178
- const tc = toolCalls.get(event.item_id);
3179
- if (tc) {
3180
- callbacks?.onToolCallEnd?.(tc.name);
3181
- }
3182
- }
3183
3227
  break;
3184
3228
  case "response.failed": {
3185
3229
  const resp = event.response;
@@ -3574,7 +3618,11 @@ async function runResponsesApiTurn(messages, systemPrompt, callbacks, turnConfig
3574
3618
  const startTime = Date.now();
3575
3619
  try {
3576
3620
  debugLog3(`Tool ${tc.name}(${tc.callId}) args: ${JSON.stringify(parsedArgs)}`);
3577
- const execute = toolDef.execute;
3621
+ const exec2 = toolDef.execute;
3622
+ if (typeof exec2 !== "function") {
3623
+ throw new Error(`Tool "${tc.name}" has no execute method`);
3624
+ }
3625
+ const execute = exec2;
3578
3626
  const toolResult = await execute(parsedArgs, {});
3579
3627
  debugLog3(`Tool ${tc.name}(${tc.callId}) result: ${JSON.stringify(toolResult).slice(0, 200)}`);
3580
3628
  writeAuditEntry({
@@ -3719,6 +3767,12 @@ async function runSdkTurn(messages, systemPrompt, callbacks) {
3719
3767
  maxSteps: 10,
3720
3768
  temperature: 0.7,
3721
3769
  abortSignal: abortController.signal,
3770
+ toolCallStreaming: true,
3771
+ onChunk: ({ chunk }) => {
3772
+ if (chunk.type === "tool-call-streaming-start") {
3773
+ callbacks.onToolCallStart?.(chunk.toolName);
3774
+ }
3775
+ },
3722
3776
  onStepFinish: ({ toolCalls, toolResults }) => {
3723
3777
  resetIdleTimer();
3724
3778
  if (toolCalls && toolCalls.length > 0) {
@@ -4189,7 +4243,8 @@ function extractNarrativeContent(skillMd) {
4189
4243
  return skillMd.slice(0, firstEngineIdx);
4190
4244
  }
4191
4245
  function buildManifestFromMeta(meta) {
4192
- const name = typeof meta.name === "string" ? meta.name.trim() : void 0;
4246
+ const rawName = typeof meta.name === "string" ? meta.name.trim() : void 0;
4247
+ const name = rawName && /^[a-z0-9_-]+$/.test(rawName) ? rawName : void 0;
4193
4248
  const version = typeof meta.version === "string" ? meta.version.trim() : "0.0.0";
4194
4249
  const description = typeof meta.description === "string" ? meta.description.trim() : name ?? "Unknown";
4195
4250
  const apiBase = typeof meta.apiBase === "string" ? meta.apiBase.trim().replace(/\/$/, "") : void 0;
@@ -4519,14 +4574,14 @@ async function runPluginsPicker() {
4519
4574
 
4520
4575
  // src/ui/App.tsx
4521
4576
  import { useState as useState4, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect3 } from "react";
4522
- import { Box as Box7, Text as Text8, useApp, useInput } from "ink";
4577
+ import { Box as Box9, Text as Text10, useApp, useInput } from "ink";
4523
4578
 
4524
4579
  // src/ui/StatusBar.tsx
4525
- import React, { useState, useEffect, useRef, useCallback } from "react";
4526
- import { Box, Text } from "ink";
4527
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4580
+ import React2, { useState, useEffect, useRef, useCallback } from "react";
4581
+ import { Box as Box2, Text as Text2 } from "ink";
4582
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4528
4583
  var POLL_INTERVAL_MS = 6e4;
4529
- var StatusBar = React.memo(function StatusBar2({
4584
+ var StatusBar = React2.memo(function StatusBar2({
4530
4585
  agentName,
4531
4586
  pluginName,
4532
4587
  isAstraNova,
@@ -4583,50 +4638,50 @@ var StatusBar = React.memo(function StatusBar2({
4583
4638
  }, [canFetchData, poll]);
4584
4639
  const { market, portfolio } = data;
4585
4640
  const apActive = autopilotMode !== "off";
4586
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [
4587
- /* @__PURE__ */ jsxs(Box, { children: [
4588
- /* @__PURE__ */ jsx(Text, { bold: true, color: "#00ff00", children: pluginName }),
4589
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4590
- /* @__PURE__ */ jsx(Text, { color: "#ff8800", children: agentName }),
4641
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, justifyContent: "space-between", children: [
4642
+ /* @__PURE__ */ jsxs2(Box2, { children: [
4643
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#00ff00", children: pluginName }),
4644
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4645
+ /* @__PURE__ */ jsx2(Text2, { color: "#ff8800", children: agentName }),
4591
4646
  !isAstraNova && pluginData && pluginMap?.status?.fields.map((field) => {
4592
4647
  const value = getNestedValue(pluginData, field.path);
4593
4648
  if (value == null) return null;
4594
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
4595
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4596
- /* @__PURE__ */ jsxs(Text, { color: field.color, children: [
4649
+ return /* @__PURE__ */ jsxs2(React2.Fragment, { children: [
4650
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4651
+ /* @__PURE__ */ jsxs2(Text2, { color: field.color, children: [
4597
4652
  field.label,
4598
4653
  ": ",
4599
4654
  String(value)
4600
4655
  ] })
4601
4656
  ] }, field.path);
4602
4657
  }),
4603
- !isAstraNova && !pluginData && pluginMap?.status && /* @__PURE__ */ jsxs(Fragment, { children: [
4604
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4605
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
4658
+ !isAstraNova && !pluginData && pluginMap?.status && /* @__PURE__ */ jsxs2(Fragment, { children: [
4659
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4660
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "loading..." })
4606
4661
  ] }),
4607
- canFetchData && market && /* @__PURE__ */ jsxs(Fragment, { children: [
4608
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4609
- /* @__PURE__ */ jsx(Text, { color: "#ffff00", children: "$NOVA " }),
4610
- /* @__PURE__ */ jsx(Text, { color: "white", children: formatPrice(market.price) }),
4611
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4612
- /* @__PURE__ */ jsx(Text, { color: moodColor(market.mood), children: market.mood })
4662
+ canFetchData && market && /* @__PURE__ */ jsxs2(Fragment, { children: [
4663
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4664
+ /* @__PURE__ */ jsx2(Text2, { color: "#ffff00", children: "$NOVA " }),
4665
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: formatPrice(market.price) }),
4666
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4667
+ /* @__PURE__ */ jsx2(Text2, { color: moodColor(market.mood), children: market.mood })
4613
4668
  ] }),
4614
- canFetchData && portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
4615
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4616
- /* @__PURE__ */ jsxs(Text, { color: "#00ffff", children: [
4669
+ canFetchData && portfolio && /* @__PURE__ */ jsxs2(Fragment, { children: [
4670
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4671
+ /* @__PURE__ */ jsxs2(Text2, { color: "#00ffff", children: [
4617
4672
  formatNum(portfolio.cash),
4618
4673
  " $SIM"
4619
4674
  ] }),
4620
- portfolio.tokens > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4621
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4622
- /* @__PURE__ */ jsxs(Text, { color: "#ff00ff", children: [
4675
+ portfolio.tokens > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
4676
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4677
+ /* @__PURE__ */ jsxs2(Text2, { color: "#ff00ff", children: [
4623
4678
  formatNum(portfolio.tokens),
4624
4679
  " $NOVA"
4625
4680
  ] })
4626
4681
  ] }),
4627
- portfolio.pnl !== 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4628
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4629
- /* @__PURE__ */ jsxs(Text, { color: portfolio.pnl >= 0 ? "#00ff00" : "#ff4444", children: [
4682
+ portfolio.pnl !== 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
4683
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4684
+ /* @__PURE__ */ jsxs2(Text2, { color: portfolio.pnl >= 0 ? "#00ff00" : "#ff4444", children: [
4630
4685
  "P&L ",
4631
4686
  portfolio.pnl >= 0 ? "+" : "",
4632
4687
  formatNum(portfolio.pnl),
@@ -4636,22 +4691,22 @@ var StatusBar = React.memo(function StatusBar2({
4636
4691
  "%)"
4637
4692
  ] })
4638
4693
  ] }),
4639
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4640
- /* @__PURE__ */ jsxs(Text, { color: "#e2f902", children: [
4694
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4695
+ /* @__PURE__ */ jsxs2(Text2, { color: "#e2f902", children: [
4641
4696
  "Net ",
4642
4697
  formatNum(portfolio.portfolioValue)
4643
4698
  ] })
4644
4699
  ] }),
4645
- isAstraNova && !canFetchData && /* @__PURE__ */ jsxs(Fragment, { children: [
4646
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4647
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "pending verification" })
4700
+ isAstraNova && !canFetchData && /* @__PURE__ */ jsxs2(Fragment, { children: [
4701
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4702
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "pending verification" })
4648
4703
  ] }),
4649
- canFetchData && !market && !portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
4650
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
4651
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
4704
+ canFetchData && !market && !portfolio && /* @__PURE__ */ jsxs2(Fragment, { children: [
4705
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2502 " }),
4706
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "loading..." })
4652
4707
  ] })
4653
4708
  ] }),
4654
- apActive && /* @__PURE__ */ jsxs(Text, { color: "#00ff00", children: [
4709
+ apActive && /* @__PURE__ */ jsxs2(Text2, { color: "#00ff00", children: [
4655
4710
  "AP: \u25CF ",
4656
4711
  autopilotMode.toUpperCase(),
4657
4712
  " ",
@@ -4724,14 +4779,14 @@ async function fetchPortfolio(agentName) {
4724
4779
  }
4725
4780
 
4726
4781
  // src/ui/ChatView.tsx
4727
- import { Box as Box5, Text as Text5 } from "ink";
4782
+ import { Box as Box7, Text as Text7 } from "ink";
4728
4783
 
4729
4784
  // src/ui/MarkdownText.tsx
4730
- import { Text as Text4, Box as Box4 } from "ink";
4785
+ import { Text as Text5, Box as Box5 } from "ink";
4731
4786
 
4732
4787
  // src/ui/PortfolioCard.tsx
4733
- import { Box as Box2, Text as Text2 } from "ink";
4734
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4788
+ import { Box as Box3, Text as Text3 } from "ink";
4789
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
4735
4790
  function PortfolioCard({ data }) {
4736
4791
  const price = data.currentPrice ?? 0;
4737
4792
  const cash = data.cash ?? 0;
@@ -4743,14 +4798,14 @@ function PortfolioCard({ data }) {
4743
4798
  const claimable = data.claimable ? Number(data.claimable) / 1e9 : 0;
4744
4799
  const pnlColor = pnl >= 0 ? "#00ff00" : "#ff4444";
4745
4800
  const pnlSign = pnl >= 0 ? "+" : "";
4746
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
4747
- /* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#00ffff", children: "Portfolio Overview" }) }),
4748
- /* @__PURE__ */ jsx2(Row, { label: "$SIM Balance", value: formatNum2(cash), color: "#00ffff" }),
4749
- /* @__PURE__ */ jsx2(Row, { label: "$NOVA Holdings", value: tokens > 0 ? formatNum2(tokens) : "\u2014", color: "#ff00ff" }),
4750
- /* @__PURE__ */ jsx2(Row, { label: "$NOVA Price", value: price > 0 ? formatPrice2(price) : "\u2014", color: "#ffff00" }),
4751
- /* @__PURE__ */ jsx2(Row, { label: "Portfolio Value", value: formatNum2(value), color: "white" }),
4752
- /* @__PURE__ */ jsx2(Row, { label: "P&L", value: `${pnlSign}${formatNum2(pnl)} (${pnlSign}${pnlPct.toFixed(1)}%)`, color: pnlColor }),
4753
- (data.hasWallet !== void 0 || data.walletLocal !== void 0) && /* @__PURE__ */ jsx2(
4801
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
4802
+ /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "#00ffff", children: "Portfolio Overview" }) }),
4803
+ /* @__PURE__ */ jsx3(Row, { label: "$SIM Balance", value: formatNum2(cash), color: "#00ffff" }),
4804
+ /* @__PURE__ */ jsx3(Row, { label: "$NOVA Holdings", value: tokens > 0 ? formatNum2(tokens) : "\u2014", color: "#ff00ff" }),
4805
+ /* @__PURE__ */ jsx3(Row, { label: "$NOVA Price", value: price > 0 ? formatPrice2(price) : "\u2014", color: "#ffff00" }),
4806
+ /* @__PURE__ */ jsx3(Row, { label: "Portfolio Value", value: formatNum2(value), color: "white" }),
4807
+ /* @__PURE__ */ jsx3(Row, { label: "P&L", value: `${pnlSign}${formatNum2(pnl)} (${pnlSign}${pnlPct.toFixed(1)}%)`, color: pnlColor }),
4808
+ (data.hasWallet !== void 0 || data.walletLocal !== void 0) && /* @__PURE__ */ jsx3(
4754
4809
  Row,
4755
4810
  {
4756
4811
  label: "Wallet",
@@ -4758,19 +4813,19 @@ function PortfolioCard({ data }) {
4758
4813
  color: data.hasWallet ? "#00ff00" : data.walletLocal ? "#ffff00" : "gray"
4759
4814
  }
4760
4815
  ),
4761
- (earned > 0 || claimable > 0) && /* @__PURE__ */ jsxs2(Fragment2, { children: [
4762
- /* @__PURE__ */ jsx2(Row, { label: "$ASTRA Earned", value: earned > 0 ? formatAstra(earned) : "\u2014", color: "#ffff00" }),
4763
- /* @__PURE__ */ jsx2(Row, { label: "Claimable", value: claimable > 0 ? formatAstra(claimable) : "\u2014", color: claimable > 0 ? "#00ff00" : "gray" })
4816
+ (earned > 0 || claimable > 0) && /* @__PURE__ */ jsxs3(Fragment2, { children: [
4817
+ /* @__PURE__ */ jsx3(Row, { label: "$ASTRA Earned", value: earned > 0 ? formatAstra(earned) : "\u2014", color: "#ffff00" }),
4818
+ /* @__PURE__ */ jsx3(Row, { label: "Claimable", value: claimable > 0 ? formatAstra(claimable) : "\u2014", color: claimable > 0 ? "#00ff00" : "gray" })
4764
4819
  ] })
4765
4820
  ] });
4766
4821
  }
4767
4822
  function Row({ label, value, color }) {
4768
- return /* @__PURE__ */ jsxs2(Box2, { children: [
4769
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
4823
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
4824
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
4770
4825
  label,
4771
4826
  ": "
4772
4827
  ] }),
4773
- /* @__PURE__ */ jsx2(Text2, { color, bold: true, children: value })
4828
+ /* @__PURE__ */ jsx3(Text3, { color, bold: true, children: value })
4774
4829
  ] });
4775
4830
  }
4776
4831
  function formatPrice2(price) {
@@ -4790,40 +4845,40 @@ function formatAstra(n) {
4790
4845
  }
4791
4846
 
4792
4847
  // src/ui/RewardsCard.tsx
4793
- import { Box as Box3, Text as Text3 } from "ink";
4794
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
4848
+ import { Box as Box4, Text as Text4 } from "ink";
4849
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
4795
4850
  function RewardsCard({ data }) {
4796
4851
  const total = data.totalAstra ? Number(data.totalAstra) / 1e9 : 0;
4797
4852
  const epoch = data.epochAstra ? Number(data.epochAstra) / 1e9 : 0;
4798
4853
  const bonus = data.bonusAstra ? Number(data.bonusAstra) / 1e9 : 0;
4799
4854
  const statusColor = data.claimStatus === "claimable" ? "#00ff00" : data.claimStatus === "sent" ? "#00ffff" : "#ffff00";
4800
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
4801
- /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
4802
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "#ffff00", children: "$ASTRA Rewards" }),
4803
- data.seasonId && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
4855
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 1, marginY: 1, children: [
4856
+ /* @__PURE__ */ jsxs4(Box4, { marginBottom: 1, children: [
4857
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "#ffff00", children: "$ASTRA Rewards" }),
4858
+ data.seasonId && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
4804
4859
  " ",
4805
4860
  data.seasonId
4806
4861
  ] })
4807
4862
  ] }),
4808
- /* @__PURE__ */ jsx3(Row2, { label: "Total Earned", value: formatAstra2(total), color: "#ffff00" }),
4809
- /* @__PURE__ */ jsx3(Row2, { label: "Epoch Rewards", value: formatAstra2(epoch), color: "#00ffff" }),
4810
- /* @__PURE__ */ jsx3(Row2, { label: "Season Bonus", value: formatAstra2(bonus), color: "#ff00ff" }),
4811
- /* @__PURE__ */ jsx3(Row2, { label: "Status", value: data.claimStatus ?? "\u2014", color: statusColor }),
4812
- /* @__PURE__ */ jsx3(Row2, { label: "Epochs Rewarded", value: data.epochsRewarded?.toString() ?? "\u2014", color: "white" }),
4813
- data.bestEpochPnl !== void 0 && data.bestEpochPnl > 0 && /* @__PURE__ */ jsx3(Row2, { label: "Best Epoch P&L", value: `+${data.bestEpochPnl.toFixed(2)}`, color: "#00ff00" }),
4814
- data.txSignature && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
4815
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tx: " }),
4816
- /* @__PURE__ */ jsx3(Text3, { color: "#00ffff", children: data.txSignature })
4863
+ /* @__PURE__ */ jsx4(Row2, { label: "Total Earned", value: formatAstra2(total), color: "#ffff00" }),
4864
+ /* @__PURE__ */ jsx4(Row2, { label: "Epoch Rewards", value: formatAstra2(epoch), color: "#00ffff" }),
4865
+ /* @__PURE__ */ jsx4(Row2, { label: "Season Bonus", value: formatAstra2(bonus), color: "#ff00ff" }),
4866
+ /* @__PURE__ */ jsx4(Row2, { label: "Status", value: data.claimStatus ?? "\u2014", color: statusColor }),
4867
+ /* @__PURE__ */ jsx4(Row2, { label: "Epochs Rewarded", value: data.epochsRewarded?.toString() ?? "\u2014", color: "white" }),
4868
+ data.bestEpochPnl !== void 0 && data.bestEpochPnl > 0 && /* @__PURE__ */ jsx4(Row2, { label: "Best Epoch P&L", value: `+${data.bestEpochPnl.toFixed(2)}`, color: "#00ff00" }),
4869
+ data.txSignature && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
4870
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Tx: " }),
4871
+ /* @__PURE__ */ jsx4(Text4, { color: "#00ffff", children: data.txSignature })
4817
4872
  ] })
4818
4873
  ] });
4819
4874
  }
4820
4875
  function Row2({ label, value, color }) {
4821
- return /* @__PURE__ */ jsxs3(Box3, { children: [
4822
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
4876
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
4877
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
4823
4878
  label,
4824
4879
  ": "
4825
4880
  ] }),
4826
- /* @__PURE__ */ jsx3(Text3, { color, bold: true, children: value })
4881
+ /* @__PURE__ */ jsx4(Text4, { color, bold: true, children: value })
4827
4882
  ] });
4828
4883
  }
4829
4884
  function formatAstra2(n) {
@@ -4834,7 +4889,7 @@ function formatAstra2(n) {
4834
4889
  }
4835
4890
 
4836
4891
  // src/ui/MarkdownText.tsx
4837
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
4892
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
4838
4893
  function MarkdownText({ children }) {
4839
4894
  const lines = children.split("\n");
4840
4895
  const elements = [];
@@ -4854,14 +4909,14 @@ function MarkdownText({ children }) {
4854
4909
  const raw = jsonLines.join("\n");
4855
4910
  if (cardType === "portfolio") {
4856
4911
  const data = JSON.parse(raw);
4857
- elements.push(/* @__PURE__ */ jsx4(PortfolioCard, { data }, elements.length));
4912
+ elements.push(/* @__PURE__ */ jsx5(PortfolioCard, { data }, elements.length));
4858
4913
  } else {
4859
4914
  const data = JSON.parse(raw);
4860
- elements.push(/* @__PURE__ */ jsx4(RewardsCard, { data }, elements.length));
4915
+ elements.push(/* @__PURE__ */ jsx5(RewardsCard, { data }, elements.length));
4861
4916
  }
4862
4917
  } catch {
4863
4918
  elements.push(
4864
- /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: jsonLines.join("\n") }) }, elements.length)
4919
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: jsonLines.join("\n") }) }, elements.length)
4865
4920
  );
4866
4921
  }
4867
4922
  continue;
@@ -4875,13 +4930,13 @@ function MarkdownText({ children }) {
4875
4930
  }
4876
4931
  i++;
4877
4932
  elements.push(
4878
- /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, marginY: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "gray", children: codeLines.join("\n") }) }, elements.length)
4933
+ /* @__PURE__ */ jsx5(Box5, { marginLeft: 2, marginY: 0, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: codeLines.join("\n") }) }, elements.length)
4879
4934
  );
4880
4935
  continue;
4881
4936
  }
4882
4937
  if (/^---+$/.test(line.trim())) {
4883
4938
  elements.push(
4884
- /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(40) }) }, elements.length)
4939
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500".repeat(40) }) }, elements.length)
4885
4940
  );
4886
4941
  i++;
4887
4942
  continue;
@@ -4889,7 +4944,7 @@ function MarkdownText({ children }) {
4889
4944
  const headerMatch = /^(#{1,4})\s+(.+)$/.exec(line);
4890
4945
  if (headerMatch) {
4891
4946
  elements.push(
4892
- /* @__PURE__ */ jsx4(Box4, { marginTop: elements.length > 0 ? 1 : 0, children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: headerMatch[2] }) }, elements.length)
4947
+ /* @__PURE__ */ jsx5(Box5, { marginTop: elements.length > 0 ? 1 : 0, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: headerMatch[2] }) }, elements.length)
4893
4948
  );
4894
4949
  i++;
4895
4950
  continue;
@@ -4898,8 +4953,8 @@ function MarkdownText({ children }) {
4898
4953
  if (bulletMatch) {
4899
4954
  const indent = Math.floor((bulletMatch[1]?.length ?? 0) / 2);
4900
4955
  elements.push(
4901
- /* @__PURE__ */ jsxs4(Box4, { marginLeft: indent * 2, children: [
4902
- /* @__PURE__ */ jsx4(Text4, { children: " \u25CF " }),
4956
+ /* @__PURE__ */ jsxs5(Box5, { marginLeft: indent * 2, children: [
4957
+ /* @__PURE__ */ jsx5(Text5, { children: " \u25CF " }),
4903
4958
  renderInline(bulletMatch[2])
4904
4959
  ] }, elements.length)
4905
4960
  );
@@ -4910,8 +4965,8 @@ function MarkdownText({ children }) {
4910
4965
  if (numMatch) {
4911
4966
  const indent = Math.floor((numMatch[1]?.length ?? 0) / 2);
4912
4967
  elements.push(
4913
- /* @__PURE__ */ jsxs4(Box4, { marginLeft: indent * 2, children: [
4914
- /* @__PURE__ */ jsxs4(Text4, { children: [
4968
+ /* @__PURE__ */ jsxs5(Box5, { marginLeft: indent * 2, children: [
4969
+ /* @__PURE__ */ jsxs5(Text5, { children: [
4915
4970
  " ",
4916
4971
  numMatch[2],
4917
4972
  ". "
@@ -4923,16 +4978,16 @@ function MarkdownText({ children }) {
4923
4978
  continue;
4924
4979
  }
4925
4980
  if (line.trim() === "") {
4926
- elements.push(/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { children: " " }) }, elements.length));
4981
+ elements.push(/* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { children: " " }) }, elements.length));
4927
4982
  i++;
4928
4983
  continue;
4929
4984
  }
4930
4985
  elements.push(
4931
- /* @__PURE__ */ jsx4(Box4, { flexWrap: "wrap", children: renderInline(line) }, elements.length)
4986
+ /* @__PURE__ */ jsx5(Box5, { flexWrap: "wrap", children: renderInline(line) }, elements.length)
4932
4987
  );
4933
4988
  i++;
4934
4989
  }
4935
- return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: elements });
4990
+ return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: elements });
4936
4991
  }
4937
4992
  function renderInline(text3) {
4938
4993
  const parts = [];
@@ -4945,7 +5000,7 @@ function renderInline(text3) {
4945
5000
  candidates.push({
4946
5001
  index: boldMatch.index,
4947
5002
  length: boldMatch[0].length,
4948
- node: /* @__PURE__ */ jsx4(Text4, { bold: true, children: boldMatch[1] ?? boldMatch[2] }, key++)
5003
+ node: /* @__PURE__ */ jsx5(Text5, { bold: true, children: boldMatch[1] ?? boldMatch[2] }, key++)
4949
5004
  });
4950
5005
  }
4951
5006
  const codeMatch = /`([^`]+?)`/.exec(remaining);
@@ -4953,7 +5008,7 @@ function renderInline(text3) {
4953
5008
  candidates.push({
4954
5009
  index: codeMatch.index,
4955
5010
  length: codeMatch[0].length,
4956
- node: /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: codeMatch[1] }, key++)
5011
+ node: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: codeMatch[1] }, key++)
4957
5012
  });
4958
5013
  }
4959
5014
  const italicMatch = /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/.exec(remaining);
@@ -4961,58 +5016,83 @@ function renderInline(text3) {
4961
5016
  candidates.push({
4962
5017
  index: italicMatch.index,
4963
5018
  length: italicMatch[0].length,
4964
- node: /* @__PURE__ */ jsx4(Text4, { italic: true, children: italicMatch[1] ?? italicMatch[2] }, key++)
5019
+ node: /* @__PURE__ */ jsx5(Text5, { italic: true, children: italicMatch[1] ?? italicMatch[2] }, key++)
4965
5020
  });
4966
5021
  }
4967
5022
  candidates.sort((a, b) => a.index - b.index);
4968
5023
  const pick = candidates[0];
4969
5024
  if (!pick) {
4970
- parts.push(/* @__PURE__ */ jsx4(Text4, { children: remaining }, key++));
5025
+ parts.push(/* @__PURE__ */ jsx5(Text5, { children: remaining }, key++));
4971
5026
  break;
4972
5027
  }
4973
5028
  if (pick.index > 0) {
4974
- parts.push(/* @__PURE__ */ jsx4(Text4, { children: remaining.slice(0, pick.index) }, key++));
5029
+ parts.push(/* @__PURE__ */ jsx5(Text5, { children: remaining.slice(0, pick.index) }, key++));
4975
5030
  }
4976
5031
  parts.push(pick.node);
4977
5032
  remaining = remaining.slice(pick.index + pick.length);
4978
5033
  }
4979
- return /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: parts });
5034
+ return /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: parts });
4980
5035
  }
4981
5036
 
5037
+ // src/ui/ErrorBoundary.tsx
5038
+ import React3 from "react";
5039
+ import { Text as Text6, Box as Box6 } from "ink";
5040
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
5041
+ var ErrorBoundary = class extends React3.Component {
5042
+ state = { error: null };
5043
+ static getDerivedStateFromError(error) {
5044
+ return { error };
5045
+ }
5046
+ componentDidCatch(error) {
5047
+ process.stderr.write(`[astra] Render error: ${error.message}
5048
+ `);
5049
+ }
5050
+ render() {
5051
+ if (this.state.error) {
5052
+ return this.props.fallback ?? /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: "red", dimColor: true, children: [
5053
+ "[render error: ",
5054
+ this.state.error.message,
5055
+ "]"
5056
+ ] }) });
5057
+ }
5058
+ return this.props.children;
5059
+ }
5060
+ };
5061
+
4982
5062
  // src/ui/ChatView.tsx
4983
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
5063
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
4984
5064
  function ChatView({
4985
5065
  messages,
4986
5066
  streamingText
4987
5067
  }) {
4988
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", paddingX: 1, children: [
5068
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", paddingX: 1, children: [
4989
5069
  messages.map((msg, i) => {
4990
5070
  if (msg.role === "log") {
4991
- return /* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: msg.content }) }, i);
5071
+ return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: msg.content }) }, i);
4992
5072
  }
4993
5073
  if (msg.role === "autopilot") {
4994
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
4995
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "#ff00ff", children: " Autopilot" }),
4996
- /* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "wrap", children: msg.content }) })
5074
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
5075
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "#ff00ff", children: " Autopilot" }),
5076
+ /* @__PURE__ */ jsx7(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "wrap", children: msg.content }) })
4997
5077
  ] }, i);
4998
5078
  }
4999
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
5000
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: msg.role === "user" ? "#00ff00" : "#00ffff", children: msg.role === "user" ? " You" : " Agent" }),
5001
- /* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(MarkdownText, { children: msg.content }) : /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: msg.content }) })
5079
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
5080
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: msg.role === "user" ? "#00ff00" : "#00ffff", children: msg.role === "user" ? " You" : " Agent" }),
5081
+ /* @__PURE__ */ jsx7(Box7, { marginLeft: 1, children: msg.role === "assistant" ? /* @__PURE__ */ jsx7(ErrorBoundary, { children: /* @__PURE__ */ jsx7(MarkdownText, { children: msg.content }) }) : /* @__PURE__ */ jsx7(Text7, { wrap: "wrap", children: msg.content }) })
5002
5082
  ] }, i);
5003
5083
  }),
5004
- streamingText !== void 0 && streamingText.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
5005
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "#00ffff", children: " Agent" }),
5006
- /* @__PURE__ */ jsx5(Box5, { marginLeft: 1, children: /* @__PURE__ */ jsx5(MarkdownText, { children: streamingText }) })
5084
+ streamingText !== void 0 && streamingText.length > 0 && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
5085
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "#00ffff", children: " Agent" }),
5086
+ /* @__PURE__ */ jsx7(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx7(ErrorBoundary, { children: /* @__PURE__ */ jsx7(MarkdownText, { children: streamingText }) }) })
5007
5087
  ] })
5008
5088
  ] });
5009
5089
  }
5010
5090
 
5011
5091
  // src/ui/Input.tsx
5012
5092
  import { useState as useState2 } from "react";
5013
- import { Box as Box6, Text as Text6 } from "ink";
5093
+ import { Box as Box8, Text as Text8 } from "ink";
5014
5094
  import TextInput from "ink-text-input";
5015
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
5095
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
5016
5096
  function Input({
5017
5097
  isActive,
5018
5098
  onSubmit
@@ -5024,9 +5104,9 @@ function Input({
5024
5104
  onSubmit(trimmed);
5025
5105
  setValue("");
5026
5106
  };
5027
- return /* @__PURE__ */ jsxs6(Box6, { width: "100%", paddingX: 2, paddingY: 1, children: [
5028
- /* @__PURE__ */ jsx6(Text6, { color: isActive ? "yellow" : "gray", bold: true, children: "\u276F\u276F " }),
5029
- /* @__PURE__ */ jsx6(
5107
+ return /* @__PURE__ */ jsxs8(Box8, { width: "100%", paddingX: 2, paddingY: 1, children: [
5108
+ /* @__PURE__ */ jsx8(Text8, { color: isActive ? "yellow" : "gray", bold: true, children: "\u276F\u276F " }),
5109
+ /* @__PURE__ */ jsx8(
5030
5110
  TextInput,
5031
5111
  {
5032
5112
  value,
@@ -5042,10 +5122,10 @@ function Input({
5042
5122
 
5043
5123
  // src/ui/Spinner.tsx
5044
5124
  import { useState as useState3, useEffect as useEffect2 } from "react";
5045
- import { Text as Text7 } from "ink";
5046
- import { jsxs as jsxs7 } from "react/jsx-runtime";
5125
+ import { Text as Text9 } from "ink";
5126
+ import { jsxs as jsxs9 } from "react/jsx-runtime";
5047
5127
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5048
- var INTERVAL_MS = 80;
5128
+ var INTERVAL_MS = 120;
5049
5129
  function Spinner({ label }) {
5050
5130
  const [frame, setFrame] = useState3(0);
5051
5131
  useEffect2(() => {
@@ -5054,7 +5134,7 @@ function Spinner({ label }) {
5054
5134
  }, INTERVAL_MS);
5055
5135
  return () => clearInterval(timer);
5056
5136
  }, []);
5057
- return /* @__PURE__ */ jsxs7(Text7, { color: "#ff00ff", children: [
5137
+ return /* @__PURE__ */ jsxs9(Text9, { color: "#ff00ff", children: [
5058
5138
  FRAMES[frame],
5059
5139
  " ",
5060
5140
  label ?? "Thinking..."
@@ -5062,7 +5142,7 @@ function Spinner({ label }) {
5062
5142
  }
5063
5143
 
5064
5144
  // src/ui/App.tsx
5065
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
5145
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
5066
5146
  function App({
5067
5147
  agentName,
5068
5148
  skillContext,
@@ -5099,6 +5179,27 @@ function App({
5099
5179
  isLoadingRef.current = isLoading;
5100
5180
  }, [isLoading]);
5101
5181
  const [toolName, setToolName] = useState4(void 0);
5182
+ const streamBufferRef = useRef2("");
5183
+ const streamFlushTimerRef = useRef2(null);
5184
+ const flushStreamBuffer = useCallback2(() => {
5185
+ streamFlushTimerRef.current = null;
5186
+ setStreamingText((prev) => (prev ?? "") + streamBufferRef.current);
5187
+ streamBufferRef.current = "";
5188
+ }, []);
5189
+ const appendStreamChunk = useCallback2((chunk) => {
5190
+ streamBufferRef.current += chunk;
5191
+ if (!streamFlushTimerRef.current) {
5192
+ streamFlushTimerRef.current = setTimeout(flushStreamBuffer, 50);
5193
+ }
5194
+ }, [flushStreamBuffer]);
5195
+ const clearStream = useCallback2(() => {
5196
+ if (streamFlushTimerRef.current) {
5197
+ clearTimeout(streamFlushTimerRef.current);
5198
+ streamFlushTimerRef.current = null;
5199
+ }
5200
+ streamBufferRef.current = "";
5201
+ clearStream();
5202
+ }, []);
5102
5203
  const [autopilotMode, setAutopilotMode] = useState4(initialAutopilotConfig?.mode ?? "off");
5103
5204
  const [autopilotIntervalMs, setAutopilotIntervalMs] = useState4(initialAutopilotConfig?.intervalMs ?? 3e5);
5104
5205
  const epochCallCountRef = useRef2(0);
@@ -5168,7 +5269,7 @@ function App({
5168
5269
  { ...profile, autopilotMode },
5169
5270
  {
5170
5271
  onTextChunk: displayMode === "chat" ? (chunk) => {
5171
- setStreamingText((prev) => (prev ?? "") + chunk);
5272
+ appendStreamChunk(chunk);
5172
5273
  } : () => {
5173
5274
  },
5174
5275
  onToolCallStart: displayMode === "chat" ? (name) => {
@@ -5188,7 +5289,7 @@ function App({
5188
5289
  setCoreMessages(updatedCore);
5189
5290
  const responseText = result.text.trim();
5190
5291
  if (displayMode === "chat") {
5191
- setStreamingText(void 0);
5292
+ clearStream();
5192
5293
  setChatMessages((prev) => [
5193
5294
  ...prev,
5194
5295
  { role: "assistant", content: responseText || "Market checked \u2014 holding." }
@@ -5209,7 +5310,7 @@ function App({
5209
5310
  } catch (error) {
5210
5311
  const message = error instanceof Error ? error.message : "Unknown error";
5211
5312
  if (displayMode === "chat") {
5212
- setStreamingText(void 0);
5313
+ clearStream();
5213
5314
  setChatMessages((prev) => [...prev, { role: "assistant", content: `Autopilot error: ${message}` }]);
5214
5315
  } else {
5215
5316
  addLogEntry("error", message);
@@ -5219,7 +5320,7 @@ function App({
5219
5320
  if (displayMode === "chat") setToolName(void 0);
5220
5321
  }
5221
5322
  },
5222
- [skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry]
5323
+ [skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry, appendStreamChunk, clearStream, pluginMap]
5223
5324
  );
5224
5325
  useEffect3(() => {
5225
5326
  if (!hasAutopilot || autopilotMode === "off") return;
@@ -5517,7 +5618,7 @@ Let's go through it and improve or replace it.` : "I want to create a trading st
5517
5618
  { ...profile, autopilotMode },
5518
5619
  {
5519
5620
  onTextChunk: (chunk) => {
5520
- setStreamingText((prev) => (prev ?? "") + chunk);
5621
+ appendStreamChunk(chunk);
5521
5622
  },
5522
5623
  onToolCallStart: (name) => {
5523
5624
  setToolName(name);
@@ -5550,7 +5651,7 @@ Let's go through it and improve or replace it.` : "I want to create a trading st
5550
5651
  }
5551
5652
  setChatMessages(updatedChat);
5552
5653
  setCoreMessages(updatedCore);
5553
- setStreamingText(void 0);
5654
+ clearStream();
5554
5655
  saveSession({
5555
5656
  agentName,
5556
5657
  provider: providerRef.current,
@@ -5576,13 +5677,13 @@ ${stack}
5576
5677
  { role: "assistant", content: `Error: ${message}${debugInfo}` }
5577
5678
  ]);
5578
5679
  setCoreMessages(newCoreMessages);
5579
- setStreamingText(void 0);
5680
+ clearStream();
5580
5681
  } finally {
5581
5682
  setIsLoading(false);
5582
5683
  setToolName(void 0);
5583
5684
  }
5584
5685
  },
5585
- [coreMessages, chatMessages, skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry]
5686
+ [coreMessages, chatMessages, skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry, pluginMap, hasAutopilot, hasJourneyStages, exit, debug, appendStreamChunk, clearStream, runAutopilotTurn]
5586
5687
  );
5587
5688
  useEffect3(() => {
5588
5689
  sendMessageRef.current = sendMessage;
@@ -5593,12 +5694,12 @@ ${stack}
5593
5694
  },
5594
5695
  [sendMessage]
5595
5696
  );
5596
- return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", width: "100%", height: "100%", children: [
5597
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx7(ChatView, { messages: chatMessages, streamingText }) }),
5598
- isLoading && toolName && /* @__PURE__ */ jsx7(Spinner, { label: `Calling ${toolName}...` }),
5599
- isLoading && !toolName && /* @__PURE__ */ jsx7(Spinner, { label: streamingText ? "Thinking..." : void 0 }),
5600
- /* @__PURE__ */ jsx7(Box7, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx7(Input, { isActive: !isLoading, onSubmit: handleSubmit }) }),
5601
- /* @__PURE__ */ jsx7(Box7, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx7(
5697
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", width: "100%", height: "100%", children: [
5698
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx9(ChatView, { messages: chatMessages, streamingText }) }),
5699
+ isLoading && toolName && /* @__PURE__ */ jsx9(Spinner, { label: `Calling ${toolName}...` }),
5700
+ isLoading && !toolName && /* @__PURE__ */ jsx9(Spinner, { label: streamingText ? "Thinking..." : void 0 }),
5701
+ /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx9(Input, { isActive: !isLoading, onSubmit: handleSubmit }) }),
5702
+ /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx9(
5602
5703
  StatusBar_default,
5603
5704
  {
5604
5705
  agentName,
@@ -5611,12 +5712,12 @@ ${stack}
5611
5712
  pluginMap
5612
5713
  }
5613
5714
  ) }),
5614
- /* @__PURE__ */ jsx7(Box7, { flexShrink: 0, width: "100%", paddingX: 2, marginTop: 1, justifyContent: "space-between", children: hasJourneyStages ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
5615
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /strategy \xB7 /exit" }),
5616
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "/auto on\xB7off\xB7set \xB7 Ctrl+C quit" })
5617
- ] }) : /* @__PURE__ */ jsxs8(Fragment3, { children: [
5618
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: (pluginMap?.commands?.map((c) => c.command).join(" \xB7 ") ?? "/help") + " \xB7 /exit" }),
5619
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: manifest.name })
5715
+ /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", paddingX: 2, marginTop: 1, justifyContent: "space-between", children: hasJourneyStages ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
5716
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /strategy \xB7 /exit" }),
5717
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "/auto on\xB7off\xB7set \xB7 Ctrl+C quit" })
5718
+ ] }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
5719
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: (pluginMap?.commands?.map((c) => c.command).join(" \xB7 ") ?? "/help") + " \xB7 /exit" }),
5720
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: manifest.name })
5620
5721
  ] }) })
5621
5722
  ] });
5622
5723
  }
@@ -5795,8 +5896,9 @@ async function main() {
5795
5896
  let sessionId = newSessionId();
5796
5897
  let initialCoreMessages;
5797
5898
  let initialChatMessages;
5899
+ let session = null;
5798
5900
  if (shouldContinue) {
5799
- const session = loadLatestSession(agentName);
5901
+ session = loadLatestSession(agentName);
5800
5902
  if (session) {
5801
5903
  const updatedAt = new Date(session.updatedAt);
5802
5904
  const minutesAgo = Math.round((Date.now() - updatedAt.getTime()) / 6e4);
@@ -5810,31 +5912,32 @@ async function main() {
5810
5912
  }
5811
5913
  }
5812
5914
  const initialAutopilotConfig = loadAutopilotConfig();
5813
- const lastSessionAt = shouldContinue ? (() => {
5814
- const s = loadLatestSession(agentName);
5815
- return s ? new Date(s.updatedAt) : null;
5816
- })() : null;
5915
+ const lastSessionAt = shouldContinue && session ? new Date(session.updatedAt) : null;
5817
5916
  const pendingTrades = loadAutopilotLogSince(agentName, lastSessionAt);
5818
5917
  const initialPendingTrades = pendingTrades.length;
5819
5918
  const { waitUntilExit } = render(
5820
- React5.createElement(App, {
5821
- agentName,
5822
- skillContext,
5823
- tradingContext,
5824
- walletContext,
5825
- rewardsContext,
5826
- onboardingContext,
5827
- apiContext,
5828
- profile,
5829
- sessionId,
5830
- memoryContent,
5831
- initialCoreMessages,
5832
- initialChatMessages,
5833
- initialAutopilotConfig,
5834
- initialPendingTrades,
5835
- debug,
5836
- pluginMap
5837
- })
5919
+ React7.createElement(
5920
+ AppErrorBoundary,
5921
+ null,
5922
+ React7.createElement(App, {
5923
+ agentName,
5924
+ skillContext,
5925
+ tradingContext,
5926
+ walletContext,
5927
+ rewardsContext,
5928
+ onboardingContext,
5929
+ apiContext,
5930
+ profile,
5931
+ sessionId,
5932
+ memoryContent,
5933
+ initialCoreMessages,
5934
+ initialChatMessages,
5935
+ initialAutopilotConfig,
5936
+ initialPendingTrades,
5937
+ debug,
5938
+ pluginMap
5939
+ })
5940
+ )
5838
5941
  );
5839
5942
  await waitUntilExit();
5840
5943
  if (isPluginsPickerRequested()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astra-cli/cli",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "The terminal for autonomous agents. Powered by AstraNova.",
5
5
  "type": "module",
6
6
  "bin": {