From 1597d7cc3ba202af5ae91fa0feca895560f1c49e Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 29 Oct 2026 11:50:02 +1100 Subject: [PATCH 01/13] feat(agents): let hosted agents join teams, warning about instructions Hosted agents can be picked as team members. While one is on the team, the Instructions field warns that remote agents may not get them. Local members' rosters now name the community's listed teammates too, so they can reach hosted ones. Co-authored-by: Jitter <2b2e6415e748c35180847a37aadae06a11e30dddc76b784e7fd9354c4eb42e7a@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matt Toohey --- src/bundled/agents/AgentsPage.test.tsx | 27 +++++++++++++++++++++++ src/bundled/agents/AgentsPage.tsx | 19 ++++++++++++++-- src/features/agents/service.test.ts | 30 ++++++++++++++++++++++++++ src/features/agents/service.ts | 26 +++++++++++----------- 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/bundled/agents/AgentsPage.test.tsx b/src/bundled/agents/AgentsPage.test.tsx index cb29da7b90..80c2117ac9 100644 --- a/src/bundled/agents/AgentsPage.test.tsx +++ b/src/bundled/agents/AgentsPage.test.tsx @@ -2460,6 +2460,33 @@ it("makes a team of chosen agents and lists it with its members", async () => { expect(screen.queryByRole("list", { name: "Teams" })).not.toBeInTheDocument(); }); +it("offers hosted agents as members, warning that they may not get the instructions", async () => { + const user = userEvent.setup(); + setup([ + agent("Ada", 1), + { ...agent("Sky", 2), place: "hosted" }, + { ...agent("Bo", 3), place: "hosted", unfinished: true }, + { ...agent("Cy", 4), place: "elsewhere" }, + ]); + await user.click(screen.getByRole("tab", { name: "Your teams" })); + await user.click(screen.getByRole("button", { name: "New team" })); + await user.click( + await screen.findByRole("menuitem", { name: "Create a new team" }), + ); + const members = ["Ada", "Sky", "Bo", "Cy"].map((name) => + screen.queryByRole("checkbox", { name }), + ); + expect(members.map(Boolean)).toEqual([true, true, false, false]); + const instructions = screen.getByRole("textbox", { name: "Instructions" }); + const warning = /Remote agents on this team/; + await user.click(screen.getByRole("checkbox", { name: "Ada" })); + expect(instructions).not.toHaveAccessibleDescription(warning); + await user.click(screen.getByRole("checkbox", { name: "Sky" })); + expect(instructions).toHaveAccessibleDescription(warning); + await user.click(screen.getByRole("checkbox", { name: "Sky" })); + expect(instructions).not.toHaveAccessibleDescription(warning); +}); + it("closes the team editor when leaving Your teams", async () => { const user = userEvent.setup(); const { agents } = setup([agent("Ada", 1)]); diff --git a/src/bundled/agents/AgentsPage.tsx b/src/bundled/agents/AgentsPage.tsx index 1575648112..9b0077ad2c 100644 --- a/src/bundled/agents/AgentsPage.tsx +++ b/src/bundled/agents/AgentsPage.tsx @@ -545,7 +545,9 @@ export function AgentsPage({ ) : ( agent.place === "here")} + saved={snapshot.agents.filter( + (agent) => agent.place !== "elsewhere" && !agent.unfinished, + )} teams={teams} editing={editing} onEdit={setEditingTeam} @@ -2142,6 +2144,10 @@ function TeamEditor({ !saved.some((agent) => agent.pubkey === pubkey) && agents.memberships(pubkey).length > 0, ).length; + // Their provider runs them, so they may not get the team's instructions. + const remote = saved.some( + (agent) => agent.place === "hosted" && members.includes(agent.pubkey), + ); const act = async (work: () => Promise) => { if (pending) return; setPending(true); @@ -2193,7 +2199,16 @@ function TeamEditor({ + Every member follows these after its own instructions. + {remote && ( + + Remote agents on this team may not get these instructions. + + )} + + } >