fix: stabilize ai design history switching

This commit is contained in:
2026-08-19 10:31:36 +08:00
parent abece81fdb
commit bf0b805d9f
14 changed files with 573 additions and 48 deletions

View File

@@ -464,6 +464,41 @@ describe('Works Square AI design adapter', () => {
);
});
it('passes the opaque older-history cursor through and preserves server message ids', async () => {
const pagedConversation = {
...serverConversation,
messages: [{
...serverConversation.messages[0],
message_id: 'persisted-turn-one:assistant',
}],
message_page: {
has_older: true,
next_before: 'opaque-older-page',
},
};
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(jsonResponse(pagedConversation));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
const result = await adapter.getConversation(
'workspace-one',
'conversation-one',
'opaque-older-page',
);
expect(fetchMock).toHaveBeenCalledWith(
'https://square.example/api/design/workspaces/workspace-one/conversations/conversation-one?before=opaque-older-page',
expect.objectContaining({ headers: expect.any(Object) }),
);
expect(result.messages[0]?.id).toBe('persisted-turn-one:assistant');
expect(result.messagePage).toEqual({
hasOlder: true,
nextBefore: 'opaque-older-page',
});
});
it('normalizes a missing Brief medium to null and rejects invalid non-null values', async () => {
const { medium: _medium, ...legacyBrief } = serverConversation.brief;
const legacyConversation = {
@@ -1148,6 +1183,46 @@ describe('Works Square AI design adapter', () => {
}
});
it('fails a WebSocket event stream that never opens instead of leaving it pending', async () => {
vi.useFakeTimers();
const { sockets, webSocketFactory } = scriptedSockets([{ open: 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-open-timeout' });
}
if (url.endsWith('/stream-tickets')) {
return jsonResponse({
stream_url: '/api/agents/sessions/session-open-timeout/ws?ticket=ticket-open-timeout',
});
}
throw new Error(`Unexpected request: ${url}`);
});
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
webSocketFactory,
});
try {
const opening = adapter.openWorkspaceEvents({
workspaceId: 'workspace-one',
conversationId: 'conversation-one',
});
const openingAssertion = expect(opening).rejects.toMatchObject({
status: 504,
code: 'DESIGN_EVENT_STREAM_TIMEOUT',
});
await vi.advanceTimersByTimeAsync(10_000);
await openingAssertion;
expect(sockets).toHaveLength(1);
expect(sockets[0].readyState).toBe(3);
} finally {
vi.useRealTimers();
}
});
it('maps a failed Run received from the connected WebSocket without polling', async () => {
const { webSocketFactory } = scriptedSockets([{
open: true,