feat(app): restore reload action for agent tabs

This commit is contained in:
Mohamed Boudra
2026-04-05 09:21:45 +07:00
parent 7a2d976081
commit c32bc847bf
7 changed files with 148 additions and 3 deletions
@@ -84,6 +84,7 @@ interface SplitContainerProps {
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTabsToLeft: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void; onCloseTabsToLeft: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
onCloseTabsToRight: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void; onCloseTabsToRight: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
onCloseOtherTabs: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void; onCloseOtherTabs: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
@@ -265,6 +266,7 @@ export function SplitContainer({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCloseOtherTabs, onCloseOtherTabs,
@@ -534,6 +536,7 @@ export function SplitContainer({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onReloadAgent={onReloadAgent}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
onCloseTabsToRight={onCloseTabsToRight} onCloseTabsToRight={onCloseTabsToRight}
onCloseOtherTabs={onCloseOtherTabs} onCloseOtherTabs={onCloseOtherTabs}
@@ -656,6 +659,7 @@ function SplitNodeView({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCloseOtherTabs, onCloseOtherTabs,
@@ -691,6 +695,7 @@ function SplitNodeView({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onReloadAgent={onReloadAgent}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
onCloseTabsToRight={onCloseTabsToRight} onCloseTabsToRight={onCloseTabsToRight}
onCloseOtherTabs={onCloseOtherTabs} onCloseOtherTabs={onCloseOtherTabs}
@@ -741,6 +746,7 @@ function SplitNodeView({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onReloadAgent={onReloadAgent}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
onCloseTabsToRight={onCloseTabsToRight} onCloseTabsToRight={onCloseTabsToRight}
onCloseOtherTabs={onCloseOtherTabs} onCloseOtherTabs={onCloseOtherTabs}
@@ -790,6 +796,7 @@ function SplitPaneView({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCloseOtherTabs, onCloseOtherTabs,
@@ -901,6 +908,7 @@ function SplitPaneView({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onReloadAgent={onReloadAgent}
onCloseTabsToLeft={(tabId) => onCloseTabsToLeft(tabId, paneTabs)} onCloseTabsToLeft={(tabId) => onCloseTabsToLeft(tabId, paneTabs)}
onCloseTabsToRight={(tabId) => onCloseTabsToRight(tabId, paneTabs)} onCloseTabsToRight={(tabId) => onCloseTabsToRight(tabId, paneTabs)}
onCloseOtherTabs={(tabId) => onCloseOtherTabs(tabId, paneTabs)} onCloseOtherTabs={(tabId) => onCloseOtherTabs(tabId, paneTabs)}
@@ -30,6 +30,7 @@ import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { Check, CheckCircle } from "lucide-react-native"; import { Check, CheckCircle } from "lucide-react-native";
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from "@gorhom/bottom-sheet"; import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from "@gorhom/bottom-sheet";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
// Keep parity with dropdown-menu action statuses. // Keep parity with dropdown-menu action statuses.
export type ActionStatus = "idle" | "pending" | "success"; export type ActionStatus = "idle" | "pending" | "success";
@@ -595,6 +596,7 @@ export function ContextMenuItem({
successLabel, successLabel,
closeOnSelect = true, closeOnSelect = true,
testID, testID,
tooltip,
}: PropsWithChildren<{ }: PropsWithChildren<{
description?: string; description?: string;
onSelect?: () => void; onSelect?: () => void;
@@ -612,6 +614,7 @@ export function ContextMenuItem({
successLabel?: string; successLabel?: string;
closeOnSelect?: boolean; closeOnSelect?: boolean;
testID?: string; testID?: string;
tooltip?: string;
}>): ReactElement { }>): ReactElement {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const { setOpen } = useContextMenuContext("ContextMenuItem"); const { setOpen } = useContextMenuContext("ContextMenuItem");
@@ -642,7 +645,7 @@ export function ContextMenuItem({
<Check size={16} color={theme.colors.foregroundMuted} /> <Check size={16} color={theme.colors.foregroundMuted} />
) : null); ) : null);
return ( const content = (
<Pressable <Pressable
testID={testID} testID={testID}
accessibilityRole="button" accessibilityRole="button"
@@ -704,6 +707,19 @@ export function ContextMenuItem({
{trailingContent ? <View style={styles.trailingSlot}>{trailingContent}</View> : null} {trailingContent ? <View style={styles.trailingSlot}>{trailingContent}</View> : null}
</Pressable> </Pressable>
); );
if (!tooltip) {
return content;
}
return (
<Tooltip delayDuration={250} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger asChild>{content}</TooltipTrigger>
<TooltipContent side="right" align="center" offset={10}>
<Text style={styles.tooltipText}>{tooltip}</Text>
</TooltipContent>
</Tooltip>
);
} }
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
@@ -762,6 +778,10 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
}, },
tooltipText: {
fontSize: theme.fontSize.sm,
color: theme.colors.foreground,
},
item: { item: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
@@ -27,6 +27,7 @@ import {
import Animated, { Keyframe, runOnJS } from "react-native-reanimated"; import Animated, { Keyframe, runOnJS } from "react-native-reanimated";
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Check, CheckCircle } from "lucide-react-native"; import { Check, CheckCircle } from "lucide-react-native";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
// Action status for menu items with loading/success feedback // Action status for menu items with loading/success feedback
export type ActionStatus = "idle" | "pending" | "success"; export type ActionStatus = "idle" | "pending" | "success";
@@ -471,6 +472,7 @@ export function DropdownMenuItem({
successLabel, successLabel,
closeOnSelect = true, closeOnSelect = true,
testID, testID,
tooltip,
}: PropsWithChildren<{ }: PropsWithChildren<{
description?: string; description?: string;
onSelect?: () => void; onSelect?: () => void;
@@ -491,6 +493,7 @@ export function DropdownMenuItem({
successLabel?: string; successLabel?: string;
closeOnSelect?: boolean; closeOnSelect?: boolean;
testID?: string; testID?: string;
tooltip?: string;
}>): ReactElement { }>): ReactElement {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const { setOpen } = useDropdownMenuContext("DropdownMenuItem"); const { setOpen } = useDropdownMenuContext("DropdownMenuItem");
@@ -524,7 +527,7 @@ export function DropdownMenuItem({
<Check size={16} color={theme.colors.foregroundMuted} /> <Check size={16} color={theme.colors.foregroundMuted} />
) : null); ) : null);
return ( const content = (
<Pressable <Pressable
testID={testID} testID={testID}
accessibilityRole="button" accessibilityRole="button"
@@ -586,6 +589,19 @@ export function DropdownMenuItem({
{trailingContent ? <View style={styles.trailingSlot}>{trailingContent}</View> : null} {trailingContent ? <View style={styles.trailingSlot}>{trailingContent}</View> : null}
</Pressable> </Pressable>
); );
if (!tooltip) {
return content;
}
return (
<Tooltip delayDuration={250} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger asChild>{content}</TooltipTrigger>
<TooltipContent side="right" align="center" offset={10}>
<Text style={styles.tooltipText}>{tooltip}</Text>
</TooltipContent>
</Tooltip>
);
} }
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
@@ -630,6 +646,10 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
}, },
tooltipText: {
fontSize: theme.fontSize.sm,
color: theme.colors.foreground,
},
item: { item: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
@@ -14,6 +14,7 @@ import {
ArrowRightToLine, ArrowRightToLine,
Columns2, Columns2,
Copy, Copy,
RotateCw,
Rows2, Rows2,
SquarePen, SquarePen,
SquareTerminal, SquareTerminal,
@@ -71,6 +72,7 @@ type WorkspaceDesktopTabsRowProps = {
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void; onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
onCloseTabsToRight: (tabId: string) => Promise<void> | void; onCloseTabsToRight: (tabId: string) => Promise<void> | void;
onCloseOtherTabs: (tabId: string) => Promise<void> | void; onCloseOtherTabs: (tabId: string) => Promise<void> | void;
@@ -291,11 +293,14 @@ function TabChip({
disabled={entry.disabled} disabled={entry.disabled}
destructive={entry.destructive} destructive={entry.destructive}
onSelect={entry.onSelect} onSelect={entry.onSelect}
tooltip={entry.tooltip}
leading={(() => { leading={(() => {
const iconColor = theme.colors.foregroundMuted; const iconColor = theme.colors.foregroundMuted;
switch (entry.icon) { switch (entry.icon) {
case "copy": case "copy":
return <Copy size={16} color={iconColor} />; return <Copy size={16} color={iconColor} />;
case "rotate-cw":
return <RotateCw size={16} color={iconColor} />;
case "arrow-left-to-line": case "arrow-left-to-line":
return <ArrowLeftToLine size={16} color={iconColor} />; return <ArrowLeftToLine size={16} color={iconColor} />;
case "arrow-right-to-line": case "arrow-right-to-line":
@@ -335,6 +340,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCloseOtherTabs, onCloseOtherTabs,
@@ -457,6 +463,7 @@ export function WorkspaceDesktopTabsRow({
normalizedWorkspaceId={normalizedWorkspaceId} normalizedWorkspaceId={normalizedWorkspaceId}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onReloadAgent={onReloadAgent}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
onCloseTabsToRight={onCloseTabsToRight} onCloseTabsToRight={onCloseTabsToRight}
onCloseOtherTabs={onCloseOtherTabs} onCloseOtherTabs={onCloseOtherTabs}
@@ -585,6 +592,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId, normalizedWorkspaceId,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCloseOtherTabs, onCloseOtherTabs,
@@ -608,6 +616,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId: string; normalizedWorkspaceId: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void; onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
onCloseTabsToRight: (tabId: string) => Promise<void> | void; onCloseTabsToRight: (tabId: string) => Promise<void> | void;
onCloseOtherTabs: (tabId: string) => Promise<void> | void; onCloseOtherTabs: (tabId: string) => Promise<void> | void;
@@ -630,6 +639,7 @@ function ResolvedDesktopTabChip({
tabCount, tabCount,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTab, onCloseTab,
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
@@ -644,6 +654,7 @@ function ResolvedDesktopTabChip({
onCloseTabsToRight, onCloseTabsToRight,
onCopyAgentId, onCopyAgentId,
onCopyResumeCommand, onCopyResumeCommand,
onReloadAgent,
tabCount, tabCount,
], ],
); );
@@ -21,6 +21,7 @@ import {
Ellipsis, Ellipsis,
EllipsisVertical, EllipsisVertical,
PanelRight, PanelRight,
RotateCw,
SquarePen, SquarePen,
SquareTerminal, SquareTerminal,
X, X,
@@ -182,6 +183,7 @@ type MobileWorkspaceTabSwitcherProps = {
onSelectSwitcherTab: (key: string) => void; onSelectSwitcherTab: (key: string) => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCloseTabsAbove: (tabId: string) => Promise<void> | void; onCloseTabsAbove: (tabId: string) => Promise<void> | void;
onCloseTabsBelow: (tabId: string) => Promise<void> | void; onCloseTabsBelow: (tabId: string) => Promise<void> | void;
@@ -269,6 +271,7 @@ function MobileWorkspaceTabOption({
onPress, onPress,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTab, onCloseTab,
onCloseTabsAbove, onCloseTabsAbove,
onCloseTabsBelow, onCloseTabsBelow,
@@ -284,6 +287,7 @@ function MobileWorkspaceTabOption({
onPress: () => void; onPress: () => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCloseTabsAbove: (tabId: string) => Promise<void> | void; onCloseTabsAbove: (tabId: string) => Promise<void> | void;
onCloseTabsBelow: (tabId: string) => Promise<void> | void; onCloseTabsBelow: (tabId: string) => Promise<void> | void;
@@ -299,6 +303,7 @@ function MobileWorkspaceTabOption({
menuTestIDBase, menuTestIDBase,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTab, onCloseTab,
onCloseTabsBefore: onCloseTabsAbove, onCloseTabsBefore: onCloseTabsAbove,
onCloseTabsAfter: onCloseTabsBelow, onCloseTabsAfter: onCloseTabsBelow,
@@ -342,11 +347,14 @@ function MobileWorkspaceTabOption({
disabled={entry.disabled} disabled={entry.disabled}
destructive={entry.destructive} destructive={entry.destructive}
onSelect={entry.onSelect} onSelect={entry.onSelect}
tooltip={entry.tooltip}
leading={(() => { leading={(() => {
const iconColor = theme.colors.foregroundMuted; const iconColor = theme.colors.foregroundMuted;
switch (entry.icon) { switch (entry.icon) {
case "copy": case "copy":
return <Copy size={16} color={iconColor} />; return <Copy size={16} color={iconColor} />;
case "rotate-cw":
return <RotateCw size={16} color={iconColor} />;
case "arrow-left-to-line": case "arrow-left-to-line":
return <ArrowLeftToLine size={16} color={iconColor} />; return <ArrowLeftToLine size={16} color={iconColor} />;
case "arrow-right-to-line": case "arrow-right-to-line":
@@ -389,6 +397,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onSelectSwitcherTab, onSelectSwitcherTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTab, onCloseTab,
onCloseTabsAbove, onCloseTabsAbove,
onCloseTabsBelow, onCloseTabsBelow,
@@ -460,6 +469,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onPress={onPress} onPress={onPress}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onReloadAgent={onReloadAgent}
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCloseTabsAbove={onCloseTabsAbove} onCloseTabsAbove={onCloseTabsAbove}
onCloseTabsBelow={onCloseTabsBelow} onCloseTabsBelow={onCloseTabsBelow}
@@ -1366,6 +1376,23 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
[normalizedServerId, toast], [normalizedServerId, toast],
); );
const handleReloadAgent = useCallback(
async (agentId: string) => {
if (!client || !isConnected) {
toast.error("Host is not connected");
return;
}
try {
await client.refreshAgent(agentId);
toast.show("Reloaded agent", { variant: "success" });
} catch (error) {
toast.error(error instanceof Error ? error.message : "Failed to reload agent");
}
},
[client, isConnected, toast],
);
const handleCopyWorkspacePath = useCallback(async () => { const handleCopyWorkspacePath = useCallback(async () => {
if (!isAbsolutePath(normalizedWorkspaceId)) { if (!isAbsolutePath(normalizedWorkspaceId)) {
toast.error("Workspace path not available"); toast.error("Workspace path not available");
@@ -2211,6 +2238,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
onSelectSwitcherTab={handleSelectSwitcherTab} onSelectSwitcherTab={handleSelectSwitcherTab}
onCopyResumeCommand={handleCopyResumeCommand} onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId} onCopyAgentId={handleCopyAgentId}
onReloadAgent={handleReloadAgent}
onCloseTab={handleCloseTabById} onCloseTab={handleCloseTabById}
onCloseTabsAbove={handleCloseTabsToLeft} onCloseTabsAbove={handleCloseTabsToLeft}
onCloseTabsBelow={handleCloseTabsToRight} onCloseTabsBelow={handleCloseTabsToRight}
@@ -2231,6 +2259,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
onCloseTab={handleCloseTabById} onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand} onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId} onCopyAgentId={handleCopyAgentId}
onReloadAgent={handleReloadAgent}
onCloseTabsToLeft={handleCloseTabsToLeft} onCloseTabsToLeft={handleCloseTabsToLeft}
onCloseTabsToRight={handleCloseTabsToRight} onCloseTabsToRight={handleCloseTabsToRight}
onCloseOtherTabs={handleCloseOtherTabs} onCloseOtherTabs={handleCloseOtherTabs}
@@ -2267,6 +2296,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
onCloseTab={handleCloseTabById} onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand} onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId} onCopyAgentId={handleCopyAgentId}
onReloadAgent={handleReloadAgent}
onCloseTabsToLeft={handleCloseTabsToLeftInPane} onCloseTabsToLeft={handleCloseTabsToLeftInPane}
onCloseTabsToRight={handleCloseTabsToRightInPane} onCloseTabsToRight={handleCloseTabsToRightInPane}
onCloseOtherTabs={handleCloseOtherTabsInPane} onCloseOtherTabs={handleCloseOtherTabsInPane}
@@ -15,6 +15,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
it("uses desktop tab ordering labels for desktop menus", () => { it("uses desktop tab ordering labels for desktop menus", () => {
const onCopyResumeCommand = vi.fn(); const onCopyResumeCommand = vi.fn();
const onCopyAgentId = vi.fn(); const onCopyAgentId = vi.fn();
const onReloadAgent = vi.fn();
const onCloseTab = vi.fn(); const onCloseTab = vi.fn();
const onCloseTabsBefore = vi.fn(); const onCloseTabsBefore = vi.fn();
const onCloseTabsAfter = vi.fn(); const onCloseTabsAfter = vi.fn();
@@ -28,6 +29,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123", menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTab, onCloseTab,
onCloseTabsBefore, onCloseTabsBefore,
onCloseTabsAfter, onCloseTabsAfter,
@@ -37,6 +39,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(entries.filter((entry) => entry.kind === "item").map((entry) => entry.label)).toEqual([ expect(entries.filter((entry) => entry.kind === "item").map((entry) => entry.label)).toEqual([
"Copy resume command", "Copy resume command",
"Copy agent id", "Copy agent id",
"Reload agent",
"Close to the left", "Close to the left",
"Close to the right", "Close to the right",
"Close other tabs", "Close other tabs",
@@ -53,6 +56,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-agent_123", menuTestIDBase: "workspace-tab-menu-agent_123",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onReloadAgent: vi.fn(),
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
onCloseTabsBefore: vi.fn(), onCloseTabsBefore: vi.fn(),
onCloseTabsAfter: vi.fn(), onCloseTabsAfter: vi.fn(),
@@ -62,6 +66,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(entries.filter((entry) => entry.kind === "item").map((entry) => entry.label)).toEqual([ expect(entries.filter((entry) => entry.kind === "item").map((entry) => entry.label)).toEqual([
"Copy resume command", "Copy resume command",
"Copy agent id", "Copy agent id",
"Reload agent",
"Close tabs above", "Close tabs above",
"Close tabs below", "Close tabs below",
"Close other tabs", "Close other tabs",
@@ -83,6 +88,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-draft_123", menuTestIDBase: "workspace-tab-menu-draft_123",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onReloadAgent: vi.fn(),
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
onCloseTabsBefore: vi.fn(), onCloseTabsBefore: vi.fn(),
onCloseTabsAfter: vi.fn(), onCloseTabsAfter: vi.fn(),
@@ -92,6 +98,34 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(entries.some((entry) => entry.kind === "item" && entry.label === "Copy agent id")).toBe( expect(entries.some((entry) => entry.kind === "item" && entry.label === "Copy agent id")).toBe(
false, false,
); );
expect(entries.some((entry) => entry.kind === "item" && entry.label === "Reload agent")).toBe(
false,
);
expect(entries.some((entry) => entry.kind === "separator")).toBe(false); expect(entries.some((entry) => entry.kind === "separator")).toBe(false);
}); });
it("adds reload tooltip copy for agent tabs", () => {
const entries = buildWorkspaceTabMenuEntries({
surface: "desktop",
tab: createAgentTab(),
index: 0,
tabCount: 1,
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onReloadAgent: vi.fn(),
onCloseTab: vi.fn(),
onCloseTabsBefore: vi.fn(),
onCloseTabsAfter: vi.fn(),
onCloseOtherTabs: vi.fn(),
});
expect(entries).toContainEqual(
expect.objectContaining({
kind: "item",
key: "reload-agent",
tooltip: "Reload agent to update skills, MCPs or login status.",
}),
);
});
}); });
@@ -8,8 +8,15 @@ export type WorkspaceTabMenuEntry =
kind: "item"; kind: "item";
key: string; key: string;
label: string; label: string;
icon?: "copy" | "arrow-left-to-line" | "arrow-right-to-line" | "copy-x" | "x"; icon?:
| "copy"
| "rotate-cw"
| "arrow-left-to-line"
| "arrow-right-to-line"
| "copy-x"
| "x";
hint?: string; hint?: string;
tooltip?: string;
disabled?: boolean; disabled?: boolean;
destructive?: boolean; destructive?: boolean;
testID: string; testID: string;
@@ -28,6 +35,7 @@ interface BuildWorkspaceTabMenuEntriesInput {
menuTestIDBase: string; menuTestIDBase: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCloseTabsBefore: (tabId: string) => Promise<void> | void; onCloseTabsBefore: (tabId: string) => Promise<void> | void;
onCloseTabsAfter: (tabId: string) => Promise<void> | void; onCloseTabsAfter: (tabId: string) => Promise<void> | void;
@@ -40,6 +48,7 @@ interface BuildWorkspaceDesktopTabActionsInput {
tabCount: number; tabCount: number;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void; onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
onCloseTabsToRight: (tabId: string) => Promise<void> | void; onCloseTabsToRight: (tabId: string) => Promise<void> | void;
@@ -92,6 +101,7 @@ export function buildWorkspaceTabMenuEntries(
menuTestIDBase, menuTestIDBase,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onReloadAgent,
onCloseTab, onCloseTab,
onCloseTabsBefore, onCloseTabsBefore,
onCloseTabsAfter, onCloseTabsAfter,
@@ -125,6 +135,17 @@ export function buildWorkspaceTabMenuEntries(
void onCopyAgentId(agentId); void onCopyAgentId(agentId);
}, },
}); });
entries.push({
kind: "item",
key: "reload-agent",
label: "Reload agent",
icon: "rotate-cw",
tooltip: "Reload agent to update skills, MCPs or login status.",
testID: `${menuTestIDBase}-reload-agent`,
onSelect: () => {
void onReloadAgent(agentId);
},
});
entries.push({ entries.push({
kind: "separator", kind: "separator",
key: "copy-separator", key: "copy-separator",
@@ -192,6 +213,7 @@ export function buildWorkspaceDesktopTabActions(
menuTestIDBase: contextMenuTestId, menuTestIDBase: contextMenuTestId,
onCopyResumeCommand: input.onCopyResumeCommand, onCopyResumeCommand: input.onCopyResumeCommand,
onCopyAgentId: input.onCopyAgentId, onCopyAgentId: input.onCopyAgentId,
onReloadAgent: input.onReloadAgent,
onCloseTab: input.onCloseTab, onCloseTab: input.onCloseTab,
onCloseTabsBefore: input.onCloseTabsToLeft, onCloseTabsBefore: input.onCloseTabsToLeft,
onCloseTabsAfter: input.onCloseTabsToRight, onCloseTabsAfter: input.onCloseTabsToRight,