diff --git a/server/session-lifecycle.ts b/server/session-lifecycle.ts index 625de1b..9ec165f 100644 --- a/server/session-lifecycle.ts +++ b/server/session-lifecycle.ts @@ -172,6 +172,9 @@ export class SessionLifecycle { cp.start() session.claudeProcess = cp + // Reset so the next system_init always broadcasts the model message, + // even if the model hasn't changed since the previous process. + session._lastReportedModel = undefined this.deps.globalBroadcast?.({ type: 'sessions_updated' }) const startMsg: WsServerMessage = { type: 'claude_started', sessionId } diff --git a/src/hooks/useChatSocket.hook.test.ts b/src/hooks/useChatSocket.hook.test.ts index 5ff666d..5956c14 100644 --- a/src/hooks/useChatSocket.hook.test.ts +++ b/src/hooks/useChatSocket.hook.test.ts @@ -554,12 +554,14 @@ describe('useChatSocket hook', () => { unmount() }) - it('claude_started is suppressed (no message added)', () => { + it('claude_started shows Session started message', () => { const { result, unmount } = setupConnected() act(() => MockWebSocket.latest().simulateMessage({ type: 'claude_started', sessionId: 's1', } as WsServerMessage)) - expect(result.current.messages).toHaveLength(0) + expect(result.current.messages).toHaveLength(1) + expect(result.current.messages[0].type).toBe('system') + expect((result.current.messages[0] as any).text).toBe('Session started') unmount() }) diff --git a/src/hooks/useChatSocket.test.ts b/src/hooks/useChatSocket.test.ts index e9d13b9..9f004d2 100644 --- a/src/hooks/useChatSocket.test.ts +++ b/src/hooks/useChatSocket.test.ts @@ -158,9 +158,11 @@ describe('processMessage', () => { expect((result[0] as any).model).toBe('claude-opus-4-6') }) - it('claude_started is suppressed (init shown on system_init instead)', () => { + it('claude_started shows Session started message', () => { const result = processMessage(empty(), { type: 'claude_started', sessionId: '123' } as WsServerMessage) - expect(result).toHaveLength(0) + expect(result).toHaveLength(1) + expect(result[0].type).toBe('system') + expect((result[0] as any).text).toBe('Session started') }) }) @@ -347,11 +349,13 @@ describe('rebuildFromHistory', () => { expect((result[0] as any).text).toBe('User input') }) - it('claude_started is suppressed in history rebuild', () => { + it('claude_started shows Session started in history rebuild', () => { const result = rebuildFromHistory([ { type: 'claude_started', sessionId: 's1' } as WsServerMessage, ]) - expect(result).toHaveLength(0) + expect(result).toHaveLength(1) + expect(result[0].type).toBe('system') + expect((result[0] as any).text).toBe('Session started') }) it('builds tool_group from tool_active', () => { @@ -447,15 +451,17 @@ describe('rebuildFromHistory', () => { { type: 'result' } as WsServerMessage, ] const result = rebuildFromHistory(buffer) - expect(result).toHaveLength(5) - expect(result[0].type).toBe('system') // system_message (claude_started suppressed) - expect(result[1].type).toBe('user') // user_echo - expect(result[2].type).toBe('assistant') // "Sure, I can help." (incomplete — tool_active broke the chain) - expect((result[2] as any).text).toBe('Sure, I can help.') - expect((result[2] as any).complete).toBe(false) - expect(result[3].type).toBe('tool_group') // Read tool - expect(result[4].type).toBe('assistant') // "Here it is." — marked complete by result - expect((result[4] as any).text).toBe('Here it is.') - expect((result[4] as any).complete).toBe(true) + expect(result).toHaveLength(6) + expect(result[0].type).toBe('system') // claude_started → "Session started" + expect((result[0] as any).text).toBe('Session started') + expect(result[1].type).toBe('system') // system_message init + expect(result[2].type).toBe('user') // user_echo + expect(result[3].type).toBe('assistant') // "Sure, I can help." (incomplete — tool_active broke the chain) + expect((result[3] as any).text).toBe('Sure, I can help.') + expect((result[3] as any).complete).toBe(false) + expect(result[4].type).toBe('tool_group') // Read tool + expect(result[5].type).toBe('assistant') // "Here it is." — marked complete by result + expect((result[5] as any).text).toBe('Here it is.') + expect((result[5] as any).complete).toBe(true) }) }) diff --git a/src/hooks/useChatSocket.ts b/src/hooks/useChatSocket.ts index a300d89..7bf17f0 100644 --- a/src/hooks/useChatSocket.ts +++ b/src/hooks/useChatSocket.ts @@ -68,10 +68,8 @@ function applyMessageMut(messages: ChatMessage[], msg: WsServerMessage): boolean return true case 'claude_started': - // Process spawned — don't show a message yet. The actual "Session started" - // is shown when system_init arrives (system_message subtype=init), proving - // the process is fully initialized and ready for input. - return false + messages.push({ type: 'system', subtype: 'init', text: 'Session started', key: nextKey() }) + return true case 'tool_active': if (last && last.type === 'tool_group') {