import { beforeEach, describe, expect, it, vi } from 'vitest'; import { WorksSquareDesignWorkspace } from '@electron/image-workspace/works-square-workspace'; import { getValidWorksSquareAccessToken } from '@electron/services/works-square-session'; import type { DesignWorkspaceEvent } from '../../shared/image-workspace'; vi.mock('@electron/services/works-square-session', () => ({ getValidWorksSquareAccessToken: vi.fn(), })); const getTokenMock = vi.mocked(getValidWorksSquareAccessToken); const serverConversation = { conversation_id: 'conversation-one', workspace_id: 'workspace-one', agent_session_id: 'session-one', title: '????', turn_revision: 1, phase: 'awaiting_confirmation', brief: { version: 1, status: 'ready', medium: 'image', summary: '???????????', ready: true, missing_decision: null, }, messages: [{ role: 'assistant', kind: 'confirmation', text: '??????????????', quick_replies: ['????'], generation_quote: { quote_id: 'quote-one', status: 'active', medium: 'image', brief_version: 1, brief_summary: '???????????', quoted_design_points: 1, expires_at: '2026-07-31T11:00:00Z', }, turn_revision: 1, created_at: '2026-07-31T10:00:00Z', }], created_at: '2026-07-31T10:00:00Z', updated_at: '2026-07-31T10:00:00Z', }; const serverWorkspace = { workspace_id: 'workspace-one', title: '??????', view_revision: 2, conversation_count: 1, phase: 'awaiting_confirmation', updated_at: '2026-07-31T10:00:00Z', }; function jsonResponse(payload: unknown, status = 200): Response { return new Response(JSON.stringify(payload), { status, headers: { 'Content-Type': 'application/json' }, }); } function deferred(): { promise: Promise; resolve(value: T): void } { let resolve!: (value: T) => void; const promise = new Promise((accept) => { resolve = accept; }); return { promise, resolve }; } type MockSocketScript = { frames?: unknown[]; open?: boolean; closeCode?: number; closeReason?: string; }; class MockAgentWebSocket { readonly url: string; readonly sent: string[] = []; readyState = 0; onopen: (() => void) | null = null; onmessage: ((event: { data: unknown }) => void) | null = null; onerror: ((event: unknown) => void) | null = null; onclose: ((event: { code: number; reason: string }) => void) | null = null; private closed = false; constructor(url: string, private readonly script: MockSocketScript) { this.url = url; setTimeout(() => this.runScript(), 0); } send(data: string): void { this.sent.push(data); } emitFrame(frame: unknown): void { this.onmessage?.({ data: JSON.stringify(frame) }); } close(code = 1000, reason = ''): void { this.emitClose(code, reason); } private runScript(): void { if (this.closed) return; if (this.script.open !== false) { this.readyState = 1; this.onopen?.(); for (const frame of this.script.frames ?? []) { this.onmessage?.({ data: JSON.stringify(frame) }); } } if (this.script.closeCode !== undefined) { this.emitClose(this.script.closeCode, this.script.closeReason ?? ''); } } private emitClose(code: number, reason: string): void { if (this.closed) return; this.closed = true; this.readyState = 3; this.onclose?.({ code, reason }); } } function scriptedSockets(scripts: MockSocketScript[]) { const sockets: MockAgentWebSocket[] = []; const webSocketFactory = vi.fn((url: string) => { const socket = new MockAgentWebSocket(url, scripts[sockets.length] ?? { closeCode: 1000 }); sockets.push(socket); return { socket }; }); return { sockets, webSocketFactory }; } describe('Works Square AI design adapter', () => { beforeEach(() => { getTokenMock.mockReset(); getTokenMock.mockResolvedValue('access-one'); }); it('maps the server snake_case Workspace contract into the shared client model', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse({ conversation: true, generation: true, image: true, video: false, })) .mockResolvedValueOnce(jsonResponse([serverWorkspace])); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.bootstrap()).resolves.toMatchObject({ capabilities: { conversation: true, image: true, video: false }, workspaces: [{ workspaceId: 'workspace-one', title: '??????', viewRevision: 2, conversationCount: 1, }], }); expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock.mock.calls.every(([, init]) => ( (init?.headers as Record).Authorization === 'Bearer access-one' ))).toBe(true); }); it('submits a conversation turn through the persistent Agent Gateway Session', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse(serverConversation)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-one', status: 'queued', error: null, }, 202)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-one', status: 'succeeded', error: null, })) .mockResolvedValueOnce(jsonResponse(serverConversation)); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, clientInstanceId: 'installation-one', }); await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-two', expectedTurnRevision: 1, message: '????????????', attachmentAssetIds: ['asset-reference'], })).resolves.toMatchObject({ workspaceId: 'workspace-one', turnRevision: 1, }); expect(fetchMock).toHaveBeenNthCalledWith( 2, 'https://square.example/api/agents/sessions/session-one/commands', expect.objectContaining({ method: 'POST', body: JSON.stringify({ client_command_id: 'turn-two', name: 'turn.submit', input: { expected_turn_revision: 1, message: '????????????', attachment_asset_ids: ['asset-reference'], action: null, }, }), }), ); expect(fetchMock).toHaveBeenNthCalledWith( 3, 'https://square.example/api/agents/sessions/session-one/runs/run-one', expect.objectContaining({ headers: expect.any(Object) }), ); expect(fetchMock).toHaveBeenNthCalledWith( 4, 'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one', expect.objectContaining({ headers: expect.any(Object) }), ); const commandBody = JSON.parse(String(fetchMock.mock.calls[1][1]?.body)); expect(commandBody.input).not.toHaveProperty('conversation_id'); }); it('uploads a local reference image as multipart data and maps the returned Asset', async () => { const fetchMock = vi.fn().mockResolvedValueOnce(jsonResponse({ asset_id: 'asset-uploaded', media_type: 'image', mime_type: 'image/png', width: 1200, height: 800, duration_milliseconds: null, created_at: '2026-08-06T10:00:00Z', })); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.uploadAsset({ workspaceId: 'workspace-one', fileName: 'poster.webp', mimeType: 'image/webp', bytes: new Uint8Array([1, 2, 3]), })).resolves.toMatchObject({ assetId: 'asset-uploaded', workspaceId: 'workspace-one', mimeType: 'image/png', contentPath: '/api/works/image-workspace/workspaces/workspace-one/assets/asset-uploaded/content', }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('https://square.example/api/design/workspaces/workspace-one/assets'); expect(init?.method).toBe('POST'); expect((init?.headers as Record).Authorization).toBe('Bearer access-one'); expect((init?.headers as Record)['Content-Type']).toBeUndefined(); const form = init?.body as FormData; expect(form.get('file')).toBeInstanceOf(File); expect((form.get('file') as File).name).toBe('poster.webp'); expect(await (form.get('file') as File).arrayBuffer()).toEqual( Uint8Array.from([1, 2, 3]).buffer, ); }); it('completes a design turn from the connected WebSocket without polling the run endpoint', async () => { const { sockets, webSocketFactory } = scriptedSockets([{ open: true }]); const fetchMock = vi.fn(async (input) => { const url = String(input); if (url.endsWith('/conversations/conversation-one')) { return jsonResponse({ ...serverConversation, agent_session_id: 'session-live' }); } if (url.endsWith('/stream-tickets')) { return jsonResponse({ ticket: 'ticket-live', transport: 'websocket', stream_url: '/api/agents/sessions/session-live/ws?ticket=ticket-live', expires_at: '2026-08-03T03:00:00Z', }); } if (url.endsWith('/api/agents/sessions/session-live/commands')) { queueMicrotask(() => sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-live', sequence: 6, runtime: 'design', type: 'run.completed', command_id: 'command-live', run_id: 'run-live', client_command_id: 'turn-live', schema_version: 1, terminal: true, occurred_at: '2026-08-03T02:00:05Z', payload: { status: 'succeeded' }, }, })); return jsonResponse({ command_id: 'command-live', run_id: 'run-live', status: 'accepted', }, 202); } if (url.endsWith('/conversations/conversation-one')) { return jsonResponse(serverConversation); } throw new Error(`Unexpected request: ${url}`); }); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const subscription = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); try { await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-live', expectedTurnRevision: 1, message: '????????????', })).resolves.toMatchObject({ workspaceId: 'workspace-one' }); expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/runs/'))).toHaveLength(0); } finally { subscription.close(); } }); it('does not let terminal Run completion overtake streamed design events', async () => { const { sockets, webSocketFactory } = scriptedSockets([{ open: true }]); const streamedConversation = { ...serverConversation, agent_session_id: 'session-ordered', turn_revision: 2, }; const fetchMock = vi.fn(async (input) => { const url = String(input); if (url.endsWith('/conversations/conversation-one')) { return jsonResponse(streamedConversation); } if (url.endsWith('/stream-tickets')) { return jsonResponse({ stream_url: '/api/agents/sessions/session-ordered/ws?ticket=ticket-ordered', }); } if (url.endsWith('/api/agents/sessions/session-ordered/commands')) { queueMicrotask(() => { sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-ordered', sequence: 4, runtime: 'design', type: 'design.assistant.delta', run_id: 'run-ordered', schema_version: 1, payload: { workspace_id: 'workspace-one', conversation_id: 'conversation-one', client_turn_id: 'turn-ordered', turn_revision: 2, chunk_index: 0, delta: '??????', }, }, }); sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-ordered', sequence: 5, runtime: 'design', type: 'design.conversation.updated', run_id: 'run-ordered', schema_version: 1, payload: { workspace_id: 'workspace-one', conversation_id: 'conversation-one', workspace_view_revision: 3, conversation: streamedConversation, generation_tasks: [], }, }, }); sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-ordered', sequence: 6, runtime: 'design', type: 'run.completed', run_id: 'run-ordered', schema_version: 1, payload: { status: 'succeeded' }, }, }); }); return jsonResponse({ run_id: 'run-ordered', status: 'queued', error: null }, 202); } throw new Error(`Unexpected request: ${url}`); }); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const subscription = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); const releaseFirstWrite = deferred(); const received: DesignWorkspaceEvent[] = []; const consume = (async () => { for await (const event of subscription.events) { received.push(event); if (received.length === 1) await releaseFirstWrite.promise; if (received.length === 2) break; } })(); let resolved = false; try { const turn = adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-ordered', expectedTurnRevision: 1, message: '???????', }).finally(() => { resolved = true; }); await vi.waitFor(() => expect(received).toHaveLength(1)); await new Promise((resolve) => setTimeout(resolve, 0)); expect(received[0]).toMatchObject({ type: 'design.assistant.delta', delta: '??????', }); expect(resolved).toBe(false); expect(fetchMock.mock.calls.filter(([url]) => ( String(url).endsWith('/conversations/conversation-one') ))).toHaveLength(1); releaseFirstWrite.resolve(); await consume; await expect(turn).resolves.toMatchObject({ turnRevision: 2 }); expect(received.map((event) => event.type)).toEqual([ 'design.assistant.delta', 'design.conversation.snapshot', ]); } finally { releaseFirstWrite.resolve(); subscription.close(); } }); it('bounds the delivery barrier when an opened stream has no consumer', async () => { vi.useFakeTimers(); const { sockets, webSocketFactory } = scriptedSockets([{ open: true }]); const streamedConversation = { ...serverConversation, agent_session_id: 'session-bounded', turn_revision: 2, }; const fetchMock = vi.fn(async (input) => { const url = String(input); if (url.endsWith('/conversations/conversation-one')) { return jsonResponse(streamedConversation); } if (url.endsWith('/stream-tickets')) { return jsonResponse({ stream_url: '/api/agents/sessions/session-bounded/ws?ticket=ticket-bounded', }); } if (url.endsWith('/api/agents/sessions/session-bounded/commands')) { queueMicrotask(() => { sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-bounded', sequence: 5, runtime: 'design', type: 'design.conversation.updated', run_id: 'run-bounded', schema_version: 1, payload: { workspace_id: 'workspace-one', conversation_id: 'conversation-one', workspace_view_revision: 3, conversation: streamedConversation, generation_tasks: [], }, }, }); sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-bounded', sequence: 6, runtime: 'design', type: 'run.completed', run_id: 'run-bounded', schema_version: 1, payload: { status: 'succeeded' }, }, }); }); return jsonResponse({ run_id: 'run-bounded', status: 'queued', error: null }, 202); } throw new Error(`Unexpected request: ${url}`); }); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const subscriptionPromise = adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); await vi.advanceTimersByTimeAsync(0); const subscription = await subscriptionPromise; let resolved = false; try { const turn = adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-bounded', expectedTurnRevision: 1, message: '???????????????', }).finally(() => { resolved = true; }); await vi.advanceTimersByTimeAsync(999); expect(resolved).toBe(false); await vi.advanceTimersByTimeAsync(1); await expect(turn).resolves.toMatchObject({ turnRevision: 2 }); expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/runs/'))) .toHaveLength(0); } finally { subscription.close(); vi.useRealTimers(); } }); it('maps a failed Run received from the connected WebSocket without polling', async () => { const { sockets, webSocketFactory } = scriptedSockets([{ open: true }]); const fetchMock = vi.fn(async (input) => { const url = String(input); if (url.endsWith('/conversations/conversation-one')) { return jsonResponse({ ...serverConversation, agent_session_id: 'session-failed' }); } if (url.endsWith('/stream-tickets')) { return jsonResponse({ stream_url: '/api/agents/sessions/session-failed/ws?ticket=ticket-failed', }); } if (url.endsWith('/api/agents/sessions/session-failed/commands')) { queueMicrotask(() => sockets[0]?.emitFrame({ type: 'event', event: { session_id: 'session-failed', sequence: 6, runtime: 'design', type: 'run.failed', run_id: 'run-failed', schema_version: 1, payload: { error: { code: 'agent_command_invalid', message: 'private validation detail', retryable: false, }, }, }, })); return jsonResponse({ run_id: 'run-failed', status: 'queued', error: null }, 202); } throw new Error(`Unexpected request: ${url}`); }); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const subscription = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); try { await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-failed', expectedTurnRevision: 1, message: 'invalid', })).rejects.toMatchObject({ status: 422, code: 'agent_command_invalid', message: '设计请求内容无效,请检查后重试', }); expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/runs/'))).toHaveLength(0); } finally { subscription.close(); } }); it('falls back to one Run request after the WebSocket disconnects', async () => { const { webSocketFactory } = scriptedSockets([{ open: true, closeCode: 1006 }]); const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-fallback', })) .mockResolvedValueOnce(jsonResponse({ stream_url: '/api/agents/sessions/session-fallback/ws?ticket=ticket-fallback', })) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-fallback', status: 'queued', error: null, }, 202)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-fallback', status: 'succeeded', error: null, })) .mockResolvedValueOnce(jsonResponse(serverConversation)); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const subscription = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); try { await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-fallback', expectedTurnRevision: 1, message: '???????', })).resolves.toMatchObject({ workspaceId: 'workspace-one' }); expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/runs/'))).toHaveLength(1); } finally { subscription.close(); } }); it('waits for a slow design run without flooding the run endpoint', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-08-03T02:00:00Z')); const startedAt = Date.now(); let runPolls = 0; const fetchMock = vi.fn(async (input, init) => { const url = String(input); if (url.endsWith('/conversations/conversation-one')) { return jsonResponse({ ...serverConversation, agent_session_id: 'session-slow' }); } if (url.endsWith('/api/agents/sessions/session-slow/commands')) { return jsonResponse({ run_id: 'run-slow', status: 'queued', error: null }, 202); } if (url.endsWith('/api/agents/sessions/session-slow/runs/run-slow')) { runPolls += 1; return jsonResponse({ run_id: 'run-slow', status: Date.now() - startedAt >= 150_000 ? 'succeeded' : 'running', error: null, }); } if (url.endsWith('/conversations/conversation-one')) { return jsonResponse(serverConversation); } throw new Error(`Unexpected request: ${url} ${init?.method ?? 'GET'}`); }); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); try { const outcomePromise = adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-slow', expectedTurnRevision: 1, message: '?????????????', }).then( (value) => ({ value, error: null }), (error: unknown) => ({ value: null, error }), ); await vi.advanceTimersByTimeAsync(160_000); const outcome = await outcomePromise; expect(outcome.error).toBeNull(); expect(outcome.value).toMatchObject({ workspaceId: 'workspace-one' }); expect(runPolls).toBeLessThanOrEqual(40); } finally { vi.useRealTimers(); } }); it('maps an invalid Runtime command to a user input error', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse(serverConversation)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-invalid', status: 'queued', error: null, }, 202)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-invalid', status: 'failed', error: { code: 'agent_command_invalid', message: 'private validation detail', retryable: false, }, })); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-invalid', expectedTurnRevision: 1, message: 'invalid', })).rejects.toMatchObject({ status: 422, code: 'agent_command_invalid', message: '设计请求内容无效,请检查后重试', }); }); it('keeps task creation behind structured Quote confirmation', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse(serverConversation)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-confirm', status: 'queued', error: null, }, 202)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-confirm', status: 'succeeded', error: null, })) .mockResolvedValueOnce(jsonResponse(serverConversation)); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example/', fetchImpl: fetchMock, }); const workspace = await adapter.confirmGeneration({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-two', expectedTurnRevision: 1, quoteId: 'quote-one', }); expect(workspace.workspaceId).toBe('workspace-one'); expect(fetchMock).toHaveBeenNthCalledWith( 2, 'https://square.example/api/agents/sessions/session-one/commands', expect.objectContaining({ method: 'POST', body: JSON.stringify({ client_command_id: 'turn-two', name: 'turn.submit', input: { expected_turn_revision: 1, message: '确认生成', attachment_asset_ids: [], action: { type: 'confirm_generation', quote_id: 'quote-one' }, }, }), }), ); expect(fetchMock).toHaveBeenNthCalledWith( 3, 'https://square.example/api/agents/sessions/session-one/runs/run-confirm', expect.objectContaining({ headers: expect.any(Object) }), ); expect(fetchMock).toHaveBeenNthCalledWith( 4, 'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one', expect.objectContaining({ headers: expect.any(Object) }), ); }); it('maps stable generation tasks and private asset relay paths', async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse([{ task_id: 'task-one', workspace_id: 'workspace-one', medium: 'image', status: 'succeeded', brief_version: 1, brief_summary: '??????', quote_id: 'quote-one', quoted_design_points: 1, failure_code: null, result_assets: [{ asset_id: 'asset-one', media_type: 'image', mime_type: 'image/png', width: 1024, height: 1280, duration_milliseconds: null, created_at: '2026-07-31T10:01:00Z', }], created_at: '2026-07-31T10:00:00Z', updated_at: '2026-07-31T10:01:00Z', }])); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.listTasks('workspace-one')).resolves.toMatchObject([{ taskId: 'task-one', status: 'succeeded', resultAssets: [{ assetId: 'asset-one', contentPath: '/api/works/image-workspace/workspaces/workspace-one/assets/asset-one/content', }], }]); }); it('refreshes the Main-owned session once after an upstream 401 and preserves Range', async () => { getTokenMock .mockResolvedValueOnce('expired-token') .mockResolvedValueOnce('fresh-token'); const fetchMock = vi.fn() .mockResolvedValueOnce(new Response(null, { status: 401 })) .mockResolvedValueOnce(new Response('partial', { status: 206, headers: { 'Content-Type': 'video/mp4', 'Content-Range': 'bytes 0-6/100', }, })); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); const response = await adapter.openAssetContent( 'workspace-one', 'asset-one', 'bytes=0-6', ); expect(response.status).toBe(206); expect(getTokenMock).toHaveBeenNthCalledWith(2, { fetchImpl: fetchMock, forceRefresh: true, }); expect(fetchMock).toHaveBeenNthCalledWith( 2, expect.stringContaining('/assets/asset-one/content'), expect.objectContaining({ headers: { Range: 'bytes=0-6', Authorization: 'Bearer fresh-token', }, }), ); }); it('reuses one design Agent Session and normalizes matching task events from fresh WebSocket tickets', async () => { const snapshotTask = { task_id: 'task-snapshot', workspace_id: 'workspace-one', medium: 'image', status: 'running', brief_version: 1, brief_summary: 'snapshot brief', quote_id: 'quote-snapshot', quoted_design_points: 1, failure_code: null, result_assets: [], created_at: '2026-08-02T09:59:00Z', updated_at: '2026-08-02T10:00:00Z', }; const snapshotEvent = { session_id: 'session-one', sequence: 1, runtime: 'design', type: 'design.conversation.updated', schema_version: 1, payload: { workspace_id: 'workspace-one', conversation_id: 'conversation-one', workspace_view_revision: 2, conversation: serverConversation, generation_tasks: [snapshotTask], }, }; const assistantDeltaEvent = { session_id: 'session-one', sequence: 2, runtime: 'design', type: 'design.assistant.delta', schema_version: 1, payload: { workspace_id: 'workspace-one', conversation_id: 'conversation-one', client_turn_id: 'turn-two', turn_revision: 2, chunk_index: 0, delta: '??????', }, }; const malformedDeltaEvent = { ...assistantDeltaEvent, sequence: 99, payload: { ...assistantDeltaEvent.payload, chunk_index: -1, }, }; const taskEvent = { session_id: 'session-one', sequence: 3, runtime: 'design', type: 'design.generation_task.updated', command_id: null, run_id: null, client_command_id: null, schema_version: 1, terminal: false, occurred_at: '2026-08-02T10:01:00Z', payload: { workspace_id: 'workspace-one', workspace_view_revision: 4, generation_task: { task_id: 'task-live', workspace_id: 'workspace-one', medium: 'video', status: 'running', brief_version: 2, brief_summary: '??????', quote_id: 'quote-live', quoted_design_points: 8, failure_code: null, result_assets: [], created_at: '2026-08-02T10:00:00Z', updated_at: '2026-08-02T10:01:00Z', }, }, }; const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse(serverConversation)) .mockResolvedValueOnce(jsonResponse({ ticket: 'secret-ticket-one', transport: 'websocket', stream_url: '/api/agents/sessions/session-one/ws?ticket=secret-ticket-one', expires_at: '2026-08-02T10:02:00Z', })) .mockResolvedValueOnce(jsonResponse({ ticket: 'secret-ticket-two', transport: 'websocket', stream_url: '/api/agents/sessions/session-one/ws?ticket=secret-ticket-two', expires_at: '2026-08-02T10:03:00Z', })); const { sockets, webSocketFactory } = scriptedSockets([ { frames: [ { type: 'event', event: snapshotEvent }, { type: 'event', event: malformedDeltaEvent }, { type: 'event', event: assistantDeltaEvent }, { type: 'event', event: taskEvent }, ], closeCode: 1000, }, { closeCode: 1000 }, ]); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const first = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); const received = []; for await (const event of first.events) received.push(event); first.close(); const second = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one', afterEventId: 'session-one:3', }); for await (const _event of second.events) { // The second connection only proves Session reuse and a fresh ticket. } second.close(); expect(received).toEqual([ { id: 'session-one:1', type: 'design.conversation.snapshot', workspaceId: 'workspace-one', conversationId: 'conversation-one', workspaceViewRevision: 2, conversation: expect.objectContaining({ conversationId: 'conversation-one', title: serverConversation.title, messages: [expect.objectContaining({ text: serverConversation.messages[0].text, })], }), generationTasks: [expect.objectContaining({ taskId: 'task-snapshot', medium: 'image', status: 'running', })], }, { id: 'session-one:2', type: 'design.assistant.delta', workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-two', turnRevision: 2, chunkIndex: 0, delta: '??????', }, { id: 'session-one:3', type: 'design.generation_task.updated', workspaceId: 'workspace-one', workspaceViewRevision: 4, generationTask: expect.objectContaining({ taskId: 'task-live', medium: 'video', status: 'running', }), }, ]); expect(fetchMock).toHaveBeenNthCalledWith( 1, 'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one', expect.objectContaining({ headers: expect.any(Object) }), ); expect(fetchMock.mock.calls.filter(([url]) => ( String(url).endsWith('/conversations/conversation-one') ))) .toHaveLength(1); expect(fetchMock.mock.calls.filter(([url]) => String(url).endsWith('/stream-tickets'))) .toHaveLength(2); const ticketCalls = fetchMock.mock.calls.filter(([url]) => ( String(url).endsWith('/stream-tickets') )); expect(ticketCalls.every(([, init]) => ( JSON.parse(String(init?.body)).transport === 'websocket' ))).toBe(true); expect(sockets.map((socket) => socket.url)).toEqual([ 'wss://square.example/api/agents/sessions/session-one/ws?ticket=secret-ticket-one&after_sequence=0', 'wss://square.example/api/agents/sessions/session-one/ws?ticket=secret-ticket-two&after_sequence=3', ]); }); it('reloads the persistent Conversation Session after an upstream event cursor expires', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-old' })) .mockResolvedValueOnce(jsonResponse({ stream_url: '/api/agents/sessions/session-old/ws?ticket=ticket-old', })) .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-new' })) .mockResolvedValueOnce(jsonResponse({ stream_url: '/api/agents/sessions/session-new/ws?ticket=ticket-new', })); const { sockets, webSocketFactory } = scriptedSockets([ { open: false, closeCode: 4409, closeReason: 'Agent event cursor expired' }, { closeCode: 1000 }, ]); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); await expect(adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one', afterEventId: 'session-old:99', })).rejects.toMatchObject({ status: 410 }); const recovered = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one', afterEventId: 'session-old:99', }); for await (const _event of recovered.events) { // Empty recovery stream. } expect(fetchMock.mock.calls.filter(([url]) => ( String(url).endsWith('/conversations/conversation-one') ))).toHaveLength(2); expect(fetchMock.mock.calls.some(([, init]) => init?.method === 'DELETE')).toBe(false); expect(sockets.map((socket) => socket.url)).toEqual([ 'wss://square.example/api/agents/sessions/session-old/ws?ticket=ticket-old&after_sequence=99', 'wss://square.example/api/agents/sessions/session-new/ws?ticket=ticket-new&after_sequence=0', ]); }); it('refreshes a closed persistent Session before opening the task stream', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-old' })) .mockResolvedValueOnce(jsonResponse({ code: 'agent_session_closed', message: 'closed', }, 409)) .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-new' })) .mockResolvedValueOnce(jsonResponse({ stream_url: '/api/agents/sessions/session-new/ws?ticket=ticket-new', })); const { webSocketFactory } = scriptedSockets([{ closeCode: 1000 }]); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const recovered = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); for await (const _event of recovered.events) { // Empty recovery stream. } expect(fetchMock.mock.calls.filter(([url]) => ( String(url).endsWith('/conversations/conversation-one') ))).toHaveLength(2); expect(fetchMock.mock.calls.filter(([url]) => String(url).endsWith('/stream-tickets'))) .toHaveLength(2); }); it('closes active local streams without deleting persistent Agent Sessions', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse(serverConversation)) .mockResolvedValueOnce(jsonResponse({ stream_url: '/api/agents/sessions/session-one/ws?ticket=ticket-one', })) .mockResolvedValueOnce(jsonResponse({ ...serverConversation, workspace_id: 'workspace-two', conversation_id: 'conversation-two', agent_session_id: 'session-two', })) .mockResolvedValueOnce(jsonResponse({ stream_url: '/api/agents/sessions/session-two/ws?ticket=ticket-two', })); const { sockets, webSocketFactory } = scriptedSockets([ { open: true }, { open: true }, ]); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, webSocketFactory, }); const first = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-one', conversationId: 'conversation-one' }); const second = await adapter.openWorkspaceEvents({ workspaceId: 'workspace-two', conversationId: 'conversation-two' }); await adapter.closeEventSessions(); const closeCalls = fetchMock.mock.calls.filter(([, init]) => init?.method === 'DELETE'); expect(closeCalls).toHaveLength(0); expect(sockets.map((socket) => socket.readyState)).toEqual([3, 3]); first.close(); second.close(); }); it('reports an unavailable persistent Session instead of creating a client-owned Session', async () => { const fetchMock = vi.fn().mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: null, })); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-unavailable', expectedTurnRevision: 1, message: 'hello', })).rejects.toMatchObject({ status: 503, code: 'DESIGN_CONVERSATION_SESSION_UNAVAILABLE', }); expect(fetchMock).toHaveBeenCalledTimes(1); }); it('retries a turn once with the refreshed persistent Session when the old Session is stale', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-old' })) .mockResolvedValueOnce(jsonResponse({ code: 'agent_session_not_found', message: 'stale', }, 404)) .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-new' })) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-new', status: 'queued', error: null, }, 202)) .mockResolvedValueOnce(jsonResponse({ run_id: 'run-new', status: 'succeeded', error: null, })) .mockResolvedValueOnce(jsonResponse({ ...serverConversation, agent_session_id: 'session-new' })); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.submitMessage({ workspaceId: 'workspace-one', conversationId: 'conversation-one', clientTurnId: 'turn-retry', expectedTurnRevision: 1, message: 'retry', })).resolves.toMatchObject({ conversationId: 'conversation-one' }); expect(fetchMock.mock.calls.map(([url]) => String(url))).toEqual([ 'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one', 'https://square.example/api/agents/sessions/session-old/commands', 'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one', 'https://square.example/api/agents/sessions/session-new/commands', 'https://square.example/api/agents/sessions/session-new/runs/run-new', 'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one', ]); }); });