Skip to content

fix(desktop): make owner-only relay agents mentionable by their owner - #4839

Open
TheSeydiCharyyev wants to merge 1 commit into
block:mainfrom
TheSeydiCharyyev:fix/mention-picker-relay-agents
Open

fix(desktop): make owner-only relay agents mentionable by their owner#4839
TheSeydiCharyyev wants to merge 1 commit into
block:mainfrom
TheSeydiCharyyev:fix/mention-picker-relay-agents

Conversation

@TheSeydiCharyyev

@TheSeydiCharyyev TheSeydiCharyyev commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What is broken

relayAgentIsSharedWithUser handles two response modes: anyone and allowlist. There is a third one. RespondToMode is "owner-only" | "allowlist" | "anyone", and owner-only falls through to false. It is false for every viewer, including the one person the agent answers to.

This is bigger than it looks, because owner-only is the server default. types.ts says the field omitted means "owner-only" (server default). The agent settings label that mode "Only me (default)" and print "Only you and your agents can send instructions." So the default mode promises the owner can instruct the agent, and the mention picker then hides that agent from the owner.

The hole only opens for an agent the desktop does not manage, for example one running as a container elsewhere. Managed agents are admitted by a separate path, so nobody who starts agents from the desktop would ever see this.

The fix

RelayAgent has no owner field, so the owner has to come from profile metadata. getMentionableAgentPubkeys now takes the profile lookup and resolves ownership with mergeOwnedAgentPubkeys from features/agents/knownAgentPubkeys.ts. That helper is already documented as "Agent identities controlled by the current user", and the inbox already uses it for the same question. This path just never asked it.

Ownership is matched, never assumed. An agent whose owner cannot be resolved stays hidden. A caller that passes no profiles gets exactly today's behaviour.

Channel exactness does not change. The mention path goes through relayAgentCanRespondInChannel, which still requires the agent to be in that channel, so the owner reaches the agent only where the agent actually is. DMs and unresolved composer contexts use the managed-only scope and stay fail-closed.

Tests

Two new tests in agentAutocompleteEligibility.test.mjs. Both fail on the current helper and pass after it: # pass 21 / # fail 2 before, # pass 23 / # fail 0 after.

The existing owner-only assertion in that file is untouched and still green. It calls relayAgentIsSharedWithUser without a viewer pubkey and expects false, which is still what happens: no viewer identity, no admission.

One removal

useMentions built sharedChannelIds and passed it in. That call site only ever asks for the channel or managed-only scope, and neither one reads that set — only community does. So the parameter is now optional and useMentions no longer builds it. useChannelsQuery went with it, because it fed nothing else in the hook.

Relation to #4913

This PR opened with two commits. The first removed a managed-list gate that ran ahead of the policy check. #4913 landed that behaviour and a lot more, so that commit is dropped and the branch is rebuilt on current main. What is left is the owner-only gap, which #4913 did not close.

Scope

Three other call sites share this helper: MembersSidebar, useNewMessageRecipients and ProjectsAgentPromptPage. They are unchanged. Owner data is not available at those call sites without adding a profile query, and in ProjectsAgentPromptPage the profile query runs after this set is computed. Because profiles is optional, all three keep today's behaviour.

One note on trust, since ownership now decides what the picker shows. ownerPubkey is not self-asserted. It comes from the NIP-OA auth tag on the agent's kind:0, and profile_valid_oa_owner_pubkey verifies that tag against the owner before get_users_batch returns it, so a forged or stale marker resolves to no owner at all. An agent whose owner does not verify stays hidden, and the relay still enforces the real gate on the way in.

Validation

  • pnpm --dir desktop test — 4389 passed
  • pnpm --dir desktop typecheck
  • pnpm --dir desktop check — biome, file-size ratchet, px-text, pubkey-truncation
  • useMentions.ts goes from 1000 lines to 991, so it stays under the ratchet

Refs #4776, #4833.

@TheSeydiCharyyev
TheSeydiCharyyev requested a review from a team as a code owner August 5, 2026 04:51
@jaozolins

Copy link
Copy Markdown

Related: our server-hosted agent is in the channel and has a directory card, but Desktop still hides it from @ unless we strip ownership. Please stop treating relay-directory agents as non-mentionable for the owner. +1

@TheSeydiCharyyev

Copy link
Copy Markdown
Contributor Author

Confirmed, and this PR alone does not cover your case. relayAgentIsSharedWithUser only accepts respondTo: "anyone" or an allowlist containing you, so an owner-only agent is never invocable — including for its own owner. RelayAgent carries no owner field, which is why stripping ownership works around it.

Adding a second commit here: an owner-only relay agent counts as mentionable for its owner, taking ownership from the profile data this hook already reads.

TheSeydiCharyyev added a commit to TheSeydiCharyyev/buzz that referenced this pull request Aug 5, 2026
relayAgentIsSharedWithUser accepted only `anyone` and `allowlist`, so an
owner-only agent was never invocable - not even for the one person it
answers to. With a directory entry and channel membership,
shouldHideAgentFromMentions then hid it as explicitly not-invocable, which
is why dropping ownership worked around it.

RelayAgent carries no owner field, so getMentionableAgentPubkeys now takes
an owner lookup and useMentions supplies it from the profile metadata the
hook already reads for candidate labels. Ownership is matched, never
assumed: an unknown owner keeps the agent hidden.

Raised on block#4839 by a third deployment hitting the same wall.

Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
@kaalph

kaalph commented Aug 5, 2026

Copy link
Copy Markdown

I filed #4776 and I run the same shape of setup: self-hosted relay, six communities on it, 23 agents as containers on my own VM, none of them spawned by the desktop. So this hits me directly and I'm glad someone picked it up.

I've been running the equivalent of your part 1 as a local patch for about a week. Same conclusion — the candidate loop builds everything shouldHideAgentFromMentions needs and then the managed-list check throws it away first. My version kept the gate and passed the invocable set in as a third argument, which works but is clearly the more timid fix. Removing it and letting the policy decide is better; the policy is written for exactly those candidates.

Part 2 is the interesting one for me, because I hit the symptom and never found the cause. All my agents run with respond_to: anyone. I set that early on and wrote it off as "makes sense for a private community" — your explanation is that it's the workaround, and reading relayAgentIsSharedWithUser again I think you're right. Owner-only agents would have been invisible to me too and I'd never have known why. That part is worth more than the first half.

On #4833's second half: that's what I ran into. I hand-wrote entries into managed-agents.json so the agents would show up in the Agents tab, and then mentions died with a keyring error — the app tried to start a local copy of something that has no key on that machine, and the message never got published. I patched the start path to skip those agents. Your framing is the better one: local auto-start should be an optimization, never a gate.

Two things I can't speak to. I haven't built your branch, so this is a code read, not a test report. And I patch MembersSidebar too, since the add-picker uses the same helper — I saw you left it out on purpose and I think that's the right call for this PR, it's a different decision. If a small follow-up for it would be useful, I'm happy to write one.

@jaozolins

Copy link
Copy Markdown

Same setup on my side: server agents, desktop as the client. Glad part 2 landed; that was the gap I was hitting. Thanks both.

relayAgentIsSharedWithUser admits `anyone` and `allowlist`, so an
owner-only agent fell through to false for everyone - including the one
person it answers to. `owner-only` is also the server default, so a
relay agent created without an explicit mode is invocable by nobody at
all, while the agent settings label that mode "Only me (default)" and
promise "Only you and your agents can send instructions".

RelayAgent carries no owner field, so the owner comes from profile
metadata. getMentionableAgentPubkeys now takes the profile lookup and
resolves ownership through mergeOwnedAgentPubkeys, the helper the inbox
already uses for the same question. Ownership is matched, never assumed:
an agent whose owner cannot be resolved stays hidden, and a caller that
passes no profiles keeps today's behaviour.

The mention path never reads sharedChannelIds - it only ever asks for
the channel or managed-only scope - so useMentions no longer builds or
passes it.

Reported in block#4776 and block#4833.

Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
@TheSeydiCharyyev
TheSeydiCharyyev force-pushed the fix/mention-picker-relay-agents branch from 8378df2 to 94012de Compare August 6, 2026 11:20
@TheSeydiCharyyev TheSeydiCharyyev changed the title fix(desktop): stop hiding relay-directory agents from the mention picker fix(desktop): make owner-only relay agents mentionable by their owner Aug 6, 2026
@TheSeydiCharyyev

Copy link
Copy Markdown
Contributor Author

Part 1 is gone from this PR. #4913 landed on 5 Aug and does the same thing, plus exact-channel authorization, fail-closed DMs, cached-row reauthorization and forum coverage. Your local patch and my first commit are both superseded by it. The branch is rebuilt on current main and now carries only the owner-only half.

On MembersSidebar: #4913 already removed that gate. The add-picker now takes the policy-derived set from getMentionableAgentPubkeys, so the patch you were carrying is no longer needed there.

That surface is still not whole, though. It asks for the community scope and passes no profiles, so an owner-only agent stays hidden from its own owner in the add-picker, exactly as it was in mentions. I left it out here because the owner is not available at that call site without adding a profile query. It is free if you want it as a follow-up.

On the shape of the fix, one thing worth knowing before you read the diff: relayAgentIsSharedWithUser is not the only place it has to land. Mentions now run through relayAgentCanRespondInChannel, so ownership is threaded through both. Fix only the first and channel-scoped mentions stay broken while community-scoped ones work.

The keyring problem you described is a separate defect and deserves its own issue. You have the reproduction: hand-written managed-agents.json entries, the mention dies with a keyring error, the message is never published. Nothing in this PR touches the start path.

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.

3 participants