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:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user