From fbfe520471024a2da483e3322a94f96f7838ad8e Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:48:43 +0800 Subject: [PATCH] fix(editor): preserve literal replacement text --- packages/fs/tool-str-replace-editor/src/index.ts | 5 +++-- .../tool-str-replace-editor/tests/tools.spec.ts | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/fs/tool-str-replace-editor/src/index.ts b/packages/fs/tool-str-replace-editor/src/index.ts index 801e9200b4..071ada3ec6 100644 --- a/packages/fs/tool-str-replace-editor/src/index.ts +++ b/packages/fs/tool-str-replace-editor/src/index.ts @@ -308,7 +308,8 @@ async function replaceInFile( } const before = await ctx.fs.readText(target, exec.signal) const offsets = matchOffsets(before, oldValue) - if (offsets.length === 0) { + const offset = offsets[0] + if (offset === undefined) { throw new FsError( `No replacement was performed, old_str \`${oldValue}\` did not appear verbatim in ${target.displayPath}.`, 'FS_EDIT_NOT_FOUND', @@ -325,7 +326,7 @@ async function replaceInFile( try { outcome = await ctx.fs.writeText( target, - before.replace(oldValue, newValue), + before.slice(0, offset) + newValue + before.slice(offset + oldValue.length), intent === undefined ? { kind: 'replaceIfVersion', version: info.version } : { kind: 'replaceIfVersion', version: intent.version }, diff --git a/packages/fs/tool-str-replace-editor/tests/tools.spec.ts b/packages/fs/tool-str-replace-editor/tests/tools.spec.ts index f1fafcfa04..90b182a43f 100644 --- a/packages/fs/tool-str-replace-editor/tests/tools.spec.ts +++ b/packages/fs/tool-str-replace-editor/tests/tools.spec.ts @@ -194,6 +194,21 @@ describe('tool-str-replace-editor', () => { expect(await readFile(sample, 'utf8')).toBe('one\nbetween\n\nthree\n') }) + it('writes replacement text literally', async () => { + const { ctx, root, owner } = await setup() + const sample = join(root, 'literal.txt') + const replacement = "$&|$`|$'|$$" + await writeFile(sample, 'before OLD after') + + expect((await call(ctx, owner, { + command: 'str_replace', + path: sample, + old_str: 'OLD', + new_str: replacement, + })).isError).toBe(false) + expect(await readFile(sample, 'utf8')).toBe(`before ${replacement} after`) + }) + it('lists visible entries to depth two and clips at the configured view limit', async () => { const { ctx, root, owner } = await setup({ maxOutputChars: 10_000 }) await mkdir(join(root, 'dir', 'nested', 'third'), { recursive: true })