8e3d9991fa
实现资源卡本体化与图片视频音频预览播放 实现四个资源分区独立高度与内外滚动 实现依赖视图确定性聚类与拓扑变化重排 补齐布局、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>
1909 lines
69 KiB
Rust
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)
|
|
}
|