Files
makelore/tests/unit/works-square-design-workspace.test.ts
inman 26b52d76e3
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
feat: 完善图像工作区与创作工具体验
2026-08-16 14:08:27 +08:00

2072 lines
70 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: '????',
latest_message_preview: '??????????',
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: '???????????',
final_prompt: '??????????????',
prompt_mode: 'guided',
generation_parameters: {
model: 'image-model-one',
resolution: '1024x1024',
aspect_ratio: '1:1',
duration_seconds: null,
},
generation_options: {
models: [{
value: 'image-model-one',
label: '??????',
default: true,
disabled: false,
multiplier: 1,
}],
resolutions: [{
value: '1024x1024',
label: '1024 × 1024',
default: true,
disabled: false,
multiplier: 1,
}],
durations: [],
aspect_ratios: [{
value: '1:1',
label: '1:1',
default: true,
disabled: false,
multiplier: 1,
}, {
value: '16:9',
label: '16:9',
default: false,
disabled: false,
multiplier: 1,
}],
},
pricing: {
schema: 'design-pricing-v2',
amount: 1,
rounding: 'ceil',
},
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',
};
const quoteConfirmationInput = {
finalPrompt: '??????????????',
generationParameters: {
model: 'image-model-one',
resolution: '1024x1024',
aspectRatio: '1:1',
durationSeconds: null,
},
};
function jsonResponse(payload: unknown, status = 200): Response {
return new Response(JSON.stringify(payload), {
status,
headers: { 'Content-Type': 'application/json' },
});
}
function deferred<T>(): { promise: Promise<T>; resolve(value: T): void } {
let resolve!: (value: T) => void;
const promise = new Promise<T>((accept) => {
resolve = accept;
});
return { promise, resolve };
}
type MockSocketScript = {
frames?: unknown[];
open?: boolean;
closeCode?: number;
closeReason?: string;
onSend?: (socket: MockAgentWebSocket, data: string) => void;
};
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);
this.script.onSend?.(this, 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 };
}
function acceptCommand(
socket: MockAgentWebSocket,
rawFrame: string,
runId: string,
events: unknown[],
): void {
const frame = JSON.parse(rawFrame) as Record<string, unknown>;
if (frame.type !== 'command.submit') return;
queueMicrotask(() => {
socket.emitFrame({
type: 'command.accepted',
request_id: frame.request_id,
command: {
command_id: `command-${runId}`,
run_id: runId,
status: 'queued',
error: null,
},
});
for (const event of events) socket.emitFrame(event);
});
}
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<typeof fetch>()
.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<string, string>).Authorization === 'Bearer access-one'
))).toBe(true);
});
it('returns an explicit deletion result when the Workspace API responds with JSON', async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValueOnce(jsonResponse({
workspace_id: 'workspace/one',
deleted: true,
}));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
await expect(adapter.deleteWorkspace('workspace/one')).resolves.toEqual({
workspaceId: 'workspace/one',
deleted: true,
});
expect(fetchMock).toHaveBeenCalledWith(
'https://square.example/api/design/workspaces/workspace%2Fone',
expect.objectContaining({
method: 'DELETE',
headers: expect.objectContaining({
Accept: 'application/json',
Authorization: 'Bearer access-one',
}),
}),
);
});
it('accepts a 204 deletion and clears only the deleted Workspace event/session caches', async () => {
let deleted = false;
let targetConversationReads = 0;
const fetchMock = vi.fn<typeof fetch>(async (input, init) => {
const url = String(input);
if (url.endsWith('/api/design/workspaces/workspace-one') && init?.method === 'DELETE') {
deleted = true;
return new Response(null, { status: 204 });
}
if (url.endsWith('/workspaces/workspace-one/conversations/conversation-one')) {
targetConversationReads += 1;
if (deleted) {
return jsonResponse({
detail: { code: 'workspace_not_found', message: 'deleted' },
}, 404);
}
return jsonResponse(serverConversation);
}
if (url.endsWith('/sessions/session-one/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-one/ws?ticket=ticket-one',
});
}
if (url.endsWith('/workspaces/workspace-two/conversations/conversation-two')) {
return jsonResponse({
...serverConversation,
workspace_id: 'workspace-two',
conversation_id: 'conversation-two',
agent_session_id: 'session-two',
});
}
if (url.endsWith('/sessions/session-two/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-two/ws?ticket=ticket-two',
});
}
throw new Error(`Unexpected request: ${url}`);
});
const { sockets, webSocketFactory } = scriptedSockets([{ open: true }, { open: true }]);
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
webSocketFactory,
});
const target = await adapter.openWorkspaceEvents({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
});
const keeper = await adapter.openWorkspaceEvents({
workspaceId: 'workspace-two',
conversationId: 'conversation-two',
});
await expect(adapter.deleteWorkspace('workspace-one')).resolves.toEqual({
workspaceId: 'workspace-one',
deleted: true,
});
expect(sockets.map((socket) => socket.readyState)).toEqual([3, 1]);
await expect(adapter.openWorkspaceEvents({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
})).rejects.toMatchObject({
status: 404,
code: 'workspace_not_found',
message: '设计项目不存在或无权访问',
});
expect(targetConversationReads).toBe(2);
expect(fetchMock.mock.calls.filter(([, init]) => init?.method === 'DELETE'))
.toEqual([expect.arrayContaining([
'https://square.example/api/design/workspaces/workspace-one',
])]);
target.close();
keeper.close();
});
it('preserves Workspace event/session caches when deletion fails', async () => {
let ticketSequence = 0;
const fetchMock = vi.fn<typeof fetch>(async (input, init) => {
const url = String(input);
if (url.endsWith('/workspaces/workspace-one/conversations/conversation-one')) {
return jsonResponse(serverConversation);
}
if (url.endsWith('/sessions/session-one/stream-tickets')) {
ticketSequence += 1;
return jsonResponse({
stream_url: `/api/agents/sessions/session-one/ws?ticket=ticket-${ticketSequence}`,
});
}
if (url.endsWith('/api/design/workspaces/workspace-one') && init?.method === 'DELETE') {
return jsonResponse({
detail: { code: 'workspace_unavailable', message: 'private detail' },
}, 503);
}
throw new Error(`Unexpected request: ${url}`);
});
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',
});
await expect(adapter.deleteWorkspace('workspace-one')).rejects.toMatchObject({
status: 503,
code: 'workspace_unavailable',
});
const second = await adapter.openWorkspaceEvents({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
});
expect(fetchMock.mock.calls.filter(([url]) => (
String(url).endsWith('/workspaces/workspace-one/conversations/conversation-one')
))).toHaveLength(1);
expect(sockets.map((socket) => socket.readyState)).toEqual([1, 1]);
first.close();
second.close();
});
it('lists, creates, and reads Conversations through the Workspace API', async () => {
const secondConversation = {
...serverConversation,
conversation_id: 'conversation-two',
agent_session_id: 'session-two',
title: '第二会话',
};
const fetchMock = vi.fn<typeof fetch>()
.mockResolvedValueOnce(jsonResponse(serverWorkspace))
.mockResolvedValueOnce(jsonResponse([serverConversation]))
.mockResolvedValueOnce(jsonResponse(secondConversation, 201))
.mockResolvedValueOnce(jsonResponse(secondConversation));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
await expect(adapter.getWorkspace('workspace-one')).resolves.toMatchObject({
workspaceId: 'workspace-one',
conversationCount: 1,
conversations: [{
conversationId: 'conversation-one',
latestMessagePreview: '??????????',
}],
});
await expect(adapter.createConversation({
workspaceId: 'workspace-one',
clientConversationId: 'client-conversation-two',
title: '第二会话',
})).resolves.toMatchObject({
workspaceId: 'workspace-one',
conversationId: 'conversation-two',
title: '第二会话',
});
await expect(adapter.getConversation('workspace-one', 'conversation-two'))
.resolves.toMatchObject({ conversationId: 'conversation-two' });
expect(fetchMock).toHaveBeenNthCalledWith(
1,
'https://square.example/api/design/workspaces/workspace-one',
expect.objectContaining({ headers: expect.any(Object) }),
);
expect(fetchMock).toHaveBeenNthCalledWith(
2,
'https://square.example/api/design/workspaces/workspace-one/conversations?limit=100&offset=0',
expect.objectContaining({ headers: expect.any(Object) }),
);
expect(fetchMock).toHaveBeenNthCalledWith(
3,
'https://square.example/api/design/workspaces/workspace-one/conversations',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
client_conversation_id: 'client-conversation-two',
title: '第二会话',
}),
}),
);
expect(fetchMock).toHaveBeenNthCalledWith(
4,
'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-two',
expect.objectContaining({ headers: expect.any(Object) }),
);
});
it('normalizes a missing Brief medium to null and rejects invalid non-null values', async () => {
const { medium: _medium, ...legacyBrief } = serverConversation.brief;
const legacyConversation = {
...serverConversation,
brief: legacyBrief,
};
const invalidConversation = {
...serverConversation,
brief: { ...serverConversation.brief, medium: 'audio' },
};
const fetchMock = vi.fn<typeof fetch>()
.mockResolvedValueOnce(jsonResponse(legacyConversation))
.mockResolvedValueOnce(jsonResponse(invalidConversation));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
await expect(adapter.getConversation('workspace-one', 'conversation-one'))
.resolves.toMatchObject({ brief: { medium: null } });
await expect(adapter.getConversation('workspace-one', 'conversation-one'))
.rejects.toMatchObject({ code: 'DESIGN_WORKSPACE_RESPONSE_INVALID' });
});
it('submits a conversation turn through the persistent Agent Gateway Session', async () => {
const fetchMock = vi.fn<typeof fetch>()
.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('re-quotes a generation Quote through the cloud PATCH contract and maps every option', async () => {
const quote = serverConversation.messages[0].generation_quote;
const fetchMock = vi.fn<typeof fetch>().mockResolvedValueOnce(jsonResponse(quote));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
await expect(adapter.updateGenerationQuote({
workspaceId: 'workspace-one',
quoteId: 'quote-one',
finalPrompt: '用户最新编辑后的完整提示词',
generationParameters: {
model: 'image-model-one',
resolution: '1024x1024',
aspectRatio: '16:9',
durationSeconds: null,
},
})).resolves.toMatchObject({
quoteId: 'quote-one',
finalPrompt: '??????????????',
generationOptions: {
models: [{ value: 'image-model-one', label: '??????', disabled: false }],
},
});
expect(fetchMock).toHaveBeenCalledWith(
'https://square.example/api/design/workspaces/workspace-one/generation-quotes/quote-one',
expect.objectContaining({
method: 'PATCH',
body: JSON.stringify({
final_prompt: '用户最新编辑后的完整提示词',
model: 'image-model-one',
resolution: '1024x1024',
aspect_ratio: '16:9',
duration_seconds: null,
}),
}),
);
});
it('uploads a local reference image as multipart data and maps the returned Asset', async () => {
const fetchMock = vi.fn<typeof fetch>().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<string, string>).Authorization).toBe('Bearer access-one');
expect((init?.headers as Record<string, string>)['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 { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
acceptCommand(socket, rawFrame, 'run-live', [{
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' },
},
}]);
},
}]);
const fetchMock = vi.fn<typeof fetch>(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('/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('maps a matching top-level WebSocket command error without using the HTTP fallback', async () => {
const { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
const frame = JSON.parse(rawFrame) as Record<string, unknown>;
expect(frame).toMatchObject({
type: 'command.submit',
command: {
client_command_id: 'turn-command-error',
name: 'turn.submit',
},
});
expect(frame.request_id).toEqual(expect.any(String));
queueMicrotask(() => socket.emitFrame({
type: 'error',
request_id: frame.request_id,
error: {
code: 'agent_runtime_unavailable',
message: 'Agent Runtime is temporarily unavailable',
retryable: true,
},
}));
},
}]);
const fetchMock = vi.fn<typeof fetch>(async (input) => {
const url = String(input);
if (url.endsWith('/conversations/conversation-one')) {
return jsonResponse({ ...serverConversation, agent_session_id: 'session-command-error' });
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-command-error/ws?ticket=ticket-command-error',
});
}
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-command-error',
expectedTurnRevision: 1,
message: '继续设计',
})).rejects.toMatchObject({
status: 503,
code: 'agent_runtime_unavailable',
message: 'AI 设计服务暂时不可用,请稍后重试',
});
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/commands'))).toBe(false);
} finally {
subscription.close();
}
});
it('redacts an unknown top-level WebSocket command error without using the HTTP fallback', async () => {
const privateDetail = 'private upstream host and stack trace';
const { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
const frame = JSON.parse(rawFrame) as Record<string, unknown>;
queueMicrotask(() => socket.emitFrame({
type: 'error',
request_id: frame.request_id,
error: {
code: 'gateway_private_failure',
message: privateDetail,
retryable: false,
},
}));
},
}]);
const fetchMock = vi.fn<typeof fetch>(async (input) => {
const url = String(input);
if (url.endsWith('/conversations/conversation-one')) {
return jsonResponse({ ...serverConversation, agent_session_id: 'session-private-error' });
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-private-error/ws?ticket=ticket-private-error',
});
}
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 {
const error = await adapter.submitMessage({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
clientTurnId: 'turn-private-error',
expectedTurnRevision: 1,
message: '继续设计',
}).then(
() => null,
(reason: unknown) => reason,
);
expect(error).toMatchObject({
status: 502,
code: 'gateway_private_failure',
message: 'AI 设计请求失败,请稍后重试',
});
expect((error as Error).message).not.toContain(privateDetail);
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/commands'))).toBe(false);
} finally {
subscription.close();
}
});
it('falls back to the idempotent HTTP command when the WebSocket acknowledgement times out', async () => {
vi.useFakeTimers();
const { sockets, webSocketFactory } = scriptedSockets([{ open: true }]);
let conversationReads = 0;
const fetchMock = vi.fn<typeof fetch>(async (input, init) => {
const url = String(input);
if (url.endsWith('/conversations/conversation-one')) {
conversationReads += 1;
return jsonResponse({
...serverConversation,
agent_session_id: 'session-ack-timeout',
turn_revision: conversationReads === 1 ? 1 : 2,
});
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-ack-timeout/ws?ticket=ticket-timeout',
});
}
if (url.endsWith('/api/agents/sessions/session-ack-timeout/commands')) {
expect(JSON.parse(String(init?.body))).toMatchObject({
client_command_id: 'turn-ack-timeout',
name: 'turn.submit',
});
queueMicrotask(() => sockets[0]?.emitFrame({
type: 'event',
event: {
session_id: 'session-ack-timeout',
sequence: 6,
runtime: 'design',
type: 'run.completed',
run_id: 'run-ack-timeout',
schema_version: 1,
payload: { status: 'succeeded' },
},
}));
return jsonResponse({ run_id: 'run-ack-timeout', 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;
try {
const turn = adapter.submitMessage({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
clientTurnId: 'turn-ack-timeout',
expectedTurnRevision: 1,
message: '继续设计',
});
await vi.advanceTimersByTimeAsync(5_000);
await expect(turn).resolves.toMatchObject({ turnRevision: 2 });
expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/commands')))
.toHaveLength(1);
expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/runs/')))
.toHaveLength(0);
} finally {
subscription.close();
vi.useRealTimers();
}
});
it('does not let terminal Run completion overtake streamed design events', async () => {
const streamedConversation = {
...serverConversation,
agent_session_id: 'session-ordered',
turn_revision: 2,
};
const { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
acceptCommand(socket, rawFrame, 'run-ordered', [{
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: '??????',
},
},
}, {
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: [],
},
},
}, {
type: 'event',
event: {
session_id: 'session-ordered',
sequence: 6,
runtime: 'design',
type: 'run.completed',
run_id: 'run-ordered',
schema_version: 1,
payload: { status: 'succeeded' },
},
}]);
},
}]);
const fetchMock = vi.fn<typeof fetch>(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',
});
}
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<void>();
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<void>((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 streamedConversation = {
...serverConversation,
agent_session_id: 'session-bounded',
turn_revision: 2,
};
const { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
acceptCommand(socket, rawFrame, 'run-bounded', [{
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: [],
},
},
}, {
type: 'event',
event: {
session_id: 'session-bounded',
sequence: 6,
runtime: 'design',
type: 'run.completed',
run_id: 'run-bounded',
schema_version: 1,
payload: { status: 'succeeded' },
},
}]);
},
}]);
const fetchMock = vi.fn<typeof fetch>(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',
});
}
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 { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
acceptCommand(socket, rawFrame, 'run-failed', [{
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,
},
},
},
}]);
},
}]);
const fetchMock = vi.fn<typeof fetch>(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',
});
}
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('submits a confirmed Quote through the connected WebSocket and receives its task', async () => {
const generationTask = {
task_id: 'task-confirm-live',
workspace_id: 'workspace-one',
conversation_id: 'conversation-one',
medium: 'image',
status: 'queued',
brief_version: 1,
brief_summary: '公益海报',
quote_id: 'quote-one',
quoted_design_points: 1,
failure_code: null,
result_assets: [],
created_at: '2026-08-14T05:00:00Z',
updated_at: '2026-08-14T05:00:00Z',
};
const { sockets, webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
acceptCommand(socket, rawFrame, 'run-confirm-live', [{
type: 'event',
event: {
session_id: 'session-confirm-live',
sequence: 5,
runtime: 'design',
type: 'design.generation_task.updated',
run_id: 'run-confirm-live',
schema_version: 1,
payload: {
workspace_id: 'workspace-one',
workspace_view_revision: 3,
generation_task: generationTask,
},
},
}, {
type: 'event',
event: {
session_id: 'session-confirm-live',
sequence: 6,
runtime: 'design',
type: 'run.completed',
run_id: 'run-confirm-live',
schema_version: 1,
payload: { status: 'succeeded' },
},
}]);
},
}]);
let conversationReads = 0;
const fetchMock = vi.fn<typeof fetch>(async (input) => {
const url = String(input);
if (url.endsWith('/conversations/conversation-one')) {
conversationReads += 1;
return jsonResponse({
...serverConversation,
agent_session_id: 'session-confirm-live',
turn_revision: conversationReads === 1 ? 1 : 2,
});
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-confirm-live/ws?ticket=ticket-confirm-live',
});
}
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 received: DesignWorkspaceEvent[] = [];
const consume = (async () => {
for await (const event of subscription.events) {
received.push(event);
break;
}
})();
try {
await expect(adapter.confirmGeneration({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
clientTurnId: 'turn-confirm-live',
expectedTurnRevision: 1,
quoteId: 'quote-one',
...quoteConfirmationInput,
})).resolves.toMatchObject({ turnRevision: 2 });
await consume;
expect(received).toMatchObject([{
type: 'design.generation_task.updated',
generationTask: {
taskId: 'task-confirm-live',
quoteId: 'quote-one',
status: 'queued',
},
}]);
expect(JSON.parse(sockets[0].sent[0])).toMatchObject({
type: 'command.submit',
command: {
client_command_id: 'turn-confirm-live',
name: 'turn.submit',
input: {
action: {
type: 'confirm_generation',
quote_id: 'quote-one',
final_prompt: quoteConfirmationInput.finalPrompt,
aspect_ratio: '1:1',
generation_parameters: {
model: 'image-model-one',
resolution: '1024x1024',
duration_seconds: null,
},
},
},
},
});
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/commands'))).toBe(false);
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/runs/'))).toBe(false);
} finally {
subscription.close();
}
});
it('maps a generic Agent Runtime failure from the WebSocket to a safe 503', async () => {
const { webSocketFactory } = scriptedSockets([{
open: true,
onSend(socket, rawFrame) {
acceptCommand(socket, rawFrame, 'run-unavailable', [{
type: 'event',
event: {
session_id: 'session-unavailable',
sequence: 6,
runtime: 'design',
type: 'run.failed',
run_id: 'run-unavailable',
schema_version: 1,
payload: {
error: {
code: 'agent_runtime_unavailable',
message: 'Agent Runtime is temporarily unavailable',
retryable: true,
},
},
},
}]);
},
}]);
const fetchMock = vi.fn<typeof fetch>(async (input) => {
const url = String(input);
if (url.endsWith('/conversations/conversation-one')) {
return jsonResponse({ ...serverConversation, agent_session_id: 'session-unavailable' });
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-unavailable/ws?ticket=ticket-unavailable',
});
}
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.confirmGeneration({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
clientTurnId: 'turn-unavailable',
expectedTurnRevision: 1,
quoteId: 'quote-one',
...quoteConfirmationInput,
})).rejects.toMatchObject({
status: 503,
code: 'agent_runtime_unavailable',
message: 'AI 设计服务暂时不可用,请稍后重试',
});
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<typeof fetch>()
.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<typeof fetch>(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<typeof fetch>()
.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<typeof fetch>()
.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',
...quoteConfirmationInput,
});
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',
final_prompt: quoteConfirmationInput.finalPrompt,
aspect_ratio: '1:1',
generation_parameters: {
model: 'image-model-one',
resolution: '1024x1024',
duration_seconds: null,
},
},
},
}),
}),
);
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<typeof fetch>().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<typeof fetch>()
.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 { medium: _medium, ...legacyBrief } = serverConversation.brief;
const legacyConversation = {
...serverConversation,
brief: legacyBrief,
};
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: legacyConversation,
generation_tasks: [snapshotTask],
},
};
const invalidSnapshotEvent = {
...snapshotEvent,
sequence: 98,
payload: {
...snapshotEvent.payload,
conversation: {
...serverConversation,
brief: { ...serverConversation.brief, medium: 'audio' },
},
},
};
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<typeof fetch>()
.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: invalidSnapshotEvent },
{ 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,
brief: expect.objectContaining({ medium: null }),
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<typeof fetch>()
.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<typeof fetch>()
.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<typeof fetch>()
.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('reloads persistent Session ids after local event sessions are closed', async () => {
const fetchMock = vi.fn<typeof fetch>(async (input) => {
const url = String(input);
if (url.endsWith('/api/design/capabilities')) {
return jsonResponse({
conversation: true,
generation: true,
image: true,
video: false,
});
}
if (url.endsWith('/api/design/workspaces?limit=100&offset=0')) {
return jsonResponse([]);
}
if (url.endsWith('/conversations/conversation-one')) {
return jsonResponse(serverConversation);
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-one/ws?ticket=ticket-one',
});
}
return jsonResponse({ detail: { code: 'unexpected_request', message: url } }, 500);
});
const { 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',
});
await adapter.closeEventSessions();
await adapter.bootstrap();
const second = await adapter.openWorkspaceEvents({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
});
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);
first.close();
second.close();
});
it('reports an unavailable persistent Session instead of creating a client-owned Session', async () => {
const fetchMock = vi.fn<typeof fetch>().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<typeof fetch>()
.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',
]);
});
});