Skip to content

Commit 3e68503

Browse files
committed
refactor: address inline review comments
- Extract edit operation parameters into EDIT_OPERATION_PARAMS constant for better maintainability - Improve XML parsing robustness with better validation and error handling - Add tool name to FileRestrictionError for better debugging - Add comprehensive test coverage for new error message formats - Ensure FileRestrictionError is properly re-thrown during XML validation Addresses feedback from @daniel-lxs in PR review comments
1 parent 5365916 commit 3e68503

File tree

2 files changed

+59
-23
lines changed

2 files changed

+59
-23
lines changed

src/shared/__tests__/modes.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,14 @@ describe("FileRestrictionError", () => {
375375
expect(error.name).toBe("FileRestrictionError")
376376
})
377377

378+
it("formats error message with tool name when provided", () => {
379+
const error = new FileRestrictionError("Markdown Editor", "\\.md$", undefined, "test.js", "write_to_file")
380+
expect(error.message).toBe(
381+
"Tool 'write_to_file' in mode 'Markdown Editor' can only edit files matching pattern: \\.md$. Got: test.js",
382+
)
383+
expect(error.name).toBe("FileRestrictionError")
384+
})
385+
378386
describe("debug mode", () => {
379387
it("is configured correctly", () => {
380388
const debugMode = modes.find((mode) => mode.slug === "debug")
@@ -474,6 +482,20 @@ describe("FileRestrictionError", () => {
474482
)
475483
expect(error.name).toBe("FileRestrictionError")
476484
})
485+
486+
it("formats error message with both tool name and description when provided", () => {
487+
const error = new FileRestrictionError(
488+
"Markdown Editor",
489+
"\\.md$",
490+
"Markdown files only",
491+
"test.js",
492+
"apply_diff",
493+
)
494+
expect(error.message).toBe(
495+
"Tool 'apply_diff' in mode 'Markdown Editor' can only edit files matching pattern: \\.md$ (Markdown files only). Got: test.js",
496+
)
497+
expect(error.name).toBe("FileRestrictionError")
498+
})
477499
})
478500

479501
describe("getModeSelection", () => {

src/shared/modes.ts

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,15 @@ export function getModeSelection(mode: string, promptComponent?: PromptComponent
203203
}
204204
}
205205

206+
// Edit operation parameters that indicate an actual edit operation
207+
const EDIT_OPERATION_PARAMS = ["diff", "content", "operations", "search", "replace", "args", "line"] as const
208+
206209
// Custom error class for file restrictions
207210
export class FileRestrictionError extends Error {
208-
constructor(mode: string, pattern: string, description: string | undefined, filePath: string) {
211+
constructor(mode: string, pattern: string, description: string | undefined, filePath: string, tool?: string) {
212+
const toolInfo = tool ? `Tool '${tool}' in mode '${mode}'` : `This mode (${mode})`
209213
super(
210-
`This mode (${mode}) can only edit files matching pattern: ${pattern}${description ? ` (${description})` : ""}. Got: ${filePath}`,
214+
`${toolInfo} can only edit files matching pattern: ${pattern}${description ? ` (${description})` : ""}. Got: ${filePath}`,
211215
)
212216
this.name = "FileRestrictionError"
213217
}
@@ -267,36 +271,46 @@ export function isToolAllowedForMode(
267271
if (groupName === "edit" && options.fileRegex) {
268272
const filePath = toolParams?.path
269273
// Check if this is an actual edit operation (not just path-only for streaming)
270-
const isEditOperation = !!(
271-
toolParams?.diff ||
272-
toolParams?.content ||
273-
toolParams?.operations ||
274-
toolParams?.search ||
275-
toolParams?.replace ||
276-
toolParams?.args
277-
)
274+
const isEditOperation = EDIT_OPERATION_PARAMS.some((param) => toolParams?.[param])
278275

279276
// Handle single file path validation
280277
if (filePath && isEditOperation && !doesFileMatchRegex(filePath, options.fileRegex)) {
281-
throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath)
278+
throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath, tool)
282279
}
283280

284281
// Handle XML args parameter (used by MULTI_FILE_APPLY_DIFF experiment)
285282
if (toolParams?.args && typeof toolParams.args === "string") {
286-
// Extract file paths from XML args
287-
const filePathMatches = toolParams.args.match(/<path>([^<]+)<\/path>/g)
288-
if (filePathMatches) {
289-
for (const match of filePathMatches) {
290-
const extractedPath = match.replace(/<\/?path>/g, "")
291-
if (!doesFileMatchRegex(extractedPath, options.fileRegex)) {
292-
throw new FileRestrictionError(
293-
mode.name,
294-
options.fileRegex,
295-
options.description,
296-
extractedPath,
297-
)
283+
// Extract file paths from XML args with improved validation
284+
try {
285+
const filePathMatches = toolParams.args.match(/<path>([^<]+)<\/path>/g)
286+
if (filePathMatches) {
287+
for (const match of filePathMatches) {
288+
// More robust path extraction with validation
289+
const pathMatch = match.match(/<path>([^<]+)<\/path>/)
290+
if (pathMatch && pathMatch[1]) {
291+
const extractedPath = pathMatch[1].trim()
292+
// Validate that the path is not empty and doesn't contain invalid characters
293+
if (extractedPath && !extractedPath.includes("<") && !extractedPath.includes(">")) {
294+
if (!doesFileMatchRegex(extractedPath, options.fileRegex)) {
295+
throw new FileRestrictionError(
296+
mode.name,
297+
options.fileRegex,
298+
options.description,
299+
extractedPath,
300+
tool,
301+
)
302+
}
303+
}
304+
}
298305
}
299306
}
307+
} catch (error) {
308+
// Re-throw FileRestrictionError as it's an expected validation error
309+
if (error instanceof FileRestrictionError) {
310+
throw error
311+
}
312+
// If XML parsing fails, log the error but don't block the operation
313+
console.warn(`Failed to parse XML args for file restriction validation: ${error}`)
300314
}
301315
}
302316
}

0 commit comments

Comments
 (0)