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
23 changes: 21 additions & 2 deletions cli/src/components/blocks/agent-branch-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { ToolBlockGroup } from './tool-block-group'
import { useTheme } from '../../hooks/use-theme'
import { useChatStore } from '../../state/chat-store'
import { isTextBlock } from '../../types/chat'
import { getAgentDisplayPrompt } from '../../utils/agent-display'
import {
getAgentDisplayPrompt,
getBasherFinishedOutputPreview,
} from '../../utils/agent-display'
import { getAgentStatusInfo } from '../../utils/agent-helpers'
import {
processBlocks,
Expand Down Expand Up @@ -52,12 +55,23 @@ function getCollapsedPreview(
agentBlock: AgentContentBlock,
isStreaming: boolean,
isCollapsed: boolean,
availableWidth: number,
): string {
// No preview needed if expanded and not streaming
if (!isStreaming && !isCollapsed) {
return ''
}

if (!isStreaming) {
const outputPreview = getBasherFinishedOutputPreview(
agentBlock,
Math.max(24, Math.min(120, availableWidth - 4)),
)
if (outputPreview) {
return outputPreview
}
}

// For multi-prompt editors, try progress-focused preview first
if (isMultiPromptEditor(agentBlock.agentType)) {
const multiPromptPreview = getMultiPromptPreview(
Expand Down Expand Up @@ -427,7 +441,12 @@ export const AgentBranchWrapper = memo(
const isStreaming = agentBlock.status === 'running' || agentIsStreaming

// Compute collapsed preview text
const preview = getCollapsedPreview(agentBlock, isStreaming, isCollapsed)
const preview = getCollapsedPreview(
agentBlock,
isStreaming,
isCollapsed,
availableWidth,
)
const displayPrompt = getAgentDisplayPrompt(agentBlock)

const effectiveStatus = isStreaming ? 'running' : agentBlock.status
Expand Down
75 changes: 74 additions & 1 deletion cli/src/utils/__tests__/agent-display.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, expect, test } from 'bun:test'

import { getAgentDisplayPrompt } from '../agent-display'
import {
getAgentDisplayPrompt,
getBasherFinishedOutputPreview,
truncateToSingleLinePreview,
} from '../agent-display'

import type { AgentContentBlock } from '../../types/chat'

Expand Down Expand Up @@ -64,3 +68,72 @@ describe('getAgentDisplayPrompt', () => {
expect(getAgentDisplayPrompt(block)).toBeUndefined()
})
})

describe('getBasherFinishedOutputPreview', () => {
test('returns undefined while basher is still running', () => {
const block = createAgentBlock({
status: 'running',
params: {
what_to_summarize: 'Report the test result',
},
blocks: [{ type: 'text', content: 'Tests passed' }],
})

expect(getBasherFinishedOutputPreview(block)).toBeUndefined()
})

test('uses finished basher text output before what_to_summarize', () => {
const block = createAgentBlock({
status: 'complete',
params: {
what_to_summarize: 'Report the test result',
},
blocks: [
{
type: 'text',
content: 'Tests passed\n42 assertions completed',
textType: 'text',
},
],
})

expect(getBasherFinishedOutputPreview(block)).toBe(
'Tests passed 42 assertions completed',
)
})

test('falls back to command output when no text block exists', () => {
const block = createAgentBlock({
status: 'complete',
blocks: [
{
type: 'tool',
toolCallId: 'tool-1',
toolName: 'run_terminal_command',
input: { command: 'git status --short' },
output: ' M cli/src/app.tsx\n',
},
],
})

expect(getBasherFinishedOutputPreview(block)).toBe('M cli/src/app.tsx')
})

test('ignores non-basher output', () => {
const block = createAgentBlock({
agentType: 'code-searcher',
status: 'complete',
blocks: [{ type: 'text', content: 'Search results' }],
})

expect(getBasherFinishedOutputPreview(block)).toBeUndefined()
})
})

describe('truncateToSingleLinePreview', () => {
test('collapses whitespace and truncates to the requested length', () => {
expect(truncateToSingleLinePreview('one\ntwo three four', 13)).toBe(
'one two th...',
)
})
})
68 changes: 67 additions & 1 deletion cli/src/utils/agent-display.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import { getAgentBaseName } from './message-block-helpers'

import type { AgentContentBlock } from '../types/chat'
import type {
AgentContentBlock,
TextContentBlock,
ToolContentBlock,
} from '../types/chat'

const DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH = 120
const PREVIEW_ELLIPSIS = '...'

export function truncateToSingleLinePreview(
text: string,
maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
): string | undefined {
const singleLine = text.replace(/\s+/g, ' ').trim()
if (!singleLine) {
return undefined
}

if (singleLine.length <= maxLength) {
return singleLine
}

const previewLength = Math.max(0, maxLength - PREVIEW_ELLIPSIS.length)
return `${singleLine.slice(0, previewLength).trimEnd()}${PREVIEW_ELLIPSIS}`
}

export function getAgentDisplayPrompt(
agentBlock: AgentContentBlock,
Expand All @@ -19,3 +43,45 @@ export function getAgentDisplayPrompt(
? whatToSummarize.trim()
: undefined
}

export function getBasherFinishedOutputPreview(
agentBlock: AgentContentBlock,
maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
): string | undefined {
if (
getAgentBaseName(agentBlock.agentType) !== 'basher' ||
agentBlock.status === 'running'
) {
return undefined
}

const blocks = agentBlock.blocks ?? []
return (
truncateToSingleLinePreview(getTextOutput(blocks), maxLength) ??
truncateToSingleLinePreview(getCommandOutput(blocks), maxLength)
)
}

function getTextOutput(
blocks: NonNullable<AgentContentBlock['blocks']>,
): string {
return blocks
.filter(
(block): block is TextContentBlock =>
block.type === 'text' && block.textType !== 'reasoning',
)
.map((block) => block.content)
.join('\n')
}

function getCommandOutput(
blocks: NonNullable<AgentContentBlock['blocks']>,
): string {
return blocks
.filter(
(block): block is ToolContentBlock =>
block.type === 'tool' && block.toolName === 'run_terminal_command',
)
.map((block) => block.output ?? '')
.join('\n')
}
Loading