From 713fd7b78badd2532093950002003a650efcee72 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Wed, 28 Oct 2026 20:02:39 -0700 Subject: [PATCH 1/5] fix(builderlab): enable default setup and explain unavailable harnesses Co-authored-by: Carl Signed-off-by: Taylor Ho --- .env.example | 3 ++- browser-host/vite-config.test.mjs | 15 +++++++++++++++ crates/plugin-manager/build.rs | 26 ++++++++++++++++++++++---- docs/configuration.md | 16 +++++++++------- src/bundled/agents/AgentsPage.test.tsx | 21 +++++++++++++++++++++ src/bundled/agents/AgentsPage.tsx | 16 +++++++++++++--- vite.config.ts | 2 +- 7 files changed, 83 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 3905dc2c8f..5d6cfe8338 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,8 @@ BUZZ_DEV_VIEWER= # Optional public Builderlab URL, exposed as import.meta.env.VITE_BUZZ_BUILDERLAB_URL. # Read by Vite from .env.local or the build process environment (process wins, -# including empty). Unset means empty; no deployment URL is supplied by the repo. +# including empty). Defaults to https://app.builderlab.xyz; set an empty value +# to disable Builderlab sign-in and its native network grants. # Packaging embeds the value; changing the launched app's environment has no effect. # Restart Vite for development changes; rebuild/repackage for distributed apps. # BUZZ_BUILDERLAB_URL=https://builderlab.example.com diff --git a/browser-host/vite-config.test.mjs b/browser-host/vite-config.test.mjs index 78406b929e..5f5f1e45ba 100644 --- a/browser-host/vite-config.test.mjs +++ b/browser-host/vite-config.test.mjs @@ -139,6 +139,21 @@ it("loads the broker's Vite config without native-compatibility warnings", () => ); } delete process.env.BUZZ_DATABRICKS_HOST; + // A fresh checkout uses production for both dev and packaged frontends. + // Explicit empty values retain the deployment opt-out. + for (const local of ['', 'BUZZ_BUILDERLAB_URL=']) { + writeFileSync('.env.local', local); + delete process.env.BUZZ_BUILDERLAB_URL; + for (const command of ['serve', 'build']) { + const result = await loadConfigFromFile( + { command, mode: command === 'serve' ? 'development' : 'production' }, configFile, + ); + assert.equal( + result.config.define['import.meta.env.VITE_BUZZ_BUILDERLAB_URL'], + JSON.stringify(local ? '' : 'https://app.builderlab.xyz'), + ); + } + } // Desktop shares .env.local/process inputs with the native build. // An explicit empty process value clears the frontend URL too. writeFileSync('.env.local', 'BUZZ_BUILDERLAB_URL=https://app.builderlab.xyz/'); diff --git a/crates/plugin-manager/build.rs b/crates/plugin-manager/build.rs index 73647367d8..dbedca3df2 100644 --- a/crates/plugin-manager/build.rs +++ b/crates/plugin-manager/build.rs @@ -10,7 +10,7 @@ fn builderlab_origin(local: &Path, process: Option) -> Result { expect(screen.getByRole("button", { name: "New team" })).toBeInTheDocument(); }); +it("explains an unavailable harness without claiming the key is on another device", async () => { + const user = userEvent.setup(); + setup([ + { + ...agent("Starf", 3), + type: "block.builderlab/builderlab", + place: "elsewhere", + }, + ]); + const grid = screen.getByRole("grid", { name: "Agents" }); + expect(within(grid).getByText("Harness unavailable")).toBeInTheDocument(); + expect(screen.queryByText("On another device")).not.toBeInTheDocument(); + + await user.click(within(grid).getByText("Starf")); + expect(screen.getByText(/Check Settings → Plugins/)).toBeInTheDocument(); + expect(screen.queryByText(/another of your devices/)).not.toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Delete agent" }), + ).not.toBeInTheDocument(); +}); + it("shows an agent on another device read-only, and offers only Finish setup and Delete for an unfinished hosted one", async () => { const user = userEvent.setup(); const far: Agent = { diff --git a/src/bundled/agents/AgentsPage.tsx b/src/bundled/agents/AgentsPage.tsx index ce332ce5c0..f6fbc582b2 100644 --- a/src/bundled/agents/AgentsPage.tsx +++ b/src/bundled/agents/AgentsPage.tsx @@ -119,7 +119,8 @@ const importReminderKey = "agent-community-import:dismissed"; /** The type's one-line summary of an agent; its title when it has none. */ function summaryOf(agent: Agent, type: RegisteredAgentType | undefined) { - if (agent.place === "elsewhere") return "On another device"; + if (agent.place === "elsewhere") + return type && !type.remote ? "On another device" : "Harness unavailable"; if (agent.unfinished) return "Setup unfinished"; if (!type) return "Type unavailable"; try { @@ -1176,8 +1177,17 @@ function AgentPeek({ /> {agent.place === "elsewhere" ? (

- Its key is on another of your devices, which runs it. Open Agents - there to change or delete it. + {type && !type.remote ? ( + <> + Its key is on another of your devices, which runs it. Open Agents + there to change or delete it. + + ) : ( + <> + This agent’s harness is unavailable in this client. Check Settings + → Plugins or use a build with this harness configured. + + )}

) : ( <> diff --git a/vite.config.ts b/vite.config.ts index a0b36b7e01..872901c8cd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -86,7 +86,7 @@ export default defineConfig(async ({ command, mode }) => { ), "import.meta.env.VITE_BUZZ_COMMUNITY_ALIASES": JSON.stringify(aliases), "import.meta.env.VITE_BUZZ_BUILDERLAB_URL": JSON.stringify( - env.BUZZ_BUILDERLAB_URL ?? "", + env.BUZZ_BUILDERLAB_URL ?? "https://app.builderlab.xyz", ), "import.meta.env.VITE_BUZZ_OPEN_RELAY": JSON.stringify(openOrigin), "import.meta.env.VITE_BUZZ_DATABRICKS_HOST": From 2f6b2aef2b1ac8ee8bdf6731516787bc94f4a571 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Wed, 28 Oct 2026 21:11:11 -0700 Subject: [PATCH 2/5] test(builderlab): detect silently skipped URL overrides Co-authored-by: Carl Signed-off-by: Taylor Ho --- crates/plugin-manager/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/plugin-manager/build.rs b/crates/plugin-manager/build.rs index dbedca3df2..e5902ec728 100644 --- a/crates/plugin-manager/build.rs +++ b/crates/plugin-manager/build.rs @@ -69,12 +69,12 @@ mod tests { let local = directory.path().join(".env.local"); std::fs::write( &local, - "\u{feff}export BUZZ_BUILDERLAB_URL=\"https://APP.BUILDERLAB.XYZ:443/deployment/\" # comment\n", + "\u{feff}export BUZZ_BUILDERLAB_URL=\"https://OVERRIDE.EXAMPLE:443/deployment/\" # comment\n", ) .unwrap(); assert_eq!( builderlab_origin(&local, None).unwrap(), - "https://app.builderlab.xyz" + "https://override.example" ); assert_eq!( builderlab_origin(&local, Some("https://login.example:8443/path/".into())).unwrap(), @@ -120,13 +120,13 @@ mod tests { std::fs::write( &local, format!( - "PRIVATE_VALUE={quote}multiline\nBUZZ_BUILDERLAB_URL=https://hidden.example\n{quote}\nINVALID=unquoted value\nexport BUZZ_BUILDERLAB_URL=https://app.builderlab.xyz/\nTRAILING='unfinished\n" + "PRIVATE_VALUE={quote}multiline\nBUZZ_BUILDERLAB_URL=https://hidden.example\n{quote}\nINVALID=unquoted value\nexport BUZZ_BUILDERLAB_URL=https://after-multiline.example/\nTRAILING='unfinished\n" ), ) .unwrap(); assert_eq!( builderlab_origin(&local, None).unwrap(), - "https://app.builderlab.xyz" + "https://after-multiline.example" ); } for quote in ["'", "\""] { From 501e9231437c3774fbc976f532297d32b4a36701 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Wed, 28 Oct 2026 21:13:25 -0700 Subject: [PATCH 3/5] docs(builderlab): explain native default capabilities Co-authored-by: Carl Signed-off-by: Taylor Ho --- .env.example | 2 +- docs/configuration.md | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 5d6cfe8338..cb1f6860d2 100644 --- a/.env.example +++ b/.env.example @@ -27,7 +27,7 @@ BUZZ_DEV_VIEWER= # Optional public Builderlab URL, exposed as import.meta.env.VITE_BUZZ_BUILDERLAB_URL. # Read by Vite from .env.local or the build process environment (process wins, # including empty). Defaults to https://app.builderlab.xyz; set an empty value -# to disable Builderlab sign-in and its native network grants. +# to disable Builderlab sign-in and the bundled plugin's native grants. # Packaging embeds the value; changing the launched app's environment has no effect. # Restart Vite for development changes; rebuild/repackage for distributed apps. # BUZZ_BUILDERLAB_URL=https://builderlab.example.com diff --git a/docs/configuration.md b/docs/configuration.md index 7f806ad913..b2d9308f69 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -8,7 +8,7 @@ Build defaults are readable binary data, **never secret storage**. | Input | Consumer and boundary | | --- | --- | -| `BUZZ_BUILDERLAB_URL` | Shared frontend/native build: public login URL and the bundled plugin's exact HTTPS host grant; no runtime override. | +| `BUZZ_BUILDERLAB_URL` | Shared frontend/native build: public login URL and the bundled plugin's native grants; no runtime override. | | `BUZZ_DEFAULT_COMMUNITY_URL` | Packaged frontend build: secure relay origin to select once, after identity setup, only when this viewer has no saved community record. Saved choices, including Personal space, always win. Unset in OSS builds. | | `BUZZ_DATABRICKS_HOST` | Frontend build and dev server: `https://` Databricks workspace URL passed as `DATABRICKS_HOST` to every bundled `goose-acp`, which then prefers it to the host in goose's config. Unset in OSS builds. See [Databricks host](#databricks-host). | | `BUZZ_RELAY_URL` | Live development broker's default community, not an agent relay override or a packaged default. | @@ -82,13 +82,22 @@ For desktop, supply the full URL in `.env.local` or the build process environmen so both stages receive it. Vite's additional `.env` and mode-specific files do not configure native grants. Process values take precedence, including an explicit empty value. Set `BUZZ_BUILDERLAB_URL=` to leave login unconfigured and -grant no Builderlab network origin. The desktop release workflow supplies the +clear the bundled plugin's native grants. The desktop release workflow supplies the production URL by default; see [release configuration](releases.md#builderlab-url). Any HTTPS host is supported, including an explicit port or deployment path. URL credentials, query parameters and fragments are rejected by the native build. The desktop's native plugin catalog supplies the effective manifest, granting -only the configured URL's exact origin. Never put credentials in this input. +the configured URL's exact origin. For recognized Builderlab deployments, its +existing Apps policy also grants file access and the mapped control-plane origin +(`https://compose-ctrl.builderlab.xyz` for production). Never put credentials in +this input. + +The build origin also controls whether a Builderlab session can be reused by +identity backup when its separately configured service URL has the same origin. +Backup still requires its own service URL, namespace and user choice. The existing +identity-binding validator accepts production Builderlab or the configured origin; +production binding was already allowed before this default. The bundled Builderlab login consumes this public URL and appends `/api/goose`. From 58a576862f6781673ff1a351a4f673248a71ba86 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Wed, 28 Oct 2026 21:28:45 -0700 Subject: [PATCH 4/5] test(reminders): wait for select focus before navigation Co-authored-by: Carl Signed-off-by: Taylor Ho --- src/bundled/reminders/RemindDialog.test.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bundled/reminders/RemindDialog.test.tsx b/src/bundled/reminders/RemindDialog.test.tsx index 04fe60c25b..e758c71e74 100644 --- a/src/bundled/reminders/RemindDialog.test.tsx +++ b/src/bundled/reminders/RemindDialog.test.tsx @@ -190,6 +190,9 @@ it("reveals labelled custom fields and saves a future time with the note", async screen.getByRole("combobox", { name: "When" }).focus(); await user.keyboard("{Enter}"); await screen.findByRole("option", { name: "Custom date & time" }); + await vi.waitFor(() => + expect(screen.getByRole("option", { selected: true })).toHaveFocus(), + ); await user.keyboard("{End}{Enter}"); fireEvent.change(screen.getByLabelText("Reminder date"), { target: { value: "" }, From cb86833821ca3bb07b702f03e0f4a205fb90561c Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Wed, 28 Oct 2026 21:48:21 -0700 Subject: [PATCH 5/5] revert(agents): remove harness messaging from defaults change Co-authored-by: Carl Signed-off-by: Taylor Ho --- src/bundled/agents/AgentsPage.test.tsx | 21 --------------------- src/bundled/agents/AgentsPage.tsx | 16 +++------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/src/bundled/agents/AgentsPage.test.tsx b/src/bundled/agents/AgentsPage.test.tsx index 13e1c327d7..e96924a2a1 100644 --- a/src/bundled/agents/AgentsPage.test.tsx +++ b/src/bundled/agents/AgentsPage.test.tsx @@ -2483,27 +2483,6 @@ it("offers New team again when the team being edited disappears", async () => { expect(screen.getByRole("button", { name: "New team" })).toBeInTheDocument(); }); -it("explains an unavailable harness without claiming the key is on another device", async () => { - const user = userEvent.setup(); - setup([ - { - ...agent("Starf", 3), - type: "block.builderlab/builderlab", - place: "elsewhere", - }, - ]); - const grid = screen.getByRole("grid", { name: "Agents" }); - expect(within(grid).getByText("Harness unavailable")).toBeInTheDocument(); - expect(screen.queryByText("On another device")).not.toBeInTheDocument(); - - await user.click(within(grid).getByText("Starf")); - expect(screen.getByText(/Check Settings → Plugins/)).toBeInTheDocument(); - expect(screen.queryByText(/another of your devices/)).not.toBeInTheDocument(); - expect( - screen.queryByRole("button", { name: "Delete agent" }), - ).not.toBeInTheDocument(); -}); - it("shows an agent on another device read-only, and offers only Finish setup and Delete for an unfinished hosted one", async () => { const user = userEvent.setup(); const far: Agent = { diff --git a/src/bundled/agents/AgentsPage.tsx b/src/bundled/agents/AgentsPage.tsx index f6fbc582b2..ce332ce5c0 100644 --- a/src/bundled/agents/AgentsPage.tsx +++ b/src/bundled/agents/AgentsPage.tsx @@ -119,8 +119,7 @@ const importReminderKey = "agent-community-import:dismissed"; /** The type's one-line summary of an agent; its title when it has none. */ function summaryOf(agent: Agent, type: RegisteredAgentType | undefined) { - if (agent.place === "elsewhere") - return type && !type.remote ? "On another device" : "Harness unavailable"; + if (agent.place === "elsewhere") return "On another device"; if (agent.unfinished) return "Setup unfinished"; if (!type) return "Type unavailable"; try { @@ -1177,17 +1176,8 @@ function AgentPeek({ /> {agent.place === "elsewhere" ? (

- {type && !type.remote ? ( - <> - Its key is on another of your devices, which runs it. Open Agents - there to change or delete it. - - ) : ( - <> - This agent’s harness is unavailable in this client. Check Settings - → Plugins or use a build with this harness configured. - - )} + Its key is on another of your devices, which runs it. Open Agents + there to change or delete it.

) : ( <>