From cb79c4a9c6d275e37ebb2f9caecbad918edb6530 Mon Sep 17 00:00:00 2001 From: Linghong Date: Thu, 10 Sep 2026 09:09:37 +0000 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E9=BD=90=E7=AD=96=E5=88=92=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E6=B5=8B=E8=AF=95=E5=88=B0=E6=96=B0=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=20Agent=20=E6=96=B0=E5=BB=BA=E7=AD=96=E5=88=92=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E8=B5=B0=E8=AE=BE=E8=AE=A1=E4=BC=9A=E8=AF=9D=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=EF=BC=8C=E5=B7=B2=E6=9C=89=20V2=20=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E4=BB=8D=E8=B5=B0=E5=8E=9F=E9=93=BE=E8=B7=AF=20=E8=A1=A5?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=B8=8E=E6=BE=84=E6=B8=85=E5=8D=A1=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/runtime_protocol/design_session.rs | 2 +- .../tests/appSurface.test.ts | 2 + .../tests/appSurface/design-agent.suite.ts | 70 +++++++++++++++++++ .../tests/appSurface/harness.ts | 32 +++++++++ .../tests/appSurface/plan-gdd.suite.ts | 55 ++++++++++++--- 5 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs index 0b7cfb364..b7140a978 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs @@ -2,7 +2,6 @@ use super::*; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use std::fs; use std::path::{Path, PathBuf}; use uuid::Uuid; use serde_json::Value; @@ -301,6 +300,7 @@ pub(crate) fn reject_design_phase( #[cfg(test)] mod tests { use super::*; + use std::fs; #[test] fn phase_artifact_check_includes_previous_phases() { diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index 5b9ab4807..054d30653 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -1,6 +1,7 @@ /** @vitest-environment jsdom */ import { registerAgentRuntimeCommandTests } from './appSurface/agent-runtime.suite'; import { registerAuthTests } from './appSurface/auth.suite'; +import { registerDesignAgentSurfaceTests } from './appSurface/design-agent.suite'; import { registerDeveloperAgentWindowTests, registerDeveloperToolsTests, @@ -56,4 +57,5 @@ describe('AI 游戏创作 App 界面边界', () => { registerAgentRuntimeCommandTests(); registerCanvasAssetTests(); registerPlanGddApprovalTests(); + registerDesignAgentSurfaceTests(); }); diff --git a/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts new file mode 100644 index 000000000..8b5fac582 --- /dev/null +++ b/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts @@ -0,0 +1,70 @@ +import { + App, + createProjectSupervisorRuntimeHarness, + expect, + fireEvent, + it, + React, + render, + screen, + waitFor, +} from './harness'; + +function designApprovalView() { + return { + session: { + sessionId: 'design-session-1', + projectId: 'local-project-draft', + currentPhase: 'concept', + approvedPhases: [], + pendingApproval: { + requestId: 'approval-1', + phase: 'concept', + submittedAt: 1, + }, + pendingClarification: null, + turnIndex: 2, + lastError: null, + }, + messages: [{ id: 'a1', role: 'assistant', text: '概念稿已写好。' }], + running: false, + canRetry: false, + }; +} + +export function registerDesignAgentSurfaceTests() { + it('hydrates an existing design session and decides approval through design commands', async () => { + const harness = createProjectSupervisorRuntimeHarness({ + designAgentView: designApprovalView(), + }); + window.__TAURI__ = { + core: { invoke: harness.invoke }, + event: { listen: harness.listen }, + }; + window.history.pushState({}, '', '/'); + render( + React.createElement(App, { + initialProjectPath: harness.projectPath, + orchestrationMode: 'single-supervisor', + planningStartMode: true, + projectSupervisorOnly: true, + }), + ); + + await screen.findByLabelText('批准'); + expect( + harness.invoke.mock.calls.some( + ([command]) => command === 'hydrate_design_agent_session', + ), + ).toBe(true); + expect(screen.getByLabelText('项目需求')).toHaveProperty('disabled', true); + fireEvent.click(screen.getByRole('button', { name: '批准' })); + await waitFor(() => { + expect( + harness.invoke.mock.calls.some( + ([command]) => command === 'decide_design_phase', + ), + ).toBe(true); + }); + }); +} diff --git a/apps/ai-game-creator-shell/tests/appSurface/harness.ts b/apps/ai-game-creator-shell/tests/appSurface/harness.ts index 01f3dd0c0..ab4939de2 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/harness.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/harness.ts @@ -462,6 +462,9 @@ function createProjectSupervisorRuntimeHarness({ expectedRunProfile = 'autonomous-game-build', planningV2Result = null, planningV2StartResult = null, + designAgentView = null, + designAgentContinueView = null, + designWorkspaceFiles = [], }: { projectPath?: string; sessionId?: string; @@ -475,6 +478,9 @@ function createProjectSupervisorRuntimeHarness({ expectedRunProfile?: 'standard' | 'autonomous-game-build'; planningV2Result?: Record | null; planningV2StartResult?: Record | null; + designAgentView?: Record | null; + designAgentContinueView?: Record | null; + designWorkspaceFiles?: Array>; } = {}) { const manifest = createGameCreationAppManifest( 'local-project-draft', @@ -522,6 +528,9 @@ function createProjectSupervisorRuntimeHarness({ // 默认不配置策划状态:hydrate 与接入本 harness 之前一样抛出,既有用例行为不变。 let currentPlanningV2Result = planningV2Result; let currentPlanningV2StartResult = planningV2StartResult; + let currentDesignAgentView = designAgentView; + const currentDesignContinueView = designAgentContinueView; + const currentDesignWorkspaceFiles = [...designWorkspaceFiles]; let planningV2DecisionError: string | null = null; const planningV2DecisionCalls: Array> = []; let runtimeUpdateHandler: @@ -841,6 +850,29 @@ function createProjectSupervisorRuntimeHarness({ }); return runtimeResult(); } + if (command === 'hydrate_design_agent_session') { + return currentDesignAgentView; + } + if (command === 'continue_design_agent_session') { + const next = currentDesignContinueView ?? currentDesignAgentView; + if (!next) { + throw new Error('DESIGN_AGENT_VIEW_NOT_CONFIGURED'); + } + currentDesignAgentView = next; + return next; + } + if (command === 'decide_design_phase') { + if (!currentDesignAgentView) { + throw new Error('DESIGN_AGENT_VIEW_NOT_CONFIGURED'); + } + return currentDesignAgentView; + } + if (command === 'list_design_workspace') { + return currentDesignWorkspaceFiles; + } + if (command === 'read_design_workspace_file') { + return ''; + } if (command === 'hydrate_planning_session_v2') { return currentPlanningV2Result; } diff --git a/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts index a1ee2b5ef..510d73029 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts @@ -745,9 +745,39 @@ export function registerPlanGddApprovalTests() { }); }); - it('starts a new V2 planning session and renders its question card', async () => { + it('starts a new planning entry through the design agent', async () => { const harness = createProjectSupervisorRuntimeHarness({ - planningV2StartResult: planningV2QuestionResult(), + designAgentContinueView: { + session: { + sessionId: 'design-session-1', + projectId: 'local-project-draft', + currentPhase: 'concept', + approvedPhases: [], + pendingApproval: null, + pendingClarification: { + requestId: 'clarify-1', + question: '玩家在一局中主要反复做什么?', + options: ['持续闪避', '站桩输出'], + createdAt: 1, + }, + turnIndex: 1, + lastError: null, + }, + messages: [ + { + id: 'u1', + role: 'user', + text: '做一个2D弹幕射击游戏', + }, + { + id: 'a1', + role: 'assistant', + text: '先确认核心循环。', + }, + ], + running: false, + canRetry: false, + }, }); window.__TAURI__ = { core: { invoke: harness.invoke }, @@ -764,21 +794,28 @@ export function registerPlanGddApprovalTests() { }), ); - await screen.findByLabelText('Needs input'); + await screen.findByText('玩家在一局中主要反复做什么?'); + expect( + harness.invoke.mock.calls.some( + ([command]) => command === 'hydrate_design_agent_session', + ), + ).toBe(true); expect( harness.invoke.mock.calls.some( ([command]) => command === 'start_planning_session_v2', ), - ).toBe(true); - expect(screen.getByText('玩家在一局中主要反复做什么?')).not.toBeNull(); + ).toBe(false); expect(screen.queryByText(/agent\.delegate/)).toBeNull(); - fireEvent.click(screen.getByRole('button', { name: /持续闪避/ })); - fireEvent.click(screen.getByRole('button', { name: '提交回答' })); + fireEvent.click(screen.getByRole('button', { name: '持续闪避' })); await waitFor(() => { expect(harness.invoke).toHaveBeenCalledWith( - 'continue_planning_session_v2', + 'continue_design_agent_session', expect.objectContaining({ - input: { text: '持续闪避', questionId: 'core_loop' }, + input: expect.objectContaining({ + type: 'clarification', + requestId: 'clarify-1', + optionIndex: 0, + }), }), ); });