接入做成游戏后的策划文档指令 #383

Merged
lhk229 merged 8 commits from feat/auto-start into master 2026-09-16 16:51:38 +08:00
8 changed files with 49 additions and 21 deletions
@@ -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"));
+8 -4
View File
@@ -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<string>();
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;
}
@@ -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<ProjectManifestMergeState | null>(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
@@ -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';
@@ -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',
@@ -918,7 +918,8 @@ export function ProjectSupervisorView({
rows={3}
value={chatInput}
references={chatReferences}
showTriggerButton={!directCodex}
showTriggerButton={!directCodex && !planningSurfaceActive}
showPolishAction={!planningSurfaceActive}
placeholder={
directCodex
? '描述你的想法,或 @ 引用素材'
@@ -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%;
@@ -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(<TagStatsHost planningStartMode />);
render(<TagStatsHost />);
// 先在画布上打开「编辑素材标签」面板改标签:与用户的操作路径一致。
fireEvent.click(
@@ -294,7 +294,7 @@ describe('改完素材标签后聊天 @ 选择器的标签统计与候选跟着
it('未标注标签时聊天 @ 选择器不渲染任何标签 chip', async () => {
installHostTauri();
render(<TagStatsHost planningStartMode />);
render(<TagStatsHost />);
await openChatPicker();
expect(pickerTagChips()).toEqual([]);