mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(chat): keep turn activity broad
This commit is contained in:
@@ -124,7 +124,7 @@ describe('NativeChatMessageList assistant messages', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('animates the running tool and the tail spinner without pulsing the activity text', () => {
|
||||
it('keeps the broad fallback distinct from the running tool row', () => {
|
||||
render(
|
||||
<NativeChatMessageList
|
||||
session={{
|
||||
@@ -148,44 +148,62 @@ describe('NativeChatMessageList assistant messages', () => {
|
||||
]
|
||||
}}
|
||||
isWorking
|
||||
turnActivity={{ kind: 'description', text: 'Verifying the change' }}
|
||||
expandSignal={false}
|
||||
fontScale={1}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Running pnpm test')).toHaveClass('animate-pulse')
|
||||
const activity = screen.getByText('Verifying the change')
|
||||
const toolLabel = screen.getByText('Running pnpm test')
|
||||
expect(toolLabel).toHaveClass('animate-pulse')
|
||||
expect(screen.getAllByText('Running pnpm test')).toHaveLength(1)
|
||||
const activity = screen.getByText('Working…')
|
||||
expect(activity.textContent).not.toBe(toolLabel.textContent)
|
||||
expect(activity).not.toHaveTextContent('shell')
|
||||
expect(activity).not.toHaveTextContent('pnpm test')
|
||||
const spinner = activity.closest('[data-native-chat-turn-activity]')?.querySelector('svg')
|
||||
expect(activity).not.toHaveClass('animate-pulse', 'animate-spin')
|
||||
expect(spinner).toHaveClass('animate-spin', 'motion-reduce:animate-none')
|
||||
})
|
||||
|
||||
it('restates a completed tool without claiming that it is still running', () => {
|
||||
it('uses the broad fallback after a tool settles', () => {
|
||||
render(
|
||||
<NativeChatMessageList
|
||||
session={{ ...session, status: 'working' }}
|
||||
isWorking
|
||||
turnActivity={{
|
||||
kind: 'tool',
|
||||
call: {
|
||||
type: 'tool-call',
|
||||
name: 'shell',
|
||||
input: { command: 'pnpm test' },
|
||||
state: 'completed'
|
||||
}
|
||||
session={{
|
||||
...session,
|
||||
status: 'working',
|
||||
messages: [
|
||||
{
|
||||
id: 'assistant-completed-tool',
|
||||
role: 'assistant',
|
||||
blocks: [
|
||||
{
|
||||
type: 'tool-call',
|
||||
name: 'shell',
|
||||
input: { command: 'pnpm test' },
|
||||
state: 'completed'
|
||||
},
|
||||
{ type: 'tool-result', output: 'passed' }
|
||||
],
|
||||
timestamp: 1,
|
||||
source: 'transcript'
|
||||
}
|
||||
]
|
||||
}}
|
||||
isWorking
|
||||
expandSignal={false}
|
||||
fontScale={1}
|
||||
/>
|
||||
)
|
||||
|
||||
const activity = screen.getByText('Continuing after pnpm test…')
|
||||
const settledTool = screen.getByText('shell pnpm test')
|
||||
const activity = screen.getByText('Working…')
|
||||
expect(activity.textContent).not.toBe(settledTool.textContent)
|
||||
expect(activity).not.toHaveTextContent('shell')
|
||||
expect(activity).not.toHaveTextContent('pnpm test')
|
||||
expect(activity).not.toHaveClass('animate-pulse', 'animate-spin')
|
||||
expect(activity.closest('[data-native-chat-turn-activity]')?.querySelector('svg')).toHaveClass(
|
||||
'animate-spin'
|
||||
)
|
||||
expect(screen.queryByText('Running pnpm test')).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps a completed tool row static while the turn tail spins, then removes the tail', () => {
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { NativeChatTurnActivity } from './native-chat-turn-activity'
|
||||
import { nativeChatToolActivityLabel } from './native-chat-tool-activity-label'
|
||||
import { describeActiveToolCall } from '../../../../shared/native-chat-tool-activity'
|
||||
|
||||
function completedToolActivityLabel(activity: Extract<NativeChatTurnActivity, { kind: 'tool' }>) {
|
||||
const { preview, toolName } = describeActiveToolCall(activity.call)
|
||||
return translate(
|
||||
'components.native-chat.activity.continuingAfter',
|
||||
'Continuing after {{activity}}…',
|
||||
{ activity: preview || toolName }
|
||||
)
|
||||
}
|
||||
|
||||
export function NativeChatTurnActivityLine({
|
||||
activity
|
||||
}: {
|
||||
activity?: NativeChatTurnActivity | null
|
||||
}): React.JSX.Element {
|
||||
const label =
|
||||
activity?.kind === 'description'
|
||||
? activity.text
|
||||
: activity?.kind === 'tool'
|
||||
? activity.call.state === 'running'
|
||||
? nativeChatToolActivityLabel(activity.call)
|
||||
: completedToolActivityLabel(activity)
|
||||
: translate('components.native-chat.status.working', 'Working…')
|
||||
const label = activity?.text ?? translate('components.native-chat.status.working', 'Working…')
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('selectStructuredAgentTurnActivity', () => {
|
||||
expect(activity).toEqual({ kind: 'description', text: 'Preparing the answer' })
|
||||
})
|
||||
|
||||
it('returns the latest tool for a safe present-tense restatement', () => {
|
||||
it('ignores active and settled tools so the tail can use a broad fallback', () => {
|
||||
const activity = selectStructuredAgentTurnActivity(
|
||||
[
|
||||
turnStart,
|
||||
@@ -42,16 +42,19 @@ describe('selectStructuredAgentTurnActivity', () => {
|
||||
kind: 'tool-call',
|
||||
name: 'shell',
|
||||
input: { command: 'pnpm test' },
|
||||
state: 'running'
|
||||
}),
|
||||
item(3, {
|
||||
kind: 'tool-call',
|
||||
name: 'shell',
|
||||
input: { command: 'pnpm lint' },
|
||||
state: 'completed'
|
||||
})
|
||||
],
|
||||
'turn-1'
|
||||
)
|
||||
|
||||
expect(activity).toMatchObject({
|
||||
kind: 'tool',
|
||||
call: { name: 'shell', input: { command: 'pnpm test' }, state: 'completed' }
|
||||
})
|
||||
expect(activity).toBeNull()
|
||||
})
|
||||
|
||||
it('ignores diagnostic provider frames and returns nothing after the turn settles', () => {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type { AgentJournalRenderItem } from '../../../../shared/agent-session-journal-types'
|
||||
import { normalizePromptField } from '../../../../shared/agent-status-field-normalization'
|
||||
import type { NativeChatBlock } from '../../../../shared/native-chat-types'
|
||||
|
||||
type ToolCall = Extract<NativeChatBlock, { type: 'tool-call' }>
|
||||
|
||||
export type NativeChatTurnActivity =
|
||||
| { kind: 'description'; text: string }
|
||||
| { kind: 'tool'; call: ToolCall }
|
||||
export type NativeChatTurnActivity = { kind: 'description'; text: string }
|
||||
|
||||
function activityLine(text: string): string | null {
|
||||
const lines = text
|
||||
@@ -17,7 +12,7 @@ function activityLine(text: string): string | null {
|
||||
return latest ? normalizePromptField(latest) || null : null
|
||||
}
|
||||
|
||||
/** Prefer provider-authored activity copy, then restate the latest tool in present tense. */
|
||||
/** Prefer provider-authored activity copy; callers provide the broad fallback. */
|
||||
export function selectStructuredAgentTurnActivity(
|
||||
items: readonly AgentJournalRenderItem[],
|
||||
turnId: string | null
|
||||
@@ -42,20 +37,5 @@ export function selectStructuredAgentTurnActivity(
|
||||
return { kind: 'description', text }
|
||||
}
|
||||
}
|
||||
for (let index = turnItems.length - 1; index >= 0; index -= 1) {
|
||||
const body = turnItems[index]?.body
|
||||
if (body?.kind === 'tool-call') {
|
||||
return {
|
||||
kind: 'tool',
|
||||
call: { type: 'tool-call', name: body.name, input: body.input, state: body.state }
|
||||
}
|
||||
}
|
||||
if (body?.kind === 'diff') {
|
||||
return {
|
||||
kind: 'tool',
|
||||
call: { type: 'tool-call', name: 'Diff', input: { path: body.path } }
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user