neoctl 0.1.7 → 0.1.8
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 +17 -0
- 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 +2 -0
- package/dist/repl/commands.js +3 -0
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +202 -69
- 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
|
@@ -205,6 +205,12 @@ function initialContextMetrics(model, messageCount, toolCount) {
|
|
|
205
205
|
: undefined,
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
+
function activeBackgroundTasks(runtime) {
|
|
209
|
+
return runtime.taskStore.list().filter((task) => !runtime.taskStore.isTerminal(task));
|
|
210
|
+
}
|
|
211
|
+
function runningSessionIds(runs) {
|
|
212
|
+
return [...runs.keys()];
|
|
213
|
+
}
|
|
208
214
|
function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
209
215
|
return {
|
|
210
216
|
phase: "ready",
|
|
@@ -216,8 +222,8 @@ function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
|
216
222
|
activityTick: 0,
|
|
217
223
|
};
|
|
218
224
|
}
|
|
219
|
-
function resetStatus(runtime) {
|
|
220
|
-
return initialStatus(runtime,
|
|
225
|
+
async function resetStatus(runtime) {
|
|
226
|
+
return initialStatus(runtime, await runtime.engine.contextMetrics());
|
|
221
227
|
}
|
|
222
228
|
function setTerminalTitle(title, prefix = TERMINAL_TITLE_WORKING_PREFIX) {
|
|
223
229
|
if (!stdout.isTTY)
|
|
@@ -367,10 +373,15 @@ function InkRepl({ runtime }) {
|
|
|
367
373
|
const [busy, setBusy] = useState(false);
|
|
368
374
|
const [status, setStatus] = useState(() => initialStatus(runtime));
|
|
369
375
|
const sessionTitleRef = useRef(sessionTerminalTitle(runtime.engine.snapshot().session));
|
|
370
|
-
const [
|
|
376
|
+
const [backgroundTasks, setBackgroundTasks] = useState(() => activeBackgroundTasks(runtime));
|
|
377
|
+
const [backgroundSessionRuns, setBackgroundSessionRuns] = useState([]);
|
|
378
|
+
const backgroundSessionRunsRef = useRef(new Map());
|
|
379
|
+
const suppressReattachedStreamingRef = useRef(new Set());
|
|
380
|
+
const activePromptRunRef = useRef(undefined);
|
|
371
381
|
const [animationTick, setAnimationTick] = useState(0);
|
|
372
382
|
const [terminalTitlePrefix, setTerminalTitlePrefix] = useState(TERMINAL_TITLE_READY_PREFIX);
|
|
373
|
-
const
|
|
383
|
+
const backgroundTaskCount = backgroundTasks.length;
|
|
384
|
+
const terminalTitleWorking = isActivePhase(status.phase) || backgroundTaskCount > 0 || backgroundSessionRuns.length > 0;
|
|
374
385
|
const [sessionsBrowser, setSessionsBrowser] = useState(undefined);
|
|
375
386
|
const inputRef = useRef(input);
|
|
376
387
|
const queuedInputRef = useRef(undefined);
|
|
@@ -398,15 +409,15 @@ function InkRepl({ runtime }) {
|
|
|
398
409
|
};
|
|
399
410
|
}, []);
|
|
400
411
|
useEffect(() => {
|
|
401
|
-
if (!busy && backgroundTaskCount === 0)
|
|
412
|
+
if (!busy && backgroundTaskCount === 0 && backgroundSessionRuns.length === 0)
|
|
402
413
|
return undefined;
|
|
403
414
|
const interval = setInterval(() => setAnimationTick((current) => current + 1), REPL_ANIMATION_INTERVAL_MS);
|
|
404
415
|
return () => clearInterval(interval);
|
|
405
|
-
}, [busy, backgroundTaskCount]);
|
|
416
|
+
}, [busy, backgroundTaskCount, backgroundSessionRuns.length]);
|
|
406
417
|
useEffect(() => {
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
return runtime.taskStore.subscribe(
|
|
418
|
+
const updateBackgroundTasks = () => setBackgroundTasks(activeBackgroundTasks(runtime));
|
|
419
|
+
updateBackgroundTasks();
|
|
420
|
+
return runtime.taskStore.subscribe(updateBackgroundTasks);
|
|
410
421
|
}, [runtime]);
|
|
411
422
|
useEffect(() => {
|
|
412
423
|
if (!terminalTitleWorking) {
|
|
@@ -559,7 +570,44 @@ function InkRepl({ runtime }) {
|
|
|
559
570
|
const replaceLine = (id, patch) => {
|
|
560
571
|
setLines((current) => current.map((line) => line.id === id ? { ...line, ...patch, renderedKey: undefined } : line));
|
|
561
572
|
};
|
|
562
|
-
const
|
|
573
|
+
const syncBackgroundSessionRuns = () => {
|
|
574
|
+
setBackgroundSessionRuns([...backgroundSessionRunsRef.current.values()]);
|
|
575
|
+
};
|
|
576
|
+
const detachRunningForeground = (reason) => {
|
|
577
|
+
if (!busyRef.current)
|
|
578
|
+
return false;
|
|
579
|
+
const snapshot = runtime.engine.snapshot().session;
|
|
580
|
+
const sessionId = snapshot?.sessionId ?? `session-${Date.now().toString(36)}`;
|
|
581
|
+
const run = activePromptRunRef.current;
|
|
582
|
+
if (run && !backgroundSessionRunsRef.current.has(sessionId)) {
|
|
583
|
+
const backgroundRun = {
|
|
584
|
+
sessionId,
|
|
585
|
+
title: snapshot?.title,
|
|
586
|
+
reason,
|
|
587
|
+
startedAt: Date.now(),
|
|
588
|
+
engine: runtime.engine,
|
|
589
|
+
abortController: activeAbortController.current ?? new AbortController(),
|
|
590
|
+
promise: run,
|
|
591
|
+
};
|
|
592
|
+
backgroundSessionRunsRef.current.set(sessionId, backgroundRun);
|
|
593
|
+
syncBackgroundSessionRuns();
|
|
594
|
+
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
595
|
+
run.finally(() => {
|
|
596
|
+
backgroundSessionRunsRef.current.delete(sessionId);
|
|
597
|
+
suppressReattachedStreamingRef.current.delete(backgroundRun.engine);
|
|
598
|
+
syncBackgroundSessionRuns();
|
|
599
|
+
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
600
|
+
}).catch(() => undefined);
|
|
601
|
+
}
|
|
602
|
+
activeAbortController.current = undefined;
|
|
603
|
+
interruptArmed.current = false;
|
|
604
|
+
setQueuedPromptState(undefined);
|
|
605
|
+
setBusyState(false);
|
|
606
|
+
setStatus((current) => ({ ...current, phase: "ready", detail: undefined }));
|
|
607
|
+
append(systemLine(`Detached running ${sessionId} to background for ${reason}.`));
|
|
608
|
+
return true;
|
|
609
|
+
};
|
|
610
|
+
const resetForegroundView = (metrics) => {
|
|
563
611
|
runtime.usage.reset();
|
|
564
612
|
setStatus(initialStatus(runtime, metrics));
|
|
565
613
|
resetLinesToHistory(runtime, setLines, lineId);
|
|
@@ -568,8 +616,27 @@ function InkRepl({ runtime }) {
|
|
|
568
616
|
finalizedThinkingLineId.current = undefined;
|
|
569
617
|
toolLineIds.current.clear();
|
|
570
618
|
clearPendingToolResultTimers();
|
|
619
|
+
};
|
|
620
|
+
const resumeSnapshot = (snapshot, metrics) => {
|
|
621
|
+
resetForegroundView(metrics);
|
|
571
622
|
append(systemLine(formatResume(snapshot)));
|
|
572
623
|
};
|
|
624
|
+
const reattachRunningSession = async (run) => {
|
|
625
|
+
detachRunningForeground("session switch");
|
|
626
|
+
backgroundSessionRunsRef.current.delete(run.sessionId);
|
|
627
|
+
syncBackgroundSessionRuns();
|
|
628
|
+
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
629
|
+
runtime.engine = run.engine;
|
|
630
|
+
activeAbortController.current = run.abortController;
|
|
631
|
+
interruptArmed.current = false;
|
|
632
|
+
activePromptRunRef.current = run.promise;
|
|
633
|
+
suppressReattachedStreamingRef.current.add(run.engine);
|
|
634
|
+
const metrics = await runtime.engine.contextMetrics();
|
|
635
|
+
resetForegroundView(metrics);
|
|
636
|
+
setBusyState(true);
|
|
637
|
+
setStatus((current) => ({ ...current, phase: "running", detail: "working" }));
|
|
638
|
+
append(systemLine(`reattached running session ${run.sessionId}`));
|
|
639
|
+
};
|
|
573
640
|
const finalizeLiveLine = (id) => {
|
|
574
641
|
if (id === undefined)
|
|
575
642
|
return;
|
|
@@ -719,13 +786,7 @@ function InkRepl({ runtime }) {
|
|
|
719
786
|
return;
|
|
720
787
|
}
|
|
721
788
|
if (busyRef.current) {
|
|
722
|
-
|
|
723
|
-
return;
|
|
724
|
-
setQueuedPromptState(text, submitAttachments);
|
|
725
|
-
setHistorySelection(undefined);
|
|
726
|
-
setPromptState("", 0);
|
|
727
|
-
clearAttachments();
|
|
728
|
-
return;
|
|
789
|
+
detachRunningForeground("new prompt");
|
|
729
790
|
}
|
|
730
791
|
history.current = [text, ...history.current.filter((entry) => entry !== text)].slice(0, 100);
|
|
731
792
|
setHistorySelection(undefined);
|
|
@@ -823,12 +884,13 @@ function InkRepl({ runtime }) {
|
|
|
823
884
|
if (command.type === "reset") {
|
|
824
885
|
runtime.engine.reset();
|
|
825
886
|
runtime.usage.reset();
|
|
826
|
-
setStatus(resetStatus(runtime));
|
|
887
|
+
setStatus(await resetStatus(runtime));
|
|
827
888
|
append(systemLine("transcript reset"));
|
|
828
889
|
return;
|
|
829
890
|
}
|
|
830
891
|
if (command.type === "state") {
|
|
831
|
-
|
|
892
|
+
const contextMetrics = await runtime.engine.contextMetrics();
|
|
893
|
+
append(systemLine(formatReplData({ ...runtime.engine.snapshot(), contextMetrics, communicationLog: runtime.communicationLogger.snapshot() }, 12000), EXPANDED_SUMMARY_MAX_LINES));
|
|
832
894
|
return;
|
|
833
895
|
}
|
|
834
896
|
if (command.type === "export") {
|
|
@@ -859,8 +921,25 @@ function InkRepl({ runtime }) {
|
|
|
859
921
|
}
|
|
860
922
|
return;
|
|
861
923
|
}
|
|
924
|
+
if (command.type === "new") {
|
|
925
|
+
detachRunningForeground("new session");
|
|
926
|
+
runtime.engine = runtime.engine.forkForSession(undefined, false);
|
|
927
|
+
await runtime.engine.initialize();
|
|
928
|
+
const snapshot = runtime.engine.snapshot().session;
|
|
929
|
+
const metrics = await runtime.engine.contextMetrics();
|
|
930
|
+
runtime.usage.reset();
|
|
931
|
+
setStatus(initialStatus(runtime, metrics));
|
|
932
|
+
resetLinesToHistory(runtime, setLines, lineId);
|
|
933
|
+
assistantLineId.current = undefined;
|
|
934
|
+
thinkingLineId.current = undefined;
|
|
935
|
+
finalizedThinkingLineId.current = undefined;
|
|
936
|
+
toolLineIds.current.clear();
|
|
937
|
+
clearPendingToolResultTimers();
|
|
938
|
+
append(systemLine(snapshot ? `new session ${snapshot.sessionId}` : "new session"));
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
862
941
|
if (command.type === "sessions") {
|
|
863
|
-
await handleSessionsCommand(runtime, setSessionsBrowser, (line) => append(line));
|
|
942
|
+
await handleSessionsCommand(runtime, runningSessionIds(backgroundSessionRunsRef.current), setSessionsBrowser, (line) => append(line));
|
|
864
943
|
return;
|
|
865
944
|
}
|
|
866
945
|
if (command.type === "login") {
|
|
@@ -916,20 +995,41 @@ function InkRepl({ runtime }) {
|
|
|
916
995
|
outputTokenUpdatedAt: undefined,
|
|
917
996
|
retryCooldownUntil: undefined,
|
|
918
997
|
}));
|
|
919
|
-
|
|
920
|
-
|
|
998
|
+
const engine = runtime.engine;
|
|
999
|
+
const run = (async () => {
|
|
1000
|
+
for await (const event of engine.sendUserText(promptPayload.text, { abortSignal: abortController.signal, blocks: promptPayload.blocks, displayText: text })) {
|
|
1001
|
+
if (runtime.engine !== engine)
|
|
1002
|
+
continue;
|
|
1003
|
+
if (suppressReattachedStreamingRef.current.has(engine)) {
|
|
1004
|
+
if (event.type === "message" || event.type === "terminal" || event.type === "error" || event.type === "context.metrics" || event.type === "usage") {
|
|
1005
|
+
if (event.type === "message" || event.type === "terminal" || event.type === "error")
|
|
1006
|
+
suppressReattachedStreamingRef.current.delete(engine);
|
|
1007
|
+
handleEvent(event);
|
|
1008
|
+
}
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
921
1011
|
handleEvent(event);
|
|
922
1012
|
}
|
|
1013
|
+
})();
|
|
1014
|
+
activePromptRunRef.current = run;
|
|
1015
|
+
try {
|
|
1016
|
+
await run;
|
|
923
1017
|
}
|
|
924
1018
|
catch (error) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1019
|
+
if (runtime.engine === engine) {
|
|
1020
|
+
finalizeLiveLine(assistantLineId.current);
|
|
1021
|
+
finalizeThinkingLine();
|
|
1022
|
+
finalizeActiveToolLines();
|
|
1023
|
+
assistantLineId.current = undefined;
|
|
1024
|
+
finalizedThinkingLineId.current = undefined;
|
|
1025
|
+
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
1026
|
+
}
|
|
931
1027
|
}
|
|
932
1028
|
finally {
|
|
1029
|
+
if (activePromptRunRef.current === run)
|
|
1030
|
+
activePromptRunRef.current = undefined;
|
|
1031
|
+
if (runtime.engine !== engine)
|
|
1032
|
+
return;
|
|
933
1033
|
if (activeAbortController.current === abortController)
|
|
934
1034
|
activeAbortController.current = undefined;
|
|
935
1035
|
interruptArmed.current = false;
|
|
@@ -995,7 +1095,7 @@ function InkRepl({ runtime }) {
|
|
|
995
1095
|
const blockIndex = staticLines.length + i;
|
|
996
1096
|
return sum + (blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0);
|
|
997
1097
|
}, 0);
|
|
998
|
-
const statusRenderRows = STATUS_BAR_RENDER_ROWS + (
|
|
1098
|
+
const statusRenderRows = STATUS_BAR_RENDER_ROWS + backgroundTaskStatusRenderRows(backgroundTasks.length);
|
|
999
1099
|
const sessionsBrowserHeight = sessionsBrowser ? sessionsBrowserViewHeight(sessionsBrowser) : 0;
|
|
1000
1100
|
const loginFormHeight = loginForm ? loginFormViewHeight(loginForm) : 0;
|
|
1001
1101
|
const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - sessionsBrowserHeight - loginFormHeight - dynamicMarginOverhead - 1);
|
|
@@ -1075,10 +1175,17 @@ function InkRepl({ runtime }) {
|
|
|
1075
1175
|
const selected = sessionsBrowser.sessions[sessionAbsoluteIndex(sessionsBrowser)];
|
|
1076
1176
|
if (selected) {
|
|
1077
1177
|
setSessionsBrowser(undefined);
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1178
|
+
const running = backgroundSessionRunsRef.current.get(selected.sessionId);
|
|
1179
|
+
if (running) {
|
|
1180
|
+
void reattachRunningSession(running);
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
detachRunningForeground("session switch");
|
|
1184
|
+
void handleResumeCommand(selected.sessionId, runtime, (line) => append(line)).then((result) => {
|
|
1185
|
+
if (result)
|
|
1186
|
+
resumeSnapshot(result.snapshot, result.metrics);
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1082
1189
|
}
|
|
1083
1190
|
return;
|
|
1084
1191
|
}
|
|
@@ -1220,7 +1327,7 @@ function InkRepl({ runtime }) {
|
|
|
1220
1327
|
return;
|
|
1221
1328
|
}
|
|
1222
1329
|
});
|
|
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 }),
|
|
1330
|
+
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
1331
|
}
|
|
1225
1332
|
const MessageList = React.memo(function MessageList({ lines, width, liveMaxLines, lineIndexOffset = 0, onMarkdownRenderComplete }) {
|
|
1226
1333
|
const contentWidth = messageContentWidth(width);
|
|
@@ -1572,10 +1679,27 @@ function StatusBar({ status, animationTick, width: terminalWidth }) {
|
|
|
1572
1679
|
const segments = fitStatusSegments(renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase), width);
|
|
1573
1680
|
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
1681
|
}
|
|
1575
|
-
function
|
|
1682
|
+
function backgroundTaskStatusRenderRows(taskCount) {
|
|
1683
|
+
if (taskCount <= 0)
|
|
1684
|
+
return 0;
|
|
1685
|
+
return 1 + Math.min(taskCount, 2);
|
|
1686
|
+
}
|
|
1687
|
+
function BackgroundTaskStatusLine({ tasks, width: terminalWidth }) {
|
|
1576
1688
|
const width = statusBarWidth(terminalWidth);
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1689
|
+
const summary = `◇ background tools: ${tasks.length} task${tasks.length === 1 ? "" : "s"}`;
|
|
1690
|
+
const detailTasks = tasks.slice(0, 2);
|
|
1691
|
+
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))));
|
|
1692
|
+
}
|
|
1693
|
+
function formatElapsed(ms) {
|
|
1694
|
+
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
1695
|
+
if (seconds < 60)
|
|
1696
|
+
return `${seconds}s`;
|
|
1697
|
+
const minutes = Math.floor(seconds / 60);
|
|
1698
|
+
const remainder = seconds % 60;
|
|
1699
|
+
if (minutes < 60)
|
|
1700
|
+
return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
|
|
1701
|
+
const hours = Math.floor(minutes / 60);
|
|
1702
|
+
return `${hours}h${(minutes % 60).toString().padStart(2, "0")}m`;
|
|
1579
1703
|
}
|
|
1580
1704
|
function renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase = status.phase) {
|
|
1581
1705
|
const phase = displayPhase;
|
|
@@ -1586,7 +1710,7 @@ function renderCompactStatusSegments(status, animationTick, width, inputTokens,
|
|
|
1586
1710
|
const context = renderContextParts(status.metrics);
|
|
1587
1711
|
const fixedText = [
|
|
1588
1712
|
phaseText,
|
|
1589
|
-
|
|
1713
|
+
context.percent,
|
|
1590
1714
|
`↑ ${inputValue}`,
|
|
1591
1715
|
`↓ ${outputValue}`,
|
|
1592
1716
|
].join(STATUS_SEPARATOR);
|
|
@@ -1603,9 +1727,7 @@ function renderCompactStatusSegments(status, animationTick, width, inputTokens,
|
|
|
1603
1727
|
statusDividerSegment(),
|
|
1604
1728
|
{ text: model },
|
|
1605
1729
|
statusDividerSegment(),
|
|
1606
|
-
|
|
1607
|
-
{ text: ` ${context.percent}`, color: contextColor(status.metrics) },
|
|
1608
|
-
{ text: ` of ${context.limit}` },
|
|
1730
|
+
{ text: context.percent, color: contextColor(status.metrics) },
|
|
1609
1731
|
statusDividerSegment(),
|
|
1610
1732
|
statusLabelSegment("↑", tokenInputColor),
|
|
1611
1733
|
{ text: ` ${inputValue}` },
|
|
@@ -2144,14 +2266,14 @@ function reduceStatus(status, event) {
|
|
|
2144
2266
|
}
|
|
2145
2267
|
return status;
|
|
2146
2268
|
}
|
|
2147
|
-
async function handleSessionsCommand(runtime, setBrowser, append) {
|
|
2269
|
+
async function handleSessionsCommand(runtime, runningSessionIds, setBrowser, append) {
|
|
2148
2270
|
const sessions = await runtime.engine.listSessions(Number.POSITIVE_INFINITY);
|
|
2149
2271
|
if (sessions.length === 0) {
|
|
2150
2272
|
setBrowser(undefined);
|
|
2151
2273
|
append(systemLine("No saved sessions found."));
|
|
2152
2274
|
return;
|
|
2153
2275
|
}
|
|
2154
|
-
setBrowser({ sessions, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
2276
|
+
setBrowser({ sessions, runningSessionIds, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
2155
2277
|
}
|
|
2156
2278
|
async function handleExportCommand(command, runtime) {
|
|
2157
2279
|
const snapshot = runtime.engine.snapshot();
|
|
@@ -2169,7 +2291,11 @@ async function handleExportCommand(command, runtime) {
|
|
|
2169
2291
|
}
|
|
2170
2292
|
async function handleResumeCommand(sessionId, runtime, append) {
|
|
2171
2293
|
try {
|
|
2172
|
-
|
|
2294
|
+
runtime.engine = runtime.engine.forkForSession(sessionId, true);
|
|
2295
|
+
await runtime.engine.initialize();
|
|
2296
|
+
const snapshot = runtime.engine.snapshot().session;
|
|
2297
|
+
if (!snapshot)
|
|
2298
|
+
throw new Error("session transcripts are disabled");
|
|
2173
2299
|
const metrics = await runtime.engine.contextMetrics();
|
|
2174
2300
|
return { snapshot, metrics };
|
|
2175
2301
|
}
|
|
@@ -2196,6 +2322,7 @@ async function handleDeleteSessionCommand(sessionId, current, runtime, setBrowse
|
|
|
2196
2322
|
setBrowser({
|
|
2197
2323
|
...current,
|
|
2198
2324
|
sessions: nextSessions,
|
|
2325
|
+
runningSessionIds: current.runningSessionIds.filter((id) => id !== sessionId),
|
|
2199
2326
|
pageIndex,
|
|
2200
2327
|
selectedIndex: Math.min(current.selectedIndex, Math.max(0, pageLength - 1)),
|
|
2201
2328
|
});
|
|
@@ -2342,7 +2469,7 @@ function SessionsBrowser({ state, width }) {
|
|
|
2342
2469
|
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((session, index) => {
|
|
2343
2470
|
const selected = index === state.selectedIndex;
|
|
2344
2471
|
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
2345
|
-
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth);
|
|
2472
|
+
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth, state.runningSessionIds.includes(session.sessionId));
|
|
2346
2473
|
return e(Text, { key: session.sessionId, color: "white" }, e(Text, {
|
|
2347
2474
|
color: selected ? "black" : "white",
|
|
2348
2475
|
backgroundColor: selected ? "cyan" : undefined,
|
|
@@ -2685,16 +2812,17 @@ function stripEnvQuotes(value) {
|
|
|
2685
2812
|
return value.slice(1, -1);
|
|
2686
2813
|
return value;
|
|
2687
2814
|
}
|
|
2688
|
-
function formatSessionBrowserRow(session, absoluteIndex, width) {
|
|
2815
|
+
function formatSessionBrowserRow(session, absoluteIndex, width, running = false) {
|
|
2689
2816
|
const numberPrefix = `${absoluteIndex + 1}.`.padStart(4);
|
|
2690
2817
|
const title = session.title?.trim() || "(untitled)";
|
|
2818
|
+
const runningTag = running ? " · running" : "";
|
|
2691
2819
|
const updated = session.updatedAt ? ` · ${formatSessionTimestamp(session.updatedAt)}` : "";
|
|
2692
2820
|
const messages = ` · ${session.messages} messages`;
|
|
2693
|
-
const fixedParts = `${numberPrefix} ${updated}${messages}`;
|
|
2821
|
+
const fixedParts = `${numberPrefix} ${runningTag}${updated}${messages}`;
|
|
2694
2822
|
const idBudget = Math.max(12, Math.min(32, Math.floor(width * 0.28)));
|
|
2695
2823
|
const id = truncateMiddle(session.sessionId, idBudget);
|
|
2696
2824
|
const titleBudget = Math.max(8, width - fixedParts.length - id.length - 5);
|
|
2697
|
-
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${updated}${messages}`, width);
|
|
2825
|
+
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${runningTag}${updated}${messages}`, width);
|
|
2698
2826
|
return { numberPrefix, rest: row.slice(numberPrefix.length) };
|
|
2699
2827
|
}
|
|
2700
2828
|
function formatSessionTimestamp(value) {
|
|
@@ -2978,26 +3106,11 @@ function isReplScalar(value) {
|
|
|
2978
3106
|
return value === null || value === undefined || typeof value !== "object" || value instanceof Date;
|
|
2979
3107
|
}
|
|
2980
3108
|
function formatToolResult(toolName, output, ok) {
|
|
2981
|
-
if (toolName === "edit" && isRecord(output) && isEditToolOutput(output)) {
|
|
3109
|
+
if ((toolName === "edit" || toolName === "write") && isRecord(output) && isEditToolOutput(output)) {
|
|
2982
3110
|
return { text: formatEditToolDiff(output, ok), format: "ansi", summaryMaxLines: EDIT_TOOL_SUMMARY_MAX_LINES };
|
|
2983
3111
|
}
|
|
2984
3112
|
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" };
|
|
3113
|
+
return { text: formatExecToolResult(output, ok), format: "ansi", summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
3001
3114
|
}
|
|
3002
3115
|
if (typeof output === "string" && hasAnsi(output)) {
|
|
3003
3116
|
return { text: output, format: "ansi" };
|
|
@@ -3104,6 +3217,28 @@ function isExecOutput(value) {
|
|
|
3104
3217
|
typeof record.stdout === "string" &&
|
|
3105
3218
|
typeof record.stderr === "string");
|
|
3106
3219
|
}
|
|
3220
|
+
function formatExecToolResult(output, ok) {
|
|
3221
|
+
const status = output.timedOut
|
|
3222
|
+
? "timed out"
|
|
3223
|
+
: output.exitCode === 0
|
|
3224
|
+
? "exit 0"
|
|
3225
|
+
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
3226
|
+
const lines = [
|
|
3227
|
+
"exec result",
|
|
3228
|
+
`status: ${status}`,
|
|
3229
|
+
`duration: ${output.durationMs}ms`,
|
|
3230
|
+
`command: ${output.command}`,
|
|
3231
|
+
];
|
|
3232
|
+
const stdout = output.stdout.replace(/\s+$/u, "");
|
|
3233
|
+
const stderr = output.stderr.replace(/\s+$/u, "");
|
|
3234
|
+
if (stdout)
|
|
3235
|
+
lines.push("stdout:", stdout);
|
|
3236
|
+
if (stderr)
|
|
3237
|
+
lines.push("stderr:", stderr);
|
|
3238
|
+
if (!stdout && !stderr)
|
|
3239
|
+
lines.push(ok ? "output: (none)" : "output: (not captured)");
|
|
3240
|
+
return lines.join("\n");
|
|
3241
|
+
}
|
|
3107
3242
|
function isRecord(value) {
|
|
3108
3243
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3109
3244
|
}
|
|
@@ -3254,11 +3389,9 @@ function formatGrepContextLine(line, marker) {
|
|
|
3254
3389
|
}
|
|
3255
3390
|
function renderContextParts(metrics) {
|
|
3256
3391
|
if (!metrics)
|
|
3257
|
-
return {
|
|
3258
|
-
const used = compactNumber(metrics.estimatedInputTokens);
|
|
3259
|
-
const limit = metrics.contextWindowTokens ? compactNumber(metrics.contextWindowTokens) : "?";
|
|
3392
|
+
return { percent: "?" };
|
|
3260
3393
|
const percent = metrics.contextUsageRatio === undefined ? "?" : `${(metrics.contextUsageRatio * 100).toFixed(1)}%`;
|
|
3261
|
-
return {
|
|
3394
|
+
return { percent };
|
|
3262
3395
|
}
|
|
3263
3396
|
function contextColor(metrics) {
|
|
3264
3397
|
const ratio = metrics?.contextUsageRatio;
|