mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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:
parent
0a70cb739b
commit
9dd506fddd
@@ -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:**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user