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
1 change: 1 addition & 0 deletions agents/file-explorer/code-searcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const codeSearcher: SecretAgentDefinition = {
yield {
toolName: 'set_output',
input: {
message: '',
results: toolResults,
},
includeToolCall: false,
Expand Down
45 changes: 45 additions & 0 deletions cli/src/components/tools/__tests__/code-search.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, test } from 'bun:test'
import React from 'react'
import { renderToStaticMarkup } from 'react-dom/server'

import { initializeThemeStore } from '../../../hooks/use-theme'
import { CodeSearchComponent } from '../code-search'

import type { ChatTheme } from '../../../types/theme-system'
import type { ToolBlock } from '../types'

initializeThemeStore()

const createToolBlock = (
output?: string,
): ToolBlock & { toolName: 'code_search' } => ({
type: 'tool',
toolName: 'code_search',
toolCallId: 'code-search-test',
input: {
pattern: 'getAgentBaseName',
cwd: 'cli/src/utils',
},
output,
})

describe('CodeSearchComponent', () => {
test('uses formatted match count from current code search output', () => {
const result = CodeSearchComponent.render(
createToolBlock(`Found 2 matches
./message-block-helpers.ts:
Line 13: export const getAgentBaseName = (type: string): string => {
Line 196: getAgentBaseName(options.agentType ?? '') === 'code-searcher'`),
{} as ChatTheme,
{
availableWidth: 80,
indentationOffset: 0,
labelWidth: 10,
},
)

const markup = renderToStaticMarkup(<>{result.content}</>)

expect(markup).toContain('getAgentBaseName in cli/src/utils (2 results)')
})
})
26 changes: 15 additions & 11 deletions cli/src/components/tools/code-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@ export const CodeSearchComponent = defineToolComponent({

if (toolBlock.output && typeof toolBlock.output === 'string') {
const lines = toolBlock.output.split('\n')
const matchCountLine = lines.find((line) =>
/^Found \d+ matches?$/.test(line.trim()),
)
const parsedTotalResults = matchCountLine
?.trim()
.match(/^Found (\d+) matches?$/)?.[1]

for (const line of lines) {
const trimmed = line.trim()
if (parsedTotalResults !== undefined) {
totalResults = Number(parsedTotalResults)
} else {
for (const line of lines) {
const trimmed = line.trim()

// Result lines start with a number followed by a colon
if (/^\d+:/.test(trimmed)) {
totalResults++
if (/^(?:Line\s+)?\d+:/.test(trimmed)) {
totalResults++
}
}
}
}
Expand All @@ -52,12 +61,7 @@ export const CodeSearchComponent = defineToolComponent({

// Return as content using SimpleToolCallItem
return {
content: (
<SimpleToolCallItem
name="Search"
description={summary}
/>
),
content: <SimpleToolCallItem name="Search" description={summary} />,
}
},
})
17 changes: 17 additions & 0 deletions cli/src/utils/__tests__/message-block-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,23 @@ describe('extractSpawnAgentResultContent', () => {
hasError: false,
})
})

test('uses an empty structuredOutput message as no display content', () => {
const result = extractSpawnAgentResultContent({
type: 'structuredOutput',
value: {
message: '',
results: [
{
stdout: 'Found 1 match\n./file.ts:\nLine 1: needle',
message: 'Exit code: 0',
},
],
},
})

expect(result).toEqual({ content: '', hasError: false })
})
})

describe('appendInterruptionNotice', () => {
Expand Down
34 changes: 34 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,37 @@ CLI hook testing note: React 19 + Bun + RTL `renderHook()` is unreliable; prefer
## CLI tmux Testing

For testing CLI behavior via tmux, use the helper scripts in `scripts/tmux/`. These handle bracketed paste mode and session logging automatically. Session data is saved to `debug/tmux-sessions/` in YAML format and can be viewed with `bun scripts/tmux/tmux-viewer/index.tsx`. See `scripts/tmux/README.md` for details.

Useful workflow for agents:

```bash
# Start the dev CLI in a detached tmux session.
SESSION=$(./scripts/tmux/tmux-cli.sh start --name cli-check -w 160 -h 40 --wait 6)

# Capture the initial screen. Captures are written to debug/tmux-sessions/$SESSION/.
./scripts/tmux/tmux-cli.sh capture "$SESSION" --label initial

# Send a prompt. The helper uses bracketed paste so text is not dropped.
./scripts/tmux/tmux-cli.sh send "$SESSION" "Search for getAgentBaseName and report what you find" --wait-idle 4

# Capture after the run, then inspect the saved capture text.
./scripts/tmux/tmux-cli.sh capture "$SESSION" --label after-search --wait 2

# Clean up when finished.
./scripts/tmux/tmux-cli.sh stop "$SESSION"
```

If a change can be verified with a small local harness instead of a live model-backed CLI run, run that harness inside tmux too. This still checks terminal rendering and produces a capture:

```bash
SESSION=$(./scripts/tmux/tmux-cli.sh start \
--name render-check \
-w 160 -h 20 \
--wait 1 \
--command "bun .context/my-render-check.tsx")

./scripts/tmux/tmux-cli.sh capture "$SESSION" --label rendered
./scripts/tmux/tmux-cli.sh stop "$SESSION"
```

When verifying UI output, prefer checking the saved capture file for concrete strings that should and should not appear. For example, after expanding a code-searcher agent, check that the capture shows the search summary but not raw structured payload keys like `results:` or `stdout:`.
Loading