9bb048c11b
完善 Agent Swarm 自主游戏构建、委派协作、验证门和试玩闭环 补齐浏览器验证、CLI 入口、项目状态与运行时测试覆盖 将外部 Agent Runner 冷启动超时提高到 30 秒并增加回归测试 同步真实 E2E 自测脚本、配置检查和项目决策记录
1795 lines
53 KiB
Rust
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;
|