feat: 对接设计 Agent Gateway WebSocket

需求:服务端统一 Agent Gateway 将设计任务状态流切换为 WebSocket,客户端需要实时展示生成任务并支持断线恢复。

实现:Electron Main 管理 Session、一次性 Ticket、WebSocket 心跳与游标续传,按关闭码回收会话;Renderer 继续通过本机 Host API 的 SSE 投影接收任务事件,并保留 REST 降级同步。

验证:typecheck、变更文件 ESLint、37 个聚焦测试及 build:vite 通过。
This commit is contained in:
2026-08-02 20:15:37 +08:00
parent 83e21563dc
commit 518d7ab4cd
17 changed files with 1816 additions and 50 deletions

View File

@@ -215,4 +215,60 @@ describe('AI design Main route boundary', () => {
expect(response.headers.get('content-range')).toBe('bytes 0-6/100');
expect(Buffer.concat(response.chunks).toString()).toBe('partial');
});
it('relays normalized task events over local SSE and forwards the opaque resume cursor', async () => {
const close = vi.fn();
const openWorkspaceEvents = vi.fn().mockResolvedValue({
events: (async function* () {
yield {
id: 'session-one:7',
type: 'design.generation_task.updated' as const,
workspaceId: 'workspace/one',
workspaceViewRevision: 8,
generationTask: {
taskId: 'task-live',
workspaceId: 'workspace/one',
medium: 'image' as const,
status: 'succeeded' as const,
briefVersion: 1,
briefSummary: '海洋公益海报',
quoteId: 'quote-live',
quotedDesignPoints: 1,
failureCode: null,
resultAssets: [],
createdAt: '2026-08-02T10:00:00Z',
updatedAt: '2026-08-02T10:02:00Z',
},
};
})(),
close,
});
const request = createRequest('GET');
request.headers = { 'last-event-id': 'session-one:6' };
const response = new MediaResponse();
const handled = await handleImageWorkspaceRoutes(
request,
response as unknown as ServerResponse,
new URL(
'http://127.0.0.1/api/works/image-workspace/workspaces/workspace%2Fone/events',
),
{ imageWorkspace: { openWorkspaceEvents } } as unknown as HostApiContext,
);
expect(handled).toBe(true);
expect(openWorkspaceEvents).toHaveBeenCalledWith({
workspaceId: 'workspace/one',
afterEventId: 'session-one:6',
});
expect(response.statusCode).toBe(200);
expect(response.headers.get('content-type')).toBe('text/event-stream; charset=utf-8');
expect(response.headers.get('x-accel-buffering')).toBe('no');
expect(Buffer.concat(response.chunks).toString()).toContain([
'id: session-one:7',
'event: design.generation_task.updated',
'data: {"id":"session-one:7"',
].join('\n'));
expect(close).toHaveBeenCalledOnce();
});
});