92 lines
3.4 KiB
Rust
92 lines
3.4 KiB
Rust
//! Helpers shared between `hive-ag3nt` (agent) and `hive-m1nd` (manager)
|
|
//! serve loops. Only pure functions with no wire-type dependency live here;
|
|
//! request/response-flavored helpers (`requeue_inflight`, `ack_turn`, etc.)
|
|
//! stay in each binary because they use different request enum variants.
|
|
|
|
use crate::events::Bus;
|
|
use crate::mcp::REDELIVERY_HINT;
|
|
use crate::turn::TurnOutcome;
|
|
use crate::turn_stats::TurnStatRow;
|
|
|
|
/// Assemble the per-turn wake prompt string. The role/tools/etc. live in the
|
|
/// system prompt; this is just the wake signal body. `unread` is the inbox
|
|
/// depth after this message was popped. `redelivered` prepends a "may already
|
|
/// be handled" banner.
|
|
#[must_use]
|
|
pub fn format_wake_prompt(from: &str, body: &str, unread: u64, redelivered: bool) -> String {
|
|
let banner = if redelivered { REDELIVERY_HINT } else { "" };
|
|
let pending = if unread == 0 {
|
|
String::new()
|
|
} else {
|
|
format!(
|
|
"\n\n({unread} more message(s) pending in your inbox — call `mcp__hyperhive__recv` \
|
|
with `max: {unread}` to drain them all in one round-trip before acting.)"
|
|
)
|
|
};
|
|
format!("{banner}Incoming message from `{from}`:\n---\n{body}\n---{pending}")
|
|
}
|
|
|
|
/// Current time as a Unix timestamp (seconds). Returns 0 on any error.
|
|
#[must_use]
|
|
pub fn now_unix() -> i64 {
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.ok()
|
|
.and_then(|d| i64::try_from(d.as_secs()).ok())
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
/// Assemble a `TurnStatRow` from the harness's per-turn state. Used by both
|
|
/// the agent and manager serve loops — the shape is identical, only the
|
|
/// post-turn count fetch helpers differ (and those stay in each binary).
|
|
#[must_use]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_row(
|
|
started_at: i64,
|
|
ended_at: i64,
|
|
duration_ms: i64,
|
|
model: String,
|
|
wake_from: String,
|
|
outcome: &TurnOutcome,
|
|
bus: &Bus,
|
|
open_threads_count: Option<u64>,
|
|
open_reminders_count: Option<u64>,
|
|
) -> TurnStatRow {
|
|
let cost = bus.last_cost_usage().unwrap_or_default();
|
|
let ctx = bus.last_ctx_usage().unwrap_or(cost);
|
|
let tool_calls = bus.take_tool_calls();
|
|
let tool_call_count: u64 = tool_calls.values().copied().sum();
|
|
let tool_call_breakdown_json = if tool_calls.is_empty() {
|
|
None
|
|
} else {
|
|
serde_json::to_string(&tool_calls).ok()
|
|
};
|
|
let (result_kind, note) = match outcome {
|
|
TurnOutcome::Ok => ("ok", None),
|
|
TurnOutcome::Compacted => ("compacted", None),
|
|
TurnOutcome::PromptTooLong => ("prompt_too_long", None),
|
|
TurnOutcome::RateLimited => ("rate_limited", None),
|
|
TurnOutcome::Failed(e) => ("failed", Some(format!("{e:#}"))),
|
|
};
|
|
TurnStatRow {
|
|
started_at,
|
|
ended_at,
|
|
duration_ms,
|
|
model,
|
|
wake_from,
|
|
input_tokens: cost.input_tokens,
|
|
output_tokens: cost.output_tokens,
|
|
cache_read_input_tokens: cost.cache_read_input_tokens,
|
|
cache_creation_input_tokens: cost.cache_creation_input_tokens,
|
|
last_input_tokens: ctx.input_tokens,
|
|
last_output_tokens: ctx.output_tokens,
|
|
last_cache_read_input_tokens: ctx.cache_read_input_tokens,
|
|
last_cache_creation_input_tokens: ctx.cache_creation_input_tokens,
|
|
tool_call_count,
|
|
tool_call_breakdown_json,
|
|
open_threads_count,
|
|
open_reminders_count,
|
|
result_kind,
|
|
note,
|
|
}
|
|
}
|