diff --git a/site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx b/site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx index b033162163..80b58f30c9 100644 --- a/site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx +++ b/site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx @@ -653,6 +653,39 @@ export const PlanningIndicator: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByText("Planning")).toBeVisible(); + expect( + canvas.getByRole("button", { name: "Disable plan mode" }), + ).toBeVisible(); + }, +}; + +export const DisablePlanModeFromBadge: Story = { + args: { + planModeEnabled: true, + onPlanModeToggle: fn(), + }, + play: async ({ args, canvasElement }) => { + const canvas = within(canvasElement); + const dismiss = canvas.getByRole("button", { + name: "Disable plan mode", + }); + await userEvent.click(dismiss); + expect(args.onPlanModeToggle).toHaveBeenCalledTimes(1); + expect(args.onPlanModeToggle).toHaveBeenCalledWith(false); + }, +}; + +export const PlanningIndicatorWithoutToggle: Story = { + args: { + planModeEnabled: true, + onPlanModeToggle: undefined, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(canvas.getByText("Planning")).toBeVisible(); + expect( + canvas.queryByRole("button", { name: "Disable plan mode" }), + ).not.toBeInTheDocument(); }, }; diff --git a/site/src/pages/AgentsPage/components/AgentChatInput.tsx b/site/src/pages/AgentsPage/components/AgentChatInput.tsx index 3d18cb923f..4694aa21d7 100644 --- a/site/src/pages/AgentsPage/components/AgentChatInput.tsx +++ b/site/src/pages/AgentsPage/components/AgentChatInput.tsx @@ -171,6 +171,26 @@ type ToolBadgeData = | ({ kind: "attached-workspace" } & AttachedWorkspaceInfo) | { kind: "mcp"; server: TypesGen.MCPServerConfig }; +// Small `X` button rendered inside pill-style badges (attached +// workspace, MCP server, planning indicator) to dismiss or disable +// the badge without opening the `+` menu. Callers pass the action +// handler and a descriptive aria-label. +const BadgeDismissButton: FC<{ + onClick: () => void; + ariaLabel: string; + isDisabled?: boolean; +}> = ({ onClick, ariaLabel, isDisabled = false }) => ( + +); + const ToolBadge: FC<{ badge: ToolBadgeData; onRemoveWorkspace?: () => void; @@ -210,14 +230,10 @@ const ToolBadge: FC<{ {badge.name} {onRemoveWorkspace && ( - + ariaLabel={`Remove workspace ${badge.name}`} + /> )} ); @@ -237,14 +253,10 @@ const ToolBadge: FC<{ )} {badge.server.display_name} {!isForceOn && onRemoveMcp && ( - + ariaLabel={`Remove ${badge.server.display_name}`} + /> )} ); @@ -445,6 +457,8 @@ export const AgentChatInput: FC = ({ setPlusMenuOpen(false); }; + const handleDisablePlanMode = () => onPlanModeToggle?.(false); + const fileInputRef = useRef(null); const [composerElement, setComposerElement] = useState( null, @@ -1065,6 +1079,13 @@ export const AgentChatInput: FC = ({ Planning + {onPlanModeToggle && ( + + )} )}{" "} {/* Badge row — all badges and the pill always