From a2bbeb79ee3b592d22ff519ab0bc88212ec6f468 Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Thu, 9 Jul 2026 15:41:12 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90Agent=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=9B=9E=E6=89=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent 聊天在用户消息已保存后将回复失败原因落盘为 assistant 消息 新增 --agent-chat 开发诊断入口验证单 Agent LLM 调用链 补充开发 Agent 聊天失败回执和 CLI 解析测试 --- .../src-tauri/src/cli.rs | 46 ++++++++ .../src-tauri/src/tests.rs | 17 +++ apps/ai-game-creator-shell/src/App.tsx | 78 ++++++++++---- .../tests/appSurface.test.ts | 102 ++++++++++++++++++ 4 files changed, 223 insertions(+), 20 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/cli.rs b/apps/ai-game-creator-shell/src-tauri/src/cli.rs index 5388d429a..31f24d59f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/cli.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/cli.rs @@ -3,6 +3,11 @@ use super::*; #[derive(Debug, Eq, PartialEq)] pub(crate) enum CliCommand { LlmStatus, + AgentChat { + project_path: PathBuf, + agent_id: String, + prompt: String, + }, AgentRun { project_path: PathBuf, prompt: String, @@ -63,6 +68,27 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result, S if args.first().map(String::as_str) == Some("--llm-status") { return Ok(Some(CliCommand::LlmStatus)); } + if args.first().map(String::as_str) == Some("--agent-chat") { + let project_path = args.get(1).map(String::as_str).ok_or_else(|| { + "用法:--agent-chat <本地项目绝对路径> <聊天内容>".to_string() + })?; + let agent_id = args.get(2).map(String::as_str).ok_or_else(|| { + "用法:--agent-chat <本地项目绝对路径> <聊天内容>".to_string() + })?; + if args.len() < 4 { + return Err("用法:--agent-chat <本地项目绝对路径> <聊天内容>".to_string()); + } + let prompt = args[3..].join(" "); + let prompt = prompt.trim(); + if prompt.is_empty() { + return Err("聊天内容不能为空".to_string()); + } + return Ok(Some(CliCommand::AgentChat { + project_path: PathBuf::from(project_path), + agent_id: agent_id.trim().to_string(), + prompt: prompt.to_string(), + })); + } if args.first().map(String::as_str) != Some("--agent-run") { return Ok(None); } @@ -105,6 +131,26 @@ pub(crate) fn run_cli_command(command: CliCommand) -> Result<(), String> { Err("LLM 配置未就绪".to_string()) } } + CliCommand::AgentChat { + project_path, + agent_id, + prompt, + } => { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .map_err(|error| format!("创建 CLI runtime 失败:{error}"))?; + let reply = runtime.block_on(chat_with_game_creator_role_agent_at( + &project_path, + &agent_id, + &prompt, + ))?; + println!("agent.chat.completed"); + println!("projectPath={}", project_path.display()); + println!("agentId={agent_id}"); + println!("replyText={}", reply.reply_text); + Ok(()) + } CliCommand::AgentRun { project_path, prompt, 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 2eb944627..2a1ee9c99 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests.rs @@ -5091,6 +5091,22 @@ fn cli_agent_run_requires_project_and_prompt() { wait_for_enter: true, } ); + let agent_chat = parse_cli_command(&[ + "--agent-chat".to_string(), + "/tmp/genarrative-cli-game".to_string(), + "art-director".to_string(), + "我要生成一个开罗风格的 dota".to_string(), + ]) + .expect("parse agent chat") + .expect("agent chat command"); + assert_eq!( + agent_chat, + CliCommand::AgentChat { + project_path: PathBuf::from("/tmp/genarrative-cli-game"), + agent_id: "art-director".to_string(), + prompt: "我要生成一个开罗风格的 dota".to_string(), + } + ); let no_wait = parse_cli_command(&[ "--agent-run".to_string(), "--no-wait".to_string(), @@ -5109,6 +5125,7 @@ fn cli_agent_run_requires_project_and_prompt() { ); assert!(parse_cli_command(&[]).expect("parse no cli").is_none()); assert!(parse_cli_command(&["--agent-run".to_string()]).is_err()); + assert!(parse_cli_command(&["--agent-chat".to_string()]).is_err()); } #[test] diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 657e383c4..16355c7e0 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -3002,16 +3002,35 @@ export function WorkspaceLauncher({ const message = `已保存用户消息;Agent 回复失败:${ error instanceof Error ? error.message : String(error) }`; - setAgentChatMessages([ - ...savedUserResult.messages, - { - schemaVersion: 'game-creator-conversation.v1', - role: 'assistant', - content: message, - agentId: null, - updatedAt: Date.now(), - }, - ]); + try { + const errorResult = await invoke( + 'append_local_conversation_message', + { + projectPath: projectPathForChat, + agentId: agent.id, + message: { + role: 'assistant', + content: message, + agentId: null, + }, + }, + ); + if (agentChatLoadVersionRef.current !== saveVersion) { + return; + } + setAgentChatMessages(errorResult.messages); + } catch { + setAgentChatMessages([ + ...savedUserResult.messages, + { + schemaVersion: 'game-creator-conversation.v1', + role: 'assistant', + content: message, + agentId: null, + updatedAt: Date.now(), + }, + ]); + } setAgentChatStatus(message); } else { setAgentChatInput(content); @@ -12323,16 +12342,35 @@ export function App() { const message = `已保存用户消息;Agent 回复失败:${ error instanceof Error ? error.message : String(error) }`; - setAgentConversationMessages([ - ...savedUserResult.messages, - { - schemaVersion: 'game-creator-conversation.v1', - role: 'assistant', - content: message, - agentId: null, - updatedAt: Date.now(), - }, - ]); + try { + const errorResult = await invoke( + 'append_local_conversation_message', + { + projectPath: nextProjectPath, + agentId: agent.id, + message: { + role: 'assistant', + content: message, + agentId: null, + }, + }, + ); + if (agentConversationLoadVersionRef.current !== saveVersion) { + return; + } + setAgentConversationMessages(errorResult.messages); + } catch { + setAgentConversationMessages([ + ...savedUserResult.messages, + { + schemaVersion: 'game-creator-conversation.v1', + role: 'assistant', + content: message, + agentId: null, + updatedAt: Date.now(), + }, + ]); + } setAgentConversationStatus(message); } else { setAgentConversationInput(content); diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index 4f7cddf4c..168c0be06 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -1060,6 +1060,108 @@ describe('AI 游戏创作 App 界面边界', () => { ); }); + it('persists developer agent chat reply failures after saving the user message', async () => { + const persistedMessages: Array<{ + role: 'user' | 'assistant'; + content: string; + agentId: string | null; + }> = []; + const invoke = vi.fn( + async (command: string, args?: Record) => { + if (command === 'check_game_creator_llm_config') { + return { + configured: true, + apiKeyPresent: true, + baseUrl: 'https://llm.example.test/v1', + model: 'gpt-5.5', + apiKind: 'openai_chat', + stream: false, + error: null, + agents: [ + { + agentId: 'design-director', + label: '拆解创作方向', + configured: true, + apiKeyPresent: true, + baseUrl: 'https://llm.example.test/v1', + model: 'gpt-5.5', + apiKind: 'openai_chat', + stream: false, + error: null, + }, + ], + }; + } + if (command === 'read_local_conversation') { + return { + path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl', + agentId: args?.agentId, + messages: persistedMessages.map((message, index) => ({ + schemaVersion: '1', + ...message, + updatedAt: 1000 + index, + })), + }; + } + if (command === 'chat_with_game_creator_role_agent') { + throw new Error('上游 LLM 连接失败'); + } + if (command === 'append_local_conversation_message') { + const message = args?.message as { + role: 'user' | 'assistant'; + content: string; + agentId: string | null; + }; + persistedMessages.push(message); + return { + path: '/tmp/authorized-game/.agent/conversations/agents/design-director.jsonl', + agentId: args?.agentId, + messages: persistedMessages.map((record, index) => ({ + schemaVersion: '1', + ...record, + updatedAt: 2000 + index, + })), + }; + } + throw new Error(`unexpected invoke ${command}`); + }, + ); + window.__TAURI__ = { core: { invoke } }; + renderLauncherAgentChatAt('/?agent-chat'); + + fireEvent.change(screen.getByLabelText('Agent 聊天项目目录'), { + target: { value: '/tmp/authorized-game' }, + }); + fireEvent.click(screen.getByRole('button', { name: '读取历史' })); + expect(await screen.findByText(/已读取 0 条/)).not.toBeNull(); + + fireEvent.change(screen.getByLabelText('Agent 聊天内容'), { + target: { value: '请真实回复,不要只记录' }, + }); + fireEvent.click(screen.getByRole('button', { name: '发送' })); + + expect(await screen.findByText('请真实回复,不要只记录')).not.toBeNull(); + expect( + ( + await screen.findAllByText( + /已保存用户消息;Agent 回复失败:上游 LLM 连接失败/, + ) + ).length, + ).toBeGreaterThan(0); + expect(persistedMessages).toEqual([ + { + role: 'user', + content: '请真实回复,不要只记录', + agentId: null, + }, + { + role: 'assistant', + content: '已保存用户消息;Agent 回复失败:上游 LLM 连接失败', + agentId: null, + }, + ]); + }); + it('loads selected developer agent history immediately after switching agents', async () => { const invoke = vi.fn( async (command: string, args?: Record) => {