mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 00:14:54 +00:00
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.
89 lines
3.4 KiB
TypeScript
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;
|
|
},
|
|
};
|
|
}
|