Skip to content

Hide non-invocable member agents from @-mention autocomplete - #1611

Merged
tlongwell-block merged 2 commits into
block:mainfrom
atishpatel:fix/hide-noninvocable-member-agents-from-mentions
Jul 8, 2026
Merged

Hide non-invocable member agents from @-mention autocomplete#1611
tlongwell-block merged 2 commits into
block:mainfrom
atishpatel:fix/hide-noninvocable-member-agents-from-mentions

Conversation

@atishpatel

Copy link
Copy Markdown
Contributor

Problem

Agents you can't invoke — e.g. Fizz, owned by another team — still show up in the composer's @-mention autocomplete when they've been added to a channel as members.

The autocomplete already has an invocability filter (useMentions.ts), but it only applied to agents that aren't channel members:

if (candidate.isAgent && !candidate.isMember && !mentionableAgentPubkeys.has(pubkey)) return;

Fizz is added to the channel as a member by its owning team, so !candidate.isMember is false, the check is skipped, and Fizz always appears — even though the backend (author_allowed in buzz-acp) would reject a mention of it.

Fix

Extract the show/hide decision into a pure, tested helper shouldHideAgentFromMentions(...) and apply it to all agent candidates, member or not. The "can I invoke this agent" set (getMentionableAgentPubkeys) is unchanged and continues to mirror the backend gate.

Semantics (member agents are the new case):

candidate result
not an agent show
invocable (in mentionableAgentPubkeys) show
non-member & not invocable hide (unchanged prior behavior)
member & not invocable & in relay directory (kind:10100) hide (this is Fizz)
member & not invocable & not in directory (unknown / still loading) show

The last row is deliberate: we only hide a member agent when we have an explicit not-invocable signal (a directory entry that excludes the current user). If the agent's directory profile is missing or still loading, invocability is unknown and we keep showing it — we never hide an agent we can't confirm is non-invocable. useMentions builds a directoryAgentPubkeys set from relayAgentsQuery.data to supply that signal.

Tests

Adds unit tests in agentAutocompleteEligibility.test.mjs covering every branch, including both member cases (Fizz hidden; unknown-invocability shown) and npub/hex pubkey normalization.

  • node --test → 17/17 pass
  • biome check (3 changed files) → clean
  • tsc --noEmit → 0 errors

Agents you can't invoke (e.g. Fizz, owned by another team) still appeared
in the composer's @-mention list when they were added to the channel as
members. The existing invocability filter in useMentions only applied to
non-member agents, so member agents bypassed it entirely.

Extract the decision into a pure helper, shouldHideAgentFromMentions, and
apply it to all agent candidates. Option B semantics: hide a member agent
only when we have an explicit not-invocable signal — a relay directory
(kind:10100) entry that excludes the current user. When invocability is
unknown (agent not yet in the directory / still loading), keep showing it
so we never hide an agent we can't confirm is non-invocable.

This mirrors the backend gate (author_allowed in buzz-acp), so the
autocomplete stays consistent with server-side enforcement.

Adds unit tests covering every branch, including the two Option B cases
and npub/hex pubkey normalization.

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed and re-verified at source (ran the tests + typecheck myself). Clean, minimal, well-scoped fix.

The extraction of shouldHideAgentFromMentions into a pure helper is the right move, and the branch ordering reads clearly: non-agent → show, invocable → show, non-member non-invocable → hide (preserves prior behavior), member → hide only on an explicit not-invocable directory signal. The "only hide when we can confirm non-invocable" stance on the last row is the correct conservative default — a member agent whose directory profile is still loading stays visible rather than flickering out.

Provenance is consistent: directoryAgentPubkeys and mentionableAgentPubkeys are both derived from relayAgentsQuery.data, so "in the directory but not mentionable" is a real explicit-exclusion signal, not an artifact of two unsynced sources. I also checked the other !candidate.isMember use (extractMentionPubkeys, useMentions.ts:830) — it iterates mentionCandidates, which is already filtered by this helper upstream, so a hidden member agent can't be re-extracted from text there. No missed filter path.

Verified locally: node --test on the eligibility suite → 17/17 pass; tsc --noEmit → clean. One optional note inline; nothing blocking.

Comment thread desktop/src/features/agents/lib/agentAutocompleteEligibility.ts
…e coupling

Address review feedback on block#1611: add a comment on the member branch of
shouldHideAgentFromMentions noting it treats directory presence (without
mentionability) as an explicit not-invocable signal, which is only sound
because both sets derive from the same relayAgentsQuery.data. Flags the
premature-hide risk if a future change sources the directory set elsewhere.
@tlongwell-block
tlongwell-block merged commit f929aa0 into block:main Jul 8, 2026
45 of 47 checks passed
kbst9 added a commit to kbst9/buzz that referenced this pull request Jul 28, 2026
58e8ea1 let the relay decide who may be ADDED to a channel, but the
mention surface still refused to complete a non-owned agent: useMentions
pre-filtered every agent candidate through the caller's managed-agents
list (block#2149), and extractMentionPubkeys resolves from the same filtered
candidates — so even a hand-typed @name produced no p tag. You could add
a teammate's agent to a channel and then never talk to it.

Scope the local-ownership gate to non-members. Membership is the
relay-sanctioned signal (kind:9000 + per-user channel_add_policy), so a
member agent is offered regardless of who runs it. Non-member agents
keep requiring local ownership — block#2149's cleanup of search/directory
ghosts stands: relay-only agents stay hidden, allowlisted or not.

That makes the block#1611 member branch reachable again, so tighten its
"explicit not-invocable" directory signal. Hiding on mere kind:10100
presence was built on data 58e8ea1 already established as vacuous
(set-add-policy publishes neither respond_to nor channel_ids). A member
now hides only when a populated allowlist leaves the viewer out — the
one directory fact with trustworthy exclusion semantics. Vacuous
entries, "anyone" with stale channel_ids, and owner-only (ownership is
not reliably knowable client-side) all show; whether the agent answers
stays the harness's respond_to decision (default owner-only).

knownAgentPubkeys also grows the surviving agent candidates so a
non-owned member mention classifies as an agent mention at send time
(explicit agent audience, channel prep, chip styling) instead of
falling back to the human path.

Flips the block#2149 member-agent expectations in mentions.spec.ts and adds
coverage for the other-owned-member, owner-only-entry, and
allowlist-exclusion cases. Candidate merge + search-label helpers move
to mentionCandidates.ts to keep useMentions.ts under the file-size
guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: kbst9 <kevinbsteiner@gmail.com>
NuCl34R pushed a commit to NuCl34R/buzz that referenced this pull request Aug 2, 2026
The ownership branch made an `owner-only` in-channel agent mentionable, but
`knownAgentPubkeys` stayed an alias of `mentionableAgentPubkeys`, which never
admits `owner-only` directory entries. So `isAgentPubkey` returned false for
exactly the agents the previous commit started offering: with "Keep addressed
agents active" enabled, the authored mention was dropped from
`explicitAgentPubkeys` and the agent was not retained as the persistent
audience after sending.

Offering an identity in autocomplete and then not treating it as an agent once
addressed is the silent-mention failure block#1611 set out to prevent, reintroduced
one layer down.

`getOwnedMemberAgentPubkeys` derives the same ownership rule as
`isAgentIdentityInManagedList`, so eligibility and downstream classification
are computed from one place and cannot drift apart.

Reported by Codex review on block#4204.

Signed-off-by: Ian Maurice <ian@ia-n.ca>
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