Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
analyticsOptOutAtom,
autoAdvanceTargetAtom,
ctrlTabTargetAtom,
defaultAgentModeAtom,
desktopNotificationsEnabledAtom,
extendedThinkingEnabledAtom,
soundNotificationsEnabledAtom,
type AgentMode,
type AutoAdvanceTarget,
type CtrlTabTarget,
} from "../../../lib/atoms"
Expand Down Expand Up @@ -46,6 +48,7 @@ export function AgentsPreferencesTab() {
const [analyticsOptOut, setAnalyticsOptOut] = useAtom(analyticsOptOutAtom)
const [ctrlTabTarget, setCtrlTabTarget] = useAtom(ctrlTabTargetAtom)
const [autoAdvanceTarget, setAutoAdvanceTarget] = useAtom(autoAdvanceTargetAtom)
const [defaultAgentMode, setDefaultAgentMode] = useAtom(defaultAgentModeAtom)
const isNarrowScreen = useIsNarrowScreen()

// Co-authored-by setting from Claude settings.json
Expand Down Expand Up @@ -205,6 +208,32 @@ export function AgentsPreferencesTab() {
</SelectContent>
</Select>
</div>

{/* Default Mode */}
<div className="flex items-start justify-between">
<div className="flex flex-col space-y-1">
<span className="text-sm font-medium text-foreground">
Default Mode
</span>
<span className="text-xs text-muted-foreground">
Mode for new agents (Plan = read-only, Agent = can edit)
</span>
</div>
<Select
value={defaultAgentMode}
onValueChange={(value: AgentMode) => setDefaultAgentMode(value)}
>
<SelectTrigger className="w-auto px-2">
<span className="text-xs">
{defaultAgentMode === "agent" ? "Agent" : "Plan"}
</span>
</SelectTrigger>
<SelectContent>
<SelectItem value="agent">Agent</SelectItem>
<SelectItem value="plan">Plan</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>

Expand Down
30 changes: 27 additions & 3 deletions src/renderer/features/agents/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ import { atom } from "jotai"
import { atomFamily, atomWithStorage } from "jotai/utils"
import { atomWithWindowStorage } from "../../../lib/window-storage"

// Agent mode type - extensible for future modes like "debug"
export type AgentMode = "agent" | "plan"

// Ordered list of modes - Shift+Tab cycles through these
export const AGENT_MODES: AgentMode[] = ["agent", "plan"]

// Get next mode in cycle (for Shift+Tab toggle)
export function getNextMode(current: AgentMode): AgentMode {
const idx = AGENT_MODES.indexOf(current)
return AGENT_MODES[(idx + 1) % AGENT_MODES.length]
}

// Selected agent chat ID - null means "new chat" view (persisted to restore on reload)
// Uses window-scoped storage so each Electron window can have its own selected chat
export const selectedAgentChatIdAtom = atomWithWindowStorage<string | null>(
Expand Down Expand Up @@ -189,13 +201,25 @@ export const lastSelectedModelIdAtom = atomWithStorage<string>(
{ getOnInit: true },
)

export const isPlanModeAtom = atomWithStorage<boolean>(
"agents:isPlanMode",
false,
// Storage for all sub-chat modes (persisted per subChatId)
const subChatModesStorageAtom = atomWithStorage<Record<string, AgentMode>>(
"agents:subChatModes",
{},
undefined,
{ getOnInit: true },
)

// atomFamily to get/set mode per subChatId
export const subChatModeAtomFamily = atomFamily((subChatId: string) =>
atom(
(get) => get(subChatModesStorageAtom)[subChatId] ?? "agent",
(get, set, newMode: AgentMode) => {
const current = get(subChatModesStorageAtom)
set(subChatModesStorageAtom, { ...current, [subChatId]: newMode })
},
),
)

// Model ID to full Claude model string mapping
export const MODEL_ID_MAP: Record<string, string> = {
opus: "opus",
Expand Down
13 changes: 7 additions & 6 deletions src/renderer/features/agents/commands/agents-slash-command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
filterBuiltinCommands,
BUILTIN_SLASH_COMMANDS,
} from "./builtin-commands"
import type { AgentMode } from "../atoms"

interface AgentsSlashCommandProps {
isOpen: boolean
Expand All @@ -26,7 +27,7 @@ interface AgentsSlashCommandProps {
searchText: string
position: { top: number; left: number }
projectPath?: string
isPlanMode?: boolean
mode?: AgentMode
disabledCommands?: string[]
}

Expand All @@ -38,7 +39,7 @@ export const AgentsSlashCommand = memo(function AgentsSlashCommand({
searchText,
position,
projectPath,
isPlanMode,
mode,
disabledCommands,
}: AgentsSlashCommandProps) {
const dropdownRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -125,10 +126,10 @@ export const AgentsSlashCommand = memo(function AgentsSlashCommand({
let builtinFiltered = filterBuiltinCommands(debouncedSearchText)

// Hide /plan when already in Plan mode, hide /agent when already in Agent mode
if (isPlanMode !== undefined) {
if (mode !== undefined) {
builtinFiltered = builtinFiltered.filter((cmd) => {
if (isPlanMode && cmd.name === "plan") return false
if (!isPlanMode && cmd.name === "agent") return false
if (mode === "plan" && cmd.name === "plan") return false
if (mode === "agent" && cmd.name === "agent") return false
return true
})
}
Expand All @@ -153,7 +154,7 @@ export const AgentsSlashCommand = memo(function AgentsSlashCommand({

// Return custom commands first, then builtin
return [...customFiltered, ...builtinFiltered]
}, [debouncedSearchText, customCommands, isPlanMode, disabledCommands])
}, [debouncedSearchText, customCommands, mode, disabledCommands])

// Track previous values for smarter selection reset
const prevIsOpenRef = useRef(isOpen)
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/features/agents/components/agent-send-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../../../components/ui/tooltip"
import { useResolvedHotkeyDisplayWithAlt, useResolvedHotkeyDisplay } from "../../../lib/hotkeys"
import { cn } from "../../../lib/utils"
import type { AgentMode } from "../atoms"

interface AgentSendButtonProps {
/** Whether the system is currently streaming */
Expand All @@ -33,8 +34,8 @@ interface AgentSendButtonProps {
size?: "sm" | "default" | "lg"
/** Custom aria-label */
ariaLabel?: string
/** Whether this is plan mode (orange styling) */
isPlanMode?: boolean
/** Current mode (plan=orange styling, agent=default) */
mode?: AgentMode
/** Whether input has content (used during streaming to show send-to-queue arrow) */
hasContent?: boolean
/** Whether to show voice input mode (mic icon when no content) */
Expand All @@ -60,7 +61,7 @@ export function AgentSendButton({
className = "",
size = "sm",
ariaLabel,
isPlanMode = false,
mode = "agent",
hasContent = false,
showVoiceInput = false,
isRecording = false,
Expand Down Expand Up @@ -218,7 +219,7 @@ export function AgentSendButton({

// Mode-specific styling (agent=foreground, plan=orange)
// Recording state uses same styling as normal mode (wave indicator shows recording state)
const modeClass = isPlanMode
const modeClass = mode === "plan"
? "!bg-plan-mode hover:!bg-plan-mode/90 !text-background !shadow-none"
: "!bg-foreground hover:!bg-foreground/90 !text-background !shadow-none"

Expand Down
Loading