Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions server/session-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 4 additions & 2 deletions src/hooks/useChatSocket.hook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})

Expand Down
34 changes: 20 additions & 14 deletions src/hooks/useChatSocket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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)
})
})
6 changes: 2 additions & 4 deletions src/hooks/useChatSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
Loading