Files
paseo/plugin-examples/composer-controls/client/controls.tsx
T
Mohamed Boudra 0aeb1ae1f9 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 08:10:28 +01:00

78 lines
2.4 KiB
TypeScript

import { useCallback } from "react";
import { useComposer } from "@getpaseo/plugin/client";
import { Action, Select, Toggle } from "@getpaseo/plugin/client/ui";
type ContextLength = "short" | "long";
const CONTEXT_OPTIONS = [
{ value: "short", label: "Short", description: "Answer from the files you name." },
{ value: "long", label: "Long", description: "Read the surrounding code first." },
] as const;
const CONTEXT_KEY = "composer-controls.contextLength";
export const REVIEW_KEY = "composer-controls.needsReview";
/** Reads and writes agent metadata, so the choice is saved with the agent. */
export function ContextLength() {
const composer = useComposer();
const current: ContextLength = composer.agent.metadata[CONTEXT_KEY] === "long" ? "long" : "short";
const change = useCallback(
({ value }: { value: ContextLength }) => composer.setMetadata({ [CONTEXT_KEY]: value }),
[composer],
);
return (
<Select
title="Context length"
icon="Gauge"
value={current}
options={CONTEXT_OPTIONS}
onValueChange={change}
/>
);
}
export function ReviewFlag() {
const composer = useComposer();
const change = useCallback(
({ value }: { value: boolean }) => composer.setMetadata({ [REVIEW_KEY]: value ? true : null }),
[composer],
);
return (
<Toggle
title="Needs review"
icon="Flag"
value={composer.agent.metadata[REVIEW_KEY] === true}
onValueChange={change}
/>
);
}
/**
* Changes a provider option. Only Codex reports `plan_mode`, so the control is disabled for
* other providers. The promise it returns keeps the control pending, and a rejection reaches the
* user as a toast.
*/
export function PlanFirst() {
const composer = useComposer();
const change = useCallback(
({ value }: { value: boolean }) => composer.setOptions({ plan_mode: value }),
[composer],
);
return (
<Toggle
title="Plan first"
icon="ListTodo"
disabled={composer.agent.provider !== "codex"}
value={composer.agent.options.plan_mode === true}
onValueChange={change}
/>
);
}
/** Works the same in the agent, draft and New workspace composers. */
export function Checklist() {
const composer = useComposer();
const insert = useCallback(() => composer.insertText("- [ ] Tests\n- [ ] Docs\n"), [composer]);
return <Action title="Insert checklist" icon="ListChecks" onPress={insert} />;
}