diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index ff8a0e7703..8da7fdcd85 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -49,6 +49,7 @@ export default defineConfig({ "**/profile-active-turn.spec.ts", "**/config-bridge-screenshots.spec.ts", "**/observer-feed-screenshots.spec.ts", + "**/transcript-tool-run-quality.smoke.spec.ts", "**/core-memory-screenshots.spec.ts", "**/activity-scope-label-screenshots.spec.ts", "**/welcome-agent-modal-screenshots.spec.ts", diff --git a/desktop/src/features/agents/ui/AgentSessionToolItem/ToolItem.tsx b/desktop/src/features/agents/ui/AgentSessionToolItem/ToolItem.tsx index e0f2ce9fbc..4913938cf3 100644 --- a/desktop/src/features/agents/ui/AgentSessionToolItem/ToolItem.tsx +++ b/desktop/src/features/agents/ui/AgentSessionToolItem/ToolItem.tsx @@ -29,11 +29,16 @@ export function ToolItem({ agentPubkey, item, profiles, + expanded, + onExpansionChange, }: AgentTranscriptIdentityProps & { item: Extract; profiles?: UserProfileLookup; + expanded?: boolean; + onExpansionChange?: (expanded: boolean) => void; }) { - const [isExpanded, setIsExpanded] = React.useState(false); + const [localExpanded, setLocalExpanded] = React.useState(false); + const isExpanded = expanded ?? localExpanded; const hasArgs = Object.keys(item.args).length > 0; const hasResult = item.result.trim().length > 0; const canonicalToolName = item.buzzToolName ?? item.toolName; @@ -52,9 +57,14 @@ export function ToolItem({ const agentResolvedAvatarUrl = agentProfile?.avatarUrl ?? agentAvatarUrl; const handleToggle = React.useCallback( (event: React.SyntheticEvent) => { - setIsExpanded(event.currentTarget.open); + const nextExpanded = event.currentTarget.open; + if (onExpansionChange) { + onExpansionChange(nextExpanded); + } else { + setLocalExpanded(nextExpanded); + } }, - [], + [onExpansionChange], ); if (compactSummary.presentation === "message") { diff --git a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx index 576cdbbf45..236edabeb7 100644 --- a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx +++ b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx @@ -533,7 +533,13 @@ function SameKindSummaryItem({ // the group card never has to import this module back. Not memoized: the // group card calls it during render and never uses its identity as a // dependency, and memoizing would need this component as its own dep. - const renderChild = (child: TranscriptToolRunChildSegment) => + const renderChild = ( + child: TranscriptToolRunChildSegment, + expansion?: { + expanded: boolean; + onExpansionChange: (expanded: boolean) => void; + }, + ) => child.kind === "summary" ? ( ); @@ -948,17 +956,23 @@ const TranscriptItemView = React.memo(function TranscriptItemView({ agentName, agentPubkey, item, + onExpansionChange, profiles, + expanded, }: AgentTranscriptIdentityProps & { item: TranscriptItem; profiles?: UserProfileLookup; + expanded?: boolean; + onExpansionChange?: (expanded: boolean) => void; }) { return ( ); diff --git a/desktop/src/features/agents/ui/TranscriptToolRunGroup.tsx b/desktop/src/features/agents/ui/TranscriptToolRunGroup.tsx index 57ebc86677..7fa0c7265f 100644 --- a/desktop/src/features/agents/ui/TranscriptToolRunGroup.tsx +++ b/desktop/src/features/agents/ui/TranscriptToolRunGroup.tsx @@ -23,6 +23,7 @@ import { import { setToolRunGroupExpanded, setToolRunGroupInternalStepsShown, + setToolRunGroupItemExpanded, } from "./agentSessionToolRunViewState"; import { useToolRunGroupViewState } from "./useToolRunGroupViewState"; import { formatTranscriptTimestampTitle } from "./agentSessionUtils"; @@ -121,7 +122,13 @@ export type TranscriptToolRunGroupProps = { label: React.ReactNode; showTimestamp: boolean; /** Renders one child segment. Passed in to avoid a list↔group import cycle. */ - renderChild: (child: TranscriptToolRunChildSegment) => React.ReactNode; + renderChild: ( + child: TranscriptToolRunChildSegment, + expansion?: { + expanded: boolean; + onExpansionChange: (expanded: boolean) => void; + }, + ) => React.ReactNode; }; /** @@ -200,7 +207,24 @@ export function TranscriptToolRunGroup({ {label} - {visibleChildren.map((child) => renderChild(child))} + {visibleChildren.map((child) => + renderChild( + child, + child.kind === "item" && child.item.type === "tool" + ? { + expanded: state.expandedItemIds.includes(child.item.id), + onExpansionChange: (expanded) => + update((previous) => + setToolRunGroupItemExpanded( + previous, + child.item.id, + expanded, + ), + ), + } + : undefined, + ), + )} {hiddenItems.length > 0 ? (