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 b952fd470..f26cee66e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent.rs @@ -216,14 +216,16 @@ 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)?; + let task_snapshot = read_game_creator_agent_runtime_task_snapshot(&task_path)?; + state.task_queue = task_snapshot.task_queue.clone(); Ok(AgentRuntimeResult { state, session_path: session_path.to_string_lossy().into_owned(), event_path: event_path.to_string_lossy().into_owned(), task_path: task_path.to_string_lossy().into_owned(), + task_queue: task_snapshot.task_queue, recent_events, - recent_tasks, + recent_tasks: task_snapshot.recent_tasks, }) } @@ -2142,6 +2144,7 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin .last() .map(format_agent_runtime_task_observation) .unwrap_or_else(|| "-".to_string()); + let task_queue = format_agent_runtime_task_queue_observation(&result.task_queue); let recent_tool = state .recent_tool_calls .last() @@ -2154,7 +2157,7 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin .filter(|value| !value.trim().is_empty()) .unwrap_or_else(|| "-".to_string()); format!( - "agentId: {}\nstatus: {}\nphase: {}\nrunId: {}\n当前目标: {}\n当前任务: {}\n当前动作: {}\n等待: {}\n下一步: {}\n计划: {}\n最近任务: {}\n最近工具: {}\n错误: {}", + "agentId: {}\nstatus: {}\nphase: {}\nrunId: {}\n当前目标: {}\n当前任务: {}\n当前动作: {}\n等待: {}\n下一步: {}\n计划: {}\n任务队列: {}\n最近任务: {}\n最近工具: {}\n错误: {}", state.agent_id, state.status, state.phase, @@ -2165,6 +2168,7 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin waiting_on, next_step, plan, + task_queue, recent_task, recent_tool, error @@ -2180,6 +2184,18 @@ fn agent_runtime_status_text(value: &str, max_chars: usize) -> String { } } +fn format_agent_runtime_task_queue_observation(queue: &AgentRuntimeTaskQueueSummary) -> String { + format!( + "total={} pending={} running={} completed={} failed={} latest={}", + queue.total, + queue.pending, + queue.running, + queue.completed, + queue.failed, + queue.latest_run_id.as_deref().unwrap_or("-") + ) +} + fn format_agent_runtime_task_observation(task: &AgentRuntimeTaskRecord) -> String { format!( "{} / {} / {} / {}", @@ -2321,8 +2337,9 @@ pub(crate) fn start_game_creator_agent_runtime_task_at( } 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)?; + refresh_game_creator_agent_runtime_task_queue(root, &mut state)?; + write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_event( root, &state, @@ -2365,8 +2382,9 @@ pub(crate) fn advance_game_creator_agent_runtime_turn_at( } 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)?; + refresh_game_creator_agent_runtime_task_queue(root, &mut state)?; + write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_event( root, &state, @@ -2396,8 +2414,9 @@ pub(crate) fn finish_game_creator_agent_runtime_turn_at( .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)?; + refresh_game_creator_agent_runtime_task_queue(root, &mut state)?; + write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_event( root, &state, @@ -2435,8 +2454,9 @@ pub(crate) fn fail_game_creator_agent_runtime_turn_at( 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)?; + refresh_game_creator_agent_runtime_task_queue(root, &mut state)?; + write_game_creator_agent_runtime_state(root, &state)?; append_game_creator_agent_runtime_event( root, &state, @@ -2512,6 +2532,7 @@ fn default_game_creator_agent_runtime_state(agent_id: &str, run_id: &str) -> Age ], observations: Vec::new(), recent_tool_calls: Vec::new(), + task_queue: AgentRuntimeTaskQueueSummary::default(), allowed_tools: default_game_creator_agent_runtime_allowed_tools(), tool_policy: AgentRuntimeToolPolicySnapshot::default(), last_response: None, @@ -2991,6 +3012,15 @@ fn append_game_creator_agent_runtime_task_record( .map_err(|error| format!("写入 Agent Runtime 任务失败:{}: {error}", path.display())) } +fn refresh_game_creator_agent_runtime_task_queue( + root: &Path, + state: &mut AgentRuntimeState, +) -> Result<(), String> { + let task_path = game_creator_agent_runtime_task_path(root, &state.agent_id); + state.task_queue = read_game_creator_agent_runtime_task_snapshot(&task_path)?.task_queue; + Ok(()) +} + fn game_creator_agent_runtime_task_status(state: &AgentRuntimeState) -> String { if state.error.is_some() || state.status == "failed" || state.phase == "failed" { "failed".to_string() @@ -3041,15 +3071,25 @@ fn read_recent_game_creator_agent_runtime_events( } } -fn read_recent_game_creator_agent_runtime_tasks( +struct AgentRuntimeTaskSnapshot { + task_queue: AgentRuntimeTaskQueueSummary, + recent_tasks: Vec, +} + +fn read_game_creator_agent_runtime_task_snapshot( path: &Path, -) -> Result, String> { +) -> Result { let records = read_all_game_creator_agent_runtime_tasks(path)?; - let mut recent = latest_game_creator_agent_runtime_tasks(records); + let latest = latest_game_creator_agent_runtime_tasks(records); + let task_queue = summarize_game_creator_agent_runtime_task_queue(&latest); + let mut recent = latest; if recent.len() > AGENT_RUNTIME_RECENT_TASK_LIMIT { recent = recent[recent.len() - AGENT_RUNTIME_RECENT_TASK_LIMIT..].to_vec(); } - Ok(recent) + Ok(AgentRuntimeTaskSnapshot { + task_queue, + recent_tasks: recent, + }) } fn read_next_pending_game_creator_agent_runtime_task( @@ -3114,6 +3154,29 @@ fn latest_game_creator_agent_runtime_tasks( latest } +fn summarize_game_creator_agent_runtime_task_queue( + records: &[AgentRuntimeTaskRecord], +) -> AgentRuntimeTaskQueueSummary { + let mut summary = AgentRuntimeTaskQueueSummary { + total: records.len() as u32, + ..AgentRuntimeTaskQueueSummary::default() + }; + for record in records { + match record.status.as_str() { + "pending" => summary.pending += 1, + "running" => summary.running += 1, + "completed" => summary.completed += 1, + "failed" => summary.failed += 1, + _ => {} + } + if record.updated_at >= summary.updated_at { + summary.updated_at = record.updated_at; + summary.latest_run_id = Some(record.run_id.clone()); + } + } + summary +} + fn truncate_agent_runtime_text(value: &str, max_chars: usize) -> String { let value = value.trim(); let mut output = String::new(); @@ -3227,6 +3290,12 @@ fn render_agent_runtime_prompt_context(root: &Path, agent_id: &str) -> Result 0 { + lines.push(format!( + "任务队列:{}", + format_agent_runtime_task_queue_observation(&runtime.task_queue) + )); + } if let Some(last_response) = state .last_response .as_deref() 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 3119300b1..5576bd568 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -168,6 +168,8 @@ struct AgentRuntimeState { #[serde(default)] recent_tool_calls: Vec, #[serde(default)] + task_queue: AgentRuntimeTaskQueueSummary, + #[serde(default)] allowed_tools: Vec, #[serde(default)] tool_policy: AgentRuntimeToolPolicySnapshot, @@ -223,6 +225,39 @@ struct AgentRuntimeToolCallRecord { updated_at: u64, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +struct AgentRuntimeTaskQueueSummary { + #[serde(default)] + total: u32, + #[serde(default)] + pending: u32, + #[serde(default)] + running: u32, + #[serde(default)] + completed: u32, + #[serde(default)] + failed: u32, + #[serde(default)] + latest_run_id: Option, + #[serde(default)] + updated_at: u64, +} + +impl Default for AgentRuntimeTaskQueueSummary { + fn default() -> Self { + Self { + total: 0, + pending: 0, + running: 0, + completed: 0, + failed: 0, + latest_run_id: None, + updated_at: 0, + } + } +} + #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] struct AgentRuntimeEvent { @@ -288,6 +323,7 @@ struct AgentRuntimeResult { session_path: String, event_path: String, task_path: String, + task_queue: AgentRuntimeTaskQueueSummary, recent_events: Vec, recent_tasks: Vec, } 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 edde3d360..71a4c7f0b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests.rs @@ -1142,11 +1142,19 @@ async fn role_agent_legacy_alias_maps_to_canonical_task_runtime_and_route() { .expect("parse runtime wire json"); assert_eq!(runtime_wire["currentGoal"], "排队规划美术资产"); assert_eq!(runtime_wire["waitingOn"], "Agent 输出计划或回复"); + assert_eq!(runtime_wire["taskQueue"]["total"], 1); + assert_eq!(runtime_wire["taskQueue"]["running"], 1); + assert_eq!( + runtime_wire["taskQueue"]["latestRunId"], + "alias-runtime-run" + ); let alias_read = read_game_creator_agent_runtime_at(&root, "art-asset").expect("read alias runtime"); assert_eq!(alias_read.state.agent_id, "art-asset-plan"); assert_eq!(alias_read.state.current_goal, "排队规划美术资产"); assert_eq!(alias_read.state.waiting_on, "Agent 输出计划或回复"); + assert_eq!(alias_read.task_queue.total, 1); + assert_eq!(alias_read.task_queue.running, 1); assert!(alias_read .session_path .ends_with(".agent/runtime/agents/art-asset-plan.json")); @@ -1963,6 +1971,9 @@ async fn background_agent_runtime_plan_request_includes_same_agent_continuity_co assert!(second_design_request.contains("# Agent Runtime 连续上下文")); assert!(second_design_request.contains("当前状态:status=running")); assert!(second_design_request.contains("runId=design-continuity-second")); + assert!(second_design_request.contains( + "任务队列:total=2 pending=0 running=1 completed=1 failed=0 latest=design-continuity-second" + )); assert!(second_design_request.contains("最近回复:首轮完成:已经读取连续上下文笔记。")); assert!(second_design_request.contains("最近工具动作")); assert!(second_design_request.contains("file.read [ok]")); diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index cb799730a..612a54d93 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -244,6 +244,7 @@ interface AgentRuntimeState { plan: string[]; observations: string[]; recentToolCalls?: AgentRuntimeToolCallRecord[]; + taskQueue?: AgentRuntimeTaskQueueSummary; allowedTools: string[]; toolPolicy?: AgentRuntimeToolPolicySnapshot; lastResponse: string | null; @@ -270,6 +271,16 @@ interface AgentRuntimeToolCallRecord { updatedAt: number; } +interface AgentRuntimeTaskQueueSummary { + total: number; + pending: number; + running: number; + completed: number; + failed: number; + latestRunId: string | null; + updatedAt: number; +} + interface AgentRuntimeEventRecord { schemaVersion: string; agentId: string; @@ -305,6 +316,7 @@ interface AgentRuntimeResult { sessionPath: string; eventPath: string; taskPath?: string; + taskQueue?: AgentRuntimeTaskQueueSummary; recentEvents?: AgentRuntimeEventRecord[]; recentTasks?: AgentRuntimeTaskRecord[]; } @@ -533,6 +545,15 @@ function normalizeAgentRuntimeState( deniedTools: [], updatedAt: 0, }, + taskQueue: state.taskQueue ?? previous?.taskQueue ?? { + total: 0, + pending: 0, + running: 0, + completed: 0, + failed: 0, + latestRunId: null, + updatedAt: 0, + }, recentEvents: state.recentEvents ?? previous?.recentEvents ?? [], recentTasks: state.recentTasks ?? previous?.recentTasks ?? [], }; @@ -545,6 +566,7 @@ function agentRuntimeStateFromResult( return normalizeAgentRuntimeState( { ...result.state, + taskQueue: result.taskQueue ?? result.state.taskQueue, recentEvents: result.recentEvents ?? result.state.recentEvents, recentTasks: result.recentTasks ?? result.state.recentTasks, }, @@ -605,6 +627,25 @@ function formatAgentRuntimeEvent(event: AgentRuntimeEventRecord) { return `${event.eventType} · ${event.status} / ${event.phase} · ${summary}${detail}`; } +function formatAgentRuntimeTaskQueue( + queue: AgentRuntimeTaskQueueSummary | null | undefined, +) { + if (!queue || queue.total <= 0) { + return null; + } + const parts = [ + `pending ${queue.pending}`, + `running ${queue.running}`, + `completed ${queue.completed}`, + `failed ${queue.failed}`, + `total ${queue.total}`, + ]; + if (queue.latestRunId) { + parts.push(`latest ${queue.latestRunId}`); + } + return `任务队列:${parts.join(' · ')}`; +} + function AgentRuntimeStatusPanel({ runtime, error, @@ -631,6 +672,7 @@ function AgentRuntimeStatusPanel({ const recentToolCalls = (runtime.recentToolCalls ?? []).slice(-3).reverse(); const recentTasks = (runtime.recentTasks ?? []).slice(-3).reverse(); const toolPolicy = runtime.toolPolicy; + const taskQueueSummary = formatAgentRuntimeTaskQueue(runtime.taskQueue); const nextStep = runtime.nextStep ?? agentRuntimeNextStepFromPhase(runtime.phase); const currentGoal = runtime.currentGoal ?? runtime.currentTask; const waitingOn = runtime.waitingOn ?? agentRuntimeWaitingOnFromPhase(runtime.phase); @@ -647,6 +689,7 @@ function AgentRuntimeStatusPanel({ {runtime.currentAction} {waitingOn ? {`等待:${waitingOn}`} : null} {nextStep ? {`下一步:${nextStep}`} : null} + {taskQueueSummary ? {taskQueueSummary} : null} {toolPolicy ? ( {`工具策略:auto ${toolPolicy.autoTools.length} · confirm ${toolPolicy.confirmTools.length} · deny ${toolPolicy.deniedTools.length}`} @@ -833,6 +876,7 @@ interface AgentStatusCard { runtimeNextStep: string | null; runtimeTask: string | null; runtimeRunId: string | null; + runtimeTaskQueue: AgentRuntimeTaskQueueSummary | null; runtimeRecentTasks: AgentRuntimeTaskRecord[]; } @@ -10153,6 +10197,7 @@ export function deriveAgentStatusCards( : null, runtimeTask: runtime?.currentTask ?? null, runtimeRunId: runtime?.runId ?? null, + runtimeTaskQueue: runtime?.taskQueue ?? null, runtimeRecentTasks: runtime?.recentTasks ?? [], }; }); @@ -10185,6 +10230,7 @@ function sameAgentStatusCard(left: AgentStatusCard, right: AgentStatusCard) { left.runtimeNextStep === right.runtimeNextStep && left.runtimeTask === right.runtimeTask && left.runtimeRunId === right.runtimeRunId && + sameAgentRuntimeTaskQueue(left.runtimeTaskQueue, right.runtimeTaskQueue) && left.hasRecentEvidence === right.hasRecentEvidence && left.taskGraphState === right.taskGraphState && sameStringArray(left.inputPaths, right.inputPaths) && @@ -10205,6 +10251,24 @@ function sameAgentStatusCard(left: AgentStatusCard, right: AgentStatusCard) { ); } +function sameAgentRuntimeTaskQueue( + left: AgentRuntimeTaskQueueSummary | null, + right: AgentRuntimeTaskQueueSummary | null, +) { + if (!left || !right) { + return left === right; + } + return ( + left.total === right.total && + left.pending === right.pending && + left.running === right.running && + left.completed === right.completed && + left.failed === right.failed && + left.latestRunId === right.latestRunId && + left.updatedAt === right.updatedAt + ); +} + function sameAgentRuntimeTasks( left: AgentRuntimeTaskRecord[], right: AgentRuntimeTaskRecord[], @@ -11028,10 +11092,14 @@ function summarizeAgentStatusCardsForChat( formatAgentCardRuntimeStatus(agent), ].filter(Boolean); const latestRuntimeTask = agent.runtimeRecentTasks.at(-1); + const runtimeTaskQueue = formatAgentRuntimeTaskQueue( + agent.runtimeTaskQueue, + ); return [ `- ${parts.join(' · ')}`, ` ${agent.summary}`, agent.runtimeGoal ? ` 当前目标:${agent.runtimeGoal}` : null, + runtimeTaskQueue ? ` ${runtimeTaskQueue}` : null, latestRuntimeTask ? ` 最近任务:${formatAgentRecentRuntimeTask(latestRuntimeTask)}` : null, @@ -20262,6 +20330,9 @@ export function App() { agent, ); const agentRuntimeStatus = formatAgentCardRuntimeStatus(agent); + const agentRuntimeTaskQueue = formatAgentRuntimeTaskQueue( + agent.runtimeTaskQueue, + ); const recentRuntimeTasks = agent.runtimeRecentTasks .slice(-2) .reverse(); @@ -20284,6 +20355,9 @@ export function App() { {agent.runtimeTask ? ( {`当前任务:${agent.runtimeTask}`} ) : null} + {agentRuntimeTaskQueue ? ( + {agentRuntimeTaskQueue} + ) : null} {recentRuntimeTasks.map((task, index) => ( { error: null, updatedAt: 1234, }; + const runtimeTaskQueue = { + total: 1, + pending: 1, + running: 0, + completed: 0, + failed: 0, + latestRunId: 'runtime-art-asset-plan-1', + updatedAt: 1234, + }; const cards = deriveAgentStatusCards(manifest, trace, { 'art-asset-plan': { schemaVersion: 'game-creator-agent-runtime.v1', @@ -512,6 +521,7 @@ describe('AI 游戏创作 App 界面边界', () => { lastResponse: null, error: null, updatedAt: 1234, + taskQueue: runtimeTaskQueue, recentTasks: [runtimeTask], }, }); @@ -545,6 +555,7 @@ describe('AI 游戏创作 App 界面边界', () => { runtimeAction: '拆解素材规格', runtimeTask: '补齐主角规范图资产列表', runtimeRunId: 'runtime-art-asset-plan-1', + runtimeTaskQueue, runtimeRecentTasks: [runtimeTask], }); expect(cards.find((card) => card.id === 'code-prototype')).toMatchObject({ @@ -1372,6 +1383,25 @@ describe('AI 游戏创作 App 界面边界', () => { waitingOn: '工具观察结果', plan: ['读取项目笔记', '结合观察修正建议', '回复开发者'], observations: ['思考摘要:需要先看项目笔记', 'file.read:ok · 已读取 game/notes.txt'], + recentToolCalls: [ + { + tool: 'file.read', + status: 'ok', + reason: '读取笔记', + summary: '已读取 game/notes.txt', + detail: 'game/notes.txt: 连续上下文笔记', + updatedAt: 4004, + }, + ], + taskQueue: { + total: 1, + pending: 0, + running: 1, + completed: 0, + failed: 0, + latestRunId: 'launcher-agent-task-test', + updatedAt: 4000, + }, allowedTools: ['conversation.read', 'conversation.write'], lastResponse: null, error: null, @@ -1481,6 +1511,7 @@ describe('AI 游戏创作 App 界面边界', () => { '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', taskPath: '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: runningRuntimeState.taskQueue, recentEvents: runningRuntimeEvents, recentTasks: [runningRuntimeTask], }; @@ -1503,6 +1534,10 @@ describe('AI 游戏创作 App 界面边界', () => { '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', taskPath: '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: { + ...runningRuntimeState.taskQueue, + latestRunId: runId, + }, recentEvents: runningRuntimeEvents, recentTasks: [{ ...runningRuntimeTask, runId }], }; @@ -1530,11 +1565,20 @@ describe('AI 游戏创作 App 界面边界', () => { expect(screen.getByText('当前目标:完成角色规范阶段')).not.toBeNull(); expect(screen.getByText('调用工具 file.read')).not.toBeNull(); expect(screen.getByText('等待:工具观察结果')).not.toBeNull(); + expect( + screen.getByText( + /任务队列:pending 0 · running 1 · completed 0 · failed 0 · total 1 · latest launcher-agent-task-/, + ), + ).not.toBeNull(); expect(screen.getByText('读取项目笔记')).not.toBeNull(); expect(screen.getByText('结合观察修正建议')).not.toBeNull(); expect(screen.getByText('回复开发者')).not.toBeNull(); expect(screen.getByText('思考摘要:需要先看项目笔记')).not.toBeNull(); expect(screen.getByText('file.read:ok · 已读取 game/notes.txt')).not.toBeNull(); + expect(screen.getByText('最近动作')).not.toBeNull(); + expect( + screen.getByText('file.read · ok · 已读取 game/notes.txt · 读取笔记'), + ).not.toBeNull(); expect(screen.getByText('最近事件')).not.toBeNull(); expect( screen.getByText( @@ -1589,6 +1633,15 @@ describe('AI 游戏创作 App 界面边界', () => { currentAction: '生成 Agent 工具计划', plan: ['读取上下文', '回复开发者'], observations: ['上一条任务正在运行。'], + taskQueue: { + total: 1, + pending: 0, + running: 1, + completed: 0, + failed: 0, + latestRunId: 'launcher-agent-task-running', + updatedAt: 5000, + }, allowedTools: ['conversation.read', 'conversation.write'], lastResponse: null, error: null, @@ -1650,6 +1703,7 @@ describe('AI 游戏创作 App 界面边界', () => { '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', taskPath: '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: runningRuntimeState.taskQueue, recentEvents: [], recentTasks: [runningTask], }; @@ -1663,6 +1717,15 @@ describe('AI 游戏创作 App 界面边界', () => { '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', taskPath: '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: { + total: 2, + pending: 1, + running: 1, + completed: 0, + failed: 0, + latestRunId: String(args?.runId ?? 'launcher-agent-task-pending'), + updatedAt: 5001, + }, recentEvents: [], recentTasks: [ runningTask, @@ -1702,6 +1765,11 @@ describe('AI 游戏创作 App 界面边界', () => { expect( screen.getByText(/pending \/ queued · 排队整理第二个需求/), ).not.toBeNull(); + expect( + screen.getByText( + /任务队列:pending 1 · running 1 · completed 0 · failed 0 · total 2 · latest launcher-agent-task-/, + ), + ).not.toBeNull(); }); it('shows developer agent runtime read failures', async () => { @@ -13281,6 +13349,15 @@ describe('AI 游戏创作 App 界面边界', () => { waitingOn: 'Agent 输出计划或回复', plan: ['读取项目上下文'], observations: ['已创建本轮 Agent Runtime run。'], + taskQueue: { + total: 2, + pending: 1, + running: 1, + completed: 0, + failed: 0, + latestRunId: 'runtime-design-director-1', + updatedAt: 10, + }, allowedTools: ['conversation.read'], lastResponse: null, error: null, @@ -13309,6 +13386,7 @@ describe('AI 游戏创作 App 界面边界', () => { '/tmp/authorized-game/.agent/runtime/events/design-director.jsonl', taskPath: '/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl', + taskQueue: runtimeState.taskQueue, recentEvents: [], recentTasks: [runtimeTask], }, @@ -13342,6 +13420,9 @@ describe('AI 游戏创作 App 界面边界', () => { '当前目标:补齐第一关节奏目标', ); expect(designCard.textContent).toContain('当前任务:拆解关卡节奏'); + expect(designCard.textContent).toContain( + '任务队列:pending 1 · running 1 · completed 0 · failed 0 · total 2 · latest runtime-design-director-1', + ); expect(designCard.textContent).toContain( '最近任务:pending / queued · 排队补齐世界观拆解', ); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 89aa40aef..790e8e760 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4062,6 +4062,7 @@ - 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 对话页和项目内 Agent 对话弹窗的 Runtime 面板接入 `recentEvents`,展示最近 `thinking_summary / plan / action / observation / response / error` 事件,避免只从当前状态、observation 字符串或最近工具动作里倒推 Agent loop。 +- 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,不得创建新任务。 - 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `file.list`。Agent 可自行列出项目文件摘要或相对路径范围内的条目,先观察项目结构再决定是否读取具体文件;该工具必须受 `file.list` 项目权限策略保护,observation 只返回项目相对路径、类型和大小,不读取文件内容、不返回本机绝对路径。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index aa72ae02d..6104eda8b 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -37,6 +37,7 @@ Agent Runtime 负责: - 2026-07-10 补充:Agent Runtime state 新增 `recentToolCalls`,每次后台工具执行后记录最近 20 条结构化工具动作,包含 tool、status、reason、summary、detail 和 updatedAt;开发窗口、项目内 Agent 对话弹窗和主窗口 Agent 状态列表可直接展示“最近动作”,不再只能从 observation 字符串里猜测 action / observation 对应关系。字段只保存过滤后的摘要和观察细节,不保存原始 API Key 或任意未过滤输入。 - 2026-07-10 补充:Agent Runtime state 新增 `currentGoal` 和 `waitingOn`,把本轮目标与当前等待对象从 `currentTask / currentAction / nextStep` 中显式拆出来;后台任务启动、工具 observation、完成和失败都会刷新该状态,开发窗口、项目内 Agent 对话弹窗、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示同一份目标 / 等待信息,避免开发者只能从动作文本里猜 Agent 卡在 LLM、工具、同伴还是人工输入。 - 2026-07-10 补充:`recentEvents` 接入前端归一态和 Runtime 状态面板,事件事实源仍是 `.agent/runtime/events/.jsonl`;面板按时间展示最近 `thinking_summary / plan / action / observation / response / error` 事件,现在能同时看到 Agent 的计划、最近观察、最近事件、最近工具动作和任务队列。 +- 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 与端口,不返回用户项目绝对路径。 - 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。