Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions crates/plugin-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,6 @@ pub fn bundled_manifests() -> Vec<Manifest> {
.expect("workflows manifest"),
serde_json::from_str(include_str!("../../../src/bundled/feedback/manifest.json"))
.expect("feedback manifest"),
serde_json::from_str(include_str!(
"../../../src/bundled/hosted-communities/manifest.json"
))
.expect("hosted communities manifest"),
serde_json::from_str(include_str!(
"../../../src/bundled/moderation/manifest.json"
))
Expand Down Expand Up @@ -364,7 +360,6 @@ fn enabled_by_default(id: &str) -> bool {
| "buzz.codex"
| "buzz.goose"
| "buzz.workflows"
| "block.hosted-communities"
| "block.builderlab"
| "buzz.moderation"
| "buzz.relay-staff"
Expand Down
2 changes: 1 addition & 1 deletion docs/identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ the signing key across rebuilds therefore extends trust to those programs.

Desktop builds configured with `BUZZ_NSEC_BACKUP_SERVICE_URL` and
`BUZZ_NSEC_BACKUP_NAMESPACE` back up a local identity only after an explicit saved
automatic-backup choice, Builderlab or Hosted communities login, and matching key
automatic-backup choice, Builderlab login, and matching key
binding. Unset, self-managed, corrupt or mismatched consent prevents all backup
requests, including retries. These are paired
native build-process inputs; unset leaves the feature disabled. The service URL
Expand Down
2 changes: 1 addition & 1 deletion docs/input-correction-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ capitalization, but do not explicitly disable autocorrect.
| `features/communities/CommunityDialog.tsx` | Relay URL and invite code |
| `features/browser/BrowserHostView.tsx` | Address bar |
| `features/profiles/AvatarEditor.tsx` | Picture URL and emoji |
| `bundled/hosted-communities/HostedCommunities.tsx` | Domain slug, transfer recipient npub |
| `bundled/builderlab/communities/HostedCommunities.tsx` | Domain slug, transfer recipient npub |
| `bundled/channels/CreateChannelDialog.tsx` | Channel name |
| `bundled/channels/ChannelLifecycleDialog.tsx` | Exact channel-name confirmation |
| `bundled/workflows/WorkflowEditor.tsx` | Workflow identifiers and YAML |
Expand Down
28 changes: 15 additions & 13 deletions docs/plugin-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ contracts with their own layout and local navigation.
**Channels is the only required bundled plugin.** Bestie,
Todos and Usage are off by default. Feedback, Diff viewer, Identity
Naming, Agent Activity, Terminal, Links, GitHub, Inbox,
Repositories, Agents, Claude, Codex, Workflows, Builderlab, Hosted communities,
Repositories, Agents, Claude, Codex, Workflows, Builderlab,
Membership, and Moderation are on by default, but optional. Both browser and
native catalogs declare that policy.

Expand All @@ -523,7 +523,7 @@ without a saved flag use the current default, so changing a default also changes
those existing profiles. There is no migration or forced reset: a browser profile that previously saved its full
plugin snapshot can retain Bestie enabled. Native profiles store per-plugin
overrides. Default-on does not promise platform support: Terminal contributes UI
only on macOS/Linux desktop; Hosted communities needs either the desktop app or
only on macOS/Linux desktop; Builderlab's hosted communities need either the desktop app or
the live development broker. Disabling Membership removes its Invite to community shortcut.

Membership and Moderation share app-owned access controllers composed in
Expand Down Expand Up @@ -593,9 +593,10 @@ Create resolves its agents and its teams' members to those that are the owner's
agents in this community and sends them with the Canvas as ordinary channel setup,
which the session applies and retries as before.

Hosted communities (`block.hosted-communities`) is a Block-specific bundled plugin
under Settings → Plugins → Builderlab hosting. It manages Block-hosted relays through a Builderlab
account: browser sign-in, binding the local Buzz identity (a locally signed kind
Hosted communities are part of the Block-specific Builderlab plugin
(`block.builderlab`, `communities/`), shown under Settings → Plugins → Builderlab
beside remote agents. They manage Block-hosted relays through the plugin's one
Builderlab sign-in: binding the local Buzz identity (a locally signed kind
24243 challenge), and create/archive/unarchive/transfer. A server-declared,
default-off capability also exposes owner deletion for archived communities. The
card persists the bound four-field request before admission. A fresh request can
Expand All @@ -610,14 +611,15 @@ re-read and verified before dispatch; browser local storage has no atomic compar
so exactly simultaneous contexts remain a documented client-side race;
it never signs deletion or infers acceptance from a missing list row. Joining
stays in the existing Join a community dialog; the card only copies the new relay address. Its
account backend follows the same split as `HostService`: live development uses the
broker's `/api/builderlab/*` routes (`browser-host/builderlab.mjs`), which keep the session
credential and signing key in Node. Desktop builds use `native.ts`, the same routes
over the shared `oauth_callback` sign-in, host HTTP to the manifest's single
`https://app.builderlab.xyz` grant, and `identity_sign_builderlab_binding`, which
validates the challenge and signs only the fixed kind 24243 binding. The desktop
credential lives in plugin memory for the plugin's lifetime and is never persisted,
so restarting the app signs out. Other browser builds show the unavailable notice.
account backend follows the same split as `HostService`: live development with a
pinned viewer uses the broker's `/api/builderlab/*` routes (`browser-host/builderlab.mjs`),
which keep their own session credential and signing key in Node. Desktop builds use
`native.ts`, the same routes with the plugin's shared session credential, host HTTP to
the configured `BUZZ_BUILDERLAB_URL` grant, and `identity_sign_builderlab_binding`, which
validates the challenge and signs only the fixed kind 24243 binding. They read the
account after sign-in's automatic identity link settles. The desktop credential lives in
plugin memory for the plugin's lifetime and is never persisted, so restarting the app
signs out. Builds without a configured Builderlab URL show the unavailable notice.

### Composer accessories

Expand Down
2 changes: 1 addition & 1 deletion docs/shell-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ semantic tokens, UI authoring rules and the local component reference.
a shared Popover containing NavigationItem rows with their existing context menus;
this richer picker retains community actions as well as switching. Arrow keys,
Home and End move focus. Join and Create community appear at the bottom; creation
opens the registered Hosted communities settings flow. Dialogs return focus to
opens the registered Builderlab settings flow. Dialogs return focus to
the surviving community trigger.
Switching pages preserves the desktop sidebar’s current visibility. The manual
collapse toggle remains available across destinations.
Expand Down
2 changes: 1 addition & 1 deletion docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ and [contributing](contributing.md) for maintained commands and test organizatio
| Gate | What is still needed |
| --- | --- |
| Native lifecycle acceptance | Agree app-data **and keyring** isolation, then exercise actual CLI install/update → running-app observation, on-disk rollback/backup, disable/recovery/safe mode and process restart. Browser fixture storage is not native IPC/filesystem evidence. |
| Native identity and networking | External plugins can declare exact commands and HTTPS origins for bounded native calls. Packaged real-data acceptance and per-user login/session behavior remain open. Hosted communities' Builderlab sign-in and identity binding run natively in desktop builds; the session lasts until the app restarts. The declarations are disclosure, not plugin isolation; private keys stay outside plugin JavaScript. Do not widen CSP to unrestricted networking. |
| Native identity and networking | External plugins can declare exact commands and HTTPS origins for bounded native calls. Packaged real-data acceptance and per-user login/session behavior remain open. The Builderlab sign-in, shared by hosted communities, remote agents and apps, and its identity binding run natively in desktop builds; the session lasts until the app restarts. The declarations are disclosure, not plugin isolation; private keys stay outside plugin JavaScript. Do not widen CSP to unrestricted networking. |
| Supported author contract | The conversation preview adds generated type-only `@buzz/author`, scaffold consumption and external Composer/Message reuse. Final validation, archived cross-host compatibility and native acceptance remain open; no stable SDK promise. |
| Independent plugin acceptance | Independently build a real-data page and a non-GitHub panel without private host imports, copied contracts or bespoke host patches. In a packaged app: install disabled → enable/use → A/B switching → update/remount → disable → rollback → failed-start recovery. Include revocation, unknown/rejected writes and stalled cleanup. |
| Distribution | PR CI is wired in `.github/workflows/ci.yml`; decide release signing, updating, provenance and compatibility support. Record packaged artifacts and platform-specific results. Native test targets at the recorded gate contain zero cases; compilation is not GUI acceptance. |
Expand Down
23 changes: 0 additions & 23 deletions src-tauri/src/host_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,29 +499,6 @@ mod tests {
assert!(!allows_origin(&grants, &url));
}

#[test]
fn bundled_hosted_communities_reach_only_the_builderlab_origin() {
let temp = tempfile::tempdir().unwrap();
let manager =
buzzodz_plugins::Manager::open(Some(temp.path().into()), "test", false).unwrap();
let grants = manager
.host_grants("block.hosted-communities", "bundled")
.unwrap();
assert_eq!(grants.network_origins, ["https://app.builderlab.xyz"]);
assert!(grants.commands.is_empty());
for (url, allowed) in [
("https://app.builderlab.xyz/api/goose/v1/auth/me", true),
("https://builderlab.xyz/api/goose/v1/auth/me", false),
(
"https://app.builderlab.xyz:8443/api/goose/v1/auth/me",
false,
),
] {
let (url, _, _) = validate_request(&request(url)).unwrap();
assert_eq!(allows_origin(&grants, &url), allowed, "{url}");
}
}

#[test]
fn uploads_only_an_archive_file_in_home() {
let url = reqwest::Url::parse("https://apps.example.test/deploy").unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ function ConnectedApp({ services }: { services: AppServices }) {
void services.navigation.open({
version: 1,
kind: "settings",
section: "block.hosted-communities/hosted",
section: "block.builderlab/login",
})
}
/>
Expand Down
16 changes: 2 additions & 14 deletions src/app/pages.integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,7 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
.snapshot()
.map((card) => card.pluginId)
.sort(),
[
"block.builderlab",
"block.hosted-communities",
"buzz.channels",
"buzz.emoji",
"buzz.pairing",
],
["block.builderlab", "buzz.channels", "buzz.emoji", "buzz.pairing"],
);
// The device-local template library remains reachable without a selected community.
assert.equal(
Expand Down Expand Up @@ -207,13 +201,7 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
.snapshot()
.map((card) => card.pluginId)
.sort(),
[
"block.builderlab",
"block.hosted-communities",
"buzz.channels",
"buzz.emoji",
"buzz.pairing",
],
["block.builderlab", "buzz.channels", "buzz.emoji", "buzz.pairing"],
);

await vi.waitFor(() =>
Expand Down
12 changes: 2 additions & 10 deletions src/app/settings-sections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,9 @@ const cards = [
card("other/groups"),
card("example/membership", { section: "administration" }),
card("example/hosted", { group: "Communities" }),
card("block.hosted-communities/hosted", {
group: "Plugins",
title: "Builderlab hosting",
}),
card("block.builderlab/login", {
group: "Plugins",
title: "Builderlab agents",
title: "Builderlab",
}),
];
const primary = { id: "primary", name: "Primary" };
Expand All @@ -53,11 +49,7 @@ const app = [
"updates",
...(developerMode ? ["developer"] : []),
];
const plugins = [
"plugins",
"block.builderlab/login",
"block.hosted-communities/hosted",
];
const plugins = ["plugins", "block.builderlab/login"];
const outline = (model: ReturnType<typeof settingsSections>) =>
model.groups.map((group) => [
group.id,
Expand Down
15 changes: 9 additions & 6 deletions src/bundled/builderlab/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Builderlab

Create agents that Builderlab hosts for you, so they keep working even when Buzz is closed.
One Builderlab account for communities and agents that Builderlab hosts for you, so they keep working even when Buzz is closed.

## Getting started

1. Go to **Settings → Plugins → Builderlab agents** and choose **Sign in with Builderlab**.
2. In **Agents**, choose **New agent** and pick **Builderlab (hosted)** as the **Harness**.
3. Choose a model and reasoning effort, optionally add instructions, then create the agent.
4. Edit instructions later in **Identity**, and model settings in **Settings**.
1. Go to **Settings → Plugins → Builderlab** and choose **Sign in with Builderlab**.
2. To host a community, choose **Create community** under **Communities**, then **Copy address**, open the community picker and choose **Join a community**.
3. To host an agent, in **Agents** choose **New agent** and pick **Builderlab (hosted)** as the **Harness**.
4. Choose a model and reasoning effort, optionally add instructions, then create the agent.
5. Edit instructions later in **Identity**, and model settings in **Settings**.

---

Expand Down Expand Up @@ -43,7 +44,9 @@ SSO is a separate integration.

## Remote agents

The bundled `block.builderlab` plugin adds Settings → Plugins → Builderlab agents.
The bundled `block.builderlab` plugin adds Settings → Plugins → Builderlab: one
sign-in, then hosted communities (`communities/`, see
[plugin architecture](../../../docs/plugin-architecture.md)) and remote agents.
With a valid `BUZZ_BUILDERLAB_URL` build input, it also registers
`block.builderlab/builderlab` in Agents' new-agent type picker.
`adapter-type/` connects this hosted type to Beekeeper: provision registers the
Expand Down
48 changes: 29 additions & 19 deletions src/bundled/builderlab/agents/RemoteAgents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import {
useState,
useSyncExternalStore,
} from "react";
import { RobotIcon } from "../../../shared/design-system/icons";
import {
ArrowsClockwiseIcon,
RobotIcon,
} from "../../../shared/design-system/icons";
import { Button } from "../../../shared/design-system/ui/Button";
import { EmptyState } from "../../../shared/design-system/ui/EmptyState";
import { InlineHeader } from "../../../shared/design-system/ui/Header";
import { SettingsGroup } from "../../../shared/design-system/ui/SettingsGroup";
import type { OAuthSession } from "../oauth/session";
import type { AgentClient, RemoteAgent } from "./client";

Expand Down Expand Up @@ -82,12 +86,18 @@ function AgentList({
return (
<section
data-buzz-ui=""
className="mt-6 flex flex-col gap-3"
className="mt-section-gap flex flex-col gap-3"
aria-label="Remote agents"
>
<InlineHeader
title="Remote agents"
subtitle="Create, edit and delete agents in the Agents tab."
actions={
<Button variant="ghost" size="sm" loading={loading} onClick={refresh}>
<ArrowsClockwiseIcon aria-hidden="true" />{" "}
{error ? "Retry" : "Refresh agents"}
</Button>
}
/>
{loading && (
<p role="status" className="text-body-sm text-secondary">
Expand All @@ -108,24 +118,24 @@ function AgentList({
/>
)}
{agents && agents.length > 0 && (
<ul className="flex flex-col gap-3">
{agents.map((agent) => (
<li key={agent.id} className="flex flex-col gap-1">
<div className="text-body-sm text-primary">
{agent.name} · {agent.status}
</div>
<span className="break-all text-mono text-secondary">
{agent.pubkey}
</span>
</li>
))}
</ul>
<SettingsGroup>
<ul className="m-0 list-none p-0">
{agents.map((agent) => (
<li
key={agent.id}
className="flex min-w-0 flex-col gap-1 border-b border-line py-4"
>
<p className="m-0 text-label">
{agent.name} · {agent.status}
</p>
<span className="break-all text-mono text-muted">
{agent.pubkey}
</span>
</li>
))}
</ul>
</SettingsGroup>
)}
<div>
<Button variant="outline" loading={loading} onClick={refresh}>
{error ? "Retry" : "Refresh agents"}
</Button>
</div>
</section>
);
}
2 changes: 1 addition & 1 deletion src/bundled/builderlab/apps/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ it("uploads the artifact to the app's deploy endpoint", async () => {
it("points the agent at Settings when signed out or refused", async () => {
const signedOut = await setup([], false);
await expect(signedOut.client.list()).rejects.toThrow(
/not signed in.*Settings → Plugins → Builderlab agents/,
/not signed in.*Settings → Plugins → Builderlab, then/,
);
expect(signedOut.requests).toEqual([]);
const refused = await setup([json(401, {})]);
Expand Down
2 changes: 1 addition & 1 deletion src/bundled/builderlab/apps/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { OAuthSession } from "../oauth/session";
const PROTOCOL = "0.2.0";
export type RuntimeProfile = "static" | "fetch-js";
export const SIGN_IN =
"Ask your owner to sign in at Settings → Plugins → Builderlab agents, then try again.";
"Ask your owner to sign in at Settings → Plugins → Builderlab, then try again.";

type Json = Record<string, unknown>;
const object = (value: unknown): Json =>
Expand Down
Loading
Loading