diff --git a/packages/core/src/tool/plugin/edit.ts b/packages/core/src/tool/plugin/edit.ts
index 93867fa5f9..c55eb2e72b 100644
--- a/packages/core/src/tool/plugin/edit.ts
+++ b/packages/core/src/tool/plugin/edit.ts
@@ -20,13 +20,13 @@ export const name = "edit"
export const Input = Schema.Struct({
path: Schema.String.annotate({
- description:
- "File path to edit. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval.",
+ description: "File to edit",
}),
- oldString: Schema.String.annotate({ description: "Exact text to replace" }),
- newString: Schema.String.annotate({ description: "Replacement text, which must differ from oldString" }),
+ oldString: Schema.String.annotate({ description: "Exact text to find and replace" }),
+ newString: Schema.String.annotate({ description: "Text to replace oldString with (must differ from oldString)" }),
replaceAll: Schema.optionalKey(Schema.Boolean).annotate({
- description: "Replace all exact occurrences of oldString (default false)",
+ description:
+ "Whether to replace every occurrence of oldString. When false, oldString must match exactly once. Defaults to false.",
}),
})
@@ -99,20 +99,13 @@ export const Plugin = {
name,
options: { codemode: false, permission: "edit" },
description:
- "Replace exact text in one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval.",
+ "Edit the contents of a file by finding and replacing exact text. When editing text from Read output, preserve the exact indentation (tabs or spaces) and omit the line-number prefix, such as `1: `. Never include the prefix in oldString or newString. The edit fails if oldString is not found. By default, oldString must identify a UNIQUE location. Multiple matches FAIL unless replaceAll is true. Add more surrounding context to disambiguate, or set replaceAll to true to replace every occurrence. Use replaceAll when the change should apply to every occurrence, such as renaming a variable.",
input: Input,
output: Output,
execute: (input, context) => {
const unableToEdit = (effect: Effect.Effect) =>
effect.pipe(
- Effect.mapError((error) =>
- error instanceof FileMutation.StaleContentError
- ? new ToolFailure({
- message: "File changed after permission approval. Read it again before editing.",
- error,
- })
- : new ToolFailure({ message: `Unable to edit ${input.path}`, error }),
- ),
+ Effect.mapError((error) => new ToolFailure({ message: `Unable to edit ${input.path}`, error })),
)
return Effect.gen(function* () {
@@ -186,9 +179,8 @@ export const Plugin = {
)
const next = splitBom(replaced)
const result = yield* unableToEdit(
- files.writeIfUnchanged({
+ files.write({
target,
- expected: source.content,
content: joinBom(next.text, source.bom || next.bom),
}),
)
diff --git a/packages/core/test/tool-edit.test.ts b/packages/core/test/tool-edit.test.ts
index a22ba21f39..97c3578839 100644
--- a/packages/core/test/tool-edit.test.ts
+++ b/packages/core/test/tool-edit.test.ts
@@ -455,7 +455,7 @@ describe("EditTool", () => {
),
)
- it.live("rejects an in-place content change after matching but before conditional commit", () =>
+ it.live("applies the edit when content changes after matching", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
(tmp) => {
@@ -470,17 +470,9 @@ describe("EditTool", () => {
),
Effect.andThen((result) =>
Effect.gen(function* () {
- // The message-less StaleContentError cause must not erase the tool's
- // curated failure message; the canonical error is the sole authority.
- expect(result).toEqual({
- status: "error",
- error: {
- type: "tool.execution",
- message: "File changed after permission approval. Read it again before editing.",
- },
- })
- expect(yield* Effect.promise(() => fs.readFile(target, "utf8"))).toBe("newer\n")
- expect(writes).toEqual([])
+ expect(result).toMatchObject({ status: "completed", output: { replacements: 1 } })
+ expect(yield* Effect.promise(() => fs.readFile(target, "utf8"))).toBe("after\n")
+ expect(writes).toEqual([target])
}),
),
)