Skip to content

fix(desktop): make cross-owner channel-member agents mentionable - #4716

Open
oleg-ai-dev wants to merge 1 commit into
block:mainfrom
oleg-ai-dev:fix-cross-owner-agent-mentions
Open

fix(desktop): make cross-owner channel-member agents mentionable#4716
oleg-ai-dev wants to merge 1 commit into
block:mainfrom
oleg-ai-dev:fix-cross-owner-agent-mentions

Conversation

@oleg-ai-dev

Copy link
Copy Markdown

Problem

A bot agent that is a member of a channel shared between two different Buzz identities can never be @-mentioned (or manually addressed via a typed mention) by anyone except the human who owns/hosts it — even when the agent's respond_to is anyone. Reported and reproduced independently across desktop v0.4.20–v0.5.3, macOS/Windows, hosted/self-hosted relays: #3776 (canonical, most precise), #3277 (original report + a documented client-side stopgap), #2349 (long thread with the business case for team use), #2950.

Heads up to reviewers: I'm aware this issue already has a large number of open PRs attempting the same fix. I looked through the ones I could find before opening this — none of the ones I checked source respond_to/respond_to_allowlist from the correct (instance-tier) fields (see Cause 2 below), which as far as I can tell would leave the exact same bug at a different codepath if merged as-is. Flagging that here explicitly in case it's useful for triage, independent of whether this particular PR is the one that ends up merged.

Root cause (two independent, stacked causes — both are required to fix this)

Cause 1 — the mention picker drops any agent you don't personally manage. isAgentIdentityInManagedList (in agentAutocompleteEligibility.ts) only admits a candidate if it isn't flagged as an agent, or if its pubkey is in the viewer's own managedAgentPubkeys — built solely from the local list_managed_agents Tauri command. An agent owned by the other identity never enters that set (by design — managed-agent records hold device-local secrets and are never minted from a relay event). So the other person's agent is dropped in useMentions.ts before shouldHideAgentFromMentions / relayAgentIsSharedWithUser ever run — including for a manually typed mention, since extractMentionPubkeys only resolves p tags from the already-filtered candidate list.

Cause 2 — the eligibility check reads an event kind nothing publishes. Even with Cause 1 fixed, relayAgentIsSharedWithUser reads channelIds/respondTo/respondToAllowlist from a RelayAgent sourced from list_relay_agents (agent_discovery.rs), which queries the relay for kind:10100. Nothing in the codebase publishes kind:10100 as an agent profile (the only writer is buzz channels set-add-policy, which writes unrelated channel_add_policy content). Agent identity/config is actually published as kind:0 + kind:30177 (managed_agents/agent_events.rs), which the eligibility code never reads — so the RelayAgent it needs is always empty, and relayAgentIsSharedWithUser always returns false.

Note: the agent-side authorization logic is already correct and needed no change — author_allowed in buzz-acp already permits any author for RespondTo::Anyone outside DMs. The agent would answer if it could be addressed; it just could never produce a mention.

Fix

  • Cause 1: thread the already-computed mentionableAgentPubkeys set (which already accounts for relay-directory sharing) into the isAgentIdentityInManagedList gate, so a real channel member is admitted when it's shared, while relay-directory-only (non-member) agents stay hidden exactly as before. Same relaxation applied to extractMentionPubkeys so a p-tag is actually emitted for a manually typed mention of such an agent.
  • Cause 2: repoint list_relay_agents (agent_discovery.rs) from kind:10100 to kind:30177, and resolve channel membership via a kind:39002 #p query — the same pattern buzz-acp's own channel-discovery code already uses. The agent's pubkey is read from kind:30177's d tag (not the event's own pubkey, since kind:30177 is a parameterized-replaceable event published by the owner). respond_to/respond_to_allowlist are sourced from the record's instance-tier runtime fields, not the definition-tier fields — the snapshot this event is built from carries both, and they can diverge in practice (the running harness is spawned from instance-tier fields). Sourcing from definition-tier would ship a policy the running agent doesn't actually enforce (agent visible but silent, or answering but hidden).

Test plan

  • Added/updated unit coverage in agentAutocompleteEligibility.test.mjs for the new gate logic, including the two cases that must stay hidden: a non-member agent that's only relay-directory-listed, and a channel member with an explicit not-invocable directory entry.
  • Added an e2e test in mentions.spec.ts mirroring the exact reported repro: identity A creates an agent with respond_to: anyone, adds it as a bot member of a channel shared with identity B; identity B can now see and use the mention, and a non-member/non-invocable agent stays hidden.
  • Verified manually end-to-end against a local dev relay with two separate identities, and separately against a real hosted community with two independent machines/humans.
  • cargo fmt --all -- --check and cargo fmt (Tauri crate) clean.
  • cargo clippy --all-features -- -D warnings reports zero issues in either file this PR touches (agent_discovery.rs, nostr_convert.rs). Note: the same command surfaces ~30 pre-existing warnings/errors in unrelated files on a clean origin/main checkout under Windows + --all-features (confirmed by checking out main and re-running) — those are not introduced by this change and are left untouched per the "no drive-by fixes" guidance in CONTRIBUTING.md.
  • Full desktop unit suite: 4131/4131 passing. tsc --noEmit clean. biome check clean on all changed files.

Closes #3776. Related: #3277, #2349, #2950.

Copy link
Copy Markdown

@oleg-ai-dev @dexsynccom @anindyar, after comparing all three patches, I think each contributes part of the best fix. #4714 keeps the mention change narrow. #4716 correctly identifies the instance-tier policy and adds the cross-identity E2E proof this bug needs. #4713 recognizes that runtime discovery should be signed by the agent itself. I would preserve all three contributions.

I still don’t think #4716’s kind:30177 path is safe as written. The event is owner-signed, while the agent pubkey comes from an unverified d tag. It also represents managed configuration, not necessarily what the running harness currently enforces.

My preferred combination is #4714’s mention fix, #4716’s policy handling and E2E coverage, and an agent-signed kind:10100 runtime profile. Channel membership remains authoritative in kind:39002.

kind:10100 then needs one publisher to own the complete document; the harness and set-add-policy cannot safely publish partial replacement bodies independently.

If the kind:30177 approach can prove the owner-to-agent binding and effective-runtime semantics, I’m happy to reconsider it.

@rmichelena

Copy link
Copy Markdown

Opened #5483 on the same layer, so this is a note from the one other PR that touches agent_discovery.rs rather than a competing claim — most of what's here I agree with, including the 39002 membership resolution and the instance-tier vs definition-tier point, which I hadn't seen stated anywhere else.

One factual correction on the premise for repointing, because it changes what the fix should be:

Nothing in the codebase publishes kind:10100 as an agent profile

True of Desktop's own code, but not of deployments. Headless buzz-acp seats publish their own kind:10100 — via buzz channels set-add-policy, operator scripts, or hand-rolled with nak. #2987 is a report from exactly such an operator, and the 0.5.9 follow-up on that thread documents a working record they publish by hand. #5546 and #5691 are open PRs to have the harness publish it too.

So repointing list_relay_agents from 10100 to 30177 doesn't remove a dead read — it trades one blind spot for the mirror-image one:

Agent 10100-seeded 30177-seeded (this PR)
headless seat, self-published 10100, no 30177 listed dropped
Desktop-managed 30177, never self-published dropped listed

The second row is the bug both of us set out to fix; the first row is the population #2987 is about. A headless agent has no Desktop, so nothing publishes a kind:30177 for it — after this PR those agents would disappear from autocomplete, which is the same symptom at a different codepath.

I've changed #5483 to read the union of both kinds, keyed by agent pubkey, then enrich with 39002. Worth flagging one thing I hit doing that, since it applies to any unfiltered 30177 read: once the query is no longer scoped by a known set of agent pubkeys, the d tag is an unverified claim about someone else's agent, so each record has to be checked against that agent's own NIP-OA owner (kind:0) before it can seed or override a directory entry.

Not asking you to close this — the union is a small change on top of what you already have, and if you'd rather carry it here I'm happy to close mine instead. The part I'd push back on either way is dropping 10100 as a source.

…ests

main's frontend mention gate (block#4913, block#6338) and backend list_relay_agents
sourcing already fix the original bug this branch targeted, so those
changes are dropped as superseded during this rebase. block#6338 added
coverage for the owner-only-build variant, but the default-build path for
a cross-owner relay agent with respond_to=anyone was still untested.

Decouple the default relay-agent-directory fixture (alice/charlie) from
the real human TEST_IDENTITIES pubkeys they coincidentally reused, extend
the relay-agent mock seed to also register real channel bot membership,
and add tests mirroring the exact reported repro: a bot member of a
shared channel, owned by another identity, with respond_to=anyone, must
be mentionable — and must stay hidden if it is only relay-directory-listed
without real channel membership.

Signed-off-by: oleg-ai-dev <oleg.ai.development@gmail.com>
@oleg-ai-dev
oleg-ai-dev force-pushed the fix-cross-owner-agent-mentions branch from 4a0c092 to 16ff686 Compare August 24, 2026 10:20
@oleg-ai-dev

Copy link
Copy Markdown
Author

Rebased this onto current main and resolved the conflict — worth flagging that the resolution changes this PR's scope significantly.

Going through the conflicts, I found that the frontend gate fix (commit 1) and the backend list_relay_agents sourcing fix (commit 3) are both now superseded by work that landed on main after this PR was opened:

  • Frontend: fix(desktop): allow shared agent mentions #4913 (merged 2026-08-05, the day after this PR opened) renamed isAgentIdentityInManagedListisAgentIdentityInAllowedList and wired it to consume getMentionableAgentPubkeys()'s output directly, which fixes the same "cross-owner shared channel-member agent isn't mentionable" bug this PR targeted — via a cleaner mechanism than what I had here. Fix cross-owner relay agent mentions in owner-only builds #6338 (2026-08-20) extended it further for owner-only builds. @wolfyy970 — this addresses your concern about the kind:30177 d-tag trust: getMentionableAgentPubkeys combined with the anti-forgery checks on main now cover it.
  • Backend: list_relay_agents on main now queries both kind:10100 and kind:30177 and unions them by pubkey, enriched via kind:39002, with ownership verification on the 30177 d-tag claim. @rmichelena — this looks like exactly the union approach from fix(desktop): enrich relay agent directory for @mention eligibility (#5363) #5483; repointing to 30177-only (what I had here) would have reintroduced the headless-agent regression you flagged, so I dropped that commit rather than force it through.

Both of those commits became empty diffs once rebased onto main and were skipped. What's left is regression-test coverage: going through main's existing e2e suite, I found #6338 tests the owner-only-build variant of a cross-owner respond_to=anyone relay agent, but the default-build path for that exact scenario (the original reported repro) wasn't covered anywhere. Added that here — two Playwright tests plus the mock-bridge plumbing (real channel membership for relay-agent seeds) needed to exercise it.

CI status locally: tsc --noEmit, biome check, cargo check, and the full unit suite (5,397 tests) all pass.

Given main already ships the fix, happy to close this in favor of the tests-only diff being cherry-picked elsewhere if that's preferred — let me know.

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.

Cross-owner agent mentions are impossible: candidates filtered by local ownership, and eligibility reads a kind (10100) nothing publishes

3 participants