diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs index 762b67983..db84a285b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs @@ -1494,7 +1494,7 @@ async fn background_agent_runtime_deletes_file_then_verifies_before_completion() assert!(prompt_input.contains("只删除项目内普通文件")); let verification_request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(Duration::from_secs(10)) .expect("verification plan request"); assert!(verification_request.contains("file.delete")); assert!(verification_request.contains("已删除 game/obsolete-runtime-file.txt")); diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index ee075cfa5..9523363ac 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -605,16 +605,17 @@ function isPersistableDirectCodexConversationMessage(message: ChatMessage) { return /^[a-z0-9][a-z0-9-]{5,159}$/iu.test(turnId); } -function claimInitialSupervisorMessageForPage(projectPath: string) { +function claimInitialSupervisorMessageForPage(projectPath: string, scope = '') { let claimedProjectPaths = initialSupervisorMessageClaimsByPage.get(window); if (!claimedProjectPaths) { claimedProjectPaths = new Set(); initialSupervisorMessageClaimsByPage.set(window, claimedProjectPaths); } - if (claimedProjectPaths.has(projectPath)) { + const claimKey = `${projectPath}\u0000${scope}`; + if (claimedProjectPaths.has(claimKey)) { return false; } - claimedProjectPaths.add(projectPath); + claimedProjectPaths.add(claimKey); return true; } @@ -683,6 +684,7 @@ type AppProps = { activeVersionId?: ProjectSupervisorComponentProps['activeVersionId']; supervisorChatOnly?: boolean; initialSupervisorMessage?: string; + initialSupervisorMessageClaimScope?: string; initialCreationType?: HomeCreationType | null; initialAttachments?: LauncherImportedAttachment[]; playRequest?: ProjectSupervisorComponentProps['playRequest']; @@ -733,6 +735,7 @@ export function App({ activeVersionId = null, supervisorChatOnly = false, initialSupervisorMessage = '', + initialSupervisorMessageClaimScope = '', initialCreationType = null, initialAttachments = [], playRequest = null, @@ -870,6 +873,7 @@ export function App({ const initialSupervisorMessageLatchRef = useRef({ projectPath: initialProjectPath, prompt: initialSupervisorMessage.trim(), + claimScope: initialSupervisorMessageClaimScope, creationType: initialCreationType, attachments: toDirectCodexTurnAttachments(initialAttachments), }); @@ -7501,7 +7505,7 @@ export function App({ } if ( chatAgentBusy || - !claimInitialSupervisorMessageForPage(latch.projectPath) + !claimInitialSupervisorMessageForPage(latch.projectPath, latch.claimScope) ) { return; } diff --git a/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx b/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx index 4727bf323..c40dca2e4 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx +++ b/apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx @@ -36,7 +36,10 @@ import type { WorkspaceLauncherShellProps } from './model'; import { NonEmptyProjectDialog, ProjectsPage } from './ProjectCreation'; import { useAccountWallet } from './useAccountWallet'; import { useDeveloperAgentPanel } from './useDeveloperAgentPanel'; -import { useHomeProjectCreation } from './useHomeProjectCreation'; +import { + DESIGN_ARTIFACTS_BUILD_PROMPT, + useHomeProjectCreation, +} from './useHomeProjectCreation'; import { useRecentProjects } from './useRecentProjects'; export function WorkspaceLauncherShell({ @@ -113,7 +116,7 @@ export function WorkspaceLauncherShell({ const agentRuntimeMode: 'design' | 'game' = planningStartMode ? 'design' : 'game'; - const suppressInitialGameTurn = switchedToGameRuntime; + const omitOriginalPlanningTurnInputs = switchedToGameRuntime; const activeProjectContextRef = useRef(currentProjectContext); const manifestMergeRef = useRef(null); activeProjectContextRef.current = currentProjectContext; @@ -661,20 +664,27 @@ export function WorkspaceLauncherShell({ initialProjectManifest={currentProjectContext.manifest} initialProjectKind={currentProjectContext.projectKind} initialSupervisorMessage={ - !suppressInitialGameTurn - ? currentProjectContext.initialPrompt - : '' + switchedToGameRuntime + ? DESIGN_ARTIFACTS_BUILD_PROMPT + : !omitOriginalPlanningTurnInputs + ? currentProjectContext.initialPrompt + : '' } initialCreationType={ - !suppressInitialGameTurn - ? currentProjectContext.creationType - : null + switchedToGameRuntime + ? 'game' + : !omitOriginalPlanningTurnInputs + ? currentProjectContext.creationType + : null } initialAttachments={ - !suppressInitialGameTurn + !omitOriginalPlanningTurnInputs ? currentProjectContext.attachments : [] } + initialSupervisorMessageClaimScope={ + switchedToGameRuntime ? 'approved-design-build' : '' + } activeVersionId={activeVersionId} orchestrationMode="single-supervisor" projectSupervisorOnly diff --git a/apps/ai-game-creator-shell/src/features/app-shell/model.ts b/apps/ai-game-creator-shell/src/features/app-shell/model.ts index 5c54a5055..90ace801c 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/model.ts +++ b/apps/ai-game-creator-shell/src/features/app-shell/model.ts @@ -35,6 +35,7 @@ export type ProjectSupervisorComponentProps = { initialProjectManifest?: GameCreationAppManifest; initialProjectKind?: 'web' | 'godot' | 'cocos'; initialSupervisorMessage?: string; + initialSupervisorMessageClaimScope?: string; initialCreationType?: HomeCreationType | null; initialAttachments?: LauncherImportedAttachment[]; orchestrationMode?: 'single-supervisor' | 'professional-dag'; diff --git a/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts b/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts index 56201d7d3..27059edca 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts +++ b/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts @@ -66,7 +66,7 @@ type UseHomeProjectCreationOptions = { rememberRecentWorkspace: (projectPath: string) => void; }; -const APPROVED_GDD_BUILD_PROMPT = [ +export const APPROVED_GDD_BUILD_PROMPT = [ '请按照附件中的已批准 GDD 开始建造这款游戏。', '', '这份 GDD 已覆盖游戏定位与一句话概念、类型与美术方向、游戏支柱、核心循环、目标用户、平台与输入事实、MVP 系统、暂不纳入范围、创作者提示和原型验证项。', @@ -74,6 +74,9 @@ const APPROVED_GDD_BUILD_PROMPT = [ '请先阅读并理解附件中的 fast_gdd.md,以它作为本次建造的主要依据,优先实现其中 MVP 范围内的可运行游戏原型。', ].join('\n'); +export const DESIGN_ARTIFACTS_BUILD_PROMPT = + '查看当前项目 design_artifacts/ 目录及其子目录下的文档,理解其游戏设计,并按照这些文档将游戏实现出来。'; + function createTextAttachmentFile(content: string) { const file = new File([content], 'fast_gdd.md', { type: 'text/markdown', diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index d222b1fbd..604b908e0 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -918,7 +918,8 @@ export function ProjectSupervisorView({ rows={3} value={chatInput} references={chatReferences} - showTriggerButton={!directCodex} + showTriggerButton={!directCodex && !planningSurfaceActive} + showPolishAction={!planningSurfaceActive} placeholder={ directCodex ? '描述你的想法,或 @ 引用素材' diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index 651000f9c..5b0352492 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -10354,6 +10354,15 @@ button.design-workspace-tree__entry:hover, /* 策划聊天区包含阶段控制卡、消息、Runtime 状态和输入框。GameAgent 资源工作台的 消息列表默认占满整个聊天区,策划模式需要单独恢复五行布局,避免输入框被推到视口外。 */ +/* 策划工作台保留标题行,避免共用跨行规则将标题挤到底部。 */ +.game-workbench-layout--design .game-workbench-chat { + grid-template-rows: auto minmax(0, 1fr); +} + +.game-workbench-layout--design .game-workbench-chat .project-supervisor-surface { + grid-row: 2; +} + .game-workbench-layout--design .project-supervisor-surface { display: block; height: 100%; diff --git a/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx b/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx index b21591830..950c78f6e 100644 --- a/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx @@ -89,9 +89,10 @@ function graphFor(manifest: GameCreationAppManifest) { * * 这里只把壳换成用例自己的 `useState`(壳那份 CAS 归并由 * `workspaceLauncherManifestMerge.test.tsx` 单独钉住),画布、聊天输入区、 - * 标签统计与候选全部是被测的真实实现。 + * 标签统计与候选全部是被测的真实实现。策划 Agent 会隐藏 @ 入口,这条链路钉住 + * game Agent 的既有行为。 */ -function TagStatsHost({ planningStartMode }: { planningStartMode: boolean }) { +function TagStatsHost() { const [manifest, setManifest] = useState(() => { const initial = createFixtureManifest(); disk.manifest = initial; @@ -117,7 +118,6 @@ function TagStatsHost({ planningStartMode }: { planningStartMode: boolean }) { initialProjectPath={PROJECT_PATH} initialProjectManifest={manifest} projectSupervisorOnly - planningStartMode={planningStartMode} onManifestChange={onManifestChange} /> } @@ -251,7 +251,7 @@ describe('改完素材标签后聊天 @ 选择器的标签统计与候选跟着 it('在资源画布改完标签保存后,聊天 @ 选择器出现该标签及其计数,并按它收窄候选', async () => { const { classificationWrites } = installHostTauri(); - render(); + render(); // 先在画布上打开「编辑素材标签」面板改标签:与用户的操作路径一致。 fireEvent.click( @@ -294,7 +294,7 @@ describe('改完素材标签后聊天 @ 选择器的标签统计与候选跟着 it('未标注标签时聊天 @ 选择器不渲染任何标签 chip', async () => { installHostTauri(); - render(); + render(); await openChatPicker(); expect(pickerTagChips()).toEqual([]);