diff --git a/electron/image-workspace/works-square-workspace.ts b/electron/image-workspace/works-square-workspace.ts index 360dc4b..91a5129 100644 --- a/electron/image-workspace/works-square-workspace.ts +++ b/electron/image-workspace/works-square-workspace.ts @@ -190,8 +190,9 @@ type TaskEventQueue = { const AGENT_WEBSOCKET_OPEN = 1; const AGENT_WEBSOCKET_PING_INTERVAL_MS = 20_000; -const AGENT_RUN_POLL_INTERVAL_MS = 250; -const AGENT_RUN_TIMEOUT_MS = 120_000; +const AGENT_RUN_INITIAL_POLL_INTERVAL_MS = 1_000; +const AGENT_RUN_MAX_POLL_INTERVAL_MS = 5_000; +const AGENT_RUN_TIMEOUT_MS = 10 * 60_000; function mapBrief(brief: ServerBrief): DesignBrief { return { @@ -737,6 +738,7 @@ export class WorksSquareDesignWorkspace implements DesignWorkspaceModule { private async waitForAgentRun(sessionId: string, runId: string): Promise { const deadline = Date.now() + AGENT_RUN_TIMEOUT_MS; + let pollInterval = AGENT_RUN_INITIAL_POLL_INTERVAL_MS; while (true) { const run = await this.requestJson( `/api/agents/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(runId)}`, @@ -751,7 +753,8 @@ export class WorksSquareDesignWorkspace implements DesignWorkspaceModule { '设计 Agent 响应超时,请稍后重试', ); } - await new Promise((resolve) => setTimeout(resolve, AGENT_RUN_POLL_INTERVAL_MS)); + await new Promise((resolve) => setTimeout(resolve, pollInterval)); + pollInterval = Math.min(pollInterval * 2, AGENT_RUN_MAX_POLL_INTERVAL_MS); } } diff --git a/tests/unit/works-square-design-workspace.test.ts b/tests/unit/works-square-design-workspace.test.ts index 23d9864..8697cdf 100644 --- a/tests/unit/works-square-design-workspace.test.ts +++ b/tests/unit/works-square-design-workspace.test.ts @@ -210,6 +210,59 @@ describe('Works Square AI design adapter', () => { ); }); + 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('/api/agents/sessions')) { + return jsonResponse({ session_id: 'session-slow', status: 'active' }, 201); + } + 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('/api/design/workspaces/workspace-one')) { + return jsonResponse(serverWorkspace); + } + 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', + 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({