Let plugins contribute contextual composer pills (#3956)

* feat(plugin): allow composer pill contributions

Keep pill presentation host-owned while plugin components render contextual content. Pill callbacks reuse the selected host's API, RPC, surface, and registered-panel navigation capabilities without exposing router or workspace layout internals.

* fix(plugin): place composer pills in track bar

* fix(plugin): make composer pills client-owned
This commit is contained in:
Mohamed Boudra
2026-08-28 16:02:00 +08:00
committed by GitHub
parent a501e1e051
commit fe803f6e70
36 changed files with 1096 additions and 101 deletions
@@ -81,10 +81,13 @@ import { Text } from "react-native";
import {
Icon,
type PluginAgentPanelProps,
type PluginClientContext,
type PluginComposerPillProps,
useAgent,
usePaseo,
useWorkspace,
} from "@getpaseo/plugin";
import { inspect } from "./inspect.shared";
export function Surface() {
const paseo = usePaseo();
@@ -108,12 +111,30 @@ export function AgentPanel({ workspaceId, agentId }: PluginAgentPanelProps) {
});
return <Text>{workspaceName}: {agentTitle}</Text>;
}
export function ComposerPill({ workspaceId, agentId }: PluginComposerPillProps) {
return <Text>{workspaceId}: {agentId}</Text>;
}
export function contributeClient(client: PluginClientContext) {
return client.addComposerPill({
id: "open-review",
title: "Open review",
workspaceId: "workspace-a",
agentId: "agent-a",
Component: ComposerPill,
async onPress() {
await client.rpc(inspect, {});
client.openPanel("review", { workspaceId: "workspace-a", agentId: "agent-a" });
},
});
}
`,
),
writeFile(
path.join(directory, "index.ts"),
`import type { PluginContext } from "@getpaseo/plugin";
import { AgentPanel, Surface } from "./main.client";
import { AgentPanel, contributeClient, Surface } from "./main.client";
import { inspectConfig } from "./inspect.server";
import { inspect } from "./inspect.shared";
@@ -138,6 +159,7 @@ export default function contribute(plugin: PluginContext) {
openPanel("review");
},
});
plugin.addClientSide(contributeClient);
return () => {};
}
`,
@@ -155,8 +155,26 @@ declare module "@getpaseo/plugin" {
agentId: string;
}
export interface PluginComposerPillProps extends PluginHostProps {
workspaceId: string;
agentId: string;
}
export interface PluginComposerPillContribution {
id: string;
title: string;
workspaceId: string;
agentId: string;
Component: ComponentType<PluginComposerPillProps>;
onPress(): void | Promise<void>;
}
export type PluginPanelLocation = "workspace" | "explorer";
export interface PluginOpenPanelOptions { location?: PluginPanelLocation; }
export interface PluginClientOpenPanelOptions extends PluginOpenPanelOptions {
workspaceId: string;
agentId?: string;
}
export type PluginWorkspacePanelContribution =
| { id: string; title: string; icon: string; locations?: readonly PluginPanelLocation[]; context: "workspace"; Component: ComponentType<PluginWorkspacePanelProps> }
@@ -241,6 +259,12 @@ declare module "@getpaseo/plugin" {
openPanel(id: string, options?: PluginOpenPanelOptions): void;
}
export interface PluginClientContext extends PluginCommandCapabilities {
addComposerPill(contribution: PluginComposerPillContribution): PluginCleanup;
openPanel(id: string, options: PluginClientOpenPanelOptions): void;
}
export type PluginClientContribution = (client: PluginClientContext) => PluginCleanup;
export type PluginCommandCenterItemContribution =
| { id: string; title: string; icon: string; keywords?: readonly string[]; context: "global"; onSelect(context: PluginGlobalCommandContext): void | Promise<void> }
| { id: string; title: string; icon: string; keywords?: readonly string[]; context: "workspace"; onSelect(context: PluginWorkspaceCommandContext): void | Promise<void> }
@@ -258,6 +282,7 @@ declare module "@getpaseo/plugin" {
addSidebarItem(contribution: PluginSidebarContribution): void;
addWorkspacePanel(contribution: PluginWorkspacePanelContribution): void;
addCommandCenterItem(contribution: PluginCommandCenterItemContribution): void;
addClientSide(contribution: PluginClientContribution): void;
addAttachmentSource(contribution: PluginAttachmentSourceContribution): void;
addTheme(contribution: PluginThemeContribution): void;
addTimelineTransformer<ItemType extends AgentTimelineItem["type"]>(contribution: PluginTimelineTransformerContribution<ItemType>): void;