import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const { gatewayRpcMock, hostApiFetchMock, agentsState } = vi.hoisted(() => ({ gatewayRpcMock: vi.fn(), hostApiFetchMock: vi.fn(), agentsState: { agents: [] as Array>, }, })); vi.mock('@/stores/gateway', () => ({ useGatewayStore: { getState: () => ({ rpc: gatewayRpcMock, }), }, })); vi.mock('@/stores/agents', () => ({ useAgentsStore: { getState: () => agentsState, }, })); vi.mock('@/lib/host-api', () => ({ hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args), })); describe('chat target routing', () => { beforeEach(() => { vi.resetModules(); vi.useFakeTimers(); vi.setSystemTime(new Date('2026-03-11T12:00:00Z')); window.localStorage.clear(); agentsState.agents = [ { id: 'main', name: 'Main', isDefault: true, modelDisplay: 'MiniMax', inheritedModel: true, workspace: '~/.openclaw/workspace', agentDir: '~/.openclaw/agents/main/agent', mainSessionKey: 'agent:main:main', channelTypes: [], }, { id: 'research', name: 'Research', isDefault: false, modelDisplay: 'Claude', inheritedModel: false, workspace: '~/.openclaw/workspace-research', agentDir: '~/.openclaw/agents/research/agent', mainSessionKey: 'agent:research:desk', channelTypes: [], }, ]; gatewayRpcMock.mockReset(); gatewayRpcMock.mockImplementation(async (method: string) => { if (method === 'chat.history') { return { messages: [] }; } if (method === 'chat.send') { return { runId: 'run-text' }; } if (method === 'chat.abort') { return { ok: true }; } if (method === 'sessions.list') { return { sessions: [] }; } throw new Error(`Unexpected gateway RPC: ${method}`); }); hostApiFetchMock.mockReset(); hostApiFetchMock.mockResolvedValue({ success: true, result: { runId: 'run-media' } }); }); afterEach(() => { vi.useRealTimers(); }); it('switches to the selected agent main session before sending text', async () => { const { useChatStore } = await import('@/stores/chat'); useChatStore.setState({ currentSessionKey: 'agent:main:main', currentAgentId: 'main', sessions: [{ key: 'agent:main:main' }], messages: [{ role: 'assistant', content: 'Existing main history' }], sessionLabels: {}, sessionLastActivity: {}, sending: false, activeRunId: null, streamingText: '', streamingMessage: null, streamingTools: [], pendingFinal: false, lastUserMessageAt: null, pendingToolImages: [], error: null, loading: false, thinkingLevel: null, }); await useChatStore.getState().sendMessage('Hello direct agent', undefined, 'research'); const state = useChatStore.getState(); expect(state.currentSessionKey).toBe('agent:research:desk'); expect(state.currentAgentId).toBe('research'); expect(state.sessions.some((session) => session.key === 'agent:research:desk')).toBe(true); expect(state.messages.at(-1)?.content).toBe('Hello direct agent'); const historyCall = gatewayRpcMock.mock.calls.find(([method]) => method === 'chat.history'); expect(historyCall?.[1]).toEqual({ sessionKey: 'agent:research:desk', limit: 200 }); const sendCall = gatewayRpcMock.mock.calls.find(([method]) => method === 'chat.send'); expect(sendCall?.[1]).toMatchObject({ sessionKey: 'agent:research:desk', message: 'Hello direct agent', deliver: false, }); expect(typeof (sendCall?.[1] as { idempotencyKey?: unknown })?.idempotencyKey).toBe('string'); }); it('uses the selected agent main session for attachment sends', async () => { const { useChatStore } = await import('@/stores/chat'); useChatStore.setState({ currentSessionKey: 'agent:main:main', currentAgentId: 'main', sessions: [{ key: 'agent:main:main' }], messages: [], sessionLabels: {}, sessionLastActivity: {}, sending: false, activeRunId: null, streamingText: '', streamingMessage: null, streamingTools: [], pendingFinal: false, lastUserMessageAt: null, pendingToolImages: [], error: null, loading: false, thinkingLevel: null, }); await useChatStore.getState().sendMessage( '', [ { fileName: 'design.png', mimeType: 'image/png', fileSize: 128, stagedPath: '/tmp/design.png', preview: 'data:image/png;base64,abc', }, ], 'research', ); expect(useChatStore.getState().currentSessionKey).toBe('agent:research:desk'); expect(hostApiFetchMock).toHaveBeenCalledWith( '/api/chat/send-with-media', expect.objectContaining({ method: 'POST', body: expect.any(String), }), ); const payload = JSON.parse( (hostApiFetchMock.mock.calls[0]?.[1] as { body: string }).body, ) as { sessionKey: string; message: string; media: Array<{ filePath: string }>; }; expect(payload.sessionKey).toBe('agent:research:desk'); expect(payload.message).toBe('Process the attached file(s).'); expect(payload.media[0]?.filePath).toBe('/tmp/design.png'); }); });