@robota-sdk/agent-cli 3.0.0-beta.18 → 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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startCli
4
- } from "./chunk-M723M4M4.js";
4
+ } from "./chunk-RE4JCNEA.js";
5
5
 
6
6
  // src/bin.ts
7
7
  process.on("uncaughtException", (err) => {
@@ -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";
150
+ import { useState as useState7, useRef as useRef5 } from "react";
151
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,33 +1209,6 @@ 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
1212
  // src/ui/StreamingIndicator.tsx
989
1213
  import { Box as Box7, Text as Text9 } from "ink";
990
1214
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -1018,240 +1242,21 @@ function StreamingIndicator({ text, activeTools }) {
1018
1242
 
1019
1243
  // src/ui/App.tsx
1020
1244
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1021
- var msgIdCounter = 0;
1022
- function nextId() {
1023
- msgIdCounter += 1;
1024
- return `msg_${msgIdCounter}`;
1025
- }
1026
- var NOOP_TERMINAL = {
1027
- write: () => {
1028
- },
1029
- writeLine: () => {
1030
- },
1031
- writeMarkdown: () => {
1032
- },
1033
- writeError: () => {
1034
- },
1035
- prompt: () => Promise.resolve(""),
1036
- select: () => Promise.resolve(0),
1037
- spinner: () => ({ stop: () => {
1038
- }, update: () => {
1039
- } })
1040
- };
1041
- function useSession(props) {
1042
- const [permissionRequest, setPermissionRequest] = useState5(null);
1043
- const [streamingText, setStreamingText] = useState5("");
1044
- const [activeTools, setActiveTools] = useState5([]);
1045
- const permissionQueueRef = useRef3([]);
1046
- const processingRef = useRef3(false);
1047
- const processNextPermission = useCallback3(() => {
1048
- if (processingRef.current) return;
1049
- const next = permissionQueueRef.current[0];
1050
- if (!next) {
1051
- setPermissionRequest(null);
1052
- return;
1053
- }
1054
- processingRef.current = true;
1055
- setPermissionRequest({
1056
- toolName: next.toolName,
1057
- toolArgs: next.toolArgs,
1058
- resolve: (result) => {
1059
- permissionQueueRef.current.shift();
1060
- processingRef.current = false;
1061
- setPermissionRequest(null);
1062
- next.resolve(result);
1063
- setTimeout(() => processNextPermission(), 0);
1064
- }
1065
- });
1066
- }, []);
1067
- const sessionRef = useRef3(null);
1068
- if (sessionRef.current === null) {
1069
- const permissionHandler = (toolName, toolArgs) => {
1070
- return new Promise((resolve) => {
1071
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
1072
- processNextPermission();
1073
- });
1074
- };
1075
- const onTextDelta = (delta) => {
1076
- setStreamingText((prev) => prev + delta);
1077
- };
1078
- const TOOL_ARG_DISPLAY_MAX = 80;
1079
- const TOOL_ARG_TRUNCATE_AT = 77;
1080
- const onToolExecution = (event) => {
1081
- if (event.type === "start") {
1082
- let firstArg = "";
1083
- if (event.toolArgs) {
1084
- const firstVal = Object.values(event.toolArgs)[0];
1085
- const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
1086
- firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
1087
- }
1088
- setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
1089
- } else {
1090
- setActiveTools(
1091
- (prev) => prev.map(
1092
- (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
1093
- )
1094
- );
1095
- }
1096
- };
1097
- const paths = projectPaths(props.cwd ?? process.cwd());
1098
- sessionRef.current = createSession({
1099
- config: props.config,
1100
- context: props.context,
1101
- terminal: NOOP_TERMINAL,
1102
- sessionLogger: new FileSessionLogger(paths.logs),
1103
- projectInfo: props.projectInfo,
1104
- sessionStore: props.sessionStore,
1105
- permissionMode: props.permissionMode,
1106
- maxTurns: props.maxTurns,
1107
- permissionHandler,
1108
- onTextDelta,
1109
- onToolExecution
1110
- });
1111
- }
1112
- const clearStreamingText = useCallback3(() => {
1113
- setStreamingText("");
1114
- setActiveTools([]);
1115
- }, []);
1116
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
1117
- }
1118
- function useMessages() {
1119
- const [messages, setMessages] = useState5([]);
1120
- const addMessage = useCallback3((msg) => {
1121
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
1122
- }, []);
1123
- return { messages, setMessages, addMessage };
1124
- }
1125
- var EXIT_DELAY_MS = 500;
1126
- function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
1127
- return useCallback3(
1128
- async (input) => {
1129
- const parts = input.slice(1).split(/\s+/);
1130
- const cmd = parts[0]?.toLowerCase() ?? "";
1131
- const args = parts.slice(1).join(" ");
1132
- const clearMessages = () => setMessages([]);
1133
- const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
1134
- if (result.pendingModelId) {
1135
- pendingModelChangeRef.current = result.pendingModelId;
1136
- setPendingModelId(result.pendingModelId);
1137
- }
1138
- if (result.exitRequested) {
1139
- setTimeout(() => exit(), EXIT_DELAY_MS);
1140
- }
1141
- return result.handled;
1142
- },
1143
- [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
1144
- );
1145
- }
1146
- function syncContextState(session, setter) {
1147
- const ctx = session.getContextState();
1148
- setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
1149
- }
1150
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
1151
- setIsThinking(true);
1152
- clearStreamingText();
1153
- const historyBefore = session.getHistory().length;
1154
- try {
1155
- const response = await session.run(prompt);
1156
- clearStreamingText();
1157
- const history = session.getHistory();
1158
- const toolLines = extractToolCalls(
1159
- history,
1160
- historyBefore
1161
- );
1162
- if (toolLines.length > 0) {
1163
- addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
1164
- }
1165
- addMessage({ role: "assistant", content: response || "(empty response)" });
1166
- syncContextState(session, setContextState);
1167
- } catch (err) {
1168
- clearStreamingText();
1169
- if (err instanceof DOMException && err.name === "AbortError") {
1170
- addMessage({ role: "system", content: "Cancelled." });
1171
- } else {
1172
- const errMsg = err instanceof Error ? err.message : String(err);
1173
- addMessage({ role: "system", content: `Error: ${errMsg}` });
1174
- }
1175
- } finally {
1176
- setIsThinking(false);
1177
- }
1178
- }
1179
- function buildSkillPrompt(input, registry) {
1180
- const parts = input.slice(1).split(/\s+/);
1181
- const cmd = parts[0]?.toLowerCase() ?? "";
1182
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
1183
- if (!skillCmd) return null;
1184
- const args = parts.slice(1).join(" ").trim();
1185
- const userInstruction = args || skillCmd.description;
1186
- if (skillCmd.skillContent) {
1187
- return `<skill name="${cmd}">
1188
- ${skillCmd.skillContent}
1189
- </skill>
1190
-
1191
- Execute the "${cmd}" skill: ${userInstruction}`;
1192
- }
1193
- return `Use the "${cmd}" skill: ${userInstruction}`;
1194
- }
1195
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
1196
- return useCallback3(
1197
- async (input) => {
1198
- if (input.startsWith("/")) {
1199
- const handled = await handleSlashCommand(input);
1200
- if (handled) {
1201
- syncContextState(session, setContextState);
1202
- return;
1203
- }
1204
- const prompt = buildSkillPrompt(input, registry);
1205
- if (!prompt) return;
1206
- return runSessionPrompt(
1207
- prompt,
1208
- session,
1209
- addMessage,
1210
- clearStreamingText,
1211
- setIsThinking,
1212
- setContextState
1213
- );
1214
- }
1215
- addMessage({ role: "user", content: input });
1216
- return runSessionPrompt(
1217
- input,
1218
- session,
1219
- addMessage,
1220
- clearStreamingText,
1221
- setIsThinking,
1222
- setContextState
1223
- );
1224
- },
1225
- [
1226
- session,
1227
- addMessage,
1228
- handleSlashCommand,
1229
- clearStreamingText,
1230
- setIsThinking,
1231
- setContextState,
1232
- registry
1233
- ]
1234
- );
1235
- }
1236
- function useCommandRegistry(cwd) {
1237
- const registryRef = useRef3(null);
1238
- if (registryRef.current === null) {
1239
- const registry = new CommandRegistry();
1240
- registry.addSource(new BuiltinCommandSource());
1241
- registry.addSource(new SkillCommandSource(cwd));
1242
- registryRef.current = registry;
1243
- }
1244
- return registryRef.current;
1245
- }
1245
+ var EXIT_DELAY_MS2 = 500;
1246
1246
  function App(props) {
1247
1247
  const { exit } = useApp();
1248
1248
  const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
1249
1249
  const { messages, setMessages, addMessage } = useMessages();
1250
- const [isThinking, setIsThinking] = useState5(false);
1251
- const [contextState, setContextState] = useState5({ percentage: 0, usedTokens: 0, maxTokens: 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
+ });
1252
1257
  const registry = useCommandRegistry(props.cwd ?? process.cwd());
1253
- const pendingModelChangeRef = useRef3(null);
1254
- const [pendingModelId, setPendingModelId] = useState5(null);
1258
+ const pendingModelChangeRef = useRef5(null);
1259
+ const [pendingModelId, setPendingModelId] = useState7(null);
1255
1260
  const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
1256
1261
  const handleSubmit = useSubmitHandler(
1257
1262
  session,
@@ -1300,7 +1305,7 @@ function App(props) {
1300
1305
  const settingsPath = getUserSettingsPath();
1301
1306
  updateModelInSettings(settingsPath, pendingModelId);
1302
1307
  addMessage({ role: "system", content: `Model changed to ${getModelName(pendingModelId)}. Restarting...` });
1303
- setTimeout(() => exit(), 500);
1308
+ setTimeout(() => exit(), EXIT_DELAY_MS2);
1304
1309
  } catch (err) {
1305
1310
  addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
1306
1311
  }