Files
paseo/packages/app/src/composer/model.ts
T
Mohamed Boudra e64b0709d2 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-04 14:10:40 +01:00

51 lines
2.2 KiB
TypeScript

import { createContext, useContext } from "react";
import type { JsonValue, ProviderOptions } from "@getpaseo/protocol/agent-types";
import type { UserComposerAttachment } from "@/attachments/types";
/** Everything a plugin can read of a composer. A new object whenever any of it changes. */
export interface ComposerState {
/** `"agent"`: a running agent. `"draft"`: a new agent in a workspace. `"new-workspace"`: the New workspace screen. */
context: "agent" | "draft" | "new-workspace";
prompt: string;
attachments: readonly UserComposerAttachment[];
provider: string | null;
model: string | null;
mode: string | null;
thinking: string | null;
/** The options the agent runs with, defaults included. */
options: ProviderOptions;
metadata: Record<string, JsonValue>;
}
/**
* A composer as plugins use it: its state, a way to hear about changes, and the commands that
* change it. The commands enforce the rules the built-in controls follow (a draft being created
* is locked, a running agent keeps its provider). A running agent's model lives in the stores and
* exists whether or not its tab is open; a draft's is built from its mounted composer.
*/
export interface ComposerModel {
getState(): ComposerState;
subscribe(listener: () => void): () => void;
setPrompt(text: string): void;
/** Inserts at the cursor, replacing the selection. */
insertText(text: string): void;
changeAttachments(updater: (current: UserComposerAttachment[]) => UserComposerAttachment[]): void;
setProvider(provider: string): Promise<void>;
setModel(modelId: string): Promise<void>;
setMode(modeId: string): Promise<void>;
setThinking(thinkingOptionId: string): Promise<void>;
/** Top-level keys replace; `null` removes the agent's own value. */
setOptions(patch: ProviderOptions): Promise<void>;
/** Top-level keys replace; `null` removes the key. */
setMetadata(patch: Record<string, JsonValue>): Promise<void>;
}
const ComposerModelContext = createContext<ComposerModel | null>(null);
export const ComposerModelProvider = ComposerModelContext.Provider;
/** The model of the composer this control sits in. */
export function useComposerModel(): ComposerModel | null {
return useContext(ComposerModelContext);
}