Skip to content

Commit 03b373b

Browse files
Update basher collapsed output preview (#647)
Co-authored-by: James Grugett <jahooma@gmail.com>
1 parent 2c76162 commit 03b373b

3 files changed

Lines changed: 162 additions & 4 deletions

File tree

cli/src/components/blocks/agent-branch-wrapper.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import { ToolBlockGroup } from './tool-block-group'
1717
import { useTheme } from '../../hooks/use-theme'
1818
import { useChatStore } from '../../state/chat-store'
1919
import { isTextBlock } from '../../types/chat'
20-
import { getAgentDisplayPrompt } from '../../utils/agent-display'
20+
import {
21+
getAgentDisplayPrompt,
22+
getBasherFinishedOutputPreview,
23+
} from '../../utils/agent-display'
2124
import { getAgentStatusInfo } from '../../utils/agent-helpers'
2225
import {
2326
processBlocks,
@@ -52,12 +55,23 @@ function getCollapsedPreview(
5255
agentBlock: AgentContentBlock,
5356
isStreaming: boolean,
5457
isCollapsed: boolean,
58+
availableWidth: number,
5559
): string {
5660
// No preview needed if expanded and not streaming
5761
if (!isStreaming && !isCollapsed) {
5862
return ''
5963
}
6064

65+
if (!isStreaming) {
66+
const outputPreview = getBasherFinishedOutputPreview(
67+
agentBlock,
68+
Math.max(24, Math.min(120, availableWidth - 4)),
69+
)
70+
if (outputPreview) {
71+
return outputPreview
72+
}
73+
}
74+
6175
// For multi-prompt editors, try progress-focused preview first
6276
if (isMultiPromptEditor(agentBlock.agentType)) {
6377
const multiPromptPreview = getMultiPromptPreview(
@@ -427,7 +441,12 @@ export const AgentBranchWrapper = memo(
427441
const isStreaming = agentBlock.status === 'running' || agentIsStreaming
428442

429443
// Compute collapsed preview text
430-
const preview = getCollapsedPreview(agentBlock, isStreaming, isCollapsed)
444+
const preview = getCollapsedPreview(
445+
agentBlock,
446+
isStreaming,
447+
isCollapsed,
448+
availableWidth,
449+
)
431450
const displayPrompt = getAgentDisplayPrompt(agentBlock)
432451

433452
const effectiveStatus = isStreaming ? 'running' : agentBlock.status

cli/src/utils/__tests__/agent-display.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { describe, expect, test } from 'bun:test'
22

3-
import { getAgentDisplayPrompt } from '../agent-display'
3+
import {
4+
getAgentDisplayPrompt,
5+
getBasherFinishedOutputPreview,
6+
truncateToSingleLinePreview,
7+
} from '../agent-display'
48

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

@@ -64,3 +68,72 @@ describe('getAgentDisplayPrompt', () => {
6468
expect(getAgentDisplayPrompt(block)).toBeUndefined()
6569
})
6670
})
71+
72+
describe('getBasherFinishedOutputPreview', () => {
73+
test('returns undefined while basher is still running', () => {
74+
const block = createAgentBlock({
75+
status: 'running',
76+
params: {
77+
what_to_summarize: 'Report the test result',
78+
},
79+
blocks: [{ type: 'text', content: 'Tests passed' }],
80+
})
81+
82+
expect(getBasherFinishedOutputPreview(block)).toBeUndefined()
83+
})
84+
85+
test('uses finished basher text output before what_to_summarize', () => {
86+
const block = createAgentBlock({
87+
status: 'complete',
88+
params: {
89+
what_to_summarize: 'Report the test result',
90+
},
91+
blocks: [
92+
{
93+
type: 'text',
94+
content: 'Tests passed\n42 assertions completed',
95+
textType: 'text',
96+
},
97+
],
98+
})
99+
100+
expect(getBasherFinishedOutputPreview(block)).toBe(
101+
'Tests passed 42 assertions completed',
102+
)
103+
})
104+
105+
test('falls back to command output when no text block exists', () => {
106+
const block = createAgentBlock({
107+
status: 'complete',
108+
blocks: [
109+
{
110+
type: 'tool',
111+
toolCallId: 'tool-1',
112+
toolName: 'run_terminal_command',
113+
input: { command: 'git status --short' },
114+
output: ' M cli/src/app.tsx\n',
115+
},
116+
],
117+
})
118+
119+
expect(getBasherFinishedOutputPreview(block)).toBe('M cli/src/app.tsx')
120+
})
121+
122+
test('ignores non-basher output', () => {
123+
const block = createAgentBlock({
124+
agentType: 'code-searcher',
125+
status: 'complete',
126+
blocks: [{ type: 'text', content: 'Search results' }],
127+
})
128+
129+
expect(getBasherFinishedOutputPreview(block)).toBeUndefined()
130+
})
131+
})
132+
133+
describe('truncateToSingleLinePreview', () => {
134+
test('collapses whitespace and truncates to the requested length', () => {
135+
expect(truncateToSingleLinePreview('one\ntwo three four', 13)).toBe(
136+
'one two th...',
137+
)
138+
})
139+
})

cli/src/utils/agent-display.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
import { getAgentBaseName } from './message-block-helpers'
22

3-
import type { AgentContentBlock } from '../types/chat'
3+
import type {
4+
AgentContentBlock,
5+
TextContentBlock,
6+
ToolContentBlock,
7+
} from '../types/chat'
8+
9+
const DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH = 120
10+
const PREVIEW_ELLIPSIS = '...'
11+
12+
export function truncateToSingleLinePreview(
13+
text: string,
14+
maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
15+
): string | undefined {
16+
const singleLine = text.replace(/\s+/g, ' ').trim()
17+
if (!singleLine) {
18+
return undefined
19+
}
20+
21+
if (singleLine.length <= maxLength) {
22+
return singleLine
23+
}
24+
25+
const previewLength = Math.max(0, maxLength - PREVIEW_ELLIPSIS.length)
26+
return `${singleLine.slice(0, previewLength).trimEnd()}${PREVIEW_ELLIPSIS}`
27+
}
428

529
export function getAgentDisplayPrompt(
630
agentBlock: AgentContentBlock,
@@ -19,3 +43,45 @@ export function getAgentDisplayPrompt(
1943
? whatToSummarize.trim()
2044
: undefined
2145
}
46+
47+
export function getBasherFinishedOutputPreview(
48+
agentBlock: AgentContentBlock,
49+
maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
50+
): string | undefined {
51+
if (
52+
getAgentBaseName(agentBlock.agentType) !== 'basher' ||
53+
agentBlock.status === 'running'
54+
) {
55+
return undefined
56+
}
57+
58+
const blocks = agentBlock.blocks ?? []
59+
return (
60+
truncateToSingleLinePreview(getTextOutput(blocks), maxLength) ??
61+
truncateToSingleLinePreview(getCommandOutput(blocks), maxLength)
62+
)
63+
}
64+
65+
function getTextOutput(
66+
blocks: NonNullable<AgentContentBlock['blocks']>,
67+
): string {
68+
return blocks
69+
.filter(
70+
(block): block is TextContentBlock =>
71+
block.type === 'text' && block.textType !== 'reasoning',
72+
)
73+
.map((block) => block.content)
74+
.join('\n')
75+
}
76+
77+
function getCommandOutput(
78+
blocks: NonNullable<AgentContentBlock['blocks']>,
79+
): string {
80+
return blocks
81+
.filter(
82+
(block): block is ToolContentBlock =>
83+
block.type === 'tool' && block.toolName === 'run_terminal_command',
84+
)
85+
.map((block) => block.output ?? '')
86+
.join('\n')
87+
}

0 commit comments

Comments
 (0)