Desktop: an agent on respond_to=allowlist is hidden from its own owner, while owner-only shows it
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
The mention picker admits the owner of an `owner-only` relay agent, but it does not admit the owner of an `allowlist` relay agent. The harness admits the owner in both modes. So widening an agent's policy from `owner-only` to `allowlist` removes that agent from its own owner's autocomplete.
Both branches live in the same function, eight lines apart.
## The two sides
**Harness** — `crates/buzz-acp/src/lib.rs:249-257`. The owner is accepted in both modes; `Allowlist` only adds the external pubkey list on top:
```rust
match respond_to {
RespondTo::Anyone => true,
RespondTo::Nobody => false,
RespondTo::OwnerOnly => is_owner_or_sibling(author, owner_cache, rest_client).await,
RespondTo::Allowlist => {
allowlist.contains(author)
|| is_owner_or_sibling(author, owner_cache, rest_client).await
}
}
```
The doc comment on the same function (`lib.rs:218-222`) states it as the intended rule, not as an accident:
> Both `OwnerOnly` and `Allowlist` accept the owner and same-owner siblings; `Allowlist` additionally accepts the explicit external pubkey list.
Startup logging treats owner matching under `allowlist` as a feature that can be *missing* (`lib.rs:2029-2034`):
> respond-to=allowlist but no owner is set — allowlisted pubkeys will still be accepted, but owner-based matching is unavailable until owner is resolved.
**Client** — `desktop/src/features/agents/lib/agentAutocompleteEligibility.ts:24-36`. The `owner-only` branch resolves the owner. The `allowlist` branch reads the array literally and never looks at `agent.ownerPubkey`:
```ts
if (
agent.respondTo === "owner-only" &&
normalizedCurrentPubkey &&
agent.ownerPubkey
) {
return normalizePubkey(agent.ownerPubkey) === normalizedCurrentPubkey;
}
if (agent.respondTo === "allowlist" && normalizedCurrentPubkey) {
return agent.respondToAllowlist
.map((pubkey) => normalizePubkey(pubkey))
.includes(normalizedCurrentPubkey);
}
```
## Two results
**1. Widening the policy hides the agent.** An owner who moves an agent from `owner-only` to `allowlist`, to let one more person in, loses the agent from their own picker. The agent still answers them — the harness accepts the owner — but the owner can no longer select it, and a typed mention is not tagged.
**2. An empty allowlist hides the agent from everybody, including the owner.** `respond_to=allowlist` with an empty `respond_to_allowlist` is accepted by the harness for the owner, and hidden by the client from all users. Nothing reports an error.
## Who is affected
`getMentionableAgentPubkeys` seeds its set from `managedAgentPubkeys` unconditionally (`agentAutocompleteEligibility.ts:76-78`), so an agent this Desktop runs stays mentionable whatever its `respond_to` is. The affected population is agents the viewer owns but does not run under this Desktop: headless agents, and agents running on the owner's other machine.
## Steps to reproduce
1. Run an agent outside Desktop with `--respond-to allowlist` and an allowlist that does not contain the owner's own pubkey. `BUZZ_AUTH_TAG` / `--agent-owner` is set, so the harness knows its owner.
2. Open Desktop as that owner, in a channel the agent is in.
3. Type `@` and look for the agent.
Expected: the agent is offered, because it answers the owner.
Actual: the agent is not offered. Setting the same agent to `--respond-to owner-only` makes it appear.
## Test coverage
`agentAutocompleteEligibility.test.mjs:104` covers the allowlist branch for a viewer in the array and for a viewer not in the array. The negative fixture has no `ownerPubkey` field, so the owner case is not pinned in either direction — it is uncovered rather than intended.
## Not a duplicate
- **#3125** — the viewer *is* in the array and is dropped by the managed-list gate from #2149. Different mechanism, and the viewer there is not the owner.
- **#3030** — the saved allowlist differs from the running one. That is persistence; this is the eligibility read of a correct record.
## On a fix
The `allowlist` branch can accept the viewer when `agent.ownerPubkey` matches, mirroring the branch above it. Two limits are worth stating with it:
- Where the owner does not resolve, the branch must keep failing closed. On closed relays the NIP-OA attestation often does not materialize (#4223, relay-side fix in #5581), and the same gap already affects `owner-only`.
- Sibling parity is not needed here. `is_owner_or_sibling` also accepts same-owner sibling *agents*, but the picker's `currentPubkey` is the human viewer, so only the direct owner check applies.
Until the client matches the harness, putting the owner's own hex into `respond_to_allowlist` is the reliable configuration.
Verified on `main` at `93114c9c`.
Contributor guide
Research direction
Read desktop/src/features/agents/lib/agentAutocompleteEligibility.ts, especially the owner-only and allowlist branches, then inspect agentAutocompleteEligibility.test.mjs around line 104. Add coverage for an owner whose pubkey is absent from the allowlist, and ensure the owner is offered while unresolved owners still fail closed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, typescript
- Domain
- desktop, frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100