feat(acp): propagate automatic reply context

Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz>
Signed-off-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz>
(cherry picked from commit 1535e6e5b3a42d0bca12b18627c83ae1164f3275)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap
2026-07-27 12:05:07 -07:00
committed by Michael Neale
parent 9b09d1ba26
commit 3fcd7acef9
9 changed files with 326 additions and 24 deletions

View File

@@ -480,6 +480,33 @@ pub struct SendMessageParams {
pub files: Vec<String>,
}
fn reply_to_from_sources(
explicit: Option<String>,
env_value: Option<String>,
file_path: Option<&std::ffi::OsStr>,
) -> Option<String> {
explicit
.or_else(|| {
env_value.and_then(|value| {
let value = value.trim();
(!value.is_empty()).then(|| value.to_owned())
})
})
.or_else(|| {
let path = file_path?;
let value = std::fs::read_to_string(path).ok()?;
(!value.trim().is_empty()).then(|| value.trim().to_owned())
})
}
fn automatic_reply_to(explicit: Option<String>) -> Option<String> {
reply_to_from_sources(
explicit,
std::env::var("BUZZ_REPLY_TO").ok(),
std::env::var_os("BUZZ_REPLY_TO_FILE").as_deref(),
)
}
pub async fn cmd_send_message(
client: &BuzzClient,
mut p: SendMessageParams,
@@ -490,6 +517,7 @@ pub async fn cmd_send_message(
// bugs for agent and human users alike.
p.content = read_or_stdin(&p.content)?;
validate_content_size(&p.content)?;
p.reply_to = automatic_reply_to(p.reply_to);
if let Some(ref r) = p.reply_to {
validate_hex64(r)?;
}
@@ -876,7 +904,9 @@ pub async fn dispatch(
#[cfg(test)]
mod tests {
use super::{find_root_from_tags, match_profiles_by_name, parse_member_pubkeys};
use super::{
find_root_from_tags, match_profiles_by_name, parse_member_pubkeys, reply_to_from_sources,
};
use buzz_sdk::mentions::{
extract_at_mentions_with_known, extract_at_names, match_names_to_profiles, MentionProfile,
};
@@ -892,6 +922,53 @@ mod tests {
const PK_VALID_B: &str = "c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05";
const PK_VALID_C: &str = "f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68";
#[test]
fn explicit_reply_to_precedes_environment_and_file() {
let file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(file.path(), ID_B).unwrap();
assert_eq!(
reply_to_from_sources(
Some(ID_A.into()),
Some(ID_B.into()),
Some(file.path().as_os_str()),
)
.as_deref(),
Some(ID_A)
);
}
#[test]
fn reply_to_environment_precedes_file() {
let file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(file.path(), ID_A).unwrap();
assert_eq!(
reply_to_from_sources(
None,
Some(format!(" {ID_B}\n")),
Some(file.path().as_os_str()),
)
.as_deref(),
Some(ID_B)
);
}
#[test]
fn reply_to_file_is_fallback_and_empty_values_are_ignored() {
let file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(file.path(), format!(" {ID_A}\n")).unwrap();
assert_eq!(
reply_to_from_sources(None, Some(" ".into()), Some(file.path().as_os_str()))
.as_deref(),
Some(ID_A)
);
std::fs::write(file.path(), "").unwrap();
assert_eq!(
reply_to_from_sources(None, None, Some(file.path().as_os_str())),
None
);
assert_eq!(reply_to_from_sources(None, None, None), None);
}
#[test]
fn root_marker_wins_over_reply_marker() {
let tags = json!([