mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Personas could only be created by clicking through Buzz Desktop, so nothing scriptable could stand up an agent roster — including the agents themselves. `buzz personas create|list|get|delete` writes the same kind:30175 coordinates Desktop reads, from a flag set or straight from a `.agent.json` export. These are owner-authored events, so the signing key IS the owner: no NIP-OA auth tag is involved, and running with a different key publishes to a coordinate space the owner's Desktop never reads. Publishing a definition does not start an agent; launching one mints key material and stays a Desktop operation. Two relay behaviors shape the implementation: - A write must be stamped past the coordinate's current head. NIP-33 breaks a `created_at` tie by lowest event id, so a same-second rewrite can otherwise lose to the event it was replacing. - `soft_delete_by_coordinate` matches `created_at <= tombstone`, and its result only feeds a debug log. A tombstone older than its target is accepted, deletes nothing, and reports OK — so delete stamps from the head it just read and then re-reads the coordinate to confirm. The re-read only raises a conflict for a head strictly newer than the tombstone, since a lagging replica can still return the deleted head. `--from` refuses rather than repairs a snapshot that would publish a persona Desktop can't mint from: `respondTo=allowlist` with no pubkeys, an unknown `respondTo`, or definition text carrying invisible characters. Avatars follow Desktop's reader rather than a CLI-only rule. `--avatar` takes a local image and carries it inline as a data URL when it fits the bounds Desktop renders — 8 KiB for SVG, 256 KiB for raster, both inside the relay's 256 KiB content cap. Anything larger is downscaled to 512px and re-encoded, then re-checked against the bound: flat art typically lands back inside it and never reaches media storage at all, and only what still doesn't fit is uploaded. Re-encoding is also what makes that upload work. Media storage refuses images carrying metadata — EXIF, colour profiles, comments — as an identity channel, so a photo straight off a camera failed outright. Decoding and re-encoding drops metadata by construction rather than by stripping known chunks, so it cannot drift from the relay's allowlist the way a structural stripper would. EXIF orientation is baked into the pixels first, because dropping the tag without applying it publishes the avatar sideways. GIF passes through untouched, since re-encoding would flatten animation, and WebP re-encodes to PNG because `image`'s WebP encoder is lossless-only and would inflate a lossy source. `--from` carries a snapshot's inlined avatar through that same path, so a Desktop export round-trips with its image. The upload runs after the `--replace` conflict check: an upload that a rejected write would strand leaves an orphan blob behind. Signed-off-by: Max Lampert <maxwell@squareup.com>