mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Merge branch 'main' into feature/add_sse_mcp
This commit is contained in:
commit
0d0f7e37a6
31 changed files with 4346 additions and 4989 deletions
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"roo-cline": patch
|
||||
---
|
||||
|
||||
Add o3-mini support to openai compatible
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const copyWasmFiles = {
|
|||
"java",
|
||||
"php",
|
||||
"swift",
|
||||
"kotlin",
|
||||
]
|
||||
|
||||
languages.forEach((lang) => {
|
||||
|
|
|
|||
|
|
@ -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
8982
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
13
src/__mocks__/strip-bom.js
Normal file
13
src/__mocks__/strip-bom.js
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
28
src/services/tree-sitter/queries/kotlin.ts
Normal file
28
src/services/tree-sitter/queries/kotlin.ts
Normal 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
|
||||
`
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
isn’t 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 })),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue