fix(desktop): share managed agents across owners - #4857
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>
Signed-off-by: Tom Hegarty <tom@heg2.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 836d8a5ca1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// | ||
| /// Both directions are pinned by tests in `types/tests.rs`. | ||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] |
There was a problem hiding this comment.
Update the relay-agent mapper with the IPC casing
This flips list_relay_agents IPC output to camelCase, but fromRawRelayAgent in desktop/src/shared/api/tauri.ts still reads the old snake_case properties (agent_type, channel_ids, respond_to, respond_to_allowlist). In the real app, those fields now map to undefined/[]/null, so relayAgentIsSharedWithUser cannot admit any relay-published agent and the cross-owner mention fix remains broken; update or remove the raw mapper as part of this casing change.
AGENTS.md reference: AGENTS.md:L434-L434
Useful? React with 👍 / 👎.
| if (suggestion.unavailableReason) { | ||
| toast.error(suggestion.unavailableReason); | ||
| return; |
There was a problem hiding this comment.
Block unavailable mentions on keyboard selection
This guard only runs for mouse selection. If the same unavailable row is accepted with Enter or Tab, handleMentionKeyDown returns the selected suggestion and MessageComposer calls applyMentionInsert, so an offline or not-in-channel relay agent can still be inserted and sent despite the new error state; the availability check needs to live in the shared selection/key path.
Useful? React with 👍 / 👎.
Summary
allowlistagentsptagRoot cause
Two independent filters made cross-owner sharing unreachable.
RelayAgentInfoserialized snake_case across the Tauri boundary while TypeScript reads camelCase, sorespondTo,respondToAllowlist, andchannelIdswere silentlyundefined. After that,useMentionsgated agent candidates against the sender's locally managed pubkeys before the relay authorization logic ran. A foreign-owned channel bot could therefore be both a member and allowlist-authorized yet never reach autocomplete.The picker already stored selected identities by pubkey, but the missing foreign candidate meant duplicate names commonly resolved to the only visible sender-owned identity. Unavailable directory entries also remained actionable without explaining why invocation could not succeed. Finally, policy edits were persisted and projected to the relay, but the running
buzz-acpprocess continued using its spawn-time environment until restart.Security
This does not widen agent policy. Owner-only foreign agents remain undiscoverable. Selected people admits only normalized pubkeys in
respondToAllowlist. Anyone still requires a shared channel. Offline and membership failures are blocked before send. The runtime restart is scoped to the edited agent pubkey.Event evidence
The new Playwright regression seeds two separately owned, allowlisted channel bots named
quinn. It selectseeee…eeee, publishes a message, and asserts the signed event contains["p", "eeee…eeee"]while explicitly rejecting the same-nameabab…ababtag. A second case verifies an authorized offline agent showsquinn is offline and cannot be invoked.and publishes nothing.Verification
corepack pnpm test— 4,291 passedcorepack pnpm exec tsc --noEmitcorepack pnpm build:e2ecorepack pnpm exec playwright test tests/e2e/mentions.spec.ts --project=smoke --grep "allowlisted foreign|authorized offline"— 2 passedgit diff --checkRust/Tauri tests could not be executed from this Windows OneDrive worktree because Git/Hermit package symlinks are materialized as pointer files. The added serde tests and existing runtime normalization / spawn-snapshot tests are included for CI to run.
Manual verification
ptag and confirm one agent reply.The Tauri casing and initial relay-agent eligibility work carries forward the fix and tests from #4546 with original authorship preserved.