From 5058b42ca9deeb827e29627ffa6e697ad8f903a7 Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Fri, 10 Jul 2026 04:46:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E6=97=B6=E5=90=8C=E6=AD=A5Agent?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 Agent Runtime Tauri 实时更新事件 前端开发窗口和主窗口监听并合并 Runtime 状态 补充后台任务实时更新测试和 Runtime 文档 --- .../src-tauri/src/agent.rs | 38 +++- .../src-tauri/src/main.rs | 14 +- apps/ai-game-creator-shell/src/App.tsx | 87 +++++++ .../tests/appSurface.test.ts | 215 +++++++++++++++++- .../shared-memory/decision-log.md | 2 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 1 + 6 files changed, 342 insertions(+), 15 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 25e0a7c52..0edbfeb2c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent.rs @@ -1,5 +1,35 @@ use super::*; +static GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE: OnceLock = OnceLock::new(); + +pub(crate) fn set_game_creator_agent_runtime_update_app_handle(app: tauri::AppHandle) { + let _ = GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE.set(app); +} + +fn emit_game_creator_agent_runtime_update(root: &Path, agent_id: &str) { + let Some(app) = GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE.get() else { + return; + }; + let Ok(runtime) = read_game_creator_agent_runtime_at(root, agent_id) else { + return; + }; + let agent_id = runtime.state.agent_id.clone(); + let run_id = runtime.state.run_id.clone(); + let status = runtime.state.status.clone(); + let phase = runtime.state.phase.clone(); + let _ = app.emit( + "game-creator-agent-runtime-update", + GameCreatorAgentRuntimeUpdateEvent { + project_path: root.to_string_lossy().into_owned(), + agent_id, + run_id, + status, + phase, + runtime, + }, + ); +} + pub(crate) async fn generate_local_game_draft_at( root: &Path, prompt: &str, @@ -370,7 +400,9 @@ pub(crate) fn start_game_creator_agent_background_task_at( )?; let Some(runtime_lock) = try_acquire_game_creator_agent_runtime_task_lock(root, &agent_id)? else { - return read_game_creator_agent_runtime_at(root, &agent_id); + let result = read_game_creator_agent_runtime_at(root, &agent_id)?; + emit_game_creator_agent_runtime_update(root, &agent_id); + return Ok(result); }; let state = start_game_creator_agent_runtime_task_at( root, @@ -3269,7 +3301,9 @@ fn append_game_creator_agent_runtime_event( serde_json::to_writer(&mut file, &event) .map_err(|error| format!("序列化 Agent Runtime 事件失败:{error}"))?; file.write_all(b"\n") - .map_err(|error| format!("写入 Agent Runtime 事件失败:{}: {error}", path.display())) + .map_err(|error| format!("写入 Agent Runtime 事件失败:{}: {error}", path.display()))?; + emit_game_creator_agent_runtime_update(root, &state.agent_id); + Ok(()) } fn append_game_creator_agent_runtime_task( 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 a754f991a..0fc345f3a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -341,7 +341,7 @@ struct AgentRuntimeTaskRecord { updated_at: u64, } -#[derive(Debug, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] struct AgentRuntimeResult { state: AgentRuntimeState, @@ -353,6 +353,17 @@ struct AgentRuntimeResult { recent_tasks: Vec, } +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +struct GameCreatorAgentRuntimeUpdateEvent { + project_path: String, + agent_id: String, + run_id: String, + status: String, + phase: String, + runtime: AgentRuntimeResult, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] struct GameCreatorAgentProgressEvent { @@ -1154,6 +1165,7 @@ fn main() { .manage(game_creator_preview_registry()) .setup(|app| { configure_game_creator_runtime_config_dir(app.handle())?; + set_game_creator_agent_runtime_update_app_handle(app.handle().clone()); #[cfg(all(debug_assertions, not(test)))] open_developer_window(app.handle())?; Ok(()) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 86f155cf2..73ea01953 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -334,6 +334,15 @@ interface AgentRuntimeResult { recentTasks?: AgentRuntimeTaskRecord[]; } +interface GameCreatorAgentRuntimeUpdateEvent { + projectPath: string; + agentId: string; + runId: string; + status: string; + phase: string; + runtime: AgentRuntimeResult; +} + interface GameCreatorLlmConfigStatus { configured: boolean; apiKeyPresent: boolean; @@ -2676,6 +2685,10 @@ export function WorkspaceLauncher({ useState(null); const [agentChatRuntimeError, setAgentChatRuntimeError] = useState(''); const agentChatLoadVersionRef = useRef(0); + const agentChatProjectPathRef = useRef(agentChatProjectPath); + agentChatProjectPathRef.current = agentChatProjectPath; + const agentChatSelectedAgentIdRef = useRef(agentChatSelectedAgentId); + agentChatSelectedAgentIdRef.current = agentChatSelectedAgentId; useEffect(() => { const invoke = resolveTauriInvoke(); @@ -2710,6 +2723,41 @@ export function WorkspaceLauncher({ }; }, [recentWorkspaces, recentWorkspaceRefreshKey]); + useEffect(() => { + const listen = window.__TAURI__?.event?.listen; + if (!listen) { + return; + } + let cleanup: (() => void) | null = null; + let disposed = false; + void listen( + 'game-creator-agent-runtime-update', + (event) => { + const payload = event.payload; + if ( + payload.projectPath !== agentChatProjectPathRef.current || + payload.agentId !== agentChatSelectedAgentIdRef.current + ) { + return; + } + setAgentChatRuntime((current) => + agentRuntimeStateFromResult(payload.runtime, current), + ); + setAgentChatRuntimeError(''); + }, + ).then((unlisten) => { + if (disposed) { + unlisten(); + return; + } + cleanup = unlisten; + }); + return () => { + disposed = true; + cleanup?.(); + }; + }, []); + useEffect(() => { let disposed = false; void getClientProfileDashboard() @@ -11862,6 +11910,8 @@ export function App() { const [selectedAgent, setSelectedAgent] = useState( null, ); + const selectedAgentIdRef = useRef(null); + selectedAgentIdRef.current = selectedAgent?.id ?? null; const [agentConversationInput, setAgentConversationInput] = useState(''); const [agentConversationStatus, setAgentConversationStatus] = useState('未选择 agent'); @@ -11961,6 +12011,43 @@ export function App() { }; }, []); + useEffect(() => { + const listen = window.__TAURI__?.event?.listen; + if (!listen) { + return; + } + let cleanup: (() => void) | null = null; + let disposed = false; + void listen( + 'game-creator-agent-runtime-update', + (event) => { + const payload = event.payload; + if (payload.projectPath !== localProjectPathRef.current) { + return; + } + const nextRuntime = agentRuntimeStateFromResult(payload.runtime); + rememberAgentRuntimeState(nextRuntime); + if (payload.agentId !== selectedAgentIdRef.current) { + return; + } + setAgentConversationRuntime((current) => + normalizeAgentRuntimeState(nextRuntime, current), + ); + setAgentConversationRuntimeError(''); + }, + ).then((unlisten) => { + if (disposed) { + unlisten(); + return; + } + cleanup = unlisten; + }); + return () => { + disposed = true; + cleanup?.(); + }; + }, []); + useEffect(() => { latestMessagesRef.current = messages; const invoke = resolveTauriInvoke(); diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index 8f4eba67f..7ff8c77ee 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -1192,13 +1192,18 @@ describe('AI 游戏创作 App 界面边界', () => { eventName: string, handler: (event: { payload: Record }) => void, ) => { - expect(eventName).toBe('game-creator-role-agent-chat-stream'); - streamHandler = handler; - return () => { - if (streamHandler === handler) { - streamHandler = null; - } - }; + if (eventName === 'game-creator-role-agent-chat-stream') { + streamHandler = handler; + return () => { + if (streamHandler === handler) { + streamHandler = null; + } + }; + } + if (eventName === 'game-creator-agent-runtime-update') { + return () => {}; + } + throw new Error(`unexpected listen ${eventName}`); }, ); const invoke = vi.fn( @@ -1465,6 +1470,32 @@ describe('AI 游戏创作 App 界面边界', () => { error: null, updatedAt: 4000, }; + const completedRuntimeState = { + ...runningRuntimeState, + status: 'idle', + phase: 'completed', + currentAction: '等待下一轮输入', + waitingOn: '开发者下一轮输入', + nextStep: '等待下一轮输入', + taskQueue: { + total: 1, + pending: 0, + running: 0, + completed: 1, + failed: 0, + latestRunId: 'launcher-agent-task-test', + updatedAt: 4010, + }, + lastResponse: '角色规范已整理。', + updatedAt: 4010, + }; + const completedRuntimeTask = { + ...runningRuntimeTask, + status: 'completed', + phase: 'completed', + currentAction: '等待下一轮输入', + updatedAt: 4010, + }; const runningRuntimeEvents = [ { schemaVersion: 'game-creator-agent-runtime.v1', @@ -1589,7 +1620,26 @@ describe('AI 游戏创作 App 界面边界', () => { throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { core: { invoke } }; + let runtimeUpdateHandler: + | ((event: { payload: Record }) => void) + | null = null; + const listen = vi.fn( + async ( + eventName: string, + handler: (event: { payload: Record }) => void, + ) => { + if (eventName === 'game-creator-agent-runtime-update') { + runtimeUpdateHandler = handler; + return () => { + if (runtimeUpdateHandler === handler) { + runtimeUpdateHandler = null; + } + }; + } + throw new Error(`unexpected listen ${eventName}`); + }, + ); + window.__TAURI__ = { core: { invoke }, event: { listen } }; renderLauncherAgentChatAt('/?agent-chat'); fireEvent.change(screen.getByLabelText('Agent 聊天项目目录'), { @@ -1669,6 +1719,54 @@ describe('AI 游戏创作 App 界面边界', () => { agentId: null, }, ]); + + await act(async () => { + runtimeUpdateHandler?.({ + payload: { + projectPath: '/tmp/authorized-game', + agentId: 'design-director', + runId: 'launcher-agent-task-test', + status: 'idle', + phase: 'completed', + runtime: { + state: completedRuntimeState, + sessionPath: + '/tmp/authorized-game/.agent/runtime/agents/design-director.json', + eventPath: + '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', + taskPath: + '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: completedRuntimeState.taskQueue, + recentEvents: [ + ...runningRuntimeEvents, + { + schemaVersion: 'game-creator-agent-runtime.v1', + agentId: 'design-director', + taskId: 'design-director', + sessionId: 'agent-session-design-director', + runId: 'launcher-agent-task-test', + source: 'agent-background-task', + eventType: 'turn.completed', + status: 'idle', + phase: 'completed', + summary: 'Agent Runtime 完成本轮处理。', + detail: '角色规范已整理。', + updatedAt: 4010, + }, + ], + recentTasks: [completedRuntimeTask], + }, + }, + }); + }); + + expect(await screen.findByText('idle / completed')).not.toBeNull(); + expect(screen.getByText('等待:开发者下一轮输入')).not.toBeNull(); + expect( + screen.getByText( + 'turn.completed · idle / completed · Agent Runtime 完成本轮处理。 · 角色规范已整理。', + ), + ).not.toBeNull(); }); it('shows queued developer agent background tasks when the agent is already running', async () => { @@ -13438,6 +13536,32 @@ describe('AI 游戏创作 App 界面边界', () => { error: null, updatedAt: 10, }; + const completedRuntimeState = { + ...runtimeState, + status: 'idle', + phase: 'completed', + currentAction: '等待下一轮输入', + waitingOn: '开发者下一轮输入', + nextStep: '等待下一轮输入', + taskQueue: { + total: 2, + pending: 1, + running: 0, + completed: 1, + failed: 0, + latestRunId: 'runtime-design-director-1', + updatedAt: 12, + }, + lastResponse: '已补齐第一关节奏目标。', + updatedAt: 12, + }; + const completedRuntimeTask = { + ...runtimeTask, + status: 'completed', + phase: 'completed', + currentAction: '等待下一轮输入', + updatedAt: 12, + }; const invoke = vi.fn( async (command: string, args?: Record) => { if (command === 'append_local_permission_log') { @@ -13481,7 +13605,29 @@ describe('AI 游戏创作 App 界面边界', () => { throw new Error(`unexpected invoke ${command}`); }, ); - window.__TAURI__ = { core: { invoke } }; + let runtimeUpdateHandler: + | ((event: { payload: Record }) => void) + | null = null; + const listen = vi.fn( + async ( + eventName: string, + handler: (event: { payload: Record }) => void, + ) => { + if (eventName === 'game-creator-agent-progress') { + return () => {}; + } + if (eventName === 'game-creator-agent-runtime-update') { + runtimeUpdateHandler = handler; + return () => { + if (runtimeUpdateHandler === handler) { + runtimeUpdateHandler = null; + } + }; + } + throw new Error(`unexpected listen ${eventName}`); + }, + ); + window.__TAURI__ = { core: { invoke }, event: { listen } }; renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game'); @@ -13515,6 +13661,45 @@ describe('AI 游戏创作 App 界面边界', () => { ), ).toHaveLength(1); }); + + await act(async () => { + runtimeUpdateHandler?.({ + payload: { + projectPath: '/tmp/authorized-game', + agentId: 'design-director', + runId: 'runtime-design-director-1', + status: 'idle', + phase: 'completed', + runtime: { + state: completedRuntimeState, + sessionPath: + '/tmp/authorized-game/.agent/runtime/agents/design-director.json', + eventPath: + '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', + taskPath: + '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: completedRuntimeState.taskQueue, + recentEvents: [], + recentTasks: [completedRuntimeTask], + }, + }, + }); + }); + + await waitFor(() => { + const designCard = within(agentStatusList).getByRole('button', { + name: /拆解创作方向/, + }); + expect(designCard.textContent).toContain( + 'Runtime:idle / completed · Loop 2/3 · 等待下一轮输入 · 等待 开发者下一轮输入 · 下一步 等待下一轮输入 · run runtime-design-director-1', + ); + expect(designCard.textContent).toContain( + '任务队列:pending 1 · running 0 · completed 1 · failed 0 · total 2 · latest runtime-design-director-1', + ); + expect(designCard.textContent).toContain( + '最近任务:completed / completed · 排队补齐世界观拆解', + ); + }); }); it('ignores stale agent conversation reads after switching agents', async () => { @@ -20593,9 +20778,15 @@ describe('AI 游戏创作 App 界面边界', () => { }, ); const listen = vi.fn( - async (_event: string, handler: typeof progressHandler) => { - progressHandler = handler; - return vi.fn(); + async (eventName: string, handler: typeof progressHandler) => { + if (eventName === 'game-creator-agent-progress') { + progressHandler = handler; + return vi.fn(); + } + if (eventName === 'game-creator-agent-runtime-update') { + return vi.fn(); + } + throw new Error(`unexpected listen ${eventName}`); }, ); window.__TAURI__ = { core: { invoke }, event: { listen } }; diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 34360429b..23cd295b9 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -20,6 +20,7 @@ - 背景:开发用单 Agent 聊天已经能真实调用各 Agent 的 LLM 路由并持久化对话,但 Agent 仍主要表现为同步问答,用户无法明确投递一个任务让某个 Agent 独立运行,也无法同时启动多个 Agent 的工作。 - 决策:在现有 `.agent/runtime` 和 `.agent/conversations` 基础上新增单 Agent 后台任务入口。Tauri 命令 `start_game_creator_agent_runtime_task` 立即写入该 Agent 的 runtime state/event/task history,追加用户任务到 `.agent/conversations/agents/.jsonl`,随后在 App 进程内启动 tokio task 执行最小 Agent loop:Agent 按轮输出 `thinkingSummary / plan / actions / response`,Runtime 按白名单和项目权限策略执行工具并记录 `action / observation` 事件,再把已有 observation 放回下一轮 prompt,让 Agent 修正计划、继续行动或用空 actions + response 收束;当前后台任务最多执行 3 轮 loop,仍未收束时再按最后计划和全部观察生成最终回复。完成或失败后把 assistant 回复或错误追加回对话,并写入 `.agent/agent.db` 审计记录。工具箱包含只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate`;`memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加共享黑板,`agent.message` 写目标 Agent 对话,`agent.delegate` 把任务投递到目标 Agent 的独立后台队列;策略要求确认或拒绝时不执行写入、运行或委派,只把策略结果作为 observation 回给 Agent。每个 Agent 的任务历史落在 `.agent/runtime/tasks/.jsonl`,读 runtime 时按 `runId` 去重返回最近任务,任务视角状态使用 `pending / running / completed / failed`,Runtime state 增加 `nextStep`,UI 在 Runtime 面板和主 Agent 状态卡展示当前任务、动作、下一步与最近任务。不同 Agent 使用独立 `.agent/runtime/locks/.lock`,允许并行运行;同一 Agent 已有运行任务时,新任务会先进入该 Agent 的 pending 队列,当前 drain 持锁完成后串行继续下一条 pending。该能力仍不是独立 OS 进程或跨重启离线常驻 worker。 +- 2026-07-10 补充:后台 Runtime 每次追加 `.agent/runtime/events/.jsonl` 后会通过 Tauri `game-creator-agent-runtime-update` 事件广播当前 `AgentRuntimeResult`;开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都只把该事件作为实时 UI 通知并复用前端 runtime 归一化合并,事实源仍是 `.agent/runtime/agents`、`events` 和 `tasks` 文件。 - 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `preview.start`,让 Agent 在完成写盘或静态自检后能按策略自行启动当前项目的 `127.0.0.1` 本地 HTTP 预览。该工具复用 `preview.start` 权限策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑;写入 `.agent/agent.db` 的审计类型为 `agent.runtime.preview.start`。发给 LLM 的 observation 只包含 localhost URL 和端口,不包含用户项目绝对路径。 - 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `canvas.asset_generate`,让美术类 Agent 可在 loop 中自行请求生成首版美术素材。该工具读取 AppData / Tauri 配置中的 `editorApi`,复用 `canvas.asset_generate` 权限策略、项目写锁、External Editor API 生成和下载链路、manifest 资产登记以及 `canvas.asset_generate` 本地索引记录;另写 `agent.runtime.canvas.asset_generate` 记录到 `.agent/agent.db`,标明触发的 agent 与本地素材路径。API Key 不进入 prompt observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。 - 补充:规范 Agent ID 统一使用 manifest taskId,例如 `art-asset-plan` 和 `code-prototype`;历史前端曾使用的 `group-role` 别名只在 Tauri command 层兼容并映射到规范 taskId。主窗口 Agent 状态列表通过 `read_game_creator_agent_runtimes` 批量读取 `.agent/runtime/agents/.json` 和最近任务,把每个 Agent 的 Runtime 状态、当前动作和最近 task 直接显示在状态卡片和 `/agents` 汇总里。 @@ -4064,6 +4065,7 @@ - 2026-07-10 调整:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`。后台 Agent loop 每轮规划前刷新当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,字段只做运行观测,不改变 loop 上限或权限 gate。 - 2026-07-10 调整:Agent Runtime state 新增 `planSteps / activePlanStepIndex`。Runtime 从 Agent 输出的 `plan` 派生结构化计划步骤,并在 action / observation / response / error 生命周期中更新 `pending / active / completed / failed` 和 detail;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示步骤进度,不再只依赖不可定位的 plan 字符串。 - 2026-07-10 调整:开发单 Agent 对话页和项目内 Agent 对话弹窗的 Runtime 面板接入 `recentEvents`,展示最近 `thinking_summary / plan / action / observation / response / error` 事件,避免只从当前状态、observation 字符串或最近工具动作里倒推 Agent loop。 +- 2026-07-10 调整:后台 Runtime 每次追加 `.agent/runtime/events/.jsonl` 后会发送 `game-creator-agent-runtime-update` Tauri 事件,payload 带当前 `AgentRuntimeResult`;开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表实时合并该结果,但事件不替代 `.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 调整: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 9c0b65b04..612805bd1 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -39,6 +39,7 @@ Agent Runtime 负责: - 2026-07-10 补充:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`,结构化记录后台 Agent loop 当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,帮助判断 Agent 是刚开始规划、正在 replan,还是接近本轮 loop 上限。该字段只做运行观测,不改变后台 loop 的执行上限或工具权限。 - 2026-07-10 补充:Agent Runtime state 新增 `planSteps / activePlanStepIndex`,从 Agent 输出的 `plan` 派生结构化计划步骤,并在工具 action / observation / response / error 生命周期中更新 `pending / active / completed / failed` 和 detail;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示当前计划步骤与步骤进度,避免只能展示一串不可定位的 plan 文本。 - 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 补充:单 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 与端口,不返回用户项目绝对路径。