Prevent stale worktree startup labels in chat

The desktop status store retained the last non-default server verb until idle, so a one-time worktree startup label could leak into later thinking or execution bubbles. The fix treats default Thinking and verb-less status updates as a reset for transient status text while preserving explicit non-default labels.

Constraint: Server-side duplicate worktree startup guards are still valid and should remain the source of launch truth.

Rejected: Suppress the label in StreamingIndicator | stale state would still leak to any future consumer of statusVerb.

Confidence: high

Scope-risk: narrow

Directive: Keep statusVerb transient; default Thinking or missing verbs must not preserve prior special labels.

Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts --run

Tested: cd desktop && bun run lint

Tested: bun test src/server/__tests__/conversations.test.ts -t worktree --timeout 30000

Tested: git diff --check
This commit is contained in:
程序员阿江(Relakkes) 2026-05-18 23:37:28 +08:00
parent 3c23788ff7
commit 11f83a76c5
2 changed files with 41 additions and 2 deletions

View File

@ -2230,6 +2230,41 @@ describe('chatStore history mapping', () => {
vi.useRealTimers()
})
it('clears transient worktree startup text when normal thinking resumes', () => {
useChatStore.setState({
sessions: {
[TEST_SESSION_ID]: makeSession({
chatState: 'idle',
}),
},
})
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'status',
state: 'thinking',
verb: 'Creating worktree',
})
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.statusVerb).toBe('Creating worktree')
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'status',
state: 'thinking',
verb: 'Thinking',
})
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.statusVerb).toBe('')
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'status',
state: 'thinking',
verb: 'Creating worktree',
})
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'status',
state: 'thinking',
})
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.statusVerb).toBe('')
})
it('sends a desktop notification when the agent finishes a markdown reply', () => {
vi.useFakeTimers()

View File

@ -799,9 +799,13 @@ export const useChatStore = create<ChatStore>((set, get) => ({
const shouldFlush = hasPendingStreamText && msg.state === 'idle'
return {
chatState: preserveStreamingTurn ? 'streaming' : msg.state,
...(msg.verb && msg.verb !== 'Thinking' ? { statusVerb: msg.verb } : {}),
statusVerb: msg.state === 'idle'
? ''
: msg.verb && msg.verb !== 'Thinking'
? msg.verb
: '',
...(msg.tokens ? { tokenUsage: { ...session.tokenUsage, output_tokens: msg.tokens } } : {}),
...(msg.state === 'idle' ? { activeThinkingId: null, statusVerb: '' } : {}),
...(msg.state === 'idle' ? { activeThinkingId: null } : {}),
...(shouldFlush ? {
messages: appendAssistantTextMessage(session.messages, pendingText, Date.now()),
streamingText: '',