Merge branch 'main' into feature/add_sse_mcp

This commit is contained in:
aheizi 2025-03-10 12:46:11 +08:00
commit 0d0f7e37a6
31 changed files with 4346 additions and 4989 deletions

View file

@ -1,5 +0,0 @@
---
"roo-cline": patch
---
Add o3-mini support to openai compatible

View file

@ -1,5 +1,22 @@
# Roo Code Changelog
## [3.8.4] - 2025-03-09
- Roll back multi-diff progress indicator temporarily to fix a double-confirmation in saving edits
- Add an option in the prompts tab to save tokens by disabling the ability to ask Roo to create/edit custom modes for you (thanks @hannesrudolph!)
## [3.8.3] - 2025-03-09
- Fix VS Code LM API model picker truncation issue
## [3.8.2] - 2025-03-08
- Create an auto-approval toggle for subtask creation and completion (thanks @shaybc!)
- Show a progress indicator when using the multi-diff editing strategy (thanks @qdaxb!)
- Add o3-mini support to the OpenAI-compatible provider (thanks @yt3trees!)
- Fix encoding issue where unreadable characters were sometimes getting added to the beginning of files
- Fix issue where settings dropdowns were getting truncated in some cases
## [3.8.1] - 2025-03-07
- Show the reserved output tokens in the context window visualization

View file

@ -52,6 +52,7 @@ const copyWasmFiles = {
"java",
"php",
"swift",
"kotlin",
]
languages.forEach((lang) => {

View file

@ -30,9 +30,10 @@ module.exports = {
"^strip-ansi$": "<rootDir>/src/__mocks__/strip-ansi.js",
"^default-shell$": "<rootDir>/src/__mocks__/default-shell.js",
"^os-name$": "<rootDir>/src/__mocks__/os-name.js",
"^strip-bom$": "<rootDir>/src/__mocks__/strip-bom.js",
},
transformIgnorePatterns: [
"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name)/)",
"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name|strip-bom)/)",
],
roots: ["<rootDir>/src", "<rootDir>/webview-ui/src"],
modulePathIgnorePatterns: [".vscode-test"],

8982
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
"displayName": "Roo Code (prev. Roo Cline)",
"description": "A whole dev team of AI agents in your editor.",
"publisher": "RooVeterinaryInc",
"version": "3.8.1",
"version": "3.8.4",
"icon": "assets/icons/rocket.png",
"galleryBanner": {
"color": "#617A91",
@ -305,6 +305,7 @@
"sound-play": "^1.1.0",
"string-similarity": "^4.0.4",
"strip-ansi": "^7.1.0",
"strip-bom": "^5.0.0",
"tmp": "^0.2.3",
"tree-sitter-wasms": "^0.1.11",
"turndown": "^7.2.0",

View file

@ -0,0 +1,13 @@
// Mock implementation of strip-bom
module.exports = function stripBom(string) {
if (typeof string !== "string") {
throw new TypeError("Expected a string")
}
// Removes UTF-8 BOM
if (string.charCodeAt(0) === 0xfeff) {
return string.slice(1)
}
return string
}

View file

@ -1417,6 +1417,18 @@ export class Cline {
return true
}
const askFinishSubTaskApproval = async () => {
// ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished
// and return control to the parent task to continue running the rest of the sub-tasks
const toolMessage = JSON.stringify({
tool: "finishTask",
content:
"Subtask completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to return the result to the parent task.",
})
return await askApproval("tool", toolMessage)
}
const handleError = async (action: string, error: Error) => {
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
await this.say(
@ -2945,13 +2957,6 @@ export class Cline {
// havent sent a command message yet so first send completion_result then command
await this.say("completion_result", result, undefined, false)
telemetryService.captureTaskCompleted(this.taskId)
if (this.isSubTask) {
// tell the provider to remove the current subtask and resume the previous task in the stack
await this.providerRef
.deref()
?.finishSubTask(`Task complete: ${lastMessage?.text}`)
break
}
}
// complete command message
@ -2970,13 +2975,17 @@ export class Cline {
} else {
await this.say("completion_result", result, undefined, false)
telemetryService.captureTaskCompleted(this.taskId)
if (this.isSubTask) {
// tell the provider to remove the current subtask and resume the previous task in the stack
await this.providerRef
.deref()
?.finishSubTask(`Task complete: ${lastMessage?.text}`)
}
if (this.isSubTask) {
const didApprove = await askFinishSubTaskApproval()
if (!didApprove) {
break
}
// tell the provider to remove the current subtask and resume the previous task in the stack
await this.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`)
break
}
// we already sent completion_result says, an empty string asks relinquishes control over button and field

View file

@ -198,9 +198,9 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
}
}
function getWorkspaceProblems(cwd: string): string {
async function getWorkspaceProblems(cwd: string): Promise<string> {
const diagnostics = vscode.languages.getDiagnostics()
const result = diagnosticsToProblemsString(
const result = await diagnosticsToProblemsString(
diagnostics,
[vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning],
cwd,

View file

@ -11,12 +11,19 @@ export async function getModesSection(context: vscode.ExtensionContext): Promise
// Get all modes with their overrides from extension state
const allModes = await getAllModesWithPrompts(context)
return `====
// Get enableCustomModeCreation setting from extension state
const shouldEnableCustomModeCreation = (await context.globalState.get<boolean>("enableCustomModeCreation")) ?? true
let modesContent = `====
MODES
- These are the currently available modes:
${allModes.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")}
${allModes.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")}`
// Only include custom modes documentation if the feature is enabled
if (shouldEnableCustomModeCreation) {
modesContent += `
- Custom modes can be configured in two ways:
1. Globally via '${customModesPath}' (created automatically on startup)
@ -56,4 +63,7 @@ Both files should follow this structure:
}
]
}`
}
return modesContent
}

View file

@ -984,6 +984,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("alwaysAllowModeSwitch", message.bool)
await this.postStateToWebview()
break
case "alwaysAllowSubtasks":
await this.updateGlobalState("alwaysAllowSubtasks", message.bool)
await this.postStateToWebview()
break
case "askResponse":
this.getCurrentCline()?.handleWebviewAskResponse(
message.askResponse!,
@ -993,9 +997,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
break
case "clearTask":
// clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
await this.finishSubTask(
`new_task finished with an error!, it was stopped and canceled by the user.`,
)
await this.finishSubTask(`Task error: It was stopped and canceled by the user.`)
await this.postStateToWebview()
break
case "didShowAnnouncement":
@ -1476,6 +1478,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("enhancementApiConfigId", message.text)
await this.postStateToWebview()
break
case "enableCustomModeCreation":
await this.updateGlobalState("enableCustomModeCreation", message.bool ?? true)
await this.postStateToWebview()
break
case "autoApprovalEnabled":
await this.updateGlobalState("autoApprovalEnabled", message.bool ?? false)
await this.postStateToWebview()
@ -2177,6 +2183,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
soundEnabled,
diffEnabled,
enableCheckpoints,
@ -2224,6 +2231,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowBrowser: alwaysAllowBrowser ?? false,
alwaysAllowMcp: alwaysAllowMcp ?? false,
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
alwaysAllowSubtasks: alwaysAllowSubtasks ?? false,
uriScheme: vscode.env.uriScheme,
currentTaskItem: this.getCurrentCline()?.taskId
? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
@ -2385,6 +2393,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false,
taskHistory: stateValues.taskHistory,
allowedCommands: stateValues.allowedCommands,
soundEnabled: stateValues.soundEnabled ?? false,

View file

@ -70,11 +70,12 @@ export function getNewDiagnostics(
// // - New error in file3 (1:1)
// will return empty string if no problems with the given severity are found
export function diagnosticsToProblemsString(
export async function diagnosticsToProblemsString(
diagnostics: [vscode.Uri, vscode.Diagnostic[]][],
severities: vscode.DiagnosticSeverity[],
cwd: string,
): string {
): Promise<string> {
const documents = new Map<vscode.Uri, vscode.TextDocument>()
let result = ""
for (const [uri, fileDiagnostics] of diagnostics) {
const problems = fileDiagnostics.filter((d) => severities.includes(d.severity))
@ -100,7 +101,10 @@ export function diagnosticsToProblemsString(
}
const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
const source = diagnostic.source ? `${diagnostic.source} ` : ""
result += `\n- [${source}${label}] Line ${line}: ${diagnostic.message}`
const document = documents.get(uri) || (await vscode.workspace.openTextDocument(uri))
documents.set(uri, document)
const lineContent = document.lineAt(diagnostic.range.start.line).text
result += `\n- [${source}${label}] ${line} | ${lineContent} : ${diagnostic.message}`
}
}
}

View file

@ -7,6 +7,7 @@ import { formatResponse } from "../../core/prompts/responses"
import { DecorationController } from "./DecorationController"
import * as diff from "diff"
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
import stripBom from "strip-bom"
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
@ -104,7 +105,7 @@ export class DiffViewProvider {
const edit = new vscode.WorkspaceEdit()
const rangeToReplace = new vscode.Range(0, 0, endLine + 1, 0)
const contentToReplace = accumulatedLines.slice(0, endLine + 1).join("\n") + "\n"
edit.replace(document.uri, rangeToReplace, contentToReplace)
edit.replace(document.uri, rangeToReplace, this.stripAllBOMs(contentToReplace))
await vscode.workspace.applyEdit(edit)
// Update decorations
this.activeLineController.setActiveLine(endLine)
@ -128,7 +129,11 @@ export class DiffViewProvider {
}
// Apply the final content
const finalEdit = new vscode.WorkspaceEdit()
finalEdit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), accumulatedContent)
finalEdit.replace(
document.uri,
new vscode.Range(0, 0, document.lineCount, 0),
this.stripAllBOMs(accumulatedContent),
)
await vscode.workspace.applyEdit(finalEdit)
// Clear all decorations at the end (after applying final edit)
this.fadedOverlayController.clear()
@ -172,7 +177,7 @@ export class DiffViewProvider {
initial fix is usually correct and it may just take time for linters to catch up.
*/
const postDiagnostics = vscode.languages.getDiagnostics()
const newProblems = diagnosticsToProblemsString(
const newProblems = await diagnosticsToProblemsString(
getNewDiagnostics(this.preDiagnostics, postDiagnostics),
[
vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
@ -336,6 +341,16 @@ export class DiffViewProvider {
}
}
private stripAllBOMs(input: string): string {
let result = input
let previous
do {
previous = result
result = stripBom(result)
} while (result !== previous)
return result
}
// close editor if open?
async reset() {
this.editType = undefined

View file

@ -34,7 +34,7 @@ export async function listFiles(dirPath: string, recursive: boolean, limit: numb
"pkg",
"Pods",
".*", // '!**/.*' excludes hidden directories, while '!**/.*/**' excludes only their contents. This way we are at least aware of the existence of hidden directories.
].map((dir) => `**/${dir}/**`)
].map((dir) => `${dirPath}/**/${dir}/**`)
const options = {
cwd: dirPath,

View file

@ -169,6 +169,8 @@ describe("Tree-sitter Service", () => {
"/test/path/main.rs",
"/test/path/program.cpp",
"/test/path/code.go",
"/test/path/app.kt",
"/test/path/script.kts",
]
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
@ -197,6 +199,8 @@ describe("Tree-sitter Service", () => {
rs: { parser: mockParser, query: mockQuery },
cpp: { parser: mockParser, query: mockQuery },
go: { parser: mockParser, query: mockQuery },
kt: { parser: mockParser, query: mockQuery },
kts: { parser: mockParser, query: mockQuery },
})
;(fs.readFile as jest.Mock).mockResolvedValue("function test() {}")
@ -207,6 +211,8 @@ describe("Tree-sitter Service", () => {
expect(result).toContain("main.rs")
expect(result).toContain("program.cpp")
expect(result).toContain("code.go")
expect(result).toContain("app.kt")
expect(result).toContain("script.kts")
})
it("should normalize paths in output", async () => {

View file

@ -92,6 +92,17 @@ describe("Language Parser", () => {
expect(parsers.hpp).toBeDefined()
})
it("should handle Kotlin files correctly", async () => {
const files = ["test.kt", "test.kts"]
const parsers = await loadRequiredLanguageParsers(files)
expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-kotlin.wasm"))
expect(parsers.kt).toBeDefined()
expect(parsers.kts).toBeDefined()
expect(parsers.kt.query).toBeDefined()
expect(parsers.kts.query).toBeDefined()
})
it("should throw error for unsupported file extensions", async () => {
const files = ["test.unsupported"]

View file

@ -80,6 +80,9 @@ function separateFiles(allFiles: string[]): { filesToParse: string[]; remainingF
"java",
"php",
"swift",
// Kotlin
"kt",
"kts",
].map((e) => `.${e}`)
const filesToParse = allFiles.filter((file) => extensions.includes(path.extname(file))).slice(0, 50) // 50 files max
const remainingFiles = allFiles.filter((file) => !filesToParse.includes(file))

View file

@ -13,6 +13,7 @@ import {
javaQuery,
phpQuery,
swiftQuery,
kotlinQuery,
} from "./queries"
export interface LanguageParser {
@ -120,6 +121,11 @@ export async function loadRequiredLanguageParsers(filesToParse: string[]): Promi
language = await loadLanguage("swift")
query = language.query(swiftQuery)
break
case "kt":
case "kts":
language = await loadLanguage("kotlin")
query = language.query(kotlinQuery)
break
default:
throw new Error(`Unsupported language: ${ext}`)
}

View file

@ -10,3 +10,4 @@ export { default as cQuery } from "./c"
export { default as csharpQuery } from "./c-sharp"
export { default as goQuery } from "./go"
export { default as swiftQuery } from "./swift"
export { default as kotlinQuery } from "./kotlin"

View file

@ -0,0 +1,28 @@
/*
- class declarations (including interfaces)
- function declarations
- object declarations
- property declarations
- type alias declarations
*/
export default `
(class_declaration
(type_identifier) @name.definition.class
) @definition.class
(function_declaration
(simple_identifier) @name.definition.function
) @definition.function
(object_declaration
(type_identifier) @name.definition.object
) @definition.object
(property_declaration
(simple_identifier) @name.definition.property
) @definition.property
(type_alias
(type_identifier) @name.definition.type
) @definition.type
`

View file

@ -109,6 +109,7 @@ export interface ExtensionState {
alwaysAllowMcp?: boolean
alwaysApproveResubmit?: boolean
alwaysAllowModeSwitch?: boolean
alwaysAllowSubtasks?: boolean
browserToolEnabled?: boolean
requestDelaySeconds: number
rateLimitSeconds: number // Minimum time between successive requests (0 = disabled)
@ -128,6 +129,7 @@ export interface ExtensionState {
terminalOutputLimit?: number
mcpEnabled: boolean
enableMcpServerCreation: boolean
enableCustomModeCreation?: boolean
mode: Mode
modeApiConfigs?: Record<Mode, string>
enhancementApiConfigId?: string
@ -168,6 +170,7 @@ export type ClineAsk =
| "mistake_limit_reached"
| "browser_action_launch"
| "use_mcp_server"
| "finishTask"
export type ClineSay =
| "task"
@ -207,6 +210,7 @@ export interface ClineSayTool {
| "searchFiles"
| "switchMode"
| "newTask"
| "finishTask"
path?: string
diff?: string
content?: string

View file

@ -48,6 +48,7 @@ export interface WebviewMessage {
| "alwaysAllowBrowser"
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
| "alwaysAllowSubtasks"
| "playSound"
| "soundEnabled"
| "soundVolume"
@ -71,6 +72,7 @@ export interface WebviewMessage {
| "terminalOutputLimit"
| "mcpEnabled"
| "enableMcpServerCreation"
| "enableCustomModeCreation"
| "searchCommits"
| "alwaysApproveResubmit"
| "requestDelaySeconds"

View file

@ -40,6 +40,7 @@ export const GLOBAL_STATE_KEYS = [
"alwaysAllowBrowser",
"alwaysAllowMcp",
"alwaysAllowModeSwitch",
"alwaysAllowSubtasks",
"taskHistory",
"openAiBaseUrl",
"openAiModelId",
@ -84,6 +85,7 @@ export const GLOBAL_STATE_KEYS = [
"enhancementApiConfigId",
"experiments", // Map of experiment IDs to their enabled state
"autoApprovalEnabled",
"enableCustomModeCreation", // Enable the ability for Roo to create custom modes
"customModes", // Array of custom modes
"unboundModelId",
"requestyModelId",

View file

@ -30,6 +30,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowMcp,
alwaysAllowModeSwitch,
setAlwaysAllowModeSwitch,
alwaysAllowSubtasks,
setAlwaysAllowSubtasks,
alwaysApproveResubmit,
setAlwaysApproveResubmit,
autoApprovalEnabled,
@ -75,11 +77,17 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
},
{
id: "switchModes",
label: "Switch modes & create tasks",
label: "Switch modes",
shortName: "Modes",
enabled: alwaysAllowModeSwitch ?? false,
description:
"Allows automatic switching between different AI modes and creating new tasks without requiring approval.",
description: "Allows automatic switching between different modes without requiring approval.",
},
{
id: "subtasks",
label: "Create & complete subtasks",
shortName: "Subtasks",
enabled: alwaysAllowSubtasks ?? false,
description: "Allow creation and completion of subtasks without requiring approval.",
},
{
id: "retryRequests",
@ -136,6 +144,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue })
}, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch])
const handleSubtasksChange = useCallback(() => {
const newValue = !(alwaysAllowSubtasks ?? false)
setAlwaysAllowSubtasks(newValue)
vscode.postMessage({ type: "alwaysAllowSubtasks", bool: newValue })
}, [alwaysAllowSubtasks, setAlwaysAllowSubtasks])
const handleRetryChange = useCallback(() => {
const newValue = !(alwaysApproveResubmit ?? false)
setAlwaysApproveResubmit(newValue)
@ -150,6 +164,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
useBrowser: handleBrowserChange,
useMcp: handleMcpChange,
switchModes: handleModeSwitchChange,
subtasks: handleSubtasksChange,
retryRequests: handleRetryChange,
}

View file

@ -451,7 +451,7 @@ export const ChatRowContent = ({
<div style={headerStyle}>
{toolIcon("new-file")}
<span style={{ fontWeight: "bold" }}>
Roo wants to create a new task in <code>{tool.mode}</code> mode:
Roo wants to create a new subtask in <code>{tool.mode}</code> mode:
</span>
</div>
<div style={{ paddingLeft: "26px", marginTop: "4px" }}>
@ -459,6 +459,18 @@ export const ChatRowContent = ({
</div>
</>
)
case "finishTask":
return (
<>
<div style={headerStyle}>
{toolIcon("checklist")}
<span style={{ fontWeight: "bold" }}>Roo wants to finish this subtask</span>
</div>
<div style={{ paddingLeft: "26px", marginTop: "4px" }}>
<code>{tool.content}</code>
</div>
</>
)
default:
return null
}

View file

@ -61,6 +61,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setMode,
autoApprovalEnabled,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
customModes,
telemetrySetting,
} = useExtensionState()
@ -148,6 +149,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setPrimaryButtonText("Save")
setSecondaryButtonText("Reject")
break
case "finishTask":
setPrimaryButtonText("Complete Subtask and Return")
setSecondaryButtonText(undefined)
break
default:
setPrimaryButtonText("Approve")
setSecondaryButtonText("Reject")
@ -641,8 +646,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message)) ||
(alwaysAllowModeSwitch &&
message.ask === "tool" &&
(JSON.parse(message.text || "{}")?.tool === "switchMode" ||
JSON.parse(message.text || "{}")?.tool === "newTask"))
JSON.parse(message.text || "{}")?.tool === "switchMode") ||
(alwaysAllowSubtasks &&
message.ask === "tool" &&
["newTask", "finishTask"].includes(JSON.parse(message.text || "{}")?.tool))
)
},
[
@ -657,6 +664,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
alwaysAllowMcp,
isMcpToolAlwaysAllowed,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
],
)

View file

@ -71,6 +71,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
preferredLanguage,
setPreferredLanguage,
customModes,
enableCustomModeCreation,
setEnableCustomModeCreation,
} = useExtensionState()
// Memoize modes to preserve array order
@ -341,6 +343,17 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
return () => document.removeEventListener("click", handleClickOutside)
}, [showConfigMenu])
// Add effect to sync enableCustomModeCreation with backend
useEffect(() => {
if (enableCustomModeCreation !== undefined) {
// Send the value to the extension's global state
vscode.postMessage({
type: "enableCustomModeCreation", // Using dedicated message type
bool: enableCustomModeCreation,
})
}
}, [enableCustomModeCreation])
useEffect(() => {
const handler = (event: MessageEvent) => {
const message = event.data
@ -541,7 +554,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
in your workspace.
</div>
</div>
<div className="mt-5">
<div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
<h3 className="text-vscode-foreground m-0">Modes</h3>
@ -1010,6 +1022,35 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
)}
</div>
{/*
NOTE: This setting is placed in PromptsView rather than SettingsView since it
directly affects the functionality related to modes and custom mode creation,
which are managed in this component. This is an intentional deviation from
the standard pattern described in cline_docs/settings.md.
*/}
<div className="mb-4 mt-4">
<VSCodeCheckbox
checked={enableCustomModeCreation ?? true}
onChange={(e: any) => {
// Just update the local state through React context
// The React context will update the global state
setEnableCustomModeCreation(e.target.checked)
}}>
<span style={{ fontWeight: "500" }}>Enable Custom Mode Creation Through Prompts</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
When enabled, Roo allows you to create custom modes using prompts like Make me a custom
mode that. Disabling this reduces your system prompt by about 700 tokens when this feature
isnt needed. When disabled you can still manually create custom modes using the + button
above or by editing the related config JSON.
</p>
</div>
</div>
<div

View file

@ -1076,27 +1076,31 @@ const ApiOptions = ({
Language Model
</label>
{vsCodeLmModels.length > 0 ? (
<Dropdown
id="vscode-lm-model"
<Select
value={
apiConfiguration?.vsCodeLmModelSelector
? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}`
: ""
}
onChange={handleInputChange("vsCodeLmModelSelector", (e) => {
const valueStr = (e as DropdownOption)?.value
onValueChange={handleInputChange("vsCodeLmModelSelector", (valueStr) => {
const [vendor, family] = valueStr.split("/")
return { vendor, family }
})}
options={[
{ value: "", label: "Select a model..." },
...vsCodeLmModels.map((model) => ({
value: `${model.vendor}/${model.family}`,
label: `${model.vendor} - ${model.family}`,
})),
]}
className="w-full"
/>
})}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a model..." />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{vsCodeLmModels.map((model) => (
<SelectItem
key={`${model.vendor}/${model.family}`}
value={`${model.vendor}/${model.family}`}>
{`${model.vendor} - ${model.family}`}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
) : (
<div className="text-sm text-vscode-descriptionForeground">
The VS Code Language Model API allows you to run models provided by other VS Code

View file

@ -18,6 +18,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
requestDelaySeconds: number
alwaysAllowMcp?: boolean
alwaysAllowModeSwitch?: boolean
alwaysAllowSubtasks?: boolean
alwaysAllowExecute?: boolean
allowedCommands?: string[]
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
@ -32,6 +33,7 @@ export const AutoApproveSettings = ({
requestDelaySeconds,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysAllowExecute,
allowedCommands,
setCachedStateField,
@ -173,10 +175,21 @@ export const AutoApproveSettings = ({
<VSCodeCheckbox
checked={alwaysAllowModeSwitch}
onChange={(e: any) => setCachedStateField("alwaysAllowModeSwitch", e.target.checked)}>
<span className="font-medium">Always approve mode switching & task creation</span>
<span className="font-medium">Always approve mode switching</span>
</VSCodeCheckbox>
<p className="text-vscode-descriptionForeground text-sm mt-0">
Automatically switch between different AI modes and create new tasks without requiring approval
Automatically switch between different modes without requiring approval
</p>
</div>
<div>
<VSCodeCheckbox
checked={alwaysAllowSubtasks}
onChange={(e: any) => setCachedStateField("alwaysAllowSubtasks", e.target.checked)}>
<span className="font-medium">Always approve creation & completion of subtasks</span>
</VSCodeCheckbox>
<p className="text-vscode-descriptionForeground text-sm mt-0">
Allow creation and completion of subtasks without requiring approval
</p>
</div>

View file

@ -63,6 +63,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
alwaysAllowExecute,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysAllowWrite,
alwaysApproveResubmit,
browserToolEnabled,
@ -184,6 +185,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
vscode.postMessage({ type: "updateExperimental", values: experiments })
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks })
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
setChangeDetected(false)
@ -364,6 +366,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
requestDelaySeconds={requestDelaySeconds}
alwaysAllowMcp={alwaysAllowMcp}
alwaysAllowModeSwitch={alwaysAllowModeSwitch}
alwaysAllowSubtasks={alwaysAllowSubtasks}
alwaysAllowExecute={alwaysAllowExecute}
allowedCommands={allowedCommands}
setCachedStateField={setCachedStateField}

View file

@ -31,6 +31,7 @@ export interface ExtensionStateContextType extends ExtensionState {
setAlwaysAllowBrowser: (value: boolean) => void
setAlwaysAllowMcp: (value: boolean) => void
setAlwaysAllowModeSwitch: (value: boolean) => void
setAlwaysAllowSubtasks: (value: boolean) => void
setBrowserToolEnabled: (value: boolean) => void
setShowRooIgnoredFiles: (value: boolean) => void
setShowAnnouncement: (value: boolean) => void
@ -52,6 +53,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setMcpEnabled: (value: boolean) => void
enableMcpServerCreation: boolean
setEnableMcpServerCreation: (value: boolean) => void
enableCustomModeCreation?: boolean
setEnableCustomModeCreation: (value: boolean) => void
alwaysApproveResubmit?: boolean
setAlwaysApproveResubmit: (value: boolean) => void
requestDelaySeconds: number
@ -117,6 +120,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
checkpointStorage: "task",
fuzzyMatchThreshold: 1.0,
preferredLanguage: "English",
enableCustomModeCreation: true,
writeDelayMs: 1000,
browserViewportSize: "900x600",
screenshotQuality: 75,
@ -247,6 +251,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
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 })),
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
@ -273,6 +278,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setCustomSupportPrompts: (value) => setState((prevState) => ({ ...prevState, customSupportPrompts: value })),
setEnhancementApiConfigId: (value) =>
setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
setEnableCustomModeCreation: (value) =>
setState((prevState) => ({ ...prevState, enableCustomModeCreation: value })),
setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })),
setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })),