Skip to content

fix(desktop): make relay-published agents mentionable - #4546

Open
purybr365 wants to merge 1 commit into
block:mainfrom
purybr365:fix/relay-agent-info-camelcase
Open

fix(desktop): make relay-published agents mentionable#4546
purybr365 wants to merge 1 commit into
block:mainfrom
purybr365:fix/relay-agent-info-camelcase

Conversation

@purybr365

@purybr365 purybr365 commented Aug 3, 2026

Copy link
Copy Markdown

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 mention
autocomplete no matter what the directory entry advertises (respond_to: "anyone",
"allowlist" naming the user, matching channel_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. RelayAgentInfo crosses the Tauri boundary in the wrong casing

list_relay_agents returns Vec<RelayAgentInfo>, which had no
#[serde(rename_all = "camelCase")], so it serialized snake_case. The TypeScript
RelayAgent type reads agentType / channelIds / respondTo / respondToAllowlist.
All four were therefore undefined on the frontend:

if (agent.respondTo === "allowlist" && ...)   // undefined — branch never taken
return agent.respondTo === "anyone" && ...    // undefined — always false

relayAgentIsSharedWithUser returned false for every relay agent, so
getMentionableAgentPubkeys never contained one. The failure is silent in both
directions: the === "anyone" comparison short-circuits before
agent.channelIds.some(...), so it reads as "not shared" rather than throwing, and
the TS type asserts a shape nothing verifies at runtime.

No open PR addresses this, which matters — see Related below.

2. useMentions gates on the managed set, not the invocable set

if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) return;

managedAgentPubkeys comes from managedAgentsQuery.data — agents this desktop
spawns as subprocesses. Nothing published to a relay can enter that set, so any
candidate with isAgent === true that isn't locally managed was dropped here,
before shouldHideAgentFromMentions — the directory-aware policy that understands
invocability and membership — ever ran.

isAgent goes true for a headless agent via any of three client-side signals, so
this can't be worked around from the relay while keeping the agent identity intact:
the kind:0 NIP-OA auth tag, a channel role of bot, or simply having a kind:10100
entry (relayAgentNamesByPubkey).

Implementation notes

  • RelayAgentInfo now serializes camelCase and keeps per-field snake_case aliases,
    because kind:10100 event content is snake_case (agents_from_events). Both
    directions are pinned by tests.
  • types.rs sat exactly at the 1000-line ratchet limit, so RelayAgentInfo moves to
    types/relay_agent_info.rs beside the existing catalog_source and requests
    submodules rather than growing the file.
  • Dropping managedAgentPubkeys from the useMemo dependency list is required by
    lint/correctness/useExhaustiveDependencies once its last in-memo use is gone.
  • Adds a Common Gotchas entry for the Tauri-boundary casing trap.

Related issue

Searched before opening — this is a well-reported bug with no open PR covering
defect 1:

Overlapping PRs — deliberate, not an oversight. #4453, #4517 and #4536 each fix
defect 2 (#4453 most thoroughly: it renames the gate to isAgentAutocompleteEligible
and also covers MembersSidebar). None of them touch the Tauri boundary, so on a real
relay mentionableAgentPubkeys stays empty and all three remain inert — the gate is
widened to admit a set that relayAgentIsSharedWithUser can never populate. That's
worth 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-build and just desktop-tauri-fmt-check all pass locally.

New coverage:

  • RelayAgentInfo serializes camelCase with no snake_case leftovers, and still
    deserializes snake_case directory content.
  • The two gates composed the way useMentions composes them: a shared relay agent
    survives without being locally managed (both anyone and allowlist), an agent
    sharing no channel is still dropped, and locally managed agents keep working.
  • A guard on the call site itself — the composition tests invoke the gates directly, so
    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 machine
available 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 isAgent goes false and the agent is treated as an ordinary member — which
costs the agent badge, the agent profile panels, and agent-vs-human classification by
other agents (buzz-acp's profile_event_is_agent, which affects reply-thread
anchoring).

@purybr365
purybr365 requested a review from a team as a code owner August 3, 2026 15:38
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
purybr365 force-pushed the fix/relay-agent-info-camelcase branch from bc1c888 to c397b75 Compare August 3, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant