Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 82 additions & 2 deletions src/web-ui/src/flow_chat/store/modernFlowChatStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,26 @@ vi.mock('./FlowChatStore', () => ({
}));

vi.mock('../tool-cards/toolCardMetadata', () => ({
isCollapsibleTool: (toolName: string) => ['Read', 'LS', 'Grep', 'Glob', 'WebSearch', 'Bash', 'Git'].includes(toolName),
isCollapsibleTool: (toolName: string) => [
'Read',
'LS',
'Grep',
'Glob',
'WebSearch',
'WebFetch',
'GetFileDiff',
'GetToolSpec',
'ReviewSessionSummary',
'TerminalControl',
'SessionControl',
'ExecControl',
'view_image',
'ReadCanvas',
'ControlHub',
].includes(toolName),
READ_TOOL_NAMES: new Set(['Read']),
SEARCH_TOOL_NAMES: new Set(['Grep', 'Glob', 'WebSearch']),
COMMAND_TOOL_NAMES: new Set(['Bash', 'Git']),
COMMAND_TOOL_NAMES: new Set(),
}));

import { sessionToVirtualItems, type VirtualItem } from './modernFlowChatStore';
Expand Down Expand Up @@ -136,6 +152,70 @@ describe('sessionToVirtualItems explore grouping', () => {
expect(items.map(item => item.type)).toEqual(['user-message', 'explore-group']);
});

it.each([
'WebFetch',
'GetFileDiff',
'GetToolSpec',
'ReviewSessionSummary',
'TerminalControl',
'SessionControl',
'ExecControl',
'view_image',
'ReadCanvas',
'ControlHub',
])('collects non-critical %s rounds into explore groups', (toolName) => {
const session = makeSession({
sessionId: `non-critical-${toolName}`,
dialogTurns: [{
id: 'turn-1',
sessionId: `non-critical-${toolName}`,
userMessage: {
id: 'user-1',
content: 'Help',
timestamp: 900,
},
modelRounds: [makeRound({
items: [makeTool(`tool-${toolName}`, toolName)],
})],
status: 'completed',
startTime: 900,
}],
});

expect(sessionToVirtualItems(session).map(item => item.type)).toEqual([
'user-message',
'explore-group',
]);
});

it.each(['Bash', 'Git', 'ExecCommand', 'TodoWrite', 'ContextCompression', 'Skill', 'SessionMessage'])(
'keeps conditionally important %s rounds visible',
(toolName) => {
const session = makeSession({
sessionId: `critical-${toolName}`,
dialogTurns: [{
id: 'turn-1',
sessionId: `critical-${toolName}`,
userMessage: {
id: 'user-1',
content: 'Help',
timestamp: 900,
},
modelRounds: [makeRound({
items: [makeTool(`tool-${toolName}`, toolName)],
})],
status: 'completed',
startTime: 900,
}],
});

expect(sessionToVirtualItems(session).map(item => item.type)).toEqual([
'user-message',
'model-round',
]);
},
);

it('projects the absolute Turn index for a sparse history-window message', () => {
const session = makeSession({
sessionId: 'history-window-session',
Expand Down
27 changes: 26 additions & 1 deletion src/web-ui/src/flow_chat/tool-cards/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,36 @@

import { describe, expect, it } from 'vitest';

import { getToolCardComponent } from './index';
import {
DEDICATED_TOOL_CARD_NAMES,
getToolCardComponent,
isCollapsibleTool,
TOOL_CARD_COMPONENTS,
usesDefaultToolCard,
} from './index';
import { TaskToolDisplay } from './TaskToolDisplay';

describe('tool card registry', () => {
it('projects managed Review workers through the unified coverage card', () => {
expect(getToolCardComponent('LaunchReviewAgent')).toBe(TaskToolDisplay);
});

it('keeps lightweight dedicated-card classification aligned with the component registry', () => {
expect([...DEDICATED_TOOL_CARD_NAMES].sort()).toEqual(
Object.keys(TOOL_CARD_COMPONENTS).sort(),
);
});

it.each(['ControlHub', 'FinalizeMiniApp', 'PublishMiniApp', 'PublishAppearance'])(
'treats %s as a default-card explore tool',
(toolName) => {
expect(usesDefaultToolCard(toolName)).toBe(true);
expect(isCollapsibleTool(toolName)).toBe(true);
},
);

it('does not classify MCP tools as default-card explore tools', () => {
expect(usesDefaultToolCard('mcp__server__tool')).toBe(false);
expect(isCollapsibleTool('mcp__server__tool')).toBe(false);
});
});
2 changes: 2 additions & 0 deletions src/web-ui/src/flow_chat/tool-cards/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ export {
READ_TOOL_NAMES,
SEARCH_TOOL_NAMES,
COMMAND_TOOL_NAMES,
DEDICATED_TOOL_CARD_NAMES,
isCollapsibleTool,
isCollapsibleItem,
isCollapsibleItemWithContext,
usesDefaultToolCard,
} from './toolCardMetadata';

const log = createLogger('ToolCardRegistry');
Expand Down
76 changes: 71 additions & 5 deletions src/web-ui/src/flow_chat/tool-cards/toolCardMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,13 +498,79 @@ export function getAllToolNames(): string[] {

// ==================== Collapsible explorer tools ====================

/**
* Tools with a dedicated FlowChat card renderer.
*
* Keep this lightweight mirror aligned with TOOL_CARD_COMPONENTS. The registry
* test enforces equality so classification callers do not need to import every
* card component just to tell dedicated cards from the DefaultToolCard.
*/
export const DEDICATED_TOOL_CARD_NAMES = new Set([
'Read',
'Write',
'Edit',
'Delete',
'Grep',
'Glob',
'LS',
'WebSearch',
'WebFetch',
'Task',
'LaunchReviewAgent',
'TodoWrite',
'submit_code_review',
'ContextCompression',
'GetToolSpec',
'Skill',
'AskUserQuestion',
'ReviewSessionSummary',
'Git',
'GetFileDiff',
'CreatePlan',
'TerminalControl',
'SessionControl',
'SessionMessage',
'Bash',
'ExecCommand',
'WriteStdin',
'ExecControl',
'InitMiniApp',
'PageDeploy',
'PagePublish',
'GenerativeUI',
'ComputerUse',
'view_image',
'CreateCanvas',
'ReadCanvas',
'UpdateCanvas',
'PatchCanvas',
]);

/** Whether FlowChat renders this tool through DefaultToolCard. */
export function usesDefaultToolCard(toolName: string): boolean {
return !isMcpToolName(toolName) && !DEDICATED_TOOL_CARD_NAMES.has(toolName);
}


/**
* Collapsible explorer tools.
* They are auto-collapsed during streaming to reduce visual noise.
* Explicit non-critical tools collected into explore groups.
* Tools rendered by DefaultToolCard are also collected; see isCollapsibleTool.
*/
export const COLLAPSIBLE_TOOL_NAMES = new Set([
'Read', 'LS', 'Grep', 'Glob', 'WebSearch', 'Bash', 'Git',
'Read',
'LS',
'Grep',
'Glob',
'WebSearch',
'WebFetch',
'GetFileDiff',
'GetToolSpec',
'ReviewSessionSummary',
'TerminalControl',
'SessionControl',
'ExecControl',
'view_image',
'ReadCanvas',
]);

/** Read tools (counted in readCount). */
Expand All @@ -514,11 +580,11 @@ export const READ_TOOL_NAMES = new Set(['Read', 'LS']);
export const SEARCH_TOOL_NAMES = new Set(['Grep', 'Glob', 'WebSearch']);

/** Command tools (counted in commandCount). */
export const COMMAND_TOOL_NAMES = new Set(['Bash', 'Git']);
export const COMMAND_TOOL_NAMES = new Set<string>();

/** Check whether a tool is collapsible. */
export function isCollapsibleTool(toolName: string): boolean {
return COLLAPSIBLE_TOOL_NAMES.has(toolName);
return COLLAPSIBLE_TOOL_NAMES.has(toolName) || usesDefaultToolCard(toolName);
}

/**
Expand Down