接入做成游戏后的策划文档指令 #383
@@ -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"));
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
Reference in New Issue
Block a user