@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
@@ -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: string,
75
+ thoughtBuffer: ThoughtBuffer,
68
76
  includeThinking: boolean,
69
- ): string {
70
- if (!includeThinking || !thoughtBuffer.trim()) {
71
- return '';
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>${thoughtBuffer.trim()}</think>\n`);
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
- jsonResponseText: string,
91
+ responseText: string,
80
92
  ): string {
81
93
  const remainingBuffered = context.emojiFilter?.flushBuffer();
82
94
  if (!remainingBuffered) {
83
- return jsonResponseText;
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 jsonResponseText;
105
+ return responseText;
94
106
  }
95
107
  if (context.jsonOutput) {
96
- return jsonResponseText + remainingBuffered;
108
+ return responseText + remainingBuffered;
97
109
  }
98
110
  process.stdout.write(remainingBuffered);
99
- return jsonResponseText;
111
+ return responseText;
100
112
  }
101
113
 
102
114
  function handleThinking(
103
- thought: { subject?: string; description?: string },
115
+ thought: {
116
+ subject?: string;
117
+ description?: string;
118
+ streamId?: string;
119
+ },
104
120
  context: StreamConsumerContext,
105
121
  writeProfileName: () => void,
106
- thoughtBuffer: string,
122
+ thoughtBuffer: ThoughtBuffer,
107
123
  includeThinking: boolean,
108
- ): string {
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
- return thoughtBuffer ? `${thoughtBuffer} ${thoughtText}` : thoughtText;
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
- jsonResponseText: string,
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 jsonResponseText;
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 jsonResponseText;
196
+ return responseText;
164
197
  }
165
198
  if (context.jsonOutput) {
166
- return jsonResponseText + outputValue;
199
+ return responseText + outputValue;
167
200
  }
168
201
  process.stdout.write(outputValue);
169
- return jsonResponseText;
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
- jsonResponseText: string,
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: jsonResponseText.trimEnd(),
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('\n');
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', structured.message, structured);
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
- jsonResponseText: string,
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, jsonResponseText, startTime, getMetrics());
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
- if (!context.jsonOutput && context.streamFormatter === null) {
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
- emitStreamError(
340
- context.streamFormatter,
341
- 'warning',
342
- REFUSAL_NOTICE_MESSAGE,
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
- jsonResponseText,
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
- const stop = event.stop;
355
- const stopMessage = `Agent execution stopped: ${
356
- stop?.systemMessage?.trim() ?? stop?.reason ?? ''
357
- }`;
358
- process.stderr.write(`${stopMessage}\n`);
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
- debugLogger.error('Operation cancelled.');
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: string,
380
- jsonResponseText: string,
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 = flushEmojiBuffer(context, jsonResponseText);
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: string;
398
- jsonResponseText: string;
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.jsonResponseText = handleText(
532
+ state.responseText = handleText(
425
533
  event.text,
426
534
  context,
427
535
  writeProfileName,
428
- state.jsonResponseText,
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
- return throwStructuredStreamError(event.error, context.streamFormatter);
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.createProfileNameWriter();
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
- jsonResponseText: '',
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.jsonResponseText,
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 AnsiOutput,
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
- chunk: string | AnsiOutput,
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, chunk) => {
182
+ outputUpdateHandler: (toolCallId, update) => {
183
183
  if (!mounted.current) return;
184
- refs.updateToolCallOutput(mainSchedulerId, toolCallId, chunk);
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, chunk) => {
218
- refs.updateToolCallOutput(schedulerId, toolCallId, chunk);
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
- // Determine skills command based on config and admin settings
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: CliUiRuntime | null) {
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 (this.folderTrustEnabled && !this.isTrustedFolder) {
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.`,
@@ -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>{component}</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>,