镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(agents): keep library agents visible through refresh/disconnect and wait for the launch agent list - #1909

Open
matt2e wants to merge 6 commits into
mainfrom
paging-11
Open

matt2e wants to merge 6 commits into
mainfrom
paging-11

Conversation

@matt2e

@matt2e matt2e commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Problem

  • Library-only agents disappeared on every library read. This covers hosted agents, agents created on another device, and every agent on web. Mentions, author names, agent icons, typing labels and the Solo default-agent chip all dropped them while a read was in flight. The gap lasted exactly as long as the read (2.04 s with a 2 s read in the live take).
  • A failed read hid them until the next successful read. A dropped relay connection hid them for the whole outage, and after reconnecting too (≥ 11.6 s in the live take, still missing 3.7 s after "Reconnected").
  • Four DM paths refused agents during launch. New message, search's person action and the agent's Message button refused an agent the native list had not returned yet. The RecipientPicker showed "No matching people." for it. In the live take a restored New message draft sent at launch was refused every time.

Why it was built this way

Git history shows no security or ghost-agent reason for either behavior:

Archives already moved to stale() in #1665. The library was the last store at that call site that still emptied on disconnect.

Fix

  • Rows survive refreshes. The library and name directory keep the last read through loading and error. pending/complete still mark the read as partial, so inference and templates keep refusing until a ready read.
  • Disconnect marks the library stale instead of clearing it. stale() replaces clear(). In-flight reads are still aborted, and reconnecting still rereads. Sign-out, cache clear and dispose still empty the library.
  • One shared admission rule. admissible means managed, or the library read is ready. Channel addAgents, Solo start and tagged sends use it, so stale rows are display-only. addAgents rereads once before refusing an unknown key.
  • DM paths wait for the native list instead of refusing. The four DM paths gate on the new managedPending flag. The three that act on a click await refresh(false), which joins the native list already in flight and never waits on the relay library.
  • docs/agents.md is updated.

Net +83 production lines; no new timers or abstractions.

Before / after

State diagram: the agent library and the DM paths before and after the fix

Video evidence

All four takes are side by side: BEFORE (a3a6223d7) on the left, AFTER on the right, with the clocks synchronized. They were recorded in the Playwright browser fixture with library or native reads held for exactly 2 s.

A1, Refresh member data. Watch the message author: BEFORE shows a raw key with no agent icon for 2 s, while AFTER keeps "Owned Honey".

A1.mp4

A2, relay disconnect. Watch the author: BEFORE drops it from the disconnect until after "Reconnected", while AFTER keeps it and still offers it in @Hon while offline.

A2.mp4

B1, sending a restored New message draft during launch. Watch the line above the composer: BEFORE refuses at once and stays refused, while AFTER waits and creates the DM once the list lands.

B1.mp4

B2, the recipient picker during launch. Watch under "To: Owned": BEFORE shows "No matching people." for about 1 s, while AFTER lists the agent as soon as people load.

B2.mp4
Take BEFORE AFTER
A1: author name/icon gap during a 2 s read 2.04 s none
A2: author name/icon gap across a disconnect ≥ 11.6 s (to end of take) none
B1: send at launch refused DM create issued 10 ms after the list lands
B2: "No matching people." at launch 0.96 s never

Cost / risk

  • Rendering is unchanged. Publishes and renders per refresh are the same as before, and rows are never remounted.
  • Waiting is new only where the app used to refuse instantly. That means the DM paths while the native list is loading. An offline addAgents refusal of a library-only agent now takes 3.5–10 s (one reread) instead of being instant.
  • Stale rows stay after reconnect until a reread. They are display-only, and admission refuses them.
  • Admission safety depends on callers using admissible. All three admission paths I know of (channel add, Solo start, tagged send) use it and are tested.
  • Not fixed here (pre-existing). On desktop, a partial inventory read (relay half fails, local half succeeds) still drops relay-only agents until the next full read. This is left as a follow-up.

Testing

  • Unit tests fail on the parent and pass on this branch. With the branch's tests copied onto the parent sources, 18 of 249 tests in the 10 affected files fail; the tests added by the later commits also fail on their parent commits (the bridge test fails with the mapping inverted). Covered: admission across loading, error and stale libraries; DM waits; the picker; Solo start; tagged sends; admissible; joining the in-flight native list; and the local-agents bridge mapping.
  • ProfilePanel.test.tsx now expects the library-configured name ("Configured Carl") to stay through a relay drop instead of falling back to the public name; it fails on the parent's clear-on-disconnect.
  • Typecheck, lint and format pass.
  • The four Playwright fixture takes above were run live.
  • Not run:
    • the native desktop app;
    • a real relay;
    • DM creation end to end (the fixture has no DM-create endpoint).
  • Unit tests only: the agent Message button and search person-action paths. They cannot be reached at launch in the fixture.

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review October 30, 2026 06:03
@matt2e
matt2e requested a review from a team as a code owner October 30, 2026 06:03

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Two reproducible issues in recipient selection and native-read waiting.

Comment thread src/features/agents/service.ts Resolved

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source review via Wes’s account, integrating independent systems/lifecycle, correctness/security and product/accessibility passes.

One low-priority finding is recorded inline. The stale-display/admission separation and launch-time wait paths were reviewed against their callers and lifecycle guards.

Reviewed head fa94a50 against base a65463a. Source-only: no tests, builds, app execution or independent CI assessment. Native DM creation, real-relay behavior, performance, rendered focus and assistive-technology behavior remain unverified. Inspected the public description, commit messages, diagram and one-second video frame samples; the fixture recordings show an earlier revision and do not establish current-head runtime correctness.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating independent systems/lifecycle, correctness/security and product/accessibility passes.

No new actionable findings in the rebase integration. The existing P3 recipient-picker loading-status finding remains unresolved at this head: the native-list wait can still leave an empty picker without loading feedback. Please retain that existing finding; no duplicate inline comment is added.

Compared the earlier reviewed fa94a50 with this revision: four patches are unchanged; the first adapts to the current name-fallback and reconnect code. Checked retained library rows, freshness/admission separation, native-list joins, cancellation and error/retry callers.

Reviewed head 0c8eb04 against base 4bcd920 (merge base 561265d). Source-only: no tests, builds or app execution. Hosted checks were still running in the single snapshot. Native DM creation, real-relay recovery, performance, rendered focus and assistive-technology behavior remain unverified. Inspected the public description, five commit messages, diagram and sampled frames from all four unchanged fixture videos; those recordings show an earlier revision and do not establish current-head runtime correctness.

matt2e added a commit that referenced this pull request Oct 30, 2026
…perseding native lists

Addresses PR #1909 review comments.

Recipient picker: while the native agent list loaded, the picker listed
and accepted every directory agent, so one the viewer does not control
could be chosen and was only refused at Send. It now keeps offering
only controlled agents throughout. While the list loads it still holds
Space and withholds "No matching people.", and now shows its existing
"Loading people" status for that wait too, so the list is never blank
without feedback (DESIGN.md, "States, feedback, and motion"). Controlled
agents appear once the list arrives.

Native list joins: a refresh that joined list A resolved when A
finished even if a later list B, started by a change such as a
creation, had superseded it and A's result was discarded. A New message
send that joined the launch list could then refuse while the list was
still loading. `AgentsService.refresh` now has a superseded read's
waiters follow the read that replaced it, as `library.refresh` does,
so a finished refresh always means the current list.

Tests: the picker test now asserts that only controlled agents are
listed while the native list loads, with the loading status in place of
an empty result. A service test asserts that a joined refresh waits
through a superseding read. Both fail on the previous sources: the
picker test on each change separately, the service test because the
joined waiter resolved early. The 24 related test files, typecheck,
biome and design:check pass.

Production diff: +11/-15 (net -4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating independent systems/lifecycle, correctness/security and product/accessibility passes.

No new actionable findings. The prior missing loading-status finding is repaired: the picker announces loading while the native agent list is pending. The follow-up also keeps uncontrolled agents out of selection and makes joined refresh callers wait for superseding native reads. Checked the five-file delta from 0c8eb04, its regression-test source, and the affected callers and lifecycle guards.

Reviewed head 5831134 against base 4bcd920 (merge base 561265d). Source-only: no tests, builds, app execution or CI assessment. Native DM creation, real-relay behavior, performance, rendered focus and assistive-technology behavior remain unverified. Inspected the public description, six commit messages, diagram and sampled frames from all four unchanged fixture videos; unsampled footage was not reviewed, and those earlier-revision recordings do not establish current-head runtime correctness.

matt2e added a commit that referenced this pull request Nov 2, 2026
…perseding native lists

Addresses PR #1909 review comments.

Recipient picker: while the native agent list loaded, the picker listed
and accepted every directory agent, so one the viewer does not control
could be chosen and was only refused at Send. It now keeps offering
only controlled agents throughout. While the list loads it still holds
Space and withholds "No matching people.", and now shows its existing
"Loading people" status for that wait too, so the list is never blank
without feedback (DESIGN.md, "States, feedback, and motion"). Controlled
agents appear once the list arrives.

Native list joins: a refresh that joined list A resolved when A
finished even if a later list B, started by a change such as a
creation, had superseded it and A's result was discarded. A New message
send that joined the launch list could then refuse while the list was
still loading. `AgentsService.refresh` now has a superseded read's
waiters follow the read that replaced it, as `library.refresh` does,
so a finished refresh always means the current list.

Tests: the picker test now asserts that only controlled agents are
listed while the native list loads, with the loading status in place of
an empty result. A service test asserts that a joined refresh waits
through a superseding read. Both fail on the previous sources: the
picker test on each change separately, the service test because the
joined waiter resolved early. The 24 related test files, typecheck,
biome and design:check pass.

Production diff: +11/-15 (net -4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated follow-up source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

No new actionable findings. The prior recipient-filtering, loading-status and superseding-native-read fixes remain intact. Reviewed the rebase integration with hosted-agent canMessageAgent eligibility, existing-DM reopening, refresh/retry and cancellation boundaries. No changes requested from this source review.

Reviewed head 36397014e43a29ecf2d8a2212cb9e63bf76e6490, base e7ae7b18cff03f371382ae60fc1064b60a667ed2 (merge-base ca836a3109be30a7fd46db97bdcceaf32ebabf65). Source-only: no tests, builds or app execution. One hosted-check snapshot showed 39 passed and two skipped (Windows validation and flake check); this does not establish native/real-relay behavior, performance, rendered focus or assistive-technology behavior. The public description, six commit messages, diagram and 16 sampled frames across all four videos were inspected; unsampled footage remains unreviewed, and those earlier fixture recordings do not validate this head.

matt2e added a commit that referenced this pull request Nov 3, 2026
…perseding native lists

Addresses PR #1909 review comments.

Recipient picker: while the native agent list loaded, the picker listed
and accepted every directory agent, so one the viewer does not control
could be chosen and was only refused at Send. It now keeps offering
only controlled agents throughout. While the list loads it still holds
Space and withholds "No matching people.", and now shows its existing
"Loading people" status for that wait too, so the list is never blank
without feedback (DESIGN.md, "States, feedback, and motion"). Controlled
agents appear once the list arrives.

Native list joins: a refresh that joined list A resolved when A
finished even if a later list B, started by a change such as a
creation, had superseded it and A's result was discarded. A New message
send that joined the launch list could then refuse while the list was
still loading. `AgentsService.refresh` now has a superseded read's
waiters follow the read that replaced it, as `library.refresh` does,
so a finished refresh always means the current list.

Tests: the picker test now asserts that only controlled agents are
listed while the native list loads, with the loading status in place of
an empty result. A service test asserts that a joined refresh waits
through a superseding read. Both fail on the previous sources: the
picker test on each change separately, the service test because the
joined waiter resolved early. The 24 related test files, typecheck,
biome and design:check pass.

Production diff: +11/-15 (net -4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

No new actionable findings in the rebase integration. The prior recipient-filtering, loading-status and superseding-native-read fixes remain intact. Compared all six feature patches with previously reviewed 36397014e43a29ecf2d8a2212cb9e63bf76e6490, including the short-query picker state, keyed search cancellation, native refresh bridge and retained-display/admission boundaries. No changes requested from this source review.

Reviewed head 823454777007257fbc374827e8fd68b06f3c71e6 against base 90ce5e37a851909cc0e5fd4da2f469010e4eb7bf. Source-only: no tests, builds or app execution. One hosted-check snapshot showed 39 successful checks and two skipped (Windows native validation and flake check); native/real-relay behavior, performance, rendered focus and assistive-technology behavior remain unverified. Inspected the public description, six commit messages and attached diagram. The four historical fixture videos were not viewed in this cycle and do not establish current-head runtime correctness.

kazemek21 added a commit that referenced this pull request Nov 4, 2026
## TL;DR
Solo chats retain explicit model and effort choices per chat and agent
until Reset, including choices made before the first message. Untouched
chats keep provider defaults.

Choices use a feature-owned IndexedDB transaction for
read/merge/compare/write. The existing composer catalog, menus and
scoped draft state remain their owners; no generic storage framework or
second authoritative localStorage copy is added. IndexedDB is the sole
authority: a missing row means no saved choice. This unreleased feature
does not import earlier development-only localStorage bytes; those bytes
remain untouched and non-authoritative. Scope cleanup shares the
save/transfer ownership lock so an already-queued save cannot recreate
deleted choices. Other windows are notified only after commit and reread
the authoritative store.

Send waits for initial loading and pending-save acknowledgement. Failed
reads offer **Retry saved choices** rather than silently using defaults;
failed saves keep local intent. First-send transfer and refused-start
restoration use ownership receipts, so a later edit or equal-value Reset
is not overwritten. The existing toast retains the refused prompt,
attachments and validated choices for **Retry restoring message**,
including repeated ownership/storage failure.

## Verification
Blox verified an uploaded content snapshot matching
`6f87b1101d21f3071cf0a64ab0197ae1442a4429`: typecheck, targeted lint,
**622 tests across the three affected full files**, and **14 browser
cases** across Chromium/WebKit (zero skipped/flaky). All six final
changed-file hashes match the tested snapshot. Historical `a004b3cd`
verified 709 tests across seven files plus design checks; those broader
results are not claimed as a new-head run. These are content-snapshot
checks, not claims that the candidate commit was checked out remotely.
Pre-commit hooks ran; only duplicate types/tests/design/clippy pre-push
jobs were skipped because required CI runs them. The verification box
was deleted.

The latest feedback regressions fail against `a004b3cd` and pass with
these fixes: a refused prompt-write reports the text-save cause,
compensates prepared choices, and succeeds through Retry; fresh IDB
initialization remains available when the unrelated localStorage read
throws; and scope purge waits behind already-queued saves in both
browser engines.

The [failed WebKit
job](https://github.com/block/buzz-app/actions/runs/41872987261/job/126725503435)
was not dismissed as a flake: an owned amd64 trace showed the second Web
Lock holder reading stale localStorage after the first writer committed,
then replacing the durable map. The transactional repair preserves both
agents in the unchanged two-page regression. Mounted tests cover
pending/failed edits, Send commit barriers, unavailable types,
navigation and refused-start recovery. The latest regressions preserve
corrective edit errors during a saved-choice read failure and retain
valid first-send choices when the catalog needs no selector. Both fail
with their previous production behavior and pass with the fixes.
Removing the later-Reset ownership guard produces the intended
regression failure; the restored source passes.

**Browser cases: four added, none removed** versus the merge base; this
feedback batch adds one and updates one. They cover
keyboard/Escape/Reset/focus, actual cross-page commits/notifications,
real IDB initialization/abort/first-send compare-clear custody, and Web
Lock queue ordering for purge. These browser boundaries cannot be
established by jsdom; lifecycle and failure permutations remain in
mounted tests. Shared menus, Button, Toast and LoadingRegion are reused
without new styling. Prior picker checks covered both themes, 360/1280px
widths, normal/24px text, keyboard and pointer: [before
selection](https://d24qwcpro867f5.cloudfront.net/repos/block/buzz-app/prs/2342/solo-choices-before-03b33b1.png),
[selected
choices](https://d24qwcpro867f5.cloudfront.net/repos/block/buzz-app/prs/2342/solo-choices-selected-03b33b1.png).

**Boundaries:** Cooperation is within the same storage bucket, not older
clients or separate profiles. Retry retention ends when this window
reloads or its notice is dismissed; it is not crash recovery. Native
simultaneous WKWebView/provider execution and full-chat runtime proof
remain unverified. Earlier failed attempts and test-contamination
evidence remain retained; passing repetitions do not establish that
every browser failure is fixed. The separate JavaScript fixture failure
was repaired on main by
[#2444](#2444); this branch
includes that merged source, not an unmerged human patch.

## Related
Fixes NEXUS-4155

This is native host Solo composer behavior, separate from plugin task
setup and shared-channel default routing. It does not depend on
[#2306](#2306) or
[#1909](#1909).

Part of [Nexus & Agent Tooling
status](https://g2.sqprod.co/apps/kylez-nexus-buzz-plugins-status).

_Generated with Codex (gpt-6.1-sol, high) on behalf of kylez._

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
matt2e added a commit that referenced this pull request Nov 5, 2026
…perseding native lists

Addresses PR #1909 review comments.

Recipient picker: while the native agent list loaded, the picker listed
and accepted every directory agent, so one the viewer does not control
could be chosen and was only refused at Send. It now keeps offering
only controlled agents throughout. While the list loads it still holds
Space and withholds "No matching people.", and now shows its existing
"Loading people" status for that wait too, so the list is never blank
without feedback (DESIGN.md, "States, feedback, and motion"). Controlled
agents appear once the list arrives.

Native list joins: a refresh that joined list A resolved when A
finished even if a later list B, started by a change such as a
creation, had superseded it and A's result was discarded. A New message
send that joined the launch list could then refuse while the list was
still loading. `AgentsService.refresh` now has a superseded read's
waiters follow the read that replaced it, as `library.refresh` does,
so a finished refresh always means the current list.

Tests: the picker test now asserts that only controlled agents are
listed while the native list loads, with the loading status in place of
an empty result. A service test asserts that a joined refresh waits
through a superseding read. Both fail on the previous sources: the
picker test on each change separately, the service test because the
joined waiter resolved early. The 24 related test files, typecheck,
biome and design:check pass.

Production diff: +11/-15 (net -4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

No new actionable findings in the rebase integration. The prior recipient-filtering, loading-status and superseding-native-read fixes remain intact. Compared the six feature patches with previously reviewed 823454777007257fbc374827e8fd68b06f3c71e6, focusing on verified-name recipient selection, existing-DM visibility, remote-agent labels, native-list lifecycle and Solo/channel admission. The documented distinction between hosted DM eligibility and channel-admission evidence is preserved. No changes requested from this source review.

Reviewed head 1438e6593a3f375a41b339bfd2ca251f5517f77c against base fe3dd1947181de66dc6e4fd8ca5d3b61573c0ca2 (merge-base 1c3047558547e76bd006c2dc76951b3f1d601c5d). Source-only: no tests, builds, app execution or CI assessment. Native/real-relay behavior, performance, rendered focus and assistive-technology behavior remain unverified. Inspected the public description, six feature commit messages and attached diagram. The four historical fixture videos were not viewed in this cycle and do not validate this head.

kazemek21 added a commit that referenced this pull request Nov 6, 2026
…uzz tools (#2707)

## TL;DR

An approved external app (like Nexus) now gets the selected agent's
whole Buzz MCP tool catalog and native dispatcher, including tools
plugins add later, after one explicit "full access" approval. Before, it
got a hand-picked list of six tools with rewritten arguments. Older
narrow approvals still connect but grant no tools until the owner
approves full access again, so no existing grant widens on its own.

Initiative status:
https://g2.sqprod.co/apps/kylez-nexus-buzz-plugins-status

Fixes NEXUS-4262

## What changes

- **One scope, `agent`.** New links request `scope=agent`, and `agent`
can't be combined with other scopes. Rust pairing accepts only
`["agent"]` for new approvals (`valid_scopes`). Saved
`read`/`post`/`create` approvals still load, renew and resume, but grant
nothing.
- **No adapter.** `respondExternal` now passes `tools/list` and
`tools/call` to `respond` in `rpc.ts`, using the same
`sessionTools(added)` catalog as native sessions. Plugin-added tools
come from `addedTools.snapshot()` (the source native harnesses use) and
are re-read on every message, so enabling or disabling a plugin changes
the catalog live. The old policy map, argument rewriting and Bestie
injection are removed.
- **The agent's own authority.** The connection uses the agent's run
handle (`runners.handle(key, undefined, true)`), so it gets device
settings, plugin requests, Git access and handoff exactly as a native
run does. Plugin installs still go through the owner's confirmation.
Memory reads use the agent's `memoryListing`, like the native plugin
path. Protected agents are still refused. `plugin_disable` acts at once,
as it does natively, and the consent text says so.
- **Handoff with true provenance.** `self_handoff` goes through the
run's own handoff and `Agents.wakeIn`, but its `from` is `{ external:
true }`. `wakeSection` shows it as an `<app-wake>` section that says an
external app the owner approved sent it, and that it is not a message, a
watch or timer, or another of the agent's conversations.
- **Files where the agent reads them.** `send` with `path`/`files` calls
a new optional `AgentType.readFile`. Claude, Codex, Pi and goose
implement it with the reader their own tools use, in the agent's saved
workspace on this device. Relative paths start there; absolute and `~/`
paths reach any file the account can read, as they do natively, and the
consent text says so. A Claude or Codex agent bound to an execution
target refuses, because only a conversation's session opens that target.
A type without `readFile` refuses with the same message its tools give
natively. Closing the connection cancels a read in progress.
- **No implicit conversation.** Calls run with an empty context, so
every tool needs explicit channel or message IDs.
- **Consent.** The dialog now states full access plainly: future plugin
tools, Git access, waking the agent, Nexus workers, settings changes,
posting any file the agent can read (including outside its workspace),
and immediate plugin disabling. A legacy narrow link shows an
update-required notice with Deny only, which sends the normal denial
callback. `approve()` refuses non-`agent` requests as a second check.
Agent settings flags a saved legacy approval as no longer working.

## Companion-client contract (for the Nexus consumer)

| | Behavior |
| --- | --- |
| Request | `scope=agent` alone. Narrow scopes get the update-required
notice and only a denial callback. |
| `tools/list` | The full native catalog: 66 built-in tools with
settings, plus plugin tools. Each tool is listed as natively, plus
`_meta: {"buzz/requiredScopes": ["agent"]}`. Result `_meta`:
`buzz/grantedScopes` (`["agent"]`, or `[]` for a legacy approval) and
`buzz/supportedScopes: ["agent"]`. Cursors are rejected (single page). |
| `tools/call` without the grant | `isError` result, nothing dispatched,
`_meta["buzz/error"] = {code: "authorization_required", required_scopes:
["agent"]}`. |
| `tools/call` with the grant | Native result and errors, unchanged. |
| `self_handoff` | Native arguments. The wake reaches the agent as an
`<app-wake>` from the app. |
| `send` `path`/`files` | Read by the agent's type in its saved
workspace. Agents on an execution target, and types without a reader,
return their refusal. |
| `channel_create` | Takes native arguments. A private Solo chat with
the owner's Bestie must now pass `visibility: "private"`, `solo: true`
and `members` explicitly. Buzz no longer fills them in. |

An unupdated consumer that accepts only `read`/`post`/`create` will
reject `supportedScopes: ["agent"]`. Its upgrade path is to accept
`agent` and request `scope=agent`, which takes one owner approval.

## Overlap

These open PRs touch nearby files. This PR neither depends on nor
replaces any of them:

- #2260 and #2685 cover external pairing and identity.
- #2647 adds plugin skills to `session-tools`.
- #2622 covers Bestie settings.
- #1939, #1909, #1854, #2311 and #2070 change `service.ts`/`runner.ts`.
- #2610 has no shared files.

## Validation

Ran on a Linux Blox box against the current head:

- `tsc --noEmit` and `tsc -p tsconfig.design.json`;
- Biome on the touched files;
- Vitest for `src/buzz-mcp`, the external connection, dialog and
settings tests, `AgentsPage.test.tsx`, `service.test.ts`,
`plugin-wake.test.ts` and the Claude, Codex, Pi and goose runtime tests.

New tests cover the external handoff's trigger and `<app-wake>` wording,
file reads through the type's reader (target workspace, cancellation on
close, native refusal), the Claude and Codex workspace and
execution-target cases, a saved narrow approval that a link asks to
resume as `agent`, and memory read from the selected agent. The Rust
pairing tests (`host_mcp::pairing`) need the Tauri system libraries, so
they are left to PR CI. No browser journeys were added or removed.
Independent security reviews traced authorize, approve, resume, renew
and call, and found no path that grants tools without the `agent` scope.

Not done:

- The native desktop consent dialog and a live external connection have
not been exercised against this build, including a live `<app-wake>`
turn and a file read in a running Claude, Codex, Pi or goose agent.
- No human testing yet.
- The Nexus side of the contract is a separate change.

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

---------

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
matt2e added a commit that referenced this pull request Nov 7, 2026
…perseding native lists

Addresses PR #1909 review comments.

Recipient picker: while the native agent list loaded, the picker listed
and accepted every directory agent, so one the viewer does not control
could be chosen and was only refused at Send. It now keeps offering
only controlled agents throughout. While the list loads it still holds
Space and withholds "No matching people.", and now shows its existing
"Loading people" status for that wait too, so the list is never blank
without feedback (DESIGN.md, "States, feedback, and motion"). Controlled
agents appear once the list arrives.

Native list joins: a refresh that joined list A resolved when A
finished even if a later list B, started by a change such as a
creation, had superseded it and A's result was discarded. A New message
send that joined the launch list could then refuse while the list was
still loading. `AgentsService.refresh` now has a superseded read's
waiters follow the read that replaced it, as `library.refresh` does,
so a finished refresh always means the current list.

Tests: the picker test now asserts that only controlled agents are
listed while the native list loads, with the loading status in place of
an empty result. A service test asserts that a joined refresh waits
through a superseding read. Both fail on the previous sources: the
picker test on each change separately, the service test because the
joined waiter resolved early. The 24 related test files, typecheck,
biome and design:check pass.

Production diff: +11/-15 (net -4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

No new actionable findings in this focused rebase follow-up at 4a0e3d4f85f4a782f90f69f0293fd6d02a0f0d20, base/merge-base cae3922574c5c1f0ce5d32231810229e94b1bb40, compared with reviewed 1438e6593a3f375a41b339bfd2ca251f5517f77c.

The six feature patches retain the reviewed behavior: five are patch-identical and the first differs only in documentation context. We checked their integration with the changed native-agent service, relay recovery, DM/picker and Solo/composer consumers. Retained library rows remain display evidence, admission still requires managed-or-ready-library evidence, and native waits preserve joining/supersession and cancellation. The prior picker filtering/loading and superseding-read repairs remain intact; their regression coverage was inspected as source.

This was a focused integration review, not a full review of unrelated changes in the new base. No tests, builds or app execution were performed. Native/real-relay behavior, performance, rendering, focus and assistive-technology behavior remain unverified. I inspected the current PR description and attached diagram; the four historical fixture videos were not reviewed in this round and do not establish current-head validation.

@tellaho

tellaho commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Design review: no design blockers found at 4a0e3d4f85f4a782f90f69f0293fd6d02a0f0d20. Agents keep their familiar names during refresh/disconnection, and starting a chat waits for the agent list. The recipient picker distinguishes loading from an empty result while preserving eligibility checks.

Reviewed source, the diagram and sampled frames from all four attached fixture recordings. Those recordings show an older revision (011465070), not this head. Tests were not run; native/real-relay DM creation, live failure/retry, keyboard/focus, dark mode, narrow/wide layouts, enlarged text and many-agent rendering remain unverified.

matt2e and others added 5 commits November 9, 2026 11:41
…ng agents

The relay agent library republished `loading`, `error` and, on a live
disconnect, an emptied `idle` snapshot, while every consumer admitted its
rows only when `ready`. Library-only agents blinked out of mentions, the
Solo default-agent chip, typing/working labels, agent icons and alias
names on each refresh, stayed hidden after a failed read, and vanished
for a whole outage. Separately, New message, search and the agent
profile's Message button read `agentChoices.snapshot()` once and refused
an agent the native inventory had not listed yet at launch, and the
recipient picker hid it as uncontrolled.

Library (half A):
- `choices.ts` and the name directory carry the last library read through
  loading, error and stale states; `pending`/`complete` still mark it
  partial, so inference and templates keep refusing until a ready read.
- A live disconnect marks the library stale (rows kept, `idle`, so
  reconnect rereads a retained library) instead of clearing it; sign-out,
  cache clear and dispose still empty it.
- Channel-agent admission still requires a ready library read for
  library-only agents; stale rows are display-only.

Consumers (half B):
- `NewMessage.validateAgents`, the search person action and
  `AgentMessageButton` await `agentChoices.refresh()` before refusing an
  agent that is missing while `pending`, as SoloDraft already does.
- `RecipientPicker` keeps directory agents listed, holds Space and
  withholds "No matching people." while agents are pending.

docs/agents.md now describes stale-but-usable library rows, the
wait-before-refusing rule and stale-on-disconnect.

Production diff: +99/-66 across 8 files (net +33; the name-directory
change is mostly reindentation). New tests fail on the previous sources
and pass here; typecheck and lint pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…y before refusing admission

Follow-up to b2fce98. The DM paths gated on the combined `pending`,
which is mostly library state, yet only native rows can make an agent
`managed`:

- `RecipientPicker` listed uncontrolled directory agents, held Space and
  hid "No matching people." during every routine library refresh, and
  for a whole outage after a disconnect left the library stale-idle.
- New message, the search person action and `AgentMessageButton` waited
  on a library read (up to ~10 s offline) whose result could not change
  the decision.

`AgentChoicesSnapshot` gains `managedPending` (native list loading). The
picker gates on it; the three await paths gate on it and call
`agentChoices.refresh(false)`, so they never wait on the relay library.
`NewMessage.validateAgents` now takes the send's `controller.signal`.

Channel admission keeps its ready-library gate for library-only agents,
but `addAgents` no longer refuses a listed agent just because the library
is loading, failed or stale: when a key is unknown it awaits one
`agentChoices.refresh()` (only if the library is not ready, joining a
read in flight) and re-checks before refusing. Archive state is checked
after that read.

Tests: admission refuses a library-only agent from loading, error and
stale-idle libraries when the reread fails and admits it when it
succeeds, with one read (joined when in flight); the picker keeps
uncontrolled agents hidden while only the library loads; the await paths
call `refresh(false)` and do not wait on library-only pending. The new
and changed tests fail on b2fce98's sources (admission also fails with
the gate removed). Typecheck, lint and format pass.

Production diff: +44/-21 (net +23) here; +127/-71 (net +56) with
b2fce98.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…nt list

Follow-up to 6cc276c, closing the cheap residual risks of keeping
library rows through refreshes.

- `AgentChoicesSnapshot.admissible` is `selectable` limited to `managed`
  agents unless the library is ready: the rule session admission already
  applied inline, now shared. Session admission, SoloDraft start and the
  composer's tagged-send pre-check use it.
- SoloDraft start no longer creates a Solo chat with a stale or
  failed-read library row: such a member takes the existing refresh path
  and is refused if the reread does not renew it (R1). The default-agent
  `picked` stays on `selectable`; it is display only and start re-checks.
- A tagged send whose own reread leaves the library unready refuses there
  instead of `addAgents` reading again, halving the offline wait (R2).
- `AgentsService.refresh({ join })` returns a native list already in
  flight. The `LocalAgents` bridge joins unless `fresh`, matching
  `library.refresh({ fresh })`, so a DM click during launch waits on the
  launch read rather than discarding it (R8). Internal post-mutation
  refreshes still start a new list.

Tests: SoloDraft rereads a stale row and refuses or starts on the result;
the composer refuses after one library read without calling `addAgents`;
choices exposes `admissible` (empty for stale/error/loading library rows,
kept for managed agents) and passes `fresh` to native; AgentsService
joins the launch read. All fail on 6cc276c's sources. The slow
choices.test.ts poll uses `{ interval: 0 }`. Typecheck, biome lint and
format pass; 43 test files that build agent choices pass.

Production diff: +43/-26 (net +17) here; +175/-92 (net +83) with
b2fce98 and 6cc276c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Follow-up to 0114650's review. The `LocalAgents` bridge in
`createServices` maps `refresh({ fresh })` to
`AgentsService.refresh({ join: !fresh })`, but no test tied the two
together: mistyping it as `join: fresh` would let post-setup, removal
and delete reads join a list already in flight that may predate the
change, and every test would still pass.

`services.test.ts` now captures the real bridge from `createLocalAgents`
and asserts `fresh: true` reaches Agents as `join: false` and the
default as `join: true`. It fails with the mapping inverted.

SoloDraft's local admission helper is renamed from `selectable` to
`admissible`, matching the snapshot field it reads, so it is not
mistaken for `snapshot().selectable`, which the default-agent chip
still uses on purpose. No behavior change.

Touched tests (services.test.ts, src/features/solo) pass; typecheck,
lint and format pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Follow-up to b2fce98, which keeps relay agent library rows through a
live disconnect (stale, `idle`) instead of clearing them. ProfilePanel's
provider-recovery test still expected the heading to fall back to the
public profile name ("Public Carl") once the relay dropped, so the
pre-push unit job failed. The heading now stays "Configured Carl" after
the drop; the later "Public Carl" assertion, after the provider is
disabled, is unchanged.

ProfilePanel.test.tsx passes. In the full Vitest run (802 files) no
other test pinned the old clear-on-disconnect display. Four unrelated
files (project-git, vite-config, claude-code usage, codex-sh) timed
out under full-suite load and pass when rerun alone. Typecheck and
biome pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…perseding native lists

Addresses PR #1909 review comments.

Recipient picker: while the native agent list loaded, the picker listed
and accepted every directory agent, so one the viewer does not control
could be chosen and was only refused at Send. It now keeps offering
only controlled agents throughout. While the list loads it still holds
Space and withholds "No matching people.", and now shows its existing
"Loading people" status for that wait too, so the list is never blank
without feedback (DESIGN.md, "States, feedback, and motion"). Controlled
agents appear once the list arrives.

Native list joins: a refresh that joined list A resolved when A
finished even if a later list B, started by a change such as a
creation, had superseded it and A's result was discarded. A New message
send that joined the launch list could then refuse while the list was
still loading. `AgentsService.refresh` now has a superseded read's
waiters follow the read that replaced it, as `library.refresh` does,
so a finished refresh always means the current list.

Tests: the picker test now asserts that only controlled agents are
listed while the native list loads, with the loading status in place of
an empty result. A service test asserts that a joined refresh waits
through a superseding read. Both fail on the previous sources: the
picker test on each change separately, the service test because the
joined waiter resolved early. The 24 related test files, typecheck,
biome and design:check pass.

Production diff: +11/-15 (net -4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

No new actionable findings at f1c3cfe773d2174e24f990078b59499d35076294, base/merge-base c9239f737f4985979eb5939e84c33ab3da2b1321, compared with reviewed 4a0e3d4f85f4a782f90f69f0293fd6d02a0f0d20.

Five feature patches are unchanged; the first adapts to the base’s partial-library recovery contract. We checked retained display rows versus admission, reconnect failure recovery and generation fencing, native-list joining/supersession, and the changed picker/retry consumers. The prior recipient filtering, loading-status and superseding-read repairs remain intact. Regression tests were inspected as source.

This is a focused integration review; unrelated changes in the new base were not fully reviewed. No tests, builds, app execution or CI assessment were performed. Native/real-relay behavior, performance, rendering, focus and assistive-technology behavior remain unverified. The current public description and attached diagram were inspected; the four historical fixture videos were not viewed this round and do not validate this head.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants