cancel_thread: new mcp tool — unify reminder + question cancel on both surfaces

This commit is contained in:
damocles 2026-05-18 18:07:44 +02:00
parent fcd407da11
commit b1d0a62cb9
11 changed files with 331 additions and 25 deletions

View file

@ -196,6 +196,54 @@ impl OperatorQuestions {
Ok((question, asker, target))
}
/// Cancel a pending question on behalf of `canceller`. Returns
/// `(question, asker, target)` so the caller can fire the usual
/// `QuestionAnswered` event to the asker with a `[cancelled by
/// <canceller>]` sentinel.
///
/// Auth: the canceller must be one of:
/// - the original asker (an agent withdrawing their own ask),
/// - the operator (already covered by the existing `answer` path
/// but allowed here too for symmetry / dashboard cancel),
/// - the manager (privileged hive-wide cleanup).
///
/// Not the target — that's covered by `answer` (responding with
/// an actual reply, sentinel or otherwise).
pub fn cancel(
&self,
id: i64,
canceller: &str,
) -> Result<(String, String, Option<String>)> {
let conn = self.conn.lock().unwrap();
let row: Option<(String, String, Option<String>, Option<i64>)> = conn
.query_row(
"SELECT question, asker, target, answered_at FROM operator_questions WHERE id = ?1",
params![id],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.optional()?;
let Some((question, asker, target, answered_at)) = row else {
bail!("question {id} not found");
};
if answered_at.is_some() {
bail!("question {id} already answered/cancelled");
}
let authorised = canceller == asker
|| canceller == hive_sh4re::OPERATOR_RECIPIENT
|| canceller == hive_sh4re::MANAGER_AGENT;
if !authorised {
bail!(
"question {id}: '{canceller}' not allowed to cancel (asker = '{asker}')"
);
}
let sentinel = format!("[cancelled by {canceller}]");
conn.execute(
"UPDATE operator_questions SET answer = ?1, answered_at = ?2 WHERE id = ?3",
params![sentinel, now_unix(), id],
)?;
Ok((question, asker, target))
}
#[allow(dead_code)]
pub fn get(&self, id: i64) -> Result<Option<OpQuestion>> {
let conn = self.conn.lock().unwrap();