Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/main.rs
T
AIGameCreator App 9bb048c11b 完善自主构建运行时并提高 Runner 启动容错
完善 Agent Swarm 自主游戏构建、委派协作、验证门和试玩闭环

补齐浏览器验证、CLI 入口、项目状态与运行时测试覆盖

将外部 Agent Runner 冷启动超时提高到 30 秒并增加回归测试

同步真实 E2E 自测脚本、配置检查和项目决策记录
2026-07-21 12:53:27 +08:00

1795 lines
53 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, Arc, 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, LlmMessageContentPart, 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 agent_native_tools;
mod assets;
mod browser;
mod cli;
mod collaboration;
mod command_exec;
mod command_output;
mod command_sandbox;
mod command_sandbox_trampoline;
mod commands;
mod config;
mod context_compaction;
#[cfg(all(debug_assertions, not(test)))]
mod debug;
mod delegation;
mod git_inspect;
mod goal;
mod image_inspect;
mod isolated_agent;
mod mcp;
mod patchset;
mod preview;
mod process_session;
mod process_session_bridge;
mod project;
mod provider_handoff;
mod provider_retry;
mod repository_context;
mod runner;
mod swarm_cli;
mod tool_plan_handoff;
mod user_input;
mod windows;
use agent::*;
use agent_native_tools::*;
use assets::*;
use browser::*;
use cli::*;
use collaboration::*;
use command_exec::*;
use command_output::*;
use command_sandbox::*;
use commands::*;
use config::*;
use context_compaction::*;
use delegation::*;
use git_inspect::*;
use goal::*;
use image_inspect::*;
use isolated_agent::*;
use mcp::*;
use patchset::*;
use preview::*;
use process_session::*;
use project::*;
use repository_context::*;
use runner::*;
use swarm_cli::*;
use user_input::*;
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, Deserialize, 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(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorChatAgentReply {
reply_text: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorRoleAgentChatStreamEvent {
project_path: String,
agent_id: String,
run_id: String,
status: String,
delta_text: String,
accumulated_text: String,
finish_reason: Option<String>,
session_id: Option<String>,
runtime_status: Option<String>,
runtime_phase: Option<String>,
runtime_summary: Option<String>,
runtime_state: Option<AgentRuntimeState>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeState {
#[serde(default)]
schema_version: String,
#[serde(default)]
agent_id: String,
#[serde(default)]
task_id: String,
#[serde(default)]
session_id: String,
#[serde(default)]
run_id: String,
#[serde(default)]
source: String,
#[serde(default = "default_agent_runtime_run_profile")]
run_profile: String,
#[serde(default)]
run_profile_binding_fingerprint: String,
#[serde(default)]
parent_agent_id: Option<String>,
#[serde(default)]
parent_run_id: Option<String>,
#[serde(default)]
delegation_id: Option<String>,
#[serde(default)]
status: String,
#[serde(default)]
phase: String,
#[serde(default)]
current_task: String,
#[serde(default)]
current_goal: String,
#[serde(default)]
goal_id: Option<String>,
#[serde(default)]
goal_revision: u64,
#[serde(default)]
goal_status: Option<String>,
#[serde(default)]
goal_outcome: Option<String>,
#[serde(default)]
goal_constraints: Vec<String>,
#[serde(default)]
goal_verification: Vec<String>,
#[serde(default)]
current_action: String,
#[serde(default)]
waiting_on: String,
#[serde(default)]
next_step: String,
#[serde(default)]
loop_iteration: u32,
#[serde(default)]
max_loop_iterations: u32,
#[serde(default)]
tool_action_budget: u32,
#[serde(default)]
plan_revision: u64,
#[serde(default)]
plan_explanation: String,
#[serde(default)]
plan: Vec<String>,
#[serde(default)]
plan_steps: Vec<AgentRuntimePlanStep>,
#[serde(default)]
active_plan_step_index: Option<u32>,
#[serde(default)]
observations: Vec<String>,
#[serde(default)]
recent_tool_calls: Vec<AgentRuntimeToolCallRecord>,
#[serde(default)]
pending_tool_action: Option<AgentRuntimePendingToolActionSummary>,
#[serde(default)]
task_queue: AgentRuntimeTaskQueueSummary,
#[serde(default)]
allowed_tools: Vec<String>,
#[serde(default)]
tool_policy: AgentRuntimeToolPolicySnapshot,
#[serde(default)]
applied_steer_cursor: u64,
#[serde(default)]
applied_steer_refs: Vec<AgentRuntimeSteerRef>,
#[serde(default)]
queued_steer_count: u32,
#[serde(default)]
context_usage: AgentRuntimeContextUsage,
#[serde(default)]
last_response: Option<String>,
#[serde(default)]
error: Option<String>,
#[serde(default)]
updated_at: u64,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeContextUsage {
#[serde(default)]
estimated_input_tokens: u64,
#[serde(default)]
auto_compact_token_limit: u64,
#[serde(default)]
last_prompt_tokens: Option<u64>,
#[serde(default)]
last_completion_tokens: Option<u64>,
#[serde(default)]
last_total_tokens: Option<u64>,
#[serde(default)]
compaction_revision: u64,
#[serde(default)]
compaction_count: u64,
#[serde(default)]
last_compaction_trigger: Option<String>,
#[serde(default)]
last_compacted_at: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeSteerRef {
#[serde(default)]
steer_id: String,
#[serde(default)]
sequence: u64,
#[serde(default)]
message_id: String,
#[serde(default)]
instruction_sha256: String,
#[serde(default)]
content_chars: u32,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeToolPolicySnapshot {
#[serde(default = "default_agent_runtime_run_profile")]
run_profile: String,
#[serde(default)]
run_profile_binding_fingerprint: String,
#[serde(default)]
allowed_tools: Vec<String>,
#[serde(default)]
auto_tools: Vec<String>,
#[serde(default)]
confirm_tools: Vec<String>,
#[serde(default)]
denied_tools: Vec<String>,
#[serde(default)]
updated_at: u64,
}
impl Default for AgentRuntimeToolPolicySnapshot {
fn default() -> Self {
Self {
run_profile: default_agent_runtime_run_profile(),
run_profile_binding_fingerprint: String::new(),
allowed_tools: Vec::new(),
auto_tools: Vec::new(),
confirm_tools: Vec::new(),
denied_tools: Vec::new(),
updated_at: 0,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeToolCallRecord {
#[serde(default)]
action_id: Option<String>,
#[serde(default)]
tool: String,
#[serde(default)]
status: String,
#[serde(default)]
action_fingerprint: Option<String>,
#[serde(default)]
input_summary: Option<String>,
#[serde(default)]
reason: Option<String>,
#[serde(default)]
summary: String,
#[serde(default)]
detail: Option<String>,
#[serde(default)]
updated_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimePendingToolActionSummary {
#[serde(default)]
action_id: String,
#[serde(default)]
action_fingerprint: String,
#[serde(default)]
tool: String,
#[serde(default)]
input_summary: Option<String>,
#[serde(default)]
reason: Option<String>,
#[serde(default)]
requested_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimePlanStep {
#[serde(default)]
index: u32,
#[serde(default)]
title: String,
#[serde(default)]
status: String,
#[serde(default)]
detail: Option<String>,
#[serde(default)]
updated_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeTaskQueueSummary {
#[serde(default)]
total: u32,
#[serde(default)]
pending: u32,
#[serde(default)]
running: u32,
#[serde(default)]
waiting_for_confirmation: u32,
#[serde(default)]
waiting_for_user_input: u32,
#[serde(default)]
paused: u32,
#[serde(default)]
cancelled: u32,
#[serde(default)]
completed: u32,
#[serde(default)]
failed: u32,
#[serde(default)]
latest_run_id: Option<String>,
#[serde(default)]
updated_at: u64,
}
impl Default for AgentRuntimeTaskQueueSummary {
fn default() -> Self {
Self {
total: 0,
pending: 0,
running: 0,
waiting_for_confirmation: 0,
waiting_for_user_input: 0,
paused: 0,
cancelled: 0,
completed: 0,
failed: 0,
latest_run_id: None,
updated_at: 0,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeEvent {
#[serde(default)]
schema_version: String,
#[serde(default)]
agent_id: String,
#[serde(default)]
task_id: String,
#[serde(default)]
session_id: String,
#[serde(default)]
run_id: String,
#[serde(default)]
source: String,
#[serde(default)]
event_type: String,
#[serde(default)]
action_id: Option<String>,
#[serde(default)]
status: String,
#[serde(default)]
phase: String,
#[serde(default)]
summary: String,
#[serde(default)]
detail: Option<String>,
#[serde(default)]
updated_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct AgentRuntimeResponseStream {
schema_version: String,
agent_id: String,
task_id: String,
session_id: String,
run_id: String,
request_kind: String,
request_slot: String,
applied_steer_cursor: u64,
response_revision: u64,
sequence: u64,
status: String,
accumulated_text: String,
finish_reason: Option<String>,
started_at: u64,
updated_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeTaskRecord {
#[serde(default)]
schema_version: String,
#[serde(default)]
agent_id: String,
#[serde(default)]
task_id: String,
#[serde(default)]
session_id: String,
#[serde(default)]
run_id: String,
#[serde(default)]
source: String,
#[serde(default = "default_agent_runtime_run_profile")]
run_profile: String,
#[serde(default)]
run_profile_binding_fingerprint: String,
#[serde(default)]
parent_agent_id: Option<String>,
#[serde(default)]
parent_run_id: Option<String>,
#[serde(default)]
delegation_id: Option<String>,
#[serde(default)]
goal_id: Option<String>,
#[serde(default)]
goal_revision: u64,
#[serde(default)]
goal_status: Option<String>,
#[serde(default)]
task: String,
#[serde(default)]
status: String,
#[serde(default)]
phase: String,
#[serde(default)]
current_action: String,
#[serde(default)]
terminal_detail: Option<String>,
#[serde(default)]
error: Option<String>,
#[serde(default)]
updated_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct AgentGoalRecord {
schema_version: String,
project_id: String,
goal_id: String,
agent_id: String,
session_id: String,
run_id: String,
revision: u64,
status: String,
outcome: String,
constraints: Vec<String>,
verification: Vec<String>,
#[serde(default)]
completion_evidence: Vec<String>,
#[serde(default)]
response_fingerprint: Option<String>,
created_at: u64,
#[serde(default)]
pause_requested_at: Option<u64>,
#[serde(default)]
paused_at: Option<u64>,
#[serde(default)]
completed_at: Option<u64>,
#[serde(default)]
cleared_at: Option<u64>,
#[serde(default)]
error: Option<String>,
updated_at: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentGoalMutationResult {
goal: AgentGoalRecord,
runtime: AgentRuntimeResult,
provider_interrupted: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeResult {
state: AgentRuntimeState,
#[serde(skip_serializing_if = "Option::is_none")]
accepted_run_id: Option<String>,
session_path: String,
event_path: String,
task_path: String,
task_queue: AgentRuntimeTaskQueueSummary,
recent_events: Vec<AgentRuntimeEvent>,
recent_tasks: Vec<AgentRuntimeTaskRecord>,
response_stream: Option<AgentRuntimeResponseStream>,
user_input_request: Option<AgentRuntimeUserInputRequestView>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeSteerResult {
runtime: AgentRuntimeResult,
steer_id: String,
sequence: u64,
status: String,
provider_interrupted: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorAgentRuntimeUpdateEvent {
project_path: String,
agent_id: String,
run_id: String,
status: String,
phase: String,
runtime: AgentRuntimeResult,
}
#[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,
reasoning_effort: String,
stream: bool,
web_search_enabled: bool,
context_window_tokens: u64,
auto_compact_token_limit: u64,
tool_output_token_limit: u64,
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,
reasoning_effort: String,
stream: bool,
web_search_enabled: bool,
context_window_tokens: u64,
auto_compact_token_limit: u64,
tool_output_token_limit: u64,
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>,
mcp_servers: Option<BTreeMap<String, GameCreatorMcpServerConfig>>,
}
#[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")]
reasoning_effort: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
web_search_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
context_window_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
auto_compact_token_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
tool_output_token_limit: Option<u64>,
#[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,
#[serde(default)]
mcp_servers: BTreeMap<String, GameCreatorMcpServerConfig>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorMcpServerConfig {
#[serde(default = "default_game_creator_mcp_enabled")]
enabled: bool,
#[serde(default)]
required: bool,
#[serde(default = "default_game_creator_mcp_transport")]
transport: String,
#[serde(default)]
command: String,
#[serde(default)]
args: Vec<String>,
#[serde(default)]
cwd: String,
#[serde(default)]
env: BTreeMap<String, String>,
#[serde(default)]
url: String,
#[serde(default)]
bearer_token: String,
#[serde(default)]
http_headers: BTreeMap<String, String>,
#[serde(default)]
allow_insecure_localhost: bool,
#[serde(default = "default_game_creator_mcp_startup_timeout_ms")]
startup_timeout_ms: u64,
#[serde(default = "default_game_creator_mcp_tool_timeout_ms")]
tool_timeout_ms: u64,
#[serde(default)]
enabled_tools: Vec<String>,
#[serde(default)]
disabled_tools: Vec<String>,
#[serde(default = "default_game_creator_mcp_approval_mode")]
default_approval_mode: String,
#[serde(default)]
tools: BTreeMap<String, GameCreatorMcpToolConfig>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorMcpToolConfig {
#[serde(default)]
enabled: Option<bool>,
#[serde(default)]
approval_mode: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct GameCreatorLlmConfig {
api_key: String,
base_url: String,
model: String,
api_kind: String,
reasoning_effort: String,
stream: bool,
web_search_enabled: bool,
#[serde(default = "default_game_creator_llm_context_window_tokens")]
context_window_tokens: u64,
#[serde(default = "default_game_creator_llm_auto_compact_token_limit")]
auto_compact_token_limit: u64,
#[serde(default = "default_game_creator_llm_tool_output_token_limit")]
tool_output_token_limit: u64,
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(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentRuntimeContextCompactionResult {
agent_id: String,
session_id: String,
run_id: Option<String>,
trigger: String,
revision: u64,
estimated_tokens_before: u64,
estimated_tokens_after: u64,
prompt_tokens: Option<u64>,
completion_tokens: Option<u64>,
total_tokens: Option<u64>,
covered_agent_messages: u64,
covered_project_messages: u64,
covered_observations: u64,
reused: bool,
compacted_at: u64,
}
#[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>,
message_id: Option<String>,
updated_at: u64,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalConversationResult {
path: String,
agent_id: Option<String>,
session_id: Option<String>,
messages: Vec<LocalConversationMessageRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentConversationSessionRecord {
session_id: String,
title: String,
created_at: u64,
updated_at: u64,
archived_at: Option<u64>,
message_count: u64,
legacy: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
forked_from_session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
forked_message_count: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct AgentConversationSessionListResult {
path: String,
agent_id: String,
active_session_id: String,
sessions: Vec<AgentConversationSessionRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ProjectPermissionPolicy {
denied_commands: Vec<String>,
confirm_commands: Vec<String>,
#[serde(default)]
agent_policies: BTreeMap<String, ProjectAgentPermissionPolicy>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct ProjectAgentPermissionPolicy {
#[serde(default)]
denied_commands: Vec<String>,
#[serde(default)]
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(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct LocalProjectDiffEntry {
path: String,
status: String,
}
#[derive(Clone, 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_GAME_CREATOR_LLM_REASONING_EFFORT: &str = "high";
const DEFAULT_GAME_CREATOR_LLM_CONTEXT_WINDOW_TOKENS: u64 = 128_000;
const DEFAULT_GAME_CREATOR_LLM_AUTO_COMPACT_TOKEN_LIMIT: u64 = 64_000;
const DEFAULT_GAME_CREATOR_LLM_TOOL_OUTPUT_TOKEN_LIMIT: u64 = 12_000;
const DEFAULT_GAME_CREATOR_LLM_MAX_RETRIES: u32 = 2;
fn default_game_creator_llm_context_window_tokens() -> u64 {
DEFAULT_GAME_CREATOR_LLM_CONTEXT_WINDOW_TOKENS
}
fn default_game_creator_llm_auto_compact_token_limit() -> u64 {
DEFAULT_GAME_CREATOR_LLM_AUTO_COMPACT_TOKEN_LIMIT
}
fn default_game_creator_llm_tool_output_token_limit() -> u64 {
DEFAULT_GAME_CREATOR_LLM_TOOL_OUTPUT_TOKEN_LIMIT
}
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_CHAT_AGENT_MAX_OUTPUT_TOKENS: u32 = 1800;
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; 3] = [
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"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 AGENT_CONVERSATION_SESSION_SCHEMA_VERSION: &str = "game-creator-agent-sessions.v1";
const AGENT_RUNTIME_SCHEMA_VERSION: &str = "game-creator-agent-runtime.v1";
const AGENT_RUNTIME_RUN_PROFILE_STANDARD: &str = "standard";
const AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD: &str = "autonomous-game-build";
const AGENT_RUNTIME_AUTONOMOUS_SOURCE_FIELD_MAX_CHARS: usize = 8_000;
const AGENT_RUNTIME_AUTONOMOUS_SOURCE_TOTAL_MAX_CHARS: usize = 10_000;
const AGENT_RUNTIME_RECENT_EVENT_LIMIT: usize = 20;
const AGENT_RUNTIME_RECENT_TASK_LIMIT: usize = 12;
const GAME_CREATOR_CONVERSATION_CONTEXT_MAX_MESSAGES: usize = 12;
fn default_agent_runtime_run_profile() -> String {
AGENT_RUNTIME_RUN_PROFILE_STANDARD.to_string()
}
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(),
mcp_servers: BTreeMap::new(),
}
}
}
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(),
reasoning_effort: DEFAULT_GAME_CREATOR_LLM_REASONING_EFFORT.to_string(),
stream: false,
web_search_enabled: false,
context_window_tokens: DEFAULT_GAME_CREATOR_LLM_CONTEXT_WINDOW_TOKENS,
auto_compact_token_limit: DEFAULT_GAME_CREATOR_LLM_AUTO_COMPACT_TOKEN_LIMIT,
tool_output_token_limit: DEFAULT_GAME_CREATOR_LLM_TOOL_OUTPUT_TOKEN_LIMIT,
request_timeout_ms: GAME_CREATOR_LLM_REQUEST_TIMEOUT_MS,
max_retries: DEFAULT_GAME_CREATOR_LLM_MAX_RETRIES,
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],
}
pub(crate) const GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID: &str = "project-supervisor";
const GAME_CREATOR_LEGACY_CHAT_AGENT_CONFIG_ID: &str = "chat";
const GAME_CREATOR_PROJECT_SUPERVISOR_MEMORY_PATH: &str = "memory/agents/project-supervisor.md";
static PROJECT_SUPERVISOR_AGENT_ROLES: [AgentRoleDefinition; 1] = [AgentRoleDefinition {
id: "project-supervisor",
role: "Project Supervisor",
task_id: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
tool_id: "agent.runtime.project-supervisor",
brief_path_name: "project-supervisor.md",
}];
static PROJECT_SUPERVISOR_AGENT_DEFINITION: AgentGroupDefinition = AgentGroupDefinition {
id: "supervisor",
label: "项目总控",
role: "Project Supervisor",
brief_path_name: "project-supervisor.md",
roles: &PROJECT_SUPERVISOR_AGENT_ROLES,
};
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: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string(),
label: "项目总控 Agent".to_string(),
},
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 mut args = std::env::args().skip(1).collect::<Vec<_>>();
#[cfg(target_os = "linux")]
if command_sandbox_trampoline::is_trampoline_mode(&args) {
match command_sandbox_trampoline::run_trampoline() {
Ok(exit_code) => std::process::exit(exit_code),
Err(_) => std::process::exit(125),
}
}
#[cfg(target_os = "linux")]
if command_sandbox_trampoline::is_process_session_trampoline_mode(&args) {
match command_sandbox_trampoline::run_process_session_trampoline() {
Ok(exit_code) => std::process::exit(exit_code),
Err(_) => std::process::exit(125),
}
}
#[cfg(target_os = "linux")]
if is_process_session_child_mode(&args) {
match run_process_session_child(&args) {
Ok(exit_code) => std::process::exit(exit_code),
Err(_) => std::process::exit(125),
}
}
let runtime_config_dir = match take_cli_runtime_config_dir(&mut args) {
Ok(config_dir) => config_dir,
Err(error) => {
eprintln!("{error}");
std::process::exit(1);
}
};
if args.first().map(String::as_str) == Some("--agent-runner") {
if args.len() != 1 {
eprintln!("用法:--agent-runner --config-dir <AppData 绝对路径>");
std::process::exit(1);
}
let Some(config_dir) = runtime_config_dir else {
eprintln!("Agent Runner 必须显式传入 --config-dir <AppData 绝对路径>");
std::process::exit(1);
};
set_game_creator_runtime_config_dir(config_dir.clone());
if let Err(error) = run_external_agent_runner_server(config_dir) {
eprintln!("agent.runner.failed: {error}");
std::process::exit(1);
}
return;
}
match parse_cli_command(&args) {
Ok(Some(mut command)) => {
let config_dir =
match prepare_cli_command_paths(&mut command, runtime_config_dir.as_deref()) {
Ok(config_dir) => config_dir,
Err(error) => {
eprintln!("agent.runner.failed: {error}");
std::process::exit(1);
}
};
if let Some(config_dir) = config_dir {
let configured = if command.is_read_only_status() {
configure_external_agent_runner_read_only(&config_dir)
} else {
configure_external_agent_runner(&config_dir)
};
if let Err(error) = configured {
eprintln!("agent.runner.failed: {error}");
std::process::exit(1);
}
set_game_creator_runtime_config_dir(config_dir);
}
if command.requires_started_external_agent_runner() {
if let Err(error) = ensure_external_agent_runner_started() {
eprintln!("agent.runner.failed: {error}");
std::process::exit(1);
}
}
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())
.plugin(tauri_plugin_clipboard_manager::init())
.manage(game_creator_preview_registry())
.setup(|app| {
configure_game_creator_runtime_config_dir(app.handle())?;
let config_dir = game_creator_runtime_config_dir().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"客户端 AppData 配置目录未初始化",
)
})?;
configure_external_agent_runner(config_dir).map_err(|error| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("配置 Agent Runner 失败:{error}"),
)
})?;
ensure_external_agent_runner_started().map_err(|error| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("启动 Agent Runner 失败:{error}"),
)
})?;
set_game_creator_agent_runtime_update_app_handle(app.handle().clone());
#[cfg(all(debug_assertions, not(test)))]
open_developer_window(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,
chat_with_game_creator_agent,
chat_with_game_creator_role_agent,
chat_with_game_creator_role_agent_stream,
start_game_creator_agent_runtime_task,
start_game_creator_supervisor_runtime_task,
compact_game_creator_agent_runtime_context,
read_game_creator_agent_goal,
start_game_creator_agent_goal,
edit_game_creator_agent_goal,
pause_game_creator_agent_goal,
resume_game_creator_agent_goal,
clear_game_creator_agent_goal,
steer_game_creator_agent_runtime_task,
cancel_game_creator_agent_runtime_task,
retry_game_creator_agent_runtime_task,
confirm_retry_game_creator_agent_runtime_task,
confirm_game_creator_agent_runtime_task,
reject_game_creator_agent_runtime_task,
answer_game_creator_agent_runtime_user_input,
read_game_creator_agent_runtime,
read_game_creator_agent_runtimes,
resume_game_creator_agent_runtime_tasks,
confirm_resume_game_creator_agent_runtime_tasks,
schedule_game_creator_agent_ready_tasks,
check_game_creator_llm_config,
read_game_creator_app_config,
write_game_creator_app_config,
read_game_creator_mcp_catalog,
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,
read_local_project_image_preview,
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,
list_game_creator_agent_sessions,
create_game_creator_agent_session,
fork_game_creator_agent_session,
set_active_game_creator_agent_session,
archive_game_creator_agent_session,
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,
open_project_supervisor_chat_window,
start_local_game_preview,
activate_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;