Skip to content

fix(desktop): stop hiding relay-directory agents from the mention picker - #4839

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

fix(desktop): stop hiding relay-directory agents from the mention picker#4839
TheSeydiCharyyev wants to merge 2 commits into
block:mainfrom
TheSeydiCharyyev:fix/mention-picker-relay-agents

Conversation

@TheSeydiCharyyev

@TheSeydiCharyyev TheSeydiCharyyev commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Two gates kept relay-directory agents out of the @ picker. Both are in this PR.

1. A local-managed-list gate ran ahead of the eligibility policy.

useMentions fetches the relay agent directory, derives mentionableAgentPubkeys from it, and adds every relay agent as a mention candidate — then dropped them all one line before the check that consumes any of that:

if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) {
  return;                                   // ← every non-locally-managed agent
}
if (shouldHideAgentFromMentions({ ..., mentionableAgentPubkeys, directoryAgentPubkeys })) {
  return;
}

managedAgentPubkeys only holds agents the desktop spawns itself, so an agent hosted anywhere else never reached shouldHideAgentFromMentions — even though the hook had already fetched it (relayAgentsQuery), decided it was invocable (getMentionableAgentPubkeys, same query), and queued it as a candidate. For a deployment where no agent is desktop-spawned, that candidate loop is dead code. shouldHideAgentFromMentions is written for exactly these candidates, so the gate is removed and the policy decides.

2. owner-only agents were invocable by nobody — including their owner.

relayAgentIsSharedWithUser accepted only respondTo: "anyone" and an allowlist containing the current user. An owner-only agent fell through to false, so it never entered mentionableAgentPubkeys; with a directory entry and channel membership, shouldHideAgentFromMentions then hid it as explicitly not-invocable. That is why dropping ownership works 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 (profiles[pubkey].ownerPubkey). Ownership is matched, never assumed — an unknown owner keeps the agent hidden.

Locally managed agents are unaffected throughout: getMentionableAgentPubkeys seeds its set from managedAgentPubkeys, so they stay invocable and stay shown.

Mentioning a directory-only agent does not try to launch anything locally. useMentionSendFlow narrows the launch path with normalizedMentionPubkeys.filter((pubkey) => managedAgentsByPubkey.has(pubkey)) before calling ensureManagedAgentMentionsReady, so an agent that is not in the local store is published as an ordinary mention. (That is the difference from the second half of #4833, where the agent had been added to managed-agents.json and the app then tried to start a local copy.)

Out of scope: the add-members picker in MembersSidebar calls the same helper and is deliberately untouched here. It has no equivalent policy to fall back on, so opening it up is a separate decision.

Related issue

Refs #4776 and #4833, filed independently within a day of each other against the same gate, plus a third deployment reported in this PR's comments — that report is what surfaced the owner-only half. No open PR touches useMentions.ts or agentAutocompleteEligibility.ts.

Testing

desktop package on Windows 11, node 22.17.1, pnpm 11.4.0.

  • Full unit suite: 3926 tests, 0 failures (pnpm test).
  • pnpm check clean across 1756 files; pnpm typecheck clean; pnpm build succeeds.
  • Removing the first gate made managedAgentPubkeys an unnecessary dependency of the mentionCandidates memo; biome's useExhaustiveDependencies flagged it and it is dropped in the same change.
  • useMentions.ts sits exactly on its 1000-line ratchet, so the added comments are terse by necessity; the reasoning lives here instead.

New cases in agentAutocompleteEligibility.test.mjs:

  • relayAgentIsSharedWithUser: an owner-only agent is invocable by its owner — also asserts someone else's owner-only agent stays hidden, and that unknown ownership does not admit it.
  • getMentionableAgentPubkeys: admits an owner-only relay agent for its owner — two owner-only agents, only the owned one is admitted.
  • a relay-directory agent the desktop does not manage stays mentionable — the Desktop drops relay-directory agents from the mention picker when it doesn't manage them locally #4776 shape end to end.

The first two fail against the pre-change helper (# fail 2) and pass after, so the owner-only half is a genuine regression test. The third pins the policy and documents the ordering trap but exercises the helpers rather than the hook, so it passes either way — desktop has no React hook test harness, and the call-site ordering in commit 1 is argued from the code path above rather than from a failing test. E2E coverage through the mock bridge can be added if that is preferred.

useMentions fetches the relay agent directory, derives
mentionableAgentPubkeys from it, and adds every relay agent as a mention
candidate - then dropped them all one line before the check that consumes
any of that. The gate was isAgentIdentityInManagedList against
managedAgentPubkeys, which only holds agents the desktop spawns itself, so
an agent hosted anywhere else never reached shouldHideAgentFromMentions.

That policy is written for exactly these candidates: invocable agents are
shown, non-member non-invocable ones are hidden, and a channel member is
hidden only on an explicit not-invocable directory entry. Dropping the
earlier gate lets it decide. Locally managed agents are unaffected - they
are in mentionableAgentPubkeys either way, so the policy shows them.

Mentioning a directory-only agent does not try to launch anything locally:
useMentionSendFlow narrows the launch path to
managedAgentsByPubkey.has(pubkey), so such a mention is simply published.

The add-members picker uses the same helper and is deliberately left alone
here - it has no equivalent policy to fall back on.

Refs block#4776
Refs block#4833

Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
@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.

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.

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