diff --git a/.gitignore b/.gitignore index 495756a..95d86e5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ uv.lock node_modules logs dist +.qoder diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 21cd5a4..5f4eac0 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -1097,15 +1097,20 @@ impl AgentLoop { .zip(tool_results.iter()) .find(|(_, result)| result.state == ToolExecutionState::PendingUserAction) { + // 从工具输出中提取有意义的内容,跳过内部标记和元数据行 + let content_line = pending_result + .output + .lines() + .map(|l| l.trim()) + .find(|line| { + !line.is_empty() + && !line.starts_with("__PICOBOT_") + && !line.starts_with("[session_id:") + }) + .unwrap_or(DEFAULT_PENDING_ASSISTANT_MESSAGE); let assistant_message = ChatMessage::assistant(format!( "{}\n\n当前等待中的工具: {}", - pending_result - .output - .lines() - .next() - .filter(|line| !line.trim().is_empty()) - .unwrap_or(DEFAULT_PENDING_ASSISTANT_MESSAGE), - tool_call.name, + content_line, tool_call.name, )); emitted_messages.push(assistant_message.clone()); self.emit_live_tool_call_message(assistant_message.clone()).await; diff --git a/src/tools/bash.rs b/src/tools/bash.rs index a4f5f44..66bf5f9 100644 --- a/src/tools/bash.rs +++ b/src/tools/bash.rs @@ -18,6 +18,8 @@ use crate::tools::{extract_u64, extract_bool, check_null_args}; const MAX_TIMEOUT_SECS: u64 = 600; const MAX_OUTPUT_CHARS: usize = 50_000; const PENDING_USER_ACTION_MARKER: &str = "__PICOBOT_PENDING_USER_ACTION__"; +/// 检测到 stdin 等待后,给 read_stream 任务将最后的提示内容传入 channel 的时间 +const STDIN_FLUSH_MS: u64 = 500; const INTERACTIVE_HINT: &str = "进程正在等待输入。请使用 session_id 和 stdin_input 参数回复交互内容。"; const NON_INTERACTIVE_HINT: &str = @@ -182,12 +184,17 @@ impl BashTool { let session_line = session_id .map(|id| format!("[session_id: {}]\n", id)) .unwrap_or_default(); + let output_section = if output.trim().is_empty() { + "(进程尚未输出内容。进程正在等待输入,请使用 session_id 和 stdin_input 参数发送输入内容。)" + } else { + &self.truncate_output(output.trim()) + }; format!( "{}\n{}{}\n\n{}", PENDING_USER_ACTION_MARKER, session_line, hint, - self.truncate_output(output.trim()) + output_section ) } @@ -472,25 +479,26 @@ impl BashTool { // Periodic safety net: check OS-level process state if let Some(pid) = child.id() { if crate::platform::is_process_waiting_on_stdin(pid) == Some(true) { + // 给 read_stream 任务时间将最后的提示内容传入 channel + tokio::time::sleep(Duration::from_millis(STDIN_FLUSH_MS)).await; if let Some(rx_ref) = rx.as_mut() { drain_available_chunks(rx_ref, &stdout_buf, &stderr_buf).await; } let combined = format_command_output(&stdout_buf.lock().await, &stderr_buf.lock().await, None); - if !combined.trim().is_empty() { - if let Some(stdin) = child_stdin { - if let Some(rx_val) = rx.take() { - let session_id = self.session_manager.save_session( - child, stdin, rx_val, - stdout_buf.lock().await.clone(), - stderr_buf.lock().await.clone(), - ).await; - return Ok(self.pending_output(&combined, Some(&session_id))); - } + // 始终创建 session,即使输出为空(进程可能还没写出提示) + if let Some(stdin) = child_stdin { + if let Some(rx_val) = rx.take() { + let session_id = self.session_manager.save_session( + child, stdin, rx_val, + stdout_buf.lock().await.clone(), + stderr_buf.lock().await.clone(), + ).await; + return Ok(self.pending_output(&combined, Some(&session_id))); } - let _ = child.start_kill(); - let _ = child.wait().await; - return Ok(self.pending_output(&combined, None)); } + let _ = child.start_kill(); + let _ = child.wait().await; + return Ok(self.pending_output(&combined, None)); } } @@ -523,9 +531,13 @@ impl BashTool { // OS-level check: if blocked on stdin, save as session if let Some(pid) = child.id() { - if crate::platform::is_process_waiting_on_stdin(pid) == Some(true) - && !combined.trim().is_empty() - { + if crate::platform::is_process_waiting_on_stdin(pid) == Some(true) { + // 给 read_stream 任务时间将最后的提示内容传入 channel + tokio::time::sleep(Duration::from_millis(STDIN_FLUSH_MS)).await; + if let Some(rx_ref) = rx.as_mut() { + drain_available_chunks(rx_ref, &stdout_buf, &stderr_buf).await; + } + let combined = format_command_output(&stdout_buf.lock().await, &stderr_buf.lock().await, None); if let Some(stdin) = child_stdin { if let Some(rx_val) = rx.take() { let session_id = self.session_manager.save_session(