Skip to content

fix(desktop): keep owned in-channel agents mentionable across machines - #4204

Open
NuCl34R wants to merge 2 commits into
block:mainfrom
NuCl34R:fix/owned-agents-mentionable-across-machines
Open

fix(desktop): keep owned in-channel agents mentionable across machines#4204
NuCl34R wants to merge 2 commits into
block:mainfrom
NuCl34R:fix/owned-agents-mentionable-across-machines

Conversation

@NuCl34R

@NuCl34R NuCl34R commented Aug 1, 2026

Copy link
Copy Markdown

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 useMentions drop it, and fixing either one alone leaves the default configuration broken:

  1. isAgentIdentityInManagedList rejects any agent absent from this machine's managed list. A remote agent always fails here.
  2. shouldHideAgentFromMentions then reads the agent's kind:10100 entry as an explicit not-invocable signal. getMentionableAgentPubkeys only admits relay agents whose respond_to is anyone or an allowlist naming the current user, so an agent left on the default owner-only (RespondTo::default(), crates/buzz-acp/src/config.rs) never lands there.

Replaying both gates against an owner-only in-channel agent owned by the viewer and present in the directory:

gate 1 (isAgentIdentityInManagedList) passes : true
gate 2 (shouldHideAgentFromMentions) hides   : true
=> visible in autocomplete: false

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:

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 is 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), and MembersSidebar already gates member-card actions on exactly this comparison (viewerIsOwner). An agent cannot claim an owner it does not have.

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. This also addresses the gate-ordering inconsistency raised in the review of #4047.

Deliberately unchanged

Tests

e2e regression (desktop/tests/e2e/mentions.spec.ts) drives the rendered autocomplete rather than the predicate, so it stays honest if useMentions ever stops routing candidates through the policy. nadia is an in-channel, viewer-owned, owner-only relay agent absent from managedAgents.

Reproduced on both revisions:

  • base 3d7712c: fails — the dropdown never appears for @nadia
  • this branch: passes

Unit (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 without currentPubkey, and both gates applied rather than just the managed-list one.

Full suite: pnpm test 3919/3919, pnpm typecheck clean, pnpm lint no new findings.

e2e: mentions, team-mentions and channels are green (124), reproduced across two runs. The rest of --project=smoke shows a handful of failures on this machine (composer-selection-formatting, messaging avatar, onboarding-agent-defaults concurrency, thread-focus-mode, relay-reconnect, and a hover tooltip in mentions.spec.ts). They reproduce on unmodified 3d7712c and the set changes between runs, so they look like local environment flakiness rather than anything this branch introduces. Worth a second opinion from CI.

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>
@NuCl34R
NuCl34R requested a review from a team as a code owner August 1, 2026 23:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +249 to +254
!isAgentMentionEligible({
candidate: { ...candidate, pubkey },
currentPubkey,
directoryAgentPubkeys,
managedAgentPubkeys,
mentionableAgentPubkeys,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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 knownAgentPubkeysisAgentPubkeyexplicitAgentPubkeys 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>
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.

1 participant