@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.
Files changed (126) hide show
  1. package/package.json +13 -13
  2. package/src/commands/mcp/add.ts +8 -4
  3. package/src/config/cliArgParser.ts +2 -0
  4. package/src/config/config.ts +2 -0
  5. package/src/config/configBuilder.ts +3 -0
  6. package/src/config/configError.ts +23 -0
  7. package/src/config/extensions/extensionEnablement.ts +62 -1
  8. package/src/config/interactiveContext.ts +2 -1
  9. package/src/config/intermediateConfig.ts +4 -0
  10. package/src/config/keyBindings.ts +29 -3
  11. package/src/config/policy.ts +7 -1
  12. package/src/config/postConfigRuntime.ts +20 -14
  13. package/src/config/settings-schema/schema-ui.ts +9 -0
  14. package/src/config/settingsLoader.ts +21 -17
  15. package/src/config/settingsSchema.ts +2 -2
  16. package/src/config/trustedFolders.ts +275 -58
  17. package/src/config/yargsOptions.ts +7 -0
  18. package/src/generated/git-commit.json +1 -1
  19. package/src/nonInteractiveCli.ts +1 -0
  20. package/src/nonInteractiveCliSupport.ts +171 -51
  21. package/src/runtime/interactiveToolScheduler.ts +6 -6
  22. package/src/services/BuiltinCommandLoader.ts +33 -25
  23. package/src/services/FileCommandLoader.ts +34 -7
  24. package/src/test-utils/async.ts +20 -0
  25. package/src/test-utils/render.tsx +10 -1
  26. package/src/ui/App.tsx +17 -10
  27. package/src/ui/AppContainerRuntime.tsx +7 -9
  28. package/src/ui/cliUiRuntime.ts +28 -0
  29. package/src/ui/commands/authCommand.ts +240 -3
  30. package/src/ui/commands/compressCommand.ts +9 -1
  31. package/src/ui/commands/directoryCommand.tsx +1 -1
  32. package/src/ui/commands/logoutCommand.ts +2 -2
  33. package/src/ui/commands/permissionsCommand.ts +77 -20
  34. package/src/ui/commands/policiesCommand.ts +6 -1
  35. package/src/ui/commands/quotaCommand.ts +501 -0
  36. package/src/ui/commands/statsCommand.ts +2 -2
  37. package/src/ui/commands/statsQuota.ts +1 -1
  38. package/src/ui/commands/subagentCommand.ts +59 -27
  39. package/src/ui/components/AuthDialog.tsx +3 -3
  40. package/src/ui/components/Composer.tsx +1 -0
  41. package/src/ui/components/DialogManager.tsx +9 -2
  42. package/src/ui/components/FolderTrustDialog.tsx +71 -60
  43. package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
  44. package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
  45. package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
  46. package/src/ui/components/inputPromptHooks.ts +2 -0
  47. package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
  48. package/src/ui/components/inputPromptTypes.ts +6 -0
  49. package/src/ui/components/messages/AiMessage.tsx +24 -12
  50. package/src/ui/components/messages/ToolMessage.tsx +26 -40
  51. package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
  52. package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
  53. package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
  54. package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
  55. package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
  56. package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
  57. package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
  58. package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
  59. package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
  60. package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
  61. package/src/ui/contexts/KeypressContext.tsx +13 -2
  62. package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
  63. package/src/ui/contexts/TodoContext.tsx +0 -13
  64. package/src/ui/contexts/TodoProvider.tsx +1 -34
  65. package/src/ui/contexts/UIActionsContext.tsx +2 -1
  66. package/src/ui/contexts/UIStateContext.tsx +3 -2
  67. package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
  68. package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
  69. package/src/ui/hooks/agentStream/types.ts +1 -0
  70. package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
  71. package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
  72. package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
  73. package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
  74. package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
  75. package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
  76. package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
  77. package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
  78. package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
  79. package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
  80. package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
  81. package/src/ui/hooks/useFolderTrust.ts +118 -81
  82. package/src/ui/hooks/useGitBranchName.ts +79 -33
  83. package/src/ui/hooks/useIdeTrustListener.ts +5 -19
  84. package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
  85. package/src/ui/hooks/useReactToolScheduler.ts +13 -17
  86. package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
  87. package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
  88. package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
  89. package/src/ui/layouts/InlineContent.tsx +198 -0
  90. package/src/ui/trustDialogHelpers.ts +190 -0
  91. package/src/utils/cleanup-state.ts +115 -0
  92. package/src/utils/cleanup.ts +32 -34
  93. package/src/utils/sandbox-containers.ts +183 -53
  94. package/src/utils/sandbox-exec.ts +9 -3
  95. package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
  96. package/src/zed-integration/acp-types.ts +35 -0
  97. package/src/zed-integration/fileSystemService.ts +1 -1
  98. package/src/zed-integration/runZedIntegration.ts +142 -0
  99. package/src/zed-integration/zed-agent-event-handler.ts +179 -0
  100. package/src/zed-integration/zed-agent-setup.ts +43 -0
  101. package/src/zed-integration/zed-command-registry.ts +167 -0
  102. package/src/zed-integration/zed-config-options.ts +280 -0
  103. package/src/zed-integration/zed-content-utils.ts +2 -2
  104. package/src/zed-integration/zed-helpers.ts +132 -0
  105. package/src/zed-integration/zed-initialize.ts +64 -0
  106. package/src/zed-integration/zed-plan-update.ts +20 -0
  107. package/src/zed-integration/zed-prompt-command.ts +77 -0
  108. package/src/zed-integration/zed-session-config.ts +119 -0
  109. package/src/zed-integration/zed-session-errors.ts +337 -0
  110. package/src/zed-integration/zed-session-events.ts +216 -0
  111. package/src/zed-integration/zed-session-info.ts +255 -0
  112. package/src/zed-integration/zed-session-lifecycle.ts +186 -0
  113. package/src/zed-integration/zed-session-listing.ts +153 -0
  114. package/src/zed-integration/zed-session-loader.ts +193 -0
  115. package/src/zed-integration/zed-session-pagination.ts +240 -0
  116. package/src/zed-integration/zed-session-replay.ts +564 -0
  117. package/src/zed-integration/zed-stream-batcher.ts +236 -0
  118. package/src/zed-integration/zed-terminal-manager.ts +416 -0
  119. package/src/zed-integration/zed-terminal-setup.ts +72 -0
  120. package/src/zed-integration/zed-test-helpers.ts +349 -4
  121. package/src/zed-integration/zed-tool-handler.ts +47 -5
  122. package/src/zed-integration/zedIntegration.ts +538 -587
  123. package/src/services/todo-continuation/todoContinuationService.ts +0 -610
  124. package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
  125. package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
  126. 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
- const { exit, display, shell, copyMode, ideContext, mcp } = params;
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
- (key: Key) => {
263
- // Priority 1: Copy mode (highest priority - immediate exit from copy mode)
264
- if (handleCopyModeKey(key, copyMode, mouseStateBeforeCopyModeRef)) {
265
- return;
266
- }
267
-
268
- // Priority 2: Exit keys (Ctrl+C/D)
269
- if (handleExitKeys(key, exit)) {
270
- return;
271
- }
272
-
273
- // Calculate constrain height mode transition
274
- let enteringConstrainHeightMode = false;
275
- if (!display.constrainHeight) {
276
- enteringConstrainHeightMode = true;
277
- display.setConstrainHeight(true);
278
- }
279
-
280
- // Priority 3: Display toggles
281
- handleDisplayKeys(key, display, mcp, enteringConstrainHeightMode);
282
-
283
- // Priority 4: IDE and shell keys
284
- handleIdeAndShellKeys(key, ideContext, shell);
285
-
286
- // Handle IDE status command separately (needs handleSlashCommand)
287
- if (
288
- keyMatchers[Command.SHOW_IDE_CONTEXT_DETAIL](key) &&
289
- ideContext.getIdeMode() &&
290
- ideContext.ideContextState !== undefined &&
291
- ideContext.ideContextState !== null
292
- ) {
293
- void display.handleSlashCommand('/ide status');
294
- }
295
- },
296
- [exit, display, shell, copyMode, ideContext, mcp],
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 (codeNumber === '\\'.charCodeAt(0))
267
- return { name: '\\', shift, meta, ctrl };
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
- const setPaused = useCallback(
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 = [thoughtSummary.subject, thoughtSummary.description]
36
- .filter(Boolean)
37
- .join(': ');
65
+ let thoughtText = buildThoughtText(thoughtSummary);
38
66
  const sanitized = sanitizeContent(thoughtText);
39
67
  thoughtText = sanitized.blocked ? '' : sanitized.text;
40
- const thinkingBlock = buildThinkingBlock(
41
- thoughtText,
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.push(thinkingBlock);
46
- const rawIdentity = getContentPrefixIdentity();
47
- const liveProfileName = rawIdentity === '' ? null : rawIdentity;
48
- setPendingHistoryItem((item) => {
49
- const existingProfileName = (
50
- item as HistoryItemAi | HistoryItemAiContent | undefined
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 buildThinkingBlock(
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
- existingBlocks: ThinkingBlock[],
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
  }
@@ -21,4 +21,5 @@ export interface QueuedSubmission {
21
21
  query: AgentRequestInput;
22
22
  options?: { isContinuation: boolean };
23
23
  promptId?: string;
24
+ readonly queueId?: number;
24
25
  }