允许Agent调度Ready任务

后台 Runtime 工具箱新增 agent.schedule_ready

Agent 可按权限策略把 manifest ready task 投递到对应队列

补充自动调度和确认阻断的 Rust 测试

同步 Runtime V1 文档和项目记忆
This commit is contained in:
AIGameCreator App
2026-07-10 07:15:27 +08:00
parent 436d67ae24
commit 739e89ea38
4 changed files with 304 additions and 4 deletions
File diff suppressed because one or more lines are too long
@@ -2970,6 +2970,237 @@ async fn background_agent_runtime_delegate_respects_project_policy() {
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn background_agent_runtime_can_schedule_ready_tasks_from_tool() {
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 scheduler tool");
let (design_sender, design_receiver) = mpsc::channel();
let design_plan_json = serde_json::json!({
"thinkingSummary": "完成当前任务后调度下一个 ready Agent",
"plan": ["标记当前任务完成", "调度 manifest ready task"],
"actions": [
{
"tool": "task.update",
"reason": "当前设计拆解任务已经完成",
"input": { "taskId": "design-director", "status": "completed" }
},
{
"tool": "agent.schedule_ready",
"reason": "依赖设计拆解的任务现在可以交给对应 Agent",
"input": { "limit": 1 }
}
],
"response": ""
})
.to_string();
let design_base_url = spawn_mock_llm_server_responses_with_capture(
vec![
design_plan_json,
"已完成设计拆解,并调度下一个 ready 任务。".to_string(),
],
Some(design_sender),
);
let (foundation_sender, foundation_receiver) = mpsc::channel();
let foundation_plan_json = serde_json::json!({
"thinkingSummary": "收到玩法规格 ready 任务",
"plan": ["标记玩法规格任务完成"],
"actions": [
{
"tool": "task.update",
"reason": "玩法规格已整理完成",
"input": { "taskId": "design-foundation", "status": "completed" }
}
],
"response": ""
})
.to_string();
let foundation_base_url = spawn_mock_llm_server_responses_with_capture(
vec![foundation_plan_json, "已完成玩法规格任务。".to_string()],
Some(foundation_sender),
);
let _config_guard = write_test_local_config(format!(
r#"{{
"agentLlm": {{
"design-director": {{
"apiKey": "design-key",
"baseUrl": {design_base_url:?},
"model": "design-runtime-model",
"apiKind": "openai_responses"
}},
"design-foundation": {{
"apiKey": "foundation-key",
"baseUrl": {foundation_base_url:?},
"model": "foundation-runtime-model",
"apiKind": "openai_responses"
}}
}}
}}"#
));
start_game_creator_agent_background_task_at(
&root,
"design-director",
"完成设计拆解并调度后续 ready 任务",
"design-schedule-ready-run",
)
.expect("start scheduler agent task");
let design_plan_request = design_receiver
.recv_timeout(Duration::from_secs(2))
.expect("design plan llm request");
assert!(design_plan_request.contains("agent.schedule_ready"));
let foundation_plan_request = foundation_receiver
.recv_timeout(Duration::from_secs(2))
.expect("foundation plan llm request");
assert!(foundation_plan_request.contains("处理 manifest ready 任务:确定玩法规格"));
assert!(foundation_plan_request.contains("design-foundation"));
let design_final_request = design_receiver
.recv_timeout(Duration::from_secs(2))
.expect("design final llm request");
assert!(design_final_request.contains("agent.schedule_ready"));
assert!(design_final_request.contains("已调度 1 个 Ready 任务"));
assert!(design_final_request.contains("design-foundation"));
let foundation_final_request = foundation_receiver
.recv_timeout(Duration::from_secs(2))
.expect("foundation final llm request");
assert!(foundation_final_request.contains("任务 design-foundation 已更新为 completed"));
let design_runtime = wait_for_agent_runtime_idle(&root, "design-director");
assert_eq!(design_runtime.status, "idle");
assert!(design_runtime
.tool_policy
.auto_tools
.contains(&"agent.schedule_ready".to_string()));
assert!(design_runtime.observations.iter().any(|item| {
item.contains("agent.schedule_readyok · 已调度 1 个 Ready 任务")
}));
assert!(design_runtime.recent_tool_calls.iter().any(|call| {
call.tool == "agent.schedule_ready"
&& call.status == "ok"
&& call
.detail
.as_deref()
.is_some_and(|detail| detail.contains("design-foundation"))
}));
let foundation_runtime = wait_for_agent_runtime_idle(&root, "design-foundation");
assert_eq!(foundation_runtime.status, "idle");
assert_eq!(foundation_runtime.source, "agent-ready-task-scheduler");
assert!(foundation_runtime
.observations
.iter()
.any(|item| item.contains("task.updateok · 任务 design-foundation 已更新为 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");
assert_task_status(&manifest_after, "design-foundation", "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("\"agentId\":\"design-foundation\""));
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn background_agent_runtime_schedule_ready_respects_project_policy() {
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!["agent.schedule_ready".to_string()],
agent_policies: BTreeMap::new(),
},
)
.expect("write policy");
let (sender, receiver) = mpsc::channel();
let plan_json = serde_json::json!({
"thinkingSummary": "尝试调度 ready task",
"plan": ["请求调度 ready task", "等待权限结果"],
"actions": [
{
"tool": "agent.schedule_ready",
"reason": "应该把 ready task 交给对应 Agent",
"input": { "limit": 1 }
}
],
"response": ""
})
.to_string();
let base_url = spawn_mock_llm_server_responses_with_capture(vec![plan_json], 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"
}}
}}
}}"#
));
start_game_creator_agent_background_task_at(
&root,
"design-director",
"尝试调度 ready task",
"design-schedule-ready-policy-run",
)
.expect("start scheduler agent task");
let plan_request = receiver
.recv_timeout(Duration::from_secs(2))
.expect("design plan llm request");
assert!(plan_request.contains("agent.schedule_ready"));
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
let design_runtime = wait_for_agent_runtime_confirmation(&root, "design-director");
assert_eq!(design_runtime.status, "waiting-for-confirmation");
assert_eq!(design_runtime.phase, "waiting-for-confirmation");
assert_eq!(design_runtime.task_queue.waiting_for_confirmation, 1);
assert!(design_runtime
.tool_policy
.confirm_tools
.contains(&"agent.schedule_ready".to_string()));
assert!(design_runtime.observations.iter().any(|item| {
item.contains(
"agent.schedule_readywaiting-for-confirmation · 项目权限策略要求用户确认:agent.schedule_ready",
)
}));
assert!(design_runtime.recent_tool_calls.iter().any(|call| {
call.tool == "agent.schedule_ready" && call.status == "waiting-for-confirmation"
}));
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", "pending");
assert_task_status(&manifest_after, "design-foundation", "pending");
let foundation_runtime =
read_game_creator_agent_runtime_at(&root, "design-foundation").expect("runtime");
assert!(foundation_runtime.recent_tasks.is_empty());
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("\"agentId\":\"design-foundation\""));
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn background_agent_runtime_can_write_memory_and_project_files() {
let root = unique_project_path();
@@ -30,6 +30,7 @@
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `agent.run_status`,让 Agent 可在 loop 中读取自己、目标 Agent 或一组 Agent 的 Runtime 状态摘要,用于判断同伴是否正在运行、最近任务和最近工具动作。该工具复用 `agent.run_status` 项目权限策略,策略要求确认或拒绝时不会读取状态;observation 只包含 agentId、status、phase、runId、当前任务、当前动作、下一步、计划摘要、最近任务、最近工具和错误摘要,不返回 `.agent/runtime/*` 文件绝对路径。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `agent.delegate`,让 Agent 可把明确子任务投递到另一个 Agent 的独立后台队列。该工具复用目标 Agent 既有锁和 pending drain 语义,不创建平行 runtime;同一目标 Agent 串行,不同 Agent 可并行。该工具使用独立 `agent.delegate` 权限策略,策略要求确认或拒绝时不会写目标对话、不会启动目标后台任务,也不会写 `agent.runtime.agent.delegate` 审计记录。
- 2026-07-10 补充:主窗口 Agent 状态栏在开发模式新增“调度 Ready”控制,用来显式调用 `schedule_game_creator_agent_ready_tasks`。该控制先读取项目策略,命中 `agent.schedule_ready` confirm 时走现有确认弹窗,确认后才把 manifest ready task 投递到对应 Agent Runtime;普通用户窗口不展示这个开发控制。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `agent.schedule_ready`,让 Agent 在完成或更新 manifest task 后可按项目权限策略自行调度新 ready task。该工具复用同一个 scheduler:扫描依赖已完成且仍为 pending 的 manifest task,先标为 running,再按 taskId 投递到对应 Agent 的既有后台队列;策略要求确认时当前 Agent 停在 `waiting-for-confirmation`,不会静默启动下游 Agent。
- 影响范围:`apps/ai-game-creator-shell` 的 Tauri command、Agent Runtime state/event、开发窗口单 Agent 聊天、项目内 Agent 对话弹窗、`appSurface.test.ts` 和 AI 游戏创作 App 实施计划。
- 验证方式:运行 Tauri Rust 后台 Agent 并行测试、壳前端 appSurface 测试、壳 typecheck、编码检查和 `git diff --check`
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`
File diff suppressed because one or more lines are too long