@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
|
@@ -29,6 +29,7 @@ type StreamConsumerContext = {
|
|
|
29
29
|
config: Config;
|
|
30
30
|
jsonOutput: boolean;
|
|
31
31
|
streamJsonOutput: boolean;
|
|
32
|
+
quiet: boolean;
|
|
32
33
|
streamFormatter: StreamJsonFormatter | null;
|
|
33
34
|
emojiFilter: EmojiFilter | undefined;
|
|
34
35
|
createProfileNameWriter: () => () => void;
|
|
@@ -63,24 +64,35 @@ function emitStreamError(
|
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
type ThoughtBufferEntry = {
|
|
68
|
+
streamId?: string;
|
|
69
|
+
text: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type ThoughtBuffer = ThoughtBufferEntry[];
|
|
73
|
+
|
|
66
74
|
function flushThoughtBuffer(
|
|
67
|
-
thoughtBuffer:
|
|
75
|
+
thoughtBuffer: ThoughtBuffer,
|
|
68
76
|
includeThinking: boolean,
|
|
69
|
-
):
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
): ThoughtBuffer {
|
|
78
|
+
const thoughtText = thoughtBuffer
|
|
79
|
+
.map((entry) => entry.text.trim())
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.join(' ');
|
|
82
|
+
if (!includeThinking || !thoughtText) {
|
|
83
|
+
return [];
|
|
72
84
|
}
|
|
73
|
-
process.stdout.write(`<think>${
|
|
74
|
-
return
|
|
85
|
+
process.stdout.write(`<think>${thoughtText}</think>\n`);
|
|
86
|
+
return [];
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
function flushEmojiBuffer(
|
|
78
90
|
context: StreamConsumerContext,
|
|
79
|
-
|
|
91
|
+
responseText: string,
|
|
80
92
|
): string {
|
|
81
93
|
const remainingBuffered = context.emojiFilter?.flushBuffer();
|
|
82
94
|
if (!remainingBuffered) {
|
|
83
|
-
return
|
|
95
|
+
return responseText;
|
|
84
96
|
}
|
|
85
97
|
if (context.streamFormatter) {
|
|
86
98
|
context.streamFormatter.emitEvent({
|
|
@@ -90,22 +102,26 @@ function flushEmojiBuffer(
|
|
|
90
102
|
content: remainingBuffered,
|
|
91
103
|
delta: true,
|
|
92
104
|
});
|
|
93
|
-
return
|
|
105
|
+
return responseText;
|
|
94
106
|
}
|
|
95
107
|
if (context.jsonOutput) {
|
|
96
|
-
return
|
|
108
|
+
return responseText + remainingBuffered;
|
|
97
109
|
}
|
|
98
110
|
process.stdout.write(remainingBuffered);
|
|
99
|
-
return
|
|
111
|
+
return responseText;
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
function handleThinking(
|
|
103
|
-
thought: {
|
|
115
|
+
thought: {
|
|
116
|
+
subject?: string;
|
|
117
|
+
description?: string;
|
|
118
|
+
streamId?: string;
|
|
119
|
+
},
|
|
104
120
|
context: StreamConsumerContext,
|
|
105
121
|
writeProfileName: () => void,
|
|
106
|
-
thoughtBuffer:
|
|
122
|
+
thoughtBuffer: ThoughtBuffer,
|
|
107
123
|
includeThinking: boolean,
|
|
108
|
-
):
|
|
124
|
+
): ThoughtBuffer {
|
|
109
125
|
if (!includeThinking) {
|
|
110
126
|
return thoughtBuffer;
|
|
111
127
|
}
|
|
@@ -123,14 +139,31 @@ function handleThinking(
|
|
|
123
139
|
thoughtText = filterResult.filtered;
|
|
124
140
|
}
|
|
125
141
|
}
|
|
126
|
-
|
|
142
|
+
if (!thoughtText.trim()) {
|
|
143
|
+
return thoughtBuffer;
|
|
144
|
+
}
|
|
145
|
+
if (thought.streamId === undefined) {
|
|
146
|
+
return [...thoughtBuffer, { text: thoughtText }];
|
|
147
|
+
}
|
|
148
|
+
const existingIndex = thoughtBuffer.findIndex(
|
|
149
|
+
(entry) => entry.streamId === thought.streamId,
|
|
150
|
+
);
|
|
151
|
+
if (existingIndex < 0) {
|
|
152
|
+
return [
|
|
153
|
+
...thoughtBuffer,
|
|
154
|
+
{ streamId: thought.streamId, text: thoughtText },
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
return thoughtBuffer.map((entry, index) =>
|
|
158
|
+
index === existingIndex ? { ...entry, text: thoughtText } : entry,
|
|
159
|
+
);
|
|
127
160
|
}
|
|
128
161
|
|
|
129
162
|
function handleText(
|
|
130
163
|
text: string,
|
|
131
164
|
context: StreamConsumerContext,
|
|
132
165
|
writeProfileName: () => void,
|
|
133
|
-
|
|
166
|
+
responseText: string,
|
|
134
167
|
): string {
|
|
135
168
|
writeProfileName();
|
|
136
169
|
let outputValue = text;
|
|
@@ -142,7 +175,7 @@ function handleText(
|
|
|
142
175
|
'[Error: Response blocked due to emoji detection]\n',
|
|
143
176
|
);
|
|
144
177
|
}
|
|
145
|
-
return
|
|
178
|
+
return responseText;
|
|
146
179
|
}
|
|
147
180
|
outputValue =
|
|
148
181
|
typeof filterResult.filtered === 'string' ? filterResult.filtered : '';
|
|
@@ -160,13 +193,17 @@ function handleText(
|
|
|
160
193
|
delta: true,
|
|
161
194
|
});
|
|
162
195
|
}
|
|
163
|
-
return
|
|
196
|
+
return responseText;
|
|
164
197
|
}
|
|
165
198
|
if (context.jsonOutput) {
|
|
166
|
-
return
|
|
199
|
+
return responseText + outputValue;
|
|
167
200
|
}
|
|
168
201
|
process.stdout.write(outputValue);
|
|
169
|
-
return
|
|
202
|
+
return responseText;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function handleQuietText(text: string, state: StreamState): void {
|
|
206
|
+
state.quietTextBuffer += text;
|
|
170
207
|
}
|
|
171
208
|
|
|
172
209
|
function emitToolUse(
|
|
@@ -243,12 +280,20 @@ function displayToolResult(
|
|
|
243
280
|
|
|
244
281
|
function emitFinalResult(
|
|
245
282
|
context: StreamConsumerContext,
|
|
246
|
-
|
|
283
|
+
responseText: string,
|
|
247
284
|
startTime: number,
|
|
248
285
|
metrics: SessionMetrics,
|
|
249
286
|
finishReason?: 'refusal',
|
|
250
287
|
): void {
|
|
251
288
|
if (context.streamFormatter) {
|
|
289
|
+
if (context.quiet) {
|
|
290
|
+
context.streamFormatter.emitEvent({
|
|
291
|
+
type: JsonStreamEventType.MESSAGE,
|
|
292
|
+
timestamp: new Date().toISOString(),
|
|
293
|
+
role: 'assistant',
|
|
294
|
+
content: responseText,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
252
297
|
context.streamFormatter.emitEvent({
|
|
253
298
|
type: JsonStreamEventType.RESULT,
|
|
254
299
|
timestamp: new Date().toISOString(),
|
|
@@ -261,7 +306,7 @@ function emitFinalResult(
|
|
|
261
306
|
} else if (context.jsonOutput) {
|
|
262
307
|
const payload: JsonOutput = {
|
|
263
308
|
session_id: context.config.getSessionId(),
|
|
264
|
-
response:
|
|
309
|
+
response: responseText.trimEnd(),
|
|
265
310
|
stats: metrics,
|
|
266
311
|
};
|
|
267
312
|
if (finishReason !== undefined) {
|
|
@@ -269,7 +314,7 @@ function emitFinalResult(
|
|
|
269
314
|
}
|
|
270
315
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
271
316
|
} else {
|
|
272
|
-
process.stdout.write(
|
|
317
|
+
process.stdout.write(`${responseText}\n`);
|
|
273
318
|
}
|
|
274
319
|
}
|
|
275
320
|
|
|
@@ -300,8 +345,9 @@ function reconstructError(structured: StructuredError): Error {
|
|
|
300
345
|
function throwStructuredStreamError(
|
|
301
346
|
structured: StructuredError,
|
|
302
347
|
formatter: StreamJsonFormatter | null,
|
|
348
|
+
message = structured.message,
|
|
303
349
|
): never {
|
|
304
|
-
emitStreamError(formatter, 'error',
|
|
350
|
+
emitStreamError(formatter, 'error', message, structured);
|
|
305
351
|
const error = reconstructError(structured);
|
|
306
352
|
if (formatter !== null) {
|
|
307
353
|
markMachineErrorReported(error);
|
|
@@ -312,7 +358,7 @@ function throwStructuredStreamError(
|
|
|
312
358
|
function handleDone(
|
|
313
359
|
event: Extract<AgentEvent, { type: 'done' }>,
|
|
314
360
|
context: StreamConsumerContext,
|
|
315
|
-
|
|
361
|
+
responseText: string,
|
|
316
362
|
startTime: number,
|
|
317
363
|
getMetrics: () => SessionMetrics,
|
|
318
364
|
): void {
|
|
@@ -320,7 +366,7 @@ function handleDone(
|
|
|
320
366
|
case 'stop':
|
|
321
367
|
case 'loop-detected':
|
|
322
368
|
case 'context-overflow':
|
|
323
|
-
emitFinalResult(context,
|
|
369
|
+
emitFinalResult(context, responseText, startTime, getMetrics());
|
|
324
370
|
return;
|
|
325
371
|
case 'refusal': {
|
|
326
372
|
// @issue:2329 — surface the safety-classifier refusal as a user-visible
|
|
@@ -333,17 +379,25 @@ function handleDone(
|
|
|
333
379
|
// (no streamFormatter and no jsonOutput). In stream-json mode the
|
|
334
380
|
// structured warning event carries the signal; in plain-JSON mode the
|
|
335
381
|
// finish_reason field carries it.
|
|
336
|
-
|
|
382
|
+
//
|
|
383
|
+
// In quiet mode all warning emissions are suppressed (issue #728).
|
|
384
|
+
if (
|
|
385
|
+
!context.quiet &&
|
|
386
|
+
!context.jsonOutput &&
|
|
387
|
+
context.streamFormatter === null
|
|
388
|
+
) {
|
|
337
389
|
process.stderr.write(`WARNING: ${REFUSAL_NOTICE_MESSAGE}\n`);
|
|
338
390
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
391
|
+
if (!context.quiet) {
|
|
392
|
+
emitStreamError(
|
|
393
|
+
context.streamFormatter,
|
|
394
|
+
'warning',
|
|
395
|
+
REFUSAL_NOTICE_MESSAGE,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
344
398
|
emitFinalResult(
|
|
345
399
|
context,
|
|
346
|
-
|
|
400
|
+
responseText,
|
|
347
401
|
startTime,
|
|
348
402
|
getMetrics(),
|
|
349
403
|
'refusal',
|
|
@@ -351,15 +405,19 @@ function handleDone(
|
|
|
351
405
|
return;
|
|
352
406
|
}
|
|
353
407
|
case 'hook-stopped': {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
408
|
+
if (!context.quiet) {
|
|
409
|
+
const stop = event.stop;
|
|
410
|
+
const stopMessage = `Agent execution stopped: ${
|
|
411
|
+
stop?.systemMessage?.trim() ?? stop?.reason ?? ''
|
|
412
|
+
}`;
|
|
413
|
+
process.stderr.write(`${stopMessage}\n`);
|
|
414
|
+
}
|
|
359
415
|
return;
|
|
360
416
|
}
|
|
361
417
|
case 'aborted':
|
|
362
|
-
|
|
418
|
+
if (!context.quiet) {
|
|
419
|
+
debugLogger.error('Operation cancelled.');
|
|
420
|
+
}
|
|
363
421
|
return;
|
|
364
422
|
case 'max-turns':
|
|
365
423
|
throw new FatalTurnLimitedError(MAX_TURNS_MESSAGE);
|
|
@@ -376,8 +434,9 @@ function handleDone(
|
|
|
376
434
|
}
|
|
377
435
|
|
|
378
436
|
function finalizeStream(
|
|
379
|
-
thoughtBuffer:
|
|
380
|
-
|
|
437
|
+
thoughtBuffer: ThoughtBuffer,
|
|
438
|
+
responseText: string,
|
|
439
|
+
quietTextBuffer: string,
|
|
381
440
|
pendingDone: Extract<AgentEvent, { type: 'done' }> | null,
|
|
382
441
|
context: StreamConsumerContext,
|
|
383
442
|
includeThinking: boolean,
|
|
@@ -385,7 +444,9 @@ function finalizeStream(
|
|
|
385
444
|
getMetrics: () => SessionMetrics,
|
|
386
445
|
): void {
|
|
387
446
|
flushThoughtBuffer(thoughtBuffer, includeThinking);
|
|
388
|
-
const finalText =
|
|
447
|
+
const finalText = context.quiet
|
|
448
|
+
? filterQuietText(quietTextBuffer, context)
|
|
449
|
+
: flushEmojiBuffer(context, responseText);
|
|
389
450
|
if (pendingDone !== null) {
|
|
390
451
|
handleDone(pendingDone, context, finalText, startTime, getMetrics);
|
|
391
452
|
} else {
|
|
@@ -393,12 +454,56 @@ function finalizeStream(
|
|
|
393
454
|
}
|
|
394
455
|
}
|
|
395
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Applies the emoji filter to the fully accumulated quiet-mode text buffer.
|
|
459
|
+
* Uses filterText (not filterStreamChunk) because the buffer is complete text,
|
|
460
|
+
* not a partial streaming chunk — filterText handles full-string matching which
|
|
461
|
+
* is correct for finalized content.
|
|
462
|
+
*/
|
|
463
|
+
function filterQuietText(text: string, context: StreamConsumerContext): string {
|
|
464
|
+
if (!context.emojiFilter) {
|
|
465
|
+
return text;
|
|
466
|
+
}
|
|
467
|
+
const result = context.emojiFilter.filterText(text);
|
|
468
|
+
if (result.blocked) {
|
|
469
|
+
return '';
|
|
470
|
+
}
|
|
471
|
+
return typeof result.filtered === 'string' ? result.filtered : '';
|
|
472
|
+
}
|
|
473
|
+
|
|
396
474
|
interface StreamState {
|
|
397
|
-
thoughtBuffer:
|
|
398
|
-
|
|
475
|
+
thoughtBuffer: ThoughtBuffer;
|
|
476
|
+
responseText: string;
|
|
477
|
+
quietTextBuffer: string;
|
|
399
478
|
pendingDone: Extract<AgentEvent, { type: 'done' }> | null;
|
|
400
479
|
}
|
|
401
480
|
|
|
481
|
+
function handleQuietEvent(event: AgentEvent, state: StreamState): boolean {
|
|
482
|
+
switch (event.type) {
|
|
483
|
+
case 'text':
|
|
484
|
+
// Buffer text instead of writing immediately; only the final turn's
|
|
485
|
+
// text (after the last tool call) is emitted at stream completion.
|
|
486
|
+
handleQuietText(event.text, state);
|
|
487
|
+
return true;
|
|
488
|
+
case 'tool-call':
|
|
489
|
+
// Discard intermediate talk before tool calls so only the final
|
|
490
|
+
// response remains in the buffer (issue #728).
|
|
491
|
+
state.quietTextBuffer = '';
|
|
492
|
+
return true;
|
|
493
|
+
case 'tool-result':
|
|
494
|
+
// Suppress all tool result display in quiet mode.
|
|
495
|
+
return true;
|
|
496
|
+
case 'loop-detected':
|
|
497
|
+
// Suppress non-essential stream warnings/errors in quiet mode.
|
|
498
|
+
return true;
|
|
499
|
+
case 'hook-blocked':
|
|
500
|
+
// Suppress hook-blocked stderr messages in quiet mode.
|
|
501
|
+
return true;
|
|
502
|
+
default:
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
402
507
|
function dispatchAgentEvent(
|
|
403
508
|
event: AgentEvent,
|
|
404
509
|
state: StreamState,
|
|
@@ -406,6 +511,9 @@ function dispatchAgentEvent(
|
|
|
406
511
|
writeProfileName: () => void,
|
|
407
512
|
includeThinking: boolean,
|
|
408
513
|
): void {
|
|
514
|
+
if (context.quiet && handleQuietEvent(event, state)) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
409
517
|
switch (event.type) {
|
|
410
518
|
case 'thinking':
|
|
411
519
|
state.thoughtBuffer = handleThinking(
|
|
@@ -421,11 +529,11 @@ function dispatchAgentEvent(
|
|
|
421
529
|
state.thoughtBuffer,
|
|
422
530
|
includeThinking,
|
|
423
531
|
);
|
|
424
|
-
state.
|
|
532
|
+
state.responseText = handleText(
|
|
425
533
|
event.text,
|
|
426
534
|
context,
|
|
427
535
|
writeProfileName,
|
|
428
|
-
state.
|
|
536
|
+
state.responseText,
|
|
429
537
|
);
|
|
430
538
|
return;
|
|
431
539
|
case 'tool-call':
|
|
@@ -459,7 +567,14 @@ function dispatchAgentEvent(
|
|
|
459
567
|
return;
|
|
460
568
|
}
|
|
461
569
|
case 'idle-timeout':
|
|
462
|
-
|
|
570
|
+
if (context.quiet) {
|
|
571
|
+
throw reconstructError(event.error);
|
|
572
|
+
}
|
|
573
|
+
return throwStructuredStreamError(
|
|
574
|
+
event.error,
|
|
575
|
+
context.streamFormatter,
|
|
576
|
+
'Stream idle timeout: no response received within the allowed time.',
|
|
577
|
+
);
|
|
463
578
|
case 'error':
|
|
464
579
|
return throwStructuredStreamError(event.error, context.streamFormatter);
|
|
465
580
|
case 'done':
|
|
@@ -487,14 +602,18 @@ export async function processAgentStream(
|
|
|
487
602
|
startTime: number,
|
|
488
603
|
getMetrics: () => SessionMetrics,
|
|
489
604
|
): Promise<void> {
|
|
490
|
-
const writeProfileName = context.
|
|
605
|
+
const writeProfileName = context.quiet
|
|
606
|
+
? (): void => {}
|
|
607
|
+
: context.createProfileNameWriter();
|
|
491
608
|
const includeThinking =
|
|
609
|
+
!context.quiet &&
|
|
492
610
|
!context.jsonOutput &&
|
|
493
611
|
!context.streamJsonOutput &&
|
|
494
612
|
context.config.getEphemeralSetting('reasoning.includeInResponse') !== false;
|
|
495
613
|
const state: StreamState = {
|
|
496
|
-
thoughtBuffer:
|
|
497
|
-
|
|
614
|
+
thoughtBuffer: [],
|
|
615
|
+
responseText: '',
|
|
616
|
+
quietTextBuffer: '',
|
|
498
617
|
pendingDone: null,
|
|
499
618
|
};
|
|
500
619
|
for await (const event of events) {
|
|
@@ -508,7 +627,8 @@ export async function processAgentStream(
|
|
|
508
627
|
}
|
|
509
628
|
finalizeStream(
|
|
510
629
|
state.thoughtBuffer,
|
|
511
|
-
state.
|
|
630
|
+
state.responseText,
|
|
631
|
+
state.quietTextBuffer,
|
|
512
632
|
state.pendingDone,
|
|
513
633
|
context,
|
|
514
634
|
includeThinking,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
type ToolSchedulerContract,
|
|
29
29
|
hasInteractiveSubagentScheduler,
|
|
30
30
|
DEFAULT_AGENT_ID,
|
|
31
|
-
type
|
|
31
|
+
type LiveOutputUpdate,
|
|
32
32
|
type MessageBus,
|
|
33
33
|
} from '@vybestack/llxprt-code-core';
|
|
34
34
|
import { DebugLogger } from '@vybestack/llxprt-code-telemetry';
|
|
@@ -149,7 +149,7 @@ export type SchedulerRefs = {
|
|
|
149
149
|
updateToolCallOutput: (
|
|
150
150
|
schedulerId: symbol,
|
|
151
151
|
toolCallId: string,
|
|
152
|
-
|
|
152
|
+
update: LiveOutputUpdate,
|
|
153
153
|
) => void;
|
|
154
154
|
replaceToolCallsForScheduler: (
|
|
155
155
|
schedulerId: symbol,
|
|
@@ -179,9 +179,9 @@ function createMainSchedulerCallbacks(
|
|
|
179
179
|
SchedulerConfigWithExplicitMessageBus['getOrCreateScheduler']
|
|
180
180
|
>[1] {
|
|
181
181
|
return {
|
|
182
|
-
outputUpdateHandler: (toolCallId,
|
|
182
|
+
outputUpdateHandler: (toolCallId, update) => {
|
|
183
183
|
if (!mounted.current) return;
|
|
184
|
-
refs.updateToolCallOutput(mainSchedulerId, toolCallId,
|
|
184
|
+
refs.updateToolCallOutput(mainSchedulerId, toolCallId, update);
|
|
185
185
|
refs.setLastToolOutputTime(Date.now());
|
|
186
186
|
},
|
|
187
187
|
onAllToolCallsComplete: async (completedToolCalls) => {
|
|
@@ -214,8 +214,8 @@ function createSubagentCallbacks(
|
|
|
214
214
|
SchedulerConfigWithExplicitMessageBus['getOrCreateScheduler']
|
|
215
215
|
>[1] {
|
|
216
216
|
return {
|
|
217
|
-
outputUpdateHandler: (toolCallId,
|
|
218
|
-
refs.updateToolCallOutput(schedulerId, toolCallId,
|
|
217
|
+
outputUpdateHandler: (toolCallId, update) => {
|
|
218
|
+
refs.updateToolCallOutput(schedulerId, toolCallId, update);
|
|
219
219
|
refs.setLastToolOutputTime(Date.now());
|
|
220
220
|
},
|
|
221
221
|
onToolCallsUpdate: (calls) => {
|
|
@@ -37,6 +37,7 @@ import { loggingCommand } from '../ui/commands/loggingCommand.js';
|
|
|
37
37
|
import { uiprofileCommand } from '../ui/commands/uiprofileCommand.js';
|
|
38
38
|
import { mouseCommand } from '../ui/commands/mouseCommand.js';
|
|
39
39
|
import { quitCommand } from '../ui/commands/quitCommand.js';
|
|
40
|
+
import { quotaCommand } from '../ui/commands/quotaCommand.js';
|
|
40
41
|
import { restoreCommand } from '../ui/commands/restoreCommand.js';
|
|
41
42
|
import { statsCommand } from '../ui/commands/statsCommand.js';
|
|
42
43
|
import { themeCommand } from '../ui/commands/themeCommand.js';
|
|
@@ -154,34 +155,11 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
|
|
154
155
|
...(isDevelopment ? [uiprofileCommand] : []),
|
|
155
156
|
quitCommand,
|
|
156
157
|
restoreCommand(this.config),
|
|
158
|
+
quotaCommand,
|
|
157
159
|
statsCommand,
|
|
158
160
|
themeCommand,
|
|
159
161
|
toolsCommand,
|
|
160
|
-
|
|
161
|
-
...((): SlashCommand[] => {
|
|
162
|
-
if (this.config?.isSkillsSupportEnabled() !== true) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
if (this.config.getSkillManager().isAdminEnabled() === false) {
|
|
166
|
-
return [
|
|
167
|
-
{
|
|
168
|
-
name: 'skills',
|
|
169
|
-
description: 'Manage skills',
|
|
170
|
-
kind: CommandKind.BUILT_IN,
|
|
171
|
-
autoExecute: false,
|
|
172
|
-
subCommands: [],
|
|
173
|
-
action: async (
|
|
174
|
-
_context: CommandContext,
|
|
175
|
-
): Promise<MessageActionReturn> => ({
|
|
176
|
-
type: 'message',
|
|
177
|
-
messageType: 'error',
|
|
178
|
-
content: 'Skills are disabled by your admin.',
|
|
179
|
-
}),
|
|
180
|
-
},
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
return [skillsCommand];
|
|
184
|
-
})(),
|
|
162
|
+
...this.resolveSkillsCommand(),
|
|
185
163
|
settingsCommand,
|
|
186
164
|
vimCommand,
|
|
187
165
|
providerCommand,
|
|
@@ -215,4 +193,34 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
|
|
215
193
|
|
|
216
194
|
return allDefinitions.filter((cmd): cmd is SlashCommand => cmd !== null);
|
|
217
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Determine skills command based on config and admin settings.
|
|
199
|
+
* Returns [] when skills support is disabled, a stub error command when
|
|
200
|
+
* disabled by admin, or [skillsCommand] when enabled.
|
|
201
|
+
*/
|
|
202
|
+
private resolveSkillsCommand(): SlashCommand[] {
|
|
203
|
+
if (this.config?.isSkillsSupportEnabled() !== true) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
if (this.config.getSkillManager().isAdminEnabled() === false) {
|
|
207
|
+
return [
|
|
208
|
+
{
|
|
209
|
+
name: 'skills',
|
|
210
|
+
description: 'Manage skills',
|
|
211
|
+
kind: CommandKind.BUILT_IN,
|
|
212
|
+
autoExecute: false,
|
|
213
|
+
subCommands: [],
|
|
214
|
+
action: async (
|
|
215
|
+
_context: CommandContext,
|
|
216
|
+
): Promise<MessageActionReturn> => ({
|
|
217
|
+
type: 'message',
|
|
218
|
+
messageType: 'error',
|
|
219
|
+
content: 'Skills are disabled by your admin.',
|
|
220
|
+
}),
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
return [skillsCommand];
|
|
225
|
+
}
|
|
218
226
|
}
|
|
@@ -9,7 +9,6 @@ import path from 'path';
|
|
|
9
9
|
import toml from '@iarna/toml';
|
|
10
10
|
import { glob } from 'glob';
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
-
import type { CliUiRuntime } from '../ui/cliUiRuntime.js';
|
|
13
12
|
import { Storage } from '@vybestack/llxprt-code-settings';
|
|
14
13
|
import { debugLogger } from '@vybestack/llxprt-code-telemetry';
|
|
15
14
|
import type { ICommandLoader } from './types.js';
|
|
@@ -36,6 +35,21 @@ interface CommandDirectory {
|
|
|
36
35
|
extensionName?: string;
|
|
37
36
|
}
|
|
38
37
|
|
|
38
|
+
export interface FileCommandRuntime {
|
|
39
|
+
readonly storage?: Storage;
|
|
40
|
+
getProjectRoot(): string;
|
|
41
|
+
getExtensions(): Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
isActive: boolean;
|
|
44
|
+
path: string;
|
|
45
|
+
}>;
|
|
46
|
+
getFolderTrust(): boolean;
|
|
47
|
+
isTrustedFolder(): boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const FILE_COMMANDS_UNTRUSTED_MESSAGE =
|
|
51
|
+
'File-based commands are disabled because this folder is not trusted.';
|
|
52
|
+
|
|
39
53
|
/**
|
|
40
54
|
* Defines the Zod schema for a command definition file. This serves as the
|
|
41
55
|
* single source of truth for both validation and type inference.
|
|
@@ -60,12 +74,8 @@ const TomlCommandDefSchema = z.object({
|
|
|
60
74
|
*/
|
|
61
75
|
export class FileCommandLoader implements ICommandLoader {
|
|
62
76
|
private readonly projectRoot: string;
|
|
63
|
-
private readonly folderTrustEnabled: boolean;
|
|
64
|
-
private readonly isTrustedFolder: boolean;
|
|
65
77
|
|
|
66
|
-
constructor(private readonly config:
|
|
67
|
-
this.folderTrustEnabled = config?.getFolderTrust() === true;
|
|
68
|
-
this.isTrustedFolder = config?.isTrustedFolder() === true;
|
|
78
|
+
constructor(private readonly config: FileCommandRuntime | null) {
|
|
69
79
|
this.projectRoot = config?.getProjectRoot() ?? process.cwd();
|
|
70
80
|
}
|
|
71
81
|
|
|
@@ -81,7 +91,7 @@ export class FileCommandLoader implements ICommandLoader {
|
|
|
81
91
|
* @returns A promise that resolves to an array of all loaded SlashCommands.
|
|
82
92
|
*/
|
|
83
93
|
async loadCommands(signal: AbortSignal): Promise<SlashCommand[]> {
|
|
84
|
-
if (
|
|
94
|
+
if (!this.canUseFileCommands()) {
|
|
85
95
|
return [];
|
|
86
96
|
}
|
|
87
97
|
|
|
@@ -131,6 +141,15 @@ export class FileCommandLoader implements ICommandLoader {
|
|
|
131
141
|
return allCommands;
|
|
132
142
|
}
|
|
133
143
|
|
|
144
|
+
private canUseFileCommands(): boolean {
|
|
145
|
+
const config = this.config;
|
|
146
|
+
return (
|
|
147
|
+
config == null ||
|
|
148
|
+
config.getFolderTrust() !== true ||
|
|
149
|
+
config.isTrustedFolder() === true
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
134
153
|
/**
|
|
135
154
|
* Get all command directories in order for loading.
|
|
136
155
|
* User commands → Project commands → Extension commands
|
|
@@ -299,6 +318,14 @@ export class FileCommandLoader implements ICommandLoader {
|
|
|
299
318
|
context: CommandContext,
|
|
300
319
|
_args: string,
|
|
301
320
|
): Promise<SlashCommandActionReturn> => {
|
|
321
|
+
if (!this.canUseFileCommands()) {
|
|
322
|
+
return {
|
|
323
|
+
type: 'message',
|
|
324
|
+
messageType: 'error',
|
|
325
|
+
content: FILE_COMMANDS_UNTRUSTED_MESSAGE,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
302
329
|
if (!context.invocation) {
|
|
303
330
|
debugLogger.error(
|
|
304
331
|
`[FileCommandLoader] Critical error: Command '${baseCommandName}' was executed without invocation context.`,
|
package/src/test-utils/async.ts
CHANGED
|
@@ -6,6 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
import { act } from 'react';
|
|
8
8
|
|
|
9
|
+
export interface Deferred<T> {
|
|
10
|
+
readonly promise: Promise<T>;
|
|
11
|
+
readonly resolve: (value: T) => void;
|
|
12
|
+
readonly reject: (error: Error) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createDeferred<T>(): Deferred<T> {
|
|
16
|
+
let resolvePromise: ((value: T) => void) | undefined;
|
|
17
|
+
let rejectPromise: ((error: Error) => void) | undefined;
|
|
18
|
+
const promise = new Promise<T>((resolve, reject) => {
|
|
19
|
+
resolvePromise = resolve;
|
|
20
|
+
rejectPromise = reject;
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
promise,
|
|
24
|
+
resolve: (value: T) => resolvePromise?.(value),
|
|
25
|
+
reject: (error: Error) => rejectPromise?.(error),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
// The waitFor from vitest doesn't properly wrap in act(), so we have to
|
|
10
30
|
// implement our own like the one in @testing-library/react
|
|
11
31
|
// or @testing-library/react-native
|
|
@@ -10,6 +10,7 @@ import React, { act, createContext, useContext } from 'react';
|
|
|
10
10
|
import { LoadedSettings, type Settings } from '../config/settings.js';
|
|
11
11
|
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
|
|
12
12
|
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
|
|
13
|
+
import { ShellCommandDisplayProvider } from '../ui/contexts/ShellCommandDisplayContext.js';
|
|
13
14
|
import { UIStateContext, type UIState } from '../ui/contexts/UIStateContext.js';
|
|
14
15
|
import { StreamingState } from '../ui/types.js';
|
|
15
16
|
|
|
@@ -172,7 +173,15 @@ export const renderWithProviders = (
|
|
|
172
173
|
<SettingsContext.Provider value={settings}>
|
|
173
174
|
<UIStateContext.Provider value={uiState as UIState}>
|
|
174
175
|
<MockRuntimeContextProvider>
|
|
175
|
-
<KeypressProvider>
|
|
176
|
+
<KeypressProvider>
|
|
177
|
+
<ShellCommandDisplayProvider
|
|
178
|
+
alwaysDisplayFullShellCommand={
|
|
179
|
+
settings.merged.ui.alwaysDisplayFullShellCommand ?? true
|
|
180
|
+
}
|
|
181
|
+
>
|
|
182
|
+
{component}
|
|
183
|
+
</ShellCommandDisplayProvider>
|
|
184
|
+
</KeypressProvider>
|
|
176
185
|
</MockRuntimeContextProvider>
|
|
177
186
|
</UIStateContext.Provider>
|
|
178
187
|
</SettingsContext.Provider>,
|