Files
paseo/packages/app/src/plugins/composer-items/source.ts
T
Mohamed Boudra f665cc697c 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.
2026-11-05 23:25:42 +01:00

89 lines
3.4 KiB
TypeScript

import type { PluginComposer, PluginComposerSelector } from "@getpaseo/plugin/client";
import type { PluginComposersSource } from "@getpaseo/plugin/client/host";
import { agentComposer } from "@/composer/agent-composer";
import type { ComposerState } from "@/composer/model";
import { queryClient } from "@/data/query-client";
import { useDraftStore } from "@/stores/draft-store";
import {
getLastWorkspaceSelection,
subscribeLastWorkspaceSelection,
} from "@/stores/navigation-active-workspace-store";
import { useSessionStore } from "@/stores/session-store";
import { useWorkspaceLayoutStore } from "@/stores/workspace-layout-store";
import { getFocusedAgentId } from "../command-center/context";
import type { InstalledPlugin } from "../types";
import { toPluginComposer } from "./model";
const NO_COMPOSERS: readonly PluginComposer[] = Object.freeze([]);
const sources = new WeakMap<InstalledPlugin, PluginComposersSource>();
/**
* The composers a plugin reaches by reference: one per running agent, open in a tab or not. The
* same source serves `useComposers` in every surface and `composers()` in every command.
*/
export function pluginComposersSource(plugin: InstalledPlugin): PluginComposersSource {
let source = sources.get(plugin);
if (!source) {
source = createSource(plugin);
sources.set(plugin, source);
}
return source;
}
function createSource(plugin: InstalledPlugin): PluginComposersSource {
const { serverId } = plugin;
// Handles and result arrays are reused while nothing they read has changed, so a subscriber
// that re-reads on every notification renders only when its composers did change.
const handles = new WeakMap<ComposerState, PluginComposer>();
const results = new WeakMap<PluginComposer, readonly PluginComposer[]>();
function runningAgentId(agentId: string): string | null {
const session = useSessionStore.getState().sessions[serverId];
const agent = session?.agents.get(agentId) ?? session?.agentDetails.get(agentId) ?? null;
return agent && !agent.archivedAt ? agent.id : null;
}
function focusedAgentId(): string | null {
const selection = getLastWorkspaceSelection();
if (selection?.serverId !== serverId) return null;
const layout =
useWorkspaceLayoutStore.getState().layoutByWorkspace[
`${selection.serverId}:${selection.workspaceId}`
] ?? null;
return getFocusedAgentId(layout);
}
return {
subscribe(listener) {
const stops = [
useSessionStore.subscribe(listener),
useDraftStore.subscribe(listener),
useWorkspaceLayoutStore.subscribe(listener),
subscribeLastWorkspaceSelection(listener),
queryClient.getQueryCache().subscribe(listener),
];
return () => {
for (const stop of stops) stop();
};
},
resolve(selector: PluginComposerSelector) {
const requested = "agentId" in selector ? selector.agentId : focusedAgentId();
const agentId = requested === null ? null : runningAgentId(requested);
if (agentId === null) return NO_COMPOSERS;
const model = agentComposer(serverId, agentId);
const state = model.getState();
let composer = handles.get(state);
if (!composer) {
composer = toPluginComposer({ plugin, model, state });
handles.set(state, composer);
}
let result = results.get(composer);
if (!result) {
result = Object.freeze([composer]);
results.set(composer, result);
}
return result;
},
};
}