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

Keep replies inline with immediate previews and preserved drafts - #2610

Open
mahanti wants to merge 29 commits into
mainfrom
am-1104-reply-cleanup
Open

mahanti wants to merge 29 commits into
mainfrom
am-1104-reply-cleanup

Conversation

@mahanti

@mahanti mahanti commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Holding message text previews a reply in the existing composer across Messages, Activity conversations, Bestie and conversation tabs. The preview appears after 100 ms; composer focus waits for pointer release. Dragging to select, scrolling, or Escape cancels the provisional target without losing the draft. Reply actions retain the designer-approved MessageIcon, with the icon contract documented separately from thread tabs and links.

Cancellation restores prior following only when no newer reading intent or arrivals supersede the preview. Channel and thread owners preserve the selection anchor when messages arrive during a hold, including partially loaded threads. Explicit Solo replies create threads, and the exact thread reader retains its existing verified-evidence send gates.

The final scroll repairs keep upward channel input from restarting near-bottom following and let a downward wheel at the actual thread end resume following, including wheel input forwarded synchronously by the floating action bar. The thread-scroll measurements and keyboard-preference journey open real thread links. Main through 15fdd36 is integrated. Channel resize restoration also retires an anchor when newer native upward scrolling has superseded its captured position, preventing a queued callback from reversing a continuous wheel gesture.

Validation

  • Earlier coverage added three arrivals-during-preview cases (channel, thread, and partially loaded thread), with none removed. Those failed before their repairs in both engines. All six isolated thread-scroll measurement samples passed; corpus, gesture distances, and thresholds are unchanged. The related community-menu test now checks setup focus before opening rather than racing autofocus; restoration and removed-composer assertions remain intact.

  • The existing channel scroll case remains unchanged. One additional slow-scroll case splits the same 350px input into 20px and 330px, proving the first upward step survives preview cancellation. Before repair it failed with a zero bottom gap in Chromium and WebKit.

  • The existing rejected-Join journey now deliberately returns to the end through the floating message-action bar, after scrolling far enough upward to pause following. It checks actual pointer targets and waits for native gesture completion. Before repair Chromium reproduced the exact hosted overlap of 23.90625px. The notice-overlap and end-position assertions are unchanged.

  • Native selection, scroll geometry, top-layer wheel forwarding, and real resize ordering require browser coverage. No assertions were relaxed, no retries were added, and no production test seam or new lifecycle owner was introduced.

  • At 321e431, all 114 cases across six affected browser files passed in Chromium and WebKit locally, and all 114 passed on Linux. Linux also passed 63/63 WebKit reply repetitions and 70/70 Chromium search repetitions (seven full runs each, two workers, zero retries), plus 2/2 focused WebKit cases. Final head 50575c6 passed all mandatory push hooks, including types, related unit tests, and design checks. Hosted CI run 42163080324 passed on final head 50575c6: 73 successful checks, one configured Windows skip, no failures. Independent review found no blockers. Human acceptance covered those channel and thread repairs in the Mac desktop app. A subsequent pre-push unit regression found completed tiny near-bottom gestures retained active input intent; 50575c6 clears that intent before the final scrollend sample. All 137 existing owner tests and all 68 affected browser cases pass on Mac and Linux after that one-line repair. All 63 final WebKit reply repetitions passed on Linux with zero retries. Human acceptance of this last boundary remains pending.

The shared composer, ReplyContext, message action bar, and existing channel/thread scroll owners remain responsible for the UI. Earlier desktop checks covered identity/community loading, Bestie/Activity replies, drafts, Escape, and light/dark narrow/wide presentation. This follow-up changes scroll behavior; artwork is unchanged. Ready for review at Arjun’s explicit request on 2026-11-06. Automated validation and independent review are complete. Human acceptance of the final completed-tiny-gesture boundary remains pending; this readiness request does not attest that check was performed.

Conflict and shallow-scroll follow-up (2026-11-06)

Integrated main through 15fdd36 without rewriting the PR history. The MessageRow conflicts retain reply holds and incoming agent activity behavior/tests. The community-menu test retains both setup-focus checking and awaited menu autofocus. The later authorization-fixture overlap retains main’s keyboard option-focus sequence and all custody/recovery assertions. Removed the duplicate ChannelTimelineProps import created by the automatic merge.

Kalvin's P3 coverage question exposed a concrete Chromium boundary: a restored row settled at Y=54.984375 for a requested Y=55.984375. Exact equality kept the old anchor active and prevented arrivals from resuming near-bottom following. The existing channel owner now accepts its one-pixel scroll tolerance when retiring that settled standalone anchor; same-row, membership, and bottom-clamp guards remain intact. No new lifecycle state, timer, retry, or scroll policy was added.

Added two browser scenarios, with none removed: channel and thread arrivals after a completed 20px upward gesture, before further scrolling. Native wheel completion and real layout/delivery require the browser layer. The channel case failed before repair in Chromium. Channels retain their existing <80px follow policy; threads retain the upward reading intent already implemented in this PR. The tests document and exercise that distinction.

The current Inbox fixture uses main's event-based reading-position readiness and cleanup. This supersedes the earlier PR fixture synchronization while retaining every unread assertion and the existing fake-time limits. The resolved complete file passed 150/150 tests locally.

Final synchronization retains main's thread catch-up test fix from #2837, including its measurement frames and the negative assertion at 299ms before catch-up at 300ms. The earlier local synchronization change is superseded by that upstream version; the PR's Solo thread composer assertions remain intact.

The Linux WebKit failure on 17ee960 exposed two resize/scrollend orderings in the existing channel owner: a queued no-op anchor restoration could miss the final follow sample, and a late native wheel step could be reclassified as active input after the gesture had ended. The repair samples a fresh converged restoration after reader input while retaining preview-owned and cold-restoration anchors, and preserves completed-gesture state when superseding stale restoration. Production changes are eight additions and one replacement in ChannelTimeline.tsx; no tests, thresholds, timeouts, or retries changed for this repair.

Validation of that exact source snapshot on Linux: 292/292 owner tests; 34/34 complete message-click-reply and thread-resize browser cases across Chromium and WebKit; 30/30 focused shallow/slow/pending-arrival repetitions with zero retries. After integrating main, aa55728 passed all 58 cases across message-click-reply, thread-resize, activity-lifecycle and local-agent-activity in both engines on macOS (4.1 minutes). Independent review found no blockers (9/10). All mandatory push hooks passed on aa55728: TypeScript and related unit tests (498.56 seconds), design types/guards, Rust selection, and policy checks. DCO passed, and GitHub reports the PR mergeable. Hosted CI run 42271653744, attempt 2 passed on aa55728. Both required checks (CI required and DCO Check) passed, and GitHub reports the current head mergeable. All browser journey and repeated-check shards passed in the first attempt. The native lane exceeded its existing 20-minute budget, also reproduced on current main; a targeted retry of that cancelled lane and the aggregate passed without changing the budget or coverage. Native human acceptance remains separate.

The owner returned the PR to ready-for-review at 17:15:48 UTC on 2026-11-06; that concurrent status update is preserved. Native human acceptance of this precise boundary remains pending, and no review-completed attestation is added. In the updated desktop build, hold message prose until the reply preview appears, scroll upward about 20px and release, then observe a peer arrival: a channel near its end follows it, while a thread preserves the reading position. The draft and canceled reply state must remain intact. Continue farther upward to verify ordinary paused reading. Hosted checks and this attended desktop check are tracked separately from the macOS fixture results above.

mahanti and others added 4 commits November 4, 2026 19:40
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti

mahanti commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Independent agent review of 92ba37af9 found no blockers in the reply repairs and incoming forwarding/Inbox integration (9/10, bounded source review). The five affected browser files passed all 96 Chromium/WebKit cases, including the four formerly failing WebKit cases. Arjun explicitly accepted identity/community loading and the requested native reply flows on 2026-11-05. Chromium visual inspection also covered dark-wide and light/dark narrow layouts at 200% interface size. These are separate from the agent’s browser behavior checks.

These captures show actual updated components with disposable synthetic fixture data. They establish the displayed light-theme browser states only.

Reply controls at narrow width: the Reply message icon appears beside the preserved Forward action.

Narrow message actions

Inline reply: a 100 ms hold presents the preview inside the existing composer and retains the draft.

Inline reply with retained draft

Escape: canceling removes the reply target while preserving the draft and reading position.

Canceled reply with draft preserved

Required hosted CI and DCO passed on 92ba37af9: https://github.com/block/buzz-app/actions/runs/42082454184. Human acceptance and independent agent review are complete. The PR is ready for review, with re-review requested from kalvinnchau; code-owner approval is still required before merge.

mahanti and others added 2 commits November 5, 2026 09:20
Co-authored-by: bmo <a7b504d9a2bfe905589e9ac237d7d8d023b8d10547060ec9618e6487b55d756c@buzz.block.builderlab.xyz>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Use real message links for thread-pane setup while preserving draft, focus, media and activity assertions. Verify Reply in channel and Solo composers without expecting pane navigation.

Co-authored-by: bmo <a7b504d9a2bfe905589e9ac237d7d8d023b8d10547060ec9618e6487b55d756c@buzz.block.builderlab.xyz>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 5, 2026 15:07
@mahanti
mahanti requested a review from a team as a code owner November 5, 2026 15:07

@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 behavioral regressions and one memoization issue found. The held-pointer CI failure remains unresolved despite passing local repetitions.

@mahanti
mahanti marked this pull request as draft November 5, 2026 15:37
mahanti and others added 2 commits November 5, 2026 10:53
Keep the immediate 100 ms hold interaction intact. Prevent target messages from seeding another author's agent mentions, reuse the timeline reply callback, and exercise held scrolling in the verified timeline gutter.

Co-authored-by: bmo <a7b504d9a2bfe905589e9ac237d7d8d023b8d10547060ec9618e6487b55d756c@buzz.block.builderlab.xyz>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Co-authored-by: bmo <a7b504d9a2bfe905589e9ac237d7d8d023b8d10547060ec9618e6487b55d756c@buzz.block.builderlab.xyz>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 5, 2026 16:35
Co-authored-by: bmo <a7b504d9a2bfe905589e9ac237d7d8d023b8d10547060ec9618e6487b55d756c@buzz.block.builderlab.xyz>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as draft November 5, 2026 17:38
mahanti and others added 2 commits November 5, 2026 12:38
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 5, 2026 18:31
@mahanti
mahanti requested a review from kalvinnchau November 5, 2026 18:31

@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 additional findings: delayed text selection loses focus, and Reply icon guidance contradicts the implementation.

@mahanti
mahanti marked this pull request as draft November 5, 2026 19:05
Keep the 100 ms preview, defer composer focus until release, and cancel provisional reply targets when dragging or selection wins. Preserve the chosen Reply icon and document its approved usage.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 5, 2026 19:51

@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.

🤖 One remaining cancellation defect: withdrawing the reply preview does not restore live-bottom following.

@mahanti
mahanti marked this pull request as draft November 5, 2026 21:39
mahanti and others added 3 commits November 5, 2026 16:49
Restore the prior reading state only while the provisional reply still owns it. Preserve newer scroll intent in channel and thread timelines, with browser regressions for selection cancellation and upward scrolling.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Establish option focus before Home/End and Enter so the save-failure test exercises the intended selection. Preserve existing success, persistence, and error assertions.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Carry Bestie reply state into its extracted workspace component and keep Activity on shared conversation tabs. Adapt the Inbox reply test to current props and tabs, and await applied split geometry in resize assertions.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 5, 2026 22:49
mahanti and others added 2 commits November 5, 2026 18:41
…rements

Keep the current channel or thread reading anchor when rows arrive during a provisional reply. Compare the existing loaded message snapshots so strict threads work without loading older pages or changing unread policy.

Open the thread-scroll measurements through the actual thread link, and wait for settled pane geometry before aiming scrolling input. Cover arrivals before selection in both scroll owners and a partially loaded strict thread; wait for real pointer readiness and an active, settled preview in wheel cases.

Integrate main 028e5a1, including its timeline width measurement changes.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Integrate Builderlab catalog/settings updates, the keyboard latest-message ordering fix, and the channel-settings Shift+Escape correction. The reviewed held-reply cancellation fix is unchanged.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti

mahanti commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Addressed the P1 thread-scroll measurement failure in a6741b37c7dc19bea044627a44f38538de3798cf, published in final head d21eda9596255d2e9fcb815bc1c8e12f4bd4be63.

Both opening paths now use a real Buzz thread link instead of inline Reply. The opening click remains inside the measured interval; scrolling waits for the opening pane's geometry to settle before aiming input. Corpus, gesture distances, and assertion thresholds are unchanged. All six isolated open/fling/reverse samples passed across Chromium and WebKit.

The P2 selection-anchor fix and its channel/thread/partially-loaded-thread regressions are included. All 114 affected browser cases passed, including the notification and cancellation cases that failed in prior hosted WebKit CI. Mandatory hooks and DCO passed; independent review and Arjun's integrated desktop acceptance are complete. The new hosted run is still pending: https://github.com/block/buzz-app/actions/runs/42132643607. I am keeping the PR draft until that result is checked.

mahanti and others added 2 commits November 5, 2026 19:38
Integrate main and update the keyboard preference journey to open a thread link. Retire stale channel preview positioning, refresh paused-reader arrival controls, and respect newer thread scrolling while retaining real resize clamps.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Keep the composer focus-restoration and removed-field fallback checks while avoiding an assertion against a transient pre-menu focus state after the asynchronous menu open.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti

mahanti commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Fixed and pushed as 50575c6. Hosted CI run 42163080324 completed successfully: 73 checks passed, one configured Windows skip, no failures. Both previously failing jobs are green. All review threads are resolved. Mandatory push hooks passed; final repair passed 137 owner tests and 68 browser cases on Mac and Linux, plus 63 Linux WebKit reply repetitions without retries. The PR remains draft pending human acceptance of the final tiny-scroll/scrollend boundary; earlier Mac checks were accepted. https://github.com/block/buzz-app/actions/runs/42163080324

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 6, 2026 02:08

@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.

🤖 One remaining WebKit reading-position failure; the prior native-selection and thread-scroll measurement issues are repaired. Required CI remains red.

mahanti and others added 2 commits November 5, 2026 22:49
Record upward channel intent before sampling near-bottom follow, and resume thread following for downward wheel input at the actual end. Cover slow cancellation and synchronous toolbar forwarding without changing the existing full-wheel or notice-overlap assertions. Integrate main.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Capture the queued restoration's scroll position and upward-input evidence. Newer native movement retires the restoration generation before it can overwrite the reader, including cleanup writeback. Preserve browser clamps and settled-state bookkeeping. Integrate the current navigation contract from main.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as draft November 6, 2026 04:07
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>

@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.

🤖 No new proven regression found. The completed tiny-scroll contract still needs explicit acceptance and coverage; see inline comment.

"selection with pending arrivals",
"scroll",
"slow scroll",
]) {

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.

🤖 [P3] Cover arrivals after the completed shallow scroll

The slow-scroll case checks a positive gap after 20 px, then scrolls another 330 px before testing arrivals. It therefore does not distinguish the completed shallow-gesture contract: ChannelTimeline.tsx:1824 clears upwardGesture before final position sampling and can resume following within its 80 px bottom threshold, while ThreadPanel.tsx:1879 keeps a shallow upward move paused. The thread variant is skipped here. This is a contract/coverage question, not a proven regression against main. Please explicitly accept or reconcile the two behaviors and assert an arrival after the shallow gesture has completed, before the second scroll; cover the thread behavior or document why it differs.

Preserve the reply-hold and agent activity changes when resolving conflicts,
keep the awaited community-menu focus barrier, and remove the duplicate
ChannelTimelineProps import introduced by the automatic merge.

Cover live arrivals after completed shallow gestures in channels and threads.
Retire a settled channel anchor within the existing one-pixel scroll tolerance:
Chromium rounded the restored position by one pixel and otherwise retained
that anchor indefinitely, preventing near-bottom follow from resuming.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as draft November 6, 2026 16:00
mahanti and others added 4 commits November 6, 2026 11:33
Integrate main through a58e604. Complete the native thread reveal frame
before the Inbox reading fixture takes over timers, then dispatch the window
focus event under the controlled clock. Current main and the prior merged PR
both reproduced the same stalled-reading assertion; the full Inbox file now
passes all 150 cases without changing assertions, timeouts, or production
reading semantics.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Deliver the root insertion's MutationObserver callback before advancing the
frame clock. The reading observer now keeps its lifecycle when only the
latest message changes, so the fixture must exercise its deferred measurement
instead of depending on an effect restart. Keep the 300ms dwell and exact
catch-up assertion unchanged.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Resolve the overlapping catch-up fixture change in favor of upstream's
measurement frames and 299ms/300ms boundary assertions. Preserve the PR's
Solo thread composer coverage and reply/scroll owners while integrating
current coordination-history behavior.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Keep upstream's keyboard option-focus sequence when resolving the duplicate
authorization test repair. Preserve its custody and recovery assertions while
integrating the current Messages navigation and Canvas fixes.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
@mahanti
mahanti marked this pull request as ready for review November 6, 2026 17:15
Sample a fresh converged restoration after reader input even when its scroll
is a no-op, retaining existing preview and cold-restoration anchors. Do not
reclassify a late observation of a finished wheel gesture as active input.
This preserves the shallow reading offset until arrivals and keeps larger
upward gestures paused.

Integrate current main and retain its event-based Inbox readiness fixture.
The scroll source matches the independently reviewed Linux snapshot that
passed 292 owner tests, 34 browser cases and 30 focused repetitions.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
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>
kazemek21 added a commit that referenced this pull request Nov 10, 2026
## TL;DR
Captured explicit reads now check the current saved manual-unread
journal inside the replacing transaction, so a newer local
choice—including one saved by another window—is not erased. Retries
retain their original cut; the additive
`unread.prepareThreadRead(channelId, rootId)` captures only retained
incoming replies without reading the root or advancing the channel.

The fence also preserves earlier same-window queued manual actions,
authorizing their revisions only after a successful save whose prior
same-key intent is also captured. An earlier queued save cannot mask an
intervening unread choice from another window. It leaves reading-lease
ownership, passive observations, remote-marker ordering, journal format
and relay protocol unchanged.

Mark all now reserves one shared saved revision cut before queued
writes. Each predicate retains only the revision, not a full journal
copy.

### Validation
- Shared-cut correction: pinned Linux Blox run of both complete owner
files passed 383 tests (174 read-state; 209 unread). The per-sweep
capture-budget and capture-failure regressions fail on the prior
implementation; older queued-save controls pass. No new browser cases.
- Linux Blox, pinned tools: both owner files passed 366 tests on the
initial head. After review fixes, `pnpm exec vitest run
src/features/relay/unread.test.ts` passed all 206 tests. The final
queue-ordering fix passes all 174 tests in
`src/features/relay/read-state.test.ts`, typecheck and the commit/push
hooks.
- Pre-fix channel regressions: six failures; bypassing the new
transaction fences makes eleven targeted concurrency regressions fail.
Replacing canonical thread selection with parent-edge traversal makes
the narrow-thread regression fail. Restoring the implementation passes
both owner files.
- Three successful-save interleavings fail before the final fix and pass
afterward, with three matching controls: channel, thread and message
marks overwritten by an older queued save after another window marks
unread.
- Eight additional regressions fail before the review fixes and pass
afterward: root/reply deletion before or after persistence, and shared
sidebar hints with absent, present or cross-window forced-message
overlays.
- Touched-file Biome, `pnpm typecheck`, `pnpm author:build`, strict
generated-SDK consumer compilation and repository commit/push hooks.
- Deterministic fixture gates cover newer channel/thread/message marks,
second-window saves at the transaction boundary, queued ordering, retry
cuts, later arrivals, independent root/thread markers and
access/lifecycle expiry. No browser tests were added or removed.

Generated type-only SDK declarations expose the additive method through
the existing `UnreadCapability` export; existing channel and message
APIs remain compatible. Capture requires initialized read-state storage.
The thread API does not discover history; receipts remain device-local.
A narrow read leaves the shared sidebar hint until channel open
reconciles it, since that hint has no subtree ownership. Its shared
message-force fence is conservative: a newer mark elsewhere in the same
channel can also reject the save. This does not establish native runtime
behavior, identical remote-tuple ordering or atomicity through an
operation's eventual return.

Related independent work remains with #2060 (quiet Solo/Inbox), #2610
(selected-message UI), #3181 (passive recovery), and #2939/#1666/#2956
(notification/audience behavior). This change is limited to the explicit
mutation/preparation seam.

Fixes NEXUS-4426

[Initiative
status](https://g2.sqprod.co/apps/kylez-buzz-improvements-status)

*Posted by Codex (gpt-6.1-sol) on behalf of kylez.*

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Codex <noreply@openai.com>

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.

2 participants