feat(cli): decouple history filtering from tool output layout

- Introduce 'ui.enableCompactToolOutput' (default: true) to control tool output layout.
- Separated the overloaded 'output.verbosity' setting into two distinct concerns: 'output.verbosity' now strictly filters history content, while 'ui.enableCompactToolOutput' toggles between dense and boxed layouts.
- Update useGeminiStream and ToolGroupMessage to respect the new architectural separation.
This commit is contained in:
Jarrod Whelan
2026-02-10 03:12:41 -08:00
parent 5f99cbe560
commit 6d053e4227
4 changed files with 52 additions and 22 deletions

View File

@@ -606,6 +606,16 @@ const SETTINGS_SCHEMA = {
description: 'Show the spinner during operations.',
showInDialog: true,
},
enableCompactToolOutput: {
type: 'boolean',
label: 'Enable Compact Tool Output',
category: 'UI',
requiresRestart: false,
default: true,
description:
'Render tool outputs in a compact, single-line format when possible.',
showInDialog: true,
},
customWittyPhrases: {
type: 'array',
label: 'Custom Witty Phrases',

View File

@@ -66,7 +66,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
const config = useConfig();
const { constrainHeight } = useUIState();
const { merged: settings } = useSettings();
const isVerboseMode = settings.output?.verbosity === 'verbose';
const compactMode = settings.ui.enableCompactToolOutput;
const isEventDriven = config.isEventDrivenSchedulerEnabled();
@@ -74,18 +74,30 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
// pre-execution states (Confirming, Pending) from the History log.
// They live in the Global Queue or wait for their turn.
const visibleToolCalls = useMemo(() => {
if (!isEventDriven) {
return toolCalls;
}
// Only show tools that are actually running or finished.
// We explicitly exclude Pending and Confirming to ensure they only
// appear in the Global Queue until they are approved and start executing.
return toolCalls.filter(
(t) =>
t.status !== ToolCallStatus.Pending &&
t.status !== ToolCallStatus.Confirming,
);
}, [toolCalls, isEventDriven]);
// Standard filtering for Event Driven mode
const filteredTools = isEventDriven
? toolCalls.filter(
(t) =>
t.status !== ToolCallStatus.Pending &&
t.status !== ToolCallStatus.Confirming,
)
: toolCalls;
// Additional filtering for compact mode:
// In compact mode, we hide 'Pending' tools from the history log to avoid flickering
// unless we are in a non-compact (boxed) view where we want to show the placeholder.
return filteredTools.filter((tool) => {
const isShellToolCall = isShellTool(tool.name);
const useDenseView =
compactMode &&
!isShellToolCall &&
tool.status !== ToolCallStatus.Confirming;
if (!useDenseView) return true;
// In dense view, we only show tools that have started or finished.
return tool.status !== ToolCallStatus.Pending;
});
}, [toolCalls, isEventDriven, compactMode]);
const isEmbeddedShellFocused = visibleToolCalls.some((t) =>
isThisShellFocused(
@@ -172,9 +184,9 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
const isFirst = index === 0;
const isShellToolCall = isShellTool(tool.name);
// Use dense view if not verbose, not a shell tool (for interactivity), and not confirming (needs prompt)
// Use dense view if compact mode is enabled, not a shell tool (for interactivity), and not confirming (needs prompt)
const useDenseView =
!isVerboseMode &&
compactMode &&
!isShellToolCall &&
tool.status !== ToolCallStatus.Confirming;
@@ -268,7 +280,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
// HOWEVER, if borderBottomOverride is true, it means the scheduler explicitly
// wants to close a box. Since dense tools don't have boxes, this must be closing
// a non-dense (e.g. shell) tool box. So we must allow it.
if (!isVerboseMode && borderBottomOverride !== true) return null;
if (compactMode && borderBottomOverride !== true) return null;
if (borderBottomOverride !== undefined) {
return (
@@ -292,8 +304,8 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
const isShell = isShellTool(lastTool.name);
const isConfirming = lastTool.status === ToolCallStatus.Confirming;
// Logic: If dense view (not verbose, not shell, not confirming), hide border by default
const isDense = !isVerboseMode && !isShell && !isConfirming;
// Logic: If dense view (compact mode, not shell, not confirming), hide border by default
const isDense = compactMode && !isShell && !isConfirming;
if (isDense) return null;
@@ -319,7 +331,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
/>
);
})()}
{!isVerboseMode
{compactMode
? null
: (borderBottomOverride ?? true) &&
visibleToolCalls.length > 0 && (

View File

@@ -358,7 +358,7 @@ export const useGeminiStream = (
addItem,
]);
const isVerboseMode = settings.merged.output?.verbosity === 'verbose';
const enableCompactToolOutput = settings.merged.ui.enableCompactToolOutput;
const pendingToolGroupItems = useMemo((): HistoryItemWithoutId[] => {
const remainingTools = toolCalls.filter(
@@ -381,7 +381,8 @@ export const useGeminiStream = (
// Once all tools are terminal and pushed, the last history item handles the closing border.
// NOTE: In dense mode, we skip this if there are no shell tools (which require boxes).
const requiresBoxLayout =
isVerboseMode || toolCalls.some((tc) => isShellTool(tc.request.name));
!enableCompactToolOutput ||
toolCalls.some((tc) => isShellTool(tc.request.name));
if (!requiresBoxLayout) {
return items;
@@ -431,7 +432,7 @@ export const useGeminiStream = (
}
return items;
}, [toolCalls, pushedToolCallIds, isVerboseMode]);
}, [toolCalls, pushedToolCallIds, enableCompactToolOutput]);
const activeToolPtyId = useMemo(() => {
const executingShellTool = toolCalls.find(

View File

@@ -359,6 +359,13 @@
"default": true,
"type": "boolean"
},
"enableCompactToolOutput": {
"title": "Enable Compact Tool Output",
"description": "Render tool outputs in a compact, single-line format when possible.",
"markdownDescription": "Render tool outputs in a compact, single-line format when possible.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`",
"default": true,
"type": "boolean"
},
"customWittyPhrases": {
"title": "Custom Witty Phrases",
"description": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.",