mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
refactor(core): introduce SubagentState enum for progress (#26934)
This commit is contained in:
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
AgentInterface,
|
||||
} from '@a2a-js/sdk';
|
||||
import type { SendMessageResult } from './a2a-client-manager.js';
|
||||
import type { SubagentActivityItem } from './types.js';
|
||||
import { type SubagentActivityItem, SubagentState } from './types.js';
|
||||
|
||||
export const AUTH_REQUIRED_MSG = `[Authorization Required] The agent has indicated it requires authorization to proceed. Please follow the agent's instructions.`;
|
||||
|
||||
@@ -143,7 +143,7 @@ export class A2AResultReassembler {
|
||||
id: 'auth-required',
|
||||
type: 'thought',
|
||||
content: AUTH_REQUIRED_MSG,
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class A2AResultReassembler {
|
||||
id: `msg-${index}`,
|
||||
type: 'thought',
|
||||
content: msg.trim(),
|
||||
status: 'completed',
|
||||
status: SubagentState.COMPLETED,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -161,7 +161,7 @@ export class A2AResultReassembler {
|
||||
id: 'pending',
|
||||
type: 'thought',
|
||||
content: 'Working...',
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
type SubagentActivityItem,
|
||||
AgentTerminateMode,
|
||||
isToolActivityError,
|
||||
SubagentState,
|
||||
} from '../types.js';
|
||||
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
|
||||
import { createBrowserAgentDefinition } from './browserAgentFactory.js';
|
||||
@@ -123,7 +124,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.agentName,
|
||||
recentActivity: [],
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
};
|
||||
updateOutput(initialProgress);
|
||||
}
|
||||
@@ -137,7 +138,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
id: randomUUID(),
|
||||
type: 'thought',
|
||||
content: sanitizedMsg,
|
||||
status: 'completed',
|
||||
status: SubagentState.COMPLETED,
|
||||
});
|
||||
if (recentActivity.length > MAX_RECENT_ACTIVITY) {
|
||||
recentActivity = recentActivity.slice(-MAX_RECENT_ACTIVITY);
|
||||
@@ -146,7 +147,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.agentName,
|
||||
recentActivity: [...recentActivity],
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
} as SubagentProgress);
|
||||
}
|
||||
: undefined;
|
||||
@@ -175,7 +176,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
lastItem &&
|
||||
lastItem.type === 'thought' &&
|
||||
lastItem.status === 'running'
|
||||
lastItem.status === SubagentState.RUNNING
|
||||
) {
|
||||
lastItem.content = sanitizeThoughtContent(text);
|
||||
} else {
|
||||
@@ -183,7 +184,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
id: randomUUID(),
|
||||
type: 'thought',
|
||||
content: sanitizeThoughtContent(text),
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
}
|
||||
updated = true;
|
||||
@@ -210,7 +211,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
displayName,
|
||||
description,
|
||||
args,
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
updated = true;
|
||||
break;
|
||||
@@ -227,9 +228,11 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
callId != null &&
|
||||
recentActivity[i].id === callId &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = isError ? 'error' : 'completed';
|
||||
recentActivity[i].status = isError
|
||||
? SubagentState.ERROR
|
||||
: SubagentState.COMPLETED;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
@@ -242,7 +245,9 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
const callId = activity.data['callId']
|
||||
? String(activity.data['callId'])
|
||||
: undefined;
|
||||
const newStatus = isCancellation ? 'cancelled' : 'error';
|
||||
const newStatus = isCancellation
|
||||
? SubagentState.CANCELLED
|
||||
: SubagentState.ERROR;
|
||||
|
||||
if (callId) {
|
||||
// Mark the specific tool as error/cancelled
|
||||
@@ -250,7 +255,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].id === callId &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = newStatus;
|
||||
updated = true;
|
||||
@@ -260,7 +265,10 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
} else {
|
||||
// No specific tool — mark ALL running tool_call items
|
||||
for (const item of recentActivity) {
|
||||
if (item.type === 'tool_call' && item.status === 'running') {
|
||||
if (
|
||||
item.type === 'tool_call' &&
|
||||
item.status === SubagentState.RUNNING
|
||||
) {
|
||||
item.status = newStatus;
|
||||
updated = true;
|
||||
}
|
||||
@@ -293,7 +301,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.agentName,
|
||||
recentActivity: [...recentActivity],
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
};
|
||||
updateOutput(progress);
|
||||
}
|
||||
@@ -330,13 +338,13 @@ ${output.result}`;
|
||||
// GOAL = agent completed its task normally.
|
||||
// ABORTED = user cancelled.
|
||||
// Others (ERROR, MAX_TURNS, ERROR_NO_COMPLETE_TASK_CALL) = error.
|
||||
let progressState: SubagentProgress['state'];
|
||||
let progressState: SubagentState;
|
||||
if (output.terminate_reason === AgentTerminateMode.ABORTED) {
|
||||
progressState = 'cancelled';
|
||||
progressState = SubagentState.CANCELLED;
|
||||
} else if (output.terminate_reason === AgentTerminateMode.GOAL) {
|
||||
progressState = 'completed';
|
||||
progressState = SubagentState.COMPLETED;
|
||||
} else {
|
||||
progressState = 'error';
|
||||
progressState = SubagentState.ERROR;
|
||||
}
|
||||
|
||||
const progress: SubagentProgress = {
|
||||
@@ -366,8 +374,8 @@ ${output.result}`;
|
||||
|
||||
// Mark any running items as error/cancelled
|
||||
for (const item of recentActivity) {
|
||||
if (item.status === 'running') {
|
||||
item.status = isAbort ? 'cancelled' : 'error';
|
||||
if (item.status === SubagentState.RUNNING) {
|
||||
item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +383,7 @@ ${output.result}`;
|
||||
isSubagentProgress: true,
|
||||
agentName: this.agentName,
|
||||
recentActivity: [...recentActivity],
|
||||
state: isAbort ? 'cancelled' : 'error',
|
||||
state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR,
|
||||
};
|
||||
|
||||
if (updateOutput) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type SubagentProgress,
|
||||
SubagentActivityErrorType,
|
||||
SUBAGENT_REJECTED_ERROR_PREFIX,
|
||||
SubagentState,
|
||||
} from './types.js';
|
||||
import { LocalSubagentInvocation } from './local-invocation.js';
|
||||
import { LocalAgentExecutor } from './local-executor.js';
|
||||
@@ -215,7 +216,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
]);
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
expect(display.state).toBe('completed');
|
||||
expect(display.state).toBe(SubagentState.COMPLETED);
|
||||
expect(display.result).toBe('Analysis complete.');
|
||||
expect(display.terminateReason).toBe(AgentTerminateMode.GOAL);
|
||||
});
|
||||
@@ -234,7 +235,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
expect(display.state).toBe('completed');
|
||||
expect(display.state).toBe(SubagentState.COMPLETED);
|
||||
expect(display.result).toBe('Partial progress...');
|
||||
expect(display.terminateReason).toBe(AgentTerminateMode.TIMEOUT);
|
||||
});
|
||||
@@ -340,7 +341,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'thought',
|
||||
content: 'Error: Failed',
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -376,7 +377,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'tool_call',
|
||||
content: 'ls',
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -418,7 +419,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'tool_call',
|
||||
content: 'ls',
|
||||
status: 'cancelled',
|
||||
status: SubagentState.CANCELLED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -443,7 +444,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect(result.error).toBeUndefined();
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
expect(display.state).toBe('completed');
|
||||
expect(display.state).toBe(SubagentState.COMPLETED);
|
||||
expect(display.result).toBe('Done');
|
||||
});
|
||||
|
||||
@@ -466,7 +467,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'thought',
|
||||
content: `Error: ${error.message}`,
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -488,7 +489,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect(display.recentActivity).toContainEqual(
|
||||
expect.objectContaining({
|
||||
content: `Error: ${creationError.message}`,
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
SUBAGENT_REJECTED_ERROR_PREFIX,
|
||||
SUBAGENT_CANCELLED_ERROR_MESSAGE,
|
||||
isToolActivityError,
|
||||
SubagentState,
|
||||
} from './types.js';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { z } from 'zod';
|
||||
@@ -117,7 +118,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [],
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
};
|
||||
updateOutput(initialProgress);
|
||||
}
|
||||
@@ -137,7 +138,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
lastItem &&
|
||||
lastItem.type === 'thought' &&
|
||||
lastItem.status === 'running'
|
||||
lastItem.status === SubagentState.RUNNING
|
||||
) {
|
||||
lastItem.content = sanitizeThoughtContent(text);
|
||||
} else {
|
||||
@@ -145,7 +146,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
id: randomUUID(),
|
||||
type: 'thought',
|
||||
content: sanitizeThoughtContent(text),
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
}
|
||||
updated = true;
|
||||
@@ -174,7 +175,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
displayName,
|
||||
description,
|
||||
args,
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
updated = true;
|
||||
|
||||
@@ -193,9 +194,11 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].content === name &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = isError ? 'error' : 'completed';
|
||||
recentActivity[i].status = isError
|
||||
? SubagentState.ERROR
|
||||
: SubagentState.COMPLETED;
|
||||
updated = true;
|
||||
|
||||
this.publishActivity(recentActivity[i]);
|
||||
@@ -224,9 +227,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].content === toolName &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = 'cancelled';
|
||||
recentActivity[i].status = SubagentState.CANCELLED;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
@@ -237,9 +240,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].content === toolName &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = 'error';
|
||||
recentActivity[i].status = SubagentState.ERROR;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
@@ -253,7 +256,10 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isCancellation || isRejection
|
||||
? sanitizedError
|
||||
: `Error: ${sanitizedError}`,
|
||||
status: isCancellation || isRejection ? 'cancelled' : 'error',
|
||||
status:
|
||||
isCancellation || isRejection
|
||||
? SubagentState.CANCELLED
|
||||
: SubagentState.ERROR,
|
||||
});
|
||||
updated = true;
|
||||
break;
|
||||
@@ -267,7 +273,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity], // Copy to avoid mutation issues
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
};
|
||||
|
||||
updateOutput(progress);
|
||||
@@ -287,7 +293,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity],
|
||||
state: 'cancelled',
|
||||
state: SubagentState.CANCELLED,
|
||||
};
|
||||
|
||||
if (updateOutput) {
|
||||
@@ -303,7 +309,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity],
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: output.result,
|
||||
terminateReason: output.terminate_reason,
|
||||
};
|
||||
@@ -334,8 +340,8 @@ ${output.result}`;
|
||||
|
||||
// Mark any running items as error/cancelled
|
||||
for (const item of recentActivity) {
|
||||
if (item.status === 'running') {
|
||||
item.status = isAbort ? 'cancelled' : 'error';
|
||||
if (item.status === SubagentState.RUNNING) {
|
||||
item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +349,12 @@ ${output.result}`;
|
||||
// But only if it's NOT an abort, or if we want to show "Cancelled" as a thought
|
||||
if (!isAbort) {
|
||||
const lastActivity = recentActivity[recentActivity.length - 1];
|
||||
if (!lastActivity || lastActivity.status !== 'error') {
|
||||
if (!lastActivity || lastActivity.status !== SubagentState.ERROR) {
|
||||
recentActivity.push({
|
||||
id: randomUUID(),
|
||||
type: 'thought',
|
||||
content: `Error: ${errorMessage}`,
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
});
|
||||
// Maintain size limit
|
||||
// No limit on UI events sent via bus
|
||||
@@ -359,7 +365,7 @@ ${output.result}`;
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity],
|
||||
state: isAbort ? 'cancelled' : 'error',
|
||||
state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR,
|
||||
};
|
||||
|
||||
if (updateOutput) {
|
||||
|
||||
@@ -20,7 +20,11 @@ import {
|
||||
type A2AClientManager,
|
||||
} from './a2a-client-manager.js';
|
||||
|
||||
import type { RemoteAgentDefinition, SubagentProgress } from './types.js';
|
||||
import {
|
||||
type RemoteAgentDefinition,
|
||||
type SubagentProgress,
|
||||
SubagentState,
|
||||
} from './types.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
|
||||
import type { A2AAuthProvider } from './auth-provider/types.js';
|
||||
@@ -268,7 +272,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
"Failed to create auth provider for agent 'test-agent'",
|
||||
);
|
||||
@@ -461,7 +467,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: expect.arrayContaining([
|
||||
expect.objectContaining({ content: 'Working...' }),
|
||||
]),
|
||||
@@ -470,7 +476,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: 'HelloHello World',
|
||||
}),
|
||||
);
|
||||
@@ -508,7 +514,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
@@ -533,7 +541,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: 'error',
|
||||
state: SubagentState.ERROR,
|
||||
result: expect.stringContaining('Network error'),
|
||||
});
|
||||
});
|
||||
@@ -616,7 +624,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: expect.arrayContaining([
|
||||
expect.objectContaining({ content: 'Working...' }),
|
||||
]),
|
||||
@@ -625,7 +633,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: 'Thinking...Final Answer',
|
||||
}),
|
||||
);
|
||||
@@ -693,7 +701,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: expect.arrayContaining([
|
||||
expect.objectContaining({ content: 'Working...' }),
|
||||
]),
|
||||
@@ -702,7 +710,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: 'Generating...\n\nArtifact (Result):\nPart 1 Part 2',
|
||||
}),
|
||||
);
|
||||
@@ -760,7 +768,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
a2aError.userMessage,
|
||||
);
|
||||
@@ -782,7 +792,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
'Error calling remote agent: something unexpected',
|
||||
);
|
||||
@@ -813,7 +825,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
// Should contain both the partial output and the error message
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
result: expect.stringContaining('Partial response'),
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type RemoteAgentDefinition,
|
||||
type AgentInputs,
|
||||
type SubagentProgress,
|
||||
SubagentState,
|
||||
getAgentCardLoadOptions,
|
||||
getRemoteAgentTargetUrl,
|
||||
} from './types.js';
|
||||
@@ -138,13 +139,13 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
updateOutput({
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: [
|
||||
{
|
||||
id: 'pending',
|
||||
type: 'thought',
|
||||
content: 'Working...',
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -193,7 +194,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
updateOutput({
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
result: reassembler.toString(),
|
||||
});
|
||||
@@ -225,7 +226,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
const finalProgress: SubagentProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: finalOutput,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
};
|
||||
@@ -249,7 +250,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
const errorProgress: SubagentProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'error',
|
||||
state: SubagentState.ERROR,
|
||||
result: fullDisplay,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
DEFAULT_QUERY_STRING,
|
||||
type RemoteAgentDefinition,
|
||||
type SubagentProgress,
|
||||
SubagentState,
|
||||
getRemoteAgentTargetUrl,
|
||||
getAgentCardLoadOptions,
|
||||
} from './types.js';
|
||||
@@ -233,7 +234,7 @@ class RemoteSubagentProtocol implements AgentProtocol {
|
||||
this._latestProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName: this._agentName,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
result: currentText,
|
||||
};
|
||||
@@ -259,7 +260,7 @@ class RemoteSubagentProtocol implements AgentProtocol {
|
||||
const finalProgress: SubagentProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName: this._agentName,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: finalOutput,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
};
|
||||
|
||||
@@ -88,6 +88,13 @@ export interface SubagentActivityEvent {
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export enum SubagentState {
|
||||
RUNNING = 'running',
|
||||
COMPLETED = 'completed',
|
||||
ERROR = 'error',
|
||||
CANCELLED = 'cancelled',
|
||||
}
|
||||
|
||||
export interface SubagentActivityItem {
|
||||
id: string;
|
||||
type: 'thought' | 'tool_call';
|
||||
@@ -95,14 +102,14 @@ export interface SubagentActivityItem {
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
args?: string;
|
||||
status: 'running' | 'completed' | 'error' | 'cancelled';
|
||||
status: SubagentState;
|
||||
}
|
||||
|
||||
export interface SubagentProgress {
|
||||
isSubagentProgress: true;
|
||||
agentName: string;
|
||||
recentActivity: SubagentActivityItem[];
|
||||
state?: 'running' | 'completed' | 'error' | 'cancelled';
|
||||
state?: SubagentState;
|
||||
result?: string;
|
||||
terminateReason?: AgentTerminateMode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user