Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/commands.rs
T
menghao 8e3d9991fa
Project CI / Repository checks (push) Successful in 1m5s
Project CI / Frontend tests (push) Successful in 3m0s
Project CI / Backend tests (push) Successful in 4m4s
Project CI / Native shell tests (push) Successful in 14m17s
完成资源管理的功能修复 (#154)
实现资源卡本体化与图片视频音频预览播放

实现四个资源分区独立高度与内外滚动

实现依赖视图确定性聚类与拓扑变化重排

补齐布局、SVG、AppSurface、性能和媒体回归

同步工作台PRD、技术方案与项目共享记忆

---------

Co-authored-by: kdletters <kdletters@qq.com>
Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/154
Co-authored-by: menghao <mh18530625731@163.com>
Co-committed-by: menghao <mh18530625731@163.com>
2026-08-12 13:14:55 +08:00

1909 lines
69 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 import_local_godot_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")?;
import_local_godot_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),
is_godot_project: is_godot_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_godot_project_directory(root: &Path) -> bool {
if !root.is_dir() {
return false;
}
let project_file = root.join("project.godot");
fs::symlink_metadata(project_file)
.map(|metadata| metadata.is_file() && !metadata.file_type().is_symlink())
.unwrap_or(false)
}
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) async fn pick_local_project_directory(
app: tauri::AppHandle,
) -> Result<Option<String>, String> {
let (sender, receiver) = tokio::sync::oneshot::channel();
let mut dialog = app.dialog().file().set_title("选择游戏项目目录");
if let Some(window) = app.get_webview_window("client") {
dialog = dialog.set_parent(&window);
}
dialog.pick_folder(move |path| {
let _ = sender.send(path);
});
let Some(path) = receiver
.await
.map_err(|_| "项目目录选择器意外关闭".to_string())?
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) async fn pick_local_file(app: tauri::AppHandle) -> Result<Option<String>, String> {
let (sender, receiver) = tokio::sync::oneshot::channel();
let mut dialog = app.dialog().file().set_title("选择本地文件");
if let Some(window) = app.get_webview_window("client") {
dialog = dialog.set_parent(&window);
}
dialog.pick_file(move |path| {
let _ = sender.send(path);
});
let Some(path) = receiver
.await
.map_err(|_| "本地文件选择器意外关闭".to_string())?
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) fn read_local_project_resource_canvas_layout(
project_path: String,
mode: ProjectResourceCanvasLayoutMode,
) -> Result<ProjectResourceCanvasLayout, String> {
read_project_resource_canvas_layout_at(Path::new(project_path.trim()), mode)
}
#[tauri::command]
pub(crate) fn read_local_project_resource_graph(
project_path: String,
expected_project_id: String,
resources: Vec<ProjectResourceGraphNodeInput>,
) -> Result<ProjectResourceGraphReadModel, String> {
let root = validated_local_project_directory_path(project_path.trim())?;
enforce_project_auto_permission_policy(&root, "asset.list")?;
read_project_resource_graph_at(&root, expected_project_id.trim(), resources)
}
#[tauri::command]
pub(crate) fn update_local_project_resource_canvas_layout(
project_path: String,
expected_project_id: String,
mode: ProjectResourceCanvasLayoutMode,
expected_revision: u64,
positions: Vec<ProjectResourceCanvasPosition>,
) -> Result<UpdateProjectResourceCanvasLayoutResult, String> {
update_project_resource_canvas_layout_at(
Path::new(project_path.trim()),
mode,
&expected_project_id,
expected_revision,
positions,
)
}
#[tauri::command]
pub(crate) fn create_local_project_asset_canvas_draft(
input: CreateAssetCanvasDraftInput,
) -> Result<CreateAssetCanvasDraftResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
create_asset_canvas_draft_at(&root, &input)
}
#[tauri::command]
pub(crate) fn read_local_project_asset_canvas_draft(
input: ReadAssetCanvasDraftInput,
) -> Result<ReadAssetCanvasDraftResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
read_asset_canvas_draft_at(&root, &input)
}
#[tauri::command]
pub(crate) fn discover_local_project_asset_canvas_draft(
input: DiscoverAssetCanvasDraftInput,
) -> Result<DiscoverAssetCanvasDraftResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
discover_asset_canvas_draft_at(&root, &input)
}
#[tauri::command]
pub(crate) fn update_local_project_asset_canvas_draft(
input: UpdateAssetCanvasDraftInput,
) -> Result<UpdateAssetCanvasDraftResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
update_asset_canvas_draft_at(&root, &input)
}
#[tauri::command]
pub(crate) fn store_local_project_asset_canvas_media(
input: StoreAssetCanvasMediaInput,
) -> Result<StoreAssetCanvasMediaResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
store_asset_canvas_media_at(&root, &input)
}
#[tauri::command]
pub(crate) fn stage_local_project_asset_canvas_image(
input: StageAssetCanvasImageInput,
) -> Result<StageAssetCanvasImageResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
stage_asset_canvas_image_at(&root, &input)
}
#[tauri::command]
pub(crate) async fn generate_local_project_asset_canvas_image(
app: tauri::AppHandle,
input: GenerateAssetCanvasImageInput,
) -> Result<GenerateAssetCanvasImageResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
let progress_app = app.clone();
let execution = generate_asset_canvas_image_at(&root, &input, move |payload| {
let _ = progress_app.emit(ASSET_CANVAS_GENERATION_PROGRESS_EVENT, payload);
})
.await?;
if let Some(event) = execution.event.as_ref() {
publish_asset_canvas_event_after_commit_at(&root, event, |payload| {
app.emit(
"game-creator-local-asset-committed",
asset_canvas_committed_public_event(payload),
)
.map_err(|error| error.to_string())
});
}
Ok(execution.result)
}
#[tauri::command]
pub(crate) async fn recover_local_project_asset_canvas_generations(
app: tauri::AppHandle,
input: RecoverAssetCanvasGenerationsInput,
) -> Result<RecoverAssetCanvasGenerationsResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
let progress_app = app.clone();
let execution = recover_asset_canvas_generations_at(&root, &input, move |payload| {
let _ = progress_app.emit(ASSET_CANVAS_GENERATION_PROGRESS_EVENT, payload);
})
.await?;
for event in &execution.events {
publish_asset_canvas_event_after_commit_at(&root, event, |payload| {
app.emit(
"game-creator-local-asset-committed",
asset_canvas_committed_public_event(payload),
)
.map_err(|error| error.to_string())
});
}
Ok(execution.result)
}
#[tauri::command]
pub(crate) async fn confirm_local_project_asset_canvas_generation_service_identity(
input: ConfirmAssetCanvasGenerationServiceIdentityInput,
) -> Result<ConfirmAssetCanvasGenerationServiceIdentityResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
confirm_asset_canvas_generation_service_identity_at(&root, &input).await
}
#[tauri::command]
pub(crate) fn read_local_project_asset_canvas_media(
input: ReadAssetCanvasMediaInput,
) -> Result<ReadAssetCanvasMediaResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
read_asset_canvas_media_at(&root, &input)
}
#[tauri::command]
pub(crate) fn discard_local_project_asset_canvas_draft(
input: DiscardAssetCanvasDraftInput,
) -> Result<DiscardAssetCanvasDraftResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
discard_asset_canvas_draft_at(&root, &input)
}
#[tauri::command]
pub(crate) fn commit_local_project_asset(
app: tauri::AppHandle,
input: CommitAssetCanvasInput,
) -> Result<CommitAssetCanvasResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
let execution = commit_asset_canvas_at(&root, &input)?;
if let Some(event) = execution.event.as_ref() {
publish_asset_canvas_event_after_commit_at(&root, event, |payload| {
app.emit(
"game-creator-local-asset-committed",
asset_canvas_committed_public_event(payload),
)
.map_err(|error| error.to_string())
});
}
Ok(execution.result)
}
#[tauri::command]
pub(crate) fn recover_local_project_asset_canvas_transactions(
app: tauri::AppHandle,
input: RecoverAssetCanvasTransactionsInput,
) -> Result<RecoverAssetCanvasTransactionsResult, String> {
let root = validated_local_project_directory_path(input.project_path.trim())?;
let execution = recover_asset_canvas_transactions_at(&root, &input.expected_project_id)?;
for event in &execution.events {
publish_asset_canvas_event_after_commit_at(&root, event, |payload| {
app.emit(
"game-creator-local-asset-committed",
asset_canvas_committed_public_event(payload),
)
.map_err(|error| error.to_string())
});
}
Ok(execution.result)
}
#[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 start_game_creator_supervisor_runtime_task(
project_path: String,
session_id: Option<String>,
task: String,
run_id: String,
run_profile: Option<String>,
source: Option<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")?;
let run_profile = run_profile
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(AGENT_RUNTIME_RUN_PROFILE_STANDARD);
let source = source
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE);
if !agent_runtime_supervisor_source_is_trusted(source) {
return Err("Project Supervisor 提交 source 不受信任".to_string());
}
start_game_creator_supervisor_background_task_for_session_at(
root,
session_id.as_deref(),
task.trim(),
run_id.trim(),
source,
run_profile,
)
}
#[tauri::command]
pub(crate) async fn compact_game_creator_agent_runtime_context(
project_path: String,
agent_id: String,
session_id: Option<String>,
) -> Result<AgentRuntimeContextCompactionResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "agent.compact")?;
if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
compact_external_agent_runner_context(root, agent_id.trim(), session_id.as_deref())
} else {
compact_game_creator_agent_runtime_session_at(root, agent_id.trim(), session_id.as_deref())
.await
}
}
#[tauri::command]
pub(crate) fn read_game_creator_agent_goal(
project_path: String,
agent_id: String,
session_id: String,
) -> Result<Option<AgentGoalRecord>, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "agent.run_status")?;
read_game_creator_agent_goal_at(root, agent_id.trim(), session_id.trim())
}
#[tauri::command]
pub(crate) fn start_game_creator_agent_goal(
project_path: String,
agent_id: String,
session_id: Option<String>,
outcome: String,
constraints: Vec<String>,
verification: Vec<String>,
run_id: String,
) -> Result<AgentGoalMutationResult, 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_goal_at(
root,
agent_id.trim(),
session_id.as_deref(),
outcome.trim(),
constraints,
verification,
run_id.trim(),
)
}
#[tauri::command]
pub(crate) fn edit_game_creator_agent_goal(
project_path: String,
agent_id: String,
session_id: String,
goal_id: String,
expected_revision: u64,
outcome: String,
constraints: Vec<String>,
verification: Vec<String>,
) -> Result<AgentGoalMutationResult, 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")?;
edit_game_creator_agent_goal_at(
root,
agent_id.trim(),
session_id.trim(),
goal_id.trim(),
expected_revision,
outcome.trim(),
constraints,
verification,
)
}
#[tauri::command]
pub(crate) fn pause_game_creator_agent_goal(
project_path: String,
agent_id: String,
session_id: String,
goal_id: String,
expected_revision: u64,
) -> Result<AgentGoalMutationResult, 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")?;
pause_game_creator_agent_goal_at(
root,
agent_id.trim(),
session_id.trim(),
goal_id.trim(),
expected_revision,
)
}
#[tauri::command]
pub(crate) fn resume_game_creator_agent_goal(
project_path: String,
agent_id: String,
session_id: String,
goal_id: String,
expected_revision: u64,
) -> Result<AgentGoalMutationResult, 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")?;
resume_game_creator_agent_goal_at(
root,
agent_id.trim(),
session_id.trim(),
goal_id.trim(),
expected_revision,
)
}
#[tauri::command]
pub(crate) fn clear_game_creator_agent_goal(
project_path: String,
agent_id: String,
session_id: String,
goal_id: String,
expected_revision: u64,
) -> Result<AgentGoalMutationResult, 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")?;
clear_game_creator_agent_goal_at(
root,
agent_id.trim(),
session_id.trim(),
goal_id.trim(),
expected_revision,
)
}
#[tauri::command]
pub(crate) async fn steer_game_creator_agent_runtime_task(
project_path: String,
agent_id: String,
session_id: String,
run_id: String,
steer_id: String,
instruction: String,
run_profile: Option<String>,
source: Option<String>,
) -> Result<AgentRuntimeSteerResult, 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")?;
if let Some(source) = source
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
if !agent_runtime_supervisor_source_is_trusted(source) {
return Err("Project Supervisor steer source 不受信任".to_string());
}
let task = read_latest_game_creator_agent_runtime_task_by_run_id(
root,
agent_id.trim(),
run_id.trim(),
)?
.ok_or_else(|| "Agent Runtime steer 的 Run 不存在".to_string())?;
if task.source != source {
return Err("Agent Runtime steer source 与当前 Run 不一致".to_string());
}
}
let mut result = steer_game_creator_agent_runtime_task_for_profile_at(
root,
agent_id.trim(),
session_id.trim(),
run_id.trim(),
steer_id.trim(),
instruction.trim(),
run_profile.as_deref(),
"tauri",
)?;
let external_runner =
external_agent_runner_enabled() && !external_agent_runner_is_server_process();
let wake_external_runner_without_interrupt = || -> Result<(), String> {
let provider_interrupted =
steer_external_agent_runner(root, agent_id.trim(), run_id.trim(), steer_id.trim())?;
if provider_interrupted {
return Err("Agent Runner 的 runtime.steer 非法中断了 Provider".to_string());
}
Ok(())
};
let state = result.runtime.state.clone();
let requires_supervisor_decision = state.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
&& state.parent_agent_id.is_none()
&& state.parent_run_id.is_none();
if requires_supervisor_decision {
match decide_game_creator_agent_runtime_steer_at(
root,
&state,
steer_id.trim(),
result.sequence,
instruction.trim(),
)
.await
{
Ok(decision) => {
result.assistant_reply = Some(decision.reply.clone());
result.interrupt_decision = Some(decision.interrupt_current_provider);
result.decision_reason = Some(decision.reason.clone());
if decision.interrupt_current_provider {
result.provider_interrupted = if external_runner {
interrupt_external_agent_runner_provider_for_steer_decision(
root,
agent_id.trim(),
run_id.trim(),
steer_id.trim(),
)?
} else {
interrupt_game_creator_agent_runtime_provider_for_decided_steer_at(
root,
agent_id.trim(),
run_id.trim(),
steer_id.trim(),
)?
};
if !external_runner {
wake_pending_game_creator_agent_background_tasks_at(root)
.map_err(|error| error.to_string())?;
}
} else if external_runner {
wake_external_runner_without_interrupt()?;
} else {
wake_pending_game_creator_agent_background_tasks_at(root)
.map_err(|error| error.to_string())?;
}
}
Err(error) => {
result.assistant_reply = Some(
append_game_creator_agent_runtime_steer_decision_failure_reply_at(
root,
&state,
steer_id.trim(),
&error,
)?,
);
result.decision_reason = Some("decision-failed".to_string());
if external_runner {
wake_external_runner_without_interrupt()?;
} else {
wake_pending_game_creator_agent_background_tasks_at(root)
.map_err(|error| error.to_string())?;
}
}
}
} else if external_runner {
wake_external_runner_without_interrupt()?;
} else {
wake_pending_game_creator_agent_background_tasks_at(root)
.map_err(|error| error.to_string())?;
}
result.runtime = read_game_creator_agent_runtime_for_session_at(
root,
agent_id.trim(),
Some(session_id.trim()),
)?;
Ok(result)
}
#[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_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")?;
// 正式工作台的“在当前项目重试”按钮本身就是用户对本次 agent.resume 的明确确认。
enforce_project_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 answer_game_creator_agent_runtime_user_input(
project_path: String,
agent_id: String,
run_id: String,
action_id: String,
request_id: String,
response_id: String,
answers: BTreeMap<String, 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")?;
answer_game_creator_agent_runtime_user_input_at(
root,
agent_id.trim(),
run_id.trim(),
action_id.trim(),
request_id.trim(),
response_id.trim(),
answers,
)
}
#[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, "agent.run_status")?;
if !has_recoverable_game_creator_agent_background_tasks_at(root)? {
return Ok(Vec::new());
}
enforce_project_permission_policy(root, "conversation.write")?;
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 read_game_creator_mcp_catalog(
project_path: String,
) -> Result<GameCreatorMcpCatalog, String> {
read_external_agent_runner_mcp_catalog(Path::new(project_path.trim()))
}
#[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),
generation_route: None,
generation_kind: None,
reference_resource_ids: Vec::new(),
},
)
}
#[tauri::command]
pub(crate) async fn derive_local_project_resource(
input: DeriveLocalProjectResourceInput,
) -> Result<DeriveLocalProjectResourceResult, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
derive_local_project_resource_at(input).await
}
#[tauri::command]
pub(crate) fn list_pending_local_project_resource_edits(
input: ListPendingLocalProjectResourceEditsInput,
) -> Result<Vec<PendingLocalProjectResourceEdit>, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "file.list")?;
list_pending_local_project_resource_edits_at(input)
}
#[tauri::command]
pub(crate) async fn resume_local_project_resource_edit(
input: ResumeLocalProjectResourceEditInput,
) -> Result<DeriveLocalProjectResourceResult, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
resume_local_project_resource_edit_at(input).await
}
#[tauri::command]
pub(crate) async fn request_local_project_resource_edit_service_identity_confirmation(
input: RequestResourceEditServiceIdentityConfirmationInput,
) -> Result<ResourceEditServiceIdentityConfirmation, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
request_resource_edit_service_identity_confirmation_at(input).await
}
#[tauri::command]
pub(crate) async fn confirm_local_project_resource_edit_service_identity(
input: ConfirmResourceEditServiceIdentityInput,
) -> Result<ConfirmResourceEditServiceIdentityResult, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
confirm_resource_edit_service_identity_at(input).await
}
#[tauri::command]
pub(crate) async fn archive_failed_local_project_resource_edit(
input: ArchiveFailedLocalProjectResourceEditInput,
) -> Result<ArchiveFailedLocalProjectResourceEditResult, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
archive_failed_local_project_resource_edit_at(input).await
}
#[tauri::command]
pub(crate) fn normalize_local_project_raster_resource(
input: NormalizeLocalProjectRasterResourceInput,
) -> Result<NormalizeLocalProjectRasterResourceResult, String> {
let root = Path::new(input.project_path.trim());
enforce_project_permission_policy(root, "asset.register")?;
normalize_local_project_raster_resource_at(input)
}
#[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 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) async fn read_local_project_image_preview(
preview_manager: tauri::State<'_, ProjectResourcePreviewReadManager>,
project_path: String,
relative_path: String,
scope_id: String,
request_id: String,
) -> Result<LocalProjectImagePreview, String> {
preview_manager
.run(&scope_id, &request_id, move |cancellation| {
read_local_project_image_preview_at(&project_path, &relative_path, cancellation)
})
.await
}
pub(crate) fn read_local_project_image_preview_at(
project_path: &str,
relative_path: &str,
cancellation: &ProjectResourcePreviewScopeCancellation,
) -> Result<LocalProjectImagePreview, String> {
cancellation.check()?;
let root = Path::new(project_path.trim());
enforce_project_auto_permission_policy(root, "file.read")?;
cancellation.check()?;
let normalized_path = normalize_relative_path(relative_path.trim())?;
let manifest = read_manifest(&root.join(".agent/manifest.json"))?;
cancellation.check()?;
let is_registered_asset = manifest
.assets
.iter()
.any(|asset| asset.local_path == normalized_path);
let is_completed_task_artifact = manifest.tasks.iter().any(|task| {
task.status == GameCreationAppTaskStatus::Completed
&& task.artifacts.iter().any(|path| path == &normalized_path)
});
if !is_registered_asset && !is_completed_task_artifact {
return Err("只能预览已登记资源或已完成任务的图片产物".to_string());
}
cancellation.check()?;
load_local_project_image_preview_with_cancellation(root, &normalized_path, cancellation)
}
#[tauri::command]
pub(crate) async fn read_local_project_text_preview(
preview_manager: tauri::State<'_, ProjectResourcePreviewReadManager>,
project_path: String,
relative_path: String,
scope_id: String,
request_id: String,
) -> Result<LocalProjectTextPreview, String> {
preview_manager
.run(&scope_id, &request_id, move |cancellation| {
read_local_project_text_preview_at(&project_path, &relative_path, cancellation)
})
.await
}
pub(crate) fn read_local_project_text_preview_at(
project_path: &str,
relative_path: &str,
cancellation: &ProjectResourcePreviewScopeCancellation,
) -> Result<LocalProjectTextPreview, String> {
cancellation.check()?;
let root = Path::new(project_path.trim());
enforce_project_auto_permission_policy(root, "file.read")?;
cancellation.check()?;
let normalized_path = normalize_relative_path(relative_path.trim())?;
let manifest = read_manifest(&root.join(".agent/manifest.json"))?;
cancellation.check()?;
let is_registered_document = manifest.assets.iter().any(|asset| {
asset.local_path == normalized_path
&& is_supported_project_text_resource(&asset.local_path, &asset.media_type)
}) || manifest.tasks.iter().any(|task| {
task.status == GameCreationAppTaskStatus::Completed
&& task.artifacts.iter().any(|path| path == &normalized_path)
&& is_supported_project_text_resource(&normalized_path, "")
});
if !is_registered_document {
return Err("只能读取当前项目已登记的文档资源".to_string());
}
cancellation.check()?;
load_local_project_text_preview_with_cancellation(root, &normalized_path, cancellation)
}
#[tauri::command]
pub(crate) async fn read_local_project_media_preview(
preview_manager: tauri::State<'_, ProjectResourcePreviewReadManager>,
project_path: String,
relative_path: String,
category: String,
scope_id: String,
request_id: String,
) -> Result<LocalProjectMediaPreview, String> {
preview_manager
.run(&scope_id, &request_id, move |cancellation| {
read_local_project_media_preview_at(
&project_path,
&relative_path,
&category,
cancellation,
)
})
.await
}
pub(crate) fn read_local_project_media_preview_at(
project_path: &str,
relative_path: &str,
category: &str,
cancellation: &ProjectResourcePreviewScopeCancellation,
) -> Result<LocalProjectMediaPreview, String> {
cancellation.check()?;
let root = Path::new(project_path.trim());
enforce_project_auto_permission_policy(root, "file.read")?;
cancellation.check()?;
let normalized_path = normalize_relative_path(relative_path.trim())?;
let manifest = read_manifest(&root.join(".agent/manifest.json"))?;
cancellation.check()?;
let kind = match category.trim() {
"art" => ProjectMediaPreviewKind::Art,
"audio" => ProjectMediaPreviewKind::Audio,
_ => return Err("媒体预览类别只支持 art 或 audio".to_string()),
};
let is_registered_media = manifest.assets.iter().any(|asset| {
asset.local_path == normalized_path
&& match kind {
ProjectMediaPreviewKind::Art => {
is_supported_project_art_media_resource(&asset.local_path, &asset.media_type)
}
ProjectMediaPreviewKind::Audio => {
is_supported_project_audio_resource(&asset.local_path, &asset.media_type)
}
}
}) || manifest.tasks.iter().any(|task| {
task.status == GameCreationAppTaskStatus::Completed
&& task.artifacts.iter().any(|path| path == &normalized_path)
&& match kind {
ProjectMediaPreviewKind::Art => {
is_supported_project_art_media_resource(&normalized_path, "")
}
ProjectMediaPreviewKind::Audio => {
is_supported_project_audio_resource(&normalized_path, "")
}
}
});
if !is_registered_media {
return Err("只能预览当前项目已登记的媒体资源".to_string());
}
cancellation.check()?;
load_local_project_media_preview_with_cancellation(root, &normalized_path, kind, cancellation)
}
#[tauri::command]
pub(crate) fn cancel_local_project_resource_preview_scope(
preview_manager: tauri::State<'_, ProjectResourcePreviewReadManager>,
scope_id: String,
) -> Result<(), String> {
preview_manager.cancel_scope(&scope_id)
}
#[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 fork_game_creator_agent_session(
project_path: String,
agent_id: String,
source_session_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")?;
fork_game_creator_agent_session_at(
root,
agent_id.trim(),
source_session_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,
message_id: Option<String>,
) -> 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")?;
match message_id.as_deref() {
Some(message_id) => append_local_conversation_message_for_session_idempotent_at(
root,
agent_id.as_deref(),
session_id.as_deref(),
message,
message_id,
),
None => 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)
}