Hide non-invocable member agents from @-mention autocomplete - #1611
Conversation
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
left a comment
There was a problem hiding this comment.
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.
…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.
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>
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>
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:Fizz is added to the channel as a member by its owning team, so
!candidate.isMemberis false, the check is skipped, and Fizz always appears — even though the backend (author_allowedinbuzz-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):
mentionableAgentPubkeys)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.
useMentionsbuilds adirectoryAgentPubkeysset fromrelayAgentsQuery.datato supply that signal.Tests
Adds unit tests in
agentAutocompleteEligibility.test.mjscovering every branch, including both member cases (Fizz hidden; unknown-invocability shown) and npub/hex pubkey normalization.node --test→ 17/17 passbiome check(3 changed files) → cleantsc --noEmit→ 0 errors