@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
|
@@ -48,6 +48,8 @@ export interface DisplayKeybindingDeps {
|
|
|
48
48
|
setRenderMarkdown: (v: boolean) => void;
|
|
49
49
|
isTodoPanelCollapsed: boolean;
|
|
50
50
|
setIsTodoPanelCollapsed: (v: boolean) => void;
|
|
51
|
+
isQueuedMessagesPanelCollapsed: boolean;
|
|
52
|
+
setIsQueuedMessagesPanelCollapsed: (v: boolean) => void;
|
|
51
53
|
constrainHeight: boolean;
|
|
52
54
|
setConstrainHeight: (v: boolean) => void;
|
|
53
55
|
refreshStatic: () => void;
|
|
@@ -195,6 +197,13 @@ function handleDisplayKeys(
|
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
199
|
|
|
200
|
+
if (keyMatchers[Command.TOGGLE_QUEUED_MESSAGES](key)) {
|
|
201
|
+
display.setIsQueuedMessagesPanelCollapsed(
|
|
202
|
+
!display.isQueuedMessagesPanelCollapsed,
|
|
203
|
+
);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
198
207
|
if (
|
|
199
208
|
keyMatchers[Command.SHOW_MORE_LINES](key) &&
|
|
200
209
|
!enteringConstrainHeightMode
|
|
@@ -253,48 +262,52 @@ function handleIdeAndShellKeys(
|
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
export function useKeybindings(params: UseKeybindingsParams): void {
|
|
256
|
-
|
|
265
|
+
// Keep the latest deps in a ref so the keypress handler closure below never
|
|
266
|
+
// goes stale, while keeping the callback identity stable. useKeypress
|
|
267
|
+
// registers the handler once (empty deps) — if the callback changed on every
|
|
268
|
+
// render, registration would churn or miss keys during rapid dep updates.
|
|
269
|
+
const latestParamsRef = useRef(params);
|
|
270
|
+
latestParamsRef.current = params;
|
|
257
271
|
|
|
258
272
|
// Instance-local mouse state tracking for copy mode save/restore.
|
|
259
273
|
const mouseStateBeforeCopyModeRef = useRef<boolean | null>(null);
|
|
260
274
|
|
|
261
|
-
const handleGlobalKeypress = useCallback(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
);
|
|
275
|
+
const handleGlobalKeypress = useCallback((key: Key) => {
|
|
276
|
+
const { exit, display, shell, copyMode, ideContext, mcp } =
|
|
277
|
+
latestParamsRef.current;
|
|
278
|
+
// Priority 1: Copy mode (highest priority - immediate exit from copy mode)
|
|
279
|
+
if (handleCopyModeKey(key, copyMode, mouseStateBeforeCopyModeRef)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Priority 2: Exit keys (Ctrl+C/D)
|
|
284
|
+
if (handleExitKeys(key, exit)) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Calculate constrain height mode transition
|
|
289
|
+
let enteringConstrainHeightMode = false;
|
|
290
|
+
if (!display.constrainHeight) {
|
|
291
|
+
enteringConstrainHeightMode = true;
|
|
292
|
+
display.setConstrainHeight(true);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Priority 3: Display toggles
|
|
296
|
+
handleDisplayKeys(key, display, mcp, enteringConstrainHeightMode);
|
|
297
|
+
|
|
298
|
+
// Priority 4: IDE and shell keys
|
|
299
|
+
handleIdeAndShellKeys(key, ideContext, shell);
|
|
300
|
+
|
|
301
|
+
// Handle IDE status command separately (needs handleSlashCommand)
|
|
302
|
+
if (
|
|
303
|
+
keyMatchers[Command.SHOW_IDE_CONTEXT_DETAIL](key) &&
|
|
304
|
+
ideContext.getIdeMode() &&
|
|
305
|
+
ideContext.ideContextState !== undefined &&
|
|
306
|
+
ideContext.ideContextState !== null
|
|
307
|
+
) {
|
|
308
|
+
void display.handleSlashCommand('/ide status');
|
|
309
|
+
}
|
|
310
|
+
}, []);
|
|
298
311
|
|
|
299
312
|
useKeypress(handleGlobalKeypress, {
|
|
300
313
|
isActive: true,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useCallback } from 'react';
|
|
8
|
+
import { StreamingState, type HistoryItemWithoutId } from '../../../types.js';
|
|
9
|
+
import { isToolExecuting } from './useInputHandling.js';
|
|
10
|
+
|
|
11
|
+
interface SteerAgent {
|
|
12
|
+
injectSteer(text: string): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SanitizedContent {
|
|
16
|
+
text: string;
|
|
17
|
+
blocked: boolean;
|
|
18
|
+
feedback?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useSteer(
|
|
22
|
+
agent: SteerAgent,
|
|
23
|
+
streamingState: StreamingState,
|
|
24
|
+
sanitizeContent: (text: string) => SanitizedContent,
|
|
25
|
+
pendingHistoryItems: HistoryItemWithoutId[],
|
|
26
|
+
addMessage: (message: string) => void,
|
|
27
|
+
): (text: string) => boolean {
|
|
28
|
+
return useCallback(
|
|
29
|
+
(text: string): boolean => {
|
|
30
|
+
if (streamingState !== StreamingState.Responding) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
let sanitized: SanitizedContent;
|
|
34
|
+
try {
|
|
35
|
+
sanitized = sanitizeContent(text);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (sanitized.blocked || sanitized.text.length === 0) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (!isToolExecuting(pendingHistoryItems)) {
|
|
43
|
+
addMessage(sanitized.text);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
agent.injectSteer(sanitized.text);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
[streamingState, sanitizeContent, pendingHistoryItems, addMessage, agent],
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -263,8 +263,14 @@ function applyKeyCodeModifier(
|
|
|
263
263
|
if (codeNumber >= 'a'.charCodeAt(0) && codeNumber <= 'z'.charCodeAt(0)) {
|
|
264
264
|
return { name: String.fromCharCode(codeNumber), shift, meta, ctrl };
|
|
265
265
|
}
|
|
266
|
-
if (
|
|
267
|
-
|
|
266
|
+
if (
|
|
267
|
+
codeNumber === '\\'.charCodeAt(0) ||
|
|
268
|
+
codeNumber === ']'.charCodeAt(0) ||
|
|
269
|
+
codeNumber === '^'.charCodeAt(0) ||
|
|
270
|
+
codeNumber === '_'.charCodeAt(0)
|
|
271
|
+
) {
|
|
272
|
+
return { name: String.fromCharCode(codeNumber), shift, meta, ctrl };
|
|
273
|
+
}
|
|
268
274
|
}
|
|
269
275
|
return { name: 'undefined', shift, meta, ctrl };
|
|
270
276
|
}
|
|
@@ -300,6 +306,11 @@ function parseNonEscapeKey(
|
|
|
300
306
|
} else if (!escaped && ch.charCodeAt(0) <= 0x1a) {
|
|
301
307
|
name = String.fromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
|
302
308
|
ctrl = true;
|
|
309
|
+
} else if (!escaped && ch.charCodeAt(0) >= 0x1c && ch.charCodeAt(0) <= 0x1f) {
|
|
310
|
+
// C0 control chars above Ctrl+Z map to the literal character on the key
|
|
311
|
+
// (ASCII offset 0x40): \x1c → \, \x1d → ], \x1e → ^, \x1f → _
|
|
312
|
+
name = String.fromCharCode(ch.charCodeAt(0) + 0x40);
|
|
313
|
+
ctrl = true;
|
|
303
314
|
} else if (/^[0-9A-Za-z]$/.test(ch)) {
|
|
304
315
|
name = ch.toLowerCase();
|
|
305
316
|
shift = /^[A-Z]$/.test(ch);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type React from 'react';
|
|
8
|
+
import {
|
|
9
|
+
createContext,
|
|
10
|
+
useCallback,
|
|
11
|
+
useContext,
|
|
12
|
+
useMemo,
|
|
13
|
+
useState,
|
|
14
|
+
} from 'react';
|
|
15
|
+
import { SHELL_COMMAND_NAME, SHELL_NAME } from '../constants.js';
|
|
16
|
+
import { useKeypress } from '../hooks/useKeypress.js';
|
|
17
|
+
import { ToolCallStatus } from '../types.js';
|
|
18
|
+
|
|
19
|
+
interface ShellCommandDisplayState {
|
|
20
|
+
getShowFullDescription: (callId: string) => boolean;
|
|
21
|
+
toggleShowFullDescription: (callId: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ShellCommandDisplayContext =
|
|
25
|
+
createContext<ShellCommandDisplayState | null>(null);
|
|
26
|
+
|
|
27
|
+
interface ShellCommandDisplayProviderProps {
|
|
28
|
+
alwaysDisplayFullShellCommand: boolean;
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const ShellCommandDisplayProvider: React.FC<
|
|
33
|
+
ShellCommandDisplayProviderProps
|
|
34
|
+
> = ({ alwaysDisplayFullShellCommand, children }) => {
|
|
35
|
+
const [callDisplayOverrides, setCallDisplayOverrides] = useState<
|
|
36
|
+
ReadonlyMap<string, boolean>
|
|
37
|
+
>(() => new Map());
|
|
38
|
+
|
|
39
|
+
const getShowFullDescription = useCallback(
|
|
40
|
+
(callId: string): boolean =>
|
|
41
|
+
callDisplayOverrides.get(callId) ?? alwaysDisplayFullShellCommand,
|
|
42
|
+
[alwaysDisplayFullShellCommand, callDisplayOverrides],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const toggleShowFullDescription = useCallback(
|
|
46
|
+
(callId: string): void => {
|
|
47
|
+
setCallDisplayOverrides((previousOverrides) => {
|
|
48
|
+
const currentValue =
|
|
49
|
+
previousOverrides.get(callId) ?? alwaysDisplayFullShellCommand;
|
|
50
|
+
return new Map([
|
|
51
|
+
...previousOverrides.entries(),
|
|
52
|
+
[callId, !currentValue],
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
[alwaysDisplayFullShellCommand],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const value = useMemo<ShellCommandDisplayState>(
|
|
60
|
+
() => ({ getShowFullDescription, toggleShowFullDescription }),
|
|
61
|
+
[getShowFullDescription, toggleShowFullDescription],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ShellCommandDisplayContext.Provider value={value}>
|
|
66
|
+
{children}
|
|
67
|
+
</ShellCommandDisplayContext.Provider>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function useShellCommandDisplay(
|
|
72
|
+
callId: string,
|
|
73
|
+
name: string,
|
|
74
|
+
status: ToolCallStatus,
|
|
75
|
+
isFocused: boolean,
|
|
76
|
+
): boolean {
|
|
77
|
+
const context = useContext(ShellCommandDisplayContext);
|
|
78
|
+
if (context === null) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'useShellCommandDisplay must be used within ShellCommandDisplayProvider',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const isShellTool = name === SHELL_COMMAND_NAME || name === SHELL_NAME;
|
|
85
|
+
const { getShowFullDescription, toggleShowFullDescription } = context;
|
|
86
|
+
const onKeypress = useCallback(
|
|
87
|
+
(key: { ctrl?: boolean; name?: string }): void => {
|
|
88
|
+
if (
|
|
89
|
+
isShellTool &&
|
|
90
|
+
key.ctrl === true &&
|
|
91
|
+
key.name === 'r' &&
|
|
92
|
+
status === ToolCallStatus.Executing
|
|
93
|
+
) {
|
|
94
|
+
toggleShowFullDescription(callId);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
[callId, isShellTool, status, toggleShowFullDescription],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
useKeypress(onKeypress, { isActive: isFocused && isShellTool });
|
|
101
|
+
|
|
102
|
+
return isShellTool && getShowFullDescription(callId);
|
|
103
|
+
}
|
|
@@ -11,15 +11,6 @@ interface TodoContextType {
|
|
|
11
11
|
todos: Todo[];
|
|
12
12
|
updateTodos: (todos: Todo[]) => void;
|
|
13
13
|
refreshTodos: () => void;
|
|
14
|
-
/**
|
|
15
|
-
* Whether task-list continuation is paused via the pause tool.
|
|
16
|
-
* Persisted across sessions to survive --continue restores.
|
|
17
|
-
*/
|
|
18
|
-
paused: boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Set the paused state. Persists to disk immediately.
|
|
21
|
-
*/
|
|
22
|
-
setPaused: (paused: boolean) => void;
|
|
23
14
|
}
|
|
24
15
|
|
|
25
16
|
const defaultContextValue: TodoContextType = {
|
|
@@ -34,10 +25,6 @@ const defaultContextValue: TodoContextType = {
|
|
|
34
25
|
'TodoContext refreshTodos not implemented - use TodoProvider',
|
|
35
26
|
);
|
|
36
27
|
},
|
|
37
|
-
paused: false,
|
|
38
|
-
setPaused: () => {
|
|
39
|
-
throw new Error('TodoContext setPaused not implemented - use TodoProvider');
|
|
40
|
-
},
|
|
41
28
|
};
|
|
42
29
|
|
|
43
30
|
export const TodoContext =
|
|
@@ -27,7 +27,6 @@ interface TodoProviderProps {
|
|
|
27
27
|
*/
|
|
28
28
|
function useTaskState(sessionId: string, agentId: string | undefined) {
|
|
29
29
|
const [todos, setTodos] = useState<Todo[]>([]);
|
|
30
|
-
const [paused, setPausedState] = useState<boolean>(false);
|
|
31
30
|
const [loading, setLoading] = useState(true);
|
|
32
31
|
const [error, setError] = useState<string | null>(null);
|
|
33
32
|
|
|
@@ -42,16 +41,13 @@ function useTaskState(sessionId: string, agentId: string | undefined) {
|
|
|
42
41
|
agentId,
|
|
43
42
|
);
|
|
44
43
|
const loadedTodos = await store.readTodos();
|
|
45
|
-
const pausedState = await store.readPausedState();
|
|
46
44
|
setTodos(loadedTodos);
|
|
47
|
-
setPausedState(pausedState);
|
|
48
45
|
setError(null);
|
|
49
46
|
} catch (err) {
|
|
50
47
|
setError(
|
|
51
48
|
`Failed to load todos: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
52
49
|
);
|
|
53
50
|
setTodos([]);
|
|
54
|
-
setPausedState(false);
|
|
55
51
|
} finally {
|
|
56
52
|
setLoading(false);
|
|
57
53
|
}
|
|
@@ -60,8 +56,6 @@ function useTaskState(sessionId: string, agentId: string | undefined) {
|
|
|
60
56
|
return {
|
|
61
57
|
todos,
|
|
62
58
|
setTodos,
|
|
63
|
-
paused,
|
|
64
|
-
setPausedState,
|
|
65
59
|
loading,
|
|
66
60
|
setLoading,
|
|
67
61
|
error,
|
|
@@ -105,7 +99,6 @@ function useTaskPersistence(
|
|
|
105
99
|
sessionId: string,
|
|
106
100
|
agentId: string | undefined,
|
|
107
101
|
setTodos: (todos: Todo[]) => void,
|
|
108
|
-
setPausedState: (paused: boolean) => void,
|
|
109
102
|
setError: (error: string | null) => void,
|
|
110
103
|
) {
|
|
111
104
|
const updateTodos = useCallback(
|
|
@@ -127,26 +120,7 @@ function useTaskPersistence(
|
|
|
127
120
|
[agentId, sessionId, setTodos, setError],
|
|
128
121
|
);
|
|
129
122
|
|
|
130
|
-
|
|
131
|
-
(newPaused: boolean) => {
|
|
132
|
-
setPausedState(newPaused);
|
|
133
|
-
const store = new TodoStore(
|
|
134
|
-
sessionId,
|
|
135
|
-
{
|
|
136
|
-
dataDirResolver: () => Storage.getGlobalDataDir(),
|
|
137
|
-
},
|
|
138
|
-
agentId,
|
|
139
|
-
);
|
|
140
|
-
store.writePausedState(newPaused).catch((err: unknown) => {
|
|
141
|
-
setError(
|
|
142
|
-
`Failed to save paused state: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
},
|
|
146
|
-
[agentId, sessionId, setPausedState, setError],
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
return { updateTodos, setPaused };
|
|
123
|
+
return { updateTodos };
|
|
150
124
|
}
|
|
151
125
|
|
|
152
126
|
/**
|
|
@@ -162,7 +136,6 @@ function useTaskManagement(sessionId: string, agentId: string | undefined) {
|
|
|
162
136
|
sessionId,
|
|
163
137
|
agentId,
|
|
164
138
|
state.setTodos,
|
|
165
|
-
state.setPausedState,
|
|
166
139
|
state.setError,
|
|
167
140
|
);
|
|
168
141
|
|
|
@@ -175,12 +148,10 @@ function useTaskManagement(sessionId: string, agentId: string | undefined) {
|
|
|
175
148
|
|
|
176
149
|
return {
|
|
177
150
|
todos: state.todos,
|
|
178
|
-
paused: state.paused,
|
|
179
151
|
loading: state.loading,
|
|
180
152
|
error: state.error,
|
|
181
153
|
refreshTodos: state.refreshTodos,
|
|
182
154
|
updateTodos: persistence.updateTodos,
|
|
183
|
-
setPaused: persistence.setPaused,
|
|
184
155
|
};
|
|
185
156
|
}
|
|
186
157
|
|
|
@@ -196,8 +167,6 @@ export const TodoProvider: React.FC<TodoProviderProps> = ({
|
|
|
196
167
|
todos: management.todos,
|
|
197
168
|
updateTodos: management.updateTodos,
|
|
198
169
|
refreshTodos: management.refreshTodos,
|
|
199
|
-
paused: management.paused,
|
|
200
|
-
setPaused: management.setPaused,
|
|
201
170
|
loading: management.loading,
|
|
202
171
|
error: management.error,
|
|
203
172
|
}),
|
|
@@ -205,8 +174,6 @@ export const TodoProvider: React.FC<TodoProviderProps> = ({
|
|
|
205
174
|
management.todos,
|
|
206
175
|
management.updateTodos,
|
|
207
176
|
management.refreshTodos,
|
|
208
|
-
management.paused,
|
|
209
|
-
management.setPaused,
|
|
210
177
|
management.loading,
|
|
211
178
|
management.error,
|
|
212
179
|
],
|
|
@@ -28,6 +28,7 @@ export interface UIActions {
|
|
|
28
28
|
|
|
29
29
|
// Input actions
|
|
30
30
|
handleUserInputSubmit: (value: string) => void;
|
|
31
|
+
handleSteer: (text: string) => boolean;
|
|
31
32
|
handleClearScreen: () => void;
|
|
32
33
|
|
|
33
34
|
// Theme dialog
|
|
@@ -95,7 +96,7 @@ export interface UIActions {
|
|
|
95
96
|
exitToolsDialog: () => void;
|
|
96
97
|
|
|
97
98
|
// Folder trust dialog
|
|
98
|
-
handleFolderTrustSelect: (choice: FolderTrustChoice) => void
|
|
99
|
+
handleFolderTrustSelect: (choice: FolderTrustChoice) => Promise<void>;
|
|
99
100
|
|
|
100
101
|
// Welcome onboarding
|
|
101
102
|
welcomeActions: {
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
} from '@vybestack/llxprt-code-core';
|
|
26
26
|
import type { ToolInfo } from '@vybestack/llxprt-code-agents';
|
|
27
27
|
import type { SlashCommandRuntime } from '../cliUiRuntime.js';
|
|
28
|
+
import type { QueuedSubmission } from '../hooks/agentStream/types.js';
|
|
28
29
|
import type { SlashCommand, CommandContext } from '../commands/types.js';
|
|
29
30
|
|
|
30
31
|
import type { LoadedSettings } from '../../config/settings.js';
|
|
@@ -140,7 +141,6 @@ export interface UIState {
|
|
|
140
141
|
ctrlCPressedOnce: boolean;
|
|
141
142
|
ctrlDPressedOnce: boolean;
|
|
142
143
|
showEscapePrompt: boolean;
|
|
143
|
-
showIdeRestartPrompt: boolean;
|
|
144
144
|
quittingMessages: HistoryItem[] | null;
|
|
145
145
|
|
|
146
146
|
// Display options
|
|
@@ -148,6 +148,8 @@ export interface UIState {
|
|
|
148
148
|
showErrorDetails: boolean;
|
|
149
149
|
showToolDescriptions: boolean;
|
|
150
150
|
isTodoPanelCollapsed: boolean;
|
|
151
|
+
isQueuedMessagesPanelCollapsed: boolean;
|
|
152
|
+
queuedSubmissions: readonly QueuedSubmission[];
|
|
151
153
|
isNarrow: boolean;
|
|
152
154
|
vimModeEnabled: boolean;
|
|
153
155
|
vimMode: string | undefined;
|
|
@@ -200,7 +202,6 @@ export interface UIState {
|
|
|
200
202
|
currentIDE: IdeInfo | undefined;
|
|
201
203
|
|
|
202
204
|
// Trust
|
|
203
|
-
isRestarting: boolean;
|
|
204
205
|
isTrustedFolder: boolean;
|
|
205
206
|
|
|
206
207
|
// Welcome onboarding
|
|
@@ -342,30 +342,6 @@ export function deduplicateToolCallRequests(
|
|
|
342
342
|
});
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
/**
|
|
346
|
-
* Creates a ThinkingBlock from a thought event, deduplicating against existing blocks.
|
|
347
|
-
* Returns null if the thought is empty or already present in existingBlocks.
|
|
348
|
-
*/
|
|
349
|
-
export function buildThinkingBlock(
|
|
350
|
-
thoughtText: string,
|
|
351
|
-
existingBlocks: ThinkingBlock[],
|
|
352
|
-
): ThinkingBlock | null {
|
|
353
|
-
if (!thoughtText) {
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
const alreadyHasThought = existingBlocks.some(
|
|
357
|
-
(tb) => tb.thought === thoughtText,
|
|
358
|
-
);
|
|
359
|
-
if (alreadyHasThought) {
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
return {
|
|
363
|
-
type: 'thinking',
|
|
364
|
-
thought: thoughtText,
|
|
365
|
-
sourceField: 'thought',
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
345
|
/**
|
|
370
346
|
* Builds the full-split pending history item for the no-split case in
|
|
371
347
|
* handleContentEvent. Preserves the existing item's type and profileName.
|
|
@@ -15,6 +15,36 @@ import {
|
|
|
15
15
|
type HistoryItemWithoutId,
|
|
16
16
|
} from '../../types.js';
|
|
17
17
|
|
|
18
|
+
function findStreamBlockIndex(
|
|
19
|
+
blocks: ThinkingBlock[],
|
|
20
|
+
streamId: string | undefined,
|
|
21
|
+
): number {
|
|
22
|
+
if (streamId === undefined) {
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
return blocks.findIndex((block) => block.streamId === streamId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hasDuplicateNoStreamThought(
|
|
29
|
+
blocks: ThinkingBlock[],
|
|
30
|
+
thoughtText: string,
|
|
31
|
+
streamId: string | undefined,
|
|
32
|
+
): boolean {
|
|
33
|
+
return (
|
|
34
|
+
streamId === undefined &&
|
|
35
|
+
thoughtText !== '' &&
|
|
36
|
+
blocks.some(
|
|
37
|
+
(block) => block.streamId === undefined && block.thought === thoughtText,
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildThoughtText(thoughtSummary: ThoughtSummary): string {
|
|
43
|
+
return [thoughtSummary.subject, thoughtSummary.description]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(': ');
|
|
46
|
+
}
|
|
47
|
+
|
|
18
48
|
export function applyThoughtToState(
|
|
19
49
|
thoughtSummary: ThoughtSummary,
|
|
20
50
|
sanitizeContent: (text: string) => {
|
|
@@ -32,52 +62,102 @@ export function applyThoughtToState(
|
|
|
32
62
|
): void {
|
|
33
63
|
setLastAgentActivityTime(Date.now());
|
|
34
64
|
setThought(thoughtSummary);
|
|
35
|
-
let thoughtText =
|
|
36
|
-
.filter(Boolean)
|
|
37
|
-
.join(': ');
|
|
65
|
+
let thoughtText = buildThoughtText(thoughtSummary);
|
|
38
66
|
const sanitized = sanitizeContent(thoughtText);
|
|
39
67
|
thoughtText = sanitized.blocked ? '' : sanitized.text;
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
|
|
69
|
+
const streamBlockIndex = findStreamBlockIndex(
|
|
42
70
|
thinkingBlocksRef.current,
|
|
71
|
+
thoughtSummary.streamId,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (streamBlockIndex >= 0) {
|
|
75
|
+
const existingBlock = thinkingBlocksRef.current[streamBlockIndex];
|
|
76
|
+
const updatedThought = thoughtText || existingBlock.thought;
|
|
77
|
+
thinkingBlocksRef.current = thinkingBlocksRef.current.map((block, index) =>
|
|
78
|
+
index === streamBlockIndex
|
|
79
|
+
? {
|
|
80
|
+
...existingBlock,
|
|
81
|
+
thought: updatedThought,
|
|
82
|
+
...(thoughtSummary.streamStatus !== undefined
|
|
83
|
+
? { streamStatus: thoughtSummary.streamStatus }
|
|
84
|
+
: {}),
|
|
85
|
+
}
|
|
86
|
+
: block,
|
|
87
|
+
);
|
|
88
|
+
updatePendingWithThinking(
|
|
89
|
+
getContentPrefixIdentity,
|
|
90
|
+
thinkingBlocksRef,
|
|
91
|
+
setPendingHistoryItem,
|
|
92
|
+
);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
hasDuplicateNoStreamThought(
|
|
98
|
+
thinkingBlocksRef.current,
|
|
99
|
+
thoughtText,
|
|
100
|
+
thoughtSummary.streamId,
|
|
101
|
+
)
|
|
102
|
+
) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const thinkingBlock = createThinkingBlockFromSummary(
|
|
107
|
+
thoughtText,
|
|
108
|
+
thoughtSummary,
|
|
43
109
|
);
|
|
44
110
|
if (thinkingBlock) {
|
|
45
|
-
thinkingBlocksRef.current.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)?.profileName;
|
|
52
|
-
const profileName = liveProfileName ?? existingProfileName;
|
|
53
|
-
const itemType =
|
|
54
|
-
item?.type === 'gemini_content' ? 'gemini_content' : 'gemini';
|
|
55
|
-
return {
|
|
56
|
-
type: itemType,
|
|
57
|
-
text: item?.text ?? '',
|
|
58
|
-
...(profileName != null ? { profileName } : {}),
|
|
59
|
-
thinkingBlocks: [...thinkingBlocksRef.current],
|
|
60
|
-
};
|
|
61
|
-
});
|
|
111
|
+
thinkingBlocksRef.current = [...thinkingBlocksRef.current, thinkingBlock];
|
|
112
|
+
updatePendingWithThinking(
|
|
113
|
+
getContentPrefixIdentity,
|
|
114
|
+
thinkingBlocksRef,
|
|
115
|
+
setPendingHistoryItem,
|
|
116
|
+
);
|
|
62
117
|
}
|
|
63
118
|
}
|
|
64
119
|
|
|
65
|
-
function
|
|
120
|
+
function updatePendingWithThinking(
|
|
121
|
+
getContentPrefixIdentity: () => string | null,
|
|
122
|
+
thinkingBlocksRef: React.MutableRefObject<ThinkingBlock[]>,
|
|
123
|
+
setPendingHistoryItem: (
|
|
124
|
+
updater: (item: HistoryItemWithoutId | null) => HistoryItemWithoutId | null,
|
|
125
|
+
) => void,
|
|
126
|
+
): void {
|
|
127
|
+
const rawIdentity = getContentPrefixIdentity();
|
|
128
|
+
const liveProfileName = rawIdentity === '' ? null : rawIdentity;
|
|
129
|
+
setPendingHistoryItem((item) => {
|
|
130
|
+
const existingProfileName = (
|
|
131
|
+
item as HistoryItemAi | HistoryItemAiContent | undefined
|
|
132
|
+
)?.profileName;
|
|
133
|
+
const profileName = liveProfileName ?? existingProfileName;
|
|
134
|
+
const itemType =
|
|
135
|
+
item?.type === 'gemini_content' ? 'gemini_content' : 'gemini';
|
|
136
|
+
return {
|
|
137
|
+
type: itemType,
|
|
138
|
+
text: item?.text ?? '',
|
|
139
|
+
...(profileName != null ? { profileName } : {}),
|
|
140
|
+
thinkingBlocks: [...thinkingBlocksRef.current],
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createThinkingBlockFromSummary(
|
|
66
146
|
thoughtText: string,
|
|
67
|
-
|
|
147
|
+
thoughtSummary: ThoughtSummary,
|
|
68
148
|
): ThinkingBlock | null {
|
|
69
149
|
if (!thoughtText) {
|
|
70
150
|
return null;
|
|
71
151
|
}
|
|
72
|
-
const alreadyHasThought = existingBlocks.some(
|
|
73
|
-
(tb) => tb.thought === thoughtText,
|
|
74
|
-
);
|
|
75
|
-
if (alreadyHasThought) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
152
|
return {
|
|
79
153
|
type: 'thinking',
|
|
80
154
|
thought: thoughtText,
|
|
81
155
|
sourceField: 'thought',
|
|
156
|
+
...(thoughtSummary.streamId !== undefined
|
|
157
|
+
? { streamId: thoughtSummary.streamId }
|
|
158
|
+
: {}),
|
|
159
|
+
...(thoughtSummary.streamStatus !== undefined
|
|
160
|
+
? { streamStatus: thoughtSummary.streamStatus }
|
|
161
|
+
: {}),
|
|
82
162
|
};
|
|
83
163
|
}
|