Files
buzz/docs/forum-agent-invitation.md
Logan Johnson e7e29937a1 feat(desktop): invite owned agents from standalone forums (#7125)
## 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.

![Forum invitation with Invite and
Cancel](https://raw.githubusercontent.com/block/buzz/6481bd088d661975672addcc834231df6ac88705/pr-7125--forum-invite.png)

*Earlier mock-browser capture, not current-head runtime proof. [Error
and successful-post
captures](https://github.com/block/buzz/pull/7125#issuecomment-5513883462);
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>
2026-09-04 17:33:30 +00:00

53 lines
3.2 KiB
Markdown

# Standalone forum agent invitation
The standalone forum composer reuses the remote mention preparation/publication
contract (`docs/remote-mention-routing.md`). An eligible owned relay nonmember
opens an explicit Invite/Cancel dialog. There is no implicit reference-only send.
Invite revalidates the captured exact recipients in the preparation phase,
performs the existing authorized bot-member add, awaits membership invalidation,
then performs a fresh publication-phase check against the captured destination.
A successful add alone is never authorization to publish.
Cancel resolves the pending submission without clearing its text or selected
identity. Retry can use the same exact key. Rejected adds and preparation errors
remain visible inside the dialog; final authorization errors remain visible in
the composer and retain its draft. Channel change/unmount cancels pending work,
and completions must still belong to the mounted captured channel. An add already
accepted by the relay is not rolled back on cancellation; no message is sent.
This adapter does not create/manage local agents or change notes/channel-less
surfaces. Regression coverage in `forum-agent-invitation.spec.ts` covers exact
p-tags, membership refresh before final authorization, cancel/retry, three add
failures, policy revocation before Invite and during add, and navigation during
an outstanding add. Browser fixtures are mock IPC; signed native ownership and
membership validation live in PR6, not in the frontend bridge.
## Dispatched reply recovery
After authorized dispatch, transport rejection recovers the captured text,
completed/uploaded media and exact selected mention refs under the source draft
key. This extends the standalone-forum follow-up noted in the shared routing
document: a cached A → B → A visit with no new intent no longer loses its reply.
The departed editor is never called. A pristine, empty current source visit may
adopt the recovered store entry; B cannot receive A's text or attachments.
The existing same-window draft authority fences recovery across visits. New text
(including edit → delete), completed-media changes/upload intent, selected refs,
explicit draft deletion, a new send or a community/identity reset revoke older
recovery. A different existing stored payload is not overwritten. Optimistic
clear and recovery are programmatic, not authored deletion; synchronous media
snapshots protect cleanup immediately after recovery. No recovery path repeats
publication, undoes an accepted add or releases another visit's pending send.
`ForumComposer.lifecycle.test.mjs` exercises ordinary and invited sends,
source-key re-entry, newer intent/deletion/media/refs, scope reset and late
completion. The browser spec additionally rejects deferred `send_channel_message`
after cached A → B → A with uploaded media and verifies recovery versus newer
text/deletion, B isolation and exact recipients on an explicit retry.
Recovery is not a durable in-flight send journal: renderer reload/crash while the
transport is pending can still lose the in-memory snapshot. Local in-flight
upload custody, cross-window coordination, native authorization and atomic
relay publication are outside this correction.