Commit Graph

7 Commits

Author SHA1 Message Date
Tom Brow
eee75a1c7b perf(mobile): verify owned-agent registrations natively (#8938)
## Summary

`loadOwnedAgentDirectory` checks the signature of each kind:30177 agent
registration before it trusts the registration's `model`. It did this
with `nostr.Event.fromJson`, which runs the `nostr` package's pure-Dart
Schnorr code on the UI isolate.

This change uses `verifiedNostrEventFromJson`, which the app's other
verification sites already use. It makes the same timestamp, event id
and BIP-340 signature checks with the bundled libsecp256k1. It also
requires lowercase hex for the pubkey and signature, which relay events
already use.

A registration that fails verification still lists its agent without a
model, as before.

## Measurements

- **Synthetic** (Mac, `flutter test`, 20 signed registrations, warm):
pure Dart 163 ms, native 2.6 ms.
- **Device** (iPhone 12 mini, Profile build, first channel open after
launch, first 5 s; runs not matched): `loadOwnedAgentDirectory`
inclusive CPU was 71 ms before (69 ms in `Event._assertValid`) and 2 ms
after.

## Testing

- New test: a registration with a wrong signature still lists the agent,
with no model. The correctly signed registration keeps its model.
- `just mobile-check mobile-test` passes (pre-push hook).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Tom Brow <tomb@squareup.com>
Co-authored-by: Tom Brow <tomb@squareup.com>
Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:00:26 -08:00
Tom Brow
9d1b684455 fix(mobile): show remote agent instructions from the registry (#8908)
After creating a remote (Builderbot) agent with instructions on mobile,
its profile said "Instructions aren't available on mobile for this
agent."

Two causes:
- Mobile called `list-agents` without `include_instructions: true`, so
registry rows had no `agent_instructions`. Desktop already sends this
flag.
- The profile read instructions only from published desktop agent
config, which remote agents don't have.

Now mobile requests instructions in `list-agents` (both the listing and
the post-create readback). For an agent in the active community owner's
registry listing, the profile shows the row's `agent_instructions`. Like
desktop, a missing field means "No instructions set." Desktop agents
keep the published-config path.

Because listings now carry instructions (up to 20,000 characters each),
`list-agents` gets a 4 MB response bound instead of 64 KB. The
delete-retry journal, which lives in plain preferences, drops
instructions before it's written.

### Before / after

iPhone SE, remote-agent-pilot community: profile of an agent just
created on mobile with the instructions "Reply in one short sentence."

| Before (main) | After (this PR) |
| --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/5d7333ea429e6c8c8aae93846d23c3a2bb9bda25/pr-8908--before-instructions.png"
width="300"> | <img
src="https://raw.githubusercontent.com/block/buzz/5d7333ea429e6c8c8aae93846d23c3a2bb9bda25/pr-8908--after-instructions.png"
width="300"> |

### Validation

- Extended `agents_sheet_test.dart`: the remote-agent case asserts the
`include_instructions` flag and that the profile shows the registry
text. It fails without the fix.
- New/extended tests for the larger `list-agents` bound and the retry
journal; each fails with its fix reverted.
- `just mobile-check` and the mobile test suite pass.
- iPhone SE (release build, remote-agent-pilot community): created an
agent with instructions on mobile, then opened its profile, which showed
them. Deleted the test agent afterward.

buzz-review-completed (manual testing waived by Tom)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Tom Brow <tomb@squareup.com>
Co-authored-by: Tom Brow <tomb@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:53:02 -08:00
Tom Brow
543ecf2b1f fix(mobile): list the same owned agents as desktop (#8857)
## Summary

Mobile's Agents modal listed agents that desktop does not show. Most
were agents created in the earlier Buzz app (this repository's
`desktop/`), which the owner had not imported into Buzz 1.0
([block/buzz-app](https://github.com/block/buzz-app)). @ completion
offered them as well.

Desktop lists an owned agent only when the owner's newest kind:30177
record for that agent has a `pluginId/typeId` `runtime` tag and a name.
Records written by the earlier app have no `runtime` tag. Importing an
agent on desktop republishes its record with one
([`listingsOf`](662e91800a/src/features/agents/service.ts (L1413-L1460))).
Mobile now applies the same rule:

- **Agents modal:** shows listed owned agents, plus the account's remote
agents that have a verified owner profile here (so one stays listed
while a deletion that already removed its registration is retried).
Owned kind:10100 directory entries no longer add agents on their own.
- **@ completion:** channel members are always offered. Listed owned
agents are offered in streams and forums, where they can be added, as on
desktop. An owned agent that isn't listed is no longer offered from the
directory or from search, because mobile can't import it. The check that
runs before sending a restored draft follows the same rule.

The owned-agent directory moved to `lib/shared/agents` so the channels
feature can use it.

Shared agents owned by other people still come from the kind:10100
directory, which desktop doesn't use for @. That difference is unchanged
here.

### Related issue

None found.

### Validation

iPhone 17 simulator, debug build, against a local relay seeded with one
owner and two owned agents. Both agents have a NIP-OA profile. "Current
Agent" has a desktop runtime tag. "Old App Agent" has a registration
written the way the earlier app writes it, with no runtime tag. The
before captures are `origin/main` with the same data.

| | Before | After |
| --- | --- | --- |
| Agents modal | ![Agents modal before: Current Agent and Old App
Agent](https://github.com/user-attachments/assets/9089a820-9b30-4372-a133-7a0aa00fb6d8)
| ![Agents modal after: only Current
Agent](https://github.com/user-attachments/assets/f0585e0c-1bbf-4605-910f-90f6f9dc033a)
|
| `@` in a stream | ![Mention list before: only the
member](https://github.com/user-attachments/assets/aa88a5d6-5dec-41e9-aebd-07a62d06e48e)
| ![Mention list after: the member and Current
Agent](https://github.com/user-attachments/assets/d4de914d-23a4-4437-83b7-4b23dd641aac)
|
| `@Old` in a stream | ![Search before: Old App Agent
offered](https://github.com/user-attachments/assets/e038eb64-1fed-40f8-960b-18d6fad0a86f)
| ![Search after: nothing
offered](https://github.com/user-attachments/assets/47246f69-e939-402d-acfc-d0c764df967c)
|

Added tests:
- Directory loading: remote, desktop, earlier-app, later-imported, and
reverted agents, plus agents owned by someone else; only the newest
record counts. A table test covers the runtime and name rule.
- Agents modal widget test: an earlier-app agent is not shown.
- Mention candidates: only listed owned agents are offered outside the
channel, including from search.
- Compose bar: a restored draft that mentions an unlisted owned agent
outside the channel is rejected before sending.

I disabled the runtime check and the unlisted-agent filter, and the four
new guarding tests failed. The full mobile suite passes.

Human test: not applicable for this mobile change under the author's
standing exception.

buzz-review-completed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Tom Brow <tomb@squareup.com>
Co-authored-by: Tom Brow <tomb@squareup.com>
2026-11-01 07:36:37 -08:00
Tom Brow
b41caeb510 fix(mobile): time out relay connects that never finish the handshake (#8878)
When a phone's network silently stops working (crowded event Wi-Fi, a
weak cell signal), Buzz Mobile can stay offline for minutes. In a device
test, the app took 30s to notice the dead connection, then hid the
channel's messages behind a loading skeleton. It tried to reconnect
once, and that attempt hung for 5 minutes (303s). It never retried,
because it only retries after an attempt fails.

This PR gives every reconnect attempt a 10s limit. When an attempt runs
out of time, the app closes it and tries again with its normal backoff.

### What was wrong

Opening the relay connection has two code paths. The one used with
hosted relay auth already had a 10s limit. The other path had none, so a
stuck attempt waited for the operating system to give up, which takes
minutes. Both paths now use the same limit. Each attempt also gets its
own HTTP client, so a timed-out attempt is actually closed rather than
left running in the background.

### Validation

I tested on an iPhone SE (iOS 27.0.1) running a profile build, with its
relay traffic routed through a Mac proxy that could silently drop
everything.

- **Before:** during a 5.6 minute outage, the app made one reconnect
attempt, which hung for 303s.
- **After:** during a 2.5 minute outage, each attempt gave up after 10s
and the app kept retrying. It reconnected 36s after the network came
back, a delay mostly set by the 30s cap on time between retries.

Added tests: `relay_socket_liveness_test` now covers an attempt that
never completes, with and without hosted relay auth headers. It checks
that the attempt fails and that the abandoned connection is closed. The
test fails without the fix.

Two existing tests were opening real relay connections by accident or
design:

- `deep_link_dispatcher_test` now uses a fake channel list, like its
neighboring tests.
- `remote_agents_test` talks to a real local relay on purpose, so it now
uses a real HTTP client instead of the test framework's mock.

The full mobile test suite passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Tom Brow <tomb@squareup.com>
Co-authored-by: Tom Brow <tomb@squareup.com>
2026-11-01 07:32:28 -08:00
klopez4212
0cde1fe2f4 Make mobile Agents a full-page owned-agent directory (#8693)
Replace the mobile Agents sheet with a full-page directory of verified
owned agents. Add dedicated creation and avatar pages with
catalog-backed model selection, reuse the profile emoji/photo/camera
controls, and expose Builderbot model settings while labeling desktop
agents “Manage on desktop.” Share one account-scoped model catalog
across editable cards and retries. Choose Bestie from the agent profile
using the shared encrypted main-agent record (text-only action, filled
star once selected), and move confirmed deletion to a top-left trash
control with native iOS glass. Include uploaded avatars in owner
registrations so desktop can display them.

Validation: mobile analysis and all 3,958 hosted-enabled app tests
passed; default-build tests run by the push hook. Earlier iPhone build
installed and iterated with the user; the current iOS build succeeds,
but final changes still need a device pass. Independent review found no
new blockers. Actual camera capture and live Bestie sync remain
unconfirmed; the existing shared `main-agent` wire format and Bestie
terminology are retained.

### Snapshots

Widget-harness captures with test data (not native iOS chrome).

| Agents directory | Image avatar editor |
| --- | --- |
| ![Agents
directory](https://github.com/user-attachments/assets/c68a0505-90a5-41db-980f-e3197cac63ac)
| ![Image avatar
editor](https://github.com/user-attachments/assets/9b126186-1e7c-4169-9a74-8f7f252cfa93)
|

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
2026-10-29 18:30:17 +00:00
Tom Brow
a7e65c91da feat(mobile): choose remote agent models and gate agents on Builderlab sign-in (#8650)
Follow-up to [#8626](https://github.com/block/buzz/pull/8626). Mobile
remote-agent creation now matches desktop.

- **Model and reasoning effort:** Create agent has Model and Reasoning
effort menus. Their options come from the remote service's catalog, and
nothing is preselected (same as desktop). The server checks the
selection against the catalog again before registering, and saves it on
the agent.
- **Builderlab gate:** creating a remote agent requires a hosted
community and Builderlab sign-in. The Agents directory, community bots
and the Bestie chooser stay available without it. Signing out closes an
open Create agent sheet; the Agents sheet stays open.
- **Enrollment:** `attest-agent` enrolls the agent in the requested
community, as on desktop, so the separate `add-buzz-subscription` call
is gone.
- **Quieter progress:** spinners replace the progress bars and the
"Working…" label. Status and success text ("is ready", "Remote agent
deleted.", setup steps) is gone, and only failures show text. Refreshing
an already-loaded agent list no longer blanks it.
- **Shared setting menu:** `BuzzSettingMenu` is a bordered setting row
that opens a Material 3 menu spanning the row's width. Opening it
dismisses the keyboard. The channel-TTL setting in Create channel uses
it too.

Agent avatars and QR/link-based creation follow in stacked PRs. Editing
existing agents comes later.

| Agents | Create | Model menu |
| --- | --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/d0bee87c45d30b9bdd2163a75966ccd5d64f322a/pr-8650--1-agents.png"
width="240"> | <img
src="https://raw.githubusercontent.com/block/buzz/d0bee87c45d30b9bdd2163a75966ccd5d64f322a/pr-8650--2-create.png"
width="240"> | <img
src="https://raw.githubusercontent.com/block/buzz/d0bee87c45d30b9bdd2163a75966ccd5d64f322a/pr-8650--3-model-menu.png"
width="240"> |

### Validation

- Checked on the staging iOS simulator with a Builderlab account: the
Agents list, the Create sheet, and the model and effort menus (including
with the keyboard open).
- Added tests: catalog-gated model selection (including switching models
clears an unsupported effort), the Builderlab sign-in gate, controller
creation and resume with a model, a failed listing reported instead of
an empty registry, and create sheet busy and failure states (including
the catalog reload after a rejected create).
- The hosted-only agent tests are skipped without Builderlab defines,
which `mobile-test` doesn't pass. They pass locally with the staging
defines (20/20).
- Not covered: Android.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
2026-10-29 08:43:45 -07:00
Tom Brow
d4a07063c8 feat(mobile): manage Builderlab remote agents (#8626)
Adds Builderlab remote agents to mobile: a member signed in to
Builderlab can list, create and delete their own hosted agents from the
channels header, and active remote agents show as Online.

Everything is gated by `HostedEnvironment.enabled` (the build-time
`BUZZ_AUTH_DOMAINS` define). App Store and internal release builds do
not set it, so they render and fetch nothing new.

### Behavior

- **Agents** button in the channels header (native iOS bar and Material
header) for hosted communities only. It opens a sheet listing the
community's agents; tapping one pushes its profile inside the same
sheet.
- **Create**: for the member's own agents (the NIP-OA owner is the
member, not the community). Registers the agent with Beekeeper, attests
it to the community, saves optional private instructions, subscribes it
to the relay, then publishes the kind 30177 registration with a NIP-OA
owner proof. A failed step resumes with the same agent identity on
retry; instructions stay in secure storage, never in preferences.
- **Delete**: removes the relay registration, confirms it is gone, then
deletes the Beekeeper agent. A partial failure keeps a retry row in the
list.
- **Presence**: an ACTIVE agent with a live kind 30177 registration on
the active relay shows Online in profiles, channel rows, DMs and
mentions, since remote agents wake on messages. Local presence data is
unchanged.
- Sign-in reuses the shared hosted flow. The account's saved Buzz key
must match the server binding; nothing is adopted or rewritten.
- `BuilderlabAccountClient` gains an allowlist for the six Beekeeper
routes desktop already uses.

Not included from the prototype: the separate entrypoint and second
sign-in, test-community creation, staging-only hosts, QR-code agent
links, and the USB proxy.

### Validation

- Added tests: build-time gate, account/key binding before listing,
setup resume with private instructions, deletion contract and retry,
owner-proof verification, status parsing, presented presence requiring a
live registration in the active community and following registration
publish and removal (mutation-checked), account-change clearing, load
retry, recovery of a pending setup whose instructions were lost, a lost
registration response that can only resume with the same idempotency key
(from any community), an HTTP-rejected registration that can be
discarded, deletion of other agents while a setup is unfinished,
rejection of owner proofs whose signed conditions or profile signature
fail, registration publish and removal over a loopback WebSocket relay
for an `http://`-saved community, and widget tests for in-sheet detail
navigation, creation overlay with failure then resume, and delete
confirmation.
- The new tests also run in the CI lane with `BUZZ_AUTH_DOMAINS`
configured, so the enabled path is covered.
- Full mobile format, analyze and test suite pass locally.
- Independent agent review: no gate leak or key-handling issue found;
its robustness findings (discarding a setup that never got a server
identity, clearing registry state on account change, retrying a skipped
load, guarded notifications) are fixed; discard and account change have
added tests.
- Simulator: a `BUZZ_AUTH_DOMAINS` debug build launches to Builderlab
sign-in. The signed-in flow was not re-run on this branch.
- Codex Security Review: an earlier head received a published review,
and its two findings were fixed. On 745ad85 and be2e31b the review hit
its 45-minute timeout and was cancelled without a result, so the final
head has no published security review.
- Human app test: not applicable under the mobile testing exception. The
end-to-end create, mention and delete flow was demonstrated on the
prototype branch against Builderlab; this PR keeps the same Beekeeper
and relay contracts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:05:44 -07:00