Files
paseo/plugin-examples/sidebar-views/client/rows.tsx
T
Mohamed Boudra a886bfbcdd Give plugins an agent's background work count and a background flag on hooks (#9362)
* feat(plugins): give plugins the agent's sidebar indicator and background flag

Plugin UI re-derived agent state from status and attention fields and could
not see background work, so it showed agents as done while a shell they left
running was still live. Agent snapshots now carry the sidebar's own bucket as
indicator/indicatorLabel, computed by the same function the sidebar uses so
the two cannot drift.

Lifecycle hooks and the agent.open.before request now carry `background`, so a
hook can skip a hidden helper agent without a lookup. A hook cannot change it;
an omitted value means unchanged, like metadata.

`finished` attention still means the turn finished; background work is the
indicator's concern.

* fix(plugins): keep session-open hooks that omit background typechecking

A hook that returns its own object instead of spreading the request has no
background field; the result type now treats it as optional, like metadata,
matching the runtime where an omitted value means unchanged.

* refactor(plugins): give agent snapshots the background work count instead of an indicator

* fix(plugins): keep legacy session-open hooks that omit background typechecking
2026-11-11 15:10:13 +07:00

150 lines
3.7 KiB
TypeScript

import { useCallback, useMemo } from "react";
import { Pressable, Text, View } from "react-native";
import {
useWorkspace,
useAgent,
useProject,
type WorkspaceRef,
type AgentRef,
type ProjectRef,
type PluginSidebarViewProps,
} from "@getpaseo/plugin/client";
export function Heading({ title, view }: { title: string; view: PluginSidebarViewProps }) {
const style = useMemo(
() => ({
color: view.theme.colors.foregroundMuted,
fontSize: 11,
paddingHorizontal: 12,
paddingTop: 12,
paddingBottom: 4,
}),
[view.theme],
);
return (
<Text accessibilityRole="header" style={style}>
{title}
</Text>
);
}
export function WorkspaceRow({
reference,
view,
onLongPress,
}: {
reference: WorkspaceRef;
view: PluginSidebarViewProps;
onLongPress?: () => void;
}) {
const workspace = useWorkspace(reference);
const open = useCallback(
() => view.navigation.openWorkspace(reference),
[view.navigation, reference],
);
const style = useMemo(
() => ({ color: view.theme.colors.foreground, fontSize: 12, lineHeight: 16 }),
[view.theme],
);
const detail = useMemo(
() => ({ color: view.theme.colors.foregroundMuted, fontSize: 11 }),
[view.theme],
);
const background = useMemo(
() => ({
...rowStyle,
backgroundColor:
view.active.workspace?.key === reference.key ? view.theme.colors.surface2 : undefined,
}),
[view.active.workspace, view.theme, reference],
);
if (!workspace) return null;
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={`Open ${workspace.title ?? workspace.name}`}
onPress={open}
onLongPress={onLongPress}
style={background}
>
<Text numberOfLines={1} style={style}>
{workspace.title ?? workspace.name}
</Text>
<Text numberOfLines={1} style={detail}>
{workspace.indicatorLabel}
</Text>
</Pressable>
);
}
export function AgentRow({
reference,
view,
}: {
reference: AgentRef;
view: PluginSidebarViewProps;
}) {
const agent = useAgent(reference);
const open = useCallback(
() => view.navigation.openAgent(reference),
[view.navigation, reference],
);
const style = useMemo(
() => ({ color: view.theme.colors.foreground, fontSize: 12 }),
[view.theme],
);
const detail = useMemo(
() => ({ color: view.theme.colors.foregroundMuted, fontSize: 11 }),
[view.theme],
);
if (!agent) return null;
const tasks = agent.backgroundWork.count;
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={`Open agent ${agent.title ?? agent.provider}`}
onPress={open}
style={rowStyle}
>
<Text numberOfLines={1} style={style}>
{agent.title ?? agent.provider}
</Text>
{tasks > 0 ? (
<Text numberOfLines={1} style={detail}>
{tasks === 1 ? "1 background task" : `${tasks} background tasks`}
</Text>
) : null}
</Pressable>
);
}
export function ProjectHeading({
reference,
view,
}: {
reference: ProjectRef;
view: PluginSidebarViewProps;
}) {
const project = useProject(reference);
const open = useCallback(
() => view.navigation.openProject(reference),
[view.navigation, reference],
);
if (!project) return null;
return (
<View>
<Heading title={project.displayName} view={view} />
<Pressable
accessibilityRole="button"
accessibilityLabel={`New workspace in ${project.displayName}`}
onPress={open}
>
<Heading title="+ New workspace" view={view} />
</Pressable>
</View>
);
}
const rowStyle = {
minHeight: 28,
paddingHorizontal: 12,
paddingVertical: 4,
justifyContent: "center",
} as const;