Repository navigation
feat(goose): add optional huddle voice and shared voice settings - #2944
Draft
tlongwell-block wants to merge 8 commits into
Draft
tlongwell-block wants to merge 8 commits into
tlongwell-block wants to merge 8 commits into
Conversation
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Release canceled pre-start voice leases without interrupting typed work. Pin switch-off cleanup, both disabled launch environments, and failed storage admission with regression and mutation controls. Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
A --dev run with GOOSE_ACP_BIN=/abs/path stages that executable instead of the pinned release, after the same macOS system-library check, and logs its SHA-256. Package builds refuse it, and the next run without it restores the pinned bytes. Co-authored-by: Lain <7d83e66e949bde01875eadd5794011c24c661ec1ddb489cc759d075f4440315a@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Select the retained session after reserving it from typed work and before opening voice. Release the reservation if selection fails, and recheck cancellation before starting media. Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
* origin/main: (47 commits) fix(relay): report a community's membership refusal once, not as raw 403s (#3012) test(browser): fix red main and two cross-PR flakes (#3015) feat(agent-activity): keep remote agent outcomes readable (#2913) test(solo): cover agent tagging behind a background read-state snapshot (#2941) fix(desktop): quit on SIGTERM, SIGINT and SIGHUP so agents stop with Buzz (#2905) feat(unread): opening a channel quiets its sidebar row (#2998) fix(goose): end the turn when a final send succeeds (#2997) fix(relay): report a community's refusal once, not as raw 403s (#2978) Give fast Builderlab models their own section and a bolt in the model picker (#2592) feat(dev): `just desktop me` keeps Keychain approvals across rebuilds (#3002) fix(relay): show one quiet reconnect toast after 30 s, above the composer (#2968) feat(memory): render rich markdown and navigable buzz links (#2981) fix(identity): never strand first run on a key another account holds (#2996) Let agent_create propose instructions (#2332) fix(unread): raise a local read floor over dropped marks, and halve save validation (#2977) Add Codex huddle auto-join and thinking presence (#2927) feat(claude): set up Claude from a button beside Harness (#2975) docs(plugins): document the decisions API in the authoring guide (#2994) fix(agent-harness): stop telling agents to use GitHub-flavored Markdown (#2639) feat(identity): brand federated sign-in and return to the app (#2982) ... Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> # Conflicts: # src/bundled/codex/voice.ts
…uctions * origin/main: refactor(settings): move external identity authorization to security (#3032) test(typeahead): serve the fixture pages as a build so cold loads fit (#2793) feat(channels): list unread top-level mentions in sidebar Activity, and count only unread mentions on the Inbox card (#2722) fix(solo): delete a removed workspace's settings after the groups write (#2819) fix(solo): ask before dismissing chat and workspace settings drops edits (#2616) fix(messages): open the forwarded message from Forward's confirmation (#2802) fix(pairing): tell the tool why its relay was refused, and refuse at once when Buzz can't ask (#2823)
…emory Goose voice now fills live-voice/start's per-start instructions (goose #13375) with the agent's name, instructions, team and core memory, so the voice model answers as the agent, as Codex voice already does. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
matt2e
added a commit
that referenced
this pull request
Nov 9, 2026
A Claude turn that fails for a missing or rejected key replies with a
sentence that links that provider's section of Agent setup. Codex and
Goose posted the raw provider error with no link, so the owner had to
know the fix lives under Settings -> Agent setup -> that provider. And
Claude's sentence replaced the provider's words, which then survived
only on the Activity tab.
Now all three reply the same way: the sentence and its Settings link
first, then, on lines of their own, what the provider said, quoted and
attributed. An error none of them recognises posts exactly as before,
and so does Buzz's own refusal to start Claude without a key, which
has no provider's words to quote.
Reproduction (desktop only; traced in code, with the error text taken
from the real binaries on this workstation, codex-cli 0.166.0 and goose
1.57.0, pointed at a local server returning OpenAI's and Anthropic's
401 bodies):
- Codex signed out (empty CODEX_HOME): the turn fails with
"unexpected status 401 Unauthorized: Missing bearer or basic
authentication in header, url: .../v1/responses".
- Codex with a refused API key: "unexpected status 401 Unauthorized:
Incorrect API key provided: sk-...".
- Codex with a ChatGPT sign-in that stopped working: one of the
binary's six refresh messages, all "... could not be refreshed[ ...].
Please [log out and ]sign in again.": "Your access token could not be
refreshed because your refresh token has expired / was already used /
was revoked. Please log out and sign in again.", "Your access token
could not be refreshed. Please log out and sign in again.", "...
because you have since logged out or signed in to another account.
Please sign in again." and "Your authentication session could not be
refreshed automatically. Please log out and sign in again."
(auth_recovery_unavailable).
- Goose without ANTHROPIC_API_KEY: session/prompt fails with
{code: -32603, message: "Internal error", data: "Error getting agent
reply: Configuration value not found: ANTHROPIC_API_KEY"}.
- Goose with a refused key: {code: -32000, message: "Authentication
required"}. The thread reads it bare from session/prompt, as "goose
could not start: Authentication required" from session/new, and as
"Error: Authentication required" from a deferred handover.
- Claude with no key: Buzz refuses to start it with "Claude needs an
Anthropic API key. ..."; with a refused key, the turn ends with
"Invalid API key · Fix external API key".
Reply in the thread, before -> after (links are
buzz://settings?section=agents&provider=<type key>, shortened here):
Codex, signed out
before: Codex could not finish: unexpected status 401 Unauthorized:
Missing bearer or basic authentication in header, url:
https://api.openai.com/v1/responses.
after: Codex isn't signed in. Head to [Settings](…buzz.codex%2Fcodex)
to sign in.
Codex said:
> unexpected status 401 Unauthorized: Missing bearer or basic
> authentication in header, url:
> https://api.openai.com/v1/responses
Codex, refused API key or any refresh message above
before: Codex could not finish: Your authentication session could not
be refreshed automatically. Please log out and sign in again..
after: OpenAI rejected Codex's sign-in. It may have expired or been
revoked. Head to [Settings](…buzz.codex%2Fcodex) to sign in
again.
Codex said:
> Your authentication session could not be refreshed
> automatically. Please log out and sign in again.
Goose, missing key
before: ⚠️ I couldn't finish that: Internal error: Error getting agent
reply: Configuration value not found: ANTHROPIC_API_KEY
after: ⚠️ I couldn't finish that: Goose is missing an API key
(ANTHROPIC_API_KEY). Head to [Settings](…buzz.goose%2Fgoose)
to add it.
goose said:
> Internal error: Error getting agent reply: Configuration
> value not found: ANTHROPIC_API_KEY
Goose, refused key or sign-in
before: ⚠️ I couldn't finish that: goose could not start:
Authentication required
after: ⚠️ I couldn't finish that: Goose's provider rejected its API
key or sign-in. Head to [Settings](…buzz.goose%2Fgoose) to
check its provider.
goose said:
> goose could not start: Authentication required
Claude, refused key
before: ⚠️ I couldn't finish that: Anthropic rejected Claude's API
key. It may have been revoked or run out of credit. Head to
[Settings](…buzz.claude-code%2Fclaude-code) to replace it.
after: (the same sentence)
Claude said:
> Invalid API key · Fix external API key
Claude, missing key (unchanged: Buzz refused to start Claude)
before: ⚠️ I couldn't finish that: Claude is missing an API key. Head
to [Settings](…buzz.claude-code%2Fclaude-code) to add one.
after: the same
Any other failure: unchanged, e.g. "Codex could not finish: model
busy.", "Codex could not finish: MCP OAuth refresh token was rejected;
reauthorization required.", "⚠️ I couldn't finish that: Internal
error: Error getting agent reply: Request failed: connection refused"
and "⚠️ I couldn't finish that: Error: overloaded".
The quotes above are wrapped to fit here. In the reply, a blank line
follows the sentence, the quote starts on the line right after the
label, each provider message is one "> " line, and only line breaks of
its own add more, each quoted.
Format. Thread messages render as Markdown (MessageMarkdown:
react-markdown with remark-gfm and remark-breaks), and styled
blockquotes are part of it, so the provider's words go in a "> " quote.
A short "Codex said:" / "goose said:" / "Claude said:" line above the
quote says whose words they are; without it, "Authentication required"
reads as Buzz's own. The reply is
"<sentence>\n\n<who> said:\n> <words>": the blank line makes the
sentence and the label paragraphs of their own, and the quote starts on
the label's next line, which ends its paragraph. Each publish path
(Codex's `publish`, goose's and Claude's `report()`) hands the content
to the agent's publish, which native code signs verbatim
(app_agent_publish), so the newlines reach the thread.
Spacing. In the thread's styles (Messages.module.css), each paragraph
and blockquote in a message has 1rem (--space-4) margins above and
below, which collapse between neighbours, and a blockquote pads its
bar by 2px (--space-half). Measured in headless Chromium with those
styles and the 14px/22px message type: the label sits 16px below the
sentence, the quote's bar 16px below the label, and its first line
18px. So the quote doesn't sit tighter under the label than the label
does under the sentence. Tightening it would take a thread-wide rule
like the one lists already have: --space-1 margins on both
.text > p:has(+ blockquote) and .text > p + blockquote (both, since
collapsed margins take the larger), for 4px to the bar and 6px to the
text. That changes every message whose paragraph runs into a quote, so
this commit leaves the styles alone.
The shared features/agents/failure-words.ts `withProviderWords`
formats it: it trims the provider's words, quotes at most 500
characters of them (as goose's and Claude's reports already slice any
error to 500), ends words it cut short with "…", and quotes every line.
With no words to quote, the sentence stands alone. The sentence isn't
capped. Goose's and Claude's reports slice the error to 500 before the
helper sees it, so only a Codex quote can carry the "…". Codex
quotes its words without the notice's "Codex could not finish: " or
"could not start: ", and without the period that notice adds, so the
quote ends as Codex's message did (no "again.."). Other notices, such
as a failed handover's, are quoted whole.
Each plugin recognises its own error shapes in its setup module
(Codex's sign-in, goose's provider keys), as claude-code/auth.tsx does;
nothing moves into the Agents service. Codex's and goose's
`explainTurn` return the whole reply. Claude's `explainTurn` keeps its
sentences, and claude-code/index.ts quotes Claude after whatever it
explains: #2995 adds a signed-out branch inside that method, so editing
it would conflict, and wrapping it quotes #2995's new sentence too. It
doesn't quote Buzz's own refusal to start Claude without a key
(auth.tsx's `refusedByBuzz`, which reads NO_KEY): those aren't Claude's
words, and they would repeat the sentence's advice with an older menu
path.
Codex matches "Missing bearer or basic authentication" for signed out,
and "Incorrect API key provided" or /could not be refreshed\b[^.\n]*\.
Please (?:log out and )?sign in again/i for a refused sign-in. The
refresh match stays within one sentence and the next, so it can't join
two unrelated errors: "configured values could not be refreshed:
enterprise IdP session has no OIDC ID token; sign in again." stays raw,
as does a "could not be refreshed" line followed by a "Please sign in
again" line. The space before "Please" is literal rather than \s+, which
all six messages fit, so a line break never joins the two. An MCP
server's OAuth failure ("MCP OAuth refresh token was rejected; ...",
"MCP OAuth access token is expired and refresh failed; ...") is not
Codex's sign-in, so it doesn't match either.
Goose matches "Configuration value not found: <NAME>API_KEY" for a
missing key, and "Authentication required" at the start or after ": "
for a refusal, which leaves the phrase inside other text alone. A turn
doesn't say whether the provider refused a key or a sign-in, and a
sign-in is redone outside Buzz, so the sentence asks to check the
provider rather than to update a key.
Codex applies its hook in `publish`, which every thread notice goes
through, so no call site changes. A sign-in Codex refuses at
thread/start or turn/start lands in execute()'s catch and reads "Codex
could not finish: ...". Only the protection preflight and `entry.after`
produce "Codex could not start: ...", and neither fails for a sign-in.
Goose applies its hook in `report()`, as Claude does. The Activity
tab's status keeps Codex's own notice.
Codex's Settings section offers Sign in / Sign in again, not a key
field, so its sentences say sign in. The Codex sign-in sentence stays
as it is. The sign-in dialog's framing ("Security reviews need a
ChatGPT sign-in made in Buzz") is a follow-up copy fix, not part of
this change. Remote execution runtimes keep the pass-through `explain`,
as Claude's do, since their credentials live on the target.
The link parses (buzzLinkTarget) to {kind: "settings", section:
"agents", provider: "buzz.codex/codex" | "buzz.goose/goose"}, and Agent
setup opens the provider dialog whose type key equals `provider`
(AgentDefaults.tsx), the path Claude's link takes.
Tests:
- features/agents/failure-words.test.ts: "follows the explanation with
the provider's words, quoted and attributed", "quotes every line of
the provider's words, without stray whitespace", "quotes at most 500
characters of the provider's words, marking the cut", "lets the
explanation stand alone when the provider said nothing".
- codex/runtime.test.ts, through the plugin's apply: "links Codex's
Settings and quotes Codex when a turn fails because it is signed out"
/ "... OpenAI rejects its API key" / "... its ChatGPT sign-in
expired" / "... it cannot recover its sign-in"; "reports a turn that
failed otherwise (model busy) as Codex said it" / "... (MCP OAuth
refresh token was rejected; reauthorization required) ..." / "...
(MCP OAuth access token is expired and refresh failed;
reauthorization required) ..." / "... (configured values could not be
refreshed: x) ..." / "... (configured values could not be refreshed:
enterprise IdP session has no OIDC ID token; sign in again.) ...".
- codex/runtime.test.ts, with the fake app-server refusing the method
with the revoked-refresh-token message: "links Codex's Settings and
quotes Codex when Codex refuses thread/start for its sign-in, keeping
Codex's words for the Activity tab" / "... turn/start ...".
- codex/setup.test.ts: "quotes Codex's words after the link, without
the notice around them", "quotes at most 500 characters of Codex's
words, marking the cut", "leaves a notice it can't explain exactly as
Codex's runtime wrote it".
- goose/runtime.test.ts: "reports a turn whose goose lacks its
provider's key, linking Goose's Settings and quoting goose" / "...
had its key refused, linking Goose's Settings and quoting goose" /
"... had its key refused opening the session, linking Goose's
Settings and quoting goose" / "... failed otherwise, in goose's
words".
- goose/setup.test.ts: "explainTurn links Settings and quotes goose for
a refusal reported as "Authentication required"" / "... "goose could
not start: Authentication required"" / "... "Error: Authentication
required""; "explainTurn leaves a mention of authentication inside
other text as goose said it".
- goose/index.test.ts: "links a failed turn's refused key to Goose's
own Agent setup, quoting goose".
- claude-code/index.test.ts: "links a failed turn's key to Claude's own
Agent setup, quoting only what Claude said" (missing key: the
sentence alone; refused key: quoted; "Error: overloaded" unchanged).
Verification (base 7444eb1, origin/main when this was branched):
| Check | Result |
| ------------------------------------------------------ | ---------------------------------------- |
| New tests, production files at 7444eb1 | 18 fail; the other-error guards pass |
| New tests, production files at the Settings-link-only | 16 fail (every quote); guards pass |
| version of this change | |
| New tests, helper with 8f515d2d37's single newline | 19 fail (every quote) |
| before "X said:" | |
| Replies rendered through MessageMarkdown (throwaway) | <p> sentence + link, <p> "X said:", then |
| | <blockquote>; line breaks kept inside it |
| Thread spacing, headless Chromium with the real | sentence to label 16px; label to quote |
| tokens and Messages.module.css | bar 16px, to its first line 18px |
| failure-words, codex runtime/setup/index, goose | 12 files, 629 tests pass |
| runtime/index/setup, claude-code auth/index/runtime/ | |
| setup, navigation buzz-links test files | |
| bin/pnpm exec tsc --noEmit | clean |
| biome check --error-on-warnings on changed files | clean |
| git diff --check | clean |
| git merge-tree with #2959, #2960, #2995, #2951, #2961, | clean, as with 7444eb1 |
| #2944, #2938, #2859 | |
| git merge-tree with #2995: claude-code/index.ts and | clean; biome clean on the merged files |
| auth.tsx keep both #2995's signed-out branch and | |
| refusedByBuzz | |
| git merge-tree with #2921 | same 22 files as with 7444eb1; its |
| | hunks in claude-code/index.ts and |
| | goose/runtime(.test).ts byte-identical |
Not verified: not run in the desktop app, and the spacing was measured
in Chromium rather than the app's webview. The error text came from the
real binaries against a local 401 server, not from OpenAI or Anthropic
themselves. Goose's "Error: Authentication required" shape is tested
only through explainTurn: SessionControls swallows a control action's
error, so no runtime path delivers it today. No docs change:
docs/buzz-help.md and docs/agents.md don't describe failure replies for
any harness.
Overlap: #2959 (draft) replaces the "could not finish" publish with
`notify(reason)`, which calls `this.publish`, and keeps the notice's
wording, so its notices get explained and quoted with no change to its
lines. This commit edits neither that hunk nor #2951's, and leaves
execute()'s local refusal `explain` named as it is, since #2959
rewrites that area (`this.explain` doesn't clash with it). #2995 edits
Claude's `explainTurn`, its doc comment, the lines around NO_KEY and
claude-code/index.ts's imports and setup; this commit leaves all of
those lines alone, adds `refusedByBuzz` after the ClaudeAuth class, and
imports it and the helper after ./reads. In goose/runtime.ts the new
field stays clear of the class-head and constructor hunks in both #2944
and #2921.
In goose/runtime.test.ts the explainTurn import sits after the
./runtime import, clear of the import lines #2921, #2944 and #2960
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e
added a commit
that referenced
this pull request
Nov 9, 2026
…3081) ## Problem When a provider refuses an agent's key or sign-in, the turn's reply in the thread gave the owner no route to the fix: - **Codex** posted OpenAI's raw error: `Codex could not finish: unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses.` - **Goose** posted the ACP error bare: `⚠️ I couldn't finish that: Authentication required`, or `Internal error: Error getting agent reply: Configuration value not found: ANTHROPIC_API_KEY`. - **Claude** already replied with a sentence linking its section of Settings → Agent setup, but that sentence replaced Claude's own words, which then survived only on the Activity tab. The owner had to know that the fix lives under Settings → Agent setup → that provider. ## Change All three now reply the same way: a sentence with a **Settings** link to that provider's dialog, then, on lines of their own, what the provider said, quoted and attributed: ``` <sentence with Settings link> <Provider> said: > <quoted provider text> ``` The link is `buzz://settings?section=agents&provider=<type key>` (`buzz.codex%2Fcodex`, `buzz.goose%2Fgoose`, `buzz.claude-code%2Fclaude-code`). It parses to `{kind: "settings", section: "agents", provider}` and Agent setup opens the dialog whose type key matches, the path Claude's link already took. An error none of them recognises posts byte-for-byte as before. So does Buzz's own refusal to start Claude without a key, which has no provider words to quote. ### Before → after The **after** column shows the sentence. Each reply then continues with a blank line, `Codex said:` / `goose said:` / `Claude said:`, and the provider's words quoted. For Codex the quote is the message without the `Codex could not finish: ` wrapper and its trailing period; for Goose and Claude it is the error as reported (sliced to 500 characters, as those reports already did). | Provider | Case | Before | After (sentence; quote follows) | | --- | --- | --- | --- | | Codex | signed out (`Missing bearer or basic authentication`) | `Codex could not finish: unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses.` | Codex isn't signed in. Head to **Settings** to sign in. | | Codex | refused API key (`Incorrect API key provided`) or any of the binary's six `… could not be refreshed[…]. Please [log out and ]sign in again.` messages | `Codex could not finish: Your authentication session could not be refreshed automatically. Please log out and sign in again..` | OpenAI rejected Codex's sign-in. It may have expired or been revoked. Head to **Settings** to sign in again. | | Goose | missing key (`Configuration value not found: <NAME>API_KEY`) | `⚠️ I couldn't finish that: Internal error: Error getting agent reply: Configuration value not found: ANTHROPIC_API_KEY` |⚠️ I couldn't finish that: Goose is missing an API key (ANTHROPIC_API_KEY). Head to **Settings** to add it. | | Goose | refused key or sign-in (`Authentication required` at the start or after `: `) | `⚠️ I couldn't finish that: goose could not start: Authentication required` |⚠️ I couldn't finish that: Goose's provider rejected its API key or sign-in. Head to **Settings** to check its provider. | | Claude | refused key | `⚠️ I couldn't finish that: Anthropic rejected Claude's API key. It may have been revoked or run out of credit. Head to **Settings** to replace it.` | the same sentence, now followed by `Claude said:` and `> Invalid API key · Fix external API key` | | Claude | missing key (Buzz refused to start Claude) | `⚠️ I couldn't finish that: Claude is missing an API key. Head to **Settings** to add one.` | unchanged: the sentence alone | | any | other failure | e.g. `Codex could not finish: model busy.`, `Codex could not finish: MCP OAuth refresh token was rejected; reauthorization required.`, `⚠️ I couldn't finish that: Error: overloaded` | unchanged | ### Design - **Each plugin recognises its own error shapes** in its setup module, as `claude-code/auth.tsx` already does: `codex/setup.ts` `explainTurn` (sign-in) and `goose/setup.ts` `explainTurn` (provider keys). Nothing moves into the Agents service. - **Shared formatter:** `src/features/agents/failure-words.ts` `withProviderWords(explanation, who, words)` trims the provider's words, quotes at most 500 characters (ending a cut with `…`), quotes every line, and returns `<explanation>\n\n<who> said:\n> …`. With no words it returns the sentence alone. The explanation itself is not capped. - **Codex applies the hook inside `publish`**, which every thread notice goes through, so no call site changes. Draft #2959 replaces the "could not finish" publish with `notify(reason)`, which calls `this.publish`, so its notices are explained and quoted with no change to its lines. A sign-in Codex refuses at `thread/start` or `turn/start` lands in `execute()`'s catch and is covered. The Activity tab's status keeps Codex's own notice. - **Goose applies the hook in `report()`**, as Claude does. - **Claude keeps its `explainTurn` sentences**; `claude-code/index.ts` quotes Claude after whatever it explains, except when `refusedByBuzz` (Buzz's own NO_KEY refusal) produced the error. #2995 adds a signed-out branch inside `explainTurn`, so this change stays out of that method and merges cleanly with it (verified with `git merge-tree`). - **Codex's refresh match** `/could not be refreshed\b[^.\n]*\. Please (?:log out and )?sign in again/i` stays within one sentence and the next, so `configured values could not be refreshed: enterprise IdP session has no OIDC ID token; sign in again.` and the MCP OAuth refresh errors stay raw. - **Remote execution runtimes** keep their pass-through `explain`, as Claude's do; their credentials live on the target. - **No style change.** In the thread, paragraphs and blockquotes have `--space-4` (16 px) margins that collapse, so the label sits 16 px below the sentence and the quote's bar 16 px below the label (first quoted line 18 px, after the bar's padding). Tightening it would be a thread-wide rule for every paragraph that runs into a quote, so this PR leaves `Messages.module.css` alone. ## Testing Tests added in `failure-words.test.ts`, `codex/runtime.test.ts` (through the plugin's `apply`, and with the fake app-server refusing `thread/start` / `turn/start`), `codex/setup.test.ts`, `goose/runtime.test.ts`, `goose/setup.test.ts`, `goose/index.test.ts` and `claude-code/index.test.ts`. They cover each matched shape, the 500-character cut, multi-line quoting, and guards that the unmatched errors above (model busy, both MCP OAuth messages, the two `configured values could not be refreshed` forms, `Error: overloaded`) post exactly as before. Base: `7444eb1299` (origin/main when branched). | Check | Result | | --- | --- | | New tests, production files at `7444eb1299` | 18 fail; the other-error guards pass | | New tests, production files at the Settings-link-only version of this change | 16 fail (every quote); guards pass | | New tests, helper with a single newline before `X said:` | 19 fail (every quote) | | Replies rendered through `MessageMarkdown` (throwaway) | `<p>` sentence + link, `<p>` "X said:", then `<blockquote>`; line breaks kept inside it | | Thread spacing, headless Chromium with the real tokens and `Messages.module.css` | sentence → label 16 px; label → quote bar 16 px, → its first line 18 px | | failure-words, codex runtime/setup/index, goose runtime/index/setup, claude-code auth/index/runtime/setup, navigation buzz-links test files | 12 files, 629 tests pass | | `bin/pnpm exec tsc --noEmit` | clean | | `biome check --error-on-warnings` on changed files | clean | | `git diff --check` | clean | | `git merge-tree` with #2959, #2960, #2995, #2951, #2961, #2944, #2938, #2859 | clean, as with `7444eb1299` | | `git merge-tree` with #2995 | clean; `claude-code/index.ts` and `auth.tsx` keep both #2995's signed-out branch and `refusedByBuzz`; biome clean on the merged files | | `git merge-tree` with #2921 | same 22 conflicting files as `7444eb1299` already has with it; its hunks in `claude-code/index.ts` and `goose/runtime(.test).ts` byte-identical, so none added | The error text came from the real binaries on a workstation (codex-cli 0.166.0, goose 1.57.0) pointed at a local server returning OpenAI's and Anthropic's 401 bodies, not from OpenAI or Anthropic themselves. ## In-app check **This is a browser-build fallback recording, not the desktop app.** The recording workstation had no relay to connect to (no `.env.local`, no local relay), no display server or WebKitGTK, and native identity there needs a real community sign-in; posting from a debug identity into a real community would send messages to an outside service. The fixture stands in for the two agents and the turn: on a mention it posts the reply that each build's own `codex/setup.ts` / `goose/setup.ts` `explainTurn` composes from the raw error strings captured from the real binaries. Real in the recording: each worktree's Buzz frontend, the Playwright fixture relay and production broker, the Agents service delivering the mention, `MessageMarkdown` with the real `Messages.module.css`, `buzz://` link routing, and Settings → Agent setup with the real Codex and Goose plugins bridged to the real binaries. Spacing measured in the real thread matched the headless numbers above (16 / 16 / 18 px). Left is **BEFORE** = origin/main `7444eb1299`; right is **AFTER** = `13b8178396`. Both halves run the same script; only the right half then follows the Settings links. https://github.com/user-attachments/assets/b9ed7624-410c-4dc4-b61e-88a3e20e944f | BEFORE (`7444eb1299`) | AFTER (`13b8178396`) | | --- | --- | |  |  | AFTER (`13b8178396`), close-up of the thread column: sentence, 16 px gap, "X said:", 16 px gap, then the quote's bar.  AFTER (`13b8178396`): clicking Codex's Settings link opens Settings → Agent setup with the Codex dialog.  ## Overlap - **#2959 (draft):** its `notify(reason)` calls `this.publish`, so its notices are quoted with no change to its lines. This PR edits neither that hunk nor #2951's. - **#2995:** edits Claude's `explainTurn`, its doc comment, the NO_KEY lines and `claude-code/index.ts`'s imports and setup. This PR leaves those lines alone and adds `refusedByBuzz` after the `ClaudeAuth` class. - **#2921:** already conflicts with main in 22 files; this PR adds no new conflict (its hunks in the shared files are byte-identical). ## Follow-ups (not in this PR) - The rendered gap above and below "X said:" is 16 px each because paragraph and blockquote margins collapse. Tightening the paragraph-into-quote gap is a thread-wide CSS change. - The Codex Settings dialog's framing ("Security reviews need a ChatGPT sign-in made in Buzz") is a separate copy fix. - Goose's bundled hf-hub prints the same "Authentication required" words, so a Hugging Face refusal would get the provider sentence too. The sentence says "check its provider" rather than naming a key, which still fits, but it is worth a look. - Goose's `Error: Authentication required` shape is tested only through `explainTurn`: `SessionControls` swallows a control action's error, so no runtime path delivers it today. - A Goose agent whose provider override differs from goose's default has no way in Buzz to repair that provider's key alone: the Goose dialog's only save action writes device defaults (provider and model) and reconfigures every inheriting agent. The Settings link therefore opens the dialog on the default provider, and the sentence says "check its provider" without naming one. A credentials-only save for a non-default provider is its own change. (A commit that named the provider and preselected it in the dialog was tried on this branch and reverted after review, for this reason.) - No docs change: `docs/buzz-help.md` and `docs/agents.md` do not describe failure replies for any harness. - Not run in the native desktop app; human check pending. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: mtoohey <mtoohey@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goose agents can join huddles with an optional voice model while using their existing ACP session for delegated tools. Voice defaults off on each device. The Goose settings dialog reads and edits OpenAI or Custom GPT-Live settings through goose's config and keyring.
The voice model also gets who the agent is: each start sends goose's per-start
instructions(aaif-goose/goose#13375) with the agent's name, instructions, team and core memory, read from the host's core-memory cache. goose appends them after its own voice instructions, for that start only. A goose without the field ignores it.The implementation reuses the Codex WebRTC/PCM media graph and shared settings controls. Typed work queues for the whole live interaction, including quiet gaps, and runs after voice stops and prompt cleanup finishes. Canceling an opening releases its lease while preserving existing typed work and tool authority. Before voice starts, the retained session selects the agent’s provider, model and thinking level while typed work is reserved. A failed selection releases the reservation without opening voice. Marked live transcripts stay out of typed responses.
Configuration saves and voice starts are serialized. Changing the endpoint requires a new key; failed writes disable voice; every start checks the local switch. Switch changes retire idle processes, and new work waits for shutdown before using a replacement. Endpoint/key edits keep the process, relying on goose reading credentials freshly on each start.
For development with a compatible build,
GOOSE_ACP_BIN=/absolute/path/goose-acp just desktopstages local bytes and prints their SHA-256. The same macOS system-library check applies. Package builds refuse the override; a subsequent run without it restores the pinned release. The shipped pin remains 1.57.0.Validation:
0dac5d614(over a merge of mainc9239f73): a runtime test pins the exact start text and a voice test pins the field onlive-voice/start; both fail with the wiring removed. Goose suites 369 passed, 9 skipped;pnpm checkclean; push hook 2,280 passed. Live through the ACP field to Frankie with a real 5.9 KB instructions + core memory payload, built from #13375: greeting addressed the owner by name 2/2, ahostnamedelegation returned a spoken result, and a control without the field gave the generic greeting. goose main5bad61a8accepts and ignores the field. Not yet run in a native huddle or on hosted OpenAI.75096f8e0removes Rust toolchain discovery from staging tests by supplying target fixtures; macOS library validation remains active. All 43 script tests pass. Single-file local duration fell from 504ms to 173ms (single samples). Hosted timeout resolution remains to be confirmed.b556155fe: the new runtime regression fails on318ed215because no model/thinking settings are applied before voice opens. Goose/shared ACP tests pass 387 tests with 8 skipped after the fix, including selection-failure cleanup. Wren’s exact-head independent Goose/ACP run also passes 387/8 skipped, with review 9/9/9. Push hooks pass. Full fresh-cache suite: 19,527 passed, 22 skipped (1,035 files, 517.37s). Production code was unchanged during the run; one unrelated test assertion was restored to its original form, then the final runtime file passed 74/74 at the committed head. Eva’s native A2 rerun passes with the frontend atb556155fe, unchanged native binary at318ed215, and goose57fcbe3c: with the provider default restored, the session used the agent-selected model, ranhostnamelocally, and returned a spoken result.318ed215395b606c5c8eeaa2eb1d8a6673bdd5a4: full Vitest suite passes with a fresh transform cache and four workers: 19,525 passed, 22 skipped, 1,035 files, 545.07s. HEAD verified before and after; working tree clean. All 43 script tests pass, including local staging, absolute/missing-path rejection and rejection outside development. Pre-push typecheck/related tests and design checks pass.7cebffc9, whose tree is identical to the integration head. Its three override tests pass; the full Node dev-command suite passes 45/45 when inheritedTAURI_CONFIGis unset. With that variable set, two launch-resolution tests fail.1d7d43182: owner full Vitest run passed 19,522 tests with 22 skipped; final test-only lint cleanup separately passed 72 runtime tests. Independent re-review scored 9/9/9 for lifecycle correctness and Desktop guards. Its full run under heavy load had 69 failures and six unhandled errors; all 27 failing files then passed with two workers (1,515 tests). This is evidence of contention sensitivity, not an independently green full-suite run.57fcbe3caconfirm the card’s read sequence returns a CLI-written endpoint and missing secret, and its OpenAI save sequence removes the endpoint. Native rendering of that state (D4) remains unverified.Draft gates and limits:
goose.liveTranscriptmarker. The bundled 1.57.0 lacks these capabilities; the development override enables testing without changing the release pin.318ed215connected the huddle seat and passed spoken A1, then exposed missing agent-model selection in A2. A2 now passes with the model-selection correction. Further native checks passed four consecutive delegated tool requests with spoken results, barge-in, leave/rejoin and key rotation (old seat survives, rejoin with invalid new key fails, restoring key succeeds). Owner-only input, stale-result rejection and human tryout remain open; the custom server still intermittently announces delegation without starting a tool run, and one spoken response exposed a thinking delimiter.sidebar-geometry.spec.mjs:245). This PR does not change that surface; baseline causality has not been independently reproduced. Atb556155fe, the staging test exceeded its five-second timeout in a JavaScript shard and flake check. The test-only follow-up removes unnecessary Rust discovery; its hosted checks are pending.config.yamlin goose’s current format through its existing config migration.To try: launch with the compatible development binary, open agent settings → Voice settings → Voice, choose a provider, save endpoint/key/model/voice, enable Goose in huddles, and invite a Goose agent. Ask it to run a simple tool and speak the result, then leave and verify queued typed work succeeds.