import { beforeEach, describe, expect, it, vi } from 'vitest' import type { MessageEntry } from '../types/session' import { useSessionRuntimeStore } from './sessionRuntimeStore' const { sendMock, getMemberBySessionIdMock, sendMessageToMemberMock, handleTeamCreatedMock, handleTeamUpdateMock, handleTeamDeletedMock, fetchSessionTasksMock, clearTasksMock, setTasksFromTodosMock, markCompletedAndDismissedMock, resetCompletedTasksMock, refreshTasksMock, cliTaskStoreSnapshot, } = vi.hoisted(() => ({ sendMock: vi.fn(), getMemberBySessionIdMock: vi.fn<(sessionId: string) => any>(() => null), sendMessageToMemberMock: vi.fn(async () => {}), handleTeamCreatedMock: vi.fn(), handleTeamUpdateMock: vi.fn(), handleTeamDeletedMock: vi.fn(), fetchSessionTasksMock: vi.fn(), clearTasksMock: vi.fn(), setTasksFromTodosMock: vi.fn(), markCompletedAndDismissedMock: vi.fn(), resetCompletedTasksMock: vi.fn(async () => {}), refreshTasksMock: vi.fn(), cliTaskStoreSnapshot: { tasks: [] as Array<{ id: string; subject: string; status: string; activeForm?: string }>, sessionId: null as string | null, }, })) vi.mock('../api/websocket', () => ({ wsManager: { connect: vi.fn(), disconnect: vi.fn(), onMessage: vi.fn(() => () => {}), clearHandlers: vi.fn(), send: sendMock, }, })) vi.mock('../api/sessions', () => ({ sessionsApi: { getMessages: vi.fn(async () => ({ messages: [] })), getSlashCommands: vi.fn(async () => ({ commands: [] })), }, })) vi.mock('./teamStore', () => ({ useTeamStore: { getState: () => ({ getMemberBySessionId: getMemberBySessionIdMock, sendMessageToMember: sendMessageToMemberMock, handleTeamCreated: handleTeamCreatedMock, handleTeamUpdate: handleTeamUpdateMock, handleTeamDeleted: handleTeamDeletedMock, }), }, })) vi.mock('./tabStore', () => ({ useTabStore: { getState: () => ({ updateTabStatus: vi.fn(), updateTabTitle: vi.fn(), }), }, })) vi.mock('./sessionStore', () => ({ useSessionStore: { getState: () => ({ updateSessionTitle: vi.fn(), }), }, })) vi.mock('./cliTaskStore', () => ({ useCLITaskStore: { getState: () => ({ fetchSessionTasks: fetchSessionTasksMock, tasks: cliTaskStoreSnapshot.tasks, sessionId: cliTaskStoreSnapshot.sessionId, clearTasks: clearTasksMock, setTasksFromTodos: setTasksFromTodosMock, markCompletedAndDismissed: markCompletedAndDismissedMock, resetCompletedTasks: resetCompletedTasksMock, refreshTasks: refreshTasksMock, }), }, })) import { mapHistoryMessagesToUiMessages, useChatStore } from './chatStore' const TEST_SESSION_ID = 'test-session-1' const initialState = useChatStore.getState() describe('chatStore history mapping', () => { beforeEach(() => { sendMock.mockReset() getMemberBySessionIdMock.mockReset() getMemberBySessionIdMock.mockReturnValue(null) sendMessageToMemberMock.mockReset() fetchSessionTasksMock.mockReset() clearTasksMock.mockReset() setTasksFromTodosMock.mockReset() markCompletedAndDismissedMock.mockReset() resetCompletedTasksMock.mockReset() refreshTasksMock.mockReset() cliTaskStoreSnapshot.tasks = [] cliTaskStoreSnapshot.sessionId = null useSessionRuntimeStore.setState({ selections: {} }) localStorage.clear() useChatStore.setState({ ...initialState, sessions: {}, }) }) it('preserves thinking blocks when restoring transcript history', () => { const messages: MessageEntry[] = [ { id: 'assistant-1', type: 'assistant', timestamp: '2026-04-06T00:00:00.000Z', model: 'opus', parentToolUseId: 'agent-1', content: [ { type: 'thinking', thinking: 'internal reasoning' }, { type: 'text', text: '目录结构分析' }, { type: 'tool_use', name: 'Read', id: 'tool-1', input: { file_path: 'src/App.tsx' } }, ], }, { id: 'user-1', type: 'user', timestamp: '2026-04-06T00:00:01.000Z', parentToolUseId: 'agent-1', content: [ { type: 'tool_result', tool_use_id: 'tool-1', content: 'ok', is_error: false }, ], }, ] const mapped = mapHistoryMessagesToUiMessages(messages) expect(mapped.map((message) => message.type)).toEqual([ 'thinking', 'assistant_text', 'tool_use', 'tool_result', ]) expect(mapped[2]).toMatchObject({ parentToolUseId: 'agent-1' }) expect(mapped[3]).toMatchObject({ parentToolUseId: 'agent-1' }) }) it('merges consecutive assistant text blocks when restoring transcript history', () => { const messages: MessageEntry[] = [ { id: 'assistant-merge-1', type: 'assistant', timestamp: '2026-04-06T00:00:00.000Z', model: 'opus', content: [ { type: 'text', text: '第一段:Windows 下的桌面端输出。' }, { type: 'text', text: '\r\n第二段:刷新后也不应该被拆开。' }, ], }, ] const mapped = mapHistoryMessagesToUiMessages(messages) expect(mapped).toMatchObject([ { type: 'assistant_text', content: '第一段:Windows 下的桌面端输出。\r\n第二段:刷新后也不应该被拆开。', }, ]) }) it('surfaces teammate prompt content when mapping member transcript history', () => { const messages: MessageEntry[] = [ { id: 'user-1', type: 'user', timestamp: '2026-04-06T00:00:00.000Z', content: 'Review the auth diff and call out risks.', }, ] const mapped = mapHistoryMessagesToUiMessages(messages, { includeTeammateMessages: true, }) expect(mapped).toMatchObject([ { type: 'user_text', content: 'Review the auth diff and call out risks.', }, ]) }) it('preserves source user ids when restoring array-content user prompts', () => { const messages: MessageEntry[] = [ { id: 'user-with-attachment', type: 'user', timestamp: '2026-04-06T00:00:00.000Z', content: [ { type: 'text', text: '请看这个文件' }, { type: 'file', name: 'report.md' }, ], }, ] const mapped = mapHistoryMessagesToUiMessages(messages) expect(mapped).toMatchObject([ { id: 'user-with-attachment', type: 'user_text', content: '请看这个文件', attachments: [{ type: 'file', name: 'report.md' }], }, ]) }) it('keeps parent tool linkage for live tool events', () => { // Initialize the session first 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: [{ name: 'old-command', description: 'Old command' }], agentTaskNotifications: {}, elapsedTimer: null, }, }, }) useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { type: 'tool_use_complete', toolName: 'Read', toolUseId: 'tool-1', input: { file_path: 'src/App.tsx' }, parentToolUseId: 'agent-1', }) useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { type: 'tool_result', toolUseId: 'tool-1', content: 'ok', isError: false, parentToolUseId: 'agent-1', }) expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([ { type: 'tool_use', toolUseId: 'tool-1', parentToolUseId: 'agent-1', }, { type: 'tool_result', toolUseId: 'tool-1', parentToolUseId: 'agent-1', }, ]) }) it('replays saved runtime selection when reconnecting a session', () => { useSessionRuntimeStore.getState().setSelection(TEST_SESSION_ID, { providerId: 'provider-1', modelId: 'kimi-k2.6', }) useChatStore.getState().connectToSession(TEST_SESSION_ID) expect(sendMock).toHaveBeenCalledWith(TEST_SESSION_ID, { type: 'set_runtime_config', providerId: 'provider-1', modelId: 'kimi-k2.6', }) expect(sendMock.mock.calls.slice(0, 2)).toEqual([ [ TEST_SESSION_ID, { type: 'set_runtime_config', providerId: 'provider-1', modelId: 'kimi-k2.6', }, ], [TEST_SESSION_ID, { type: 'prewarm_session' }], ]) }) it('prewarms regular desktop sessions when connecting', () => { useChatStore.getState().connectToSession(TEST_SESSION_ID) expect(sendMock).toHaveBeenCalledWith(TEST_SESSION_ID, { type: 'prewarm_session', }) }) it('does not prewarm team member sessions', () => { getMemberBySessionIdMock.mockReturnValue({ agentId: 'reviewer@test-team', role: 'reviewer', status: 'running', }) useChatStore.getState().connectToSession(TEST_SESSION_ID) expect(sendMock).not.toHaveBeenCalledWith(TEST_SESSION_ID, { type: 'prewarm_session', }) }) it('does not prewarm synthetic app tabs', () => { useChatStore.getState().connectToSession('__settings__') expect(sendMock).not.toHaveBeenCalledWith('__settings__', { type: 'prewarm_session', }) }) it('sends explicit runtime overrides over websocket', () => { useChatStore.getState().setSessionRuntime(TEST_SESSION_ID, { providerId: null, modelId: 'claude-opus-4-7', }) expect(sendMock).toHaveBeenCalledWith(TEST_SESSION_ID, { type: 'set_runtime_config', providerId: null, modelId: 'claude-opus-4-7', }) }) it('keeps AskUserQuestion permission requests out of the message list while tracking the pending request', () => { useChatStore.setState({ sessions: { [TEST_SESSION_ID]: { messages: [ { id: 'ask-1', type: 'tool_use', toolName: 'AskUserQuestion', toolUseId: 'tool-ask-1', input: { questions: [ { question: 'Should we persist data?', options: [{ label: 'No' }, { label: 'Yes' }], }, ], }, timestamp: 1, }, ], 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: 'permission_request', requestId: 'perm-ask-1', toolName: 'AskUserQuestion', toolUseId: 'tool-ask-1', input: { questions: [ { question: 'Should we persist data?', options: [{ label: 'No' }, { label: 'Yes' }], }, ], }, }) const session = useChatStore.getState().sessions[TEST_SESSION_ID] expect(session?.pendingPermission).toMatchObject({ requestId: 'perm-ask-1', toolName: 'AskUserQuestion', toolUseId: 'tool-ask-1', }) expect(session?.messages).toHaveLength(1) expect(session?.messages[0]).toMatchObject({ type: 'tool_use', toolUseId: 'tool-ask-1', }) }) it('sends permission mode updates to the active session only', () => { useChatStore.getState().setSessionPermissionMode('nonexistent-session', 'acceptEdits') expect(sendMock).not.toHaveBeenCalled() useChatStore.setState({ sessions: { 'session-1': { 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().setSessionPermissionMode('session-1', 'acceptEdits') expect(sendMock).toHaveBeenCalledWith('session-1', { type: 'set_permission_mode', mode: 'acceptEdits', }) }) it('stores terminal task notifications for agent tool cards', () => { 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: 'system_notification', subtype: 'task_notification', data: { task_id: 'agent-task-1', tool_use_id: 'agent-tool-1', status: 'completed', summary: 'Agent "修复异常处理" completed', output_file: '/tmp/agent-output.txt', }, }) expect( useChatStore.getState().sessions[TEST_SESSION_ID]?.agentTaskNotifications[ 'agent-tool-1' ], ).toMatchObject({ taskId: 'agent-task-1', toolUseId: 'agent-tool-1', status: 'completed', summary: 'Agent "修复异常处理" completed', outputFile: '/tmp/agent-output.txt', }) }) it('clears local desktop chat state when the server confirms /clear', () => { useChatStore.setState({ sessions: { [TEST_SESSION_ID]: { messages: [ { id: 'u1', type: 'user_text', content: '/clear', timestamp: Date.now() }, { id: 'a1', type: 'assistant_text', content: 'old context', timestamp: Date.now() }, ], chatState: 'thinking', connectionState: 'connected', streamingText: 'pending', streamingToolInput: 'tool', activeToolUseId: 'tool-1', activeToolName: 'Read', activeThinkingId: 'thinking-1', pendingPermission: null, pendingComputerUsePermission: null, tokenUsage: { input_tokens: 12, output_tokens: 34 }, elapsedSeconds: 5, statusVerb: 'Thinking', slashCommands: [], agentTaskNotifications: {}, elapsedTimer: null, }, }, }) useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { type: 'system_notification', subtype: 'session_cleared', message: 'Conversation cleared', }) const session = useChatStore.getState().sessions[TEST_SESSION_ID] expect(session?.messages).toEqual([]) expect(session?.streamingText).toBe('') expect(session?.chatState).toBe('idle') expect(session?.tokenUsage).toEqual({ input_tokens: 0, output_tokens: 0 }) expect(session?.slashCommands).toEqual([]) expect(clearTasksMock).toHaveBeenCalled() }) it('renders compact boundary notifications as system messages', () => { 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: 'system_notification', subtype: 'compact_boundary', message: 'Context compacted', }) expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([ { type: 'system', content: 'Context compacted' }, ]) }) it('flushes the previous assistant draft before starting a new user turn', () => { useChatStore.setState({ sessions: { [TEST_SESSION_ID]: { messages: [], chatState: 'streaming', 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().sendMessage(TEST_SESSION_ID, '你是什么模型?') expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([ { type: 'assistant_text', content: '上一次分析结果 **还在流式区域**', }, { type: 'user_text', content: '你是什么模型?', }, ]) expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.streamingText).toBe('') }) it('resets completed CLI tasks before continuing the next user turn', () => { cliTaskStoreSnapshot.sessionId = TEST_SESSION_ID cliTaskStoreSnapshot.tasks = [ { id: '1', subject: 'Existing completed task', status: 'completed' }, { id: '2', subject: 'Another completed task', status: 'completed' }, ] 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().sendMessage(TEST_SESSION_ID, '继续下一轮') expect(resetCompletedTasksMock).toHaveBeenCalledTimes(1) expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([ { type: 'task_summary', tasks: [ { id: '1', subject: 'Existing completed task', status: 'completed' }, { id: '2', subject: 'Another completed task', status: 'completed' }, ], }, { type: 'user_text', content: '继续下一轮', }, ]) }) it('tracks Computer Use approval requests separately from generic tool permissions', () => { 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: 'computer_use_permission_request', requestId: 'cu-1', request: { requestId: 'cu-1', reason: 'Open Finder and inspect a file', apps: [ { requestedName: 'Finder', resolved: { bundleId: 'com.apple.finder', displayName: 'Finder', }, isSentinel: false, alreadyGranted: false, proposedTier: 'full', }, ], requestedFlags: { clipboardRead: true }, screenshotFiltering: 'native', }, }) expect( useChatStore.getState().sessions[TEST_SESSION_ID]?.pendingComputerUsePermission, ).toMatchObject({ requestId: 'cu-1', request: { reason: 'Open Finder and inspect a file', }, }) expect( useChatStore.getState().sessions[TEST_SESSION_ID]?.chatState, ).toBe('permission_pending') }) it('keeps delayed text blocks from one streamed assistant turn in a single message', () => { 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: '第一段:先到达。', }) vi.advanceTimersByTime(60) useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { type: 'content_start', blockType: 'text', }) useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { type: 'content_delta', text: '\r\n第二段:稍后到达,但仍属于同一轮回复。', }) 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: '第一段:先到达。\r\n第二段:稍后到达,但仍属于同一轮回复。', }, ]) vi.runOnlyPendingTimers() 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: { [TEST_SESSION_ID]: { messages: [], chatState: 'permission_pending', connectionState: 'connected', streamingText: '', streamingToolInput: '', activeToolUseId: null, activeToolName: null, activeThinkingId: null, pendingPermission: null, pendingComputerUsePermission: { requestId: 'cu-1', request: { requestId: 'cu-1', reason: 'Open Finder', apps: [], requestedFlags: {}, screenshotFiltering: 'native', }, }, tokenUsage: { input_tokens: 0, output_tokens: 0 }, elapsedSeconds: 0, statusVerb: '', slashCommands: [], agentTaskNotifications: {}, elapsedTimer: null, }, }, }) useChatStore.getState().respondToComputerUsePermission(TEST_SESSION_ID, 'cu-1', { granted: [], denied: [], flags: { clipboardRead: true, clipboardWrite: false, systemKeyCombos: false, }, userConsented: true, }) expect(sendMock).toHaveBeenCalledWith(TEST_SESSION_ID, { type: 'computer_use_permission_response', requestId: 'cu-1', response: { granted: [], denied: [], flags: { clipboardRead: true, clipboardWrite: false, systemKeyCombos: false, }, userConsented: true, }, }) expect( useChatStore.getState().sessions[TEST_SESSION_ID]?.pendingComputerUsePermission, ).toBeNull() expect( useChatStore.getState().sessions[TEST_SESSION_ID]?.chatState, ).toBe('tool_executing') }) it('routes member-session messages through team mailbox delivery instead of websocket', async () => { const memberSessionId = 'team-member:security-reviewer@test-team' getMemberBySessionIdMock.mockReturnValue({ agentId: 'security-reviewer@test-team', role: 'security-reviewer', status: 'running', }) useChatStore.setState({ sessions: { [memberSessionId]: { 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().sendMessage(memberSessionId, 'Check the latest regression') await Promise.resolve() expect(sendMessageToMemberMock).toHaveBeenCalledWith( memberSessionId, 'Check the latest regression', ) expect(sendMock).not.toHaveBeenCalled() const sessionMessages = useChatStore.getState().sessions[memberSessionId]?.messages ?? [] expect(sessionMessages[sessionMessages.length - 1]).toMatchObject({ type: 'user_text', content: 'Check the latest regression', pending: true, }) }) it('refreshes CLI tasks when switching to an already-connected session', () => { 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().connectToSession(TEST_SESSION_ID) expect(fetchSessionTasksMock).toHaveBeenCalledWith(TEST_SESSION_ID) }) })