From 7a807a2f5269455aafa2973a7b37831d7329e38a Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Thu, 13 Aug 2026 14:28:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A7=E7=89=88=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=20Brief=20=E5=AA=92=E4=BB=8B=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image-workspace/works-square-workspace.ts | 17 +++++-- .../works-square-design-workspace.test.ts | 44 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/electron/image-workspace/works-square-workspace.ts b/electron/image-workspace/works-square-workspace.ts index 3a5c496..fb69d1d 100644 --- a/electron/image-workspace/works-square-workspace.ts +++ b/electron/image-workspace/works-square-workspace.ts @@ -36,7 +36,7 @@ import { type ServerBrief = { version: number; status: DesignBrief['status']; - medium: DesignBrief['medium']; + medium?: DesignBrief['medium']; summary: string; ready: boolean; missing_decision: string | null; @@ -219,10 +219,18 @@ const AGENT_RUN_TIMEOUT_MS = 10 * 60_000; const DESIGN_EVENT_DELIVERY_BARRIER_TIMEOUT_MS = 1_000; function mapBrief(brief: ServerBrief): DesignBrief { + const medium = brief.medium ?? null; + if (medium !== null && medium !== 'image' && medium !== 'video') { + throw new DesignWorkspaceModuleError( + 502, + 'DESIGN_WORKSPACE_RESPONSE_INVALID', + 'AI 设计服务返回了无效的媒介类型', + ); + } return { version: brief.version, status: brief.status, - medium: brief.medium, + medium, summary: brief.summary, ready: brief.ready, missingDecision: brief.missing_decision, @@ -357,7 +365,10 @@ function isServerConversation(value: unknown): value is ServerConversation { && Boolean(brief) && Number.isInteger(brief?.version) && ['draft', 'ready', 'confirmed'].includes(String(brief?.status)) - && (brief?.medium === null || brief?.medium === 'image' || brief?.medium === 'video') + && (brief?.medium === undefined + || brief.medium === null + || brief.medium === 'image' + || brief.medium === 'video') && typeof brief?.summary === 'string' && typeof brief?.ready === 'boolean' && (brief?.missing_decision === null || typeof brief?.missing_decision === 'string') diff --git a/tests/unit/works-square-design-workspace.test.ts b/tests/unit/works-square-design-workspace.test.ts index 528e26b..91cdb94 100644 --- a/tests/unit/works-square-design-workspace.test.ts +++ b/tests/unit/works-square-design-workspace.test.ts @@ -232,6 +232,30 @@ describe('Works Square AI design adapter', () => { ); }); + 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() + .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() .mockResolvedValueOnce(jsonResponse(serverConversation)) @@ -940,6 +964,11 @@ describe('Works Square AI design adapter', () => { }); 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', @@ -964,10 +993,21 @@ describe('Works Square AI design adapter', () => { workspace_id: 'workspace-one', conversation_id: 'conversation-one', workspace_view_revision: 2, - conversation: serverConversation, + 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, @@ -1038,6 +1078,7 @@ describe('Works Square AI design adapter', () => { const { sockets, webSocketFactory } = scriptedSockets([ { frames: [ + { type: 'event', event: invalidSnapshotEvent }, { type: 'event', event: snapshotEvent }, { type: 'event', event: malformedDeltaEvent }, { type: 'event', event: assistantDeltaEvent }, @@ -1077,6 +1118,7 @@ describe('Works Square AI design adapter', () => { conversation: expect.objectContaining({ conversationId: 'conversation-one', title: serverConversation.title, + brief: expect.objectContaining({ medium: null }), messages: [expect.objectContaining({ text: serverConversation.messages[0].text, })],