批准 GDD 后直接启动建造
点击做成游戏后直接创建自动游戏工作区 导入 fast_gdd.md 并携带固定建造指令启动 Direct Codex 清理首页 GDD 预填链路并补充重复点击回归测试 同步 Fast GDD 技术方案与共享决策记录
This commit is contained in:
@@ -77,10 +77,7 @@ export function WorkspaceLauncherShell({
|
||||
activeProjectAgentResults,
|
||||
setAgentResults: setActiveProjectAgentResults,
|
||||
resetLauncherHomeDraft,
|
||||
homePrefillAttachments,
|
||||
homePrefillPrompt,
|
||||
clearHomePrefill,
|
||||
prepareHomeGameFromApprovedGdd,
|
||||
startGameFromApprovedGdd,
|
||||
createHomeDraftAutomatically,
|
||||
openProject,
|
||||
} = homeProject;
|
||||
@@ -264,7 +261,6 @@ export function WorkspaceLauncherShell({
|
||||
currentUser={currentUser}
|
||||
onLogout={() => {
|
||||
resetLauncherHomeDraft();
|
||||
clearHomePrefill();
|
||||
accountWallet.resetWalletBalance();
|
||||
onLogout();
|
||||
}}
|
||||
@@ -298,9 +294,6 @@ export function WorkspaceLauncherShell({
|
||||
onStatusChange={setStatus}
|
||||
recentProjectRows={recentProjectRows}
|
||||
onCreateDraftAutomatically={createHomeDraftAutomatically}
|
||||
initialAttachments={homePrefillAttachments}
|
||||
initialPrompt={homePrefillPrompt}
|
||||
onInitialPrefillApplied={clearHomePrefill}
|
||||
onProjectsOpen={() => setLauncherView('projects')}
|
||||
onProjectOpen={(path) => {
|
||||
setProjectPath(path);
|
||||
@@ -359,7 +352,7 @@ export function WorkspaceLauncherShell({
|
||||
setActiveProjectAgentRuntimeSummaries
|
||||
}
|
||||
onAgentResultsChange={setActiveProjectAgentResults}
|
||||
onMakeGameFromApprovedGdd={prepareHomeGameFromApprovedGdd}
|
||||
onMakeGameFromApprovedGdd={startGameFromApprovedGdd}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -49,7 +49,7 @@ type UseHomeProjectCreationOptions = {
|
||||
rememberRecentWorkspace: (projectPath: string) => void;
|
||||
};
|
||||
|
||||
const APPROVED_GDD_HOME_PROMPT = [
|
||||
const APPROVED_GDD_BUILD_PROMPT = [
|
||||
'请按照附件中的已批准 GDD 开始建造这款游戏。',
|
||||
'',
|
||||
'这份 GDD 已覆盖游戏定位与一句话概念、类型与美术方向、游戏支柱、核心循环、目标用户、平台与输入事实、MVP 系统、暂不纳入范围、创作者提示和原型验证项。',
|
||||
@@ -57,6 +57,19 @@ const APPROVED_GDD_HOME_PROMPT = [
|
||||
'请先阅读并理解附件中的 fast_gdd.md,以它作为本次建造的主要依据,优先实现其中 MVP 范围内的可运行游戏原型。',
|
||||
].join('\n');
|
||||
|
||||
function createTextAttachmentFile(content: string) {
|
||||
const file = new File([content], 'fast_gdd.md', {
|
||||
type: 'text/markdown',
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
if (typeof file.arrayBuffer !== 'function') {
|
||||
Object.defineProperty(file, 'arrayBuffer', {
|
||||
value: async () => new TextEncoder().encode(content).buffer,
|
||||
});
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
export function useHomeProjectCreation({
|
||||
setStatus,
|
||||
setLauncherView,
|
||||
@@ -86,13 +99,7 @@ export function useHomeProjectCreation({
|
||||
const resetLauncherHomeDraft = useLauncherHomeDraftStore(
|
||||
(state) => state.reset,
|
||||
);
|
||||
const setHomeCreationType = useLauncherHomeDraftStore(
|
||||
(state) => state.setCreationType,
|
||||
);
|
||||
const [homePrefillAttachments, setHomePrefillAttachments] = useState<
|
||||
HomeAttachmentDraft[]
|
||||
>([]);
|
||||
const [homePrefillPrompt, setHomePrefillPrompt] = useState('');
|
||||
const approvedGddStartInFlightRef = useRef(false);
|
||||
|
||||
function validateProjectPath(nextProjectPath: string) {
|
||||
const trimmedProjectPath = nextProjectPath.trim();
|
||||
@@ -107,41 +114,48 @@ export function useHomeProjectCreation({
|
||||
return trimmedProjectPath;
|
||||
}
|
||||
|
||||
async function prepareHomeGameFromApprovedGdd(nextProjectPath: string) {
|
||||
async function startGameFromApprovedGdd(nextProjectPath: string) {
|
||||
if (approvedGddStartInFlightRef.current) {
|
||||
return;
|
||||
}
|
||||
approvedGddStartInFlightRef.current = true;
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke) {
|
||||
throw new Error('需要在陶泥儿客户端内运行');
|
||||
}
|
||||
const projectPath = nextProjectPath.trim();
|
||||
if (!projectPath) {
|
||||
throw new Error('当前项目路径无效');
|
||||
}
|
||||
try {
|
||||
if (!invoke) {
|
||||
throw new Error('需要在陶泥儿客户端内运行');
|
||||
}
|
||||
const projectPath = nextProjectPath.trim();
|
||||
if (!projectPath) {
|
||||
throw new Error('当前项目路径无效');
|
||||
}
|
||||
|
||||
setStatus('正在读取已批准 GDD');
|
||||
const result = await invoke<LocalProjectFileResult>(
|
||||
'read_local_project_file',
|
||||
{
|
||||
projectPath,
|
||||
relativePath: 'game/fast_gdd.md',
|
||||
commandId: 'file.read',
|
||||
},
|
||||
);
|
||||
const file = new File([result.content], 'fast_gdd.md', {
|
||||
type: 'text/markdown',
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
setStatus('正在读取已批准 GDD');
|
||||
const result = await invoke<LocalProjectFileResult>(
|
||||
'read_local_project_file',
|
||||
{
|
||||
projectPath,
|
||||
relativePath: 'game/fast_gdd.md',
|
||||
commandId: 'file.read',
|
||||
},
|
||||
);
|
||||
const file = createTextAttachmentFile(result.content);
|
||||
|
||||
resetLauncherHomeDraft();
|
||||
setHomeCreationType('game');
|
||||
setHomePrefillPrompt(APPROVED_GDD_HOME_PROMPT);
|
||||
setHomePrefillAttachments([
|
||||
{
|
||||
id: `approved-gdd-${Date.now().toString(36)}`,
|
||||
file,
|
||||
},
|
||||
]);
|
||||
setLauncherView('home');
|
||||
setStatus('已回到做游戏入口');
|
||||
await createHomeDraftAutomatically(
|
||||
{
|
||||
creationType: 'game',
|
||||
prompt: APPROVED_GDD_BUILD_PROMPT,
|
||||
attachments: [
|
||||
{
|
||||
id: `approved-gdd-${Date.now().toString(36)}`,
|
||||
file,
|
||||
},
|
||||
],
|
||||
},
|
||||
'direct-build',
|
||||
);
|
||||
} finally {
|
||||
approvedGddStartInFlightRef.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
function enterProjectDevelopment(context: LauncherProjectContext) {
|
||||
@@ -653,13 +667,7 @@ export function useHomeProjectCreation({
|
||||
projectBusy: projectAction !== null,
|
||||
pendingNonEmptyProject,
|
||||
resetLauncherHomeDraft,
|
||||
homePrefillAttachments,
|
||||
homePrefillPrompt,
|
||||
clearHomePrefill: () => {
|
||||
setHomePrefillAttachments([]);
|
||||
setHomePrefillPrompt('');
|
||||
},
|
||||
prepareHomeGameFromApprovedGdd,
|
||||
startGameFromApprovedGdd,
|
||||
createHomeDraft,
|
||||
createHomeDraftAutomatically,
|
||||
openProject,
|
||||
|
||||
@@ -221,7 +221,7 @@ export function PlanGddStageProgress({
|
||||
.finally(() => setMakingGame(false));
|
||||
}}
|
||||
>
|
||||
{makingGame ? '正在准备' : '做成游戏'}
|
||||
{makingGame ? '正在启动' : '做成游戏'}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -28,9 +28,6 @@ type RichInputAreaProps = {
|
||||
placeholder: string;
|
||||
onChange: (value: Draft) => void;
|
||||
onEnter: () => void;
|
||||
initialAttachments?: readonly HomeAttachmentDraft[];
|
||||
initialPrompt?: string;
|
||||
onInitialPrefillApplied?: () => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
const INSERT_ATTACHMENTS_COMMAND: LexicalCommand<HomeAttachmentDraft[]> =
|
||||
@@ -107,19 +104,8 @@ function selectEditableEndWhenNeeded() {
|
||||
function EditorPlugins({
|
||||
onChange,
|
||||
onEnter,
|
||||
initialAttachments = [],
|
||||
initialPrompt = '',
|
||||
onInitialPrefillApplied,
|
||||
}: Pick<
|
||||
RichInputAreaProps,
|
||||
| 'onChange'
|
||||
| 'onEnter'
|
||||
| 'initialAttachments'
|
||||
| 'initialPrompt'
|
||||
| 'onInitialPrefillApplied'
|
||||
>) {
|
||||
}: Pick<RichInputAreaProps, 'onChange' | 'onEnter'>) {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const initialPrefillAppliedRef = useRef(false);
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
@@ -202,32 +188,6 @@ function EditorPlugins({
|
||||
[editor],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
initialPrefillAppliedRef.current ||
|
||||
(initialPrompt.trim().length === 0 && initialAttachments.length === 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
initialPrefillAppliedRef.current = true;
|
||||
if (initialPrompt.length > 0) {
|
||||
editor.dispatchCommand(INSERT_CLIPBOARD_TEXT_COMMAND, initialPrompt);
|
||||
}
|
||||
if (initialAttachments.length > 0) {
|
||||
editor.dispatchCommand(INSERT_ATTACHMENTS_COMMAND, [
|
||||
...initialAttachments,
|
||||
]);
|
||||
}
|
||||
onChange(editor.getEditorState());
|
||||
onInitialPrefillApplied?.();
|
||||
}, [
|
||||
editor,
|
||||
initialAttachments,
|
||||
initialPrompt,
|
||||
onChange,
|
||||
onInitialPrefillApplied,
|
||||
]);
|
||||
|
||||
return (
|
||||
<OnChangePlugin
|
||||
onChange={(editorState) => {
|
||||
@@ -296,13 +256,7 @@ export default function RichInputArea(props: RichInputAreaProps) {
|
||||
/>
|
||||
{props.children}
|
||||
</div>
|
||||
<EditorPlugins
|
||||
onChange={props.onChange}
|
||||
onEnter={props.onEnter}
|
||||
initialAttachments={props.initialAttachments}
|
||||
initialPrompt={props.initialPrompt}
|
||||
onInitialPrefillApplied={props.onInitialPrefillApplied}
|
||||
/>
|
||||
<EditorPlugins onChange={props.onChange} onEnter={props.onEnter} />
|
||||
</LexicalComposer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
} from './components/RichInputArea/richTextToPrompt';
|
||||
import InspirationGallery from './InspirationGallery';
|
||||
import {
|
||||
type HomeAttachmentDraft,
|
||||
type HomeCreationType,
|
||||
type HomeDraft,
|
||||
useLauncherHomeDraftStore,
|
||||
@@ -104,9 +103,6 @@ type HomeViewProps = {
|
||||
onProjectsOpen: () => void;
|
||||
onProjectOpen: (path: string) => void;
|
||||
onProjectPick: () => void;
|
||||
initialAttachments?: readonly HomeAttachmentDraft[];
|
||||
initialPrompt?: string;
|
||||
onInitialPrefillApplied?: () => void;
|
||||
};
|
||||
|
||||
export default function HomeView({
|
||||
@@ -117,9 +113,6 @@ export default function HomeView({
|
||||
onProjectsOpen,
|
||||
onProjectOpen,
|
||||
onProjectPick,
|
||||
initialAttachments = [],
|
||||
initialPrompt = '',
|
||||
onInitialPrefillApplied,
|
||||
}: HomeViewProps) {
|
||||
const homeCreationType = useLauncherHomeDraftStore(
|
||||
(state) => state.creationType,
|
||||
@@ -241,9 +234,6 @@ export default function HomeView({
|
||||
value={homeRichText}
|
||||
placeholder={activeCreationType.placeholder}
|
||||
onChange={setHomeRichText}
|
||||
initialAttachments={initialAttachments}
|
||||
initialPrompt={initialPrompt}
|
||||
onInitialPrefillApplied={onInitialPrefillApplied}
|
||||
onEnter={() => {
|
||||
void createFromHome();
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { PROJECT_SUPERVISOR_PLAN_SOURCE } from '../../src/app/constants';
|
||||
import type { ProjectStartMode } from '../../src/app/types';
|
||||
import type { ProjectSupervisorComponentProps } from '../../src/features/app-shell/model';
|
||||
import { useHomeProjectCreation } from '../../src/features/app-shell/useHomeProjectCreation';
|
||||
import { WorkspaceLauncherShell } from '../../src/features/app-shell/WorkspaceLauncher';
|
||||
import HomeView, { type HomeDraft } from '../../src/view/home';
|
||||
import type { LauncherView } from '../../src/view/layout';
|
||||
import {
|
||||
act,
|
||||
@@ -31,15 +29,8 @@ import {
|
||||
within,
|
||||
} from './harness';
|
||||
|
||||
function ApprovedGddHomeBridgeHarness({
|
||||
onCreateDraftAutomatically,
|
||||
}: {
|
||||
onCreateDraftAutomatically: (
|
||||
draft: HomeDraft,
|
||||
startMode: ProjectStartMode,
|
||||
) => Promise<string>;
|
||||
}) {
|
||||
const [launcherView, setLauncherView] = React.useState<LauncherView>(
|
||||
function ApprovedGddStartHarness() {
|
||||
const [, setLauncherView] = React.useState<LauncherView>(
|
||||
'project-development',
|
||||
);
|
||||
const [, setStatus] = React.useState('');
|
||||
@@ -51,19 +42,12 @@ function ApprovedGddHomeBridgeHarness({
|
||||
rememberRecentWorkspace: () => undefined,
|
||||
});
|
||||
|
||||
if (launcherView === 'home') {
|
||||
return React.createElement(HomeView, {
|
||||
hasPromo: false,
|
||||
onStatusChange: setStatus,
|
||||
recentProjectRows: [],
|
||||
onCreateDraftAutomatically,
|
||||
onProjectsOpen: () => undefined,
|
||||
onProjectOpen: () => undefined,
|
||||
onProjectPick: () => undefined,
|
||||
initialAttachments: controller.homePrefillAttachments,
|
||||
initialPrompt: controller.homePrefillPrompt,
|
||||
onInitialPrefillApplied: controller.clearHomePrefill,
|
||||
});
|
||||
if (controller.currentProjectContext) {
|
||||
return React.createElement(
|
||||
'p',
|
||||
{ 'aria-label': '已进入自动游戏项目' },
|
||||
controller.currentProjectContext.initialPrompt,
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
@@ -71,9 +55,9 @@ function ApprovedGddHomeBridgeHarness({
|
||||
{
|
||||
type: 'button',
|
||||
onClick: () =>
|
||||
void controller.prepareHomeGameFromApprovedGdd('/tmp/planning-project'),
|
||||
void controller.startGameFromApprovedGdd('/tmp/planning-project'),
|
||||
},
|
||||
'把已批准 GDD 带到做游戏',
|
||||
'直接用已批准 GDD 开始建造',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1552,75 +1536,71 @@ export function registerHomeProjectCreationTests() {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns to the game entry with the approved GDD without starting creation', async () => {
|
||||
const onCreateDraftAutomatically = vi.fn(async () => 'unexpected');
|
||||
it('starts an automatic game project from the approved GDD', async () => {
|
||||
const automaticProjectPath = '/tmp/approved-gdd-game';
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'approved-gdd-game',
|
||||
'已批准 GDD 游戏',
|
||||
);
|
||||
const gddContent = '# Fast GDD\n\n批准后的方案内容';
|
||||
const invoke = vi.fn(async (command: string) => {
|
||||
if (command === 'read_local_project_file') {
|
||||
return {
|
||||
path: 'game/fast_gdd.md',
|
||||
absolutePath: '/tmp/planning-project/game/fast_gdd.md',
|
||||
content: '# Fast GDD\n\n批准后的方案内容',
|
||||
content: gddContent,
|
||||
};
|
||||
}
|
||||
if (command === 'create_automatic_local_game_project') {
|
||||
return {
|
||||
projectPath: automaticProjectPath,
|
||||
manifestPath: `${automaticProjectPath}/.agent/manifest.json`,
|
||||
manifest,
|
||||
};
|
||||
}
|
||||
if (command === 'upload_local_asset') {
|
||||
return {
|
||||
id: 'approved-gdd-asset',
|
||||
localPath: 'assets/uploads/fast_gdd.md',
|
||||
absolutePath: `${automaticProjectPath}/assets/uploads/fast_gdd.md`,
|
||||
manifestPath: `${automaticProjectPath}/.agent/manifest.json`,
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
});
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
render(
|
||||
React.createElement(ApprovedGddHomeBridgeHarness, {
|
||||
onCreateDraftAutomatically,
|
||||
}),
|
||||
);
|
||||
render(React.createElement(ApprovedGddStartHarness));
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: '把已批准 GDD 带到做游戏' }),
|
||||
screen.getByRole('button', { name: '直接用已批准 GDD 开始建造' }),
|
||||
);
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: '直接用已批准 GDD 开始建造' }),
|
||||
);
|
||||
|
||||
await screen.findByText('fast_gdd.md');
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('创作想法').textContent).toContain(
|
||||
'请按照附件中的已批准 GDD 开始建造这款游戏。',
|
||||
);
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command]) => command === 'create_automatic_local_game_project',
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
expect(screen.getByLabelText('创作想法').textContent).toContain(
|
||||
'这份 GDD 已覆盖游戏定位与一句话概念、类型与美术方向、游戏支柱、核心循环、目标用户、平台与输入事实、MVP 系统、暂不纳入范围、创作者提示和原型验证项。',
|
||||
expect(screen.getByLabelText('已进入自动游戏项目').textContent).toContain(
|
||||
'请按照附件中的已批准 GDD 开始建造这款游戏。',
|
||||
);
|
||||
const creationTypes = screen.getByRole('group', { name: '创作类型' });
|
||||
expect(
|
||||
within(creationTypes)
|
||||
.getByRole('button', { name: '做游戏' })
|
||||
.getAttribute('aria-pressed'),
|
||||
).toBe('true');
|
||||
expect(onCreateDraftAutomatically).not.toHaveBeenCalled();
|
||||
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
|
||||
projectPath: '/tmp/planning-project',
|
||||
relativePath: 'game/fast_gdd.md',
|
||||
commandId: 'file.read',
|
||||
});
|
||||
expect(invoke).not.toHaveBeenCalledWith(
|
||||
'create_automatic_local_game_project',
|
||||
);
|
||||
expect(invoke).not.toHaveBeenCalledWith(
|
||||
'start_game_creator_supervisor_runtime_task',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(invoke).not.toHaveBeenCalledWith(
|
||||
'chat_with_game_creator_direct_codex',
|
||||
expect.anything(),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '开启创作' }));
|
||||
await waitFor(() => {
|
||||
expect(onCreateDraftAutomatically).toHaveBeenCalledTimes(1);
|
||||
expect(invoke).toHaveBeenCalledWith('upload_local_asset', {
|
||||
projectPath: automaticProjectPath,
|
||||
fileName: 'fast_gdd.md',
|
||||
mediaType: 'text/markdown',
|
||||
bytes: Array.from(new TextEncoder().encode(gddContent)),
|
||||
});
|
||||
});
|
||||
const submittedDraft = onCreateDraftAutomatically.mock.calls[0]?.[0] as
|
||||
| HomeDraft
|
||||
| undefined;
|
||||
expect(submittedDraft?.prompt).toContain(
|
||||
'请按照附件中的已批准 GDD 开始建造这款游戏。',
|
||||
);
|
||||
expect(submittedDraft?.attachments.map(({ file }) => file.name)).toEqual([
|
||||
'fast_gdd.md',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps the home composer out of chat mode while automatic project creation is pending', async () => {
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
- 关联文档:相关 PRD、技术文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 2026-08-30 批准 GDD 通过首页做游戏入口回填参考附件
|
||||
## 2026-08-30 批准 GDD 直接进入做游戏链路
|
||||
|
||||
- 背景:立项策划 GDD 批准后需要给用户一个进入做游戏的自然出口,但用户明确要求点击按钮时不要直接创建项目或启动完整制作流程。
|
||||
- 决策:批准态 GDD 交付行提供“做成游戏”按钮。点击后读取当前项目的权威 `game/fast_gdd.md`,回到首页并预选“做游戏”、把该文件作为 `text/markdown` 参考附件放入输入框;只有用户随后主动提交首页输入,才沿现有自动建项、附件导入和 Direct Codex 链路继续。该动作不复制原项目的 `approvedGddRef`、planning sidecar 或 approval receipt。
|
||||
- 影响范围:AGC 前端 GDD 交付行、WorkspaceLauncher 首页预填状态、首页 RichInputArea 一次性附件注入;不新增 HTTP API、SpacetimeDB schema、迁移、OpenAPI 或正式构建绑定。
|
||||
- 验证方式:批准态按钮、首页预填附件、回填过程不触发创建项目/启动 Runtime/Direct Codex 的 appSurface 回归;类型检查、编码检查和 `git diff --check` 通过。
|
||||
- 背景:立项策划 GDD 批准后需要给用户一个进入做游戏的自然出口,产品决策改为点击按钮后直接开始建造。
|
||||
- 决策:批准态 GDD 交付行提供“做成游戏”按钮。点击后读取当前项目的权威 `game/fast_gdd.md`,直接创建自动游戏工作区、导入 `text/markdown` 参考附件,并以固定建造指令自动启动 Direct Codex;不再回首页等待用户二次提交。该动作不复制原项目的 `approvedGddRef`、planning sidecar 或 approval receipt。
|
||||
- 影响范围:AGC 前端 GDD 交付行与现有自动建项/附件导入/Direct Codex 链路;移除首页 RichInputArea 的 GDD 一次性预填链路;不新增 HTTP API、SpacetimeDB schema、迁移、OpenAPI 或正式构建绑定。
|
||||
- 验证方式:批准态按钮直接创建工作区、导入附件、携带固定首条指令进入项目工作台且重复点击不重复创建的 appSurface 回归;类型检查、编码检查和 `git diff --check` 通过。
|
||||
- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。
|
||||
|
||||
---
|
||||
@@ -7835,9 +7835,3 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- `autonomous-game-build` 中,manifest `dependencies` 只作为上下文,不阻塞 ready;代码、设计、美术、音频和发布任务允许并行启动,child 不依赖固定回执顺序或固定 run 身份才能推进。
|
||||
- 任务最终状态不再提前绑定平台画布、preview、static smoke 或发布产物检查;这些内容不参与该档位的完成判定,也不会因缺失而重置已完成任务。父 run 在任务图进入终态后直接收束并回复。
|
||||
- 本档位仍沿用现有项目根和工具权限边界;本次调整只解除流程编排与平台产物验收前置,不新增第二套任务系统。
|
||||
|
||||
## 2026-08-30 批准 GDD 回填建造指令
|
||||
|
||||
- “做成游戏”回首页时,除了把当前有效的 `game/fast_gdd.md` 作为 `text/markdown` 参考附件预填,还要在首页输入框预填固定建造指令。
|
||||
- 固定指令只说明 GDD 覆盖的栏目(定位、类型与美术、支柱、核心循环、目标用户、平台与输入、MVP 系统、范围边界、创作者提示、原型验证),不读取具体 GDD 做总结或缩写。
|
||||
- 预填动作仍不创建项目、不发送首页需求、不启动 Agent;用户主动提交后,固定文字与附件才作为现有做游戏链路的输入。
|
||||
|
||||
@@ -180,7 +180,7 @@ D9/D10 描述的「manifest ready-task 调度器在 Supervisor 下游启动策
|
||||
| 构建引用 | `approvedGddRef {gddId, version, fingerprint}` |
|
||||
| 现有 design 组 UI 名 | `设计实现组`,替代原“策划 Agent”卡片名称 |
|
||||
| 新组件名 | `GDD 审批卡` |
|
||||
| 固定入口动作 | 当前客户端为 `做成游戏`:回首页预填 `game/fast_gdd.md` 参考附件,不自动创建项目或启动 Runtime;用户再次提交首页输入后才进入做游戏链路 |
|
||||
| 固定入口动作 | 当前客户端为 `做成游戏`:读取 `game/fast_gdd.md`,直接创建自动游戏工作区、导入参考附件并以固定建造指令启动 Direct Codex;不再回首页等待用户二次提交 |
|
||||
|
||||
`approvalRequestId` 与 `responseId` 是两个不同的持久 ID:前者由 Runtime 在 GDD 提交前生成、进入不可变 GDD,一张审批卡终身不变;后者由 UI 在用户执行一次决定时生成,并在传输重试中复用。不得继续用含义不明的单个 `requestId` 同时承担两种职责。
|
||||
|
||||
@@ -278,8 +278,7 @@ flowchart TD
|
||||
SPLAN -->|"在自己的 runtime/session 上建 pending,向用户提问"| U
|
||||
SPLAN -->|"answersSha256 绑回 delivery,再发 continuation 委派<br/>(最多 3 轮,受 clarification_round 上限约束,见第 23.5 节)"| PLANAGENT
|
||||
PLANAGENT --> GDD["不可变 GDD + approve receipt"]
|
||||
GDD -->|"用户动作:做成游戏;回首页预填 game/fast_gdd.md"| HOME["首页做游戏入口<br/>参考附件:fast_gdd.md<br/>不自动创建/启动"]
|
||||
HOME -->|"用户再次提交首页输入"| BUILD
|
||||
GDD -->|"用户动作:做成游戏;读取并导入 game/fast_gdd.md"| BUILD["自动创建游戏工作区<br/>参考附件:fast_gdd.md<br/>固定建造指令"]
|
||||
U -.->|"直接开建"| BUILD
|
||||
BUILD --> DAG["现行 16 任务 DAG"]
|
||||
U --> CHAT
|
||||
@@ -1518,8 +1517,8 @@ type PlanningBaselineInput =
|
||||
- 仅首页“做方案”新建项目提交 `standard + project-supervisor-plan`(2026-08-13 按 D11 更正,旧值 `project-supervisor-plan-chat` 作废);“做游戏”和“做素材”保持 `autonomous-game-build` 直接开建。前端只提交 Supervisor 根 run 的身份,**不提交也不感知策划子 Agent**——后者由 Supervisor 在服务端通过 `agent.delegate` 派生,页面侧不得直接创建或引用它。
|
||||
- 项目页新建、打开既有项目和 Godot 导入不新增“进入立项策划”入口,保持现行构建/打开语义;只有已存在 planning sidecar 或 active plan lineage 的项目恢复原有策划链路。
|
||||
- 策划阶段聊天输入属于当前 run:有活跃决策卡/审批卡时,输入回到该卡片对应 action;无 active run 时才可创建新的 plan continuation。
|
||||
- approved 后显示“做成游戏”。点击后读取当前有效的 `game/fast_gdd.md`,回到首页并把它作为 `text/markdown` 参考附件预填到“做游戏”输入框,同时预填固定的建造指令:说明 GDD 覆盖的栏目,并要求 Agent 先阅读附件、按 GDD 的 MVP 范围开始建造;这段文字不根据具体 GDD 内容生成。该动作不创建新项目、不发送首页需求、不启动 `autonomous-game-build`。
|
||||
- 用户随后主动提交首页输入时,才沿现有做游戏链路创建新的自动项目、导入附件并启动 Direct Codex。这里的附件和预填文字都是本次建造的输入,不把原项目的 `approvedGddRef` 复制到新项目。
|
||||
- approved 后显示“做成游戏”。点击后读取当前有效的 `game/fast_gdd.md`,直接沿现有自动做游戏链路创建新的工作区、导入 `text/markdown` 参考附件,并以固定建造指令作为 `initialSupervisorMessage` 自动启动 Direct Codex;固定指令只说明 GDD 覆盖的栏目,不根据具体 GDD 内容生成总结。该动作不回首页等待二次提交,不把原项目的 `approvedGddRef` 复制到新项目。
|
||||
- 用户仍可在项目工作台继续补充需求;普通首页“做游戏”入口的手动提交行为保持不变。
|
||||
|
||||
### 18.2 GDD 审批卡
|
||||
|
||||
@@ -1705,7 +1704,7 @@ receipt 永远压过 stale pending:一旦该版本存在有效 receipt,pendi
|
||||
| agent.db | 专用幂等 helper;same key conflict;日志达到普通容量、尾部截断与压缩后仍能补齐并保留决定记录 |
|
||||
| source/security | durable exact identity;三个 action tool(`file.read` / `file.list` / `plan.submit_gdd`)广告与执行;MCP 空且 webSearchEnabled=false;control functions 单列;tool-plan/batch/ledger/context/repair/completion 全部跳过 collaboration;plan retry 保留 source/profile;除 Runtime-owned submit 外的副作用工具拒绝 |
|
||||
| Prompt | **2026-08-13 按 D11 改写**:不新增 composition,Supervisor 根 run 沿用现役 supervisor composition、策划子 Agent 复用 `runtime` composition(见第 4.2 节);`decision-checkpoint` 请求 kind 随 D10 作废(见第 5.1 节)。仍冻结:3 轮/单题/固定选项;回答后设计解释与 prototype item;平台事实;恢复摘要;direct-out 条件 |
|
||||
| frontend | 首页“做方案”目录提交与 Enter 自动创建均为 `standard + project-supervisor-plan`;“做游戏/做素材”均保持 `autonomous-game-build`;项目页新建/打开不首次注入 planning;stable approvalRequestId/responseId;busy;stale card;hydrate strict input/view;无目录空态;receipt 隐藏 stale pending;corrupt authority typed error;project open/reload/resume/submit/decision 刷新;recovery pending;批准 GDD 后“做成游戏”回首页预填 `fast_gdd.md` 且不启动创建/Runtime;用户再次提交后沿现有附件导入链路执行 |
|
||||
| frontend | 首页“做方案”目录提交与 Enter 自动创建均为 `standard + project-supervisor-plan`;“做游戏/做素材”均保持 `autonomous-game-build`;项目页新建/打开不首次注入 planning;stable approvalRequestId/responseId;busy;stale card;hydrate strict input/view;无目录空态;receipt 隐藏 stale pending;corrupt authority typed error;project open/reload/resume/submit/decision 刷新;recovery pending;批准 GDD 后“做成游戏”直接创建自动工作区、导入 `fast_gdd.md` 并自动启动 Direct Codex;重复点击不重复创建;普通首页链路不回归 |
|
||||
| M2 integration | explicit approved/direct mode;锁内重验 receipt;ref 贯穿 task/run/completion/context;无 ref 非回归;恢复不换稿 |
|
||||
|
||||
关键强杀点逐项覆盖:
|
||||
@@ -1996,7 +1995,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1`~`M1A-4`、`M1B-1`
|
||||
| `M1C-2b` | 澄清中转接线、轮次派生、预算注入 | `M1C-2a` | **实现与本包门禁已完成并已快进合回 `feat/five_min_design`**:首 child 的 revision 1 session、`NeedsUserInput → awaiting_user_input`、回答绑定后 continuation 的确定性 session 投影、审批后 `revise/reject` 用户修订谱系及 Provider 活跃时间 usage fact/fold 已接线;末次 `plan.submit_gdd` usage 在 receipt/session successor 落盘且 standalone/v4 anchors 精确消费后于同一项目锁内折叠,真实 receipt 回归证明累计值恰好推进一次,重复审批与 recovery reconcile 不二次推进。`planning_clarification_*` **13 passed / 0 failed**(M1C-2c 语义回归另见本包),另有 static deliveries 44、planning storage 13、planning submit 53、Provider usage 4、末次 usage receipt 1、barrier detail 3 条定向回归通过;格式、offline all-targets、编码与 diff 门禁通过。锁序承诺只适用于 **M1C-2b 新增的 planning 澄清写投影路径**;`main_loop` 既有通用 completion blocker 的 execution→project 路径不在本包。第 4 轮信封在正常路径不可达:`agent.delegate` 已在工具边界按血缘上限硬拒并返回 failed observation;coordinator 的超三轮 reconciliation 仅用于损坏血缘纵深防御。审批 UI、hydrate、构建准入和下游完整构建不在本包范围 |
|
||||
| `M1C-2c` | 决策卡 A/B 语义(第 23.9 节,2026-08-18 实现完成并合回):选项 → 台账映射改为 A/B 均 `confirmed/user_option`、信封 label 形状校验、planning role brief 与 Supervisor playbook/final-reply 文案(B 必须是真实岔路、第 3 项恒定且 description 须给出可执行验证方式、改口转述规则、提问纪律) | `M1C-2b` | **实现与门禁完成,已由 `6e4bd9703` 合回 `feat/five_min_design`**:Runtime 已实现 A/B/固定第三项校验、B 不再生成 `default_pending`、`answerSummary` 逐字保真;非法 C/缺项 fail-closed,A/B/自由填写回归已通过。`planning_clarification_*` 13、`project_planning` prompt 5、`planning_submit` 定向回归、prompt bundle、格式、编码、diff、offline all-targets 均通过;不含 M1D-1 前端、hydrate、构建准入或下游完整构建 |
|
||||
| `M1D-1` | 前端 hydrate 与 GDD 审批卡;决策卡按第 23.9 节实现(label 动态渲染、默认焦点 A、Other 槽不变) | `M1C-2b` | **已完成并合入 `feat/five_min_design`(落地 `0052a80da`,其后 ESLint 修正 `5b11a0530`)**:新增严格 `{projectPath}` hydrate command、`plan-gdd-state-view.v1` Rust read model、审批卡与独立 GDD 正文详情弹层;页面只消费 hydrate,决定 responseId 按审批请求/动作复用,`recoveryPending` 仅提供恢复重试;审批前置 pending 与错绑 session 继续 fail-closed。 |
|
||||
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目以 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。**2026-08-30 补充:批准 GDD 后的“做成游戏”只回首页预填 `game/fast_gdd.md` 参考附件,不自动创建项目或启动 Runtime;仍未接入正式 `approvedGddRef` 构建绑定。** |
|
||||
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目以 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。**2026-08-30 变更:批准 GDD 后的“做成游戏”直接创建自动工作区、导入 `game/fast_gdd.md` 并自动启动 Direct Codex;仍未接入正式 `approvedGddRef` 构建绑定。** |
|
||||
| `M1E` | 端到端与故障注入收口 | `M1D-2` | **已完成**:planning 覆盖审计与 submit 拒绝上限收口完成。`PLAN_INVALID_REQUEST` / Provider input 或候选 GDD 的 `PLAN_SIZE_LIMIT` 每 child run 最多 5 次 rejected observation,第 5 次在 observation durable 后终态失败;counter durable,重启不清零。若在第五条 rejected observation 落盘与终态失败之间崩溃,恢复入口会按 durable counter 直接终态失败,不请求第六次 Provider tool-plan。既有不可变 GDD/receipt 的超限读取及 lineage 版本已耗尽均改走 reconciliation,不误耗 Provider 重试额度。第 21 节已存在的三轮、续跑、receipt/replay、投影恢复及 hydrate 回归复核通过;不为“拼接已有单测”新增脆弱大 E2E。 |
|
||||
|
||||
**2026-08-18 M1D 审查修复快照**:对 `14c00017c..bf2185fba` 做规格对照审查后,修复三条决定链路缺陷并补齐回归。① `decidePlanGdd` 的失败分支原来不 hydrate,命中后端任一 `PLAN_STALE_APPROVAL` 分支后卡片会停在已失效的 pending 身份上、`recoveryPending` 永不翻真导致「重试恢复」入口不渲染,现已按第 18.3 节在失败分支同样重灌(顺序钉死:`hydratePlanGddState` 入口会清空错误,必须先 hydrate 再写决定错误)。② responseId 复用键原为 `approvalRequestId:action`,不含 comment,违反第 13.2 节「改变 action/comment 必须换新 responseId」,现改为比对 `{action, comment}` 完整意图,判据方向为宁可多换不可少换。③ 第 18.2 节「`recoveryPending` 时不允许提交决定」原来只作用于三个触发按钮,已打开的评论弹层仍可提交,现已同门控并保留用户已输入内容。回归位于 `tests/appSurface/plan-gdd.suite.ts`,三条均经变异验证(逆转对应修复即变红);`appSurface.test.ts` 381 passed,`agc:typecheck`、ESLint、编码检查通过。其中锁错误回传绝对路径、阶段进度轮次差一格与 design 组展示名收口三条已于同日补修(见 decision-log 同日两条);仅 hydrate 身份校验与落盘投影修复的顺序一条单列后续,未并入。
|
||||
|
||||
Reference in New Issue
Block a user