diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 253f98fa49..5b729a125f 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -36,6 +36,7 @@ export const globalSettingsSchema = z.object({ alwaysAllowReadOnlyOutsideWorkspace: z.boolean().optional(), alwaysAllowWrite: z.boolean().optional(), alwaysAllowWriteOutsideWorkspace: z.boolean().optional(), + alwaysAllowWriteProtected: z.boolean().optional(), writeDelayMs: z.number().optional(), alwaysAllowBrowser: z.boolean().optional(), alwaysApproveResubmit: z.boolean().optional(), @@ -177,6 +178,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { alwaysAllowReadOnlyOutsideWorkspace: false, alwaysAllowWrite: true, alwaysAllowWriteOutsideWorkspace: false, + alwaysAllowWriteProtected: false, writeDelayMs: 1000, alwaysAllowBrowser: true, alwaysApproveResubmit: true, diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index aebd1fe3ae..914f02ecd6 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -153,6 +153,7 @@ export const clineMessageSchema = z.object({ checkpoint: z.record(z.string(), z.unknown()).optional(), progressStatus: toolProgressStatusSchema.optional(), contextCondense: contextCondenseSchema.optional(), + isProtected: z.boolean().optional(), }) export type ClineMessage = z.infer diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 54916cf89f..21c973ab50 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -261,8 +261,15 @@ export async function presentAssistantMessage(cline: Task) { type: ClineAsk, partialMessage?: string, progressStatus?: ToolProgressStatus, + isProtected?: boolean, ) => { - const { response, text, images } = await cline.ask(type, partialMessage, false, progressStatus) + const { response, text, images } = await cline.ask( + type, + partialMessage, + false, + progressStatus, + isProtected || false, + ) if (response !== "yesButtonClicked") { // Handle both messageResponse and noButtonClicked with text. diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index dc6e08c8a0..3f38789fdc 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import * as path from "path" import * as diff from "diff" import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController" +import { RooProtectedController } from "../protect/RooProtectedController" export const formatResponse = { toolDenied: () => `The user denied this operation.`, @@ -95,6 +96,7 @@ Otherwise, if you have not completed the task and do not need additional informa didHitLimit: boolean, rooIgnoreController: RooIgnoreController | undefined, showRooIgnoredFiles: boolean, + rooProtectedController?: RooProtectedController, ): string => { const sorted = files .map((file) => { @@ -143,7 +145,13 @@ Otherwise, if you have not completed the task and do not need additional informa // Otherwise, mark it with a lock symbol rooIgnoreParsed.push(LOCK_TEXT_SYMBOL + " " + filePath) } else { - rooIgnoreParsed.push(filePath) + // Check if file is write-protected (only for non-ignored files) + const isWriteProtected = rooProtectedController?.isWriteProtected(absoluteFilePath) || false + if (isWriteProtected) { + rooIgnoreParsed.push("🛡️ " + filePath) + } else { + rooIgnoreParsed.push(filePath) + } } } } diff --git a/src/core/protect/RooProtectedController.ts b/src/core/protect/RooProtectedController.ts new file mode 100644 index 0000000000..b74b6a9bb9 --- /dev/null +++ b/src/core/protect/RooProtectedController.ts @@ -0,0 +1,103 @@ +import path from "path" +import ignore, { Ignore } from "ignore" + +export const SHIELD_SYMBOL = "\u{1F6E1}" + +/** + * Controls write access to Roo configuration files by enforcing protection patterns. + * Prevents auto-approved modifications to sensitive Roo configuration files. + */ +export class RooProtectedController { + private cwd: string + private ignoreInstance: Ignore + + // Predefined list of protected Roo configuration patterns + private static readonly PROTECTED_PATTERNS = [ + ".rooignore", + ".roomodes", + ".roorules*", + ".clinerules*", + ".roo/**", + ".rooprotected", // For future use + ] + + constructor(cwd: string) { + this.cwd = cwd + // Initialize ignore instance with protected patterns + this.ignoreInstance = ignore() + this.ignoreInstance.add(RooProtectedController.PROTECTED_PATTERNS) + } + + /** + * Check if a file is write-protected + * @param filePath - Path to check (relative to cwd) + * @returns true if file is write-protected, false otherwise + */ + isWriteProtected(filePath: string): boolean { + try { + // Normalize path to be relative to cwd and use forward slashes + const absolutePath = path.resolve(this.cwd, filePath) + const relativePath = path.relative(this.cwd, absolutePath).toPosix() + + // Use ignore library to check if file matches any protected pattern + return this.ignoreInstance.ignores(relativePath) + } catch (error) { + // If there's an error processing the path, err on the side of caution + // Ignore is designed to work with relative file paths, so will throw error for paths outside cwd + console.error(`Error checking protection for ${filePath}:`, error) + return false + } + } + + /** + * Get set of write-protected files from a list + * @param paths - Array of paths to filter (relative to cwd) + * @returns Set of protected file paths + */ + getProtectedFiles(paths: string[]): Set { + const protectedFiles = new Set() + + for (const filePath of paths) { + if (this.isWriteProtected(filePath)) { + protectedFiles.add(filePath) + } + } + + return protectedFiles + } + + /** + * Filter an array of paths, marking which ones are protected + * @param paths - Array of paths to check (relative to cwd) + * @returns Array of objects with path and protection status + */ + annotatePathsWithProtection(paths: string[]): Array<{ path: string; isProtected: boolean }> { + return paths.map((filePath) => ({ + path: filePath, + isProtected: this.isWriteProtected(filePath), + })) + } + + /** + * Get display message for protected file operations + */ + getProtectionMessage(): string { + return "This is a Roo configuration file and requires approval for modifications" + } + + /** + * Get formatted instructions about protected files for the LLM + * @returns Formatted instructions about file protection + */ + getInstructions(): string { + const patterns = RooProtectedController.PROTECTED_PATTERNS.join(", ") + return `# Protected Files\n\n(The following Roo configuration file patterns are write-protected and always require approval for modifications, regardless of autoapproval settings. When using list_files, you'll notice a ${SHIELD_SYMBOL} next to files that are write-protected.)\n\nProtected patterns: ${patterns}` + } + + /** + * Get the list of protected patterns (for testing/debugging) + */ + static getProtectedPatterns(): readonly string[] { + return RooProtectedController.PROTECTED_PATTERNS + } +} diff --git a/src/core/protect/__tests__/RooProtectedController.spec.ts b/src/core/protect/__tests__/RooProtectedController.spec.ts new file mode 100644 index 0000000000..63d8809285 --- /dev/null +++ b/src/core/protect/__tests__/RooProtectedController.spec.ts @@ -0,0 +1,141 @@ +import path from "path" +import { RooProtectedController } from "../RooProtectedController" + +describe("RooProtectedController", () => { + const TEST_CWD = "/test/workspace" + let controller: RooProtectedController + + beforeEach(() => { + controller = new RooProtectedController(TEST_CWD) + }) + + describe("isWriteProtected", () => { + it("should protect .rooignore file", () => { + expect(controller.isWriteProtected(".rooignore")).toBe(true) + }) + + it("should protect files in .roo directory", () => { + expect(controller.isWriteProtected(".roo/config.json")).toBe(true) + expect(controller.isWriteProtected(".roo/settings/user.json")).toBe(true) + expect(controller.isWriteProtected(".roo/modes/custom.json")).toBe(true) + }) + + it("should protect .rooprotected file", () => { + expect(controller.isWriteProtected(".rooprotected")).toBe(true) + }) + + it("should protect .roomodes files", () => { + expect(controller.isWriteProtected(".roomodes")).toBe(true) + }) + + it("should protect .roorules* files", () => { + expect(controller.isWriteProtected(".roorules")).toBe(true) + expect(controller.isWriteProtected(".roorules.md")).toBe(true) + }) + + it("should protect .clinerules* files", () => { + expect(controller.isWriteProtected(".clinerules")).toBe(true) + expect(controller.isWriteProtected(".clinerules.md")).toBe(true) + }) + + it("should not protect other files starting with .roo", () => { + expect(controller.isWriteProtected(".roosettings")).toBe(false) + expect(controller.isWriteProtected(".rooconfig")).toBe(false) + }) + + it("should not protect regular files", () => { + expect(controller.isWriteProtected("src/index.ts")).toBe(false) + expect(controller.isWriteProtected("package.json")).toBe(false) + expect(controller.isWriteProtected("README.md")).toBe(false) + }) + + it("should not protect files that contain 'roo' but don't start with .roo", () => { + expect(controller.isWriteProtected("src/roo-utils.ts")).toBe(false) + expect(controller.isWriteProtected("config/roo.config.js")).toBe(false) + }) + + it("should handle nested paths correctly", () => { + expect(controller.isWriteProtected(".roo/config.json")).toBe(true) // .roo/** matches at root + expect(controller.isWriteProtected("nested/.rooignore")).toBe(true) // .rooignore matches anywhere by default + expect(controller.isWriteProtected("nested/.roomodes")).toBe(true) // .roomodes matches anywhere by default + expect(controller.isWriteProtected("nested/.roorules.md")).toBe(true) // .roorules* matches anywhere by default + }) + + it("should handle absolute paths by converting to relative", () => { + const absolutePath = path.join(TEST_CWD, ".rooignore") + expect(controller.isWriteProtected(absolutePath)).toBe(true) + }) + + it("should handle paths with different separators", () => { + expect(controller.isWriteProtected(".roo\\config.json")).toBe(true) + expect(controller.isWriteProtected(".roo/config.json")).toBe(true) + }) + }) + + describe("getProtectedFiles", () => { + it("should return set of protected files from a list", () => { + const files = ["src/index.ts", ".rooignore", "package.json", ".roo/config.json", "README.md"] + + const protectedFiles = controller.getProtectedFiles(files) + + expect(protectedFiles).toEqual(new Set([".rooignore", ".roo/config.json"])) + }) + + it("should return empty set when no files are protected", () => { + const files = ["src/index.ts", "package.json", "README.md"] + + const protectedFiles = controller.getProtectedFiles(files) + + expect(protectedFiles).toEqual(new Set()) + }) + }) + + describe("annotatePathsWithProtection", () => { + it("should annotate paths with protection status", () => { + const files = ["src/index.ts", ".rooignore", ".roo/config.json", "package.json"] + + const annotated = controller.annotatePathsWithProtection(files) + + expect(annotated).toEqual([ + { path: "src/index.ts", isProtected: false }, + { path: ".rooignore", isProtected: true }, + { path: ".roo/config.json", isProtected: true }, + { path: "package.json", isProtected: false }, + ]) + }) + }) + + describe("getProtectionMessage", () => { + it("should return appropriate protection message", () => { + const message = controller.getProtectionMessage() + expect(message).toBe("This is a Roo configuration file and requires approval for modifications") + }) + }) + + describe("getInstructions", () => { + it("should return formatted instructions about protected files", () => { + const instructions = controller.getInstructions() + + expect(instructions).toContain("# Protected Files") + expect(instructions).toContain("write-protected") + expect(instructions).toContain(".rooignore") + expect(instructions).toContain(".roo/**") + expect(instructions).toContain("\u{1F6E1}") // Shield symbol + }) + }) + + describe("getProtectedPatterns", () => { + it("should return the list of protected patterns", () => { + const patterns = RooProtectedController.getProtectedPatterns() + + expect(patterns).toEqual([ + ".rooignore", + ".roomodes", + ".roorules*", + ".clinerules*", + ".roo/**", + ".rooprotected", + ]) + }) + }) +}) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index e881749e86..a6a9d89986 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -65,6 +65,7 @@ import { SYSTEM_PROMPT } from "../prompts/system" import { ToolRepetitionDetector } from "../tools/ToolRepetitionDetector" import { FileContextTracker } from "../context-tracking/FileContextTracker" import { RooIgnoreController } from "../ignore/RooIgnoreController" +import { RooProtectedController } from "../protect/RooProtectedController" import { type AssistantMessageContent, parseAssistantMessage, presentAssistantMessage } from "../assistant-message" import { truncateConversationIfNeeded } from "../sliding-window" import { ClineProvider } from "../webview/ClineProvider" @@ -144,6 +145,7 @@ export class Task extends EventEmitter { toolRepetitionDetector: ToolRepetitionDetector rooIgnoreController?: RooIgnoreController + rooProtectedController?: RooProtectedController fileContextTracker: FileContextTracker urlContentFetcher: UrlContentFetcher terminalProcess?: RooTerminalProcess @@ -223,6 +225,7 @@ export class Task extends EventEmitter { this.taskNumber = -1 this.rooIgnoreController = new RooIgnoreController(this.cwd) + this.rooProtectedController = new RooProtectedController(this.cwd) this.fileContextTracker = new FileContextTracker(provider, this.taskId) this.rooIgnoreController.initialize().catch((error) => { @@ -406,6 +409,7 @@ export class Task extends EventEmitter { text?: string, partial?: boolean, progressStatus?: ToolProgressStatus, + isProtected?: boolean, ): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> { // If this Cline instance was aborted by the provider, then the only // thing keeping us alive is a promise still running in the background, @@ -433,6 +437,7 @@ export class Task extends EventEmitter { lastMessage.text = text lastMessage.partial = partial lastMessage.progressStatus = progressStatus + lastMessage.isProtected = isProtected // TODO: Be more efficient about saving and posting only new // data or one whole message at a time so ignore partial for // saves, and only post parts of partial message instead of @@ -444,7 +449,7 @@ export class Task extends EventEmitter { // state. askTs = Date.now() this.lastMessageTs = askTs - await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial }) + await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial, isProtected }) throw new Error("Current ask promise was ignored (#2)") } } else { @@ -471,6 +476,7 @@ export class Task extends EventEmitter { lastMessage.text = text lastMessage.partial = false lastMessage.progressStatus = progressStatus + lastMessage.isProtected = isProtected await this.saveClineMessages() this.updateClineMessage(lastMessage) } else { @@ -480,7 +486,7 @@ export class Task extends EventEmitter { this.askResponseImages = undefined askTs = Date.now() this.lastMessageTs = askTs - await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) + await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, isProtected }) } } } else { @@ -490,7 +496,7 @@ export class Task extends EventEmitter { this.askResponseImages = undefined askTs = Date.now() this.lastMessageTs = askTs - await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) + await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, isProtected }) } await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 }) diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index 0963bc78cc..af8d91713f 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -66,6 +66,9 @@ export async function insertContentTool( return } + // Check if file is write-protected + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false + const absolutePath = path.resolve(cline.cwd, relPath) const fileExists = await fileExistsAtPath(absolutePath) @@ -124,10 +127,11 @@ export async function insertContentTool( ...sharedMessageProps, diff, lineNumber: lineNumber, + isProtected: isWriteProtected, } satisfies ClineSayTool) const didApprove = await cline - .ask("tool", completeMessage, false) + .ask("tool", completeMessage, isWriteProtected) .then((response) => response.response === "yesButtonClicked") if (!didApprove) { diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index 6d40de711c..1fabca22c1 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -64,6 +64,7 @@ export async function listFilesTool( didHitLimit, cline.rooIgnoreController, showRooIgnoredFiles, + cline.rooProtectedController, ) const completeMessage = JSON.stringify({ ...sharedMessageProps, content: result } satisfies ClineSayTool) diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index ba36cd3759..8a75d58c5a 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -161,7 +161,6 @@ Expected structure: Original error: ${errorMessage}` throw new Error(detailedError) } - } else if (legacyPath && typeof legacyDiffContent === "string") { // Handle legacy parameters (old way) usingLegacyParams = true @@ -236,6 +235,9 @@ Original error: ${errorMessage}` continue } + // Check if file is write-protected + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false + // Verify file exists const absolutePath = path.resolve(cline.cwd, relPath) const fileExists = await fileExistsAtPath(absolutePath) @@ -258,6 +260,11 @@ Original error: ${errorMessage}` // Handle batch approval if there are multiple files if (operationsToApprove.length > 1) { + // Check if any files are write-protected + const hasProtectedFiles = operationsToApprove.some( + (opResult) => cline.rooProtectedController?.isWriteProtected(opResult.path) || false, + ) + // Prepare batch diff data const batchDiffs = operationsToApprove.map((opResult) => { const readablePath = getReadablePath(cline.cwd, opResult.path) @@ -279,9 +286,10 @@ Original error: ${errorMessage}` const completeMessage = JSON.stringify({ tool: "appliedDiff", batchDiffs, + isProtected: hasProtectedFiles, } satisfies ClineSayTool) - const { response, text, images } = await cline.ask("tool", completeMessage, false) + const { response, text, images } = await cline.ask("tool", completeMessage, hasProtectedFiles) // Process batch response if (response === "yesButtonClicked") { @@ -485,9 +493,11 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} await cline.diffViewProvider.scrollToFirstDiff() // For batch operations, we've already gotten approval + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false const sharedMessageProps: ClineSayTool = { tool: "appliedDiff", path: getReadablePath(cline.cwd, relPath), + isProtected: isWriteProtected, } // If single file, ask for approval @@ -511,7 +521,9 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} ) } - didApprove = await askApproval("tool", operationMessage, toolProgressStatus) + // Check if file is write-protected + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false + didApprove = await askApproval("tool", operationMessage, toolProgressStatus, isWriteProtected) } if (!didApprove) { diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 58d246b133..967d5339ba 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -123,6 +123,9 @@ export async function searchAndReplaceTool( return } + // Check if file is write-protected + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(validRelPath) || false + const absolutePath = path.resolve(cline.cwd, validRelPath) const fileExists = await fileExistsAtPath(absolutePath) @@ -207,9 +210,13 @@ export async function searchAndReplaceTool( await cline.diffViewProvider.update(newContent, true) // Request user approval for changes - const completeMessage = JSON.stringify({ ...sharedMessageProps, diff } satisfies ClineSayTool) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff, + isProtected: isWriteProtected, + } satisfies ClineSayTool) const didApprove = await cline - .ask("tool", completeMessage, false) + .ask("tool", completeMessage, isWriteProtected) .then((response) => response.response === "yesButtonClicked") if (!didApprove) { diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index f7543d6d8b..d4469e9099 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -56,6 +56,9 @@ export async function writeToFileTool( return } + // Check if file is write-protected + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false + // Check if file exists using cached map or fs.access let fileExists: boolean @@ -90,6 +93,7 @@ export async function writeToFileTool( path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), content: newContent, isOutsideWorkspace, + isProtected: isWriteProtected, } try { @@ -201,7 +205,7 @@ export async function writeToFileTool( : undefined, } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) + const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) if (!didApprove) { await cline.diffViewProvider.revertChanges() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 9d4114825d..57fa16a848 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1276,6 +1276,7 @@ export class ClineProvider alwaysAllowReadOnlyOutsideWorkspace, alwaysAllowWrite, alwaysAllowWriteOutsideWorkspace, + alwaysAllowWriteProtected, alwaysAllowExecute, alwaysAllowBrowser, alwaysAllowMcp, @@ -1369,6 +1370,7 @@ export class ClineProvider alwaysAllowReadOnlyOutsideWorkspace: alwaysAllowReadOnlyOutsideWorkspace ?? false, alwaysAllowWrite: alwaysAllowWrite ?? false, alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? false, + alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? false, alwaysAllowExecute: alwaysAllowExecute ?? false, alwaysAllowBrowser: alwaysAllowBrowser ?? false, alwaysAllowMcp: alwaysAllowMcp ?? false, @@ -1529,6 +1531,7 @@ export class ClineProvider alwaysAllowReadOnlyOutsideWorkspace: stateValues.alwaysAllowReadOnlyOutsideWorkspace ?? false, alwaysAllowWrite: stateValues.alwaysAllowWrite ?? false, alwaysAllowWriteOutsideWorkspace: stateValues.alwaysAllowWriteOutsideWorkspace ?? false, + alwaysAllowWriteProtected: stateValues.alwaysAllowWriteProtected ?? false, alwaysAllowExecute: stateValues.alwaysAllowExecute ?? false, alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false, alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index a60c5fea41..673f1bc17b 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -155,6 +155,10 @@ export const webviewMessageHandler = async ( await updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined) await provider.postStateToWebview() break + case "alwaysAllowWriteProtected": + await updateGlobalState("alwaysAllowWriteProtected", message.bool ?? undefined) + await provider.postStateToWebview() + break case "alwaysAllowExecute": await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined) await provider.postStateToWebview() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 3f6333dc37..921a457965 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -151,6 +151,7 @@ export type ExtensionState = Pick< | "alwaysAllowReadOnlyOutsideWorkspace" | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" + | "alwaysAllowWriteProtected" // | "writeDelayMs" // Optional in GlobalSettings, required here. | "alwaysAllowBrowser" | "alwaysApproveResubmit" @@ -276,6 +277,7 @@ export interface ClineSayTool { mode?: string reason?: string isOutsideWorkspace?: boolean + isProtected?: boolean additionalFileCount?: number // Number of additional files in the same read_file request search?: string replace?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ae93e3ae76..26d7f7a536 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -29,6 +29,7 @@ export interface WebviewMessage { | "alwaysAllowReadOnlyOutsideWorkspace" | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" + | "alwaysAllowWriteProtected" | "alwaysAllowExecute" | "webviewDidLaunch" | "newTask" diff --git a/src/shared/tools.ts b/src/shared/tools.ts index ffaf41f93f..98a1be3ef2 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -8,6 +8,7 @@ export type AskApproval = ( type: ClineAsk, partialMessage?: string, progressStatus?: ToolProgressStatus, + forceApproval?: boolean, ) => Promise export type HandleError = (action: string, error: Error) => Promise diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index e71b05be50..eaf5ead616 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -313,11 +313,20 @@ export const ChatRowContent = ({ return ( <>
- {toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")} + {tool.isProtected ? ( + + ) : ( + toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit") + )} - {tool.isOutsideWorkspace - ? t("chat:fileOperations.wantsToEditOutsideWorkspace") - : t("chat:fileOperations.wantsToEdit")} + {tool.isProtected + ? t("chat:fileOperations.wantsToEditProtected") + : tool.isOutsideWorkspace + ? t("chat:fileOperations.wantsToEditOutsideWorkspace") + : t("chat:fileOperations.wantsToEdit")}
- {toolIcon("insert")} + {tool.isProtected ? ( + + ) : ( + toolIcon("insert") + )} - {tool.isOutsideWorkspace - ? t("chat:fileOperations.wantsToEditOutsideWorkspace") - : tool.lineNumber === 0 - ? t("chat:fileOperations.wantsToInsertAtEnd") - : t("chat:fileOperations.wantsToInsertWithLineNumber", { - lineNumber: tool.lineNumber, - })} + {tool.isProtected + ? t("chat:fileOperations.wantsToEditProtected") + : tool.isOutsideWorkspace + ? t("chat:fileOperations.wantsToEditOutsideWorkspace") + : tool.lineNumber === 0 + ? t("chat:fileOperations.wantsToInsertAtEnd") + : t("chat:fileOperations.wantsToInsertWithLineNumber", { + lineNumber: tool.lineNumber, + })}
- {toolIcon("replace")} + {tool.isProtected ? ( + + ) : ( + toolIcon("replace") + )} - {message.type === "ask" - ? t("chat:fileOperations.wantsToSearchReplace") - : t("chat:fileOperations.didSearchReplace")} + {tool.isProtected && message.type === "ask" + ? t("chat:fileOperations.wantsToEditProtected") + : message.type === "ask" + ? t("chat:fileOperations.wantsToSearchReplace") + : t("chat:fileOperations.didSearchReplace")}
- {toolIcon("new-file")} - {t("chat:fileOperations.wantsToCreate")} + {tool.isProtected ? ( + + ) : ( + toolIcon("new-file") + )} + + {tool.isProtected + ? t("chat:fileOperations.wantsToEditProtected") + : t("chat:fileOperations.wantsToCreate")} +
& { alwaysAllowReadOnlyOutsideWorkspace?: boolean alwaysAllowWrite?: boolean alwaysAllowWriteOutsideWorkspace?: boolean + alwaysAllowWriteProtected?: boolean writeDelayMs: number alwaysAllowBrowser?: boolean alwaysApproveResubmit?: boolean @@ -30,6 +31,7 @@ type AutoApproveSettingsProps = HTMLAttributes & { | "alwaysAllowReadOnlyOutsideWorkspace" | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" + | "alwaysAllowWriteProtected" | "writeDelayMs" | "alwaysAllowBrowser" | "alwaysApproveResubmit" @@ -47,6 +49,7 @@ export const AutoApproveSettings = ({ alwaysAllowReadOnlyOutsideWorkspace, alwaysAllowWrite, alwaysAllowWriteOutsideWorkspace, + alwaysAllowWriteProtected, writeDelayMs, alwaysAllowBrowser, alwaysApproveResubmit, @@ -138,10 +141,23 @@ export const AutoApproveSettings = ({ {t("settings:autoApprove.write.outsideWorkspace.label")} -
+
{t("settings:autoApprove.write.outsideWorkspace.description")}
+
+ + setCachedStateField("alwaysAllowWriteProtected", e.target.checked) + } + data-testid="always-allow-write-protected-checkbox"> + {t("settings:autoApprove.write.protected.label")} + +
+ {t("settings:autoApprove.write.protected.description")} +
+
(({ onDone, t alwaysAllowSubtasks, alwaysAllowWrite, alwaysAllowWriteOutsideWorkspace, + alwaysAllowWriteProtected, alwaysApproveResubmit, autoCondenseContext, autoCondenseContextPercent, @@ -253,6 +254,7 @@ const SettingsView = forwardRef(({ onDone, t }) vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite }) vscode.postMessage({ type: "alwaysAllowWriteOutsideWorkspace", bool: alwaysAllowWriteOutsideWorkspace }) + vscode.postMessage({ type: "alwaysAllowWriteProtected", bool: alwaysAllowWriteProtected }) vscode.postMessage({ type: "alwaysAllowExecute", bool: alwaysAllowExecute }) vscode.postMessage({ type: "alwaysAllowBrowser", bool: alwaysAllowBrowser }) vscode.postMessage({ type: "alwaysAllowMcp", bool: alwaysAllowMcp }) @@ -571,6 +573,7 @@ const SettingsView = forwardRef(({ onDone, t alwaysAllowReadOnlyOutsideWorkspace={alwaysAllowReadOnlyOutsideWorkspace} alwaysAllowWrite={alwaysAllowWrite} alwaysAllowWriteOutsideWorkspace={alwaysAllowWriteOutsideWorkspace} + alwaysAllowWriteProtected={alwaysAllowWriteProtected} writeDelayMs={writeDelayMs} alwaysAllowBrowser={alwaysAllowBrowser} alwaysApproveResubmit={alwaysApproveResubmit} diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 372ae066f9..1ca901cfa7 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo ha llegit aquest fitxer:", "wantsToEdit": "Roo vol editar aquest fitxer:", "wantsToEditOutsideWorkspace": "Roo vol editar aquest fitxer fora de l'espai de treball:", + "wantsToEditProtected": "Roo vol editar un fitxer de configuració protegit:", "wantsToCreate": "Roo vol crear un nou fitxer:", "wantsToSearchReplace": "Roo vol realitzar cerca i substitució en aquest fitxer:", "didSearchReplace": "Roo ha realitzat cerca i substitució en aquest fitxer:", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index edadf729ed..205ff89e3a 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Incloure fitxers fora de l'espai de treball", "description": "Permetre a Roo crear i editar fitxers fora de l'espai de treball actual sense requerir aprovació." + }, + "protected": { + "label": "Incloure fitxers protegits", + "description": "Permetre a Roo crear i editar fitxers protegits (com .rooignore i fitxers de configuració .roo/) sense requerir aprovació." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 451cea47d3..764073e17c 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -146,6 +146,7 @@ "didRead": "Roo hat diese Datei gelesen:", "wantsToEdit": "Roo möchte diese Datei bearbeiten:", "wantsToEditOutsideWorkspace": "Roo möchte diese Datei außerhalb des Arbeitsbereichs bearbeiten:", + "wantsToEditProtected": "Roo möchte eine geschützte Konfigurationsdatei bearbeiten:", "wantsToCreate": "Roo möchte eine neue Datei erstellen:", "wantsToSearchReplace": "Roo möchte in dieser Datei suchen und ersetzen:", "didSearchReplace": "Roo hat Suchen und Ersetzen in dieser Datei durchgeführt:", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 6597d8f717..044c4f5220 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Dateien außerhalb des Arbeitsbereichs einbeziehen", "description": "Roo erlauben, Dateien außerhalb des aktuellen Arbeitsbereichs ohne Genehmigung zu erstellen und zu bearbeiten." + }, + "protected": { + "label": "Geschützte Dateien einbeziehen", + "description": "Roo erlauben, geschützte Dateien (wie .rooignore und .roo/ Konfigurationsdateien) ohne Genehmigung zu erstellen und zu bearbeiten." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 34236c5636..f5d741ff4b 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -156,6 +156,7 @@ "didRead": "Roo read this file:", "wantsToEdit": "Roo wants to edit this file:", "wantsToEditOutsideWorkspace": "Roo wants to edit this file outside of the workspace:", + "wantsToEditProtected": "Roo wants to edit a protected configuration file:", "wantsToApplyBatchChanges": "Roo wants to apply changes to multiple files:", "wantsToCreate": "Roo wants to create a new file:", "wantsToSearchReplace": "Roo wants to search and replace in this file:", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index d1c30270cf..b7f2a014c9 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Include files outside workspace", "description": "Allow Roo to create and edit files outside the current workspace without requiring approval." + }, + "protected": { + "label": "Include protected files", + "description": "Allow Roo to create and edit protected files (like .rooignore and .roo/ configuration files) without requiring approval." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index dc0a49332f..2bd8eac84b 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo leyó este archivo:", "wantsToEdit": "Roo quiere editar este archivo:", "wantsToEditOutsideWorkspace": "Roo quiere editar este archivo fuera del espacio de trabajo:", + "wantsToEditProtected": "Roo quiere editar un archivo de configuración protegido:", "wantsToCreate": "Roo quiere crear un nuevo archivo:", "wantsToSearchReplace": "Roo quiere realizar búsqueda y reemplazo en este archivo:", "didSearchReplace": "Roo realizó búsqueda y reemplazo en este archivo:", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index c9a356e4cd..b9d0d25ec3 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Incluir archivos fuera del espacio de trabajo", "description": "Permitir a Roo crear y editar archivos fuera del espacio de trabajo actual sin requerir aprobación." + }, + "protected": { + "label": "Incluir archivos protegidos", + "description": "Permitir a Roo crear y editar archivos protegidos (como .rooignore y archivos de configuración .roo/) sin requerir aprobación." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 4a7d298fbf..b864c7b133 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -142,6 +142,7 @@ "didRead": "Roo a lu ce fichier :", "wantsToEdit": "Roo veut éditer ce fichier :", "wantsToEditOutsideWorkspace": "Roo veut éditer ce fichier en dehors de l'espace de travail :", + "wantsToEditProtected": "Roo veut éditer un fichier de configuration protégé :", "wantsToCreate": "Roo veut créer un nouveau fichier :", "wantsToSearchReplace": "Roo veut effectuer une recherche et remplacement sur ce fichier :", "didSearchReplace": "Roo a effectué une recherche et remplacement sur ce fichier :", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 430c879396..87cc5c7a0a 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Inclure les fichiers en dehors de l'espace de travail", "description": "Permettre à Roo de créer et modifier des fichiers en dehors de l'espace de travail actuel sans nécessiter d'approbation." + }, + "protected": { + "label": "Inclure les fichiers protégés", + "description": "Permettre à Roo de créer et modifier des fichiers protégés (comme .rooignore et les fichiers de configuration .roo/) sans nécessiter d'approbation." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index c4f7a322bf..375aa0d6a2 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo ने इस फ़ाइल को पढ़ा:", "wantsToEdit": "Roo इस फ़ाइल को संपादित करना चाहता है:", "wantsToEditOutsideWorkspace": "Roo कार्यक्षेत्र के बाहर इस फ़ाइल को संपादित करना चाहता है:", + "wantsToEditProtected": "Roo एक सुरक्षित कॉन्फ़िगरेशन फ़ाइल को संपादित करना चाहता है:", "wantsToCreate": "Roo एक नई फ़ाइल बनाना चाहता है:", "wantsToSearchReplace": "Roo इस फ़ाइल में खोज और प्रतिस्थापन करना चाहता है:", "didSearchReplace": "Roo ने इस फ़ाइल में खोज और प्रतिस्थापन किया:", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 31ab2191d5..86afe59319 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "वर्कस्पेस के बाहर की फाइलें शामिल करें", "description": "Roo को अनुमोदन की आवश्यकता के बिना वर्तमान वर्कस्पेस के बाहर फाइलें बनाने और संपादित करने की अनुमति दें।" + }, + "protected": { + "label": "संरक्षित फाइलें शामिल करें", + "description": "Roo को अनुमोदन की आवश्यकता के बिना संरक्षित फाइलें (.rooignore और .roo/ कॉन्फ़िगरेशन फाइलें जैसी) बनाने और संपादित करने की अनुमति दें।" } }, "browser": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 4dd5666134..93d1526540 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo ha letto questo file:", "wantsToEdit": "Roo vuole modificare questo file:", "wantsToEditOutsideWorkspace": "Roo vuole modificare questo file al di fuori dell'area di lavoro:", + "wantsToEditProtected": "Roo vuole modificare un file di configurazione protetto:", "wantsToCreate": "Roo vuole creare un nuovo file:", "wantsToSearchReplace": "Roo vuole eseguire ricerca e sostituzione in questo file:", "didSearchReplace": "Roo ha eseguito ricerca e sostituzione in questo file:", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 51d1dd640d..50c6528210 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Includi file al di fuori dell'area di lavoro", "description": "Permetti a Roo di creare e modificare file al di fuori dell'area di lavoro attuale senza richiedere approvazione." + }, + "protected": { + "label": "Includi file protetti", + "description": "Permetti a Roo di creare e modificare file protetti (come .rooignore e file di configurazione .roo/) senza richiedere approvazione." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 44a472605f..0c9038ff6f 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -145,6 +145,7 @@ "didRead": "Rooはこのファイルを読みました:", "wantsToEdit": "Rooはこのファイルを編集したい:", "wantsToEditOutsideWorkspace": "Rooはワークスペース外のこのファイルを編集したい:", + "wantsToEditProtected": "Rooは保護された設定ファイルを編集したい:", "wantsToCreate": "Rooは新しいファイルを作成したい:", "wantsToSearchReplace": "Rooはこのファイルで検索と置換を行う:", "didSearchReplace": "Rooはこのファイルで検索と置換を実行しました:", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 93cbe43a6e..7e82190b7a 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "ワークスペース外のファイルを含める", "description": "Rooが承認なしで現在のワークスペース外のファイルを作成・編集することを許可します。" + }, + "protected": { + "label": "保護されたファイルを含める", + "description": "Rooが保護されたファイル(.rooignoreや.roo/設定ファイルなど)を承認なしで作成・編集することを許可します。" } }, "browser": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 6c73a9c8e4..183d69ce98 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo가 이 파일을 읽었습니다:", "wantsToEdit": "Roo가 이 파일을 편집하고 싶어합니다:", "wantsToEditOutsideWorkspace": "Roo가 워크스페이스 외부의 이 파일을 편집하고 싶어합니다:", + "wantsToEditProtected": "Roo가 보호된 설정 파일을 편집하고 싶어합니다:", "wantsToCreate": "Roo가 새 파일을 만들고 싶어합니다:", "wantsToSearchReplace": "Roo가 이 파일에서 검색 및 바꾸기를 수행하고 싶어합니다:", "didSearchReplace": "Roo가 이 파일에서 검색 및 바꾸기를 수행했습니다:", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index a7ec710f23..a2dc6e9b64 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "워크스페이스 외부 파일 포함", "description": "Roo가 승인 없이 현재 워크스페이스 외부의 파일을 생성하고 편집할 수 있도록 허용합니다." + }, + "protected": { + "label": "보호된 파일 포함", + "description": "Roo가 보호된 파일(.rooignore 및 .roo/ 구성 파일 등)을 승인 없이 생성하고 편집할 수 있도록 허용합니다." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 3ae43f6dda..95b6d1bb78 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -140,6 +140,7 @@ "didRead": "Roo heeft dit bestand gelezen:", "wantsToEdit": "Roo wil dit bestand bewerken:", "wantsToEditOutsideWorkspace": "Roo wil dit bestand buiten de werkruimte bewerken:", + "wantsToEditProtected": "Roo wil een beveiligd configuratiebestand bewerken:", "wantsToCreate": "Roo wil een nieuw bestand aanmaken:", "wantsToSearchReplace": "Roo wil zoeken en vervangen in dit bestand:", "didSearchReplace": "Roo heeft zoeken en vervangen uitgevoerd op dit bestand:", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 8ec7bb35c4..94d63a71db 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Inclusief bestanden buiten werkruimte", "description": "Sta Roo toe om bestanden buiten de huidige werkruimte aan te maken en te bewerken zonder goedkeuring." + }, + "protected": { + "label": "Inclusief beschermde bestanden", + "description": "Sta Roo toe om beschermde bestanden (zoals .rooignore en .roo/ configuratiebestanden) aan te maken en te bewerken zonder goedkeuring." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 8be03dcaa5..57f0ee8537 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo przeczytał ten plik:", "wantsToEdit": "Roo chce edytować ten plik:", "wantsToEditOutsideWorkspace": "Roo chce edytować ten plik poza obszarem roboczym:", + "wantsToEditProtected": "Roo chce edytować chroniony plik konfiguracyjny:", "wantsToCreate": "Roo chce utworzyć nowy plik:", "wantsToSearchReplace": "Roo chce wykonać wyszukiwanie i zamianę w tym pliku:", "didSearchReplace": "Roo wykonał wyszukiwanie i zamianę w tym pliku:", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index aade54b772..41eae85d79 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Uwzględnij pliki poza obszarem roboczym", "description": "Pozwól Roo na tworzenie i edycję plików poza bieżącym obszarem roboczym bez konieczności zatwierdzania." + }, + "protected": { + "label": "Uwzględnij pliki chronione", + "description": "Pozwól Roo na tworzenie i edycję plików chronionych (takich jak .rooignore i pliki konfiguracyjne .roo/) bez konieczności zatwierdzania." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 90a88043b3..bab4a19fa9 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo leu este arquivo:", "wantsToEdit": "Roo quer editar este arquivo:", "wantsToEditOutsideWorkspace": "Roo quer editar este arquivo fora do espaço de trabalho:", + "wantsToEditProtected": "Roo quer editar um arquivo de configuração protegido:", "wantsToCreate": "Roo quer criar um novo arquivo:", "wantsToSearchReplace": "Roo quer realizar busca e substituição neste arquivo:", "didSearchReplace": "Roo realizou busca e substituição neste arquivo:", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 8e1a5af79a..35254166a4 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Incluir arquivos fora do espaço de trabalho", "description": "Permitir que o Roo crie e edite arquivos fora do espaço de trabalho atual sem exigir aprovação." + }, + "protected": { + "label": "Incluir arquivos protegidos", + "description": "Permitir que o Roo crie e edite arquivos protegidos (como .rooignore e arquivos de configuração .roo/) sem exigir aprovação." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 1bf2b002c0..ed8c755c3d 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -140,6 +140,7 @@ "didRead": "Roo прочитал этот файл:", "wantsToEdit": "Roo хочет отредактировать этот файл:", "wantsToEditOutsideWorkspace": "Roo хочет отредактировать этот файл вне рабочей области:", + "wantsToEditProtected": "Roo хочет отредактировать защищённый файл конфигурации:", "wantsToCreate": "Roo хочет создать новый файл:", "wantsToSearchReplace": "Roo хочет выполнить поиск и замену в этом файле:", "didSearchReplace": "Roo выполнил поиск и замену в этом файле:", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index acf9235253..51b3206537 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Включая файлы вне рабочей области", "description": "Разрешить Roo создавать и редактировать файлы вне текущей рабочей области без необходимости одобрения." + }, + "protected": { + "label": "Включить защищенные файлы", + "description": "Разрешить Roo создавать и редактировать защищенные файлы (такие как .rooignore и файлы конфигурации .roo/) без необходимости одобрения." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 11ac664baa..4f133c4e45 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo bu dosyayı okudu:", "wantsToEdit": "Roo bu dosyayı düzenlemek istiyor:", "wantsToEditOutsideWorkspace": "Roo çalışma alanı dışındaki bu dosyayı düzenlemek istiyor:", + "wantsToEditProtected": "Roo korumalı bir yapılandırma dosyasını düzenlemek istiyor:", "wantsToCreate": "Roo yeni bir dosya oluşturmak istiyor:", "wantsToSearchReplace": "Roo bu dosyada arama ve değiştirme yapmak istiyor:", "didSearchReplace": "Roo bu dosyada arama ve değiştirme yaptı:", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 2445ea6c91..9900008861 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Çalışma alanı dışındaki dosyaları dahil et", "description": "Roo'nun onay gerektirmeden mevcut çalışma alanı dışında dosya oluşturmasına ve düzenlemesine izin ver." + }, + "protected": { + "label": "Korumalı dosyaları dahil et", + "description": "Roo'nun korumalı dosyaları (.rooignore ve .roo/ yapılandırma dosyaları gibi) onay gerektirmeden oluşturmasına ve düzenlemesine izin ver." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 54201d74b2..7e510c5a52 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo đã đọc tệp này:", "wantsToEdit": "Roo muốn chỉnh sửa tệp này:", "wantsToEditOutsideWorkspace": "Roo muốn chỉnh sửa tệp này bên ngoài không gian làm việc:", + "wantsToEditProtected": "Roo muốn chỉnh sửa tệp cấu hình được bảo vệ:", "wantsToCreate": "Roo muốn tạo một tệp mới:", "wantsToSearchReplace": "Roo muốn thực hiện tìm kiếm và thay thế trong tệp này:", "didSearchReplace": "Roo đã thực hiện tìm kiếm và thay thế trong tệp này:", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 9dc39075c7..5f260bd845 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "Bao gồm các tệp ngoài không gian làm việc", "description": "Cho phép Roo tạo và chỉnh sửa các tệp bên ngoài không gian làm việc hiện tại mà không yêu cầu phê duyệt." + }, + "protected": { + "label": "Bao gồm các tệp được bảo vệ", + "description": "Cho phép Roo tạo và chỉnh sửa các tệp được bảo vệ (như .rooignore và các tệp cấu hình .roo/) mà không yêu cầu phê duyệt." } }, "browser": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index f1e0b89cac..d637a75e4b 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -145,6 +145,7 @@ "didRead": "已读取文件:", "wantsToEdit": "需要编辑文件:", "wantsToEditOutsideWorkspace": "需要编辑外部文件:", + "wantsToEditProtected": "需要编辑受保护的配置文件:", "wantsToCreate": "需要新建文件:", "wantsToSearchReplace": "需要在此文件中搜索和替换:", "didSearchReplace": "已完成搜索和替换:", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index be8f221a81..d35e7a4054 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "包含工作区外的文件", "description": "允许 Roo 创建和编辑当前工作区外的文件,无需批准。" + }, + "protected": { + "label": "包含受保护的文件", + "description": "允许 Roo 创建和编辑受保护的文件(如 .rooignore 和 .roo/ 配置文件),无需批准。" } }, "browser": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index fbb217b4fc..401384145d 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -145,6 +145,7 @@ "didRead": "Roo 已讀取此檔案:", "wantsToEdit": "Roo 想要編輯此檔案:", "wantsToEditOutsideWorkspace": "Roo 想要編輯此工作區外的檔案:", + "wantsToEditProtected": "Roo 想要編輯受保護的設定檔案:", "wantsToCreate": "Roo 想要建立新檔案:", "wantsToSearchReplace": "Roo 想要在此檔案中搜尋和取代:", "didSearchReplace": "Roo 已在此檔案執行搜尋和取代:", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 901ec23416..5f96f692e4 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -83,6 +83,10 @@ "outsideWorkspace": { "label": "包含工作區外的檔案", "description": "允許 Roo 在目前工作區外建立和編輯檔案,無需核准。" + }, + "protected": { + "label": "包含受保護的檔案", + "description": "允許 Roo 建立和編輯受保護的檔案(如 .rooignore 和 .roo/ 設定檔),無需核准。" } }, "browser": {