diff --git a/docs/plans/2026-03-22-agent-tool-display-design.md b/docs/plans/2026-03-22-agent-tool-display-design.md new file mode 100644 index 00000000000..b6720a9a263 --- /dev/null +++ b/docs/plans/2026-03-22-agent-tool-display-design.md @@ -0,0 +1,106 @@ +# Agent Tool Display Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add a dedicated VSCode/web UI display for Agent tool executions so subagent progress, summaries, and failures render from structured `rawOutput` instead of falling back to the generic tool card. + +**Architecture:** Preserve ACP `rawOutput` through the VSCode session/update pipeline into `ToolCallData`, then let the shared web UI router detect `task_execution` payloads and render a dedicated `AgentToolCall` component. Keep the change shared in `packages/webui` so VSCode and `ChatViewer` stay aligned. + +**Tech Stack:** TypeScript, React, Vitest, shared `@qwen-code/webui` tool-call components. + +### Task 1: Lock in the failing data-flow behavior + +**Files:** + +- Modify: `packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.test.ts` +- Create: `packages/vscode-ide-companion/src/webview/hooks/useToolCalls.test.tsx` + +**Step 1: Write the failing tests** + +- Add a session handler test asserting `tool_call_update` forwards `rawOutput` when ACP sends a `task_execution` payload. +- Add a hook test asserting `useToolCalls` stores and updates `rawOutput` for an agent tool call. + +**Step 2: Run test to verify it fails** + +Run: `npm test --workspace=packages/vscode-ide-companion -- --run qwenSessionUpdateHandler.test.ts useToolCalls.test.tsx` + +Expected: failures because `rawOutput` is not preserved in the current handler/hook pipeline. + +### Task 2: Lock in the failing renderer behavior + +**Files:** + +- Create: `packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx` + +**Step 1: Write the failing test** + +- Render the routed tool call with `kind: 'other'` plus `rawOutput.type === 'task_execution'`. +- Assert the task description, active child tool, summary, and failure reason render from a dedicated agent display instead of generic text output. + +**Step 2: Run test to verify it fails** + +Run: `npm test --workspace=packages/vscode-ide-companion -- --run packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx` + +Expected: failure because the router only keys off `kind` and no dedicated agent component exists. + +### Task 3: Preserve structured agent output end-to-end + +**Files:** + +- Modify: `packages/vscode-ide-companion/src/types/chatTypes.ts` +- Modify: `packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts` +- Modify: `packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts` +- Modify: `packages/webui/src/components/toolcalls/shared/types.ts` + +**Step 1: Implement the minimal data model changes** + +- Add optional `rawOutput` to the VSCode session/webview tool-call types. +- Forward `rawOutput` in `QwenSessionUpdateHandler`. +- Store/merge `rawOutput` in `useToolCalls`. +- Expose `rawOutput` in shared web UI tool-call data types. + +**Step 2: Run the focused tests** + +Run: `npm test --workspace=packages/vscode-ide-companion -- --run qwenSessionUpdateHandler.test.ts useToolCalls.test.tsx` + +Expected: pass. + +### Task 4: Add the shared agent tool-call UI + +**Files:** + +- Create: `packages/webui/src/components/toolcalls/AgentToolCall.tsx` +- Modify: `packages/webui/src/components/toolcalls/index.ts` +- Modify: `packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx` +- Modify: `packages/webui/src/components/ChatViewer/ChatViewer.tsx` + +**Step 1: Implement the minimal renderer** + +- Add a guard for `rawOutput.type === 'task_execution'`. +- Render task description as the header. +- Show agent name + status, currently running child tools, completion summary, and failure/cancel reason. +- Keep the layout compatible with multiple parallel agent cards by rendering each tool call independently. + +**Step 2: Run the focused renderer test** + +Run: `npm test --workspace=packages/vscode-ide-companion -- --run packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx` + +Expected: pass. + +### Task 5: Verify the integrated surface + +**Files:** + +- Modify: `packages/webui/src/index.ts` + +**Step 1: Export the new shared component if needed** + +- Re-export any new component/types needed by VSCode or `ChatViewer`. + +**Step 2: Run package verification** + +Run: `npm test --workspace=packages/vscode-ide-companion -- --run qwenSessionUpdateHandler.test.ts useToolCalls.test.tsx packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx` +Run: `npm run check-types --workspace=packages/vscode-ide-companion` +Run: `npm run typecheck --workspace=packages/webui` + +Expected: all targeted tests and typechecks pass. diff --git a/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.test.ts b/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.test.ts index ab2e34179b6..c0fff31d679 100644 --- a/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.test.ts +++ b/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.test.ts @@ -169,6 +169,48 @@ describe('QwenSessionUpdateHandler', () => { locations: undefined, }); }); + + it('forwards rawOutput for structured agent execution updates', () => { + const rawOutput = { + type: 'task_execution', + subagentName: 'Explore', + taskDescription: 'Explore auth logic', + taskPrompt: 'Inspect auth flow implementation', + status: 'running', + toolCalls: [ + { + callId: 'child-1', + name: 'read', + status: 'executing', + }, + ], + }; + + const toolCallUpdate = { + sessionId: 'test-session', + update: { + sessionUpdate: 'tool_call_update', + toolCallId: 'call-agent', + kind: 'other', + title: 'Launch agent', + status: 'in_progress', + rawOutput, + }, + } as SessionNotification; + + handler.handleSessionUpdate(toolCallUpdate); + + expect(mockCallbacks.onToolCall).toHaveBeenCalledWith({ + toolCallId: 'call-agent', + kind: 'other', + title: 'Launch agent', + status: 'in_progress', + rawInput: undefined, + rawOutput, + content: undefined, + locations: undefined, + }); + }); }); describe('plan handling', () => { diff --git a/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts b/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts index 06e03d454e8..17ab8076894 100644 --- a/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts +++ b/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts @@ -111,6 +111,7 @@ export class QwenSessionUpdateHandler { title: (update.title as string) || undefined, status: (update.status as string) || undefined, rawInput: update.rawInput, + rawOutput: (update as { rawOutput?: unknown }).rawOutput, content: update.content as | Array> | undefined, @@ -134,6 +135,7 @@ export class QwenSessionUpdateHandler { title: (update.title as string) || undefined, status: (update.status as string) || undefined, rawInput: update.rawInput, + rawOutput: (update as { rawOutput?: unknown }).rawOutput, content: update.content as | Array> | undefined, diff --git a/packages/vscode-ide-companion/src/types/chatTypes.ts b/packages/vscode-ide-companion/src/types/chatTypes.ts index 58afaada498..15f4b63db17 100644 --- a/packages/vscode-ide-companion/src/types/chatTypes.ts +++ b/packages/vscode-ide-companion/src/types/chatTypes.ts @@ -29,6 +29,7 @@ export interface ToolCallUpdateData { title?: string; status?: string; rawInput?: unknown; + rawOutput?: unknown; content?: Array>; locations?: Array<{ path: string; line?: number | null }>; timestamp?: number; @@ -88,6 +89,7 @@ export interface ToolCallUpdate { title?: string; status?: 'pending' | 'in_progress' | 'completed' | 'failed'; rawInput?: unknown; + rawOutput?: unknown; content?: Array<{ type: 'content' | 'diff'; content?: { diff --git a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx new file mode 100644 index 00000000000..ea31c1a698b --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.test.tsx @@ -0,0 +1,151 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @vitest-environment jsdom */ + +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { ToolCallRouter } from './index.js'; + +vi.mock('@qwen-code/webui', async () => { + const React = await vi.importActual('react'); + + const renderLabel = (label: string) => + function MockTool({ + toolCall, + }: { + toolCall: { + title?: string; + rawOutput?: { + taskDescription?: string; + terminateReason?: string; + }; + }; + }) { + return React.createElement( + 'div', + undefined, + `${label}:${toolCall.rawOutput?.taskDescription || toolCall.title || ''}:${toolCall.rawOutput?.terminateReason || ''}`, + ); + }; + + return { + shouldShowToolCall: () => true, + isAgentExecutionToolCall: (toolCall: { rawOutput?: { type?: string } }) => + toolCall.rawOutput?.type === 'task_execution', + GenericToolCall: renderLabel('generic'), + ThinkToolCall: renderLabel('think'), + SaveMemoryToolCall: renderLabel('memory'), + EditToolCall: renderLabel('edit'), + WriteToolCall: renderLabel('write'), + SearchToolCall: renderLabel('search'), + UpdatedPlanToolCall: renderLabel('plan'), + ShellToolCall: renderLabel('shell'), + ReadToolCall: renderLabel('read'), + WebFetchToolCall: renderLabel('web'), + AgentToolCall: renderLabel('agent'), + }; +}); + +describe('ToolCallRouter agent execution rendering', () => { + let container: HTMLDivElement | null = null; + let root: Root | null = null; + + beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + if (root) { + act(() => { + root?.unmount(); + }); + root = null; + } + if (container) { + container.remove(); + container = null; + } + }); + + it('renders a dedicated view for structured agent progress and summary', () => { + act(() => { + root?.render( + , + ); + }); + + expect(container?.textContent).toContain('agent:Explore auth logic'); + }); + + it('renders the agent failure reason from structured rawOutput', () => { + act(() => { + root?.render( + , + ); + }); + + expect(container?.textContent).toContain( + 'agent:Explore auth logic:Subagent crashed', + ); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx index 143c02ca812..c83f20faa0a 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx @@ -11,6 +11,8 @@ import type { FC } from 'react'; import { shouldShowToolCall, // All ToolCall components from webui + AgentToolCall, + isAgentExecutionToolCall, GenericToolCall, ThinkToolCall, EditToolCall, @@ -21,13 +23,19 @@ import { ReadToolCall, WebFetchToolCall, } from '@qwen-code/webui'; -import type { BaseToolCallProps } from '@qwen-code/webui'; +import type { BaseToolCallProps, ToolCallData } from '@qwen-code/webui'; /** * Factory function that returns the appropriate tool call component based on kind */ -export const getToolCallComponent = (kind: string): FC => { - const normalizedKind = kind.toLowerCase(); +export const getToolCallComponent = ( + toolCall: ToolCallData, +): FC => { + if (isAgentExecutionToolCall(toolCall)) { + return AgentToolCall; + } + + const normalizedKind = toolCall.kind.toLowerCase(); // Route to specialized components switch (normalizedKind) { @@ -90,7 +98,7 @@ export const ToolCallRouter: React.FC = ({ toolCall }) => { } // Get the appropriate component for this kind - const Component = getToolCallComponent(toolCall.kind); + const Component = getToolCallComponent(toolCall); // Render the specialized component return ; diff --git a/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.test.tsx b/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.test.tsx new file mode 100644 index 00000000000..c1f15144d7c --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.test.tsx @@ -0,0 +1,121 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @vitest-environment jsdom */ + +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { useToolCalls } from './useToolCalls.js'; +import type { ToolCallUpdate } from '../../types/chatTypes.js'; + +type HookSnapshot = ReturnType; + +let latestSnapshot: HookSnapshot | null = null; + +function HookHarness() { + latestSnapshot = useToolCalls(); + return null; +} + +describe('useToolCalls', () => { + let container: HTMLDivElement | null = null; + let root: Root | null = null; + + beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + + act(() => { + root?.render(); + }); + }); + + afterEach(() => { + latestSnapshot = null; + if (root) { + act(() => { + root?.unmount(); + }); + root = null; + } + if (container) { + container.remove(); + container = null; + } + }); + + it('stores structured rawOutput for agent tool calls across updates', () => { + const startUpdate = { + type: 'tool_call', + toolCallId: 'agent-1', + kind: 'other', + title: 'Launch agent', + status: 'in_progress', + rawOutput: { + type: 'task_execution', + subagentName: 'Explore', + taskDescription: 'Explore auth logic', + taskPrompt: 'Inspect auth flow implementation', + status: 'running', + }, + } as ToolCallUpdate & { rawOutput: unknown }; + + act(() => { + latestSnapshot?.handleToolCallUpdate(startUpdate); + }); + + expect(latestSnapshot?.toolCalls.get('agent-1')).toMatchObject({ + toolCallId: 'agent-1', + kind: 'other', + title: 'Launch agent', + status: 'in_progress', + rawOutput: { + type: 'task_execution', + taskDescription: 'Explore auth logic', + }, + }); + + const completionUpdate = { + type: 'tool_call_update', + toolCallId: 'agent-1', + status: 'completed', + rawOutput: { + type: 'task_execution', + subagentName: 'Explore', + taskDescription: 'Explore auth logic', + taskPrompt: 'Inspect auth flow implementation', + status: 'completed', + executionSummary: { + totalToolCalls: 3, + totalTokens: 1234, + totalDurationMs: 2200, + }, + }, + } as ToolCallUpdate & { rawOutput: unknown }; + + act(() => { + latestSnapshot?.handleToolCallUpdate(completionUpdate); + }); + + expect(latestSnapshot?.toolCalls.get('agent-1')).toMatchObject({ + status: 'completed', + rawOutput: { + type: 'task_execution', + status: 'completed', + executionSummary: { + totalToolCalls: 3, + totalTokens: 1234, + totalDurationMs: 2200, + }, + }, + }); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts b/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts index d471cacc06a..309695f001c 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts @@ -180,6 +180,7 @@ export const useToolCalls = () => { title: safeTitle(update.title), status: update.status || 'pending', rawInput: update.rawInput as string | object | undefined, + rawOutput: update.rawOutput, content, locations: update.locations, timestamp: resolveTimestamp(update), @@ -216,6 +217,9 @@ export const useToolCalls = () => { ...(update.kind && { kind: update.kind }), ...(update.title && { title: safeTitle(update.title) }), ...(update.status && { status: update.status }), + ...(update.rawOutput !== undefined && { + rawOutput: update.rawOutput, + }), content: mergedContent, ...(update.locations && { locations: update.locations }), timestamp: nextTimestamp, @@ -227,6 +231,7 @@ export const useToolCalls = () => { title: update.title ? safeTitle(update.title) : '', status: update.status || 'pending', rawInput: update.rawInput as string | object | undefined, + rawOutput: update.rawOutput, content: updatedContent, locations: update.locations, timestamp: resolveTimestamp(update), diff --git a/packages/webui/src/components/ChatViewer/ChatViewer.tsx b/packages/webui/src/components/ChatViewer/ChatViewer.tsx index 7ad9135dee2..c95aa140e82 100644 --- a/packages/webui/src/components/ChatViewer/ChatViewer.tsx +++ b/packages/webui/src/components/ChatViewer/ChatViewer.tsx @@ -15,6 +15,7 @@ import { UserMessage } from '../messages/UserMessage.js'; import { AssistantMessage } from '../messages/Assistant/AssistantMessage.js'; import { ThinkingMessage } from '../messages/ThinkingMessage.js'; import { + AgentToolCall, GenericToolCall, ThinkToolCall, EditToolCall, @@ -25,6 +26,7 @@ import { ReadToolCall, WebFetchToolCall, shouldShowToolCall, + isAgentExecutionToolCall, } from '../toolcalls/index.js'; import type { ToolCallData as BaseToolCallData } from '../toolcalls/index.js'; import './ChatViewer.css'; @@ -145,8 +147,12 @@ function parseTimestamp(isoString: string): number { /** * Get the appropriate tool call component based on kind */ -function getToolCallComponent(kind: string) { - const normalizedKind = kind.toLowerCase(); +function getToolCallComponent(toolCall: BaseToolCallData) { + if (isAgentExecutionToolCall(toolCall)) { + return AgentToolCall; + } + + const normalizedKind = toolCall.kind.toLowerCase(); switch (normalizedKind) { case 'read': @@ -313,7 +319,7 @@ export const ChatViewer = forwardRef( // Handle tool calls if (msg.type === 'tool_call' && msg.toolCall) { - const ToolCallComponent = getToolCallComponent(msg.toolCall.kind); + const ToolCallComponent = getToolCallComponent(msg.toolCall); if (!ToolCallComponent) { return null; diff --git a/packages/webui/src/components/toolcalls/AgentToolCall.tsx b/packages/webui/src/components/toolcalls/AgentToolCall.tsx new file mode 100644 index 00000000000..b182482c697 --- /dev/null +++ b/packages/webui/src/components/toolcalls/AgentToolCall.tsx @@ -0,0 +1,156 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * Dedicated agent tool call component for structured subagent execution output. + */ + +import type { FC } from 'react'; +import { ToolCallCard, ToolCallRow, safeTitle } from './shared/index.js'; +import type { + AgentExecutionRawOutput, + BaseToolCallProps, + ToolCallData, +} from './shared/index.js'; + +const MAX_VISIBLE_TOOL_CALLS = 5; + +export const isAgentExecutionRawOutput = ( + value: unknown, +): value is AgentExecutionRawOutput => + Boolean( + value && + typeof value === 'object' && + 'type' in value && + (value as { type?: unknown }).type === 'task_execution' && + 'taskDescription' in value && + 'status' in value, + ); + +export const isAgentExecutionToolCall = ( + toolCall: ToolCallData, +): toolCall is ToolCallData & { rawOutput: AgentExecutionRawOutput } => + isAgentExecutionRawOutput(toolCall.rawOutput); + +const STATUS_LABELS: Record = { + running: 'Running', + completed: 'Completed', + failed: 'Failed', + cancelled: 'Cancelled', +}; + +const CHILD_STATUS_LABELS: Record< + NonNullable[number]['status'], + string +> = { + executing: 'Running', + awaiting_approval: 'Awaiting approval', + success: 'Completed', + failed: 'Failed', +}; + +const formatDuration = (durationMs: number): string => { + if (durationMs < 1000) { + return `${durationMs}ms`; + } + + if (durationMs < 60_000) { + return `${(durationMs / 1000).toFixed(durationMs % 1000 === 0 ? 0 : 1)}s`; + } + + const minutes = Math.floor(durationMs / 60_000); + const seconds = Math.round((durationMs % 60_000) / 1000); + return `${minutes}m ${seconds}s`; +}; + +const getHeaderTitle = ( + data: AgentExecutionRawOutput, + fallbackTitle: ToolCallData['title'], +): string => data.taskDescription || safeTitle(fallbackTitle) || 'Agent Task'; + +export const AgentToolCall: FC = ({ toolCall }) => { + if (!isAgentExecutionToolCall(toolCall)) { + return null; + } + + const data = toolCall.rawOutput; + const visibleToolCalls = data.toolCalls?.slice(-MAX_VISIBLE_TOOL_CALLS) ?? []; + const hiddenToolCallCount = Math.max( + 0, + (data.toolCalls?.length ?? 0) - visibleToolCalls.length, + ); + + return ( + + +
+ {getHeaderTitle(data, toolCall.title)} +
+
+ + +
+ {data.subagentName} + + {STATUS_LABELS[data.status]} + +
+
+ + {visibleToolCalls.length > 0 && ( + +
+ {visibleToolCalls.map((childToolCall) => ( +
+ + {childToolCall.name} + + + {CHILD_STATUS_LABELS[childToolCall.status]} + + {childToolCall.description && ( + + {childToolCall.description} + + )} + {childToolCall.error && ( + {childToolCall.error} + )} +
+ ))} + {hiddenToolCallCount > 0 && ( +
+ +{hiddenToolCallCount} more tool calls +
+ )} +
+
+ )} + + {data.executionSummary && ( + +
+ {data.executionSummary.totalToolCalls} tool calls + + {data.executionSummary.totalTokens.toLocaleString()} tokens + + {formatDuration(data.executionSummary.totalDurationMs)} +
+
+ )} + + {(data.status === 'failed' || data.status === 'cancelled') && + data.terminateReason && ( + +
+ {data.terminateReason} +
+
+ )} +
+ ); +}; diff --git a/packages/webui/src/components/toolcalls/index.ts b/packages/webui/src/components/toolcalls/index.ts index c8a64ca568f..9b292d21397 100644 --- a/packages/webui/src/components/toolcalls/index.ts +++ b/packages/webui/src/components/toolcalls/index.ts @@ -10,6 +10,11 @@ export * from './shared/index.js'; // Business ToolCall components export { ThinkToolCall } from './ThinkToolCall.js'; export { GenericToolCall } from './GenericToolCall.js'; +export { + AgentToolCall, + isAgentExecutionRawOutput, + isAgentExecutionToolCall, +} from './AgentToolCall.js'; export { EditToolCall } from './EditToolCall.js'; export { WriteToolCall } from './WriteToolCall.js'; export { SearchToolCall } from './SearchToolCall.js'; diff --git a/packages/webui/src/components/toolcalls/shared/index.ts b/packages/webui/src/components/toolcalls/shared/index.ts index 7c5a37224e5..aa1e696e969 100644 --- a/packages/webui/src/components/toolcalls/shared/index.ts +++ b/packages/webui/src/components/toolcalls/shared/index.ts @@ -31,6 +31,11 @@ export { // Types export type { + AgentExecutionRawOutput, + AgentExecutionStatus, + AgentExecutionSummary, + AgentExecutionToolCall, + AgentToolCallStatus, ToolCallContent, ToolCallLocation, ToolCallStatus, diff --git a/packages/webui/src/components/toolcalls/shared/types.ts b/packages/webui/src/components/toolcalls/shared/types.ts index 01c200226e4..8092ebab1f4 100644 --- a/packages/webui/src/components/toolcalls/shared/types.ts +++ b/packages/webui/src/components/toolcalls/shared/types.ts @@ -37,6 +37,48 @@ export interface ToolCallLocation { */ export type ToolCallStatus = 'pending' | 'in_progress' | 'completed' | 'failed'; +export type AgentExecutionStatus = + | 'running' + | 'completed' + | 'failed' + | 'cancelled'; + +export type AgentToolCallStatus = + | 'executing' + | 'awaiting_approval' + | 'success' + | 'failed'; + +export interface AgentExecutionSummary { + totalToolCalls: number; + totalTokens: number; + totalDurationMs: number; + successfulToolCalls?: number; + failedToolCalls?: number; + successRate?: number; +} + +export interface AgentExecutionToolCall { + callId: string; + name: string; + status: AgentToolCallStatus; + error?: string; + description?: string; +} + +export interface AgentExecutionRawOutput { + type: 'task_execution'; + subagentName: string; + subagentColor?: string; + taskDescription: string; + taskPrompt: string; + status: AgentExecutionStatus; + terminateReason?: string; + result?: string; + executionSummary?: AgentExecutionSummary; + toolCalls?: AgentExecutionToolCall[]; +} + /** * Base tool call data interface */ @@ -46,6 +88,7 @@ export interface ToolCallData { title: string | object; status: ToolCallStatus; rawInput?: string | object; + rawOutput?: unknown; content?: ToolCallContent[]; locations?: ToolCallLocation[]; timestamp?: number; diff --git a/packages/webui/src/index.ts b/packages/webui/src/index.ts index 7a445bcb179..d58711cfb5c 100644 --- a/packages/webui/src/index.ts +++ b/packages/webui/src/index.ts @@ -152,6 +152,9 @@ export { // Business ToolCall components ThinkToolCall, GenericToolCall, + AgentToolCall, + isAgentExecutionRawOutput, + isAgentExecutionToolCall, EditToolCall, WriteToolCall, SearchToolCall, @@ -163,6 +166,11 @@ export { } from './components/toolcalls'; export type { ToolCallContainerProps, + AgentExecutionRawOutput, + AgentExecutionStatus, + AgentExecutionSummary, + AgentExecutionToolCall, + AgentToolCallStatus, ToolCallContent, ToolCallData, BaseToolCallProps,