mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Prevent task-progress updates from corrupting streamed reply formatting
Desktop chat currently reuses one status channel for assistant streaming and background task progress. When a task update arrived mid-turn, the store flushed the in-flight markdown into a completed message, which caused the remaining deltas to render as a second bubble and broke markdown styling. Keep the reply in streaming state until the turn actually becomes idle, and lock the behavior with a regression test that injects task progress between text deltas. Constraint: Background task progress currently shares the same status channel as assistant streaming Rejected: Retune markdown renderer styles | would hide the symptom without preserving message integrity Confidence: high Scope-risk: narrow Reversibility: clean Directive: Do not flush streaming assistant text on non-idle status transitions unless the turn is actually ending Tested: bun run test src/stores/chatStore.test.ts; bun run lint Not-tested: Manual desktop UI verification with a live agent session
This commit is contained in:
parent
48da5ccbfa
commit
90c3db1790
@ -618,6 +618,71 @@ describe('chatStore history mapping', () => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('does not split one streamed markdown reply when task progress arrives mid-stream', () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
useChatStore.setState({
|
||||
sessions: {
|
||||
[TEST_SESSION_ID]: {
|
||||
messages: [],
|
||||
chatState: 'idle',
|
||||
connectionState: 'connected',
|
||||
streamingText: '',
|
||||
streamingToolInput: '',
|
||||
activeToolUseId: null,
|
||||
activeToolName: null,
|
||||
activeThinkingId: null,
|
||||
pendingPermission: null,
|
||||
pendingComputerUsePermission: null,
|
||||
tokenUsage: { input_tokens: 0, output_tokens: 0 },
|
||||
elapsedSeconds: 0,
|
||||
statusVerb: '',
|
||||
slashCommands: [],
|
||||
agentTaskNotifications: {},
|
||||
elapsedTimer: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
|
||||
type: 'content_start',
|
||||
blockType: 'text',
|
||||
})
|
||||
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
|
||||
type: 'content_delta',
|
||||
text: '1. **`core/audio/waveform.py:19-31`** — 同步阻塞 I/O。',
|
||||
})
|
||||
vi.advanceTimersByTime(60)
|
||||
|
||||
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
|
||||
type: 'status',
|
||||
state: 'tool_executing',
|
||||
verb: 'Task in progress',
|
||||
})
|
||||
|
||||
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
|
||||
type: 'content_delta',
|
||||
text: ' 建议直接用 `subprocess.PIPE` 流式处理。',
|
||||
})
|
||||
vi.advanceTimersByTime(60)
|
||||
|
||||
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
|
||||
type: 'message_complete',
|
||||
usage: { input_tokens: 1, output_tokens: 2 },
|
||||
})
|
||||
|
||||
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([
|
||||
{
|
||||
type: 'assistant_text',
|
||||
content:
|
||||
'1. **`core/audio/waveform.py:19-31`** — 同步阻塞 I/O。 建议直接用 `subprocess.PIPE` 流式处理。',
|
||||
},
|
||||
])
|
||||
|
||||
vi.runOnlyPendingTimers()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('sends Computer Use approval payloads back over websocket', () => {
|
||||
useChatStore.setState({
|
||||
sessions: {
|
||||
|
||||
@ -511,9 +511,16 @@ export const useChatStore = create<ChatStore>((set, get) => ({
|
||||
case 'status':
|
||||
update((session) => {
|
||||
const pendingText = `${session.streamingText}${consumePendingDelta()}`
|
||||
const shouldFlush = pendingText.trim() && session.chatState === 'streaming' && msg.state !== 'streaming'
|
||||
const hasPendingStreamText =
|
||||
session.chatState === 'streaming' && pendingText.trim().length > 0
|
||||
// Background task progress can arrive while the assistant is still
|
||||
// streaming one markdown reply. Keep that turn intact so we do not
|
||||
// split formatting markers (for example backticks/strong markers)
|
||||
// across separate bubbles.
|
||||
const preserveStreamingTurn = hasPendingStreamText && msg.state !== 'idle'
|
||||
const shouldFlush = hasPendingStreamText && msg.state === 'idle'
|
||||
return {
|
||||
chatState: msg.state,
|
||||
chatState: preserveStreamingTurn ? 'streaming' : msg.state,
|
||||
...(msg.verb && msg.verb !== 'Thinking' ? { statusVerb: msg.verb } : {}),
|
||||
...(msg.tokens ? { tokenUsage: { ...session.tokenUsage, output_tokens: msg.tokens } } : {}),
|
||||
...(msg.state === 'idle' ? { activeThinkingId: null, statusVerb: '' } : {}),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user