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,
|
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)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct CancelLooseEndArgs {
|
pub struct CancelLooseEndArgs {
|
||||||
/// Which kind of thread to cancel — `"question"` for an open
|
/// Which kind of thread to cancel — `"question"` for an open
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ mod args;
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
pub use args::{
|
pub use args::{
|
||||||
AckUntilArgs, AgentGetLooseEndsArgs, AnswerArgs, AskArgs, CancelLooseEndArgs,
|
AckUntilArgs, AgentGetLooseEndsArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs,
|
||||||
CancelScheduleArgs, CompactArgs, CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs,
|
CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs,
|
||||||
GetAgentMetaArgs, GetHostJournalArgs, GetLogsArgs, KillArgs, MarkTodosDoneArgs, RecvArgs,
|
GetLogsArgs, KillArgs, MarkTodosDoneArgs, RecvArgs, RemindArgs, RequestInitConfigArgs,
|
||||||
RemindArgs, RequestInitConfigArgs, RequestSchedulePromptArgs, RestartArgs, SendArgs,
|
RequestSchedulePromptArgs, RestartArgs, SendArgs, SetStatusArgs, StartArgs, UpdateArgs,
|
||||||
SetStatusArgs, StartArgs, UpdateArgs, UpdateMetaInputsArgs,
|
UpdateMetaInputsArgs,
|
||||||
};
|
};
|
||||||
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
|
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
|
||||||
|
|
||||||
|
|
@ -169,111 +169,6 @@ impl AgentServer {
|
||||||
.await
|
.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(
|
#[tool(
|
||||||
description = "Pop messages from this agent's inbox. Returns one or more messages, or \
|
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 \
|
an empty marker if nothing is waiting. Always an immediate 'anything pending?' peek \
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue