@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
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { useCallback, useEffect, useRef } from 'react';
|
|
15
15
|
import type { Agent } from '@vybestack/llxprt-code-agents';
|
|
16
|
+
import { debugLogger } from '@vybestack/llxprt-code-telemetry';
|
|
16
17
|
import {
|
|
17
18
|
type MessageSenderType,
|
|
18
19
|
type RecordingIntegration,
|
|
@@ -41,6 +42,23 @@ import {
|
|
|
41
42
|
import type { QueuedSubmission } from './types.js';
|
|
42
43
|
import type { StreamRuntime } from '../../cliUiRuntime.js';
|
|
43
44
|
|
|
45
|
+
export type SubmissionDisposition =
|
|
46
|
+
| 'consumed'
|
|
47
|
+
| 'requeue'
|
|
48
|
+
| 'requeue-await-event';
|
|
49
|
+
|
|
50
|
+
const MAX_QUEUED_SUBMISSION_RETRIES = 3;
|
|
51
|
+
// A one-second pause avoids a tight failure loop while keeping transient
|
|
52
|
+
// submitter-initialization races responsive.
|
|
53
|
+
const QUEUED_SUBMISSION_RETRY_DELAY_MS = 1000;
|
|
54
|
+
|
|
55
|
+
export type SubmissionExecutor = (
|
|
56
|
+
query: AgentRequestInput,
|
|
57
|
+
options?: { isContinuation: boolean },
|
|
58
|
+
prompt_id?: string,
|
|
59
|
+
fromQueue?: boolean,
|
|
60
|
+
) => Promise<SubmissionDisposition>;
|
|
61
|
+
|
|
44
62
|
/**
|
|
45
63
|
* Shared content-prefix identity resolver for the AgentEvent dispatcher. Reads
|
|
46
64
|
* fresh runtime state at call time so a single stable reference can be reused.
|
|
@@ -76,6 +94,12 @@ export interface UseSubmitQueryDeps {
|
|
|
76
94
|
thinkingBlocksRef: React.MutableRefObject<ThinkingBlock[]>;
|
|
77
95
|
turnCancelledRef: React.MutableRefObject<boolean>;
|
|
78
96
|
queuedSubmissionsRef: React.MutableRefObject<QueuedSubmission[]>;
|
|
97
|
+
enqueueSubmission: (submission: QueuedSubmission) => void;
|
|
98
|
+
requeueSubmission: (submission: QueuedSubmission) => void;
|
|
99
|
+
dequeueSubmission: () => QueuedSubmission | undefined;
|
|
100
|
+
clearSubmissions: () => void;
|
|
101
|
+
tryReserveDrain: () => boolean;
|
|
102
|
+
releaseDrain: () => void;
|
|
79
103
|
setPendingHistoryItem: React.Dispatch<
|
|
80
104
|
React.SetStateAction<HistoryItemWithoutId | null>
|
|
81
105
|
>;
|
|
@@ -116,14 +140,7 @@ export interface UseSubmitQueryDeps {
|
|
|
116
140
|
) => Promise<void>)
|
|
117
141
|
| null
|
|
118
142
|
>;
|
|
119
|
-
submitQueryRef: React.MutableRefObject<
|
|
120
|
-
| ((
|
|
121
|
-
query: AgentRequestInput,
|
|
122
|
-
options?: { isContinuation: boolean },
|
|
123
|
-
prompt_id?: string,
|
|
124
|
-
) => Promise<void>)
|
|
125
|
-
| null
|
|
126
|
-
>;
|
|
143
|
+
submitQueryRef: React.MutableRefObject<SubmissionExecutor | null>;
|
|
127
144
|
isResponding: boolean;
|
|
128
145
|
streamingState: StreamingState;
|
|
129
146
|
}
|
|
@@ -155,6 +172,7 @@ export interface UseSubmitQueryReturn {
|
|
|
155
172
|
|
|
156
173
|
export function useSubmitQuery(deps: UseSubmitQueryDeps): UseSubmitQueryReturn {
|
|
157
174
|
const { startNewPrompt, getPromptCount } = useSessionStats();
|
|
175
|
+
const activeTurnRef = useRef(false);
|
|
158
176
|
|
|
159
177
|
const handlers = useStreamEventHandlers({
|
|
160
178
|
runtime: deps.runtime,
|
|
@@ -168,7 +186,7 @@ export function useSubmitQuery(deps: UseSubmitQueryDeps): UseSubmitQueryReturn {
|
|
|
168
186
|
pendingHistoryItemRef: deps.pendingHistoryItemRef,
|
|
169
187
|
thinkingBlocksRef: deps.thinkingBlocksRef,
|
|
170
188
|
turnCancelledRef: deps.turnCancelledRef,
|
|
171
|
-
|
|
189
|
+
clearSubmissions: deps.clearSubmissions,
|
|
172
190
|
setPendingHistoryItem: deps.setPendingHistoryItem,
|
|
173
191
|
setIsResponding: deps.setIsResponding,
|
|
174
192
|
setThought: deps.setThought,
|
|
@@ -187,19 +205,28 @@ export function useSubmitQuery(deps: UseSubmitQueryDeps): UseSubmitQueryReturn {
|
|
|
187
205
|
|
|
188
206
|
const processAgentEvent = useProcessAgentEvent(deps, handlers);
|
|
189
207
|
|
|
190
|
-
const scheduleNextQueuedSubmission = useScheduleNext(deps);
|
|
208
|
+
const scheduleNextQueuedSubmission = useScheduleNext(deps, activeTurnRef);
|
|
191
209
|
|
|
192
|
-
const
|
|
210
|
+
const executeSubmission = useSubmitQueryCallback({
|
|
193
211
|
...deps,
|
|
194
212
|
displayUserMessage: handlers.displayUserMessage,
|
|
195
213
|
prepareQueryForAgent: handlers.prepareQueryForAgent,
|
|
196
214
|
handleLoopDetectedEvent: handlers.handleLoopDetectedEvent,
|
|
197
|
-
scheduleNextQueuedSubmission,
|
|
198
215
|
startNewPrompt,
|
|
199
216
|
getPromptCount,
|
|
217
|
+
activeTurnRef,
|
|
218
|
+
scheduleNextQueuedSubmission,
|
|
200
219
|
});
|
|
220
|
+
const submitQuery = useCallback<UseSubmitQueryReturn['submitQuery']>(
|
|
221
|
+
async (query, options, promptId) => {
|
|
222
|
+
// Dispositions are an internal queue-drain protocol; the public API
|
|
223
|
+
// intentionally remains Promise<void> for existing callers.
|
|
224
|
+
await executeSubmission(query, options, promptId);
|
|
225
|
+
},
|
|
226
|
+
[executeSubmission],
|
|
227
|
+
);
|
|
201
228
|
|
|
202
|
-
useSubmitQueryEffects(deps,
|
|
229
|
+
useSubmitQueryEffects(deps, executeSubmission, scheduleNextQueuedSubmission);
|
|
203
230
|
|
|
204
231
|
return {
|
|
205
232
|
submitQuery,
|
|
@@ -263,26 +290,36 @@ function useSubmitQueryEffects(
|
|
|
263
290
|
submitQuery: ReturnType<typeof useSubmitQueryCallback>,
|
|
264
291
|
scheduleNextQueuedSubmission: () => void,
|
|
265
292
|
) {
|
|
293
|
+
const { submitQueryRef, streamingState, runtime, enqueueSubmission } = deps;
|
|
266
294
|
useEffect(() => {
|
|
267
|
-
|
|
268
|
-
}, [submitQuery,
|
|
295
|
+
submitQueryRef.current = submitQuery;
|
|
296
|
+
}, [submitQuery, submitQueryRef]);
|
|
269
297
|
|
|
270
298
|
useEffect(() => {
|
|
271
|
-
if (
|
|
299
|
+
if (streamingState === StreamingState.Idle) {
|
|
272
300
|
scheduleNextQueuedSubmission();
|
|
273
301
|
}
|
|
274
|
-
}, [
|
|
302
|
+
}, [streamingState, scheduleNextQueuedSubmission]);
|
|
275
303
|
|
|
276
304
|
useEffect(() => {
|
|
277
|
-
const
|
|
305
|
+
const unsubscribe = runtime.events.onMcpClientUpdate(
|
|
306
|
+
scheduleNextQueuedSubmission,
|
|
307
|
+
);
|
|
308
|
+
return () => {
|
|
309
|
+
unsubscribe();
|
|
310
|
+
};
|
|
311
|
+
}, [runtime, scheduleNextQueuedSubmission]);
|
|
312
|
+
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
const isAgentBusy = () => streamingState !== StreamingState.Idle;
|
|
278
315
|
const triggerAgentTurn = async (message: string) => {
|
|
279
|
-
|
|
316
|
+
enqueueSubmission({
|
|
280
317
|
query: [{ type: 'text', text: message }],
|
|
281
318
|
});
|
|
282
319
|
scheduleNextQueuedSubmission();
|
|
283
320
|
};
|
|
284
321
|
|
|
285
|
-
const unsubscribe =
|
|
322
|
+
const unsubscribe = runtime.asyncTasks.setupAsyncTaskAutoTrigger(
|
|
286
323
|
isAgentBusy,
|
|
287
324
|
triggerAgentTurn,
|
|
288
325
|
);
|
|
@@ -291,32 +328,206 @@ function useSubmitQueryEffects(
|
|
|
291
328
|
unsubscribe();
|
|
292
329
|
};
|
|
293
330
|
}, [
|
|
294
|
-
|
|
295
|
-
|
|
331
|
+
runtime,
|
|
332
|
+
streamingState,
|
|
296
333
|
scheduleNextQueuedSubmission,
|
|
297
|
-
|
|
334
|
+
enqueueSubmission,
|
|
335
|
+
]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function useDrainCleanup(
|
|
339
|
+
drainTimeoutRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
|
|
340
|
+
retryTimeoutRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
|
|
341
|
+
pendingSubmissionRef: React.MutableRefObject<QueuedSubmission | null>,
|
|
342
|
+
mountedRef: React.MutableRefObject<boolean>,
|
|
343
|
+
requeueSubmission: (submission: QueuedSubmission) => void,
|
|
344
|
+
releaseDrain: () => void,
|
|
345
|
+
) {
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
mountedRef.current = true;
|
|
348
|
+
return () => {
|
|
349
|
+
mountedRef.current = false;
|
|
350
|
+
if (drainTimeoutRef.current !== null) {
|
|
351
|
+
clearTimeout(drainTimeoutRef.current);
|
|
352
|
+
drainTimeoutRef.current = null;
|
|
353
|
+
}
|
|
354
|
+
if (retryTimeoutRef.current !== null) {
|
|
355
|
+
clearTimeout(retryTimeoutRef.current);
|
|
356
|
+
retryTimeoutRef.current = null;
|
|
357
|
+
}
|
|
358
|
+
if (pendingSubmissionRef.current !== null) {
|
|
359
|
+
requeueSubmission(pendingSubmissionRef.current);
|
|
360
|
+
pendingSubmissionRef.current = null;
|
|
361
|
+
releaseDrain();
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}, [
|
|
365
|
+
drainTimeoutRef,
|
|
366
|
+
retryTimeoutRef,
|
|
367
|
+
mountedRef,
|
|
368
|
+
pendingSubmissionRef,
|
|
369
|
+
releaseDrain,
|
|
370
|
+
requeueSubmission,
|
|
298
371
|
]);
|
|
299
372
|
}
|
|
300
373
|
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
374
|
+
function useDrainSubmission(
|
|
375
|
+
drainTimeoutRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
|
|
376
|
+
retryTimeoutRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
|
|
377
|
+
pendingSubmissionRef: React.MutableRefObject<QueuedSubmission | null>,
|
|
378
|
+
retryCountRef: React.MutableRefObject<number>,
|
|
379
|
+
scheduleRef: React.MutableRefObject<() => void>,
|
|
380
|
+
submitQueryRef: UseSubmitQueryDeps['submitQueryRef'],
|
|
381
|
+
requeueSubmission: UseSubmitQueryDeps['requeueSubmission'],
|
|
382
|
+
releaseDrain: UseSubmitQueryDeps['releaseDrain'],
|
|
383
|
+
) {
|
|
384
|
+
return useCallback(
|
|
385
|
+
async (next: QueuedSubmission): Promise<void> => {
|
|
386
|
+
drainTimeoutRef.current = null;
|
|
387
|
+
let disposition: SubmissionDisposition = 'requeue-await-event';
|
|
388
|
+
try {
|
|
389
|
+
const submit = submitQueryRef.current;
|
|
390
|
+
if (submit === null) {
|
|
391
|
+
debugLogger.error(
|
|
392
|
+
'Cannot drain queued submission before the submitter is ready.',
|
|
393
|
+
);
|
|
394
|
+
} else {
|
|
395
|
+
disposition = await submit(
|
|
396
|
+
next.query,
|
|
397
|
+
next.options,
|
|
398
|
+
next.promptId,
|
|
399
|
+
true,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
} catch (error: unknown) {
|
|
403
|
+
debugLogger.error('Queued submission failed unexpectedly:', error);
|
|
404
|
+
} finally {
|
|
405
|
+
const ownsPendingSubmission = pendingSubmissionRef.current === next;
|
|
406
|
+
const shouldRetry =
|
|
407
|
+
ownsPendingSubmission &&
|
|
408
|
+
disposition === 'requeue-await-event' &&
|
|
409
|
+
retryCountRef.current < MAX_QUEUED_SUBMISSION_RETRIES;
|
|
410
|
+
const retryLimitReached =
|
|
411
|
+
ownsPendingSubmission &&
|
|
412
|
+
disposition === 'requeue-await-event' &&
|
|
413
|
+
!shouldRetry;
|
|
414
|
+
|
|
415
|
+
if (ownsPendingSubmission) {
|
|
416
|
+
pendingSubmissionRef.current = null;
|
|
417
|
+
if (disposition !== 'consumed' && !retryLimitReached) {
|
|
418
|
+
requeueSubmission(next);
|
|
419
|
+
}
|
|
420
|
+
releaseDrain();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (shouldRetry) {
|
|
424
|
+
retryCountRef.current += 1;
|
|
425
|
+
retryTimeoutRef.current = setTimeout(() => {
|
|
426
|
+
retryTimeoutRef.current = null;
|
|
427
|
+
scheduleRef.current();
|
|
428
|
+
}, QUEUED_SUBMISSION_RETRY_DELAY_MS);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (ownsPendingSubmission && !shouldRetry) {
|
|
432
|
+
retryCountRef.current = 0;
|
|
433
|
+
if (retryLimitReached) {
|
|
434
|
+
debugLogger.error(
|
|
435
|
+
'Dropping queued submission after repeated drain failures.',
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
scheduleRef.current();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
[
|
|
443
|
+
drainTimeoutRef,
|
|
444
|
+
retryTimeoutRef,
|
|
445
|
+
pendingSubmissionRef,
|
|
446
|
+
retryCountRef,
|
|
447
|
+
scheduleRef,
|
|
448
|
+
submitQueryRef,
|
|
449
|
+
requeueSubmission,
|
|
450
|
+
releaseDrain,
|
|
451
|
+
],
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function useScheduleNext(
|
|
456
|
+
deps: UseSubmitQueryDeps,
|
|
457
|
+
activeTurnRef: React.MutableRefObject<boolean>,
|
|
458
|
+
) {
|
|
459
|
+
const {
|
|
460
|
+
queuedSubmissionsRef,
|
|
461
|
+
dequeueSubmission,
|
|
462
|
+
requeueSubmission,
|
|
463
|
+
submitQueryRef,
|
|
464
|
+
tryReserveDrain,
|
|
465
|
+
releaseDrain,
|
|
466
|
+
} = deps;
|
|
467
|
+
const drainTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
468
|
+
const retryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
469
|
+
const pendingSubmissionRef = useRef<QueuedSubmission | null>(null);
|
|
470
|
+
const retryCountRef = useRef(0);
|
|
471
|
+
const mountedRef = useRef(true);
|
|
472
|
+
// The mutable indirection breaks the schedule/drain callback cycle while
|
|
473
|
+
// allowing delayed retries to invoke the latest stable scheduler.
|
|
474
|
+
const scheduleRef = useRef<() => void>(() => undefined);
|
|
475
|
+
const streamingStateRef = useRef(deps.streamingState);
|
|
476
|
+
streamingStateRef.current = deps.streamingState;
|
|
477
|
+
useDrainCleanup(
|
|
478
|
+
drainTimeoutRef,
|
|
479
|
+
retryTimeoutRef,
|
|
480
|
+
pendingSubmissionRef,
|
|
481
|
+
mountedRef,
|
|
482
|
+
requeueSubmission,
|
|
483
|
+
releaseDrain,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const drainSubmission = useDrainSubmission(
|
|
487
|
+
drainTimeoutRef,
|
|
488
|
+
retryTimeoutRef,
|
|
489
|
+
pendingSubmissionRef,
|
|
490
|
+
retryCountRef,
|
|
491
|
+
scheduleRef,
|
|
492
|
+
submitQueryRef,
|
|
493
|
+
requeueSubmission,
|
|
494
|
+
releaseDrain,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
const schedule = useCallback(() => {
|
|
498
|
+
const cannotDrain =
|
|
499
|
+
!mountedRef.current ||
|
|
500
|
+
activeTurnRef.current ||
|
|
501
|
+
streamingStateRef.current !== StreamingState.Idle ||
|
|
502
|
+
queuedSubmissionsRef.current.length === 0;
|
|
503
|
+
if (cannotDrain || !tryReserveDrain()) {
|
|
304
504
|
return;
|
|
305
505
|
}
|
|
506
|
+
if (retryTimeoutRef.current !== null) {
|
|
507
|
+
clearTimeout(retryTimeoutRef.current);
|
|
508
|
+
retryTimeoutRef.current = null;
|
|
509
|
+
}
|
|
306
510
|
|
|
307
|
-
const next =
|
|
511
|
+
const next = dequeueSubmission();
|
|
308
512
|
if (!next) {
|
|
513
|
+
releaseDrain();
|
|
309
514
|
return;
|
|
310
515
|
}
|
|
311
|
-
|
|
312
|
-
setTimeout(() => {
|
|
313
|
-
void
|
|
314
|
-
next.query,
|
|
315
|
-
next.options,
|
|
316
|
-
next.promptId,
|
|
317
|
-
);
|
|
516
|
+
pendingSubmissionRef.current = next;
|
|
517
|
+
drainTimeoutRef.current = setTimeout(() => {
|
|
518
|
+
void drainSubmission(next);
|
|
318
519
|
}, 0);
|
|
319
|
-
}, [
|
|
520
|
+
}, [
|
|
521
|
+
activeTurnRef,
|
|
522
|
+
queuedSubmissionsRef,
|
|
523
|
+
retryTimeoutRef,
|
|
524
|
+
dequeueSubmission,
|
|
525
|
+
drainSubmission,
|
|
526
|
+
tryReserveDrain,
|
|
527
|
+
releaseDrain,
|
|
528
|
+
]);
|
|
529
|
+
scheduleRef.current = schedule;
|
|
530
|
+
return schedule;
|
|
320
531
|
}
|
|
321
532
|
|
|
322
533
|
interface SubmitQueryCallbackDeps extends UseSubmitQueryDeps {
|
|
@@ -331,39 +542,57 @@ interface SubmitQueryCallbackDeps extends UseSubmitQueryDeps {
|
|
|
331
542
|
shouldProceed: boolean;
|
|
332
543
|
}>;
|
|
333
544
|
handleLoopDetectedEvent: () => void;
|
|
334
|
-
scheduleNextQueuedSubmission: () => void;
|
|
335
545
|
startNewPrompt: () => void;
|
|
336
546
|
getPromptCount: () => number;
|
|
547
|
+
activeTurnRef: React.MutableRefObject<boolean>;
|
|
548
|
+
scheduleNextQueuedSubmission: () => void;
|
|
337
549
|
}
|
|
338
550
|
|
|
339
551
|
function useSubmitQueryCallback(cbd: SubmitQueryCallbackDeps) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
552
|
+
// Keep the callback identity stable while every invocation reads current deps.
|
|
553
|
+
const latestCbdRef = useRef(cbd);
|
|
554
|
+
latestCbdRef.current = cbd;
|
|
555
|
+
return useCallback<SubmissionExecutor>(
|
|
556
|
+
async (query, options, promptId, fromQueue = false) => {
|
|
557
|
+
const current = latestCbdRef.current;
|
|
346
558
|
// submitQuery handles NEW user prompts only; the Agent's event stream
|
|
347
559
|
// drives multi-turn continuation internally.
|
|
348
560
|
void options;
|
|
349
561
|
|
|
350
|
-
if (
|
|
351
|
-
|
|
562
|
+
if (
|
|
563
|
+
current.activeTurnRef.current ||
|
|
564
|
+
isQueueable(current.streamingState)
|
|
565
|
+
) {
|
|
566
|
+
if (fromQueue) {
|
|
567
|
+
return 'requeue';
|
|
568
|
+
}
|
|
569
|
+
current.enqueueSubmission({
|
|
352
570
|
query,
|
|
353
|
-
promptId
|
|
571
|
+
promptId,
|
|
354
572
|
});
|
|
355
|
-
return;
|
|
573
|
+
return 'consumed';
|
|
356
574
|
}
|
|
357
575
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
576
|
+
current.activeTurnRef.current = true;
|
|
577
|
+
try {
|
|
578
|
+
const turn = initTurn(current, query, promptId, current.getPromptCount);
|
|
579
|
+
if (shouldDisplayUserMessage(turn.trimmedStr)) {
|
|
580
|
+
current.displayUserMessage(
|
|
581
|
+
turn.trimmedStr,
|
|
582
|
+
turn.userMessageTimestamp,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
await runSubmitQueryCore(current, query, turn);
|
|
587
|
+
return 'consumed';
|
|
588
|
+
} finally {
|
|
589
|
+
current.activeTurnRef.current = false;
|
|
590
|
+
// The active-turn owner schedules only after the turn settles. The
|
|
591
|
+
// scheduler's Idle check and reservation then prevent overlapping work.
|
|
592
|
+
current.scheduleNextQueuedSubmission();
|
|
362
593
|
}
|
|
363
|
-
|
|
364
|
-
await runSubmitQueryCore(cbd, query, turn);
|
|
365
594
|
},
|
|
366
|
-
[
|
|
595
|
+
[],
|
|
367
596
|
);
|
|
368
597
|
}
|
|
369
598
|
|
|
@@ -379,7 +608,6 @@ async function runSubmitQueryCore(
|
|
|
379
608
|
turn.promptId,
|
|
380
609
|
);
|
|
381
610
|
if (!shouldProceed || queryToSend === null) {
|
|
382
|
-
cbd.scheduleNextQueuedSubmission();
|
|
383
611
|
return;
|
|
384
612
|
}
|
|
385
613
|
|
|
@@ -409,11 +637,8 @@ async function runSubmitQueryCore(
|
|
|
409
637
|
);
|
|
410
638
|
}
|
|
411
639
|
} finally {
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
// with a fresh AbortController; if the signals differ, the newer turn
|
|
415
|
-
// already set isResponding(true) and clearing it here would cancel the
|
|
416
|
-
// new turn (issue #2259).
|
|
640
|
+
// A superseded turn no longer owns responding state; stale cleanup here
|
|
641
|
+
// would clear the newer turn's active indicator.
|
|
417
642
|
if (isCurrentTurn(cbd, turn)) {
|
|
418
643
|
cbd.setIsResponding(false);
|
|
419
644
|
}
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
removeMCPStatusChangeListener,
|
|
16
16
|
} from '@vybestack/llxprt-code-mcp';
|
|
17
17
|
import {
|
|
18
|
+
CoreEvent,
|
|
19
|
+
coreEvents,
|
|
18
20
|
GitService,
|
|
19
21
|
IdeClient,
|
|
20
22
|
Logger,
|
|
@@ -265,11 +267,13 @@ function subscribeToExternalCommandChanges(
|
|
|
265
267
|
client.addStatusChangeListener(listener);
|
|
266
268
|
});
|
|
267
269
|
addMCPStatusChangeListener(listener);
|
|
270
|
+
coreEvents.on(CoreEvent.FolderTrustChanged, listener);
|
|
268
271
|
return () => {
|
|
269
272
|
void IdeClient.getInstance().then((client) => {
|
|
270
273
|
client.removeStatusChangeListener(listener);
|
|
271
274
|
});
|
|
272
275
|
removeMCPStatusChangeListener(listener);
|
|
276
|
+
coreEvents.off(CoreEvent.FolderTrustChanged, listener);
|
|
273
277
|
};
|
|
274
278
|
}
|
|
275
279
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { useState, useEffect } from 'react';
|
|
8
8
|
import { ApprovalMode, type Agent } from '@vybestack/llxprt-code-agents';
|
|
9
|
+
import { coreEvents, CoreEvent } from '@vybestack/llxprt-code-core';
|
|
9
10
|
import { useKeypress } from './useKeypress.js';
|
|
10
11
|
import { keyMatchers, Command } from '../keyMatchers.js';
|
|
11
12
|
import type { HistoryItemWithoutId } from '../types.js';
|
|
@@ -32,6 +33,18 @@ export function useAutoAcceptIndicator({
|
|
|
32
33
|
setShowAutoAcceptIndicator(currentMode);
|
|
33
34
|
}, [currentMode]);
|
|
34
35
|
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const handleFolderTrustChanged = (trusted: boolean): void => {
|
|
38
|
+
setShowAutoAcceptIndicator(
|
|
39
|
+
trusted ? agent.getApprovalMode() : ApprovalMode.DEFAULT,
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
coreEvents.on(CoreEvent.FolderTrustChanged, handleFolderTrustChanged);
|
|
43
|
+
return () => {
|
|
44
|
+
coreEvents.off(CoreEvent.FolderTrustChanged, handleFolderTrustChanged);
|
|
45
|
+
};
|
|
46
|
+
}, [agent]);
|
|
47
|
+
|
|
35
48
|
useKeypress(
|
|
36
49
|
(key) => {
|
|
37
50
|
let nextApprovalMode: ApprovalMode | undefined;
|