fix(acp): wake agents for mentions added by edits

Include kind:40003 in the default mention subscriptions across normal, dynamic-channel, and setup listeners. Preserve explicit kind overrides and document the actionable default.

Signed-off-by: npub1em3jmyn4vu57urqf03txrwreccvejvwdy5c4er8nnrwt7rc4tncscs3ssu <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz>
(cherry picked from commit 7ad7d79a84)
Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
This commit is contained in:
npub1em3jmyn4vu57urqf03txrwreccvejvwdy5c4er8nnrwt7rc4tncscs3ssu
2026-08-17 10:12:16 -04:00
committed by Larry
parent 0a70cb739b
commit 9dd506fddd
4 changed files with 74 additions and 36 deletions
+3 -3
View File
@@ -222,16 +222,16 @@ Start with **N=2** for most deployments. Increase if queue depth grows under loa
## Forum Channels
By default, the ACP harness subscribes to stream message kinds (9, 46010, 40007). To receive forum events, opt in with `--kinds` and disable the mention filter (forum posts don't @mention agents):
By default, the ACP harness subscribes to actionable stream kinds (9 messages, 40003 edits that add a mention, 46010 workflow approvals, and 40007 reminders). To receive forum events, opt in with `--kinds` and disable the mention filter (forum posts don't @mention agents):
**CLI flags:**
```bash
buzz-acp --kinds 9,46010,40007,45001,45002,45003 --no-mention-filter
buzz-acp --kinds 9,40003,46010,40007,45001,45002,45003 --no-mention-filter
```
**Or with `--subscribe all`:**
```bash
buzz-acp --subscribe all --kinds 9,46010,40007,45001,45002,45003
buzz-acp --subscribe all --kinds 9,40003,46010,40007,45001,45002,45003
```
**Per-channel config:**
+45 -22
View File
@@ -6,6 +6,10 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_EDIT, KIND_STREAM_REMINDER,
KIND_WORKFLOW_APPROVAL_REQUESTED,
};
use clap::Parser;
use clap::ValueEnum;
use nostr::Keys;
@@ -1249,16 +1253,26 @@ pub fn load_rules(path: &std::path::Path) -> Result<Vec<SubscriptionRule>, Confi
Ok(config.rules)
}
/// Event kinds that carry actionable direct mentions by default.
///
/// Message edits are included because Desktop emits `p` tags only for
/// recipients newly added by an edit. Receiving kind 40003 therefore wakes an
/// agent once for a newly added mention without re-waking it for ordinary edits.
pub(crate) fn default_mention_kinds() -> Vec<u32> {
vec![
KIND_STREAM_MESSAGE,
KIND_STREAM_MESSAGE_EDIT,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
}
/// Resolve per-channel NIP-01 filters from config + discovered channels.
pub fn resolve_channel_filters(
config: &Config,
discovered_channels: &[Uuid],
rules: &[SubscriptionRule],
) -> HashMap<Uuid, ChannelFilter> {
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
};
let target_channels: Vec<Uuid> = if let Some(ref overrides) = config.channels_override {
overrides
.iter()
@@ -1273,13 +1287,10 @@ pub fn resolve_channel_filters(
match config.subscribe_mode {
SubscribeMode::Mentions => {
let kinds = config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
});
let kinds = config
.kinds_override
.clone()
.unwrap_or_else(default_mention_kinds);
let require_mention = !config.no_mention_filter;
for ch in &target_channels {
result.insert(
@@ -1357,10 +1368,6 @@ pub fn resolve_dynamic_channel_filter(
channel_id: Uuid,
rules: &[crate::filter::SubscriptionRule],
) -> Option<ChannelFilter> {
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
};
// In Mentions/All mode, if the operator explicitly constrained channels
// with --channels, only allow dynamic subscription to channels in that
// allowlist. Config mode ignores --channels (per CLI contract) and uses
@@ -1378,13 +1385,12 @@ pub fn resolve_dynamic_channel_filter(
match config.subscribe_mode {
SubscribeMode::Mentions => Some(ChannelFilter {
kinds: Some(config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
})),
kinds: Some(
config
.kinds_override
.clone()
.unwrap_or_else(default_mention_kinds),
),
require_mention: !config.no_mention_filter,
}),
SubscribeMode::All => Some(ChannelFilter {
@@ -1529,11 +1535,28 @@ mod tests {
assert!(f.require_mention, "mentions mode requires mention");
let kinds = f.kinds.as_ref().expect("should have kinds");
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE));
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE_EDIT));
assert!(kinds.contains(&buzz_core::kind::KIND_WORKFLOW_APPROVAL_REQUESTED));
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_REMINDER));
}
}
#[test]
fn test_dynamic_mentions_mode_includes_message_edits() {
let config = test_config(SubscribeMode::Mentions);
let filter = resolve_dynamic_channel_filter(&config, Uuid::new_v4(), &[])
.expect("dynamic channel should be subscribed");
assert!(filter.require_mention);
assert!(
filter
.kinds
.expect("mentions mode should constrain kinds")
.contains(&KIND_STREAM_MESSAGE_EDIT),
"newly mentioned agents must receive message edits on dynamic channels"
);
}
#[test]
fn test_mentions_mode_custom_kinds() {
let mut config = test_config(SubscribeMode::Mentions);
+4 -8
View File
@@ -22,7 +22,6 @@ use acp::{AcpClient, EnvVar, McpServer};
use anyhow::Result;
use buzz_core::kind::{
KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_STREAM_MESSAGE,
KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
};
use buzz_core::observer::{
decrypt_observer_payload, encrypt_observer_payload, OBSERVER_FRAME_TELEMETRY,
@@ -2074,13 +2073,10 @@ async fn tokio_main() -> Result<()> {
vec![SubscriptionRule {
name: "mentions".into(),
channels: filter::ChannelScope::All("all".into()),
kinds: config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
}),
kinds: config
.kinds_override
.clone()
.unwrap_or_else(config::default_mention_kinds),
require_mention: !config.no_mention_filter,
filter: None,
compiled_filter: None,
+22 -3
View File
@@ -37,7 +37,7 @@ use std::collections::HashSet;
use anyhow::Result;
use buzz_core::kind::{
KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_STREAM_MESSAGE,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_MESSAGE_EDIT, KIND_WORKFLOW_APPROVAL_REQUESTED,
};
use nostr::EventId;
use serde::{Deserialize, Serialize};
@@ -410,7 +410,7 @@ pub(crate) async fn run_setup_listener(config: Config, payload: SetupPayload) ->
}
// Ignore non-message kinds (relay housekeeping, etc.).
if kind_u32 != KIND_STREAM_MESSAGE && kind_u32 != KIND_WORKFLOW_APPROVAL_REQUESTED {
if !is_setup_nudge_kind(kind_u32) {
continue;
}
@@ -513,6 +513,13 @@ pub(crate) fn should_nudge_for_event(
true
}
fn is_setup_nudge_kind(kind: u32) -> bool {
matches!(
kind,
KIND_STREAM_MESSAGE | KIND_STREAM_MESSAGE_EDIT | KIND_WORKFLOW_APPROVAL_REQUESTED
)
}
/// Build the subscription rules used in setup mode.
///
/// Always uses "mentions" mode: setup mode must not react to every event.
@@ -525,7 +532,7 @@ fn build_setup_subscription_rules(config: &Config) -> Vec<filter::SubscriptionRu
let kinds = config
.kinds_override
.clone()
.unwrap_or_else(|| vec![KIND_STREAM_MESSAGE, KIND_WORKFLOW_APPROVAL_REQUESTED]);
.unwrap_or_else(crate::config::default_mention_kinds);
match &config.subscribe_mode {
// Config mode: load the actual rules, but they will be filtered by
@@ -760,6 +767,18 @@ mod tests {
));
}
#[test]
fn setup_listener_defaults_include_newly_mentioned_message_edits() {
assert!(
crate::config::default_mention_kinds().contains(&KIND_STREAM_MESSAGE_EDIT),
"setup listener shares the normal actionable-mention defaults"
);
assert!(
is_setup_nudge_kind(KIND_STREAM_MESSAGE_EDIT),
"setup listener must process a delivered mention edit"
);
}
#[test]
fn nudge_body_names_all_requirements() {
let payload = SetupPayload {