Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/main.rs
T
AIGameCreator App 7afec12958 拆分游戏创作壳 Tauri 入口
拆出 CLI、命令、配置、Agent、素材、项目、预览和窗口模块
拆出 Rust 单测并保留 main.rs 作为 Tauri 薄入口
扩展壳配置与原生壳门禁的 Rust 源码扫描范围
同步 AI 游戏创作 App 技术方案和决策记录
2026-07-04 16:25:45 +08:00

963 lines
28 KiB
Rust

#![cfg_attr(all(not(dev), target_os = "windows"), windows_subsystem = "windows")]
use std::collections::BTreeMap;
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Mutex, OnceLock};
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use platform_agent::{
build_game_creation_seed_task_graph, plan_game_creation_agent_pass,
route_game_creation_repair_issues,
};
use platform_llm::{
LlmApiKind, LlmClient, LlmConfig, LlmMessage, LlmProvider, LlmRunRequest,
DEFAULT_RETRY_BACKOFF_MS,
};
use reqwest::header;
use serde::{Deserialize, Serialize};
use shared_contracts::game_creation_app::{
new_game_creation_app_manifest, new_game_creation_app_seed_tasks,
GameCreationAgentArtifactTrace, GameCreationAgentCapabilityDescriptor,
GameCreationAgentPassPlanTrace, GameCreationAgentRepairRouteTrace, GameCreationAgentRunStep,
GameCreationAgentRunTaskGraphTrace, GameCreationAgentRunTrace, GameCreationAgentToolCallTrace,
GameCreationAppAgentGroup, GameCreationAppAssetManifestEntry, GameCreationAppAssetSource,
GameCreationAppAssetSourceKind, GameCreationAppCommandRunState,
GameCreationAppCommandRunStatus, GameCreationAppLimitedRunCommandDescriptor,
GameCreationAppManifest, GameCreationAppPermission, GameCreationAppPreviewState,
GameCreationAppPreviewStatus, GameCreationAppTaskState, GameCreationAppTaskStatus,
GAME_CREATION_AGENT_CAPABILITIES, GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
GAME_CREATION_AGENT_TOOL_CALL_MAX, GAME_CREATION_APP_COMMANDS,
GAME_CREATION_APP_LIMITED_RUN_COMMANDS,
};
use tauri::{Emitter, Manager};
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_opener::OpenerExt;
// 调试落盘模块(保存 LLM 原始输出 / 失败输入,排查截断、空返回等)放在 debug_drafts.rs。
// 用 #[cfg] 编译期门控:仅开发(debug)且非测试构建编入;生产 release 与 cargo test 下整体剔除。
mod agent;
mod assets;
mod cli;
mod commands;
mod config;
#[cfg(all(debug_assertions, not(test)))]
mod debug;
mod preview;
mod project;
mod windows;
use agent::*;
use assets::*;
use cli::*;
use commands::*;
use config::*;
use preview::*;
use project::*;
use windows::*;
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct InitLocalProjectResult {
project_path: String,
manifest_path: String,
manifest: GameCreationAppManifest,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectDirectoryStatus {
project_path: String,
exists: bool,
is_directory: bool,
is_game_creator_project: bool,
project_name: Option<String>,
manifest_error: Option<String>,
recent_run_status: Option<String>,
recent_run_stop_reason: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalPreviewResult {
url: String,
port: u16,
root: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalPreviewStatus {
status: String,
url: Option<String>,
port: Option<u16>,
root: Option<String>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GenerateLocalGameDraftResult {
project_path: String,
game_index_path: String,
design_path: String,
short_memory_path: String,
long_memory_path: String,
manifest: GameCreationAppManifest,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorAgentProgressEvent {
project_path: String,
stage: String,
message: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorLlmConfigStatus {
configured: bool,
api_key_present: bool,
base_url: Option<String>,
model: Option<String>,
api_kind: String,
stream: bool,
error: Option<String>,
agents: Vec<GameCreatorAgentLlmConfigStatus>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorAgentLlmConfigStatus {
agent_id: String,
label: String,
configured: bool,
api_key_present: bool,
base_url: Option<String>,
model: Option<String>,
api_kind: String,
stream: bool,
error: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorAppConfigFile {
llm: Option<GameCreatorLlmConfigFile>,
agent_llm: Option<BTreeMap<String, GameCreatorLlmConfigFile>>,
editor_api: Option<GameCreatorEditorApiConfigFile>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorLlmConfigFile {
#[serde(skip_serializing_if = "Option::is_none")]
api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
api_kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
request_timeout_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
max_retries: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
retry_backoff_ms: Option<u64>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorEditorApiConfigFile {
base_url: Option<String>,
api_key: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorAppConfig {
llm: GameCreatorLlmConfig,
#[serde(default)]
agent_llm: BTreeMap<String, GameCreatorLlmConfigFile>,
editor_api: GameCreatorEditorApiConfig,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorLlmConfig {
api_key: String,
base_url: String,
model: String,
api_kind: String,
stream: bool,
request_timeout_ms: u64,
max_retries: u32,
retry_backoff_ms: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorEditorApiConfig {
base_url: String,
api_key: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorAppConfigView {
path: String,
config: GameCreatorAppConfig,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRunControlResult {
run_id: String,
status: String,
lifecycle_status: String,
next_step: String,
message: String,
activity_path: String,
output_path: String,
context_bundle_path: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct UploadLocalAssetResult {
id: String,
local_path: String,
absolute_path: String,
manifest_path: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ImportCanvasExportResult {
import_root: String,
metadata_path: String,
imported_count: usize,
assets: Vec<UploadLocalAssetResult>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct SyncCanvasProjectAssetsResult {
canvas_project_id: String,
import_root: String,
imported_count: usize,
assets: Vec<UploadLocalAssetResult>,
}
#[derive(Debug, Eq, PartialEq)]
struct GeneratedPlatformArtAsset {
asset: UploadLocalAssetResult,
resource_id: Option<String>,
asset_object_id: Option<String>,
task_id: Option<String>,
model: Option<String>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct OpenCanvasProjectResult {
url: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CanvasExportMetadata {
project_title: String,
exported_at: String,
layers: Vec<CanvasExportLayerMetadata>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CanvasExportLayerMetadata {
title: String,
file: Option<String>,
visible: CanvasExportVisibleMetadata,
export_error: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CanvasExportVisibleMetadata {
#[serde(rename = "type")]
layer_type: String,
model: String,
task: String,
object: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalGameMemoryResult {
scope: String,
path: String,
content: String,
exists: bool,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalAgentMemoryResult {
task_id: String,
path: String,
content: String,
exists: bool,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LimitedLocalCommandResult {
command_id: String,
status: String,
output: String,
log_path: String,
updated_at: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectFileEntry {
path: String,
kind: String,
size: u64,
modified_at: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ListLocalProjectFilesResult {
project_path: String,
files: Vec<LocalProjectFileEntry>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectFileResult {
path: String,
absolute_path: String,
content: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectFileMutationResult {
path: String,
absolute_path: String,
deleted: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalConversationMessage {
role: String,
content: String,
agent_id: Option<String>,
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalConversationMessageRecord {
schema_version: String,
role: String,
content: String,
agent_id: Option<String>,
updated_at: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalConversationResult {
path: String,
agent_id: Option<String>,
messages: Vec<LocalConversationMessageRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ProjectPermissionPolicy {
denied_commands: Vec<String>,
confirm_commands: Vec<String>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ProjectPermissionPolicyView {
path: String,
policy: ProjectPermissionPolicy,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectIndexedFile {
path: String,
size: u64,
checksum: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectIndexResult {
project_path: String,
index_path: String,
file_count: usize,
total_bytes: u64,
files: Vec<LocalProjectIndexedFile>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectCheckpointResult {
checkpoint_id: String,
checkpoint_path: String,
file_count: usize,
total_bytes: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectExportPackageResult {
package_path: String,
package_relative_path: String,
file_count: usize,
total_bytes: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectExportPackageSummary {
package_path: String,
package_relative_path: String,
total_bytes: u64,
modified_at: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectExportPackagesResult {
project_path: String,
packages: Vec<LocalProjectExportPackageSummary>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectDiffEntry {
path: String,
status: String,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectDiffResult {
checkpoint_id: String,
added: Vec<LocalProjectDiffEntry>,
changed: Vec<LocalProjectDiffEntry>,
deleted: Vec<LocalProjectDiffEntry>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectRestoreResult {
checkpoint_id: String,
restored_count: usize,
deleted_count: usize,
}
const DEFAULT_GAME_INDEX_HTML: &str = r#"<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Genarrative Game Draft</title>
<style>
body { margin: 0; display: grid; min-height: 100vh; place-items: center; background: #101827; color: #d9e7ff; font: 16px system-ui, sans-serif; }
main { width: min(720px, calc(100vw - 32px)); }
</style>
</head>
<body><main>还没有生成游戏。回到聊天输入创意并确认生成后,这里会写入可试玩原型。</main></body>
</html>
"#;
const DEFAULT_EDITOR_BASE_URL: &str = "http://127.0.0.1:3000";
const GAME_CREATOR_CONFIG_FILE_NAME: &str = "game-creator.config.json";
const GAME_CREATOR_LOCAL_CONFIG_FILE_NAME: &str = "game-creator.config.local.json";
const DEFAULT_GAME_CREATOR_LLM_BASE_URL: &str = "https://api.openai.com/v1";
const DEFAULT_GAME_CREATOR_LLM_MODEL: &str = "gpt-4.1";
const DEFAULT_GAME_CREATOR_LLM_API_KIND: &str = "openai_responses";
const DEFAULT_CANVAS_SYNC_API_BASE_URL: &str = "http://127.0.0.1:8082";
const DEFAULT_GAME_CREATOR_APP_CONFIG_JSON: &str = include_str!("../../game-creator.config.json");
const GAME_CREATOR_LLM_MAX_OUTPUT_TOKENS: u32 = 320000;
const GAME_CREATOR_PLANNER_MAX_OUTPUT_TOKENS: u32 = 900;
const GAME_CREATOR_ROLE_AGENT_MAX_OUTPUT_TOKENS: u32 = 1200;
const GAME_CREATOR_REQUIRED_LLM_AGENT_IDS: [&str; 2] = ["planner", "generator"];
const MIN_GAME_CREATOR_LLM_REQUEST_TIMEOUT_MS: u64 = 1_000;
const GAME_CREATOR_LLM_REQUEST_TIMEOUT_MS: u64 = 180_000;
const GAME_CREATOR_AGENT_LOOP_MAX_PASSES: u8 = 3;
const GAME_CREATOR_AGENT_TOOL_CALL_MAX: u16 = GAME_CREATION_AGENT_TOOL_CALL_MAX;
const GAME_CREATOR_AGENT_RUN_HISTORY_MAX_COUNT: usize = 100;
const GAME_CREATOR_AGENT_DB_SCHEMA_VERSION: &str = "game-creator-agent-db.v1";
const PROJECT_BLACKBOARD_MEMORY_PATH: &str = "memory/blackboard.md";
const PROJECT_PERMISSION_POLICY_PATH: &str = ".agent/policy.json";
const PROJECT_INDEX_PATH: &str = ".agent/project.index.json";
const PROJECT_WRITE_LOCK_PATH: &str = ".agent/project.lock";
const LOCAL_CONVERSATION_SCHEMA_VERSION: &str = "game-creator-conversation.v1";
const GAME_CREATOR_CONVERSATION_CONTEXT_MAX_MESSAGES: usize = 12;
const MAX_CANVAS_EXPORT_FILES: usize = 500;
const MAX_CANVAS_EXPORT_BYTES: u64 = 512 * 1024 * 1024;
const MAX_PROJECT_EXPORT_PACKAGE_FILES: usize = 1200;
const MAX_PROJECT_EXPORT_PACKAGE_BYTES: u64 = 512 * 1024 * 1024;
const GAME_CREATOR_AGENT_ARTIFACT_PATHS: [&str; 15] = [
".agent/agent.db",
".agent/spec.md",
".agent/findings.md",
".agent/logs/agent.log",
".agent/logs/command.log",
".agent/logs/preview.log",
"memory/session.md",
"memory/project.md",
PROJECT_BLACKBOARD_MEMORY_PATH,
"game/game_design.md",
"game/balance.json",
"assets/manifest.art.json",
"assets/manifest.audio.json",
"exports/README.md",
"game/index.html",
];
static GAME_CREATOR_RUNTIME_CONFIG_DIR: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
impl Default for GameCreatorAppConfig {
fn default() -> Self {
Self {
llm: GameCreatorLlmConfig::default(),
agent_llm: BTreeMap::new(),
editor_api: GameCreatorEditorApiConfig::default(),
}
}
}
impl Default for GameCreatorLlmConfig {
fn default() -> Self {
Self {
api_key: String::new(),
base_url: DEFAULT_GAME_CREATOR_LLM_BASE_URL.to_string(),
model: DEFAULT_GAME_CREATOR_LLM_MODEL.to_string(),
api_kind: DEFAULT_GAME_CREATOR_LLM_API_KIND.to_string(),
stream: false,
request_timeout_ms: GAME_CREATOR_LLM_REQUEST_TIMEOUT_MS,
max_retries: 0,
retry_backoff_ms: DEFAULT_RETRY_BACKOFF_MS,
}
}
}
impl Default for GameCreatorEditorApiConfig {
fn default() -> Self {
Self {
base_url: DEFAULT_CANVAS_SYNC_API_BASE_URL.to_string(),
api_key: String::new(),
}
}
}
#[derive(Clone, Copy, Debug)]
struct AgentRoleDefinition {
id: &'static str,
role: &'static str,
task_id: &'static str,
tool_id: &'static str,
brief_path_name: &'static str,
}
#[derive(Clone, Copy, Debug)]
struct AgentGroupDefinition {
id: &'static str,
label: &'static str,
role: &'static str,
brief_path_name: &'static str,
roles: &'static [AgentRoleDefinition],
}
static DESIGN_AGENT_ROLES: [AgentRoleDefinition; 2] = [
AgentRoleDefinition {
id: "director",
role: "Director",
task_id: "design-director",
tool_id: "agent.role.brief.design.director",
brief_path_name: "director.md",
},
AgentRoleDefinition {
id: "gameplay",
role: "Gameplay",
task_id: "design-foundation",
tool_id: "agent.role.brief.design.gameplay",
brief_path_name: "gameplay.md",
},
];
static BALANCE_AGENT_ROLES: [AgentRoleDefinition; 2] = [
AgentRoleDefinition {
id: "director",
role: "Director",
task_id: "balance-director",
tool_id: "agent.role.brief.balance.director",
brief_path_name: "director.md",
},
AgentRoleDefinition {
id: "difficulty",
role: "Difficulty",
task_id: "balance-seed",
tool_id: "agent.role.brief.balance.difficulty",
brief_path_name: "difficulty.md",
},
];
static ART_AGENT_ROLES: [AgentRoleDefinition; 3] = [
AgentRoleDefinition {
id: "director",
role: "Director",
task_id: "art-director",
tool_id: "agent.role.brief.art.director",
brief_path_name: "director.md",
},
AgentRoleDefinition {
id: "asset",
role: "Asset",
task_id: "art-asset-plan",
tool_id: "agent.role.brief.art.asset",
brief_path_name: "asset.md",
},
AgentRoleDefinition {
id: "polish",
role: "Polish",
task_id: "art-polish",
tool_id: "agent.role.brief.art.polish",
brief_path_name: "polish.md",
},
];
static AUDIO_AGENT_ROLES: [AgentRoleDefinition; 2] = [
AgentRoleDefinition {
id: "director",
role: "Director",
task_id: "audio-director",
tool_id: "agent.role.brief.audio.director",
brief_path_name: "director.md",
},
AgentRoleDefinition {
id: "sfx",
role: "SFX",
task_id: "audio-asset-plan",
tool_id: "agent.role.brief.audio.sfx",
brief_path_name: "sfx.md",
},
];
static CODE_AGENT_ROLES: [AgentRoleDefinition; 5] = [
AgentRoleDefinition {
id: "director",
role: "Director",
task_id: "code-director",
tool_id: "agent.role.brief.code.director",
brief_path_name: "director.md",
},
AgentRoleDefinition {
id: "code",
role: "Code",
task_id: "code-prototype",
tool_id: "agent.role.brief.code.code",
brief_path_name: "code.md",
},
AgentRoleDefinition {
id: "review",
role: "Review",
task_id: "quality-review",
tool_id: "agent.role.brief.code.review",
brief_path_name: "review.md",
},
AgentRoleDefinition {
id: "preview",
role: "Preview",
task_id: "preview-readiness",
tool_id: "agent.role.brief.code.preview",
brief_path_name: "preview.md",
},
AgentRoleDefinition {
id: "playtest",
role: "Playtest",
task_id: "preview-playtest",
tool_id: "agent.role.brief.code.playtest",
brief_path_name: "playtest.md",
},
];
static PUBLISHING_AGENT_ROLES: [AgentRoleDefinition; 2] = [
AgentRoleDefinition {
id: "director",
role: "Director",
task_id: "publish-strategy",
tool_id: "agent.role.brief.publishing.director",
brief_path_name: "director.md",
},
AgentRoleDefinition {
id: "publish",
role: "Publish",
task_id: "publish-package",
tool_id: "agent.role.brief.publishing.publish",
brief_path_name: "publish.md",
},
];
const GAME_CREATOR_AGENT_GROUP_DEFINITIONS: [AgentGroupDefinition; 6] = [
AgentGroupDefinition {
id: "design",
label: "策划组",
role: "Director + Gameplay",
brief_path_name: "design.md",
roles: &DESIGN_AGENT_ROLES,
},
AgentGroupDefinition {
id: "balance",
label: "数值组",
role: "Director + Difficulty",
brief_path_name: "balance.md",
roles: &BALANCE_AGENT_ROLES,
},
AgentGroupDefinition {
id: "art",
label: "美术组",
role: "Director + Asset + Polish",
brief_path_name: "art.md",
roles: &ART_AGENT_ROLES,
},
AgentGroupDefinition {
id: "audio",
label: "音乐组",
role: "Director + SFX",
brief_path_name: "audio.md",
roles: &AUDIO_AGENT_ROLES,
},
AgentGroupDefinition {
id: "code",
label: "程序组",
role: "Director + Code + Review + Preview + Playtest",
brief_path_name: "code.md",
roles: &CODE_AGENT_ROLES,
},
AgentGroupDefinition {
id: "publishing",
label: "运营组",
role: "Director + Publish",
brief_path_name: "publishing.md",
roles: &PUBLISHING_AGENT_ROLES,
},
];
struct GameCreatorLlmAgentStatusDefinition {
agent_id: String,
label: String,
}
fn game_creator_llm_agent_status_definitions() -> Vec<GameCreatorLlmAgentStatusDefinition> {
let mut agents = vec![
GameCreatorLlmAgentStatusDefinition {
agent_id: "planner".to_string(),
label: "Planner".to_string(),
},
GameCreatorLlmAgentStatusDefinition {
agent_id: "orchestrator".to_string(),
label: "Orchestrator".to_string(),
},
GameCreatorLlmAgentStatusDefinition {
agent_id: "generator".to_string(),
label: "Generator".to_string(),
},
GameCreatorLlmAgentStatusDefinition {
agent_id: "evaluator".to_string(),
label: "Evaluator".to_string(),
},
];
for group in GAME_CREATOR_AGENT_GROUP_DEFINITIONS {
for role in group.roles {
if agents.iter().any(|agent| agent.agent_id == role.task_id) {
continue;
}
agents.push(GameCreatorLlmAgentStatusDefinition {
agent_id: role.task_id.to_string(),
label: format!("{} / {}", group.label, role.role),
});
}
}
agents
}
#[derive(Clone, Debug)]
struct AgentPassArtifactPaths {
draft_json: String,
design_markdown: String,
balance_json: String,
art_manifest_json: String,
audio_manifest_json: String,
publish_readme: String,
game_html: String,
handoff_markdown: String,
}
#[derive(Clone, Debug)]
struct AgentRoleBrief {
group_definition: AgentGroupDefinition,
role_definition: AgentRoleDefinition,
markdown: String,
relative_path: String,
memory_relative_path: String,
status: String,
tool_id: String,
summary: String,
}
#[derive(Clone, Debug)]
struct AgentGroupBrief {
definition: AgentGroupDefinition,
markdown: String,
relative_path: String,
role_briefs: Vec<AgentRoleBrief>,
}
#[derive(Clone, Debug)]
struct AgentPassAgenda {
relative_path: String,
task_graph_relative_path: String,
active_task_ids: Vec<String>,
carried_task_ids: Vec<String>,
dependency_waves: Vec<Vec<String>>,
repair_focus: Vec<String>,
repair_routes: Vec<GameCreationAgentRepairRouteTrace>,
summary: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct LlmGameDraft {
title: String,
design_markdown: String,
balance: serde_json::Value,
art_manifest: serde_json::Value,
audio_manifest: serde_json::Value,
publish_readme: String,
handoffs: Vec<LlmAgentHandoff>,
game_html: String,
handoff_summary: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct LlmAgentHandoff {
group: String,
role: String,
summary: String,
outputs: Vec<String>,
next: String,
}
#[derive(Clone, Debug)]
struct GameCreatorAgentLoopResult {
run_id: String,
draft: LlmGameDraft,
spec_markdown: String,
findings_markdown: String,
passes: u8,
steps: Vec<GameCreationAgentRunStep>,
}
fn main() {
let args = std::env::args().skip(1).collect::<Vec<_>>();
match parse_cli_command(&args) {
Ok(Some(command)) => {
if let Err(error) = run_cli_command(command) {
eprintln!("agent.run.failed: {error}");
std::process::exit(1);
}
return;
}
Ok(None) => {}
Err(error) => {
eprintln!("{error}");
std::process::exit(1);
}
}
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.manage(PreviewRegistry::default())
.setup(|app| {
configure_game_creator_runtime_config_dir(app.handle())?;
Ok(())
})
.invoke_handler(tauri::generate_handler![
init_local_game_project,
is_local_project_directory_non_empty,
inspect_local_project_directory,
pick_local_project_directory,
pick_local_file,
open_local_project_directory,
control_agent_run,
generate_local_game_draft,
check_game_creator_llm_config,
read_game_creator_app_config,
write_game_creator_app_config,
upload_local_asset,
register_local_asset,
import_canvas_asset,
import_canvas_export,
sync_canvas_project_assets,
generate_platform_art_asset,
open_canvas_project,
get_game_creation_agent_capabilities,
get_limited_local_commands,
run_limited_local_command,
append_local_permission_log,
list_local_project_files,
read_local_project_file,
write_local_project_file,
delete_local_project_file,
read_local_game_memory,
read_local_agent_memory,
write_local_agent_memory,
write_local_game_memory,
delete_local_game_memory,
read_local_conversation,
append_local_conversation_message,
build_local_project_index,
create_local_project_checkpoint,
export_local_project_package,
list_local_project_export_packages,
diff_local_project_checkpoint,
restore_local_project_checkpoint,
read_project_permission_policy,
write_project_permission_policy,
open_game_creator_workspace_window,
open_game_creator_launcher_window,
start_local_game_preview,
open_local_game_preview,
stop_local_game_preview,
get_local_game_preview_status,
get_local_game_manifest
])
.run(tauri::generate_context!())
.expect("failed to run Genarrative AI Game Creator shell");
}
#[cfg(test)]
mod tests;