mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-14 23:21:19 +00:00
Add logic to prevent auto-approving edits of configuration files (#4667)
* Add logic to prevent auto-approving edits of configuration files * Fix tests * Update patterns
This commit is contained in:
parent
10b2fb32ed
commit
7d0b22f9e6
55 changed files with 488 additions and 32 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<typeof clineMessageSchema>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
src/core/protect/RooProtectedController.ts
Normal file
103
src/core/protect/RooProtectedController.ts
Normal file
|
|
@ -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<string> {
|
||||
const protectedFiles = new Set<string>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
141
src/core/protect/__tests__/RooProtectedController.spec.ts
Normal file
141
src/core/protect/__tests__/RooProtectedController.spec.ts
Normal file
|
|
@ -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",
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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<ClineEvents> {
|
|||
|
||||
toolRepetitionDetector: ToolRepetitionDetector
|
||||
rooIgnoreController?: RooIgnoreController
|
||||
rooProtectedController?: RooProtectedController
|
||||
fileContextTracker: FileContextTracker
|
||||
urlContentFetcher: UrlContentFetcher
|
||||
terminalProcess?: RooTerminalProcess
|
||||
|
|
@ -223,6 +225,7 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
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<ClineEvents> {
|
|||
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<ClineEvents> {
|
|||
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<ClineEvents> {
|
|||
// 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<ClineEvents> {
|
|||
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<ClineEvents> {
|
|||
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<ClineEvents> {
|
|||
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 })
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ export async function listFilesTool(
|
|||
didHitLimit,
|
||||
cline.rooIgnoreController,
|
||||
showRooIgnoredFiles,
|
||||
cline.rooProtectedController,
|
||||
)
|
||||
|
||||
const completeMessage = JSON.stringify({ ...sharedMessageProps, content: result } satisfies ClineSayTool)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export interface WebviewMessage {
|
|||
| "alwaysAllowReadOnlyOutsideWorkspace"
|
||||
| "alwaysAllowWrite"
|
||||
| "alwaysAllowWriteOutsideWorkspace"
|
||||
| "alwaysAllowWriteProtected"
|
||||
| "alwaysAllowExecute"
|
||||
| "webviewDidLaunch"
|
||||
| "newTask"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export type AskApproval = (
|
|||
type: ClineAsk,
|
||||
partialMessage?: string,
|
||||
progressStatus?: ToolProgressStatus,
|
||||
forceApproval?: boolean,
|
||||
) => Promise<boolean>
|
||||
|
||||
export type HandleError = (action: string, error: Error) => Promise<void>
|
||||
|
|
|
|||
|
|
@ -313,11 +313,20 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")}
|
||||
{tool.isProtected ? (
|
||||
<span
|
||||
className="codicon codicon-lock"
|
||||
style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
|
||||
/>
|
||||
) : (
|
||||
toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")
|
||||
)}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{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")}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
|
|
@ -335,15 +344,24 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("insert")}
|
||||
{tool.isProtected ? (
|
||||
<span
|
||||
className="codicon codicon-lock"
|
||||
style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
|
||||
/>
|
||||
) : (
|
||||
toolIcon("insert")
|
||||
)}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{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,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
|
|
@ -361,11 +379,20 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("replace")}
|
||||
{tool.isProtected ? (
|
||||
<span
|
||||
className="codicon codicon-lock"
|
||||
style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
|
||||
/>
|
||||
) : (
|
||||
toolIcon("replace")
|
||||
)}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{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")}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
|
|
@ -405,8 +432,19 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("new-file")}
|
||||
<span style={{ fontWeight: "bold" }}>{t("chat:fileOperations.wantsToCreate")}</span>
|
||||
{tool.isProtected ? (
|
||||
<span
|
||||
className="codicon codicon-lock"
|
||||
style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
|
||||
/>
|
||||
) : (
|
||||
toolIcon("new-file")
|
||||
)}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{tool.isProtected
|
||||
? t("chat:fileOperations.wantsToEditProtected")
|
||||
: t("chat:fileOperations.wantsToCreate")}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
path={tool.path}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
alwaysAllowReadOnlyOutsideWorkspace,
|
||||
alwaysAllowWrite,
|
||||
alwaysAllowWriteOutsideWorkspace,
|
||||
alwaysAllowWriteProtected,
|
||||
alwaysAllowExecute,
|
||||
alwaysAllowMcp,
|
||||
allowedCommands,
|
||||
|
|
@ -914,13 +915,18 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}
|
||||
|
||||
const isOutsideWorkspace = !!tool.isOutsideWorkspace
|
||||
const isProtected = message.isProtected
|
||||
|
||||
if (isReadOnlyToolAction(message)) {
|
||||
return alwaysAllowReadOnly && (!isOutsideWorkspace || alwaysAllowReadOnlyOutsideWorkspace)
|
||||
}
|
||||
|
||||
if (isWriteToolAction(message)) {
|
||||
return alwaysAllowWrite && (!isOutsideWorkspace || alwaysAllowWriteOutsideWorkspace)
|
||||
return (
|
||||
alwaysAllowWrite &&
|
||||
(!isOutsideWorkspace || alwaysAllowWriteOutsideWorkspace) &&
|
||||
(!isProtected || alwaysAllowWriteProtected)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -934,6 +940,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
isReadOnlyToolAction,
|
||||
alwaysAllowWrite,
|
||||
alwaysAllowWriteOutsideWorkspace,
|
||||
alwaysAllowWriteProtected,
|
||||
isWriteToolAction,
|
||||
alwaysAllowExecute,
|
||||
isAllowedCommand,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
|
|||
alwaysAllowReadOnlyOutsideWorkspace?: boolean
|
||||
alwaysAllowWrite?: boolean
|
||||
alwaysAllowWriteOutsideWorkspace?: boolean
|
||||
alwaysAllowWriteProtected?: boolean
|
||||
writeDelayMs: number
|
||||
alwaysAllowBrowser?: boolean
|
||||
alwaysApproveResubmit?: boolean
|
||||
|
|
@ -30,6 +31,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
|
|||
| "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")}
|
||||
</span>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-3">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.write.outsideWorkspace.description")}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={alwaysAllowWriteProtected}
|
||||
onChange={(e: any) =>
|
||||
setCachedStateField("alwaysAllowWriteProtected", e.target.checked)
|
||||
}
|
||||
data-testid="always-allow-write-protected-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.write.protected.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-3">
|
||||
{t("settings:autoApprove.write.protected.description")}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
alwaysAllowSubtasks,
|
||||
alwaysAllowWrite,
|
||||
alwaysAllowWriteOutsideWorkspace,
|
||||
alwaysAllowWriteProtected,
|
||||
alwaysApproveResubmit,
|
||||
autoCondenseContext,
|
||||
autoCondenseContextPercent,
|
||||
|
|
@ -253,6 +254,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ 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<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
alwaysAllowReadOnlyOutsideWorkspace={alwaysAllowReadOnlyOutsideWorkspace}
|
||||
alwaysAllowWrite={alwaysAllowWrite}
|
||||
alwaysAllowWriteOutsideWorkspace={alwaysAllowWriteOutsideWorkspace}
|
||||
alwaysAllowWriteProtected={alwaysAllowWriteProtected}
|
||||
writeDelayMs={writeDelayMs}
|
||||
alwaysAllowBrowser={alwaysAllowBrowser}
|
||||
alwaysApproveResubmit={alwaysApproveResubmit}
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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 :",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@
|
|||
"didRead": "Roo ने इस फ़ाइल को पढ़ा:",
|
||||
"wantsToEdit": "Roo इस फ़ाइल को संपादित करना चाहता है:",
|
||||
"wantsToEditOutsideWorkspace": "Roo कार्यक्षेत्र के बाहर इस फ़ाइल को संपादित करना चाहता है:",
|
||||
"wantsToEditProtected": "Roo एक सुरक्षित कॉन्फ़िगरेशन फ़ाइल को संपादित करना चाहता है:",
|
||||
"wantsToCreate": "Roo एक नई फ़ाइल बनाना चाहता है:",
|
||||
"wantsToSearchReplace": "Roo इस फ़ाइल में खोज और प्रतिस्थापन करना चाहता है:",
|
||||
"didSearchReplace": "Roo ने इस फ़ाइल में खोज और प्रतिस्थापन किया:",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@
|
|||
"outsideWorkspace": {
|
||||
"label": "वर्कस्पेस के बाहर की फाइलें शामिल करें",
|
||||
"description": "Roo को अनुमोदन की आवश्यकता के बिना वर्तमान वर्कस्पेस के बाहर फाइलें बनाने और संपादित करने की अनुमति दें।"
|
||||
},
|
||||
"protected": {
|
||||
"label": "संरक्षित फाइलें शामिल करें",
|
||||
"description": "Roo को अनुमोदन की आवश्यकता के बिना संरक्षित फाइलें (.rooignore और .roo/ कॉन्फ़िगरेशन फाइलें जैसी) बनाने और संपादित करने की अनुमति दें।"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@
|
|||
"didRead": "Rooはこのファイルを読みました:",
|
||||
"wantsToEdit": "Rooはこのファイルを編集したい:",
|
||||
"wantsToEditOutsideWorkspace": "Rooはワークスペース外のこのファイルを編集したい:",
|
||||
"wantsToEditProtected": "Rooは保護された設定ファイルを編集したい:",
|
||||
"wantsToCreate": "Rooは新しいファイルを作成したい:",
|
||||
"wantsToSearchReplace": "Rooはこのファイルで検索と置換を行う:",
|
||||
"didSearchReplace": "Rooはこのファイルで検索と置換を実行しました:",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@
|
|||
"outsideWorkspace": {
|
||||
"label": "ワークスペース外のファイルを含める",
|
||||
"description": "Rooが承認なしで現在のワークスペース外のファイルを作成・編集することを許可します。"
|
||||
},
|
||||
"protected": {
|
||||
"label": "保護されたファイルを含める",
|
||||
"description": "Rooが保護されたファイル(.rooignoreや.roo/設定ファイルなど)を承認なしで作成・編集することを許可します。"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@
|
|||
"didRead": "Roo가 이 파일을 읽었습니다:",
|
||||
"wantsToEdit": "Roo가 이 파일을 편집하고 싶어합니다:",
|
||||
"wantsToEditOutsideWorkspace": "Roo가 워크스페이스 외부의 이 파일을 편집하고 싶어합니다:",
|
||||
"wantsToEditProtected": "Roo가 보호된 설정 파일을 편집하고 싶어합니다:",
|
||||
"wantsToCreate": "Roo가 새 파일을 만들고 싶어합니다:",
|
||||
"wantsToSearchReplace": "Roo가 이 파일에서 검색 및 바꾸기를 수행하고 싶어합니다:",
|
||||
"didSearchReplace": "Roo가 이 파일에서 검색 및 바꾸기를 수행했습니다:",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@
|
|||
"outsideWorkspace": {
|
||||
"label": "워크스페이스 외부 파일 포함",
|
||||
"description": "Roo가 승인 없이 현재 워크스페이스 외부의 파일을 생성하고 편집할 수 있도록 허용합니다."
|
||||
},
|
||||
"protected": {
|
||||
"label": "보호된 파일 포함",
|
||||
"description": "Roo가 보호된 파일(.rooignore 및 .roo/ 구성 파일 등)을 승인 없이 생성하고 편집할 수 있도록 허용합니다."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@
|
|||
"didRead": "Roo прочитал этот файл:",
|
||||
"wantsToEdit": "Roo хочет отредактировать этот файл:",
|
||||
"wantsToEditOutsideWorkspace": "Roo хочет отредактировать этот файл вне рабочей области:",
|
||||
"wantsToEditProtected": "Roo хочет отредактировать защищённый файл конфигурации:",
|
||||
"wantsToCreate": "Roo хочет создать новый файл:",
|
||||
"wantsToSearchReplace": "Roo хочет выполнить поиск и замену в этом файле:",
|
||||
"didSearchReplace": "Roo выполнил поиск и замену в этом файле:",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@
|
|||
"outsideWorkspace": {
|
||||
"label": "Включая файлы вне рабочей области",
|
||||
"description": "Разрешить Roo создавать и редактировать файлы вне текущей рабочей области без необходимости одобрения."
|
||||
},
|
||||
"protected": {
|
||||
"label": "Включить защищенные файлы",
|
||||
"description": "Разрешить Roo создавать и редактировать защищенные файлы (такие как .rooignore и файлы конфигурации .roo/) без необходимости одобрения."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -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ı:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@
|
|||
"didRead": "已读取文件:",
|
||||
"wantsToEdit": "需要编辑文件:",
|
||||
"wantsToEditOutsideWorkspace": "需要编辑外部文件:",
|
||||
"wantsToEditProtected": "需要编辑受保护的配置文件:",
|
||||
"wantsToCreate": "需要新建文件:",
|
||||
"wantsToSearchReplace": "需要在此文件中搜索和替换:",
|
||||
"didSearchReplace": "已完成搜索和替换:",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@
|
|||
"outsideWorkspace": {
|
||||
"label": "包含工作区外的文件",
|
||||
"description": "允许 Roo 创建和编辑当前工作区外的文件,无需批准。"
|
||||
},
|
||||
"protected": {
|
||||
"label": "包含受保护的文件",
|
||||
"description": "允许 Roo 创建和编辑受保护的文件(如 .rooignore 和 .roo/ 配置文件),无需批准。"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@
|
|||
"didRead": "Roo 已讀取此檔案:",
|
||||
"wantsToEdit": "Roo 想要編輯此檔案:",
|
||||
"wantsToEditOutsideWorkspace": "Roo 想要編輯此工作區外的檔案:",
|
||||
"wantsToEditProtected": "Roo 想要編輯受保護的設定檔案:",
|
||||
"wantsToCreate": "Roo 想要建立新檔案:",
|
||||
"wantsToSearchReplace": "Roo 想要在此檔案中搜尋和取代:",
|
||||
"didSearchReplace": "Roo 已在此檔案執行搜尋和取代:",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@
|
|||
"outsideWorkspace": {
|
||||
"label": "包含工作區外的檔案",
|
||||
"description": "允許 Roo 在目前工作區外建立和編輯檔案,無需核准。"
|
||||
},
|
||||
"protected": {
|
||||
"label": "包含受保護的檔案",
|
||||
"description": "允許 Roo 建立和編輯受保護的檔案(如 .rooignore 和 .roo/ 設定檔),無需核准。"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue