@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.cjs +290 -285
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-M723M4M4.js → chunk-RE4JCNEA.js} +282 -277
- package/dist/node/index.cjs +290 -285
- package/dist/node/index.js +1 -1
- package/package.json +3 -3
package/dist/node/bin.js
CHANGED
|
@@ -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
|
|
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/
|
|
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,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
|
|
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] =
|
|
1251
|
-
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
|
+
});
|
|
1252
1257
|
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1253
|
-
const pendingModelChangeRef =
|
|
1254
|
-
const [pendingModelId, setPendingModelId] =
|
|
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(),
|
|
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
|
}
|