@robota-sdk/agent-cli 3.0.0-beta.27 → 3.0.0-beta.28

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/node/bin.cjs CHANGED
@@ -166,7 +166,7 @@ var PrintTerminal = class {
166
166
  var import_ink11 = require("ink");
167
167
 
168
168
  // src/ui/App.tsx
169
- var import_react12 = require("react");
169
+ var import_react13 = require("react");
170
170
  var import_ink10 = require("ink");
171
171
  var import_agent_core3 = require("@robota-sdk/agent-core");
172
172
 
@@ -175,6 +175,7 @@ var import_react = require("react");
175
175
  var import_agent_sdk = require("@robota-sdk/agent-sdk");
176
176
  var TOOL_ARG_DISPLAY_MAX = 80;
177
177
  var TAIL_KEEP = 30;
178
+ var MAX_COMPLETED_TOOLS = 50;
178
179
  var NOOP_TERMINAL = {
179
180
  write: () => {
180
181
  },
@@ -240,12 +241,25 @@ function useSession(props) {
240
241
  { toolName: event.toolName, firstArg, isRunning: true }
241
242
  ]);
242
243
  } else {
243
- const result = event.denied ? "denied" : event.success === false ? "error" : "success";
244
- setActiveTools(
245
- (prev) => prev.map(
246
- (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result } : t
247
- )
248
- );
244
+ const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
245
+ setActiveTools((prev) => {
246
+ const updated = prev.map(
247
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result: toolResult } : t
248
+ );
249
+ const completed = updated.filter((t) => !t.isRunning);
250
+ if (completed.length > MAX_COMPLETED_TOOLS) {
251
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
252
+ let removed = 0;
253
+ return updated.filter((t) => {
254
+ if (!t.isRunning && removed < excess) {
255
+ removed++;
256
+ return false;
257
+ }
258
+ return true;
259
+ });
260
+ }
261
+ return updated;
262
+ });
249
263
  }
250
264
  };
251
265
  const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
@@ -278,6 +292,7 @@ function useSession(props) {
278
292
 
279
293
  // src/ui/hooks/useMessages.ts
280
294
  var import_react2 = require("react");
295
+ var MAX_RENDERED_MESSAGES = 100;
281
296
  var msgIdCounter = 0;
282
297
  function nextId() {
283
298
  msgIdCounter += 1;
@@ -286,7 +301,13 @@ function nextId() {
286
301
  function useMessages() {
287
302
  const [messages, setMessages] = (0, import_react2.useState)([]);
288
303
  const addMessage = (0, import_react2.useCallback)((msg) => {
289
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
304
+ setMessages((prev) => {
305
+ const updated = [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }];
306
+ if (updated.length > MAX_RENDERED_MESSAGES) {
307
+ return updated.slice(-MAX_RENDERED_MESSAGES);
308
+ }
309
+ return updated;
310
+ });
290
311
  }, []);
291
312
  return { messages, setMessages, addMessage };
292
313
  }
@@ -1205,6 +1226,7 @@ function usePluginCallbacks(cwd) {
1205
1226
  }
1206
1227
 
1207
1228
  // src/ui/MessageList.tsx
1229
+ var import_react7 = __toESM(require("react"), 1);
1208
1230
  var import_ink = require("ink");
1209
1231
 
1210
1232
  // src/ui/render-markdown.ts
@@ -1267,7 +1289,9 @@ function ToolMessage({ message }) {
1267
1289
  ] }, i))
1268
1290
  ] });
1269
1291
  }
1270
- function MessageItem({ message }) {
1292
+ var MessageItem = import_react7.default.memo(function MessageItem2({
1293
+ message
1294
+ }) {
1271
1295
  if (message.role === "tool") {
1272
1296
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolMessage, { message });
1273
1297
  }
@@ -1284,7 +1308,7 @@ function MessageItem({ message }) {
1284
1308
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: " " }),
1285
1309
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
1286
1310
  ] });
1287
- }
1311
+ });
1288
1312
  function MessageList({ messages }) {
1289
1313
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageItem, { message: msg }, msg.id)) });
1290
1314
  }
@@ -1350,11 +1374,11 @@ function StatusBar({
1350
1374
  }
1351
1375
 
1352
1376
  // src/ui/InputArea.tsx
1353
- var import_react9 = __toESM(require("react"), 1);
1377
+ var import_react10 = __toESM(require("react"), 1);
1354
1378
  var import_ink6 = require("ink");
1355
1379
 
1356
1380
  // src/ui/CjkTextInput.tsx
1357
- var import_react7 = require("react");
1381
+ var import_react8 = require("react");
1358
1382
  var import_ink3 = require("ink");
1359
1383
  var import_chalk = __toESM(require("chalk"), 1);
1360
1384
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -1374,9 +1398,9 @@ function CjkTextInput({
1374
1398
  focus = true,
1375
1399
  showCursor = true
1376
1400
  }) {
1377
- const valueRef = (0, import_react7.useRef)(value);
1378
- const cursorRef = (0, import_react7.useRef)(value.length);
1379
- const [, forceRender] = (0, import_react7.useState)(0);
1401
+ const valueRef = (0, import_react8.useRef)(value);
1402
+ const cursorRef = (0, import_react8.useRef)(value.length);
1403
+ const [, forceRender] = (0, import_react8.useState)(0);
1380
1404
  if (value !== valueRef.current) {
1381
1405
  valueRef.current = value;
1382
1406
  if (cursorRef.current > value.length) {
@@ -1453,15 +1477,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
1453
1477
  }
1454
1478
 
1455
1479
  // src/ui/WaveText.tsx
1456
- var import_react8 = require("react");
1480
+ var import_react9 = require("react");
1457
1481
  var import_ink4 = require("ink");
1458
1482
  var import_jsx_runtime4 = require("react/jsx-runtime");
1459
1483
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
1460
1484
  var INTERVAL_MS = 400;
1461
1485
  var CHARS_PER_GROUP = 4;
1462
1486
  function WaveText({ text }) {
1463
- const [tick, setTick] = (0, import_react8.useState)(0);
1464
- (0, import_react8.useEffect)(() => {
1487
+ const [tick, setTick] = (0, import_react9.useState)(0);
1488
+ (0, import_react9.useEffect)(() => {
1465
1489
  const timer = setInterval(() => {
1466
1490
  setTick((prev) => prev + 1);
1467
1491
  }, INTERVAL_MS);
@@ -1527,16 +1551,16 @@ function parseSlashInput(value) {
1527
1551
  return { isSlash: true, parentCommand: parent, filter: rest };
1528
1552
  }
1529
1553
  function useAutocomplete(value, registry) {
1530
- const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
1531
- const [dismissed, setDismissed] = (0, import_react9.useState)(false);
1532
- const prevValueRef = import_react9.default.useRef(value);
1554
+ const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
1555
+ const [dismissed, setDismissed] = (0, import_react10.useState)(false);
1556
+ const prevValueRef = import_react10.default.useRef(value);
1533
1557
  if (prevValueRef.current !== value) {
1534
1558
  prevValueRef.current = value;
1535
1559
  if (dismissed) setDismissed(false);
1536
1560
  }
1537
1561
  const parsed = parseSlashInput(value);
1538
1562
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
1539
- const filteredCommands = (0, import_react9.useMemo)(() => {
1563
+ const filteredCommands = (0, import_react10.useMemo)(() => {
1540
1564
  if (!registry || !parsed.isSlash || dismissed) return [];
1541
1565
  if (isSubcommandMode) {
1542
1566
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -1570,7 +1594,7 @@ function useAutocomplete(value, registry) {
1570
1594
  };
1571
1595
  }
1572
1596
  function InputArea({ onSubmit, isDisabled, registry }) {
1573
- const [value, setValue] = (0, import_react9.useState)("");
1597
+ const [value, setValue] = (0, import_react10.useState)("");
1574
1598
  const {
1575
1599
  showPopup,
1576
1600
  filteredCommands,
@@ -1579,7 +1603,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1579
1603
  isSubcommandMode,
1580
1604
  setShowPopup
1581
1605
  } = useAutocomplete(value, registry);
1582
- const handleSubmit = (0, import_react9.useCallback)(
1606
+ const handleSubmit = (0, import_react10.useCallback)(
1583
1607
  (text) => {
1584
1608
  const trimmed = text.trim();
1585
1609
  if (trimmed.length === 0) return;
@@ -1592,7 +1616,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1592
1616
  },
1593
1617
  [showPopup, filteredCommands, selectedIndex, onSubmit]
1594
1618
  );
1595
- const selectCommand = (0, import_react9.useCallback)(
1619
+ const selectCommand = (0, import_react10.useCallback)(
1596
1620
  (cmd) => {
1597
1621
  const parsed = parseSlashInput(value);
1598
1622
  if (parsed.parentCommand) {
@@ -1653,7 +1677,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1653
1677
  }
1654
1678
 
1655
1679
  // src/ui/ConfirmPrompt.tsx
1656
- var import_react10 = require("react");
1680
+ var import_react11 = require("react");
1657
1681
  var import_ink7 = require("ink");
1658
1682
  var import_jsx_runtime7 = require("react/jsx-runtime");
1659
1683
  function ConfirmPrompt({
@@ -1661,9 +1685,9 @@ function ConfirmPrompt({
1661
1685
  options = ["Yes", "No"],
1662
1686
  onSelect
1663
1687
  }) {
1664
- const [selected, setSelected] = (0, import_react10.useState)(0);
1665
- const resolvedRef = (0, import_react10.useRef)(false);
1666
- const doSelect = (0, import_react10.useCallback)(
1688
+ const [selected, setSelected] = (0, import_react11.useState)(0);
1689
+ const resolvedRef = (0, import_react11.useRef)(false);
1690
+ const doSelect = (0, import_react11.useCallback)(
1667
1691
  (index) => {
1668
1692
  if (resolvedRef.current) return;
1669
1693
  resolvedRef.current = true;
@@ -1696,7 +1720,7 @@ function ConfirmPrompt({
1696
1720
  }
1697
1721
 
1698
1722
  // src/ui/PermissionPrompt.tsx
1699
- var import_react11 = __toESM(require("react"), 1);
1723
+ var import_react12 = __toESM(require("react"), 1);
1700
1724
  var import_ink8 = require("ink");
1701
1725
  var import_jsx_runtime8 = require("react/jsx-runtime");
1702
1726
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
@@ -1706,15 +1730,15 @@ function formatArgs(args) {
1706
1730
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
1707
1731
  }
1708
1732
  function PermissionPrompt({ request }) {
1709
- const [selected, setSelected] = import_react11.default.useState(0);
1710
- const resolvedRef = import_react11.default.useRef(false);
1711
- const prevRequestRef = import_react11.default.useRef(request);
1733
+ const [selected, setSelected] = import_react12.default.useState(0);
1734
+ const resolvedRef = import_react12.default.useRef(false);
1735
+ const prevRequestRef = import_react12.default.useRef(request);
1712
1736
  if (prevRequestRef.current !== request) {
1713
1737
  prevRequestRef.current = request;
1714
1738
  resolvedRef.current = false;
1715
1739
  setSelected(0);
1716
1740
  }
1717
- const doResolve = import_react11.default.useCallback(
1741
+ const doResolve = import_react12.default.useCallback(
1718
1742
  (index) => {
1719
1743
  if (resolvedRef.current) return;
1720
1744
  resolvedRef.current = true;
@@ -1832,15 +1856,15 @@ function App(props) {
1832
1856
  { ...props, config: configWithPluginHooks }
1833
1857
  );
1834
1858
  const { messages, setMessages, addMessage } = useMessages();
1835
- const [isThinking, setIsThinking] = (0, import_react12.useState)(false);
1859
+ const [isThinking, setIsThinking] = (0, import_react13.useState)(false);
1836
1860
  const initialCtx = session.getContextState();
1837
- const [contextState, setContextState] = (0, import_react12.useState)({
1861
+ const [contextState, setContextState] = (0, import_react13.useState)({
1838
1862
  percentage: initialCtx.usedPercentage,
1839
1863
  usedTokens: initialCtx.usedTokens,
1840
1864
  maxTokens: initialCtx.maxTokens
1841
1865
  });
1842
- const pendingModelChangeRef = (0, import_react12.useRef)(null);
1843
- const [pendingModelId, setPendingModelId] = (0, import_react12.useState)(null);
1866
+ const pendingModelChangeRef = (0, import_react13.useRef)(null);
1867
+ const [pendingModelId, setPendingModelId] = (0, import_react13.useState)(null);
1844
1868
  const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
1845
1869
  const handleSlashCommand = useSlashCommands(
1846
1870
  session,
@@ -1952,10 +1976,42 @@ function renderApp(options) {
1952
1976
  `);
1953
1977
  }
1954
1978
  });
1979
+ let lastFrame = "";
1980
+ const originalWrite = process.stdout.write.bind(process.stdout);
1981
+ const MIN_FRAME_LENGTH = 50;
1982
+ process.stdout.write = (chunk, ...args) => {
1983
+ if (typeof chunk === "string" && chunk.length > MIN_FRAME_LENGTH) {
1984
+ lastFrame = chunk;
1985
+ }
1986
+ return originalWrite(chunk, ...args);
1987
+ };
1988
+ originalWrite("\x1B[?1049h");
1989
+ let altScreenExited = false;
1990
+ const leaveAltScreen = () => {
1991
+ if (altScreenExited) return;
1992
+ altScreenExited = true;
1993
+ process.stdout.write = originalWrite;
1994
+ originalWrite("\x1B[?1049l");
1995
+ if (lastFrame) {
1996
+ originalWrite(lastFrame);
1997
+ }
1998
+ };
1999
+ process.on("exit", leaveAltScreen);
2000
+ process.on("SIGINT", () => {
2001
+ leaveAltScreen();
2002
+ process.exit(0);
2003
+ });
2004
+ process.on("SIGTERM", () => {
2005
+ leaveAltScreen();
2006
+ process.exit(0);
2007
+ });
1955
2008
  const instance = (0, import_ink11.render)(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(App, { ...options }), {
1956
- exitOnCtrlC: true
2009
+ exitOnCtrlC: false
1957
2010
  });
1958
- instance.waitUntilExit().catch((err) => {
2011
+ instance.waitUntilExit().then(() => {
2012
+ leaveAltScreen();
2013
+ }).catch((err) => {
2014
+ leaveAltScreen();
1959
2015
  if (err) {
1960
2016
  process.stderr.write(`
1961
2017
  [EXIT ERROR] ${err}
package/dist/node/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startCli
4
- } from "./chunk-QTZS5QXJ.js";
4
+ } from "./chunk-Y742CWPA.js";
5
5
 
6
6
  // src/bin.ts
7
7
  process.on("uncaughtException", (err) => {
@@ -158,6 +158,7 @@ import { useState, useCallback, useRef } from "react";
158
158
  import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
159
159
  var TOOL_ARG_DISPLAY_MAX = 80;
160
160
  var TAIL_KEEP = 30;
161
+ var MAX_COMPLETED_TOOLS = 50;
161
162
  var NOOP_TERMINAL = {
162
163
  write: () => {
163
164
  },
@@ -223,12 +224,25 @@ function useSession(props) {
223
224
  { toolName: event.toolName, firstArg, isRunning: true }
224
225
  ]);
225
226
  } else {
226
- const result = event.denied ? "denied" : event.success === false ? "error" : "success";
227
- setActiveTools(
228
- (prev) => prev.map(
229
- (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result } : t
230
- )
231
- );
227
+ const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
228
+ setActiveTools((prev) => {
229
+ const updated = prev.map(
230
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result: toolResult } : t
231
+ );
232
+ const completed = updated.filter((t) => !t.isRunning);
233
+ if (completed.length > MAX_COMPLETED_TOOLS) {
234
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
235
+ let removed = 0;
236
+ return updated.filter((t) => {
237
+ if (!t.isRunning && removed < excess) {
238
+ removed++;
239
+ return false;
240
+ }
241
+ return true;
242
+ });
243
+ }
244
+ return updated;
245
+ });
232
246
  }
233
247
  };
234
248
  const paths = projectPaths(props.cwd ?? process.cwd());
@@ -261,6 +275,7 @@ function useSession(props) {
261
275
 
262
276
  // src/ui/hooks/useMessages.ts
263
277
  import { useState as useState2, useCallback as useCallback2 } from "react";
278
+ var MAX_RENDERED_MESSAGES = 100;
264
279
  var msgIdCounter = 0;
265
280
  function nextId() {
266
281
  msgIdCounter += 1;
@@ -269,7 +284,13 @@ function nextId() {
269
284
  function useMessages() {
270
285
  const [messages, setMessages] = useState2([]);
271
286
  const addMessage = useCallback2((msg) => {
272
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
287
+ setMessages((prev) => {
288
+ const updated = [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }];
289
+ if (updated.length > MAX_RENDERED_MESSAGES) {
290
+ return updated.slice(-MAX_RENDERED_MESSAGES);
291
+ }
292
+ return updated;
293
+ });
273
294
  }, []);
274
295
  return { messages, setMessages, addMessage };
275
296
  }
@@ -1193,6 +1214,7 @@ function usePluginCallbacks(cwd) {
1193
1214
  }
1194
1215
 
1195
1216
  // src/ui/MessageList.tsx
1217
+ import React2 from "react";
1196
1218
  import { Box, Text } from "ink";
1197
1219
 
1198
1220
  // src/ui/render-markdown.ts
@@ -1255,7 +1277,9 @@ function ToolMessage({ message }) {
1255
1277
  ] }, i))
1256
1278
  ] });
1257
1279
  }
1258
- function MessageItem({ message }) {
1280
+ var MessageItem = React2.memo(function MessageItem2({
1281
+ message
1282
+ }) {
1259
1283
  if (message.role === "tool") {
1260
1284
  return /* @__PURE__ */ jsx(ToolMessage, { message });
1261
1285
  }
@@ -1272,7 +1296,7 @@ function MessageItem({ message }) {
1272
1296
  /* @__PURE__ */ jsx(Text, { children: " " }),
1273
1297
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
1274
1298
  ] });
1275
- }
1299
+ });
1276
1300
  function MessageList({ messages }) {
1277
1301
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx(MessageItem, { message: msg }, msg.id)) });
1278
1302
  }
@@ -1338,7 +1362,7 @@ function StatusBar({
1338
1362
  }
1339
1363
 
1340
1364
  // src/ui/InputArea.tsx
1341
- import React4, { useState as useState5, useCallback as useCallback5, useMemo as useMemo2 } from "react";
1365
+ import React5, { useState as useState5, useCallback as useCallback5, useMemo as useMemo2 } from "react";
1342
1366
  import { Box as Box4, Text as Text6, useInput as useInput2 } from "ink";
1343
1367
 
1344
1368
  // src/ui/CjkTextInput.tsx
@@ -1517,7 +1541,7 @@ function parseSlashInput(value) {
1517
1541
  function useAutocomplete(value, registry) {
1518
1542
  const [selectedIndex, setSelectedIndex] = useState5(0);
1519
1543
  const [dismissed, setDismissed] = useState5(false);
1520
- const prevValueRef = React4.useRef(value);
1544
+ const prevValueRef = React5.useRef(value);
1521
1545
  if (prevValueRef.current !== value) {
1522
1546
  prevValueRef.current = value;
1523
1547
  if (dismissed) setDismissed(false);
@@ -1684,7 +1708,7 @@ function ConfirmPrompt({
1684
1708
  }
1685
1709
 
1686
1710
  // src/ui/PermissionPrompt.tsx
1687
- import React6 from "react";
1711
+ import React7 from "react";
1688
1712
  import { Box as Box6, Text as Text8, useInput as useInput4 } from "ink";
1689
1713
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1690
1714
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
@@ -1694,15 +1718,15 @@ function formatArgs(args) {
1694
1718
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
1695
1719
  }
1696
1720
  function PermissionPrompt({ request }) {
1697
- const [selected, setSelected] = React6.useState(0);
1698
- const resolvedRef = React6.useRef(false);
1699
- const prevRequestRef = React6.useRef(request);
1721
+ const [selected, setSelected] = React7.useState(0);
1722
+ const resolvedRef = React7.useRef(false);
1723
+ const prevRequestRef = React7.useRef(request);
1700
1724
  if (prevRequestRef.current !== request) {
1701
1725
  prevRequestRef.current = request;
1702
1726
  resolvedRef.current = false;
1703
1727
  setSelected(0);
1704
1728
  }
1705
- const doResolve = React6.useCallback(
1729
+ const doResolve = React7.useCallback(
1706
1730
  (index) => {
1707
1731
  if (resolvedRef.current) return;
1708
1732
  resolvedRef.current = true;
@@ -1940,10 +1964,42 @@ function renderApp(options) {
1940
1964
  `);
1941
1965
  }
1942
1966
  });
1967
+ let lastFrame = "";
1968
+ const originalWrite = process.stdout.write.bind(process.stdout);
1969
+ const MIN_FRAME_LENGTH = 50;
1970
+ process.stdout.write = (chunk, ...args) => {
1971
+ if (typeof chunk === "string" && chunk.length > MIN_FRAME_LENGTH) {
1972
+ lastFrame = chunk;
1973
+ }
1974
+ return originalWrite(chunk, ...args);
1975
+ };
1976
+ originalWrite("\x1B[?1049h");
1977
+ let altScreenExited = false;
1978
+ const leaveAltScreen = () => {
1979
+ if (altScreenExited) return;
1980
+ altScreenExited = true;
1981
+ process.stdout.write = originalWrite;
1982
+ originalWrite("\x1B[?1049l");
1983
+ if (lastFrame) {
1984
+ originalWrite(lastFrame);
1985
+ }
1986
+ };
1987
+ process.on("exit", leaveAltScreen);
1988
+ process.on("SIGINT", () => {
1989
+ leaveAltScreen();
1990
+ process.exit(0);
1991
+ });
1992
+ process.on("SIGTERM", () => {
1993
+ leaveAltScreen();
1994
+ process.exit(0);
1995
+ });
1943
1996
  const instance = render(/* @__PURE__ */ jsx11(App, { ...options }), {
1944
- exitOnCtrlC: true
1997
+ exitOnCtrlC: false
1945
1998
  });
1946
- instance.waitUntilExit().catch((err) => {
1999
+ instance.waitUntilExit().then(() => {
2000
+ leaveAltScreen();
2001
+ }).catch((err) => {
2002
+ leaveAltScreen();
1947
2003
  if (err) {
1948
2004
  process.stderr.write(`
1949
2005
  [EXIT ERROR] ${err}
@@ -182,7 +182,7 @@ var PrintTerminal = class {
182
182
  var import_ink11 = require("ink");
183
183
 
184
184
  // src/ui/App.tsx
185
- var import_react12 = require("react");
185
+ var import_react13 = require("react");
186
186
  var import_ink10 = require("ink");
187
187
  var import_agent_core3 = require("@robota-sdk/agent-core");
188
188
 
@@ -191,6 +191,7 @@ var import_react = require("react");
191
191
  var import_agent_sdk = require("@robota-sdk/agent-sdk");
192
192
  var TOOL_ARG_DISPLAY_MAX = 80;
193
193
  var TAIL_KEEP = 30;
194
+ var MAX_COMPLETED_TOOLS = 50;
194
195
  var NOOP_TERMINAL = {
195
196
  write: () => {
196
197
  },
@@ -256,12 +257,25 @@ function useSession(props) {
256
257
  { toolName: event.toolName, firstArg, isRunning: true }
257
258
  ]);
258
259
  } else {
259
- const result = event.denied ? "denied" : event.success === false ? "error" : "success";
260
- setActiveTools(
261
- (prev) => prev.map(
262
- (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result } : t
263
- )
264
- );
260
+ const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
261
+ setActiveTools((prev) => {
262
+ const updated = prev.map(
263
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false, result: toolResult } : t
264
+ );
265
+ const completed = updated.filter((t) => !t.isRunning);
266
+ if (completed.length > MAX_COMPLETED_TOOLS) {
267
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
268
+ let removed = 0;
269
+ return updated.filter((t) => {
270
+ if (!t.isRunning && removed < excess) {
271
+ removed++;
272
+ return false;
273
+ }
274
+ return true;
275
+ });
276
+ }
277
+ return updated;
278
+ });
265
279
  }
266
280
  };
267
281
  const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
@@ -294,6 +308,7 @@ function useSession(props) {
294
308
 
295
309
  // src/ui/hooks/useMessages.ts
296
310
  var import_react2 = require("react");
311
+ var MAX_RENDERED_MESSAGES = 100;
297
312
  var msgIdCounter = 0;
298
313
  function nextId() {
299
314
  msgIdCounter += 1;
@@ -302,7 +317,13 @@ function nextId() {
302
317
  function useMessages() {
303
318
  const [messages, setMessages] = (0, import_react2.useState)([]);
304
319
  const addMessage = (0, import_react2.useCallback)((msg) => {
305
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
320
+ setMessages((prev) => {
321
+ const updated = [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }];
322
+ if (updated.length > MAX_RENDERED_MESSAGES) {
323
+ return updated.slice(-MAX_RENDERED_MESSAGES);
324
+ }
325
+ return updated;
326
+ });
306
327
  }, []);
307
328
  return { messages, setMessages, addMessage };
308
329
  }
@@ -1221,6 +1242,7 @@ function usePluginCallbacks(cwd) {
1221
1242
  }
1222
1243
 
1223
1244
  // src/ui/MessageList.tsx
1245
+ var import_react7 = __toESM(require("react"), 1);
1224
1246
  var import_ink = require("ink");
1225
1247
 
1226
1248
  // src/ui/render-markdown.ts
@@ -1283,7 +1305,9 @@ function ToolMessage({ message }) {
1283
1305
  ] }, i))
1284
1306
  ] });
1285
1307
  }
1286
- function MessageItem({ message }) {
1308
+ var MessageItem = import_react7.default.memo(function MessageItem2({
1309
+ message
1310
+ }) {
1287
1311
  if (message.role === "tool") {
1288
1312
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolMessage, { message });
1289
1313
  }
@@ -1300,7 +1324,7 @@ function MessageItem({ message }) {
1300
1324
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: " " }),
1301
1325
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
1302
1326
  ] });
1303
- }
1327
+ });
1304
1328
  function MessageList({ messages }) {
1305
1329
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageItem, { message: msg }, msg.id)) });
1306
1330
  }
@@ -1366,11 +1390,11 @@ function StatusBar({
1366
1390
  }
1367
1391
 
1368
1392
  // src/ui/InputArea.tsx
1369
- var import_react9 = __toESM(require("react"), 1);
1393
+ var import_react10 = __toESM(require("react"), 1);
1370
1394
  var import_ink6 = require("ink");
1371
1395
 
1372
1396
  // src/ui/CjkTextInput.tsx
1373
- var import_react7 = require("react");
1397
+ var import_react8 = require("react");
1374
1398
  var import_ink3 = require("ink");
1375
1399
  var import_chalk = __toESM(require("chalk"), 1);
1376
1400
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -1390,9 +1414,9 @@ function CjkTextInput({
1390
1414
  focus = true,
1391
1415
  showCursor = true
1392
1416
  }) {
1393
- const valueRef = (0, import_react7.useRef)(value);
1394
- const cursorRef = (0, import_react7.useRef)(value.length);
1395
- const [, forceRender] = (0, import_react7.useState)(0);
1417
+ const valueRef = (0, import_react8.useRef)(value);
1418
+ const cursorRef = (0, import_react8.useRef)(value.length);
1419
+ const [, forceRender] = (0, import_react8.useState)(0);
1396
1420
  if (value !== valueRef.current) {
1397
1421
  valueRef.current = value;
1398
1422
  if (cursorRef.current > value.length) {
@@ -1469,15 +1493,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
1469
1493
  }
1470
1494
 
1471
1495
  // src/ui/WaveText.tsx
1472
- var import_react8 = require("react");
1496
+ var import_react9 = require("react");
1473
1497
  var import_ink4 = require("ink");
1474
1498
  var import_jsx_runtime4 = require("react/jsx-runtime");
1475
1499
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
1476
1500
  var INTERVAL_MS = 400;
1477
1501
  var CHARS_PER_GROUP = 4;
1478
1502
  function WaveText({ text }) {
1479
- const [tick, setTick] = (0, import_react8.useState)(0);
1480
- (0, import_react8.useEffect)(() => {
1503
+ const [tick, setTick] = (0, import_react9.useState)(0);
1504
+ (0, import_react9.useEffect)(() => {
1481
1505
  const timer = setInterval(() => {
1482
1506
  setTick((prev) => prev + 1);
1483
1507
  }, INTERVAL_MS);
@@ -1543,16 +1567,16 @@ function parseSlashInput(value) {
1543
1567
  return { isSlash: true, parentCommand: parent, filter: rest };
1544
1568
  }
1545
1569
  function useAutocomplete(value, registry) {
1546
- const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
1547
- const [dismissed, setDismissed] = (0, import_react9.useState)(false);
1548
- const prevValueRef = import_react9.default.useRef(value);
1570
+ const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
1571
+ const [dismissed, setDismissed] = (0, import_react10.useState)(false);
1572
+ const prevValueRef = import_react10.default.useRef(value);
1549
1573
  if (prevValueRef.current !== value) {
1550
1574
  prevValueRef.current = value;
1551
1575
  if (dismissed) setDismissed(false);
1552
1576
  }
1553
1577
  const parsed = parseSlashInput(value);
1554
1578
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
1555
- const filteredCommands = (0, import_react9.useMemo)(() => {
1579
+ const filteredCommands = (0, import_react10.useMemo)(() => {
1556
1580
  if (!registry || !parsed.isSlash || dismissed) return [];
1557
1581
  if (isSubcommandMode) {
1558
1582
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -1586,7 +1610,7 @@ function useAutocomplete(value, registry) {
1586
1610
  };
1587
1611
  }
1588
1612
  function InputArea({ onSubmit, isDisabled, registry }) {
1589
- const [value, setValue] = (0, import_react9.useState)("");
1613
+ const [value, setValue] = (0, import_react10.useState)("");
1590
1614
  const {
1591
1615
  showPopup,
1592
1616
  filteredCommands,
@@ -1595,7 +1619,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1595
1619
  isSubcommandMode,
1596
1620
  setShowPopup
1597
1621
  } = useAutocomplete(value, registry);
1598
- const handleSubmit = (0, import_react9.useCallback)(
1622
+ const handleSubmit = (0, import_react10.useCallback)(
1599
1623
  (text) => {
1600
1624
  const trimmed = text.trim();
1601
1625
  if (trimmed.length === 0) return;
@@ -1608,7 +1632,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1608
1632
  },
1609
1633
  [showPopup, filteredCommands, selectedIndex, onSubmit]
1610
1634
  );
1611
- const selectCommand = (0, import_react9.useCallback)(
1635
+ const selectCommand = (0, import_react10.useCallback)(
1612
1636
  (cmd) => {
1613
1637
  const parsed = parseSlashInput(value);
1614
1638
  if (parsed.parentCommand) {
@@ -1669,7 +1693,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1669
1693
  }
1670
1694
 
1671
1695
  // src/ui/ConfirmPrompt.tsx
1672
- var import_react10 = require("react");
1696
+ var import_react11 = require("react");
1673
1697
  var import_ink7 = require("ink");
1674
1698
  var import_jsx_runtime7 = require("react/jsx-runtime");
1675
1699
  function ConfirmPrompt({
@@ -1677,9 +1701,9 @@ function ConfirmPrompt({
1677
1701
  options = ["Yes", "No"],
1678
1702
  onSelect
1679
1703
  }) {
1680
- const [selected, setSelected] = (0, import_react10.useState)(0);
1681
- const resolvedRef = (0, import_react10.useRef)(false);
1682
- const doSelect = (0, import_react10.useCallback)(
1704
+ const [selected, setSelected] = (0, import_react11.useState)(0);
1705
+ const resolvedRef = (0, import_react11.useRef)(false);
1706
+ const doSelect = (0, import_react11.useCallback)(
1683
1707
  (index) => {
1684
1708
  if (resolvedRef.current) return;
1685
1709
  resolvedRef.current = true;
@@ -1712,7 +1736,7 @@ function ConfirmPrompt({
1712
1736
  }
1713
1737
 
1714
1738
  // src/ui/PermissionPrompt.tsx
1715
- var import_react11 = __toESM(require("react"), 1);
1739
+ var import_react12 = __toESM(require("react"), 1);
1716
1740
  var import_ink8 = require("ink");
1717
1741
  var import_jsx_runtime8 = require("react/jsx-runtime");
1718
1742
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
@@ -1722,15 +1746,15 @@ function formatArgs(args) {
1722
1746
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
1723
1747
  }
1724
1748
  function PermissionPrompt({ request }) {
1725
- const [selected, setSelected] = import_react11.default.useState(0);
1726
- const resolvedRef = import_react11.default.useRef(false);
1727
- const prevRequestRef = import_react11.default.useRef(request);
1749
+ const [selected, setSelected] = import_react12.default.useState(0);
1750
+ const resolvedRef = import_react12.default.useRef(false);
1751
+ const prevRequestRef = import_react12.default.useRef(request);
1728
1752
  if (prevRequestRef.current !== request) {
1729
1753
  prevRequestRef.current = request;
1730
1754
  resolvedRef.current = false;
1731
1755
  setSelected(0);
1732
1756
  }
1733
- const doResolve = import_react11.default.useCallback(
1757
+ const doResolve = import_react12.default.useCallback(
1734
1758
  (index) => {
1735
1759
  if (resolvedRef.current) return;
1736
1760
  resolvedRef.current = true;
@@ -1848,15 +1872,15 @@ function App(props) {
1848
1872
  { ...props, config: configWithPluginHooks }
1849
1873
  );
1850
1874
  const { messages, setMessages, addMessage } = useMessages();
1851
- const [isThinking, setIsThinking] = (0, import_react12.useState)(false);
1875
+ const [isThinking, setIsThinking] = (0, import_react13.useState)(false);
1852
1876
  const initialCtx = session.getContextState();
1853
- const [contextState, setContextState] = (0, import_react12.useState)({
1877
+ const [contextState, setContextState] = (0, import_react13.useState)({
1854
1878
  percentage: initialCtx.usedPercentage,
1855
1879
  usedTokens: initialCtx.usedTokens,
1856
1880
  maxTokens: initialCtx.maxTokens
1857
1881
  });
1858
- const pendingModelChangeRef = (0, import_react12.useRef)(null);
1859
- const [pendingModelId, setPendingModelId] = (0, import_react12.useState)(null);
1882
+ const pendingModelChangeRef = (0, import_react13.useRef)(null);
1883
+ const [pendingModelId, setPendingModelId] = (0, import_react13.useState)(null);
1860
1884
  const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
1861
1885
  const handleSlashCommand = useSlashCommands(
1862
1886
  session,
@@ -1968,10 +1992,42 @@ function renderApp(options) {
1968
1992
  `);
1969
1993
  }
1970
1994
  });
1995
+ let lastFrame = "";
1996
+ const originalWrite = process.stdout.write.bind(process.stdout);
1997
+ const MIN_FRAME_LENGTH = 50;
1998
+ process.stdout.write = (chunk, ...args) => {
1999
+ if (typeof chunk === "string" && chunk.length > MIN_FRAME_LENGTH) {
2000
+ lastFrame = chunk;
2001
+ }
2002
+ return originalWrite(chunk, ...args);
2003
+ };
2004
+ originalWrite("\x1B[?1049h");
2005
+ let altScreenExited = false;
2006
+ const leaveAltScreen = () => {
2007
+ if (altScreenExited) return;
2008
+ altScreenExited = true;
2009
+ process.stdout.write = originalWrite;
2010
+ originalWrite("\x1B[?1049l");
2011
+ if (lastFrame) {
2012
+ originalWrite(lastFrame);
2013
+ }
2014
+ };
2015
+ process.on("exit", leaveAltScreen);
2016
+ process.on("SIGINT", () => {
2017
+ leaveAltScreen();
2018
+ process.exit(0);
2019
+ });
2020
+ process.on("SIGTERM", () => {
2021
+ leaveAltScreen();
2022
+ process.exit(0);
2023
+ });
1971
2024
  const instance = (0, import_ink11.render)(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(App, { ...options }), {
1972
- exitOnCtrlC: true
2025
+ exitOnCtrlC: false
1973
2026
  });
1974
- instance.waitUntilExit().catch((err) => {
2027
+ instance.waitUntilExit().then(() => {
2028
+ leaveAltScreen();
2029
+ }).catch((err) => {
2030
+ leaveAltScreen();
1975
2031
  if (err) {
1976
2032
  process.stderr.write(`
1977
2033
  [EXIT ERROR] ${err}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startCli
3
- } from "./chunk-QTZS5QXJ.js";
3
+ } from "./chunk-Y742CWPA.js";
4
4
 
5
5
  // src/index.ts
6
6
  import { Session, SessionStore, query, TRUST_TO_MODE } from "@robota-sdk/agent-sdk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-cli",
3
- "version": "3.0.0-beta.27",
3
+ "version": "3.0.0-beta.28",
4
4
  "description": "AI coding assistant CLI built on Robota SDK",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,8 +35,8 @@
35
35
  "marked-terminal": "^7.3.0",
36
36
  "react": "19.2.4",
37
37
  "string-width": "^8.2.0",
38
- "@robota-sdk/agent-sdk": "3.0.0-beta.27",
39
- "@robota-sdk/agent-core": "3.0.0-beta.27"
38
+ "@robota-sdk/agent-core": "3.0.0-beta.28",
39
+ "@robota-sdk/agent-sdk": "3.0.0-beta.28"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/marked": "^6.0.0",