Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
37 changes: 37 additions & 0 deletions src/core/config/__tests__/CustomModesSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,49 @@ describe("CustomModesSettings", () => {
{
...validMode,
customInstructions: "Optional instructions",
enabledForSwitching: false,
},
],
}

// TypeScript compilation will fail if the type is incorrect
expect(settings.customModes[0].customInstructions).toBeDefined()
expect(settings.customModes[0].enabledForSwitching).toBeDefined()
})

test("enabledForSwitching can be set to false", () => {
const settings = {
customModes: [
{
...validMode,
enabledForSwitching: false,
},
],
}

expect(() => {
customModesSettingsSchema.parse(settings)
}).not.toThrow()
expect(settings.customModes[0].enabledForSwitching).toBe(false)
})

test("enabledForSwitching can be undefined (default to enabled)", () => {
const settings = {
customModes: [
{
...validMode,
// enabledForSwitching is not specified
},
],
}

expect(() => {
customModesSettingsSchema.parse(settings)
}).not.toThrow()
// Since validMode doesn't have enabledForSwitching, it should be undefined
// which means it's enabled by default
const parsedSettings = customModesSettingsSchema.parse(settings)
expect(parsedSettings.customModes[0].enabledForSwitching).toBeUndefined()
})
})
})
16 changes: 14 additions & 2 deletions src/core/prompts/sections/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as path from "path"
import * as vscode from "vscode"
import { promises as fs } from "fs"
import { ModeConfig, getAllModesWithPrompts } from "../../../shared/modes"
import { GlobalFileNames } from "../../../shared/globalFileNames"

export async function getModesSection(context: vscode.ExtensionContext): Promise<string> {
const settingsDir = path.join(context.globalStorageUri.fsPath, "settings")
Expand All @@ -11,12 +10,25 @@ export async function getModesSection(context: vscode.ExtensionContext): Promise
// Get all modes with their overrides from extension state
const allModes = await getAllModesWithPrompts(context)

let modesContent = `====
// Filter out modes that are disabled for switching
const enabledModes = allModes.filter((mode) => {
// If enabledForSwitching is explicitly set to false, filter out the mode
// Otherwise, include the mode (undefined or true means enabled)
return mode.enabledForSwitching !== false
})

let modesContent = enabledModes.length
? `====

MODES

- These are the currently available modes:
${allModes.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")}`
: `====

MODES

`

modesContent += `
If the user asks you to create or edit a new mode for this project, you should read the instructions by using the fetch_instructions tool, like this:
Expand Down
3 changes: 2 additions & 1 deletion src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ async function generatePrompt(

${getSharedToolUseSection()}

${getToolDescriptionsForMode(
${await getToolDescriptionsForMode(
mode,
cwd,
supportsComputerUse,
context,
effectiveDiffStrategy,
browserViewportSize,
mcpHub,
Expand Down
25 changes: 20 additions & 5 deletions src/core/prompts/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ import { getSwitchModeDescription } from "./switch-mode"
import { getNewTaskDescription } from "./new-task"
import { DiffStrategy } from "../../diff/DiffStrategy"
import { McpHub } from "../../../services/mcp/McpHub"
import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes"
import {
Mode,
ModeConfig,
getModeConfig,
isToolAllowedForMode,
getGroupName,
hasAnySwitchableModes,
} from "../../../shared/modes"
import { ToolName, TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tool-groups"
import { ToolArgs } from "./types"
import { ExtensionContext } from "vscode"

// Map of tool names to their description functions
const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined> = {
Expand All @@ -42,16 +50,17 @@ const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined>
args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "",
}

export function getToolDescriptionsForMode(
export async function getToolDescriptionsForMode(
mode: Mode,
cwd: string,
supportsComputerUse: boolean,
context: ExtensionContext,
diffStrategy?: DiffStrategy,
browserViewportSize?: string,
mcpHub?: McpHub,
customModes?: ModeConfig[],
experiments?: Record<string, boolean>,
): string {
): Promise<string> {
const config = getModeConfig(mode, customModes)
const args: ToolArgs = {
cwd,
Expand All @@ -76,8 +85,14 @@ export function getToolDescriptionsForMode(
}
})

// Add always available tools
ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool))
// Add always available tools except switch_mode if no modes are switchable
ALWAYS_AVAILABLE_TOOLS.forEach(async (tool) => {
// Skip adding switch_mode tool if no modes are switchable
if (tool === "switch_mode" && !(await hasAnySwitchableModes(context))) {
return
}
tools.add(tool)
})

// Map tool descriptions for allowed tools
const descriptions = Array.from(tools).map((toolName) => {
Expand Down
2 changes: 1 addition & 1 deletion src/core/tools/switchModeTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function switchModeTool(

// Verify the mode exists
const targetMode = getModeBySlug(mode_slug, (await cline.providerRef.deref()?.getState())?.customModes)
if (!targetMode) {
if (!targetMode || targetMode.enabledForSwitching === false) {
pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`))
return
}
Expand Down
2 changes: 2 additions & 0 deletions src/exports/roo-code.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ type GlobalSettings = {
]
)[]
source?: ("global" | "project") | undefined
enabledForSwitching?: boolean | undefined
}[]
| undefined
customModePrompts?:
Expand All @@ -342,6 +343,7 @@ type GlobalSettings = {
| {
roleDefinition?: string | undefined
customInstructions?: string | undefined
enabledForSwitching?: boolean | undefined
}
| undefined
}
Expand Down
2 changes: 2 additions & 0 deletions src/exports/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ type GlobalSettings = {
]
)[]
source?: ("global" | "project") | undefined
enabledForSwitching?: boolean | undefined
}[]
| undefined
customModePrompts?:
Expand All @@ -345,6 +346,7 @@ type GlobalSettings = {
| {
roleDefinition?: string | undefined
customInstructions?: string | undefined
enabledForSwitching?: boolean | undefined
}
| undefined
}
Expand Down
2 changes: 2 additions & 0 deletions src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export const modeConfigSchema = z.object({
customInstructions: z.string().optional(),
groups: groupEntryArraySchema,
source: z.enum(["global", "project"]).optional(),
enabledForSwitching: z.boolean().optional(),
})

export type ModeConfig = z.infer<typeof modeConfigSchema>
Expand Down Expand Up @@ -252,6 +253,7 @@ export type CustomModesSettings = z.infer<typeof customModesSettingsSchema>
export const promptComponentSchema = z.object({
roleDefinition: z.string().optional(),
customInstructions: z.string().optional(),
enabledForSwitching: z.boolean().optional(),
})

export type PromptComponent = z.infer<typeof promptComponentSchema>
Expand Down
20 changes: 20 additions & 0 deletions src/shared/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export async function getAllModesWithPrompts(context: vscode.ExtensionContext):
...mode,
roleDefinition: customModePrompts[mode.slug]?.roleDefinition ?? mode.roleDefinition,
customInstructions: customModePrompts[mode.slug]?.customInstructions ?? mode.customInstructions,
enabledForSwitching: customModePrompts[mode.slug]?.enabledForSwitching,
}))
}

Expand Down Expand Up @@ -281,6 +282,7 @@ export async function getFullModeDetails(
...baseMode,
roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition,
customInstructions: fullCustomInstructions,
enabledForSwitching: promptComponent?.enabledForSwitching,
}
}

Expand All @@ -303,3 +305,21 @@ export function getCustomInstructions(modeSlug: string, customModes?: ModeConfig
}
return mode.customInstructions ?? ""
}

export function getEnabledForSwitching(modeSlug: string, customModes?: ModeConfig[]): boolean {
const mode = getModeBySlug(modeSlug, customModes)
if (!mode) {
console.warn(`No mode found for slug: ${modeSlug}`)
return true
}
return mode.enabledForSwitching ?? true
}

// Check if any modes are enabled for switching
export async function hasAnySwitchableModes(context: vscode.ExtensionContext): Promise<boolean> {
// Get all modes (built-in and custom)
const allModes = await getAllModesWithPrompts(context)

// Check if any mode is enabled for switching
return allModes.some((mode) => mode.enabledForSwitching !== false)
}
Loading