mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 15:34:39 +00:00
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.
This commit is contained in:
@@ -14,5 +14,16 @@ Each component calls `useComposer()` and uses the same commands in every compose
|
||||
controls inline when the composer has room and as rows in the composer's overflow sheet when it
|
||||
does not, including on a phone.
|
||||
|
||||
[client/controls.tsx](client/controls.tsx) holds the four components; [index.client.tsx](index.client.tsx)
|
||||
registers them with `addComposerItem`.
|
||||
## Reaching a composer from outside it
|
||||
|
||||
Two more contributions use a composer without sitting in one. Both name the agent instead of
|
||||
reading the composer they are mounted in, and neither needs the agent's tab to be open.
|
||||
|
||||
| Contribution | What it does |
|
||||
| ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
||||
| **Composer tools** (Explorer panel) | `useComposers({ focused: true })` inserts the checklist into the focused agent's prompt. |
|
||||
| **Mark for review** (Command Center) | `composers({ agentId })` sets the same `needsReview` metadata as the toggle, for the agent you selected. |
|
||||
|
||||
[client/controls.tsx](client/controls.tsx) holds the four composer components,
|
||||
[client/panel.tsx](client/panel.tsx) the panel, and [index.client.tsx](index.client.tsx) registers
|
||||
everything.
|
||||
|
||||
@@ -10,7 +10,7 @@ const CONTEXT_OPTIONS = [
|
||||
] as const;
|
||||
|
||||
const CONTEXT_KEY = "composer-controls.contextLength";
|
||||
const REVIEW_KEY = "composer-controls.needsReview";
|
||||
export const REVIEW_KEY = "composer-controls.needsReview";
|
||||
|
||||
/** Reads and writes agent metadata, so the choice is saved with the agent. */
|
||||
export function ContextLength() {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { useComposers, type PluginWorkspacePanelProps } from "@getpaseo/plugin/client";
|
||||
import { REVIEW_KEY } from "./controls";
|
||||
|
||||
/**
|
||||
* Lives in the Explorer, away from any composer, and reaches the composer of the agent whose tab
|
||||
* is focused. The agent's tab does not have to be open for the composer to exist: the text lands in
|
||||
* its prompt either way.
|
||||
*/
|
||||
export function ComposerTools({ theme, layout }: PluginWorkspacePanelProps) {
|
||||
const [focused] = useComposers({ focused: true });
|
||||
const styles = useMemo(
|
||||
() => ({
|
||||
screen: {
|
||||
flex: 1,
|
||||
padding: layout.compact ? 16 : 24,
|
||||
gap: 12,
|
||||
backgroundColor: theme.colors.surface0,
|
||||
},
|
||||
label: { color: theme.colors.foregroundMuted },
|
||||
button: {
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
backgroundColor: focused ? theme.colors.accent : theme.colors.surface2,
|
||||
},
|
||||
buttonText: {
|
||||
color: focused ? theme.colors.accentForeground : theme.colors.foregroundMuted,
|
||||
textAlign: "center" as const,
|
||||
},
|
||||
}),
|
||||
[focused, layout.compact, theme],
|
||||
);
|
||||
const insert = useCallback(() => focused?.insertText("- [ ] Tests\n- [ ] Docs\n"), [focused]);
|
||||
return (
|
||||
<View style={styles.screen}>
|
||||
<Text style={styles.label}>
|
||||
{focused ? `Focused agent: ${focused.agent.provider}` : "No agent is focused"}
|
||||
</Text>
|
||||
{focused ? (
|
||||
<Text style={styles.label}>
|
||||
{focused.agent.metadata[REVIEW_KEY] === true ? "Needs review: yes" : "Needs review: no"}
|
||||
</Text>
|
||||
) : null}
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Insert checklist into the focused agent"
|
||||
disabled={!focused}
|
||||
onPress={insert}
|
||||
style={styles.button}
|
||||
>
|
||||
<Text style={styles.buttonText}>Insert checklist into the focused agent</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,30 @@
|
||||
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
||||
import { Checklist, ContextLength, PlanFirst, ReviewFlag } from "./client/controls";
|
||||
import { Checklist, ContextLength, PlanFirst, REVIEW_KEY, ReviewFlag } from "./client/controls";
|
||||
import { ComposerTools } from "./client/panel";
|
||||
|
||||
export default function contribute(client: PluginClientContext) {
|
||||
client.addComposerItem({ id: "context-length", Component: ContextLength });
|
||||
client.addComposerItem({ id: "review-flag", Component: ReviewFlag });
|
||||
client.addComposerItem({ id: "plan-first", Component: PlanFirst });
|
||||
client.addComposerItem({ id: "checklist", Component: Checklist });
|
||||
client.addWorkspacePanel({
|
||||
id: "composer-tools",
|
||||
title: "Composer tools",
|
||||
icon: "ListChecks",
|
||||
locations: ["explorer"],
|
||||
Component: ComposerTools,
|
||||
});
|
||||
client.addCommandCenterItem({
|
||||
id: "mark-for-review",
|
||||
title: "Composer controls: mark for review",
|
||||
icon: "Flag",
|
||||
context: "agent",
|
||||
async onSelect({ agent, composers }) {
|
||||
// The agent's tab does not have to be open.
|
||||
for (const composer of composers({ agentId: agent.id })) {
|
||||
await composer.setMetadata({ [REVIEW_KEY]: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user