fix(desktop): keep owned in-channel agents mentionable across machines - #4204
fix(desktop): keep owned in-channel agents mentionable across machines#4204NuCl34R wants to merge 2 commits into
Conversation
An agent whose process runs on another machine is unmentionable from every machine except the one hosting it, even while it sits in the channel answering mentions (block#2508, block#3739, block#2349). Two gates drop it, and a fix to either one alone is not enough: - `isAgentIdentityInManagedList` rejects any agent absent from *this* machine's managed list. - `shouldHideAgentFromMentions` then reads the agent's kind:10100 entry as an explicit not-invocable signal. `getMentionableAgentPubkeys` only admits `respond_to: anyone` or an allowlist naming us, so an agent on the default `owner-only` (`RespondTo::default()`) never lands there. block#1243 set out to "scope mention/add autocomplete to reachable identities" and described an eligible agent as "my managed/owned agent", but only the managed list was ever consulted. This restores the missing half, scoped to what actually proves reachability: channel membership (it is here and answering) + ownership (owner-only accepts its owner by definition) Membership is the evidence, ownership the permission, and neither depends on which machine hosts the process. Ownership is not self-declared: `Profile.ownerPubkey` comes from a NIP-OA attestation verified against the agent's own pubkey, and `MembersSidebar` already gates member-card actions on exactly this comparison (`viewerIsOwner`). Deliberately unchanged: - Owned agents that are NOT channel members stay hidden. A profile-only agent we own has no evidence of running anywhere; block#1243 hides it on purpose and the `mira` e2e fixture pins that. - Online presence is not used as a criterion. It would also surface other people's agents, which block#1611 set out to prevent. `isAgentMentionEligible` consolidates the two predicates in one place. They encode a single policy but are order-dependent: the managed-list gate can reject a candidate before the invocability gate applies its "invocable => show" rule, so composing them at the call site invites applying only half the policy. The e2e regression drives the rendered autocomplete rather than the predicate, so it stays honest if `useMentions` stops routing candidates through the policy: `nadia` is an in-channel, viewer-owned, `owner-only` relay agent absent from `managedAgents`. It fails at base 3d7712c and passes here. Refs block#3739, block#2508, block#2349. Signed-off-by: Ian Maurice <ian@ia-n.ca>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee2e09ba37
ℹ️ 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".
| !isAgentMentionEligible({ | ||
| candidate: { ...candidate, pubkey }, | ||
| currentPubkey, | ||
| directoryAgentPubkeys, | ||
| managedAgentPubkeys, | ||
| mentionableAgentPubkeys, |
There was a problem hiding this comment.
Classify newly eligible owned agents as agents downstream
When this branch admits an in-channel, viewer-owned owner-only agent, its pubkey is still absent from knownAgentPubkeys because that set remains an alias of mentionableAgentPubkeys, which excludes owner-only directory entries. Consequently mentions.isAgentPubkey returns false in useMentionSendFlow, so with “Keep addressed agents active” enabled the authored mention is omitted from explicitAgentPubkeys and is not retained as the persistent agent audience after sending. Include the ownership-eligible member pubkeys in the downstream agent-classification set as well as in autocomplete.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed and fixed in 7605893.
You are right that the two sets had drifted: autocomplete started offering an in-channel, viewer-owned owner-only agent while downstream classification still read a set that never admits owner-only directory entries. Traced it through knownAgentPubkeys → isAgentPubkey → explicitAgentPubkeys and reproduced the reasoning end to end.
Worth naming why this mattered beyond the reported symptom: offering an identity in the picker and then not treating it as an agent once addressed is the same silently-lost mention that #1611 set out to prevent, reintroduced one layer down.
Rather than adding the pubkeys in a second place, the fix derives them from the same ownership rule as the eligibility predicate (getOwnedMemberAgentPubkeys), so autocomplete and downstream classification are computed from one source and cannot drift apart again.
Desktop suite 3921/3921, typecheck and lint clean, and the mentions/team-mentions/channels e2e specs still green.
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
An agent whose process runs on another machine is unmentionable from every machine except the one hosting it, even while it sits in the channel answering mentions. Reported in #2508, #3739 and #2349.
Two gates in
useMentionsdrop it, and fixing either one alone leaves the default configuration broken:isAgentIdentityInManagedListrejects any agent absent from this machine's managed list. A remote agent always fails here.shouldHideAgentFromMentionsthen reads the agent's kind:10100 entry as an explicit not-invocable signal.getMentionableAgentPubkeysonly admits relay agents whoserespond_toisanyoneor an allowlist naming the current user, so an agent left on the defaultowner-only(RespondTo::default(),crates/buzz-acp/src/config.rs) never lands there.Replaying both gates against an
owner-onlyin-channel agent owned by the viewer and present in the directory:What changed
#1243 set out to "scope mention/add autocomplete to reachable identities" and described an eligible agent as "my managed/owned agent", but only the managed list was ever consulted. This restores the missing half, scoped to what actually proves reachability:
Membership is the evidence, ownership the permission, and neither depends on which machine hosts the process.
Ownership is not self-declared.
Profile.ownerPubkeyis derived from a NIP-OA attestation verified against the agent's own pubkey (profile_valid_oa_owner_pubkey,desktop/src-tauri/src/nostr_convert.rs), andMembersSidebaralready gates member-card actions on exactly this comparison (viewerIsOwner). An agent cannot claim an owner it does not have.isAgentMentionEligibleconsolidates the two predicates in one place. They encode a single policy but are order-dependent: the managed-list gate can reject a candidate before the invocability gate applies its "invocable => show" rule, so composing them at the call site invites applying only half the policy. This also addresses the gate-ordering inconsistency raised in the review of #4047.Deliberately unchanged
mirafixture pins that; this PR keeps it passing.MembersSidebaris untouched. Its add-member autocomplete excludes existing members by construction, so a membership-scoped rule would be dead code there.Tests
e2e regression (
desktop/tests/e2e/mentions.spec.ts) drives the rendered autocomplete rather than the predicate, so it stays honest ifuseMentionsever stops routing candidates through the policy.nadiais an in-channel, viewer-owned,owner-onlyrelay agent absent frommanagedAgents.Reproduced on both revisions:
3d7712c: fails — the dropdown never appears for@nadiaUnit (
agentAutocompleteEligibility.test.mjs): 11 added cases covering both gates and the consolidated policy — owned member accepted, owned non-member still hidden, other-owner member still hidden, pubkey normalization, ownership branch inert withoutcurrentPubkey, and both gates applied rather than just the managed-list one.Full suite:
pnpm test3919/3919,pnpm typecheckclean,pnpm lintno new findings.e2e:
mentions,team-mentionsandchannelsare green (124), reproduced across two runs. The rest of--project=smokeshows a handful of failures on this machine (composer-selection-formatting,messagingavatar,onboarding-agent-defaultsconcurrency,thread-focus-mode,relay-reconnect, and a hover tooltip inmentions.spec.ts). They reproduce on unmodified3d7712cand the set changes between runs, so they look like local environment flakiness rather than anything this branch introduces. Worth a second opinion from CI.