Remove preferredLanguage and just rely on VSCode language

This commit is contained in:
Matt Rubens 2025-03-11 11:11:56 -04:00
parent ae6903b26c
commit baa346dc46
22 changed files with 1666 additions and 386 deletions

View file

@ -1,4 +1,13 @@
const vscode = {
env: {
language: "en", // Default language for tests
appName: "Visual Studio Code Test",
appHost: "desktop",
appRoot: "/test/path",
machineId: "test-machine-id",
sessionId: "test-session-id",
shell: "/bin/zsh",
},
window: {
showInformationMessage: jest.fn(),
showErrorMessage: jest.fn(),

View file

@ -1102,7 +1102,6 @@ export class Cline {
browserViewportSize,
mode,
customModePrompts,
preferredLanguage,
experiments,
enableMcpServerCreation,
browserToolEnabled,
@ -1124,7 +1123,6 @@ export class Cline {
customModePrompts,
customModes,
this.customInstructions,
preferredLanguage,
this.diffEnabled,
experiments,
enableMcpServerCreation,
@ -3665,13 +3663,12 @@ export class Cline {
customModePrompts,
experiments = {} as Record<ExperimentId, boolean>,
customInstructions: globalCustomInstructions,
preferredLanguage,
} = (await this.providerRef.deref()?.getState()) ?? {}
const currentMode = mode ?? defaultModeSlug
const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, {
cwd,
globalCustomInstructions,
preferredLanguage,
vscodeLanguage: vscode.env.language,
})
details += `\n\n# Current Mode\n`
details += `<slug>${currentMode}</slug>\n`

File diff suppressed because it is too large Load diff

View file

@ -46,8 +46,6 @@ const mockContext = {
} as unknown as vscode.ExtensionContext
describe("File-Based Custom System Prompt", () => {
const experiments = {}
beforeEach(() => {
// Reset mocks before each test
jest.clearAllMocks()
@ -66,18 +64,17 @@ describe("File-Based Custom System Prompt", () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"test/path", // Using a relative path without leading slash
false,
undefined,
undefined,
undefined,
defaultModeSlug,
customModePrompts,
undefined,
undefined,
undefined,
undefined,
experiments,
true,
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
customModePrompts, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
true, // enableMcpServerCreation
)
// Should contain default sections
@ -101,18 +98,17 @@ describe("File-Based Custom System Prompt", () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"test/path", // Using a relative path without leading slash
false,
undefined,
undefined,
undefined,
defaultModeSlug,
undefined,
undefined,
undefined,
undefined,
undefined,
experiments,
true,
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
true, // enableMcpServerCreation
)
// Should contain role definition and file-based system prompt
@ -120,7 +116,6 @@ describe("File-Based Custom System Prompt", () => {
expect(prompt).toContain(fileCustomSystemPrompt)
// Should not contain any of the default sections
expect(prompt).not.toContain("TOOL USE")
expect(prompt).not.toContain("CAPABILITIES")
expect(prompt).not.toContain("MODES")
})
@ -146,18 +141,17 @@ describe("File-Based Custom System Prompt", () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"test/path", // Using a relative path without leading slash
false,
undefined,
undefined,
undefined,
defaultModeSlug,
customModePrompts,
undefined,
undefined,
undefined,
undefined,
experiments,
true,
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
customModePrompts, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
true, // enableMcpServerCreation
)
// Should contain custom role definition and file-based system prompt
@ -165,7 +159,6 @@ describe("File-Based Custom System Prompt", () => {
expect(prompt).toContain(fileCustomSystemPrompt)
// Should not contain any of the default sections
expect(prompt).not.toContain("TOOL USE")
expect(prompt).not.toContain("CAPABILITIES")
expect(prompt).not.toContain("MODES")
})

View file

@ -3,20 +3,20 @@ import { getCapabilitiesSection } from "../sections/capabilities"
import { DiffStrategy, DiffResult } from "../../diff/types"
describe("addCustomInstructions", () => {
test("adds preferred language to custom instructions", async () => {
test("adds vscode language to custom instructions", async () => {
const result = await addCustomInstructions(
"mode instructions",
"global instructions",
"/test/path",
"test-mode",
{ preferredLanguage: "French" },
{ vscodeLanguage: "fr" },
)
expect(result).toContain("Language Preference:")
expect(result).toContain("You should always speak and think in the French language")
expect(result).toContain('You should always speak and think in the "fr" language')
})
test("works without preferred language", async () => {
test("works without vscode language", async () => {
const result = await addCustomInstructions(
"mode instructions",
"global instructions",

View file

@ -6,7 +6,7 @@ import { SearchReplaceDiffStrategy } from "../../../core/diff/strategies/search-
import * as vscode from "vscode"
import fs from "fs/promises"
import os from "os"
import { defaultModeSlug, modes } from "../../../shared/modes"
import { defaultModeSlug, modes, Mode, isToolAllowedForMode } from "../../../shared/modes"
// Import path utils to get access to toPosix string extension
import "../../../utils/path"
import { addCustomInstructions } from "../sections/custom-instructions"
@ -18,46 +18,63 @@ jest.mock("../sections/modes", () => ({
getModesSection: jest.fn().mockImplementation(async () => `====\n\nMODES\n\n- Test modes section`),
}))
jest.mock("../sections/custom-instructions", () => ({
addCustomInstructions: jest
.fn()
.mockImplementation(async (modeCustomInstructions, globalCustomInstructions, cwd, mode, options) => {
const sections = []
// Mock the custom instructions
jest.mock("../sections/custom-instructions", () => {
const addCustomInstructions = jest.fn()
return {
addCustomInstructions,
__setMockImplementation: (impl: any) => {
addCustomInstructions.mockImplementation(impl)
},
}
})
// Add language preference if provided
if (options?.preferredLanguage) {
sections.push(
`Language Preference:\nYou should always speak and think in the ${options.preferredLanguage} language.`,
)
}
// Set up default mock implementation
const { __setMockImplementation } = jest.requireMock("../sections/custom-instructions")
__setMockImplementation(
async (
modeCustomInstructions: string,
globalCustomInstructions: string,
cwd: string,
mode: string,
options?: { vscodeLanguage?: string },
) => {
const sections = []
// Add global instructions first
if (globalCustomInstructions?.trim()) {
sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`)
}
// Add language preference if provided
if (options?.vscodeLanguage) {
sections.push(
`Language Preference:\nYou should always speak and think in the "${options.vscodeLanguage}" language.`,
)
}
// Add mode-specific instructions after
if (modeCustomInstructions?.trim()) {
sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`)
}
// Add global instructions first
if (globalCustomInstructions?.trim()) {
sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`)
}
// Add rules
const rules = []
if (mode) {
rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`)
}
rules.push(`# Rules from .clinerules:\nMock generic rules`)
// Add mode-specific instructions after
if (modeCustomInstructions?.trim()) {
sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`)
}
if (rules.length > 0) {
sections.push(`Rules:\n${rules.join("\n")}`)
}
// Add rules
const rules = []
if (mode) {
rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`)
}
rules.push(`# Rules from .clinerules:\nMock generic rules`)
const joinedSections = sections.join("\n\n")
return joinedSections
? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}`
: ""
}),
}))
if (rules.length > 0) {
sections.push(`Rules:\n${rules.join("\n")}`)
}
const joinedSections = sections.join("\n\n")
return joinedSections
? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}`
: ""
},
)
// Mock environment-specific values for consistent tests
jest.mock("os", () => ({
@ -69,6 +86,13 @@ jest.mock("default-shell", () => "/bin/zsh")
jest.mock("os-name", () => () => "Linux")
// Mock vscode language
jest.mock("vscode", () => ({
env: {
language: "en",
},
}))
jest.mock("../../../utils/shell", () => ({
getShell: () => "/bin/zsh",
}))
@ -126,7 +150,7 @@ const createMockMcpHub = (): McpHub =>
describe("SYSTEM_PROMPT", () => {
let mockMcpHub: McpHub
let experiments: Record<string, boolean>
let experiments: Record<string, boolean> | undefined
beforeAll(() => {
// Ensure fs mock is properly initialized
@ -146,6 +170,10 @@ describe("SYSTEM_PROMPT", () => {
"/mock/mcp/path",
]
dirs.forEach((dir) => mockFs._mockDirectories.add(dir))
})
beforeEach(() => {
// Reset experiments before each test to ensure they're disabled by default
experiments = {
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
@ -175,7 +203,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -196,7 +223,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes,
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -219,7 +245,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes,
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -240,7 +265,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes,
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -261,7 +285,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes,
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -282,7 +305,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
true, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -304,7 +326,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
false, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -326,7 +347,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -336,7 +356,11 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt).toMatchSnapshot()
})
it("should include preferred language in custom instructions", async () => {
it("should include vscode language in custom instructions", async () => {
// Mock vscode.env.language
const vscode = jest.requireMock("vscode")
vscode.env = { language: "es" }
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
@ -348,14 +372,16 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
"Spanish", // preferredLanguage
undefined, // diffEnabled
experiments,
undefined, // experiments
true, // enableMcpServerCreation
)
expect(prompt).toContain("Language Preference:")
expect(prompt).toContain("You should always speak and think in the Spanish language")
expect(prompt).toContain('You should always speak and think in the "es" language')
// Reset mock
vscode.env = { language: "en" }
})
it("should include custom mode role definition at top and instructions at bottom", async () => {
@ -381,7 +407,6 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
customModes, // customModes
"Global instructions", // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
@ -409,18 +434,17 @@ describe("SYSTEM_PROMPT", () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false,
undefined,
undefined,
undefined,
defaultModeSlug,
customModePrompts,
undefined,
undefined,
undefined,
undefined,
experiments,
true, // enableMcpServerCreation
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug as Mode, // mode
customModePrompts, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
false, // enableMcpServerCreation
)
// Role definition from promptComponent should be at the top
@ -440,18 +464,17 @@ describe("SYSTEM_PROMPT", () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false,
undefined,
undefined,
undefined,
defaultModeSlug,
customModePrompts,
undefined,
undefined,
undefined,
undefined,
experiments,
true, // enableMcpServerCreation
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug as Mode, // mode
customModePrompts, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
false, // enableMcpServerCreation
)
// Should use the default mode's role definition
@ -460,6 +483,15 @@ describe("SYSTEM_PROMPT", () => {
describe("experimental tools", () => {
it("should disable experimental tools by default", async () => {
// Set experiments to explicitly disable experimental tools
const experimentsConfig = {
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
}
// Reset experiments
experiments = experimentsConfig
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
@ -471,23 +503,29 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments, // experiments - undefined should disable all experimental tools
experimentsConfig, // Explicitly disable experimental tools
true, // enableMcpServerCreation
)
// Verify experimental tools are not included in the prompt
expect(prompt).not.toContain(EXPERIMENT_IDS.SEARCH_AND_REPLACE)
expect(prompt).not.toContain(EXPERIMENT_IDS.INSERT_BLOCK)
// Check that experimental tool sections are not included
const toolSections = prompt.split("\n## ").slice(1)
const toolNames = toolSections.map((section) => section.split("\n")[0].trim())
expect(toolNames).not.toContain("search_and_replace")
expect(toolNames).not.toContain("insert_content")
expect(prompt).toMatchSnapshot()
})
it("should enable experimental tools when explicitly enabled", async () => {
const experiments = {
// Set experiments for testing experimental features
const experimentsEnabled = {
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.INSERT_BLOCK]: true,
}
// Reset default experiments
experiments = undefined
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
@ -499,23 +537,31 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
experimentsEnabled, // Use the enabled experiments
true, // enableMcpServerCreation
)
// Get all tool sections
const toolSections = prompt.split("## ").slice(1) // Split by section headers and remove first non-tool part
const toolNames = toolSections.map((section) => section.split("\n")[0].trim())
// Verify experimental tools are included in the prompt when enabled
expect(prompt).toContain(EXPERIMENT_IDS.SEARCH_AND_REPLACE)
expect(prompt).toContain(EXPERIMENT_IDS.INSERT_BLOCK)
expect(toolNames).toContain("search_and_replace")
expect(toolNames).toContain("insert_content")
expect(prompt).toMatchSnapshot()
})
it("should selectively enable experimental tools", async () => {
const experiments = {
// Set experiments for testing selective enabling
const experimentsSelective = {
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
}
// Reset default experiments
experiments = undefined
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
@ -527,15 +573,19 @@ describe("SYSTEM_PROMPT", () => {
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
experimentsSelective, // Use the selective experiments
true, // enableMcpServerCreation
)
// Get all tool sections
const toolSections = prompt.split("## ").slice(1) // Split by section headers and remove first non-tool part
const toolNames = toolSections.map((section) => section.split("\n")[0].trim())
// Verify only enabled experimental tools are included
expect(prompt).toContain(EXPERIMENT_IDS.SEARCH_AND_REPLACE)
expect(prompt).not.toContain(EXPERIMENT_IDS.INSERT_BLOCK)
expect(toolNames).toContain("search_and_replace")
expect(toolNames).not.toContain("insert_content")
expect(prompt).toMatchSnapshot()
})
it("should list all available editing tools in base instruction", async () => {
@ -555,9 +605,8 @@ describe("SYSTEM_PROMPT", () => {
undefined,
undefined,
undefined,
undefined,
true, // diffEnabled
experiments,
experiments, // experiments
true, // enableMcpServerCreation
)
@ -567,7 +616,6 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt).toContain("insert_content (for adding lines to existing files)")
expect(prompt).toContain("search_and_replace (for finding and replacing individual pieces of text)")
})
it("should provide detailed instructions for each enabled tool", async () => {
const experiments = {
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
@ -585,8 +633,7 @@ describe("SYSTEM_PROMPT", () => {
undefined,
undefined,
undefined,
undefined,
true,
true, // diffEnabled
experiments,
true, // enableMcpServerCreation
)
@ -606,7 +653,7 @@ describe("SYSTEM_PROMPT", () => {
})
describe("addCustomInstructions", () => {
let experiments: Record<string, boolean>
let experiments: Record<string, boolean> | undefined
beforeAll(() => {
// Ensure fs mock is properly initialized
const mockFs = jest.requireMock("fs/promises")
@ -619,10 +666,8 @@ describe("addCustomInstructions", () => {
throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`)
})
experiments = {
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
}
// Initialize experiments as undefined by default
experiments = undefined
})
beforeEach(() => {
@ -640,10 +685,9 @@ describe("addCustomInstructions", () => {
"architect", // mode
undefined, // customModePrompts
undefined, // customModes
undefined,
undefined,
undefined,
experiments,
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
true, // enableMcpServerCreation
)
@ -661,10 +705,9 @@ describe("addCustomInstructions", () => {
"ask", // mode
undefined, // customModePrompts
undefined, // customModes
undefined,
undefined,
undefined,
experiments,
undefined, // globalCustomInstructions
undefined, // diffEnabled
undefined, // experiments
true, // enableMcpServerCreation
)
@ -685,9 +728,8 @@ describe("addCustomInstructions", () => {
undefined, // customModePrompts
undefined, // customModes,
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
undefined, // experiments
true, // enableMcpServerCreation
)
@ -709,9 +751,8 @@ describe("addCustomInstructions", () => {
undefined, // customModePrompts
undefined, // customModes,
undefined, // globalCustomInstructions
undefined, // preferredLanguage
undefined, // diffEnabled
experiments,
undefined, // experiments
false, // enableMcpServerCreation
)
@ -751,7 +792,7 @@ describe("addCustomInstructions", () => {
it("should include preferred language when provided", async () => {
const instructions = await addCustomInstructions("", "", "/test/path", defaultModeSlug, {
preferredLanguage: "Spanish",
vscodeLanguage: "es",
})
expect(instructions).toMatchSnapshot()
})
@ -767,7 +808,7 @@ describe("addCustomInstructions", () => {
"",
"/test/path",
defaultModeSlug,
{ preferredLanguage: "French" },
{ vscodeLanguage: "fr" },
)
expect(instructions).toMatchSnapshot()
})

View file

@ -113,11 +113,11 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
{ preferredLanguage: "Spanish" },
{ vscodeLanguage: "es" },
)
expect(result).toContain("Language Preference:")
expect(result).toContain("Spanish")
expect(result).toContain("es")
expect(result).toContain("Global Instructions:\nglobal instructions")
expect(result).toContain("Mode-specific Instructions:\nmode instructions")
expect(result).toContain("Rules from .clinerules-test-mode:\nmode specific rules")

View file

@ -1,5 +1,6 @@
import fs from "fs/promises"
import path from "path"
import * as vscode from "vscode"
async function safeReadFile(filePath: string): Promise<string> {
try {
@ -33,7 +34,7 @@ export async function addCustomInstructions(
globalCustomInstructions: string,
cwd: string,
mode: string,
options: { preferredLanguage?: string; rooIgnoreInstructions?: string } = {},
options: { vscodeLanguage?: string; rooIgnoreInstructions?: string } = {},
): Promise<string> {
const sections = []
@ -45,9 +46,9 @@ export async function addCustomInstructions(
}
// Add language preference if provided
if (options.preferredLanguage) {
if (options.vscodeLanguage) {
sections.push(
`Language Preference:\nYou should always speak and think in the ${options.preferredLanguage} language.`,
`Language Preference:\nYou should always speak and think in the "${options.vscodeLanguage}" language.`,
)
}

View file

@ -16,6 +16,7 @@ function getEditingInstructions(diffStrategy?: DiffStrategy, experiments?: Recor
} else {
availableTools.push("write_to_file (for creating new files or complete file rewrites)")
}
if (experiments?.["insert_content"]) {
availableTools.push("insert_content (for adding lines to existing files)")
}

View file

@ -37,9 +37,8 @@ async function generatePrompt(
promptComponent?: PromptComponent,
customModeConfigs?: ModeConfig[],
globalCustomInstructions?: string,
preferredLanguage?: string,
diffEnabled?: boolean,
experiments?: Record<string, boolean>,
experiments?: Record<string, boolean> | boolean | undefined,
enableMcpServerCreation?: boolean,
rooIgnoreInstructions?: string,
): Promise<string> {
@ -61,6 +60,12 @@ async function generatePrompt(
: Promise.resolve(""),
])
// Convert experiments to an object if it's a boolean
const experimentSettings =
typeof experiments === "boolean"
? {} // Empty object if experiments is just a boolean
: (experiments ?? {})
const basePrompt = `${roleDefinition}
${getSharedToolUseSection()}
@ -73,7 +78,7 @@ ${getToolDescriptionsForMode(
browserViewportSize,
mcpHub,
customModeConfigs,
experiments,
experimentSettings,
)}
${getToolUseGuidelinesSection()}
@ -84,13 +89,13 @@ ${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy
${modesSection}
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, experiments)}
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, experimentSettings)}
${getSystemInfoSection(cwd, mode, customModeConfigs)}
${getObjectiveSection()}
${await addCustomInstructions(promptComponent?.customInstructions || modeConfig.customInstructions || "", globalCustomInstructions || "", cwd, mode, { preferredLanguage, rooIgnoreInstructions })}`
${await addCustomInstructions(promptComponent?.customInstructions || modeConfig.customInstructions || "", globalCustomInstructions || "", cwd, mode, { vscodeLanguage: vscode.env.language, rooIgnoreInstructions })}`
return basePrompt
}
@ -106,9 +111,8 @@ export const SYSTEM_PROMPT = async (
customModePrompts?: CustomModePrompts,
customModes?: ModeConfig[],
globalCustomInstructions?: string,
preferredLanguage?: string,
diffEnabled?: boolean,
experiments?: Record<string, boolean>,
experiments?: Record<string, boolean> | boolean | undefined,
enableMcpServerCreation?: boolean,
rooIgnoreInstructions?: string,
): Promise<string> => {
@ -135,15 +139,28 @@ export const SYSTEM_PROMPT = async (
// If a file-based custom system prompt exists, use it
if (fileCustomSystemPrompt) {
const roleDefinition = promptComponent?.roleDefinition || currentMode.roleDefinition
const customInstructions = await addCustomInstructions(
promptComponent?.customInstructions || currentMode.customInstructions || "",
globalCustomInstructions || "",
cwd,
mode,
{ vscodeLanguage: vscode.env.language, rooIgnoreInstructions },
)
// For file-based prompts, don't include the tool sections
return `${roleDefinition}
${fileCustomSystemPrompt}
${await addCustomInstructions(promptComponent?.customInstructions || currentMode.customInstructions || "", globalCustomInstructions || "", cwd, mode, { preferredLanguage, rooIgnoreInstructions })}`
${customInstructions}`
}
// If diff is disabled, don't pass the diffStrategy
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined
// Convert experiments to an object if it's a boolean
const experimentSettings =
typeof experiments === "boolean"
? {} // Empty object if experiments is just a boolean
: (experiments ?? {})
return generatePrompt(
context,
@ -156,9 +173,8 @@ ${await addCustomInstructions(promptComponent?.customInstructions || currentMode
promptComponent,
customModes,
globalCustomInstructions,
preferredLanguage,
diffEnabled,
experiments,
experimentSettings,
enableMcpServerCreation,
rooIgnoreInstructions,
)

View file

@ -61,13 +61,15 @@ export function getToolDescriptionsForMode(
const tools = new Set<string>()
const experimentSettings = experiments ?? {}
// Add tools from mode's groups
config.groups.forEach((groupEntry) => {
const groupName = getGroupName(groupEntry)
const toolGroup = TOOL_GROUPS[groupName]
if (toolGroup) {
toolGroup.tools.forEach((tool) => {
if (isToolAllowedForMode(tool as ToolName, mode, customModes ?? [], experiments ?? {})) {
if (isToolAllowedForMode(tool as ToolName, mode, customModes ?? [], experimentSettings)) {
tools.add(tool)
}
})

View file

@ -1389,10 +1389,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("rateLimitSeconds", message.value ?? 0)
await this.postStateToWebview()
break
case "preferredLanguage":
await this.updateGlobalState("preferredLanguage", message.text)
await this.postStateToWebview()
break
case "writeDelayMs":
await this.updateGlobalState("writeDelayMs", message.value)
await this.postStateToWebview()
@ -1930,7 +1926,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
apiConfiguration,
customModePrompts,
customInstructions,
preferredLanguage,
browserViewportSize,
diffEnabled,
mcpEnabled,
@ -1968,7 +1963,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
customModePrompts,
customModes,
customInstructions,
preferredLanguage,
diffEnabled,
experiments,
enableMcpServerCreation,
@ -2344,7 +2338,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
screenshotQuality,
remoteBrowserHost,
remoteBrowserEnabled,
preferredLanguage,
writeDelayMs,
terminalOutputLineLimit,
fuzzyMatchThreshold,
@ -2404,7 +2397,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
screenshotQuality: screenshotQuality ?? 75,
remoteBrowserHost,
remoteBrowserEnabled: remoteBrowserEnabled ?? false,
preferredLanguage: preferredLanguage ?? "English",
writeDelayMs: writeDelayMs ?? 1000,
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
@ -2563,37 +2555,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
writeDelayMs: stateValues.writeDelayMs ?? 1000,
terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
mode: stateValues.mode ?? defaultModeSlug,
preferredLanguage:
stateValues.preferredLanguage ??
(() => {
// Get VSCode's locale setting
const vscodeLang = vscode.env.language
// Map VSCode locale to our supported languages
const langMap: { [key: string]: string } = {
en: "English",
ar: "Arabic",
"pt-br": "Brazilian Portuguese",
ca: "Catalan",
cs: "Czech",
fr: "French",
de: "German",
hi: "Hindi",
hu: "Hungarian",
it: "Italian",
ja: "Japanese",
ko: "Korean",
pl: "Polish",
pt: "Portuguese",
ru: "Russian",
zh: "Simplified Chinese",
"zh-cn": "Simplified Chinese",
es: "Spanish",
"zh-tw": "Traditional Chinese",
tr: "Turkish",
}
// Return mapped language or default to English
return langMap[vscodeLang] ?? langMap[vscodeLang.split("-")[0]] ?? "English"
})(),
// Pass the VSCode language code directly
vscodeLanguage: vscode.env.language,
mcpEnabled: stateValues.mcpEnabled ?? true,
enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true,
alwaysApproveResubmit: stateValues.alwaysApproveResubmit ?? false,

View file

@ -420,7 +420,6 @@ describe("ClineProvider", () => {
const mockState: ExtensionState = {
version: "1.0.0",
preferredLanguage: "English",
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
@ -535,20 +534,12 @@ describe("ClineProvider", () => {
expect(state).toHaveProperty("writeDelayMs")
})
test("preferredLanguage defaults to VSCode language when not set", async () => {
test("vscodeLanguage is set to VSCode language", async () => {
// Mock VSCode language as Spanish
;(vscode.env as any).language = "es-ES"
const state = await provider.getState()
expect(state.preferredLanguage).toBe("Spanish")
})
test("preferredLanguage defaults to English for unsupported VSCode language", async () => {
// Mock VSCode language as an unsupported language
;(vscode.env as any).language = "unsupported-LANG"
const state = await provider.getState()
expect(state.preferredLanguage).toBe("English")
expect(state.vscodeLanguage).toBe("es-ES")
})
test("diffEnabled defaults to true when not set", async () => {
@ -1216,7 +1207,7 @@ describe("ClineProvider", () => {
expect(callArgs[4]).toHaveProperty("getToolDescription") // diffStrategy
expect(callArgs[5]).toBe("900x600") // browserViewportSize
expect(callArgs[6]).toBe("code") // mode
expect(callArgs[11]).toBe(true) // diffEnabled
expect(callArgs[10]).toBe(true) // diffEnabled
// Run the test again to verify it's consistent
await handler({ type: "getSystemPrompt", mode: "code" })
@ -1274,7 +1265,7 @@ describe("ClineProvider", () => {
expect(callArgs[4]).toHaveProperty("getToolDescription") // diffStrategy
expect(callArgs[5]).toBe("900x600") // browserViewportSize
expect(callArgs[6]).toBe("code") // mode
expect(callArgs[11]).toBe(false) // diffEnabled should be false
expect(callArgs[10]).toBe(false) // diffEnabled should be false
})
test("uses correct mode-specific instructions when mode is specified", async () => {

View file

@ -164,7 +164,6 @@ export type GlobalStateKey =
| "screenshotQuality"
| "remoteBrowserHost"
| "fuzzyMatchThreshold"
| "preferredLanguage" // Language setting for Cline's communication
| "writeDelayMs"
| "terminalOutputLineLimit"
| "mcpEnabled"

View file

@ -131,7 +131,7 @@ export interface ExtensionState {
remoteBrowserHost?: string
remoteBrowserEnabled?: boolean
fuzzyMatchThreshold?: number
preferredLanguage: string
vscodeLanguage?: string
writeDelayMs: number
terminalOutputLineLimit?: number
mcpEnabled: boolean

View file

@ -64,7 +64,6 @@ export interface WebviewMessage {
| "toggleMcpServer"
| "updateMcpTimeout"
| "fuzzyMatchThreshold"
| "preferredLanguage"
| "writeDelayMs"
| "enhancePrompt"
| "enhancedPrompt"

View file

@ -6,7 +6,6 @@ jest.mock("../../core/prompts/sections/custom-instructions", () => ({
}))
import { isToolAllowedForMode, FileRestrictionError, ModeConfig, getFullModeDetails, modes } from "../modes"
import * as vscode from "vscode"
import { addCustomInstructions } from "../../core/prompts/sections/custom-instructions"
describe("isToolAllowedForMode", () => {
@ -402,7 +401,7 @@ describe("FileRestrictionError", () => {
const options = {
cwd: "/test/path",
globalCustomInstructions: "Global instructions",
preferredLanguage: "en",
vscodeLanguage: "en",
}
await getFullModeDetails("debug", undefined, undefined, options)
@ -412,7 +411,7 @@ describe("FileRestrictionError", () => {
"Global instructions",
"/test/path",
"debug",
{ preferredLanguage: "en" },
{ vscodeLanguage: "en" },
)
})

View file

@ -82,7 +82,6 @@ export const GLOBAL_STATE_KEYS = [
"screenshotQuality",
"remoteBrowserHost",
"fuzzyMatchThreshold",
"preferredLanguage", // Language setting for Cline's communication.
"writeDelayMs",
"terminalOutputLineLimit",
"mcpEnabled",

View file

@ -195,10 +195,13 @@ export function isToolAllowedForMode(
}
// Check tool requirements if any exist
if (toolRequirements && tool in toolRequirements) {
if (!toolRequirements[tool]) {
if (toolRequirements && typeof toolRequirements === "object") {
if (tool in toolRequirements && !toolRequirements[tool]) {
return false
}
} else if (toolRequirements === false) {
// If toolRequirements is a boolean false, all tools are disabled
return false
}
const mode = getModeBySlug(modeSlug, customModes)
@ -275,7 +278,7 @@ export async function getFullModeDetails(
options?: {
cwd?: string
globalCustomInstructions?: string
preferredLanguage?: string
vscodeLanguage?: string
},
): Promise<ModeConfig> {
// First get the base mode config from custom modes or built-in modes
@ -295,7 +298,7 @@ export async function getFullModeDetails(
options.globalCustomInstructions || "",
options.cwd,
modeSlug,
{ preferredLanguage: options.preferredLanguage },
{ vscodeLanguage: options.vscodeLanguage },
)
}

View file

@ -9,19 +9,6 @@ import {
VSCodeRadioGroup,
VSCodeRadio,
} from "@vscode/webview-ui-toolkit/react"
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
import {
Button,
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui"
import { cn } from "@/lib/utils"
import { useExtensionState } from "../../context/ExtensionStateContext"
import {
Mode,
@ -69,8 +56,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
mode,
customInstructions,
setCustomInstructions,
preferredLanguage,
setPreferredLanguage,
customModes,
enableCustomModeCreation,
setEnableCustomModeCreation,
@ -82,9 +67,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
const [testPrompt, setTestPrompt] = useState("")
const [isEnhancing, setIsEnhancing] = useState(false)
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [open, setOpen] = useState(false)
const [isCustomLanguage, setIsCustomLanguage] = useState(false)
const [customLanguage, setCustomLanguage] = useState("")
const [selectedPromptContent, setSelectedPromptContent] = useState("")
const [selectedPromptTitle, setSelectedPromptTitle] = useState("")
const [isToolsEditMode, setIsToolsEditMode] = useState(false)
@ -428,93 +410,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
<TabContent>
<div className="pb-5 border-b border-vscode-input-border">
<div className="mb-5">
<div className="font-bold mb-1">Preferred Language</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="combobox"
role="combobox"
aria-expanded={open}
className="w-full justify-between">
{preferredLanguage ?? "Select language..."}
<CaretSortIcon className="opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="p-0">
<Command>
<CommandInput placeholder="Search language..." className="h-9" />
<CommandList>
<CommandGroup>
{[
{ value: "English", label: "English" },
{ value: "Arabic", label: "Arabic - العربية" },
{
value: "Brazilian Portuguese",
label: "Portuguese - Português (Brasil)",
},
{ value: "Catalan", label: "Catalan - Català" },
{ value: "Czech", label: "Czech - Čeština" },
{ value: "French", label: "French - Français" },
{ value: "German", label: "German - Deutsch" },
{ value: "Hindi", label: "Hindi - हिन्दी" },
{ value: "Hungarian", label: "Hungarian - Magyar" },
{ value: "Italian", label: "Italian - Italiano" },
{ value: "Japanese", label: "Japanese - 日本語" },
{ value: "Korean", label: "Korean - 한국어" },
{ value: "Polish", label: "Polish - Polski" },
{ value: "Portuguese", label: "Portuguese - Português (Portugal)" },
{ value: "Russian", label: "Russian - Русский" },
{ value: "Simplified Chinese", label: "Simplified Chinese - 简体中文" },
{ value: "Spanish", label: "Spanish - Español" },
{
value: "Traditional Chinese",
label: "Traditional Chinese - 繁體中文",
},
{ value: "Turkish", label: "Turkish - Türkçe" },
].map((language) => (
<CommandItem
key={language.value}
value={language.value}
onSelect={(value) => {
setPreferredLanguage(value)
vscode.postMessage({
type: "preferredLanguage",
text: value,
})
setOpen(false)
}}>
{language.label}
<CheckIcon
className={cn(
"ml-auto",
preferredLanguage === language.value
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
<div className="border-t border-[var(--vscode-input-border)]">
<button
className="w-full px-2 py-1.5 text-sm text-left hover:bg-[var(--vscode-list-hoverBackground)]"
onClick={() => {
setIsCustomLanguage(true)
setOpen(false)
}}>
+ Choose another language
</button>
</div>
</PopoverContent>
</Popover>
<p className="text-xs mt-1.5 text-vscode-descriptionForeground">
Select the language that Roo should use for communication.
</p>
</div>
<div className="font-bold mb-1">Custom Instructions for All Modes</div>
<div className="text-sm text-vscode-descriptionForeground mb-2">
These instructions apply to all modes. They provide a base set of behaviors that can be enhanced
@ -1509,41 +1404,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
)}
{isCustomLanguage && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-[var(--vscode-editor-background)] p-6 rounded-lg w-96">
<h3 className="text-lg font-semibold mb-4">Add Custom Language</h3>
<input
type="text"
className="w-full p-2 mb-4 bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border border-[var(--vscode-input-border)] rounded"
placeholder="Enter language name"
value={customLanguage}
onChange={(e) => setCustomLanguage(e.target.value)}
/>
<div className="flex justify-end gap-2">
<Button variant="secondary" onClick={() => setIsCustomLanguage(false)}>
Cancel
</Button>
<Button
onClick={() => {
if (customLanguage.trim()) {
setPreferredLanguage(customLanguage.trim())
vscode.postMessage({
type: "preferredLanguage",
text: customLanguage.trim(),
})
setIsCustomLanguage(false)
setCustomLanguage("")
}
}}
disabled={!customLanguage.trim()}>
Add
</Button>
</div>
</div>
</div>
)}
</Tab>
)
}

View file

@ -39,8 +39,6 @@ export interface ExtensionStateContextType extends ExtensionState {
setEnableCheckpoints: (value: boolean) => void
setBrowserViewportSize: (value: string) => void
setFuzzyMatchThreshold: (value: number) => void
preferredLanguage: string
setPreferredLanguage: (value: string) => void
setWriteDelayMs: (value: number) => void
screenshotQuality?: number
setScreenshotQuality: (value: number) => void
@ -118,7 +116,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
enableCheckpoints: true,
checkpointStorage: "task",
fuzzyMatchThreshold: 1.0,
preferredLanguage: "English",
vscodeLanguage: "en", // Default language code
enableCustomModeCreation: true,
writeDelayMs: 1000,
browserViewportSize: "900x600",
@ -260,7 +258,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setBrowserViewportSize: (value: string) =>
setState((prevState) => ({ ...prevState, browserViewportSize: value })),
setFuzzyMatchThreshold: (value) => setState((prevState) => ({ ...prevState, fuzzyMatchThreshold: value })),
setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })),
setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })),
setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })),
setTerminalOutputLineLimit: (value) =>

View file

@ -109,7 +109,6 @@ describe("mergeExtensionState", () => {
shouldShowAnnouncement: false,
enableCheckpoints: true,
checkpointStorage: "task",
preferredLanguage: "English",
writeDelayMs: 1000,
requestDelaySeconds: 5,
rateLimitSeconds: 0,