fix(desktop): make relay-published agents mentionable - #4546
Open
purybr365 wants to merge 1 commit into
Open
Conversation
A headless agent published to the relay could never be @-mentioned, no matter what its kind:10100 directory entry advertised. Two independent defects stacked: 1. `RelayAgentInfo` crossed the Tauri boundary as snake_case while the TypeScript `RelayAgent` type reads camelCase, so `respondTo`, `channelIds` and `respondToAllowlist` were always `undefined` on the frontend and `relayAgentIsSharedWithUser` returned false for every relay agent. It now serializes camelCase, and keeps per-field snake_case aliases so kind:10100 event content still parses. 2. `useMentions` gated agent identities on `managedAgentPubkeys` — the locally-spawned set — which dropped every relay agent before the directory-aware `shouldHideAgentFromMentions` could admit it. It now gates on `mentionableAgentPubkeys`, a superset that adds relay agents `relayAgentIsSharedWithUser` resolves as shared with the current user. Non-invocable agents are still dropped, which is what the gate is for. Either defect alone is sufficient to hide the agent, so both had to go. Between them they left `relayAgentIsSharedWithUser`, `getMentionableAgentPubkeys` and the kind:10100 directory unreachable for their primary use case: bring-your-own agents hosted outside the desktop. `types.rs` was at the 1000-line ratchet limit, so `RelayAgentInfo` moves to `types/relay_agent_info.rs` alongside the existing `catalog_source` and `requests` submodules rather than growing the file. Tests cover the serde round-trip in both casings, the two-gate composition, and a guard on the call site so gate 1 cannot silently narrow back to the managed set. Adds a Common Gotchas entry for the Tauri-boundary casing trap, which fails silently in both directions. Signed-off-by: Pury <puryp365@gmail.com>
purybr365
force-pushed
the
fix/relay-agent-info-camelcase
branch
from
August 3, 2026 16:26
bc1c888 to
c397b75
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An agent hosted outside Buzz Desktop — a systemd/BYO agent that publishes its own
kind:10100 directory entry — can never be
@-mentioned. It stays out of mentionautocomplete no matter what the directory entry advertises (
respond_to: "anyone","allowlist"naming the user, matchingchannel_ids, channel membership, avatar,valid NIP-OA attestation).
Two independent defects stack. Either one alone is sufficient to hide the agent,
which is why fixing just the frontend gate produces no visible change.
1.
RelayAgentInfocrosses the Tauri boundary in the wrong casinglist_relay_agentsreturnsVec<RelayAgentInfo>, which had no#[serde(rename_all = "camelCase")], so it serialized snake_case. The TypeScriptRelayAgenttype readsagentType/channelIds/respondTo/respondToAllowlist.All four were therefore
undefinedon the frontend:relayAgentIsSharedWithUserreturnedfalsefor every relay agent, sogetMentionableAgentPubkeysnever contained one. The failure is silent in bothdirections: the
=== "anyone"comparison short-circuits beforeagent.channelIds.some(...), so it reads as "not shared" rather than throwing, andthe TS type asserts a shape nothing verifies at runtime.
No open PR addresses this, which matters — see Related below.
2.
useMentionsgates on the managed set, not the invocable setmanagedAgentPubkeyscomes frommanagedAgentsQuery.data— agents this desktopspawns as subprocesses. Nothing published to a relay can enter that set, so any
candidate with
isAgent === truethat isn't locally managed was dropped here,before
shouldHideAgentFromMentions— the directory-aware policy that understandsinvocability and membership — ever ran.
isAgentgoes true for a headless agent via any of three client-side signals, sothis can't be worked around from the relay while keeping the agent identity intact:
the kind:0 NIP-OA
authtag, a channel role ofbot, or simply having a kind:10100entry (
relayAgentNamesByPubkey).Implementation notes
RelayAgentInfonow serializes camelCase and keeps per-field snake_casealiases,because kind:10100 event content is snake_case (
agents_from_events). Bothdirections are pinned by tests.
types.rssat exactly at the 1000-line ratchet limit, soRelayAgentInfomoves totypes/relay_agent_info.rsbeside the existingcatalog_sourceandrequestssubmodules rather than growing the file.
managedAgentPubkeysfrom theuseMemodependency list is required bylint/correctness/useExhaustiveDependenciesonce its last in-memo use is gone.Related issue
Searched before opening — this is a well-reported bug with no open PR covering
defect 1:
shouldHideAgentFromMentions's invocability logic is unreachable #2987 — external agents publishing kind:10100 become un-mentionable;shouldHideAgentFromMentions's invocability logic unreachable (closest match)kind:10100 records
Overlapping PRs — deliberate, not an oversight. #4453, #4517 and #4536 each fix
defect 2 (#4453 most thoroughly: it renames the gate to
isAgentAutocompleteEligibleand also covers
MembersSidebar). None of them touch the Tauri boundary, so on a realrelay
mentionableAgentPubkeysstays empty and all three remain inert — the gate iswidened to admit a set that
relayAgentIsSharedWithUsercan never populate. That'sworth flagging on its own.
Happy to rebase and drop my defect-2 change in favour of #4453 if it lands first — the
serde fix is the part nothing else covers, and it's what makes the others work.
Testing
just desktop-check,just desktop-typecheck,just desktop-test(4005 tests),just desktop-buildandjust desktop-tauri-fmt-checkall pass locally.New coverage:
RelayAgentInfoserializes camelCase with no snake_case leftovers, and stilldeserializes snake_case directory content.
useMentionscomposes them: a shared relay agentsurvives without being locally managed (both
anyoneandallowlist), an agentsharing no channel is still dropped, and locally managed agents keep working.
they would not catch gate 1 silently narrowing back to the managed set. Verified this
guard fails on the unfixed code and passes on the fixed code.
Not run locally:
desktop-tauri-check/desktop-tauri-test, because the machineavailable has no GTK/WebKit development libraries. The serde behaviour those tests
assert was instead verified against a standalone harness using the real kind:10100
payload this relay serves, confirming camelCase output and snake_case input both work.
CI covers the crate build.
No screenshots. This changes which entries appear in existing autocomplete, with no
visual or layout change. A mock-mode capture would also misrepresent it: the e2e bridge
replaces Tauri IPC, so defect 1 — the serialization boundary — cannot occur under it,
and a "before" shot would show the bug already absent.
Reproduced against a self-hosted relay with two systemd agents holding valid
attestations, correct kind:10100 entries (
respond_to: "anyone"and"allowlist"naming the owner) and channel membership. Neither appeared in autocomplete. The only
workaround on a stock build is to strip the attestation and withdraw the directory
entry so
isAgentgoes false and the agent is treated as an ordinary member — whichcosts the agent badge, the agent profile panels, and agent-vs-human classification by
other agents (
buzz-acp'sprofile_event_is_agent, which affects reply-threadanchoring).