Skip to content

Let a local Codex agent join its owner's huddle by voice - #2321

Merged
tlongwell-block merged 44 commits into
mainfrom
eva/codex-huddle-voice
Nov 6, 2026
Merged

tlongwell-block merged 44 commits into
mainfrom
eva/codex-huddle-voice

Conversation

@tlongwell-block

@tlongwell-block tlongwell-block commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Opened by Eva (Tyler's Buzz agent) at his request.

Head dc7af410 on main c202642f. 40 files, +4,202/−319, about half of it tests.

Problem

Buzz huddles connect people, but a local Codex agent can't hear its owner or answer in the room.

Outcome

An eligible agent gets its own audio seat in its owner's huddle. The owner can speak to it, interrupt it, and ask it to use its existing Buzz tools. The agent speaks as itself and posts final transcripts with speaker credit.

What this PR changes

1. Agent voice seats (the feature)

Native seat (src-tauri/src/relay/huddle.rs, relay/huddle/audio.rs):

  • The single call slot is now a map, with one claim() rule: one call for the person, plus one per agent.
  • seat_agent checks the signed-in viewer and the owner before dialing. It joins only an existing room, and never creates, announces or publishes membership.
  • Each seat signs NIP-42 as its agent. That's AppAgent::audio_auth in crates/agent-controller, through the host signer: app_agents::signer is now pub(crate). It never falls back to the human signer.
  • NIP-FI: the seat carries the agent's own assertion, and its expiry comes from the new Admission::expires_at. The heartbeat rechecks admission, so a deleted or removed agent loses its seat within 10 s.
  • Owner audio filter: decoding checks the current roster pubkey on every frame, before any decoder exists. Peer indices get reused, so the check is by pubkey. Every device of the owner is admitted, and everyone else is dropped.
    • v2 caveat: the protocol has no occupancy epoch, so a delayed packet from a previous occupant can be attributed to the owner after an index is reassigned. Strict owner-only audio needs a media epoch or relay ordering. This is documented in docs/huddles.md and in help.

Host seat lifecycle (src/features/huddle/voice.ts, bundled/huddles/index.tsx):

  • Seats follow the owner's connected call on this desktop, using that call's room roster.
  • The room is resolved only when a local, owner-matched voice candidate exists. Until that succeeds, no seat opens, and a failed read stays silent and retries on the next roster or agent update.
  • A seat qualifies only for a running local agent that:
    • is owned by the signed-in viewer,
    • is on the same relay origin,
    • has verified membership in the room itself (parent membership is not enough),
    • has a voice hook.
  • Unknown or stale eligibility fails closed.
  • Teardown aborts audio at once and waits for an idempotent close().
  • The TS side repeats the owner check as a second layer.

Agent runtime contract (features/agents/service.ts, runner.ts):

  • New AgentVoiceSeat, AgentVoicePipe and AgentVoiceConnection types, plus an optional voice hook on agent types. The contract carries PCM, not WebRTC types.
  • openVoice resolves an agent exactly like a text launch: the same start, config, team and protection gate. The gate is extracted into assertProtected, with the same logic.
  • Transcripts are posted with h tags only, as <name> (transcribed by <agent>): …, and never mention anyone.

Codex adapter (bundled/codex/voice.ts, runtime.ts, index.ts):

  • The seat uses the room's existing Codex thread, with WebRTC realtime and automatic handoffs.
  • Voice-delegated turns are adopted into the ordinary turn and Buzz-tool path; room mentions steer the active delegated turn. Queued typed work waits for adopted voice work, and typed work survives hanging up.
  • loadThread is extracted from execute, and text and adopted voice requests are now separate types.
  • Context:
    • The seat reads Buzz instructions, team and core memory once, at open, and gives the same block to the realtime model and the coding thread.
    • Codex keeps a resumed thread's first developer message and ignores the developerInstructions passed to thread/resume. So when the room already has a thread, the seat injects its context with thread/inject_items before any handoff (16c36113).
    • Spoken turns don't refresh context mid-call; typed turns still do.
  • Protected agents are refused by the voice hook before any setup, and again by the runtime. It isn't known whether Codex protection reviews realtime input. Devices without RTCPeerConnection are refused too.
  • A typed turn on a seat's thread still resolves its own model and effort pick. Voice-delegated turns keep the seat's.

UI:

  • The owner sees a "listening to you" caption on their own active agents (HuddleAvatarCloud, huddle_window.rs, window.ts). Remote participants see no listening badge.
  • Voice errors show in a dismissible toast (HuddleCapsule) and in the companion window's alert. Errors and dismissals are scoped to their call id.

2. Huddle-room text behavior, without a voice seat

  • A typed message to a Codex agent in a huddle room is answered at top level, with threadReplies off. This applies whether or not a seat is open (isHuddleRoom, ChannelFacts.about). Reply tags now come from replyRoot; for other channels they're unchanged.

3. Huddle audio for everyone (also on branch eva/huddle-audio-fixes, c5a5ecbb)

These change calls between people too. They were split onto their own branch from main, so they can be reviewed or landed first, and this PR merges that branch.

  • Socket writes go through their own queue (five messages; newest dropped when full) instead of being awaited inline. Before, a backpressured write froze receive and playout for up to 2 s. A send stuck for 2 s still ends the call.
  • Playout catch-up: missed 10 ms ticks burst instead of being skipped, with a yield between overdue pulls.
  • Multiple devices per person: they're mixed once per tick and clipped after summing. Before, two devices pushed 20 ms per 10 ms tick into one playout and overflowed it.
  • Mic sender: the mic uses a serial sender holding the newest four waiting frames plus one in flight, instead of dropping every frame captured while a send is in flight. Mute discards waiting frames. Agent voice uses the same sender.
  • One playout helper: owner playback and Codex input share playout.ts (60 ms lead for the owner, 20 ms for Codex, 200 ms drop cap). Playback now re-anchors 20 ms out only after a true underrun; before, it re-anchored whenever less than 20 ms was queued, which left small holes in continuous audio.

4. Linux

  • WebRTC is enabled on the main webview (set_enable_webrtc), because WebKitGTK hides RTCPeerConnection otherwise. Nothing opens a peer connection unless a voice seat does.

Docs

docs/huddles.md, docs/buzz-help.md ("Agents in Huddles") and bundled/codex/README.md.

Authorization

A voice-delegated turn's input is the realtime model's restatement of the owner's speech (realtime_delegation_from_handoff), not the owner's words. Anything that reaches the owner's mic also reaches that model. The turn runs with the same tools and per-turn authorization as a text mention.

Accepted for v1

  • With two agents in one room, each one transcribes the owner, so the owner's speech is posted once per agent.
  • A seat whose turns are all voice-delegated saves no session prompt.
  • Transcript publication has no rate bound.
  • Owner-on-another-device is out of scope.

Review fixes since e1462b42 (Carl's review 5802619256)

  • 68753a54 + 4af6d5d6 (Eva): protected agents are refused before setup, in both the voice hook and the runtime. A typed turn keeps its own model and effort while a seat is open, in both the idle and the queued case.
  • d6947137 (Meli): room resolution is gated on candidates, with a silent, event-driven retry. The companion window shows voice errors. Docs and help carry the v2 caveat.
  • Each new regression fails without its fix and passes with it.
  • Deferred, optional: voice is still reachable through the shared types() snapshot. Scoping it per plugin would change the identity of what types() returns, and the agents service isn't a permission boundary.

Validation

dc7af410 merges main c202642f into ac3ba6b3. The merge was clean, and it brings in #2837, which fixes the ThreadPanel catch-up test that failed CI at ac3ba6b3.

Check at dc7af410 Result
biome, tsc --noEmit Clean
Full vitest 1,003 files, 18,956 passed, 0 failed, 22 skipped
Native clippy --all-targets -D warnings Clean
Native lib --skip window_state 1,002 passed, 0 failed, 2 ignored
Pre-push hooks All passed

window_state crashes with SIGSEGV on clean main as well. This PR doesn't touch it.

Live call at ac3ba6b3 (macOS, ChatGPT sign-in, BlackHole as a virtual mic, agent Max Trace). The merge from main changes no voice, huddle or Codex code.

  • Seat: Max Trace took his seat about 1.5 s after being added to the room. WebRTC was up and OpenAI received audio.
  • Delegation:
    • Disk space: he ran df -h and the answer matched the real Data volume, 11Gi free and 99% used.
    • Hostname: he ran hostname and read it back correctly.
  • Barge-in: counting stopped at "Five" when the owner spoke, then he answered "Paris."
  • Stale context after rejoin (the 16c36113 fix):
    1. By voice, core was set to "Pelican" and the seat opened.
    2. Then core was changed to "Falcon", and the agent was removed and re-added.
    3. Codex logged thread/resume, then thread/inject_items, then thread/realtime/start.
    4. The delegated turn answered "Test code word is Falcon two two nine zero." from its developer context, without a tool call.
    5. The original developer message still holds Pelican and the injected item holds Falcon, so the fix is what supplied it.
  • Companion window: when the agent's Codex process was killed, the "Agent voice stopped" toast appeared, and so did the companion window's alert.
    • Known follow-up: the alert shows raw Codex stderr, ANSI escapes included. It comes from the existing rpc.ts exit path on main.
  • Not exercised live:
    • a protected agent's refusal (unit-tested only);
    • a physical mic;
    • Linux.

Reviews

  • Carl's three blockers are fixed at this head. They need a fresh pass.
  • At a731b19e/e1462b42:
    • Wren: minimalness 9, elegance 9.
    • Meli: 9/9/9 for her slice, and she reopened readiness on Carl's review.
    • Sami: the native code clears 9.
    • Quinn: his scope findings are in sections 2 to 4.

tlongwell-block and others added 30 commits November 3, 2026 15:37
An agent call joins an existing room signing NIP-42 as the agent, with its
owner's NIP-OA attestation and its own NIP-FI evidence where its community
takes one. The relay already admits delegated agents, so nothing changes
there. Native, not the webview, decides who the agent hears: the owner's
pubkey comes from the agent record, every frame is checked by pubkey before
a decoder exists (indices are reused), and every owner device is heard.

Calls are keyed by id. The person keeps at most one call, each agent at most
one seat, and close, touch, pcm and document teardown work as before. An agent
seat skips room creation, announcement and membership publication, which
stay the person's. It ends within one 10s heartbeat once the agent is deleted
or leaves the community, or the owner's grant changes.

Linux WebKitGTK also enables WebRTC, which agent voice needs.

Co-authored-by: Lain <7d83e66e949bde01875eadd5794011c24c661ec1ddb489cc759d075f4440315a@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
In its organization's community, the audio upgrade sends the agent's minted assertion and nothing else; without it the relay would judge the seat as plain.

Co-authored-by: Lain <7d83e66e949bde01875eadd5794011c24c661ec1ddb489cc759d075f4440315a@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Agent rows outlive an account switch on this desktop, so the seat refuses an agent another account owns, and a call whose viewer is no longer the one signed in. Both refusals come before anything is dialed; a control shows the owner's own seat does dial.

Co-authored-by: Lain <7d83e66e949bde01875eadd5794011c24c661ec1ddb489cc759d075f4440315a@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Follow the connected human call with independent agent audio seats only for verified room members owned by the viewer. Abort on eligibility loss, retain reservations until teardown finishes, and renew native leases.

Resolve voice config through the text launch resolver and retain it for the seat. Publish final transcripts through the owning plugin with room-only tags. Voice follows the owner on this desktop; calls on another device do not seat agents here.

Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Carry seat PCM over Codex WebRTC with automatic handoffs. Adopt backend voice turns into the ordinary tool lane, retain resolved opening settings, and forward final transcripts through the host. Flatten huddle replies so owner mentions steer voice work. Close media immediately and drain retained thread work before releasing the seat; typed requests survive hangup.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Remove the adapter gate and its direct-runtime test in favor of host protection coverage. Simplify realtime config setup and document optional-event narrowing.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Schedule bounded owner frames using their actual duration so native 10 ms playout reaches the model. Document the input contract separately from output and cover 480-sample scheduling and invalid sizes.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Require text events structurally and precompute the publish/activity root independently of the tool root. Reuse the huddle classification and document voice lane teardown ordering.

Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
* origin/main: (26 commits)
  fix(identity): read FI backup status through the native forwarder (#2344)
  fix(relay): show the relay's reason when it refuses a write (#2031)
  perf(relay): look up archived identities in a per-snapshot set (#2359)
  fix(messages): keep link previews clear of message hover actions (#2351)
  fix(security): close workflow, Canvas, upload, and broker gaps (#2360)
  fix: prevent false leave failures after membership updates (#2310)
  feat(bestie): remove your Bestie, and let remote agents be Bestie after a warning (#2041)
  feat(messages): open the sender's profile from the byline name (#2253)
  feat(native-mcp): one-command launcher and MCP registration for agent driving (#2272)
  perf(composer): stop per-keystroke chip re-renders and reparses in long rich drafts (#2251)
  fix(agents): prevent history from forging prompt requests (#2223)
  Enable automatic key backup in preview releases (#2325)
  feat(read-state): n-blob NIP-RS sync with a 30-day floor, frontier only (#1286)
  feat(huddles): provide read-only huddleStatus for optional consumers (#2327)
  feat(search): show verified names in people search (#2322)
  Fix voice-note sending and restore waveform previews (#2283)
  Preserve Solo queued messages across restarts (#1704)
  feat(plugins): resolve attachment renderers by order, like panels and links (#2284)
  fix(channels): keep muted DM names from becoming bold (#2184)
  fix: command-click opening desktop message links in the browser (#2288)
  ...

Co-authored-by: Codex <[email protected]>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>

# Conflicts:
#	src/bundled/huddles/index.tsx
#	src/features/huddle/service.test.ts
Serialize native sends through one per-seat FIFO instead of dropping every
frame while an invoke is pending. Keep the newest eight waiting frames and
clear them on stop or failure; remove the redundant Codex busy gate.

Cover the real Codex-to-seat boundary with a five-frame slow-send regression,
plus overflow and abort/failure cleanup. Human mic capture is unchanged.

Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Catch up missed native pulls, admit socket packets before playout, and yield
between overdue pulls. Keep frontend playback contiguous while audio remains
queued; re-anchor at 20 ms only after a true underrun.

Add async real-Opus catch-up and frontend scheduling regressions. Live
100/100/100 ms stalls retain a stable ~122 ms queue with no repeat gaps or
recovery expansion. A 150 ms stall retains ~171 ms and some concealment;
yielding is not an I/O-delivery guarantee.

Validation: 18 native audio tests and 8 frontend audio tests pass; typecheck
passes. Full Rust workspace has 26 passing targets plus the pre-existing
mock-window AppKit SIGSEGV in the library target. Full frontend has one
custody-loss test failure; its complete file passed on recheck. Neither full
suite is wholly green. Human listening and upstream roughness remain open.

Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Resolve Codex voice seats against main's execution targets and agent
protection: voice opens the agent's protected server, resumes only a
thread saved under the same protection state, records that state when it
saves, and refuses agents bound to a plugin execution target. Text turns
keep main's pending/refusal bookkeeping on top of the shared loadThread.
Await the now-asynchronous host start in openVoice. The test fixture's
turn ids follow turn/start order, so a refused start still takes an id.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
…input

Human playback and the Codex input bridge each scheduled PCM buffers with
their own copy of the timing rules, and the copies had drifted: Codex still
re-anchored every frame at currentTime + 20 ms, so a frame arriving with 7 ms
still queued left a 13 ms hole in continuous audio. Both destinations now use
createPlayout: contiguous while audio is queued, re-anchor 20 ms out only on a
true underrun, drop beyond 200 ms ahead. Startup lead stays per destination
(60 ms owner, 20 ms Codex).

The new Codex regression fails at the previous head (start 0.043, expected
0.03) and passes here.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
The catch-up regression from 7afdbc9 held a std MutexGuard across
writer.await, which clippy -D warnings rejects (await_holding_lock). Scope the
guard to the assertions; behavior is unchanged.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
The bound checks where an incoming frame would start, not where it ends, so an
accepted long frame can end past 200 ms. Describe the rule as implemented.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Every outbound send (mic frames, heartbeat pings, pongs) was awaited inside
the one select loop that also reads the socket and pulls NetEq playout, so a
backpressured write froze delivery to the speaker for up to the 2 s stall
bound. Writes now go through a 5-message queue to a writer future in the
same task. A full queue drops the newest message, as `huddle_pcm` already
does for mic input. A send stuck for 2 s still ends the call with the same
error, and a clean stop drains the queue and closes within the old bound.

The new test fills the client's send buffer against a relay that never
reads: at the previous head playout made 9 of 100 due pulls in 1 s; with
the writer split it keeps pace.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
A controlled stall: the relay is not reading yet and the client's WebSocket
and kernel send buffers hold large filler messages, so the heartbeat Ping is
the send in flight. Ten mic frames arrive while the queue is full and the
relay sends a Ping. After the release, the relay sees frames 0-4 in order,
the drop gap, frames 10-12 with media time matching sequence, and the Pong
(tungstenite's automatic reply). A clean stop delivers Close and returns Ok
within the bound. Raising the queue capacity to 6 fails the order check, and
both stall tests fail at 3ce992b.

The fill helper is shared with the playout stall test. It now feeds whole
WebSocket messages under an overall deadline instead of writing raw bytes and
ignoring errors. The stop comment states that accepted audio is still sent.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
The recovery test relied on two 100 ms sleeps for its ordering. It now proves
it: the mic channel has capacity 1, so once ten frames are sent the loop has
taken frames 0-4 into all five slots, and the relay follows its Ping with a
roster update whose participants event proves the Ping was read first, while
nothing had drained. Two mutations confirm this at this head: asserting that
the explicit Pong's try_send fails still passes, and deleting the explicit
Pong still delivers the probe Pong, so the test pins tungstenite's automatic
reply under saturation. The whole recovery phase is bounded at 1.5 s, and
media time is checked on every frame. Stop is tested after the queue drains.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
tlongwell-block and others added 5 commits November 5, 2026 14:08
Two conflicts, both additive on each side:
- src/bundled/codex/runtime.ts: main threads author names into the text
  turn's `turnInput` (#2628); the branch moved that block into its
  text-request path. Kept the branch's structure and applied main's argument.
- src/features/agents/service.ts: main adds HostedInstructions; the branch
  registers HostedAgentType. Kept both.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
A voice request reaches Codex as a <realtime_delegation> turn. Codex ran
the tool locally, posted the answer with buzz__send as the base prompt
asks, and ended with an empty final message. The voice model speaks only
that final message, so it got nothing and made an answer up, or guessed
while the lookup was still running.

Tell Codex to put a spoken request's result in its final answer, and the
voice model to acknowledge and wait instead of stating unreturned facts.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Conflicts: codex/runtime.ts takes main's request.activityScope for
acknowledgeActivity (the voice request scopes activity to the room);
federated_identity/agent/mod.rs keeps both main's assertion() and the
branch's expires_at().

Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Main added a test-only AppAgentHost::holding that also takes AgentTokens,
duplicating the branch's copy, and made authorize() consult NIP-FI
discovery. Use main's holding, and have fixture_at mark its own community
as advertising NIP-FI, as requiring() already does for relay.example.

Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Tyler Longwell <[email protected]>
Mix simultaneous devices by pubkey once per native playout tick, clipping
only the final sum. Keep owner filtering before decoding and independent
people on separate output streams.

Use one serial sender for human and agent PCM. Retain the newest four
waiting frames, discard waiting microphone audio on mute, and clear all
waiting frames on abort or failure. This preserves human capture bursts
while reducing the agent's prior eight-frame waiting buffer to four.

Cover frame order, mute and leave, late failures, and real-codec mixing
including final clipping and device removal. Document the queue limits
and multi-device listening behavior.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
@tlongwell-block
tlongwell-block marked this pull request as ready for review November 6, 2026 13:55
@tlongwell-block
tlongwell-block requested a review from a team as a code owner November 6, 2026 13:55
tlongwell-block and others added 3 commits November 6, 2026 09:38
Codex keeps a resumed thread's first developer message and ignores the
developerInstructions passed to thread/resume, so a voice seat opened on a
room that already had a Codex conversation delegated work with stale Buzz
instructions and core memory while the realtime model had the current copy.
Inject the seat's context as a developer item before any handoff.

Verified against real Codex 0.167.0 over a loopback model: the injected item
reaches the next model request and survives a later resume.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Socket writes go through their own bounded queue, so a stalled send no longer
blocks receive or playout. Missed playout ticks are caught up with a yield
between overdue pulls instead of skipped. Frames from one person's several
devices are mixed once per tick and clipped after summing; before, two devices
pushed 20 ms per 10 ms tick into one playout and overflowed it.

Microphone PCM uses a serial sender keeping the newest four waiting frames plus
one in flight, rather than dropping every frame captured while a send is in
flight; mute discards waiting frames. Frontend playout moves into one shared
helper with unchanged timing.

Split from the agent voice seat work (#2321) so calls between people are
reviewed on their own.

Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
* eva/huddle-audio-fixes: (29 commits)
  fix(huddle): keep calls flowing under stalled sends and multiple devices
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  fix(inbox): open DM thread rows as the thread (#2587)
  test(channels): complete the context navigation fixture (#2783)
  fix(channels): open a thread's channel at its root message from the #channel link (#2377)
  fix(people): find people by a pasted npub or hex key (#2396)
  test(bestie): open the narrow long-history thread with clickRow (#2723)
  Account-first onboarding and sign-in recovery (#2579)
  fix(agents): scroll the avatar dock with a mouse wheel (#2567)
  fix(avatars): keep Done in view when the avatar popup is short (#2706)
  feat(messages): highlight reactions you've made (#2709)
  feat(usage): move Usage from the sidebar into Settings (#2742)
  fix(messages): keep a playing row mounted in the channel timeline (#2695)
  Fix plugin status declaration closure for author consumers (#2643)
  fix(navigation): keep empty new chats out of Back history (#1617)
  [buzz-app] Recover remote agent message outcomes (#2542)
  feat(messages): name background send destinations and attach the files that fit (#2406)
  feat(drafts): clear a draft from its row (#2725)
  Authorize external application identities in Buzz (#2685)
  ...

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>

# Conflicts:
#	docs/huddles.md
#	src-tauri/src/relay/huddle/audio.rs

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

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Blocking findings (3). Court review at e1462b42 covered four lanes: native audio and signing, host seat lifecycle, Codex adapter, and authorization. Each blocker below is explained in its inline thread.

  1. Protected Codex agents get a voice seat, despite the documented refusal.
  2. Typed turns ignore their model and effort picks while a seat is open.
  3. A failed room resolve shows "Agent voice stopped" to callers who have no voice agent.

Three optional notes are inline too: direct voice access from plugins, a v2 owner-filter limitation at index reuse, and the voice error missing from the Huddle window.

Checked with no finding:

  • Agent NIP-42 signing never falls back to the human signer.
  • Seat claims and teardown.
  • Admission expiry and revocation, which match the human call path.
  • Outbound queue and device mixing.
  • Fail-closed eligibility.
  • assertProtected parity with the text path.
  • h-tag-only transcripts.
  • Resumed-context injection, voice turn adoption ordering and huddle reply roots.

Validation gaps: this was a source review, with no local suites and no live call. The PR's own list of checks not re-run at this head still applies: speech and barge-in, delegation, physical mic, and Linux. Two questions are unverified: how faithfully the realtime model restates the owner's request, and whether protection reviews realtime input.

Merge criteria: fix the three blockers, each with a focused test, then complete a live call at the new head.

async voice(seat: AgentVoiceSeat, agent: AgentHandle) {
if (this.disposed) throw new Error("Codex runtime is disposed");
seat.signal.throwIfAborted();
// Voice runs on this computer; a plugin execution target has no voice seat.

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.

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Blocking: protected Codex agents now get a voice seat, contrary to README.md:44 and the PR description.

a6250d31 dropped the adapter's refusal in favor of the host gate. Then #1879 (on main since 2026-11-04) declared protection: true on the Codex type (index.ts:166). assertProtected (runner.ts:784) checks only whether the type supports protection in general, so a protected Codex agent now passes. Neither index.ts's voice nor runtime.voice refuses it. The seat then opens the protected server, shares core memory and instructions with the realtime model, streams the owner's audio and posts transcripts.

We have not verified whether native protection reviews realtime input. That is why the documented refusal matters here.

Fix: restore a voice-specific refusal for agent.protection before setup, thread or media work, and add a test that a protected Codex agent is refused. If protected voice is meant to ship, that's a product decision: update the docs and prove that an adopted voice turn's tool calls are reviewed. The only protection test today (service.test.ts:2362) covers a runtime without protection support.

Comment thread src/bundled/codex/runtime.ts Outdated
this.status(agent.key, key, "Idle");
active.finished = true;
const loaded =
lane.voice?.loaded ??

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.

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Blocking: while a voice seat is open, typed turns ignore their model and effort picks.

execute reuses lane.voice.loaded whenever a seat exists. A typed request therefore sends the seat-open model/effort to turn/start (:2755–2756), and prepare's per-request pick (:2419) never runs.

Scenario: a seat opens with A/low. The owner then sends a typed room mention choosing B/high, either while the agent is idle or queued behind voice work. The turn runs on A/low.

Fix: resolve a typed request's per-turn model and effort independently of the seat's retained thread resources. Cover both the idle and the queued case. The existing pick tests (runtime.test.ts:2067–2155) don't combine picks with voice.

Comment thread src/features/huddle/voice.ts Outdated
const room = call.room;
if (call.phase === "connected" && room && call.id !== resolvedCall) {
resolvedCall = call.id;
void session.channels.resolve?.([room]).then(sync, (error: unknown) => {

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.

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Blocking: a failed room resolve tells callers with no voice agent that "Agent voice stopped".

This resolve runs for every connected call, whether or not any eligible voice agent exists. Its rejection goes to report → huddles.voiceError, which renders the "Agent voice stopped" toast (HuddleCapsule.tsx:186) with a raw Error: … string.

Scenario: someone with no agents joins a Huddle and a relay hiccup fails the resolve. They're told an agent's voice stopped. Because resolvedCall is already set, the resolve is never retried for that call.

Fix: resolve only when a running, owner-matched agent with a voice type exists. Treat a resolve failure as silent and fail-closed, not as an agent-voice error, and let the next list change retry it.

},
}),
} as unknown as AgentType);
...(voice

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.

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Optional: voice is a raw callable on the shared types() snapshot.

Any plugin that injects agents can call openVoice for a running agent and any channel. The room-membership, huddle-room and owner-in-call rules exist only in huddle/voice.ts. Plugins are trusted and the agents service isn't a permission boundary (docs/plugin-architecture.md:289, 594), so this isn't a security blocker. It is contract drift from handle()'s scoping to the calling plugin's own types.

Suggestion: give only Huddles a host-scoped entry point, or re-check room eligibility next to openVoice.

let Some(peer) = peers.get(&index) else {
return Ok(());
};
if self.only.as_ref().is_some_and(|only| only != peer) {

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.

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Optional: the v2 protocol can't fully guarantee owner-only audio.

This filter checks the current pubkey for an index. The v2 audio client has no per-frame occupancy epoch, and the relay prioritizes control frames over audio. So when an index is reassigned to one of the owner's devices, stranger audio already queued at that index can pass the filter. Decoder reset on reassignment (:358) can't reject a stale packet that arrives after the roster update. The room and outbound queues hold 8 and 16 frames.

Human calls on main share this limitation, and we found no way to sustain the leak or force the reassignment. Worth a doc caveat now, and a media epoch, or an equivalent relay ordering guarantee, before calling the seat strictly owner-only.

? picture
: null,
own: key === call.destination?.viewer,
listening:

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.

🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.

Optional (UI): the Huddle window never shows the voice error.

window.ts:148 forwards call.error, but the connected Huddle window renders only the presentation error (HuddleWindowView.tsx:158). In the companion window the agent's "listening to you" label just disappears, with no explanation. Show the same notice there.

tlongwell-block and others added 6 commits November 6, 2026 10:55
Restore the voice adapter's refusal of protected agents. Codex now declares
protection support, so the host's shared gate passes them, and nothing shows
that protection reviews realtime input. The refusal happens before any setup,
thread or media work, as the README states.

A typed turn on a voice seat's thread reused the seat-open model and effort,
skipping its own pick. Resolve the typed turn's model and effort per request
while keeping the seat's thread resources; adopted voice turns keep the seat's.

Both regressions fail without their fix: the protected seat reaches setup, and
idle and queued typed picks run on the seat's model and effort.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Resolve room membership only for local owner-matched voice candidates and
wait for resolution before opening seats. Failed reads stay silent and
retry on later notifications; stale call completions cannot affect the
current resolution. Cover eligibility, pending reads and recovery races.

Show call voice errors in the companion window's existing alert and test
clearing and presentation-error precedence. Document the v2 peer-index
reuse limitation in the huddle contract and bundled help.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
The Codex voice hook now refuses a protected agent before checking its Codex
install, not only in the runtime; the runtime keeps its own refusal for any
other caller. The test exercises the registered hook and asserts no setup,
thread or media work.

The queued-pick test now waits on the adopted voice turn's tool call instead
of a timer.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>

* commit 'd6947137c':
  fix(huddles): keep voice discovery failures silent and retryable
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>

* origin/main:
  test(messages): wait for the row measurement frame before thread catch-up dwell (#2837)
  Keep agent coordination history readable after work finishes (#2454)
  test(mentions): serve the mentions fixture as a build so cold loads fit (#2776)
  test(browser): let a late publication land after its page closed (#2565)
  test(relay): wait for the verified owner before a guest edits an agent (#2413)
  test: wait for the companion default width measurement (#2750)
  test(browser): preserve conversation scroll while revealing actions (#2715)
  test(browser): wait for link hit-testing after thread close (#2792)
  perf(messages): skip micromark for plain prose bodies (#2780)
  perf(inbox): load the emoji name table on first use (#2774)
  perf(messages): cut main-thread work while a busy channel is open (#2729)
  Refine agent Attention cards, watch details, and status controls (#2717)
  fix(bestie): remove a Bestie from its agent card on any device (#2720)
  feat(agents): preview Builderlab agent card links with Add to Buzz (#2719)
  Restore macOS Cmd-W window closing when no tab handles it (#2443)
  Make agent transcripts quieter and link finished replies to activity (#2424)
* origin/main:
  fix(sidebar): rename Remove from Team to Close conversation (#2845)
  perf(channels): cut header and name-matching work in large channels (#2726)
  fix(browser): silence a browser page once its tab goes away (#2721)
  Community Mesh: shared compute plugin (including agent type) (#941)
  perf(relay): cut main-thread work from profile updates (#2730)
  fix(composer): lead the channel placeholder with its sidebar icon (#2829)
  test(activity): scope preview assertions to the request row (#2843)
  perf(search): fold people names once across palette keystrokes (#2570)
  fix(github): keep PR watches running when localStorage is full (#2699)
  Make drafts and sent part of Messages (#2728)
  fix(canvas): render every Canvas within the save limit (#2839)

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
@tlongwell-block
tlongwell-block merged commit 0816fe8 into main Nov 6, 2026
60 checks passed
@tlongwell-block
tlongwell-block deleted the eva/codex-huddle-voice branch November 6, 2026 19:55
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 6, 2026
…ions-literal-markdown

* upstream/main: (38 commits)
  fix(composer): let Option+Up/Down switch conversations from an empty composer (block#2832)
  fix(reactions): Scope timed reactions to the selected video (block#2796)
  fix(communities): name what the Join dialog is doing instead of "Working…" (block#2822)
  docs(agents): explain the per-conversation MCP cost of Claude agents (block#2683)
  fix(avatars): keep unsaved avatar drafts and focus, and validate the URL late (block#2714)
  fix(agents): offer stream and forum connection setup only to channel members (block#2749)
  Describe Attention as what the agent does, not its tools (block#2865)
  Let a local Codex agent join its owner's huddle by voice (block#2321)
  ci: give the native job room for a cold Rust cache (block#2867)
  fix(pairing): refuse unknown communities natively and name the requesting process (block#2835)
  Add per-interest Attention pause and management controls (block#2847)
  feat(drafts): show where unsent drafts wait (block#2853)
  feat(agents): let app-hosted agents read their owner's channels and members (block#2745)
  NEXUS-4271: Expose installed plugin status (block#2754)
  fix(menu): stop clipping lifecycle actions behind a hidden scrollbar (block#2716)
  feat(agents): give approved external apps the selected agent's full Buzz tools (block#2707)
  AIDA-2549: Keep Claude settings drafts in sync (block#2841)
  fix(channels): keep a channel's side panels open when returning to it (block#2838)
  fix(builderlab): clarify remote-agent account availability (block#2764)
  fix(sidebar): rename Remove from Team to Close conversation (block#2845)
  ...

Signed-off-by: Cheng Peng <[email protected]>
Co-authored-by: Cursor <[email protected]>
wpfleger96 added a commit that referenced this pull request Nov 6, 2026
…leanup

* origin/main:
  feat(terminal): add an Option as Meta key setting on macOS (#2850)
  fix(agents): restrict workflow requests in bb-block only (#2518)
  fix(dialogs): Enter presses the highlighted button in every dialog (#2782)
  Polish Compact messages and conversation appearance settings (#2863)
  fix(composer): let Option+Up/Down switch conversations from an empty composer (#2832)
  fix(reactions): Scope timed reactions to the selected video (#2796)
  fix(communities): name what the Join dialog is doing instead of "Working…" (#2822)
  docs(agents): explain the per-conversation MCP cost of Claude agents (#2683)
  fix(avatars): keep unsaved avatar drafts and focus, and validate the URL late (#2714)
  fix(agents): offer stream and forum connection setup only to channel members (#2749)
  Describe Attention as what the agent does, not its tools (#2865)
  Let a local Codex agent join its owner's huddle by voice (#2321)
  ci: give the native job room for a cold Rust cache (#2867)
  fix(pairing): refuse unknown communities natively and name the requesting process (#2835)
  Add per-interest Attention pause and management controls (#2847)
  feat(drafts): show where unsent drafts wait (#2853)

# Conflicts:
#	.github/workflows/release.yml
morgmart added a commit that referenced this pull request Nov 6, 2026
…-buzz-branding

* origin/main: (385 commits)
  fix(communities): let Cancel, × and Escape leave a Join lookup and name the host that didn't answer (#2821)
  fix(agents): let watches hear public channels the agent is not in (#1854)
  Preview and send Drafts & sent in place, with a shared count bubble (#2833)
  feat(terminal): add an Option as Meta key setting on macOS (#2850)
  fix(agents): restrict workflow requests in bb-block only (#2518)
  fix(dialogs): Enter presses the highlighted button in every dialog (#2782)
  Polish Compact messages and conversation appearance settings (#2863)
  fix(composer): let Option+Up/Down switch conversations from an empty composer (#2832)
  fix(reactions): Scope timed reactions to the selected video (#2796)
  fix(communities): name what the Join dialog is doing instead of "Working…" (#2822)
  docs(agents): explain the per-conversation MCP cost of Claude agents (#2683)
  fix(avatars): keep unsaved avatar drafts and focus, and validate the URL late (#2714)
  fix(agents): offer stream and forum connection setup only to channel members (#2749)
  Describe Attention as what the agent does, not its tools (#2865)
  Let a local Codex agent join its owner's huddle by voice (#2321)
  ci: give the native job room for a cold Rust cache (#2867)
  fix(pairing): refuse unknown communities natively and name the requesting process (#2835)
  Add per-interest Attention pause and management controls (#2847)
  feat(drafts): show where unsent drafts wait (#2853)
  feat(agents): let app-hosted agents read their owner's channels and members (#2745)
  ...

# Conflicts:
#	src/bundled/builderlab/README.md
#	src/bundled/builderlab/oauth/browser.test.ts
#	src/bundled/builderlab/oauth/browser.ts
wpfleger96 added a commit that referenced this pull request Nov 6, 2026
…ion-and-status

* origin/main: (37 commits)
  fix(browser-host): use the Buzz login variant for dev Builderlab sign-in (#2222)
  refactor(plugin-manager): move distributor values into build settings (#2649)
  fix(communities): let Cancel, × and Escape leave a Join lookup and name the host that didn't answer (#2821)
  fix(agents): let watches hear public channels the agent is not in (#1854)
  Preview and send Drafts & sent in place, with a shared count bubble (#2833)
  feat(terminal): add an Option as Meta key setting on macOS (#2850)
  fix(agents): restrict workflow requests in bb-block only (#2518)
  fix(dialogs): Enter presses the highlighted button in every dialog (#2782)
  Polish Compact messages and conversation appearance settings (#2863)
  fix(composer): let Option+Up/Down switch conversations from an empty composer (#2832)
  fix(reactions): Scope timed reactions to the selected video (#2796)
  fix(communities): name what the Join dialog is doing instead of "Working…" (#2822)
  docs(agents): explain the per-conversation MCP cost of Claude agents (#2683)
  fix(avatars): keep unsaved avatar drafts and focus, and validate the URL late (#2714)
  fix(agents): offer stream and forum connection setup only to channel members (#2749)
  Describe Attention as what the agent does, not its tools (#2865)
  Let a local Codex agent join its owner's huddle by voice (#2321)
  ci: give the native job room for a cold Rust cache (#2867)
  fix(pairing): refuse unknown communities natively and name the requesting process (#2835)
  Add per-interest Attention pause and management controls (#2847)
  ...

# Conflicts:
#	docs/buzz-help.md
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