From 2c5635a35a6c48da6206de03b3d2dfcf4738ab9c Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Fri, 10 Jul 2026 06:58:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=B0=83=E5=BA=A6Ready?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=BB=99Agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 agent.schedule_ready 命令,将 manifest ready task 投递到对应 Agent 后台队列。 为调度入口接入默认确认权限、Tauri handler 和 native-only 门禁。 补充 Runtime 测试覆盖 ready 任务调度和默认策略拦截。 同步 Agent Runtime 技术方案和项目决策记录。 --- .../scripts/check-config.mjs | 1 + .../src-tauri/src/agent.rs | 134 +++++++++++++++++- .../src-tauri/src/commands.rs | 13 ++ .../src-tauri/src/main.rs | 1 + .../src-tauri/src/tests.rs | 101 +++++++++++++ apps/ai-game-creator-shell/src/App.tsx | 5 +- .../shared-memory/decision-log.md | 1 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 4 +- .../shared/src/contracts/gameCreationApp.ts | 1 + .../shared-contracts/src/game_creation_app.rs | 4 +- 10 files changed, 258 insertions(+), 7 deletions(-) diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index d42276459..311c68009 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -55,6 +55,7 @@ const rustSharedContractSource = fs.readFileSync( const allowedUncalledTauriCommands = [ 'open_game_creator_launcher_window', 'open_game_creator_workspace_window', + 'schedule_game_creator_agent_ready_tasks', ]; const sourceExtensions = new Set([ '.json', diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent.rs b/apps/ai-game-creator-shell/src-tauri/src/agent.rs index 10017dd9e..cbd668cfe 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent.rs @@ -388,6 +388,26 @@ fn start_game_creator_agent_background_task_with_confirmed_tool_at( run_id: &str, confirmed_command_id: Option<&str>, confirmation_note: &str, +) -> Result<(AgentRuntimeResult, String), String> { + start_game_creator_agent_background_task_with_source_at( + root, + agent_id, + task, + run_id, + "agent-background-task", + confirmed_command_id, + confirmation_note, + ) +} + +fn start_game_creator_agent_background_task_with_source_at( + root: &Path, + agent_id: &str, + task: &str, + run_id: &str, + source: &str, + confirmed_command_id: Option<&str>, + confirmation_note: &str, ) -> Result<(AgentRuntimeResult, String), String> { let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?; validate_project_root(root)?; @@ -395,9 +415,14 @@ fn start_game_creator_agent_background_task_with_confirmed_tool_at( if task.is_empty() { return Err("Agent 后台任务不能为空".to_string()); } + let source = if source.trim().is_empty() { + "agent-background-task" + } else { + source.trim() + }; let run_id = unique_game_creator_agent_runtime_run_id(root, &agent_id, run_id)?; let pending_task = - append_game_creator_agent_runtime_pending_task(root, &agent_id, task, &run_id)?; + append_game_creator_agent_runtime_pending_task(root, &agent_id, task, &run_id, source)?; append_local_conversation_message_at( root, Some(&agent_id), @@ -441,7 +466,7 @@ fn start_game_creator_agent_background_task_with_confirmed_tool_at( &agent_id, task, &run_id, - "agent-background-task", + source, "后台任务已投递", game_creator_agent_background_task_default_plan(), )?; @@ -465,6 +490,108 @@ fn start_game_creator_agent_background_task_with_confirmed_tool_at( Ok((result, run_id)) } +pub(crate) fn schedule_game_creator_agent_ready_tasks_at( + root: &Path, + limit: usize, +) -> Result, String> { + validate_project_root(root)?; + let limit = if limit == 0 { 16 } else { limit.min(16) }; + let ready_tasks = { + let _lock = acquire_project_write_lock(root, "agent.schedule_ready")?; + let manifest = read_manifest_for_project(root)?; + let ready_task_ids = ready_task_ids_for_tasks(&manifest.tasks); + let mut ready_tasks = Vec::new(); + for task_id in ready_task_ids.into_iter().take(limit) { + let Some(task) = manifest.tasks.iter().find(|task| task.id == task_id) else { + continue; + }; + update_manifest_task_status_at(root, &task.id, GameCreationAppTaskStatus::Running)?; + ready_tasks.push(task.clone()); + } + ready_tasks + }; + + let mut results = Vec::new(); + for task in ready_tasks { + let run_id = format!("ready-{}-{}", task.id, unix_timestamp_nanos()); + let task_text = render_manifest_ready_task_background_prompt(&task); + match start_game_creator_agent_background_task_with_source_at( + root, + &task.id, + &task_text, + &run_id, + "agent-ready-task-scheduler", + None, + "", + ) { + Ok((result, actual_run_id)) => { + append_agent_db_record( + root, + serde_json::json!({ + "recordType": "agent.runtime.ready_task.scheduled", + "agentId": result.state.agent_id, + "taskId": task.id.clone(), + "runId": actual_run_id, + "source": result.state.source, + "title": task.title.clone(), + "group": agent_runtime_task_group_label(&task.group), + "role": task.role.clone(), + }), + )?; + results.push(result); + } + Err(error) => { + let _ = update_manifest_task_status_at( + root, + &task.id, + GameCreationAppTaskStatus::Failed, + ); + let _ = append_agent_db_record( + root, + serde_json::json!({ + "recordType": "agent.runtime.ready_task.schedule_failed", + "agentId": task.id.clone(), + "taskId": task.id.clone(), + "source": "agent-ready-task-scheduler", + "title": task.title.clone(), + "error": sanitize_agent_runtime_text(&error, 240), + }), + ); + return Err(error); + } + } + } + Ok(results) +} + +fn render_manifest_ready_task_background_prompt(task: &GameCreationAppTaskState) -> String { + let dependencies = if task.dependencies.is_empty() { + "无".to_string() + } else { + task.dependencies.join(", ") + }; + let artifacts = if task.artifacts.is_empty() { + "未指定".to_string() + } else { + task.artifacts.join(", ") + }; + let acceptance = if task.acceptance_criteria.is_empty() { + "未指定".to_string() + } else { + task.acceptance_criteria.join(";") + }; + format!( + "处理 manifest ready 任务:{}\n\n任务 ID:{}\n专业组:{}\n角色:{}\n依赖:{}\n预期产物:{}\n验收标准:{}\n\n请按你的 Agent 职责自主规划、调用可用工具、记录观察,并在完成或阻塞时更新任务状态。", + task.title, + task.id, + agent_runtime_task_group_label(&task.group), + task.role, + dependencies, + artifacts, + acceptance + ) +} + pub(crate) fn cancel_game_creator_agent_runtime_task_at( root: &Path, agent_id: &str, @@ -4254,6 +4381,7 @@ fn append_game_creator_agent_runtime_pending_task( agent_id: &str, task: &str, run_id: &str, + source: &str, ) -> Result { let record = AgentRuntimeTaskRecord { schema_version: AGENT_RUNTIME_SCHEMA_VERSION.to_string(), @@ -4261,7 +4389,7 @@ fn append_game_creator_agent_runtime_pending_task( task_id: agent_id.to_string(), session_id: format!("agent-session-{agent_id}"), run_id: run_id.to_string(), - source: "agent-background-task".to_string(), + source: source.trim().to_string(), task: sanitize_agent_runtime_text(task, 180), status: "pending".to_string(), phase: "queued".to_string(), diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index 2d5400461..ad46a17f1 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -479,6 +479,19 @@ pub(crate) fn resume_game_creator_agent_runtime_tasks( 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, 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() diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index 955240158..77791d0bc 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -1206,6 +1206,7 @@ fn main() { read_game_creator_agent_runtime, read_game_creator_agent_runtimes, resume_game_creator_agent_runtime_tasks, + schedule_game_creator_agent_ready_tasks, check_game_creator_llm_config, read_game_creator_app_config, write_game_creator_app_config, diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests.rs b/apps/ai-game-creator-shell/src-tauri/src/tests.rs index e82b82b85..7e26afc62 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests.rs @@ -3918,6 +3918,107 @@ async fn background_agent_runtime_task_update_respects_project_policy() { fs::remove_dir_all(root).ok(); } +#[tokio::test] +async fn background_agent_runtime_can_schedule_ready_manifest_tasks() { + let root = unique_project_path(); + init_local_game_project_at(&root, "project-1", "月光厨房").expect("project init"); + write_project_permission_policy_at( + &root, + ProjectPermissionPolicy { + denied_commands: Vec::new(), + confirm_commands: Vec::new(), + agent_policies: BTreeMap::new(), + }, + ) + .expect("allow ready task scheduler"); + + let (sender, receiver) = mpsc::channel(); + let plan_json = serde_json::json!({ + "thinkingSummary": "开始处理 ready manifest 任务", + "plan": ["读取任务要求", "标记任务完成"], + "actions": [ + { + "tool": "task.update", + "reason": "ready 任务已经完成拆解", + "input": { "taskId": "design-director", "status": "completed" } + } + ], + "response": "" + }) + .to_string(); + let base_url = spawn_mock_llm_server_responses_with_capture( + vec![plan_json, "已完成 ready manifest 任务。".to_string()], + Some(sender), + ); + let _config_guard = write_test_local_config(format!( + r#"{{ + "agentLlm": {{ + "design-director": {{ + "apiKey": "design-key", + "baseUrl": {base_url:?}, + "model": "design-runtime-model", + "apiKind": "openai_responses" + }} + }} +}}"# + )); + + let scheduled = + schedule_game_creator_agent_ready_tasks_at(&root, 0).expect("schedule ready tasks"); + assert_eq!(scheduled.len(), 1); + assert_eq!(scheduled[0].state.agent_id, "design-director"); + assert_eq!(scheduled[0].state.source, "agent-ready-task-scheduler"); + assert!(scheduled[0] + .state + .current_task + .contains("处理 manifest ready 任务:拆解创作方向")); + + let plan_request = receiver + .recv_timeout(Duration::from_secs(2)) + .expect("plan llm request"); + assert!(plan_request.contains("处理 manifest ready 任务:拆解创作方向")); + assert!(plan_request.contains("task.update")); + let final_request = receiver + .recv_timeout(Duration::from_secs(2)) + .expect("final reply llm request"); + assert!(final_request.contains("任务 design-director 已更新为 completed")); + + let runtime = wait_for_agent_runtime_idle(&root, "design-director"); + assert_eq!(runtime.status, "idle"); + assert_eq!(runtime.source, "agent-ready-task-scheduler"); + assert!(runtime + .observations + .iter() + .any(|item| item.contains("task.update:ok · 任务 design-director 已更新为 completed"))); + let manifest_after: Value = + serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap()) + .expect("manifest json"); + assert_task_status(&manifest_after, "design-director", "completed"); + let agent_db = fs::read_to_string(root.join(".agent/agent.db")).expect("agent db"); + assert!(agent_db.contains("\"recordType\":\"agent.runtime.ready_task.scheduled\"")); + assert!(agent_db.contains("\"source\":\"agent-ready-task-scheduler\"")); + + fs::remove_dir_all(root).ok(); +} + +#[test] +fn schedule_ready_manifest_tasks_command_requires_auto_policy() { + let root = unique_project_path(); + init_local_game_project_at(&root, "project-1", "月光厨房").expect("project init"); + + let error = schedule_game_creator_agent_ready_tasks(root.to_string_lossy().into_owned(), 0) + .expect_err("default policy should require confirmation"); + assert!(error.contains("项目权限策略要求用户确认:agent.schedule_ready")); + let manifest: Value = + serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap()) + .expect("manifest json"); + assert_task_status(&manifest, "design-director", "pending"); + let agent_db = fs::read_to_string(root.join(".agent/agent.db")).expect("agent db"); + assert!(!agent_db.contains("\"recordType\":\"agent.runtime.ready_task.scheduled\"")); + + fs::remove_dir_all(root).ok(); +} + #[tokio::test] async fn background_agent_runtime_can_run_limited_static_smoke() { let root = unique_project_path(); diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index d38ee7f18..eb95e2596 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -11086,6 +11086,7 @@ function isProjectPolicyConfirmableCommandId(value: string) { 'agent.retry', 'agent.resume', 'agent.delegate', + 'agent.schedule_ready', 'agent.audit', 'agent.trace_read', 'preview.status', @@ -15309,7 +15310,7 @@ export function App() { ...current, { role: 'assistant', - text: '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、project.export_package、project.export_list、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、task.create、task.update、agent.run_status、agent.kill、agent.retry、agent.resume、agent.delegate、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', + text: '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、project.export_package、project.export_list、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、task.create、task.update、agent.run_status、agent.kill、agent.retry、agent.resume、agent.delegate、agent.schedule_ready、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', }, ]); return; @@ -15372,7 +15373,7 @@ export function App() { ...current, { role: 'assistant', - text: '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、project.export_package、project.export_list、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、task.create、task.update、agent.run_status、agent.kill、agent.retry、agent.resume、agent.delegate、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', + text: '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、project.export_package、project.export_list、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、task.create、task.update、agent.run_status、agent.kill、agent.retry、agent.resume、agent.delegate、agent.schedule_ready、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', }, ]); return; diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index f769d5e8a..dd6c83026 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4068,6 +4068,7 @@ - 2026-07-10 调整:Agent Runtime 和本地对话使用 append-only JSONL 作为事实源时,进程内必须按目标文件路径串行追加整行。`.agent/agent.db`、`.agent/conversations/**/*.jsonl`、`.agent/runtime/events/*.jsonl`、`.agent/runtime/tasks/*.jsonl`、`.agent/activity.jsonl` 和 `.agent/output.jsonl` 统一走共享追加 helper,避免多个后台 Agent 并行完成时 JSON record 与换行交错。 - 2026-07-10 调整:Agent Runtime 待确认工具动作支持确认后继续。开发者确认 `waiting-for-confirmation` run 时,Runtime 为新 run 写入一次性 `.agent/runtime/confirmations///.json` 票据并重新入队;工具权限 gate 在 deny 之后、confirm 阶段消费该票据,只放行对应 Agent/run/command 一次。原 waiting run 追加 `completed/confirmed` 任务记录,避免队列长期显示等待确认;审计记录写 `agent.runtime.tool_confirmation.approved`。 - 2026-07-10 调整:Agent Runtime 工具箱新增 `task.create`,用于让 Agent 把目标拆成新的 manifest 任务,而不只能更新 seed task。该工具默认 `confirm` 权限,写入前要求 taskId 唯一、依赖指向已有任务、列表长度受限,并写 `agent.runtime.task.create` 审计;策略要求确认或拒绝时不修改 `.agent/manifest.json`。 +- 2026-07-10 调整:Agent Runtime 新增 `agent.schedule_ready` 调度入口,默认 `confirm` 权限。命令会扫描 `.agent/manifest.json` 中依赖已完成且仍为 `pending` 的 ready task,先把任务标成 `running`,再用 taskId 作为 Agent id 投递到既有后台队列,source 记为 `agent-ready-task-scheduler`,并写 `agent.runtime.ready_task.scheduled` 审计;后续执行仍走原 per-agent 锁、任务 JSONL、LLM loop、工具策略和事件流,不新增独立 worker。默认确认策略下该命令不会静默调度。 - 2026-07-10 调整:Agent Runtime state 新增 `recentToolCalls`,后台 loop 每次执行白名单工具后记录最近 20 条结构化动作,包含 tool、status、reason、summary、detail 和 updatedAt。状态面板展示最近动作时使用该字段,不解析 observation 文本;写入前继续过滤敏感上下文,不保存原始密钥或任意未过滤输入。 - 2026-07-10 调整:Agent Runtime state 新增 `currentGoal` 和 `waitingOn`。`currentGoal` 固定表达本轮任务目标,`waitingOn` 表达当前等待 LLM、工具观察、开发者输入或失败处理;后台任务生命周期、`agent.run_status` observation、下一轮 planning prompt、开发单 Agent 对话页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都必须展示同一份目标 / 等待状态。 - 2026-07-10 调整:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`。后台 Agent loop 每轮规划前刷新当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,字段只做运行观测,不改变 loop 上限或权限 gate。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 8953f6392..235bfd0dc 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -49,6 +49,7 @@ Agent Runtime 负责: - 2026-07-10 补充:`recentEvents` 接入前端归一态和 Runtime 状态面板,事件事实源仍是 `.agent/runtime/events/.jsonl`;面板按时间展示最近 `thinking_summary / plan / action / observation / response / error` 事件,现在能同时看到 Agent 的计划、最近观察、最近事件、最近工具动作和任务队列。 - 2026-07-10 补充:后台 Runtime 每次追加 `.agent/runtime/events/.jsonl` 后会通过 Tauri `game-creator-agent-runtime-update` 事件广播当前 `AgentRuntimeResult`,开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态卡用同一套前端归一化逻辑合并状态;该事件只做实时 UI 通知,`.agent/runtime/agents`、`events` 和 `tasks` 仍是重开项目后的事实源。 - 2026-07-10 补充:Agent Runtime state / result 新增 `taskQueue`,从 `.agent/runtime/tasks/.jsonl` 中每个 `runId` 的最新记录汇总 `total / pending / running / completed / failed / latestRunId`;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都读取该摘要,用于判断同一 Agent 是否仍有排队任务。该字段是运行观测摘要,不新增调度器、SQLite 或独立 worker。 +- 2026-07-10 补充:Runtime 新增 `agent.schedule_ready` 调度入口。开发构建可在权限确认后扫描 manifest ready task,把依赖已完成且仍为 `pending` 的任务标成 `running`,并按 taskId 投递到对应 Agent 的既有后台队列;source 固定为 `agent-ready-task-scheduler`,审计记录写 `agent.runtime.ready_task.scheduled`。该入口只把 manifest ready task 接入现有 per-agent 队列、锁、JSONL、LLM loop、工具策略和事件流,不新增独立 worker,也不会在默认确认策略下静默启动。 - 2026-07-10 补充:单 Agent 聊天和后台 planning prompt 会读取同一个 Agent 的 Runtime 连续上下文,把本 Agent 最近 status / phase / runId / 当前任务 / 下一步、最近回复、计划、观察、最近 3 条工具动作、最近事件、最近 3 条任务记录和工具策略摘要带入下一轮推理;上下文按规范 taskId 隔离,不读取其他 Agent 的 runtime 文件,并在进入 prompt 前过滤密钥和本机绝对路径。新后台 run 启动时会继承本 Agent 上次 `recentToolCalls` 和 `lastResponse`,让多轮任务不丢失结构化行动证据。 - 2026-07-10 补充:后台任务工具箱已加入 `preview.start`。Agent 可在 loop 中自行请求启动当前项目的本地 HTTP 预览;Runtime 会复用 `preview.start` 策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑,并把 `agent.runtime.preview.start` 写入 `.agent/agent.db`。该 observation 只向 LLM 返回 localhost URL 与端口,不返回用户项目绝对路径。 - 2026-07-10 补充:后台任务工具箱已加入 `canvas.asset_generate`。Agent 可在 loop 中自行给出素材 prompt,通过 AppData / Tauri 配置里的 `editorApi` 调用 External Editor API 生成首版美术素材、下载到 `assets/canvas-generated/` 并登记 manifest;Runtime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。 @@ -280,7 +281,7 @@ game-project/ - Tauri Rust 入口保持薄壳:`src-tauri/src/main.rs` 只保留共享类型 / 常量、模块声明、CLI preflight、`tauri::Builder`、运行时配置初始化和 `invoke_handler` 清单;命令行入口放在 `cli.rs`,Tauri command 包装放在 `commands.rs`,运行时配置与 LLM 配置检查放在 `config.rs`,Agent loop 与生成编排放在 `agent.rs`,上传 / 画板 / 平台美术生成接入放在 `assets.rs`,本地项目文件、记忆、对话、权限、checkpoint、manifest 和通用路径工具放在 `project.rs`,本地 HTTP 预览与 preview 命令放在 `preview.rs`,旧窗口兼容命令放在 `windows.rs`,Rust 单测放在 `tests.rs`。后续继续拆分时保持 Tauri command 名、JSON 字段、`.agent/*` 路径和错误语义不变。 - 本地项目初始化会创建 `game/`、`assets/`、`memory/`、`memory/agents/`、`exports/`、`.agent/logs/`,写入 `.agent/manifest.json`,生成 append-only JSONL 本地项目索引 `.agent/agent.db`,并生成默认 `game/index.html`。 - v1 conversation 记录使用 append-only JSONL,每行带 `schemaVersion`、`role`、`content`、`agentId` 和 `updatedAt`,作为聊天历史和单 agent 对话历史的事实源;目录在首次写入时创建。 -- 开发窗口和项目内 Agent 对话弹窗的“后台运行”只启动或排队单 Agent 后台任务,不阻塞等待回复;用户可刷新同一 Agent 对话或 runtime 状态查看进度和结果,也可对当前 run 执行取消 / 重试,待确认 run 还可执行“确认继续”。后台任务会向 `.agent/runtime/tasks/.jsonl` 追加任务视角记录,任务状态使用 `pending / running / waiting-for-confirmation / cancelled / completed / failed`,读取时按 `runId` 去重返回最近任务;`runId` 在同一 Agent 内是单个 run 的身份,后台入队会自动把重复 runId 改写为唯一实际 runId,防止不同任务互相覆盖;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行;若某个工具动作命中确认策略,该 Agent 会停在 `waiting-for-confirmation` 并暂停继续消费队列,等待后续确认或策略调整;确认继续会为新 run 写入一次性 `.agent/runtime/confirmations///.json` 票据,权限 gate 在 deny 之后、confirm 阶段消费票据并只放行对应工具一次,原 run 追加 `completed/confirmed` 任务记录;若用户取消 pending 任务,drain 不再消费该 run,若取消 running 任务,则在当前 LLM 或工具调用返回后的检查点停止,不继续执行工具或保存最终 assistant 回复。客户端重开项目时会对当前项目路径自动尝试一次 Runtime 恢复;恢复命令必须通过 `agent.resume` 自动权限,默认确认策略下不会静默启动;同一 Agent 同时存在上一进程遗留 `running` 和 `pending` 时,先重接 `running`,再由 drain 继续 `pending`。后台任务的核心 loop 最多 3 轮:每轮把已有 observation 带回 LLM 让 Agent 重新规划,只有 actions 为空且 response 非空时提前收束,否则继续执行白名单工具,跑满后再进入最终回复整理。后台任务完成后会把 assistant 回复追加到对应 `.agent/conversations/agents/.jsonl`,并向 `.agent/agent.db` 写入 `agent.runtime.background_task.queued` / `agent.runtime.background_task` / `agent.runtime.background_task.recovered` / `agent.runtime.tool_observation` / `agent.runtime.tool_confirmation_required` / `agent.runtime.tool_confirmation.approved` / `agent.runtime.memory.write` / `agent.runtime.file.write` / `agent.runtime.task.create` / `agent.runtime.task.update` / `agent.runtime.command.run_limited` / `agent.runtime.blackboard.write` / `agent.runtime.agent.message` / `agent.runtime.agent.delegate` / `agent.runtime.background_task.cancelled` / `agent.runtime.background_task.retry` / `agent.runtime.background_task.completed` / `agent.runtime.background_task.failed` 审计记录。当前工具箱开放只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`task.list`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`task.create`、`task.update`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate`;`memory.write scope=agent` 只允许写当前 Agent 自己的私有记忆,跨 Agent 共享必须改用 `blackboard.write` 或 `agent.message`;`task.create` 只追加新 manifest 任务,`task.update` 只更新已有任务状态;若项目策略拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent 修正计划;若项目策略要求确认且没有一次性确认票据,Runtime 会保留待确认状态而不执行该工具。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;append-only JSONL 写入按目标文件路径在当前进程内串行追加完整行,覆盖 `.agent/agent.db`、对话、Runtime events/tasks、activity 和 output,减少多个 Agent 同时完成时的行交错风险。 +- 开发窗口和项目内 Agent 对话弹窗的“后台运行”只启动或排队单 Agent 后台任务,不阻塞等待回复;用户可刷新同一 Agent 对话或 runtime 状态查看进度和结果,也可对当前 run 执行取消 / 重试,待确认 run 还可执行“确认继续”。后台任务会向 `.agent/runtime/tasks/.jsonl` 追加任务视角记录,任务状态使用 `pending / running / waiting-for-confirmation / cancelled / completed / failed`,读取时按 `runId` 去重返回最近任务;`runId` 在同一 Agent 内是单个 run 的身份,后台入队会自动把重复 runId 改写为唯一实际 runId,防止不同任务互相覆盖;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行;若某个工具动作命中确认策略,该 Agent 会停在 `waiting-for-confirmation` 并暂停继续消费队列,等待后续确认或策略调整;确认继续会为新 run 写入一次性 `.agent/runtime/confirmations///.json` 票据,权限 gate 在 deny 之后、confirm 阶段消费票据并只放行对应工具一次,原 run 追加 `completed/confirmed` 任务记录;若用户取消 pending 任务,drain 不再消费该 run,若取消 running 任务,则在当前 LLM 或工具调用返回后的检查点停止,不继续执行工具或保存最终 assistant 回复。客户端重开项目时会对当前项目路径自动尝试一次 Runtime 恢复;恢复命令必须通过 `agent.resume` 自动权限,默认确认策略下不会静默启动;同一 Agent 同时存在上一进程遗留 `running` 和 `pending` 时,先重接 `running`,再由 drain 继续 `pending`。开发构建还可通过 `agent.schedule_ready` 权限确认入口把 manifest ready task 投递进同一后台队列,命令会先把 ready task 标成 `running`,再用 taskId 作为 Agent id 入队,source 为 `agent-ready-task-scheduler`;该入口不新增独立 worker。后台任务的核心 loop 最多 3 轮:每轮把已有 observation 带回 LLM 让 Agent 重新规划,只有 actions 为空且 response 非空时提前收束,否则继续执行白名单工具,跑满后再进入最终回复整理。后台任务完成后会把 assistant 回复追加到对应 `.agent/conversations/agents/.jsonl`,并向 `.agent/agent.db` 写入 `agent.runtime.background_task.queued` / `agent.runtime.background_task` / `agent.runtime.background_task.recovered` / `agent.runtime.ready_task.scheduled` / `agent.runtime.tool_observation` / `agent.runtime.tool_confirmation_required` / `agent.runtime.tool_confirmation.approved` / `agent.runtime.memory.write` / `agent.runtime.file.write` / `agent.runtime.task.create` / `agent.runtime.task.update` / `agent.runtime.command.run_limited` / `agent.runtime.blackboard.write` / `agent.runtime.agent.message` / `agent.runtime.agent.delegate` / `agent.runtime.background_task.cancelled` / `agent.runtime.background_task.retry` / `agent.runtime.background_task.completed` / `agent.runtime.background_task.failed` 审计记录。当前工具箱开放只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`task.list`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`task.create`、`task.update`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate`;`memory.write scope=agent` 只允许写当前 Agent 自己的私有记忆,跨 Agent 共享必须改用 `blackboard.write` 或 `agent.message`;`task.create` 只追加新 manifest 任务,`task.update` 只更新已有任务状态;若项目策略拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent 修正计划;若项目策略要求确认且没有一次性确认票据,Runtime 会保留待确认状态而不执行该工具。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;append-only JSONL 写入按目标文件路径在当前进程内串行追加完整行,覆盖 `.agent/agent.db`、对话、Runtime events/tasks、activity 和 output,减少多个 Agent 同时完成时的行交错风险。 - 2026-07-10 补充:当前工具箱还开放 `preview.start`,审计记录类型为 `agent.runtime.preview.start`;该工具不会打开任意 URL,只启动当前授权项目的 `127.0.0.1` 本地预览,并和 Tauri 用户命令共用同一个 `PreviewRegistry`。 - 2026-07-10 补充:当前工具箱还开放 `canvas.asset_generate`,审计记录类型为 `agent.runtime.canvas.asset_generate`;该工具只通过配置好的 External Editor API 生成并回流素材,不暴露任意上传 / 任意网络请求能力。 - 2026-07-10 补充:当前工具箱还开放 `task.list`;该工具只读取 manifest 任务图、状态、依赖、产物和 `readyTaskIds`,并受 `task.list` 项目权限策略保护。 @@ -290,6 +291,7 @@ game-project/ - 2026-07-10 补充:当前工具箱还开放 `project.diff`;该工具只读返回 checkpoint 对比后的新增、修改、删除计数和相对路径摘要,并受 `project.diff` 项目权限策略保护。 - 2026-07-10 补充:当前工具箱还开放 `agent.run_status`;该工具只读返回自己、目标 Agent 或一组 Agent 的 Runtime 状态摘要,并受 `agent.run_status` 项目权限策略保护。 - 2026-07-10 补充:当前工具箱还开放 `agent.delegate`,审计记录类型为 `agent.runtime.agent.delegate`;该工具只把任务投递到目标 Agent 既有后台队列,不创建平行 runtime,不绕过目标 Agent 的锁和队列,也受 `agent.delegate` 项目权限策略保护。 +- 2026-07-10 补充:当前 Runtime 命令还开放 `agent.schedule_ready`,审计记录类型为 `agent.runtime.ready_task.scheduled`;该命令把 manifest ready task 批量投递到对应 Agent 的既有后台队列,默认需要确认,不属于 Agent loop 内可直接调用的工具。 - 普通用户可在聊天框输入 `/project /绝对路径` 生成待确认的 `project.create` 命令,用于授权并初始化本地项目目录;相对路径不会生成待确认命令;开发窗口仍可直接编辑项目路径。 - 单窗口首页和项目组页可选择、打开、新建或显示当前输入的项目绝对路径;最近项目行也可显示目录,非法或相对路径不会调用系统文件管理器。 - 普通用户侧的生成、上传、运行、自检、预览状态 / 启动 / 打开 / 停止、记忆写入和画板资产导入都必须先完成 `/project` 初始化;未初始化时只提示设置本地项目,不落到默认临时目录。 diff --git a/packages/shared/src/contracts/gameCreationApp.ts b/packages/shared/src/contracts/gameCreationApp.ts index 0487b588b..7fe9d38f2 100644 --- a/packages/shared/src/contracts/gameCreationApp.ts +++ b/packages/shared/src/contracts/gameCreationApp.ts @@ -33,6 +33,7 @@ export const GAME_CREATION_APP_COMMANDS = [ { id: 'agent.retry', permission: 'confirm' }, { id: 'agent.resume', permission: 'confirm' }, { id: 'agent.delegate', permission: 'confirm' }, + { id: 'agent.schedule_ready', permission: 'confirm' }, { id: 'agent.capabilities', permission: 'auto' }, { id: 'agent.audit', permission: 'auto' }, { id: 'llm.config_check', permission: 'auto' }, diff --git a/server-rs/crates/shared-contracts/src/game_creation_app.rs b/server-rs/crates/shared-contracts/src/game_creation_app.rs index f6ed4c9ec..28601d62e 100644 --- a/server-rs/crates/shared-contracts/src/game_creation_app.rs +++ b/server-rs/crates/shared-contracts/src/game_creation_app.rs @@ -21,7 +21,7 @@ pub struct GameCreationAppCommandDescriptor { pub permission: GameCreationAppPermission, } -pub const GAME_CREATION_APP_COMMANDS: [GameCreationAppCommandDescriptor; 47] = [ +pub const GAME_CREATION_APP_COMMANDS: [GameCreationAppCommandDescriptor; 48] = [ command("help.show", GameCreationAppPermission::Auto), command("project.create", GameCreationAppPermission::Confirm), command("project.status", GameCreationAppPermission::Auto), @@ -42,6 +42,7 @@ pub const GAME_CREATION_APP_COMMANDS: [GameCreationAppCommandDescriptor; 47] = [ command("agent.retry", GameCreationAppPermission::Confirm), command("agent.resume", GameCreationAppPermission::Confirm), command("agent.delegate", GameCreationAppPermission::Confirm), + command("agent.schedule_ready", GameCreationAppPermission::Confirm), command("agent.capabilities", GameCreationAppPermission::Auto), command("agent.audit", GameCreationAppPermission::Auto), command("llm.config_check", GameCreationAppPermission::Auto), @@ -693,6 +694,7 @@ mod tests { "agent.retry", "agent.resume", "agent.delegate", + "agent.schedule_ready", ] { let lifecycle_command = GAME_CREATION_APP_COMMANDS .iter()