AI 游戏创作智能体 App v1 最小落地

新增独立 Tauri 壳 apps/ai-game-creator-shell,只保留聊天入口,正式用户窗口不承载游戏预览画面,开发模式通过独立窗口展示任务、文件、记忆、预览和日志

新增游戏创作专业组与种子任务图契约,覆盖策划/美术/程序/数值/音乐/运营 6 组下 15 个组内角色

新增共享契约本地项目 manifest、内置命令权限枚举、run trace schema 和 ready-task 选择器

扩展平台 LLM 支持流式请求,增加 GENARRATIVE_GAME_CREATOR_LLM_STREAM 开关

新增 AI 游戏创作 App 的聊天命令集:/help /capabilities /audit /project /llm-status /status /files /assets /read /tasks /trace /smoke /run /preview /preview-status /preview-open /preview-stop /memory /remember /forget-memory /canvas /sync-canvas-project /import-canvas-asset /import-canvas-export

新增本地项目初始化、权限 gate(pending/confirm/cancel)、文件上传、受限命令白名单 game.static_smoke 和本地 HTTP 预览

新增 Planner / 6 组角色 agent / Generator / Evaluator / ArtifactWriter / Playtest 文件驱动 loop,最多 3 轮返工,结构化 repairRoutes

新增实时 run trace 写入 .agent/run.latest.json 与 .agent/runs/,包含 taskGraph、passPlans 和步级 toolCalls

新增短期/长期记忆 memory/session.md 和 memory/project.md,支持聊天读取/追加/覆盖/删除

新增画板对接:canvas.project_open、canvas.project_sync、canvas.asset_import、canvas.export_import

新增 npm run ai-game-creator-shell:check 开发验收入口,覆盖 typecheck、单元测试、端到端 smoke

新增 check:native-shells 静态守门:release 只登记 main 聊天窗口,CSP 禁止内嵌预览,开发窗口只在 debug 下打开,用户侧预览命令必须调用 open_local_game_preview
This commit is contained in:
AIGameCreator App
2026-06-25 16:13:18 +08:00
parent 2511618add
commit 72a6d635da
37 changed files with 25597 additions and 5 deletions
+105
View File
@@ -22,6 +22,17 @@ const desktopShellConfigCheckSource = fs.readFileSync(
'apps/desktop-shell/scripts/check-config.mjs',
'utf8',
);
const aiGameCreatorShellAppSource = fs.readFileSync(
'apps/ai-game-creator-shell/src/App.tsx',
'utf8',
);
const aiGameCreatorShellTauriConfig = JSON.parse(
fs.readFileSync('apps/ai-game-creator-shell/src-tauri/tauri.conf.json', 'utf8'),
);
const aiGameCreatorShellTauriSource = fs.readFileSync(
'apps/ai-game-creator-shell/src-tauri/src/main.rs',
'utf8',
);
const productionShellScanRoots = [
'apps/mobile-shell',
@@ -1812,6 +1823,16 @@ const steps = [
command: npmCommand,
args: ['run', 'desktop-shell:typecheck'],
},
{
label: 'ai-game-creator-shell-check',
command: npmCommand,
args: ['run', 'ai-game-creator-shell:check'],
},
{
label: 'ai-game-creator-shell-release-build-smoke',
command: npmCommand,
args: ['run', 'ai-game-creator-shell:build', '--', '--no-bundle'],
},
{
label: 'desktop-shell-test',
command: npmCommand,
@@ -1915,6 +1936,87 @@ function assertGeneratedNativeShellArtifactsAreIgnored() {
}
}
function assertAiGameCreatorShellUserDevBoundary() {
const windows = aiGameCreatorShellTauriConfig.app?.windows ?? [];
if (windows.length !== 1 || windows[0]?.label !== 'main') {
throw new Error('AI game creator release shell must register only the main chat window');
}
const releaseCsp = aiGameCreatorShellTauriConfig.app?.security?.csp ?? '';
const devCsp = aiGameCreatorShellTauriConfig.app?.security?.devCsp ?? '';
const releaseFrameSrc =
releaseCsp
.split(';')
.map((directive) => directive.trim())
.find((directive) => directive.startsWith('frame-src ')) ?? '';
const devFrameSrc =
devCsp
.split(';')
.map((directive) => directive.trim())
.find((directive) => directive.startsWith('frame-src ')) ?? '';
if (releaseFrameSrc.includes('http://127.0.0.1:*')) {
throw new Error('AI game creator release shell must not allow embedded local preview frames');
}
if (!devFrameSrc.includes('http://127.0.0.1:*')) {
throw new Error('AI game creator dev shell must keep local preview iframe access for the developer window');
}
for (const snippet of [
'function isDeveloperMode()',
'if (!import.meta.env.DEV)',
"return params.has('dev') || window.location.hash === '#dev';",
"{devMode ? (",
'className="developer-pane"',
'className="chat-pane"',
]) {
if (!aiGameCreatorShellAppSource.includes(snippet)) {
throw new Error(`AI game creator user/dev UI boundary drifted: missing ${snippet}`);
}
}
const previewFrameIndexes = [
...aiGameCreatorShellAppSource.matchAll(/<iframe\b/g),
].map((match) => match.index ?? -1);
const devModeBranchIndex = aiGameCreatorShellAppSource.indexOf('{devMode ? (');
const developerPaneIndex = aiGameCreatorShellAppSource.indexOf('className="developer-pane"');
if (previewFrameIndexes.length !== 1) {
throw new Error('AI game creator user shell must not embed game preview frames');
}
if (
devModeBranchIndex < 0 ||
developerPaneIndex < 0 ||
previewFrameIndexes.some(
(index) => index < devModeBranchIndex || index < developerPaneIndex,
)
) {
throw new Error('AI game creator preview iframe must stay inside the dev-only pane');
}
for (const snippet of [
'async function openPreviewInExternalBrowser(',
"await invoke<LocalPreviewStatus>('open_local_game_preview'",
"return '已交给外部浏览器打开。';",
'const openMessage = await openPreviewInExternalBrowser(',
"appendLocalPermissionLog(\n result.projectPath,\n 'permission.confirm',\n 'project.create',",
]) {
if (!aiGameCreatorShellAppSource.includes(snippet)) {
throw new Error(`AI game creator external preview boundary drifted: missing ${snippet}`);
}
}
for (const snippet of [
'#[cfg(debug_assertions)]\nfn developer_window_url()',
'tauri::WebviewUrl::App(PathBuf::from("index.html?dev"))',
'WebviewWindowBuilder::new(app, "developer", developer_window_url())',
'#[cfg(debug_assertions)]\n open_developer_window(app)?;',
'fn open_local_game_preview(',
'.open_url(&url, None::<&str>)',
]) {
if (!aiGameCreatorShellTauriSource.includes(snippet)) {
throw new Error(`AI game creator developer window boundary drifted: missing ${snippet}`);
}
}
}
function collectProductionShellFiles(entryPath) {
const normalizedPath = entryPath.split(path.sep).join('/');
if (productionShellExcludedPaths.has(normalizedPath)) {
@@ -4345,6 +4447,9 @@ console.log('[check:native-shells] generated-native-shell-artifact-boundary');
assertNoTrackedGeneratedNativeShellArtifacts();
assertGeneratedNativeShellArtifactsAreIgnored();
console.log('[check:native-shells] ai-game-creator-shell-user-dev-boundary');
assertAiGameCreatorShellUserDevBoundary();
console.log('[check:native-shells] production-shell-dev-scaffold-scan');
assertNoProductionShellDevScaffoldTerms();