mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
* Replace assistant-prose heuristic with explicit turn lifecycle markers Extract provider-authored turn boundaries (completion, interruption) directly from Claude/Codex transcripts so the chat view knows when work ends without guessing from message presence. Reconciles live hook state with transcript lifecycle: when a terminal boundary lands, it settles a dropped Stop hook instead of letting prose mislead the UI into showing 'working' after done. * fix(review): cover Claude terminal stop_reasons and RPC lifecycle frames Treat max_tokens/stop_sequence/refusal as completed markers so capable hosts do not stay working after a dropped Stop, and assert lifecycle payloads on runtime subscribe/read frames plus mid-turn non-terminal stop_reason cases. * test(native-chat): clarify that lifecycle field is optional Add type assertion and comment documenting that lifecycle field is optional and can be omitted in truncation-gating test fixtures. * fix(native-chat): settle status on interruption despite working subagent When Claude's turn is explicitly interrupted, the session should show ready immediately — even if background subagents are still running. Add an interruption check before consulting the hook's working-subagents flag so interruptions take precedence. Also normalize omitted lifecycle timestamps to null instead of leaving them undefined, and add test coverage for both cases. * fix(native-chat): settle loading spinner on explicit turn boundaries Explicit transcript turn-lifecycle markers now fully replace the prose-fallback settlement path. Remove the now-unused `turnLifecycleCapable` flag and wire lifecycle to suppress spinner even when hook status lingers. Refine Claude lifecycle detection to distinguish terminal stops from mid-turn tool_use rows, exclude harness noise from new-generation detection, and apply clock-skew slack over SSH/relay. Serialize PTY sends per line to prevent rapid prompts from gluing before Enter, clearing unsubmitted input on cancel. Update working suppression to detect epoch rollovers so interrupt+next-turn without a ready gap resets the spinner correctly.
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
NATIVE_CHAT_INTERRUPTED_STATUS_TEXT,
|
|
type NativeChatMessage
|
|
} from '../../shared/native-chat-types'
|
|
import { stripNoiseMessages } from '../../shared/native-chat-noise'
|
|
import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude'
|
|
import { decodeCodexTranscriptLine } from './transcript-line-decoders-codex'
|
|
|
|
function expectNormalizedInterruption(message: NativeChatMessage | null): void {
|
|
expect(message).toMatchObject({
|
|
role: 'system',
|
|
blocks: [{ type: 'text', text: NATIVE_CHAT_INTERRUPTED_STATUS_TEXT }],
|
|
source: 'transcript'
|
|
})
|
|
expect(stripNoiseMessages(message ? [message] : [])).toEqual([message])
|
|
}
|
|
|
|
describe('native chat transcript interruption messages', () => {
|
|
it('normalizes Claude interruption boilerplate into one visible status row', () => {
|
|
const message = decodeClaudeTranscriptLine(
|
|
JSON.stringify({
|
|
type: 'user',
|
|
uuid: 'interrupt-row',
|
|
interruptedMessageId: 'assistant-request-1',
|
|
timestamp: '2026-07-16T23:46:01.000Z',
|
|
message: {
|
|
role: 'user',
|
|
content: [{ type: 'text', text: '[Request interrupted by user]' }]
|
|
}
|
|
}),
|
|
'fallback'
|
|
)
|
|
|
|
expectNormalizedInterruption(message)
|
|
expect(message?.id).toBe('interrupt-row')
|
|
})
|
|
|
|
it('normalizes Codex turn_aborted into one visible status row', () => {
|
|
const message = decodeCodexTranscriptLine(
|
|
JSON.stringify({
|
|
type: 'event_msg',
|
|
timestamp: '2026-07-16T23:46:01.000Z',
|
|
payload: { type: 'turn_aborted', reason: 'interrupted', turn_id: 'turn-2' }
|
|
}),
|
|
'fallback'
|
|
)
|
|
|
|
expectNormalizedInterruption(message)
|
|
expect(message?.id).toBe('fallback')
|
|
})
|
|
})
|