Watch
0
0
Fork
You've already forked hyperhive
0

agent mcp: bound the in-agent socket round trip so a stalled harness cannot hang a turn

This commit is contained in:
damocles 2026-10-21 21:50:57 +02:00 • committed by mara
commit 70d1b542e3

View file

@ -343,19 +343,38 @@ pub(super) async fn matrix_unread_summary() -> Option<Vec<MatrixRoomUnread>> {
pub(super) async fn dial_agent_socket(
req: &hive_agent_sock::Request,
) -> Option<hive_agent_sock::Response> {
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
let socket = std::env::var_os("HIVE_AGENT_SOCKET").map(std::path::PathBuf::from)?;
if !socket.exists() {
return None;
}
let mut stream = UnixStream::connect(&socket).await.ok()?;
let mut line = serde_json::to_string(req).ok()?;
line.push('\n');
stream.write_all(line.as_bytes()).await.ok()?;
let mut lines = BufReader::new(stream).lines();
let resp_line = lines.next_line().await.ok()??;
serde_json::from_str(&resp_line).ok()
dial_socket(&socket, req, AGENT_SOCKET_DEADLINE).await
}
/// Longest a tool call waits on the in-agent socket, connect through reply.
/// Matches the server's own per-connection request-line deadline: a harness
/// that accepts and then stalls must not hang the model's turn.
const AGENT_SOCKET_DEADLINE: std::time::Duration = std::time::Duration::from_secs(30);
/// One round trip on the socket at `socket`, abandoned (as `None`) after
/// `deadline`.
async fn dial_socket(
socket: &std::path::Path,
req: &hive_agent_sock::Request,
deadline: std::time::Duration,
) -> Option<hive_agent_sock::Response> {
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
tokio::time::timeout(deadline, async {
let mut stream = UnixStream::connect(socket).await.ok()?;
let mut line = serde_json::to_string(req).ok()?;
line.push('\n');
stream.write_all(line.as_bytes()).await.ok()?;
let mut lines = BufReader::new(stream).lines();
let resp_line = lines.next_line().await.ok()??;
serde_json::from_str(&resp_line).ok()
})
.await
.ok()?
}
/// Query the harness's in-agent socket for this agent's local todos
@ -540,7 +559,36 @@ pub fn annotate_retries(mut s: String, retries: u32) -> String {
#[cfg(test)]
mod tests {
use super::{format_ack, format_recv};
use super::{dial_socket, format_ack, format_recv};
#[tokio::test]
async fn a_socket_that_accepts_and_never_answers_is_given_up_on() {
let path = std::env::temp_dir().join(format!(
"hive-agent-mcp-dial-test-{}-{}.sock",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let listener = tokio::net::UnixListener::bind(&path).unwrap();
// Accept, then hold the connection open without ever replying.
let _stalled = tokio::spawn(async move {
let conn = listener.accept().await;
std::future::pending::<()>().await;
drop(conn);
});
let resp = dial_socket(
&path,
&hive_agent_sock::Request::ListReminders,
std::time::Duration::from_millis(100),
)
.await;
let _ = std::fs::remove_file(&path);
assert!(resp.is_none());
}
fn msg(id: i64, from: &str, body: &str) -> hive_sh4re::inbox::DeliveredMessage {
hive_sh4re::inbox::DeliveredMessage {