@ai-setting/roy-agent-cli 1.7.42 → 1.7.45

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.
@@ -609,6 +609,7 @@ ${COLORS.system("[已中断]")}
609
609
  this.emitChunk({
610
610
  type: "tool.result",
611
611
  toolName: name,
612
+ toolId: payload.id,
612
613
  toolResult: payload.result?.output
613
614
  });
614
615
  }
@@ -1559,69 +1560,94 @@ var init_theme = __esm(() => {
1559
1560
  });
1560
1561
 
1561
1562
  // src/tui/StatusBar.tsx
1563
+ import React2 from "react";
1562
1564
  import { Box as Box2, Text as Text3 } from "ink";
1563
1565
  import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
1564
- var StatusBar = ({
1566
+ function detectColumns() {
1567
+ const c = process.stdout?.columns;
1568
+ if (typeof c === "number" && c > 0)
1569
+ return c;
1570
+ return 80;
1571
+ }
1572
+ function shortenWorkdir(input) {
1573
+ if (!input)
1574
+ return input;
1575
+ if (input.length <= WORKDIR_TAIL_MAX)
1576
+ return input;
1577
+ const tail = input.slice(input.length - WORKDIR_TAIL_MAX);
1578
+ return `…/${tail}`;
1579
+ }
1580
+ function clipModel(input, max = MODEL_NARROW_MAX) {
1581
+ if (!input)
1582
+ return input;
1583
+ if (input.length <= max)
1584
+ return input;
1585
+ return input.slice(0, max - 1) + "…";
1586
+ }
1587
+ function planSegments(props, columns) {
1588
+ const segs = [];
1589
+ const tier = columns >= 80 ? 4 : columns >= 60 ? 3 : columns >= 40 ? 2 : 1;
1590
+ if (props.model) {
1591
+ const text = tier === 1 ? clipModel(props.model) ?? "" : props.model;
1592
+ segs.push({ text, color: "primary", dimPrefix: "\uD83E\uDD16 " });
1593
+ }
1594
+ if (tier >= 4 || tier === 3) {
1595
+ if (props.workdir) {
1596
+ segs.push({
1597
+ text: shortenWorkdir(props.workdir) ?? "",
1598
+ color: "muted",
1599
+ dimPrefix: "\uD83D\uDCC1 "
1600
+ });
1601
+ }
1602
+ }
1603
+ if (tier >= 4 || tier === 3 || tier === 2) {
1604
+ if (typeof props.tokenCount === "number") {
1605
+ segs.push({ text: `n=${props.tokenCount}`, color: "accent", dimPrefix: "" });
1606
+ }
1607
+ }
1608
+ if (tier >= 4) {
1609
+ segs.push({ text: "Ctrl+C to exit", color: "muted" });
1610
+ }
1611
+ return segs;
1612
+ }
1613
+ var WORKDIR_TAIL_MAX = 30, MODEL_NARROW_MAX = 24, StatusBar = ({
1565
1614
  model,
1566
1615
  workdir,
1567
- tokenCount
1616
+ tokenCount,
1617
+ columns
1568
1618
  }) => {
1569
1619
  const theme = getTheme();
1620
+ const cols = typeof columns === "number" ? columns : detectColumns();
1621
+ const segs = planSegments({ model, workdir, tokenCount }, cols);
1622
+ if (segs.length === 0) {
1623
+ return /* @__PURE__ */ jsxDEV3(Box2, {
1624
+ paddingX: 1
1625
+ }, undefined, false, undefined, this);
1626
+ }
1570
1627
  return /* @__PURE__ */ jsxDEV3(Box2, {
1571
- borderStyle: "single",
1572
- borderColor: "gray",
1573
1628
  paddingX: 1,
1574
- justifyContent: "space-between",
1575
- children: [
1576
- /* @__PURE__ */ jsxDEV3(Box2, {
1577
- children: model ? /* @__PURE__ */ jsxDEV3(Text3, {
1578
- children: [
1579
- /* @__PURE__ */ jsxDEV3(Text3, {
1580
- dimColor: true,
1581
- children: "\uD83E\uDD16 "
1582
- }, undefined, false, undefined, this),
1583
- /* @__PURE__ */ jsxDEV3(Text3, {
1584
- color: theme.primary,
1585
- children: model
1586
- }, undefined, false, undefined, this)
1587
- ]
1588
- }, undefined, true, undefined, this) : null
1589
- }, undefined, false, undefined, this),
1590
- /* @__PURE__ */ jsxDEV3(Box2, {
1591
- children: workdir ? /* @__PURE__ */ jsxDEV3(Text3, {
1592
- children: [
1593
- /* @__PURE__ */ jsxDEV3(Text3, {
1594
- dimColor: true,
1595
- children: "\uD83D\uDCC1 "
1596
- }, undefined, false, undefined, this),
1597
- /* @__PURE__ */ jsxDEV3(Text3, {
1598
- children: workdir
1599
- }, undefined, false, undefined, this)
1600
- ]
1601
- }, undefined, true, undefined, this) : null
1602
- }, undefined, false, undefined, this),
1603
- /* @__PURE__ */ jsxDEV3(Box2, {
1629
+ flexDirection: "row",
1630
+ children: segs.map((seg, i) => {
1631
+ const color = seg.color === "primary" ? theme.primary : seg.color === "accent" ? theme.accent : theme.muted;
1632
+ const isFirst = i === 0;
1633
+ return /* @__PURE__ */ jsxDEV3(React2.Fragment, {
1604
1634
  children: [
1605
- typeof tokenCount === "number" ? /* @__PURE__ */ jsxDEV3(Text3, {
1606
- children: [
1607
- /* @__PURE__ */ jsxDEV3(Text3, {
1608
- dimColor: true,
1609
- children: "tokens: "
1610
- }, undefined, false, undefined, this),
1611
- /* @__PURE__ */ jsxDEV3(Text3, {
1612
- color: theme.accent,
1613
- children: tokenCount
1614
- }, undefined, false, undefined, this)
1615
- ]
1616
- }, undefined, true, undefined, this) : null,
1617
- /* @__PURE__ */ jsxDEV3(Text3, {
1635
+ !isFirst ? /* @__PURE__ */ jsxDEV3(Text3, {
1618
1636
  dimColor: true,
1619
- children: " Ctrl+C to exit"
1637
+ children: " · "
1638
+ }, undefined, false, undefined, this) : null,
1639
+ seg.dimPrefix ? /* @__PURE__ */ jsxDEV3(Text3, {
1640
+ dimColor: true,
1641
+ children: seg.dimPrefix
1642
+ }, undefined, false, undefined, this) : null,
1643
+ /* @__PURE__ */ jsxDEV3(Text3, {
1644
+ color,
1645
+ children: seg.text
1620
1646
  }, undefined, false, undefined, this)
1621
1647
  ]
1622
- }, undefined, true, undefined, this)
1623
- ]
1624
- }, undefined, true, undefined, this);
1648
+ }, i, true, undefined, this);
1649
+ })
1650
+ }, undefined, false, undefined, this);
1625
1651
  };
1626
1652
  var init_StatusBar = __esm(() => {
1627
1653
  init_theme();
@@ -1676,7 +1702,7 @@ __export(exports_repl_ink, {
1676
1702
  REPLInk: () => REPLInk,
1677
1703
  MultilineInput: () => MultilineInput
1678
1704
  });
1679
- import * as React2 from "react";
1705
+ import * as React3 from "react";
1680
1706
  import { Box as Box3, Text as Text4, Static, useApp, useInput as useInput2 } from "ink";
1681
1707
  import { createLogger as createLogger5 } from "@ai-setting/roy-agent-core";
1682
1708
  import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
@@ -1701,7 +1727,7 @@ function REPLInk(props) {
1701
1727
  onEscape
1702
1728
  } = props;
1703
1729
  const { exit: appExit } = useApp();
1704
- const [messages, setMessages] = React2.useState(() => [
1730
+ const [messages, setMessages] = React3.useState(() => [
1705
1731
  withMessageId({
1706
1732
  role: "system",
1707
1733
  kind: "welcome",
@@ -1709,47 +1735,47 @@ function REPLInk(props) {
1709
1735
  }),
1710
1736
  ...initialMessages.map(withMessageId)
1711
1737
  ]);
1712
- const [mode, setMode] = React2.useState(initialMode);
1713
- const [statusBarCtx, setStatusBarCtxState] = React2.useState({
1738
+ const [mode, setMode] = React3.useState(initialMode);
1739
+ const [statusBarCtx, setStatusBarCtxState] = React3.useState({
1714
1740
  mode: initialMode,
1715
1741
  cwd: initialStatusBarCtx.cwd ?? initialContext.cwd,
1716
1742
  gitBranch: initialStatusBarCtx.gitBranch,
1717
1743
  model: initialStatusBarCtx.model ?? initialContext.model
1718
1744
  });
1719
- const [inputValue, setInputValue] = React2.useState("");
1720
- const [popupIndex, setPopupIndex] = React2.useState(0);
1721
- const filteredCmds = React2.useMemo(() => {
1745
+ const [inputValue, setInputValue] = React3.useState("");
1746
+ const [popupIndex, setPopupIndex] = React3.useState(0);
1747
+ const filteredCmds = React3.useMemo(() => {
1722
1748
  if (!/^\/\S*$/.test(inputValue))
1723
1749
  return [];
1724
1750
  return filterCommands(inputValue);
1725
1751
  }, [inputValue]);
1726
1752
  const showPopup = filteredCmds.length > 0;
1727
- React2.useEffect(() => {
1753
+ React3.useEffect(() => {
1728
1754
  setPopupIndex(0);
1729
1755
  }, [filteredCmds]);
1730
1756
  const safePopupIndex = clampSelectedIndex(popupIndex, filteredCmds.length);
1731
- const handlePopupSelect = React2.useCallback((cmd) => {
1757
+ const handlePopupSelect = React3.useCallback((cmd) => {
1732
1758
  multilineInputRef.current?.setBuffer(cmd.cmd + " ");
1733
1759
  setInputValue(cmd.cmd + " ");
1734
1760
  }, []);
1735
- const handlePopupClose = React2.useCallback(() => {
1761
+ const handlePopupClose = React3.useCallback(() => {
1736
1762
  const current = multilineInputRef.current?.getBuffer() ?? "";
1737
1763
  const stripped = current.replace(/^\/+/, "");
1738
1764
  multilineInputRef.current?.setBuffer(stripped);
1739
1765
  setInputValue(stripped);
1740
1766
  }, []);
1741
- const handlePopupIndexChange = React2.useCallback((idx) => {
1767
+ const handlePopupIndexChange = React3.useCallback((idx) => {
1742
1768
  setPopupIndex(idx);
1743
1769
  }, []);
1744
- const streamingIndexRef = React2.useRef(-1);
1745
- const lastFlushTsRef = React2.useRef(0);
1770
+ const streamingIndexRef = React3.useRef(-1);
1771
+ const lastFlushTsRef = React3.useRef(0);
1746
1772
  const STALE_STREAM_THRESHOLD_MS = 1000;
1747
- const multilineInputRef = React2.useRef(null);
1748
- const lastEscapeTsRef = React2.useRef(0);
1749
- const pendingDeltaRef = React2.useRef("");
1750
- const throttleTimerRef = React2.useRef(null);
1751
- const isFirstDeltaInStreamRef = React2.useRef(true);
1752
- const flushStreamingDelta = React2.useCallback(() => {
1773
+ const multilineInputRef = React3.useRef(null);
1774
+ const lastEscapeTsRef = React3.useRef(0);
1775
+ const pendingDeltaRef = React3.useRef("");
1776
+ const throttleTimerRef = React3.useRef(null);
1777
+ const isFirstDeltaInStreamRef = React3.useRef(true);
1778
+ const flushStreamingDelta = React3.useCallback(() => {
1753
1779
  const delta = pendingDeltaRef.current;
1754
1780
  pendingDeltaRef.current = "";
1755
1781
  throttleTimerRef.current = null;
@@ -1778,7 +1804,7 @@ function REPLInk(props) {
1778
1804
  });
1779
1805
  lastFlushTsRef.current = Date.now();
1780
1806
  }, []);
1781
- React2.useEffect(() => {
1807
+ React3.useEffect(() => {
1782
1808
  return () => {
1783
1809
  if (throttleTimerRef.current !== null) {
1784
1810
  clearTimeout(throttleTimerRef.current);
@@ -1786,8 +1812,8 @@ function REPLInk(props) {
1786
1812
  }
1787
1813
  };
1788
1814
  }, []);
1789
- const apiRef = React2.useRef(null);
1790
- React2.useEffect(() => {
1815
+ const apiRef = React3.useRef(null);
1816
+ React3.useEffect(() => {
1791
1817
  const api = {
1792
1818
  appendMessage: (msg) => {
1793
1819
  setMessages((msgs) => [...msgs, withMessageId(msg)]);
@@ -1863,7 +1889,7 @@ function REPLInk(props) {
1863
1889
  apiRef.current = api;
1864
1890
  onReady?.(api);
1865
1891
  }, [appExit, onCommand, onReady]);
1866
- const handleEscapeCascade = React2.useCallback(() => {
1892
+ const handleEscapeCascade = React3.useCallback(() => {
1867
1893
  const now = Date.now();
1868
1894
  if (now - lastEscapeTsRef.current < 100) {
1869
1895
  return;
@@ -1907,7 +1933,7 @@ function REPLInk(props) {
1907
1933
  }
1908
1934
  }
1909
1935
  });
1910
- const handleSubmit = React2.useCallback(async (value) => {
1936
+ const handleSubmit = React3.useCallback(async (value) => {
1911
1937
  if (!value || value.trim() === "") {
1912
1938
  return;
1913
1939
  }
@@ -1936,7 +1962,7 @@ function REPLInk(props) {
1936
1962
  ]);
1937
1963
  }
1938
1964
  }, [onCommand, onSubmit]);
1939
- const staticItems = React2.useMemo(() => {
1965
+ const staticItems = React3.useMemo(() => {
1940
1966
  if (messages.length <= 1)
1941
1967
  return [];
1942
1968
  return messages.slice(0, -1);
@@ -1944,8 +1970,8 @@ function REPLInk(props) {
1944
1970
  const tailMsg = messages.length > 0 ? messages[messages.length - 1] : null;
1945
1971
  const terminalWidth = process.stdout.columns ?? 80;
1946
1972
  const terminalHeight = process.stdout.rows ?? 24;
1947
- const dynamicTailBounds = React2.useMemo(() => computeDynamicTailBounds(terminalHeight, 12, showPopup), [terminalHeight, showPopup]);
1948
- const staticTailBounds = React2.useMemo(() => computeDynamicTailBounds(terminalHeight, 12, showPopup), [terminalHeight, showPopup]);
1973
+ const dynamicTailBounds = React3.useMemo(() => computeDynamicTailBounds(terminalHeight, 12, showPopup), [terminalHeight, showPopup]);
1974
+ const staticTailBounds = React3.useMemo(() => computeDynamicTailBounds(terminalHeight, 12, showPopup), [terminalHeight, showPopup]);
1949
1975
  const tailKey = messages.length > 0 ? tailMsg?.id ?? `tail-${messages.length - 1}` : "tail-empty";
1950
1976
  const statusBarModel = shortModel(statusBarCtx.model);
1951
1977
  const statusBarWorkdir = statusBarCtx.cwd;
@@ -2012,7 +2038,7 @@ function shortModel(model) {
2012
2038
  }
2013
2039
  async function renderInkREPL(props) {
2014
2040
  const ink = await import("ink");
2015
- const { waitUntilExit } = ink.render(React2.createElement(REPLInk, props));
2041
+ const { waitUntilExit } = ink.render(React3.createElement(REPLInk, props));
2016
2042
  await waitUntilExit();
2017
2043
  }
2018
2044
  var MarkdownBounded, inkLogger, _msgIdCounter = 0, MultilineInput, MessageView;
@@ -2024,7 +2050,7 @@ var init_repl_ink = __esm(() => {
2024
2050
  init_Markdown();
2025
2051
  init_StatusBar();
2026
2052
  init_dynamic_tail();
2027
- MarkdownBounded = React2.memo(({ text, bounds, width }) => {
2053
+ MarkdownBounded = React3.memo(({ text, bounds, width }) => {
2028
2054
  const out = renderBoundedAssistant(text, bounds, width);
2029
2055
  if (!out) {
2030
2056
  return null;
@@ -2034,16 +2060,16 @@ var init_repl_ink = __esm(() => {
2034
2060
  }, undefined, false, undefined, this);
2035
2061
  });
2036
2062
  inkLogger = createLogger5("repl-ink");
2037
- MultilineInput = React2.memo(React2.forwardRef(function MultilineInput2(props, ref) {
2063
+ MultilineInput = React3.memo(React3.forwardRef(function MultilineInput2(props, ref) {
2038
2064
  const { onSubmit, placeholder, popupActive, onChange, onEscape } = props;
2039
- const [buffer, setBuffer] = React2.useState("");
2040
- const [cursor, setCursor] = React2.useState(0);
2041
- React2.useEffect(() => {
2065
+ const [buffer, setBuffer] = React3.useState("");
2066
+ const [cursor, setCursor] = React3.useState(0);
2067
+ React3.useEffect(() => {
2042
2068
  if (popupActive || buffer.startsWith("/")) {
2043
2069
  onChange?.(buffer);
2044
2070
  }
2045
2071
  }, [buffer, onChange, popupActive]);
2046
- React2.useImperativeHandle(ref, () => ({
2072
+ React3.useImperativeHandle(ref, () => ({
2047
2073
  clearBuffer: () => {
2048
2074
  setBuffer("");
2049
2075
  setCursor(0);
@@ -2281,7 +2307,7 @@ var init_repl_ink = __esm(() => {
2281
2307
  })
2282
2308
  }, undefined, false, undefined, this);
2283
2309
  }));
2284
- MessageView = React2.memo(function MessageView2({
2310
+ MessageView = React3.memo(function MessageView2({
2285
2311
  msg,
2286
2312
  terminalWidth,
2287
2313
  tailBounds
@@ -9461,7 +9487,7 @@ class CliQuietModeService {
9461
9487
  // package.json
9462
9488
  var package_default = {
9463
9489
  name: "@ai-setting/roy-agent-cli",
9464
- version: "1.7.42",
9490
+ version: "1.7.45",
9465
9491
  type: "module",
9466
9492
  description: "CLI for roy-agent - Non-interactive command execution",
9467
9493
  main: "./dist/index.js",
@@ -13591,7 +13617,7 @@ ${chalk7.bold("╰")}${chalk7.dim("─".repeat(49))}${chalk7.bold("╯")}
13591
13617
  }
13592
13618
  function createInteractiveCommand(externalEnvService) {
13593
13619
  return {
13594
- command: "interactive",
13620
+ command: "run",
13595
13621
  aliases: ["i", "repl"],
13596
13622
  describe: "交互式模式 - 多轮对话交互",
13597
13623
  builder: (yargs) => yargs.option("session", {
@@ -13676,7 +13702,7 @@ function createInteractiveCommand(externalEnvService) {
13676
13702
  configPath: args.config,
13677
13703
  plugins: [...componentPluginNames, ...coderPluginNames],
13678
13704
  loadPlugins: true,
13679
- commandName: "interactive"
13705
+ commandName: "run"
13680
13706
  });
13681
13707
  }
13682
13708
  const env = envService.getEnvironment();
@@ -13983,16 +14009,16 @@ function createInteractiveCommand(externalEnvService) {
13983
14009
  var InteractiveCommand = createInteractiveCommand();
13984
14010
 
13985
14011
  // src/commands/run.ts
13986
- import React6 from "react";
14012
+ import React7 from "react";
13987
14013
  import { render } from "ink";
13988
14014
 
13989
14015
  // src/RunTui.tsx
13990
- import { useState as useState6, useCallback as useCallback4, useRef as useRef2, useEffect as useEffect5 } from "react";
13991
- import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
14016
+ import { useState as useState6, useCallback as useCallback4, useRef as useRef3, useEffect as useEffect5 } from "react";
14017
+ import { Box as Box7, Text as Text9, useApp as useApp2, useStdout } from "ink";
13992
14018
 
13993
14019
  // src/tui/App.tsx
13994
14020
  init_Markdown();
13995
- import { Box as Box4, Static as Static2, Text as Text6 } from "ink";
14021
+ import { Box as Box5, Static as Static2, Text as Text7 } from "ink";
13996
14022
 
13997
14023
  // src/tui/Spinner.tsx
13998
14024
  init_theme();
@@ -14029,8 +14055,262 @@ var Spinner = ({
14029
14055
 
14030
14056
  // src/tui/App.tsx
14031
14057
  init_StatusBar();
14058
+
14059
+ // src/tui/ToolCall.tsx
14060
+ import { Box as Box4, Text as Text6 } from "ink";
14032
14061
  init_theme();
14062
+
14063
+ // src/tui/tool-call.ts
14064
+ var TOOL_META = {
14065
+ bash: { icon: "❯", label: "Bash", argKeys: ["command", "cmd"] },
14066
+ shell: { icon: "❯", label: "Bash", argKeys: ["command", "cmd"] },
14067
+ echo: { icon: "\uD83D\uDCAC", label: "Echo", argKeys: ["message", "text"] },
14068
+ read_file: {
14069
+ icon: "\uD83D\uDCD6",
14070
+ label: "Read",
14071
+ argKeys: ["file_path", "path", "filePath", "filename"]
14072
+ },
14073
+ write_file: {
14074
+ icon: "\uD83D\uDCDD",
14075
+ label: "Write",
14076
+ argKeys: ["file_path", "path", "filePath", "filename"]
14077
+ },
14078
+ edit_file: {
14079
+ icon: "✏️",
14080
+ label: "Edit",
14081
+ argKeys: ["file_path", "path", "filePath", "filename"]
14082
+ },
14083
+ glob: { icon: "\uD83D\uDD0D", label: "Glob", argKeys: ["pattern"] },
14084
+ grep: { icon: "\uD83D\uDD0E", label: "Grep", argKeys: ["pattern", "query"] },
14085
+ query_background_process: {
14086
+ icon: "\uD83D\uDCE1",
14087
+ label: "Background Process",
14088
+ argKeys: ["id", "process_id"]
14089
+ },
14090
+ understand_image: {
14091
+ icon: "\uD83D\uDDBC️",
14092
+ label: "Understand Image",
14093
+ argKeys: ["image_path", "path", "url"]
14094
+ },
14095
+ delegate_task: {
14096
+ icon: "\uD83E\uDD16",
14097
+ label: "Task",
14098
+ argKeys: ["description", "prompt"]
14099
+ },
14100
+ stop_task: { icon: "\uD83D\uDED1", label: "Stop Task", argKeys: ["task_id", "id"] },
14101
+ task_search: { icon: "\uD83D\uDD0E", label: "Task Search", argKeys: ["query"] },
14102
+ ask_user: { icon: "❓", label: "Ask User", argKeys: ["question"] },
14103
+ workflow_run: {
14104
+ icon: "\uD83D\uDD00",
14105
+ label: "Run Workflow",
14106
+ argKeys: ["workflow", "name", "path"]
14107
+ },
14108
+ web_search: { icon: "\uD83C\uDF10", label: "Web Search", argKeys: ["query"] },
14109
+ webfetch: { icon: "\uD83C\uDF10", label: "Fetch", argKeys: ["url"] },
14110
+ fetch: { icon: "\uD83C\uDF10", label: "Fetch", argKeys: ["url"] },
14111
+ skill: { icon: "\uD83C\uDFAF", label: "Skill", argKeys: ["name"] }
14112
+ };
14113
+ var TOOL_ALIASES = {
14114
+ read: "read_file",
14115
+ write: "write_file",
14116
+ edit: "edit_file",
14117
+ search: "grep",
14118
+ task: "delegate_task",
14119
+ run_terminal_cmd: "bash",
14120
+ terminal: "bash"
14121
+ };
14122
+ function humanizeToolName(name) {
14123
+ if (!name)
14124
+ return "Tool";
14125
+ const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
14126
+ return spaced.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
14127
+ }
14128
+ function firstLine(value, max = 120) {
14129
+ const line = value.split(/\r?\n/)[0]?.trim() ?? "";
14130
+ return line.length > max ? `${line.slice(0, max - 1)}…` : line;
14131
+ }
14132
+ function summarizeArgs(args) {
14133
+ for (const value of Object.values(args)) {
14134
+ if (typeof value === "string" && value.trim())
14135
+ return firstLine(value);
14136
+ if (typeof value === "number" || typeof value === "boolean") {
14137
+ return String(value);
14138
+ }
14139
+ if (Array.isArray(value) && value.length > 0) {
14140
+ return firstLine(value.map((v) => String(v)).join(" "));
14141
+ }
14142
+ }
14143
+ return "";
14144
+ }
14145
+ function describeToolCall(name, args) {
14146
+ const meta = TOOL_META[name] ?? TOOL_META[TOOL_ALIASES[name] ?? ""];
14147
+ const label = meta?.label ?? humanizeToolName(name);
14148
+ const icon = meta?.icon ?? "⚙";
14149
+ let detail = "";
14150
+ if (args && Object.keys(args).length > 0) {
14151
+ const keys = meta?.argKeys ?? [];
14152
+ for (const key of keys) {
14153
+ const value = args[key];
14154
+ if (typeof value === "string" && value.trim()) {
14155
+ detail = firstLine(value);
14156
+ break;
14157
+ }
14158
+ if (value !== undefined && value !== null && typeof value !== "object") {
14159
+ detail = String(value);
14160
+ break;
14161
+ }
14162
+ }
14163
+ if (!detail)
14164
+ detail = summarizeArgs(args);
14165
+ }
14166
+ return { icon, label, detail };
14167
+ }
14168
+ function stringifyToolOutput(output) {
14169
+ if (output === undefined || output === null)
14170
+ return "";
14171
+ if (typeof output === "string")
14172
+ return output;
14173
+ if (typeof output === "object") {
14174
+ const obj = output;
14175
+ for (const key of ["output", "text", "content", "message"]) {
14176
+ if (typeof obj[key] === "string")
14177
+ return obj[key];
14178
+ }
14179
+ try {
14180
+ return JSON.stringify(output, null, 2);
14181
+ } catch {
14182
+ return String(output);
14183
+ }
14184
+ }
14185
+ return String(output);
14186
+ }
14187
+ function formatToolOutput(output, options = {}) {
14188
+ const { maxLines = 12, maxChars = 200 } = options;
14189
+ const text = stringifyToolOutput(output).replace(/\r\n?/g, `
14190
+ `).replace(/\s+$/, "");
14191
+ if (!text)
14192
+ return "";
14193
+ const lines = text.split(`
14194
+ `);
14195
+ const shown = lines.slice(0, maxLines);
14196
+ const hidden = lines.length - shown.length;
14197
+ const body = shown.map((line) => firstLine(line, maxChars)).join(`
14198
+ `);
14199
+ return hidden > 0 ? `${body}
14200
+ … (+${hidden} more lines)` : body;
14201
+ }
14202
+ function formatDuration(ms) {
14203
+ if (!Number.isFinite(ms) || ms < 0)
14204
+ return "";
14205
+ if (ms < 1000)
14206
+ return `${Math.round(ms)}ms`;
14207
+ const seconds = ms / 1000;
14208
+ if (seconds < 10)
14209
+ return `${seconds.toFixed(1)}s`;
14210
+ if (seconds < 60)
14211
+ return `${Math.round(seconds)}s`;
14212
+ const minutes = Math.floor(seconds / 60);
14213
+ const rest = Math.round(seconds % 60);
14214
+ return `${minutes}m${rest.toString().padStart(2, "0")}s`;
14215
+ }
14216
+
14217
+ // src/tui/ToolCall.tsx
14033
14218
  import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
14219
+ var StatusBadge = ({ info }) => {
14220
+ const theme = getTheme();
14221
+ if (info.status === "running") {
14222
+ return /* @__PURE__ */ jsxDEV6(Spinner, {
14223
+ label: "running"
14224
+ }, undefined, false, undefined, this);
14225
+ }
14226
+ const duration = info.durationMs !== undefined ? ` ${formatDuration(info.durationMs)}` : "";
14227
+ if (info.status === "error") {
14228
+ return /* @__PURE__ */ jsxDEV6(Text6, {
14229
+ color: theme.error,
14230
+ children: [
14231
+ "✗",
14232
+ duration || " failed"
14233
+ ]
14234
+ }, undefined, true, undefined, this);
14235
+ }
14236
+ return /* @__PURE__ */ jsxDEV6(Text6, {
14237
+ color: theme.success,
14238
+ children: [
14239
+ "✓",
14240
+ duration
14241
+ ]
14242
+ }, undefined, true, undefined, this);
14243
+ };
14244
+ var ToolCall = ({
14245
+ info,
14246
+ showOutput = true
14247
+ }) => {
14248
+ const theme = getTheme();
14249
+ const { icon, label, detail } = describeToolCall(info.name, info.args);
14250
+ const outputLines = showOutput && info.output ? info.output.split(`
14251
+ `) : [];
14252
+ const errorLines = showOutput && info.error ? info.error.split(`
14253
+ `).slice(0, 12) : [];
14254
+ return /* @__PURE__ */ jsxDEV6(Box4, {
14255
+ flexDirection: "column",
14256
+ marginBottom: 1,
14257
+ children: [
14258
+ /* @__PURE__ */ jsxDEV6(Box4, {
14259
+ children: [
14260
+ /* @__PURE__ */ jsxDEV6(Text6, {
14261
+ color: theme.tool,
14262
+ children: [
14263
+ icon,
14264
+ " "
14265
+ ]
14266
+ }, undefined, true, undefined, this),
14267
+ /* @__PURE__ */ jsxDEV6(Text6, {
14268
+ bold: true,
14269
+ color: theme.tool,
14270
+ children: label
14271
+ }, undefined, false, undefined, this),
14272
+ /* @__PURE__ */ jsxDEV6(Text6, {
14273
+ children: " "
14274
+ }, undefined, false, undefined, this),
14275
+ /* @__PURE__ */ jsxDEV6(StatusBadge, {
14276
+ info
14277
+ }, undefined, false, undefined, this)
14278
+ ]
14279
+ }, undefined, true, undefined, this),
14280
+ detail ? /* @__PURE__ */ jsxDEV6(Box4, {
14281
+ marginLeft: 2,
14282
+ children: /* @__PURE__ */ jsxDEV6(Text6, {
14283
+ dimColor: true,
14284
+ children: detail
14285
+ }, undefined, false, undefined, this)
14286
+ }, undefined, false, undefined, this) : null,
14287
+ outputLines.map((line, i) => /* @__PURE__ */ jsxDEV6(Box4, {
14288
+ marginLeft: 2,
14289
+ children: /* @__PURE__ */ jsxDEV6(Text6, {
14290
+ dimColor: true,
14291
+ children: [
14292
+ i === 0 ? "└ " : " ",
14293
+ line
14294
+ ]
14295
+ }, undefined, true, undefined, this)
14296
+ }, `out-${i}`, false, undefined, this)),
14297
+ errorLines.map((line, i) => /* @__PURE__ */ jsxDEV6(Box4, {
14298
+ marginLeft: 2,
14299
+ children: /* @__PURE__ */ jsxDEV6(Text6, {
14300
+ color: theme.error,
14301
+ children: [
14302
+ i === 0 ? "└ " : " ",
14303
+ line
14304
+ ]
14305
+ }, undefined, true, undefined, this)
14306
+ }, `err-${i}`, false, undefined, this))
14307
+ ]
14308
+ }, undefined, true, undefined, this);
14309
+ };
14310
+
14311
+ // src/tui/App.tsx
14312
+ init_theme();
14313
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
14034
14314
  var ROLE_PREFIX = {
14035
14315
  user: "\uD83D\uDC64 You",
14036
14316
  assistant: "\uD83E\uDD16 Agent",
@@ -14039,7 +14319,7 @@ var ROLE_PREFIX = {
14039
14319
  var RoleLabel = ({ role }) => {
14040
14320
  const theme = getTheme();
14041
14321
  const roleColor = role === "user" ? theme.user : role === "assistant" ? theme.assistant : theme.tool;
14042
- return /* @__PURE__ */ jsxDEV6(Text6, {
14322
+ return /* @__PURE__ */ jsxDEV7(Text7, {
14043
14323
  bold: true,
14044
14324
  color: roleColor,
14045
14325
  children: ROLE_PREFIX[role]
@@ -14047,16 +14327,16 @@ var RoleLabel = ({ role }) => {
14047
14327
  };
14048
14328
  var HistoryEntry = ({ item }) => {
14049
14329
  if (item.role === "user") {
14050
- return /* @__PURE__ */ jsxDEV6(Box4, {
14330
+ return /* @__PURE__ */ jsxDEV7(Box5, {
14051
14331
  flexDirection: "column",
14052
14332
  marginBottom: 1,
14053
14333
  children: [
14054
- /* @__PURE__ */ jsxDEV6(RoleLabel, {
14334
+ /* @__PURE__ */ jsxDEV7(RoleLabel, {
14055
14335
  role: "user"
14056
14336
  }, undefined, false, undefined, this),
14057
- /* @__PURE__ */ jsxDEV6(Box4, {
14337
+ /* @__PURE__ */ jsxDEV7(Box5, {
14058
14338
  marginLeft: 2,
14059
- children: /* @__PURE__ */ jsxDEV6(Text6, {
14339
+ children: /* @__PURE__ */ jsxDEV7(Text7, {
14060
14340
  children: item.content
14061
14341
  }, undefined, false, undefined, this)
14062
14342
  }, undefined, false, undefined, this)
@@ -14064,16 +14344,21 @@ var HistoryEntry = ({ item }) => {
14064
14344
  }, undefined, true, undefined, this);
14065
14345
  }
14066
14346
  if (item.role === "tool") {
14067
- return /* @__PURE__ */ jsxDEV6(Box4, {
14347
+ if (item.tool) {
14348
+ return /* @__PURE__ */ jsxDEV7(ToolCall, {
14349
+ info: item.tool
14350
+ }, undefined, false, undefined, this);
14351
+ }
14352
+ return /* @__PURE__ */ jsxDEV7(Box5, {
14068
14353
  flexDirection: "column",
14069
14354
  marginBottom: 1,
14070
14355
  children: [
14071
- /* @__PURE__ */ jsxDEV6(RoleLabel, {
14356
+ /* @__PURE__ */ jsxDEV7(RoleLabel, {
14072
14357
  role: "tool"
14073
14358
  }, undefined, false, undefined, this),
14074
- /* @__PURE__ */ jsxDEV6(Box4, {
14359
+ /* @__PURE__ */ jsxDEV7(Box5, {
14075
14360
  marginLeft: 2,
14076
- children: /* @__PURE__ */ jsxDEV6(Text6, {
14361
+ children: /* @__PURE__ */ jsxDEV7(Text7, {
14077
14362
  dimColor: true,
14078
14363
  children: item.content
14079
14364
  }, undefined, false, undefined, this)
@@ -14081,17 +14366,17 @@ var HistoryEntry = ({ item }) => {
14081
14366
  ]
14082
14367
  }, undefined, true, undefined, this);
14083
14368
  }
14084
- return /* @__PURE__ */ jsxDEV6(Box4, {
14369
+ return /* @__PURE__ */ jsxDEV7(Box5, {
14085
14370
  flexDirection: "column",
14086
14371
  marginBottom: 1,
14087
14372
  children: [
14088
- /* @__PURE__ */ jsxDEV6(RoleLabel, {
14373
+ item.continuation ? null : /* @__PURE__ */ jsxDEV7(RoleLabel, {
14089
14374
  role: "assistant"
14090
14375
  }, undefined, false, undefined, this),
14091
- /* @__PURE__ */ jsxDEV6(Box4, {
14376
+ /* @__PURE__ */ jsxDEV7(Box5, {
14092
14377
  marginLeft: 2,
14093
14378
  flexDirection: "column",
14094
- children: /* @__PURE__ */ jsxDEV6(Markdown, {
14379
+ children: /* @__PURE__ */ jsxDEV7(Markdown, {
14095
14380
  text: item.content
14096
14381
  }, undefined, false, undefined, this)
14097
14382
  }, undefined, false, undefined, this)
@@ -14104,66 +14389,69 @@ var App = ({
14104
14389
  model,
14105
14390
  workdir,
14106
14391
  streamingText,
14392
+ liveTools,
14107
14393
  tokenCount
14108
14394
  }) => {
14109
14395
  const theme = getTheme();
14110
- return /* @__PURE__ */ jsxDEV6(Box4, {
14396
+ return /* @__PURE__ */ jsxDEV7(Box5, {
14111
14397
  flexDirection: "column",
14112
- minHeight: 20,
14113
14398
  children: [
14114
- /* @__PURE__ */ jsxDEV6(Box4, {
14399
+ /* @__PURE__ */ jsxDEV7(Box5, {
14115
14400
  borderStyle: "round",
14116
14401
  borderColor: theme.primary,
14117
14402
  paddingX: 1,
14118
14403
  marginBottom: 1,
14119
14404
  children: [
14120
- /* @__PURE__ */ jsxDEV6(Text6, {
14405
+ /* @__PURE__ */ jsxDEV7(Text7, {
14121
14406
  bold: true,
14122
14407
  color: theme.primary,
14123
14408
  children: "\uD83D\uDE80 Roy Agent"
14124
14409
  }, undefined, false, undefined, this),
14125
- /* @__PURE__ */ jsxDEV6(Text6, {
14410
+ /* @__PURE__ */ jsxDEV7(Text7, {
14126
14411
  dimColor: true,
14127
14412
  children: " — interactive TUI"
14128
14413
  }, undefined, false, undefined, this)
14129
14414
  ]
14130
14415
  }, undefined, true, undefined, this),
14131
- /* @__PURE__ */ jsxDEV6(Static2, {
14416
+ /* @__PURE__ */ jsxDEV7(Static2, {
14132
14417
  items: history,
14133
- children: (item, index) => /* @__PURE__ */ jsxDEV6(HistoryEntry, {
14418
+ children: (item, index) => /* @__PURE__ */ jsxDEV7(HistoryEntry, {
14134
14419
  item
14135
14420
  }, item.id ?? `${item.role}:${index}`, false, undefined, this)
14136
14421
  }, undefined, false, undefined, this),
14137
- status === "thinking" ? /* @__PURE__ */ jsxDEV6(Box4, {
14422
+ status === "thinking" ? /* @__PURE__ */ jsxDEV7(Box5, {
14138
14423
  marginBottom: 1,
14139
- children: /* @__PURE__ */ jsxDEV6(Spinner, {
14424
+ children: /* @__PURE__ */ jsxDEV7(Spinner, {
14140
14425
  label: "Agent is thinking..."
14141
14426
  }, undefined, false, undefined, this)
14142
14427
  }, undefined, false, undefined, this) : null,
14143
- status === "streaming" && streamingText ? /* @__PURE__ */ jsxDEV6(Box4, {
14428
+ status === "streaming" && streamingText ? /* @__PURE__ */ jsxDEV7(Box5, {
14144
14429
  flexDirection: "column",
14145
14430
  marginBottom: 1,
14146
14431
  children: [
14147
- /* @__PURE__ */ jsxDEV6(RoleLabel, {
14432
+ /* @__PURE__ */ jsxDEV7(RoleLabel, {
14148
14433
  role: "assistant"
14149
14434
  }, undefined, false, undefined, this),
14150
- /* @__PURE__ */ jsxDEV6(Box4, {
14435
+ /* @__PURE__ */ jsxDEV7(Box5, {
14151
14436
  marginLeft: 2,
14152
- children: /* @__PURE__ */ jsxDEV6(Markdown, {
14437
+ children: /* @__PURE__ */ jsxDEV7(Markdown, {
14153
14438
  text: streamingText
14154
14439
  }, undefined, false, undefined, this)
14155
14440
  }, undefined, false, undefined, this)
14156
14441
  ]
14157
- }, undefined, true, undefined, this) : status === "streaming" ? /* @__PURE__ */ jsxDEV6(Box4, {
14442
+ }, undefined, true, undefined, this) : status === "streaming" ? /* @__PURE__ */ jsxDEV7(Box5, {
14158
14443
  marginBottom: 1,
14159
- children: /* @__PURE__ */ jsxDEV6(Spinner, {
14444
+ children: /* @__PURE__ */ jsxDEV7(Spinner, {
14160
14445
  label: "Generating response..."
14161
14446
  }, undefined, false, undefined, this)
14162
14447
  }, undefined, false, undefined, this) : null,
14163
- /* @__PURE__ */ jsxDEV6(Box4, {
14164
- flexGrow: 1
14165
- }, undefined, false, undefined, this),
14166
- /* @__PURE__ */ jsxDEV6(StatusBar, {
14448
+ liveTools && liveTools.length > 0 ? /* @__PURE__ */ jsxDEV7(Box5, {
14449
+ flexDirection: "column",
14450
+ children: liveTools.map((tool, i) => /* @__PURE__ */ jsxDEV7(ToolCall, {
14451
+ info: tool
14452
+ }, `live-tool-${i}`, false, undefined, this))
14453
+ }, undefined, false, undefined, this) : null,
14454
+ /* @__PURE__ */ jsxDEV7(StatusBar, {
14167
14455
  model,
14168
14456
  workdir,
14169
14457
  tokenCount
@@ -14177,9 +14465,79 @@ init_Markdown();
14177
14465
  init_StatusBar();
14178
14466
 
14179
14467
  // src/tui/TextInput.tsx
14180
- import { useEffect as useEffect4, useState as useState4 } from "react";
14181
- import { Box as Box5, Text as Text7, useInput as useInput3 } from "ink";
14182
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
14468
+ import { useEffect as useEffect4, useReducer, useState as useState4 } from "react";
14469
+ import { Box as Box6, Text as Text8, useInput as useInput3 } from "ink";
14470
+
14471
+ // src/tui/text-input-editing.ts
14472
+ function clampCursor(value, cursor) {
14473
+ if (!Number.isFinite(cursor))
14474
+ return value.length;
14475
+ return Math.max(0, Math.min(Math.trunc(cursor), value.length));
14476
+ }
14477
+ function insertText(state, text) {
14478
+ const cursor = clampCursor(state.value, state.cursor);
14479
+ const value = state.value.slice(0, cursor) + text + state.value.slice(cursor);
14480
+ return { value, cursor: cursor + text.length };
14481
+ }
14482
+ function deleteBefore(state) {
14483
+ const cursor = clampCursor(state.value, state.cursor);
14484
+ if (cursor === 0)
14485
+ return { value: state.value, cursor };
14486
+ const value = state.value.slice(0, cursor - 1) + state.value.slice(cursor);
14487
+ return { value, cursor: cursor - 1 };
14488
+ }
14489
+ function deleteAt(state) {
14490
+ const { value } = state;
14491
+ const cursor = clampCursor(value, state.cursor);
14492
+ if (cursor >= value.length)
14493
+ return { value, cursor };
14494
+ return { value: value.slice(0, cursor) + value.slice(cursor + 1), cursor };
14495
+ }
14496
+ function moveLeft(state) {
14497
+ return {
14498
+ value: state.value,
14499
+ cursor: Math.max(0, clampCursor(state.value, state.cursor) - 1)
14500
+ };
14501
+ }
14502
+ function moveRight(state) {
14503
+ const cursor = clampCursor(state.value, state.cursor);
14504
+ return {
14505
+ value: state.value,
14506
+ cursor: Math.min(state.value.length, cursor + 1)
14507
+ };
14508
+ }
14509
+ function splitAtCursor(state) {
14510
+ const cursor = clampCursor(state.value, state.cursor);
14511
+ return {
14512
+ before: state.value.slice(0, cursor),
14513
+ after: state.value.slice(cursor),
14514
+ cursor
14515
+ };
14516
+ }
14517
+ function editReducer(state, action) {
14518
+ switch (action.type) {
14519
+ case "insert":
14520
+ return insertText(state, action.text);
14521
+ case "backspace":
14522
+ return deleteBefore(state);
14523
+ case "delete":
14524
+ return deleteAt(state);
14525
+ case "left":
14526
+ return moveLeft(state);
14527
+ case "right":
14528
+ return moveRight(state);
14529
+ case "set":
14530
+ return {
14531
+ value: action.value,
14532
+ cursor: action.cursor ?? action.value.length
14533
+ };
14534
+ case "clear":
14535
+ return { value: "", cursor: 0 };
14536
+ }
14537
+ }
14538
+
14539
+ // src/tui/TextInput.tsx
14540
+ import { jsxDEV as jsxDEV8, Fragment } from "react/jsx-dev-runtime";
14183
14541
  var TextInput = ({
14184
14542
  label,
14185
14543
  prefix,
@@ -14188,7 +14546,10 @@ var TextInput = ({
14188
14546
  placeholder,
14189
14547
  history = []
14190
14548
  }) => {
14191
- const [value, setValue] = useState4("");
14549
+ const [{ value, cursor }, dispatch] = useReducer(editReducer, {
14550
+ value: "",
14551
+ cursor: 0
14552
+ });
14192
14553
  const [historyIndex, setHistoryIndex] = useState4(-1);
14193
14554
  const [draft, setDraft] = useState4("");
14194
14555
  useEffect4(() => {
@@ -14201,7 +14562,7 @@ var TextInput = ({
14201
14562
  }
14202
14563
  if (key.return) {
14203
14564
  const submitted = value;
14204
- setValue("");
14565
+ dispatch({ type: "clear" });
14205
14566
  setHistoryIndex(-1);
14206
14567
  setDraft("");
14207
14568
  onSubmit(submitted);
@@ -14211,7 +14572,15 @@ var TextInput = ({
14211
14572
  if (historyIndex !== -1) {
14212
14573
  setHistoryIndex(-1);
14213
14574
  }
14214
- setValue((v) => v.slice(0, -1));
14575
+ dispatch({ type: "backspace" });
14576
+ return;
14577
+ }
14578
+ if (key.leftArrow) {
14579
+ dispatch({ type: "left" });
14580
+ return;
14581
+ }
14582
+ if (key.rightArrow) {
14583
+ dispatch({ type: "right" });
14215
14584
  return;
14216
14585
  }
14217
14586
  if (key.upArrow) {
@@ -14222,7 +14591,7 @@ var TextInput = ({
14222
14591
  }
14223
14592
  const nextIndex = Math.min(historyIndex + 1, history.length - 1);
14224
14593
  setHistoryIndex(nextIndex);
14225
- setValue(history[nextIndex] ?? "");
14594
+ dispatch({ type: "set", value: history[nextIndex] ?? "" });
14226
14595
  return;
14227
14596
  }
14228
14597
  if (key.downArrow) {
@@ -14231,10 +14600,10 @@ var TextInput = ({
14231
14600
  const nextIndex = historyIndex - 1;
14232
14601
  if (nextIndex < 0) {
14233
14602
  setHistoryIndex(-1);
14234
- setValue(draft);
14603
+ dispatch({ type: "set", value: draft });
14235
14604
  } else {
14236
14605
  setHistoryIndex(nextIndex);
14237
- setValue(history[nextIndex] ?? "");
14606
+ dispatch({ type: "set", value: history[nextIndex] ?? "" });
14238
14607
  }
14239
14608
  return;
14240
14609
  }
@@ -14242,12 +14611,13 @@ var TextInput = ({
14242
14611
  setHistoryIndex(-1);
14243
14612
  }
14244
14613
  if (input) {
14245
- setValue((v) => v + input);
14614
+ dispatch({ type: "insert", text: input });
14246
14615
  }
14247
14616
  });
14248
- return /* @__PURE__ */ jsxDEV7(Box5, {
14617
+ const { before, after } = splitAtCursor({ value, cursor });
14618
+ return /* @__PURE__ */ jsxDEV8(Box6, {
14249
14619
  children: [
14250
- /* @__PURE__ */ jsxDEV7(Text7, {
14620
+ /* @__PURE__ */ jsxDEV8(Text8, {
14251
14621
  bold: true,
14252
14622
  color: "green",
14253
14623
  children: [
@@ -14255,21 +14625,42 @@ var TextInput = ({
14255
14625
  " "
14256
14626
  ]
14257
14627
  }, undefined, true, undefined, this),
14258
- /* @__PURE__ */ jsxDEV7(Text7, {
14628
+ /* @__PURE__ */ jsxDEV8(Text8, {
14259
14629
  bold: true,
14260
14630
  children: [
14261
14631
  label,
14262
14632
  ": "
14263
14633
  ]
14264
14634
  }, undefined, true, undefined, this),
14265
- /* @__PURE__ */ jsxDEV7(Text7, {
14266
- children: value
14635
+ value ? /* @__PURE__ */ jsxDEV8(Fragment, {
14636
+ children: [
14637
+ /* @__PURE__ */ jsxDEV8(Text8, {
14638
+ children: before
14639
+ }, undefined, false, undefined, this),
14640
+ /* @__PURE__ */ jsxDEV8(Text8, {
14641
+ inverse: true,
14642
+ children: "▌"
14643
+ }, undefined, false, undefined, this),
14644
+ /* @__PURE__ */ jsxDEV8(Text8, {
14645
+ children: after
14646
+ }, undefined, false, undefined, this)
14647
+ ]
14648
+ }, undefined, true, undefined, this) : placeholder ? /* @__PURE__ */ jsxDEV8(Fragment, {
14649
+ children: [
14650
+ /* @__PURE__ */ jsxDEV8(Text8, {
14651
+ inverse: true,
14652
+ children: "▌"
14653
+ }, undefined, false, undefined, this),
14654
+ /* @__PURE__ */ jsxDEV8(Text8, {
14655
+ dimColor: true,
14656
+ children: placeholder
14657
+ }, undefined, false, undefined, this)
14658
+ ]
14659
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV8(Text8, {
14660
+ inverse: true,
14661
+ children: "▌"
14267
14662
  }, undefined, false, undefined, this),
14268
- !value && placeholder ? /* @__PURE__ */ jsxDEV7(Text7, {
14269
- dimColor: true,
14270
- children: placeholder
14271
- }, undefined, false, undefined, this) : null,
14272
- historyIndex !== -1 ? /* @__PURE__ */ jsxDEV7(Text7, {
14663
+ historyIndex !== -1 ? /* @__PURE__ */ jsxDEV8(Text8, {
14273
14664
  dimColor: true,
14274
14665
  children: " (history)"
14275
14666
  }, undefined, false, undefined, this) : null
@@ -14282,37 +14673,119 @@ init_theme();
14282
14673
  init_welcome_card2();
14283
14674
 
14284
14675
  // src/tui/use-streaming-accumulator.ts
14285
- import { useCallback as useCallback3, useState as useState5 } from "react";
14286
- function useStreamingAccumulator() {
14676
+ import { useCallback as useCallback3, useRef as useRef2, useState as useState5 } from "react";
14677
+
14678
+ // src/tui/scrollback.ts
14679
+ var FENCE_RE = /^\s*(```|~~~)/;
14680
+ function lineStartOffset(lines, index) {
14681
+ let off = 0;
14682
+ for (let i = 0;i < index; i++)
14683
+ off += (lines[i] ?? "").length + 1;
14684
+ return off;
14685
+ }
14686
+ function splitStable(text, opts = {}) {
14687
+ if (!text)
14688
+ return { stable: "", tail: "" };
14689
+ if (opts.force)
14690
+ return { stable: text, tail: "" };
14691
+ const lines = text.split(`
14692
+ `);
14693
+ let inFence = false;
14694
+ let boundary = -1;
14695
+ for (let i = 0;i < lines.length; i++) {
14696
+ const line = lines[i] ?? "";
14697
+ const isLast = i === lines.length - 1;
14698
+ const next = lineStartOffset(lines, i) + line.length + 1;
14699
+ if (FENCE_RE.test(line)) {
14700
+ inFence = !inFence;
14701
+ } else if (!inFence && !isLast && line.trim() === "") {
14702
+ boundary = next;
14703
+ }
14704
+ }
14705
+ if (boundary > 0) {
14706
+ return { stable: text.slice(0, boundary), tail: text.slice(boundary) };
14707
+ }
14708
+ const maxTailLines = opts.maxTailLines ?? 0;
14709
+ if (maxTailLines > 0 && !inFence && lines.length > maxTailLines) {
14710
+ const keepFrom = lines.length - 1;
14711
+ const off = lineStartOffset(lines, keepFrom);
14712
+ if (off > 0) {
14713
+ return { stable: text.slice(0, off), tail: text.slice(off) };
14714
+ }
14715
+ }
14716
+ return { stable: "", tail: text };
14717
+ }
14718
+
14719
+ // src/tui/use-streaming-accumulator.ts
14720
+ var DEFAULT_MAX_TAIL_LINES = 24;
14721
+ function useStreamingAccumulator(options = {}) {
14722
+ const maxTailLines = options.maxTailLines ?? DEFAULT_MAX_TAIL_LINES;
14287
14723
  const [status, setStatus] = useState5("idle");
14288
14724
  const [streamingText, setStreamingText] = useState5("");
14725
+ const pendingRef = useRef2("");
14726
+ const emittedRef = useRef2("");
14289
14727
  const start = useCallback3(() => {
14728
+ pendingRef.current = "";
14729
+ emittedRef.current = "";
14290
14730
  setStreamingText("");
14291
14731
  setStatus("thinking");
14292
14732
  }, []);
14293
14733
  const append = useCallback3((chunk) => {
14734
+ pendingRef.current += chunk;
14735
+ setStreamingText(pendingRef.current);
14294
14736
  setStatus("streaming");
14295
- setStreamingText((prev) => prev + chunk);
14296
14737
  }, []);
14297
- const finish = useCallback3(() => {
14298
- setStatus("idle");
14299
- let snapshot = "";
14300
- setStreamingText((prev) => {
14301
- snapshot = prev;
14302
- return "";
14738
+ const drainStable = useCallback3(() => {
14739
+ const { stable, tail } = splitStable(pendingRef.current, {
14740
+ maxTailLines
14303
14741
  });
14304
- return snapshot;
14742
+ if (!stable)
14743
+ return "";
14744
+ pendingRef.current = tail;
14745
+ emittedRef.current += stable;
14746
+ setStreamingText(tail);
14747
+ return stable;
14748
+ }, [maxTailLines]);
14749
+ const flushPending = useCallback3(() => {
14750
+ const pending = pendingRef.current;
14751
+ if (!pending)
14752
+ return "";
14753
+ pendingRef.current = "";
14754
+ emittedRef.current += pending;
14755
+ setStreamingText("");
14756
+ return pending;
14757
+ }, []);
14758
+ const finish = useCallback3((finalText) => {
14759
+ const pending = pendingRef.current;
14760
+ pendingRef.current = "";
14761
+ setStreamingText("");
14762
+ setStatus("idle");
14763
+ if (finalText === undefined) {
14764
+ emittedRef.current += pending;
14765
+ return pending;
14766
+ }
14767
+ if (finalText.startsWith(emittedRef.current)) {
14768
+ const suffix = finalText.slice(emittedRef.current.length);
14769
+ emittedRef.current = finalText;
14770
+ return suffix;
14771
+ }
14772
+ const emitted = emittedRef.current;
14773
+ let i = 0;
14774
+ const max = Math.min(emitted.length, finalText.length);
14775
+ while (i < max && emitted.charCodeAt(i) === finalText.charCodeAt(i)) {
14776
+ i++;
14777
+ }
14778
+ emittedRef.current = finalText;
14779
+ return finalText.slice(i);
14305
14780
  }, []);
14306
14781
  const fail = useCallback3((_err) => {
14782
+ const rest = pendingRef.current;
14783
+ pendingRef.current = "";
14784
+ setStreamingText("");
14307
14785
  setStatus("idle");
14308
- let snapshot = "";
14309
- setStreamingText((prev) => {
14310
- snapshot = prev;
14311
- return "";
14312
- });
14313
- return snapshot;
14786
+ return rest;
14314
14787
  }, []);
14315
- return { status, streamingText, start, append, finish, fail };
14788
+ return { status, streamingText, start, append, drainStable, flushPending, finish, fail };
14316
14789
  }
14317
14790
 
14318
14791
  // src/RunTui.tsx
@@ -14391,7 +14864,7 @@ function dispatchSlashCommand(raw, ctx) {
14391
14864
  }
14392
14865
 
14393
14866
  // src/RunTui.tsx
14394
- import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
14867
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
14395
14868
  var RunScreen = ({
14396
14869
  handleQuery,
14397
14870
  streamHandleQuery,
@@ -14399,6 +14872,8 @@ var RunScreen = ({
14399
14872
  workdir,
14400
14873
  maxLines: initialMaxLines,
14401
14874
  markdownEnabled: markdownEnabled2,
14875
+ showToolCalls = true,
14876
+ showToolResults = true,
14402
14877
  onExit,
14403
14878
  onRegisterHandlers
14404
14879
  }) => {
@@ -14406,14 +14881,22 @@ var RunScreen = ({
14406
14881
  const [errorMessage, setErrorMessage] = useState6(null);
14407
14882
  const [maxLines, setMaxLines] = useState6(initialMaxLines);
14408
14883
  const [userHistory, setUserHistory] = useState6([]);
14409
- const stream = useStreamingAccumulator();
14884
+ const [liveTools, setLiveTools] = useState6([]);
14885
+ const { stdout } = useStdout();
14886
+ const maxTailLines = Math.max(4, (stdout?.rows ?? 24) - 12);
14887
+ const stream = useStreamingAccumulator({ maxTailLines });
14410
14888
  const status = stream.status;
14411
14889
  const { exit } = useApp2();
14412
- const userTurnCountRef = useRef2(0);
14413
- const assistantTurnCountRef = useRef2(0);
14414
- const isProcessingRef = useRef2(0);
14415
- const executeQueryRef = useRef2(null);
14416
- const historyIdRef = useRef2(0);
14890
+ const userTurnCountRef = useRef3(0);
14891
+ const assistantTurnCountRef = useRef3(0);
14892
+ const isProcessingRef = useRef3(0);
14893
+ const executeQueryRef = useRef3(null);
14894
+ const historyIdRef = useRef3(0);
14895
+ const liveRef = useRef3([]);
14896
+ const toolSeqRef = useRef3(0);
14897
+ const syncLiveTools = useCallback4(() => {
14898
+ setLiveTools(liveRef.current.map((t) => t.info));
14899
+ }, []);
14417
14900
  const width = process.stdout.columns ?? 80;
14418
14901
  const doExit = useCallback4(() => {
14419
14902
  onExit?.();
@@ -14422,25 +14905,108 @@ var RunScreen = ({
14422
14905
  const executeQuery = useCallback4(async (message) => {
14423
14906
  isProcessingRef.current += 1;
14424
14907
  stream.start();
14908
+ let assistantBlocks = 0;
14909
+ const pushAssistant = (content) => {
14910
+ const id = `h${++historyIdRef.current}`;
14911
+ const continuation = assistantBlocks > 0;
14912
+ assistantBlocks += 1;
14913
+ setHistory((h) => [
14914
+ ...h,
14915
+ { role: "assistant", content, id, continuation }
14916
+ ]);
14917
+ };
14918
+ const commitTool = (info) => {
14919
+ const id = `h${++historyIdRef.current}`;
14920
+ setHistory((h) => [
14921
+ ...h,
14922
+ { role: "tool", content: "", tool: info, id }
14923
+ ]);
14924
+ };
14925
+ const findLiveIndex = (event) => {
14926
+ if (event.id) {
14927
+ const byId = liveRef.current.findIndex((t) => t.key === event.id);
14928
+ if (byId >= 0)
14929
+ return byId;
14930
+ }
14931
+ return liveRef.current.findIndex((t) => t.info.name === event.name);
14932
+ };
14933
+ const handleToolEvent = (event) => {
14934
+ if (event.kind === "call") {
14935
+ const pending = stream.flushPending();
14936
+ if (pending.trim())
14937
+ pushAssistant(pending);
14938
+ const key = event.id ?? `${event.name}#${++toolSeqRef.current}`;
14939
+ liveRef.current = [
14940
+ ...liveRef.current,
14941
+ {
14942
+ key,
14943
+ startedAt: Date.now(),
14944
+ info: { name: event.name, args: event.args, status: "running" }
14945
+ }
14946
+ ];
14947
+ syncLiveTools();
14948
+ return;
14949
+ }
14950
+ const idx = findLiveIndex(event);
14951
+ const entry = idx >= 0 ? liveRef.current[idx] : undefined;
14952
+ const durationMs = entry ? Date.now() - entry.startedAt : undefined;
14953
+ const info = event.kind === "error" ? {
14954
+ name: event.name,
14955
+ args: entry?.info.args,
14956
+ status: "error",
14957
+ error: event.error ?? "tool failed",
14958
+ durationMs
14959
+ } : {
14960
+ name: event.name,
14961
+ args: entry?.info.args,
14962
+ status: "completed",
14963
+ output: showToolResults ? formatToolOutput(event.output) : undefined,
14964
+ durationMs
14965
+ };
14966
+ if (idx >= 0) {
14967
+ liveRef.current = liveRef.current.filter((_, i) => i !== idx);
14968
+ syncLiveTools();
14969
+ }
14970
+ commitTool(info);
14971
+ };
14972
+ const settleLiveTools = (status2, error) => {
14973
+ if (liveRef.current.length === 0)
14974
+ return;
14975
+ for (const t of liveRef.current) {
14976
+ commitTool({
14977
+ ...t.info,
14978
+ status: status2,
14979
+ ...error ? { error } : {},
14980
+ durationMs: Date.now() - t.startedAt
14981
+ });
14982
+ }
14983
+ liveRef.current = [];
14984
+ syncLiveTools();
14985
+ };
14425
14986
  try {
14426
14987
  let responseText;
14427
14988
  if (streamHandleQuery) {
14428
- responseText = await streamHandleQuery(message, (chunk) => stream.append(chunk));
14989
+ responseText = await streamHandleQuery(message, (chunk) => {
14990
+ stream.append(chunk);
14991
+ const stable = stream.drainStable();
14992
+ if (stable)
14993
+ pushAssistant(stable);
14994
+ }, showToolCalls ? handleToolEvent : undefined);
14995
+ const tail = stream.finish(responseText);
14996
+ if (tail.trim())
14997
+ pushAssistant(tail);
14429
14998
  } else {
14430
14999
  responseText = await handleQuery(message);
15000
+ stream.finish();
15001
+ const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
15002
+ pushAssistant(truncateForTerminal(rendered, maxLines));
14431
15003
  }
14432
- const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
14433
- const finalText = truncateForTerminal(rendered, maxLines);
14434
- stream.finish();
14435
- const assistantId = `h${++historyIdRef.current}`;
14436
- setHistory((h) => [
14437
- ...h,
14438
- { role: "assistant", content: finalText, id: assistantId }
14439
- ]);
15004
+ settleLiveTools("completed");
14440
15005
  assistantTurnCountRef.current += 1;
14441
15006
  } catch (err) {
14442
15007
  const msg = err instanceof Error ? err.message : String(err);
14443
15008
  stream.fail(err instanceof Error ? err : new Error(msg));
15009
+ settleLiveTools("error", msg);
14444
15010
  setErrorMessage(msg);
14445
15011
  const errorId = `h${++historyIdRef.current}`;
14446
15012
  setHistory((h) => [
@@ -14450,7 +15016,17 @@ var RunScreen = ({
14450
15016
  } finally {
14451
15017
  isProcessingRef.current -= 1;
14452
15018
  }
14453
- }, [handleQuery, markdownEnabled2, maxLines, stream, streamHandleQuery, width]);
15019
+ }, [
15020
+ handleQuery,
15021
+ markdownEnabled2,
15022
+ maxLines,
15023
+ showToolCalls,
15024
+ showToolResults,
15025
+ stream,
15026
+ streamHandleQuery,
15027
+ syncLiveTools,
15028
+ width
15029
+ ]);
14454
15030
  useEffect5(() => {
14455
15031
  executeQueryRef.current = executeQuery;
14456
15032
  }, [executeQuery]);
@@ -14558,19 +15134,20 @@ var RunScreen = ({
14558
15134
  const handleCancel = useCallback4(() => {
14559
15135
  doExit();
14560
15136
  }, [doExit]);
14561
- return /* @__PURE__ */ jsxDEV8(Box6, {
15137
+ return /* @__PURE__ */ jsxDEV9(Box7, {
14562
15138
  flexDirection: "column",
14563
15139
  children: [
14564
- /* @__PURE__ */ jsxDEV8(App, {
15140
+ /* @__PURE__ */ jsxDEV9(App, {
14565
15141
  history,
14566
15142
  status,
14567
15143
  model,
14568
15144
  workdir,
14569
- streamingText: stream.streamingText
15145
+ streamingText: stream.streamingText,
15146
+ liveTools
14570
15147
  }, undefined, false, undefined, this),
14571
- status === "idle" ? /* @__PURE__ */ jsxDEV8(Box6, {
15148
+ status === "idle" ? /* @__PURE__ */ jsxDEV9(Box7, {
14572
15149
  marginTop: 1,
14573
- children: /* @__PURE__ */ jsxDEV8(TextInput, {
15150
+ children: /* @__PURE__ */ jsxDEV9(TextInput, {
14574
15151
  label: "You",
14575
15152
  prefix: "\uD83D\uDC64",
14576
15153
  placeholder: 'Type a message, "/help", or "exit"',
@@ -14579,9 +15156,9 @@ var RunScreen = ({
14579
15156
  history: userHistory
14580
15157
  }, undefined, false, undefined, this)
14581
15158
  }, undefined, false, undefined, this) : null,
14582
- errorMessage ? /* @__PURE__ */ jsxDEV8(Box6, {
15159
+ errorMessage ? /* @__PURE__ */ jsxDEV9(Box7, {
14583
15160
  marginTop: 1,
14584
- children: /* @__PURE__ */ jsxDEV8(Text8, {
15161
+ children: /* @__PURE__ */ jsxDEV9(Text9, {
14585
15162
  color: "red",
14586
15163
  children: [
14587
15164
  "⚠ ",
@@ -14690,7 +15267,7 @@ async function loadHandleQuery(options, hooks) {
14690
15267
  workDir: options.workdir,
14691
15268
  loadPlugins: true,
14692
15269
  plugins: options.plugins,
14693
- commandName: "run"
15270
+ commandName: "interactive"
14694
15271
  });
14695
15272
  const env = envService.getEnvironment();
14696
15273
  if (!env) {
@@ -14793,14 +15370,41 @@ async function loadHandleQuery(options, hooks) {
14793
15370
  };
14794
15371
  }
14795
15372
  function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext, eventSourceSystemPrompt) {
14796
- return async (message, onChunk) => {
15373
+ return async (message, onChunk, onTool) => {
14797
15374
  queryExecutor.setChunkCallback((event) => {
14798
- if (event.type === "llm.text") {
14799
- if (event.delta) {
14800
- onChunk(event.delta);
14801
- } else if (event.content) {
14802
- onChunk(event.content);
14803
- }
15375
+ switch (event.type) {
15376
+ case "llm.text":
15377
+ if (event.delta) {
15378
+ onChunk(event.delta);
15379
+ } else if (event.content) {
15380
+ onChunk(event.content);
15381
+ }
15382
+ break;
15383
+ case "tool.call":
15384
+ onTool?.({
15385
+ kind: "call",
15386
+ id: event.toolId,
15387
+ name: event.toolName ?? "tool",
15388
+ args: event.toolArgs
15389
+ });
15390
+ break;
15391
+ case "tool.result":
15392
+ onTool?.({
15393
+ kind: "result",
15394
+ id: event.toolId,
15395
+ name: event.toolName ?? "tool",
15396
+ output: event.toolResult
15397
+ });
15398
+ break;
15399
+ case "tool.error":
15400
+ onTool?.({
15401
+ kind: "error",
15402
+ name: event.toolName ?? "tool",
15403
+ error: event.toolError
15404
+ });
15405
+ break;
15406
+ default:
15407
+ break;
14804
15408
  }
14805
15409
  });
14806
15410
  const mergedContext = {
@@ -14814,15 +15418,15 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
14814
15418
  };
14815
15419
  const result = await queryExecutor.execute(message, sessionId, {
14816
15420
  showReasoning: options.reasoning ?? true,
14817
- showToolCalls: options.toolCalls ?? true,
14818
- showToolResults: options.toolResults ?? false
15421
+ showToolCalls: true,
15422
+ showToolResults: true
14819
15423
  }, undefined, mergedContext);
14820
15424
  return String(result ?? "");
14821
15425
  };
14822
15426
  }
14823
15427
  function createRunCommand() {
14824
15428
  return {
14825
- command: "run",
15429
+ command: "interactive",
14826
15430
  describe: "Start an interactive conversation with the agent (Ink TUI)",
14827
15431
  builder: (yargs) => yargs.option("model", {
14828
15432
  alias: "m",
@@ -14876,7 +15480,7 @@ function createRunCommand() {
14876
15480
  }).option("tool-results", {
14877
15481
  type: "boolean",
14878
15482
  describe: "显示工具执行结果",
14879
- default: false
15483
+ default: true
14880
15484
  }).option("event-source", {
14881
15485
  alias: "es",
14882
15486
  type: "array",
@@ -14935,13 +15539,15 @@ function createRunCommand() {
14935
15539
  const streamHandleQuery = createStreamHandleQuery(queryExecutor, sessionId, argv, agentContext, eventSourceSystemPrompt);
14936
15540
  const stopEventHandler = eventHandler.start();
14937
15541
  const stopTaskEventHandler = taskEventHandler.start();
14938
- const { waitUntilExit } = render(React6.createElement(RunTui_default, {
15542
+ const { waitUntilExit } = render(React7.createElement(RunTui_default, {
14939
15543
  handleQuery,
14940
15544
  streamHandleQuery,
14941
15545
  model: argv.model,
14942
15546
  workdir: argv.workdir,
14943
15547
  maxLines,
14944
15548
  markdownEnabled: argv.markdown !== false,
15549
+ showToolCalls: argv.toolCalls !== false,
15550
+ showToolResults: argv.toolResults !== false,
14945
15551
  onRegisterHandlers: (handlers) => {
14946
15552
  runState.onNotification = handlers.onNotification;
14947
15553
  runState.isIdle = handlers.isIdle;
@@ -14972,14 +15578,14 @@ function createRunCommand() {
14972
15578
  var RunCommand = createRunCommand();
14973
15579
 
14974
15580
  // src/commands/chat.ts
14975
- import React8 from "react";
15581
+ import React9 from "react";
14976
15582
  import { render as render2 } from "ink";
14977
15583
 
14978
15584
  // src/ChatTui.tsx
14979
- import { useEffect as useEffect6, useRef as useRef3, useState as useState7 } from "react";
14980
- import { Box as Box7, useApp as useApp3 } from "ink";
15585
+ import { useEffect as useEffect6, useRef as useRef4, useState as useState7 } from "react";
15586
+ import { Box as Box8, useApp as useApp3 } from "ink";
14981
15587
  init_markdown();
14982
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
15588
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
14983
15589
  var ChatScreen = ({
14984
15590
  handleQuery,
14985
15591
  streamHandleQuery,
@@ -14992,7 +15598,7 @@ var ChatScreen = ({
14992
15598
  const [history, setHistory] = useState7([
14993
15599
  { role: "user", content: message, id: "h-init-1" }
14994
15600
  ]);
14995
- const historyIdRef = useRef3(1);
15601
+ const historyIdRef = useRef4(1);
14996
15602
  const stream = useStreamingAccumulator();
14997
15603
  const { exit } = useApp3();
14998
15604
  const width = process.stdout.columns ?? 80;
@@ -15035,9 +15641,9 @@ var ChatScreen = ({
15035
15641
  cancelled = true;
15036
15642
  };
15037
15643
  }, [handleQuery, streamHandleQuery, message, maxLines, markdownEnabled2, exit, width, stream]);
15038
- return /* @__PURE__ */ jsxDEV9(Box7, {
15644
+ return /* @__PURE__ */ jsxDEV10(Box8, {
15039
15645
  flexDirection: "column",
15040
- children: /* @__PURE__ */ jsxDEV9(App, {
15646
+ children: /* @__PURE__ */ jsxDEV10(App, {
15041
15647
  history,
15042
15648
  status: stream.status,
15043
15649
  model,
@@ -15123,7 +15729,7 @@ function createChatCommand() {
15123
15729
  }
15124
15730
  const maxLines = Number.parseInt(argv.maxLines ?? "500", 10);
15125
15731
  const handleQuery = await loadHandleQuery2(argv);
15126
- const { waitUntilExit } = render2(React8.createElement(ChatTui_default, {
15732
+ const { waitUntilExit } = render2(React9.createElement(ChatTui_default, {
15127
15733
  handleQuery,
15128
15734
  message: argv.message,
15129
15735
  model: argv.model,
@@ -22778,7 +23384,7 @@ function formatDate(date) {
22778
23384
  const d = typeof date === "string" ? new Date(date) : date;
22779
23385
  return d.toLocaleString("zh-CN");
22780
23386
  }
22781
- function formatDuration(ms) {
23387
+ function formatDuration2(ms) {
22782
23388
  if (ms === undefined || ms === null)
22783
23389
  return "-";
22784
23390
  if (ms < 1000)
@@ -22926,7 +23532,7 @@ function renderRunsList(runs, options) {
22926
23532
  const rows = runs.map((r) => {
22927
23533
  const id = truncateVisual3(r.id, ID_WIDTH).padEnd(ID_WIDTH);
22928
23534
  const status = statusColor(r.status)(r.status.padEnd(STATUS_WIDTH));
22929
- const duration = formatDuration(r.durationMs).padEnd(DURATION_WIDTH);
23535
+ const duration = formatDuration2(r.durationMs).padEnd(DURATION_WIDTH);
22930
23536
  const started = formatDate(r.startedAt);
22931
23537
  return `${id}${GAP}${status}${GAP}${duration}${GAP}${started}`;
22932
23538
  });
@@ -22954,7 +23560,7 @@ function renderRunDetail(run) {
22954
23560
  if (run.completedAt) {
22955
23561
  lines.push(` ${chalk67.cyan("Completed:")} ${formatDate(run.completedAt)}`);
22956
23562
  }
22957
- lines.push(` ${chalk67.cyan("Duration:")} ${formatDuration(run.durationMs)}`);
23563
+ lines.push(` ${chalk67.cyan("Duration:")} ${formatDuration2(run.durationMs)}`);
22958
23564
  if (run.error) {
22959
23565
  lines.push(chalk67.bold(`
22960
23566
  ❌ Error
@@ -23020,7 +23626,7 @@ function renderRunResult(result) {
23020
23626
  }
23021
23627
  lines.push(` Run ID: ${result.runId}`);
23022
23628
  if (result.durationMs) {
23023
- lines.push(` Duration: ${formatDuration(result.durationMs)}`);
23629
+ lines.push(` Duration: ${formatDuration2(result.durationMs)}`);
23024
23630
  }
23025
23631
  if (result.error) {
23026
23632
  lines.push(chalk67.red(`