补齐Agent聊天失败回执
Agent 聊天在用户消息已保存后将回复失败原因落盘为 assistant 消息 新增 --agent-chat 开发诊断入口验证单 Agent LLM 调用链 补充开发 Agent 聊天失败回执和 CLI 解析测试
This commit is contained in:
@@ -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<Option<CliCommand>, 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 <本地项目绝对路径> <agentId> <聊天内容>".to_string()
|
||||
})?;
|
||||
let agent_id = args.get(2).map(String::as_str).ok_or_else(|| {
|
||||
"用法:--agent-chat <本地项目绝对路径> <agentId> <聊天内容>".to_string()
|
||||
})?;
|
||||
if args.len() < 4 {
|
||||
return Err("用法:--agent-chat <本地项目绝对路径> <agentId> <聊天内容>".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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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<LocalConversationResult>(
|
||||
'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<LocalConversationResult>(
|
||||
'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);
|
||||
|
||||
@@ -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<string, unknown>) => {
|
||||
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<string, unknown>) => {
|
||||
|
||||
Reference in New Issue
Block a user