From eee974fb9ec98a19f0aa36899760f6d008c8f618 Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Fri, 10 Jul 2026 01:52:41 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90Agent=20Runtime=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=AD=96=E7=95=A5=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runtime state 新增 toolPolicy,派生工具级 auto、confirm 和 deny 策略。 后台规划 prompt 注入当前工具策略,Agent 可在决策前感知权限边界。 状态面板展示工具策略摘要,补充策略映射和 prompt 注入测试。 --- .../src-tauri/src/agent.rs | 85 ++++++++++++++++++- .../src-tauri/src/main.rs | 29 +++++++ .../src-tauri/src/tests.rs | 80 +++++++++++++++++ apps/ai-game-creator-shell/src/App.tsx | 28 ++++++ .../shared-memory/decision-log.md | 1 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 3 +- 6 files changed, 224 insertions(+), 2 deletions(-) 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 0299789fb..d85b1351d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent.rs @@ -214,6 +214,7 @@ pub(crate) fn read_game_creator_agent_runtime_at( } }; normalize_game_creator_agent_runtime_state(&mut state, &agent_id); + let _ = refresh_game_creator_agent_runtime_tool_policy(root, &mut state); let recent_events = read_recent_game_creator_agent_runtime_events(&event_path)?; let recent_tasks = read_recent_game_creator_agent_runtime_tasks(&task_path)?; Ok(AgentRuntimeResult { @@ -828,8 +829,11 @@ fn build_game_creator_agent_background_tool_plan_request( serde_json::to_string_pretty(observations) .map_err(|error| format!("序列化 Agent 工具观察失败:{error}"))? }; + let tool_policy = agent_runtime_tool_policy_snapshot_at(root)?; + let tool_policy_json = serde_json::to_string_pretty(&tool_policy) + .map_err(|error| format!("序列化 Agent 工具策略失败:{error}"))?; let prompt = format!( - "项目上下文如下。你正在执行后台 Agent loop 第 {loop_index} 轮。请基于目标、已有工具观察和当前上下文修正计划,再决定是否调用最多 {AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT} 个白名单工具。只输出 JSON 对象,不要 markdown。\n\n{context}\n\n后台任务:\n{task}\n\n已有工具观察:\n{observations_json}\n\nJSON schema:{{\"thinkingSummary\":\"一句话理解\",\"plan\":[\"步骤\"],\"actions\":[{{\"tool\":\"memory.read|memory.write|conversation.read|asset.list|project.index|file.read|file.write|task.list|task.update|command.run_limited|preview.start|canvas.asset_generate|blackboard.write|agent.message\",\"reason\":\"为什么需要\",\"input\":{{}}}}],\"response\":\"如果无需继续调用工具,可直接给最终回复\"}}\n\n工具输入约定:memory.read 使用 {{\"scope\":\"session|project|blackboard|agent\"}};memory.write 使用 {{\"scope\":\"agent|project|session|blackboard\",\"title\":\"标题\",\"content\":\"要沉淀的稳定结论\",\"mode\":\"append|overwrite\"}};file.read 使用 {{\"path\":\"项目内相对路径\"}};file.write 使用 {{\"path\":\"项目内相对路径\",\"content\":\"文件内容\"}};task.list input 可为空,用于读取 manifest 任务图、状态和 readyTaskIds;task.update 使用 {{\"taskId\":\"manifest taskId\",\"status\":\"pending|running|waiting-for-confirmation|completed|failed\"}};command.run_limited 使用 {{\"commandId\":\"game.static_smoke\"}},只支持本地静态自检;preview.start input 可为空,用于启动当前项目的 127.0.0.1 本地 HTTP 预览;canvas.asset_generate 使用 {{\"prompt\":\"要生成的美术素材描述\"}},通过配置的 External Editor API 生成首版素材并登记到 assets;blackboard.write 使用 {{\"title\":\"标题\",\"content\":\"要共享给所有 Agent 的稳定结论\"}};agent.message 使用 {{\"agentId\":\"目标 taskId\",\"content\":\"给目标 Agent 的定向消息\"}};如果已有观察足够,请返回空 actions 并填写 response。其他工具 input 可为空。" + "当前工具策略:\n{tool_policy_json}\n\n项目上下文如下。你正在执行后台 Agent loop 第 {loop_index} 轮。请基于目标、已有工具观察和当前上下文修正计划,再决定是否调用最多 {AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT} 个白名单工具。只输出 JSON 对象,不要 markdown。\n\n{context}\n\n后台任务:\n{task}\n\n已有工具观察:\n{observations_json}\n\nJSON schema:{{\"thinkingSummary\":\"一句话理解\",\"plan\":[\"步骤\"],\"actions\":[{{\"tool\":\"memory.read|memory.write|conversation.read|asset.list|project.index|file.read|file.write|task.list|task.update|command.run_limited|preview.start|canvas.asset_generate|blackboard.write|agent.message\",\"reason\":\"为什么需要\",\"input\":{{}}}}],\"response\":\"如果无需继续调用工具,可直接给最终回复\"}}\n\n工具输入约定:memory.read 使用 {{\"scope\":\"session|project|blackboard|agent\"}};memory.write 使用 {{\"scope\":\"agent|project|session|blackboard\",\"title\":\"标题\",\"content\":\"要沉淀的稳定结论\",\"mode\":\"append|overwrite\"}};file.read 使用 {{\"path\":\"项目内相对路径\"}};file.write 使用 {{\"path\":\"项目内相对路径\",\"content\":\"文件内容\"}};task.list input 可为空,用于读取 manifest 任务图、状态和 readyTaskIds;task.update 使用 {{\"taskId\":\"manifest taskId\",\"status\":\"pending|running|waiting-for-confirmation|completed|failed\"}};command.run_limited 使用 {{\"commandId\":\"game.static_smoke\"}},只支持本地静态自检;preview.start input 可为空,用于启动当前项目的 127.0.0.1 本地 HTTP 预览;canvas.asset_generate 使用 {{\"prompt\":\"要生成的美术素材描述\"}},通过配置的 External Editor API 生成首版素材并登记到 assets;blackboard.write 使用 {{\"title\":\"标题\",\"content\":\"要共享给所有 Agent 的稳定结论\"}};agent.message 使用 {{\"agentId\":\"目标 taskId\",\"content\":\"给目标 Agent 的定向消息\"}};如果已有观察足够,请返回空 actions 并填写 response。其他工具 input 可为空。" ); let request = LlmRunRequest::new(vec![ LlmMessage::system(game_creator_agent_runtime_tool_plan_system_prompt()), @@ -969,6 +973,74 @@ fn game_creator_agent_runtime_tool_command_id(tool: &str) -> Option<&'static str } } +fn agent_runtime_executable_tools() -> Vec<&'static str> { + vec![ + "memory.read", + "memory.write", + "conversation.read", + "asset.list", + "project.index", + "file.read", + "file.write", + "task.list", + "task.update", + "command.run_limited", + "preview.start", + "canvas.asset_generate", + "blackboard.write", + "agent.message", + ] +} + +fn agent_runtime_tool_policy_snapshot_at( + root: &Path, +) -> Result { + let view = read_project_permission_policy_at(root)?; + let mut auto_tools = Vec::new(); + let mut confirm_tools = Vec::new(); + let mut denied_tools = Vec::new(); + for tool in agent_runtime_executable_tools() { + let Some(command_id) = game_creator_agent_runtime_tool_command_id(tool) else { + continue; + }; + if view + .policy + .denied_commands + .iter() + .any(|command| command == command_id) + { + denied_tools.push(tool.to_string()); + } else if view + .policy + .confirm_commands + .iter() + .any(|command| command == command_id) + { + confirm_tools.push(tool.to_string()); + } else { + auto_tools.push(tool.to_string()); + } + } + Ok(AgentRuntimeToolPolicySnapshot { + allowed_tools: agent_runtime_executable_tools() + .into_iter() + .map(str::to_string) + .collect(), + auto_tools, + confirm_tools, + denied_tools, + updated_at: unix_timestamp(), + }) +} + +fn refresh_game_creator_agent_runtime_tool_policy( + root: &Path, + state: &mut AgentRuntimeState, +) -> Result<(), String> { + state.tool_policy = agent_runtime_tool_policy_snapshot_at(root)?; + Ok(()) +} + fn game_creator_agent_runtime_tool_policy_block(root: &Path, command_id: &str) -> Option { let view = match read_project_permission_policy_at(root) { Ok(view) => view, @@ -1831,6 +1903,7 @@ pub(crate) fn start_game_creator_agent_runtime_task_at( state.next_step = "等待 Agent 输出计划或回复".to_string(); state.plan = plan; state.observations = vec!["已创建本轮 Agent Runtime run。".to_string()]; + refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?; state.updated_at = unix_timestamp(); write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_task(root, &state)?; @@ -1873,6 +1946,7 @@ pub(crate) fn advance_game_creator_agent_runtime_turn_at( if !observation.trim().is_empty() { state.observations.push(observation.trim().to_string()); } + refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?; state.updated_at = unix_timestamp(); write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_task(root, &state)?; @@ -1902,6 +1976,7 @@ pub(crate) fn finish_game_creator_agent_runtime_turn_at( state .observations .push("Agent 已完成回复,assistant 消息等待或已经由前端落盘。".to_string()); + refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?; state.updated_at = unix_timestamp(); write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_task(root, &state)?; @@ -1939,6 +2014,7 @@ pub(crate) fn fail_game_creator_agent_runtime_turn_at( state.current_action = "等待开发者处理失败".to_string(); state.next_step = "等待开发者处理失败".to_string(); state.error = Some(sanitize_agent_runtime_text(error, 500)); + let _ = refresh_game_creator_agent_runtime_tool_policy(root, &mut state); state.updated_at = unix_timestamp(); write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_task(root, &state)?; @@ -2016,6 +2092,7 @@ fn default_game_creator_agent_runtime_state(agent_id: &str, run_id: &str) -> Age observations: Vec::new(), recent_tool_calls: Vec::new(), allowed_tools: default_game_creator_agent_runtime_allowed_tools(), + tool_policy: AgentRuntimeToolPolicySnapshot::default(), last_response: None, error: None, updated_at: unix_timestamp(), @@ -2092,6 +2169,12 @@ fn normalize_game_creator_agent_runtime_state(state: &mut AgentRuntimeState, age if state.updated_at == 0 { state.updated_at = unix_timestamp(); } + if state.tool_policy.allowed_tools.is_empty() { + state.tool_policy.allowed_tools = agent_runtime_executable_tools() + .into_iter() + .map(str::to_string) + .collect(); + } if state.recent_tool_calls.len() > AGENT_RUNTIME_RECENT_TOOL_CALL_LIMIT { let keep_from = state .recent_tool_calls 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 531cf692d..9f1305afd 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -166,6 +166,8 @@ struct AgentRuntimeState { #[serde(default)] allowed_tools: Vec, #[serde(default)] + tool_policy: AgentRuntimeToolPolicySnapshot, + #[serde(default)] last_response: Option, #[serde(default)] error: Option, @@ -173,6 +175,33 @@ struct AgentRuntimeState { updated_at: u64, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +struct AgentRuntimeToolPolicySnapshot { + #[serde(default)] + allowed_tools: Vec, + #[serde(default)] + auto_tools: Vec, + #[serde(default)] + confirm_tools: Vec, + #[serde(default)] + denied_tools: Vec, + #[serde(default)] + updated_at: u64, +} + +impl Default for AgentRuntimeToolPolicySnapshot { + fn default() -> Self { + Self { + allowed_tools: Vec::new(), + auto_tools: Vec::new(), + confirm_tools: Vec::new(), + denied_tools: Vec::new(), + updated_at: 0, + } + } +} + #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] struct AgentRuntimeToolCallRecord { 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 693aea557..1d168e9ab 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests.rs @@ -1326,6 +1326,66 @@ async fn chat_with_game_creator_role_agent_stream_emits_deltas() { fs::remove_dir_all(root).ok(); } +#[test] +fn agent_runtime_tool_policy_snapshot_reflects_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!["file.write".to_string()], + confirm_commands: vec!["memory.write".to_string(), "task.update".to_string()], + }, + ) + .expect("write policy"); + + let runtime = start_game_creator_agent_runtime_task_at( + &root, + "design-director", + "检查工具策略", + "policy-snapshot-run", + "agent-chat", + "读取工具策略", + vec!["核对工具策略".to_string()], + ) + .expect("start runtime"); + + assert!(runtime + .tool_policy + .allowed_tools + .contains(&"task.list".to_string())); + assert!(runtime + .tool_policy + .auto_tools + .contains(&"task.list".to_string())); + assert!(runtime + .tool_policy + .confirm_tools + .contains(&"memory.write".to_string())); + assert!(runtime + .tool_policy + .confirm_tools + .contains(&"blackboard.write".to_string())); + assert!(runtime + .tool_policy + .confirm_tools + .contains(&"task.update".to_string())); + assert!(runtime + .tool_policy + .denied_tools + .contains(&"file.write".to_string())); + + let read_back = + read_game_creator_agent_runtime_at(&root, "design-director").expect("read runtime"); + assert!(read_back + .state + .tool_policy + .denied_tools + .contains(&"file.write".to_string())); + + fs::remove_dir_all(root).ok(); +} + #[tokio::test] async fn role_agent_runtime_turn_persists_session_events_and_index() { let root = unique_project_path(); @@ -1363,6 +1423,23 @@ async fn role_agent_runtime_turn_persists_session_events_and_index() { assert!(runtime .allowed_tools .contains(&"conversation.read".to_string())); + assert!(runtime + .tool_policy + .allowed_tools + .contains(&"task.list".to_string())); + assert!(runtime + .tool_policy + .auto_tools + .contains(&"memory.read".to_string())); + assert!(runtime + .tool_policy + .confirm_tools + .contains(&"task.update".to_string())); + assert!(runtime + .tool_policy + .confirm_tools + .contains(&"blackboard.write".to_string())); + assert!(runtime.tool_policy.denied_tools.is_empty()); let result = read_game_creator_agent_runtime_at(&root, "art-director").expect("read runtime state"); assert!(result @@ -1500,6 +1577,9 @@ async fn background_agent_runtime_task_executes_plan_tool_observation_loop() { let plan_request = receiver .recv_timeout(Duration::from_secs(2)) .expect("plan llm request"); + assert!(plan_request.contains("当前工具策略")); + assert!(plan_request.contains("confirmTools")); + assert!(plan_request.contains("task.update")); assert!(plan_request.contains("只输出 JSON 对象")); assert!(plan_request.contains("后台分析当前玩法循环")); let final_request = receiver diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index fe8696bea..1528136dc 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -243,12 +243,21 @@ interface AgentRuntimeState { observations: string[]; recentToolCalls?: AgentRuntimeToolCallRecord[]; allowedTools: string[]; + toolPolicy?: AgentRuntimeToolPolicySnapshot; lastResponse: string | null; error: string | null; updatedAt: number; recentTasks?: AgentRuntimeTaskRecord[]; } +interface AgentRuntimeToolPolicySnapshot { + allowedTools: string[]; + autoTools: string[]; + confirmTools: string[]; + deniedTools: string[]; + updatedAt: number; +} + interface AgentRuntimeToolCallRecord { tool: string; status: string; @@ -511,6 +520,13 @@ function agentRuntimeStateFromResult( nextStep: result.state.nextStep ?? agentRuntimeNextStepFromPhase(result.state.phase), recentToolCalls: result.state.recentToolCalls ?? [], + toolPolicy: result.state.toolPolicy ?? { + allowedTools: result.state.allowedTools ?? [], + autoTools: [], + confirmTools: [], + deniedTools: [], + updatedAt: 0, + }, recentTasks: result.recentTasks ?? result.state.recentTasks ?? [], }; } @@ -566,6 +582,7 @@ function AgentRuntimeStatusPanel({ const observations = runtime.observations.slice(-2); const recentToolCalls = (runtime.recentToolCalls ?? []).slice(-3).reverse(); const recentTasks = (runtime.recentTasks ?? []).slice(-3).reverse(); + const toolPolicy = runtime.toolPolicy; const nextStep = runtime.nextStep ?? agentRuntimeNextStepFromPhase(runtime.phase); return (
@@ -578,6 +595,17 @@ function AgentRuntimeStatusPanel({ {runtime.currentTask ?

{runtime.currentTask}

: null} {runtime.currentAction} {nextStep ? {`下一步:${nextStep}`} : null} + {toolPolicy ? ( + + {`工具策略:auto ${toolPolicy.autoTools.length} · confirm ${toolPolicy.confirmTools.length} · deny ${toolPolicy.deniedTools.length}`} + {toolPolicy.deniedTools.length > 0 + ? ` · deny: ${toolPolicy.deniedTools.slice(0, 3).join(', ')}` + : ''} + {toolPolicy.confirmTools.length > 0 + ? ` · confirm: ${toolPolicy.confirmTools.slice(0, 3).join(', ')}` + : ''} + + ) : null} {planItems.length > 0 ? (
    {planItems.map((item, index) => ( diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 49203efc3..8fe659c23 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4053,6 +4053,7 @@ - 2026-07-01 调整:AI 游戏创作 App 在 `memory/session.md` 与 `memory/project.md` 之外新增项目级黑板 `memory/blackboard.md`,只记录重要跨 agent 决策、依赖和风险摘要;每个角色 agent 拥有私有记忆 `memory/agents//.md`。角色 brief 必须读取自己的私有记忆和项目黑板;`game.generate_draft` 通过 Evaluator 与 `game.static_smoke` 后,追加项目黑板摘要和各角色成功产出摘要,不得覆盖既有记忆。失败 run 仍只保留 trace 和 pass 快照,不写最终记忆摘要。 - 2026-07-06 调整:AI 游戏创作 App 主聊天普通文本改为进入主聊天 Agent,而不是直接排队 `game.generate_draft`;主聊天 Agent 读取短期记忆、长期记忆、项目黑板、最近项目对话和本地资产摘要作为背景,支持 `agentLlm.chat` 单独 provider 配置,但只做自然语言交互、澄清和 slash 命令建议,不写项目、不运行工具、不伪装生成结果。显式 `/generate <创作想法>` 或 `/draft <创作想法>` 才进入 `game.generate_draft` 待确认流。 - 2026-07-09 调整:AI 游戏创作 App 新增 Agent Runtime V1 最小可观测状态。单 Agent 对话和生成 loop 中的角色 brief 必须写 `.agent/runtime/agents/.json` 与 `.agent/runtime/events/.jsonl`,记录 `agentId`、`taskId`、`sessionId`、`runId`、`source`、`status`、`phase`、当前任务 / 动作、计划、观测、允许工具、最近回复和错误;单 Agent 流式聊天事件要回传最新 `runtimeState`,开发单 Agent 聊天页和项目内单 Agent 对话弹窗只读展示该状态,读取失败必须可见提示,不得静默伪装为空状态。`source=agent-chat` 表示开发者单 Agent 对话,`source=generate-draft` 表示生成 loop 角色 brief;carry-over brief 只记录继承和完成,不伪装成重新调用 LLM。Runtime state 写入使用临时文件替换,event JSONL 读取跳过坏行,用户 prompt / 回复摘要进入 runtime 与 `agent.db` 前复用敏感上下文过滤;`.agent/runtime/` 是运行观测状态,不进入项目索引、checkpoint diff 或 restore 删除范围。该层仍是本地 JSONL 状态与事件,不引入 SQLite、常驻独立进程、远程 runner 或可中断上游 LLM 的承诺。 +- 2026-07-10 调整:Agent Runtime state 新增 `toolPolicy`,从项目权限策略派生工具级 `allowedTools`、`autoTools`、`confirmTools` 和 `deniedTools`。后台 planning prompt 必须带入该快照,让 Agent 在规划阶段知道工具策略;执行阶段仍由 Runtime 白名单和项目权限 gate 决定。`blackboard.write` 继承 `memory.write` 策略,`agent.message` 继承 `conversation.write` 策略。 - 2026-07-10 调整:Agent Runtime state 新增 `recentToolCalls`,后台 loop 每次执行白名单工具后记录最近 20 条结构化动作,包含 tool、status、reason、summary、detail 和 updatedAt。状态面板展示最近动作时使用该字段,不解析 observation 文本;写入前继续过滤敏感上下文,不保存原始密钥或任意未过滤输入。 - 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `task.list`。Agent 可自行读取 `.agent/manifest.json` 的 seed task 状态、依赖、产物交接和按依赖计算的 `readyTaskIds`,用于判断下一步任务;该工具必须受 `task.list` 项目权限策略保护,策略要求确认或拒绝时不得把任务图细节放进 observation。 - 2026-07-10 调整:Agent Runtime V1 后台工具箱新增受策略保护的 `task.update`。Agent 只能把 `.agent/manifest.json` 中已有 seed task 的状态更新为 `pending`、`running`、`waiting-for-confirmation`、`completed` 或 `failed`,Runtime 必须复用项目写锁、`task.update` 权限策略和 `.agent/agent.db` 审计记录;策略要求确认或拒绝时不得修改 manifest,不得创建新任务。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index f9cb3a868..66c920830 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -33,6 +33,7 @@ Agent Runtime 负责: - 命令能力:内置命令调用、权限 gate、执行日志;v1 只允许白名单受限命令,不执行任意 shell。 - 编排能力:任务拆分、任务图依赖、专业组调度、多智能体协作;Runtime V1 会为单 Agent 对话和生成 loop 中的角色 brief 写入独立 runtime state / event,先解决“每个 Agent 正在做什么、跑到哪一步、最近一次 task/run 是什么”的可观测性。 - 后台任务能力:开发窗口单 Agent 聊天和项目内 Agent 对话弹窗可把当前输入投递为单 Agent 后台任务,Tauri 命令 `start_game_creator_agent_runtime_task` 会立即写入该 Agent 的 `.agent/runtime/agents/.json`、`.agent/runtime/events/.jsonl`、`.agent/runtime/tasks/.jsonl` 和 `.agent/conversations/agents/.jsonl`,随后在 App 进程内启动 tokio task 执行最小 Agent loop:每轮让该 Agent 输出 `thinkingSummary / plan / actions / response`,Runtime 按白名单和项目权限策略执行工具动作,写入 `action / observation` 事件,再把 observation 放入下一轮 prompt 让 Agent 修正计划、继续行动或用空 actions + response 收束;后台任务最多执行 3 轮 loop,仍未收束时再按最后计划和全部观察生成最终回复并追加回对话。工具箱包含只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`file.read`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write` 和 `agent.message`;`memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径并记录审计,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加 `memory/blackboard.md`,`agent.message` 给目标 `.agent/conversations/agents/.jsonl` 写入 tool 留言,策略要求确认或拒绝时不执行写入或运行,只把策略结果作为 observation 回给 Agent。`read_game_creator_agent_runtime` 会按 `runId` 去重返回最近任务,`read_game_creator_agent_runtimes` 批量读取所有规范 taskId 的 runtime;开发窗口、项目内 Agent 对话弹窗和主窗口 Agent 状态列表展示最近任务、当前任务、当前动作、下一步和运行阶段。不同 Agent 使用各自 runtime 锁,可以并行运行;同一 Agent 已有运行任务时,新任务会先写成 `pending / queued`,由当前后台 drain 在完成后串行继续执行。该能力仍属于 Runtime V1 的进程内任务,不是独立 OS 进程或跨重启离线常驻 worker。 +- 2026-07-10 补充:Agent Runtime state 新增 `toolPolicy`,按当前项目 `.agent/policy.json` 派生工具级 `allowedTools / autoTools / confirmTools / deniedTools` 快照;后台 planning prompt 会带入该快照,让 Agent 在规划时知道哪些工具会自动执行、需要确认或被拒绝。`blackboard.write` 继承 `memory.write` 策略,`agent.message` 继承 `conversation.write` 策略;实际执行仍以 Runtime 的白名单和项目权限 gate 为准。 - 2026-07-10 补充:Agent Runtime state 新增 `recentToolCalls`,每次后台工具执行后记录最近 20 条结构化工具动作,包含 tool、status、reason、summary、detail 和 updatedAt;开发窗口、项目内 Agent 对话弹窗和主窗口 Agent 状态列表可直接展示“最近动作”,不再只能从 observation 字符串里猜测 action / observation 对应关系。字段只保存过滤后的摘要和观察细节,不保存原始 API Key 或任意未过滤输入。 - 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。 @@ -257,7 +258,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 状态查看进度和结果。后台任务会向 `.agent/runtime/tasks/.jsonl` 追加任务视角记录,任务状态使用 `pending / running / completed / failed`,读取时按 `runId` 去重返回最近任务;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行。后台任务的核心 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.tool_observation` / `agent.runtime.memory.write` / `agent.runtime.file.write` / `agent.runtime.command.run_limited` / `agent.runtime.blackboard.write` / `agent.runtime.agent.message` / `agent.runtime.background_task.completed` / `agent.runtime.background_task.failed` 审计记录。当前工具箱开放只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`file.read`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write` 和 `agent.message`;若项目策略要求确认或拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent。`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;`.agent/agent.db` 追加写入按整行 JSONL 写入,减少多个 Agent 同时完成时的行交错风险。 +- 开发窗口和项目内 Agent 对话弹窗的“后台运行”只启动或排队单 Agent 后台任务,不阻塞等待回复;用户可刷新同一 Agent 对话或 runtime 状态查看进度和结果。后台任务会向 `.agent/runtime/tasks/.jsonl` 追加任务视角记录,任务状态使用 `pending / running / completed / failed`,读取时按 `runId` 去重返回最近任务;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行。后台任务的核心 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.tool_observation` / `agent.runtime.memory.write` / `agent.runtime.file.write` / `agent.runtime.command.run_limited` / `agent.runtime.blackboard.write` / `agent.runtime.agent.message` / `agent.runtime.background_task.completed` / `agent.runtime.background_task.failed` 审计记录。当前工具箱开放只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`file.read`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write` 和 `agent.message`;若项目策略要求确认或拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;`.agent/agent.db` 追加写入按整行 JSONL 写入,减少多个 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` 项目权限策略保护。