feat(cli): core logic for dense tool output and history merging

- Update useGeminiStream to detect dense tool batches (non-verbose, non-shell).
- Update useHistoryManager to merge consecutive tool_group items into a single history entry.
- Add unit tests for history manager merging logic.
This commit is contained in:
Jarrod Whelan
2026-02-10 00:40:56 -08:00
parent 142ccf2140
commit e88d4ffe97
3 changed files with 39 additions and 1 deletions

View File

@@ -80,6 +80,8 @@ import { useSessionStats } from '../contexts/SessionContext.js';
import { useKeypress } from './useKeypress.js';
import type { LoadedSettings } from '../../config/settings.js';
import { isShellTool } from '../components/messages/ToolShared.js';
type ToolResponseWithParts = ToolCallResponseInfo & {
llmContent?: PartListUnion;
};
@@ -356,6 +358,8 @@ export const useGeminiStream = (
addItem,
]);
const isVerboseMode = settings.merged.output?.verbosity === 'verbose';
const pendingToolGroupItems = useMemo((): HistoryItemWithoutId[] => {
const remainingTools = toolCalls.filter(
(tc) => !pushedToolCallIds.has(tc.request.callId),
@@ -375,6 +379,14 @@ export const useGeminiStream = (
// Always show a bottom border slice if we have ANY tools in the batch
// and we haven't finished pushing the whole batch to history yet.
// 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));
if (!requiresBoxLayout) {
return items;
}
const allTerminal =
toolCalls.length > 0 &&
toolCalls.every(
@@ -419,7 +431,7 @@ export const useGeminiStream = (
}
return items;
}, [toolCalls, pushedToolCallIds]);
}, [toolCalls, pushedToolCallIds, isVerboseMode]);
const activeToolPtyId = useMemo(() => {
const executingShellTool = toolCalls.find(

View File

@@ -323,4 +323,29 @@ describe('useHistoryManager', () => {
expect(thirdItem.type).toBe('tool_group');
expect(thirdItem.tools).toHaveLength(1);
});
it('should update borderBottom when merging tool groups', () => {
const { result } = renderHook(() => useHistory());
const timestamp = Date.now();
const toolGroup1: HistoryItemWithoutId = {
type: 'tool_group',
tools: [{ callId: '1', name: 'tool-a' } as IndividualToolCallDisplay],
borderBottom: false,
};
const toolGroup2: HistoryItemWithoutId = {
type: 'tool_group',
tools: [{ callId: '2', name: 'tool-b' } as IndividualToolCallDisplay],
borderBottom: true,
};
act(() => {
result.current.addItem(toolGroup1, timestamp);
result.current.addItem(toolGroup2, timestamp + 1);
});
expect(result.current.history).toHaveLength(1);
const mergedItem = result.current.history[0] as HistoryItemToolGroup;
expect(mergedItem.tools).toHaveLength(2);
expect(mergedItem.borderBottom).toBe(true);
});
});

View File

@@ -78,6 +78,7 @@ export function useHistory({
const updatedLastItem: HistoryItem = {
...lastItem,
tools: [...lastItem.tools, ...newItem.tools],
borderBottom: newItem.borderBottom,
};
return [...prevHistory.slice(0, -1), updatedLastItem];
}