fix(desktop): authorize remote mentions at publication - #7124
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Reviewed f3ad8ad2d202047da1170516ba8d072ecec777e0..7ffead0f8ae128782991153054abd388e4ff04d9 (exact head 7ffead0f8ae128782991153054abd388e4ff04d9).
Risk: high — this changes mention authorization, membership mutation, publication recipients, async cancellation, and draft recovery across renderer→Tauri→relay boundaries.
Behavior/contracts traced: owned relay-agent discovery vs local custody; preparation-time invitation eligibility vs publication-time destination membership; channel/thread/new-DM destination binding; member/bot adds; notifying p recipients vs reference-only mention tags; edits and media continuation; Escape/navigation/unmount/replacement cancellation; cross-visit draft ownership and buffered timeline catch-up.
Findings: no blocking or non-blocking code/product defects found. Fresh relay evidence and fresh local-runtime evidence authorize only their own identities; unavailable directories fail closed. Selected recipients are not silently discarded. Final validation binds the eventual destination, while reference-only send strips notification recipients but retains textual references. Cancellation and late failure cannot revive abandoned intent or overwrite a newer/explicitly cleared draft.
Author action: none.
Verification owner: CI/release gate for the remaining broad/package-native confidence gaps; no author rework.
Validation at the reviewed exact head:
- Independent systems lane: desktop typecheck passed; 87 focused revalidation/draft/edit/cancellation tests passed.
- Independent product/UI lane: 124 focused unit tests and 18 isolated production-seam Playwright tests passed, including invite/pending/failure, policy and membership revocation, add/publish failures, both DM variants, navigation/unmount, duplicate action, reference-only/media, and cross-thread/cross-visit recovery.
- Additional reviewer run: the full Desktop JS test command completed with 5,882/5,882 passing.
- GitHub exact-head checks are successful: Desktop Core, all four Desktop Smoke E2E shards, Desktop E2E Relay, both Desktop E2E Integration shards and aggregate, macOS build, Semgrep, zizmor, and DCO. PR remains mergeable at the pinned base/head.
Manual/native evidence: reviewed the supplied invite, pending, and authorization-error captures; the distinct actions and failure state are clear. No independent packaged Tauri/hardware journey was completed.
Residual risk: one independent broad Desktop run timed out/interrupted without an observed assertion failure, and the first Playwright attempt collided with another reviewer server before the isolated rerun passed. Packaged-native behavior was not independently witnessed. Exact-head CI plus the isolated production-seam coverage owns those confidence gaps; neither establishes an author-actionable defect.
— :bot: Jude’s code review agent
jedwards27
left a comment
There was a problem hiding this comment.
Review verdict: approve
Reviewed the exact live head 7ffead0f8ae128782991153054abd388e4ff04d9 and integrated both assigned review lanes. No author-actionable defect remains.
The change closes the publication boundary correctly: remotely supplied mentions are re-authorized before publication rather than trusting stale draft state, while the existing local/exact-recipient behavior remains intact. Both independent lanes reported clear.
Verification
- Full Desktop package suite at this exact head: 5,882/5,882 passed.
- Focused changed-path evidence: 124/124 passed.
- Browser E2E evidence: 18/18 passed.
- Mutation/regression proof exercised the authorization fence and failed when the protective behavior was removed.
Residual native/Tauri and live-relay observation is a reviewer/release confidence gap, not author rework.
— :bot: Jude’s code review agent
f3ad8ad to
ae23c1c
Compare
7ffead0 to
1144465
Compare
|
@jedwards27 — requesting renewed formal review of exact published head This is the independently scoped-approved routing integration: seven original patches replay unchanged; two reconciliations preserve root's generic publication-error toast alongside authorization errors and preserve programmatic editability updates. Two added production-hook tests require the generic error, recovered draft and released pending state for normal and queued-media publication. No other source change was made during publication. Validation/provenance is detailed in the updated PR body: 5,995 Desktop / 42 focused / 22 isolated mock-IPC browser + 1 voice-note tests passed on the integration candidate before its semantics-preserving formatting amendment; independent AST comparison plus 4 fresh final-head assertions cover that amendment. Final publication typecheck, amended-test formatting and diff checks pass. These are not live-native/relay certification, and no new full No inline review threads or author-actionable comments existed at publication. The two older informational screenshot comments were minimized as outdated while keeping the evidence linked in the body and retaining their audit history. Formal reviews were not modified. |
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Reviewed 3a56d17824522580fe04cae463b54f4c7ba66021..1144465d00273cf74b7c22544ae5a3299bd98560 (exact head 1144465d00273cf74b7c22544ae5a3299bd98560). This supersedes my prior approval at 7ffead0f8ae128782991153054abd388e4ff04d9.
Risk: high — mention authorization, membership mutation, publication recipients, async cancellation, and draft recovery cross renderer→Tauri→relay boundaries.
Behavior/contracts traced: reconciliation onto the newly approved discovery root; preparation eligibility vs publication-time membership; channel/thread/new-DM destination binding; member/bot adds; notifying recipients vs reference-only tags; generic vs authorization errors; edit/media continuations; Escape/navigation/unmount/replacement cancellation; cross-visit draft authority; buffered timeline catch-up.
Findings: no blocking or non-blocking code/product defects found. Range-diff preserves the prior routing/recovery series; the meaningful reconciliation delta keeps the base branch's generic formatMessageSendError behavior while retaining the specific authorization error. Publication re-fetches selected identities for the eventual destination, unavailable evidence fails closed, and selected recipients are not silently discarded. Reference-only send remains distinct from Invite and Cancel. Late cancellation/failure cannot revive abandoned intent or overwrite a newer or explicitly cleared draft.
Author action: none.
Verification owner: CI/release gate for completion of any still-running required jobs and any desired packaged-native/live-relay observation; no author rework.
Validation at the reviewed exact head:
- Two independent full Desktop JS runs passed
5,995/5,995; exact-head E2E app build passed. - Isolated
remote-owned-mentions.spec.tspassed18/18, covering revocation/no-send, invite/reference-only, delayed add/publish cancellation, A→B→A draft authority, media, and catch-up routing. - Additional focused rerun passed
42/42cancellation/recovery/publication-error tests. git diff --checkand review preflight passed; base/head remained exact, clean, mergeable, with no unresolved review threads.- At submission, relay/integration E2E, both integration shards, macOS and Windows builds, Semgrep, zizmor, and DCO were green; Desktop Core and four smoke shards were still running, with no failed required gate.
Manual/native evidence: source and renderer/browser evidence cover the visible states and production renderer flow. No independent packaged Tauri/live-relay native authorization journey was completed for this changed head.
Residual risk: browser tests use mock IPC and therefore do not independently certify the live Tauri relay-directory query. Initial focused Playwright attempts collided with an unrelated process on port 4173; the isolated port 4184 rerun passed. These are confidence/tooling gaps, not author-actionable defects.
— :bot: Jude’s code review agent
🤖 ## Summary An agent you own could be missing from **New message → To:** and **Channel members → Add people and agents** on a machine that has never managed it. This PR lets those existing lists find your agent without requiring a shared channel first. Desktop now checks records proving you own it, rather than looking only at agents in channels you've already joined. **No new screen or control is added.** For example, an agent with verified ownership and **Who can send instructions → Only me (default)** can now appear even with no shared channels. Each screen still applies its existing access rules; this does not make every discovered agent selectable everywhere. | Screen / control | Before | After this PR alone | | --- | --- | --- | | **New message → To:** recipient picker | An owned agent absent from this machine and shared-channel bot lists could be missing. | Its named **agent** row can appear; selecting it adds a recipient chip. This is recipient selection, not a guarantee that a later message will reach or wake the agent. | | **Channel members → Add people and agents** | The same agent could be missing from **Not in this channel** search results. | Its row can appear with the existing **Add** button. If you can add members, that button submits the existing channel-membership request; finding the row alone changes no membership. | | **Stream / forum composer → @ suggestions** | An owned agent already in the channel under an ordinary member role could be missing from agent suggestions. | Its actual membership is recognized without requiring the bot role. Agents not managed on this device still need membership in that channel. | | **Pulse → Agents** | An agent absent from both local management and the server's agent list was omitted from the count and author lookup. | The count and feed's author lookup can include it; notes appear only if it has published them. | Being listed does **not** mean the agent is online, add it to a channel, or grant local Start/Edit controls. For agents not managed on this device, global **Search** still excludes those configured for “Only me”, and DM @ selection is not added here. DM @ selection and message-driven nonmember invitation are addressed in [#7124](#7124); the standalone forum **Invite / Cancel** flow is in [#7125](#7125). <details> <summary>Ownership and membership checks</summary> A discovery lead is not proof: the latest agent profile must have a valid signature and exactly one valid ownership attestation—the owner's signed link to that agent. Its response policy must be signed by that verified owner; an invalid latest policy cannot restore an older permission. Membership comes separately from the latest server-signed roster, including removals. Existing profile cards, owner labels and agent-avatar shapes also use this stricter verification: malformed or forged evidence must not supply ownership/agent classification on its own. Valid ownership was already recognized; no profile-picture or badge design changes. Attestation time conditions apply to the signed event's timestamp, not a live expiry timer. Existing legacy compatibility and builds requiring verified owner policy retain their respective rules. Discovery and sending remain separate operations, not an atomic permission check. </details> ### Review corrections - When runtime and owner policy overlap, **explicit online/away/offline from the verified latest runtime is retained**. Policy still supplies ownership/permissions; claimed runtime membership is not restored. Missing/unrecognized status stays unknown, and invalid latest policy cannot revive runtime permissions. - Discovery without runtime evidence is now **unknown**, not offline: native conversion, both IPC adapters, Pulse, Projects and profile/session consumers preserve that distinction. Unknown has no status dot and is not promoted to a deployed/running agent. - Both relay-only picker paths retain the authenticated owner, including the existing **managed by you** label. The analogous global Search projection is fixed without changing its existing “anyone” filter. - Authorized stored profile activity remains visible when liveness becomes unknown/absent or the active turn ends. History reads do not start a live subscription, grant access, or imply current availability. ### Related issue Independent base: `main`. Child: [#7124](#7124), then [#7125](#7125). Extracted from [#7114](#7114), retained as historical source (`98fe33ec`). [Behavior contract](https://github.com/block/buzz/blob/3a56d17824522580fe04cae463b54f4c7ba66021/docs/owned-agent-discovery.md). Originating [Buzz discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848) · channel `f7a9536a-1738-4bad-a888-b3ea25010ef1`. ### Testing Current candidate: `3a56d17824522580fe04cae463b54f4c7ba66021`, a four-file native/test/doc runtime-status repair atop published `ae23c1c9680a881cee7eed94e259bf15bf8ce3f7`. Branch ancestry is main `1c8321cd08feb597f8bcff5195c21148fb3e98ed`; refreshed main `0e878664b08cdf7fb2d89d940bc2aa92cdc485f7` adds only the independent CI-workflow split. Read-only mergeability succeeds; this is not a tested merged-tree claim. **Local CI attempt and continuation (not an uninterrupted green run):** the new exact-head `just ci` passed formatting/static checks, workspace and Tauri clippy, workspace Rust tests, **5,910 desktop tests**, desktop production build and Tauri check. Its native main target finished **3,073 passed / 1 failed / 19 ignored** (exit 101): `cheap_discovery_reports_absent_before_any_forced_probe` saw a process-global login-shell counter of 2 instead of 0. The counter includes unrelated version/adapter probes whose tests do not hold the failed test's PATH mutex; no managed-agent discovery implementation changed in the runtime repair. The unchanged failing test then passed **three isolated invocations**. Only the failed native workspace lane was retried with `RUST_TEST_THREADS=1 just desktop-tauri-test`: **3,074 main-target tests passed / 19 ignored**, all additional workspace targets passed (exit 0). The previously unrun `just web-build mobile-test` tail then passed (exit 0; **2,019 mobile tests**). Earlier successful lanes were reused; no source/guard changes or blanket CI rerun. The original failure and all diagnostic/retry logs are retained. - **71 native `nostr_convert` tests pass**, including seven new production merge regressions: online/away/offline, missing/invalid status, policy-only, status-less latest replacement and forged latest replacement. Before production repair, those seven yielded **4 failures / 3 passing controls**. - Reused frontend evidence from `ae23c1c9` (frontend is unchanged): Desktop TypeScript and isolated E2E build pass; **9 browser tests / 0 retries**, covering both relay-only picker journeys and seven adjacent stop-control regressions. Real UI with mock Tauri IPC, not live relay/native webview. - Earlier `ae23c1c9` local `just ci` passed without failures, including 3,067 native main-target tests / 19 ignored and 2,019 mobile tests; not substituted for the new source gate above. - Reused unchanged repair evidence: **17 real-store/hook history regressions**, **161 focused tests**, and independent **9 mounted owner/bot/identity revocation/regrant transitions** with zero hook-phase native calls. The regression was falsified before repair (14 failures, 3 controls). - Signed local-server fixtures cover discovery with no local/shared record, ordinary-role membership, forged ownership, invalid signatures, duplicate authentication, wrong-owner/latest-invalid policy, revoked membership and wrong destinations. These establish native data checks, not a live agent response. GitHub checks and renewed technical/security review must apply to the current published head; earlier-head green checks are not replacement-head proof. Local source review is not formal code-owner/latest-push approval or exact-range security authorization. A green security workflow with substantive review skipped is not security clearance. ### Screenshots #### Relay-only picker evidence — `ae23c1c9680a881cee7eed94e259bf15bf8ce3f7` These cropped rows come from the two real production picker journeys in [`owned-agent-discovery.spec.ts`](https://github.com/block/buzz/blob/ae23c1c9680a881cee7eed94e259bf15bf8ce3f7/desktop/tests/e2e/owned-agent-discovery.spec.ts), using mock Tauri IPC with **no local agents and no user-search duplicate**. The fixture supplies verified-owner data and unknown availability; the browser test checks its presentation, not native signature verification. Both exact-tip journeys pass without retries. No live relay, native webview, invitation, delivery or wakeup is claimed. Before the repair, both relay-only candidate constructors discarded the owner, so the existing “managed by you” label was absent. These are after-repair captures; no before image was captured. #### New Message → To The relay-only agent retains its authenticated owner label.  #### Channel members → Add people and agents The matching result retains “managed by you” beside the existing Add action; the test does not click Add or claim membership changed.  --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Pending base-change re-review: unchanged routing headThe remaining gate is the already requested automated review after #7122 merged and this PR was retargeted to The timeline records that exact-head approval 5092775370 was dismissed at 2026-09-02 20:18:58 UTC with “The base branch was changed.” The replacement request at 20:19:26 remains pending. All current checks are successful or legitimate path skips, and GitHub reports MERGEABLE, but REVIEW_REQUIRED/BLOCKED. This does not restore or bypass the dismissed approval. @jedwards27 — please complete the existing automated request against the current |
1144465 to
e66b949
Compare
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Reviewed ee883d73fb84752d26f52c6606ad3cadc9f12cc7..0b303f805e4420eae682087555075c5a7472e40e at exact head 0b303f805e4420eae682087555075c5a7472e40e.
Risk: high — publication-time authorization crosses renderer state, Tauri IPC, destination-scoped relay policy, cancellation, edit semantics, and draft recovery.
Changed-head reconciliation: range-diff against the prior approved stack found commits 2–12 patch-equivalent, patch 1 reconciled onto the new base, and one new patch adding a production-seam regression test for edit authority. The broad prior-head comparison is rebase noise rather than thousands of lines of new routing behavior. No production behavior changed after the stale approval.
Behavior/contracts traced: fresh exact-recipient revalidation immediately before publication; captured destination/thread binding; source-visit and authored-revision fencing; cancellation after relevant awaits; renderer → Tauri → relay selected-key propagation; relay-derived owner/policy and channel-membership filtering; edit snapshots and revalidation of exact selected agents plus typed recipients; notifying-versus-reference tag behavior; invite/reference-only/Cancel/error/retry; media/edit continuation; navigation, deletion, unmount, A→B→A recovery; keyboard/focus, wrapping, and catch-up.
Findings: no blocking or non-blocking code defect found. The new test observes intendedAgentPubkeys at the production edit seam and exact saved reference/notifying recipients. Removing that production argument made the test fail, establishing causal coverage.
Author action: none.
Validation at exact clean head:
- Full Desktop JS suite: 6,409/6,409 pass
pnpm typecheck: passpnpm build:e2e: pass- Focused authorization/edit/cancellation/recovery suite: 101/101 pass
- Production-renderer remote-owned mentions: 18/18 pass
- Focused production-built mention/remote-owned Playwright: 39/39 pass
- Causal mutation of edit
intendedAgentPubkeys: 0/1 pass as expected; restored tree passed and remained clean git diff --check: pass; all 13/13 commits carry sign-off- Live API head/base matched and GitHub reported the PR mergeable before submission
One initial broad three-spec Playwright attempt overcommitted the local preview server and ended in ERR_CONNECTION_REFUSED after 45 passes; the focused serial production-built rerun passed 39/39. This is local harness/resource noise, not a product failure.
CI/release evidence: macOS and Windows builds, relay/integration jobs, all four smoke shards, Semgrep, zizmor, and DCO are green. Desktop Core remained in progress at submission.
Manual/native evidence: packaged Tauri/live-relay interaction and direct assistive-technology observation were not independently rerun.
Residual risk / verification owner: CI owns terminal Desktop Core. Release/manual QA owns any packaged-native, live-relay rollout, and direct assistive-technology observation still required. These are confidence gaps, not author-actionable defects. Any head movement invalidates this approval.
## Summary In Desktop's standalone Forums, selecting an owned agent from another device could leave a post or reply unsendable if the agent had not joined the forum. This adds **Invite / Cancel** to the send flow so you can resolve membership without leaving your draft. - **Invite** checks response policy and your permission to add members, adds the agent to the forum, waits for refreshed membership, then rechecks authorization before posting to the original destination. Membership is forum-wide, not limited to one post. - **Cancel / Escape** keeps the text, attachments and selected recipients for retry. Unlike chat's **Do nothing / Send anyway**, this dialog has no reference-only send choice. Invite is disabled while pending; Cancel remains available. - Leaving the source post/reply cancels its pending invitation, even if you return. Errors remain visible, focus returns to the initiating editor when appropriate, and late completion cannot resume a cancelled post or interfere with a newer attempt. - A rejected send restores text, uploaded media and exact selected recipients to the source draft only if no newer edit, deletion, upload intent or send supersedes it. Clipboard verification settles before recipient capture, with stale edits/navigation fenced out. ### Related issue Targets `main` after #7124 merged. This PR reuses its publication checks and draft protection; the five forum commits have been replayed unchanged onto the merged parent. Owned-agent discovery (#7122) is already merged. Split from #7114. Forum creation/templates, channel-less Notes and local-agent management are unchanged. Duplicate-name binding from #7133 is already merged and retained by this stack. Inviting does not start a remote agent or promise that it is online or will reply. ### Testing Forum composer lifecycle tests, including clipboard-settlement cases, and TypeScript/changed-file formatting checks passed after the restack. Earlier invitation, focus and transport-recovery browser coverage is retained, not claimed as a fresh full browser run on this head. See [live CI](https://github.com/block/buzz/pull/7125/checks) for current-head results. Browser evidence uses mock IPC; no full local `just ci` pass or native/live-relay validation is claimed. To try it: open a forum post or reply, select an owned nonmember agent and send. Cancel, then retry without reselecting; Invite should add that agent before posting. Deny the add to check the visible error and retained draft. Navigate away/back during a pending invitation or rejected send; no stale publication or overwrite of a newer draft should occur.  *Earlier mock-browser capture, not current-head runtime proof. [Error and successful-post captures](#7125 (comment)); no before-state/native capture available.* **Limits:** cancellation cannot undo accepted membership changes or dispatched posts; authorization and publication are not atomic. Recovery is same-window, subject to browser storage limits, and is not a durable in-flight send journal: reload/crash can lose a pending snapshot. Cross-window coordination and in-flight upload custody are unchanged. The parent's legacy member-agent compatibility does not establish ownership for nonmember invitations. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…lock#7133) ## Summary Selecting two people or agents named Scout could replace the first recipient with the second even though the message still looked right. This binds each selection to its exact identity: the first keeps `@Scout`, and a conflicting selection gets `@Scout (<full public key>)`. Removing one no longer removes or redirects the other. - Reuse the existing **@ suggestion list**; team selection and automatic agent addressing reserve and reuse distinct labels too. Typing an ambiguous name manually shows an instruction to use the picker and preserves the draft without publishing—in chat, edits and standalone forums. - Share literal mention matching across recipient extraction, removal, display and editing so a shorter name cannot claim another recipient's longer or qualified label. - Rebuild authored references when a message is edited, and use the latest authorized snapshot when reopening or forwarding. Rendering/editing can recover qualified identities only from the message's recorded references, not from a key typed into its body. Unresolvable historical names remain literal rather than guessed recipients. - Wrap full-key labels within narrow/zoomed layouts while keeping their complete accessible label and ordinary mention icons. Edit activation waits for the action menu's focus cleanup before focusing the editor. ### Related issue Targets `main`; mention spacing (block#7128) is already merged. Split from block#7114. This is independent of the block#7124 → block#7125 remote-invitation stack and does not expand agent eligibility or invitation permissions. The separate Enter-selection suffix issue remains tracked in block#7253. ### Testing The desktop unit suite and focused mock-Chromium checks passed on the published integration candidate, including pending-paste selection, edit/forwarding, copy and narrow-layout cases; formatting, types and frontend builds passed. On `0b3b18c0`, 64 focused trust/paste/selection unit tests and six mock-Chromium tests passed with zero browser retries, including actual timeline chip copy → fresh channel paste → send and mismatched-key rejection; TypeScript, changed-file Biome and an isolated E2E build passed. The broader browser run had copy failures before the focused repairs and is not claimed as wholly green. See [live CI](https://github.com/block/buzz/pull/7133/checks) for current-head results. No full local `just ci` pass, native/live-relay or cross-browser validation is claimed. To try it: select two same-name recipients, remove one, send, then edit/reopen and forward; only the intended identities should remain. Type an ambiguous name without choosing a suggestion and check that sending retains the draft with an error. Inspect full-key labels in a narrow window at 150% text size, and open Edit and type immediately.   *Earlier mock-browser captures, not current-head runtime proof. No new before-state capture; screenshots alone do not prove recipient delivery.* **Clipboard trust:** generated full-key-qualified mentions now retain their exact recipient after copy/paste when the full key matches the clipboard record and community directory/profile state independently vouches for the base alias, including numeric collision suffixes. A qualifier alone does not establish trust. Arbitrary historical labels still cannot always be reconstructed. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Bad Janet <150b20bdf6130418df9239dd1bd082c71612c8d653b47c277200365b9be215dc@buzz> (cherry picked from commit ee883d7) Signed-off-by: nambse <sefa.esendemir@gmail.com>
🤖 ## Summary In Buzz Desktop, you could own an agent running on another device but be unable to mention it in a channel where it had not yet joined: it was filtered out before you could invite it. A selected agent could also disappear from the message's recipients when permissions changed. This lets you select an eligible agent in the existing **@ menu**, invite it from the message composer, and send to that agent—or see an error and keep your draft rather than silently sending without it. #### Where the experience changes | Screen / control | Before → after | | --- | --- | | A channel's **Message #…** composer, or a message's **Reply in thread to …** composer | Type `@` (or use the existing @ button), choose your agent, write the message and press **Send message**. An owned agent not yet in the channel can now reach the existing **“Mention people outside this channel?”** dialog when its response settings allow you to address it. | | That dialog's **Invite** button | Previously the membership requirement could block the agent before the invitation. Now Invite checks permission to add it, adds it as an agent member of the **channel** (not just the thread), then rechecks membership and response permission before sending the waiting message. An agent already in the channel needs no invitation. | | Existing direct message, or the new-message screen with the **To:** field | A mention is checked against the conversation the message will actually enter, including a newly created direct message, rather than the old or not-yet-created destination. This does not add an Invite control to direct messages. | | Editing a message / sending attachments | The selected agent remains part of the send or edit attempt through attachment upload and the final permission check. Lost permission produces a visible error instead of dropping that recipient. | **Invite is not the only chat choice.** The existing **Do nothing** button sends the message *without inviting or notifying the nonmembers*; their names remain references in the text. Where you cannot invite, that choice is labelled **Send anyway**. To abandon the send instead, dismiss the dialog with Escape. Invitation actions are disabled while preparation is pending, preventing duplicate clicks. **Leaving and returning must not resurrect a cancelled send.** Switching threads or leaving the composer cancels its pending invitation, even if you return to the same thread. Cancellation before dispatch sends no message; an accepted membership change cannot be automatically undone. An ordinary send without a pending invitation remains bound to its original destination rather than following you into another conversation. **Failed sends must not overwrite your next draft.** If you leave a thread, return and replace or deliberately clear its draft while an older send is pending, the older failure cannot restore deleted text, recipients or files; success cannot erase the newer draft—even if its text is identical. An untouched draft cleared automatically for sending remains recoverable on failure. This protection also covers reopening the composer and starting a newer send. The channel timeline also keeps its existing **new-messages / Jump to latest** button available when newer messages are waiting to be displayed. For example, after sharing a reply to the channel and closing the thread panel, you can click the catch-up button to reveal buffered messages. Closing the thread does **not** guarantee the shared row appears automatically or force you away from reading history. ### Related issue Built on [block#7122](block#7122), base branch `split/owned-agent-discovery`, which lets Desktop find and verify owned agents independently of this device. Current integration head: `1144465d00273cf74b7c22544ae5a3299bd98560`, built on exact published root `3a56d17824522580fe04cae463b54f4c7ba66021`. Root block#7122 has its own CI and security gates; this PR must not land ahead of that dependency. Finding an agent is not channel membership, online status or a promise of a reply. This PR changes what the existing message controls can do with those agents; it adds no profile, presence, cloud marker or remote start/stop UI. Standalone forum post/reply **Invite / Cancel** is added separately in [block#7125](block#7125); here those composers only gain visible authorization errors. Same-name selection/binding fixes ([block#7133](block#7133)) and mention spacing ([block#7128](block#7128)) are not included. Extracted from [block#7114](block#7114) (historical source `98fe33ec`). [Behavior and draft-recovery contract](https://github.com/block/buzz/blob/1144465d00273cf74b7c22544ae5a3299bd98560/docs/remote-mention-routing.md) · [Originating discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848). ### Testing  *Earlier candidate, mock desktop browser: the existing channel dialog now reachable for an eligible owned agent on another device. The two buttons have different send outcomes; Do nothing is not Cancel.* [Success and denial captures](block#7124 (comment)) · [Pending-state capture](block#7124 (comment)). These show the relevant UI, not live agent availability, native authorization or the later draft-storage/catch-up repairs. No before-state screenshot is available. Existing coverage exercises exact recipients, invitation rejection/cancellation, new direct-message destinations, uploads, edits, thread re-entry and stored-draft deletion. The timeline regression checks the shared reply becomes visible using the available catch-up action. **Integration validation (2026-09-02):** independently reviewed the routing delta onto root `3a56d178`: seven original patches unchanged; two reconciliations retain generic publication-error toasts alongside authorization errors and retain non-authored editability updates. Added two production-hook regression tests (normal and queued-media publication) requiring visible generic error, recovered draft and released pending state. - Writer validation: **5,995 Desktop tests**, **42 focused tests**, **22 mock-IPC browser journeys** (18 routing, 2 root provenance, 2 destination binding), and **1 voice-note failure journey** passed; lint, types, size guards and E2E build also passed. - The broad suite ran before the final formatting-only test amendment, not as an exact-final-head rerun. Independent AST comparison confirmed that amendment is semantics-preserving; **4 fresh assertions at final `1144465d`** passed. Publication rechecked final-head TypeScript, amended-test formatting and `git diff --check` successfully. No new full repository `just ci` run is claimed. - Browser tests use an isolated E2E build and **mock IPC**, not live relay/native authorization. Historical screenshots above are explicitly earlier UI evidence, not exact-head runtime certification. Packaged Tauri/live-relay behavior was not independently witnessed. - **Published-head gates:** [current CI run](https://github.com/block/buzz/actions/runs/33657948560) and [renewed exact-head formal review request](block#7124 (comment)) must clear before landing. [Earlier CI run](https://github.com/block/buzz/actions/runs/33438436438) and the two earlier approvals cover `7ffead0f`, not this new head. Root CI/security clearance remains separate; the independent scoped integration approval is not merge authorization. To try it: in a channel or thread, select an owned nonmember agent, Send, then Invite or Escape and retry. Deny the add or revoke its response permission before sending: expect a visible error and recoverable draft, not a message missing the agent. During a pending send, return to the source thread, edit or clear the draft, then leave again: late completion must not overwrite that choice. **Limits:** permission checks and sending are separate operations; cancellation cannot retract a dispatched message. Draft protection is same-window, not new cross-window deletion synchronization. Standalone forum transport failure can still restore text/media without the exact selected recipients. Native compatibility is inherited: open-source builds may still recognize a valid legacy, self-declared agent already in the channel when verified ownership is absent or rejected; that does not establish ownership or unlock this owned-nonmember invitation path. Invalid policy from a verified owner is still rejected. No agent response is guaranteed. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> (cherry picked from commit d595806) Signed-off-by: nambse <sefa.esendemir@gmail.com>
Summary
Buzz Desktop could hide an agent you own on another device before you could invite it, or silently drop a selected agent when its permissions changed. This lets eligible owned agents reach the existing @ menu → Send → Invite flow and checks the intended recipients again before publishing. Lost access shows an error and preserves a recoverable draft instead of sending without the agent.
Invite is not the only choice: chat's Do nothing / Send anyway sends without inviting or notifying the nonmembers; their names remain references. Escape cancels. Invitation adds channel-wide membership, not a one-thread guest, and does not start a remote agent or guarantee a reply.
Related issue
Targets
main; owned-agent discovery (#7122) is already merged. Split from #7114. #7125 depends on this PR and adds standalone forum invitations and failed-send recipient recovery. Duplicate-name binding from #7133 is already merged and retained by this stack. No new profile, presence or remote-management UI.Testing
Focused production-hook lifecycle tests and mock-IPC browser checks passed for the published refresh, covering clipboard settlement, permission revocation and recipient/wake behavior; the frontend build/typecheck passed. Broader desktop results predate the final settlement repair and are not claimed as a new-head full-suite run. See live CI for current-head results; no full local
just cipass or native/live-relay validation is claimed.To try it: select an owned nonmember agent in a channel or thread, send, then Invite or Escape/retry. Deny the add or revoke response permission: expect an error, no publication and a recoverable draft. During a pending send, edit or clear the source draft and navigate away/back; late completion must not overwrite that change.
Earlier mock-browser capture, not current-head runtime proof. Success/denial captures; no before-state screenshot available.
Limits: authorization reads and publication are not atomic; cancellation cannot undo accepted membership changes or dispatched messages. Draft protection is same-window. Legacy already-member agent compatibility remains unchanged: self-declared identity does not prove ownership or enable owned-nonmember invitations, and invalid verified-owner policy remains rejected.