Skip to content

fix(desktop): share managed agents across owners - #4857

Open
thegarty wants to merge 2 commits into
block:mainfrom
thegarty:codex/cross-owner-agent-sharing
Open

fix(desktop): share managed agents across owners#4857
thegarty wants to merge 2 commits into
block:mainfrom
thegarty:codex/cross-owner-agent-sharing

Conversation

@thegarty

@thegarty thegarty commented Aug 5, 2026

Copy link
Copy Markdown

Summary

  • surface channel-member agents that the current sender may invoke, including foreign-owned allowlist agents
  • keep duplicate display names as distinct pubkey identities and publish the selected identity's exact structured p tag
  • block unavailable authorized agents with explicit offline / not-in-channel feedback
  • apply saved access-policy changes to the live runtime by restarting only the edited active agent when its spawn snapshot reports drift

Root cause

Two independent filters made cross-owner sharing unreachable. RelayAgentInfo serialized snake_case across the Tauri boundary while TypeScript reads camelCase, so respondTo, respondToAllowlist, and channelIds were silently undefined. After that, useMentions gated 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-acp process 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 selects eeee…eeee, publishes a message, and asserts the signed event contains ["p", "eeee…eeee"] while explicitly rejecting the same-name abab…abab tag. A second case verifies an authorized offline agent shows quinn is offline and cannot be invoked. and publishes nothing.

Verification

  • corepack pnpm test — 4,291 passed
  • corepack pnpm exec tsc --noEmit
  • focused Biome check on all changed Desktop files
  • corepack pnpm build:e2e
  • corepack pnpm exec playwright test tests/e2e/mentions.spec.ts --project=smoke --grep "allowlisted foreign|authorized offline" — 2 passed
  • git diff --check

Rust/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

  1. Create users A and B and same-named agents owned by each; add both bot identities to a shared channel.
  2. Keep A's agent on Selected people and allowlist B.
  3. As B, type the shared name; confirm both identities are disambiguated by owner and/or stable pubkey.
  4. Select A's identity and send; inspect the event for only A's agent pubkey in the structured p tag and confirm one agent reply.
  5. Remove B from the allowlist, remove channel membership, and stop the agent in turn; verify private agents disappear and unavailable authorized identities explain the failure.
  6. Save a Selected people policy change on a running agent; confirm the applying/live toast and that only that agent restarts.

The Tauri casing and initial relay-agent eligibility work carries forward the fix and tests from #4546 with original authorship preserved.

purybr365 and others added 2 commits August 5, 2026 17:23
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>
@thegarty
thegarty marked this pull request as ready for review August 5, 2026 07:40
@thegarty
thegarty requested a review from a team as a code owner August 5, 2026 07:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +125 to +127
if (suggestion.unavailableReason) {
toast.error(suggestion.unavailableReason);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

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.

2 participants