@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 +353 -290
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-4WB3L3RU.js → chunk-RE4JCNEA.js} +342 -279
- package/dist/node/index.cjs +353 -290
- package/dist/node/index.js +1 -1
- package/package.json +3 -3
|
@@ -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
|
|
151
|
-
import { Box as
|
|
150
|
+
import { useState as useState7, useRef as useRef5 } from "react";
|
|
151
|
+
import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
|
|
152
|
+
import { getModelName } from "@robota-sdk/agent-core";
|
|
153
|
+
|
|
154
|
+
// src/ui/hooks/useSession.ts
|
|
155
|
+
import { useState, useCallback, useRef } from "react";
|
|
156
|
+
import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
|
|
157
|
+
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
158
|
+
var TOOL_ARG_TRUNCATE_AT = 77;
|
|
159
|
+
var NOOP_TERMINAL = {
|
|
160
|
+
write: () => {
|
|
161
|
+
},
|
|
162
|
+
writeLine: () => {
|
|
163
|
+
},
|
|
164
|
+
writeMarkdown: () => {
|
|
165
|
+
},
|
|
166
|
+
writeError: () => {
|
|
167
|
+
},
|
|
168
|
+
prompt: () => Promise.resolve(""),
|
|
169
|
+
select: () => Promise.resolve(0),
|
|
170
|
+
spinner: () => ({ stop: () => {
|
|
171
|
+
}, update: () => {
|
|
172
|
+
} })
|
|
173
|
+
};
|
|
174
|
+
function useSession(props) {
|
|
175
|
+
const [permissionRequest, setPermissionRequest] = useState(null);
|
|
176
|
+
const [streamingText, setStreamingText] = useState("");
|
|
177
|
+
const [activeTools, setActiveTools] = useState([]);
|
|
178
|
+
const permissionQueueRef = useRef([]);
|
|
179
|
+
const processingRef = useRef(false);
|
|
180
|
+
const processNextPermission = useCallback(() => {
|
|
181
|
+
if (processingRef.current) return;
|
|
182
|
+
const next = permissionQueueRef.current[0];
|
|
183
|
+
if (!next) {
|
|
184
|
+
setPermissionRequest(null);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
processingRef.current = true;
|
|
188
|
+
setPermissionRequest({
|
|
189
|
+
toolName: next.toolName,
|
|
190
|
+
toolArgs: next.toolArgs,
|
|
191
|
+
resolve: (result) => {
|
|
192
|
+
permissionQueueRef.current.shift();
|
|
193
|
+
processingRef.current = false;
|
|
194
|
+
setPermissionRequest(null);
|
|
195
|
+
next.resolve(result);
|
|
196
|
+
setTimeout(() => processNextPermission(), 0);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}, []);
|
|
200
|
+
const sessionRef = useRef(null);
|
|
201
|
+
if (sessionRef.current === null) {
|
|
202
|
+
const permissionHandler = (toolName, toolArgs) => {
|
|
203
|
+
return new Promise((resolve) => {
|
|
204
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
205
|
+
processNextPermission();
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
const onTextDelta = (delta) => {
|
|
209
|
+
setStreamingText((prev) => prev + delta);
|
|
210
|
+
};
|
|
211
|
+
const onToolExecution = (event) => {
|
|
212
|
+
if (event.type === "start") {
|
|
213
|
+
let firstArg = "";
|
|
214
|
+
if (event.toolArgs) {
|
|
215
|
+
const firstVal = Object.values(event.toolArgs)[0];
|
|
216
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
217
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
|
|
218
|
+
}
|
|
219
|
+
setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
|
|
220
|
+
} else {
|
|
221
|
+
setActiveTools(
|
|
222
|
+
(prev) => prev.map(
|
|
223
|
+
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
|
|
224
|
+
)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const paths = projectPaths(props.cwd ?? process.cwd());
|
|
229
|
+
sessionRef.current = createSession({
|
|
230
|
+
config: props.config,
|
|
231
|
+
context: props.context,
|
|
232
|
+
terminal: NOOP_TERMINAL,
|
|
233
|
+
sessionLogger: new FileSessionLogger(paths.logs),
|
|
234
|
+
projectInfo: props.projectInfo,
|
|
235
|
+
sessionStore: props.sessionStore,
|
|
236
|
+
permissionMode: props.permissionMode,
|
|
237
|
+
maxTurns: props.maxTurns,
|
|
238
|
+
permissionHandler,
|
|
239
|
+
onTextDelta,
|
|
240
|
+
onToolExecution
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const clearStreamingText = useCallback(() => {
|
|
244
|
+
setStreamingText("");
|
|
245
|
+
setActiveTools([]);
|
|
246
|
+
}, []);
|
|
247
|
+
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/ui/hooks/useMessages.ts
|
|
251
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
252
|
+
var msgIdCounter = 0;
|
|
253
|
+
function nextId() {
|
|
254
|
+
msgIdCounter += 1;
|
|
255
|
+
return `msg_${msgIdCounter}`;
|
|
256
|
+
}
|
|
257
|
+
function useMessages() {
|
|
258
|
+
const [messages, setMessages] = useState2([]);
|
|
259
|
+
const addMessage = useCallback2((msg) => {
|
|
260
|
+
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
261
|
+
}, []);
|
|
262
|
+
return { messages, setMessages, addMessage };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
266
|
+
import { useCallback as useCallback3 } from "react";
|
|
152
267
|
|
|
153
268
|
// src/commands/slash-executor.ts
|
|
154
269
|
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
@@ -273,9 +388,133 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
|
|
|
273
388
|
}
|
|
274
389
|
}
|
|
275
390
|
|
|
276
|
-
// src/ui/
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
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 =
|
|
577
|
-
const cursorRef =
|
|
578
|
-
const [, forceRender] =
|
|
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
|
|
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] =
|
|
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] =
|
|
730
|
-
const [dismissed, setDismissed] =
|
|
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] =
|
|
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 =
|
|
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 =
|
|
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
|
|
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] =
|
|
864
|
-
const resolvedRef =
|
|
865
|
-
const doSelect =
|
|
1114
|
+
const [selected, setSelected] = useState6(0);
|
|
1115
|
+
const resolvedRef = useRef4(false);
|
|
1116
|
+
const doSelect = useCallback6(
|
|
866
1117
|
(index) => {
|
|
867
1118
|
if (resolvedRef.current) return;
|
|
868
1119
|
resolvedRef.current = true;
|
|
@@ -958,254 +1209,54 @@ function PermissionPrompt({ request }) {
|
|
|
958
1209
|
] });
|
|
959
1210
|
}
|
|
960
1211
|
|
|
961
|
-
// src/
|
|
962
|
-
|
|
963
|
-
var TOOL_ARG_TRUNCATE_LENGTH = 77;
|
|
964
|
-
function extractToolCalls(history, startIndex) {
|
|
965
|
-
const lines = [];
|
|
966
|
-
for (let i = startIndex; i < history.length; i++) {
|
|
967
|
-
const msg = history[i];
|
|
968
|
-
if (msg.role === "assistant" && msg.toolCalls) {
|
|
969
|
-
for (const tc of msg.toolCalls) {
|
|
970
|
-
const value = parseFirstArgValue(tc.function.arguments);
|
|
971
|
-
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
|
|
972
|
-
lines.push(`${tc.function.name}(${truncated})`);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return lines;
|
|
977
|
-
}
|
|
978
|
-
function parseFirstArgValue(argsJson) {
|
|
979
|
-
try {
|
|
980
|
-
const parsed = JSON.parse(argsJson);
|
|
981
|
-
const firstVal = Object.values(parsed)[0];
|
|
982
|
-
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
983
|
-
} catch {
|
|
984
|
-
return argsJson;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// src/ui/App.tsx
|
|
1212
|
+
// src/ui/StreamingIndicator.tsx
|
|
1213
|
+
import { Box as Box7, Text as Text9 } from "ink";
|
|
989
1214
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}
|
|
995
|
-
var NOOP_TERMINAL = {
|
|
996
|
-
write: () => {
|
|
997
|
-
},
|
|
998
|
-
writeLine: () => {
|
|
999
|
-
},
|
|
1000
|
-
writeMarkdown: () => {
|
|
1001
|
-
},
|
|
1002
|
-
writeError: () => {
|
|
1003
|
-
},
|
|
1004
|
-
prompt: () => Promise.resolve(""),
|
|
1005
|
-
select: () => Promise.resolve(0),
|
|
1006
|
-
spinner: () => ({ stop: () => {
|
|
1007
|
-
}, update: () => {
|
|
1008
|
-
} })
|
|
1009
|
-
};
|
|
1010
|
-
function useSession(props) {
|
|
1011
|
-
const [permissionRequest, setPermissionRequest] = useState5(null);
|
|
1012
|
-
const [streamingText, setStreamingText] = useState5("");
|
|
1013
|
-
const permissionQueueRef = useRef3([]);
|
|
1014
|
-
const processingRef = useRef3(false);
|
|
1015
|
-
const processNextPermission = useCallback3(() => {
|
|
1016
|
-
if (processingRef.current) return;
|
|
1017
|
-
const next = permissionQueueRef.current[0];
|
|
1018
|
-
if (!next) {
|
|
1019
|
-
setPermissionRequest(null);
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
processingRef.current = true;
|
|
1023
|
-
setPermissionRequest({
|
|
1024
|
-
toolName: next.toolName,
|
|
1025
|
-
toolArgs: next.toolArgs,
|
|
1026
|
-
resolve: (result) => {
|
|
1027
|
-
permissionQueueRef.current.shift();
|
|
1028
|
-
processingRef.current = false;
|
|
1029
|
-
setPermissionRequest(null);
|
|
1030
|
-
next.resolve(result);
|
|
1031
|
-
setTimeout(() => processNextPermission(), 0);
|
|
1032
|
-
}
|
|
1033
|
-
});
|
|
1034
|
-
}, []);
|
|
1035
|
-
const sessionRef = useRef3(null);
|
|
1036
|
-
if (sessionRef.current === null) {
|
|
1037
|
-
const permissionHandler = (toolName, toolArgs) => {
|
|
1038
|
-
return new Promise((resolve) => {
|
|
1039
|
-
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
1040
|
-
processNextPermission();
|
|
1041
|
-
});
|
|
1042
|
-
};
|
|
1043
|
-
const onTextDelta = (delta) => {
|
|
1044
|
-
setStreamingText((prev) => prev + delta);
|
|
1045
|
-
};
|
|
1046
|
-
const paths = projectPaths(props.cwd ?? process.cwd());
|
|
1047
|
-
sessionRef.current = createSession({
|
|
1048
|
-
config: props.config,
|
|
1049
|
-
context: props.context,
|
|
1050
|
-
terminal: NOOP_TERMINAL,
|
|
1051
|
-
sessionLogger: new FileSessionLogger(paths.logs),
|
|
1052
|
-
projectInfo: props.projectInfo,
|
|
1053
|
-
sessionStore: props.sessionStore,
|
|
1054
|
-
permissionMode: props.permissionMode,
|
|
1055
|
-
maxTurns: props.maxTurns,
|
|
1056
|
-
permissionHandler,
|
|
1057
|
-
onTextDelta
|
|
1058
|
-
});
|
|
1215
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
1216
|
+
const hasTools = activeTools.length > 0;
|
|
1217
|
+
const hasText = text.length > 0;
|
|
1218
|
+
if (!hasTools && !hasText) {
|
|
1219
|
+
return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
|
|
1059
1220
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const args = parts.slice(1).join(" ");
|
|
1077
|
-
const clearMessages = () => setMessages([]);
|
|
1078
|
-
const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
|
|
1079
|
-
if (result.pendingModelId) {
|
|
1080
|
-
pendingModelChangeRef.current = result.pendingModelId;
|
|
1081
|
-
setPendingModelId(result.pendingModelId);
|
|
1082
|
-
}
|
|
1083
|
-
if (result.exitRequested) {
|
|
1084
|
-
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
1085
|
-
}
|
|
1086
|
-
return result.handled;
|
|
1087
|
-
},
|
|
1088
|
-
[session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
function StreamingIndicator({ text }) {
|
|
1092
|
-
if (text) {
|
|
1093
|
-
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1094
|
-
/* @__PURE__ */ jsxs7(Text9, { color: "cyan", bold: true, children: [
|
|
1095
|
-
"Robota:",
|
|
1096
|
-
" "
|
|
1097
|
-
] }),
|
|
1221
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1222
|
+
hasTools && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
1223
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", bold: true, children: "Tools:" }),
|
|
1224
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1225
|
+
activeTools.map((t, i) => /* @__PURE__ */ jsxs7(Text9, { color: t.isRunning ? "yellow" : "green", children: [
|
|
1226
|
+
" ",
|
|
1227
|
+
t.isRunning ? "\u27F3" : "\u2713",
|
|
1228
|
+
" ",
|
|
1229
|
+
t.toolName,
|
|
1230
|
+
"(",
|
|
1231
|
+
t.firstArg,
|
|
1232
|
+
")"
|
|
1233
|
+
] }, `${t.toolName}-${i}`))
|
|
1234
|
+
] }),
|
|
1235
|
+
hasText && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
1236
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "Robota:" }),
|
|
1098
1237
|
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1099
1238
|
/* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
1100
|
-
] })
|
|
1101
|
-
}
|
|
1102
|
-
return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
|
|
1103
|
-
}
|
|
1104
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
|
|
1105
|
-
setIsThinking(true);
|
|
1106
|
-
clearStreamingText();
|
|
1107
|
-
const historyBefore = session.getHistory().length;
|
|
1108
|
-
try {
|
|
1109
|
-
const response = await session.run(prompt);
|
|
1110
|
-
clearStreamingText();
|
|
1111
|
-
const history = session.getHistory();
|
|
1112
|
-
const toolLines = extractToolCalls(
|
|
1113
|
-
history,
|
|
1114
|
-
historyBefore
|
|
1115
|
-
);
|
|
1116
|
-
if (toolLines.length > 0) {
|
|
1117
|
-
addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
|
|
1118
|
-
}
|
|
1119
|
-
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
1120
|
-
setContextPercentage(session.getContextState().usedPercentage);
|
|
1121
|
-
} catch (err) {
|
|
1122
|
-
clearStreamingText();
|
|
1123
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
1124
|
-
addMessage({ role: "system", content: "Cancelled." });
|
|
1125
|
-
} else {
|
|
1126
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1127
|
-
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
1128
|
-
}
|
|
1129
|
-
} finally {
|
|
1130
|
-
setIsThinking(false);
|
|
1131
|
-
}
|
|
1239
|
+
] })
|
|
1240
|
+
] });
|
|
1132
1241
|
}
|
|
1133
|
-
function buildSkillPrompt(input, registry) {
|
|
1134
|
-
const parts = input.slice(1).split(/\s+/);
|
|
1135
|
-
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
1136
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
1137
|
-
if (!skillCmd) return null;
|
|
1138
|
-
const args = parts.slice(1).join(" ").trim();
|
|
1139
|
-
const userInstruction = args || skillCmd.description;
|
|
1140
|
-
if (skillCmd.skillContent) {
|
|
1141
|
-
return `<skill name="${cmd}">
|
|
1142
|
-
${skillCmd.skillContent}
|
|
1143
|
-
</skill>
|
|
1144
1242
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
|
|
1150
|
-
return useCallback3(
|
|
1151
|
-
async (input) => {
|
|
1152
|
-
if (input.startsWith("/")) {
|
|
1153
|
-
const handled = await handleSlashCommand(input);
|
|
1154
|
-
if (handled) {
|
|
1155
|
-
setContextPercentage(session.getContextState().usedPercentage);
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
const prompt = buildSkillPrompt(input, registry);
|
|
1159
|
-
if (!prompt) return;
|
|
1160
|
-
return runSessionPrompt(
|
|
1161
|
-
prompt,
|
|
1162
|
-
session,
|
|
1163
|
-
addMessage,
|
|
1164
|
-
clearStreamingText,
|
|
1165
|
-
setIsThinking,
|
|
1166
|
-
setContextPercentage
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
addMessage({ role: "user", content: input });
|
|
1170
|
-
return runSessionPrompt(
|
|
1171
|
-
input,
|
|
1172
|
-
session,
|
|
1173
|
-
addMessage,
|
|
1174
|
-
clearStreamingText,
|
|
1175
|
-
setIsThinking,
|
|
1176
|
-
setContextPercentage
|
|
1177
|
-
);
|
|
1178
|
-
},
|
|
1179
|
-
[
|
|
1180
|
-
session,
|
|
1181
|
-
addMessage,
|
|
1182
|
-
handleSlashCommand,
|
|
1183
|
-
clearStreamingText,
|
|
1184
|
-
setIsThinking,
|
|
1185
|
-
setContextPercentage,
|
|
1186
|
-
registry
|
|
1187
|
-
]
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
function useCommandRegistry(cwd) {
|
|
1191
|
-
const registryRef = useRef3(null);
|
|
1192
|
-
if (registryRef.current === null) {
|
|
1193
|
-
const registry = new CommandRegistry();
|
|
1194
|
-
registry.addSource(new BuiltinCommandSource());
|
|
1195
|
-
registry.addSource(new SkillCommandSource(cwd));
|
|
1196
|
-
registryRef.current = registry;
|
|
1197
|
-
}
|
|
1198
|
-
return registryRef.current;
|
|
1199
|
-
}
|
|
1243
|
+
// src/ui/App.tsx
|
|
1244
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1245
|
+
var EXIT_DELAY_MS2 = 500;
|
|
1200
1246
|
function App(props) {
|
|
1201
1247
|
const { exit } = useApp();
|
|
1202
|
-
const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
|
|
1248
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
|
|
1203
1249
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1204
|
-
const [isThinking, setIsThinking] =
|
|
1205
|
-
const
|
|
1250
|
+
const [isThinking, setIsThinking] = useState7(false);
|
|
1251
|
+
const initialCtx = session.getContextState();
|
|
1252
|
+
const [contextState, setContextState] = useState7({
|
|
1253
|
+
percentage: initialCtx.usedPercentage,
|
|
1254
|
+
usedTokens: initialCtx.usedTokens,
|
|
1255
|
+
maxTokens: initialCtx.maxTokens
|
|
1256
|
+
});
|
|
1206
1257
|
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1207
|
-
const pendingModelChangeRef =
|
|
1208
|
-
const [pendingModelId, setPendingModelId] =
|
|
1258
|
+
const pendingModelChangeRef = useRef5(null);
|
|
1259
|
+
const [pendingModelId, setPendingModelId] = useState7(null);
|
|
1209
1260
|
const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
|
|
1210
1261
|
const handleSubmit = useSubmitHandler(
|
|
1211
1262
|
session,
|
|
@@ -1213,7 +1264,7 @@ function App(props) {
|
|
|
1213
1264
|
handleSlashCommand,
|
|
1214
1265
|
clearStreamingText,
|
|
1215
1266
|
setIsThinking,
|
|
1216
|
-
|
|
1267
|
+
setContextState,
|
|
1217
1268
|
registry
|
|
1218
1269
|
);
|
|
1219
1270
|
useInput5(
|
|
@@ -1223,26 +1274,26 @@ function App(props) {
|
|
|
1223
1274
|
},
|
|
1224
1275
|
{ isActive: !permissionRequest }
|
|
1225
1276
|
);
|
|
1226
|
-
return /* @__PURE__ */
|
|
1227
|
-
/* @__PURE__ */
|
|
1228
|
-
/* @__PURE__ */
|
|
1277
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1278
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1279
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: `
|
|
1229
1280
|
____ ___ ____ ___ _____ _
|
|
1230
1281
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
1231
1282
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
1232
1283
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
1233
1284
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
1234
1285
|
` }),
|
|
1235
|
-
/* @__PURE__ */
|
|
1286
|
+
/* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
|
|
1236
1287
|
" v",
|
|
1237
1288
|
props.version ?? "0.0.0"
|
|
1238
1289
|
] })
|
|
1239
1290
|
] }),
|
|
1240
|
-
/* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
1242
|
-
isThinking && /* @__PURE__ */
|
|
1291
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
1292
|
+
/* @__PURE__ */ jsx10(MessageList, { messages }),
|
|
1293
|
+
isThinking && /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx10(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
1243
1294
|
] }),
|
|
1244
|
-
permissionRequest && /* @__PURE__ */
|
|
1245
|
-
pendingModelId && /* @__PURE__ */
|
|
1295
|
+
permissionRequest && /* @__PURE__ */ jsx10(PermissionPrompt, { request: permissionRequest }),
|
|
1296
|
+
pendingModelId && /* @__PURE__ */ jsx10(
|
|
1246
1297
|
ConfirmPrompt,
|
|
1247
1298
|
{
|
|
1248
1299
|
message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
|
|
@@ -1254,7 +1305,7 @@ function App(props) {
|
|
|
1254
1305
|
const settingsPath = getUserSettingsPath();
|
|
1255
1306
|
updateModelInSettings(settingsPath, pendingModelId);
|
|
1256
1307
|
addMessage({ role: "system", content: `Model changed to ${getModelName(pendingModelId)}. Restarting...` });
|
|
1257
|
-
setTimeout(() => exit(),
|
|
1308
|
+
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
1258
1309
|
} catch (err) {
|
|
1259
1310
|
addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
1260
1311
|
}
|
|
@@ -1264,7 +1315,7 @@ function App(props) {
|
|
|
1264
1315
|
}
|
|
1265
1316
|
}
|
|
1266
1317
|
),
|
|
1267
|
-
/* @__PURE__ */
|
|
1318
|
+
/* @__PURE__ */ jsx10(
|
|
1268
1319
|
StatusBar,
|
|
1269
1320
|
{
|
|
1270
1321
|
permissionMode: session.getPermissionMode(),
|
|
@@ -1272,12 +1323,12 @@ function App(props) {
|
|
|
1272
1323
|
sessionId: session.getSessionId(),
|
|
1273
1324
|
messageCount: messages.length,
|
|
1274
1325
|
isThinking,
|
|
1275
|
-
contextPercentage,
|
|
1276
|
-
contextUsedTokens:
|
|
1277
|
-
contextMaxTokens:
|
|
1326
|
+
contextPercentage: contextState.percentage,
|
|
1327
|
+
contextUsedTokens: contextState.usedTokens,
|
|
1328
|
+
contextMaxTokens: contextState.maxTokens
|
|
1278
1329
|
}
|
|
1279
1330
|
),
|
|
1280
|
-
/* @__PURE__ */
|
|
1331
|
+
/* @__PURE__ */ jsx10(
|
|
1281
1332
|
InputArea,
|
|
1282
1333
|
{
|
|
1283
1334
|
onSubmit: handleSubmit,
|
|
@@ -1285,12 +1336,12 @@ function App(props) {
|
|
|
1285
1336
|
registry
|
|
1286
1337
|
}
|
|
1287
1338
|
),
|
|
1288
|
-
/* @__PURE__ */
|
|
1339
|
+
/* @__PURE__ */ jsx10(Text10, { children: " " })
|
|
1289
1340
|
] });
|
|
1290
1341
|
}
|
|
1291
1342
|
|
|
1292
1343
|
// src/ui/render.tsx
|
|
1293
|
-
import { jsx as
|
|
1344
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1294
1345
|
function renderApp(options) {
|
|
1295
1346
|
process.on("unhandledRejection", (reason) => {
|
|
1296
1347
|
process.stderr.write(`
|
|
@@ -1301,7 +1352,7 @@ function renderApp(options) {
|
|
|
1301
1352
|
`);
|
|
1302
1353
|
}
|
|
1303
1354
|
});
|
|
1304
|
-
const instance = render(/* @__PURE__ */
|
|
1355
|
+
const instance = render(/* @__PURE__ */ jsx11(App, { ...options }), {
|
|
1305
1356
|
exitOnCtrlC: true
|
|
1306
1357
|
});
|
|
1307
1358
|
instance.waitUntilExit().catch((err) => {
|
|
@@ -1314,6 +1365,18 @@ function renderApp(options) {
|
|
|
1314
1365
|
}
|
|
1315
1366
|
|
|
1316
1367
|
// src/cli.ts
|
|
1368
|
+
function hasValidSettingsFile(filePath) {
|
|
1369
|
+
if (!existsSync3(filePath)) return false;
|
|
1370
|
+
try {
|
|
1371
|
+
const raw = readFileSync3(filePath, "utf8").trim();
|
|
1372
|
+
if (raw.length === 0) return false;
|
|
1373
|
+
const parsed = JSON.parse(raw);
|
|
1374
|
+
const provider = parsed.provider;
|
|
1375
|
+
return !!provider?.apiKey;
|
|
1376
|
+
} catch {
|
|
1377
|
+
return false;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1317
1380
|
function readVersion() {
|
|
1318
1381
|
try {
|
|
1319
1382
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -1338,7 +1401,7 @@ async function ensureConfig(cwd) {
|
|
|
1338
1401
|
const userPath = getUserSettingsPath();
|
|
1339
1402
|
const projectPath = join3(cwd, ".robota", "settings.json");
|
|
1340
1403
|
const localPath = join3(cwd, ".robota", "settings.local.json");
|
|
1341
|
-
if (
|
|
1404
|
+
if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
|
|
1342
1405
|
return;
|
|
1343
1406
|
}
|
|
1344
1407
|
process.stdout.write("\n");
|