From a132b3b74d6f574184dd84bd34d276ec699fca1d Mon Sep 17 00:00:00 2001
From: Larry
<627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Date: Thu, 24 Sep 2026 17:24:37 -0400
Subject: [PATCH 1/5] Document unified inventory and verify focused import and
compact-row acceptance
Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
---
docs/agent-control.md | 116 +++++--
docs/agents.md | 35 ++-
src/app/agent-control.integration.test.ts | 16 +-
src/bundled/agents/AgentsPage.test.tsx | 33 +-
src/bundled/agents/ManagedAgentActions.tsx | 4 +-
tests/browser/agent-control.spec.mjs | 337 +++++++++++++++++++++
tests/fixtures/agent-control.tsx | 8 +-
7 files changed, 504 insertions(+), 45 deletions(-)
diff --git a/docs/agent-control.md b/docs/agent-control.md
index 951ee2c90..e52943b70 100644
--- a/docs/agent-control.md
+++ b/docs/agent-control.md
@@ -1,8 +1,8 @@
# Local agent controls
The Agents page uses one app-owned native controller for creating, importing,
-editing and running local agents. Managed cards are keyed by exact identity and
-community. Browser-only access keeps the read-only old library; it cannot run
+editing and running local agents. Inventory cards join records by exact public key;
+managed actions remain keyed by native identity/community record. Browser-only access keeps the read-only old library; it cannot run
agents. The only product entry point is ordinary desktop startup.
## Normal desktop workflow
@@ -16,16 +16,17 @@ configuration and persistent native settings. Coordinate the native rebuild/rela
quit other Foundation copies first. Saved enabled agents can restore on startup.
Keep imported agents disabled and old Buzz running until an attended handover.
-Open **Agents → My agents** for imported identities, their destination community,
-process evidence and visible **Start / Stop**. **Edit**, **Duplicate**, and
-**Delete** are in the card’s three-dot menu. Duplicate seeds Create with editable
-settings and a fresh identity; write-only environment values require re-entry.
-Delete stops the local process and removes this app's settings and Keychain key
-after confirmation. It does not archive the relay identity or erase messages.
-Deployed remote records are refused.
-Same-key identities at different destinations have separate
-cards; actions use native ID/revision, never the display name. Managed controls
-remain available when the old library is disconnected, unavailable or archived.
+Open **Agents** for discovered and imported identities grouped by known community
+associations. Each identity card keeps the configured destinations’ **Start / Stop**
+controls. **Edit**, **Duplicate**, and **Delete** are in the card’s three-dot menu.
+Duplicate seeds Create with editable settings and a fresh identity; write-only
+environment values require re-entry. Delete stops the local process and removes
+this app's settings and Keychain key after confirmation. The key remains while
+another setup of the same identity still uses it. Delete does not archive the
+relay identity or erase messages. Deployed remote records are refused. Actions
+use native ID/revision, never the display name. Older hosts without parked
+inventory retain the **My agents** and read-only library sections. Managed
+controls remain available when discovery is disconnected, unavailable or archived.
**Add agent** shares the Edit fields and model browser. In the development desktop,
Create generates a native key, obtains the captured viewer's owner authorization,
@@ -50,12 +51,37 @@ worker count. With **Each thread** conversation context, separate threads can
use different workers while retaining separate histories. Existing saved agents
keep their settings; add the variable and restart them to enable more workers.
-**Not imported from old Buzz** is a separate collapsible section. Expanding it
-loads installed identities for the connected community; already-managed exact
-identities are excluded. Each remaining row says **Not imported** and has its own
-**Import** action. Source/destination overrides and source warnings stay under
-Import options. Import focuses the imported card and says **Imported, not started**.
-It does not start a listener, invite an agent or change the old library.
+**Clone to this community** opens the existing creation dialog with only the old
+agent’s name and resolved instructions. Review that text for embedded secrets.
+Runtime settings and workspace use this app’s defaults and remain editable.
+Clone generates a new native identity; it does not copy identity keys, environment
+values, command arguments, paths, history or membership. Saving leaves the new
+agent stopped. The source is read-only and no legacy credential access occurs.
+
+**Import** preserves the selected old-installation identity and private key. It
+requires an explicit destination and a fresh source/destination-bound preview.
+Successful import saves a configured, stopped setup. It does not start a listener,
+invite an agent, or modify the source installation. An identity already held
+locally cannot be imported again into another community; use **Clone** instead.
+The native prepare and commit boundaries both enforce that exact-key rule.
+
+The unified inventory offers Import only under **Available to import**, once per
+exact public key. Multiple old installations require an explicit source choice.
+The selected row opens the import review. Without a selected community, such
+as in Personal space, the review asks for the destination before it loads a
+fresh preview. Each startup reads every old installation again: an identity
+deleted from all of them, or whose installation is removed, leaves the list. A
+damaged source keeps its previous entries. Source read failures remain visible.
+Older hosts retain the separate installation browser as a compatibility path.
+
+**Use here** is recovery for older incomplete local imports, not a normal next
+step after Import. It retains the identity/key, requires owner-authorized community
+confirmation, and leaves the recovered setup stopped with app-launch start off.
+Only the development broker serves that confirmation today, so packaged
+connections disable **Use here** and explain why; **Clone** is unaffected. Native code refuses a new
+community when that identity already has a configured setup elsewhere. A retry
+for the already recovered destination is harmless. Existing historical setups
+remain visible and controllable; this rule does not move or delete them.
Local team-linked imports snapshot the deployment team's instructions from the
chosen library's `agents/teams.json`, alongside the resolved persona prompt.
@@ -74,7 +100,7 @@ perform the same attended old-Buzz handover as for a fresh import.
To use an agent, open a channel and select it from **@ mentions**. Both mention
menus include the selected community’s people directory alongside channel members
-and managed agents. Directory reads are bounded; narrow the search for more people.
+and configured managed agents. Directory reads are bounded; narrow the search for more people.
A nonmember is labeled **Not in channel · Choose whether to add when you send**.
Selection alone does nothing. Send asks, as block/buzz desktop does: **Invite**
or **Do nothing**. Without add permission, the only action is **Send anyway**.
@@ -89,7 +115,7 @@ before a new add; unknown outcomes are never silently replaced. Channel, thread,
and forum-channel composers share this behavior. DM participants and session
admission rules are unchanged.
-A confirmed outgoing channel or thread mention now starts an exact imported local
+Once an identity is configured by Import or legacy **Use here** recovery, a confirmed outgoing channel or thread mention starts that exact local
agent (public key + community), without a separate Start click. Import itself
remains non-starting. Stop cancels earlier pending mention wakes and active work;
a later deliberate mention can start the agent again. Plain name text without
@@ -554,8 +580,10 @@ Settings says **Shell setup not verified**; Buzz does not check it before Start.
Snapshots name the deciding key (`launchModelEnv`/`launchProviderEnv`,
including `DATABRICKS_MODEL` or a hidden provider behind a blank buzz-agent
model) and omit the resolved value.
-- Import previews only the chosen installed/development library and requires an
- explicit secure **Destination community** origin. Old Buzz ignores saved relay
+- Local browsing reads only the chosen installed/development library without a
+ destination. Native keeps no pending import for that read and invalidates any
+ prior import token. An actionable import preview requires an explicit secure
+ **Destination community** origin. Old Buzz ignores saved relay
pins at runtime; blank, stale or malformed saved pins do not route or hide
identities here. Native validates the chosen destination, shows it beside each
exact key, and retains it with the preview token through commit. Source or
@@ -818,3 +846,47 @@ configuration. The current internal release repository builds the old desktop;
its generic build environment injection is not a Pi resource-bundling contract
for this app. Signed bundling, automatic employee provisioning and release
pipeline migration require separate release work; no release is published here.
+
+### Community setup confirmation
+
+Local installation import validates the source configuration, owner authorization
+and private key. It does not require relay inventory or a community confirmation.
+The imported agent stays stopped.
+
+For explicit setup in a community, the broker can sign the selected owner's
+intent for an identity/community pair. Native code verifies that signature against
+the retained source-owner authorization. This does not establish channel membership,
+key availability or exclusive community membership. It does not reserve a community
+before import. Setup recovery cannot add another community to an identity that
+already has a configured setup elsewhere; copying that agent requires Clone.
+
+Joined-community discovery reads each joined community's scoped inventory without
+selecting it or opening a relay session. Exact keys appear once with all known
+associations. Each failed community read has its own warning and Refresh retry;
+successful reads remain visible. Discovery does not provide credentials, an import
+source, or permission to extend an existing local identity into another community.
+
+### Local inventory actions
+
+Startup copies only identity names, public keys and source labels into a durable
+inventory. It does not read keys, configure a setup, or start an imported agent.
+Import copies the selected local key and settings, independent of relay inventory.
+New imports require a destination and are saved configured but stopped.
+**Use here** only recovers older incomplete imports. **Start** remains a separate
+action; a later deliberate mention can also start a configured agent. Existing
+saved setups without the configured flag keep their prior behavior.
+
+The unified card's **Import** opens the existing installation form with its exact
+identity and known local source selected. The source remains editable. **Clone**
+from a local source or imported identity opens a review of only its name and
+instructions; creation generates a fresh key. Clone never imports the old key.
+
+Community groups show known associations, not exclusive membership or admission.
+An inventory failure does not block local Import or setup confirmation. Configured
+setups show Start, Stop and Edit only in the current community. Other-community
+local agents offer Clone to bring a new identity here, without changing the source.
+Archived discovery rows remain hidden after sources join, except where local
+controls must remain reachable. Linked profiles remain visible on identity cards.
+
+Before starting an imported identity, stop the old agent and disable its automatic
+startup in the old application. Do not run duplicate copies of the same identity.
diff --git a/docs/agents.md b/docs/agents.md
index a63d813b4..ec33583c0 100644
--- a/docs/agents.md
+++ b/docs/agents.md
@@ -23,14 +23,14 @@ add-existing membership, Save/recovery and all runner management are out of V1.
- Only definition ID/name, identity public key/name/definition link, and optional
avatar artwork leave the host. Prompts, configuration, credentials and execution receipts are not
projected. This is local library evidence, **not verified ownership**.
-- Selected definitions remain one card each, including definitions without an
- identity. Exact linked keys remain available in each card’s identity disclosure, including namesakes; unlinked and
- unmatched identities use Custom agents/Other identities groupings. Unlike old Buzz's
- runtime-dependent representative selection, this read-only view shows all
- non-archived linked keys and has no profile/start action or running badge.
-- Confirmed relay archives hide identity rows, not definition cards. Missing
- archive evidence is labeled; it does not erase the saved library. This is
- display behavior, never mention permission.
+- The library shows one tile per exact identity, grouped only by explicit profile
+ links. Each tile discloses its full public key. Profiles with no linked identity
+ appear separately; an archived identity does not become an empty profile.
+- Only distinct keys with the same displayed name need a short npub suffix. Names
+ alone never create a profile group. Suffix collisions extend deterministically using
+ the complete inventory, including identities hidden by archive filtering.
+- Missing archive evidence keeps identities visible. Archive filtering affects
+ display only, never mention permission or runtime control.
- One lazy host read per opening/Refresh; no polling or relay-directory startup
scan. Concurrent host requests coalesce. Read caps: 8 MiB / 2000 records;
malformed/missing files fail visibly without echoing their contents. The host
@@ -520,5 +520,20 @@ service. Each relay session binds its own view. A ready native record takes
precedence only in its matching community; otherwise the ready legacy display
inventory supplies the name, then the public profile. Plugin disable restores
public-profile names. These labels never change identity keys, membership,
-credentials, or runtime admission. Profile panels consume this view; other name
-surfaces are being migrated separately.
+credentials, or runtime admission. Profile panels, messages, mention choices,
+activity, conversation labels and new notifications consume this view. Mention
+parsing still uses signed identity evidence before resolving its visible label.
+
+### Additive community inventory
+
+The active session reads the owner's kind-30175 profiles and kind-30177 identities
+from its accessible relay. It also retains the local library reader. The inventory
+joins exact public keys, not equal names; explicit profile references use the
+publisher's slug mapping only when local definitions do not collide. Local names
+and artwork win for matching keys. Native configuration still wins within its
+matching community. A failed source leaves the other source visible with a warning.
+
+Discovery is not global coverage, verified membership, credentials, or execution
+status. Native cards keep their controls. Other known identities appear in a
+read-only section, while the existing old-desktop import flow stays available.
+No keys, config, memory, membership, or runtime state are changed by discovery.
diff --git a/src/app/agent-control.integration.test.ts b/src/app/agent-control.integration.test.ts
index eb5b2f923..aaba33d57 100644
--- a/src/app/agent-control.integration.test.ts
+++ b/src/app/agent-control.integration.test.ts
@@ -113,9 +113,21 @@ it("retains working injected control across Agents disable/re-enable and communi
.find((entry) => entry.pluginId === "buzz.agents");
// The production registration wrapper passes its injected capability as props.
const component = page?.component as unknown as () => {
- props: { control: AgentControl };
+ props: {
+ control: AgentControl;
+ communities: Pick<
+ typeof services.communities,
+ "snapshot" | "subscribe"
+ >;
+ };
};
- return component().props.control;
+ const props = component().props;
+ expect(Object.keys(props.communities).sort()).toEqual([
+ "snapshot",
+ "subscribe",
+ ]);
+ expect(props.communities.snapshot()).toBe(services.communities.snapshot());
+ return props.control;
};
expect(injectedControl()).toBe(control);
await control.refresh();
diff --git a/src/bundled/agents/AgentsPage.test.tsx b/src/bundled/agents/AgentsPage.test.tsx
index 12fe0e510..f688e587d 100644
--- a/src/bundled/agents/AgentsPage.test.tsx
+++ b/src/bundled/agents/AgentsPage.test.tsx
@@ -561,13 +561,14 @@ it("focuses the imported managed identity without starting it", async () => {
fireEvent.click(
await screen.findByRole("button", { name: "Import Fixture agent" }),
);
- const notice = await screen.findByText(
- "Imported, not started. Start it when you are ready.",
- );
+ const notice = await screen.findByText(/Imported, not started\./);
const imported = notice.closest("article");
if (!imported) throw Error("Imported card missing");
expect(imported).toHaveTextContent("wss://third.example");
expect(notice.parentElement).toHaveFocus();
+ expect(
+ within(imported).queryByRole("button", { name: "Use here" }),
+ ).toBeNull();
expect(within(imported).getByRole("button", { name: "Start" })).toBeEnabled();
expect(f.calls.some((call) => call.action === "start")).toBe(false);
});
@@ -1241,9 +1242,7 @@ it("credential import keeps real Stop controls reachable without trapping the ed
await gate;
});
await waitFor(() => expect(control.snapshot().busy).toBe(false));
- expect(
- screen.queryByText("Imported, not started. Start it when you are ready."),
- ).toBeNull();
+ expect(screen.queryByText(/Imported, not started\./)).toBeNull();
await act(async () => control.refresh());
const imported = control
.snapshot()
@@ -2680,9 +2679,7 @@ it("uses snapshot capabilities rather than JS wrappers and retains older-host im
fireEvent.click(
await screen.findByRole("button", { name: "Import Fixture agent" }),
);
- const notice = await screen.findByText(
- "Imported, not started. Start it when you are ready.",
- );
+ const notice = await screen.findByText(/Imported, not started\./);
const card = notice.closest("article");
if (!card) throw Error("Imported card missing");
expect(within(card).getByRole("button", { name: "Start" })).toBeEnabled();
@@ -2954,3 +2951,21 @@ it("keeps the chosen source authoritative when an earlier preview finishes late"
]),
);
});
+
+it("does not offer setup recovery when an older host lacks the native capability", async () => {
+ setup("connected", (fixture) => {
+ delete fixture.data.localInventoryActions;
+ Object.assign(fixture.agent, {
+ configured: false,
+ enabled: false,
+ status: "stopped",
+ runningRevision: null,
+ });
+ });
+ await screen.findAllByText(
+ "Update the desktop app to set up this imported identity.",
+ );
+ expect(screen.queryByRole("button", { name: "Use here" })).toBeNull();
+ for (const start of screen.getAllByRole("button", { name: "Start" }))
+ expect(start).toBeDisabled();
+});
diff --git a/src/bundled/agents/ManagedAgentActions.tsx b/src/bundled/agents/ManagedAgentActions.tsx
index 05e6cd7dc..a349720bf 100644
--- a/src/bundled/agents/ManagedAgentActions.tsx
+++ b/src/bundled/agents/ManagedAgentActions.tsx
@@ -102,7 +102,9 @@ export function ManagedAgentActions({
Imported, not started.{" "}
{agent.configured === false
- ? canUseHere
- ? "Choose Use here to set up this identity in a community."
- : ""
+ ? "Choose Use here to set up this identity in a community."
: "Start it when you are ready."}
)}
{agent.configured === false &&
- (onUseHere && canUseHere ? (
+ (onUseHere ? (
onUseHere(agent.pubkey, "use")}
>
Use here
- ) : canUseHere ? (
+ ) : control.configureHere ? (
Date: Thu, 1 Oct 2026 16:41:56 -0400
Subject: [PATCH 5/5] test(browser): count only panel tabs in the crowded-tab
journey
#505 turned the new-tab picker's category switcher into a tablist
(Channels, DMs, Tools). The crowded-tab journey still read every
role=tab in the workspace, so with the picker open it counted 2 panel
tabs plus 3 picker tabs and failed at channel-tabs.spec.mjs:309 on
Chromium and WebKit. Its last()/first() lookups also resolved to picker
tabs instead of the strip.
Scope those lookups to the "Panel tabs" tablist, matching how #505
scoped the other counts in this file. Product behavior is unchanged.
Co-authored-by: classy-murderbot
Signed-off-by: Logan Johnson
(cherry picked from commit 4ef0d8022787b2ed9c5da71598cf1c6b25a8474a)
---
tests/browser/channel-tabs.spec.mjs | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/tests/browser/channel-tabs.spec.mjs b/tests/browser/channel-tabs.spec.mjs
index 65b586e0e..8188eabf0 100644
--- a/tests/browser/channel-tabs.spec.mjs
+++ b/tests/browser/channel-tabs.spec.mjs
@@ -298,18 +298,18 @@ test("crowded tab strip scrolls only horizontally with Add tab fixed and a thin
});
const initial = await add.boundingBox();
const height = (await header.boundingBox()).height;
- const tabBounds = await workspace.getByRole("tab").first().boundingBox();
+ const tabBounds = await list.getByRole("tab").first().boundingBox();
const expectTabPosition = async () => {
- const bounds = await workspace.getByRole("tab").last().boundingBox();
+ const bounds = await list.getByRole("tab").last().boundingBox();
expect(bounds.y).toBeCloseTo(tabBounds.y, 1);
expect(bounds.height).toBeCloseTo(tabBounds.height, 1);
};
for (let i = 0; i < 5; i++) {
await add.click();
- await expect(workspace.getByRole("tab")).toHaveCount(i + 2);
+ await expect(list.getByRole("tab")).toHaveCount(i + 2);
await expect(workspace.getByRole("searchbox")).toBeFocused();
}
- const tabs = workspace.getByRole("tab");
+ const tabs = list.getByRole("tab");
const closeButtons = workspace.getByRole("button", {
name: "Close New tab tab",
exact: true,
@@ -346,14 +346,14 @@ test("crowded tab strip scrolls only horizontally with Add tab fixed and a thin
expect(geometry.trackHeight).toBe("4px");
expect((await add.boundingBox()).x).toBeCloseTo(initial.x, 1);
expect((await header.boundingBox()).height).toBeCloseTo(height, 1);
- await workspace.getByRole("tab").last().press("Home");
- await expect(workspace.getByRole("tab").first()).toBeFocused();
+ await list.getByRole("tab").last().press("Home");
+ await expect(list.getByRole("tab").first()).toBeFocused();
await expect(closeButtons.first()).toHaveCSS("opacity", "1");
await expect(list).toHaveJSProperty("scrollTop", 0);
await expectTabPosition();
expect((await add.boundingBox()).x).toBeCloseTo(initial.x, 1);
- await workspace.getByRole("tab").first().press("End");
- await expect(workspace.getByRole("tab").last()).toBeFocused();
+ await list.getByRole("tab").first().press("End");
+ await expect(list.getByRole("tab").last()).toBeFocused();
await expect(list).toHaveJSProperty("scrollTop", 0);
await expectTabPosition();
expect((await add.boundingBox()).x).toBeCloseTo(initial.x, 1);