hive-agent-mcp: remove the ask/answer MCP tools
This commit is contained in:
parent
c661d699ad
commit
c10e551b2a
2 changed files with 5 additions and 151 deletions
|
|
@ -157,47 +157,6 @@ pub struct UpdateArgs {
|
|||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct AskArgs {
|
||||
/// The question to surface.
|
||||
pub question: String,
|
||||
/// Optional fixed-choice answers. The dashboard renders these as
|
||||
/// chips alongside a free-text fallback ("Other…") so the operator
|
||||
/// is never trapped by an incomplete list; peer-agent recipients
|
||||
/// see the list in their inbox event and can return any string.
|
||||
#[serde(default)]
|
||||
pub options: Vec<String>,
|
||||
/// When true, options are rendered as checkboxes — the answerer
|
||||
/// can pick any subset. The answer comes back as a single string
|
||||
/// with selections joined by ", ". Ignored when `options` is empty.
|
||||
#[serde(default)]
|
||||
pub multi: bool,
|
||||
/// Optional auto-cancel after `ttl_seconds` (capped server-side at
|
||||
/// 6 hours). On expiry the question resolves with answer
|
||||
/// `[expired]` and the asker receives the usual
|
||||
/// `question_answered` system event (with `answerer:
|
||||
/// "ttl-watchdog"`). `None` (default) = wait indefinitely.
|
||||
#[serde(default)]
|
||||
pub ttl_seconds: Option<u64>,
|
||||
/// Recipient. Omit (or pass `"operator"`) to ask the human
|
||||
/// operator via the dashboard. Pass another agent's logical name
|
||||
/// to ask that peer — they receive a `question_asked` event in
|
||||
/// their inbox and answer via `mcp__hyperhive__answer`.
|
||||
#[serde(default)]
|
||||
pub to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct AnswerArgs {
|
||||
/// Id of the question being answered — comes from the
|
||||
/// `question_asked` event in your inbox.
|
||||
pub id: i64,
|
||||
/// Free-text answer body. Soft-capped at 4 KiB by the same
|
||||
/// `MESSAGE_MAX_BYTES` limit as `send`; keep it short or write the
|
||||
/// detail to a file and pass a path.
|
||||
pub answer: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct CancelLooseEndArgs {
|
||||
/// Which kind of thread to cancel — `"question"` for an open
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ mod args;
|
|||
mod render;
|
||||
|
||||
pub use args::{
|
||||
AckUntilArgs, AgentGetLooseEndsArgs, AnswerArgs, AskArgs, CancelLooseEndArgs,
|
||||
CancelScheduleArgs, CompactArgs, CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs,
|
||||
GetAgentMetaArgs, GetHostJournalArgs, GetLogsArgs, KillArgs, MarkTodosDoneArgs, RecvArgs,
|
||||
RemindArgs, RequestInitConfigArgs, RequestSchedulePromptArgs, RestartArgs, SendArgs,
|
||||
SetStatusArgs, StartArgs, UpdateArgs, UpdateMetaInputsArgs,
|
||||
AckUntilArgs, AgentGetLooseEndsArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs,
|
||||
CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs,
|
||||
GetLogsArgs, KillArgs, MarkTodosDoneArgs, RecvArgs, RemindArgs, RequestInitConfigArgs,
|
||||
RequestSchedulePromptArgs, RestartArgs, SendArgs, SetStatusArgs, StartArgs, UpdateArgs,
|
||||
UpdateMetaInputsArgs,
|
||||
};
|
||||
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
|
||||
|
||||
|
|
@ -169,111 +169,6 @@ impl AgentServer {
|
|||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Surface a structured question to either the operator OR a peer agent. \
|
||||
Returns immediately with a question id — do NOT wait inline. When the recipient \
|
||||
answers, a system message with event `question_answered { id, question, answer, \
|
||||
answerer }` lands in your inbox; handle it on a future turn. \n\n\
|
||||
Recipient: omit `to` (or set `to: \"operator\"`) for the human operator on the \
|
||||
dashboard. Set `to: \"<agent-name>\"` to ask a peer agent — they receive a \
|
||||
`question_asked { id, asker, question, options, multi }` event in their inbox \
|
||||
and answer via `mcp__hyperhive__answer`. \n\n\
|
||||
`options` is advisory: pass a short fixed-choice list when applicable, otherwise \
|
||||
leave empty for free text. Set `multi: true` to let the answerer pick multiple \
|
||||
options (checkboxes on the dashboard, hint to the agent otherwise) — answer comes \
|
||||
back as a comma-separated string. Set `ttl_seconds` to auto-cancel a \
|
||||
no-longer-relevant question — on expiry the answer is `[expired]` (with \
|
||||
`answerer: \"ttl-watchdog\"`) and the same `question_answered` event fires."
|
||||
)]
|
||||
async fn ask(&self, Parameters(args): Parameters<AskArgs>) -> String {
|
||||
let log = format!("{args:?}");
|
||||
run_tool_envelope("ask", log, async move {
|
||||
let to = match args.to.map(|t| hive_types::Ident::parse(&t)).transpose() {
|
||||
Ok(to) => to,
|
||||
Err(reason) => return format!("invalid `to` agent name: {reason}"),
|
||||
};
|
||||
let question = args.question;
|
||||
let target = to
|
||||
.as_ref()
|
||||
.map_or_else(|| "operator".to_owned(), std::string::ToString::to_string);
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_core_agent_sock::Request::Ask {
|
||||
question: question.clone(),
|
||||
options: args.options,
|
||||
multi: args.multi,
|
||||
ttl_seconds: args.ttl_seconds,
|
||||
to,
|
||||
})
|
||||
.await;
|
||||
let s = match resp {
|
||||
Ok(hive_core_agent_sock::Response::QuestionQueued { id }) => {
|
||||
// Best-effort local questions-mirror record — a dial
|
||||
// failure just means `get_loose_ends` won't show this
|
||||
// row locally; the actual question is already queued
|
||||
// in c0re regardless.
|
||||
let _ = dial_agent_socket(&hive_agent_sock::Request::RecordAskedQuestion {
|
||||
id,
|
||||
target,
|
||||
question,
|
||||
})
|
||||
.await;
|
||||
format!(
|
||||
"question queued (id={id}); answer will arrive as a system \
|
||||
`question_answered` event in your inbox"
|
||||
)
|
||||
}
|
||||
other => reply_err(other, "ask"),
|
||||
};
|
||||
annotate_retries(s, retries)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Answer a question that was routed to YOU via a `question_asked` system \
|
||||
event in your inbox. Pass the `id` from that event and your `answer` string. The \
|
||||
answer will surface in the asker's inbox as a `question_answered { id, question, \
|
||||
answer, answerer: <your-name> }` event. \n\n\
|
||||
Authorisation is strict — you can only answer questions where you are the declared \
|
||||
target (i.e. the asker did `ask(to: \"<your-name>\", ...)`). Trying to answer an \
|
||||
operator-targeted question or a question addressed to a different agent will fail."
|
||||
)]
|
||||
async fn answer(&self, Parameters(args): Parameters<AnswerArgs>) -> String {
|
||||
let log = format!("{args:?}");
|
||||
let id = args.id;
|
||||
run_tool_envelope("answer", log, async move {
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_core_agent_sock::Request::Answer {
|
||||
id,
|
||||
answer: args.answer,
|
||||
})
|
||||
.await;
|
||||
// Clear the local `answering` mirror row whenever this agent is
|
||||
// done owing a reply for `id`: either a genuine success, or
|
||||
// c0re telling us the question already resolved without us
|
||||
// (the asker cancelled/answered it first, surfacing as an
|
||||
// "already answered"/"not found" rejection) — in both cases
|
||||
// nothing is still owed, so the row would otherwise linger
|
||||
// stale. Any other rejection (e.g. wrong answerer) means the
|
||||
// question is still genuinely outstanding, so leave it be.
|
||||
let should_clear = match &resp {
|
||||
Ok(hive_core_agent_sock::Response::Ok) => true,
|
||||
Ok(hive_core_agent_sock::Response::Err { message }) => {
|
||||
message.contains("not found") || message.contains("already answered")
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if should_clear {
|
||||
let _ = dial_agent_socket(&hive_agent_sock::Request::ClearQuestion { id }).await;
|
||||
}
|
||||
annotate_retries(
|
||||
format_ack(resp, "answer", format!("answered question {id}")),
|
||||
retries,
|
||||
)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Pop messages from this agent's inbox. Returns one or more messages, or \
|
||||
an empty marker if nothing is waiting. Always an immediate 'anything pending?' peek \
|
||||
|
|
|
|||
Loading…
Reference in a new issue