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