@robota-sdk/agent-cli 3.0.0-beta.17 → 3.0.0-beta.19

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.
@@ -147,8 +147,123 @@ var PrintTerminal = class {
147
147
  import { render } from "ink";
148
148
 
149
149
  // src/ui/App.tsx
150
- import { useState as useState5, useCallback as useCallback3, useRef as useRef3 } from "react";
151
- import { Box as Box7, Text as Text9, useApp, useInput as useInput5 } from "ink";
150
+ import { useState as useState7, useRef as useRef5 } from "react";
151
+ import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
152
+ import { getModelName } from "@robota-sdk/agent-core";
153
+
154
+ // src/ui/hooks/useSession.ts
155
+ import { useState, useCallback, useRef } from "react";
156
+ import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
157
+ var TOOL_ARG_DISPLAY_MAX = 80;
158
+ var TOOL_ARG_TRUNCATE_AT = 77;
159
+ var NOOP_TERMINAL = {
160
+ write: () => {
161
+ },
162
+ writeLine: () => {
163
+ },
164
+ writeMarkdown: () => {
165
+ },
166
+ writeError: () => {
167
+ },
168
+ prompt: () => Promise.resolve(""),
169
+ select: () => Promise.resolve(0),
170
+ spinner: () => ({ stop: () => {
171
+ }, update: () => {
172
+ } })
173
+ };
174
+ function useSession(props) {
175
+ const [permissionRequest, setPermissionRequest] = useState(null);
176
+ const [streamingText, setStreamingText] = useState("");
177
+ const [activeTools, setActiveTools] = useState([]);
178
+ const permissionQueueRef = useRef([]);
179
+ const processingRef = useRef(false);
180
+ const processNextPermission = useCallback(() => {
181
+ if (processingRef.current) return;
182
+ const next = permissionQueueRef.current[0];
183
+ if (!next) {
184
+ setPermissionRequest(null);
185
+ return;
186
+ }
187
+ processingRef.current = true;
188
+ setPermissionRequest({
189
+ toolName: next.toolName,
190
+ toolArgs: next.toolArgs,
191
+ resolve: (result) => {
192
+ permissionQueueRef.current.shift();
193
+ processingRef.current = false;
194
+ setPermissionRequest(null);
195
+ next.resolve(result);
196
+ setTimeout(() => processNextPermission(), 0);
197
+ }
198
+ });
199
+ }, []);
200
+ const sessionRef = useRef(null);
201
+ if (sessionRef.current === null) {
202
+ const permissionHandler = (toolName, toolArgs) => {
203
+ return new Promise((resolve) => {
204
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
205
+ processNextPermission();
206
+ });
207
+ };
208
+ const onTextDelta = (delta) => {
209
+ setStreamingText((prev) => prev + delta);
210
+ };
211
+ const onToolExecution = (event) => {
212
+ if (event.type === "start") {
213
+ let firstArg = "";
214
+ if (event.toolArgs) {
215
+ const firstVal = Object.values(event.toolArgs)[0];
216
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
217
+ firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
218
+ }
219
+ setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
220
+ } else {
221
+ setActiveTools(
222
+ (prev) => prev.map(
223
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
224
+ )
225
+ );
226
+ }
227
+ };
228
+ const paths = projectPaths(props.cwd ?? process.cwd());
229
+ sessionRef.current = createSession({
230
+ config: props.config,
231
+ context: props.context,
232
+ terminal: NOOP_TERMINAL,
233
+ sessionLogger: new FileSessionLogger(paths.logs),
234
+ projectInfo: props.projectInfo,
235
+ sessionStore: props.sessionStore,
236
+ permissionMode: props.permissionMode,
237
+ maxTurns: props.maxTurns,
238
+ permissionHandler,
239
+ onTextDelta,
240
+ onToolExecution
241
+ });
242
+ }
243
+ const clearStreamingText = useCallback(() => {
244
+ setStreamingText("");
245
+ setActiveTools([]);
246
+ }, []);
247
+ return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
248
+ }
249
+
250
+ // src/ui/hooks/useMessages.ts
251
+ import { useState as useState2, useCallback as useCallback2 } from "react";
252
+ var msgIdCounter = 0;
253
+ function nextId() {
254
+ msgIdCounter += 1;
255
+ return `msg_${msgIdCounter}`;
256
+ }
257
+ function useMessages() {
258
+ const [messages, setMessages] = useState2([]);
259
+ const addMessage = useCallback2((msg) => {
260
+ setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
261
+ }, []);
262
+ return { messages, setMessages, addMessage };
263
+ }
264
+
265
+ // src/ui/hooks/useSlashCommands.ts
266
+ import { useCallback as useCallback3 } from "react";
152
267
 
153
268
  // src/commands/slash-executor.ts
154
269
  var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
@@ -273,9 +388,133 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
273
388
  }
274
389
  }
275
390
 
276
- // src/ui/App.tsx
277
- import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
278
- import { getModelName } from "@robota-sdk/agent-core";
391
+ // src/ui/hooks/useSlashCommands.ts
392
+ var EXIT_DELAY_MS = 500;
393
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
394
+ return useCallback3(
395
+ async (input) => {
396
+ const parts = input.slice(1).split(/\s+/);
397
+ const cmd = parts[0]?.toLowerCase() ?? "";
398
+ const args = parts.slice(1).join(" ");
399
+ const clearMessages = () => setMessages([]);
400
+ const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
401
+ if (result.pendingModelId) {
402
+ pendingModelChangeRef.current = result.pendingModelId;
403
+ setPendingModelId(result.pendingModelId);
404
+ }
405
+ if (result.exitRequested) {
406
+ setTimeout(() => exit(), EXIT_DELAY_MS);
407
+ }
408
+ return result.handled;
409
+ },
410
+ [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
411
+ );
412
+ }
413
+
414
+ // src/ui/hooks/useSubmitHandler.ts
415
+ import { useCallback as useCallback4 } from "react";
416
+
417
+ // src/utils/tool-call-extractor.ts
418
+ var TOOL_ARG_MAX_LENGTH = 80;
419
+ var TOOL_ARG_TRUNCATE_LENGTH = 77;
420
+ function extractToolCalls(history, startIndex) {
421
+ const lines = [];
422
+ for (let i = startIndex; i < history.length; i++) {
423
+ const msg = history[i];
424
+ if (msg.role === "assistant" && msg.toolCalls) {
425
+ for (const tc of msg.toolCalls) {
426
+ const value = parseFirstArgValue(tc.function.arguments);
427
+ const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
428
+ lines.push(`${tc.function.name}(${truncated})`);
429
+ }
430
+ }
431
+ }
432
+ return lines;
433
+ }
434
+ function parseFirstArgValue(argsJson) {
435
+ try {
436
+ const parsed = JSON.parse(argsJson);
437
+ const firstVal = Object.values(parsed)[0];
438
+ return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
439
+ } catch {
440
+ return argsJson;
441
+ }
442
+ }
443
+
444
+ // src/utils/skill-prompt.ts
445
+ function buildSkillPrompt(input, registry) {
446
+ const parts = input.slice(1).split(/\s+/);
447
+ const cmd = parts[0]?.toLowerCase() ?? "";
448
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
449
+ if (!skillCmd) return null;
450
+ const args = parts.slice(1).join(" ").trim();
451
+ const userInstruction = args || skillCmd.description;
452
+ if (skillCmd.skillContent) {
453
+ return `<skill name="${cmd}">
454
+ ${skillCmd.skillContent}
455
+ </skill>
456
+
457
+ Execute the "${cmd}" skill: ${userInstruction}`;
458
+ }
459
+ return `Use the "${cmd}" skill: ${userInstruction}`;
460
+ }
461
+
462
+ // src/ui/hooks/useSubmitHandler.ts
463
+ function syncContextState(session, setter) {
464
+ const ctx = session.getContextState();
465
+ setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
466
+ }
467
+ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
468
+ setIsThinking(true);
469
+ clearStreamingText();
470
+ const historyBefore = session.getHistory().length;
471
+ try {
472
+ const response = await session.run(prompt);
473
+ clearStreamingText();
474
+ const history = session.getHistory();
475
+ const toolLines = extractToolCalls(
476
+ history,
477
+ historyBefore
478
+ );
479
+ if (toolLines.length > 0) {
480
+ addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
481
+ }
482
+ addMessage({ role: "assistant", content: response || "(empty response)" });
483
+ syncContextState(session, setContextState);
484
+ } catch (err) {
485
+ clearStreamingText();
486
+ if (err instanceof DOMException && err.name === "AbortError") {
487
+ addMessage({ role: "system", content: "Cancelled." });
488
+ } else {
489
+ const errMsg = err instanceof Error ? err.message : String(err);
490
+ addMessage({ role: "system", content: `Error: ${errMsg}` });
491
+ }
492
+ } finally {
493
+ setIsThinking(false);
494
+ }
495
+ }
496
+ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
497
+ return useCallback4(
498
+ async (input) => {
499
+ if (input.startsWith("/")) {
500
+ const handled = await handleSlashCommand(input);
501
+ if (handled) {
502
+ syncContextState(session, setContextState);
503
+ return;
504
+ }
505
+ const prompt = buildSkillPrompt(input, registry);
506
+ if (!prompt) return;
507
+ return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
508
+ }
509
+ addMessage({ role: "user", content: input });
510
+ return runSessionPrompt(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
511
+ },
512
+ [session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
513
+ );
514
+ }
515
+
516
+ // src/ui/hooks/useCommandRegistry.ts
517
+ import { useRef as useRef2 } from "react";
279
518
 
280
519
  // src/commands/command-registry.ts
281
520
  var CommandRegistry = class {
@@ -429,6 +668,18 @@ var SkillCommandSource = class {
429
668
  }
430
669
  };
431
670
 
671
+ // src/ui/hooks/useCommandRegistry.ts
672
+ function useCommandRegistry(cwd) {
673
+ const registryRef = useRef2(null);
674
+ if (registryRef.current === null) {
675
+ const registry = new CommandRegistry();
676
+ registry.addSource(new BuiltinCommandSource());
677
+ registry.addSource(new SkillCommandSource(cwd));
678
+ registryRef.current = registry;
679
+ }
680
+ return registryRef.current;
681
+ }
682
+
432
683
  // src/ui/MessageList.tsx
433
684
  import { Box, Text } from "ink";
434
685
 
@@ -549,11 +800,11 @@ function StatusBar({
549
800
  }
550
801
 
551
802
  // src/ui/InputArea.tsx
552
- import React3, { useState as useState3, useCallback, useMemo } from "react";
803
+ import React3, { useState as useState5, useCallback as useCallback5, useMemo } from "react";
553
804
  import { Box as Box4, Text as Text6, useInput as useInput2 } from "ink";
554
805
 
555
806
  // src/ui/CjkTextInput.tsx
556
- import { useRef, useState } from "react";
807
+ import { useRef as useRef3, useState as useState3 } from "react";
557
808
  import { Text as Text3, useInput } from "ink";
558
809
  import chalk from "chalk";
559
810
  import { jsx as jsx3 } from "react/jsx-runtime";
@@ -573,9 +824,9 @@ function CjkTextInput({
573
824
  focus = true,
574
825
  showCursor = true
575
826
  }) {
576
- const valueRef = useRef(value);
577
- const cursorRef = useRef(value.length);
578
- const [, forceRender] = useState(0);
827
+ const valueRef = useRef3(value);
828
+ const cursorRef = useRef3(value.length);
829
+ const [, forceRender] = useState3(0);
579
830
  if (value !== valueRef.current) {
580
831
  valueRef.current = value;
581
832
  if (cursorRef.current > value.length) {
@@ -652,14 +903,14 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
652
903
  }
653
904
 
654
905
  // src/ui/WaveText.tsx
655
- import { useState as useState2, useEffect } from "react";
906
+ import { useState as useState4, useEffect } from "react";
656
907
  import { Text as Text4 } from "ink";
657
908
  import { jsx as jsx4 } from "react/jsx-runtime";
658
909
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
659
910
  var INTERVAL_MS = 400;
660
911
  var CHARS_PER_GROUP = 4;
661
912
  function WaveText({ text }) {
662
- const [tick, setTick] = useState2(0);
913
+ const [tick, setTick] = useState4(0);
663
914
  useEffect(() => {
664
915
  const timer = setInterval(() => {
665
916
  setTick((prev) => prev + 1);
@@ -726,8 +977,8 @@ function parseSlashInput(value) {
726
977
  return { isSlash: true, parentCommand: parent, filter: rest };
727
978
  }
728
979
  function useAutocomplete(value, registry) {
729
- const [selectedIndex, setSelectedIndex] = useState3(0);
730
- const [dismissed, setDismissed] = useState3(false);
980
+ const [selectedIndex, setSelectedIndex] = useState5(0);
981
+ const [dismissed, setDismissed] = useState5(false);
731
982
  const prevValueRef = React3.useRef(value);
732
983
  if (prevValueRef.current !== value) {
733
984
  prevValueRef.current = value;
@@ -769,7 +1020,7 @@ function useAutocomplete(value, registry) {
769
1020
  };
770
1021
  }
771
1022
  function InputArea({ onSubmit, isDisabled, registry }) {
772
- const [value, setValue] = useState3("");
1023
+ const [value, setValue] = useState5("");
773
1024
  const {
774
1025
  showPopup,
775
1026
  filteredCommands,
@@ -778,7 +1029,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
778
1029
  isSubcommandMode,
779
1030
  setShowPopup
780
1031
  } = useAutocomplete(value, registry);
781
- const handleSubmit = useCallback(
1032
+ const handleSubmit = useCallback5(
782
1033
  (text) => {
783
1034
  const trimmed = text.trim();
784
1035
  if (trimmed.length === 0) return;
@@ -791,7 +1042,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
791
1042
  },
792
1043
  [showPopup, filteredCommands, selectedIndex, onSubmit]
793
1044
  );
794
- const selectCommand = useCallback(
1045
+ const selectCommand = useCallback5(
795
1046
  (cmd) => {
796
1047
  const parsed = parseSlashInput(value);
797
1048
  if (parsed.parentCommand) {
@@ -852,7 +1103,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
852
1103
  }
853
1104
 
854
1105
  // src/ui/ConfirmPrompt.tsx
855
- import { useState as useState4, useCallback as useCallback2, useRef as useRef2 } from "react";
1106
+ import { useState as useState6, useCallback as useCallback6, useRef as useRef4 } from "react";
856
1107
  import { Box as Box5, Text as Text7, useInput as useInput3 } from "ink";
857
1108
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
858
1109
  function ConfirmPrompt({
@@ -860,9 +1111,9 @@ function ConfirmPrompt({
860
1111
  options = ["Yes", "No"],
861
1112
  onSelect
862
1113
  }) {
863
- const [selected, setSelected] = useState4(0);
864
- const resolvedRef = useRef2(false);
865
- const doSelect = useCallback2(
1114
+ const [selected, setSelected] = useState6(0);
1115
+ const resolvedRef = useRef4(false);
1116
+ const doSelect = useCallback6(
866
1117
  (index) => {
867
1118
  if (resolvedRef.current) return;
868
1119
  resolvedRef.current = true;
@@ -958,254 +1209,54 @@ function PermissionPrompt({ request }) {
958
1209
  ] });
959
1210
  }
960
1211
 
961
- // src/utils/tool-call-extractor.ts
962
- var TOOL_ARG_MAX_LENGTH = 80;
963
- var TOOL_ARG_TRUNCATE_LENGTH = 77;
964
- function extractToolCalls(history, startIndex) {
965
- const lines = [];
966
- for (let i = startIndex; i < history.length; i++) {
967
- const msg = history[i];
968
- if (msg.role === "assistant" && msg.toolCalls) {
969
- for (const tc of msg.toolCalls) {
970
- const value = parseFirstArgValue(tc.function.arguments);
971
- const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
972
- lines.push(`${tc.function.name}(${truncated})`);
973
- }
974
- }
975
- }
976
- return lines;
977
- }
978
- function parseFirstArgValue(argsJson) {
979
- try {
980
- const parsed = JSON.parse(argsJson);
981
- const firstVal = Object.values(parsed)[0];
982
- return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
983
- } catch {
984
- return argsJson;
985
- }
986
- }
987
-
988
- // src/ui/App.tsx
1212
+ // src/ui/StreamingIndicator.tsx
1213
+ import { Box as Box7, Text as Text9 } from "ink";
989
1214
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
990
- var msgIdCounter = 0;
991
- function nextId() {
992
- msgIdCounter += 1;
993
- return `msg_${msgIdCounter}`;
994
- }
995
- var NOOP_TERMINAL = {
996
- write: () => {
997
- },
998
- writeLine: () => {
999
- },
1000
- writeMarkdown: () => {
1001
- },
1002
- writeError: () => {
1003
- },
1004
- prompt: () => Promise.resolve(""),
1005
- select: () => Promise.resolve(0),
1006
- spinner: () => ({ stop: () => {
1007
- }, update: () => {
1008
- } })
1009
- };
1010
- function useSession(props) {
1011
- const [permissionRequest, setPermissionRequest] = useState5(null);
1012
- const [streamingText, setStreamingText] = useState5("");
1013
- const permissionQueueRef = useRef3([]);
1014
- const processingRef = useRef3(false);
1015
- const processNextPermission = useCallback3(() => {
1016
- if (processingRef.current) return;
1017
- const next = permissionQueueRef.current[0];
1018
- if (!next) {
1019
- setPermissionRequest(null);
1020
- return;
1021
- }
1022
- processingRef.current = true;
1023
- setPermissionRequest({
1024
- toolName: next.toolName,
1025
- toolArgs: next.toolArgs,
1026
- resolve: (result) => {
1027
- permissionQueueRef.current.shift();
1028
- processingRef.current = false;
1029
- setPermissionRequest(null);
1030
- next.resolve(result);
1031
- setTimeout(() => processNextPermission(), 0);
1032
- }
1033
- });
1034
- }, []);
1035
- const sessionRef = useRef3(null);
1036
- if (sessionRef.current === null) {
1037
- const permissionHandler = (toolName, toolArgs) => {
1038
- return new Promise((resolve) => {
1039
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
1040
- processNextPermission();
1041
- });
1042
- };
1043
- const onTextDelta = (delta) => {
1044
- setStreamingText((prev) => prev + delta);
1045
- };
1046
- const paths = projectPaths(props.cwd ?? process.cwd());
1047
- sessionRef.current = createSession({
1048
- config: props.config,
1049
- context: props.context,
1050
- terminal: NOOP_TERMINAL,
1051
- sessionLogger: new FileSessionLogger(paths.logs),
1052
- projectInfo: props.projectInfo,
1053
- sessionStore: props.sessionStore,
1054
- permissionMode: props.permissionMode,
1055
- maxTurns: props.maxTurns,
1056
- permissionHandler,
1057
- onTextDelta
1058
- });
1215
+ function StreamingIndicator({ text, activeTools }) {
1216
+ const hasTools = activeTools.length > 0;
1217
+ const hasText = text.length > 0;
1218
+ if (!hasTools && !hasText) {
1219
+ return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
1059
1220
  }
1060
- const clearStreamingText = useCallback3(() => setStreamingText(""), []);
1061
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
1062
- }
1063
- function useMessages() {
1064
- const [messages, setMessages] = useState5([]);
1065
- const addMessage = useCallback3((msg) => {
1066
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
1067
- }, []);
1068
- return { messages, setMessages, addMessage };
1069
- }
1070
- var EXIT_DELAY_MS = 500;
1071
- function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
1072
- return useCallback3(
1073
- async (input) => {
1074
- const parts = input.slice(1).split(/\s+/);
1075
- const cmd = parts[0]?.toLowerCase() ?? "";
1076
- const args = parts.slice(1).join(" ");
1077
- const clearMessages = () => setMessages([]);
1078
- const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
1079
- if (result.pendingModelId) {
1080
- pendingModelChangeRef.current = result.pendingModelId;
1081
- setPendingModelId(result.pendingModelId);
1082
- }
1083
- if (result.exitRequested) {
1084
- setTimeout(() => exit(), EXIT_DELAY_MS);
1085
- }
1086
- return result.handled;
1087
- },
1088
- [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
1089
- );
1090
- }
1091
- function StreamingIndicator({ text }) {
1092
- if (text) {
1093
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1094
- /* @__PURE__ */ jsxs7(Text9, { color: "cyan", bold: true, children: [
1095
- "Robota:",
1096
- " "
1097
- ] }),
1221
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1222
+ hasTools && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
1223
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", bold: true, children: "Tools:" }),
1224
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
1225
+ activeTools.map((t, i) => /* @__PURE__ */ jsxs7(Text9, { color: t.isRunning ? "yellow" : "green", children: [
1226
+ " ",
1227
+ t.isRunning ? "\u27F3" : "\u2713",
1228
+ " ",
1229
+ t.toolName,
1230
+ "(",
1231
+ t.firstArg,
1232
+ ")"
1233
+ ] }, `${t.toolName}-${i}`))
1234
+ ] }),
1235
+ hasText && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
1236
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "Robota:" }),
1098
1237
  /* @__PURE__ */ jsx9(Text9, { children: " " }),
1099
1238
  /* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { wrap: "wrap", children: renderMarkdown(text) }) })
1100
- ] });
1101
- }
1102
- return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
1103
- }
1104
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
1105
- setIsThinking(true);
1106
- clearStreamingText();
1107
- const historyBefore = session.getHistory().length;
1108
- try {
1109
- const response = await session.run(prompt);
1110
- clearStreamingText();
1111
- const history = session.getHistory();
1112
- const toolLines = extractToolCalls(
1113
- history,
1114
- historyBefore
1115
- );
1116
- if (toolLines.length > 0) {
1117
- addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
1118
- }
1119
- addMessage({ role: "assistant", content: response || "(empty response)" });
1120
- setContextPercentage(session.getContextState().usedPercentage);
1121
- } catch (err) {
1122
- clearStreamingText();
1123
- if (err instanceof DOMException && err.name === "AbortError") {
1124
- addMessage({ role: "system", content: "Cancelled." });
1125
- } else {
1126
- const errMsg = err instanceof Error ? err.message : String(err);
1127
- addMessage({ role: "system", content: `Error: ${errMsg}` });
1128
- }
1129
- } finally {
1130
- setIsThinking(false);
1131
- }
1239
+ ] })
1240
+ ] });
1132
1241
  }
1133
- function buildSkillPrompt(input, registry) {
1134
- const parts = input.slice(1).split(/\s+/);
1135
- const cmd = parts[0]?.toLowerCase() ?? "";
1136
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
1137
- if (!skillCmd) return null;
1138
- const args = parts.slice(1).join(" ").trim();
1139
- const userInstruction = args || skillCmd.description;
1140
- if (skillCmd.skillContent) {
1141
- return `<skill name="${cmd}">
1142
- ${skillCmd.skillContent}
1143
- </skill>
1144
1242
 
1145
- Execute the "${cmd}" skill: ${userInstruction}`;
1146
- }
1147
- return `Use the "${cmd}" skill: ${userInstruction}`;
1148
- }
1149
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
1150
- return useCallback3(
1151
- async (input) => {
1152
- if (input.startsWith("/")) {
1153
- const handled = await handleSlashCommand(input);
1154
- if (handled) {
1155
- setContextPercentage(session.getContextState().usedPercentage);
1156
- return;
1157
- }
1158
- const prompt = buildSkillPrompt(input, registry);
1159
- if (!prompt) return;
1160
- return runSessionPrompt(
1161
- prompt,
1162
- session,
1163
- addMessage,
1164
- clearStreamingText,
1165
- setIsThinking,
1166
- setContextPercentage
1167
- );
1168
- }
1169
- addMessage({ role: "user", content: input });
1170
- return runSessionPrompt(
1171
- input,
1172
- session,
1173
- addMessage,
1174
- clearStreamingText,
1175
- setIsThinking,
1176
- setContextPercentage
1177
- );
1178
- },
1179
- [
1180
- session,
1181
- addMessage,
1182
- handleSlashCommand,
1183
- clearStreamingText,
1184
- setIsThinking,
1185
- setContextPercentage,
1186
- registry
1187
- ]
1188
- );
1189
- }
1190
- function useCommandRegistry(cwd) {
1191
- const registryRef = useRef3(null);
1192
- if (registryRef.current === null) {
1193
- const registry = new CommandRegistry();
1194
- registry.addSource(new BuiltinCommandSource());
1195
- registry.addSource(new SkillCommandSource(cwd));
1196
- registryRef.current = registry;
1197
- }
1198
- return registryRef.current;
1199
- }
1243
+ // src/ui/App.tsx
1244
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1245
+ var EXIT_DELAY_MS2 = 500;
1200
1246
  function App(props) {
1201
1247
  const { exit } = useApp();
1202
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
1248
+ const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
1203
1249
  const { messages, setMessages, addMessage } = useMessages();
1204
- const [isThinking, setIsThinking] = useState5(false);
1205
- const [contextPercentage, setContextPercentage] = useState5(0);
1250
+ const [isThinking, setIsThinking] = useState7(false);
1251
+ const initialCtx = session.getContextState();
1252
+ const [contextState, setContextState] = useState7({
1253
+ percentage: initialCtx.usedPercentage,
1254
+ usedTokens: initialCtx.usedTokens,
1255
+ maxTokens: initialCtx.maxTokens
1256
+ });
1206
1257
  const registry = useCommandRegistry(props.cwd ?? process.cwd());
1207
- const pendingModelChangeRef = useRef3(null);
1208
- const [pendingModelId, setPendingModelId] = useState5(null);
1258
+ const pendingModelChangeRef = useRef5(null);
1259
+ const [pendingModelId, setPendingModelId] = useState7(null);
1209
1260
  const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
1210
1261
  const handleSubmit = useSubmitHandler(
1211
1262
  session,
@@ -1213,7 +1264,7 @@ function App(props) {
1213
1264
  handleSlashCommand,
1214
1265
  clearStreamingText,
1215
1266
  setIsThinking,
1216
- setContextPercentage,
1267
+ setContextState,
1217
1268
  registry
1218
1269
  );
1219
1270
  useInput5(
@@ -1223,26 +1274,26 @@ function App(props) {
1223
1274
  },
1224
1275
  { isActive: !permissionRequest }
1225
1276
  );
1226
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1227
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1228
- /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: `
1277
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1278
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1279
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: `
1229
1280
  ____ ___ ____ ___ _____ _
1230
1281
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1231
1282
  | |_) | | | | _ \\| | | || | / _ \\
1232
1283
  | _ <| |_| | |_) | |_| || |/ ___ \\
1233
1284
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1234
1285
  ` }),
1235
- /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
1286
+ /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
1236
1287
  " v",
1237
1288
  props.version ?? "0.0.0"
1238
1289
  ] })
1239
1290
  ] }),
1240
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1241
- /* @__PURE__ */ jsx9(MessageList, { messages }),
1242
- isThinking && /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx9(StreamingIndicator, { text: streamingText }) })
1291
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1292
+ /* @__PURE__ */ jsx10(MessageList, { messages }),
1293
+ isThinking && /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx10(StreamingIndicator, { text: streamingText, activeTools }) })
1243
1294
  ] }),
1244
- permissionRequest && /* @__PURE__ */ jsx9(PermissionPrompt, { request: permissionRequest }),
1245
- pendingModelId && /* @__PURE__ */ jsx9(
1295
+ permissionRequest && /* @__PURE__ */ jsx10(PermissionPrompt, { request: permissionRequest }),
1296
+ pendingModelId && /* @__PURE__ */ jsx10(
1246
1297
  ConfirmPrompt,
1247
1298
  {
1248
1299
  message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
@@ -1254,7 +1305,7 @@ function App(props) {
1254
1305
  const settingsPath = getUserSettingsPath();
1255
1306
  updateModelInSettings(settingsPath, pendingModelId);
1256
1307
  addMessage({ role: "system", content: `Model changed to ${getModelName(pendingModelId)}. Restarting...` });
1257
- setTimeout(() => exit(), 500);
1308
+ setTimeout(() => exit(), EXIT_DELAY_MS2);
1258
1309
  } catch (err) {
1259
1310
  addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
1260
1311
  }
@@ -1264,7 +1315,7 @@ function App(props) {
1264
1315
  }
1265
1316
  }
1266
1317
  ),
1267
- /* @__PURE__ */ jsx9(
1318
+ /* @__PURE__ */ jsx10(
1268
1319
  StatusBar,
1269
1320
  {
1270
1321
  permissionMode: session.getPermissionMode(),
@@ -1272,12 +1323,12 @@ function App(props) {
1272
1323
  sessionId: session.getSessionId(),
1273
1324
  messageCount: messages.length,
1274
1325
  isThinking,
1275
- contextPercentage,
1276
- contextUsedTokens: session.getContextState().usedTokens,
1277
- contextMaxTokens: session.getContextState().maxTokens
1326
+ contextPercentage: contextState.percentage,
1327
+ contextUsedTokens: contextState.usedTokens,
1328
+ contextMaxTokens: contextState.maxTokens
1278
1329
  }
1279
1330
  ),
1280
- /* @__PURE__ */ jsx9(
1331
+ /* @__PURE__ */ jsx10(
1281
1332
  InputArea,
1282
1333
  {
1283
1334
  onSubmit: handleSubmit,
@@ -1285,12 +1336,12 @@ function App(props) {
1285
1336
  registry
1286
1337
  }
1287
1338
  ),
1288
- /* @__PURE__ */ jsx9(Text9, { children: " " })
1339
+ /* @__PURE__ */ jsx10(Text10, { children: " " })
1289
1340
  ] });
1290
1341
  }
1291
1342
 
1292
1343
  // src/ui/render.tsx
1293
- import { jsx as jsx10 } from "react/jsx-runtime";
1344
+ import { jsx as jsx11 } from "react/jsx-runtime";
1294
1345
  function renderApp(options) {
1295
1346
  process.on("unhandledRejection", (reason) => {
1296
1347
  process.stderr.write(`
@@ -1301,7 +1352,7 @@ function renderApp(options) {
1301
1352
  `);
1302
1353
  }
1303
1354
  });
1304
- const instance = render(/* @__PURE__ */ jsx10(App, { ...options }), {
1355
+ const instance = render(/* @__PURE__ */ jsx11(App, { ...options }), {
1305
1356
  exitOnCtrlC: true
1306
1357
  });
1307
1358
  instance.waitUntilExit().catch((err) => {
@@ -1314,6 +1365,18 @@ function renderApp(options) {
1314
1365
  }
1315
1366
 
1316
1367
  // src/cli.ts
1368
+ function hasValidSettingsFile(filePath) {
1369
+ if (!existsSync3(filePath)) return false;
1370
+ try {
1371
+ const raw = readFileSync3(filePath, "utf8").trim();
1372
+ if (raw.length === 0) return false;
1373
+ const parsed = JSON.parse(raw);
1374
+ const provider = parsed.provider;
1375
+ return !!provider?.apiKey;
1376
+ } catch {
1377
+ return false;
1378
+ }
1379
+ }
1317
1380
  function readVersion() {
1318
1381
  try {
1319
1382
  const thisFile = fileURLToPath(import.meta.url);
@@ -1338,7 +1401,7 @@ async function ensureConfig(cwd) {
1338
1401
  const userPath = getUserSettingsPath();
1339
1402
  const projectPath = join3(cwd, ".robota", "settings.json");
1340
1403
  const localPath = join3(cwd, ".robota", "settings.local.json");
1341
- if (existsSync3(userPath) || existsSync3(projectPath) || existsSync3(localPath)) {
1404
+ if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
1342
1405
  return;
1343
1406
  }
1344
1407
  process.stdout.write("\n");