@ai-setting/roy-agent-cli 1.7.41 → 1.7.43

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