Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/commands.rs
T
AIGameCreator App 5e627a4677 完善通用开发 Agent Runtime V1.1
新增仓库上下文指纹复核与 durable action 漂移保护
拆分 App CLI 与独立 Runner 的投递执行边界并补齐恢复诊断
加入桌面和移动端浏览器验证及跨源网络阻断
实现隔离子 Agent 私有记忆与终止状态传播
补齐 Provider 协议确认副作用重放与 join 真实端到端验证
同步共享契约实施计划决策记录与开发流程文档
2026-07-12 20:58:42 +08:00

1051 lines
38 KiB
Rust

use super::*;
#[tauri::command]
pub(crate) fn init_local_game_project(
project_path: String,
project_id: String,
name: String,
) -> Result<InitLocalProjectResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.create")?;
let _lock = acquire_project_write_lock(root, "project.create")?;
init_local_game_project_at(root, project_id.trim(), name.trim())
}
#[tauri::command]
pub(crate) fn is_local_project_directory_non_empty(project_path: String) -> Result<bool, String> {
let root = Path::new(project_path.trim());
if root.as_os_str().is_empty() {
return Err("项目目录不能为空".to_string());
}
if !root.is_absolute() {
return Err("项目目录必须是绝对路径".to_string());
}
if project_path_has_control_chars(root) {
return Err("项目目录不能包含控制字符".to_string());
}
if !root.exists() {
return Ok(false);
}
if !root.is_dir() {
return Err("项目目录已存在但不是文件夹".to_string());
}
fs::read_dir(root)
.map_err(|error| format!("读取项目目录失败:{}: {error}", root.display()))?
.next()
.transpose()
.map_err(|error| format!("读取项目目录失败:{}: {error}", root.display()))
.map(|entry| entry.is_some())
}
#[tauri::command]
pub(crate) fn inspect_local_project_directory(
project_path: String,
) -> Result<LocalProjectDirectoryStatus, String> {
let root = Path::new(project_path.trim());
if root.as_os_str().is_empty() {
return Err("项目目录不能为空".to_string());
}
if !root.is_absolute() {
return Err("项目目录必须是绝对路径".to_string());
}
if project_path_has_control_chars(root) {
return Err("项目目录不能包含控制字符".to_string());
}
let recent_run_trace = recent_game_creator_run_trace(root);
Ok(LocalProjectDirectoryStatus {
project_path: root.to_string_lossy().into_owned(),
exists: root.exists(),
is_directory: root.is_dir(),
is_game_creator_project: is_game_creator_project_directory(root),
project_name: game_creator_project_name(root),
manifest_error: game_creator_project_manifest_error(root),
recent_run_status: recent_run_trace.as_ref().map(|trace| trace.status.clone()),
recent_run_stop_reason: recent_run_trace.map(|trace| trace.stop_reason),
})
}
pub(crate) fn is_game_creator_project_directory(root: &Path) -> bool {
if !root.is_dir() {
return false;
}
let manifest_path = root.join(".agent/manifest.json");
read_manifest(&manifest_path).is_ok()
}
pub(crate) fn game_creator_project_name(root: &Path) -> Option<String> {
let manifest_path = root.join(".agent/manifest.json");
let manifest = read_manifest(&manifest_path).ok()?;
let name = manifest.name.trim();
if name.is_empty() {
None
} else {
Some(name.to_string())
}
}
pub(crate) fn game_creator_project_manifest_error(root: &Path) -> Option<String> {
if !root.is_dir() {
return None;
}
let manifest_path = root.join(".agent/manifest.json");
if !manifest_storage_exists(&manifest_path).unwrap_or(true) {
return None;
}
read_manifest(&manifest_path).err()
}
pub(crate) fn recent_game_creator_run_trace(root: &Path) -> Option<GameCreationAgentRunTrace> {
let trace_path = root.join(".agent/run.latest.json");
let content = fs::read_to_string(trace_path).ok()?;
serde_json::from_str::<GameCreationAgentRunTrace>(&content).ok()
}
#[tauri::command]
pub(crate) fn pick_local_project_directory(
app: tauri::AppHandle,
) -> Result<Option<String>, String> {
let Some(path) = app.dialog().file().blocking_pick_folder() else {
return Ok(None);
};
path.into_path()
.map(|path| Some(path.to_string_lossy().into_owned()))
.map_err(|error| format!("读取项目目录失败:{error}"))
}
#[tauri::command]
pub(crate) fn pick_local_file(app: tauri::AppHandle) -> Result<Option<String>, String> {
let Some(path) = app.dialog().file().blocking_pick_file() else {
return Ok(None);
};
path.into_path()
.map(|path| Some(path.to_string_lossy().into_owned()))
.map_err(|error| format!("读取本地文件失败:{error}"))
}
#[tauri::command]
pub(crate) fn open_local_project_directory(
app: tauri::AppHandle,
project_path: String,
) -> Result<(), String> {
let path = validated_local_project_directory_path(project_path.trim())?;
app.opener()
.open_path(path.to_string_lossy().into_owned(), None::<&str>)
.map_err(|error| format!("打开项目目录失败:{error}"))
}
pub(crate) fn validated_local_project_directory_path(
project_path: &str,
) -> Result<PathBuf, String> {
let path = Path::new(project_path);
if path.as_os_str().is_empty() {
return Err("项目目录不能为空".to_string());
}
if !path.is_absolute() {
return Err("项目目录必须是绝对路径".to_string());
}
if !path.exists() {
return Err("项目目录不存在".to_string());
}
if !path.is_dir() {
return Err("项目路径不是文件夹".to_string());
}
Ok(path.to_path_buf())
}
#[tauri::command]
pub(crate) fn get_local_game_manifest(
project_path: String,
command_id: Option<String>,
) -> Result<GameCreationAppManifest, String> {
let root = Path::new(project_path.trim());
let command_id = command_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("project.status");
if !matches!(
command_id,
"project.status" | "asset.list" | "task.list" | "agent.audit"
) {
return Err(format!("不支持通过 manifest 执行命令:{command_id}"));
}
enforce_project_permission_policy(root, command_id)?;
read_manifest_for_project(root)
}
#[tauri::command]
pub(crate) async fn control_agent_run(
app: tauri::AppHandle,
project_path: String,
action: String,
detail: Option<String>,
) -> Result<AgentRunControlResult, String> {
let root = Path::new(project_path.trim());
let command_id = match action.trim() {
"status" => "agent.run_status",
"kill" => "agent.kill",
"retry" => "agent.retry",
"resume" => "agent.resume",
_ => "agent.run_status",
};
enforce_project_permission_policy(root, command_id)?;
if matches!(action.trim(), "retry" | "resume") {
enforce_project_permission_policy(root, "game.generate_draft")?;
}
let _lock = if command_id == "agent.run_status" {
None
} else {
Some(acquire_project_write_lock(root, command_id)?)
};
control_agent_run_at(
root,
action.trim(),
detail
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty()),
Some(&AgentProgressEmitter::new(&app, project_path.trim())),
)
.await
}
#[tauri::command]
pub(crate) async fn generate_local_game_draft(
app: tauri::AppHandle,
project_path: String,
prompt: String,
) -> Result<GenerateLocalGameDraftResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "game.generate_draft")?;
let _lock = acquire_project_write_lock(root, "game.generate_draft")?;
advance_agent_runtime_project_revision_locked(root)?;
generate_local_game_draft_at(
root,
prompt.trim(),
Some(&AgentProgressEmitter::new(&app, project_path.trim())),
)
.await
}
#[tauri::command]
pub(crate) async fn chat_with_game_creator_agent(
project_path: String,
prompt: String,
) -> Result<GameCreatorChatAgentReply, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
chat_with_game_creator_agent_at(root, prompt.trim()).await
}
#[tauri::command]
pub(crate) async fn chat_with_game_creator_role_agent(
project_path: String,
agent_id: String,
session_id: Option<String>,
prompt: String,
) -> Result<GameCreatorChatAgentReply, String> {
let root = Path::new(project_path.trim());
let agent_id = normalize_game_creator_runtime_agent_id(agent_id.trim())?;
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
let runtime_lock = try_acquire_game_creator_agent_runtime_task_lock(root, &agent_id)?
.ok_or_else(|| format!("Agent 正在执行其他前台或后台任务:{agent_id}"))?;
let result = chat_with_game_creator_role_agent_runtime_for_session_at(
root,
&agent_id,
session_id.as_deref(),
prompt.trim(),
"",
)
.await
.map(|(reply, _runtime)| reply);
spawn_next_game_creator_agent_background_task_drain_with_lock(root, &agent_id, runtime_lock);
match result {
Ok(reply) => Ok(reply),
Err(error) => Err(error),
}
}
#[tauri::command]
pub(crate) async fn chat_with_game_creator_role_agent_stream(
app: tauri::AppHandle,
project_path: String,
agent_id: String,
session_id: Option<String>,
prompt: String,
run_id: String,
) -> Result<GameCreatorChatAgentReply, String> {
let project_path = project_path.trim().to_string();
let agent_id = normalize_game_creator_runtime_agent_id(agent_id.trim())?;
let run_id = run_id.trim().to_string();
let root = Path::new(project_path.as_str());
let session_id =
resolve_agent_conversation_session_id_at(root, &agent_id, session_id.as_deref(), true)?;
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
let runtime_lock = try_acquire_game_creator_agent_runtime_task_lock(root, &agent_id)?
.ok_or_else(|| format!("Agent 正在执行其他前台或后台任务:{agent_id}"))?;
let emit_app = app.clone();
let event_project_path = project_path.clone();
let event_agent_id = agent_id.clone();
let event_run_id = run_id.clone();
let command_result = async {
let mut runtime_state = start_game_creator_agent_runtime_turn_for_session_at(
root,
agent_id.as_str(),
Some(&session_id),
prompt.trim(),
&run_id,
)?;
runtime_state = advance_game_creator_agent_runtime_turn_at(
root,
runtime_state,
"llm",
"请求 Agent LLM",
"已读取项目上下文,正在让 Agent 独立推理。",
)?;
let mut streaming_runtime_state = runtime_state.clone();
streaming_runtime_state.current_action = "正在接收 Agent 回复".to_string();
let _ = app.emit(
"game-creator-role-agent-chat-stream",
GameCreatorRoleAgentChatStreamEvent {
project_path: event_project_path.clone(),
agent_id: event_agent_id.clone(),
run_id: event_run_id.clone(),
status: "started".to_string(),
delta_text: String::new(),
accumulated_text: String::new(),
finish_reason: None,
session_id: Some(runtime_state.session_id.clone()),
runtime_status: Some(runtime_state.status.clone()),
runtime_phase: Some(runtime_state.phase.clone()),
runtime_summary: Some(runtime_state.current_action.clone()),
runtime_state: Some(runtime_state.clone()),
},
);
let result = chat_with_game_creator_role_agent_stream_for_session_at(
root,
agent_id.as_str(),
Some(&session_id),
prompt.trim(),
|delta| {
let _ = emit_app.emit(
"game-creator-role-agent-chat-stream",
GameCreatorRoleAgentChatStreamEvent {
project_path: event_project_path.clone(),
agent_id: event_agent_id.clone(),
run_id: event_run_id.clone(),
status: "delta".to_string(),
delta_text: delta.delta_text.clone(),
accumulated_text: delta.accumulated_text.clone(),
finish_reason: delta.finish_reason.clone(),
session_id: Some(streaming_runtime_state.session_id.clone()),
runtime_status: Some("running".to_string()),
runtime_phase: Some("llm".to_string()),
runtime_summary: Some("正在接收 Agent 回复".to_string()),
runtime_state: Some(streaming_runtime_state.clone()),
},
);
},
)
.await;
match result {
Ok(reply) => {
let completed_runtime = finish_game_creator_agent_runtime_turn_at(
root,
runtime_state,
&reply.reply_text,
)?;
let _ = app.emit(
"game-creator-role-agent-chat-stream",
GameCreatorRoleAgentChatStreamEvent {
project_path: project_path.clone(),
agent_id: agent_id.clone(),
run_id: run_id.clone(),
status: "completed".to_string(),
delta_text: String::new(),
accumulated_text: reply.reply_text.clone(),
finish_reason: None,
session_id: Some(completed_runtime.session_id.clone()),
runtime_status: Some(completed_runtime.status.clone()),
runtime_phase: Some(completed_runtime.phase.clone()),
runtime_summary: Some(completed_runtime.current_action.clone()),
runtime_state: Some(completed_runtime),
},
);
Ok(reply)
}
Err(error) => {
let failed_runtime =
fail_game_creator_agent_runtime_turn_at(root, runtime_state, &error).ok();
let _ = app.emit(
"game-creator-role-agent-chat-stream",
GameCreatorRoleAgentChatStreamEvent {
project_path: project_path.clone(),
agent_id: agent_id.clone(),
run_id: run_id.clone(),
status: "failed".to_string(),
delta_text: String::new(),
accumulated_text: String::new(),
finish_reason: None,
session_id: failed_runtime
.as_ref()
.map(|runtime| runtime.session_id.clone()),
runtime_status: failed_runtime
.as_ref()
.map(|runtime| runtime.status.clone()),
runtime_phase: failed_runtime.as_ref().map(|runtime| runtime.phase.clone()),
runtime_summary: failed_runtime
.as_ref()
.map(|runtime| runtime.current_action.clone()),
runtime_state: failed_runtime,
},
);
Err(error)
}
}
}
.await;
spawn_next_game_creator_agent_background_task_drain_with_lock(root, &agent_id, runtime_lock);
match command_result {
Ok(reply) => Ok(reply),
Err(error) => Err(error),
}
}
#[tauri::command]
pub(crate) fn start_game_creator_agent_runtime_task(
project_path: String,
agent_id: String,
session_id: Option<String>,
task: String,
run_id: String,
) -> Result<AgentRuntimeResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
start_game_creator_agent_background_task_for_session_at(
root,
agent_id.trim(),
session_id.as_deref(),
task.trim(),
run_id.trim(),
)
}
#[tauri::command]
pub(crate) fn cancel_game_creator_agent_runtime_task(
project_path: String,
agent_id: String,
run_id: String,
) -> Result<AgentRuntimeResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
cancel_game_creator_agent_runtime_task_at(root, agent_id.trim(), run_id.trim())
}
#[tauri::command]
pub(crate) fn retry_game_creator_agent_runtime_task(
project_path: String,
agent_id: String,
run_id: String,
next_run_id: String,
) -> Result<AgentRuntimeResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
enforce_project_auto_permission_policy(root, "agent.resume")?;
retry_game_creator_agent_runtime_task_at(
root,
agent_id.trim(),
run_id.trim(),
next_run_id.trim(),
)
}
#[tauri::command]
pub(crate) fn confirm_game_creator_agent_runtime_task(
project_path: String,
agent_id: String,
run_id: String,
action_id: String,
note: String,
) -> Result<AgentRuntimeResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
// 该命令只由开发者显式点击“确认继续”触发。
enforce_project_permission_policy(root, "agent.resume")?;
confirm_game_creator_agent_runtime_task_at(
root,
agent_id.trim(),
run_id.trim(),
action_id.trim(),
note.trim(),
)
}
#[tauri::command]
pub(crate) fn reject_game_creator_agent_runtime_task(
project_path: String,
agent_id: String,
run_id: String,
action_id: String,
note: String,
) -> Result<AgentRuntimeResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
// 该命令只由开发者显式点击“拒绝并继续”触发。
enforce_project_permission_policy(root, "agent.resume")?;
reject_game_creator_agent_runtime_task_at(
root,
agent_id.trim(),
run_id.trim(),
action_id.trim(),
note.trim(),
)
}
#[tauri::command]
pub(crate) fn read_game_creator_agent_runtime(
project_path: String,
agent_id: String,
session_id: Option<String>,
) -> Result<AgentRuntimeResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
read_game_creator_agent_runtime_for_session_at(root, agent_id.trim(), session_id.as_deref())
}
#[tauri::command]
pub(crate) fn read_game_creator_agent_runtimes(
project_path: String,
) -> Result<Vec<AgentRuntimeResult>, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
read_game_creator_agent_runtimes_at(root)
}
#[tauri::command]
pub(crate) fn resume_game_creator_agent_runtime_tasks(
project_path: String,
) -> Result<Vec<AgentRuntimeResult>, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
enforce_project_auto_permission_policy(root, "agent.resume")?;
resume_game_creator_agent_background_tasks_at(root)
}
#[tauri::command]
pub(crate) fn confirm_resume_game_creator_agent_runtime_tasks(
project_path: String,
) -> Result<Vec<AgentRuntimeResult>, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
// 该命令只由开发者在 agent.resume 确认卡中明确批准后调用。
enforce_project_permission_policy(root, "agent.resume")?;
resume_game_creator_agent_background_tasks_at(root)
}
#[tauri::command]
pub(crate) fn schedule_game_creator_agent_ready_tasks(
project_path: String,
limit: usize,
) -> Result<Vec<AgentRuntimeResult>, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
enforce_project_auto_permission_policy(root, "agent.schedule_ready")?;
schedule_game_creator_agent_ready_tasks_at(root, limit)
}
#[tauri::command]
pub(crate) fn check_game_creator_llm_config() -> GameCreatorLlmConfigStatus {
check_game_creator_llm_config_from_config()
}
#[tauri::command]
pub(crate) fn read_game_creator_app_config() -> Result<GameCreatorAppConfigView, String> {
game_creator_app_config_view(load_game_creator_app_config()?)
}
#[tauri::command]
pub(crate) fn write_game_creator_app_config(
config: GameCreatorAppConfig,
) -> Result<GameCreatorAppConfigView, String> {
let config = normalize_game_creator_app_config(config)?;
let path = writable_game_creator_config_path()?;
let content = serde_json::to_string_pretty(&config)
.map_err(|error| format!("序列化客户端配置失败:{error}"))?;
write_game_creator_config_atomically(&path, &format!("{content}\n"))?;
game_creator_app_config_view(load_game_creator_app_config()?)
}
#[tauri::command]
pub(crate) fn upload_local_asset(
project_path: String,
file_name: String,
media_type: String,
bytes: Vec<u8>,
) -> Result<UploadLocalAssetResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "asset.upload")?;
let _lock = acquire_project_write_lock(root, "asset.upload")?;
advance_agent_runtime_project_revision_locked(root)?;
upload_local_asset_at(root, file_name.trim(), media_type.trim(), &bytes)
}
#[tauri::command]
pub(crate) fn register_local_asset(
project_path: String,
local_path: String,
kind: String,
media_type: String,
source_kind: String,
canvas_project_id: Option<String>,
resource_id: Option<String>,
asset_object_id: Option<String>,
task_id: Option<String>,
prompt: Option<String>,
model: Option<String>,
) -> Result<UploadLocalAssetResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
let _lock = acquire_project_write_lock(root, "asset.register")?;
advance_agent_runtime_project_revision_locked(root)?;
register_local_asset_at(
root,
local_path.trim(),
kind.trim(),
media_type.trim(),
source_kind.trim(),
GameCreationAppAssetSource {
kind: parse_asset_source_kind(source_kind.trim())?,
canvas_project_id: trim_optional_string(canvas_project_id),
resource_id: trim_optional_string(resource_id),
asset_object_id: trim_optional_string(asset_object_id),
task_id: trim_optional_string(task_id),
prompt: trim_optional_string(prompt),
model: trim_optional_string(model),
},
)
}
#[tauri::command]
pub(crate) fn import_canvas_asset(
project_path: String,
local_path: String,
kind: String,
media_type: String,
canvas_project_id: String,
resource_id: Option<String>,
asset_object_id: Option<String>,
task_id: Option<String>,
prompt: Option<String>,
model: Option<String>,
) -> Result<UploadLocalAssetResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "canvas.asset_import")?;
let _lock = acquire_project_write_lock(root, "canvas.asset_import")?;
advance_agent_runtime_project_revision_locked(root)?;
import_canvas_asset_at(
root,
local_path.trim(),
kind.trim(),
media_type.trim(),
canvas_project_id.trim(),
trim_optional_string(resource_id),
trim_optional_string(asset_object_id),
trim_optional_string(task_id),
trim_optional_string(prompt),
trim_optional_string(model),
)
}
#[tauri::command]
pub(crate) fn import_canvas_export(
project_path: String,
export_path: String,
canvas_project_id: String,
) -> Result<ImportCanvasExportResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "canvas.export_import")?;
let _lock = acquire_project_write_lock(root, "canvas.export_import")?;
advance_agent_runtime_project_revision_locked(root)?;
import_canvas_export_at(
root,
Path::new(export_path.trim()),
canvas_project_id.trim(),
)
}
#[tauri::command]
pub(crate) async fn sync_canvas_project_assets(
project_path: String,
canvas_project_id: String,
api_base_url: Option<String>,
api_key: Option<String>,
) -> Result<SyncCanvasProjectAssetsResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "canvas.project_sync")?;
let _lock = acquire_project_write_lock(root, "canvas.project_sync")?;
advance_agent_runtime_project_revision_locked(root)?;
sync_canvas_project_assets_at(root, canvas_project_id.trim(), api_base_url, api_key).await
}
#[tauri::command]
pub(crate) async fn generate_platform_art_asset(
project_path: String,
prompt: String,
) -> Result<UploadLocalAssetResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "canvas.asset_generate")?;
let _lock = acquire_project_write_lock(root, "canvas.asset_generate")?;
advance_agent_runtime_project_revision_locked(root)?;
let generated = generate_platform_art_asset_at(root, prompt.trim(), &[]).await?;
Ok(generated.asset)
}
#[tauri::command]
pub(crate) fn open_canvas_project(
app: tauri::AppHandle,
canvas_project_id: Option<String>,
editor_base_url: Option<String>,
) -> Result<OpenCanvasProjectResult, String> {
let url = build_canvas_project_url(editor_base_url.as_deref(), canvas_project_id.as_deref())?;
app.opener()
.open_url(&url, None::<&str>)
.map_err(|error| format!("打开画板失败:{error}"))?;
Ok(OpenCanvasProjectResult { url })
}
#[tauri::command]
pub(crate) fn get_game_creation_agent_capabilities() -> Vec<GameCreationAgentCapabilityDescriptor> {
GAME_CREATION_AGENT_CAPABILITIES.to_vec()
}
#[tauri::command]
pub(crate) fn get_limited_local_commands() -> Vec<GameCreationAppLimitedRunCommandDescriptor> {
GAME_CREATION_APP_LIMITED_RUN_COMMANDS.to_vec()
}
#[tauri::command]
pub(crate) fn run_limited_local_command(
project_path: String,
command_id: String,
) -> Result<LimitedLocalCommandResult, String> {
let root = Path::new(project_path.trim());
let command_id = command_id.trim();
enforce_project_permission_policy(root, "command.run_limited")?;
let _lock = acquire_project_write_lock(root, "command.run_limited")?;
let result = run_limited_local_command_at(root, command_id)?;
if command_id == "game.static_smoke" {
append_static_smoke_manual_trace_step(root, &result)?;
}
Ok(result)
}
#[tauri::command]
pub(crate) fn append_local_permission_log(
project_path: String,
event: String,
command_id: String,
) -> Result<(), String> {
append_local_permission_log_at(
Path::new(project_path.trim()),
event.trim(),
command_id.trim(),
)
}
#[tauri::command]
pub(crate) fn list_local_project_files(
project_path: String,
) -> Result<ListLocalProjectFilesResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "file.list")?;
list_local_project_files_at(root)
}
#[tauri::command]
pub(crate) fn read_local_project_file(
project_path: String,
relative_path: String,
command_id: Option<String>,
) -> Result<LocalProjectFileResult, String> {
let root = Path::new(project_path.trim());
let normalized_path = normalize_relative_path(relative_path.trim())?;
let command_id = command_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("file.read");
if command_id == "agent.trace_read" {
if !is_agent_trace_read_path(&normalized_path) {
return Err("agent.trace_read 只能读取 Agent run trace".to_string());
}
} else if command_id != "file.read" {
return Err(format!("不支持通过文件读取执行命令:{command_id}"));
}
enforce_project_permission_policy(root, command_id)?;
read_local_project_file_at(root, &normalized_path)
}
#[tauri::command]
pub(crate) fn write_local_project_file(
project_path: String,
relative_path: String,
content: String,
) -> Result<LocalProjectFileMutationResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "file.write")?;
let _lock = acquire_project_write_lock(root, "file.write")?;
advance_agent_runtime_project_revision_locked(root)?;
write_local_project_file_at(root, relative_path.trim(), &content)
}
#[tauri::command]
pub(crate) fn delete_local_project_file(
project_path: String,
relative_path: String,
) -> Result<LocalProjectFileMutationResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "file.delete")?;
let _lock = acquire_project_write_lock(root, "file.delete")?;
advance_agent_runtime_project_revision_locked(root)?;
delete_local_project_file_at(root, relative_path.trim())
}
#[tauri::command]
pub(crate) fn read_local_game_memory(
project_path: String,
scope: String,
) -> Result<LocalGameMemoryResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "memory.read")?;
read_local_game_memory_at(root, scope.trim())
}
#[tauri::command]
pub(crate) fn read_local_agent_memory(
project_path: String,
task_id: String,
) -> Result<LocalAgentMemoryResult, String> {
let root = Path::new(project_path.trim());
let task_id = normalize_game_creator_runtime_agent_id(task_id.trim())?;
enforce_project_permission_policy(root, "memory.read")?;
read_local_agent_memory_at(root, &task_id)
}
#[tauri::command]
pub(crate) fn write_local_agent_memory(
project_path: String,
task_id: String,
content: String,
) -> Result<LocalAgentMemoryResult, String> {
let root = Path::new(project_path.trim());
let task_id = normalize_game_creator_runtime_agent_id(task_id.trim())?;
enforce_project_permission_policy(root, "memory.write")?;
let _lock = acquire_project_write_lock(root, "memory.write")?;
advance_agent_runtime_project_revision_locked(root)?;
write_local_agent_memory_at(root, &task_id, &content)
}
#[tauri::command]
pub(crate) fn write_local_game_memory(
project_path: String,
scope: String,
content: String,
) -> Result<LocalGameMemoryResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "memory.write")?;
let _lock = acquire_project_write_lock(root, "memory.write")?;
advance_agent_runtime_project_revision_locked(root)?;
write_local_game_memory_at(root, scope.trim(), &content)
}
#[tauri::command]
pub(crate) fn delete_local_game_memory(
project_path: String,
scope: String,
) -> Result<LocalGameMemoryResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "memory.delete")?;
let _lock = acquire_project_write_lock(root, "memory.delete")?;
advance_agent_runtime_project_revision_locked(root)?;
delete_local_game_memory_at(root, scope.trim())
}
#[tauri::command]
pub(crate) fn list_game_creator_agent_sessions(
project_path: String,
agent_id: String,
) -> Result<AgentConversationSessionListResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
list_game_creator_agent_sessions_at(root, agent_id.trim())
}
#[tauri::command]
pub(crate) fn create_game_creator_agent_session(
project_path: String,
agent_id: String,
title: String,
) -> Result<AgentConversationSessionListResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
let _lock = acquire_project_write_lock(root, "conversation.write")?;
create_game_creator_agent_session_at(root, agent_id.trim(), title.trim())
}
#[tauri::command]
pub(crate) fn set_active_game_creator_agent_session(
project_path: String,
agent_id: String,
session_id: String,
) -> Result<AgentConversationSessionListResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
let _lock = acquire_project_write_lock(root, "conversation.write")?;
set_active_game_creator_agent_session_at(root, agent_id.trim(), session_id.trim())
}
#[tauri::command]
pub(crate) fn archive_game_creator_agent_session(
project_path: String,
agent_id: String,
session_id: String,
) -> Result<AgentConversationSessionListResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
let _lock = acquire_project_write_lock(root, "conversation.write")?;
archive_game_creator_agent_session_at(root, agent_id.trim(), session_id.trim())
}
#[tauri::command]
pub(crate) fn read_local_conversation(
project_path: String,
agent_id: Option<String>,
session_id: Option<String>,
) -> Result<LocalConversationResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
read_local_conversation_for_session_at(root, agent_id.as_deref(), session_id.as_deref())
}
#[tauri::command]
pub(crate) fn append_local_conversation_message(
project_path: String,
agent_id: Option<String>,
session_id: Option<String>,
message: LocalConversationMessage,
) -> Result<LocalConversationResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.write")?;
let _lock = acquire_project_write_lock(root, "conversation.write")?;
append_local_conversation_message_for_session_at(
root,
agent_id.as_deref(),
session_id.as_deref(),
message,
)
}
#[tauri::command]
pub(crate) fn build_local_project_index(
project_path: String,
) -> Result<LocalProjectIndexResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.index")?;
let _lock = acquire_project_write_lock(root, "project.index")?;
build_local_project_index_at(root)
}
#[tauri::command]
pub(crate) fn create_local_project_checkpoint(
project_path: String,
) -> Result<LocalProjectCheckpointResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.checkpoint")?;
let _lock = acquire_project_write_lock(root, "project.checkpoint")?;
create_local_project_checkpoint_at(root)
}
#[tauri::command]
pub(crate) fn export_local_project_package(
project_path: String,
) -> Result<LocalProjectExportPackageResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.export_package")?;
let _lock = acquire_project_write_lock(root, "project.export_package")?;
advance_agent_runtime_project_revision_locked(root)?;
export_local_project_package_at(root)
}
#[tauri::command]
pub(crate) fn list_local_project_export_packages(
project_path: String,
) -> Result<LocalProjectExportPackagesResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.export_list")?;
list_local_project_export_packages_at(root)
}
#[tauri::command]
pub(crate) fn diff_local_project_checkpoint(
project_path: String,
checkpoint_id: String,
) -> Result<LocalProjectDiffResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.diff")?;
diff_local_project_checkpoint_at(root, checkpoint_id.trim())
}
#[tauri::command]
pub(crate) fn restore_local_project_checkpoint(
project_path: String,
checkpoint_id: String,
) -> Result<LocalProjectRestoreResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.restore")?;
let _lock = acquire_project_write_lock(root, "project.restore")?;
advance_agent_runtime_project_revision_locked(root)?;
restore_local_project_checkpoint_at(root, checkpoint_id.trim())
}
#[tauri::command]
pub(crate) fn read_project_permission_policy(
project_path: String,
) -> Result<ProjectPermissionPolicyView, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.policy_read")?;
read_project_permission_policy_at(root)
}
#[tauri::command]
pub(crate) fn write_project_permission_policy(
project_path: String,
policy: ProjectPermissionPolicy,
) -> Result<ProjectPermissionPolicyView, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.policy_write")?;
let _lock = acquire_project_write_lock(root, "project.policy_write")?;
write_project_permission_policy_at(root, policy)
}