feat(plugins): reach a running agent's composer by reference

useComposers(selector) in any plugin component and composers(selector) on every command context return the composer of a running agent, named by id or by focus, whether or not its tab is open.

A running agent now has one store-backed composer model apart from any mounted tab. The mounted composer plugs its cursor-aware insert in while mounted; otherwise insertText and setPrompt write the agent's saved prompt. model and thinking resolve through the provider catalog with the picker's own function, so a read equals what the picker shows.

The host reads one ComposerModel from context and no longer knows whether it is in an agent, a draft or New workspace. The daemon rejects an unlisted model or thinking option for a running agent, replacing the app-side guard.
This commit is contained in:
Mohamed Boudra
2026-11-05 23:25:42 +01:00
parent 03580ce50b
commit f665cc697c
42 changed files with 1621 additions and 272 deletions
+45
View File
@@ -250,6 +250,51 @@ describe("plugin composer SDK boundary", () => {
});
});
describe("plugin composers by reference SDK boundary", () => {
it("reaches a running agent's composer from any component and from every command context", () => {
expect(
typeErrors(`
import { useComposers, type PluginClientContext } from "@getpaseo/plugin/client";
function Button() {
const [focused] = useComposers({ focused: true });
const byId = useComposers({ agentId: "agent-1" });
if (!focused) return null;
const first: string = byId[0]?.prompt ?? "";
focused.insertText("text");
focused.setMetadata({ flag: true });
// @ts-expect-error a selector names an agent or the focused agent
useComposers({ active: true });
// @ts-expect-error focused is true or absent
useComposers({ focused: false });
return null;
}
export default function contribute(client: PluginClientContext) {
client.addWorkspacePanel({ id: "panel", title: "P", icon: "File", context: "workspace", Component: Button });
client.addCommandCenterItem({
id: "mark",
title: "Mark",
icon: "Flag",
context: "agent",
async onSelect(context) {
for (const composer of context.composers({ agentId: context.agent.id })) {
await composer.setMetadata({ review: true });
}
},
});
client.addCommandCenterItem({
id: "global",
title: "Global",
icon: "Flag",
context: "global",
onSelect: ({ composers }) => composers({ focused: true })[0]?.insertText("x"),
});
return () => {};
}
`),
).toEqual([]);
});
});
describe("plugin New workspace SDK boundary", () => {
it("lets a New workspace item read the setup and change it through typed commands and a Combobox", () => {
expect(