From a2d44c7eb636d10a6351d76395911b15e4b6fe57 Mon Sep 17 00:00:00 2001 From: damocles Date: Mon, 20 Jul 2026 22:53:55 +0200 Subject: [PATCH] feat(#2569): merge harness-local todos into get_loose_ends --- Cargo.lock | 1 + hive-agent-mcp/Cargo.toml | 1 + hive-agent-mcp/src/mcp/mod.rs | 10 ++++++++-- hive-agent-mcp/src/mcp/render.rs | 24 ++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 540bfb11..0d5b1376 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1553,6 +1553,7 @@ dependencies = [ "anyhow", "axum", "clap", + "hive-agent-sock", "hive-core-agent-sock", "hive-sh4re", "rmcp", diff --git a/hive-agent-mcp/Cargo.toml b/hive-agent-mcp/Cargo.toml index 6fad2eb9..33576ddf 100644 --- a/hive-agent-mcp/Cargo.toml +++ b/hive-agent-mcp/Cargo.toml @@ -14,6 +14,7 @@ workspace = true anyhow.workspace = true axum.workspace = true clap.workspace = true +hive-agent-sock.workspace = true hive-core-agent-sock.workspace = true hive-sh4re.workspace = true rmcp.workspace = true diff --git a/hive-agent-mcp/src/mcp/mod.rs b/hive-agent-mcp/src/mcp/mod.rs index 5ac7c35d..8acc2c6e 100644 --- a/hive-agent-mcp/src/mcp/mod.rs +++ b/hive-agent-mcp/src/mcp/mod.rs @@ -35,8 +35,8 @@ pub use args::{ pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv}; use render::{ - format_matrix_summary, loose_end_kind_label, matrix_unread_summary, parse_loose_end_kind, - render_loose_ends, reply_err, + format_matrix_summary, local_todos, loose_end_kind_label, matrix_unread_summary, + parse_loose_end_kind, render_loose_ends, reply_err, }; /// Write (or remove) the status file in the agent's own `state/` directory. @@ -333,6 +333,12 @@ impl AgentServer { ); } } + // Merge the harness's local todos (loose-ends v2) for self-queries. + // The harness owns the todo store in-container; another agent's + // todos aren't reachable from here (same as matrix above). + if is_self_query && let Some(todos) = local_todos().await { + loose_ends.extend(todos); + } let mut out = annotate_retries(render_loose_ends(&loose_ends), retries); // Append loose-end items published by external MCP daemons // (e.g. active bash tasks from hive-bash-mcp). Generic — no diff --git a/hive-agent-mcp/src/mcp/render.rs b/hive-agent-mcp/src/mcp/render.rs index dddc9eca..858e75ba 100644 --- a/hive-agent-mcp/src/mcp/render.rs +++ b/hive-agent-mcp/src/mcp/render.rs @@ -297,6 +297,30 @@ pub(super) async fn matrix_unread_summary() -> Option> { serde_json::from_value(serde_json::Value::Array(arr.clone())).ok() } +/// Query the harness's in-agent socket for this agent's local todos +/// (loose-ends v2). Returns `None` when `HIVE_AGENT_SOCKET` is unset / +/// absent or the query fails — best-effort, like [`matrix_unread_summary`], +/// so an agent without the socket is not penalised. +pub(super) async fn local_todos() -> Option> { + 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 req = + serde_json::to_string(&hive_agent_sock::Request::ListTodos { subsystem: None }).ok()?; + req.push('\n'); + stream.write_all(req.as_bytes()).await.ok()?; + let mut lines = BufReader::new(stream).lines(); + let line = lines.next_line().await.ok()??; + match serde_json::from_str::(&line).ok()? { + hive_agent_sock::Response::LooseEnds { loose_ends } => Some(loose_ends), + _ => None, + } +} + /// Format a `Vec` into a per-room summary string. /// Single room / single message collapses to one line; multi-room /// expands to a bulleted list. Returns an empty string for empty input.