mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
revert(agent): drop reply delivery MCP hook
Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -12,7 +12,6 @@ use std::sync::Arc;
|
||||
|
||||
mod paths;
|
||||
mod read_file;
|
||||
mod reply_delivery;
|
||||
mod rg;
|
||||
mod shell;
|
||||
mod shim;
|
||||
@@ -107,17 +106,7 @@ impl DevMcp {
|
||||
&self,
|
||||
Parameters(_): Parameters<todo::HookParams>,
|
||||
) -> Result<CallToolResult, ErrorData> {
|
||||
let objections = [
|
||||
self.todos.stop_objection(),
|
||||
self.state.reply_delivery.stop_objection(),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|objection| !objection.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
if objections.is_empty() {
|
||||
self.state.reply_delivery.reset_after_allowed_stop();
|
||||
}
|
||||
todo::text_result(objections.join("\n\n"))
|
||||
todo::text_result(self.todos.stop_objection())
|
||||
}
|
||||
|
||||
/// Hook: called by the agent after context compaction/handoff so the
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
const STOP_OBJECTION: &str = "You have not posted your reply to Buzz. Do not describe or promise the send. Call the `shell` MCP tool now and run `buzz messages send`. Set `--channel` to the actual channel UUID from `[Context]` and `--content` to your reply; never use literal placeholder text. Do not end the turn until that command succeeds.";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
enum DeliveryStatus {
|
||||
#[default]
|
||||
AwaitingSend,
|
||||
Sent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ReplyDeliveryState {
|
||||
status: Mutex<DeliveryStatus>,
|
||||
}
|
||||
|
||||
impl ReplyDeliveryState {
|
||||
pub fn record_shell_result(&self, command: &str, exit_code: i32, stdout: &str) {
|
||||
let is_send = is_buzz_message_send(command);
|
||||
let accepted = write_was_accepted(stdout);
|
||||
if exit_code != 0 || !is_send || !accepted {
|
||||
return;
|
||||
}
|
||||
self.with_status(|status| *status = DeliveryStatus::Sent);
|
||||
}
|
||||
|
||||
pub fn stop_objection(&self) -> String {
|
||||
self.with_status(|status| match status {
|
||||
DeliveryStatus::AwaitingSend => STOP_OBJECTION.to_owned(),
|
||||
DeliveryStatus::Sent => String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset_after_allowed_stop(&self) {
|
||||
self.with_status(|status| *status = DeliveryStatus::AwaitingSend);
|
||||
}
|
||||
|
||||
fn with_status<R>(&self, f: impl FnOnce(&mut DeliveryStatus) -> R) -> R {
|
||||
let mut guard = match self.status.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
f(&mut guard)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_buzz_message_send(command: &str) -> bool {
|
||||
let mut words = command.split_ascii_whitespace();
|
||||
let Some(executable) = words.next() else {
|
||||
return false;
|
||||
};
|
||||
let executable = executable.trim_matches(['\'', '"']);
|
||||
let is_buzz = Path::new(executable)
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.is_some_and(|name| matches!(name, "buzz" | "buzz.exe"));
|
||||
is_buzz && words.next() == Some("messages") && words.next() == Some("send")
|
||||
}
|
||||
|
||||
fn write_was_accepted(stdout: &str) -> bool {
|
||||
serde_json::from_str::<serde_json::Value>(stdout.trim())
|
||||
.ok()
|
||||
.and_then(|value| value.get("accepted").and_then(serde_json::Value::as_bool))
|
||||
== Some(true)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const ACCEPTED: &str = r#"{"event_id":"abc","accepted":true,"message":"ok"}"#;
|
||||
|
||||
#[test]
|
||||
fn successful_buzz_send_satisfies_stop_then_resets() {
|
||||
let state = ReplyDeliveryState::default();
|
||||
assert!(!state.stop_objection().is_empty());
|
||||
|
||||
state.record_shell_result(
|
||||
"buzz messages send --channel c --content hello",
|
||||
0,
|
||||
ACCEPTED,
|
||||
);
|
||||
assert!(state.stop_objection().is_empty());
|
||||
|
||||
state.reset_after_allowed_stop();
|
||||
assert!(!state.stop_objection().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_or_rejected_send_does_not_satisfy_stop() {
|
||||
let state = ReplyDeliveryState::default();
|
||||
state.record_shell_result(
|
||||
"buzz messages send --channel c --content hello",
|
||||
1,
|
||||
ACCEPTED,
|
||||
);
|
||||
state.record_shell_result(
|
||||
"buzz messages send --channel c --content hello",
|
||||
0,
|
||||
r#"{"accepted":false}"#,
|
||||
);
|
||||
assert!(!state.stop_objection().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mentioning_send_in_another_command_does_not_satisfy_stop() {
|
||||
let state = ReplyDeliveryState::default();
|
||||
state.record_shell_result("echo buzz messages send", 0, ACCEPTED);
|
||||
assert!(!state.stop_objection().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_buzz_path_is_recognized() {
|
||||
assert!(is_buzz_message_send(
|
||||
"/Applications/Buzz.app/Contents/MacOS/buzz messages send --channel c --content hi"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stop_objection_requires_the_shell_tool_and_successful_send() {
|
||||
assert!(STOP_OBJECTION.contains("`shell` MCP tool"));
|
||||
assert!(STOP_OBJECTION.contains("`buzz messages send`"));
|
||||
assert!(STOP_OBJECTION.contains("actual channel UUID"));
|
||||
assert!(STOP_OBJECTION.contains("never use literal placeholder"));
|
||||
assert!(STOP_OBJECTION.contains("command succeeds"));
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ pub struct SharedState {
|
||||
/// bootstrap hint and every `run()` call read the SAME resolution — no drift.
|
||||
pub resolved_shell: Result<(PathBuf, String), String>,
|
||||
pub artifacts: Mutex<VecDeque<PathBuf>>,
|
||||
pub reply_delivery: crate::reply_delivery::ReplyDeliveryState,
|
||||
next_call_id: Mutex<u64>,
|
||||
}
|
||||
|
||||
@@ -59,7 +58,6 @@ impl SharedState {
|
||||
bootstrap_instructions,
|
||||
resolved_shell,
|
||||
artifacts: Mutex::new(VecDeque::with_capacity(ARTIFACT_RING_SIZE)),
|
||||
reply_delivery: crate::reply_delivery::ReplyDeliveryState::default(),
|
||||
next_call_id: Mutex::new(0),
|
||||
})
|
||||
}
|
||||
@@ -308,10 +306,6 @@ pub async fn run(
|
||||
let (stderr_text, stderr_truncated, stderr_artifact) =
|
||||
finalize_stream(state, id, "stderr", stderr_cap, &mut notes);
|
||||
|
||||
state
|
||||
.reply_delivery
|
||||
.record_shell_result(&p.command, exit_code, &stdout_text);
|
||||
|
||||
let body = serde_json::json!({
|
||||
"exit_code": exit_code,
|
||||
"stdout": stdout_text,
|
||||
|
||||
Reference in New Issue
Block a user