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({

)} {agent.configured === false && - (onUseHere ? ( + (onUseHere && + state.data?.localInventoryActions && + control.configureHere ? ( - ) : state.data?.localInventoryActions && control.configureHere ? ( + ) : canUseHere ? ( {}} onClone={() => {}} /> - ) : ( -

Update the desktop app to set up this imported identity.

- ))} + ) : null)} {agent.startOnAppLaunch && (

Starts with this app.

)} diff --git a/src/features/agents/control.ts b/src/features/agents/control.ts index 691f74091..d98574115 100644 --- a/src/features/agents/control.ts +++ b/src/features/agents/control.ts @@ -286,7 +286,9 @@ export function agentLaunchBlock( agent: AgentView, ): string | null { if (agent.configured === false) - return "Choose Use here before starting this imported identity."; + return state.data?.localInventoryActions + ? "Choose Use here before starting this imported identity." + : "Update the desktop app to set up this imported identity."; if (state.status !== "ready") return "Refresh status before starting."; if (state.busy) return "Waiting for the current operation."; if (!state.data?.runtimeAvailable) From 7426c878ff6b2b8e2572566580af462a02cc9a02 Mon Sep 17 00:00:00 2001 From: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Date: Wed, 30 Sep 2026 15:33:19 -0400 Subject: [PATCH 3/5] Document native Use here confirmation in the packaged app The guidance said only the development broker served the owner confirmation, so packaged builds disabled Use here. The packaged app now confirms natively. Only hosts without local inventory actions hide Use here and ask for an update. Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> --- docs/agent-control.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/agent-control.md b/docs/agent-control.md index e52943b70..6a6856308 100644 --- a/docs/agent-control.md +++ b/docs/agent-control.md @@ -77,8 +77,10 @@ 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 +The development broker and the packaged desktop app both provide that +confirmation; the desktop app signs it natively (`relay_agent_resolve`). A host +without local inventory actions hides **Use here** and asks the user to update +the desktop app; **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. From 9fbeefc4075206f002f4668350ac9437a20eb6c0 Mon Sep 17 00:00:00 2001 From: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Date: Thu, 1 Oct 2026 16:35:15 -0400 Subject: [PATCH 4/5] Drop unreachable older-host Use here fallbacks and correct inventory docs Every shipped host that reports unconfigured identities also reports local inventory actions, so the fallbacks for a host without that capability only existed in a test fixture, and the two renderers disagreed about it. Remove both fallbacks and their test; Use here still requires the host's configureHere capability. The docs now say that every configured setup keeps Start, Stop and Edit in any community, that the import source is fixed during a healthy review, and that the separate installation browser appears only for repair. Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> --- docs/agent-control.md | 16 +++++++++------- docs/agents.md | 3 ++- src/bundled/agents/AgentsPage.test.tsx | 19 ------------------- src/bundled/agents/InventoryIdentityCard.tsx | 6 ------ src/bundled/agents/ManagedAgentActions.tsx | 11 +++-------- src/features/agents/control.ts | 4 +--- 6 files changed, 15 insertions(+), 44 deletions(-) diff --git a/docs/agent-control.md b/docs/agent-control.md index 6a6856308..adab185e3 100644 --- a/docs/agent-control.md +++ b/docs/agent-control.md @@ -78,9 +78,8 @@ Older hosts retain the separate installation browser as a compatibility path. step after Import. It retains the identity/key, requires owner-authorized community confirmation, and leaves the recovered setup stopped with app-launch start off. The development broker and the packaged desktop app both provide that -confirmation; the desktop app signs it natively (`relay_agent_resolve`). A host -without local inventory actions hides **Use here** and asks the user to update -the desktop app; **Clone** is unaffected. Native code refuses a new +confirmation; the desktop app signs it natively (`relay_agent_resolve`). 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. @@ -879,14 +878,17 @@ 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** +identity and known local source selected. The source is fixed during review; +if the preview fails or the identity is unavailable there, you can choose +another source. **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. +An inventory failure does not block local Import or setup confirmation. Every +configured setup keeps Start, Stop and Edit, whichever community is selected. +Cards for other communities also 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. diff --git a/docs/agents.md b/docs/agents.md index ec33583c0..a3bbb1e3d 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -535,5 +535,6 @@ matching community. A failed source leaves the other source visible with a warni 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. +read-only section. Each card offers its own Import; the separate installation +browser appears only for repair. No keys, config, memory, membership, or runtime state are changed by discovery. diff --git a/src/bundled/agents/AgentsPage.test.tsx b/src/bundled/agents/AgentsPage.test.tsx index 2af31193c..248c4690a 100644 --- a/src/bundled/agents/AgentsPage.test.tsx +++ b/src/bundled/agents/AgentsPage.test.tsx @@ -2951,22 +2951,3 @@ 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(); - expect(screen.queryByText(/Choose Use here/)).toBeNull(); - for (const start of screen.getAllByRole("button", { name: "Start" })) - expect(start).toBeDisabled(); -}); diff --git a/src/bundled/agents/InventoryIdentityCard.tsx b/src/bundled/agents/InventoryIdentityCard.tsx index 391310649..255005905 100644 --- a/src/bundled/agents/InventoryIdentityCard.tsx +++ b/src/bundled/agents/InventoryIdentityCard.tsx @@ -187,7 +187,6 @@ export function InventoryIdentityCard({ state.busy || state.status !== "ready" || !!decision.blocked || - !data.localInventoryActions || !control.configureHere } onClick={() => onUseHere(row.pubkey, "use")} @@ -195,11 +194,6 @@ export function InventoryIdentityCard({ Use here {decision.blocked &&

{decision.blocked}

} - {!data.localInventoryActions && ( -

- Restart an updated desktop build to use local inventory actions. -

- )} )} {(tile || decision.action === "clone") && cloneAction} diff --git a/src/bundled/agents/ManagedAgentActions.tsx b/src/bundled/agents/ManagedAgentActions.tsx index 36fa88e7c..058cd1c58 100644 --- a/src/bundled/agents/ManagedAgentActions.tsx +++ b/src/bundled/agents/ManagedAgentActions.tsx @@ -51,9 +51,6 @@ export function ManagedAgentActions({ details.current?.focus(); } }, [imported]); - const canUseHere = !!( - state.data?.localInventoryActions && control.configureHere - ); const startBlock = agentLaunchBlock(state, agent); const act = (action: "start" | "stop") => { setNotice(null); @@ -100,21 +97,19 @@ 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 ? ( - ) : 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);