refactor(core): introduce SubagentState enum for progress (#26934)

This commit is contained in:
Adam Weidman
2026-05-12 14:58:25 -04:00
committed by GitHub
parent c4973d01da
commit c987b99394
15 changed files with 172 additions and 121 deletions
@@ -6,7 +6,11 @@
import { waitFor } from '../../../test-utils/async.js';
import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentGroupDisplay } from './SubagentGroupDisplay.js';
import { Kind, CoreToolCallStatus } from '@google/gemini-cli-core';
import {
Kind,
CoreToolCallStatus,
SubagentState,
} from '@google/gemini-cli-core';
import type { IndividualToolCallDisplay } from '../../types.js';
import { describe, it, expect, vi } from 'vitest';
import { Text } from 'ink';
@@ -27,12 +31,12 @@ describe('<SubagentGroupDisplay />', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'api-monitor',
state: 'running',
state: SubagentState.RUNNING,
recentActivity: [
{
id: 'act-1',
type: 'tool_call',
status: 'running',
status: SubagentState.RUNNING,
content: '',
displayName: 'Action Required',
description: 'Verify server is running',
@@ -50,13 +54,13 @@ describe('<SubagentGroupDisplay />', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'db-manager',
state: 'completed',
state: SubagentState.COMPLETED,
result: 'Database schema validated',
recentActivity: [
{
id: 'act-2',
type: 'thought',
status: 'completed',
status: SubagentState.COMPLETED,
content: 'Database schema validated',
},
],
@@ -13,6 +13,7 @@ import {
isSubagentProgress,
checkExhaustive,
type SubagentActivityItem,
SubagentState,
} from '@google/gemini-cli-core';
import {
SubagentProgressDisplay,
@@ -66,13 +67,13 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
const singleAgent = toolCalls[0].resultDisplay;
if (isSubagentProgress(singleAgent)) {
switch (singleAgent.state) {
case 'completed':
case SubagentState.COMPLETED:
headerText = 'Agent Completed';
break;
case 'cancelled':
case SubagentState.CANCELLED:
headerText = 'Agent Cancelled';
break;
case 'error':
case SubagentState.ERROR:
headerText = 'Agent Error';
break;
default:
@@ -88,8 +89,8 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
for (const tc of toolCalls) {
const progress = tc.resultDisplay;
if (isSubagentProgress(progress)) {
if (progress.state === 'completed') completedCount++;
else if (progress.state === 'running') runningCount++;
if (progress.state === SubagentState.COMPLETED) completedCount++;
else if (progress.state === SubagentState.RUNNING) runningCount++;
} else {
// It hasn't emitted progress yet, but it is "running"
runningCount++;
@@ -200,7 +201,7 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
let content = 'Starting...';
let formattedArgs: string | undefined;
if (progress.state === 'completed') {
if (progress.state === SubagentState.COMPLETED) {
if (
progress.terminateReason &&
progress.terminateReason !== 'GOAL'
@@ -223,18 +224,18 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
}
const displayArgs =
progress.state === 'completed' ? '' : formattedArgs;
progress.state === SubagentState.COMPLETED ? '' : formattedArgs;
const renderStatusIcon = () => {
const state = progress.state ?? 'running';
const state = progress.state ?? SubagentState.RUNNING;
switch (state) {
case 'running':
case SubagentState.RUNNING:
return <Text color={theme.text.primary}>!</Text>;
case 'completed':
case SubagentState.COMPLETED:
return <Text color={theme.status.success}></Text>;
case 'cancelled':
case SubagentState.CANCELLED:
return <Text color={theme.status.warning}></Text>;
case 'error':
case SubagentState.ERROR:
return <Text color={theme.status.error}></Text>;
default:
return checkExhaustive(state);
@@ -8,6 +8,7 @@ import { describe, it, expect } from 'vitest';
import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentHistoryMessage } from './SubagentHistoryMessage.js';
import type { HistoryItemSubagent } from '../../types.js';
import { SubagentState } from '@google/gemini-cli-core';
describe('SubagentHistoryMessage', () => {
const mockItem: HistoryItemSubagent = {
@@ -18,19 +19,19 @@ describe('SubagentHistoryMessage', () => {
id: '1',
type: 'thought',
content: 'Thinking about the problem',
status: 'completed',
status: SubagentState.COMPLETED,
},
{
id: '2',
type: 'tool_call',
content: 'Calling search_web',
status: 'running',
status: SubagentState.RUNNING,
},
{
id: '3',
type: 'tool_call',
content: 'Calling read_file fail',
status: 'error',
status: SubagentState.ERROR,
},
],
};
@@ -6,7 +6,7 @@
import { render, cleanup } from '../../../test-utils/render.js';
import { SubagentProgressDisplay } from './SubagentProgressDisplay.js';
import type { SubagentProgress } from '@google/gemini-cli-core';
import { type SubagentProgress, SubagentState } from '@google/gemini-cli-core';
import { describe, it, expect, vi, afterEach } from 'vitest';
describe('<SubagentProgressDisplay />', () => {
@@ -25,7 +25,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello", "description": "Say hello"}',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -48,7 +48,7 @@ describe('<SubagentProgressDisplay />', () => {
displayName: 'RunShellCommand',
description: 'Executing echo hello',
args: '{"command": "echo hello"}',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -69,7 +69,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello"}',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -90,7 +90,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'write_file',
args: '{"file_path": "/tmp/test.txt", "content": "foo"}',
status: 'completed',
status: SubagentState.COMPLETED,
},
],
};
@@ -113,7 +113,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: JSON.stringify({ description: longDesc }),
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -133,7 +133,7 @@ describe('<SubagentProgressDisplay />', () => {
id: '5',
type: 'thought',
content: 'Thinking about life',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -149,7 +149,7 @@ describe('<SubagentProgressDisplay />', () => {
isSubagentProgress: true,
agentName: 'TestAgent',
recentActivity: [],
state: 'cancelled',
state: SubagentState.CANCELLED,
};
const { lastFrame } = await render(
@@ -167,7 +167,7 @@ describe('<SubagentProgressDisplay />', () => {
id: '6',
type: 'thought',
content: 'Request cancelled.',
status: 'error',
status: SubagentState.ERROR,
},
],
};
@@ -188,7 +188,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello"}',
status: 'error',
status: SubagentState.ERROR,
},
],
};
@@ -9,9 +9,10 @@ import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import Spinner from 'ink-spinner';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import type {
SubagentProgress,
SubagentActivityItem,
import {
type SubagentProgress,
type SubagentActivityItem,
SubagentState,
} from '@google/gemini-cli-core';
import { TOOL_STATUS } from '../../constants.js';
import { STATUS_INDICATOR_WIDTH } from './ToolShared.js';
@@ -62,13 +63,13 @@ export const SubagentProgressDisplay: React.FC<
let headerText: string | undefined;
let headerColor = theme.text.secondary;
if (progress.state === 'cancelled') {
if (progress.state === SubagentState.CANCELLED) {
headerText = `Subagent ${progress.agentName} was cancelled.`;
headerColor = theme.status.warning;
} else if (progress.state === 'error') {
} else if (progress.state === SubagentState.ERROR) {
headerText = `Subagent ${progress.agentName} failed.`;
headerColor = theme.status.error;
} else if (progress.state === 'completed') {
} else if (progress.state === SubagentState.COMPLETED) {
headerText = `Subagent ${progress.agentName} completed.`;
headerColor = theme.status.success;
} else {
@@ -107,13 +108,13 @@ export const SubagentProgressDisplay: React.FC<
);
} else if (item.type === 'tool_call') {
const statusSymbol =
item.status === 'running' ? (
item.status === SubagentState.RUNNING ? (
<Spinner type="dots" />
) : item.status === 'completed' ? (
) : item.status === SubagentState.COMPLETED ? (
<Text color={theme.status.success}>
{TOOL_STATUS.SUCCESS}
</Text>
) : item.status === 'cancelled' ? (
) : item.status === SubagentState.CANCELLED ? (
<Text color={theme.status.warning} bold>
{TOOL_STATUS.CANCELED}
</Text>
@@ -135,7 +136,7 @@ export const SubagentProgressDisplay: React.FC<
<Text
bold
color={theme.text.primary}
strikethrough={item.status === 'cancelled'}
strikethrough={item.status === SubagentState.CANCELLED}
>
{item.displayName || item.content}
</Text>
@@ -144,7 +145,9 @@ export const SubagentProgressDisplay: React.FC<
<Text
color={theme.text.secondary}
wrap="truncate"
strikethrough={item.status === 'cancelled'}
strikethrough={
item.status === SubagentState.CANCELLED
}
>
{displayArgs}
</Text>
@@ -170,7 +173,7 @@ export const SubagentProgressDisplay: React.FC<
)}
<MarkdownDisplay
text={safeJsonToMarkdown(progress.result)}
isPending={progress.state !== 'completed'}
isPending={progress.state !== SubagentState.COMPLETED}
terminalWidth={terminalWidth}
/>
</Box>
@@ -13,6 +13,7 @@ import {
ApprovalMode,
WRITE_FILE_DISPLAY_NAME,
Kind,
SubagentState,
} from '@google/gemini-cli-core';
import os from 'node:os';
import { createMockSettings } from '../../../test-utils/settings.js';
@@ -76,7 +77,7 @@ describe('ToolGroupMessage Regression Tests', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'TestAgent',
state: 'running',
state: SubagentState.RUNNING,
recentActivity: [],
},
}),
@@ -112,7 +113,7 @@ describe('ToolGroupMessage Regression Tests', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'TestAgent',
state: 'completed',
state: SubagentState.COMPLETED,
recentActivity: [],
},
}),
@@ -21,6 +21,7 @@ import {
ROOT_SCHEDULER_ID,
CoreToolCallStatus,
type WaitingToolCall,
SubagentState,
} from '@google/gemini-cli-core';
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
@@ -630,7 +631,7 @@ describe('useToolScheduler', () => {
id: '1',
type: 'thought',
content: 'Thinking...',
status: 'running',
status: SubagentState.RUNNING,
},
});
});
@@ -648,7 +649,7 @@ describe('useToolScheduler', () => {
id: '2',
type: 'tool_call',
content: 'Calling tool',
status: 'completed',
status: SubagentState.COMPLETED,
},
});
});
@@ -697,7 +698,7 @@ describe('useToolScheduler', () => {
id: '1',
type: 'thought',
content: 'Thinking...',
status: 'running',
status: SubagentState.RUNNING,
},
});
});
@@ -716,7 +717,7 @@ describe('useToolScheduler', () => {
id: '1',
type: 'thought',
content: 'Thinking... Done!',
status: 'completed',
status: SubagentState.COMPLETED,
},
});
});
@@ -726,6 +727,8 @@ describe('useToolScheduler', () => {
expect(result.current[0][0].subagentHistory![0].content).toBe(
'Thinking... Done!',
);
expect(result.current[0][0].subagentHistory![0].status).toBe('completed');
expect(result.current[0][0].subagentHistory![0].status).toBe(
SubagentState.COMPLETED,
);
});
});