diff --git a/Cargo.lock b/Cargo.lock index 011688b6..28a1a7d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1376,6 +1376,7 @@ version = "0.1.0" dependencies = [ "anyhow", "futures-util", + "hive-sh4re", "matrix-sdk", "reqwest", "rmcp", diff --git a/hive-ag3nt/src/mcp_loose_ends.rs b/hive-ag3nt/src/mcp_loose_ends.rs index c61f4260..7b6f4aad 100644 --- a/hive-ag3nt/src/mcp_loose_ends.rs +++ b/hive-ag3nt/src/mcp_loose_ends.rs @@ -10,20 +10,10 @@ use std::path::PathBuf; -/// NOTE: the base-dir resolution logic here is intentionally mirrored in -/// `hive-bash-mcp/src/paths.rs::mcp_loose_ends_dir()`. They can't share -/// code across crates — keep them in sync if the fallback logic changes. +/// Resolution lives in `hive_sh4re::paths` so the harness + every MCP +/// daemon agree on where loose-end summary files are written. fn loose_ends_dir() -> PathBuf { - let base = if let Some(p) = std::env::var_os("HYPERHIVE_HARNESS_DIR") { - PathBuf::from(p) - } else { - let state = std::env::var("HYPERHIVE_STATE_DIR").unwrap_or_default(); - let state_path = PathBuf::from(&state); - state_path - .parent() - .map_or_else(|| PathBuf::from(state), |p| p.join("harness")) - }; - base.join("mcp-loose-ends") + hive_sh4re::paths::mcp_loose_ends_dir() } /// Collect all loose-end summary strings published by external MCP daemons. diff --git a/hive-bash-mcp/src/paths.rs b/hive-bash-mcp/src/paths.rs index 178838a1..90714e4b 100644 --- a/hive-bash-mcp/src/paths.rs +++ b/hive-bash-mcp/src/paths.rs @@ -20,22 +20,13 @@ pub fn daemon_socket() -> PathBuf { .map_or_else(|| PathBuf::from(DEFAULT_DAEMON_SOCKET), PathBuf::from) } -/// Base harness directory. Uses `HYPERHIVE_HARNESS_DIR` if set (injected -/// by the hive-c0re meta flake after the harness/state split); falls -/// back to a `harness/` sibling of the state dir for pre-split -/// deployments. Shared by every per-agent harness artifact path below. +/// Base harness directory. Shared resolution lives in +/// `hive_sh4re::paths::harness_dir` so the harness + every MCP daemon +/// agree on the layout. Re-exported here as the base for the per-agent +/// artifact paths below. #[must_use] pub fn harness_dir() -> PathBuf { - if let Some(p) = std::env::var_os("HYPERHIVE_HARNESS_DIR") { - PathBuf::from(p) - } else { - // Pre-split fallback: derive harness/ as a sibling of state/. - let state = std::env::var("HYPERHIVE_STATE_DIR").unwrap_or_default(); - let state_path = PathBuf::from(&state); - state_path - .parent() - .map_or_else(|| PathBuf::from(state), |p| p.join("harness")) - } + hive_sh4re::paths::harness_dir() } /// Base directory for task files. @@ -64,13 +55,9 @@ pub fn hyperhive_socket() -> PathBuf { /// Directory where MCP daemons write loose-end summary files for the harness. /// Each daemon writes `.json` here; the harness scans the dir in /// `get_loose_ends` to surface active work from all MCPs generically. -/// -/// NOTE: the base-dir resolution logic here is intentionally mirrored in -/// `hive-ag3nt/src/mcp_loose_ends.rs::loose_ends_dir()`. They can't share -/// code across crates — keep them in sync if the fallback logic changes. #[must_use] pub fn mcp_loose_ends_dir() -> PathBuf { - harness_dir().join("mcp-loose-ends") + hive_sh4re::paths::mcp_loose_ends_dir() } /// Full path for a task's JSON metadata file. diff --git a/hive-matrix-mcp/Cargo.toml b/hive-matrix-mcp/Cargo.toml index ef4cfeef..9f74495a 100644 --- a/hive-matrix-mcp/Cargo.toml +++ b/hive-matrix-mcp/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] anyhow.workspace = true futures-util.workspace = true +hive-sh4re.workspace = true matrix-sdk.workspace = true reqwest.workspace = true rmcp.workspace = true diff --git a/hive-matrix-mcp/src/paths.rs b/hive-matrix-mcp/src/paths.rs index 92918dd9..9993a776 100644 --- a/hive-matrix-mcp/src/paths.rs +++ b/hive-matrix-mcp/src/paths.rs @@ -70,20 +70,9 @@ pub fn hyperhive_socket() -> PathBuf { /// Directory where MCP daemons write loose-end summary files for the harness. /// Each daemon writes `.json` here; the harness scans the dir in /// `get_loose_ends` to surface active work from all MCPs generically. -/// -/// NOTE: the base-dir resolution logic here is intentionally mirrored in -/// `hive-ag3nt/src/mcp_loose_ends.rs::loose_ends_dir()`. They can't share -/// code across crates — keep them in sync if the fallback logic changes. +/// Resolution lives in `hive_sh4re::paths` so the harness + every MCP +/// daemon agree on the location. #[must_use] pub fn mcp_loose_ends_dir() -> PathBuf { - let base = if let Some(p) = std::env::var_os("HYPERHIVE_HARNESS_DIR") { - PathBuf::from(p) - } else { - let state = std::env::var("HYPERHIVE_STATE_DIR").unwrap_or_default(); - let state_path = PathBuf::from(&state); - state_path - .parent() - .map_or_else(|| PathBuf::from(state), |p| p.join("harness")) - }; - base.join("mcp-loose-ends") + hive_sh4re::paths::mcp_loose_ends_dir() } diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 191677bf..7308761e 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; pub mod assets; +pub mod paths; pub mod priv_proto; // ----------------------------------------------------------------------------- diff --git a/hive-sh4re/src/paths.rs b/hive-sh4re/src/paths.rs new file mode 100644 index 00000000..f19b3ef4 --- /dev/null +++ b/hive-sh4re/src/paths.rs @@ -0,0 +1,33 @@ +//! Shared in-container filesystem-path resolution. +//! +//! Every process that runs inside an agent container - the harness plus +//! the out-of-process MCP daemons (bash, matrix, ...) - must resolve the +//! harness directory layout identically. These helpers previously had to +//! be hand-mirrored across crates (each copy carrying a "keep in sync" +//! comment) because there was no shared home for them; they live here so +//! the resolution exists exactly once. + +use std::path::PathBuf; + +/// Base harness directory for the current agent. Uses `HYPERHIVE_HARNESS_DIR` +/// if set (injected by the hive-c0re meta flake after the harness/state +/// split); falls back to a `harness/` sibling of `HYPERHIVE_STATE_DIR` for +/// pre-split / dev deployments. +#[must_use] +pub fn harness_dir() -> PathBuf { + if let Some(p) = std::env::var_os("HYPERHIVE_HARNESS_DIR") { + return PathBuf::from(p); + } + let state = std::env::var("HYPERHIVE_STATE_DIR").unwrap_or_default(); + let state_path = PathBuf::from(&state); + state_path + .parent() + .map_or_else(|| PathBuf::from(state), |p| p.join("harness")) +} + +/// Directory where out-of-process MCP daemons write loose-end summary +/// files (`.json`) for the harness to scan in `get_loose_ends`. +#[must_use] +pub fn mcp_loose_ends_dir() -> PathBuf { + harness_dir().join("mcp-loose-ends") +}