@robota-sdk/agent-cli 3.0.0-beta.35 → 3.0.0-beta.37

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.
@@ -149,9 +149,10 @@ var PrintTerminal = class {
149
149
  import { render } from "ink";
150
150
 
151
151
  // src/ui/App.tsx
152
- import { useState as useState10, useRef as useRef8 } from "react";
152
+ import { useState as useState10, useRef as useRef8, useEffect as useEffect3 } from "react";
153
153
  import { Box as Box11, Text as Text13, useApp, useInput as useInput7 } from "ink";
154
154
  import { getModelName } from "@robota-sdk/agent-core";
155
+ import { createSystemMessage as createSystemMessage3 } from "@robota-sdk/agent-core";
155
156
 
156
157
  // src/ui/hooks/useSession.ts
157
158
  import { useState, useCallback, useRef } from "react";
@@ -247,6 +248,7 @@ var NOOP_TERMINAL = {
247
248
  function useSession(props) {
248
249
  const [permissionRequest, setPermissionRequest] = useState(null);
249
250
  const [streamingText, setStreamingText] = useState("");
251
+ const streamingTextRef = useRef("");
250
252
  const [activeTools, setActiveTools] = useState([]);
251
253
  const permissionQueueRef = useRef([]);
252
254
  const processingRef = useRef(false);
@@ -278,8 +280,15 @@ function useSession(props) {
278
280
  processNextPermission();
279
281
  });
280
282
  };
283
+ let flushTimer = null;
281
284
  const onTextDelta = (delta) => {
282
- setStreamingText((prev) => prev + delta);
285
+ streamingTextRef.current += delta;
286
+ if (!flushTimer) {
287
+ flushTimer = setTimeout(() => {
288
+ setStreamingText(streamingTextRef.current);
289
+ flushTimer = null;
290
+ }, 16);
291
+ }
283
292
  };
284
293
  const onToolExecution = (event) => {
285
294
  if (event.type === "start") {
@@ -358,6 +367,7 @@ function useSession(props) {
358
367
  }
359
368
  const clearStreamingText = useCallback(() => {
360
369
  setStreamingText("");
370
+ streamingTextRef.current = "";
361
371
  setActiveTools([]);
362
372
  }, []);
363
373
  return {
@@ -372,16 +382,11 @@ function useSession(props) {
372
382
  // src/ui/hooks/useMessages.ts
373
383
  import { useState as useState2, useCallback as useCallback2 } from "react";
374
384
  var MAX_RENDERED_MESSAGES = 100;
375
- var msgIdCounter = 0;
376
- function nextId() {
377
- msgIdCounter += 1;
378
- return `msg_${msgIdCounter}`;
379
- }
380
385
  function useMessages() {
381
386
  const [messages, setMessages] = useState2([]);
382
387
  const addMessage = useCallback2((msg) => {
383
388
  setMessages((prev) => {
384
- const updated = [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }];
389
+ const updated = [...prev, msg];
385
390
  if (updated.length > MAX_RENDERED_MESSAGES) {
386
391
  return updated.slice(-MAX_RENDERED_MESSAGES);
387
392
  }
@@ -393,6 +398,7 @@ function useMessages() {
393
398
 
394
399
  // src/ui/hooks/useSlashCommands.ts
395
400
  import { useCallback as useCallback3 } from "react";
401
+ import { createSystemMessage } from "@robota-sdk/agent-core";
396
402
 
397
403
  // src/commands/slash-executor.ts
398
404
  var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
@@ -659,11 +665,14 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
659
665
  const cmd = parts[0]?.toLowerCase() ?? "";
660
666
  const args = parts.slice(1).join(" ");
661
667
  const clearMessages = () => setMessages([]);
668
+ const slashAddMessage = (msg) => {
669
+ addMessage(createSystemMessage(msg.content));
670
+ };
662
671
  const result = await executeSlashCommand(
663
672
  cmd,
664
673
  args,
665
674
  session,
666
- addMessage,
675
+ slashAddMessage,
667
676
  clearMessages,
668
677
  registry,
669
678
  pluginCallbacks
@@ -696,11 +705,18 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
696
705
 
697
706
  // src/ui/hooks/useSubmitHandler.ts
698
707
  import { useCallback as useCallback4 } from "react";
708
+ import { randomUUID } from "crypto";
699
709
  import {
700
710
  createSubagentSession,
701
711
  getBuiltInAgent,
702
712
  retrieveAgentToolDeps
703
713
  } from "@robota-sdk/agent-sdk";
714
+ import {
715
+ createUserMessage,
716
+ createAssistantMessage,
717
+ createSystemMessage as createSystemMessage2,
718
+ createToolMessage
719
+ } from "@robota-sdk/agent-core";
704
720
 
705
721
  // src/utils/tool-call-extractor.ts
706
722
  var TOOL_ARG_MAX_LENGTH = 80;
@@ -857,21 +873,50 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
857
873
  historyBefore
858
874
  );
859
875
  if (toolSummaries.length > 0) {
860
- addMessage({
861
- role: "tool",
862
- content: JSON.stringify(toolSummaries),
863
- toolName: `${toolSummaries.length} tools`
864
- });
876
+ addMessage(
877
+ createToolMessage(JSON.stringify(toolSummaries), {
878
+ toolCallId: randomUUID(),
879
+ name: `${toolSummaries.length} tools`
880
+ })
881
+ );
865
882
  }
866
- addMessage({ role: "assistant", content: response || "(empty response)" });
883
+ addMessage(createAssistantMessage(response || "(empty response)"));
867
884
  syncContextState(session, setContextState);
868
885
  } catch (err) {
869
886
  clearStreamingText();
870
- if (err instanceof DOMException && err.name === "AbortError") {
871
- addMessage({ role: "system", content: "Cancelled." });
887
+ const isAbortError = err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
888
+ if (isAbortError) {
889
+ const history = session.getHistory();
890
+ const toolSummaries = extractToolCallsWithDiff(
891
+ history,
892
+ historyBefore
893
+ );
894
+ if (toolSummaries.length > 0) {
895
+ addMessage(
896
+ createToolMessage(JSON.stringify(toolSummaries), {
897
+ toolCallId: randomUUID(),
898
+ name: `${toolSummaries.length} tools`
899
+ })
900
+ );
901
+ }
902
+ const assistantParts = [];
903
+ let lastAssistantState = "complete";
904
+ for (let i = historyBefore; i < history.length; i++) {
905
+ const msg = history[i];
906
+ if (msg && msg.role === "assistant" && msg.content) {
907
+ assistantParts.push(msg.content);
908
+ if (msg.state === "interrupted") lastAssistantState = "interrupted";
909
+ }
910
+ }
911
+ if (assistantParts.length > 0) {
912
+ addMessage(
913
+ createAssistantMessage(assistantParts.join("\n\n"), { state: lastAssistantState })
914
+ );
915
+ }
916
+ addMessage(createSystemMessage2("Interrupted by user."));
872
917
  } else {
873
918
  const errMsg = err instanceof Error ? err.message : String(err);
874
- addMessage({ role: "system", content: `Error: ${errMsg}` });
919
+ addMessage(createSystemMessage2(`Error: ${errMsg}`));
875
920
  }
876
921
  } finally {
877
922
  setIsThinking(false);
@@ -924,7 +969,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
924
969
  const runInFork = createForkRunner(session);
925
970
  const result = await executeSkill(skill, args, { runInFork });
926
971
  if (result.mode === "fork") {
927
- addMessage({ role: "assistant", content: result.result ?? "(empty response)" });
972
+ addMessage(createAssistantMessage(result.result ?? "(empty response)"));
928
973
  syncContextState(session, setContextState);
929
974
  return;
930
975
  }
@@ -959,7 +1004,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
959
1004
  hookInput
960
1005
  );
961
1006
  }
962
- addMessage({ role: "user", content: input });
1007
+ addMessage(createUserMessage(input));
963
1008
  return runSessionPrompt(
964
1009
  input,
965
1010
  session,
@@ -1435,6 +1480,7 @@ function usePluginCallbacks(cwd) {
1435
1480
  // src/ui/MessageList.tsx
1436
1481
  import React2 from "react";
1437
1482
  import { Box as Box2, Text as Text2 } from "ink";
1483
+ import { isToolMessage, isAssistantMessage } from "@robota-sdk/agent-core";
1438
1484
 
1439
1485
  // src/ui/render-markdown.ts
1440
1486
  import { marked } from "marked";
@@ -1494,7 +1540,7 @@ function DiffBlock({ file, lines }) {
1494
1540
  }
1495
1541
 
1496
1542
  // src/ui/MessageList.tsx
1497
- import { jsx, jsxs as jsxs2 } from "react/jsx-runtime";
1543
+ import { Fragment, jsx, jsxs as jsxs2 } from "react/jsx-runtime";
1498
1544
  function RoleLabel({ role }) {
1499
1545
  switch (role) {
1500
1546
  case "user":
@@ -1520,9 +1566,14 @@ function RoleLabel({ role }) {
1520
1566
  }
1521
1567
  }
1522
1568
  function ToolMessage({ message }) {
1569
+ if (!isToolMessage(message)) {
1570
+ return /* @__PURE__ */ jsx(Fragment, {});
1571
+ }
1572
+ const toolName = message.name;
1573
+ const content = message.content;
1523
1574
  let summaries = null;
1524
1575
  try {
1525
- const parsed = JSON.parse(message.content);
1576
+ const parsed = JSON.parse(content);
1526
1577
  if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
1527
1578
  summaries = parsed;
1528
1579
  }
@@ -1535,9 +1586,9 @@ function ToolMessage({ message }) {
1535
1586
  "Tool:",
1536
1587
  " "
1537
1588
  ] }),
1538
- message.toolName && /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
1589
+ toolName && /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
1539
1590
  "[",
1540
- message.toolName,
1591
+ toolName,
1541
1592
  "]"
1542
1593
  ] })
1543
1594
  ] }),
@@ -1553,16 +1604,16 @@ function ToolMessage({ message }) {
1553
1604
  ] }, i))
1554
1605
  ] });
1555
1606
  }
1556
- const lines = message.content.split("\n").filter((l) => l.trim());
1607
+ const lines = content.split("\n").filter((l) => l.trim());
1557
1608
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
1558
1609
  /* @__PURE__ */ jsxs2(Box2, { children: [
1559
1610
  /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
1560
1611
  "Tool:",
1561
1612
  " "
1562
1613
  ] }),
1563
- message.toolName && /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
1614
+ toolName && /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
1564
1615
  "[",
1565
- message.toolName,
1616
+ toolName,
1566
1617
  "]"
1567
1618
  ] })
1568
1619
  ] }),
@@ -1578,21 +1629,15 @@ function ToolMessage({ message }) {
1578
1629
  var MessageItem = React2.memo(function MessageItem2({
1579
1630
  message
1580
1631
  }) {
1581
- if (message.role === "tool") {
1632
+ if (isToolMessage(message)) {
1582
1633
  return /* @__PURE__ */ jsx(ToolMessage, { message });
1583
1634
  }
1635
+ const content = message.content ?? "";
1636
+ const isInterrupted = message.state === "interrupted";
1584
1637
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
1585
- /* @__PURE__ */ jsxs2(Box2, { children: [
1586
- /* @__PURE__ */ jsx(RoleLabel, { role: message.role }),
1587
- message.toolName && /* @__PURE__ */ jsxs2(Text2, { color: "magenta", dimColor: true, children: [
1588
- "[",
1589
- message.toolName,
1590
- "]",
1591
- " "
1592
- ] })
1593
- ] }),
1638
+ /* @__PURE__ */ jsx(Box2, { children: /* @__PURE__ */ jsx(RoleLabel, { role: message.role }) }),
1594
1639
  /* @__PURE__ */ jsx(Text2, { children: " " }),
1595
- /* @__PURE__ */ jsx(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text2, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
1640
+ /* @__PURE__ */ jsx(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text2, { wrap: "wrap", children: isAssistantMessage(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
1596
1641
  ] });
1597
1642
  });
1598
1643
  function MessageList({ messages }) {
@@ -1890,7 +1935,12 @@ function useAutocomplete(value, registry) {
1890
1935
  }
1891
1936
  };
1892
1937
  }
1893
- function InputArea({ onSubmit, isDisabled, registry }) {
1938
+ function InputArea({
1939
+ onSubmit,
1940
+ isDisabled,
1941
+ isAborting,
1942
+ registry
1943
+ }) {
1894
1944
  const [value, setValue] = useState5("");
1895
1945
  const pasteStore = useRef4(/* @__PURE__ */ new Map());
1896
1946
  const pasteIdRef = useRef4(0);
@@ -1971,19 +2021,27 @@ function InputArea({ onSubmit, isDisabled, registry }) {
1971
2021
  isSubcommandMode
1972
2022
  }
1973
2023
  ),
1974
- /* @__PURE__ */ jsx6(Box5, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ jsx6(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ jsxs5(Box5, { children: [
1975
- /* @__PURE__ */ jsx6(Text7, { color: "green", bold: true, children: "> " }),
1976
- /* @__PURE__ */ jsx6(
1977
- CjkTextInput,
1978
- {
1979
- value,
1980
- onChange: setValue,
1981
- onSubmit: handleSubmit,
1982
- onPaste: handlePaste,
1983
- placeholder: "Type a message or /help"
1984
- }
1985
- )
1986
- ] }) })
2024
+ /* @__PURE__ */ jsx6(
2025
+ Box5,
2026
+ {
2027
+ borderStyle: "single",
2028
+ borderColor: isAborting ? "yellow" : isDisabled ? "gray" : "green",
2029
+ paddingLeft: 1,
2030
+ children: isDisabled ? isAborting ? /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: " Interrupting..." }) : /* @__PURE__ */ jsx6(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ jsxs5(Box5, { children: [
2031
+ /* @__PURE__ */ jsx6(Text7, { color: "green", bold: true, children: "> " }),
2032
+ /* @__PURE__ */ jsx6(
2033
+ CjkTextInput,
2034
+ {
2035
+ value,
2036
+ onChange: setValue,
2037
+ onSubmit: handleSubmit,
2038
+ onPaste: handlePaste,
2039
+ placeholder: "Type a message or /help"
2040
+ }
2041
+ )
2042
+ ] })
2043
+ }
2044
+ )
1987
2045
  ] });
1988
2046
  }
1989
2047
 
@@ -2096,7 +2154,7 @@ function PermissionPrompt({ request }) {
2096
2154
 
2097
2155
  // src/ui/StreamingIndicator.tsx
2098
2156
  import { Box as Box8, Text as Text10 } from "ink";
2099
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2157
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2100
2158
  function getToolStyle(t) {
2101
2159
  if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
2102
2160
  if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
@@ -2107,7 +2165,7 @@ function StreamingIndicator({ text, activeTools }) {
2107
2165
  const hasTools = activeTools.length > 0;
2108
2166
  const hasText = text.length > 0;
2109
2167
  if (!hasTools && !hasText) {
2110
- return /* @__PURE__ */ jsx9(Text10, { color: "yellow", children: "Thinking..." });
2168
+ return /* @__PURE__ */ jsx9(Fragment2, {});
2111
2169
  }
2112
2170
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2113
2171
  hasTools && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
@@ -2631,6 +2689,7 @@ function App(props) {
2631
2689
  const pendingModelChangeRef = useRef8(null);
2632
2690
  const [pendingModelId, setPendingModelId] = useState10(null);
2633
2691
  const [showPluginTUI, setShowPluginTUI] = useState10(false);
2692
+ const [isAborting, setIsAborting] = useState10(false);
2634
2693
  const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
2635
2694
  const handleSlashCommand = useSlashCommands(
2636
2695
  session,
@@ -2654,11 +2713,16 @@ function App(props) {
2654
2713
  );
2655
2714
  useInput7(
2656
2715
  (_input, key) => {
2657
- if (key.ctrl && _input === "c") exit();
2658
- if (key.escape && isThinking) session.abort();
2716
+ if (key.escape && isThinking) {
2717
+ setIsAborting(true);
2718
+ session.abort();
2719
+ }
2659
2720
  },
2660
2721
  { isActive: !permissionRequest && !showPluginTUI }
2661
2722
  );
2723
+ useEffect3(() => {
2724
+ if (!isThinking) setIsAborting(false);
2725
+ }, [isThinking]);
2662
2726
  return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
2663
2727
  /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2664
2728
  /* @__PURE__ */ jsx13(Text13, { color: "cyan", bold: true, children: `
@@ -2689,19 +2753,21 @@ function App(props) {
2689
2753
  try {
2690
2754
  const settingsPath = getUserSettingsPath();
2691
2755
  updateModelInSettings(settingsPath, pendingModelId);
2692
- addMessage({
2693
- role: "system",
2694
- content: `Model changed to ${getModelName(pendingModelId)}. Restarting...`
2695
- });
2756
+ addMessage(
2757
+ createSystemMessage3(
2758
+ `Model changed to ${getModelName(pendingModelId)}. Restarting...`
2759
+ )
2760
+ );
2696
2761
  setTimeout(() => exit(), EXIT_DELAY_MS2);
2697
2762
  } catch (err) {
2698
- addMessage({
2699
- role: "system",
2700
- content: `Failed: ${err instanceof Error ? err.message : String(err)}`
2701
- });
2763
+ addMessage(
2764
+ createSystemMessage3(
2765
+ `Failed: ${err instanceof Error ? err.message : String(err)}`
2766
+ )
2767
+ );
2702
2768
  }
2703
2769
  } else {
2704
- addMessage({ role: "system", content: "Model change cancelled." });
2770
+ addMessage(createSystemMessage3("Model change cancelled."));
2705
2771
  }
2706
2772
  }
2707
2773
  }
@@ -2711,7 +2777,7 @@ function App(props) {
2711
2777
  {
2712
2778
  callbacks: pluginCallbacks,
2713
2779
  onClose: () => setShowPluginTUI(false),
2714
- addMessage: (msg) => addMessage(msg)
2780
+ addMessage: (msg) => addMessage(createSystemMessage3(msg.content))
2715
2781
  }
2716
2782
  ),
2717
2783
  /* @__PURE__ */ jsx13(
@@ -2732,6 +2798,7 @@ function App(props) {
2732
2798
  {
2733
2799
  onSubmit: handleSubmit,
2734
2800
  isDisabled: isThinking || !!permissionRequest || showPluginTUI,
2801
+ isAborting,
2735
2802
  registry
2736
2803
  }
2737
2804
  ),
@@ -2754,12 +2821,15 @@ function renderApp(options) {
2754
2821
  const instance = render(/* @__PURE__ */ jsx14(App, { ...options }), {
2755
2822
  exitOnCtrlC: true
2756
2823
  });
2757
- instance.waitUntilExit().catch((err) => {
2824
+ instance.waitUntilExit().then(() => {
2825
+ process.exit(0);
2826
+ }).catch((err) => {
2758
2827
  if (err) {
2759
2828
  process.stderr.write(`
2760
2829
  [EXIT ERROR] ${err}
2761
2830
  `);
2762
2831
  }
2832
+ process.exit(1);
2763
2833
  });
2764
2834
  }
2765
2835