Roo-Code/webview-ui/src/context/ExtensionStateContext.tsx
Hannes Rudolph 7da429da19
Revert "feat: add task header highlight for visual status indication" (#11349)
Revert "feat: add task header highlight for visual status indication (#11305)"

This reverts commit 5313cb503a.
2026-02-09 15:29:10 -07:00

629 lines
27 KiB
TypeScript

import React, { createContext, useCallback, useContext, useEffect, useState } from "react"
import {
type ProviderSettings,
type ProviderSettingsEntry,
type CustomModePrompts,
type ModeConfig,
type ExperimentId,
type TodoItem,
type TelemetrySetting,
type OrganizationAllowList,
type CloudOrganizationMembership,
type ExtensionMessage,
type ExtensionState,
type MarketplaceInstalledMetadata,
type Command,
type McpServer,
type SkillMetadata,
RouterModels,
ORGANIZATION_ALLOW_ALL,
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
} from "@roo-code/types"
import { findLastIndex } from "@roo/array"
import { checkExistKey } from "@roo/checkExistApiConfig"
import { Mode, defaultModeSlug, defaultPrompts } from "@roo/modes"
import { CustomSupportPrompts } from "@roo/support-prompt"
import { experimentDefault } from "@roo/experiments"
import { vscode } from "@src/utils/vscode"
import { convertTextMateToHljs } from "@src/utils/textMateToHljs"
export interface ExtensionStateContextType extends ExtensionState {
historyPreviewCollapsed?: boolean // Add the new state property
didHydrateState: boolean
showWelcome: boolean
theme: any
mcpServers: McpServer[]
hasSystemPromptOverride?: boolean
currentCheckpoint?: string
currentTaskTodos?: TodoItem[] // Initial todos for the current task
filePaths: string[]
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
commands: Command[]
skills: SkillMetadata[]
organizationAllowList: OrganizationAllowList
organizationSettingsVersion: number
cloudIsAuthenticated: boolean
cloudOrganizations?: CloudOrganizationMembership[]
sharingEnabled: boolean
publicSharingEnabled: boolean
mdmCompliant?: boolean
hasOpenedModeSelector: boolean // New property to track if user has opened mode selector
setHasOpenedModeSelector: (value: boolean) => void // Setter for the new property
alwaysAllowFollowupQuestions: boolean // New property for follow-up questions auto-approve
setAlwaysAllowFollowupQuestions: (value: boolean) => void // Setter for the new property
followupAutoApproveTimeoutMs: number | undefined // Timeout in ms for auto-approving follow-up questions
setFollowupAutoApproveTimeoutMs: (value: number) => void // Setter for the timeout
marketplaceItems?: any[]
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
profileThresholds: Record<string, number>
setProfileThresholds: (value: Record<string, number>) => void
setApiConfiguration: (config: ProviderSettings) => void
setCustomInstructions: (value?: string) => void
setAlwaysAllowReadOnly: (value: boolean) => void
setAlwaysAllowReadOnlyOutsideWorkspace: (value: boolean) => void
setAlwaysAllowWrite: (value: boolean) => void
setAlwaysAllowWriteOutsideWorkspace: (value: boolean) => void
setAlwaysAllowExecute: (value: boolean) => void
setAlwaysAllowBrowser: (value: boolean) => void
setAlwaysAllowMcp: (value: boolean) => void
setAlwaysAllowModeSwitch: (value: boolean) => void
setAlwaysAllowSubtasks: (value: boolean) => void
setBrowserToolEnabled: (value: boolean) => void
setShowRooIgnoredFiles: (value: boolean) => void
setEnableSubfolderRules: (value: boolean) => void
setShowAnnouncement: (value: boolean) => void
setAllowedCommands: (value: string[]) => void
setDeniedCommands: (value: string[]) => void
setAllowedMaxRequests: (value: number | undefined) => void
setAllowedMaxCost: (value: number | undefined) => void
setSoundEnabled: (value: boolean) => void
setSoundVolume: (value: number) => void
terminalShellIntegrationTimeout?: number
setTerminalShellIntegrationTimeout: (value: number) => void
terminalShellIntegrationDisabled?: boolean
setTerminalShellIntegrationDisabled: (value: boolean) => void
terminalZdotdir?: boolean
setTerminalZdotdir: (value: boolean) => void
setTtsEnabled: (value: boolean) => void
setTtsSpeed: (value: number) => void
setEnableCheckpoints: (value: boolean) => void
checkpointTimeout: number
setCheckpointTimeout: (value: number) => void
setBrowserViewportSize: (value: string) => void
setWriteDelayMs: (value: number) => void
screenshotQuality?: number
setScreenshotQuality: (value: number) => void
terminalOutputPreviewSize?: "small" | "medium" | "large"
setTerminalOutputPreviewSize: (value: "small" | "medium" | "large") => void
mcpEnabled: boolean
setMcpEnabled: (value: boolean) => void
remoteControlEnabled: boolean
setRemoteControlEnabled: (value: boolean) => void
taskSyncEnabled: boolean
setTaskSyncEnabled: (value: boolean) => void
featureRoomoteControlEnabled: boolean
setFeatureRoomoteControlEnabled: (value: boolean) => void
setCurrentApiConfigName: (value: string) => void
setListApiConfigMeta: (value: ProviderSettingsEntry[]) => void
mode: Mode
setMode: (value: Mode) => void
setCustomModePrompts: (value: CustomModePrompts) => void
setCustomSupportPrompts: (value: CustomSupportPrompts) => void
enhancementApiConfigId?: string
setEnhancementApiConfigId: (value: string) => void
setExperimentEnabled: (id: ExperimentId, enabled: boolean) => void
setAutoApprovalEnabled: (value: boolean) => void
customModes: ModeConfig[]
setCustomModes: (value: ModeConfig[]) => void
setMaxOpenTabsContext: (value: number) => void
maxWorkspaceFiles: number
setMaxWorkspaceFiles: (value: number) => void
setTelemetrySetting: (value: TelemetrySetting) => void
remoteBrowserEnabled?: boolean
setRemoteBrowserEnabled: (value: boolean) => void
awsUsePromptCache?: boolean
setAwsUsePromptCache: (value: boolean) => void
maxImageFileSize: number
setMaxImageFileSize: (value: number) => void
maxTotalImageSize: number
setMaxTotalImageSize: (value: number) => void
machineId?: string
pinnedApiConfigs?: Record<string, boolean>
setPinnedApiConfigs: (value: Record<string, boolean>) => void
togglePinnedApiConfig: (configName: string) => void
setHistoryPreviewCollapsed: (value: boolean) => void
setReasoningBlockCollapsed: (value: boolean) => void
enterBehavior?: "send" | "newline"
setEnterBehavior: (value: "send" | "newline") => void
autoCondenseContext: boolean
setAutoCondenseContext: (value: boolean) => void
autoCondenseContextPercent: number
setAutoCondenseContextPercent: (value: number) => void
routerModels?: RouterModels
includeDiagnosticMessages?: boolean
setIncludeDiagnosticMessages: (value: boolean) => void
maxDiagnosticMessages?: number
setMaxDiagnosticMessages: (value: number) => void
includeTaskHistoryInEnhance?: boolean
setIncludeTaskHistoryInEnhance: (value: boolean) => void
includeCurrentTime?: boolean
setIncludeCurrentTime: (value: boolean) => void
includeCurrentCost?: boolean
setIncludeCurrentCost: (value: boolean) => void
showWorktreesInHomeScreen: boolean
setShowWorktreesInHomeScreen: (value: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
export const mergeExtensionState = (prevState: ExtensionState, newState: Partial<ExtensionState>) => {
const { customModePrompts: prevCustomModePrompts, experiments: prevExperiments, ...prevRest } = prevState
const {
apiConfiguration,
customModePrompts: newCustomModePrompts,
customSupportPrompts,
experiments: newExperiments,
...newRest
} = newState
const customModePrompts = { ...prevCustomModePrompts, ...(newCustomModePrompts ?? {}) }
const experiments = { ...prevExperiments, ...(newExperiments ?? {}) }
const rest = { ...prevRest, ...newRest }
// Note that we completely replace the previous apiConfiguration and customSupportPrompts objects
// with new ones since the state that is broadcast is the entire objects so merging is not necessary.
return {
...rest,
apiConfiguration: apiConfiguration ?? prevState.apiConfiguration,
customModePrompts,
customSupportPrompts: customSupportPrompts ?? prevState.customSupportPrompts,
experiments,
}
}
export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, setState] = useState<ExtensionState>({
apiConfiguration: {},
version: "",
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
allowedCommands: [],
deniedCommands: [],
soundEnabled: false,
soundVolume: 0.5,
isBrowserSessionActive: false,
ttsEnabled: false,
ttsSpeed: 1.0,
enableCheckpoints: true,
checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Default to 15 seconds
language: "en", // Default language code
writeDelayMs: 1000,
browserViewportSize: "900x600",
screenshotQuality: 75,
terminalShellIntegrationTimeout: 4000,
mcpEnabled: true,
remoteControlEnabled: false,
taskSyncEnabled: false,
featureRoomoteControlEnabled: false,
currentApiConfigName: "default",
listApiConfigMeta: [],
mode: defaultModeSlug,
customModePrompts: defaultPrompts,
customSupportPrompts: {},
experiments: experimentDefault,
enhancementApiConfigId: "",
hasOpenedModeSelector: false, // Default to false (not opened yet)
autoApprovalEnabled: false,
customModes: [],
maxOpenTabsContext: 20,
maxWorkspaceFiles: 200,
cwd: "",
browserToolEnabled: true,
telemetrySetting: "unset",
showRooIgnoredFiles: true, // Default to showing .rooignore'd files with lock symbol (current behavior).
enableSubfolderRules: false, // Default to disabled - must be enabled to load rules from subdirectories
renderContext: "sidebar",
maxImageFileSize: 5, // Default max image file size in MB
maxTotalImageSize: 20, // Default max total image size in MB
pinnedApiConfigs: {}, // Empty object for pinned API configs
terminalZshOhMy: false, // Default Oh My Zsh integration setting
terminalZshP10k: false, // Default Powerlevel10k integration setting
terminalZdotdir: false, // Default ZDOTDIR handling setting
historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
reasoningBlockCollapsed: true, // Default to collapsed
enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline
cloudUserInfo: null,
cloudIsAuthenticated: false,
cloudOrganizations: [],
sharingEnabled: false,
publicSharingEnabled: false,
organizationAllowList: ORGANIZATION_ALLOW_ALL,
organizationSettingsVersion: -1,
autoCondenseContext: true,
autoCondenseContextPercent: 100,
profileThresholds: {},
codebaseIndexConfig: {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
codebaseIndexSearchMaxResults: undefined,
codebaseIndexSearchMinScore: undefined,
},
codebaseIndexModels: { ollama: {}, openai: {} },
includeDiagnosticMessages: true,
maxDiagnosticMessages: 50,
openRouterImageApiKey: "",
openRouterImageGenerationSelectedModel: "",
includeCurrentTime: true,
includeCurrentCost: true,
lockApiConfigAcrossModes: false,
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
const [theme, setTheme] = useState<any>(undefined)
const [filePaths, setFilePaths] = useState<string[]>([])
const [openedTabs, setOpenedTabs] = useState<Array<{ label: string; isActive: boolean; path?: string }>>([])
const [commands, setCommands] = useState<Command[]>([])
const [skills, setSkills] = useState<SkillMetadata[]>([])
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
const [extensionRouterModels, setExtensionRouterModels] = useState<RouterModels | undefined>(undefined)
const [marketplaceItems, setMarketplaceItems] = useState<any[]>([])
const [alwaysAllowFollowupQuestions, setAlwaysAllowFollowupQuestions] = useState(false) // Add state for follow-up questions auto-approve
const [followupAutoApproveTimeoutMs, setFollowupAutoApproveTimeoutMs] = useState<number | undefined>(undefined) // Will be set from global settings
const [marketplaceInstalledMetadata, setMarketplaceInstalledMetadata] = useState<MarketplaceInstalledMetadata>({
project: {},
global: {},
})
const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true)
const [prevCloudIsAuthenticated, setPrevCloudIsAuthenticated] = useState(false)
const [includeCurrentTime, setIncludeCurrentTime] = useState(true)
const [includeCurrentCost, setIncludeCurrentCost] = useState(true)
const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
[],
)
const setApiConfiguration = useCallback((value: ProviderSettings) => {
setState((prevState) => ({
...prevState,
apiConfiguration: {
...prevState.apiConfiguration,
...value,
},
}))
}, [])
const handleMessage = useCallback(
(event: MessageEvent) => {
const message: ExtensionMessage = event.data
switch (message.type) {
case "state": {
const newState = message.state ?? {}
setState((prevState) => mergeExtensionState(prevState, newState))
setShowWelcome(!checkExistKey(newState.apiConfiguration))
setDidHydrateState(true)
// Update alwaysAllowFollowupQuestions if present in state message
if ((newState as any).alwaysAllowFollowupQuestions !== undefined) {
setAlwaysAllowFollowupQuestions((newState as any).alwaysAllowFollowupQuestions)
}
// Update followupAutoApproveTimeoutMs if present in state message
if ((newState as any).followupAutoApproveTimeoutMs !== undefined) {
setFollowupAutoApproveTimeoutMs((newState as any).followupAutoApproveTimeoutMs)
}
// Update includeTaskHistoryInEnhance if present in state message
if ((newState as any).includeTaskHistoryInEnhance !== undefined) {
setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance)
}
// Update includeCurrentTime if present in state message
if ((newState as any).includeCurrentTime !== undefined) {
setIncludeCurrentTime((newState as any).includeCurrentTime)
}
// Update includeCurrentCost if present in state message
if ((newState as any).includeCurrentCost !== undefined) {
setIncludeCurrentCost((newState as any).includeCurrentCost)
}
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
}
if (newState.marketplaceInstalledMetadata !== undefined) {
setMarketplaceInstalledMetadata(newState.marketplaceInstalledMetadata)
}
break
}
case "action": {
if (message.action === "toggleAutoApprove") {
// Toggle the auto-approval state
setState((prevState) => {
const newValue = !(prevState.autoApprovalEnabled ?? false)
// Also send the update to the extension
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
return { ...prevState, autoApprovalEnabled: newValue }
})
}
break
}
case "theme": {
if (message.text) {
setTheme(convertTextMateToHljs(JSON.parse(message.text)))
}
break
}
case "workspaceUpdated": {
const paths = message.filePaths ?? []
const tabs = message.openedTabs ?? []
setFilePaths(paths)
setOpenedTabs(tabs)
break
}
case "commands": {
setCommands(message.commands ?? [])
break
}
case "skills": {
setSkills(message.skills ?? [])
break
}
case "messageUpdated": {
const clineMessage = message.clineMessage!
setState((prevState) => {
// worth noting it will never be possible for a more up-to-date message to be sent here or in normal messages post since the presentAssistantContent function uses lock
const lastIndex = findLastIndex(prevState.clineMessages, (msg) => msg.ts === clineMessage.ts)
if (lastIndex !== -1) {
const newClineMessages = [...prevState.clineMessages]
newClineMessages[lastIndex] = clineMessage
return { ...prevState, clineMessages: newClineMessages }
}
return prevState
})
break
}
case "mcpServers": {
setMcpServers(message.mcpServers ?? [])
break
}
case "currentCheckpointUpdated": {
setCurrentCheckpoint(message.text)
break
}
case "listApiConfig": {
setListApiConfigMeta(message.listApiConfig ?? [])
break
}
case "routerModels": {
setExtensionRouterModels(message.routerModels)
break
}
case "marketplaceData": {
if (message.marketplaceItems !== undefined) {
setMarketplaceItems(message.marketplaceItems)
}
if (message.marketplaceInstalledMetadata !== undefined) {
setMarketplaceInstalledMetadata(message.marketplaceInstalledMetadata)
}
break
}
case "taskHistoryUpdated": {
// Efficiently update just the task history without replacing entire state
if (message.taskHistory !== undefined) {
setState((prevState) => ({
...prevState,
taskHistory: message.taskHistory!,
}))
}
break
}
case "taskHistoryItemUpdated": {
const item = message.taskHistoryItem
if (!item) {
break
}
setState((prevState) => {
const existingIndex = prevState.taskHistory.findIndex((h) => h.id === item.id)
let nextHistory: typeof prevState.taskHistory
if (existingIndex === -1) {
nextHistory = [item, ...prevState.taskHistory]
} else {
nextHistory = [...prevState.taskHistory]
nextHistory[existingIndex] = item
}
// Keep UI semantics consistent with extension: newest-first ordering.
nextHistory.sort((a, b) => b.ts - a.ts)
return {
...prevState,
taskHistory: nextHistory,
currentTaskItem:
prevState.currentTaskItem?.id === item.id ? item : prevState.currentTaskItem,
}
})
break
}
}
},
[setListApiConfigMeta],
)
useEffect(() => {
window.addEventListener("message", handleMessage)
return () => {
window.removeEventListener("message", handleMessage)
}
}, [handleMessage])
useEffect(() => {
vscode.postMessage({ type: "webviewDidLaunch" })
}, [])
// Watch for authentication state changes and refresh Roo models
useEffect(() => {
const currentAuth = state.cloudIsAuthenticated ?? false
const currentProvider = state.apiConfiguration?.apiProvider
if (!prevCloudIsAuthenticated && currentAuth && currentProvider === "roo") {
// User just authenticated and Roo is the active provider - refresh Roo models
vscode.postMessage({ type: "requestRooModels" })
}
setPrevCloudIsAuthenticated(currentAuth)
}, [state.cloudIsAuthenticated, prevCloudIsAuthenticated, state.apiConfiguration?.apiProvider])
const contextValue: ExtensionStateContextType = {
...state,
reasoningBlockCollapsed: state.reasoningBlockCollapsed ?? true,
didHydrateState,
showWelcome,
theme,
mcpServers,
currentCheckpoint,
filePaths,
openedTabs,
commands,
skills,
soundVolume: state.soundVolume,
ttsSpeed: state.ttsSpeed,
writeDelayMs: state.writeDelayMs,
screenshotQuality: state.screenshotQuality,
routerModels: extensionRouterModels,
cloudIsAuthenticated: state.cloudIsAuthenticated ?? false,
cloudOrganizations: state.cloudOrganizations ?? [],
organizationSettingsVersion: state.organizationSettingsVersion ?? -1,
marketplaceItems,
marketplaceInstalledMetadata,
profileThresholds: state.profileThresholds ?? {},
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs,
remoteControlEnabled: state.remoteControlEnabled ?? false,
taskSyncEnabled: state.taskSyncEnabled,
featureRoomoteControlEnabled: state.featureRoomoteControlEnabled ?? false,
setExperimentEnabled: (id, enabled) =>
setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })),
setApiConfiguration,
setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
setAlwaysAllowReadOnlyOutsideWorkspace: (value) =>
setState((prevState) => ({ ...prevState, alwaysAllowReadOnlyOutsideWorkspace: value })),
setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })),
setAlwaysAllowWriteOutsideWorkspace: (value) =>
setState((prevState) => ({ ...prevState, alwaysAllowWriteOutsideWorkspace: value })),
setAlwaysAllowExecute: (value) => setState((prevState) => ({ ...prevState, alwaysAllowExecute: value })),
setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })),
setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })),
setAlwaysAllowSubtasks: (value) => setState((prevState) => ({ ...prevState, alwaysAllowSubtasks: value })),
setAlwaysAllowFollowupQuestions,
setFollowupAutoApproveTimeoutMs: (value) =>
setState((prevState) => ({ ...prevState, followupAutoApproveTimeoutMs: value })),
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
setDeniedCommands: (value) => setState((prevState) => ({ ...prevState, deniedCommands: value })),
setAllowedMaxRequests: (value) => setState((prevState) => ({ ...prevState, allowedMaxRequests: value })),
setAllowedMaxCost: (value) => setState((prevState) => ({ ...prevState, allowedMaxCost: value })),
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
setSoundVolume: (value) => setState((prevState) => ({ ...prevState, soundVolume: value })),
setTtsEnabled: (value) => setState((prevState) => ({ ...prevState, ttsEnabled: value })),
setTtsSpeed: (value) => setState((prevState) => ({ ...prevState, ttsSpeed: value })),
setEnableCheckpoints: (value) => setState((prevState) => ({ ...prevState, enableCheckpoints: value })),
setCheckpointTimeout: (value) => setState((prevState) => ({ ...prevState, checkpointTimeout: value })),
setBrowserViewportSize: (value: string) =>
setState((prevState) => ({ ...prevState, browserViewportSize: value })),
setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })),
setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })),
setTerminalOutputPreviewSize: (value) =>
setState((prevState) => ({ ...prevState, terminalOutputPreviewSize: value })),
setTerminalShellIntegrationTimeout: (value) =>
setState((prevState) => ({ ...prevState, terminalShellIntegrationTimeout: value })),
setTerminalShellIntegrationDisabled: (value) =>
setState((prevState) => ({ ...prevState, terminalShellIntegrationDisabled: value })),
setTerminalZdotdir: (value) => setState((prevState) => ({ ...prevState, terminalZdotdir: value })),
setMcpEnabled: (value) => setState((prevState) => ({ ...prevState, mcpEnabled: value })),
setRemoteControlEnabled: (value) => setState((prevState) => ({ ...prevState, remoteControlEnabled: value })),
setTaskSyncEnabled: (value) => setState((prevState) => ({ ...prevState, taskSyncEnabled: value }) as any),
setFeatureRoomoteControlEnabled: (value) =>
setState((prevState) => ({ ...prevState, featureRoomoteControlEnabled: value })),
setCurrentApiConfigName: (value) => setState((prevState) => ({ ...prevState, currentApiConfigName: value })),
setListApiConfigMeta,
setMode: (value: Mode) => setState((prevState) => ({ ...prevState, mode: value })),
setCustomModePrompts: (value) => setState((prevState) => ({ ...prevState, customModePrompts: value })),
setCustomSupportPrompts: (value) => setState((prevState) => ({ ...prevState, customSupportPrompts: value })),
setEnhancementApiConfigId: (value) =>
setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })),
setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })),
setMaxWorkspaceFiles: (value) => setState((prevState) => ({ ...prevState, maxWorkspaceFiles: value })),
setBrowserToolEnabled: (value) => setState((prevState) => ({ ...prevState, browserToolEnabled: value })),
setTelemetrySetting: (value) => setState((prevState) => ({ ...prevState, telemetrySetting: value })),
setShowRooIgnoredFiles: (value) => setState((prevState) => ({ ...prevState, showRooIgnoredFiles: value })),
setEnableSubfolderRules: (value) => setState((prevState) => ({ ...prevState, enableSubfolderRules: value })),
setRemoteBrowserEnabled: (value) => setState((prevState) => ({ ...prevState, remoteBrowserEnabled: value })),
setAwsUsePromptCache: (value) => setState((prevState) => ({ ...prevState, awsUsePromptCache: value })),
setMaxImageFileSize: (value) => setState((prevState) => ({ ...prevState, maxImageFileSize: value })),
setMaxTotalImageSize: (value) => setState((prevState) => ({ ...prevState, maxTotalImageSize: value })),
setPinnedApiConfigs: (value) => setState((prevState) => ({ ...prevState, pinnedApiConfigs: value })),
togglePinnedApiConfig: (configId) =>
setState((prevState) => {
const currentPinned = prevState.pinnedApiConfigs || {}
const newPinned = {
...currentPinned,
[configId]: !currentPinned[configId],
}
// If the config is now unpinned, remove it from the object
if (!newPinned[configId]) {
delete newPinned[configId]
}
return { ...prevState, pinnedApiConfigs: newPinned }
}),
setHistoryPreviewCollapsed: (value) =>
setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })),
setReasoningBlockCollapsed: (value) =>
setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })),
enterBehavior: state.enterBehavior ?? "send",
setEnterBehavior: (value) => setState((prevState) => ({ ...prevState, enterBehavior: value })),
setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })),
setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })),
setAutoCondenseContextPercent: (value) =>
setState((prevState) => ({ ...prevState, autoCondenseContextPercent: value })),
setProfileThresholds: (value) => setState((prevState) => ({ ...prevState, profileThresholds: value })),
includeDiagnosticMessages: state.includeDiagnosticMessages,
setIncludeDiagnosticMessages: (value) => {
setState((prevState) => ({ ...prevState, includeDiagnosticMessages: value }))
},
maxDiagnosticMessages: state.maxDiagnosticMessages,
setMaxDiagnosticMessages: (value) => {
setState((prevState) => ({ ...prevState, maxDiagnosticMessages: value }))
},
includeTaskHistoryInEnhance,
setIncludeTaskHistoryInEnhance,
includeCurrentTime,
setIncludeCurrentTime,
includeCurrentCost,
setIncludeCurrentCost,
showWorktreesInHomeScreen: state.showWorktreesInHomeScreen ?? true,
setShowWorktreesInHomeScreen: (value) =>
setState((prevState) => ({ ...prevState, showWorktreesInHomeScreen: value })),
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
}
export const useExtensionState = () => {
const context = useContext(ExtensionStateContext)
if (context === undefined) {
throw new Error("useExtensionState must be used within an ExtensionStateContextProvider")
}
return context
}