neoctl 0.1.7 → 0.1.9
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/README.md +29 -1
- package/dist/core/query-engine.d.ts +2 -0
- package/dist/core/query-engine.js +20 -0
- package/dist/core/query-engine.js.map +1 -1
- package/dist/model/context-window.js +1 -0
- package/dist/model/context-window.js.map +1 -1
- package/dist/repl/commands.d.ts +8 -0
- package/dist/repl/commands.js +45 -0
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +229 -73
- package/dist/repl/index.js.map +1 -1
- package/dist/web/html.js +183 -39
- package/dist/web/html.js.map +1 -1
- package/dist/web/index.js +327 -38
- package/dist/web/index.js.map +1 -1
- package/package.json +4 -1
- package/scripts/build-standalone.mjs +139 -0
package/dist/repl/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { planTool } from "../tools/builtins/plan-tool.js";
|
|
|
23
23
|
import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
|
|
24
24
|
import { createTaskTools } from "../tasks/task-tools.js";
|
|
25
25
|
import { TaskStore } from "../tasks/task-store.js";
|
|
26
|
-
import { isModelReasoningArgument, isValidReplCommandLine, parseReplCommand, helpText, replCommandDefinitions } from "./commands.js";
|
|
26
|
+
import { cliHelpText, isModelReasoningArgument, isValidReplCommandLine, parseCliReplCommandArgs, parseReplCommand, helpText, replCommandDefinitions } from "./commands.js";
|
|
27
27
|
import { estimateMarkdownLineCount, markdownRenderKey, MarkdownText } from "./markdown-renderer.js";
|
|
28
28
|
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
29
29
|
import { readClipboard } from "./clipboard.js";
|
|
@@ -86,14 +86,32 @@ function sumUsageTokens(left, right) {
|
|
|
86
86
|
return undefined;
|
|
87
87
|
return (left ?? 0) + (right ?? 0);
|
|
88
88
|
}
|
|
89
|
-
async function main() {
|
|
89
|
+
async function main(argv = process.argv.slice(2)) {
|
|
90
|
+
const initialCommand = parseCliReplCommandArgs(argv);
|
|
91
|
+
if (argv.length > 0 && !initialCommand) {
|
|
92
|
+
console.error(`Unknown or incomplete command: ${argv.join(" ")}\n\n${cliHelpText(binaryName())}`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (initialCommand?.definition.name === "/help") {
|
|
97
|
+
console.log(cliHelpText(binaryName()));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
90
100
|
const runtime = await createRuntime();
|
|
91
|
-
const instance = render(e(InkRepl, { runtime }), {
|
|
101
|
+
const instance = render(e(InkRepl, { runtime, initialCommandLine: initialCommand?.line }), {
|
|
92
102
|
exitOnCtrlC: false,
|
|
93
103
|
});
|
|
94
104
|
await instance.waitUntilExit();
|
|
95
105
|
console.log("bye.");
|
|
96
106
|
}
|
|
107
|
+
function binaryName() {
|
|
108
|
+
const arg = process.argv[1];
|
|
109
|
+
if (!arg)
|
|
110
|
+
return "neo";
|
|
111
|
+
const parsed = path.parse(arg);
|
|
112
|
+
const name = parsed.name || "neo";
|
|
113
|
+
return name === "index" ? "neo" : name;
|
|
114
|
+
}
|
|
97
115
|
function createTaskNotificationSource(taskStore) {
|
|
98
116
|
return {
|
|
99
117
|
collectUnnotifiedCompletions() {
|
|
@@ -205,6 +223,12 @@ function initialContextMetrics(model, messageCount, toolCount) {
|
|
|
205
223
|
: undefined,
|
|
206
224
|
};
|
|
207
225
|
}
|
|
226
|
+
function activeBackgroundTasks(runtime) {
|
|
227
|
+
return runtime.taskStore.list().filter((task) => !runtime.taskStore.isTerminal(task));
|
|
228
|
+
}
|
|
229
|
+
function runningSessionIds(runs) {
|
|
230
|
+
return [...runs.keys()];
|
|
231
|
+
}
|
|
208
232
|
function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
209
233
|
return {
|
|
210
234
|
phase: "ready",
|
|
@@ -216,8 +240,8 @@ function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
|
216
240
|
activityTick: 0,
|
|
217
241
|
};
|
|
218
242
|
}
|
|
219
|
-
function resetStatus(runtime) {
|
|
220
|
-
return initialStatus(runtime,
|
|
243
|
+
async function resetStatus(runtime) {
|
|
244
|
+
return initialStatus(runtime, await runtime.engine.contextMetrics());
|
|
221
245
|
}
|
|
222
246
|
function setTerminalTitle(title, prefix = TERMINAL_TITLE_WORKING_PREFIX) {
|
|
223
247
|
if (!stdout.isTTY)
|
|
@@ -346,7 +370,7 @@ function pushTextBlock(blocks, text) {
|
|
|
346
370
|
function escapeRegExp(value) {
|
|
347
371
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
348
372
|
}
|
|
349
|
-
function InkRepl({ runtime }) {
|
|
373
|
+
function InkRepl({ runtime, initialCommandLine }) {
|
|
350
374
|
const app = useApp();
|
|
351
375
|
const lineId = useRef(0);
|
|
352
376
|
const assistantLineId = useRef(undefined);
|
|
@@ -367,10 +391,15 @@ function InkRepl({ runtime }) {
|
|
|
367
391
|
const [busy, setBusy] = useState(false);
|
|
368
392
|
const [status, setStatus] = useState(() => initialStatus(runtime));
|
|
369
393
|
const sessionTitleRef = useRef(sessionTerminalTitle(runtime.engine.snapshot().session));
|
|
370
|
-
const [
|
|
394
|
+
const [backgroundTasks, setBackgroundTasks] = useState(() => activeBackgroundTasks(runtime));
|
|
395
|
+
const [backgroundSessionRuns, setBackgroundSessionRuns] = useState([]);
|
|
396
|
+
const backgroundSessionRunsRef = useRef(new Map());
|
|
397
|
+
const suppressReattachedStreamingRef = useRef(new Set());
|
|
398
|
+
const activePromptRunRef = useRef(undefined);
|
|
371
399
|
const [animationTick, setAnimationTick] = useState(0);
|
|
372
400
|
const [terminalTitlePrefix, setTerminalTitlePrefix] = useState(TERMINAL_TITLE_READY_PREFIX);
|
|
373
|
-
const
|
|
401
|
+
const backgroundTaskCount = backgroundTasks.length;
|
|
402
|
+
const terminalTitleWorking = isActivePhase(status.phase) || backgroundTaskCount > 0 || backgroundSessionRuns.length > 0;
|
|
374
403
|
const [sessionsBrowser, setSessionsBrowser] = useState(undefined);
|
|
375
404
|
const inputRef = useRef(input);
|
|
376
405
|
const queuedInputRef = useRef(undefined);
|
|
@@ -398,15 +427,15 @@ function InkRepl({ runtime }) {
|
|
|
398
427
|
};
|
|
399
428
|
}, []);
|
|
400
429
|
useEffect(() => {
|
|
401
|
-
if (!busy && backgroundTaskCount === 0)
|
|
430
|
+
if (!busy && backgroundTaskCount === 0 && backgroundSessionRuns.length === 0)
|
|
402
431
|
return undefined;
|
|
403
432
|
const interval = setInterval(() => setAnimationTick((current) => current + 1), REPL_ANIMATION_INTERVAL_MS);
|
|
404
433
|
return () => clearInterval(interval);
|
|
405
|
-
}, [busy, backgroundTaskCount]);
|
|
434
|
+
}, [busy, backgroundTaskCount, backgroundSessionRuns.length]);
|
|
406
435
|
useEffect(() => {
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
return runtime.taskStore.subscribe(
|
|
436
|
+
const updateBackgroundTasks = () => setBackgroundTasks(activeBackgroundTasks(runtime));
|
|
437
|
+
updateBackgroundTasks();
|
|
438
|
+
return runtime.taskStore.subscribe(updateBackgroundTasks);
|
|
410
439
|
}, [runtime]);
|
|
411
440
|
useEffect(() => {
|
|
412
441
|
if (!terminalTitleWorking) {
|
|
@@ -559,7 +588,44 @@ function InkRepl({ runtime }) {
|
|
|
559
588
|
const replaceLine = (id, patch) => {
|
|
560
589
|
setLines((current) => current.map((line) => line.id === id ? { ...line, ...patch, renderedKey: undefined } : line));
|
|
561
590
|
};
|
|
562
|
-
const
|
|
591
|
+
const syncBackgroundSessionRuns = () => {
|
|
592
|
+
setBackgroundSessionRuns([...backgroundSessionRunsRef.current.values()]);
|
|
593
|
+
};
|
|
594
|
+
const detachRunningForeground = (reason) => {
|
|
595
|
+
if (!busyRef.current)
|
|
596
|
+
return false;
|
|
597
|
+
const snapshot = runtime.engine.snapshot().session;
|
|
598
|
+
const sessionId = snapshot?.sessionId ?? `session-${Date.now().toString(36)}`;
|
|
599
|
+
const run = activePromptRunRef.current;
|
|
600
|
+
if (run && !backgroundSessionRunsRef.current.has(sessionId)) {
|
|
601
|
+
const backgroundRun = {
|
|
602
|
+
sessionId,
|
|
603
|
+
title: snapshot?.title,
|
|
604
|
+
reason,
|
|
605
|
+
startedAt: Date.now(),
|
|
606
|
+
engine: runtime.engine,
|
|
607
|
+
abortController: activeAbortController.current ?? new AbortController(),
|
|
608
|
+
promise: run,
|
|
609
|
+
};
|
|
610
|
+
backgroundSessionRunsRef.current.set(sessionId, backgroundRun);
|
|
611
|
+
syncBackgroundSessionRuns();
|
|
612
|
+
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
613
|
+
run.finally(() => {
|
|
614
|
+
backgroundSessionRunsRef.current.delete(sessionId);
|
|
615
|
+
suppressReattachedStreamingRef.current.delete(backgroundRun.engine);
|
|
616
|
+
syncBackgroundSessionRuns();
|
|
617
|
+
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
618
|
+
}).catch(() => undefined);
|
|
619
|
+
}
|
|
620
|
+
activeAbortController.current = undefined;
|
|
621
|
+
interruptArmed.current = false;
|
|
622
|
+
setQueuedPromptState(undefined);
|
|
623
|
+
setBusyState(false);
|
|
624
|
+
setStatus((current) => ({ ...current, phase: "ready", detail: undefined }));
|
|
625
|
+
append(systemLine(`Detached running ${sessionId} to background for ${reason}.`));
|
|
626
|
+
return true;
|
|
627
|
+
};
|
|
628
|
+
const resetForegroundView = (metrics) => {
|
|
563
629
|
runtime.usage.reset();
|
|
564
630
|
setStatus(initialStatus(runtime, metrics));
|
|
565
631
|
resetLinesToHistory(runtime, setLines, lineId);
|
|
@@ -568,8 +634,27 @@ function InkRepl({ runtime }) {
|
|
|
568
634
|
finalizedThinkingLineId.current = undefined;
|
|
569
635
|
toolLineIds.current.clear();
|
|
570
636
|
clearPendingToolResultTimers();
|
|
637
|
+
};
|
|
638
|
+
const resumeSnapshot = (snapshot, metrics) => {
|
|
639
|
+
resetForegroundView(metrics);
|
|
571
640
|
append(systemLine(formatResume(snapshot)));
|
|
572
641
|
};
|
|
642
|
+
const reattachRunningSession = async (run) => {
|
|
643
|
+
detachRunningForeground("session switch");
|
|
644
|
+
backgroundSessionRunsRef.current.delete(run.sessionId);
|
|
645
|
+
syncBackgroundSessionRuns();
|
|
646
|
+
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
647
|
+
runtime.engine = run.engine;
|
|
648
|
+
activeAbortController.current = run.abortController;
|
|
649
|
+
interruptArmed.current = false;
|
|
650
|
+
activePromptRunRef.current = run.promise;
|
|
651
|
+
suppressReattachedStreamingRef.current.add(run.engine);
|
|
652
|
+
const metrics = await runtime.engine.contextMetrics();
|
|
653
|
+
resetForegroundView(metrics);
|
|
654
|
+
setBusyState(true);
|
|
655
|
+
setStatus((current) => ({ ...current, phase: "running", detail: "working" }));
|
|
656
|
+
append(systemLine(`reattached running session ${run.sessionId}`));
|
|
657
|
+
};
|
|
573
658
|
const finalizeLiveLine = (id) => {
|
|
574
659
|
if (id === undefined)
|
|
575
660
|
return;
|
|
@@ -719,13 +804,7 @@ function InkRepl({ runtime }) {
|
|
|
719
804
|
return;
|
|
720
805
|
}
|
|
721
806
|
if (busyRef.current) {
|
|
722
|
-
|
|
723
|
-
return;
|
|
724
|
-
setQueuedPromptState(text, submitAttachments);
|
|
725
|
-
setHistorySelection(undefined);
|
|
726
|
-
setPromptState("", 0);
|
|
727
|
-
clearAttachments();
|
|
728
|
-
return;
|
|
807
|
+
detachRunningForeground("new prompt");
|
|
729
808
|
}
|
|
730
809
|
history.current = [text, ...history.current.filter((entry) => entry !== text)].slice(0, 100);
|
|
731
810
|
setHistorySelection(undefined);
|
|
@@ -823,12 +902,13 @@ function InkRepl({ runtime }) {
|
|
|
823
902
|
if (command.type === "reset") {
|
|
824
903
|
runtime.engine.reset();
|
|
825
904
|
runtime.usage.reset();
|
|
826
|
-
setStatus(resetStatus(runtime));
|
|
905
|
+
setStatus(await resetStatus(runtime));
|
|
827
906
|
append(systemLine("transcript reset"));
|
|
828
907
|
return;
|
|
829
908
|
}
|
|
830
909
|
if (command.type === "state") {
|
|
831
|
-
|
|
910
|
+
const contextMetrics = await runtime.engine.contextMetrics();
|
|
911
|
+
append(systemLine(formatReplData({ ...runtime.engine.snapshot(), contextMetrics, communicationLog: runtime.communicationLogger.snapshot() }, 12000), EXPANDED_SUMMARY_MAX_LINES));
|
|
832
912
|
return;
|
|
833
913
|
}
|
|
834
914
|
if (command.type === "export") {
|
|
@@ -859,8 +939,25 @@ function InkRepl({ runtime }) {
|
|
|
859
939
|
}
|
|
860
940
|
return;
|
|
861
941
|
}
|
|
942
|
+
if (command.type === "new") {
|
|
943
|
+
detachRunningForeground("new session");
|
|
944
|
+
runtime.engine = runtime.engine.forkForSession(undefined, false);
|
|
945
|
+
await runtime.engine.initialize();
|
|
946
|
+
const snapshot = runtime.engine.snapshot().session;
|
|
947
|
+
const metrics = await runtime.engine.contextMetrics();
|
|
948
|
+
runtime.usage.reset();
|
|
949
|
+
setStatus(initialStatus(runtime, metrics));
|
|
950
|
+
resetLinesToHistory(runtime, setLines, lineId);
|
|
951
|
+
assistantLineId.current = undefined;
|
|
952
|
+
thinkingLineId.current = undefined;
|
|
953
|
+
finalizedThinkingLineId.current = undefined;
|
|
954
|
+
toolLineIds.current.clear();
|
|
955
|
+
clearPendingToolResultTimers();
|
|
956
|
+
append(systemLine(snapshot ? `new session ${snapshot.sessionId}` : "new session"));
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
862
959
|
if (command.type === "sessions") {
|
|
863
|
-
await handleSessionsCommand(runtime, setSessionsBrowser, (line) => append(line));
|
|
960
|
+
await handleSessionsCommand(runtime, runningSessionIds(backgroundSessionRunsRef.current), setSessionsBrowser, (line) => append(line));
|
|
864
961
|
return;
|
|
865
962
|
}
|
|
866
963
|
if (command.type === "login") {
|
|
@@ -916,20 +1013,41 @@ function InkRepl({ runtime }) {
|
|
|
916
1013
|
outputTokenUpdatedAt: undefined,
|
|
917
1014
|
retryCooldownUntil: undefined,
|
|
918
1015
|
}));
|
|
919
|
-
|
|
920
|
-
|
|
1016
|
+
const engine = runtime.engine;
|
|
1017
|
+
const run = (async () => {
|
|
1018
|
+
for await (const event of engine.sendUserText(promptPayload.text, { abortSignal: abortController.signal, blocks: promptPayload.blocks, displayText: text })) {
|
|
1019
|
+
if (runtime.engine !== engine)
|
|
1020
|
+
continue;
|
|
1021
|
+
if (suppressReattachedStreamingRef.current.has(engine)) {
|
|
1022
|
+
if (event.type === "message" || event.type === "terminal" || event.type === "error" || event.type === "context.metrics" || event.type === "usage") {
|
|
1023
|
+
if (event.type === "message" || event.type === "terminal" || event.type === "error")
|
|
1024
|
+
suppressReattachedStreamingRef.current.delete(engine);
|
|
1025
|
+
handleEvent(event);
|
|
1026
|
+
}
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
921
1029
|
handleEvent(event);
|
|
922
1030
|
}
|
|
1031
|
+
})();
|
|
1032
|
+
activePromptRunRef.current = run;
|
|
1033
|
+
try {
|
|
1034
|
+
await run;
|
|
923
1035
|
}
|
|
924
1036
|
catch (error) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1037
|
+
if (runtime.engine === engine) {
|
|
1038
|
+
finalizeLiveLine(assistantLineId.current);
|
|
1039
|
+
finalizeThinkingLine();
|
|
1040
|
+
finalizeActiveToolLines();
|
|
1041
|
+
assistantLineId.current = undefined;
|
|
1042
|
+
finalizedThinkingLineId.current = undefined;
|
|
1043
|
+
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
1044
|
+
}
|
|
931
1045
|
}
|
|
932
1046
|
finally {
|
|
1047
|
+
if (activePromptRunRef.current === run)
|
|
1048
|
+
activePromptRunRef.current = undefined;
|
|
1049
|
+
if (runtime.engine !== engine)
|
|
1050
|
+
return;
|
|
933
1051
|
if (activeAbortController.current === abortController)
|
|
934
1052
|
activeAbortController.current = undefined;
|
|
935
1053
|
interruptArmed.current = false;
|
|
@@ -969,6 +1087,11 @@ function InkRepl({ runtime }) {
|
|
|
969
1087
|
setQueuedPromptState(undefined);
|
|
970
1088
|
setPromptState("", 0);
|
|
971
1089
|
}, [runtime]);
|
|
1090
|
+
useEffect(() => {
|
|
1091
|
+
if (initialCommandLine === undefined)
|
|
1092
|
+
return;
|
|
1093
|
+
void submitLine(initialCommandLine);
|
|
1094
|
+
}, []);
|
|
972
1095
|
const terminalSize = useTerminalSize();
|
|
973
1096
|
const width = terminalSize.columns;
|
|
974
1097
|
const inputLockedByQueue = busy && queuedInput !== undefined;
|
|
@@ -995,7 +1118,7 @@ function InkRepl({ runtime }) {
|
|
|
995
1118
|
const blockIndex = staticLines.length + i;
|
|
996
1119
|
return sum + (blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0);
|
|
997
1120
|
}, 0);
|
|
998
|
-
const statusRenderRows = STATUS_BAR_RENDER_ROWS + (
|
|
1121
|
+
const statusRenderRows = STATUS_BAR_RENDER_ROWS + backgroundTaskStatusRenderRows(backgroundTasks.length);
|
|
999
1122
|
const sessionsBrowserHeight = sessionsBrowser ? sessionsBrowserViewHeight(sessionsBrowser) : 0;
|
|
1000
1123
|
const loginFormHeight = loginForm ? loginFormViewHeight(loginForm) : 0;
|
|
1001
1124
|
const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - sessionsBrowserHeight - loginFormHeight - dynamicMarginOverhead - 1);
|
|
@@ -1075,10 +1198,17 @@ function InkRepl({ runtime }) {
|
|
|
1075
1198
|
const selected = sessionsBrowser.sessions[sessionAbsoluteIndex(sessionsBrowser)];
|
|
1076
1199
|
if (selected) {
|
|
1077
1200
|
setSessionsBrowser(undefined);
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1201
|
+
const running = backgroundSessionRunsRef.current.get(selected.sessionId);
|
|
1202
|
+
if (running) {
|
|
1203
|
+
void reattachRunningSession(running);
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
detachRunningForeground("session switch");
|
|
1207
|
+
void handleResumeCommand(selected.sessionId, runtime, (line) => append(line)).then((result) => {
|
|
1208
|
+
if (result)
|
|
1209
|
+
resumeSnapshot(result.snapshot, result.metrics);
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1082
1212
|
}
|
|
1083
1213
|
return;
|
|
1084
1214
|
}
|
|
@@ -1220,7 +1350,7 @@ function InkRepl({ runtime }) {
|
|
|
1220
1350
|
return;
|
|
1221
1351
|
}
|
|
1222
1352
|
});
|
|
1223
|
-
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: dynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, loginForm ? e(LoginFormView, { state: loginForm, width }) : null, e(StatusBar, { status, animationTick, width }),
|
|
1353
|
+
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: dynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, loginForm ? e(LoginFormView, { state: loginForm, width }) : null, e(StatusBar, { status, animationTick, width }), backgroundTasks.length > 0 ? e(BackgroundTaskStatusLine, { tasks: backgroundTasks, width }) : null, pasteStatus ? e(PasteStatusLine, { text: pasteStatus, width }) : null, queuedInput !== undefined ? e(QueuedInputLine, { text: queuedInput, width }) : null, e(PromptLine, { text: promptDisplayText, cursor: promptDisplayCursor, busy, locked: inputLockedByQueue, placeholder: input.length === 0 && promptPlaceholder !== undefined, ghostText: activePlaceholder, width, prompt, slashCompletions, selectedSlashCompletionIndex, attachments }));
|
|
1224
1354
|
}
|
|
1225
1355
|
const MessageList = React.memo(function MessageList({ lines, width, liveMaxLines, lineIndexOffset = 0, onMarkdownRenderComplete }) {
|
|
1226
1356
|
const contentWidth = messageContentWidth(width);
|
|
@@ -1572,10 +1702,27 @@ function StatusBar({ status, animationTick, width: terminalWidth }) {
|
|
|
1572
1702
|
const segments = fitStatusSegments(renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase), width);
|
|
1573
1703
|
return e(Box, { marginTop: 1, width, height: 1, overflow: "hidden" }, ...segments.map((segment, index) => e(Text, { key: index, color: segment.color ?? "gray", bold: segment.bold ?? false }, segment.text)));
|
|
1574
1704
|
}
|
|
1575
|
-
function
|
|
1705
|
+
function backgroundTaskStatusRenderRows(taskCount) {
|
|
1706
|
+
if (taskCount <= 0)
|
|
1707
|
+
return 0;
|
|
1708
|
+
return 1 + Math.min(taskCount, 2);
|
|
1709
|
+
}
|
|
1710
|
+
function BackgroundTaskStatusLine({ tasks, width: terminalWidth }) {
|
|
1576
1711
|
const width = statusBarWidth(terminalWidth);
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1712
|
+
const summary = `◇ background tools: ${tasks.length} task${tasks.length === 1 ? "" : "s"}`;
|
|
1713
|
+
const detailTasks = tasks.slice(0, 2);
|
|
1714
|
+
return e(Box, { flexDirection: "column", width, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(summary, width)), ...detailTasks.map((task) => e(Text, { key: task.taskId, color: "yellow" }, fitToWidth(` ${task.type}:${truncateMiddle(task.description || task.agentId || task.taskId, Math.max(12, width - 30))} · ${task.status} · ${formatElapsed(Date.now() - new Date(task.createdAt).getTime())}`, width))));
|
|
1715
|
+
}
|
|
1716
|
+
function formatElapsed(ms) {
|
|
1717
|
+
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
1718
|
+
if (seconds < 60)
|
|
1719
|
+
return `${seconds}s`;
|
|
1720
|
+
const minutes = Math.floor(seconds / 60);
|
|
1721
|
+
const remainder = seconds % 60;
|
|
1722
|
+
if (minutes < 60)
|
|
1723
|
+
return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
|
|
1724
|
+
const hours = Math.floor(minutes / 60);
|
|
1725
|
+
return `${hours}h${(minutes % 60).toString().padStart(2, "0")}m`;
|
|
1579
1726
|
}
|
|
1580
1727
|
function renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase = status.phase) {
|
|
1581
1728
|
const phase = displayPhase;
|
|
@@ -1586,7 +1733,7 @@ function renderCompactStatusSegments(status, animationTick, width, inputTokens,
|
|
|
1586
1733
|
const context = renderContextParts(status.metrics);
|
|
1587
1734
|
const fixedText = [
|
|
1588
1735
|
phaseText,
|
|
1589
|
-
|
|
1736
|
+
context.percent,
|
|
1590
1737
|
`↑ ${inputValue}`,
|
|
1591
1738
|
`↓ ${outputValue}`,
|
|
1592
1739
|
].join(STATUS_SEPARATOR);
|
|
@@ -1603,9 +1750,7 @@ function renderCompactStatusSegments(status, animationTick, width, inputTokens,
|
|
|
1603
1750
|
statusDividerSegment(),
|
|
1604
1751
|
{ text: model },
|
|
1605
1752
|
statusDividerSegment(),
|
|
1606
|
-
|
|
1607
|
-
{ text: ` ${context.percent}`, color: contextColor(status.metrics) },
|
|
1608
|
-
{ text: ` of ${context.limit}` },
|
|
1753
|
+
{ text: context.percent, color: contextColor(status.metrics) },
|
|
1609
1754
|
statusDividerSegment(),
|
|
1610
1755
|
statusLabelSegment("↑", tokenInputColor),
|
|
1611
1756
|
{ text: ` ${inputValue}` },
|
|
@@ -2144,14 +2289,14 @@ function reduceStatus(status, event) {
|
|
|
2144
2289
|
}
|
|
2145
2290
|
return status;
|
|
2146
2291
|
}
|
|
2147
|
-
async function handleSessionsCommand(runtime, setBrowser, append) {
|
|
2292
|
+
async function handleSessionsCommand(runtime, runningSessionIds, setBrowser, append) {
|
|
2148
2293
|
const sessions = await runtime.engine.listSessions(Number.POSITIVE_INFINITY);
|
|
2149
2294
|
if (sessions.length === 0) {
|
|
2150
2295
|
setBrowser(undefined);
|
|
2151
2296
|
append(systemLine("No saved sessions found."));
|
|
2152
2297
|
return;
|
|
2153
2298
|
}
|
|
2154
|
-
setBrowser({ sessions, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
2299
|
+
setBrowser({ sessions, runningSessionIds, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
2155
2300
|
}
|
|
2156
2301
|
async function handleExportCommand(command, runtime) {
|
|
2157
2302
|
const snapshot = runtime.engine.snapshot();
|
|
@@ -2169,7 +2314,11 @@ async function handleExportCommand(command, runtime) {
|
|
|
2169
2314
|
}
|
|
2170
2315
|
async function handleResumeCommand(sessionId, runtime, append) {
|
|
2171
2316
|
try {
|
|
2172
|
-
|
|
2317
|
+
runtime.engine = runtime.engine.forkForSession(sessionId, true);
|
|
2318
|
+
await runtime.engine.initialize();
|
|
2319
|
+
const snapshot = runtime.engine.snapshot().session;
|
|
2320
|
+
if (!snapshot)
|
|
2321
|
+
throw new Error("session transcripts are disabled");
|
|
2173
2322
|
const metrics = await runtime.engine.contextMetrics();
|
|
2174
2323
|
return { snapshot, metrics };
|
|
2175
2324
|
}
|
|
@@ -2196,6 +2345,7 @@ async function handleDeleteSessionCommand(sessionId, current, runtime, setBrowse
|
|
|
2196
2345
|
setBrowser({
|
|
2197
2346
|
...current,
|
|
2198
2347
|
sessions: nextSessions,
|
|
2348
|
+
runningSessionIds: current.runningSessionIds.filter((id) => id !== sessionId),
|
|
2199
2349
|
pageIndex,
|
|
2200
2350
|
selectedIndex: Math.min(current.selectedIndex, Math.max(0, pageLength - 1)),
|
|
2201
2351
|
});
|
|
@@ -2342,7 +2492,7 @@ function SessionsBrowser({ state, width }) {
|
|
|
2342
2492
|
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((session, index) => {
|
|
2343
2493
|
const selected = index === state.selectedIndex;
|
|
2344
2494
|
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
2345
|
-
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth);
|
|
2495
|
+
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth, state.runningSessionIds.includes(session.sessionId));
|
|
2346
2496
|
return e(Text, { key: session.sessionId, color: "white" }, e(Text, {
|
|
2347
2497
|
color: selected ? "black" : "white",
|
|
2348
2498
|
backgroundColor: selected ? "cyan" : undefined,
|
|
@@ -2685,16 +2835,17 @@ function stripEnvQuotes(value) {
|
|
|
2685
2835
|
return value.slice(1, -1);
|
|
2686
2836
|
return value;
|
|
2687
2837
|
}
|
|
2688
|
-
function formatSessionBrowserRow(session, absoluteIndex, width) {
|
|
2838
|
+
function formatSessionBrowserRow(session, absoluteIndex, width, running = false) {
|
|
2689
2839
|
const numberPrefix = `${absoluteIndex + 1}.`.padStart(4);
|
|
2690
2840
|
const title = session.title?.trim() || "(untitled)";
|
|
2841
|
+
const runningTag = running ? " · running" : "";
|
|
2691
2842
|
const updated = session.updatedAt ? ` · ${formatSessionTimestamp(session.updatedAt)}` : "";
|
|
2692
2843
|
const messages = ` · ${session.messages} messages`;
|
|
2693
|
-
const fixedParts = `${numberPrefix} ${updated}${messages}`;
|
|
2844
|
+
const fixedParts = `${numberPrefix} ${runningTag}${updated}${messages}`;
|
|
2694
2845
|
const idBudget = Math.max(12, Math.min(32, Math.floor(width * 0.28)));
|
|
2695
2846
|
const id = truncateMiddle(session.sessionId, idBudget);
|
|
2696
2847
|
const titleBudget = Math.max(8, width - fixedParts.length - id.length - 5);
|
|
2697
|
-
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${updated}${messages}`, width);
|
|
2848
|
+
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${runningTag}${updated}${messages}`, width);
|
|
2698
2849
|
return { numberPrefix, rest: row.slice(numberPrefix.length) };
|
|
2699
2850
|
}
|
|
2700
2851
|
function formatSessionTimestamp(value) {
|
|
@@ -2978,26 +3129,11 @@ function isReplScalar(value) {
|
|
|
2978
3129
|
return value === null || value === undefined || typeof value !== "object" || value instanceof Date;
|
|
2979
3130
|
}
|
|
2980
3131
|
function formatToolResult(toolName, output, ok) {
|
|
2981
|
-
if (toolName === "edit" && isRecord(output) && isEditToolOutput(output)) {
|
|
3132
|
+
if ((toolName === "edit" || toolName === "write") && isRecord(output) && isEditToolOutput(output)) {
|
|
2982
3133
|
return { text: formatEditToolDiff(output, ok), format: "ansi", summaryMaxLines: EDIT_TOOL_SUMMARY_MAX_LINES };
|
|
2983
3134
|
}
|
|
2984
3135
|
if (isExecOutput(output)) {
|
|
2985
|
-
|
|
2986
|
-
? "timed out"
|
|
2987
|
-
: output.exitCode === 0
|
|
2988
|
-
? "exit 0"
|
|
2989
|
-
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
2990
|
-
const sections = [
|
|
2991
|
-
`${status} · ${output.durationMs}ms`,
|
|
2992
|
-
`$ ${output.command}`,
|
|
2993
|
-
];
|
|
2994
|
-
if (output.stdout)
|
|
2995
|
-
sections.push("stdout:", output.stdout.replace(/\s+$/u, ""));
|
|
2996
|
-
if (output.stderr)
|
|
2997
|
-
sections.push("stderr:", output.stderr.replace(/\s+$/u, ""));
|
|
2998
|
-
if (!output.stdout && !output.stderr)
|
|
2999
|
-
sections.push(ok ? "no output" : "no captured output");
|
|
3000
|
-
return { text: sections.join("\n"), format: "ansi" };
|
|
3136
|
+
return { text: formatExecToolResult(output, ok), format: "ansi", summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
3001
3137
|
}
|
|
3002
3138
|
if (typeof output === "string" && hasAnsi(output)) {
|
|
3003
3139
|
return { text: output, format: "ansi" };
|
|
@@ -3104,6 +3240,28 @@ function isExecOutput(value) {
|
|
|
3104
3240
|
typeof record.stdout === "string" &&
|
|
3105
3241
|
typeof record.stderr === "string");
|
|
3106
3242
|
}
|
|
3243
|
+
function formatExecToolResult(output, ok) {
|
|
3244
|
+
const status = output.timedOut
|
|
3245
|
+
? "timed out"
|
|
3246
|
+
: output.exitCode === 0
|
|
3247
|
+
? "exit 0"
|
|
3248
|
+
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
3249
|
+
const lines = [
|
|
3250
|
+
"exec result",
|
|
3251
|
+
`status: ${status}`,
|
|
3252
|
+
`duration: ${output.durationMs}ms`,
|
|
3253
|
+
`command: ${output.command}`,
|
|
3254
|
+
];
|
|
3255
|
+
const stdout = output.stdout.replace(/\s+$/u, "");
|
|
3256
|
+
const stderr = output.stderr.replace(/\s+$/u, "");
|
|
3257
|
+
if (stdout)
|
|
3258
|
+
lines.push("stdout:", stdout);
|
|
3259
|
+
if (stderr)
|
|
3260
|
+
lines.push("stderr:", stderr);
|
|
3261
|
+
if (!stdout && !stderr)
|
|
3262
|
+
lines.push(ok ? "output: (none)" : "output: (not captured)");
|
|
3263
|
+
return lines.join("\n");
|
|
3264
|
+
}
|
|
3107
3265
|
function isRecord(value) {
|
|
3108
3266
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3109
3267
|
}
|
|
@@ -3254,11 +3412,9 @@ function formatGrepContextLine(line, marker) {
|
|
|
3254
3412
|
}
|
|
3255
3413
|
function renderContextParts(metrics) {
|
|
3256
3414
|
if (!metrics)
|
|
3257
|
-
return {
|
|
3258
|
-
const used = compactNumber(metrics.estimatedInputTokens);
|
|
3259
|
-
const limit = metrics.contextWindowTokens ? compactNumber(metrics.contextWindowTokens) : "?";
|
|
3415
|
+
return { percent: "?" };
|
|
3260
3416
|
const percent = metrics.contextUsageRatio === undefined ? "?" : `${(metrics.contextUsageRatio * 100).toFixed(1)}%`;
|
|
3261
|
-
return {
|
|
3417
|
+
return { percent };
|
|
3262
3418
|
}
|
|
3263
3419
|
function contextColor(metrics) {
|
|
3264
3420
|
const ratio = metrics?.contextUsageRatio;
|