@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.
@@ -177,11 +177,126 @@ var PrintTerminal = class {
177
177
  };
178
178
 
179
179
  // src/ui/render.tsx
180
- var import_ink10 = require("ink");
180
+ var import_ink11 = require("ink");
181
181
 
182
182
  // src/ui/App.tsx
183
- var import_react6 = require("react");
184
- var import_ink9 = require("ink");
183
+ var import_react11 = require("react");
184
+ var import_ink10 = require("ink");
185
+ var import_agent_core3 = require("@robota-sdk/agent-core");
186
+
187
+ // src/ui/hooks/useSession.ts
188
+ var import_react = require("react");
189
+ var import_agent_sdk = require("@robota-sdk/agent-sdk");
190
+ var TOOL_ARG_DISPLAY_MAX = 80;
191
+ var TOOL_ARG_TRUNCATE_AT = 77;
192
+ var NOOP_TERMINAL = {
193
+ write: () => {
194
+ },
195
+ writeLine: () => {
196
+ },
197
+ writeMarkdown: () => {
198
+ },
199
+ writeError: () => {
200
+ },
201
+ prompt: () => Promise.resolve(""),
202
+ select: () => Promise.resolve(0),
203
+ spinner: () => ({ stop: () => {
204
+ }, update: () => {
205
+ } })
206
+ };
207
+ function useSession(props) {
208
+ const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
209
+ const [streamingText, setStreamingText] = (0, import_react.useState)("");
210
+ const [activeTools, setActiveTools] = (0, import_react.useState)([]);
211
+ const permissionQueueRef = (0, import_react.useRef)([]);
212
+ const processingRef = (0, import_react.useRef)(false);
213
+ const processNextPermission = (0, import_react.useCallback)(() => {
214
+ if (processingRef.current) return;
215
+ const next = permissionQueueRef.current[0];
216
+ if (!next) {
217
+ setPermissionRequest(null);
218
+ return;
219
+ }
220
+ processingRef.current = true;
221
+ setPermissionRequest({
222
+ toolName: next.toolName,
223
+ toolArgs: next.toolArgs,
224
+ resolve: (result) => {
225
+ permissionQueueRef.current.shift();
226
+ processingRef.current = false;
227
+ setPermissionRequest(null);
228
+ next.resolve(result);
229
+ setTimeout(() => processNextPermission(), 0);
230
+ }
231
+ });
232
+ }, []);
233
+ const sessionRef = (0, import_react.useRef)(null);
234
+ if (sessionRef.current === null) {
235
+ const permissionHandler = (toolName, toolArgs) => {
236
+ return new Promise((resolve) => {
237
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
238
+ processNextPermission();
239
+ });
240
+ };
241
+ const onTextDelta = (delta) => {
242
+ setStreamingText((prev) => prev + delta);
243
+ };
244
+ const onToolExecution = (event) => {
245
+ if (event.type === "start") {
246
+ let firstArg = "";
247
+ if (event.toolArgs) {
248
+ const firstVal = Object.values(event.toolArgs)[0];
249
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
250
+ firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
251
+ }
252
+ setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
253
+ } else {
254
+ setActiveTools(
255
+ (prev) => prev.map(
256
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
257
+ )
258
+ );
259
+ }
260
+ };
261
+ const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
262
+ sessionRef.current = (0, import_agent_sdk.createSession)({
263
+ config: props.config,
264
+ context: props.context,
265
+ terminal: NOOP_TERMINAL,
266
+ sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
267
+ projectInfo: props.projectInfo,
268
+ sessionStore: props.sessionStore,
269
+ permissionMode: props.permissionMode,
270
+ maxTurns: props.maxTurns,
271
+ permissionHandler,
272
+ onTextDelta,
273
+ onToolExecution
274
+ });
275
+ }
276
+ const clearStreamingText = (0, import_react.useCallback)(() => {
277
+ setStreamingText("");
278
+ setActiveTools([]);
279
+ }, []);
280
+ return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
281
+ }
282
+
283
+ // src/ui/hooks/useMessages.ts
284
+ var import_react2 = require("react");
285
+ var msgIdCounter = 0;
286
+ function nextId() {
287
+ msgIdCounter += 1;
288
+ return `msg_${msgIdCounter}`;
289
+ }
290
+ function useMessages() {
291
+ const [messages, setMessages] = (0, import_react2.useState)([]);
292
+ const addMessage = (0, import_react2.useCallback)((msg) => {
293
+ setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
294
+ }, []);
295
+ return { messages, setMessages, addMessage };
296
+ }
297
+
298
+ // src/ui/hooks/useSlashCommands.ts
299
+ var import_react3 = require("react");
185
300
 
186
301
  // src/commands/slash-executor.ts
187
302
  var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
@@ -306,9 +421,133 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
306
421
  }
307
422
  }
308
423
 
309
- // src/ui/App.tsx
310
- var import_agent_sdk = require("@robota-sdk/agent-sdk");
311
- var import_agent_core3 = require("@robota-sdk/agent-core");
424
+ // src/ui/hooks/useSlashCommands.ts
425
+ var EXIT_DELAY_MS = 500;
426
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
427
+ return (0, import_react3.useCallback)(
428
+ async (input) => {
429
+ const parts = input.slice(1).split(/\s+/);
430
+ const cmd = parts[0]?.toLowerCase() ?? "";
431
+ const args = parts.slice(1).join(" ");
432
+ const clearMessages = () => setMessages([]);
433
+ const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
434
+ if (result.pendingModelId) {
435
+ pendingModelChangeRef.current = result.pendingModelId;
436
+ setPendingModelId(result.pendingModelId);
437
+ }
438
+ if (result.exitRequested) {
439
+ setTimeout(() => exit(), EXIT_DELAY_MS);
440
+ }
441
+ return result.handled;
442
+ },
443
+ [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
444
+ );
445
+ }
446
+
447
+ // src/ui/hooks/useSubmitHandler.ts
448
+ var import_react4 = require("react");
449
+
450
+ // src/utils/tool-call-extractor.ts
451
+ var TOOL_ARG_MAX_LENGTH = 80;
452
+ var TOOL_ARG_TRUNCATE_LENGTH = 77;
453
+ function extractToolCalls(history, startIndex) {
454
+ const lines = [];
455
+ for (let i = startIndex; i < history.length; i++) {
456
+ const msg = history[i];
457
+ if (msg.role === "assistant" && msg.toolCalls) {
458
+ for (const tc of msg.toolCalls) {
459
+ const value = parseFirstArgValue(tc.function.arguments);
460
+ const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
461
+ lines.push(`${tc.function.name}(${truncated})`);
462
+ }
463
+ }
464
+ }
465
+ return lines;
466
+ }
467
+ function parseFirstArgValue(argsJson) {
468
+ try {
469
+ const parsed = JSON.parse(argsJson);
470
+ const firstVal = Object.values(parsed)[0];
471
+ return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
472
+ } catch {
473
+ return argsJson;
474
+ }
475
+ }
476
+
477
+ // src/utils/skill-prompt.ts
478
+ function buildSkillPrompt(input, registry) {
479
+ const parts = input.slice(1).split(/\s+/);
480
+ const cmd = parts[0]?.toLowerCase() ?? "";
481
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
482
+ if (!skillCmd) return null;
483
+ const args = parts.slice(1).join(" ").trim();
484
+ const userInstruction = args || skillCmd.description;
485
+ if (skillCmd.skillContent) {
486
+ return `<skill name="${cmd}">
487
+ ${skillCmd.skillContent}
488
+ </skill>
489
+
490
+ Execute the "${cmd}" skill: ${userInstruction}`;
491
+ }
492
+ return `Use the "${cmd}" skill: ${userInstruction}`;
493
+ }
494
+
495
+ // src/ui/hooks/useSubmitHandler.ts
496
+ function syncContextState(session, setter) {
497
+ const ctx = session.getContextState();
498
+ setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
499
+ }
500
+ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
501
+ setIsThinking(true);
502
+ clearStreamingText();
503
+ const historyBefore = session.getHistory().length;
504
+ try {
505
+ const response = await session.run(prompt);
506
+ clearStreamingText();
507
+ const history = session.getHistory();
508
+ const toolLines = extractToolCalls(
509
+ history,
510
+ historyBefore
511
+ );
512
+ if (toolLines.length > 0) {
513
+ addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
514
+ }
515
+ addMessage({ role: "assistant", content: response || "(empty response)" });
516
+ syncContextState(session, setContextState);
517
+ } catch (err) {
518
+ clearStreamingText();
519
+ if (err instanceof DOMException && err.name === "AbortError") {
520
+ addMessage({ role: "system", content: "Cancelled." });
521
+ } else {
522
+ const errMsg = err instanceof Error ? err.message : String(err);
523
+ addMessage({ role: "system", content: `Error: ${errMsg}` });
524
+ }
525
+ } finally {
526
+ setIsThinking(false);
527
+ }
528
+ }
529
+ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
530
+ return (0, import_react4.useCallback)(
531
+ async (input) => {
532
+ if (input.startsWith("/")) {
533
+ const handled = await handleSlashCommand(input);
534
+ if (handled) {
535
+ syncContextState(session, setContextState);
536
+ return;
537
+ }
538
+ const prompt = buildSkillPrompt(input, registry);
539
+ if (!prompt) return;
540
+ return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
541
+ }
542
+ addMessage({ role: "user", content: input });
543
+ return runSessionPrompt(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
544
+ },
545
+ [session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
546
+ );
547
+ }
548
+
549
+ // src/ui/hooks/useCommandRegistry.ts
550
+ var import_react5 = require("react");
312
551
 
313
552
  // src/commands/command-registry.ts
314
553
  var CommandRegistry = class {
@@ -462,6 +701,18 @@ var SkillCommandSource = class {
462
701
  }
463
702
  };
464
703
 
704
+ // src/ui/hooks/useCommandRegistry.ts
705
+ function useCommandRegistry(cwd) {
706
+ const registryRef = (0, import_react5.useRef)(null);
707
+ if (registryRef.current === null) {
708
+ const registry = new CommandRegistry();
709
+ registry.addSource(new BuiltinCommandSource());
710
+ registry.addSource(new SkillCommandSource(cwd));
711
+ registryRef.current = registry;
712
+ }
713
+ return registryRef.current;
714
+ }
715
+
465
716
  // src/ui/MessageList.tsx
466
717
  var import_ink = require("ink");
467
718
 
@@ -582,11 +833,11 @@ function StatusBar({
582
833
  }
583
834
 
584
835
  // src/ui/InputArea.tsx
585
- var import_react3 = __toESM(require("react"), 1);
836
+ var import_react8 = __toESM(require("react"), 1);
586
837
  var import_ink6 = require("ink");
587
838
 
588
839
  // src/ui/CjkTextInput.tsx
589
- var import_react = require("react");
840
+ var import_react6 = require("react");
590
841
  var import_ink3 = require("ink");
591
842
  var import_chalk = __toESM(require("chalk"), 1);
592
843
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -606,9 +857,9 @@ function CjkTextInput({
606
857
  focus = true,
607
858
  showCursor = true
608
859
  }) {
609
- const valueRef = (0, import_react.useRef)(value);
610
- const cursorRef = (0, import_react.useRef)(value.length);
611
- const [, forceRender] = (0, import_react.useState)(0);
860
+ const valueRef = (0, import_react6.useRef)(value);
861
+ const cursorRef = (0, import_react6.useRef)(value.length);
862
+ const [, forceRender] = (0, import_react6.useState)(0);
612
863
  if (value !== valueRef.current) {
613
864
  valueRef.current = value;
614
865
  if (cursorRef.current > value.length) {
@@ -685,15 +936,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
685
936
  }
686
937
 
687
938
  // src/ui/WaveText.tsx
688
- var import_react2 = require("react");
939
+ var import_react7 = require("react");
689
940
  var import_ink4 = require("ink");
690
941
  var import_jsx_runtime4 = require("react/jsx-runtime");
691
942
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
692
943
  var INTERVAL_MS = 400;
693
944
  var CHARS_PER_GROUP = 4;
694
945
  function WaveText({ text }) {
695
- const [tick, setTick] = (0, import_react2.useState)(0);
696
- (0, import_react2.useEffect)(() => {
946
+ const [tick, setTick] = (0, import_react7.useState)(0);
947
+ (0, import_react7.useEffect)(() => {
697
948
  const timer = setInterval(() => {
698
949
  setTick((prev) => prev + 1);
699
950
  }, INTERVAL_MS);
@@ -759,16 +1010,16 @@ function parseSlashInput(value) {
759
1010
  return { isSlash: true, parentCommand: parent, filter: rest };
760
1011
  }
761
1012
  function useAutocomplete(value, registry) {
762
- const [selectedIndex, setSelectedIndex] = (0, import_react3.useState)(0);
763
- const [dismissed, setDismissed] = (0, import_react3.useState)(false);
764
- const prevValueRef = import_react3.default.useRef(value);
1013
+ const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(0);
1014
+ const [dismissed, setDismissed] = (0, import_react8.useState)(false);
1015
+ const prevValueRef = import_react8.default.useRef(value);
765
1016
  if (prevValueRef.current !== value) {
766
1017
  prevValueRef.current = value;
767
1018
  if (dismissed) setDismissed(false);
768
1019
  }
769
1020
  const parsed = parseSlashInput(value);
770
1021
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
771
- const filteredCommands = (0, import_react3.useMemo)(() => {
1022
+ const filteredCommands = (0, import_react8.useMemo)(() => {
772
1023
  if (!registry || !parsed.isSlash || dismissed) return [];
773
1024
  if (isSubcommandMode) {
774
1025
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -802,7 +1053,7 @@ function useAutocomplete(value, registry) {
802
1053
  };
803
1054
  }
804
1055
  function InputArea({ onSubmit, isDisabled, registry }) {
805
- const [value, setValue] = (0, import_react3.useState)("");
1056
+ const [value, setValue] = (0, import_react8.useState)("");
806
1057
  const {
807
1058
  showPopup,
808
1059
  filteredCommands,
@@ -811,7 +1062,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
811
1062
  isSubcommandMode,
812
1063
  setShowPopup
813
1064
  } = useAutocomplete(value, registry);
814
- const handleSubmit = (0, import_react3.useCallback)(
1065
+ const handleSubmit = (0, import_react8.useCallback)(
815
1066
  (text) => {
816
1067
  const trimmed = text.trim();
817
1068
  if (trimmed.length === 0) return;
@@ -824,7 +1075,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
824
1075
  },
825
1076
  [showPopup, filteredCommands, selectedIndex, onSubmit]
826
1077
  );
827
- const selectCommand = (0, import_react3.useCallback)(
1078
+ const selectCommand = (0, import_react8.useCallback)(
828
1079
  (cmd) => {
829
1080
  const parsed = parseSlashInput(value);
830
1081
  if (parsed.parentCommand) {
@@ -885,7 +1136,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
885
1136
  }
886
1137
 
887
1138
  // src/ui/ConfirmPrompt.tsx
888
- var import_react4 = require("react");
1139
+ var import_react9 = require("react");
889
1140
  var import_ink7 = require("ink");
890
1141
  var import_jsx_runtime7 = require("react/jsx-runtime");
891
1142
  function ConfirmPrompt({
@@ -893,9 +1144,9 @@ function ConfirmPrompt({
893
1144
  options = ["Yes", "No"],
894
1145
  onSelect
895
1146
  }) {
896
- const [selected, setSelected] = (0, import_react4.useState)(0);
897
- const resolvedRef = (0, import_react4.useRef)(false);
898
- const doSelect = (0, import_react4.useCallback)(
1147
+ const [selected, setSelected] = (0, import_react9.useState)(0);
1148
+ const resolvedRef = (0, import_react9.useRef)(false);
1149
+ const doSelect = (0, import_react9.useCallback)(
899
1150
  (index) => {
900
1151
  if (resolvedRef.current) return;
901
1152
  resolvedRef.current = true;
@@ -928,7 +1179,7 @@ function ConfirmPrompt({
928
1179
  }
929
1180
 
930
1181
  // src/ui/PermissionPrompt.tsx
931
- var import_react5 = __toESM(require("react"), 1);
1182
+ var import_react10 = __toESM(require("react"), 1);
932
1183
  var import_ink8 = require("ink");
933
1184
  var import_jsx_runtime8 = require("react/jsx-runtime");
934
1185
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
@@ -938,15 +1189,15 @@ function formatArgs(args) {
938
1189
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
939
1190
  }
940
1191
  function PermissionPrompt({ request }) {
941
- const [selected, setSelected] = import_react5.default.useState(0);
942
- const resolvedRef = import_react5.default.useRef(false);
943
- const prevRequestRef = import_react5.default.useRef(request);
1192
+ const [selected, setSelected] = import_react10.default.useState(0);
1193
+ const resolvedRef = import_react10.default.useRef(false);
1194
+ const prevRequestRef = import_react10.default.useRef(request);
944
1195
  if (prevRequestRef.current !== request) {
945
1196
  prevRequestRef.current = request;
946
1197
  resolvedRef.current = false;
947
1198
  setSelected(0);
948
1199
  }
949
- const doResolve = import_react5.default.useCallback(
1200
+ const doResolve = import_react10.default.useCallback(
950
1201
  (index) => {
951
1202
  if (resolvedRef.current) return;
952
1203
  resolvedRef.current = true;
@@ -991,254 +1242,54 @@ function PermissionPrompt({ request }) {
991
1242
  ] });
992
1243
  }
993
1244
 
994
- // src/utils/tool-call-extractor.ts
995
- var TOOL_ARG_MAX_LENGTH = 80;
996
- var TOOL_ARG_TRUNCATE_LENGTH = 77;
997
- function extractToolCalls(history, startIndex) {
998
- const lines = [];
999
- for (let i = startIndex; i < history.length; i++) {
1000
- const msg = history[i];
1001
- if (msg.role === "assistant" && msg.toolCalls) {
1002
- for (const tc of msg.toolCalls) {
1003
- const value = parseFirstArgValue(tc.function.arguments);
1004
- const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
1005
- lines.push(`${tc.function.name}(${truncated})`);
1006
- }
1007
- }
1008
- }
1009
- return lines;
1010
- }
1011
- function parseFirstArgValue(argsJson) {
1012
- try {
1013
- const parsed = JSON.parse(argsJson);
1014
- const firstVal = Object.values(parsed)[0];
1015
- return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
1016
- } catch {
1017
- return argsJson;
1018
- }
1019
- }
1020
-
1021
- // src/ui/App.tsx
1245
+ // src/ui/StreamingIndicator.tsx
1246
+ var import_ink9 = require("ink");
1022
1247
  var import_jsx_runtime9 = require("react/jsx-runtime");
1023
- var msgIdCounter = 0;
1024
- function nextId() {
1025
- msgIdCounter += 1;
1026
- return `msg_${msgIdCounter}`;
1027
- }
1028
- var NOOP_TERMINAL = {
1029
- write: () => {
1030
- },
1031
- writeLine: () => {
1032
- },
1033
- writeMarkdown: () => {
1034
- },
1035
- writeError: () => {
1036
- },
1037
- prompt: () => Promise.resolve(""),
1038
- select: () => Promise.resolve(0),
1039
- spinner: () => ({ stop: () => {
1040
- }, update: () => {
1041
- } })
1042
- };
1043
- function useSession(props) {
1044
- const [permissionRequest, setPermissionRequest] = (0, import_react6.useState)(null);
1045
- const [streamingText, setStreamingText] = (0, import_react6.useState)("");
1046
- const permissionQueueRef = (0, import_react6.useRef)([]);
1047
- const processingRef = (0, import_react6.useRef)(false);
1048
- const processNextPermission = (0, import_react6.useCallback)(() => {
1049
- if (processingRef.current) return;
1050
- const next = permissionQueueRef.current[0];
1051
- if (!next) {
1052
- setPermissionRequest(null);
1053
- return;
1054
- }
1055
- processingRef.current = true;
1056
- setPermissionRequest({
1057
- toolName: next.toolName,
1058
- toolArgs: next.toolArgs,
1059
- resolve: (result) => {
1060
- permissionQueueRef.current.shift();
1061
- processingRef.current = false;
1062
- setPermissionRequest(null);
1063
- next.resolve(result);
1064
- setTimeout(() => processNextPermission(), 0);
1065
- }
1066
- });
1067
- }, []);
1068
- const sessionRef = (0, import_react6.useRef)(null);
1069
- if (sessionRef.current === null) {
1070
- const permissionHandler = (toolName, toolArgs) => {
1071
- return new Promise((resolve) => {
1072
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
1073
- processNextPermission();
1074
- });
1075
- };
1076
- const onTextDelta = (delta) => {
1077
- setStreamingText((prev) => prev + delta);
1078
- };
1079
- const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
1080
- sessionRef.current = (0, import_agent_sdk.createSession)({
1081
- config: props.config,
1082
- context: props.context,
1083
- terminal: NOOP_TERMINAL,
1084
- sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
1085
- projectInfo: props.projectInfo,
1086
- sessionStore: props.sessionStore,
1087
- permissionMode: props.permissionMode,
1088
- maxTurns: props.maxTurns,
1089
- permissionHandler,
1090
- onTextDelta
1091
- });
1248
+ function StreamingIndicator({ text, activeTools }) {
1249
+ const hasTools = activeTools.length > 0;
1250
+ const hasText = text.length > 0;
1251
+ if (!hasTools && !hasText) {
1252
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
1092
1253
  }
1093
- const clearStreamingText = (0, import_react6.useCallback)(() => setStreamingText(""), []);
1094
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
1095
- }
1096
- function useMessages() {
1097
- const [messages, setMessages] = (0, import_react6.useState)([]);
1098
- const addMessage = (0, import_react6.useCallback)((msg) => {
1099
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
1100
- }, []);
1101
- return { messages, setMessages, addMessage };
1102
- }
1103
- var EXIT_DELAY_MS = 500;
1104
- function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
1105
- return (0, import_react6.useCallback)(
1106
- async (input) => {
1107
- const parts = input.slice(1).split(/\s+/);
1108
- const cmd = parts[0]?.toLowerCase() ?? "";
1109
- const args = parts.slice(1).join(" ");
1110
- const clearMessages = () => setMessages([]);
1111
- const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
1112
- if (result.pendingModelId) {
1113
- pendingModelChangeRef.current = result.pendingModelId;
1114
- setPendingModelId(result.pendingModelId);
1115
- }
1116
- if (result.exitRequested) {
1117
- setTimeout(() => exit(), EXIT_DELAY_MS);
1118
- }
1119
- return result.handled;
1120
- },
1121
- [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
1122
- );
1123
- }
1124
- function StreamingIndicator({ text }) {
1125
- if (text) {
1126
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1127
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: "cyan", bold: true, children: [
1128
- "Robota:",
1129
- " "
1130
- ] }),
1254
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1255
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "gray", bold: true, children: "Tools:" }),
1257
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1258
+ activeTools.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: t.isRunning ? "yellow" : "green", children: [
1259
+ " ",
1260
+ t.isRunning ? "\u27F3" : "\u2713",
1261
+ " ",
1262
+ t.toolName,
1263
+ "(",
1264
+ t.firstArg,
1265
+ ")"
1266
+ ] }, `${t.toolName}-${i}`))
1267
+ ] }),
1268
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1269
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: "Robota:" }),
1131
1270
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1132
1271
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
1133
- ] });
1134
- }
1135
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
1136
- }
1137
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
1138
- setIsThinking(true);
1139
- clearStreamingText();
1140
- const historyBefore = session.getHistory().length;
1141
- try {
1142
- const response = await session.run(prompt);
1143
- clearStreamingText();
1144
- const history = session.getHistory();
1145
- const toolLines = extractToolCalls(
1146
- history,
1147
- historyBefore
1148
- );
1149
- if (toolLines.length > 0) {
1150
- addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
1151
- }
1152
- addMessage({ role: "assistant", content: response || "(empty response)" });
1153
- setContextPercentage(session.getContextState().usedPercentage);
1154
- } catch (err) {
1155
- clearStreamingText();
1156
- if (err instanceof DOMException && err.name === "AbortError") {
1157
- addMessage({ role: "system", content: "Cancelled." });
1158
- } else {
1159
- const errMsg = err instanceof Error ? err.message : String(err);
1160
- addMessage({ role: "system", content: `Error: ${errMsg}` });
1161
- }
1162
- } finally {
1163
- setIsThinking(false);
1164
- }
1272
+ ] })
1273
+ ] });
1165
1274
  }
1166
- function buildSkillPrompt(input, registry) {
1167
- const parts = input.slice(1).split(/\s+/);
1168
- const cmd = parts[0]?.toLowerCase() ?? "";
1169
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
1170
- if (!skillCmd) return null;
1171
- const args = parts.slice(1).join(" ").trim();
1172
- const userInstruction = args || skillCmd.description;
1173
- if (skillCmd.skillContent) {
1174
- return `<skill name="${cmd}">
1175
- ${skillCmd.skillContent}
1176
- </skill>
1177
1275
 
1178
- Execute the "${cmd}" skill: ${userInstruction}`;
1179
- }
1180
- return `Use the "${cmd}" skill: ${userInstruction}`;
1181
- }
1182
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
1183
- return (0, import_react6.useCallback)(
1184
- async (input) => {
1185
- if (input.startsWith("/")) {
1186
- const handled = await handleSlashCommand(input);
1187
- if (handled) {
1188
- setContextPercentage(session.getContextState().usedPercentage);
1189
- return;
1190
- }
1191
- const prompt = buildSkillPrompt(input, registry);
1192
- if (!prompt) return;
1193
- return runSessionPrompt(
1194
- prompt,
1195
- session,
1196
- addMessage,
1197
- clearStreamingText,
1198
- setIsThinking,
1199
- setContextPercentage
1200
- );
1201
- }
1202
- addMessage({ role: "user", content: input });
1203
- return runSessionPrompt(
1204
- input,
1205
- session,
1206
- addMessage,
1207
- clearStreamingText,
1208
- setIsThinking,
1209
- setContextPercentage
1210
- );
1211
- },
1212
- [
1213
- session,
1214
- addMessage,
1215
- handleSlashCommand,
1216
- clearStreamingText,
1217
- setIsThinking,
1218
- setContextPercentage,
1219
- registry
1220
- ]
1221
- );
1222
- }
1223
- function useCommandRegistry(cwd) {
1224
- const registryRef = (0, import_react6.useRef)(null);
1225
- if (registryRef.current === null) {
1226
- const registry = new CommandRegistry();
1227
- registry.addSource(new BuiltinCommandSource());
1228
- registry.addSource(new SkillCommandSource(cwd));
1229
- registryRef.current = registry;
1230
- }
1231
- return registryRef.current;
1232
- }
1276
+ // src/ui/App.tsx
1277
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1278
+ var EXIT_DELAY_MS2 = 500;
1233
1279
  function App(props) {
1234
- const { exit } = (0, import_ink9.useApp)();
1235
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
1280
+ const { exit } = (0, import_ink10.useApp)();
1281
+ const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
1236
1282
  const { messages, setMessages, addMessage } = useMessages();
1237
- const [isThinking, setIsThinking] = (0, import_react6.useState)(false);
1238
- const [contextPercentage, setContextPercentage] = (0, import_react6.useState)(0);
1283
+ const [isThinking, setIsThinking] = (0, import_react11.useState)(false);
1284
+ const initialCtx = session.getContextState();
1285
+ const [contextState, setContextState] = (0, import_react11.useState)({
1286
+ percentage: initialCtx.usedPercentage,
1287
+ usedTokens: initialCtx.usedTokens,
1288
+ maxTokens: initialCtx.maxTokens
1289
+ });
1239
1290
  const registry = useCommandRegistry(props.cwd ?? process.cwd());
1240
- const pendingModelChangeRef = (0, import_react6.useRef)(null);
1241
- const [pendingModelId, setPendingModelId] = (0, import_react6.useState)(null);
1291
+ const pendingModelChangeRef = (0, import_react11.useRef)(null);
1292
+ const [pendingModelId, setPendingModelId] = (0, import_react11.useState)(null);
1242
1293
  const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
1243
1294
  const handleSubmit = useSubmitHandler(
1244
1295
  session,
@@ -1246,36 +1297,36 @@ function App(props) {
1246
1297
  handleSlashCommand,
1247
1298
  clearStreamingText,
1248
1299
  setIsThinking,
1249
- setContextPercentage,
1300
+ setContextState,
1250
1301
  registry
1251
1302
  );
1252
- (0, import_ink9.useInput)(
1303
+ (0, import_ink10.useInput)(
1253
1304
  (_input, key) => {
1254
1305
  if (key.ctrl && _input === "c") exit();
1255
1306
  if (key.escape && isThinking) session.abort();
1256
1307
  },
1257
1308
  { isActive: !permissionRequest }
1258
1309
  );
1259
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1260
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1261
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: `
1310
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
1311
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1312
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: `
1262
1313
  ____ ___ ____ ___ _____ _
1263
1314
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1264
1315
  | |_) | | | | _ \\| | | || | / _ \\
1265
1316
  | _ <| |_| | |_) | |_| || |/ ___ \\
1266
1317
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1267
1318
  ` }),
1268
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { dimColor: true, children: [
1269
1320
  " v",
1270
1321
  props.version ?? "0.0.0"
1271
1322
  ] })
1272
1323
  ] }),
1273
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1274
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessageList, { messages }),
1275
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StreamingIndicator, { text: streamingText }) })
1324
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1325
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageList, { messages }),
1326
+ isThinking && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
1276
1327
  ] }),
1277
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PermissionPrompt, { request: permissionRequest }),
1278
- pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1328
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PermissionPrompt, { request: permissionRequest }),
1329
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1279
1330
  ConfirmPrompt,
1280
1331
  {
1281
1332
  message: `Change model to ${(0, import_agent_core3.getModelName)(pendingModelId)}? This will restart the session.`,
@@ -1287,7 +1338,7 @@ function App(props) {
1287
1338
  const settingsPath = getUserSettingsPath();
1288
1339
  updateModelInSettings(settingsPath, pendingModelId);
1289
1340
  addMessage({ role: "system", content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...` });
1290
- setTimeout(() => exit(), 500);
1341
+ setTimeout(() => exit(), EXIT_DELAY_MS2);
1291
1342
  } catch (err) {
1292
1343
  addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
1293
1344
  }
@@ -1297,7 +1348,7 @@ function App(props) {
1297
1348
  }
1298
1349
  }
1299
1350
  ),
1300
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1351
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1301
1352
  StatusBar,
1302
1353
  {
1303
1354
  permissionMode: session.getPermissionMode(),
@@ -1305,12 +1356,12 @@ function App(props) {
1305
1356
  sessionId: session.getSessionId(),
1306
1357
  messageCount: messages.length,
1307
1358
  isThinking,
1308
- contextPercentage,
1309
- contextUsedTokens: session.getContextState().usedTokens,
1310
- contextMaxTokens: session.getContextState().maxTokens
1359
+ contextPercentage: contextState.percentage,
1360
+ contextUsedTokens: contextState.usedTokens,
1361
+ contextMaxTokens: contextState.maxTokens
1311
1362
  }
1312
1363
  ),
1313
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1364
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1314
1365
  InputArea,
1315
1366
  {
1316
1367
  onSubmit: handleSubmit,
@@ -1318,12 +1369,12 @@ function App(props) {
1318
1369
  registry
1319
1370
  }
1320
1371
  ),
1321
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " })
1372
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " })
1322
1373
  ] });
1323
1374
  }
1324
1375
 
1325
1376
  // src/ui/render.tsx
1326
- var import_jsx_runtime10 = require("react/jsx-runtime");
1377
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1327
1378
  function renderApp(options) {
1328
1379
  process.on("unhandledRejection", (reason) => {
1329
1380
  process.stderr.write(`
@@ -1334,7 +1385,7 @@ function renderApp(options) {
1334
1385
  `);
1335
1386
  }
1336
1387
  });
1337
- const instance = (0, import_ink10.render)(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(App, { ...options }), {
1388
+ const instance = (0, import_ink11.render)(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(App, { ...options }), {
1338
1389
  exitOnCtrlC: true
1339
1390
  });
1340
1391
  instance.waitUntilExit().catch((err) => {
@@ -1348,6 +1399,18 @@ function renderApp(options) {
1348
1399
 
1349
1400
  // src/cli.ts
1350
1401
  var import_meta = {};
1402
+ function hasValidSettingsFile(filePath) {
1403
+ if (!(0, import_node_fs3.existsSync)(filePath)) return false;
1404
+ try {
1405
+ const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
1406
+ if (raw.length === 0) return false;
1407
+ const parsed = JSON.parse(raw);
1408
+ const provider = parsed.provider;
1409
+ return !!provider?.apiKey;
1410
+ } catch {
1411
+ return false;
1412
+ }
1413
+ }
1351
1414
  function readVersion() {
1352
1415
  try {
1353
1416
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
@@ -1372,7 +1435,7 @@ async function ensureConfig(cwd) {
1372
1435
  const userPath = getUserSettingsPath();
1373
1436
  const projectPath = (0, import_node_path3.join)(cwd, ".robota", "settings.json");
1374
1437
  const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
1375
- if ((0, import_node_fs3.existsSync)(userPath) || (0, import_node_fs3.existsSync)(projectPath) || (0, import_node_fs3.existsSync)(localPath)) {
1438
+ if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
1376
1439
  return;
1377
1440
  }
1378
1441
  process.stdout.write("\n");