@vybestack/llxprt-code 0.10.0 → 0.11.0-nightly.260728.ffaa8d5d7
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/package.json +13 -13
- package/src/commands/mcp/add.ts +8 -4
- package/src/config/cliArgParser.ts +2 -0
- package/src/config/config.ts +2 -0
- package/src/config/configBuilder.ts +3 -0
- package/src/config/configError.ts +23 -0
- package/src/config/extensions/extensionEnablement.ts +62 -1
- package/src/config/interactiveContext.ts +2 -1
- package/src/config/intermediateConfig.ts +4 -0
- package/src/config/keyBindings.ts +29 -3
- package/src/config/policy.ts +7 -1
- package/src/config/postConfigRuntime.ts +20 -14
- package/src/config/settings-schema/schema-ui.ts +9 -0
- package/src/config/settingsLoader.ts +21 -17
- package/src/config/settingsSchema.ts +2 -2
- package/src/config/trustedFolders.ts +275 -58
- package/src/config/yargsOptions.ts +7 -0
- package/src/generated/git-commit.json +1 -1
- package/src/nonInteractiveCli.ts +1 -0
- package/src/nonInteractiveCliSupport.ts +171 -51
- package/src/runtime/interactiveToolScheduler.ts +6 -6
- package/src/services/BuiltinCommandLoader.ts +33 -25
- package/src/services/FileCommandLoader.ts +34 -7
- package/src/test-utils/async.ts +20 -0
- package/src/test-utils/render.tsx +10 -1
- package/src/ui/App.tsx +17 -10
- package/src/ui/AppContainerRuntime.tsx +7 -9
- package/src/ui/cliUiRuntime.ts +28 -0
- package/src/ui/commands/authCommand.ts +240 -3
- package/src/ui/commands/compressCommand.ts +9 -1
- package/src/ui/commands/directoryCommand.tsx +1 -1
- package/src/ui/commands/logoutCommand.ts +2 -2
- package/src/ui/commands/permissionsCommand.ts +77 -20
- package/src/ui/commands/policiesCommand.ts +6 -1
- package/src/ui/commands/quotaCommand.ts +501 -0
- package/src/ui/commands/statsCommand.ts +2 -2
- package/src/ui/commands/statsQuota.ts +1 -1
- package/src/ui/commands/subagentCommand.ts +59 -27
- package/src/ui/components/AuthDialog.tsx +3 -3
- package/src/ui/components/Composer.tsx +1 -0
- package/src/ui/components/DialogManager.tsx +9 -2
- package/src/ui/components/FolderTrustDialog.tsx +71 -60
- package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
- package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
- package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
- package/src/ui/components/inputPromptHooks.ts +2 -0
- package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
- package/src/ui/components/inputPromptTypes.ts +6 -0
- package/src/ui/components/messages/AiMessage.tsx +24 -12
- package/src/ui/components/messages/ToolMessage.tsx +26 -40
- package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
- package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
- package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
- package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
- package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
- package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
- package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
- package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
- package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
- package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
- package/src/ui/contexts/KeypressContext.tsx +13 -2
- package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
- package/src/ui/contexts/TodoContext.tsx +0 -13
- package/src/ui/contexts/TodoProvider.tsx +1 -34
- package/src/ui/contexts/UIActionsContext.tsx +2 -1
- package/src/ui/contexts/UIStateContext.tsx +3 -2
- package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
- package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
- package/src/ui/hooks/agentStream/types.ts +1 -0
- package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
- package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
- package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
- package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
- package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
- package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
- package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
- package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
- package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
- package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
- package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
- package/src/ui/hooks/useFolderTrust.ts +118 -81
- package/src/ui/hooks/useGitBranchName.ts +79 -33
- package/src/ui/hooks/useIdeTrustListener.ts +5 -19
- package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
- package/src/ui/hooks/useReactToolScheduler.ts +13 -17
- package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
- package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
- package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
- package/src/ui/layouts/InlineContent.tsx +198 -0
- package/src/ui/trustDialogHelpers.ts +190 -0
- package/src/utils/cleanup-state.ts +115 -0
- package/src/utils/cleanup.ts +32 -34
- package/src/utils/sandbox-containers.ts +183 -53
- package/src/utils/sandbox-exec.ts +9 -3
- package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
- package/src/zed-integration/acp-types.ts +35 -0
- package/src/zed-integration/fileSystemService.ts +1 -1
- package/src/zed-integration/runZedIntegration.ts +142 -0
- package/src/zed-integration/zed-agent-event-handler.ts +179 -0
- package/src/zed-integration/zed-agent-setup.ts +43 -0
- package/src/zed-integration/zed-command-registry.ts +167 -0
- package/src/zed-integration/zed-config-options.ts +280 -0
- package/src/zed-integration/zed-content-utils.ts +2 -2
- package/src/zed-integration/zed-helpers.ts +132 -0
- package/src/zed-integration/zed-initialize.ts +64 -0
- package/src/zed-integration/zed-plan-update.ts +20 -0
- package/src/zed-integration/zed-prompt-command.ts +77 -0
- package/src/zed-integration/zed-session-config.ts +119 -0
- package/src/zed-integration/zed-session-errors.ts +337 -0
- package/src/zed-integration/zed-session-events.ts +216 -0
- package/src/zed-integration/zed-session-info.ts +255 -0
- package/src/zed-integration/zed-session-lifecycle.ts +186 -0
- package/src/zed-integration/zed-session-listing.ts +153 -0
- package/src/zed-integration/zed-session-loader.ts +193 -0
- package/src/zed-integration/zed-session-pagination.ts +240 -0
- package/src/zed-integration/zed-session-replay.ts +564 -0
- package/src/zed-integration/zed-stream-batcher.ts +236 -0
- package/src/zed-integration/zed-terminal-manager.ts +416 -0
- package/src/zed-integration/zed-terminal-setup.ts +72 -0
- package/src/zed-integration/zed-test-helpers.ts +349 -4
- package/src/zed-integration/zed-tool-handler.ts +47 -5
- package/src/zed-integration/zedIntegration.ts +538 -587
- package/src/services/todo-continuation/todoContinuationService.ts +0 -610
- package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
- package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
- package/src/ui/hooks/useTodoContinuation.ts +0 -299
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
AgentRequestInput,
|
|
9
|
+
ContentBlock,
|
|
10
|
+
IContent,
|
|
11
|
+
} from '@vybestack/llxprt-code-core';
|
|
12
|
+
import type React from 'react';
|
|
13
|
+
import { useMemo } from 'react';
|
|
14
|
+
import { Box, Text } from 'ink';
|
|
15
|
+
import { SemanticColors } from '../colors.js';
|
|
16
|
+
import { Command, getDefaultKeyBindingHint } from '../../config/keyBindings.js';
|
|
17
|
+
import { truncateEnd } from '../utils/responsive.js';
|
|
18
|
+
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
|
19
|
+
import type { QueuedSubmission } from '../hooks/agentStream/types.js';
|
|
20
|
+
|
|
21
|
+
interface QueuedMessagesPanelProps {
|
|
22
|
+
width: number;
|
|
23
|
+
collapsed?: boolean;
|
|
24
|
+
messages: readonly QueuedSubmission[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const PANEL_HEIGHT_RATIO = 0.2;
|
|
28
|
+
const COLLAPSED_PREVIEW_WIDTH_RATIO = 0.55;
|
|
29
|
+
const FALLBACK_KEY_PREVIEW_LENGTH = 20;
|
|
30
|
+
const MIN_EXPANDED_PANEL_HEIGHT = 3;
|
|
31
|
+
const QUEUED_MESSAGES_KEY_HINT = getDefaultKeyBindingHint(
|
|
32
|
+
Command.TOGGLE_QUEUED_MESSAGES,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export type QueuedMessagesPanelView =
|
|
36
|
+
| { readonly kind: 'empty' }
|
|
37
|
+
| {
|
|
38
|
+
readonly kind: 'compact';
|
|
39
|
+
readonly width: number;
|
|
40
|
+
readonly summary: string;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
readonly kind: 'collapsed';
|
|
44
|
+
readonly width: number;
|
|
45
|
+
readonly panelHeight: number;
|
|
46
|
+
readonly summary: string;
|
|
47
|
+
readonly nextPreview?: string;
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
readonly kind: 'expanded';
|
|
51
|
+
readonly width: number;
|
|
52
|
+
readonly panelHeight: number;
|
|
53
|
+
readonly heading: string;
|
|
54
|
+
readonly messages: ReadonlyArray<{
|
|
55
|
+
readonly key: string;
|
|
56
|
+
readonly number: number;
|
|
57
|
+
readonly preview: string;
|
|
58
|
+
}>;
|
|
59
|
+
readonly moreCount: number;
|
|
60
|
+
readonly showMoreIndicator: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extract a human-readable preview string from any AgentRequestInput variant.
|
|
65
|
+
*/
|
|
66
|
+
export function extractPreviewText(query: AgentRequestInput): string {
|
|
67
|
+
if (typeof query === 'string') {
|
|
68
|
+
return query;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(query)) {
|
|
72
|
+
if (query.length === 0) {
|
|
73
|
+
return '(empty message)';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isIContentArray(query)) {
|
|
77
|
+
return extractTextFromTurns(query);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const text = extractTextFromBlocks(query as ContentBlock[]);
|
|
81
|
+
return text.length > 0 ? text : '(non-text message)';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isIContent(query)) {
|
|
85
|
+
const text = extractTextFromBlocks(query.blocks);
|
|
86
|
+
return text.length > 0 ? text : '(non-text turn)';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return '(non-text message)';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function extractTextFromTurns(turns: IContent[]): string {
|
|
93
|
+
const text = turns
|
|
94
|
+
.map((turn) => extractTextFromBlocks(turn.blocks))
|
|
95
|
+
.filter((part) => part.length > 0)
|
|
96
|
+
.join(' ');
|
|
97
|
+
return text.length > 0 ? text : '(non-text turn)';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractTextFromBlocks(blocks: ContentBlock[]): string {
|
|
101
|
+
const parts: string[] = [];
|
|
102
|
+
for (const block of blocks) {
|
|
103
|
+
if (isTextBlock(block) && block.text.length > 0) {
|
|
104
|
+
parts.push(block.text);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return parts.join(' ');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isTextBlock(block: unknown): block is { type: 'text'; text: string } {
|
|
111
|
+
if (typeof block !== 'object' || block === null) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
const candidate = block as Record<string, unknown>;
|
|
115
|
+
return candidate.type === 'text' && typeof candidate.text === 'string';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isIContent(value: unknown): value is IContent {
|
|
119
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const candidate = value as Record<string, unknown>;
|
|
123
|
+
return (
|
|
124
|
+
(candidate.speaker === 'human' ||
|
|
125
|
+
candidate.speaker === 'ai' ||
|
|
126
|
+
candidate.speaker === 'tool') &&
|
|
127
|
+
Array.isArray(candidate.blocks)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isIContentArray(value: unknown): value is IContent[] {
|
|
132
|
+
return Array.isArray(value) && value.every((item) => isIContent(item));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function stableKey(
|
|
136
|
+
message: QueuedSubmission,
|
|
137
|
+
index: number,
|
|
138
|
+
preview: string,
|
|
139
|
+
): string {
|
|
140
|
+
if (message.queueId !== undefined) {
|
|
141
|
+
return `queued-${message.queueId}`;
|
|
142
|
+
}
|
|
143
|
+
if (message.promptId !== undefined && message.promptId !== '') {
|
|
144
|
+
return `queued-${message.promptId}-${index}`;
|
|
145
|
+
}
|
|
146
|
+
return `queued-${index}-${preview.slice(0, FALLBACK_KEY_PREVIEW_LENGTH)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function calculatePanelHeight(rows: number): number {
|
|
150
|
+
return Math.max(1, Math.floor(rows * PANEL_HEIGHT_RATIO));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function calculateAvailableRows(panelHeight: number): number {
|
|
154
|
+
const borderRows = panelHeight > 1 ? 1 : 0;
|
|
155
|
+
const headerRows = 1;
|
|
156
|
+
return Math.max(0, panelHeight - borderRows - headerRows);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function calculateMaxVisibleItems(
|
|
160
|
+
panelHeight: number,
|
|
161
|
+
messageCount: number,
|
|
162
|
+
): number {
|
|
163
|
+
const availableRows = calculateAvailableRows(panelHeight);
|
|
164
|
+
if (messageCount <= availableRows) {
|
|
165
|
+
return messageCount;
|
|
166
|
+
}
|
|
167
|
+
if (availableRows <= 1) {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
return availableRows - 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function queuedMessageSummary(messageCount: number): string {
|
|
174
|
+
return `${messageCount} queued ${messageCount === 1 ? 'message' : 'messages'}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function calculateExpandedPreviewWidth(
|
|
178
|
+
panelWidth: number,
|
|
179
|
+
messageNumber: number,
|
|
180
|
+
): number {
|
|
181
|
+
const shellPaddingWidth = panelWidth > 2 ? 2 : 0;
|
|
182
|
+
const numberPrefixWidth = `${messageNumber}. `.length;
|
|
183
|
+
return Math.max(1, panelWidth - shellPaddingWidth - numberPrefixWidth);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function prepareQueuedMessagesPanelView({
|
|
187
|
+
width,
|
|
188
|
+
collapsed = false,
|
|
189
|
+
messages,
|
|
190
|
+
columns,
|
|
191
|
+
rows,
|
|
192
|
+
}: QueuedMessagesPanelProps & {
|
|
193
|
+
columns: number;
|
|
194
|
+
rows: number;
|
|
195
|
+
}): QueuedMessagesPanelView {
|
|
196
|
+
if (messages.length === 0) {
|
|
197
|
+
return { kind: 'empty' };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const panelHeight = calculatePanelHeight(rows);
|
|
201
|
+
const boundedWidth = Math.max(1, Math.min(width, columns));
|
|
202
|
+
|
|
203
|
+
if (collapsed) {
|
|
204
|
+
return {
|
|
205
|
+
kind: 'collapsed',
|
|
206
|
+
width: boundedWidth,
|
|
207
|
+
panelHeight,
|
|
208
|
+
summary: queuedMessageSummary(messages.length),
|
|
209
|
+
nextPreview: truncateEnd(
|
|
210
|
+
extractPreviewText(messages[0].query),
|
|
211
|
+
Math.max(1, Math.floor(boundedWidth * COLLAPSED_PREVIEW_WIDTH_RATIO)),
|
|
212
|
+
),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (panelHeight < MIN_EXPANDED_PANEL_HEIGHT) {
|
|
217
|
+
return {
|
|
218
|
+
kind: 'compact',
|
|
219
|
+
width: boundedWidth,
|
|
220
|
+
summary: queuedMessageSummary(messages.length),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const maxVisibleItems = calculateMaxVisibleItems(
|
|
225
|
+
panelHeight,
|
|
226
|
+
messages.length,
|
|
227
|
+
);
|
|
228
|
+
const visibleMessages = messages.slice(0, maxVisibleItems);
|
|
229
|
+
const moreCount = messages.length - visibleMessages.length;
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
kind: 'expanded',
|
|
233
|
+
width: boundedWidth,
|
|
234
|
+
panelHeight,
|
|
235
|
+
heading: `Queued Messages (${messages.length})`,
|
|
236
|
+
messages: visibleMessages.map((message, index) => {
|
|
237
|
+
const preview = extractPreviewText(message.query);
|
|
238
|
+
const messageNumber = index + 1;
|
|
239
|
+
return {
|
|
240
|
+
key: stableKey(message, index, preview),
|
|
241
|
+
number: messageNumber,
|
|
242
|
+
preview: truncateEnd(
|
|
243
|
+
preview,
|
|
244
|
+
calculateExpandedPreviewWidth(boundedWidth, messageNumber),
|
|
245
|
+
),
|
|
246
|
+
};
|
|
247
|
+
}),
|
|
248
|
+
moreCount,
|
|
249
|
+
showMoreIndicator: moreCount > 0,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function CompactQueuedMessagesPanel({
|
|
254
|
+
view,
|
|
255
|
+
}: {
|
|
256
|
+
view: Extract<QueuedMessagesPanelView, { kind: 'compact' }>;
|
|
257
|
+
}) {
|
|
258
|
+
return (
|
|
259
|
+
<Box width={view.width} height={1} overflow="hidden">
|
|
260
|
+
<Text color={SemanticColors.text.accent} bold wrap="truncate-end">
|
|
261
|
+
{view.summary}
|
|
262
|
+
</Text>
|
|
263
|
+
</Box>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function QueuedMessagesPanelShell({
|
|
268
|
+
width,
|
|
269
|
+
panelHeight,
|
|
270
|
+
children,
|
|
271
|
+
}: {
|
|
272
|
+
width: number;
|
|
273
|
+
panelHeight: number;
|
|
274
|
+
children: React.ReactNode;
|
|
275
|
+
}) {
|
|
276
|
+
return (
|
|
277
|
+
<Box
|
|
278
|
+
flexDirection="column"
|
|
279
|
+
width={width}
|
|
280
|
+
height={panelHeight}
|
|
281
|
+
overflow="hidden"
|
|
282
|
+
borderStyle="single"
|
|
283
|
+
borderColor={SemanticColors.text.accent}
|
|
284
|
+
borderTop={panelHeight > 1}
|
|
285
|
+
borderBottom={false}
|
|
286
|
+
borderLeft={false}
|
|
287
|
+
borderRight={false}
|
|
288
|
+
paddingX={width > 2 ? 1 : 0}
|
|
289
|
+
>
|
|
290
|
+
{children}
|
|
291
|
+
</Box>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function CollapsedQueuedMessagesPanel({
|
|
296
|
+
view,
|
|
297
|
+
}: {
|
|
298
|
+
view: Extract<QueuedMessagesPanelView, { kind: 'collapsed' }>;
|
|
299
|
+
}) {
|
|
300
|
+
return (
|
|
301
|
+
<QueuedMessagesPanelShell width={view.width} panelHeight={view.panelHeight}>
|
|
302
|
+
<Box flexDirection="row" minHeight={1} overflow="hidden">
|
|
303
|
+
<Text color={SemanticColors.text.primary} bold wrap="truncate-end">
|
|
304
|
+
{view.summary}
|
|
305
|
+
</Text>
|
|
306
|
+
{view.nextPreview !== undefined && (
|
|
307
|
+
<Text color={SemanticColors.text.secondary}>
|
|
308
|
+
{' '}
|
|
309
|
+
• next: {view.nextPreview}
|
|
310
|
+
</Text>
|
|
311
|
+
)}
|
|
312
|
+
<Text color={SemanticColors.text.secondary}>
|
|
313
|
+
{' '}
|
|
314
|
+
• {QUEUED_MESSAGES_KEY_HINT} to expand
|
|
315
|
+
</Text>
|
|
316
|
+
</Box>
|
|
317
|
+
</QueuedMessagesPanelShell>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function ExpandedQueuedMessagesPanel({
|
|
322
|
+
view,
|
|
323
|
+
}: {
|
|
324
|
+
view: Extract<QueuedMessagesPanelView, { kind: 'expanded' }>;
|
|
325
|
+
}) {
|
|
326
|
+
return (
|
|
327
|
+
<QueuedMessagesPanelShell width={view.width} panelHeight={view.panelHeight}>
|
|
328
|
+
<Box minHeight={1} marginBottom={0}>
|
|
329
|
+
<Text color={SemanticColors.text.accent} bold>
|
|
330
|
+
{view.heading}
|
|
331
|
+
</Text>
|
|
332
|
+
<Text color={SemanticColors.text.secondary}>
|
|
333
|
+
{' '}
|
|
334
|
+
• {QUEUED_MESSAGES_KEY_HINT} to minimize
|
|
335
|
+
</Text>
|
|
336
|
+
</Box>
|
|
337
|
+
{view.messages.map((message) => (
|
|
338
|
+
<Box key={message.key} flexDirection="row" minHeight={1}>
|
|
339
|
+
<Text color={SemanticColors.text.secondary} bold>
|
|
340
|
+
{message.number}.{' '}
|
|
341
|
+
</Text>
|
|
342
|
+
<Box flexGrow={1} overflow="hidden">
|
|
343
|
+
<Text color={SemanticColors.text.secondary} wrap="truncate-end">
|
|
344
|
+
{message.preview}
|
|
345
|
+
</Text>
|
|
346
|
+
</Box>
|
|
347
|
+
</Box>
|
|
348
|
+
))}
|
|
349
|
+
{view.showMoreIndicator && (
|
|
350
|
+
<Box flexDirection="row" minHeight={1}>
|
|
351
|
+
<Text color={SemanticColors.text.secondary}>
|
|
352
|
+
▼ +{view.moreCount} more
|
|
353
|
+
</Text>
|
|
354
|
+
</Box>
|
|
355
|
+
)}
|
|
356
|
+
</QueuedMessagesPanelShell>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function QueuedMessagesPanel({
|
|
361
|
+
width,
|
|
362
|
+
collapsed = false,
|
|
363
|
+
messages,
|
|
364
|
+
}: QueuedMessagesPanelProps) {
|
|
365
|
+
const { columns, rows } = useTerminalSize();
|
|
366
|
+
const view = useMemo(
|
|
367
|
+
() =>
|
|
368
|
+
prepareQueuedMessagesPanelView({
|
|
369
|
+
width,
|
|
370
|
+
collapsed,
|
|
371
|
+
messages,
|
|
372
|
+
columns,
|
|
373
|
+
rows,
|
|
374
|
+
}),
|
|
375
|
+
[width, collapsed, messages, columns, rows],
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
if (view.kind === 'empty') {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
if (view.kind === 'compact') {
|
|
382
|
+
return <CompactQueuedMessagesPanel view={view} />;
|
|
383
|
+
}
|
|
384
|
+
if (view.kind === 'collapsed') {
|
|
385
|
+
return <CollapsedQueuedMessagesPanel view={view} />;
|
|
386
|
+
}
|
|
387
|
+
return <ExpandedQueuedMessagesPanel view={view} />;
|
|
388
|
+
}
|
|
@@ -190,6 +190,7 @@ type InputPromptState = Pick<
|
|
|
190
190
|
InputPromptProps,
|
|
191
191
|
| 'buffer'
|
|
192
192
|
| 'onSubmit'
|
|
193
|
+
| 'onSteer'
|
|
193
194
|
| 'userMessages'
|
|
194
195
|
| 'onClearScreen'
|
|
195
196
|
| 'config'
|
|
@@ -390,6 +391,7 @@ const buildInputHandlerDeps = (
|
|
|
390
391
|
inputHistory,
|
|
391
392
|
handleSubmitAndClear: submitState.handleSubmitAndClear,
|
|
392
393
|
handleSubmit: submitState.handleSubmit,
|
|
394
|
+
handleSteer: state.onSteer,
|
|
393
395
|
shellHistory: completionState.shellHistory,
|
|
394
396
|
reverseSearchCompletion: completionState.reverseSearchCompletion,
|
|
395
397
|
shellPathCompletion: completionState.shellPathCompletion,
|
|
@@ -415,6 +415,8 @@ export type InputHandlerDeps = {
|
|
|
415
415
|
inputHistory: { navigateUp: () => void; navigateDown: () => void };
|
|
416
416
|
handleSubmitAndClear: (value: string) => void;
|
|
417
417
|
handleSubmit: (value: string) => void;
|
|
418
|
+
/** Optional steer callback: injects the text into the active agent loop at the next tool-call boundary. Returns true if consumed (streaming), false to fall through to newline. */
|
|
419
|
+
handleSteer?: (text: string) => boolean;
|
|
418
420
|
shellHistory: {
|
|
419
421
|
getPreviousCommand: () => string | null;
|
|
420
422
|
getNextCommand: () => string | null;
|
|
@@ -518,6 +520,18 @@ const handleNavigationInputKeys = (key: Key, deps: InputHandlerDeps): boolean =>
|
|
|
518
520
|
|
|
519
521
|
const handleSubmitAndEditKeys = (key: Key, deps: InputHandlerDeps): boolean => {
|
|
520
522
|
const { buffer, handleSubmit, resetCompletionState } = deps;
|
|
523
|
+
// During streaming, Ctrl+Enter (STEER) injects the current buffer as a
|
|
524
|
+
// mid-turn steer. When not streaming, handleSteer returns false and the
|
|
525
|
+
// key falls through to NEWLINE (which also matches Ctrl+Enter) for normal
|
|
526
|
+
// multi-line input.
|
|
527
|
+
if (deps.handleSteer && keyMatchers[Command.STEER](key)) {
|
|
528
|
+
const consumed = deps.handleSteer(buffer.text);
|
|
529
|
+
if (consumed) {
|
|
530
|
+
buffer.setText('');
|
|
531
|
+
resetCompletionState();
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
521
535
|
if (keyMatchers[Command.SUBMIT](key)) {
|
|
522
536
|
submitBufferInput(buffer, handleSubmit);
|
|
523
537
|
return true;
|
|
@@ -14,6 +14,12 @@ import type { CliUiRuntime } from '../cliUiRuntime.js';
|
|
|
14
14
|
export interface InputPromptProps {
|
|
15
15
|
buffer: TextBuffer;
|
|
16
16
|
onSubmit: (value: string) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Optional steer callback: during streaming, Ctrl+Enter injects the text
|
|
19
|
+
* into the active agent loop at the next tool-call boundary. Returns true
|
|
20
|
+
* if consumed (agent is streaming), false to fall through to newline.
|
|
21
|
+
*/
|
|
22
|
+
onSteer?: (text: string) => boolean;
|
|
17
23
|
userMessages: readonly string[];
|
|
18
24
|
onClearScreen: () => void;
|
|
19
25
|
config: CliUiRuntime;
|
|
@@ -24,6 +24,16 @@ interface AiMessageProps {
|
|
|
24
24
|
thinkingBlocks?: ThinkingBlock[]; // @plan:PLAN-20251202-THINKING-UI.P06
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export function getVisibleThinkingBlocks(
|
|
28
|
+
showThinking: boolean,
|
|
29
|
+
thinkingBlocks?: ThinkingBlock[],
|
|
30
|
+
): ThinkingBlock[] | undefined {
|
|
31
|
+
if (!showThinking) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return thinkingBlocks?.filter((block) => block.isHidden !== true);
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
export const AiMessage: React.FC<AiMessageProps> = ({
|
|
28
38
|
text,
|
|
29
39
|
isPending,
|
|
@@ -46,10 +56,13 @@ export const AiMessage: React.FC<AiMessageProps> = ({
|
|
|
46
56
|
const prefix = ' ';
|
|
47
57
|
const prefixWidth = prefix.length;
|
|
48
58
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
const
|
|
59
|
+
// #1723: Show thinking blocks in BOTH pending and committed items so thinking
|
|
60
|
+
// content streams in real-time. The LoadingIndicator still shows the transient
|
|
61
|
+
// thought subject, but the growing thinking block is now visible below it.
|
|
62
|
+
const visibleThinkingBlocks = getVisibleThinkingBlocks(
|
|
63
|
+
showThinking,
|
|
64
|
+
thinkingBlocks,
|
|
65
|
+
);
|
|
53
66
|
|
|
54
67
|
return (
|
|
55
68
|
<Box flexDirection="column">
|
|
@@ -63,14 +76,13 @@ export const AiMessage: React.FC<AiMessageProps> = ({
|
|
|
63
76
|
<Text color={Colors.DimComment}>{model}</Text>
|
|
64
77
|
</Box>
|
|
65
78
|
)}
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
))}
|
|
79
|
+
{visibleThinkingBlocks?.map((block, index) => (
|
|
80
|
+
<ThinkingBlockDisplay
|
|
81
|
+
key={`thinking-${index}`}
|
|
82
|
+
block={block}
|
|
83
|
+
visible={true}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
74
86
|
<Box flexDirection="row">
|
|
75
87
|
<Box width={prefixWidth}>
|
|
76
88
|
<Text
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type React from 'react';
|
|
8
|
-
import { useMemo
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
9
|
import { Box, Text } from 'ink';
|
|
10
10
|
import type { IndividualToolCallDisplay } from '../../types.js';
|
|
11
11
|
import { ToolCallStatus } from '../../types.js';
|
|
12
12
|
import { Colors } from '../../colors.js';
|
|
13
13
|
import { theme } from '../../semantic-colors.js';
|
|
14
14
|
import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js';
|
|
15
|
-
import {
|
|
15
|
+
import { useShellCommandDisplay } from '../../contexts/ShellCommandDisplayContext.js';
|
|
16
16
|
import {
|
|
17
17
|
ShellExecutionService,
|
|
18
18
|
splitCommands,
|
|
@@ -67,12 +67,14 @@ function extractEchoText(cmd: string): string | null {
|
|
|
67
67
|
* Compute current subcommand for display when details visible.
|
|
68
68
|
*/
|
|
69
69
|
function computeCurrentSubcommand(
|
|
70
|
-
|
|
70
|
+
showFullShellDescription: boolean,
|
|
71
71
|
status: ToolCallStatus,
|
|
72
72
|
description: string | undefined,
|
|
73
73
|
resultDisplay: string | object | undefined,
|
|
74
74
|
): string | null {
|
|
75
|
-
if (!
|
|
75
|
+
if (!showFullShellDescription || status !== ToolCallStatus.Executing) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
76
78
|
if (!description) return null;
|
|
77
79
|
|
|
78
80
|
const outputString =
|
|
@@ -164,39 +166,11 @@ function computeShellFocusState(
|
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
/**
|
|
168
|
-
* Hook to manage details visibility toggle.
|
|
169
|
-
*/
|
|
170
|
-
function useDetailsToggle(
|
|
171
|
-
status: ToolCallStatus,
|
|
172
|
-
isFocused: boolean,
|
|
173
|
-
): [boolean, React.Dispatch<React.SetStateAction<boolean>>] {
|
|
174
|
-
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
|
|
175
|
-
|
|
176
|
-
useKeypress(
|
|
177
|
-
(key) => {
|
|
178
|
-
if (
|
|
179
|
-
key.ctrl === true &&
|
|
180
|
-
key.name === 'r' &&
|
|
181
|
-
status === ToolCallStatus.Executing
|
|
182
|
-
) {
|
|
183
|
-
setIsDetailsVisible((prev) => !prev);
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
{ isActive: isFocused },
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return [isDetailsVisible, setIsDetailsVisible];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
169
|
/**
|
|
193
170
|
* Render the executing status hint.
|
|
194
171
|
*/
|
|
195
|
-
function renderExecutingHint(
|
|
196
|
-
|
|
197
|
-
isDetailsVisible: boolean,
|
|
198
|
-
): React.ReactNode {
|
|
199
|
-
if (status !== ToolCallStatus.Executing || isDetailsVisible) {
|
|
172
|
+
function renderExecutingHint(showExecutingHint: boolean): React.ReactNode {
|
|
173
|
+
if (!showExecutingHint) {
|
|
200
174
|
return null;
|
|
201
175
|
}
|
|
202
176
|
return (
|
|
@@ -260,6 +234,7 @@ function renderToolMessageHeader(
|
|
|
260
234
|
name: string,
|
|
261
235
|
description: string,
|
|
262
236
|
emphasis: TextEmphasis,
|
|
237
|
+
showFullDescription: boolean,
|
|
263
238
|
isThisShellFocusable: boolean,
|
|
264
239
|
isThisShellFocused: boolean,
|
|
265
240
|
): React.ReactNode {
|
|
@@ -276,6 +251,7 @@ function renderToolMessageHeader(
|
|
|
276
251
|
status={status}
|
|
277
252
|
description={description}
|
|
278
253
|
emphasis={emphasis}
|
|
254
|
+
showFullDescription={showFullDescription}
|
|
279
255
|
/>
|
|
280
256
|
{isThisShellFocusable && (
|
|
281
257
|
<Box marginLeft={1} flexShrink={0}>
|
|
@@ -299,7 +275,7 @@ function renderToolMessageContent(
|
|
|
299
275
|
borderColor: string,
|
|
300
276
|
borderDimColor: boolean,
|
|
301
277
|
status: ToolCallStatus,
|
|
302
|
-
|
|
278
|
+
showExecutingHint: boolean,
|
|
303
279
|
currentSubcommand: string | null,
|
|
304
280
|
resultDisplay: string | object | undefined,
|
|
305
281
|
availableTerminalHeight: number | undefined,
|
|
@@ -323,7 +299,7 @@ function renderToolMessageContent(
|
|
|
323
299
|
flexDirection="column"
|
|
324
300
|
overflowX="hidden"
|
|
325
301
|
>
|
|
326
|
-
{renderExecutingHint(
|
|
302
|
+
{renderExecutingHint(showExecutingHint)}
|
|
327
303
|
{renderCurrentSubcommand(currentSubcommand)}
|
|
328
304
|
<ToolResultDisplay
|
|
329
305
|
resultDisplay={resultDisplay}
|
|
@@ -355,6 +331,7 @@ export interface ToolMessageProps extends IndividualToolCallDisplay {
|
|
|
355
331
|
}
|
|
356
332
|
|
|
357
333
|
export const ToolMessage: React.FC<ToolMessageProps> = ({
|
|
334
|
+
callId,
|
|
358
335
|
name,
|
|
359
336
|
description,
|
|
360
337
|
resultDisplay,
|
|
@@ -372,7 +349,13 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
|
|
372
349
|
borderColor = Colors.Gray,
|
|
373
350
|
borderDimColor = false,
|
|
374
351
|
}) => {
|
|
375
|
-
const
|
|
352
|
+
const isShellTool = name === SHELL_NAME || name === SHELL_COMMAND_NAME;
|
|
353
|
+
const showFullShellDescription = useShellCommandDisplay(
|
|
354
|
+
callId,
|
|
355
|
+
name,
|
|
356
|
+
status,
|
|
357
|
+
isFocused,
|
|
358
|
+
);
|
|
376
359
|
|
|
377
360
|
const { isThisShellFocused, isThisShellFocusable } = computeShellFocusState(
|
|
378
361
|
name,
|
|
@@ -386,12 +369,12 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
|
|
386
369
|
const currentSubcommand = useMemo(
|
|
387
370
|
() =>
|
|
388
371
|
computeCurrentSubcommand(
|
|
389
|
-
|
|
372
|
+
showFullShellDescription,
|
|
390
373
|
status,
|
|
391
374
|
description,
|
|
392
375
|
resultDisplay,
|
|
393
376
|
),
|
|
394
|
-
[
|
|
377
|
+
[showFullShellDescription, status, description, resultDisplay],
|
|
395
378
|
);
|
|
396
379
|
|
|
397
380
|
return (
|
|
@@ -405,6 +388,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
|
|
405
388
|
name,
|
|
406
389
|
description || '',
|
|
407
390
|
emphasis,
|
|
391
|
+
showFullShellDescription,
|
|
408
392
|
isThisShellFocusable,
|
|
409
393
|
isThisShellFocused,
|
|
410
394
|
)}
|
|
@@ -413,7 +397,9 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
|
|
413
397
|
borderColor,
|
|
414
398
|
borderDimColor,
|
|
415
399
|
status,
|
|
416
|
-
|
|
400
|
+
isShellTool &&
|
|
401
|
+
status === ToolCallStatus.Executing &&
|
|
402
|
+
!showFullShellDescription,
|
|
417
403
|
currentSubcommand,
|
|
418
404
|
resultDisplay,
|
|
419
405
|
availableTerminalHeight,
|
|
@@ -25,6 +25,7 @@ export interface UIActionsParams {
|
|
|
25
25
|
|
|
26
26
|
// Input actions
|
|
27
27
|
handleUserInputSubmit: (value: string) => void;
|
|
28
|
+
handleSteer: (text: string) => boolean;
|
|
28
29
|
handleClearScreen: () => void;
|
|
29
30
|
|
|
30
31
|
// Theme dialog
|
|
@@ -92,7 +93,7 @@ export interface UIActionsParams {
|
|
|
92
93
|
exitToolsDialog: () => void;
|
|
93
94
|
|
|
94
95
|
// Folder trust dialog
|
|
95
|
-
handleFolderTrustSelect: (choice: FolderTrustChoice) => void
|
|
96
|
+
handleFolderTrustSelect: (choice: FolderTrustChoice) => Promise<void>;
|
|
96
97
|
|
|
97
98
|
// Welcome onboarding
|
|
98
99
|
welcomeActions: UIActions['welcomeActions'];
|
|
@@ -198,6 +199,7 @@ export function buildUIActions(params: UIActionsParams): UIActions {
|
|
|
198
199
|
|
|
199
200
|
// Input actions
|
|
200
201
|
handleUserInputSubmit: params.handleUserInputSubmit,
|
|
202
|
+
handleSteer: params.handleSteer,
|
|
201
203
|
handleClearScreen: params.handleClearScreen,
|
|
202
204
|
|
|
203
205
|
// Theme dialog
|