Split out of #2526 — noted by @SeanGearin while reviewing #2547.
Problem
#2526 identified two divergent @-mention parsers (Rust buzz-sdk/src/mentions.rs and TS desktop/src/features/messages/lib/hasMention.ts). There is a third: resolve_mention_pubkeys in crates/buzz-relay/src/workflow_sink.rs, whose doc comment says it "defines the matching contract" for workflow-emitted kind:9 p tags.
Its left-boundary check accepts only start-of-string, whitespace, or (:
let is_left_boundary = |i: usize| i == 0 || chars[i - 1].is_whitespace() || chars[i - 1] == '(';
So a workflow-generated **@Robby** emits zero p tags. Those p tags gate ACP agent wake (event_mentions_agent, crates/buzz-acp/src/lib.rs), which reproduces the exact stalled-handoff failure mode from #2526 on a surface where the text is machine-generated — an agent or template emitting emphasis around a mention is not a user typo, it's routine markdown.
The right side of this parser is already emphasis-tolerant (it rejects only name-continuation chars, alphanumeric | _), so only the left boundary needs widening. Note the _ wrinkle: a trailing _ after a name is a name-continuation char here, so _@robby_ has a second, smaller divergence too.
Suggested direction
After #2547 lands, buzz-sdk::mentions::is_mention_lead is the shared definition of "what may precede an @" (whitespace, (, *, _, |, start). buzz-relay already depends on buzz-sdk, so workflow_sink.rs can call it directly instead of keeping a private boundary rule — that would take the parser count needing manual sync from three to two.
Longer term (already noted in #2526): the Rust and TS implementations should be one contract with shared test vectors, or this class of drift recurs.
Split out of #2526 — noted by @SeanGearin while reviewing #2547.
Problem
#2526 identified two divergent @-mention parsers (Rust
buzz-sdk/src/mentions.rsand TSdesktop/src/features/messages/lib/hasMention.ts). There is a third:resolve_mention_pubkeysincrates/buzz-relay/src/workflow_sink.rs, whose doc comment says it "defines the matching contract" for workflow-emitted kind:9ptags.Its left-boundary check accepts only start-of-string, whitespace, or
(:So a workflow-generated
**@Robby**emits zeroptags. Thoseptags gate ACP agent wake (event_mentions_agent,crates/buzz-acp/src/lib.rs), which reproduces the exact stalled-handoff failure mode from #2526 on a surface where the text is machine-generated — an agent or template emitting emphasis around a mention is not a user typo, it's routine markdown.The right side of this parser is already emphasis-tolerant (it rejects only name-continuation chars,
alphanumeric | _), so only the left boundary needs widening. Note the_wrinkle: a trailing_after a name is a name-continuation char here, so_@robby_has a second, smaller divergence too.Suggested direction
After #2547 lands,
buzz-sdk::mentions::is_mention_leadis the shared definition of "what may precede an@" (whitespace,(,*,_,|, start).buzz-relayalready depends onbuzz-sdk, soworkflow_sink.rscan call it directly instead of keeping a private boundary rule — that would take the parser count needing manual sync from three to two.Longer term (already noted in #2526): the Rust and TS implementations should be one contract with shared test vectors, or this class of drift recurs.