diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index 15976a11c..6788bf4b1 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -17,7 +17,6 @@ import { registerProjectWorkbenchFoundationTests, registerProjectWorkbenchNavigationTests, } from './appSurface/project-development.suite'; -import { registerProjectPreviewAndRunHistoryTests } from './appSurface/project-preview.suite'; import { registerAgentStatusDerivationTests, registerRuntimeSettingsTests, @@ -47,7 +46,6 @@ describe('AI 游戏创作 App 界面边界', () => { registerRuntimeSettingsTests(); registerRecentProjectsTests(); registerProjectWorkbenchNavigationTests(); - registerProjectPreviewAndRunHistoryTests(); registerProjectConversationTests(); registerProjectAgentStatusTests(); registerPlanGddApprovalTests(); diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview.suite.ts deleted file mode 100644 index 3f09fc584..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview.suite.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { registerProjectPreviewShortcutTests } from './project-preview/preview-shortcuts.suite'; -import { registerProjectRunHistoryTests } from './project-preview/run-history.suite'; - -export function registerProjectPreviewAndRunHistoryTests() { - registerProjectPreviewShortcutTests(); - registerProjectRunHistoryTests(); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts.suite.ts deleted file mode 100644 index c354e7c1c..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts.suite.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { it } from '../harness'; -import { openPreviewShortcutProject } from './preview-shortcuts/actions'; -import { arrangePreviewShortcutTest } from './preview-shortcuts/arrange'; -import { assertPlanningAndStatusShortcutFlow } from './preview-shortcuts/assert-planning-and-status-shortcuts'; -import { assertPlaytestAndReleaseShortcutFlow } from './preview-shortcuts/assert-playtest-and-release-shortcuts'; -import { assertProjectAndDesignShortcutFlow } from './preview-shortcuts/assert-project-and-design-shortcuts'; -import { assertProjectToolsAndPreviewFlow } from './preview-shortcuts/assert-project-tools-and-preview'; - -export function registerProjectPreviewShortcutTests() { - it('runs preview shortcuts from the main project window', async () => { - const { invoke } = arrangePreviewShortcutTest(); - - openPreviewShortcutProject(); - - await assertProjectAndDesignShortcutFlow(invoke); - await assertPlanningAndStatusShortcutFlow(invoke); - await assertPlaytestAndReleaseShortcutFlow(invoke); - await assertProjectToolsAndPreviewFlow(invoke); - }, 60000); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/actions.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/actions.ts deleted file mode 100644 index 5cda70075..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/actions.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { renderAppAt } from '../../harness'; - -export function openPreviewShortcutProject() { - renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game'); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/arrange.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/arrange.ts deleted file mode 100644 index 37cd7be06..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/arrange.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createPreviewShortcutFixture } from './fixtures'; -import { createPreviewShortcutInvoke } from './invoke-mock'; - -export function arrangePreviewShortcutTest() { - const fixture = createPreviewShortcutFixture(); - const invoke = createPreviewShortcutInvoke(fixture); - window.__TAURI__ = { core: { invoke } }; - - return { invoke }; -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts deleted file mode 100644 index 3788df509..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-planning-and-status-shortcuts.ts +++ /dev/null @@ -1,1705 +0,0 @@ -import { - composerText, - expect, - fireEvent, - screen, - submitChat, - within, -} from '../../harness'; -import type { PreviewShortcutInvoke } from './invoke-mock'; -import { messageBubble } from './message-bubble'; - -export async function assertPlanningAndStatusShortcutFlow( - invoke: PreviewShortcutInvoke, -) { - const manifestReadCountBeforeRisks = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const fileReadCountBeforeRisks = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const runLocalCountBeforeRisks = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/risks'); - expect(await screen.findByText(/项目风险:/)).not.toBeNull(); - expect( - screen.getByText(/最近 run 已通过,但当前本地预览未运行。 建议:\/run/), - ).not.toBeNull(); - expect( - screen.getByText(/还有 3 个 ready 任务等待处理。 建议:\/tasks/), - ).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '处理首个风险' })); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeRisks); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeRisks); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeRisks); - - const commandCountsBeforeBlockers = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/blockers'); - const blockerMessages = await screen.findAllByText(/当前阻塞项:/); - const blockerMessage = messageBubble( - blockerMessages[blockerMessages.length - 1], - ); - expect(blockerMessage.textContent).toContain('项目:未命名游戏原型'); - expect(blockerMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(blockerMessage.textContent).toContain( - '原型已通过,但本地预览未运行。 建议:/run', - ); - expect(blockerMessage.textContent).toContain( - '原型已通过,但本地试玩包尚未导出。 建议:/export', - ); - expect(blockerMessage.textContent).toContain( - 'ready 任务 1 个:美术组 / Asset 生成首版美术素材(art-asset-plan)。 建议:/todo', - ); - expect(blockerMessage.textContent).toContain( - '边界:只整理阻塞项;不读取文件;不启动预览;不导出试玩包;不写项目', - ); - expect(blockerMessage.textContent).toContain('建议:/run'); - const blockerMessageList = document.querySelector('.message-list'); - expect(blockerMessageList).not.toBeNull(); - const blockerDraftButtons = within( - blockerMessageList as HTMLElement, - ).getAllByRole('button', { name: '处理首个阻塞' }); - fireEvent.click(blockerDraftButtons[blockerDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeBlockers.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeBlockers.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeBlockers.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeBlockers.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeBlockers.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeBlockers.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeBlockers.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeBlockers.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeBlockers.controlAgentRun); - - const commandCountsBeforeReady = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/ready'); - const readyMessages = await screen.findAllByText(/试玩就绪度:/); - const readyMessage = messageBubble(readyMessages[readyMessages.length - 1]); - expect(readyMessage.textContent).toContain('项目:未命名游戏原型'); - expect(readyMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(readyMessage.textContent).toContain('原型:最近 run 已通过'); - expect(readyMessage.textContent).toContain('预览:未启动'); - expect(readyMessage.textContent).toContain('自检:已通过'); - expect(readyMessage.textContent).toContain('试玩包:未导出'); - expect(readyMessage.textContent).toContain('任务:失败 0 / ready 1'); - expect(readyMessage.textContent).toContain('素材:美术 已有 / 音频 可后补'); - expect(readyMessage.textContent).toContain( - '结论:接近可测,先补齐预览 / 自检 / 试玩包', - ); - expect(readyMessage.textContent).toContain( - '边界:只判断就绪度;不读取文件;不启动预览;不导出试玩包;不写项目', - ); - expect(readyMessage.textContent).toContain('建议:/run'); - const readyMessageList = document.querySelector('.message-list'); - expect(readyMessageList).not.toBeNull(); - const readyDraftButtons = within( - readyMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(readyDraftButtons[readyDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeReady.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeReady.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeReady.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeReady.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeReady.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeReady.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeReady.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeReady.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeReady.controlAgentRun); - - const commandCountsBeforeEvidence = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/evidence'); - const evidenceMessages = await screen.findAllByText(/验证证据台账:/); - const evidenceMessage = messageBubble( - evidenceMessages[evidenceMessages.length - 1], - ); - expect(evidenceMessage.textContent).toContain('项目:未命名游戏原型'); - expect(evidenceMessage.textContent).toContain( - 'Run trace:已有 run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(evidenceMessage.textContent).toContain('Evaluator:通过'); - expect(evidenceMessage.textContent).toContain('静态自检:已有通过证据'); - expect(evidenceMessage.textContent).toContain('预览:未启动'); - expect(evidenceMessage.textContent).toContain('试玩包:缺失'); - expect(evidenceMessage.textContent).toContain( - '入口产物:缺失 trace 产物证据', - ); - expect(evidenceMessage.textContent).toContain( - '资产:2 个 · 美术 有 · 音频 可后补', - ); - expect(evidenceMessage.textContent).toContain('最近失败命令:暂无'); - expect(evidenceMessage.textContent).toContain('缺口:'); - expect(evidenceMessage.textContent).toContain('本地预览未运行 · 建议 /run'); - expect(evidenceMessage.textContent).toContain( - '缺少本地试玩包导出记录 · 建议 /export', - ); - expect(evidenceMessage.textContent).toContain( - '边界:只整理当前已加载证据;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(evidenceMessage.textContent).toContain('建议:/run'); - const evidenceMessageList = document.querySelector('.message-list'); - expect(evidenceMessageList).not.toBeNull(); - const evidenceDraftButtons = within( - evidenceMessageList as HTMLElement, - ).getAllByRole('button', { name: '补齐首个证据缺口' }); - fireEvent.click(evidenceDraftButtons[evidenceDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeEvidence.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeEvidence.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeEvidence.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeEvidence.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeEvidence.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeEvidence.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeEvidence.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeEvidence.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeEvidence.controlAgentRun); - - const commandCountsBeforeDeps = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/deps'); - const dependencyMessages = await screen.findAllByText(/任务依赖链:/); - const dependencyMessage = messageBubble( - dependencyMessages[dependencyMessages.length - 1], - ); - expect(dependencyMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(dependencyMessage.textContent).toContain( - '状态:active 0 / carry 0 / ready 1 / 等待依赖 13', - ); - expect(dependencyMessage.textContent).toContain( - '美术组 / Asset 生成首版美术素材(art-asset-plan) · 等待:美术组 / Director 确定视觉方向与规范图(art-director);设计实现组 / Gameplay 确定玩法规格与界面原型(design-foundation)', - ); - expect(dependencyMessage.textContent).toContain( - '设计实现组 / Gameplay 确定玩法规格与界面原型(design-foundation) · 等待:设计实现组 / Director 拆解创作方向(design-director);美术组 / Director 确定视觉方向与规范图(art-director)', - ); - expect(dependencyMessage.textContent).toContain( - '边界:只整理任务依赖;不读取任务文件;不启动 run;不修改项目', - ); - expect(dependencyMessage.textContent).toContain('建议:/criteria'); - const dependencyMessageList = document.querySelector('.message-list'); - expect(dependencyMessageList).not.toBeNull(); - const dependencyDraftButtons = within( - dependencyMessageList as HTMLElement, - ).getAllByRole('button', { name: '查看验收标准' }); - fireEvent.click(dependencyDraftButtons[dependencyDraftButtons.length - 1]); - expect(await composerText()).toBe('/criteria'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeDeps.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeDeps.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeDeps.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeDeps.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeDeps.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeDeps.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeDeps.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeDeps.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeDeps.controlAgentRun); - - const commandCountsBeforeRevise = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/revise'); - const revisionMessages = await screen.findAllByText(/改版草稿:/); - const revisionMessage = messageBubble( - revisionMessages[revisionMessages.length - 1], - ); - expect(revisionMessage.textContent).toContain('项目:未命名游戏原型'); - expect(revisionMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(revisionMessage.textContent).toContain( - '最近评审:Evaluator #1 · passed · 质量评审通过', - ); - expect(revisionMessage.textContent).toContain('最近试玩:暂无'); - expect(revisionMessage.textContent).toContain( - '推进 ready 任务:美术组 / Asset 生成首版美术素材(art-asset-plan)', - ); - expect(revisionMessage.textContent).toContain( - '补齐本地试玩:启动预览并验证首屏', - ); - expect(revisionMessage.textContent).toContain('交付:导出本地试玩包'); - expect(revisionMessage.textContent).toContain( - '保留项:保留当前已通过的核心玩法和可运行入口', - ); - expect(revisionMessage.textContent).toContain( - '调整项:推进 ready 任务:美术组 / Asset 生成首版美术素材(art-asset-plan);补齐本地试玩:启动预览并验证首屏', - ); - expect(revisionMessage.textContent).toContain( - '新增项:补一次本地预览验证;补导出本地试玩包', - ); - expect(revisionMessage.textContent).toContain( - '验收口径:通过 /ready、/qa 和 /changes 复查', - ); - expect(revisionMessage.textContent).toContain( - '参考命令:/ready;/deps;/qa;/changes', - ); - expect(revisionMessage.textContent).toContain( - '草稿:/agent-resume 改版说明:', - ); - expect(revisionMessage.textContent).toContain( - '边界:只准备改版说明;不继续 run;不读取文件;不启动预览;不导出试玩包;不写项目', - ); - expect(revisionMessage.textContent).toContain( - '建议:/agent-resume 改版说明:', - ); - const revisionMessageList = document.querySelector('.message-list'); - expect(revisionMessageList).not.toBeNull(); - const revisionDraftButtons = within( - revisionMessageList as HTMLElement, - ).getAllByRole('button', { name: '填入改版说明' }); - fireEvent.click(revisionDraftButtons[revisionDraftButtons.length - 1]); - expect(await composerText()).toEqual( - expect.stringContaining('/agent-resume 改版说明:'), - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeRevise.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeRevise.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeRevise.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeRevise.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeRevise.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeRevise.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeRevise.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeRevise.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeRevise.controlAgentRun); - - const commandCountsBeforePrivacy = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/privacy'); - const privacyMessages = await screen.findAllByText(/隐私与导出边界:/); - const privacyMessage = messageBubble( - privacyMessages[privacyMessages.length - 1], - ); - expect(privacyMessage.textContent).toContain('项目:未命名游戏原型'); - expect(privacyMessage.textContent).toContain( - '本地目录:/tmp/authorized-game', - ); - expect(privacyMessage.textContent).toContain( - '智能服务凭据由服务端按登录账号管理;不进入客户端、manifest、trace、聊天、导出包或项目文件', - ); - expect(privacyMessage.textContent).toContain( - '本地预览:未启动;仅限 127.0.0.1 本机访问', - ); - expect(privacyMessage.textContent).toContain( - '试玩包:尚未导出;只应包含 game/、assets/ 和 exports/README.md', - ); - expect(privacyMessage.textContent).toContain( - '内部文件:.agent/、memory/、日志、trace、配置和密钥不得进入试玩包', - ); - expect(privacyMessage.textContent).toContain( - '素材来源:2 个;上传 1 / 画板 1', - ); - expect(privacyMessage.textContent).toContain( - 'Trace:run-main-shortcut-trace · 3 个内部产物记录;只通过 /trace 或 /internals 查看,不作为交付内容', - ); - expect(privacyMessage.textContent).toContain( - '交付前建议:/credits;/ready;/export;/exports', - ); - expect(privacyMessage.textContent).toContain( - '边界:只整理隐私与交付口径;不读取文件;不导出;不启动预览;不写项目', - ); - expect(privacyMessage.textContent).toContain('建议:/credits'); - const privacyMessageList = document.querySelector('.message-list'); - expect(privacyMessageList).not.toBeNull(); - const privacyDraftButtons = within( - privacyMessageList as HTMLElement, - ).getAllByRole('button', { name: '查看素材来源' }); - fireEvent.click(privacyDraftButtons[privacyDraftButtons.length - 1]); - expect(await composerText()).toBe('/credits'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforePrivacy.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforePrivacy.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforePrivacy.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforePrivacy.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePrivacy.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePrivacy.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforePrivacy.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforePrivacy.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforePrivacy.controlAgentRun); - - const fileReadCountBeforeCriteria = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeCriteria = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforeCriteria = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/criteria'); - expect(await screen.findByText(/当前验收标准:/)).not.toBeNull(); - const criteriaMessages = screen.getAllByText(/当前验收标准:/); - const criteriaMessage = messageBubble( - criteriaMessages[criteriaMessages.length - 1], - ); - expect(criteriaMessage.textContent).toContain( - 'ready:美术组 / Asset 生成首版美术素材(art-asset-plan)', - ); - expect(criteriaMessage.textContent).toContain( - '验收:角色、场景、UI 和动画需求已映射到画板或本地资产,且至少一张首版核心素材图已生成并登记', - ); - expect(criteriaMessage.textContent).toContain( - '产物:assets/manifest.art.json, assets/art-spritesheet.png', - ); - const criteriaDraftButtons = screen.getAllByRole('button', { - name: '查看任务', - }); - fireEvent.click(criteriaDraftButtons[criteriaDraftButtons.length - 1]); - expect(await composerText()).toBe('/tasks'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeCriteria); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeCriteria); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeCriteria); - - const fileReadCountBeforeGroups = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeGroups = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforeGroups = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/groups'); - expect(await screen.findByText(/专业组进度:/)).not.toBeNull(); - const groupMessages = screen.getAllByText(/专业组进度:/); - const groupMessage = messageBubble(groupMessages[groupMessages.length - 1]); - expect(groupMessage.textContent).toContain( - '美术组:完成 0/3 · active 0 · carry 0 · ready 1', - ); - expect(groupMessage.textContent).toContain('下一步 Asset 生成首版美术素材'); - expect(groupMessage.textContent).toContain('程序组:完成 0/5'); - const groupDraftButtons = screen.getAllByRole('button', { - name: '查看任务', - }); - fireEvent.click(groupDraftButtons[groupDraftButtons.length - 1]); - expect(await composerText()).toBe('/tasks'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeGroups); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeGroups); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeGroups); - - const commandCountsBeforeBalance = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - }; - await submitChat('/balance'); - expect(await screen.findByText(/数值状态:/)).not.toBeNull(); - const balanceMessages = screen.getAllByText(/数值状态:/); - const balanceMessage = messageBubble( - balanceMessages[balanceMessages.length - 1], - ); - expect(balanceMessage.textContent).toContain('项目:未命名游戏原型'); - expect(balanceMessage.textContent).toContain( - 'balance-director:Director 确定数值口径 · 待处理', - ); - expect(balanceMessage.textContent).toContain( - 'balance-seed:Difficulty 生成初版数值 · 待处理', - ); - expect(balanceMessage.textContent).toContain( - '难度、节奏和得分口径可指导数值表', - ); - expect(balanceMessage.textContent).toContain( - '速度、生命、得分和难度参数可被程序组读取', - ); - expect(balanceMessage.textContent).toContain( - '数值表:game/balance.json · 待生成', - ); - expect(balanceMessage.textContent).toContain( - '试玩关联:/playtest;/feedback', - ); - expect(balanceMessage.textContent).toContain( - '建议:/agent-resume 数值调整:前 30 秒更易上手;得分反馈更明显;失败后重开节奏更快', - ); - const balanceMessageList = document.querySelector('.message-list'); - expect(balanceMessageList).not.toBeNull(); - const balanceDraftButtons = within( - balanceMessageList as HTMLElement, - ).getAllByRole('button', { name: '填写数值反馈' }); - fireEvent.click(balanceDraftButtons[balanceDraftButtons.length - 1]); - expect(await composerText()).toBe( - '/agent-resume 数值调整:前 30 秒更易上手;得分反馈更明显;失败后重开节奏更快', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeBalance.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeBalance.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeBalance.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeBalance.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeBalance.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeBalance.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeBalance.exportPackage); - - const fileReadCountBeforeBudget = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeBudget = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforeBudget = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/budget'); - expect(await screen.findByText(/运行预算:/)).not.toBeNull(); - const budgetMessages = screen.getAllByText(/运行预算:/); - const budgetMessage = messageBubble( - budgetMessages[budgetMessages.length - 1], - ); - expect(budgetMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(budgetMessage.textContent).toContain('轮次:已用 1/3 · 剩余 2'); - expect(budgetMessage.textContent).toContain( - '工具调用:已用 1/128 · 剩余 127', - ); - expect(budgetMessage.textContent).toContain('下一步:preview'); - expect(budgetMessage.textContent).toContain('建议:/publish'); - const budgetDraftButtons = screen.getAllByRole('button', { - name: '查看发布准备', - }); - fireEvent.click(budgetDraftButtons[budgetDraftButtons.length - 1]); - expect(await composerText()).toBe('/publish'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeBudget); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeBudget); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeBudget); - - const fileReadCountBeforeQa = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeQa = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforeQa = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - const startPreviewCountBeforeQa = invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length; - const openPreviewCountBeforeQa = invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length; - await submitChat('/qa'); - expect(await screen.findByText(/质量检查:/)).not.toBeNull(); - const qaMessages = screen.getAllByText(/质量检查:/); - const qaMessage = messageBubble(qaMessages[qaMessages.length - 1]); - expect(qaMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(qaMessage.textContent).toContain('Evaluator:通过 · 质量评审通过'); - expect(qaMessage.textContent).toContain('任务:完成 0/16 · ready 3 · 失败 0'); - expect(qaMessage.textContent).toContain('静态自检:通过'); - expect(qaMessage.textContent).toContain('试玩:待启动预览'); - expect(qaMessage.textContent).toContain('产物:3 个'); - expect(qaMessage.textContent).toContain('建议:/playtest'); - const qaDraftButtons = screen.getAllByRole('button', { - name: '查看试玩状态', - }); - fireEvent.click(qaDraftButtons[qaDraftButtons.length - 1]); - expect(await composerText()).toBe('/playtest'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeQa); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeQa); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeQa); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(startPreviewCountBeforeQa); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(openPreviewCountBeforeQa); - - const fileReadCountBeforeChanges = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeChanges = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const fileListCountBeforeChanges = invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length; - const runLocalCountBeforeChanges = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - const startPreviewCountBeforeChanges = invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length; - const openPreviewCountBeforeChanges = invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length; - await submitChat('/changes'); - expect(await screen.findByText(/最近变更:/)).not.toBeNull(); - const changeMessages = screen.getAllByText(/最近变更:/); - const changeMessage = messageBubble( - changeMessages[changeMessages.length - 1], - ); - expect(changeMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(changeMessage.textContent).toContain('可验产物:3 个'); - expect(changeMessage.textContent).toContain('exports/README.md · 128B'); - expect(changeMessage.textContent).toContain( - '.agent/passes/pass-1/agenda.md · 96B', - ); - expect(changeMessage.textContent).toContain( - 'Generator #1 · completed · .agent/passes/pass-1/draft.json', - ); - expect(changeMessage.textContent).toContain( - '最近命令:game.static_smoke · 完成', - ); - expect(changeMessage.textContent).toContain( - '真实差异:/checkpoints 后 /diff checkpoint-id', - ); - expect(changeMessage.textContent).toContain('建议:/read exports/README.md'); - const changeDraftButtons = screen.getAllByRole('button', { - name: '读取首个产物', - }); - fireEvent.click(changeDraftButtons[changeDraftButtons.length - 1]); - expect(await composerText()).toBe('/read exports/README.md'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeChanges); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeChanges); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(fileListCountBeforeChanges); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeChanges); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(startPreviewCountBeforeChanges); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(openPreviewCountBeforeChanges); - - const fileReadCountBeforeReview = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeReview = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforeReview = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - const startPreviewCountBeforeReview = invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length; - const openPreviewCountBeforeReview = invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length; - await submitChat('/review'); - expect(await screen.findByText(/评审状态:/)).not.toBeNull(); - const reviewMessages = screen.getAllByText(/评审状态:/); - const reviewMessage = messageBubble( - reviewMessages[reviewMessages.length - 1], - ); - expect(reviewMessage.textContent).toContain('Evaluator:通过'); - expect(reviewMessage.textContent).toContain('返工焦点:暂无'); - expect(reviewMessage.textContent).toContain( - '评审记录:/read .agent/findings.md', - ); - expect(reviewMessage.textContent).toContain( - 'Evaluator #1 · passed · 质量评审通过', - ); - fireEvent.click(screen.getByRole('button', { name: '读取评审记录' })); - expect(await composerText()).toBe('/read .agent/findings.md'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeReview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeReview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeReview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(startPreviewCountBeforeReview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(openPreviewCountBeforeReview); - - const fileReadCountBeforeContext = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - await submitChat('/context'); - expect(await screen.findByText(/上下文来源:/)).not.toBeNull(); - expect(screen.getByText(/项目对话:\/history/)).not.toBeNull(); - expect(screen.getByText(/项目黑板:\/memory blackboard/)).not.toBeNull(); - expect(screen.getByText(/Agent 私有记忆:16 个/)).not.toBeNull(); - expect( - screen.getByText(/最近 Run:run-main-shortcut-trace · \/trace/), - ).not.toBeNull(); - expect( - screen.getByText(/\.agent\/spec\.md:\/read \.agent\/spec\.md/), - ).not.toBeNull(); - const contextDraftButtons = screen.getAllByRole('button', { - name: '读取首个上下文', - }); - fireEvent.click(contextDraftButtons[contextDraftButtons.length - 1]); - expect(await composerText()).toBe('/read .agent/spec.md'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeContext); - - const fileReadCountBeforeTimeline = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforeTimeline = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforeTimeline = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - const startPreviewCountBeforeTimeline = invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length; - const openPreviewCountBeforeTimeline = invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length; - await submitChat('/timeline'); - expect(await screen.findByText(/项目时间线:/)).not.toBeNull(); - const timelineMessages = screen.getAllByText(/项目时间线:/); - const timelineMessage = messageBubble( - timelineMessages[timelineMessages.length - 1], - ); - expect(timelineMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done', - ); - expect(timelineMessage.textContent).toContain( - '命令 game.static_smoke · 完成 · 日志 /read .agent/logs/command.log', - ); - expect(timelineMessage.textContent).toContain( - 'Generator #1 / generate · completed · 生成可运行草案 · 输出 .agent/passes/pass-1/draft.json', - ); - expect(timelineMessage.textContent).toContain( - 'Evaluator #1 / evaluation · passed · 质量评审通过', - ); - const timelineDraftButtons = screen.getAllByRole('button', { - name: '读取最近日志', - }); - fireEvent.click(timelineDraftButtons[timelineDraftButtons.length - 1]); - expect(await composerText()).toBe('/read .agent/logs/command.log'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeTimeline); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeTimeline); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeTimeline); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(startPreviewCountBeforeTimeline); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(openPreviewCountBeforeTimeline); - - const manifestReadCountBeforeHandoff = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const fileReadCountBeforeHandoff = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const runLocalCountBeforeHandoff = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/handoff'); - expect(await screen.findByText(/项目交接:/)).not.toBeNull(); - const handoffMessages = screen.getAllByText(/项目交接:/); - const handoffMessage = messageBubble( - handoffMessages[handoffMessages.length - 1], - ); - expect(handoffMessage.textContent).toContain('Run:run-main-shortcut-trace'); - expect(handoffMessage.textContent).toContain( - 'Ready:art/Asset 生成首版美术素材', - ); - expect(handoffMessage.textContent).toContain('历史:已加载 0 个 run'); - const nextActionButtons = screen.getAllByRole('button', { - name: '查看下一步', - }); - fireEvent.click(nextActionButtons[nextActionButtons.length - 1]); - expect(await composerText()).toBe('/next'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeHandoff); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeHandoff); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeHandoff); - - const fileReadCountBeforeRuns = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - await submitChat('/runs'); - expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull(); - expect( - screen.getByText(/当前指针 run-main-shortcut-trace .*:\/trace/), - ).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '查看最近 Run' })); - expect(await composerText()).toBe('/trace'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeRuns); - - const fileReadCountBeforeRunFiles = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - await submitChat('/run-files'); - expect( - await screen.findByText(/Agent 运行辅助文件读取命令:/), - ).not.toBeNull(); - expect(screen.getByText(/读输出流 · \.agent\/output\.jsonl/)).not.toBeNull(); - expect( - screen.getByText(/读上下文包 · \.agent\/context\.bundle\.json/), - ).not.toBeNull(); - const runFilesMessageList = document.querySelector('.message-list'); - expect(runFilesMessageList).not.toBeNull(); - fireEvent.click( - within(runFilesMessageList as HTMLElement).getByRole('button', { - name: '读输出流', - }), - ); - expect(await composerText()).toBe('/read .agent/output.jsonl'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeRunFiles); - - const fileReadCountBeforeInternals = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - await submitChat('/internals'); - expect(await screen.findByText(/项目内部真相源读取命令:/)).not.toBeNull(); - expect( - screen.getByText(/读 manifest · \.agent\/manifest\.json/), - ).not.toBeNull(); - expect( - screen.getByText(/读项目对话 · \.agent\/conversations\/project\.jsonl/), - ).not.toBeNull(); - const internalDraftButtons = screen.getAllByRole('button', { - name: '读 manifest', - }); - fireEvent.click(internalDraftButtons[internalDraftButtons.length - 1]); - expect(await composerText()).toBe('/read .agent/manifest.json'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeInternals); - - const commandCountsBeforeTodo = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/todo'); - const todoMessages = await screen.findAllByText(/下一轮小步:/); - const todoMessage = messageBubble(todoMessages[todoMessages.length - 1]); - expect(todoMessage.textContent).toContain('项目:未命名游戏原型'); - expect(todoMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(todoMessage.textContent).toContain('编排下一步:preview'); - expect(todoMessage.textContent).toContain( - 'ready:美术组 / Asset 生成首版美术素材(art-asset-plan) · 待处理 · 验收:角色、场景、UI 和动画需求已映射到画板或本地资产,且至少一张首版核心素材图已生成并登记', - ); - expect(todoMessage.textContent).toContain( - '边界:只整理下一步;不读取任务文件;不启动 run;不修改项目', - ); - expect(todoMessage.textContent).toContain('建议:/tasks'); - const todoMessageList = document.querySelector('.message-list'); - expect(todoMessageList).not.toBeNull(); - const todoDraftButtons = within(todoMessageList as HTMLElement).getAllByRole( - 'button', - { name: '查看任务' }, - ); - fireEvent.click(todoDraftButtons[todoDraftButtons.length - 1]); - expect(await composerText()).toBe('/tasks'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeTodo.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeTodo.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeTodo.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeTodo.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTodo.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTodo.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeTodo.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeTodo.exportList); - - const commandCountsBeforePlan = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/plan'); - const planMessages = await screen.findAllByText(/下一轮分工计划:/); - const planMessage = messageBubble(planMessages[planMessages.length - 1]); - expect(planMessage.textContent).toContain('项目:未命名游戏原型'); - expect(planMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(planMessage.textContent).toContain('编排焦点:preview'); - expect(planMessage.textContent).toContain('协作顺序:美术组'); - expect(planMessage.textContent).toContain( - '美术组:ready · Asset 生成首版美术素材(art-asset-plan) · 验收:角色、场景、UI 和动画需求已映射到画板或本地资产,且至少一张首版核心素材图已生成并登记', - ); - expect(planMessage.textContent).toContain( - '空档组:设计实现组、程序组、数值组、音乐组、运营组', - ); - expect(planMessage.textContent).toContain( - '边界:只整理下一轮分工;不读取任务文件;不启动 run;不修改项目', - ); - expect(planMessage.textContent).toContain( - '建议:/agent-resume 下一轮计划:美术组 / Asset 生成首版美术素材', - ); - const planMessageList = document.querySelector('.message-list'); - expect(planMessageList).not.toBeNull(); - const planDraftButtons = within(planMessageList as HTMLElement).getAllByRole( - 'button', - { name: '继续执行计划' }, - ); - fireEvent.click(planDraftButtons[planDraftButtons.length - 1]); - expect(await composerText()).toBe( - '/agent-resume 下一轮计划:美术组 / Asset 生成首版美术素材', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforePlan.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforePlan.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforePlan.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforePlan.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePlan.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePlan.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforePlan.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforePlan.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforePlan.controlAgentRun); - - const commandCountsBeforeGuide = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/guide'); - const guideMessages = await screen.findAllByText(/使用导引:/); - const guideMessage = messageBubble(guideMessages[guideMessages.length - 1]); - expect(guideMessage.textContent).toContain('项目:未命名游戏原型'); - expect(guideMessage.textContent).toContain('当前阶段:可导出'); - expect(guideMessage.textContent).toContain( - '现在先做:先运行自检并启动本地预览,通过后再导出试玩包。', - ); - expect(guideMessage.textContent).toContain( - '推荐命令:/run / /test-plan / /share', - ); - expect(guideMessage.textContent).toContain( - '边界:只给操作导引;不读取文件;不启动 run;不启动预览;不写项目', - ); - const guideMessageList = document.querySelector('.message-list'); - expect(guideMessageList).not.toBeNull(); - const guideDraftButtons = within( - guideMessageList as HTMLElement, - ).getAllByRole('button', { name: '执行导引建议' }); - fireEvent.click(guideDraftButtons[guideDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeGuide.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeGuide.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeGuide.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeGuide.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeGuide.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeGuide.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeGuide.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeGuide.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeGuide.controlAgentRun); - - const commandCountsBeforeProgress = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/progress'); - const progressMessages = await screen.findAllByText(/项目进度:/); - const progressMessage = messageBubble( - progressMessages[progressMessages.length - 1], - ); - expect(progressMessage.textContent).toContain('项目:未命名游戏原型'); - expect(progressMessage.textContent).toContain('当前阶段:已生成'); - expect(progressMessage.textContent).toMatch( - /任务完成度:0\/\d+ · 0% · ready 1 · 失败 0/, - ); - expect(progressMessage.textContent).toContain( - '最近 Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(progressMessage.textContent).toContain('预览:未启动'); - expect(progressMessage.textContent).toContain( - '素材:共 2 个 · 美术 1 · 音频 0', - ); - expect(progressMessage.textContent).toContain( - '交付:自检 已通过 · 试玩包 未导出', - ); - expect(progressMessage.textContent).toContain( - '边界:只整理项目进度;不读取文件;不启动 run;不启动预览;不导出试玩包;不写项目', - ); - expect(progressMessage.textContent).toContain('建议:/run'); - const progressMessageList = document.querySelector('.message-list'); - expect(progressMessageList).not.toBeNull(); - const progressDraftButtons = within( - progressMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动预览' }); - fireEvent.click(progressDraftButtons[progressDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeProgress.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeProgress.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeProgress.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeProgress.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeProgress.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeProgress.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeProgress.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeProgress.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeProgress.controlAgentRun); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts deleted file mode 100644 index 2467320d1..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts +++ /dev/null @@ -1,1807 +0,0 @@ -import { - composerText, - expect, - fireEvent, - screen, - submitChat, - within, -} from '../../harness'; -import type { PreviewShortcutInvoke } from './invoke-mock'; -import { messageBubble } from './message-bubble'; - -export async function assertPlaytestAndReleaseShortcutFlow( - invoke: PreviewShortcutInvoke, -) { - const runLocalCountBeforeNext = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/next'); - expect(await screen.findByText(/下一步建议:/)).not.toBeNull(); - expect(screen.getByText(/运行自检并启动本地预览:\/run/)).not.toBeNull(); - expect(screen.getByText(/查看创作目标:\/goal/)).not.toBeNull(); - expect(screen.getByText(/查看普通用户操作导引:\/guide/)).not.toBeNull(); - expect(screen.getByText(/查看项目进度:\/progress/)).not.toBeNull(); - expect(screen.getByText(/查看创作规格包:\/spec/)).not.toBeNull(); - expect(screen.getByText(/查看本轮 MVP 范围:\/mvp/)).not.toBeNull(); - expect(screen.getByText(/查看试玩定位与卖点:\/pitch/)).not.toBeNull(); - expect(screen.getByText(/准备 30 秒试玩讲解稿:\/demo/)).not.toBeNull(); - expect(screen.getByText(/查看玩法操作与规则:\/rules/)).not.toBeNull(); - expect(screen.getByText(/查看新手引导检查:\/tutorial/)).not.toBeNull(); - expect(screen.getByText(/查看移动试玩检查:\/mobile/)).not.toBeNull(); - expect(screen.getByText(/准备兼容性说明:\/compatibility/)).not.toBeNull(); - expect( - screen.getByText(/查看可读性与无障碍检查:\/accessibility/), - ).not.toBeNull(); - expect( - screen.getByText(/查看本地化与文案检查:\/localization/), - ).not.toBeNull(); - expect(screen.getByText(/查看性能与加载检查:\/performance/)).not.toBeNull(); - expect(screen.getByText(/查看试玩前打磨清单:\/polish/)).not.toBeNull(); - expect(screen.getByText(/查看数值与难度口径:\/balance/)).not.toBeNull(); - expect(screen.getByText(/查看 .* 个 ready 任务:\/tasks/)).not.toBeNull(); - expect(screen.getByText(/查看当前任务验收标准:\/criteria/)).not.toBeNull(); - expect(screen.getByText(/查看专业组进度:\/groups/)).not.toBeNull(); - expect(screen.getByText(/查看质量检查清单:\/qa/)).not.toBeNull(); - expect(screen.getByText(/查看最近生成变更:\/changes/)).not.toBeNull(); - expect(screen.getByText(/查看下一轮分工计划:\/plan/)).not.toBeNull(); - expect(screen.getByText(/查看下一轮小步清单:\/todo/)).not.toBeNull(); - expect(screen.getByText(/查看当前阻塞项:\/blockers/)).not.toBeNull(); - expect(screen.getByText(/查看试玩就绪度:\/ready/)).not.toBeNull(); - expect(screen.getByText(/查看验证证据台账:\/evidence/)).not.toBeNull(); - expect( - screen.getByText(/查看 Agent 智能服务状态:\/llm-routes/), - ).not.toBeNull(); - expect(screen.getByText(/查看任务依赖链:\/deps/)).not.toBeNull(); - expect(screen.getByText(/准备下一轮改版说明:\/revise/)).not.toBeNull(); - expect(screen.getByText(/查看隐私与导出边界:\/privacy/)).not.toBeNull(); - expect(screen.getByText(/查看首批试玩对象:\/audience/)).not.toBeNull(); - expect(screen.getByText(/准备试玩邀请文案:\/invite/)).not.toBeNull(); - expect(screen.getByText(/准备试玩问卷问题:\/survey/)).not.toBeNull(); - expect(screen.getByText(/准备封面与缩略图检查:\/cover/)).not.toBeNull(); - expect(screen.getByText(/准备宣传截图清单:\/screenshots/)).not.toBeNull(); - expect(screen.getByText(/准备试玩短视频脚本:\/trailer/)).not.toBeNull(); - expect(screen.getByText(/准备试玩常见问答:\/faq/)).not.toBeNull(); - expect(screen.getByText(/准备社区发布文案:\/post/)).not.toBeNull(); - expect(screen.getByText(/准备上架资料清单:\/store/)).not.toBeNull(); - expect(screen.getByText(/准备媒体资料包清单:\/media-kit/)).not.toBeNull(); - expect(screen.getByText(/准备试玩更新说明:\/release-notes/)).not.toBeNull(); - expect(screen.getByText(/准备已知问题清单:\/known-issues/)).not.toBeNull(); - expect(screen.getByText(/查看素材署名与来源:\/credits/)).not.toBeNull(); - expect(screen.getByText(/查看美术素材:\/art/)).not.toBeNull(); - expect(screen.getByText(/查看发布准备清单:\/publish/)).not.toBeNull(); - expect(screen.getByText(/准备作品页文案清单:\/listing/)).not.toBeNull(); - expect(screen.getByText(/查看试玩状态:\/playtest/)).not.toBeNull(); - expect(screen.getByText(/准备手动测试计划:\/test-plan/)).not.toBeNull(); - expect(screen.getByText(/准备试玩反馈:\/feedback/)).not.toBeNull(); - expect(screen.getByText(/准备复玩观察清单:\/retention/)).not.toBeNull(); - expect(screen.getByText(/准备试玩交付清单:\/share/)).not.toBeNull(); - expect(screen.getByText(/查看最近 run 预算:\/budget/)).not.toBeNull(); - expect(screen.getByText(/查看评审和返工焦点:\/review/)).not.toBeNull(); - expect(screen.getByText(/查看生成上下文来源:\/context/)).not.toBeNull(); - expect(screen.getByText(/查看项目活动时间线:\/timeline/)).not.toBeNull(); - expect( - screen.getByText(/列出内部真相源读取命令:\/internals/), - ).not.toBeNull(); - expect(screen.getByText(/列出 Agent 轮次产物:\/passes/)).not.toBeNull(); - expect( - screen.getByText(/列出 Agent 运行辅助文件:\/run-files/), - ).not.toBeNull(); - const nextMessageList = document.querySelector('.message-list'); - expect(nextMessageList).not.toBeNull(); - fireEvent.click( - within(nextMessageList as HTMLElement).getByRole('button', { - name: '运行自检并启动本地预览', - }), - ); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeNext); - - const manifestReadCountBeforePublish = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const fileReadCountBeforePublish = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const runLocalCountBeforePublish = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - const startPreviewCountBeforePublish = invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length; - await submitChat('/publish'); - expect(await screen.findByText(/发布准备:/)).not.toBeNull(); - expect( - screen.getByText(/原型:最近 run 已通过 run-main-shortcut-trace/), - ).not.toBeNull(); - expect(screen.getByText(/预览:未启动 · 建议 \/run/)).not.toBeNull(); - expect(screen.getByText(/音频:暂无 · 建议 \/audio/)).not.toBeNull(); - expect( - screen.getByText( - /包装:最近 Run 包含 exports\/README\.md · \/read exports\/README\.md/, - ), - ).not.toBeNull(); - const publishDraftButtons = screen.getAllByRole('button', { - name: '启动预览', - }); - fireEvent.click(publishDraftButtons[publishDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforePublish); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforePublish); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforePublish); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(startPreviewCountBeforePublish); - - const commandCountsBeforeListing = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/listing'); - const listingMessages = await screen.findAllByText(/作品页草稿:/); - const listingMessage = messageBubble( - listingMessages[listingMessages.length - 1], - ); - expect(listingMessage.textContent).toContain('标题:未命名游戏原型'); - expect(listingMessage.textContent).toContain( - '一句话卖点:做一个厨房弹幕游戏', - ); - expect(listingMessage.textContent).toContain( - 'publish-strategy:Director 整理运营定位 · 待处理', - ); - expect(listingMessage.textContent).toContain( - 'publish-package:Publish 整理发布包装 · 待处理', - ); - expect(listingMessage.textContent).toContain( - '封面素材:1 个视觉素材 · 可用 1 个画板 / 生成来源', - ); - expect(listingMessage.textContent).toContain( - '说明文案:exports/README.md · 已生成', - ); - expect(listingMessage.textContent).toContain( - '标签口径:玩法类型;视觉风格;难度 / 节奏;本地可玩', - ); - expect(listingMessage.textContent).toContain( - '边界:只整理作品页文案和封面需求;不上传云端;不发布作品', - ); - expect(listingMessage.textContent).toContain('建议:/read exports/README.md'); - const listingMessageList = document.querySelector('.message-list'); - expect(listingMessageList).not.toBeNull(); - const listingDraftButtons = within( - listingMessageList as HTMLElement, - ).getAllByRole('button', { name: '读发布说明' }); - fireEvent.click(listingDraftButtons[listingDraftButtons.length - 1]); - expect(await composerText()).toBe('/read exports/README.md'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeListing.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeListing.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeListing.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeListing.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeListing.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeListing.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeListing.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeListing.exportList); - - const fileReadCountBeforePlaytest = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const manifestReadCountBeforePlaytest = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const runLocalCountBeforePlaytest = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - const startPreviewCountBeforePlaytest = invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length; - const openPreviewCountBeforePlaytest = invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length; - await submitChat('/playtest'); - const playtestMessages = await screen.findAllByText(/试玩状态:/); - const playtestMessage = messageBubble( - playtestMessages[playtestMessages.length - 1], - ); - expect(playtestMessage.textContent).toContain( - '原型:最近 run 已通过 run-main-shortcut-trace', - ); - expect(playtestMessage.textContent).toContain('预览:未启动 · 建议 /run'); - expect(playtestMessage.textContent).toContain( - 'Playtest 任务:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(playtestMessage.textContent).toContain( - '试玩日志:/read .agent/logs/preview.log', - ); - const playtestDraftButtons = screen.getAllByRole('button', { - name: '启动试玩', - }); - fireEvent.click(playtestDraftButtons[playtestDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforePlaytest); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforePlaytest); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforePlaytest); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(startPreviewCountBeforePlaytest); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(openPreviewCountBeforePlaytest); - - const commandCountsBeforeTestPlan = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - }; - await submitChat('/test-plan'); - const testPlanMessages = await screen.findAllByText(/手动测试计划:/); - const testPlanMessage = messageBubble( - testPlanMessages[testPlanMessages.length - 1], - ); - expect(testPlanMessage.textContent).toContain('项目:未命名游戏原型'); - expect(testPlanMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(testPlanMessage.textContent).toContain( - '当前证据:最近 run 已通过 run-main-shortcut-trace;预览 未启动;入口 未见 trace 产物', - ); - expect(testPlanMessage.textContent).toContain( - '启动预览:/run 后确认首屏不空白', - ); - expect(testPlanMessage.textContent).toContain( - '30 秒理解:目标、操作、得分/失败和重开可见', - ); - expect(testPlanMessage.textContent).toContain( - '输入验证:键盘/点击/触屏至少一种可完成核心动作', - ); - expect(testPlanMessage.textContent).toContain('结局验证:胜利或失败后可重开'); - expect(testPlanMessage.textContent).toContain( - '回归检查:/mobile;/accessibility;/performance;/audio', - ); - expect(testPlanMessage.textContent).toContain( - 'preview-readiness:程序组 / Preview 执行静态自检 · 待处理', - ); - expect(testPlanMessage.textContent).toContain( - 'preview-playtest:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(testPlanMessage.textContent).toContain('记录反馈:/feedback'); - expect(testPlanMessage.textContent).toContain('建议:/run'); - const testPlanMessageList = document.querySelector('.message-list'); - expect(testPlanMessageList).not.toBeNull(); - const testPlanDraftButtons = within( - testPlanMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(testPlanDraftButtons[testPlanDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeTestPlan.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeTestPlan.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeTestPlan.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeTestPlan.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTestPlan.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTestPlan.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeTestPlan.exportPackage); - - const commandCountsBeforeAudience = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/audience'); - const audienceMessages = await screen.findAllByText(/首批试玩对象:/); - const audienceMessage = messageBubble( - audienceMessages[audienceMessages.length - 1], - ); - expect(audienceMessage.textContent).toContain('项目:未命名游戏原型'); - expect(audienceMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(audienceMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(audienceMessage.textContent).toContain( - '先测人群:创作者自测 1 轮;熟悉目标的同事 1-2 人;完全没看过项目的人 3-5 人;至少 1 位移动/触屏用户', - ); - expect(audienceMessage.textContent).toContain( - '第一批测试者:3-5 人;每人 5-10 分钟;先看能否独立理解', - ); - expect(audienceMessage.textContent).toContain( - '观察重点:30 秒能否理解目标;输入是否顺;胜负/重开是否明确;难度是否过早劝退;视觉/音效是否干扰', - ); - expect(audienceMessage.textContent).toContain( - 'preview-readiness:程序组 / Preview 执行静态自检 · 待处理', - ); - expect(audienceMessage.textContent).toContain( - 'preview-playtest:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(audienceMessage.textContent).toContain( - '暂不面向:公开发布、付费用户、大规模投放、儿童/无障碍等强承诺场景', - ); - expect(audienceMessage.textContent).toContain( - '参考:/playtest;/test-plan;/feedback;/share', - ); - expect(audienceMessage.textContent).toContain( - '边界:只整理首批试玩对象;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(audienceMessage.textContent).toContain('建议:/run'); - const audienceMessageList = document.querySelector('.message-list'); - expect(audienceMessageList).not.toBeNull(); - const audienceDraftButtons = within( - audienceMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(audienceDraftButtons[audienceDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeAudience.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeAudience.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeAudience.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeAudience.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeAudience.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeAudience.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeAudience.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeAudience.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeAudience.controlAgentRun); - - const commandCountsBeforeInvite = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/invite'); - const inviteMessages = await screen.findAllByText(/试玩邀请:/); - const inviteMessage = messageBubble( - inviteMessages[inviteMessages.length - 1], - ); - expect(inviteMessage.textContent).toContain('项目:未命名游戏原型'); - expect(inviteMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(inviteMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(inviteMessage.textContent).toContain( - '邀请对象:先发 3-5 人;优先熟人/同事/没看过项目的人;暂不公开发布或大规模投放', - ); - expect(inviteMessage.textContent).toContain('5-10 分钟试玩'); - expect(inviteMessage.textContent).toContain('30 秒内能否理解目标'); - expect(inviteMessage.textContent).toContain('操作是否顺'); - expect(inviteMessage.textContent).toContain('胜负和重开是否清楚'); - expect(inviteMessage.textContent).toContain( - '发送前:先 /run 启动本地预览,再把本地试玩方式发给测试者', - ); - expect(inviteMessage.textContent).toContain( - '参考:/audience;/test-plan;/feedback;/share', - ); - expect(inviteMessage.textContent).toContain( - '边界:只准备邀请文案;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(inviteMessage.textContent).toContain('建议:/run'); - const inviteMessageList = document.querySelector('.message-list'); - expect(inviteMessageList).not.toBeNull(); - const inviteDraftButtons = within( - inviteMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(inviteDraftButtons[inviteDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeInvite.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeInvite.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeInvite.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeInvite.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeInvite.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeInvite.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeInvite.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeInvite.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeInvite.controlAgentRun); - - const commandCountsBeforeBugReport = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeBugReport.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeBugReport.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeBugReport.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeBugReport.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeBugReport.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeBugReport.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeBugReport.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeBugReport.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeBugReport.controlAgentRun); - - const commandCountsBeforeSurvey = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/survey'); - const surveyMessages = await screen.findAllByText(/试玩问卷:/); - const surveyMessage = messageBubble( - surveyMessages[surveyMessages.length - 1], - ); - expect(surveyMessage.textContent).toContain('项目:未命名游戏原型'); - expect(surveyMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(surveyMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(surveyMessage.textContent).toContain( - '使用场景:发给首批 3-5 位测试者;每人 5-10 分钟;先自由玩一局再回答', - ); - expect(surveyMessage.textContent).toContain( - '问题清单:1. 30 秒内你觉得目标是什么?2. 第一次操作哪里最卡?3. 胜负/重开是否清楚?4. 难度/节奏感觉如何?5. 最想保留和最想改的各一项?', - ); - expect(surveyMessage.textContent).toContain( - '记录格式:每题 1-5 分 + 一句话;补充设备、输入方式、是否愿意再玩一局', - ); - expect(surveyMessage.textContent).toContain( - '追踪方式:客户端问题由自动诊断提示;整体反馈走 /feedback;下一轮改动走 /revise', - ); - expect(surveyMessage.textContent).toContain( - '参考:/invite;/audience;/feedback', - ); - expect(surveyMessage.textContent).toContain( - '边界:只准备试玩问卷;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(surveyMessage.textContent).toContain('建议:/run'); - const surveyMessageList = document.querySelector('.message-list'); - expect(surveyMessageList).not.toBeNull(); - const surveyDraftButtons = within( - surveyMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(surveyDraftButtons[surveyDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeSurvey.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeSurvey.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeSurvey.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeSurvey.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeSurvey.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeSurvey.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeSurvey.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeSurvey.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeSurvey.controlAgentRun); - - const commandCountsBeforeCover = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/cover'); - const coverMessages = await screen.findAllByText(/封面与缩略图:/); - const coverMessage = messageBubble(coverMessages[coverMessages.length - 1]); - expect(coverMessage.textContent).toContain('项目:未命名游戏原型'); - expect(coverMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(coverMessage.textContent).toContain( - '可用素材:视觉素材 1 个;画板/生成候选 1 个;总资产 2 个', - ); - expect(coverMessage.textContent).toContain( - '封面候选:assets/canvas-sync/tiles.png · image/png · 画板', - ); - expect(coverMessage.textContent).toContain( - '用途尺寸:作品页封面 16:9;社区缩略图 1:1;移动首屏 9:16', - ); - expect(coverMessage.textContent).toContain( - '选择口径:优先展示核心玩法状态;避免内部路径、调试面板、密钥配置或纯空场景', - ); - expect(coverMessage.textContent).toContain( - '补齐路径:有可试玩时先 /screenshots;缺美术时 /art;作品页文案走 /listing', - ); - expect(coverMessage.textContent).toContain( - '参考:/screenshots;/listing;/media-kit;/credits', - ); - expect(coverMessage.textContent).toContain( - '边界:只准备封面与缩略图检查;不截屏;不裁剪;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(coverMessage.textContent).toContain('建议:/run'); - const coverMessageList = document.querySelector('.message-list'); - expect(coverMessageList).not.toBeNull(); - const coverDraftButtons = within( - coverMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(coverDraftButtons[coverDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeCover.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeCover.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeCover.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeCover.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeCover.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeCover.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeCover.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeCover.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeCover.controlAgentRun); - - const commandCountsBeforeScreenshots = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/screenshots'); - const screenshotMessages = await screen.findAllByText(/宣传截图:/); - const screenshotMessage = messageBubble( - screenshotMessages[screenshotMessages.length - 1], - ); - expect(screenshotMessage.textContent).toContain('项目:未命名游戏原型'); - expect(screenshotMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(screenshotMessage.textContent).toContain( - '可用素材:视觉素材 1 个;总资产 2 个', - ); - expect(screenshotMessage.textContent).toContain( - '截图目标:封面一张;核心操作一张;胜负/重开一张;移动或窄屏一张;异常/空状态不作为首批宣传图', - ); - expect(screenshotMessage.textContent).toContain( - '拍摄顺序:先确认 /run 可试玩;进入第一局 10-30 秒;截核心交互;再截结算或失败反馈', - ); - expect(screenshotMessage.textContent).toContain( - '命名建议:exports/screenshots/cover.png;gameplay.png;result.png;mobile.png', - ); - expect(screenshotMessage.textContent).toContain( - '文案搭配:每张图只配一句卖点;作品页标题和标签继续走 /listing', - ); - expect(screenshotMessage.textContent).toContain( - '参考:/listing;/publish;/share;/credits', - ); - expect(screenshotMessage.textContent).toContain( - '边界:只准备截图清单;不截屏;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(screenshotMessage.textContent).toContain('建议:/run'); - const screenshotMessageList = document.querySelector('.message-list'); - expect(screenshotMessageList).not.toBeNull(); - const screenshotDraftButtons = within( - screenshotMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(screenshotDraftButtons[screenshotDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeScreenshots.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeScreenshots.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeScreenshots.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeScreenshots.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeScreenshots.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeScreenshots.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeScreenshots.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeScreenshots.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeScreenshots.controlAgentRun); - - const commandCountsBeforeTrailer = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/trailer'); - const trailerMessages = await screen.findAllByText(/试玩短视频:/); - const trailerMessage = messageBubble( - trailerMessages[trailerMessages.length - 1], - ); - expect(trailerMessage.textContent).toContain('项目:未命名游戏原型'); - expect(trailerMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(trailerMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(trailerMessage.textContent).toContain( - '可用素材:视觉素材 1 个;音频素材 0 个;总资产 2 个', - ); - expect(trailerMessage.textContent).toContain( - '15 秒结构:0-3 秒首屏目标;3-8 秒核心操作;8-12 秒胜负 / 重开;12-15 秒结尾 CTA', - ); - expect(trailerMessage.textContent).toContain( - '镜头清单:标题 / 目标提示;玩家第一次操作;得分或失败反馈;重开按钮;结尾试玩邀请', - ); - expect(trailerMessage.textContent).toContain( - '口播节奏:一句玩法目标;一句操作说明;一句邀请试玩和反馈', - ); - expect(trailerMessage.textContent).toContain( - '录制提示:先确认 /run 可试玩;横屏或竖屏只选一种;不露内部路径、调试面板或密钥配置', - ); - expect(trailerMessage.textContent).toContain( - '参考:/screenshots;/listing;/share;/publish', - ); - expect(trailerMessage.textContent).toContain( - '边界:只准备试玩短视频脚本;不录屏;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(trailerMessage.textContent).toContain('建议:/run'); - const trailerMessageList = document.querySelector('.message-list'); - expect(trailerMessageList).not.toBeNull(); - const trailerDraftButtons = within( - trailerMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(trailerDraftButtons[trailerDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeTrailer.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeTrailer.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeTrailer.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeTrailer.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTrailer.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTrailer.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeTrailer.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeTrailer.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeTrailer.controlAgentRun); - - const commandCountsBeforeFaq = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/faq'); - const faqMessages = await screen.findAllByText(/试玩 FAQ:/); - const faqMessage = messageBubble(faqMessages[faqMessages.length - 1]); - expect(faqMessage.textContent).toContain('项目:未命名游戏原型'); - expect(faqMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(faqMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(faqMessage.textContent).toContain( - '问答清单:1. 这是什么?2. 怎么开始和重开?3. 需要反馈什么?4. 打不开或卡住怎么办?5. 能不能转发或公开?', - ); - expect(faqMessage.textContent).toContain( - '回答口径:早期本地 Web 原型;5-10 分钟试玩;重点反馈目标理解、操作手感、难度、bug 和还想不想再玩', - ); - expect(faqMessage.textContent).toContain( - '测试者提醒:先自由玩一局;不要评价完成度;问卷走 /survey;客户端问题由自动诊断提示', - ); - expect(faqMessage.textContent).toContain( - '交付搭配:/invite;/share;/screenshots;/trailer', - ); - expect(faqMessage.textContent).toContain( - '边界:只准备试玩常见问答;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(faqMessage.textContent).toContain('建议:/run'); - const faqMessageList = document.querySelector('.message-list'); - expect(faqMessageList).not.toBeNull(); - const faqDraftButtons = within(faqMessageList as HTMLElement).getAllByRole( - 'button', - { name: '启动试玩' }, - ); - fireEvent.click(faqDraftButtons[faqDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeFaq.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeFaq.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeFaq.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeFaq.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeFaq.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeFaq.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeFaq.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeFaq.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeFaq.controlAgentRun); - - const commandCountsBeforePost = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/post'); - const postMessages = await screen.findAllByText(/社区发布文案:/); - const postMessage = messageBubble(postMessages[postMessages.length - 1]); - expect(postMessage.textContent).toContain('项目:未命名游戏原型'); - expect(postMessage.textContent).toContain('一句话:做一个厨房弹幕游戏'); - expect(postMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(postMessage.textContent).toContain( - '素材准备:视觉素材 1 个;配图走 /screenshots;短视频走 /trailer', - ); - expect(postMessage.textContent).toContain( - '短文案:我做了一个早期 Web 小游戏原型《未命名游戏原型》,核心目标是做一个厨房弹幕游戏。想找 3-5 位朋友试玩 5 分钟,重点看能不能理解目标、操作顺不顺、还想不想再来一局。', - ); - expect(postMessage.textContent).toContain( - '长文案结构:一句玩法目标;一张截图或短视频;试玩方式;希望收到的三类反馈;已知限制', - ); - expect(postMessage.textContent).toContain( - '标签建议:#Web小游戏 #原型试玩 #AI游戏创作 #本地试玩', - ); - expect(postMessage.textContent).toContain( - 'CTA:愿意试玩请回复;遇到问题按 /faq 的口径反馈,客户端问题会自动提示', - ); - expect(postMessage.textContent).toContain( - '参考:/faq;/screenshots;/trailer;/store;/share', - ); - expect(postMessage.textContent).toContain( - '边界:只准备社区发布文案;不上传云端;不发布作品;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(postMessage.textContent).toContain('建议:/run'); - const postMessageList = document.querySelector('.message-list'); - expect(postMessageList).not.toBeNull(); - const postDraftButtons = within(postMessageList as HTMLElement).getAllByRole( - 'button', - { name: '启动试玩' }, - ); - fireEvent.click(postDraftButtons[postDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforePost.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforePost.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforePost.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforePost.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePost.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePost.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforePost.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforePost.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforePost.controlAgentRun); - - const commandCountsBeforeStore = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/store'); - const storeMessages = await screen.findAllByText(/上架资料:/); - const storeMessage = messageBubble(storeMessages[storeMessages.length - 1]); - expect(storeMessage.textContent).toContain('项目:未命名游戏原型'); - expect(storeMessage.textContent).toContain('一句话:做一个厨房弹幕游戏'); - expect(storeMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(storeMessage.textContent).toContain( - '资产概况:视觉 1 个;音频 0 个;总资产 2 个', - ); - expect(storeMessage.textContent).toContain( - '必备资料:作品页文案 /listing;宣传截图 /screenshots;素材署名 /credits;隐私边界 /privacy;试玩包 /export', - ); - expect(storeMessage.textContent).toContain( - '发布说明:exports/README.md · 已生成', - ); - expect(storeMessage.textContent).toContain( - '首发范围:本地 Web 原型;小规模试玩;免费体验;不承诺账号、云存档、排行榜或付费', - ); - expect(storeMessage.textContent).toContain( - '上架前检查:30 秒玩法可懂;首屏不空白;重开清楚;截图不含内部路径;素材来源可说明', - ); - expect(storeMessage.textContent).toContain( - '参考:/publish;/listing;/screenshots;/credits;/privacy;/share', - ); - expect(storeMessage.textContent).toContain( - '边界:只准备上架资料清单;不上传云端;不发布作品;不读取文件;不启动或打开预览;不导出试玩包;不写项目', - ); - expect(storeMessage.textContent).toContain('建议:/run'); - const storeMessageList = document.querySelector('.message-list'); - expect(storeMessageList).not.toBeNull(); - const storeDraftButtons = within( - storeMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(storeDraftButtons[storeDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeStore.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeStore.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeStore.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeStore.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeStore.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeStore.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeStore.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeStore.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeStore.controlAgentRun); - - const commandCountsBeforeMediaKit = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/media-kit'); - const mediaKitMessages = await screen.findAllByText(/媒体资料包:/); - const mediaKitMessage = messageBubble( - mediaKitMessages[mediaKitMessages.length - 1], - ); - expect(mediaKitMessage.textContent).toContain('项目:未命名游戏原型'); - expect(mediaKitMessage.textContent).toContain('一句话:做一个厨房弹幕游戏'); - expect(mediaKitMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(mediaKitMessage.textContent).toContain( - '素材概况:视觉素材 1 个;音频素材 0 个;总资产 2 个;发布说明 exports/README.md · 已生成', - ); - expect(mediaKitMessage.textContent).toContain( - '资料清单:作品页 /listing;宣传截图 /screenshots;短视频 /trailer;FAQ /faq;社区文案 /post;上架清单 /store', - ); - expect(mediaKitMessage.textContent).toContain( - '缺口优先级:先跑 /run 确认可试玩;再补 /screenshots 和 /trailer;最后整理 /post 与 /store', - ); - expect(mediaKitMessage.textContent).toContain( - '打包顺序:1. 确认首屏和核心玩法;2. 准备截图 / 视频 / FAQ;3. 汇总署名、隐私和发布说明', - ); - expect(mediaKitMessage.textContent).toContain( - '参考:/screenshots;/trailer;/listing;/faq;/post;/store;/share', - ); - expect(mediaKitMessage.textContent).toContain( - '边界:只准备媒体资料包清单;不截屏;不录屏;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(mediaKitMessage.textContent).toContain('建议:/run'); - const mediaKitMessageList = document.querySelector('.message-list'); - expect(mediaKitMessageList).not.toBeNull(); - const mediaKitDraftButtons = within( - mediaKitMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(mediaKitDraftButtons[mediaKitDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeMediaKit.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeMediaKit.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeMediaKit.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeMediaKit.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeMediaKit.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeMediaKit.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeMediaKit.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeMediaKit.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeMediaKit.controlAgentRun); - - const commandCountsBeforeReleaseNotes = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/release-notes'); - const releaseNotesMessages = await screen.findAllByText(/试玩更新说明:/); - const releaseNotesMessage = messageBubble( - releaseNotesMessages[releaseNotesMessages.length - 1], - ); - expect(releaseNotesMessage.textContent).toContain('项目:未命名游戏原型'); - expect(releaseNotesMessage.textContent).toContain( - '一句话:做一个厨房弹幕游戏', - ); - expect(releaseNotesMessage.textContent).toContain( - '当前版本:本地 Web 原型 · 小范围试玩 · 预览 未启动', - ); - expect(releaseNotesMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(releaseNotesMessage.textContent).toContain( - '本轮变化:可试玩版本已通过 Evaluator', - ); - expect(releaseNotesMessage.textContent).toContain( - '主要产物:exports/README.md;.agent/passes/pass-1/agenda.md', - ); - expect(releaseNotesMessage.textContent).toContain( - '素材变化:视觉素材 1 个;音频素材 0 个', - ); - expect(releaseNotesMessage.textContent).toContain( - '玩家可见说明:玩法目标;操作方式;胜负 / 重开反馈;当前已知限制', - ); - expect(releaseNotesMessage.textContent).toContain( - '已知限制:本地原型;不承诺账号、云存档、排行榜、付费或长期兼容', - ); - expect(releaseNotesMessage.textContent).toContain( - '搭配:/changes;/media-kit;/post;/store;/share', - ); - expect(releaseNotesMessage.textContent).toContain( - '边界:只准备试玩更新说明;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(releaseNotesMessage.textContent).toContain('建议:/run'); - const releaseNotesMessageList = document.querySelector('.message-list'); - expect(releaseNotesMessageList).not.toBeNull(); - const releaseNotesDraftButtons = within( - releaseNotesMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click( - releaseNotesDraftButtons[releaseNotesDraftButtons.length - 1], - ); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeReleaseNotes.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeReleaseNotes.controlAgentRun); - - const commandCountsBeforeKnownIssues = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/known-issues'); - const knownIssueMessages = await screen.findAllByText(/已知问题清单:/); - const knownIssueMessage = messageBubble( - knownIssueMessages[knownIssueMessages.length - 1], - ); - expect(knownIssueMessage.textContent).toContain('项目:未命名游戏原型'); - expect(knownIssueMessage.textContent).toContain( - '当前状态:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed;预览 未启动', - ); - expect(knownIssueMessage.textContent).toContain( - '已知问题:暂无明确失败任务;仍按早期原型标注限制', - ); - expect(knownIssueMessage.textContent).toContain( - '试玩限制:本地 Web 原型;小范围 5-10 分钟试玩;不承诺账号、云存档、排行榜、付费或长期兼容', - ); - expect(knownIssueMessage.textContent).toContain( - '反馈入口:客户端问题由自动诊断提示;整体体验走 /feedback;版本变化走 /release-notes', - ); - expect(knownIssueMessage.textContent).toContain( - '发送前检查:可试玩状态先 /run;交付口径看 /share;对外资料看 /media-kit', - ); - expect(knownIssueMessage.textContent).toContain( - '边界:只准备已知问题清单;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(knownIssueMessage.textContent).toContain('建议:/run'); - const knownIssueMessageList = document.querySelector('.message-list'); - expect(knownIssueMessageList).not.toBeNull(); - const knownIssueDraftButtons = within( - knownIssueMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(knownIssueDraftButtons[knownIssueDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeKnownIssues.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeKnownIssues.controlAgentRun); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts deleted file mode 100644 index 82cadb2be..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts +++ /dev/null @@ -1,1557 +0,0 @@ -import { - composerText, - expect, - fireEvent, - screen, - submitChat, - within, -} from '../../harness'; -import type { PreviewShortcutInvoke } from './invoke-mock'; -import { messageBubble } from './message-bubble'; - -export async function assertProjectAndDesignShortcutFlow( - invoke: PreviewShortcutInvoke, -) { - await screen.findByText('已打开:authorized-game'); - expect(screen.getByText('未命名游戏原型')).not.toBeNull(); - expect(screen.queryByText('/tmp/authorized-game')).toBeNull(); - expect(screen.getByText('preview: 未启动')).not.toBeNull(); - const projectSummary = screen.getByLabelText('项目摘要'); - expect(projectSummary.textContent).toContain('任务:已完成 0/'); - expect(projectSummary.textContent).toContain('ready'); - expect(projectSummary.textContent).toContain('资产:2 个 · 上传 1 / 画板 1'); - expect(projectSummary.textContent).toContain( - '最近命令:game.static_smoke 完成', - ); - expect(invoke).not.toHaveBeenCalledWith( - 'get_local_game_manifest', - expect.anything(), - ); - expect(screen.queryByLabelText('开发环境')).toBeNull(); - expect(screen.queryByText('编排 Trace')).toBeNull(); - expect(screen.queryByText(/LLM:已配置 · art-model/)).toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: 'LLM状态' })); - expect(await screen.findByText(/官方智能服务:已连接/)).not.toBeNull(); - const agentStatusPane = screen.getByLabelText('Agent 状态'); - expect(within(agentStatusPane).getAllByText(/智能服务:已连接/)).toHaveLength( - 2, - ); - expect(screen.queryByText(/router\.genarrative\.world/)).toBeNull(); - expect(screen.queryByText(/gpt-5\.6-sol/)).toBeNull(); - expect(screen.queryByText(/Codex App Server/)).toBeNull(); - expect(screen.queryByText(/planner-secret/)).toBeNull(); - expect(screen.queryByText(/art-secret/)).toBeNull(); - - await submitChat('/agents'); - await screen.findByText(/Agent 状态:/); - expect( - /Agent 状态:[\s\S]*美术组 \/ Asset · 生成首版美术素材 · 待处理[\s\S]*智能服务:已连接/.test( - screen.getByLabelText('聊天').textContent ?? '', - ), - ).toBe(true); - await submitChat('/agent-conversations'); - const agentConversationMessage = - await screen.findByText('Agent 对话读取命令:'); - const agentConversationBubble = messageBubble(agentConversationMessage); - expect(agentConversationBubble.textContent).toContain( - '美术组 / Asset · 生成首版美术素材:/read .agent/conversations/agents/art-asset-plan.jsonl', - ); - fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向对话' })); - expect(await composerText()).toBe( - '/read .agent/conversations/agents/design-director.jsonl', - ); - expect(invoke).not.toHaveBeenCalledWith( - 'read_local_project_file', - expect.objectContaining({ - relativePath: '.agent/conversations/agents/design-director.jsonl', - }), - ); - await submitChat('/agent-memories'); - const agentMemoryMessage = - await screen.findByText('Agent 私有记忆读取命令:'); - const agentMemoryBubble = messageBubble(agentMemoryMessage); - expect(agentMemoryBubble.textContent).toContain( - '美术组 / Asset · 生成首版美术素材:/read memory/agents/art/asset.md', - ); - fireEvent.click(screen.getByRole('button', { name: '读取拆解创作方向记忆' })); - expect(await composerText()).toBe('/read memory/agents/design/director.md'); - expect(invoke).not.toHaveBeenCalledWith( - 'read_local_project_file', - expect.objectContaining({ - relativePath: 'memory/agents/design/director.md', - }), - ); - - fireEvent.click( - within(agentStatusPane).getByRole('button', { - name: /生成首版美术素材/, - }), - ); - const agentDialog = await screen.findByRole('dialog', { - name: 'Agent 对话', - }); - expect( - within(agentDialog).getByText( - /官方智能服务:已连接,流式 开启,联网检索 开启,账号状态 已就绪/, - ), - ).not.toBeNull(); - fireEvent.click(within(agentDialog).getByRole('button', { name: '关闭' })); - - fireEvent.click(screen.getByRole('button', { name: '能力' })); - expect(await screen.findByText(/Native Runtime 能力/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '项目状态' })); - expect(await screen.findByText(/项目:未命名游戏原型/)).not.toBeNull(); - expect(screen.getByText(/目录:\/tmp\/authorized-game/)).not.toBeNull(); - - const manifestReadCountBeforeBrief = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - await submitChat('/brief'); - expect(await screen.findByText(/项目简报:/)).not.toBeNull(); - expect(screen.getByText(/任务:完成 0\/16 · ready 3/)).not.toBeNull(); - expect(screen.getByText(/最近 Run:run-main-shortcut-trace/)).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '查看下一步' })); - expect(await composerText()).toBe('/next'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeBrief); - - const manifestReadCountBeforeGoal = invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length; - const fileReadCountBeforeGoal = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - const runLocalCountBeforeGoal = invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length; - await submitChat('/goal'); - expect(await screen.findByText(/创作目标:/)).not.toBeNull(); - const goalMessages = screen.getAllByText(/创作目标:/); - const goalMessage = messageBubble(goalMessages[goalMessages.length - 1]); - expect(goalMessage.textContent).toContain('Manifest:做一个厨房弹幕游戏'); - expect(goalMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(goalMessage.textContent).toContain('Run 目标:做一个厨房弹幕游戏'); - expect(goalMessage.textContent).toContain('任务图目标:做一个厨房弹幕游戏'); - expect(goalMessage.textContent).toContain('上下文:/context'); - expect(goalMessage.textContent).toContain('建议:/agent-resume 细化目标:'); - fireEvent.click(screen.getByRole('button', { name: '补充目标' })); - expect(await composerText()).toBe('/agent-resume 细化目标:'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(manifestReadCountBeforeGoal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforeGoal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(runLocalCountBeforeGoal); - - const commandCountsBeforeSpec = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/spec'); - const specMessages = await screen.findAllByText(/创作规格包:/); - const specMessage = messageBubble(specMessages[specMessages.length - 1]); - expect(specMessage.textContent).toContain('项目:未命名游戏原型'); - expect(specMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(specMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(specMessage.textContent).toContain( - 'Planner 规格:.agent/spec.md · 已出现在最近 run', - ); - expect(specMessage.textContent).toContain( - '玩法设计:game/game_design.md · 任务声明', - ); - expect(specMessage.textContent).toContain( - '发布说明:exports/README.md · 已出现在最近 run', - ); - expect(specMessage.textContent).toContain( - '边界:只整理规格产物状态;不读取规格文件;不启动预览;不写项目', - ); - expect(specMessage.textContent).toContain('建议:/read .agent/spec.md'); - const specMessageList = document.querySelector('.message-list'); - expect(specMessageList).not.toBeNull(); - const specDraftButtons = within(specMessageList as HTMLElement).getAllByRole( - 'button', - { name: '读取规格' }, - ); - fireEvent.click(specDraftButtons[specDraftButtons.length - 1]); - expect(await composerText()).toBe('/read .agent/spec.md'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeSpec.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeSpec.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeSpec.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeSpec.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeSpec.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeSpec.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeSpec.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeSpec.exportList); - - const commandCountsBeforeMvp = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/mvp'); - expect(await screen.findByText(/MVP 范围:/)).not.toBeNull(); - const mvpMessages = screen.getAllByText(/MVP 范围:/); - const mvpMessage = messageBubble(mvpMessages[mvpMessages.length - 1]); - expect(mvpMessage.textContent).toContain('项目:未命名游戏原型'); - expect(mvpMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(mvpMessage.textContent).toContain( - 'MVP 内:可运行 Web 原型;基础输入 / 胜负 / 重开;本地预览;本地试玩包', - ); - expect(mvpMessage.textContent).toContain( - '当前状态:最近 run run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(mvpMessage.textContent).toContain( - '任务:完成 0/16 · ready 3 · 失败 0', - ); - expect(mvpMessage.textContent).toContain('预览:未启动'); - expect(mvpMessage.textContent).toContain('资产:2 个'); - expect(mvpMessage.textContent).toContain('试玩包:待导出'); - expect(mvpMessage.textContent).toContain( - '先不做:云同步;Unity/Godot;插件市场;任意 shell;深度资产精修', - ); - expect(mvpMessage.textContent).toContain('建议:/run'); - const mvpMessageList = document.querySelector('.message-list'); - expect(mvpMessageList).not.toBeNull(); - const mvpDraftButtons = within(mvpMessageList as HTMLElement).getAllByRole( - 'button', - { name: '启动预览' }, - ); - fireEvent.click(mvpDraftButtons[mvpDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeMvp.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeMvp.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeMvp.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeMvp.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeMvp.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeMvp.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeMvp.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeMvp.exportList); - - const commandCountsBeforePitch = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/pitch'); - const pitchMessages = await screen.findAllByText(/试玩定位:/); - const pitchMessage = messageBubble(pitchMessages[pitchMessages.length - 1]); - expect(pitchMessage.textContent).toContain('项目:未命名游戏原型'); - expect(pitchMessage.textContent).toContain('一句话:做一个厨房弹幕游戏'); - expect(pitchMessage.textContent).toContain( - '核心乐趣:快速验证目标、操作反馈、胜负结果和重开节奏', - ); - expect(pitchMessage.textContent).toContain( - '当前可演示:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(pitchMessage.textContent).toContain( - '讲给测试者:先说明目标,再说明操作,然后看 30 秒内是否能理解胜负和重开', - ); - expect(pitchMessage.textContent).toContain( - '不承诺:云发布;深度美术精修;账号体系;排行榜;长期运营包装', - ); - expect(pitchMessage.textContent).toContain( - '参考:/mvp;/rules;/playtest;/listing', - ); - expect(pitchMessage.textContent).toContain('建议:/run'); - const pitchMessageList = document.querySelector('.message-list'); - expect(pitchMessageList).not.toBeNull(); - const pitchDraftButtons = within( - pitchMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动预览' }); - fireEvent.click(pitchDraftButtons[pitchDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforePitch.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforePitch.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforePitch.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforePitch.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePitch.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePitch.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforePitch.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforePitch.exportList); - - const commandCountsBeforeDemo = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/demo'); - const demoMessages = await screen.findAllByText(/试玩讲解稿:/); - const demoMessage = messageBubble(demoMessages[demoMessages.length - 1]); - expect(demoMessage.textContent).toContain('项目:未命名游戏原型'); - expect(demoMessage.textContent).toContain( - '30 秒开场:这是《未命名游戏原型》,目标是做一个厨房弹幕游戏', - ); - expect(demoMessage.textContent).toContain( - '讲解顺序:目标 -> 操作 -> 反馈 -> 胜负 -> 重开', - ); - expect(demoMessage.textContent).toContain( - '口播稿:先看目标提示,尝试移动/点击完成核心动作;看到得分、受击或状态反馈后,继续到胜利或失败;结束后确认能否一键重开', - ); - expect(demoMessage.textContent).toContain( - '当前演示状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(demoMessage.textContent).toContain('最近试玩证据:暂无'); - expect(demoMessage.textContent).toContain( - '收反馈:操作是否明白;节奏是否太快;胜负是否清楚;视觉 / 音效是否帮助理解', - ); - expect(demoMessage.textContent).toContain( - '边界:只准备试玩讲解;不启动预览;不导出试玩包;不发布作品', - ); - expect(demoMessage.textContent).toContain( - '参考:/rules;/test-plan;/feedback;/share', - ); - expect(demoMessage.textContent).toContain('建议:/run'); - const demoMessageList = document.querySelector('.message-list'); - expect(demoMessageList).not.toBeNull(); - const demoDraftButtons = within(demoMessageList as HTMLElement).getAllByRole( - 'button', - { name: '启动预览' }, - ); - fireEvent.click(demoDraftButtons[demoDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeDemo.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeDemo.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeDemo.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeDemo.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeDemo.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeDemo.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeDemo.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeDemo.exportList); - - const commandCountsBeforeControls = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - }; - await submitChat('/rules'); - expect(await screen.findByText(/玩法操作:/)).not.toBeNull(); - const controlMessages = screen.getAllByText(/玩法操作:/); - const controlMessage = messageBubble( - controlMessages[controlMessages.length - 1], - ); - expect(controlMessage.textContent).toContain('项目:未命名游戏原型'); - expect(controlMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(controlMessage.textContent).toContain( - '核心口径:目标;操作;胜负;重开;本地预览', - ); - expect(controlMessage.textContent).toContain( - '规则来源:game/game_design.md · 未见 trace 产物', - ); - expect(controlMessage.textContent).toContain( - '原型入口:game/index.html · 未见 trace 产物', - ); - expect(controlMessage.textContent).toContain( - 'design-foundation:设计实现组 / Gameplay 确定玩法规格与界面原型 · 待处理', - ); - expect(controlMessage.textContent).toContain( - 'code-prototype:程序组 / Code 生成可运行原型 · 待处理', - ); - expect(controlMessage.textContent).toContain( - 'preview-playtest:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(controlMessage.textContent).toContain( - '最近程序/试玩步骤:Generator #1 · completed · 生成可运行草案', - ); - expect(controlMessage.textContent).toContain( - '相关命令:/mvp;/playtest;/feedback', - ); - expect(controlMessage.textContent).toContain( - '建议:/agent-resume 操作说明:在首屏明确移动/点击操作、胜负目标、失败后重开方式', - ); - const controlMessageList = document.querySelector('.message-list'); - expect(controlMessageList).not.toBeNull(); - const controlDraftButtons = within( - controlMessageList as HTMLElement, - ).getAllByRole('button', { name: '补充操作说明' }); - fireEvent.click(controlDraftButtons[controlDraftButtons.length - 1]); - expect(await composerText()).toBe( - '/agent-resume 操作说明:在首屏明确移动/点击操作、胜负目标、失败后重开方式', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeControls.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeControls.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeControls.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeControls.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeControls.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeControls.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeControls.exportPackage); - - const commandCountsBeforeTutorial = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/tutorial'); - const tutorialMessages = await screen.findAllByText(/新手引导:/); - const tutorialMessage = messageBubble( - tutorialMessages[tutorialMessages.length - 1], - ); - expect(tutorialMessage.textContent).toContain('项目:未命名游戏原型'); - expect(tutorialMessage.textContent).toContain('首屏目标:做一个厨房弹幕游戏'); - expect(tutorialMessage.textContent).toContain( - '首局 30 秒:看到目标;尝试操作;收到反馈;理解失败/胜利;能重开', - ); - expect(tutorialMessage.textContent).toContain( - '当前证据:原型入口 未见 trace 产物;最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(tutorialMessage.textContent).toContain( - '试玩任务:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(tutorialMessage.textContent).toContain( - '最近引导证据:Generator #1 · completed · 生成可运行草案', - ); - expect(tutorialMessage.textContent).toContain( - '需要补齐:首屏目标提示;操作提示;碰撞/得分反馈;失败或胜利提示;重开按钮', - ); - expect(tutorialMessage.textContent).toContain( - '参考:/rules;/playtest;/feedback;/pitch', - ); - expect(tutorialMessage.textContent).toContain( - '建议:/agent-resume 新手引导:在首屏加入目标、操作、反馈、失败重开提示', - ); - const tutorialMessageList = document.querySelector('.message-list'); - expect(tutorialMessageList).not.toBeNull(); - const tutorialDraftButtons = within( - tutorialMessageList as HTMLElement, - ).getAllByRole('button', { name: '补充新手引导' }); - fireEvent.click(tutorialDraftButtons[tutorialDraftButtons.length - 1]); - expect(await composerText()).toBe( - '/agent-resume 新手引导:在首屏加入目标、操作、反馈、失败重开提示', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeTutorial.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeTutorial.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeTutorial.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeTutorial.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTutorial.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeTutorial.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeTutorial.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeTutorial.exportList); - - const commandCountsBeforeMobile = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/mobile'); - const mobileMessages = await screen.findAllByText(/移动试玩:/); - const mobileMessage = messageBubble( - mobileMessages[mobileMessages.length - 1], - ); - expect(mobileMessage.textContent).toContain('项目:未命名游戏原型'); - expect(mobileMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(mobileMessage.textContent).toContain( - '输入方式:键盘 / 触屏都应能完成核心循环', - ); - expect(mobileMessage.textContent).toContain( - '当前证据:原型入口 未见 trace 产物;最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(mobileMessage.textContent).toContain( - '移动检查:触屏操作;响应式画布;横竖屏提示;按钮尺寸;失败/胜利重开', - ); - expect(mobileMessage.textContent).toContain( - 'code-prototype:程序组 / Code 生成可运行原型 · 待处理', - ); - expect(mobileMessage.textContent).toContain( - 'preview-playtest:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(mobileMessage.textContent).toContain( - '最近移动相关步骤:Generator #1 · completed · 生成可运行草案', - ); - expect(mobileMessage.textContent).toContain( - '参考:/rules;/tutorial;/playtest;/feedback', - ); - expect(mobileMessage.textContent).toContain( - '建议:/agent-resume 移动试玩:补充触屏操作、响应式画布、横竖屏提示、重开按钮', - ); - const mobileMessageList = document.querySelector('.message-list'); - expect(mobileMessageList).not.toBeNull(); - const mobileDraftButtons = within( - mobileMessageList as HTMLElement, - ).getAllByRole('button', { name: '补充移动试玩' }); - fireEvent.click(mobileDraftButtons[mobileDraftButtons.length - 1]); - expect(await composerText()).toBe( - '/agent-resume 移动试玩:补充触屏操作、响应式画布、横竖屏提示、重开按钮', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeMobile.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeMobile.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeMobile.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeMobile.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeMobile.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeMobile.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeMobile.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeMobile.exportList); - - const commandCountsBeforeCompatibility = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/compatibility'); - const compatibilityMessages = await screen.findAllByText(/兼容性说明:/); - const compatibilityMessage = messageBubble( - compatibilityMessages[compatibilityMessages.length - 1], - ); - expect(compatibilityMessage.textContent).toContain('项目:未命名游戏原型'); - expect(compatibilityMessage.textContent).toContain( - '目标:做一个厨房弹幕游戏', - ); - expect(compatibilityMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(compatibilityMessage.textContent).toContain( - '自检:game.static_smoke 已通过', - ); - expect(compatibilityMessage.textContent).toContain( - '输入兼容:键盘优先;触屏按 /mobile 复查', - ); - expect(compatibilityMessage.textContent).toContain( - '推荐环境:桌面 Chrome / Edge 最新版;本机 127.0.0.1 预览;移动浏览器只做早期体验', - ); - expect(compatibilityMessage.textContent).toContain( - '不承诺:旧浏览器、低端设备、离线模式、云存档、账号同步、手柄或多端数据一致', - ); - expect(compatibilityMessage.textContent).toContain( - '反馈口径:设备 / 浏览器 / 输入方式 / 截图或录屏;客户端检测到问题时会自动提示', - ); - expect(compatibilityMessage.textContent).toContain( - '参考:/mobile;/accessibility;/performance;/known-issues', - ); - expect(compatibilityMessage.textContent).toContain( - '边界:只准备兼容性说明;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(compatibilityMessage.textContent).toContain('建议:/run'); - const compatibilityMessageList = document.querySelector('.message-list'); - expect(compatibilityMessageList).not.toBeNull(); - const compatibilityDraftButtons = within( - compatibilityMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click( - compatibilityDraftButtons[compatibilityDraftButtons.length - 1], - ); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeCompatibility.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeCompatibility.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeCompatibility.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeCompatibility.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeCompatibility.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeCompatibility.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeCompatibility.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeCompatibility.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeCompatibility.controlAgentRun); - - const commandCountsBeforeAccessibility = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/accessibility'); - const accessibilityMessages = await screen.findAllByText(/可读性与无障碍:/); - const accessibilityMessage = messageBubble( - accessibilityMessages[accessibilityMessages.length - 1], - ); - expect(accessibilityMessage.textContent).toContain('项目:未命名游戏原型'); - expect(accessibilityMessage.textContent).toContain( - '目标:做一个厨房弹幕游戏', - ); - expect(accessibilityMessage.textContent).toContain( - '检查范围:文字可读;颜色对比;按钮/状态命名;键盘等价操作;可见焦点;非颜色唯一反馈;静音可玩', - ); - expect(accessibilityMessage.textContent).toContain( - '当前证据:原型入口 未见 trace 产物;最近 run 已通过 run-main-shortcut-trace;预览 未启动', - ); - expect(accessibilityMessage.textContent).toContain( - '补齐项:文字对比;清晰按钮标签;键盘等价操作;非颜色唯一反馈;静音可玩', - ); - expect(accessibilityMessage.textContent).toContain( - 'code-prototype:程序组 / Code 生成可运行原型 · 待处理', - ); - expect(accessibilityMessage.textContent).toContain( - 'quality-review:程序组 / Review 执行质量评审 · 待处理', - ); - expect(accessibilityMessage.textContent).toContain( - 'preview-readiness:程序组 / Preview 执行静态自检 · 待处理', - ); - expect(accessibilityMessage.textContent).toContain( - 'preview-playtest:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(accessibilityMessage.textContent).toContain( - '最近无障碍相关步骤:Evaluator #1 · passed · 质量评审通过', - ); - expect(accessibilityMessage.textContent).toContain( - '参考:/rules;/mobile;/tutorial;/qa;/playtest', - ); - expect(accessibilityMessage.textContent).toContain( - '建议:/agent-resume 可读性与无障碍:补充文字对比、清晰按钮标签、键盘等价操作、非颜色唯一反馈、静音可玩', - ); - const accessibilityMessageList = document.querySelector('.message-list'); - expect(accessibilityMessageList).not.toBeNull(); - const accessibilityDraftButtons = within( - accessibilityMessageList as HTMLElement, - ).getAllByRole('button', { name: '补充无障碍' }); - fireEvent.click( - accessibilityDraftButtons[accessibilityDraftButtons.length - 1], - ); - expect(await composerText()).toBe( - '/agent-resume 可读性与无障碍:补充文字对比、清晰按钮标签、键盘等价操作、非颜色唯一反馈、静音可玩', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeAccessibility.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeAccessibility.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeAccessibility.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeAccessibility.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeAccessibility.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeAccessibility.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeAccessibility.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeAccessibility.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeAccessibility.controlAgentRun); - - const commandCountsBeforeLocalization = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/localization'); - const localizationMessages = await screen.findAllByText(/本地化与文案:/); - const localizationMessage = messageBubble( - localizationMessages[localizationMessages.length - 1], - ); - expect(localizationMessage.textContent).toContain('项目:未命名游戏原型'); - expect(localizationMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(localizationMessage.textContent).toContain( - '默认语言:简体中文;首版不承诺多语言', - ); - expect(localizationMessage.textContent).toContain( - '文案范围:标题;目标提示;操作按钮;状态反馈;失败/胜利;重开;发布简介', - ); - expect(localizationMessage.textContent).toContain( - '当前证据:最近 run 已通过 run-main-shortcut-trace;预览 未启动;发布说明 已生成', - ); - expect(localizationMessage.textContent).toContain( - 'design-foundation:设计实现组 / Gameplay 确定玩法规格与界面原型 · 待处理', - ); - expect(localizationMessage.textContent).toContain( - 'code-prototype:程序组 / Code 生成可运行原型 · 待处理', - ); - expect(localizationMessage.textContent).toContain( - 'quality-review:程序组 / Review 执行质量评审 · 待处理', - ); - expect(localizationMessage.textContent).toContain( - 'publish-package:运营组 / Publish 整理发布包装 · 待处理', - ); - expect(localizationMessage.textContent).toContain( - '检查口径:短句优先;动词一致;玩家术语统一;错误提示可复现;UI 文案避免开发解释', - ); - expect(localizationMessage.textContent).toContain( - '暂不做:英日韩等多语言包;自动翻译;地区化素材;语音本地化;商店长文案 A/B', - ); - expect(localizationMessage.textContent).toContain( - '最近文案相关步骤:Evaluator #1 · passed · 质量评审通过', - ); - expect(localizationMessage.textContent).toContain( - '参考:/rules;/tutorial;/listing;/faq;/known-issues', - ); - expect(localizationMessage.textContent).toContain( - '边界:只整理本地化与文案检查;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(localizationMessage.textContent).toContain( - '建议:/read exports/README.md', - ); - const localizationMessageList = document.querySelector('.message-list'); - expect(localizationMessageList).not.toBeNull(); - const localizationDraftButtons = within( - localizationMessageList as HTMLElement, - ).getAllByRole('button', { name: '读发布说明' }); - fireEvent.click( - localizationDraftButtons[localizationDraftButtons.length - 1], - ); - expect(await composerText()).toBe('/read exports/README.md'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeLocalization.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeLocalization.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeLocalization.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeLocalization.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeLocalization.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeLocalization.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeLocalization.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeLocalization.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeLocalization.controlAgentRun); - - const commandCountsBeforePerformance = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/performance'); - const performanceMessages = await screen.findAllByText(/性能与加载:/); - const performanceMessage = messageBubble( - performanceMessages[performanceMessages.length - 1], - ); - expect(performanceMessage.textContent).toContain('项目:未命名游戏原型'); - expect(performanceMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(performanceMessage.textContent).toContain( - '当前证据:最近 run 已通过 run-main-shortcut-trace;预览 未启动;入口 未见 trace 产物;产物 3 个 / 416B;资产 2 个', - ); - expect(performanceMessage.textContent).toContain( - '检查范围:入口 HTML 自包含;首屏不空白;素材体积;主循环稳定;无远程依赖;预览启动', - ); - expect(performanceMessage.textContent).toContain( - 'exports/README.md · 128B · fnv1a64:exports', - ); - expect(performanceMessage.textContent).toContain( - '.agent/passes/pass-1/agenda.md · 96B · fnv1a64:agenda', - ); - expect(performanceMessage.textContent).toContain( - 'code-prototype:程序组 / Code 生成可运行原型 · 待处理', - ); - expect(performanceMessage.textContent).toContain( - 'preview-readiness:程序组 / Preview 执行静态自检 · 待处理', - ); - expect(performanceMessage.textContent).toContain( - 'preview-playtest:程序组 / Playtest 预览并试玩验收 · 待处理', - ); - expect(performanceMessage.textContent).toContain( - '最近性能相关步骤:Generator #1 · completed · 生成可运行草案', - ); - expect(performanceMessage.textContent).toContain( - '参考:/run-artifacts;/playtest;/qa;/export', - ); - expect(performanceMessage.textContent).toContain('建议:/run-artifacts'); - const performanceMessageList = document.querySelector('.message-list'); - expect(performanceMessageList).not.toBeNull(); - const performanceDraftButtons = within( - performanceMessageList as HTMLElement, - ).getAllByRole('button', { name: '列出 Run 产物' }); - fireEvent.click(performanceDraftButtons[performanceDraftButtons.length - 1]); - expect(await composerText()).toBe('/run-artifacts'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforePerformance.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforePerformance.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforePerformance.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforePerformance.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePerformance.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePerformance.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforePerformance.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforePerformance.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforePerformance.controlAgentRun); - - const commandCountsBeforePolish = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/polish'); - const polishMessages = await screen.findAllByText(/试玩前打磨:/); - const polishMessage = messageBubble( - polishMessages[polishMessages.length - 1], - ); - expect(polishMessage.textContent).toContain('项目:未命名游戏原型'); - expect(polishMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(polishMessage.textContent).toContain( - '当前证据:最近 run 已通过 run-main-shortcut-trace;预览 未启动;自检 已通过;资产 2 个(美术 1 / 音频 0)', - ); - expect(polishMessage.textContent).toContain( - '打磨范围:新手引导;移动试玩;可读性与无障碍;性能与加载;美术 / 音频素材;试玩反馈', - ); - expect(polishMessage.textContent).toContain( - '推荐顺序:/tutorial -> /mobile -> /accessibility -> /performance -> /credits -> /feedback', - ); - expect(polishMessage.textContent).toContain( - 'art-polish:美术组 / Polish 检查美术可用性 · 待处理', - ); - expect(polishMessage.textContent).toContain( - 'quality-review:程序组 / Review 执行质量评审 · 待处理', - ); - expect(polishMessage.textContent).toContain( - 'publish-package:运营组 / Publish 整理发布包装 · 待处理', - ); - expect(polishMessage.textContent).toContain( - '最近打磨相关步骤:Evaluator #1 · passed · 质量评审通过', - ); - expect(polishMessage.textContent).toContain( - '边界:只整理试玩前打磨清单;不读取文件;不启动预览;不导出试玩包;不写项目', - ); - expect(polishMessage.textContent).toContain( - '建议:/agent-resume 打磨:补齐新手引导、触屏操作、可读性、性能、素材署名和试玩反馈', - ); - const polishMessageList = document.querySelector('.message-list'); - expect(polishMessageList).not.toBeNull(); - const polishDraftButtons = within( - polishMessageList as HTMLElement, - ).getAllByRole('button', { name: '补充打磨' }); - fireEvent.click(polishDraftButtons[polishDraftButtons.length - 1]); - expect(await composerText()).toBe( - '/agent-resume 打磨:补齐新手引导、触屏操作、可读性、性能、素材署名和试玩反馈', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforePolish.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforePolish.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforePolish.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforePolish.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePolish.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforePolish.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforePolish.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforePolish.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforePolish.controlAgentRun); - - const commandCountsBeforeCredits = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/credits'); - const creditMessages = await screen.findAllByText(/素材署名:/); - const creditMessage = messageBubble( - creditMessages[creditMessages.length - 1], - ); - expect(creditMessage.textContent).toContain('当前资产:2 个'); - expect(creditMessage.textContent).toContain('来源分布:上传 1 / 画板 1'); - expect(creditMessage.textContent).toContain( - 'assets/uploads/hero.txt · text/plain · 上传 · 用户上传', - ); - expect(creditMessage.textContent).toContain( - 'assets/canvas-sync/tiles.png · image/png · 画板 · 画板 canvas-1', - ); - expect(creditMessage.textContent).toContain( - '需要确认:上传素材授权;生成素材模型;画板资源来源;本地试玩包保留来源口径', - ); - expect(creditMessage.textContent).toContain( - '参考:/assets;/art;/audio;/listing', - ); - expect(creditMessage.textContent).toContain('建议:/assets'); - const creditMessageList = document.querySelector('.message-list'); - expect(creditMessageList).not.toBeNull(); - const creditDraftButtons = within( - creditMessageList as HTMLElement, - ).getAllByRole('button', { name: '查看资产' }); - fireEvent.click(creditDraftButtons[creditDraftButtons.length - 1]); - expect(await composerText()).toBe('/assets'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeCredits.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeCredits.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeCredits.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeCredits.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeCredits.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeCredits.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeCredits.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeCredits.exportList); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts deleted file mode 100644 index 6f9d5a52e..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.ts +++ /dev/null @@ -1,832 +0,0 @@ -import { - composerText, - composerValue, - expect, - fireEvent, - screen, - submitChat, - waitFor, - within, -} from '../../harness'; -import type { PreviewShortcutInvoke } from './invoke-mock'; -import { messageBubble } from './message-bubble'; - -export async function assertProjectToolsAndPreviewFlow( - invoke: PreviewShortcutInvoke, -) { - const commandCountsBeforeFeedback = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - }; - await submitChat('/feedback'); - const feedbackMessages = await screen.findAllByText(/试玩反馈:/); - const feedbackMessage = messageBubble( - feedbackMessages[feedbackMessages.length - 1], - ); - expect(feedbackMessage.textContent).toContain( - 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', - ); - expect(feedbackMessage.textContent).toContain('预览:未启动 · 建议 /run'); - expect(feedbackMessage.textContent).toContain( - '反馈方向:操作手感;胜负目标;难度;视觉 / 音效;重开路径', - ); - expect(feedbackMessage.textContent).toContain( - '反馈模板:/agent-resume 试玩反馈:保留…;调整…;新增…', - ); - expect(feedbackMessage.textContent).toContain( - '参考:/playtest;/qa;/changes', - ); - expect(feedbackMessage.textContent).toContain('建议:/run'); - const feedbackMessageList = document.querySelector('.message-list'); - expect(feedbackMessageList).not.toBeNull(); - const feedbackDraftButtons = within( - feedbackMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动预览' }); - fireEvent.click(feedbackDraftButtons[feedbackDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeFeedback.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeFeedback.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeFeedback.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeFeedback.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeFeedback.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeFeedback.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeFeedback.exportPackage); - - const commandCountsBeforeRetention = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - controlAgentRun: invoke.mock.calls.filter( - ([command]) => command === 'control_agent_run', - ).length, - }; - await submitChat('/retention'); - const retentionMessages = await screen.findAllByText(/复玩观察:/); - const retentionMessage = messageBubble( - retentionMessages[retentionMessages.length - 1], - ); - expect(retentionMessage.textContent).toContain('项目:未命名游戏原型'); - expect(retentionMessage.textContent).toContain('目标:做一个厨房弹幕游戏'); - expect(retentionMessage.textContent).toContain( - '当前状态:最近 run 已通过 run-main-shortcut-trace;预览 未启动;自检 已通过;试玩包 待导出', - ); - expect(retentionMessage.textContent).toContain( - '首轮样本:3-5 名测试者;每人 5-10 分钟;先不解释玩法,观察是否能自己完成首局', - ); - expect(retentionMessage.textContent).toContain( - '复玩信号:是否主动重开;失败后是否理解原因;第二局是否更快进入目标;是否愿意换难度/角色/关卡;是否能说出想保留的一点', - ); - expect(retentionMessage.textContent).toContain( - '资产与包装:素材 2 个;发布说明 已生成', - ); - expect(retentionMessage.textContent).toContain('最近试玩证据:暂无'); - expect(retentionMessage.textContent).toContain( - '记录模板:保留 1 项;调弱/调强 1 项;新增 1 项;必须修 1 项;是否愿意再玩一局', - ); - expect(retentionMessage.textContent).toContain( - '暂不做:真实埋点;留存报表;用户画像;A/B 实验;排行榜或账号留存', - ); - expect(retentionMessage.textContent).toContain( - '参考:/playtest;/feedback;/survey;/share;/known-issues', - ); - expect(retentionMessage.textContent).toContain( - '边界:只准备复玩观察清单;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', - ); - expect(retentionMessage.textContent).toContain('建议:/run'); - const retentionMessageList = document.querySelector('.message-list'); - expect(retentionMessageList).not.toBeNull(); - const retentionDraftButtons = within( - retentionMessageList as HTMLElement, - ).getAllByRole('button', { name: '启动试玩' }); - fireEvent.click(retentionDraftButtons[retentionDraftButtons.length - 1]); - expect(await composerText()).toBe('/run'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeRetention.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeRetention.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeRetention.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeRetention.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeRetention.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeRetention.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeRetention.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeRetention.exportList); - expect( - invoke.mock.calls.filter(([command]) => command === 'control_agent_run'), - ).toHaveLength(commandCountsBeforeRetention.controlAgentRun); - - const commandCountsBeforeShare = { - manifestRead: invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ).length, - fileRead: invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length, - fileList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ).length, - runLocal: invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ).length, - startPreview: invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ).length, - openPreview: invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ).length, - exportPackage: invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ).length, - exportList: invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ).length, - }; - await submitChat('/share'); - const shareMessages = await screen.findAllByText(/试玩交付:/); - const shareMessage = messageBubble(shareMessages[shareMessages.length - 1]); - expect(shareMessage.textContent).toContain('项目:未命名游戏原型'); - expect(shareMessage.textContent).toContain('目录:/tmp/authorized-game'); - expect(shareMessage.textContent).toContain( - '原型:最近 run 已通过 run-main-shortcut-trace', - ); - expect(shareMessage.textContent).toContain('本地预览:未启动 · /run'); - expect(shareMessage.textContent).toContain('本地试玩包:待导出 · /export'); - expect(shareMessage.textContent).toContain( - '给测试者:玩法目标 / 操作 / 胜负 / 重开口径见 /rules', - ); - expect(shareMessage.textContent).toContain('反馈收集:/feedback'); - expect(shareMessage.textContent).toContain( - '交付边界:本地 ZIP 和本地预览;不上传云端;不生成公开分享链接', - ); - expect(shareMessage.textContent).toContain('建议:/export'); - const shareMessageList = document.querySelector('.message-list'); - expect(shareMessageList).not.toBeNull(); - const shareDraftButtons = within( - shareMessageList as HTMLElement, - ).getAllByRole('button', { name: '导出试玩包' }); - fireEvent.click(shareDraftButtons[shareDraftButtons.length - 1]); - expect(await composerText()).toBe('/export'); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'get_local_game_manifest', - ), - ).toHaveLength(commandCountsBeforeShare.manifestRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(commandCountsBeforeShare.fileRead); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_files', - ), - ).toHaveLength(commandCountsBeforeShare.fileList); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'run_limited_local_command', - ), - ).toHaveLength(commandCountsBeforeShare.runLocal); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'start_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeShare.startPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'activate_local_game_preview', - ), - ).toHaveLength(commandCountsBeforeShare.openPreview); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'export_local_project_package', - ), - ).toHaveLength(commandCountsBeforeShare.exportPackage); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'list_local_project_export_packages', - ), - ).toHaveLength(commandCountsBeforeShare.exportList); - - fireEvent.click(screen.getByRole('button', { name: '权限' })); - expect(await screen.findByText(/策略:\.agent\/policy\.json/)).not.toBeNull(); - const composerInput = screen.getByLabelText('创作想法'); - fireEvent.click(screen.getByRole('button', { name: '索引确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm project.index', - ); - fireEvent.click(screen.getByRole('button', { name: '资产登记确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm asset.register', - ); - fireEvent.click(screen.getByRole('button', { name: '记忆写入确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm memory.write', - ); - fireEvent.click(screen.getByRole('button', { name: '预览确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm preview.start', - ); - fireEvent.click(screen.getByRole('button', { name: '打开预览确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm preview.open', - ); - fireEvent.click(screen.getByRole('button', { name: '停止预览确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm preview.stop', - ); - fireEvent.click(screen.getByRole('button', { name: 'Agent确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm agent.run_status', - ); - fireEvent.click(screen.getByRole('button', { name: '读对话确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm conversation.read', - ); - fireEvent.click(screen.getByRole('button', { name: '存对话确认' })); - expect(await composerValue(composerInput)).toBe( - '/policy-confirm conversation.write', - ); - expect(screen.queryByText('project.policy_write')).toBeNull(); - expect(invoke).not.toHaveBeenCalledWith( - 'write_project_permission_policy', - expect.anything(), - ); - - fireEvent.click(screen.getByRole('button', { name: '审计' })); - expect(await screen.findByText(/Agent v1 审计:/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '资产' })); - expect( - await screen.findByText(/uploaded · assets\/uploads\/hero\.txt/), - ).not.toBeNull(); - expect( - within(screen.getByLabelText('最近项目资产')).getByText( - 'uploaded · text/plain · uploaded', - ), - ).not.toBeNull(); - const artManifestReadCountBefore = invoke.mock.calls.filter( - ([command, args]) => - command === 'read_local_project_file' && - args && - 'relativePath' in args && - args.relativePath === 'assets/manifest.art.json', - ).length; - await submitChat('/art'); - expect(await screen.findByText(/美术素材:1 个/)).not.toBeNull(); - expect(screen.getByText(/来源:画板 1/)).not.toBeNull(); - expect( - screen.getByText(/assets\/canvas-sync\/tiles\.png · image\/png · 画板/), - ).not.toBeNull(); - const visualDraftButtons = screen.getAllByRole('button', { - name: '读美术清单', - }); - fireEvent.click(visualDraftButtons[visualDraftButtons.length - 1]); - expect(await composerValue(composerInput)).toBe( - '/read assets/manifest.art.json', - ); - expect( - invoke.mock.calls.filter( - ([command, args]) => - command === 'read_local_project_file' && - args && - 'relativePath' in args && - args.relativePath === 'assets/manifest.art.json', - ), - ).toHaveLength(artManifestReadCountBefore); - const audioManifestReadCountBefore = invoke.mock.calls.filter( - ([command, args]) => - command === 'read_local_project_file' && - args && - 'relativePath' in args && - args.relativePath === 'assets/manifest.audio.json', - ).length; - await submitChat('/audio'); - expect(await screen.findByText(/音频素材:暂无登记音频/)).not.toBeNull(); - expect( - screen.getByText(/可先登记项目内音效,或把已有画板音频作为素材导入/), - ).not.toBeNull(); - const audioDraftButtons = screen.getAllByRole('button', { - name: '登记音效', - }); - fireEvent.click(audioDraftButtons[audioDraftButtons.length - 1]); - expect(await composerValue(composerInput)).toBe( - '/asset-register assets/audio/sfx.wav audio audio/wav', - ); - expect( - invoke.mock.calls.filter( - ([command, args]) => - command === 'read_local_project_file' && - args && - 'relativePath' in args && - args.relativePath === 'assets/manifest.audio.json', - ), - ).toHaveLength(audioManifestReadCountBefore); - for (const [buttonName, draftValue] of [ - ['读入口', '/read game/index.html'], - ['读设计', '/read game/game_design.md'], - ['读数值', '/read game/balance.json'], - ['读美术清单', '/read assets/manifest.art.json'], - ['读音频清单', '/read assets/manifest.audio.json'], - ['读发布说明', '/read exports/README.md'], - ] as const) { - fireEvent.click( - within(screen.getByLabelText('预览快捷操作')).getByRole('button', { - name: buttonName, - }), - ); - expect(await composerValue(composerInput)).toBe(draftValue); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - } - await submitChat('/artifacts'); - expect(await screen.findByText(/常用生成产物:/)).not.toBeNull(); - expect(screen.getByText(/读入口 · game\/index\.html/)).not.toBeNull(); - expect(screen.getByText(/读发布说明 · exports\/README\.md/)).not.toBeNull(); - const readEntryButtons = screen.getAllByRole('button', { name: '读入口' }); - fireEvent.click(readEntryButtons[readEntryButtons.length - 1]); - expect(await composerValue(composerInput)).toBe('/read game/index.html'); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - await submitChat('/run-artifacts'); - const runArtifactsHeading = - await screen.findByText('最近 Run 产物读取命令:'); - expect(messageBubble(runArtifactsHeading).textContent).toContain( - 'exports/README.md · 128B · fnv1a64:exports:/read exports/README.md', - ); - fireEvent.click(screen.getByRole('button', { name: '读取首个 Run 产物' })); - expect(await composerValue(composerInput)).toBe('/read exports/README.md'); - expect(invoke).not.toHaveBeenCalledWith( - 'read_local_project_file', - expect.objectContaining({ - relativePath: 'exports/README.md', - }), - ); - const fileReadCountBeforePasses = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - await submitChat('/passes'); - const passArtifactMessages = - await screen.findAllByText(/Agent 轮次产物读取命令:/); - const passArtifactMessage = messageBubble( - passArtifactMessages[passArtifactMessages.length - 1], - ); - expect(passArtifactMessage.textContent).toContain( - '.agent/passes/pass-1/agenda.md · 96B · fnv1a64:agenda', - ); - expect(passArtifactMessage.textContent).toContain( - '.agent/passes/pass-1/groups/art/asset.md · 192B · fnv1a64:art-asset', - ); - const passArtifactButtons = screen.getAllByRole('button', { - name: '读取首个轮次产物', - }); - fireEvent.click(passArtifactButtons[passArtifactButtons.length - 1]); - expect(await composerValue(composerInput)).toBe( - '/read .agent/passes/pass-1/agenda.md', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(fileReadCountBeforePasses); - await submitChat('/logs'); - expect(await screen.findByText(/常用日志读取命令:/)).not.toBeNull(); - expect( - screen.getByText(/读命令日志 · \.agent\/logs\/command\.log/), - ).not.toBeNull(); - expect( - screen.getByText(/读 Agent 日志 · \.agent\/logs\/agent\.log/), - ).not.toBeNull(); - const commandLogReadCountBefore = invoke.mock.calls.filter( - ([command, args]) => - command === 'read_local_project_file' && - args && - 'relativePath' in args && - args.relativePath === '.agent/logs/command.log', - ).length; - const messageList = document.querySelector('.message-list'); - expect(messageList).not.toBeNull(); - fireEvent.click( - within(messageList as HTMLElement).getByRole('button', { - name: '读命令日志', - }), - ); - expect(await composerValue(composerInput)).toBe( - '/read .agent/logs/command.log', - ); - expect( - invoke.mock.calls.filter( - ([command, args]) => - command === 'read_local_project_file' && - args && - 'relativePath' in args && - args.relativePath === '.agent/logs/command.log', - ), - ).toHaveLength(commandLogReadCountBefore); - fireEvent.click(screen.getByRole('button', { name: '打开画板' })); - expect(await composerValue(composerInput)).toBe('/canvas '); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click(screen.getByRole('button', { name: '同步画板' })); - expect(await composerValue(composerInput)).toBe('/sync-canvas-project '); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click(screen.getByRole('button', { name: '生成美术' })); - expect(await composerValue(composerInput)).toBe('/generate-art '); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click( - within(screen.getByLabelText('预览快捷操作')).getByRole('button', { - name: '登记音效', - }), - ); - expect(await composerValue(composerInput)).toBe( - '/asset-register assets/audio/sfx.wav audio audio/wav', - ); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click(screen.getByRole('button', { name: '导入画板资产' })); - expect(await composerValue(composerInput)).toBe('/import-canvas-asset '); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click(screen.getByRole('button', { name: '导入画板音频' })); - expect(await composerValue(composerInput)).toBe( - '/import-canvas-asset assets/audio/sfx.wav ', - ); - await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click( - within(screen.getByLabelText('最近项目资产')).getByRole('button', { - name: 'assets/uploads/hero.txt', - }), - ); - expect( - await screen.findByText(/文件:assets\/uploads\/hero\.txt/), - ).not.toBeNull(); - fireEvent.click( - within(screen.getByLabelText('最近项目资产')).getByRole('button', { - name: '填入读取 assets/uploads/hero.txt', - }), - ); - expect(await composerValue(composerInput)).toBe( - '/read assets/uploads/hero.txt', - ); - - fireEvent.click(screen.getByRole('button', { name: '任务' })); - expect(await screen.findByText(/任务拆分:/)).not.toBeNull(); - expect(screen.getByText(/下一步:设计实现组 \/ Director/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: 'Trace' })); - const traceHeading = await screen.findByText( - (content, element) => - element?.tagName === 'P' && - content.trimStart().startsWith('Run:run-main-shortcut-trace'), - ); - expect(messageBubble(traceHeading).textContent).toContain('产物快照:'); - expect(screen.getByText(/产物快照:/)).not.toBeNull(); - expect( - screen.getByText(/exports\/README\.md · fnv1a64:exports/), - ).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '读取首个产物' })); - expect(await composerValue(composerInput)).toBe('/read exports/README.md'); - expect(invoke).not.toHaveBeenCalledWith('read_local_project_file', { - projectPath: '/tmp/authorized-game', - relativePath: 'exports/README.md', - commandId: 'file.read', - }); - - fireEvent.click(screen.getByRole('button', { name: '文件' })); - expect(await screen.findByText(/本地项目文件:/)).not.toBeNull(); - fireEvent.click( - within(screen.getByLabelText('最近项目文件')).getByRole('button', { - name: 'game/index.html', - }), - ); - expect(await screen.findByText(/文件:game\/index\.html/)).not.toBeNull(); - fireEvent.click( - within(screen.getByLabelText('最近项目文件')).getByRole('button', { - name: '填入读取 game/index.html', - }), - ); - expect(await composerValue(composerInput)).toBe('/read game/index.html'); - fireEvent.click( - within(screen.getByLabelText('最近项目文件')).getByRole('button', { - name: '登记资产 game/index.html', - }), - ); - expect(await composerValue(composerInput)).toBe( - '/asset-register game/index.html document text/html', - ); - expect(invoke).not.toHaveBeenCalledWith( - 'register_local_asset', - expect.anything(), - ); - - fireEvent.click(screen.getByRole('button', { name: '索引' })); - expect(await screen.findByText(/索引:2 个文件,384B/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '记忆' })); - expect(await screen.findByText(/长期记忆:/)).not.toBeNull(); - expect(screen.getByText(/保留厨房主题/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '短期记忆' })); - expect(await screen.findByText(/短期记忆:/)).not.toBeNull(); - expect(screen.getByText(/本轮偏动作反馈/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '黑板' })); - expect(await screen.findByText(/黑板记忆:/)).not.toBeNull(); - expect(screen.getByText(/跨 agent 共享约束/)).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '记到黑板' })); - expect(await composerValue(composerInput)).toBe('/remember blackboard '); - fireEvent.click(screen.getByRole('button', { name: '覆盖黑板' })); - expect(await composerValue(composerInput)).toBe('/memory-set blackboard '); - fireEvent.click(screen.getByRole('button', { name: '清空黑板' })); - expect(await composerValue(composerInput)).toBe('/forget-memory blackboard'); - - fireEvent.click(screen.getByRole('button', { name: '快照' })); - expect(await screen.findByText('project.checkpoint')).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '确认' })); - expect( - await screen.findByText(/已保存 checkpoint:checkpoint-main/), - ).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '快照列表' })); - const checkpointList = await screen.findByLabelText('最近 checkpoint'); - expect(within(checkpointList).getByText('checkpoint-main')).not.toBeNull(); - expect(within(checkpointList).getByText(/3 个文件 · 256B/)).not.toBeNull(); - expect( - within(checkpointList).getByText(/createdAt 1700000002/), - ).not.toBeNull(); - expect( - within(checkpointList).getByRole('button', { - name: '对比 checkpoint-main', - }), - ).not.toBeNull(); - fireEvent.click( - within(checkpointList).getByRole('button', { - name: '填入对比 checkpoint-main', - }), - ); - expect(await composerValue(composerInput)).toBe('/diff checkpoint-main'); - fireEvent.click( - within(checkpointList).getByRole('button', { - name: '填入回滚 checkpoint-main', - }), - ); - expect(await composerValue(composerInput)).toBe('/restore checkpoint-main'); - fireEvent.click( - within(checkpointList).getByRole('button', { - name: '回滚 checkpoint-main', - }), - ); - expect(await screen.findByText(/project\.restore/)).not.toBeNull(); - expect( - screen.getByText( - '从 checkpoint-main 恢复 /tmp/authorized-game 的已跟踪项目文件', - ), - ).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '取消' })); - - fireEvent.click(screen.getByRole('button', { name: '白名单' })); - expect(await screen.findByText(/可运行受限命令:/)).not.toBeNull(); - expect(screen.getByText(/game\.static_smoke · 静态入口自检/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '静态自检' })); - expect(await screen.findByText('准备运行静态入口自检。')).not.toBeNull(); - expect(screen.getByText(/command\.run_limited/)).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '确认' })); - expect(await screen.findByText(/static smoke passed/)).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '运行' })); - expect(await screen.findByText('game.run_local')).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '确认' })); - expect( - await screen.findByText( - /运行通过,已载入客户端运行视图:http:\/\/127\.0\.0\.1:3210\//, - ), - ).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '导出' })); - expect(await screen.findByText('project.export_package')).not.toBeNull(); - expect( - screen.getByText( - '从 /tmp/authorized-game/game、assets 和 exports/README.md 导出本地试玩包', - ), - ).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '确认' })); - expect( - await screen.findByText( - /已导出本地试玩包:exports\/playtest-package-unit\.zip/, - ), - ).not.toBeNull(); - const exportRevealButtons = screen.getAllByRole('button', { - name: '显示目录', - }); - fireEvent.click(exportRevealButtons[exportRevealButtons.length - 1]); - expect(await composerText()).toBe('/open-project'); - - await submitChat('/export'); - await waitFor(() => - expect(screen.getAllByText('准备导出本地试玩包。').length).toBeGreaterThan( - 1, - ), - ); - expect(screen.getAllByText('project.export_package').length).toBeGreaterThan( - 0, - ); - fireEvent.click(screen.getByRole('button', { name: '取消' })); - - await submitChat('/exports'); - expect(await screen.findByText(/本地试玩包:/)).not.toBeNull(); - expect( - screen.getByText(/exports\/playtest-package-002\.zip · 2048B/), - ).not.toBeNull(); - expect( - screen.getByText(/exports\/playtest-package-001\.zip · 1024B/), - ).not.toBeNull(); - expect(invoke).toHaveBeenCalledWith('list_local_project_export_packages', { - projectPath: '/tmp/authorized-game', - }); - const exportListRevealButtons = screen.getAllByRole('button', { - name: '显示目录', - }); - fireEvent.click(exportListRevealButtons[exportListRevealButtons.length - 1]); - expect(await composerText()).toBe('/open-project'); - - fireEvent.click(screen.getByRole('button', { name: '启动预览' })); - expect(await screen.findByText('preview.start')).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '确认' })); - expect( - await screen.findByText( - /预览已在客户端运行视图启动:http:\/\/127\.0\.0\.1:3210\//, - ), - ).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '打开预览' })); - expect(await screen.findByText('preview.open')).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '确认' })); - expect( - await screen.findByText('已切换到客户端运行视图:http://127.0.0.1:3210/'), - ).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '预览状态' })); - expect( - await screen.findByText('预览运行中:http://127.0.0.1:3210/'), - ).not.toBeNull(); - expect(screen.getByText('preview: 运行中:127.0.0.1:3210')).not.toBeNull(); - - fireEvent.click(screen.getByRole('button', { name: '停止预览' })); - expect(await screen.findByText('预览已停止。')).not.toBeNull(); - expect(screen.getByText('preview: 已停止')).not.toBeNull(); - expect(invoke).toHaveBeenCalledWith('run_limited_local_command', { - projectPath: '/tmp/authorized-game', - commandId: 'game.static_smoke', - }); - expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', { - projectPath: '/tmp/authorized-game', - commandId: 'asset.list', - }); - expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', { - projectPath: '/tmp/authorized-game', - commandId: 'task.list', - }); - expect(invoke).toHaveBeenCalledWith('get_local_game_manifest', { - projectPath: '/tmp/authorized-game', - commandId: 'agent.audit', - }); - expect(invoke).toHaveBeenCalledWith('get_game_creation_agent_capabilities'); - expect(invoke).toHaveBeenCalledWith('list_local_project_files', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('build_local_project_index', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('read_local_game_memory', { - projectPath: '/tmp/authorized-game', - scope: 'long', - }); - expect(invoke).toHaveBeenCalledWith('read_local_game_memory', { - projectPath: '/tmp/authorized-game', - scope: 'short', - }); - expect(invoke).toHaveBeenCalledWith('read_local_game_memory', { - projectPath: '/tmp/authorized-game', - scope: 'blackboard', - }); - expect(invoke).toHaveBeenCalledWith('read_local_project_file', { - projectPath: '/tmp/authorized-game', - relativePath: '.agent/run.latest.json', - commandId: 'agent.trace_read', - }); - expect(invoke).toHaveBeenCalledWith('read_local_project_file', { - projectPath: '/tmp/authorized-game', - relativePath: 'game/index.html', - commandId: 'file.read', - }); - expect(invoke).toHaveBeenCalledWith('create_local_project_checkpoint', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('export_local_project_package', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('start_local_game_preview', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('activate_local_game_preview', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('get_local_game_preview_status', { - projectPath: '/tmp/authorized-game', - }); - expect(invoke).toHaveBeenCalledWith('stop_local_game_preview', { - projectPath: '/tmp/authorized-game', - }); -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/fixtures.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/fixtures.ts deleted file mode 100644 index e18db347c..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/fixtures.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - createGameCreationAppManifest, - createGameCreationAppSeedTasks, - GAME_CREATION_AGENT_RUN_SCHEMA_VERSION, - type GameCreationAgentRunTrace, -} from '../../harness'; - -export interface PreviewShortcutFixture { - manifest: ReturnType; - trace: GameCreationAgentRunTrace; -} - -export function createPreviewShortcutFixture(): PreviewShortcutFixture { - const manifest = createGameCreationAppManifest( - 'local-project-draft', - '未命名游戏原型', - ); - manifest.goal = '做一个厨房弹幕游戏'; - manifest.assets.push({ - id: 'asset-hero', - kind: 'uploaded', - mediaType: 'text/plain', - localPath: 'assets/uploads/hero.txt', - source: { kind: 'uploaded' }, - }); - manifest.assets.push({ - id: 'asset-canvas', - kind: 'canvas', - mediaType: 'image/png', - localPath: 'assets/canvas-sync/tiles.png', - source: { kind: 'canvas', canvasProjectId: 'canvas-1' }, - }); - manifest.commandRuns = [ - { - commandId: 'game.static_smoke', - status: 'completed', - output: 'static smoke passed', - logPath: '.agent/logs/command.log', - updatedAt: 1700000003, - }, - ]; - const trace: GameCreationAgentRunTrace = { - schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION, - runId: 'run-main-shortcut-trace', - commandId: 'game.generate_draft', - status: 'passed', - lifecycleStatus: 'done', - passes: 1, - maxPasses: 3, - toolCallCount: 1, - maxToolCalls: 128, - stopReason: 'evaluator-passed', - goal: '做一个厨房弹幕游戏', - coordination: 'Planner -> Generator', - steps: [ - { - pass: 1, - agent: 'Generator', - phase: 'generate', - status: 'completed', - inputPaths: ['.agent/spec.md'], - outputPaths: ['.agent/passes/pass-1/draft.json'], - summary: '生成可运行草案', - toolCalls: [ - { - toolId: 'llm.chat.generator', - status: 'ok', - summary: 'Generator 生成草案', - }, - ], - }, - { - pass: 1, - agent: 'Evaluator', - phase: 'evaluation', - status: 'passed', - inputPaths: ['.agent/passes/pass-1/draft.json', '.agent/findings.md'], - outputPaths: ['.agent/findings.md'], - summary: '质量评审通过', - toolCalls: [ - { - toolId: 'agent.evaluate', - status: 'ok', - summary: 'Evaluator 质量评审', - }, - ], - }, - ], - artifacts: [ - { - path: 'exports/README.md', - sizeBytes: 128, - checksum: 'fnv1a64:exports', - }, - { - path: '.agent/passes/pass-1/agenda.md', - sizeBytes: 96, - checksum: 'fnv1a64:agenda', - }, - { - path: '.agent/passes/pass-1/groups/art/asset.md', - sizeBytes: 192, - checksum: 'fnv1a64:art-asset', - }, - ], - taskGraph: { - goal: '做一个厨房弹幕游戏', - readyTaskIds: ['art-asset-plan'], - activeTaskIds: [], - carriedTaskIds: [], - repairFocus: [], - repairRoutes: [], - tasks: createGameCreationAppSeedTasks(), - }, - passPlans: [], - nextStep: 'preview', - error: null, - updatedAt: 1, - }; - - return { manifest, trace }; -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/invoke-mock.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/invoke-mock.ts deleted file mode 100644 index f5f522ea7..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/invoke-mock.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { emptyProjectPolicy, vi } from '../../harness'; -import type { PreviewShortcutFixture } from './fixtures'; - -export function createPreviewShortcutInvoke({ - manifest, - trace, -}: PreviewShortcutFixture) { - const invoke = vi.fn( - async (command: string, args?: Record) => { - if (command === 'append_local_permission_log') { - return {}; - } - if (command === 'read_project_permission_policy') { - return emptyProjectPolicy(); - } - if (command === 'init_local_game_project') { - const projectPath = String(args?.projectPath ?? ''); - return { - projectPath, - manifestPath: `${projectPath}/.agent/manifest.json`, - manifest, - }; - } - if (command === 'read_local_conversation') { - return { - path: '/tmp/authorized-game/.agent/conversations/project.jsonl', - agentId: null, - messages: [], - }; - } - if (command === 'run_limited_local_command') { - return { - commandId: 'game.static_smoke', - status: 'completed', - output: 'static smoke passed', - logPath: '.agent/logs/command.log', - }; - } - if (command === 'start_local_game_preview') { - return { - status: 'running', - url: 'http://127.0.0.1:3210/', - port: 3210, - root: String(args?.projectPath ?? ''), - }; - } - if (command === 'activate_local_game_preview') { - return { - status: 'running', - url: 'http://127.0.0.1:3210/', - port: 3210, - root: String(args?.projectPath ?? ''), - }; - } - if (command === 'get_local_game_preview_status') { - return { - status: 'running', - url: 'http://127.0.0.1:3210/', - port: 3210, - root: String(args?.projectPath ?? ''), - }; - } - if (command === 'stop_local_game_preview') { - return { - status: 'stopped', - url: null, - port: null, - root: null, - }; - } - if (command === 'get_local_game_manifest') { - return manifest; - } - if (command === 'get_game_creation_agent_capabilities') { - return [ - { - id: 'native-only-capability', - area: 'agent-runtime', - title: 'Native Runtime 能力', - }, - ]; - } - if (command === 'get_limited_local_commands') { - return [{ id: 'game.static_smoke', title: '静态入口自检' }]; - } - if (command === 'check_game_creator_llm_config') { - return { - agentMode: 'codex_app_server', - configured: true, - accountCredentialState: 'available', - officialRouteLocked: true, - baseUrl: 'https://router.genarrative.world/v1', - model: 'gpt-5.6-sol', - apiKind: 'openai_responses', - reasoningEffort: 'high', - stream: false, - webSearchEnabled: false, - error: null, - agents: [ - { - agentId: 'planner', - label: 'Planner', - agentMode: 'codex_app_server', - configured: true, - accountCredentialState: 'available', - officialRouteLocked: true, - baseUrl: 'https://router.genarrative.world/v1', - model: 'gpt-5.6-sol', - apiKind: 'openai_responses', - reasoningEffort: 'medium', - stream: true, - webSearchEnabled: false, - error: null, - }, - { - agentId: 'art-asset-plan', - label: '美术组 / Asset', - agentMode: 'codex_app_server', - configured: true, - accountCredentialState: 'available', - officialRouteLocked: true, - baseUrl: 'https://router.genarrative.world/v1', - model: 'gpt-5.6-sol', - apiKind: 'openai_responses', - reasoningEffort: 'high', - stream: true, - webSearchEnabled: true, - error: null, - }, - { - agentId: 'audio-asset-plan', - label: '音乐组 / SFX', - agentMode: 'codex_app_server', - configured: true, - accountCredentialState: 'available', - officialRouteLocked: true, - baseUrl: 'https://router.genarrative.world/v1', - model: 'gpt-5.6-sol', - apiKind: 'openai_responses', - reasoningEffort: 'high', - stream: false, - webSearchEnabled: false, - error: null, - }, - ], - }; - } - if (command === 'list_local_project_files') { - return { - projectPath: String(args?.projectPath ?? ''), - files: [ - { - path: 'game/index.html', - kind: 'file', - size: 256, - modifiedAt: 1700000001, - }, - { - path: 'assets', - kind: 'directory', - size: 0, - modifiedAt: 1700000000, - }, - { - path: '.agent/checkpoints/checkpoint-main/manifest.json', - kind: 'file', - size: 96, - modifiedAt: 1700000002, - }, - ], - }; - } - if (command === 'build_local_project_index') { - return { - projectPath: String(args?.projectPath ?? ''), - indexPath: '.agent/project.index.json', - fileCount: 2, - totalBytes: 384, - files: [ - { path: 'game/index.html', size: 256, checksum: 'fnv1a64:game' }, - { - path: 'assets/uploads/hero.png', - size: 128, - checksum: 'fnv1a64:hero', - }, - ], - }; - } - if (command === 'read_local_game_memory') { - const scope = String(args?.scope ?? 'long'); - if (scope === 'short') { - return { - scope, - path: 'memory/session.md', - content: '# 短期记忆\n- 本轮偏动作反馈\n', - exists: true, - }; - } - if (scope === 'blackboard') { - return { - scope, - path: 'memory/blackboard.md', - content: '# 项目黑板\n- 跨 agent 共享约束\n', - exists: true, - }; - } - return { - scope, - path: 'memory/project.md', - content: '# 项目长期记忆\n- 保留厨房主题\n', - exists: true, - }; - } - if (command === 'read_local_agent_memory') { - return { - taskId: String(args?.taskId ?? ''), - path: 'memory/agents/art/asset.md', - content: '# 美术私有记忆\n- 使用厨房像素素材\n', - exists: true, - }; - } - if (command === 'read_local_project_file') { - if (args?.relativePath === 'game/index.html') { - return { - path: 'game/index.html', - absolutePath: `${String(args?.projectPath ?? '')}/game/index.html`, - content: '', - }; - } - if (args?.relativePath === 'assets/uploads/hero.txt') { - return { - path: 'assets/uploads/hero.txt', - absolutePath: `${String(args?.projectPath ?? '')}/assets/uploads/hero.txt`, - content: 'hero asset', - }; - } - if ( - args?.relativePath === - '.agent/checkpoints/checkpoint-main/manifest.json' - ) { - return { - path: '.agent/checkpoints/checkpoint-main/manifest.json', - absolutePath: `${String(args?.projectPath ?? '')}/.agent/checkpoints/checkpoint-main/manifest.json`, - content: JSON.stringify({ - checkpointId: 'checkpoint-main', - fileCount: 3, - totalBytes: 256, - createdAt: 1700000002, - }), - }; - } - return { - path: String(args?.relativePath ?? ''), - absolutePath: `${String(args?.projectPath ?? '')}/${String(args?.relativePath ?? '')}`, - content: JSON.stringify(trace), - }; - } - if (command === 'create_local_project_checkpoint') { - return { - checkpointId: 'checkpoint-main', - checkpointPath: '.agent/checkpoints/checkpoint-main', - fileCount: 3, - totalBytes: 256, - }; - } - if (command === 'export_local_project_package') { - return { - packagePath: `${String(args?.projectPath ?? '')}/exports/playtest-package-unit.zip`, - packageRelativePath: 'exports/playtest-package-unit.zip', - fileCount: 4, - totalBytes: 512, - }; - } - if (command === 'list_local_project_export_packages') { - return { - projectPath: String(args?.projectPath ?? ''), - packages: [ - { - packagePath: `${String(args?.projectPath ?? '')}/exports/playtest-package-002.zip`, - packageRelativePath: 'exports/playtest-package-002.zip', - totalBytes: 2048, - modifiedAt: 1700000002000, - }, - { - packagePath: `${String(args?.projectPath ?? '')}/exports/playtest-package-001.zip`, - packageRelativePath: 'exports/playtest-package-001.zip', - totalBytes: 1024, - modifiedAt: 1700000001000, - }, - ], - }; - } - throw new Error(`unexpected invoke ${command}`); - }, - ); - - return invoke; -} - -export type PreviewShortcutInvoke = ReturnType< - typeof createPreviewShortcutInvoke ->; diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/message-bubble.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/message-bubble.ts deleted file mode 100644 index 60bd9e43b..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/message-bubble.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function messageBubble(element: Element): HTMLElement { - const bubble = element.closest('.message'); - if (!bubble) { - throw new Error('Expected chat message bubble'); - } - return bubble; -} diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts deleted file mode 100644 index cb2e23ae9..000000000 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/run-history.suite.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - composerText, - createGameCreationAppManifest, - createGameCreationAppSeedTasks, - emptyProjectPolicy, - expect, - fireEvent, - GAME_CREATION_AGENT_RUN_SCHEMA_VERSION, - type GameCreationAgentRunTrace, - it, - renderAppAt, - screen, - submitChat, - vi, - waitFor, -} from '../harness'; - -export function registerProjectRunHistoryTests() { - it('keeps run history out of the regular project chat window', async () => { - const manifest = createGameCreationAppManifest( - 'local-project-draft', - '未命名游戏原型', - ); - const trace = { - schemaVersion: GAME_CREATION_AGENT_RUN_SCHEMA_VERSION, - runId: 'run-hidden-from-chat', - commandId: 'game.generate_draft', - status: 'passed', - lifecycleStatus: 'done', - passes: 1, - maxPasses: 3, - toolCallCount: 0, - maxToolCalls: 128, - stopReason: 'evaluator-passed', - goal: '做一个厨房弹幕游戏', - coordination: 'Planner -> Generator', - steps: [], - artifacts: [], - taskGraph: { - goal: '做一个厨房弹幕游戏', - readyTaskIds: [], - activeTaskIds: [], - carriedTaskIds: [], - repairFocus: [], - repairRoutes: [], - tasks: createGameCreationAppSeedTasks(), - }, - passPlans: [], - nextStep: 'preview', - error: null, - updatedAt: 1, - } satisfies GameCreationAgentRunTrace; - const invoke = vi.fn( - async (command: string, args?: Record) => { - if (command === 'append_local_permission_log') { - return {}; - } - if (command === 'init_local_game_project') { - const projectPath = String(args?.projectPath ?? ''); - return { - projectPath, - manifestPath: `${projectPath}/.agent/manifest.json`, - manifest, - }; - } - if (command === 'read_project_permission_policy') { - return emptyProjectPolicy(); - } - if (command === 'chat_with_game_creator_agent') { - return { - replyText: `主聊天回复:${String(args?.prompt ?? '')}`, - }; - } - if (command === 'read_local_conversation') { - return { - path: '/tmp/authorized-game/.agent/conversations/project.jsonl', - agentId: null, - messages: [], - }; - } - if (command === 'list_local_project_files') { - return { - projectPath: String(args?.projectPath ?? ''), - files: [ - { - path: '.agent/runs/run-hidden-from-chat.json', - kind: 'file', - size: 100, - }, - ], - }; - } - if (command === 'read_local_project_file') { - return { - path: String(args?.relativePath ?? ''), - absolutePath: `${String(args?.projectPath ?? '')}/${String( - args?.relativePath ?? '', - )}`, - content: JSON.stringify(trace), - }; - } - throw new Error(`unexpected invoke ${command}`); - }, - ); - window.__TAURI__ = { core: { invoke } }; - renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game'); - - await waitFor(() => { - expect(invoke).toHaveBeenCalledWith('list_local_project_files', { - projectPath: '/tmp/authorized-game', - }); - }); - await waitFor(() => { - expect(invoke).toHaveBeenCalledWith('read_local_project_file', { - projectPath: '/tmp/authorized-game', - relativePath: '.agent/runs/run-hidden-from-chat.json', - commandId: 'agent.trace_read', - }); - }); - expect(screen.queryByLabelText('最近 run')).toBeNull(); - expect(screen.queryByText(/run-hidden-from-chat/)).toBeNull(); - - const runReadCountBeforeRunsCommand = invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ).length; - await submitChat('/runs'); - expect(await screen.findByText(/Run 历史读取命令:/)).not.toBeNull(); - expect( - screen.getByText( - /run-hidden-from-chat .* \.agent\/runs\/run-hidden-from-chat\.json .*:\/read \.agent\/runs\/run-hidden-from-chat\.json/, - ), - ).not.toBeNull(); - fireEvent.click(screen.getByRole('button', { name: '读取首个历史 Run' })); - expect(await composerText()).toBe( - '/read .agent/runs/run-hidden-from-chat.json', - ); - expect( - invoke.mock.calls.filter( - ([command]) => command === 'read_local_project_file', - ), - ).toHaveLength(runReadCountBeforeRunsCommand); - }); -}