@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.
package/dist/node/bin.cjs CHANGED
@@ -161,11 +161,126 @@ var PrintTerminal = class {
161
161
  };
162
162
 
163
163
  // src/ui/render.tsx
164
- var import_ink10 = require("ink");
164
+ var import_ink11 = require("ink");
165
165
 
166
166
  // src/ui/App.tsx
167
- var import_react6 = require("react");
168
- var import_ink9 = require("ink");
167
+ var import_react11 = require("react");
168
+ var import_ink10 = require("ink");
169
+ var import_agent_core3 = require("@robota-sdk/agent-core");
170
+
171
+ // src/ui/hooks/useSession.ts
172
+ var import_react = require("react");
173
+ var import_agent_sdk = require("@robota-sdk/agent-sdk");
174
+ var TOOL_ARG_DISPLAY_MAX = 80;
175
+ var TOOL_ARG_TRUNCATE_AT = 77;
176
+ var NOOP_TERMINAL = {
177
+ write: () => {
178
+ },
179
+ writeLine: () => {
180
+ },
181
+ writeMarkdown: () => {
182
+ },
183
+ writeError: () => {
184
+ },
185
+ prompt: () => Promise.resolve(""),
186
+ select: () => Promise.resolve(0),
187
+ spinner: () => ({ stop: () => {
188
+ }, update: () => {
189
+ } })
190
+ };
191
+ function useSession(props) {
192
+ const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
193
+ const [streamingText, setStreamingText] = (0, import_react.useState)("");
194
+ const [activeTools, setActiveTools] = (0, import_react.useState)([]);
195
+ const permissionQueueRef = (0, import_react.useRef)([]);
196
+ const processingRef = (0, import_react.useRef)(false);
197
+ const processNextPermission = (0, import_react.useCallback)(() => {
198
+ if (processingRef.current) return;
199
+ const next = permissionQueueRef.current[0];
200
+ if (!next) {
201
+ setPermissionRequest(null);
202
+ return;
203
+ }
204
+ processingRef.current = true;
205
+ setPermissionRequest({
206
+ toolName: next.toolName,
207
+ toolArgs: next.toolArgs,
208
+ resolve: (result) => {
209
+ permissionQueueRef.current.shift();
210
+ processingRef.current = false;
211
+ setPermissionRequest(null);
212
+ next.resolve(result);
213
+ setTimeout(() => processNextPermission(), 0);
214
+ }
215
+ });
216
+ }, []);
217
+ const sessionRef = (0, import_react.useRef)(null);
218
+ if (sessionRef.current === null) {
219
+ const permissionHandler = (toolName, toolArgs) => {
220
+ return new Promise((resolve) => {
221
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
222
+ processNextPermission();
223
+ });
224
+ };
225
+ const onTextDelta = (delta) => {
226
+ setStreamingText((prev) => prev + delta);
227
+ };
228
+ const onToolExecution = (event) => {
229
+ if (event.type === "start") {
230
+ let firstArg = "";
231
+ if (event.toolArgs) {
232
+ const firstVal = Object.values(event.toolArgs)[0];
233
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
234
+ firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
235
+ }
236
+ setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
237
+ } else {
238
+ setActiveTools(
239
+ (prev) => prev.map(
240
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
241
+ )
242
+ );
243
+ }
244
+ };
245
+ const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
246
+ sessionRef.current = (0, import_agent_sdk.createSession)({
247
+ config: props.config,
248
+ context: props.context,
249
+ terminal: NOOP_TERMINAL,
250
+ sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
251
+ projectInfo: props.projectInfo,
252
+ sessionStore: props.sessionStore,
253
+ permissionMode: props.permissionMode,
254
+ maxTurns: props.maxTurns,
255
+ permissionHandler,
256
+ onTextDelta,
257
+ onToolExecution
258
+ });
259
+ }
260
+ const clearStreamingText = (0, import_react.useCallback)(() => {
261
+ setStreamingText("");
262
+ setActiveTools([]);
263
+ }, []);
264
+ return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
265
+ }
266
+
267
+ // src/ui/hooks/useMessages.ts
268
+ var import_react2 = require("react");
269
+ var msgIdCounter = 0;
270
+ function nextId() {
271
+ msgIdCounter += 1;
272
+ return `msg_${msgIdCounter}`;
273
+ }
274
+ function useMessages() {
275
+ const [messages, setMessages] = (0, import_react2.useState)([]);
276
+ const addMessage = (0, import_react2.useCallback)((msg) => {
277
+ setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
278
+ }, []);
279
+ return { messages, setMessages, addMessage };
280
+ }
281
+
282
+ // src/ui/hooks/useSlashCommands.ts
283
+ var import_react3 = require("react");
169
284
 
170
285
  // src/commands/slash-executor.ts
171
286
  var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
@@ -290,9 +405,133 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
290
405
  }
291
406
  }
292
407
 
293
- // src/ui/App.tsx
294
- var import_agent_sdk = require("@robota-sdk/agent-sdk");
295
- var import_agent_core3 = require("@robota-sdk/agent-core");
408
+ // src/ui/hooks/useSlashCommands.ts
409
+ var EXIT_DELAY_MS = 500;
410
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
411
+ return (0, import_react3.useCallback)(
412
+ async (input) => {
413
+ const parts = input.slice(1).split(/\s+/);
414
+ const cmd = parts[0]?.toLowerCase() ?? "";
415
+ const args = parts.slice(1).join(" ");
416
+ const clearMessages = () => setMessages([]);
417
+ const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
418
+ if (result.pendingModelId) {
419
+ pendingModelChangeRef.current = result.pendingModelId;
420
+ setPendingModelId(result.pendingModelId);
421
+ }
422
+ if (result.exitRequested) {
423
+ setTimeout(() => exit(), EXIT_DELAY_MS);
424
+ }
425
+ return result.handled;
426
+ },
427
+ [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
428
+ );
429
+ }
430
+
431
+ // src/ui/hooks/useSubmitHandler.ts
432
+ var import_react4 = require("react");
433
+
434
+ // src/utils/tool-call-extractor.ts
435
+ var TOOL_ARG_MAX_LENGTH = 80;
436
+ var TOOL_ARG_TRUNCATE_LENGTH = 77;
437
+ function extractToolCalls(history, startIndex) {
438
+ const lines = [];
439
+ for (let i = startIndex; i < history.length; i++) {
440
+ const msg = history[i];
441
+ if (msg.role === "assistant" && msg.toolCalls) {
442
+ for (const tc of msg.toolCalls) {
443
+ const value = parseFirstArgValue(tc.function.arguments);
444
+ const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
445
+ lines.push(`${tc.function.name}(${truncated})`);
446
+ }
447
+ }
448
+ }
449
+ return lines;
450
+ }
451
+ function parseFirstArgValue(argsJson) {
452
+ try {
453
+ const parsed = JSON.parse(argsJson);
454
+ const firstVal = Object.values(parsed)[0];
455
+ return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
456
+ } catch {
457
+ return argsJson;
458
+ }
459
+ }
460
+
461
+ // src/utils/skill-prompt.ts
462
+ function buildSkillPrompt(input, registry) {
463
+ const parts = input.slice(1).split(/\s+/);
464
+ const cmd = parts[0]?.toLowerCase() ?? "";
465
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
466
+ if (!skillCmd) return null;
467
+ const args = parts.slice(1).join(" ").trim();
468
+ const userInstruction = args || skillCmd.description;
469
+ if (skillCmd.skillContent) {
470
+ return `<skill name="${cmd}">
471
+ ${skillCmd.skillContent}
472
+ </skill>
473
+
474
+ Execute the "${cmd}" skill: ${userInstruction}`;
475
+ }
476
+ return `Use the "${cmd}" skill: ${userInstruction}`;
477
+ }
478
+
479
+ // src/ui/hooks/useSubmitHandler.ts
480
+ function syncContextState(session, setter) {
481
+ const ctx = session.getContextState();
482
+ setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
483
+ }
484
+ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
485
+ setIsThinking(true);
486
+ clearStreamingText();
487
+ const historyBefore = session.getHistory().length;
488
+ try {
489
+ const response = await session.run(prompt);
490
+ clearStreamingText();
491
+ const history = session.getHistory();
492
+ const toolLines = extractToolCalls(
493
+ history,
494
+ historyBefore
495
+ );
496
+ if (toolLines.length > 0) {
497
+ addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
498
+ }
499
+ addMessage({ role: "assistant", content: response || "(empty response)" });
500
+ syncContextState(session, setContextState);
501
+ } catch (err) {
502
+ clearStreamingText();
503
+ if (err instanceof DOMException && err.name === "AbortError") {
504
+ addMessage({ role: "system", content: "Cancelled." });
505
+ } else {
506
+ const errMsg = err instanceof Error ? err.message : String(err);
507
+ addMessage({ role: "system", content: `Error: ${errMsg}` });
508
+ }
509
+ } finally {
510
+ setIsThinking(false);
511
+ }
512
+ }
513
+ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
514
+ return (0, import_react4.useCallback)(
515
+ async (input) => {
516
+ if (input.startsWith("/")) {
517
+ const handled = await handleSlashCommand(input);
518
+ if (handled) {
519
+ syncContextState(session, setContextState);
520
+ return;
521
+ }
522
+ const prompt = buildSkillPrompt(input, registry);
523
+ if (!prompt) return;
524
+ return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
525
+ }
526
+ addMessage({ role: "user", content: input });
527
+ return runSessionPrompt(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
528
+ },
529
+ [session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
530
+ );
531
+ }
532
+
533
+ // src/ui/hooks/useCommandRegistry.ts
534
+ var import_react5 = require("react");
296
535
 
297
536
  // src/commands/command-registry.ts
298
537
  var CommandRegistry = class {
@@ -446,6 +685,18 @@ var SkillCommandSource = class {
446
685
  }
447
686
  };
448
687
 
688
+ // src/ui/hooks/useCommandRegistry.ts
689
+ function useCommandRegistry(cwd) {
690
+ const registryRef = (0, import_react5.useRef)(null);
691
+ if (registryRef.current === null) {
692
+ const registry = new CommandRegistry();
693
+ registry.addSource(new BuiltinCommandSource());
694
+ registry.addSource(new SkillCommandSource(cwd));
695
+ registryRef.current = registry;
696
+ }
697
+ return registryRef.current;
698
+ }
699
+
449
700
  // src/ui/MessageList.tsx
450
701
  var import_ink = require("ink");
451
702
 
@@ -566,11 +817,11 @@ function StatusBar({
566
817
  }
567
818
 
568
819
  // src/ui/InputArea.tsx
569
- var import_react3 = __toESM(require("react"), 1);
820
+ var import_react8 = __toESM(require("react"), 1);
570
821
  var import_ink6 = require("ink");
571
822
 
572
823
  // src/ui/CjkTextInput.tsx
573
- var import_react = require("react");
824
+ var import_react6 = require("react");
574
825
  var import_ink3 = require("ink");
575
826
  var import_chalk = __toESM(require("chalk"), 1);
576
827
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -590,9 +841,9 @@ function CjkTextInput({
590
841
  focus = true,
591
842
  showCursor = true
592
843
  }) {
593
- const valueRef = (0, import_react.useRef)(value);
594
- const cursorRef = (0, import_react.useRef)(value.length);
595
- const [, forceRender] = (0, import_react.useState)(0);
844
+ const valueRef = (0, import_react6.useRef)(value);
845
+ const cursorRef = (0, import_react6.useRef)(value.length);
846
+ const [, forceRender] = (0, import_react6.useState)(0);
596
847
  if (value !== valueRef.current) {
597
848
  valueRef.current = value;
598
849
  if (cursorRef.current > value.length) {
@@ -669,15 +920,15 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
669
920
  }
670
921
 
671
922
  // src/ui/WaveText.tsx
672
- var import_react2 = require("react");
923
+ var import_react7 = require("react");
673
924
  var import_ink4 = require("ink");
674
925
  var import_jsx_runtime4 = require("react/jsx-runtime");
675
926
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
676
927
  var INTERVAL_MS = 400;
677
928
  var CHARS_PER_GROUP = 4;
678
929
  function WaveText({ text }) {
679
- const [tick, setTick] = (0, import_react2.useState)(0);
680
- (0, import_react2.useEffect)(() => {
930
+ const [tick, setTick] = (0, import_react7.useState)(0);
931
+ (0, import_react7.useEffect)(() => {
681
932
  const timer = setInterval(() => {
682
933
  setTick((prev) => prev + 1);
683
934
  }, INTERVAL_MS);
@@ -743,16 +994,16 @@ function parseSlashInput(value) {
743
994
  return { isSlash: true, parentCommand: parent, filter: rest };
744
995
  }
745
996
  function useAutocomplete(value, registry) {
746
- const [selectedIndex, setSelectedIndex] = (0, import_react3.useState)(0);
747
- const [dismissed, setDismissed] = (0, import_react3.useState)(false);
748
- const prevValueRef = import_react3.default.useRef(value);
997
+ const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(0);
998
+ const [dismissed, setDismissed] = (0, import_react8.useState)(false);
999
+ const prevValueRef = import_react8.default.useRef(value);
749
1000
  if (prevValueRef.current !== value) {
750
1001
  prevValueRef.current = value;
751
1002
  if (dismissed) setDismissed(false);
752
1003
  }
753
1004
  const parsed = parseSlashInput(value);
754
1005
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
755
- const filteredCommands = (0, import_react3.useMemo)(() => {
1006
+ const filteredCommands = (0, import_react8.useMemo)(() => {
756
1007
  if (!registry || !parsed.isSlash || dismissed) return [];
757
1008
  if (isSubcommandMode) {
758
1009
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -786,7 +1037,7 @@ function useAutocomplete(value, registry) {
786
1037
  };
787
1038
  }
788
1039
  function InputArea({ onSubmit, isDisabled, registry }) {
789
- const [value, setValue] = (0, import_react3.useState)("");
1040
+ const [value, setValue] = (0, import_react8.useState)("");
790
1041
  const {
791
1042
  showPopup,
792
1043
  filteredCommands,
@@ -795,7 +1046,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
795
1046
  isSubcommandMode,
796
1047
  setShowPopup
797
1048
  } = useAutocomplete(value, registry);
798
- const handleSubmit = (0, import_react3.useCallback)(
1049
+ const handleSubmit = (0, import_react8.useCallback)(
799
1050
  (text) => {
800
1051
  const trimmed = text.trim();
801
1052
  if (trimmed.length === 0) return;
@@ -808,7 +1059,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
808
1059
  },
809
1060
  [showPopup, filteredCommands, selectedIndex, onSubmit]
810
1061
  );
811
- const selectCommand = (0, import_react3.useCallback)(
1062
+ const selectCommand = (0, import_react8.useCallback)(
812
1063
  (cmd) => {
813
1064
  const parsed = parseSlashInput(value);
814
1065
  if (parsed.parentCommand) {
@@ -869,7 +1120,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
869
1120
  }
870
1121
 
871
1122
  // src/ui/ConfirmPrompt.tsx
872
- var import_react4 = require("react");
1123
+ var import_react9 = require("react");
873
1124
  var import_ink7 = require("ink");
874
1125
  var import_jsx_runtime7 = require("react/jsx-runtime");
875
1126
  function ConfirmPrompt({
@@ -877,9 +1128,9 @@ function ConfirmPrompt({
877
1128
  options = ["Yes", "No"],
878
1129
  onSelect
879
1130
  }) {
880
- const [selected, setSelected] = (0, import_react4.useState)(0);
881
- const resolvedRef = (0, import_react4.useRef)(false);
882
- const doSelect = (0, import_react4.useCallback)(
1131
+ const [selected, setSelected] = (0, import_react9.useState)(0);
1132
+ const resolvedRef = (0, import_react9.useRef)(false);
1133
+ const doSelect = (0, import_react9.useCallback)(
883
1134
  (index) => {
884
1135
  if (resolvedRef.current) return;
885
1136
  resolvedRef.current = true;
@@ -912,7 +1163,7 @@ function ConfirmPrompt({
912
1163
  }
913
1164
 
914
1165
  // src/ui/PermissionPrompt.tsx
915
- var import_react5 = __toESM(require("react"), 1);
1166
+ var import_react10 = __toESM(require("react"), 1);
916
1167
  var import_ink8 = require("ink");
917
1168
  var import_jsx_runtime8 = require("react/jsx-runtime");
918
1169
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
@@ -922,15 +1173,15 @@ function formatArgs(args) {
922
1173
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
923
1174
  }
924
1175
  function PermissionPrompt({ request }) {
925
- const [selected, setSelected] = import_react5.default.useState(0);
926
- const resolvedRef = import_react5.default.useRef(false);
927
- const prevRequestRef = import_react5.default.useRef(request);
1176
+ const [selected, setSelected] = import_react10.default.useState(0);
1177
+ const resolvedRef = import_react10.default.useRef(false);
1178
+ const prevRequestRef = import_react10.default.useRef(request);
928
1179
  if (prevRequestRef.current !== request) {
929
1180
  prevRequestRef.current = request;
930
1181
  resolvedRef.current = false;
931
1182
  setSelected(0);
932
1183
  }
933
- const doResolve = import_react5.default.useCallback(
1184
+ const doResolve = import_react10.default.useCallback(
934
1185
  (index) => {
935
1186
  if (resolvedRef.current) return;
936
1187
  resolvedRef.current = true;
@@ -975,254 +1226,54 @@ function PermissionPrompt({ request }) {
975
1226
  ] });
976
1227
  }
977
1228
 
978
- // src/utils/tool-call-extractor.ts
979
- var TOOL_ARG_MAX_LENGTH = 80;
980
- var TOOL_ARG_TRUNCATE_LENGTH = 77;
981
- function extractToolCalls(history, startIndex) {
982
- const lines = [];
983
- for (let i = startIndex; i < history.length; i++) {
984
- const msg = history[i];
985
- if (msg.role === "assistant" && msg.toolCalls) {
986
- for (const tc of msg.toolCalls) {
987
- const value = parseFirstArgValue(tc.function.arguments);
988
- const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
989
- lines.push(`${tc.function.name}(${truncated})`);
990
- }
991
- }
992
- }
993
- return lines;
994
- }
995
- function parseFirstArgValue(argsJson) {
996
- try {
997
- const parsed = JSON.parse(argsJson);
998
- const firstVal = Object.values(parsed)[0];
999
- return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
1000
- } catch {
1001
- return argsJson;
1002
- }
1003
- }
1004
-
1005
- // src/ui/App.tsx
1229
+ // src/ui/StreamingIndicator.tsx
1230
+ var import_ink9 = require("ink");
1006
1231
  var import_jsx_runtime9 = require("react/jsx-runtime");
1007
- var msgIdCounter = 0;
1008
- function nextId() {
1009
- msgIdCounter += 1;
1010
- return `msg_${msgIdCounter}`;
1011
- }
1012
- var NOOP_TERMINAL = {
1013
- write: () => {
1014
- },
1015
- writeLine: () => {
1016
- },
1017
- writeMarkdown: () => {
1018
- },
1019
- writeError: () => {
1020
- },
1021
- prompt: () => Promise.resolve(""),
1022
- select: () => Promise.resolve(0),
1023
- spinner: () => ({ stop: () => {
1024
- }, update: () => {
1025
- } })
1026
- };
1027
- function useSession(props) {
1028
- const [permissionRequest, setPermissionRequest] = (0, import_react6.useState)(null);
1029
- const [streamingText, setStreamingText] = (0, import_react6.useState)("");
1030
- const permissionQueueRef = (0, import_react6.useRef)([]);
1031
- const processingRef = (0, import_react6.useRef)(false);
1032
- const processNextPermission = (0, import_react6.useCallback)(() => {
1033
- if (processingRef.current) return;
1034
- const next = permissionQueueRef.current[0];
1035
- if (!next) {
1036
- setPermissionRequest(null);
1037
- return;
1038
- }
1039
- processingRef.current = true;
1040
- setPermissionRequest({
1041
- toolName: next.toolName,
1042
- toolArgs: next.toolArgs,
1043
- resolve: (result) => {
1044
- permissionQueueRef.current.shift();
1045
- processingRef.current = false;
1046
- setPermissionRequest(null);
1047
- next.resolve(result);
1048
- setTimeout(() => processNextPermission(), 0);
1049
- }
1050
- });
1051
- }, []);
1052
- const sessionRef = (0, import_react6.useRef)(null);
1053
- if (sessionRef.current === null) {
1054
- const permissionHandler = (toolName, toolArgs) => {
1055
- return new Promise((resolve) => {
1056
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
1057
- processNextPermission();
1058
- });
1059
- };
1060
- const onTextDelta = (delta) => {
1061
- setStreamingText((prev) => prev + delta);
1062
- };
1063
- const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
1064
- sessionRef.current = (0, import_agent_sdk.createSession)({
1065
- config: props.config,
1066
- context: props.context,
1067
- terminal: NOOP_TERMINAL,
1068
- sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
1069
- projectInfo: props.projectInfo,
1070
- sessionStore: props.sessionStore,
1071
- permissionMode: props.permissionMode,
1072
- maxTurns: props.maxTurns,
1073
- permissionHandler,
1074
- onTextDelta
1075
- });
1232
+ function StreamingIndicator({ text, activeTools }) {
1233
+ const hasTools = activeTools.length > 0;
1234
+ const hasText = text.length > 0;
1235
+ if (!hasTools && !hasText) {
1236
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
1076
1237
  }
1077
- const clearStreamingText = (0, import_react6.useCallback)(() => setStreamingText(""), []);
1078
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
1079
- }
1080
- function useMessages() {
1081
- const [messages, setMessages] = (0, import_react6.useState)([]);
1082
- const addMessage = (0, import_react6.useCallback)((msg) => {
1083
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
1084
- }, []);
1085
- return { messages, setMessages, addMessage };
1086
- }
1087
- var EXIT_DELAY_MS = 500;
1088
- function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
1089
- return (0, import_react6.useCallback)(
1090
- async (input) => {
1091
- const parts = input.slice(1).split(/\s+/);
1092
- const cmd = parts[0]?.toLowerCase() ?? "";
1093
- const args = parts.slice(1).join(" ");
1094
- const clearMessages = () => setMessages([]);
1095
- const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
1096
- if (result.pendingModelId) {
1097
- pendingModelChangeRef.current = result.pendingModelId;
1098
- setPendingModelId(result.pendingModelId);
1099
- }
1100
- if (result.exitRequested) {
1101
- setTimeout(() => exit(), EXIT_DELAY_MS);
1102
- }
1103
- return result.handled;
1104
- },
1105
- [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
1106
- );
1107
- }
1108
- function StreamingIndicator({ text }) {
1109
- if (text) {
1110
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1111
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: "cyan", bold: true, children: [
1112
- "Robota:",
1113
- " "
1114
- ] }),
1238
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1239
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1240
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "gray", bold: true, children: "Tools:" }),
1241
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1242
+ activeTools.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: t.isRunning ? "yellow" : "green", children: [
1243
+ " ",
1244
+ t.isRunning ? "\u27F3" : "\u2713",
1245
+ " ",
1246
+ t.toolName,
1247
+ "(",
1248
+ t.firstArg,
1249
+ ")"
1250
+ ] }, `${t.toolName}-${i}`))
1251
+ ] }),
1252
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: "Robota:" }),
1115
1254
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1116
1255
  /* @__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) }) })
1117
- ] });
1118
- }
1119
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
1120
- }
1121
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
1122
- setIsThinking(true);
1123
- clearStreamingText();
1124
- const historyBefore = session.getHistory().length;
1125
- try {
1126
- const response = await session.run(prompt);
1127
- clearStreamingText();
1128
- const history = session.getHistory();
1129
- const toolLines = extractToolCalls(
1130
- history,
1131
- historyBefore
1132
- );
1133
- if (toolLines.length > 0) {
1134
- addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
1135
- }
1136
- addMessage({ role: "assistant", content: response || "(empty response)" });
1137
- setContextPercentage(session.getContextState().usedPercentage);
1138
- } catch (err) {
1139
- clearStreamingText();
1140
- if (err instanceof DOMException && err.name === "AbortError") {
1141
- addMessage({ role: "system", content: "Cancelled." });
1142
- } else {
1143
- const errMsg = err instanceof Error ? err.message : String(err);
1144
- addMessage({ role: "system", content: `Error: ${errMsg}` });
1145
- }
1146
- } finally {
1147
- setIsThinking(false);
1148
- }
1256
+ ] })
1257
+ ] });
1149
1258
  }
1150
- function buildSkillPrompt(input, registry) {
1151
- const parts = input.slice(1).split(/\s+/);
1152
- const cmd = parts[0]?.toLowerCase() ?? "";
1153
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
1154
- if (!skillCmd) return null;
1155
- const args = parts.slice(1).join(" ").trim();
1156
- const userInstruction = args || skillCmd.description;
1157
- if (skillCmd.skillContent) {
1158
- return `<skill name="${cmd}">
1159
- ${skillCmd.skillContent}
1160
- </skill>
1161
1259
 
1162
- Execute the "${cmd}" skill: ${userInstruction}`;
1163
- }
1164
- return `Use the "${cmd}" skill: ${userInstruction}`;
1165
- }
1166
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
1167
- return (0, import_react6.useCallback)(
1168
- async (input) => {
1169
- if (input.startsWith("/")) {
1170
- const handled = await handleSlashCommand(input);
1171
- if (handled) {
1172
- setContextPercentage(session.getContextState().usedPercentage);
1173
- return;
1174
- }
1175
- const prompt = buildSkillPrompt(input, registry);
1176
- if (!prompt) return;
1177
- return runSessionPrompt(
1178
- prompt,
1179
- session,
1180
- addMessage,
1181
- clearStreamingText,
1182
- setIsThinking,
1183
- setContextPercentage
1184
- );
1185
- }
1186
- addMessage({ role: "user", content: input });
1187
- return runSessionPrompt(
1188
- input,
1189
- session,
1190
- addMessage,
1191
- clearStreamingText,
1192
- setIsThinking,
1193
- setContextPercentage
1194
- );
1195
- },
1196
- [
1197
- session,
1198
- addMessage,
1199
- handleSlashCommand,
1200
- clearStreamingText,
1201
- setIsThinking,
1202
- setContextPercentage,
1203
- registry
1204
- ]
1205
- );
1206
- }
1207
- function useCommandRegistry(cwd) {
1208
- const registryRef = (0, import_react6.useRef)(null);
1209
- if (registryRef.current === null) {
1210
- const registry = new CommandRegistry();
1211
- registry.addSource(new BuiltinCommandSource());
1212
- registry.addSource(new SkillCommandSource(cwd));
1213
- registryRef.current = registry;
1214
- }
1215
- return registryRef.current;
1216
- }
1260
+ // src/ui/App.tsx
1261
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1262
+ var EXIT_DELAY_MS2 = 500;
1217
1263
  function App(props) {
1218
- const { exit } = (0, import_ink9.useApp)();
1219
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
1264
+ const { exit } = (0, import_ink10.useApp)();
1265
+ const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
1220
1266
  const { messages, setMessages, addMessage } = useMessages();
1221
- const [isThinking, setIsThinking] = (0, import_react6.useState)(false);
1222
- const [contextPercentage, setContextPercentage] = (0, import_react6.useState)(0);
1267
+ const [isThinking, setIsThinking] = (0, import_react11.useState)(false);
1268
+ const initialCtx = session.getContextState();
1269
+ const [contextState, setContextState] = (0, import_react11.useState)({
1270
+ percentage: initialCtx.usedPercentage,
1271
+ usedTokens: initialCtx.usedTokens,
1272
+ maxTokens: initialCtx.maxTokens
1273
+ });
1223
1274
  const registry = useCommandRegistry(props.cwd ?? process.cwd());
1224
- const pendingModelChangeRef = (0, import_react6.useRef)(null);
1225
- const [pendingModelId, setPendingModelId] = (0, import_react6.useState)(null);
1275
+ const pendingModelChangeRef = (0, import_react11.useRef)(null);
1276
+ const [pendingModelId, setPendingModelId] = (0, import_react11.useState)(null);
1226
1277
  const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
1227
1278
  const handleSubmit = useSubmitHandler(
1228
1279
  session,
@@ -1230,36 +1281,36 @@ function App(props) {
1230
1281
  handleSlashCommand,
1231
1282
  clearStreamingText,
1232
1283
  setIsThinking,
1233
- setContextPercentage,
1284
+ setContextState,
1234
1285
  registry
1235
1286
  );
1236
- (0, import_ink9.useInput)(
1287
+ (0, import_ink10.useInput)(
1237
1288
  (_input, key) => {
1238
1289
  if (key.ctrl && _input === "c") exit();
1239
1290
  if (key.escape && isThinking) session.abort();
1240
1291
  },
1241
1292
  { isActive: !permissionRequest }
1242
1293
  );
1243
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1244
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: `
1294
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
1295
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1296
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: `
1246
1297
  ____ ___ ____ ___ _____ _
1247
1298
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1248
1299
  | |_) | | | | _ \\| | | || | / _ \\
1249
1300
  | _ <| |_| | |_) | |_| || |/ ___ \\
1250
1301
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1251
1302
  ` }),
1252
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
1303
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { dimColor: true, children: [
1253
1304
  " v",
1254
1305
  props.version ?? "0.0.0"
1255
1306
  ] })
1256
1307
  ] }),
1257
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1258
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessageList, { messages }),
1259
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StreamingIndicator, { text: streamingText }) })
1308
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageList, { messages }),
1310
+ 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 }) })
1260
1311
  ] }),
1261
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PermissionPrompt, { request: permissionRequest }),
1262
- pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1312
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PermissionPrompt, { request: permissionRequest }),
1313
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1263
1314
  ConfirmPrompt,
1264
1315
  {
1265
1316
  message: `Change model to ${(0, import_agent_core3.getModelName)(pendingModelId)}? This will restart the session.`,
@@ -1271,7 +1322,7 @@ function App(props) {
1271
1322
  const settingsPath = getUserSettingsPath();
1272
1323
  updateModelInSettings(settingsPath, pendingModelId);
1273
1324
  addMessage({ role: "system", content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...` });
1274
- setTimeout(() => exit(), 500);
1325
+ setTimeout(() => exit(), EXIT_DELAY_MS2);
1275
1326
  } catch (err) {
1276
1327
  addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
1277
1328
  }
@@ -1281,7 +1332,7 @@ function App(props) {
1281
1332
  }
1282
1333
  }
1283
1334
  ),
1284
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1335
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1285
1336
  StatusBar,
1286
1337
  {
1287
1338
  permissionMode: session.getPermissionMode(),
@@ -1289,12 +1340,12 @@ function App(props) {
1289
1340
  sessionId: session.getSessionId(),
1290
1341
  messageCount: messages.length,
1291
1342
  isThinking,
1292
- contextPercentage,
1293
- contextUsedTokens: session.getContextState().usedTokens,
1294
- contextMaxTokens: session.getContextState().maxTokens
1343
+ contextPercentage: contextState.percentage,
1344
+ contextUsedTokens: contextState.usedTokens,
1345
+ contextMaxTokens: contextState.maxTokens
1295
1346
  }
1296
1347
  ),
1297
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1348
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1298
1349
  InputArea,
1299
1350
  {
1300
1351
  onSubmit: handleSubmit,
@@ -1302,12 +1353,12 @@ function App(props) {
1302
1353
  registry
1303
1354
  }
1304
1355
  ),
1305
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " })
1356
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " })
1306
1357
  ] });
1307
1358
  }
1308
1359
 
1309
1360
  // src/ui/render.tsx
1310
- var import_jsx_runtime10 = require("react/jsx-runtime");
1361
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1311
1362
  function renderApp(options) {
1312
1363
  process.on("unhandledRejection", (reason) => {
1313
1364
  process.stderr.write(`
@@ -1318,7 +1369,7 @@ function renderApp(options) {
1318
1369
  `);
1319
1370
  }
1320
1371
  });
1321
- const instance = (0, import_ink10.render)(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(App, { ...options }), {
1372
+ const instance = (0, import_ink11.render)(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(App, { ...options }), {
1322
1373
  exitOnCtrlC: true
1323
1374
  });
1324
1375
  instance.waitUntilExit().catch((err) => {
@@ -1332,6 +1383,18 @@ function renderApp(options) {
1332
1383
 
1333
1384
  // src/cli.ts
1334
1385
  var import_meta = {};
1386
+ function hasValidSettingsFile(filePath) {
1387
+ if (!(0, import_node_fs3.existsSync)(filePath)) return false;
1388
+ try {
1389
+ const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
1390
+ if (raw.length === 0) return false;
1391
+ const parsed = JSON.parse(raw);
1392
+ const provider = parsed.provider;
1393
+ return !!provider?.apiKey;
1394
+ } catch {
1395
+ return false;
1396
+ }
1397
+ }
1335
1398
  function readVersion() {
1336
1399
  try {
1337
1400
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
@@ -1356,7 +1419,7 @@ async function ensureConfig(cwd) {
1356
1419
  const userPath = getUserSettingsPath();
1357
1420
  const projectPath = (0, import_node_path3.join)(cwd, ".robota", "settings.json");
1358
1421
  const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
1359
- if ((0, import_node_fs3.existsSync)(userPath) || (0, import_node_fs3.existsSync)(projectPath) || (0, import_node_fs3.existsSync)(localPath)) {
1422
+ if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
1360
1423
  return;
1361
1424
  }
1362
1425
  process.stdout.write("\n");