Files
buzz/crates
Matt Toohey 380cc506e1 feat(desktop): let Project Canvas widgets send direct messages
Add a dm.send command to the canvas widget SDK so a widget can deliver a
message to a user's DM as the viewing user, behind a new consent-gated
app.dm.send capability.

- Protocol: app.dm.send joins the capability schema and the consequential
  (consent-required) set in projectCanvasProtocol.ts, mirrored in the Rust
  manifest validator's ALLOWED_CAPABILITIES. The consent banner names it
  "send direct messages as you" and now joins three-plus capability
  phrases as a serial list instead of chaining " and ".
- Broker: dm.send takes strict {pubkey, message} params — 64-hex
  recipient, message capped at 2,000 chars and rejected when blank after
  trimming — and dispatches to a new sendDirectMessage delegate with the
  recipient lowercased. Like the existing `user` open target, the
  recipient is any valid pubkey rather than a host-source row, because
  legitimate recipients come from people.lookup/people.search results,
  which are not host sources; containment is the per-revision consent
  gate, the 10-per-minute command budget, and a host toast on every send.
- Delegate: useProjectCanvasBroker captures tenant scope (normalized
  relay URL + signer pubkey) before the first await so the backend fails
  closed if the community or identity changes mid-send, opens the DM via
  useOpenDmMutation (which also upserts the conversation into the
  sidebar), and sends over the HTTP path because a freshly opened DM has
  no live subscription yet. ProjectChannelHome feeds the community relay
  URL through a new relayUrl parameter.
- Host: success toasts "Canvas sent a direct message"; failures title as
  "Canvas direct message failed" instead of borrowing the task-update
  title.
- Template: the support dashboard's bug reporter now delivers the report
  as a DM to the project owner (resolved via project.metadata) when
  app.dm.send and project.metadata.read are granted, showing "Report
  sent"; without the capability it stages the report exactly as before.
  The bundled manifest requests app.dm.send.
- Docs: base_prompt.md and nest_skill.md document dm.send, its params,
  and the third consent capability; the buzz-acp doc-binding test now
  asserts both `dm.send` and `app.dm.send` so the prompt cannot drift.

Tests: new broker coverage for the capability gate, param bounds, blank
and oversized messages, unknown keys, and recipient normalization; the
external-template harness drives the real bug reporter through the SDK
(metadata query -> dm.send command -> success copy) and pins the staged
fallback when the capability is missing; protocol tests cover the
granted/consent/effective sets with the new capability; the Rust
manifest test accepts the full eight-capability set.

Verified: desktop unit suite 5898 passing, Tauri lib suite 3077 passing,
buzz-acp prompt tests passing, canvas e2e spec 7 passing, and clippy -D
warnings, rustfmt, biome, tsc, and the file-size gate all clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-09-01 16:30:27 +10:00
..