@pickle-pee/genesis-cli 0.0.1 → 0.0.2
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/input-loop.d.ts +7 -1
- package/dist/input-loop.js +22 -1
- package/dist/input-loop.js.map +1 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.js +288 -55
- package/dist/main.js.map +1 -1
- package/dist/mode-dispatch.d.ts +40 -3
- package/dist/mode-dispatch.js +725 -105
- package/dist/mode-dispatch.js.map +1 -1
- package/package.json +4 -4
package/dist/mode-dispatch.js
CHANGED
|
@@ -23,6 +23,7 @@ exports.movePermissionSelection = movePermissionSelection;
|
|
|
23
23
|
exports.permissionDecisionFromSelection = permissionDecisionFromSelection;
|
|
24
24
|
exports.formatInteractiveToolTitle = formatInteractiveToolTitle;
|
|
25
25
|
exports.formatInteractiveToolResult = formatInteractiveToolResult;
|
|
26
|
+
exports.createDebouncedCallback = createDebouncedCallback;
|
|
26
27
|
exports.computeSlashSuggestions = computeSlashSuggestions;
|
|
27
28
|
exports.formatSlashSuggestionHint = formatSlashSuggestionHint;
|
|
28
29
|
exports.acceptFirstSlashSuggestion = acceptFirstSlashSuggestion;
|
|
@@ -42,9 +43,11 @@ exports.formatTurnNotice = formatTurnNotice;
|
|
|
42
43
|
exports.mergeStreamingText = mergeStreamingText;
|
|
43
44
|
exports.wrapTranscriptContent = wrapTranscriptContent;
|
|
44
45
|
exports.computeVisibleTranscriptLines = computeVisibleTranscriptLines;
|
|
46
|
+
exports.extractPlainTextSelection = extractPlainTextSelection;
|
|
45
47
|
exports.computeTranscriptDisplayRows = computeTranscriptDisplayRows;
|
|
46
48
|
exports.materializeAssistantTranscriptBlock = materializeAssistantTranscriptBlock;
|
|
47
49
|
exports.appendAssistantTranscriptBlock = appendAssistantTranscriptBlock;
|
|
50
|
+
exports.appendTranscriptBlockWithSpacer = appendTranscriptBlockWithSpacer;
|
|
48
51
|
exports.computeFooterStartRow = computeFooterStartRow;
|
|
49
52
|
const node_child_process_1 = require("node:child_process");
|
|
50
53
|
const promises_1 = require("node:fs/promises");
|
|
@@ -71,6 +74,8 @@ function createModeHandler(mode) {
|
|
|
71
74
|
return new RpcModeHandler();
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
const TURN_NOTICE_ELAPSED_THRESHOLD_MS = 2_000;
|
|
78
|
+
const RESIZE_REDRAW_DEBOUNCE_MS = 120;
|
|
74
79
|
// ---------------------------------------------------------------------------
|
|
75
80
|
// Interactive mode
|
|
76
81
|
// ---------------------------------------------------------------------------
|
|
@@ -87,10 +92,17 @@ class InteractiveModeHandler {
|
|
|
87
92
|
_changedPaths = new Set();
|
|
88
93
|
_transcriptBlocks = [];
|
|
89
94
|
_assistantBuffer = "";
|
|
90
|
-
_streamingReservedRows = 0;
|
|
91
|
-
_streamingDisplayRows = 0;
|
|
92
|
-
_renderedStreamingStartRow = null;
|
|
93
95
|
_turnNotice = null;
|
|
96
|
+
_turnNoticeAnimationFrame = 0;
|
|
97
|
+
_turnNoticeTimer = null;
|
|
98
|
+
_turnStartedAt = null;
|
|
99
|
+
_detailPanelExpanded = false;
|
|
100
|
+
_detailPanelScroll = 0;
|
|
101
|
+
_thinkingBuffer = "";
|
|
102
|
+
_activeTurnUsageTotals = emptyUsageSnapshot();
|
|
103
|
+
_currentMessageUsage = emptyUsageSnapshot();
|
|
104
|
+
_lastTurnUsage = null;
|
|
105
|
+
_sessionUsageTotals = emptyUsageSnapshot();
|
|
94
106
|
_commandSuggestions = [];
|
|
95
107
|
_toolCalls = new Map();
|
|
96
108
|
_queuedInputs = [];
|
|
@@ -98,6 +110,9 @@ class InteractiveModeHandler {
|
|
|
98
110
|
_renderedFooterUi = null;
|
|
99
111
|
_renderedFooterStartRow = null;
|
|
100
112
|
_welcomeLines = [];
|
|
113
|
+
_transcriptScrollOffset = 0;
|
|
114
|
+
_renderedTranscriptViewportLines = [];
|
|
115
|
+
_mouseSelection = null;
|
|
101
116
|
async start(runtime) {
|
|
102
117
|
const handler = this;
|
|
103
118
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -128,11 +143,14 @@ class InteractiveModeHandler {
|
|
|
128
143
|
onResume: () => {
|
|
129
144
|
this.rerenderInteractiveRegions();
|
|
130
145
|
},
|
|
131
|
-
useAlternateScreen:
|
|
132
|
-
enableMouseTracking:
|
|
146
|
+
useAlternateScreen: true,
|
|
147
|
+
enableMouseTracking: true,
|
|
133
148
|
});
|
|
134
|
-
const
|
|
149
|
+
const debouncedResizeRedraw = createDebouncedCallback(() => {
|
|
135
150
|
this.rerenderInteractiveRegions();
|
|
151
|
+
}, RESIZE_REDRAW_DEBOUNCE_MS);
|
|
152
|
+
const onResize = () => {
|
|
153
|
+
debouncedResizeRedraw.schedule();
|
|
136
154
|
};
|
|
137
155
|
process.stdout.on("resize", onResize);
|
|
138
156
|
const resolveAgentDir = () => {
|
|
@@ -150,16 +168,24 @@ class InteractiveModeHandler {
|
|
|
150
168
|
this._changedPaths.clear();
|
|
151
169
|
this._transcriptBlocks.length = 0;
|
|
152
170
|
this._assistantBuffer = "";
|
|
153
|
-
this.
|
|
154
|
-
this._streamingDisplayRows = 0;
|
|
155
|
-
this._renderedStreamingStartRow = null;
|
|
171
|
+
this.stopTurnNoticeAnimation();
|
|
156
172
|
this._turnNotice = null;
|
|
173
|
+
this._turnNoticeAnimationFrame = 0;
|
|
174
|
+
this._turnStartedAt = null;
|
|
175
|
+
this._detailPanelExpanded = false;
|
|
176
|
+
this._detailPanelScroll = 0;
|
|
177
|
+
this._thinkingBuffer = "";
|
|
178
|
+
this._activeTurnUsageTotals = emptyUsageSnapshot();
|
|
179
|
+
this._currentMessageUsage = emptyUsageSnapshot();
|
|
180
|
+
this._lastTurnUsage = null;
|
|
181
|
+
this._sessionUsageTotals = emptyUsageSnapshot();
|
|
157
182
|
this._commandSuggestions = [];
|
|
158
183
|
this._toolCalls.clear();
|
|
159
184
|
this._queuedInputs.length = 0;
|
|
160
185
|
this._pendingPermissionSelection = 0;
|
|
161
186
|
this._renderedFooterUi = null;
|
|
162
187
|
this._renderedFooterStartRow = null;
|
|
188
|
+
this._transcriptScrollOffset = 0;
|
|
163
189
|
sessionTitle = undefined;
|
|
164
190
|
interactionState = (0, ui_1.initialInteractionState)();
|
|
165
191
|
sessionRef.current.events.on("session_closed", (event) => {
|
|
@@ -772,6 +798,9 @@ class InteractiveModeHandler {
|
|
|
772
798
|
this.rerenderInteractiveRegions();
|
|
773
799
|
}
|
|
774
800
|
},
|
|
801
|
+
onMouse: (event) => {
|
|
802
|
+
this.handleMouseEvent(event);
|
|
803
|
+
},
|
|
775
804
|
});
|
|
776
805
|
ttySession.enter();
|
|
777
806
|
this.renderWelcome(sessionRef.current);
|
|
@@ -822,6 +851,8 @@ class InteractiveModeHandler {
|
|
|
822
851
|
// Regular prompt
|
|
823
852
|
if (this._activeTurn !== null) {
|
|
824
853
|
this._queuedInputs.push(trimmed);
|
|
854
|
+
this.preserveThinkingNoticeForQueuedBacklog();
|
|
855
|
+
this.renderFooterRegion();
|
|
825
856
|
line = await inputLoop.nextLine();
|
|
826
857
|
continue;
|
|
827
858
|
}
|
|
@@ -831,6 +862,7 @@ class InteractiveModeHandler {
|
|
|
831
862
|
}
|
|
832
863
|
finally {
|
|
833
864
|
process.stdout.off("resize", onResize);
|
|
865
|
+
debouncedResizeRedraw.cancel();
|
|
834
866
|
inputLoop.close();
|
|
835
867
|
process.stdout.write(ansiResetScrollRegion());
|
|
836
868
|
ttySession.restore();
|
|
@@ -850,7 +882,61 @@ class InteractiveModeHandler {
|
|
|
850
882
|
renderPromptLine() {
|
|
851
883
|
this.renderFooterRegion();
|
|
852
884
|
}
|
|
885
|
+
handleMouseEvent(event) {
|
|
886
|
+
if (!this.isTranscriptMouseRow(event.row)) {
|
|
887
|
+
if (event.kind === "leftdown") {
|
|
888
|
+
this.clearMouseSelection();
|
|
889
|
+
}
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (event.kind === "leftdown") {
|
|
893
|
+
this._mouseSelection = {
|
|
894
|
+
anchorRow: event.row,
|
|
895
|
+
anchorColumn: event.column,
|
|
896
|
+
focusRow: event.row,
|
|
897
|
+
focusColumn: event.column,
|
|
898
|
+
};
|
|
899
|
+
this.renderTranscriptViewport();
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (this._mouseSelection === null) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
this._mouseSelection = {
|
|
906
|
+
...this._mouseSelection,
|
|
907
|
+
focusRow: event.row,
|
|
908
|
+
focusColumn: event.column,
|
|
909
|
+
};
|
|
910
|
+
this.renderTranscriptViewport();
|
|
911
|
+
if (event.kind === "leftup") {
|
|
912
|
+
const selectedText = this.currentTranscriptSelectionText();
|
|
913
|
+
if (selectedText.length > 0) {
|
|
914
|
+
copyTextToClipboard(selectedText);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
853
918
|
handleSpecialKey(key) {
|
|
919
|
+
if (key === "ctrlo") {
|
|
920
|
+
this.toggleDetailPanel();
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
if (key === "esc" && this._detailPanelExpanded) {
|
|
924
|
+
this._detailPanelExpanded = false;
|
|
925
|
+
this.renderFooterRegion();
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
if (this._detailPanelExpanded) {
|
|
929
|
+
const detailScrollDelta = detailPanelScrollDeltaForKey(key, this.currentDetailPanelViewport().viewportSize);
|
|
930
|
+
if (detailScrollDelta !== 0) {
|
|
931
|
+
this.scrollDetailPanel(detailScrollDelta);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
const transcriptScrollDelta = transcriptScrollDeltaForKey(key, this.currentTranscriptViewportRows());
|
|
936
|
+
if (transcriptScrollDelta !== 0) {
|
|
937
|
+
this.scrollTranscript(transcriptScrollDelta);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
854
940
|
if (this._pendingPermissionCallId !== null) {
|
|
855
941
|
if (key === "up" || key === "shifttab") {
|
|
856
942
|
this._pendingPermissionSelection = movePermissionSelection(this._pendingPermissionSelection, -1);
|
|
@@ -894,11 +980,17 @@ class InteractiveModeHandler {
|
|
|
894
980
|
}
|
|
895
981
|
return;
|
|
896
982
|
}
|
|
983
|
+
if (event.category === "usage" && event.type === "usage_updated") {
|
|
984
|
+
this.updateTurnUsage(event.usage, event.isFinal);
|
|
985
|
+
this.renderPromptLine();
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
897
988
|
if (!shouldRenderInteractiveTranscriptEvent(event)) {
|
|
898
989
|
this.renderPromptLine();
|
|
899
990
|
return;
|
|
900
991
|
}
|
|
901
992
|
if (event.category === "text" && event.type === "thinking_delta") {
|
|
993
|
+
this._thinkingBuffer += event.content;
|
|
902
994
|
if (this._turnNotice === null) {
|
|
903
995
|
this.startTurnFeedback();
|
|
904
996
|
}
|
|
@@ -907,10 +999,14 @@ class InteractiveModeHandler {
|
|
|
907
999
|
}
|
|
908
1000
|
if (event.category === "text" && event.type === "text_delta") {
|
|
909
1001
|
if (this._turnNotice !== "responding") {
|
|
1002
|
+
this._turnNoticeAnimationFrame = 0;
|
|
910
1003
|
this._turnNotice = "responding";
|
|
1004
|
+
this.startTurnNoticeAnimation();
|
|
911
1005
|
}
|
|
1006
|
+
const previousRows = this.currentTranscriptDisplayRows();
|
|
912
1007
|
this._assistantBuffer = mergeStreamingText(this._assistantBuffer, event.content);
|
|
913
|
-
this.
|
|
1008
|
+
this.adjustTranscriptScrollForGrowth(previousRows, this.currentTranscriptDisplayRows());
|
|
1009
|
+
this.fullRedrawInteractiveScreen();
|
|
914
1010
|
return;
|
|
915
1011
|
}
|
|
916
1012
|
this.flushAssistantBuffer(false);
|
|
@@ -931,12 +1027,11 @@ class InteractiveModeHandler {
|
|
|
931
1027
|
}
|
|
932
1028
|
const assistantBlock = materializeAssistantTranscriptBlock(this._assistantBuffer);
|
|
933
1029
|
if (assistantBlock !== null) {
|
|
1030
|
+
const previousRows = this.currentTranscriptDisplayRows();
|
|
934
1031
|
this.rememberAssistantTranscriptBlock(assistantBlock);
|
|
1032
|
+
this.adjustTranscriptScrollForGrowth(previousRows, this.currentTranscriptDisplayRows());
|
|
935
1033
|
}
|
|
936
1034
|
this._assistantBuffer = "";
|
|
937
|
-
this._streamingReservedRows = 0;
|
|
938
|
-
this._streamingDisplayRows = 0;
|
|
939
|
-
this._renderedStreamingStartRow = null;
|
|
940
1035
|
if (redrawPrompt) {
|
|
941
1036
|
this.renderPromptLine();
|
|
942
1037
|
}
|
|
@@ -946,39 +1041,35 @@ class InteractiveModeHandler {
|
|
|
946
1041
|
return;
|
|
947
1042
|
}
|
|
948
1043
|
this._turnNotice = "thinking";
|
|
1044
|
+
this._turnNoticeAnimationFrame = 0;
|
|
1045
|
+
this.startTurnNoticeAnimation();
|
|
949
1046
|
this.renderPromptLine();
|
|
950
1047
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const renderedWidth = this.terminalWidth();
|
|
955
|
-
const rows = countRenderedTerminalRows(lines, renderedWidth);
|
|
956
|
-
const previousStartRow = this._renderedStreamingStartRow;
|
|
957
|
-
const previousRows = this._streamingReservedRows;
|
|
958
|
-
this._streamingDisplayRows = rows;
|
|
959
|
-
this.renderFooterRegion();
|
|
960
|
-
const footerStartRow = this._renderedFooterStartRow ??
|
|
961
|
-
computeFooterStartRow(this._welcomeLines.length, this.terminalHeight(), this.currentFooterHeight(), this.currentTranscriptDisplayRows());
|
|
962
|
-
if (isFooterBottomAnchored(footerStartRow, this.terminalHeight(), this.currentFooterHeight())) {
|
|
963
|
-
this.reserveStreamingRows(rows);
|
|
1048
|
+
startTurnNoticeAnimation() {
|
|
1049
|
+
if (this._turnNoticeTimer !== null) {
|
|
1050
|
+
return;
|
|
964
1051
|
}
|
|
965
|
-
|
|
966
|
-
this.
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
this.
|
|
976
|
-
|
|
977
|
-
|
|
1052
|
+
this._turnNoticeTimer = setInterval(() => {
|
|
1053
|
+
if (this._turnNotice === null) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
this._turnNoticeAnimationFrame = (this._turnNoticeAnimationFrame + 1) % 3;
|
|
1057
|
+
this.renderFooterRegion();
|
|
1058
|
+
}, 400);
|
|
1059
|
+
this._turnNoticeTimer.unref?.();
|
|
1060
|
+
}
|
|
1061
|
+
stopTurnNoticeAnimation() {
|
|
1062
|
+
if (this._turnNoticeTimer === null) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
clearInterval(this._turnNoticeTimer);
|
|
1066
|
+
this._turnNoticeTimer = null;
|
|
978
1067
|
}
|
|
979
1068
|
writeTranscriptText(text, newline, redrawPrompt = true) {
|
|
980
1069
|
this.flushAssistantBuffer(false);
|
|
1070
|
+
const previousRows = this.currentTranscriptDisplayRows();
|
|
981
1071
|
this.rememberTranscriptBlock(text, newline);
|
|
1072
|
+
this.adjustTranscriptScrollForGrowth(previousRows, this.currentTranscriptDisplayRows());
|
|
982
1073
|
const logicalLines = text.split("\n");
|
|
983
1074
|
const outputLines = newline ? logicalLines : logicalLines.slice(0, -1).concat(logicalLines.at(-1) ?? "");
|
|
984
1075
|
if (outputLines.length > 0) {
|
|
@@ -1012,35 +1103,131 @@ class InteractiveModeHandler {
|
|
|
1012
1103
|
this.renderPromptLine();
|
|
1013
1104
|
}
|
|
1014
1105
|
startPromptTurn(session, prompt, sink) {
|
|
1106
|
+
this.startUserTurn(session, prompt, sink, "prompt");
|
|
1107
|
+
}
|
|
1108
|
+
startQueuedContinueTurn(session, input, sink) {
|
|
1109
|
+
this.startUserTurn(session, input, sink, "continue");
|
|
1110
|
+
}
|
|
1111
|
+
startUserTurn(session, input, sink, mode) {
|
|
1015
1112
|
this.flushAssistantBuffer(false);
|
|
1016
|
-
this.writeTranscriptText(formatTranscriptUserLine(
|
|
1113
|
+
this.writeTranscriptText(formatTranscriptUserLine(input), true, false);
|
|
1114
|
+
this._turnStartedAt = Date.now();
|
|
1115
|
+
this._detailPanelExpanded = false;
|
|
1116
|
+
this._detailPanelScroll = 0;
|
|
1117
|
+
this._thinkingBuffer = "";
|
|
1118
|
+
this._activeTurnUsageTotals = emptyUsageSnapshot();
|
|
1119
|
+
this._currentMessageUsage = emptyUsageSnapshot();
|
|
1017
1120
|
this.startTurnFeedback();
|
|
1018
|
-
this.rememberHistory(
|
|
1019
|
-
|
|
1020
|
-
|
|
1121
|
+
this.rememberHistory(input);
|
|
1122
|
+
const sendTurn = mode === "continue" ? (value) => session.continue(value) : (value) => session.prompt(value);
|
|
1123
|
+
this._activeTurn = sendTurn(input)
|
|
1021
1124
|
.catch((err) => {
|
|
1022
1125
|
sink.writeError(`Error: ${err}`);
|
|
1023
1126
|
})
|
|
1024
1127
|
.finally(() => {
|
|
1128
|
+
this.stopTurnNoticeAnimation();
|
|
1129
|
+
const completedTurnUsage = this.currentTurnUsage();
|
|
1025
1130
|
this._activeTurn = null;
|
|
1026
1131
|
this.flushAssistantBuffer(false);
|
|
1027
1132
|
this._turnNotice = null;
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1133
|
+
this._turnNoticeAnimationFrame = 0;
|
|
1134
|
+
this._turnStartedAt = null;
|
|
1135
|
+
this._detailPanelExpanded = false;
|
|
1136
|
+
this._detailPanelScroll = 0;
|
|
1137
|
+
this._thinkingBuffer = "";
|
|
1138
|
+
if (hasUsageSnapshot(completedTurnUsage)) {
|
|
1139
|
+
this._lastTurnUsage = completedTurnUsage;
|
|
1140
|
+
this._sessionUsageTotals = addUsageSnapshots(this._sessionUsageTotals, completedTurnUsage);
|
|
1141
|
+
}
|
|
1142
|
+
this._activeTurnUsageTotals = emptyUsageSnapshot();
|
|
1143
|
+
this._currentMessageUsage = emptyUsageSnapshot();
|
|
1144
|
+
const queuedInputBatch = this.drainQueuedInputs();
|
|
1145
|
+
if (queuedInputBatch !== null) {
|
|
1146
|
+
this.startQueuedContinueTurn(session, queuedInputBatch, sink);
|
|
1031
1147
|
return;
|
|
1032
1148
|
}
|
|
1033
1149
|
this.fullRedrawInteractiveScreen();
|
|
1034
1150
|
});
|
|
1035
1151
|
}
|
|
1152
|
+
drainQueuedInputs() {
|
|
1153
|
+
if (this._queuedInputs.length === 0) {
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
const queued = [...this._queuedInputs];
|
|
1157
|
+
this._queuedInputs.length = 0;
|
|
1158
|
+
return queued.join("\n\n");
|
|
1159
|
+
}
|
|
1160
|
+
preserveThinkingNoticeForQueuedBacklog() {
|
|
1161
|
+
if (this._activeTurn === null) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (this._turnNotice === "responding") {
|
|
1165
|
+
this._turnNotice = "thinking";
|
|
1166
|
+
this.startTurnNoticeAnimation();
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (this._turnNotice === null) {
|
|
1170
|
+
this.startTurnFeedback();
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
toggleDetailPanel() {
|
|
1174
|
+
if (this.currentDetailPanelContentLines().length === 0) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
this._detailPanelExpanded = !this._detailPanelExpanded;
|
|
1178
|
+
if (this._detailPanelExpanded) {
|
|
1179
|
+
this._detailPanelScroll = 0;
|
|
1180
|
+
}
|
|
1181
|
+
this.renderFooterRegion();
|
|
1182
|
+
}
|
|
1183
|
+
scrollDetailPanel(delta) {
|
|
1184
|
+
const viewport = this.currentDetailPanelViewport();
|
|
1185
|
+
if (viewport.totalLines <= viewport.viewportSize) {
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const maxScroll = Math.max(0, viewport.totalLines - viewport.viewportSize);
|
|
1189
|
+
const next = Math.max(0, Math.min(maxScroll, this._detailPanelScroll + delta));
|
|
1190
|
+
if (next === this._detailPanelScroll) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
this._detailPanelScroll = next;
|
|
1194
|
+
this.renderFooterRegion();
|
|
1195
|
+
}
|
|
1196
|
+
scrollTranscript(delta) {
|
|
1197
|
+
const maxScroll = this.currentTranscriptMaxScroll();
|
|
1198
|
+
if (delta === 0 || maxScroll <= 0) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const next = Math.max(0, Math.min(maxScroll, this._transcriptScrollOffset + delta));
|
|
1202
|
+
if (next === this._transcriptScrollOffset) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
this.clearMouseSelection(false);
|
|
1206
|
+
this._transcriptScrollOffset = next;
|
|
1207
|
+
this.renderTranscriptViewport();
|
|
1208
|
+
this.renderFooterRegion();
|
|
1209
|
+
}
|
|
1036
1210
|
buildFooterUi() {
|
|
1211
|
+
const activeToolLabel = summarizeActiveToolNotice(this._toolCalls);
|
|
1212
|
+
const detailPanel = this.currentDetailPanelViewport();
|
|
1037
1213
|
return formatInteractiveFooter({
|
|
1038
1214
|
terminalWidth: process.stdout.columns ?? 80,
|
|
1039
1215
|
prompt: this._prompt,
|
|
1040
1216
|
buffer: this._inputState.buffer,
|
|
1041
1217
|
cursor: this._inputState.cursor,
|
|
1042
1218
|
suggestions: this._commandSuggestions,
|
|
1043
|
-
turnNotice: this._turnNotice,
|
|
1219
|
+
turnNotice: activeToolLabel !== null ? "tool" : this._turnNotice,
|
|
1220
|
+
turnNoticeAnimationFrame: this._turnNoticeAnimationFrame,
|
|
1221
|
+
elapsedMs: this.currentTurnElapsedMs(),
|
|
1222
|
+
currentTurnUsage: this.currentTurnUsage(),
|
|
1223
|
+
lastTurnUsage: this._lastTurnUsage,
|
|
1224
|
+
sessionUsage: this._sessionUsageTotals,
|
|
1225
|
+
activeToolLabel,
|
|
1226
|
+
showPendingOutputIndicator: this.shouldShowPendingOutputIndicator(activeToolLabel),
|
|
1227
|
+
detailPanelExpanded: this._detailPanelExpanded,
|
|
1228
|
+
detailPanelLines: detailPanel.lines,
|
|
1229
|
+
detailPanelSummary: detailPanel.summary,
|
|
1230
|
+
queuedInputs: this._queuedInputs,
|
|
1044
1231
|
permission: this._pendingPermissionCallId !== null && this._pendingPermissionDetails !== null
|
|
1045
1232
|
? {
|
|
1046
1233
|
details: this._pendingPermissionDetails,
|
|
@@ -1049,15 +1236,132 @@ class InteractiveModeHandler {
|
|
|
1049
1236
|
: null,
|
|
1050
1237
|
});
|
|
1051
1238
|
}
|
|
1239
|
+
currentDetailPanelContentLines() {
|
|
1240
|
+
if (this._thinkingBuffer.trim().length === 0) {
|
|
1241
|
+
return [];
|
|
1242
|
+
}
|
|
1243
|
+
return wrapTranscriptContent(this._thinkingBuffer.trim(), this.terminalWidth());
|
|
1244
|
+
}
|
|
1245
|
+
shouldShowPendingOutputIndicator(activeToolLabel) {
|
|
1246
|
+
if (this._assistantBuffer.length > 0) {
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
if (activeToolLabel === null) {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
const lastNonEmptyIndex = findLastNonEmptyBlockIndex(this._transcriptBlocks);
|
|
1253
|
+
if (lastNonEmptyIndex === -1) {
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
const lastBlock = this._transcriptBlocks[lastNonEmptyIndex] ?? "";
|
|
1257
|
+
return !isTranscriptUserBlock(lastBlock) && !lastBlock.startsWith("⏺ ") && !lastBlock.startsWith(" ⎿ ");
|
|
1258
|
+
}
|
|
1259
|
+
currentDetailPanelViewport() {
|
|
1260
|
+
const lines = this.currentDetailPanelContentLines();
|
|
1261
|
+
if (lines.length === 0) {
|
|
1262
|
+
return { lines: [], summary: null, viewportSize: 0, totalLines: 0 };
|
|
1263
|
+
}
|
|
1264
|
+
if (!this._detailPanelExpanded) {
|
|
1265
|
+
return {
|
|
1266
|
+
lines: [],
|
|
1267
|
+
summary: "ctrl+o to expand",
|
|
1268
|
+
viewportSize: 0,
|
|
1269
|
+
totalLines: lines.length,
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
const viewportSize = Math.max(3, this.terminalHeight() - 8);
|
|
1273
|
+
const maxScroll = Math.max(0, lines.length - viewportSize);
|
|
1274
|
+
const start = Math.max(0, Math.min(this._detailPanelScroll, maxScroll));
|
|
1275
|
+
const end = Math.min(lines.length, start + viewportSize);
|
|
1276
|
+
const summary = lines.length <= viewportSize
|
|
1277
|
+
? "esc to collapse · ↑↓"
|
|
1278
|
+
: `esc to collapse · ↑↓ · ${start + 1}-${end}/${lines.length}`;
|
|
1279
|
+
return {
|
|
1280
|
+
lines: lines.slice(start, end),
|
|
1281
|
+
summary,
|
|
1282
|
+
viewportSize,
|
|
1283
|
+
totalLines: lines.length,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1052
1286
|
rerenderInteractiveRegions() {
|
|
1287
|
+
this.clearMouseSelection(false);
|
|
1288
|
+
this.clampTranscriptScrollOffset();
|
|
1053
1289
|
this.fullRedrawInteractiveScreen();
|
|
1054
1290
|
}
|
|
1291
|
+
adjustTranscriptScrollForGrowth(previousRows, nextRows) {
|
|
1292
|
+
this.clearMouseSelection(false);
|
|
1293
|
+
if (this._transcriptScrollOffset === 0) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const delta = Math.max(0, nextRows - previousRows);
|
|
1297
|
+
if (delta === 0) {
|
|
1298
|
+
this.clampTranscriptScrollOffset();
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
this._transcriptScrollOffset += delta;
|
|
1302
|
+
this.clampTranscriptScrollOffset();
|
|
1303
|
+
}
|
|
1304
|
+
updateTurnUsage(usage, isFinal) {
|
|
1305
|
+
const normalized = normalizeUsageSnapshot(usage);
|
|
1306
|
+
if (isFinal) {
|
|
1307
|
+
this._activeTurnUsageTotals = addUsageSnapshots(this._activeTurnUsageTotals, normalized);
|
|
1308
|
+
this._currentMessageUsage = emptyUsageSnapshot();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
this._currentMessageUsage = normalized;
|
|
1312
|
+
}
|
|
1313
|
+
currentTurnUsage() {
|
|
1314
|
+
const usage = addUsageSnapshots(this._activeTurnUsageTotals, this._currentMessageUsage);
|
|
1315
|
+
return hasUsageSnapshot(usage) ? usage : null;
|
|
1316
|
+
}
|
|
1317
|
+
currentTurnElapsedMs() {
|
|
1318
|
+
if (this._turnNotice === null || this._turnStartedAt === null) {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
return Math.max(0, Date.now() - this._turnStartedAt);
|
|
1322
|
+
}
|
|
1055
1323
|
terminalWidth() {
|
|
1056
1324
|
return Math.max(1, process.stdout.columns ?? 80);
|
|
1057
1325
|
}
|
|
1058
1326
|
terminalHeight() {
|
|
1059
1327
|
return Math.max(6, process.stdout.rows ?? 24);
|
|
1060
1328
|
}
|
|
1329
|
+
currentTranscriptViewportRows() {
|
|
1330
|
+
const footerUi = this.buildFooterUi();
|
|
1331
|
+
const transcriptTopRow = 1;
|
|
1332
|
+
const transcriptBottomRow = computeFooterStartRow(0, this.terminalHeight(), footerUi.lines.length, this.currentTranscriptDisplayRows()) - 1;
|
|
1333
|
+
return Math.max(0, transcriptBottomRow - transcriptTopRow + 1);
|
|
1334
|
+
}
|
|
1335
|
+
currentTranscriptMaxScroll() {
|
|
1336
|
+
return Math.max(0, this.currentTranscriptDisplayRows() - this.currentTranscriptViewportRows());
|
|
1337
|
+
}
|
|
1338
|
+
clampTranscriptScrollOffset() {
|
|
1339
|
+
this._transcriptScrollOffset = Math.max(0, Math.min(this._transcriptScrollOffset, this.currentTranscriptMaxScroll()));
|
|
1340
|
+
}
|
|
1341
|
+
isTranscriptMouseRow(row) {
|
|
1342
|
+
const footerStartRow = this._renderedFooterStartRow ?? computeFooterStartRow(0, this.terminalHeight(), this.currentFooterHeight(), this.currentTranscriptDisplayRows());
|
|
1343
|
+
return row >= 1 && row < footerStartRow;
|
|
1344
|
+
}
|
|
1345
|
+
clearMouseSelection(redraw = true) {
|
|
1346
|
+
if (this._mouseSelection === null) {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
this._mouseSelection = null;
|
|
1350
|
+
if (redraw) {
|
|
1351
|
+
this.renderTranscriptViewport();
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
currentTranscriptSelectionText() {
|
|
1355
|
+
if (this._mouseSelection === null) {
|
|
1356
|
+
return "";
|
|
1357
|
+
}
|
|
1358
|
+
return extractPlainTextSelection(this._renderedTranscriptViewportLines, {
|
|
1359
|
+
startRow: this._mouseSelection.anchorRow - 1,
|
|
1360
|
+
startColumn: this._mouseSelection.anchorColumn,
|
|
1361
|
+
endRow: this._mouseSelection.focusRow - 1,
|
|
1362
|
+
endColumn: this._mouseSelection.focusColumn,
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1061
1365
|
transcriptBottomRow(footerHeight = this.currentFooterHeight()) {
|
|
1062
1366
|
return Math.max(1, this.terminalHeight() - footerHeight);
|
|
1063
1367
|
}
|
|
@@ -1067,9 +1371,10 @@ class InteractiveModeHandler {
|
|
|
1067
1371
|
renderFooterRegion() {
|
|
1068
1372
|
const ui = this.buildFooterUi();
|
|
1069
1373
|
const footerHeight = ui.lines.length;
|
|
1070
|
-
const startRow = computeFooterStartRow(
|
|
1374
|
+
const startRow = computeFooterStartRow(0, this.terminalHeight(), footerHeight, this.currentTranscriptDisplayRows());
|
|
1071
1375
|
const oldStartRow = this._renderedFooterStartRow;
|
|
1072
1376
|
const oldHeight = this._renderedFooterUi?.lines.length ?? 0;
|
|
1377
|
+
const footerMoved = oldStartRow !== null && oldStartRow !== startRow;
|
|
1073
1378
|
if (oldStartRow !== null && oldStartRow !== startRow) {
|
|
1074
1379
|
for (let index = 0; index < oldHeight; index += 1) {
|
|
1075
1380
|
this.writeAbsoluteTerminalLine(oldStartRow + index, "");
|
|
@@ -1090,6 +1395,9 @@ class InteractiveModeHandler {
|
|
|
1090
1395
|
process.stdout.write((0, ui_1.ansiShowCursor)());
|
|
1091
1396
|
this._renderedFooterUi = { ...ui, renderedWidth: this.terminalWidth() };
|
|
1092
1397
|
this._renderedFooterStartRow = startRow;
|
|
1398
|
+
if (footerMoved) {
|
|
1399
|
+
this.renderTranscriptViewport();
|
|
1400
|
+
}
|
|
1093
1401
|
}
|
|
1094
1402
|
applyTranscriptViewport(footerHeight) {
|
|
1095
1403
|
process.stdout.write(ansiSetScrollRegion(1, this.transcriptBottomRow(footerHeight)));
|
|
@@ -1100,27 +1408,6 @@ class InteractiveModeHandler {
|
|
|
1100
1408
|
}
|
|
1101
1409
|
this.fullRedrawInteractiveScreen();
|
|
1102
1410
|
}
|
|
1103
|
-
reserveStreamingRows(rows) {
|
|
1104
|
-
if (rows <= this._streamingReservedRows) {
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
this.applyTranscriptViewport(this.currentFooterHeight());
|
|
1108
|
-
for (let index = this._streamingReservedRows; index < rows; index += 1) {
|
|
1109
|
-
process.stdout.write(ansiCursorTo(this.transcriptBottomRow(), 1));
|
|
1110
|
-
process.stdout.write("\n");
|
|
1111
|
-
}
|
|
1112
|
-
this._streamingReservedRows = rows;
|
|
1113
|
-
}
|
|
1114
|
-
clearTranscriptRows(startRow, endRow) {
|
|
1115
|
-
for (let row = startRow; row <= endRow; row += 1) {
|
|
1116
|
-
this.writeAbsoluteTerminalLine(row, "");
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
writeLinesAtRow(startRow, lines, width) {
|
|
1120
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
1121
|
-
this.writeAbsoluteTerminalLine(startRow + index, fitTerminalLine(lines[index] ?? "", width));
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
1411
|
writeAbsoluteTerminalLine(row, line) {
|
|
1125
1412
|
process.stdout.write(ansiDisableAutoWrap());
|
|
1126
1413
|
process.stdout.write(ansiCursorTo(row, 1));
|
|
@@ -1128,12 +1415,19 @@ class InteractiveModeHandler {
|
|
|
1128
1415
|
process.stdout.write(line);
|
|
1129
1416
|
process.stdout.write(ansiEnableAutoWrap());
|
|
1130
1417
|
}
|
|
1418
|
+
clearVisibleScreenRows() {
|
|
1419
|
+
for (let row = 1; row <= this.terminalHeight(); row += 1) {
|
|
1420
|
+
this.writeAbsoluteTerminalLine(row, "");
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1131
1423
|
rememberTranscriptBlock(text, newline) {
|
|
1132
1424
|
const block = newline ? text : text.replace(/\n$/, "");
|
|
1133
1425
|
if (block.length === 0) {
|
|
1134
1426
|
return;
|
|
1135
1427
|
}
|
|
1136
|
-
this._transcriptBlocks
|
|
1428
|
+
const nextBlocks = appendTranscriptBlockWithSpacer(this._transcriptBlocks, block);
|
|
1429
|
+
this._transcriptBlocks.length = 0;
|
|
1430
|
+
this._transcriptBlocks.push(...nextBlocks);
|
|
1137
1431
|
}
|
|
1138
1432
|
rememberAssistantTranscriptBlock(block) {
|
|
1139
1433
|
const nextBlocks = appendAssistantTranscriptBlock(this._transcriptBlocks, block);
|
|
@@ -1141,42 +1435,94 @@ class InteractiveModeHandler {
|
|
|
1141
1435
|
this._transcriptBlocks.push(...nextBlocks);
|
|
1142
1436
|
}
|
|
1143
1437
|
fullRedrawInteractiveScreen() {
|
|
1144
|
-
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1438
|
+
this.clampTranscriptScrollOffset();
|
|
1147
1439
|
process.stdout.write(ansiResetScrollRegion());
|
|
1148
1440
|
process.stdout.write((0, ui_1.ansiCursorHome)());
|
|
1149
|
-
|
|
1150
|
-
for (let index = 0; index < this._welcomeLines.length; index += 1) {
|
|
1151
|
-
this.writeAbsoluteTerminalLine(index + 1, fitTerminalLine(this._welcomeLines[index] ?? "", this.terminalWidth()));
|
|
1152
|
-
}
|
|
1441
|
+
this.clearVisibleScreenRows();
|
|
1153
1442
|
this._renderedFooterUi = null;
|
|
1154
1443
|
this._renderedFooterStartRow = null;
|
|
1155
|
-
this._streamingReservedRows = 0;
|
|
1156
|
-
this._renderedStreamingStartRow = null;
|
|
1157
|
-
this._streamingDisplayRows =
|
|
1158
|
-
this._assistantBuffer.length > 0
|
|
1159
|
-
? countRenderedTerminalRows(wrapTranscriptContent(formatTranscriptAssistantLine(this._assistantBuffer), this.terminalWidth()), this.terminalWidth())
|
|
1160
|
-
: 0;
|
|
1161
1444
|
this.renderTranscriptViewport();
|
|
1162
1445
|
this.renderFooterRegion();
|
|
1163
|
-
if (this._assistantBuffer.length > 0) {
|
|
1164
|
-
this.renderStreamingAssistantBlock();
|
|
1165
|
-
}
|
|
1166
1446
|
}
|
|
1167
1447
|
renderTranscriptViewport() {
|
|
1168
1448
|
const footerUi = this.buildFooterUi();
|
|
1169
|
-
const transcriptTopRow =
|
|
1170
|
-
const transcriptBottomRow = computeFooterStartRow(
|
|
1449
|
+
const transcriptTopRow = 1;
|
|
1450
|
+
const transcriptBottomRow = computeFooterStartRow(0, this.terminalHeight(), footerUi.lines.length, this.currentTranscriptDisplayRows()) - 1;
|
|
1171
1451
|
const availableRows = Math.max(0, transcriptBottomRow - transcriptTopRow + 1);
|
|
1172
|
-
const visibleLines = computeVisibleTranscriptLines(this.
|
|
1452
|
+
const visibleLines = computeVisibleTranscriptLines(this.currentRenderedTranscriptBlocks(), this.terminalWidth(), availableRows, this._transcriptScrollOffset);
|
|
1453
|
+
this._renderedTranscriptViewportLines = visibleLines.map((line) => stripAnsiWelcome(line));
|
|
1173
1454
|
for (let row = transcriptTopRow; row <= transcriptBottomRow; row += 1) {
|
|
1174
1455
|
const index = row - transcriptTopRow;
|
|
1175
|
-
|
|
1456
|
+
const visibleLine = visibleLines[index] ?? "";
|
|
1457
|
+
const plainLine = this._renderedTranscriptViewportLines[index] ?? "";
|
|
1458
|
+
const selectionColumns = this.selectionColumnsForRow(row);
|
|
1459
|
+
const renderedLine = selectionColumns === null
|
|
1460
|
+
? fitTerminalLine(visibleLine, this.terminalWidth())
|
|
1461
|
+
: renderSelectedPlainTranscriptLine(plainLine, selectionColumns.startColumn, selectionColumns.endColumn, this.terminalWidth());
|
|
1462
|
+
this.writeAbsoluteTerminalLine(row, renderedLine);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
selectionColumnsForRow(row) {
|
|
1466
|
+
if (this._mouseSelection === null) {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
const start = compareTerminalSelectionPoints(this._mouseSelection.anchorRow, this._mouseSelection.anchorColumn, this._mouseSelection.focusRow, this._mouseSelection.focusColumn) <= 0
|
|
1470
|
+
? {
|
|
1471
|
+
row: this._mouseSelection.anchorRow,
|
|
1472
|
+
column: this._mouseSelection.anchorColumn,
|
|
1473
|
+
}
|
|
1474
|
+
: {
|
|
1475
|
+
row: this._mouseSelection.focusRow,
|
|
1476
|
+
column: this._mouseSelection.focusColumn,
|
|
1477
|
+
};
|
|
1478
|
+
const end = start.row === this._mouseSelection.anchorRow && start.column === this._mouseSelection.anchorColumn
|
|
1479
|
+
? {
|
|
1480
|
+
row: this._mouseSelection.focusRow,
|
|
1481
|
+
column: this._mouseSelection.focusColumn,
|
|
1482
|
+
}
|
|
1483
|
+
: {
|
|
1484
|
+
row: this._mouseSelection.anchorRow,
|
|
1485
|
+
column: this._mouseSelection.anchorColumn,
|
|
1486
|
+
};
|
|
1487
|
+
if (row < start.row || row > end.row) {
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
if (start.row === end.row) {
|
|
1491
|
+
return {
|
|
1492
|
+
startColumn: Math.min(start.column, end.column),
|
|
1493
|
+
endColumn: Math.max(start.column, end.column),
|
|
1494
|
+
};
|
|
1176
1495
|
}
|
|
1496
|
+
if (row === start.row) {
|
|
1497
|
+
return {
|
|
1498
|
+
startColumn: start.column,
|
|
1499
|
+
endColumn: this.terminalWidth() + 1,
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
if (row === end.row) {
|
|
1503
|
+
return {
|
|
1504
|
+
startColumn: 1,
|
|
1505
|
+
endColumn: end.column,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
startColumn: 1,
|
|
1510
|
+
endColumn: this.terminalWidth() + 1,
|
|
1511
|
+
};
|
|
1177
1512
|
}
|
|
1178
1513
|
currentTranscriptDisplayRows() {
|
|
1179
|
-
return computeTranscriptDisplayRows(this.
|
|
1514
|
+
return computeTranscriptDisplayRows(this.currentRenderedTranscriptBlocks(), this.terminalWidth());
|
|
1515
|
+
}
|
|
1516
|
+
currentRenderedTranscriptBlocks() {
|
|
1517
|
+
const welcomeBlocks = this._welcomeLines;
|
|
1518
|
+
if (this._assistantBuffer.length === 0) {
|
|
1519
|
+
return [...welcomeBlocks, ...this._transcriptBlocks];
|
|
1520
|
+
}
|
|
1521
|
+
const assistantBlock = materializeAssistantTranscriptBlock(this._assistantBuffer);
|
|
1522
|
+
if (assistantBlock === null) {
|
|
1523
|
+
return [...welcomeBlocks, ...this._transcriptBlocks];
|
|
1524
|
+
}
|
|
1525
|
+
return appendAssistantTranscriptBlock([...welcomeBlocks, ...this._transcriptBlocks], assistantBlock);
|
|
1180
1526
|
}
|
|
1181
1527
|
}
|
|
1182
1528
|
// ---------------------------------------------------------------------------
|
|
@@ -1289,9 +1635,9 @@ function applyWelcomeBorderColor(text) {
|
|
|
1289
1635
|
}
|
|
1290
1636
|
function buildWelcomeHintLine(width) {
|
|
1291
1637
|
if (width < 64) {
|
|
1292
|
-
return "Start: Enter Help: /help
|
|
1638
|
+
return "Start: Enter Help: /help Exit: /exit";
|
|
1293
1639
|
}
|
|
1294
|
-
return "Start: type a prompt and press Enter Help: /help
|
|
1640
|
+
return "Start: type a prompt and press Enter Help: /help Exit: /exit";
|
|
1295
1641
|
}
|
|
1296
1642
|
exports.WELCOME_BIBLE_GREETINGS = [
|
|
1297
1643
|
"Let there be light.",
|
|
@@ -1330,6 +1676,7 @@ function buildWelcomeLines(input) {
|
|
|
1330
1676
|
center(`${CYAN}${input.model}${RESET} ${DIM}via${RESET} ${input.provider}`),
|
|
1331
1677
|
formatWelcomeBottomBorder(width),
|
|
1332
1678
|
buildWelcomeHintLine(width),
|
|
1679
|
+
"",
|
|
1333
1680
|
];
|
|
1334
1681
|
}
|
|
1335
1682
|
function formatWelcomeTopBorder(width, version) {
|
|
@@ -1394,7 +1741,31 @@ function formatInteractiveFooter(state) {
|
|
|
1394
1741
|
const separator = formatInteractiveInputSeparator(computeInteractiveFooterSeparatorWidth(state.terminalWidth));
|
|
1395
1742
|
const lines = [];
|
|
1396
1743
|
if (state.turnNotice !== null) {
|
|
1397
|
-
lines.push(formatTurnNotice(state.turnNotice
|
|
1744
|
+
lines.push(formatTurnNotice(state.turnNotice, {
|
|
1745
|
+
animationFrame: state.turnNoticeAnimationFrame ?? 0,
|
|
1746
|
+
elapsedMs: state.elapsedMs ?? null,
|
|
1747
|
+
usage: state.currentTurnUsage ?? null,
|
|
1748
|
+
showPendingOutputIndicator: state.showPendingOutputIndicator ?? false,
|
|
1749
|
+
queuedCount: state.queuedInputs?.length ?? 0,
|
|
1750
|
+
toolLabel: state.activeToolLabel ?? null,
|
|
1751
|
+
}));
|
|
1752
|
+
}
|
|
1753
|
+
else {
|
|
1754
|
+
if (hasUsageSnapshot(state.lastTurnUsage ?? null)) {
|
|
1755
|
+
lines.push(formatUsageSummaryLine("Last turn", state.lastTurnUsage));
|
|
1756
|
+
}
|
|
1757
|
+
if (hasUsageSnapshot(state.sessionUsage ?? null)) {
|
|
1758
|
+
lines.push(formatUsageSummaryLine("Session", state.sessionUsage));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
if ((state.detailPanelSummary?.length ?? 0) > 0) {
|
|
1762
|
+
lines.push(`${theme_js_1.INTERACTIVE_THEME.muted}↳ ${state.detailPanelSummary}${theme_js_1.INTERACTIVE_THEME.reset}`);
|
|
1763
|
+
if (state.detailPanelExpanded) {
|
|
1764
|
+
lines.push(...(state.detailPanelLines ?? []));
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
if ((state.queuedInputs?.length ?? 0) > 0) {
|
|
1768
|
+
lines.push(...formatQueuedPromptPreviewLines(state.queuedInputs ?? [], state.terminalWidth));
|
|
1398
1769
|
}
|
|
1399
1770
|
lines.push(separator);
|
|
1400
1771
|
if (state.permission !== null) {
|
|
@@ -1523,6 +1894,78 @@ function summarizeToolParameters(toolName, parameters) {
|
|
|
1523
1894
|
}
|
|
1524
1895
|
return "";
|
|
1525
1896
|
}
|
|
1897
|
+
function summarizeActiveToolNotice(toolCalls) {
|
|
1898
|
+
if (toolCalls.size === 0) {
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
const [first] = toolCalls.values();
|
|
1902
|
+
if (!first) {
|
|
1903
|
+
return "Running tools";
|
|
1904
|
+
}
|
|
1905
|
+
const title = formatInteractiveToolTitle(first.toolName, first.parameters).replace(/^⏺\s+/, "");
|
|
1906
|
+
if (toolCalls.size === 1) {
|
|
1907
|
+
return `Running ${title}`;
|
|
1908
|
+
}
|
|
1909
|
+
return `Running ${toolCalls.size} tools`;
|
|
1910
|
+
}
|
|
1911
|
+
function detailPanelScrollDeltaForKey(key, viewportSize) {
|
|
1912
|
+
switch (key) {
|
|
1913
|
+
case "up":
|
|
1914
|
+
case "wheelup":
|
|
1915
|
+
return -1;
|
|
1916
|
+
case "down":
|
|
1917
|
+
case "wheeldown":
|
|
1918
|
+
return 1;
|
|
1919
|
+
case "pageup":
|
|
1920
|
+
return -Math.max(1, viewportSize - 1);
|
|
1921
|
+
case "pagedown":
|
|
1922
|
+
return Math.max(1, viewportSize - 1);
|
|
1923
|
+
default:
|
|
1924
|
+
return 0;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function transcriptScrollDeltaForKey(key, viewportSize) {
|
|
1928
|
+
switch (key) {
|
|
1929
|
+
case "pageup":
|
|
1930
|
+
return Math.max(1, viewportSize - 1);
|
|
1931
|
+
case "pagedown":
|
|
1932
|
+
return -Math.max(1, viewportSize - 1);
|
|
1933
|
+
case "wheelup":
|
|
1934
|
+
return 3;
|
|
1935
|
+
case "wheeldown":
|
|
1936
|
+
return -3;
|
|
1937
|
+
default:
|
|
1938
|
+
return 0;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
function compareTerminalSelectionPoints(leftRow, leftColumn, rightRow, rightColumn) {
|
|
1942
|
+
if (leftRow !== rightRow) {
|
|
1943
|
+
return leftRow - rightRow;
|
|
1944
|
+
}
|
|
1945
|
+
return leftColumn - rightColumn;
|
|
1946
|
+
}
|
|
1947
|
+
function createDebouncedCallback(callback, delayMs) {
|
|
1948
|
+
let timer = null;
|
|
1949
|
+
return {
|
|
1950
|
+
schedule() {
|
|
1951
|
+
if (timer !== null) {
|
|
1952
|
+
clearTimeout(timer);
|
|
1953
|
+
}
|
|
1954
|
+
timer = setTimeout(() => {
|
|
1955
|
+
timer = null;
|
|
1956
|
+
callback();
|
|
1957
|
+
}, delayMs);
|
|
1958
|
+
timer.unref?.();
|
|
1959
|
+
},
|
|
1960
|
+
cancel() {
|
|
1961
|
+
if (timer === null) {
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
clearTimeout(timer);
|
|
1965
|
+
timer = null;
|
|
1966
|
+
},
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1526
1969
|
function normalizeToolResultLines(toolName, result, startedParameters) {
|
|
1527
1970
|
if ((!result || result.trim().length === 0) && startedParameters) {
|
|
1528
1971
|
if (toolName === "write" || toolName === "edit") {
|
|
@@ -1718,11 +2161,106 @@ function truncatePlainTerminalText(text, width) {
|
|
|
1718
2161
|
}
|
|
1719
2162
|
return output;
|
|
1720
2163
|
}
|
|
1721
|
-
function
|
|
2164
|
+
function formatQueuedPromptPreviewLines(queuedInputs, terminalWidth) {
|
|
2165
|
+
const DIM = "\x1b[2m";
|
|
2166
|
+
const YELLOW = "\x1b[33m";
|
|
2167
|
+
const RESET = "\x1b[0m";
|
|
2168
|
+
const maxVisible = 2;
|
|
2169
|
+
const previewWidth = Math.max(12, terminalWidth - 18);
|
|
2170
|
+
const lines = queuedInputs.slice(0, maxVisible).map((input, index) => {
|
|
2171
|
+
const label = queuedInputs.length > 1 ? `↳ Queued ${index + 1}: ` : "↳ Queued: ";
|
|
2172
|
+
return `${DIM}${YELLOW}${label}${RESET}${truncatePlainTerminalText(input, previewWidth)}`;
|
|
2173
|
+
});
|
|
2174
|
+
if (queuedInputs.length > maxVisible) {
|
|
2175
|
+
lines.push(`${DIM}${YELLOW}↳ +${queuedInputs.length - maxVisible} more queued${RESET}`);
|
|
2176
|
+
}
|
|
2177
|
+
return lines;
|
|
2178
|
+
}
|
|
2179
|
+
function formatTurnNotice(kind, options = {}) {
|
|
2180
|
+
const DIM = "\x1b[2m";
|
|
2181
|
+
const CYAN = "\x1b[36m";
|
|
2182
|
+
const RESET = "\x1b[0m";
|
|
2183
|
+
const suffix = ".".repeat(((options.animationFrame ?? 0) % 3) + 1);
|
|
2184
|
+
const label = kind === "thinking"
|
|
2185
|
+
? `Thinking${suffix}`
|
|
2186
|
+
: kind === "responding"
|
|
2187
|
+
? `Responding${suffix}`
|
|
2188
|
+
: `${options.toolLabel ?? "Running tools"}${suffix}`;
|
|
2189
|
+
const meta = [];
|
|
2190
|
+
if ((options.elapsedMs ?? 0) >= TURN_NOTICE_ELAPSED_THRESHOLD_MS) {
|
|
2191
|
+
meta.push(formatElapsedLabel(options.elapsedMs ?? 0));
|
|
2192
|
+
}
|
|
2193
|
+
const usageLabel = formatUsageCompact(options.usage ?? null, options.showPendingOutputIndicator ?? false);
|
|
2194
|
+
if (usageLabel.length > 0) {
|
|
2195
|
+
meta.push(usageLabel);
|
|
2196
|
+
}
|
|
2197
|
+
if ((options.queuedCount ?? 0) > 0) {
|
|
2198
|
+
meta.push(`${options.queuedCount} queued`);
|
|
2199
|
+
}
|
|
2200
|
+
return `${DIM}${CYAN}· ${label}${meta.length > 0 ? ` (${meta.join(" · ")})` : ""}${RESET}`;
|
|
2201
|
+
}
|
|
2202
|
+
function formatUsageSummaryLine(label, usage) {
|
|
1722
2203
|
const DIM = "\x1b[2m";
|
|
1723
2204
|
const CYAN = "\x1b[36m";
|
|
1724
2205
|
const RESET = "\x1b[0m";
|
|
1725
|
-
return
|
|
2206
|
+
return `${DIM}${CYAN}· ${label}: ${formatUsageCompact(usage)}${RESET}`;
|
|
2207
|
+
}
|
|
2208
|
+
function formatUsageCompact(usage, showPendingOutputIndicator = false) {
|
|
2209
|
+
if (usage && usage.output > 0) {
|
|
2210
|
+
return `↓ ${formatTokenCount(usage.output)} tokens`;
|
|
2211
|
+
}
|
|
2212
|
+
if (showPendingOutputIndicator) {
|
|
2213
|
+
return "↓";
|
|
2214
|
+
}
|
|
2215
|
+
return "";
|
|
2216
|
+
}
|
|
2217
|
+
function formatElapsedLabel(elapsedMs) {
|
|
2218
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
2219
|
+
if (totalSeconds < 60) {
|
|
2220
|
+
return `${totalSeconds}s`;
|
|
2221
|
+
}
|
|
2222
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
2223
|
+
const seconds = totalSeconds % 60;
|
|
2224
|
+
if (minutes < 60) {
|
|
2225
|
+
return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;
|
|
2226
|
+
}
|
|
2227
|
+
const hours = Math.floor(minutes / 60);
|
|
2228
|
+
const remainingMinutes = minutes % 60;
|
|
2229
|
+
return remainingMinutes === 0 ? `${hours}h` : `${hours}h ${remainingMinutes}m`;
|
|
2230
|
+
}
|
|
2231
|
+
function formatTokenCount(count) {
|
|
2232
|
+
if (count < 1_000)
|
|
2233
|
+
return `${count}`;
|
|
2234
|
+
if (count < 10_000)
|
|
2235
|
+
return `${(count / 1_000).toFixed(1).replace(/\.0$/, "")}k`;
|
|
2236
|
+
return `${Math.round(count / 1_000)}k`;
|
|
2237
|
+
}
|
|
2238
|
+
function emptyUsageSnapshot() {
|
|
2239
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 };
|
|
2240
|
+
}
|
|
2241
|
+
function normalizeUsageSnapshot(usage) {
|
|
2242
|
+
return {
|
|
2243
|
+
input: Math.max(0, usage.input),
|
|
2244
|
+
output: Math.max(0, usage.output),
|
|
2245
|
+
cacheRead: Math.max(0, usage.cacheRead),
|
|
2246
|
+
cacheWrite: Math.max(0, usage.cacheWrite),
|
|
2247
|
+
totalTokens: Math.max(0, usage.totalTokens),
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
function addUsageSnapshots(left, right) {
|
|
2251
|
+
return {
|
|
2252
|
+
input: left.input + right.input,
|
|
2253
|
+
output: left.output + right.output,
|
|
2254
|
+
cacheRead: left.cacheRead + right.cacheRead,
|
|
2255
|
+
cacheWrite: left.cacheWrite + right.cacheWrite,
|
|
2256
|
+
totalTokens: left.totalTokens + right.totalTokens,
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
function hasUsageSnapshot(usage) {
|
|
2260
|
+
if (!usage) {
|
|
2261
|
+
return false;
|
|
2262
|
+
}
|
|
2263
|
+
return usage.input > 0 || usage.output > 0 || usage.cacheRead > 0 || usage.cacheWrite > 0 || usage.totalTokens > 0;
|
|
1726
2264
|
}
|
|
1727
2265
|
function mergeStreamingText(existing, incoming) {
|
|
1728
2266
|
if (incoming.length === 0)
|
|
@@ -1781,7 +2319,7 @@ function wrapTranscriptContent(content, width) {
|
|
|
1781
2319
|
lines.push(current);
|
|
1782
2320
|
return lines;
|
|
1783
2321
|
}
|
|
1784
|
-
function computeVisibleTranscriptLines(blocks, width, maxRows) {
|
|
2322
|
+
function computeVisibleTranscriptLines(blocks, width, maxRows, offsetFromBottom = 0) {
|
|
1785
2323
|
if (maxRows <= 0 || blocks.length === 0) {
|
|
1786
2324
|
return [];
|
|
1787
2325
|
}
|
|
@@ -1789,7 +2327,77 @@ function computeVisibleTranscriptLines(blocks, width, maxRows) {
|
|
|
1789
2327
|
if (flattened.length <= maxRows) {
|
|
1790
2328
|
return flattened;
|
|
1791
2329
|
}
|
|
1792
|
-
|
|
2330
|
+
const maxOffset = Math.max(0, flattened.length - maxRows);
|
|
2331
|
+
const clampedOffset = Math.max(0, Math.min(offsetFromBottom, maxOffset));
|
|
2332
|
+
const end = flattened.length - clampedOffset;
|
|
2333
|
+
const start = Math.max(0, end - maxRows);
|
|
2334
|
+
return flattened.slice(start, end);
|
|
2335
|
+
}
|
|
2336
|
+
function extractPlainTextSelection(lines, selection) {
|
|
2337
|
+
if (lines.length === 0) {
|
|
2338
|
+
return "";
|
|
2339
|
+
}
|
|
2340
|
+
const startFirst = selection.startRow < selection.endRow ||
|
|
2341
|
+
(selection.startRow === selection.endRow && selection.startColumn <= selection.endColumn);
|
|
2342
|
+
const start = startFirst
|
|
2343
|
+
? { row: selection.startRow, column: selection.startColumn }
|
|
2344
|
+
: { row: selection.endRow, column: selection.endColumn };
|
|
2345
|
+
const end = startFirst
|
|
2346
|
+
? { row: selection.endRow, column: selection.endColumn }
|
|
2347
|
+
: { row: selection.startRow, column: selection.startColumn };
|
|
2348
|
+
const selectedLines = [];
|
|
2349
|
+
for (let row = start.row; row <= end.row; row += 1) {
|
|
2350
|
+
const line = lines[row] ?? "";
|
|
2351
|
+
const fromColumn = row === start.row ? start.column : 1;
|
|
2352
|
+
const toColumn = row === end.row ? end.column : Number.MAX_SAFE_INTEGER;
|
|
2353
|
+
selectedLines.push(slicePlainTextByDisplayColumns(line, fromColumn - 1, toColumn - 1));
|
|
2354
|
+
}
|
|
2355
|
+
return selectedLines.join("\n").replace(/\s+$/g, "").replace(/\n[ \t]+$/gm, "");
|
|
2356
|
+
}
|
|
2357
|
+
function renderSelectedPlainTranscriptLine(line, startColumn, endColumn, width) {
|
|
2358
|
+
const safeStart = Math.max(1, Math.min(startColumn, endColumn));
|
|
2359
|
+
const safeEnd = Math.max(safeStart, Math.max(startColumn, endColumn));
|
|
2360
|
+
const before = slicePlainTextByDisplayColumns(line, 0, safeStart - 1);
|
|
2361
|
+
const selected = slicePlainTextByDisplayColumns(line, safeStart - 1, safeEnd - 1);
|
|
2362
|
+
const after = slicePlainTextByDisplayColumns(line, safeEnd - 1, Number.MAX_SAFE_INTEGER);
|
|
2363
|
+
const inverse = "\x1b[7m";
|
|
2364
|
+
const reset = "\x1b[0m";
|
|
2365
|
+
return fitTerminalLine(`${before}${inverse}${selected.length > 0 ? selected : " "}${reset}${after}`, width);
|
|
2366
|
+
}
|
|
2367
|
+
function slicePlainTextByDisplayColumns(text, startColumn, endColumnExclusive) {
|
|
2368
|
+
const start = Math.max(0, startColumn);
|
|
2369
|
+
const end = Math.max(start, endColumnExclusive);
|
|
2370
|
+
let output = "";
|
|
2371
|
+
let used = 0;
|
|
2372
|
+
for (const ch of text) {
|
|
2373
|
+
const charWidth = (0, terminal_display_width_js_1.measureTerminalDisplayWidth)(ch);
|
|
2374
|
+
const nextUsed = used + charWidth;
|
|
2375
|
+
if (nextUsed > start && used < end) {
|
|
2376
|
+
output += ch;
|
|
2377
|
+
}
|
|
2378
|
+
if (used >= end) {
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
used = nextUsed;
|
|
2382
|
+
}
|
|
2383
|
+
return output;
|
|
2384
|
+
}
|
|
2385
|
+
function copyTextToClipboard(text) {
|
|
2386
|
+
if (text.length === 0) {
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
if (process.platform === "darwin") {
|
|
2390
|
+
(0, node_child_process_1.spawnSync)("pbcopy", [], { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
if (process.platform === "win32") {
|
|
2394
|
+
(0, node_child_process_1.spawnSync)("clip", [], { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
(0, node_child_process_1.spawnSync)("sh", ["-c", "command -v wl-copy >/dev/null 2>&1 && wl-copy || xclip -selection clipboard"], {
|
|
2398
|
+
input: text,
|
|
2399
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
2400
|
+
});
|
|
1793
2401
|
}
|
|
1794
2402
|
function computeTranscriptDisplayRows(blocks, width) {
|
|
1795
2403
|
return flattenTranscriptLines(blocks, width).length;
|
|
@@ -1801,20 +2409,32 @@ function materializeAssistantTranscriptBlock(buffer) {
|
|
|
1801
2409
|
return formatTranscriptAssistantLine(buffer);
|
|
1802
2410
|
}
|
|
1803
2411
|
function appendAssistantTranscriptBlock(blocks, assistantBlock) {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
2412
|
+
return appendTranscriptBlockWithSpacer(blocks, assistantBlock);
|
|
2413
|
+
}
|
|
2414
|
+
function appendTranscriptBlockWithSpacer(blocks, block) {
|
|
2415
|
+
if (block.length === 0) {
|
|
2416
|
+
return [...blocks];
|
|
2417
|
+
}
|
|
2418
|
+
const lastNonEmptyIndex = findLastNonEmptyBlockIndex(blocks);
|
|
2419
|
+
if (lastNonEmptyIndex === -1) {
|
|
2420
|
+
return [block];
|
|
2421
|
+
}
|
|
2422
|
+
const normalized = blocks.slice(0, lastNonEmptyIndex + 1);
|
|
2423
|
+
return [...normalized, "", block];
|
|
2424
|
+
}
|
|
2425
|
+
function findLastNonEmptyBlockIndex(blocks) {
|
|
2426
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
2427
|
+
if ((blocks[index] ?? "").length > 0) {
|
|
2428
|
+
return index;
|
|
2429
|
+
}
|
|
1807
2430
|
}
|
|
1808
|
-
return
|
|
2431
|
+
return -1;
|
|
1809
2432
|
}
|
|
1810
2433
|
function computeFooterStartRow(welcomeLineCount, terminalHeight, footerHeight, transcriptRows) {
|
|
1811
2434
|
const naturalStartRow = welcomeLineCount + 1 + Math.max(0, transcriptRows);
|
|
1812
2435
|
const bottomAnchoredStartRow = Math.max(1, terminalHeight - footerHeight + 1);
|
|
1813
2436
|
return Math.min(naturalStartRow, bottomAnchoredStartRow);
|
|
1814
2437
|
}
|
|
1815
|
-
function isFooterBottomAnchored(startRow, terminalHeight, footerHeight) {
|
|
1816
|
-
return startRow === Math.max(1, terminalHeight - footerHeight + 1);
|
|
1817
|
-
}
|
|
1818
2438
|
function flattenTranscriptLines(blocks, width) {
|
|
1819
2439
|
const flattened = [];
|
|
1820
2440
|
for (const block of blocks) {
|