rename: open_threads → loose_ends + cancel_thread → cancel_loose_end across wire / tools / web ui

This commit is contained in:
damocles 2026-05-18 18:22:49 +02:00
parent b1d0a62cb9
commit 6e23d087d2
16 changed files with 152 additions and 139 deletions

View file

@ -40,7 +40,7 @@ pub enum SocketReply {
QuestionQueued(i64),
Recent(Vec<hive_sh4re::InboxRow>),
Logs(String),
OpenThreads(Vec<hive_sh4re::OpenThread>),
LooseEnds(Vec<hive_sh4re::LooseEnd>),
PendingRemindersCount(u64),
Whoami {
name: String,
@ -59,7 +59,7 @@ impl From<hive_sh4re::AgentResponse> for SocketReply {
hive_sh4re::AgentResponse::Status { unread } => Self::Status(unread),
hive_sh4re::AgentResponse::Recent { rows } => Self::Recent(rows),
hive_sh4re::AgentResponse::QuestionQueued { id } => Self::QuestionQueued(id),
hive_sh4re::AgentResponse::OpenThreads { threads } => Self::OpenThreads(threads),
hive_sh4re::AgentResponse::LooseEnds { loose_ends } => Self::LooseEnds(loose_ends),
hive_sh4re::AgentResponse::PendingRemindersCount { count } => {
Self::PendingRemindersCount(count)
}
@ -87,7 +87,7 @@ impl From<hive_sh4re::ManagerResponse> for SocketReply {
hive_sh4re::ManagerResponse::QuestionQueued { id } => Self::QuestionQueued(id),
hive_sh4re::ManagerResponse::Recent { rows } => Self::Recent(rows),
hive_sh4re::ManagerResponse::Logs { content } => Self::Logs(content),
hive_sh4re::ManagerResponse::OpenThreads { threads } => Self::OpenThreads(threads),
hive_sh4re::ManagerResponse::LooseEnds { loose_ends } => Self::LooseEnds(loose_ends),
hive_sh4re::ManagerResponse::PendingRemindersCount { count } => {
Self::PendingRemindersCount(count)
}
@ -128,16 +128,16 @@ pub fn format_recv(resp: Result<SocketReply, anyhow::Error>) -> String {
}
}
/// Format helper for `get_open_threads`: renders a short bulleted list
/// Format helper for `get_loose_ends`: renders a short bulleted list
/// of pending approvals + questions. Empty list collapses to a clear
/// marker so claude doesn't go hunting for a payload that isn't there.
pub fn format_open_threads(resp: Result<SocketReply, anyhow::Error>) -> String {
pub fn format_loose_ends(resp: Result<SocketReply, anyhow::Error>) -> String {
use std::fmt::Write as _;
let threads = match resp {
Ok(SocketReply::OpenThreads(t)) => t,
Ok(SocketReply::Err(m)) => return format!("get_open_threads failed: {m}"),
Ok(other) => return format!("get_open_threads unexpected response: {other:?}"),
Err(e) => return format!("get_open_threads transport error: {e:#}"),
Ok(SocketReply::LooseEnds(t)) => t,
Ok(SocketReply::Err(m)) => return format!("get_loose_ends failed: {m}"),
Ok(other) => return format!("get_loose_ends unexpected response: {other:?}"),
Err(e) => return format!("get_loose_ends transport error: {e:#}"),
};
if threads.is_empty() {
return "(no open threads)".to_owned();
@ -145,7 +145,7 @@ pub fn format_open_threads(resp: Result<SocketReply, anyhow::Error>) -> String {
let mut out = format!("{} open thread(s):\n", threads.len());
for t in &threads {
match t {
hive_sh4re::OpenThread::Approval {
hive_sh4re::LooseEnd::Approval {
id,
agent,
commit_ref,
@ -161,7 +161,7 @@ pub fn format_open_threads(resp: Result<SocketReply, anyhow::Error>) -> String {
"- approval #{id} ({agent} @ {commit_ref}, {age_seconds}s old){desc}"
);
}
hive_sh4re::OpenThread::Question {
hive_sh4re::LooseEnd::Question {
id,
asker,
target,
@ -174,7 +174,7 @@ pub fn format_open_threads(resp: Result<SocketReply, anyhow::Error>) -> String {
"- question #{id} ({asker} → {to}, {age_seconds}s old): {question}"
);
}
hive_sh4re::OpenThread::Reminder {
hive_sh4re::LooseEnd::Reminder {
id,
owner,
message,
@ -191,16 +191,16 @@ pub fn format_open_threads(resp: Result<SocketReply, anyhow::Error>) -> String {
out
}
/// Parse the user-facing `kind` string for `cancel_thread` into the
/// Parse the user-facing `kind` string for `cancel_loose_end` into the
/// wire enum. Accepts a small alias set so claude doesn't have to
/// remember the exact spelling (`"q"` / `"r"` shorthand falls out
/// for free).
fn parse_cancel_kind(raw: &str) -> Result<hive_sh4re::CancelThreadKind, String> {
fn parse_loose_end_kind(raw: &str) -> Result<hive_sh4re::CancelLooseEndKind, String> {
match raw.trim().to_ascii_lowercase().as_str() {
"question" | "q" => Ok(hive_sh4re::CancelThreadKind::Question),
"reminder" | "r" => Ok(hive_sh4re::CancelThreadKind::Reminder),
"question" | "q" => Ok(hive_sh4re::CancelLooseEndKind::Question),
"reminder" | "r" => Ok(hive_sh4re::CancelLooseEndKind::Reminder),
other => Err(format!(
"cancel_thread: unknown kind '{other}' (expected \"question\" or \"reminder\")"
"cancel_loose_end: unknown kind '{other}' (expected \"question\" or \"reminder\")"
)),
}
}
@ -454,13 +454,13 @@ impl AgentServer {
sweep, no args. Useful at turn start to remember what you owe / what's owed to \
you without scrolling inbox history. Output is a short bulleted list with ids, \
ages in seconds, and the relevant context. Each `question` or `reminder` row \
can be cancelled by passing its id + kind to `cancel_thread`. Empty result \
can be cancelled by passing its id + kind to `cancel_loose_end`. Empty result \
is reported clearly."
)]
async fn get_open_threads(&self) -> String {
run_tool_envelope("get_open_threads", String::new(), async move {
let (resp, retries) = self.dispatch(hive_sh4re::AgentRequest::GetOpenThreads).await;
annotate_retries(format_open_threads(resp), retries)
async fn get_loose_ends(&self) -> String {
run_tool_envelope("get_loose_ends", String::new(), async move {
let (resp, retries) = self.dispatch(hive_sh4re::AgentRequest::GetLooseEnds).await;
annotate_retries(format_loose_ends(resp), retries)
})
.await
}
@ -485,24 +485,24 @@ impl AgentServer {
description = "Cancel an open thread you own — a `question` you asked (the \
asker gets `[cancelled by <you>]` as the answer and unblocks) or a `reminder` \
you scheduled (hard-deleted before it fires). `kind` is `\"question\"` or \
`\"reminder\"`; `id` is the row id from the matching `get_open_threads` entry \
`\"reminder\"`; `id` is the row id from the matching `get_loose_ends` entry \
or the `question_queued` reply you got when you submitted. Auth: you can only \
cancel rows where you're the asker / owner. Returns `ok` or an error string."
)]
async fn cancel_thread(&self, Parameters(args): Parameters<CancelThreadArgs>) -> String {
async fn cancel_loose_end(&self, Parameters(args): Parameters<CancelLooseEndArgs>) -> String {
let log = format!("{args:?}");
let kind_label = args.kind.clone();
let id = args.id;
run_tool_envelope("cancel_thread", log, async move {
let kind = match parse_cancel_kind(&args.kind) {
run_tool_envelope("cancel_loose_end", log, async move {
let kind = match parse_loose_end_kind(&args.kind) {
Ok(k) => k,
Err(e) => return e,
};
let (resp, retries) = self
.dispatch(hive_sh4re::AgentRequest::CancelThread { kind, id })
.dispatch(hive_sh4re::AgentRequest::CancelLooseEnd { kind, id })
.await;
annotate_retries(
format_ack(resp, "cancel_thread", format!("cancelled {kind_label} {id}")),
format_ack(resp, "cancel_loose_end", format!("cancelled {kind_label} {id}")),
retries,
)
})
@ -654,13 +654,13 @@ pub struct AnswerArgs {
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CancelThreadArgs {
pub struct CancelLooseEndArgs {
/// Which kind of thread to cancel — `"question"` for an open
/// `ask` that's still waiting on an answer, `"reminder"` for a
/// scheduled `remind` that hasn't fired yet. Use the `kind`
/// field straight off the `get_open_threads` row.
/// field straight off the `get_loose_ends` row.
pub kind: String,
/// Row id from the matching `get_open_threads` entry (or the
/// Row id from the matching `get_loose_ends` entry (or the
/// `question_queued` reply when you submitted it).
pub id: i64,
}
@ -995,14 +995,14 @@ impl ManagerServer {
approvals stuck waiting on the operator, reminders piling up on an offline \
agent, etc. No args. The sub-agent flavour only returns the agent's own \
threads; the manager flavour is unfiltered. Cancel any question or reminder \
row via `cancel_thread` (manager bypasses the owner check)."
row via `cancel_loose_end` (manager bypasses the owner check)."
)]
async fn get_open_threads(&self) -> String {
run_tool_envelope("get_open_threads", String::new(), async move {
async fn get_loose_ends(&self) -> String {
run_tool_envelope("get_loose_ends", String::new(), async move {
let (resp, retries) = self
.dispatch(hive_sh4re::ManagerRequest::GetOpenThreads)
.dispatch(hive_sh4re::ManagerRequest::GetLooseEnds)
.await;
annotate_retries(format_open_threads(resp), retries)
annotate_retries(format_loose_ends(resp), retries)
})
.await
}
@ -1025,24 +1025,24 @@ impl ManagerServer {
description = "Cancel any open thread in the swarm — a `question` (cancels \
with the operator-override sentinel so the asker unblocks) or a `reminder` \
(hard-deleted before fire). `kind` is `\"question\"` or `\"reminder\"`; `id` \
is the row id from `get_open_threads` or the original submission reply. \
is the row id from `get_loose_ends` or the original submission reply. \
Manager surface bypasses the owner check on the sub-agent flavour use for \
hive-wide cleanup of stuck or stale threads."
)]
async fn cancel_thread(&self, Parameters(args): Parameters<CancelThreadArgs>) -> String {
async fn cancel_loose_end(&self, Parameters(args): Parameters<CancelLooseEndArgs>) -> String {
let log = format!("{args:?}");
let kind_label = args.kind.clone();
let id = args.id;
run_tool_envelope("cancel_thread", log, async move {
let kind = match parse_cancel_kind(&args.kind) {
run_tool_envelope("cancel_loose_end", log, async move {
let kind = match parse_loose_end_kind(&args.kind) {
Ok(k) => k,
Err(e) => return e,
};
let (resp, retries) = self
.dispatch(hive_sh4re::ManagerRequest::CancelThread { kind, id })
.dispatch(hive_sh4re::ManagerRequest::CancelLooseEnd { kind, id })
.await;
annotate_retries(
format_ack(resp, "cancel_thread", format!("cancelled {kind_label} {id}")),
format_ack(resp, "cancel_loose_end", format!("cancelled {kind_label} {id}")),
retries,
)
})
@ -1092,8 +1092,8 @@ impl ManagerServer {
any agent including yourself), `ask` (structured question to the operator or a \
sub-agent non-blocking, answer arrives later as a `question_answered` event), \
`answer` (respond to a `question_asked` event directed at you), \
`get_open_threads` (hive-wide loose ends pending approvals + unanswered \
questions + pending reminders across the swarm), `cancel_thread` (cancel any \
`get_loose_ends` (hive-wide loose ends pending approvals + unanswered \
questions + pending reminders across the swarm), `cancel_loose_end` (cancel any \
question or reminder row by id), `whoami` (self-introspection canonical \
name, role, current hyperhive rev). The manager's own config lives at \
`/agents/hm1nd/config/agent.nix`."
@ -1136,9 +1136,9 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
"ask",
"answer",
"remind",
"get_open_threads",
"get_loose_ends",
"whoami",
"cancel_thread",
"cancel_loose_end",
],
Flavor::Manager => &[
"send",
@ -1152,10 +1152,10 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
"ask",
"answer",
"get_logs",
"get_open_threads",
"get_loose_ends",
"remind",
"whoami",
"cancel_thread",
"cancel_loose_end",
],
};
let mut out: Vec<String> = names