Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions src/browser/components/AgentModePicker/AgentModePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Bot, ChevronDown, Route, SquareCode } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { ChevronDown } from "lucide-react";

import { useAgent } from "@/browser/contexts/AgentContext";
import { getAgentIcon } from "@/browser/utils/agentIcons";
import { CUSTOM_EVENTS } from "@/common/constants/events";
import type { AgentDefinitionDescriptor } from "@/common/types/agentDefinition";
import { normalizeAgentId as normalizeStoredAgentId } from "@/common/utils/agentIds";
Expand Down Expand Up @@ -43,17 +43,6 @@ interface AgentOption {
subagentRunnable: boolean;
}

/** Maps well-known agent IDs to lucide icons for the dropdown */
const AGENT_ICONS: Record<string, LucideIcon> = {
plan: Route,
exec: SquareCode,
};
const DEFAULT_AGENT_ICON: LucideIcon = Bot;

function getAgentIcon(agentId: string): LucideIcon {
return AGENT_ICONS[agentId] ?? DEFAULT_AGENT_ICON;
}

export function formatAgentIdLabel(agentId: string): string {
if (!agentId) {
return "Agent";
Expand Down
2 changes: 2 additions & 0 deletions src/browser/components/InstructionsTab/InstructionsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useAPI } from "@/browser/contexts/API";
import { ErrorBoundary } from "@/browser/components/ErrorBoundary/ErrorBoundary";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/browser/components/Tooltip/Tooltip";
import { ChatInstructionsPanel } from "./AdditionalSystemContextScratchpad";
import { ModeInstructionsPanel } from "./ModeInstructionsPanel";
import { isAbortError } from "@/browser/utils/isAbortError";
import { setWorkspaceInstructionsFileCount } from "@/browser/utils/workspaceInstructionsStore";
import { cn } from "@/common/lib/utils";
Expand Down Expand Up @@ -76,6 +77,7 @@ function InstructionsTabImpl(props: InstructionsTabProps) {
onRefresh={refresh}
/>
<div className="min-h-0 flex-1 overflow-y-auto">
<ModeInstructionsPanel workspaceId={props.workspaceId} />
<ChatInstructionsPanel workspaceId={props.workspaceId} />
{error && <ErrorBanner message={error} />}
{!error && !loading && data?.files.length === 0 && <EmptyState />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { Meta, StoryObj } from "@storybook/react-vite";

import { TooltipProvider } from "@/browser/components/Tooltip/Tooltip";
import { APIProvider } from "@/browser/contexts/API";
import { AgentProvider, type AgentContextValue } from "@/browser/contexts/AgentContext";
import { createMockORPCClient } from "@/browser/stories/mocks/orpc";
import type { AgentDefinitionDescriptor } from "@/common/types/agentDefinition";

import { ModeInstructionsPanel } from "./ModeInstructionsPanel";

const WORKSPACE_ID = "ws-mode-instructions";

const EXEC_AGENT: AgentDefinitionDescriptor = {
id: "exec",
scope: "built-in",
name: "Exec",
description: "Implement changes in the repository",
uiSelectable: true,
uiRoutable: true,
subagentRunnable: true,
uiColor: "var(--color-exec-mode)",
};

const PLAN_AGENT: AgentDefinitionDescriptor = {
id: "plan",
scope: "built-in",
name: "Plan",
description: "Create a plan before coding β€” research, propose, then hand off.",
uiSelectable: true,
uiRoutable: true,
subagentRunnable: false,
base: "plan",
uiColor: "var(--color-plan-mode)",
};

const EXEC_BODY = `# Exec mode

You are in **Exec mode**. Make the requested change with minimal, reviewable
edits and verify your work.

## Standing orders

- Read before you write: confirm paths, symbols, and call-sites first.
- Prefer narrow, targeted fixes over large rewrites.
- Run typecheck and the most-relevant tests after every meaningful change.
- Never claim success until validation actually passes.

## Tools available

- File editing (replace_string, insert)
- Bash (typecheck, lint, tests, git)
- Sub-agents (\`explore\` for read-only investigation)
`;

const PLAN_BODY = `# Plan mode

You are in **Plan mode**. Your job is to think and design β€” _not_ to write
production code.

## What "done" looks like

1. A clearly-scoped problem statement.
2. A proposed implementation plan, broken into concrete steps.
3. Identified risks and the most important review surface.

> When the plan is accepted, hand off to \`exec\` for implementation.
`;

function buildContext(
agent: AgentDefinitionDescriptor,
overrides?: Partial<AgentContextValue>
): AgentContextValue {
return {
agentId: agent.id,
setAgentId: () => undefined,
currentAgent: agent,
agents: [agent],
loaded: true,
loadFailed: false,
refresh: () => Promise.resolve(),
refreshing: false,
disableWorkspaceAgents: false,
setDisableWorkspaceAgents: () => undefined,
...overrides,
};
}

function withMockProviders(context: AgentContextValue, bodies: Record<string, string>) {
return function Decorator(Story: React.ComponentType) {
return (
<APIProvider
client={createMockORPCClient({
agentDefinitions: [EXEC_AGENT, PLAN_AGENT],
agentBodies: bodies,
})}
>
<AgentProvider value={context}>
<TooltipProvider>
<div className="bg-background mx-auto max-w-md p-3">
<Story />
</div>
</TooltipProvider>
</AgentProvider>
</APIProvider>
);
};
}

const meta: Meta<typeof ModeInstructionsPanel> = {
title: "App/Right Sidebar/Instructions/ModeInstructionsPanel",
component: ModeInstructionsPanel,
parameters: { layout: "fullscreen" },
};

export default meta;
type Story = StoryObj<typeof meta>;

// Snapshot budget is tight (see tests/ui/storybook/budget.test.ts), so we
// cover the two distinct mode colors (exec=purple, plan=blue) β€” those are
// the visuals most worth protecting against regressions. Custom-scope and
// empty-body variants are exercised indirectly via the implementation tests.
export const ExecMode: Story = {
args: { workspaceId: WORKSPACE_ID },
decorators: [withMockProviders(buildContext(EXEC_AGENT), { exec: EXEC_BODY })],
};

export const PlanMode: Story = {
args: { workspaceId: WORKSPACE_ID },
decorators: [withMockProviders(buildContext(PLAN_AGENT), { plan: PLAN_BODY })],
};
Loading
Loading