fix(agent): 优化工具待处理输出和交互会话行为

- 从工具输出中过滤元数据和内部标记,仅显示有意义内容
- 增加等待stdin输入时的短暂延迟,确保提示内容传入通道
- 始终创建交互式会话,即使当前输出为空,避免丢失会话信息
- 优化交互会话保存逻辑,确保正确处理stdin等待状态
- 修改.gitignore,添加.qoder目录忽略规则
This commit is contained in:
ooodc 2026-06-13 13:28:42 +08:00
parent a7883dbed9
commit c5f4209d33
3 changed files with 42 additions and 24 deletions

1
.gitignore vendored
View File

@ -33,3 +33,4 @@ uv.lock
node_modules node_modules
logs logs
dist dist
.qoder

View File

@ -1097,15 +1097,20 @@ impl AgentLoop {
.zip(tool_results.iter()) .zip(tool_results.iter())
.find(|(_, result)| result.state == ToolExecutionState::PendingUserAction) .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!( let assistant_message = ChatMessage::assistant(format!(
"{}\n\n当前等待中的工具: {}", "{}\n\n当前等待中的工具: {}",
pending_result content_line, tool_call.name,
.output
.lines()
.next()
.filter(|line| !line.trim().is_empty())
.unwrap_or(DEFAULT_PENDING_ASSISTANT_MESSAGE),
tool_call.name,
)); ));
emitted_messages.push(assistant_message.clone()); emitted_messages.push(assistant_message.clone());
self.emit_live_tool_call_message(assistant_message.clone()).await; self.emit_live_tool_call_message(assistant_message.clone()).await;

View File

@ -18,6 +18,8 @@ use crate::tools::{extract_u64, extract_bool, check_null_args};
const MAX_TIMEOUT_SECS: u64 = 600; const MAX_TIMEOUT_SECS: u64 = 600;
const MAX_OUTPUT_CHARS: usize = 50_000; const MAX_OUTPUT_CHARS: usize = 50_000;
const PENDING_USER_ACTION_MARKER: &str = "__PICOBOT_PENDING_USER_ACTION__"; const PENDING_USER_ACTION_MARKER: &str = "__PICOBOT_PENDING_USER_ACTION__";
/// 检测到 stdin 等待后,给 read_stream 任务将最后的提示内容传入 channel 的时间
const STDIN_FLUSH_MS: u64 = 500;
const INTERACTIVE_HINT: &str = const INTERACTIVE_HINT: &str =
"进程正在等待输入。请使用 session_id 和 stdin_input 参数回复交互内容。"; "进程正在等待输入。请使用 session_id 和 stdin_input 参数回复交互内容。";
const NON_INTERACTIVE_HINT: &str = const NON_INTERACTIVE_HINT: &str =
@ -182,12 +184,17 @@ impl BashTool {
let session_line = session_id let session_line = session_id
.map(|id| format!("[session_id: {}]\n", id)) .map(|id| format!("[session_id: {}]\n", id))
.unwrap_or_default(); .unwrap_or_default();
let output_section = if output.trim().is_empty() {
"(进程尚未输出内容。进程正在等待输入,请使用 session_id 和 stdin_input 参数发送输入内容。)"
} else {
&self.truncate_output(output.trim())
};
format!( format!(
"{}\n{}{}\n\n{}", "{}\n{}{}\n\n{}",
PENDING_USER_ACTION_MARKER, PENDING_USER_ACTION_MARKER,
session_line, session_line,
hint, hint,
self.truncate_output(output.trim()) output_section
) )
} }
@ -472,25 +479,26 @@ impl BashTool {
// Periodic safety net: check OS-level process state // Periodic safety net: check OS-level process state
if let Some(pid) = child.id() { if let Some(pid) = child.id() {
if crate::platform::is_process_waiting_on_stdin(pid) == Some(true) { 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() { if let Some(rx_ref) = rx.as_mut() {
drain_available_chunks(rx_ref, &stdout_buf, &stderr_buf).await; drain_available_chunks(rx_ref, &stdout_buf, &stderr_buf).await;
} }
let combined = format_command_output(&stdout_buf.lock().await, &stderr_buf.lock().await, None); let combined = format_command_output(&stdout_buf.lock().await, &stderr_buf.lock().await, None);
if !combined.trim().is_empty() { // 始终创建 session即使输出为空进程可能还没写出提示
if let Some(stdin) = child_stdin { if let Some(stdin) = child_stdin {
if let Some(rx_val) = rx.take() { if let Some(rx_val) = rx.take() {
let session_id = self.session_manager.save_session( let session_id = self.session_manager.save_session(
child, stdin, rx_val, child, stdin, rx_val,
stdout_buf.lock().await.clone(), stdout_buf.lock().await.clone(),
stderr_buf.lock().await.clone(), stderr_buf.lock().await.clone(),
).await; ).await;
return Ok(self.pending_output(&combined, Some(&session_id))); 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 // OS-level check: if blocked on stdin, save as session
if let Some(pid) = child.id() { if let Some(pid) = child.id() {
if crate::platform::is_process_waiting_on_stdin(pid) == Some(true) if crate::platform::is_process_waiting_on_stdin(pid) == Some(true) {
&& !combined.trim().is_empty() // 给 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(stdin) = child_stdin {
if let Some(rx_val) = rx.take() { if let Some(rx_val) = rx.take() {
let session_id = self.session_manager.save_session( let session_id = self.session_manager.save_session(