Skip to content

feat(harness): edit built-in tool - #63

Merged
sanchitrk merged 18 commits into
canaryfrom
feature/edit-tool
Oct 10, 2026
Merged

sanchitrk merged 18 commits into
canaryfrom
feature/edit-tool

Conversation

@sanchitrk

@sanchitrk sanchitrk commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Builds on read (#45) and write (#62), both merged.

edit

A port of Pi's edit tool (edit.ts + edit-diff.ts), behaviour and messages unchanged:

  • Parameters: path and edits: [{oldText, newText}]. Pi's description, prompt snippet and the four guidelines.
  • Every edit is matched against the original file, not after earlier edits, and replacements are applied last to first.
  • Matching tries the exact text first, then a fuzzy form (NFKC, trailing whitespace, smart quotes, Unicode dashes and spaces). In the fuzzy case, only the lines an edit touches are rewritten; every other line keeps its original bytes.
  • A leading BOM and CRLF line endings are kept. oldText written with LF matches a CRLF file.
  • Rejected edits use Pi's messages: empty edits, empty oldText, not found, multiple occurrences, overlapping edits, no change. When any edit fails, nothing is written. Access errors read Could not edit file: <path>. Error code: <code>., and a path that isn't a file gets Path is not a file.
  • The model reads Successfully replaced N block(s) in <path>. details carries { diff, patch, firstChangedLine? }: a numbered diff with 4 context lines and long unchanged gaps collapsed, plus a unified patch.
  • The whole read → match → write holds the file's mutation lock.

Uses diff@9.0.0, the latest. Pi pins 8.0.4; the APIs used here are unchanged in 9.

prepareArguments

A new optional tool-definition hook, as in Pi. The executor runs it before validating arguments; if it throws, the call fails as invalid arguments. edit uses it to repair edits sent as a JSON string, as a single object, or as a top-level oldText/newText pair. The arguments stored with the call stay exactly as the model sent them. It runs before the decoder from #61, so repaired arguments still get strict mode's null normalisation.

Tests

  • tools.edit.test.ts: an E2E turn through the harness with 41 of Pi's cases, each on its own file. It covers multi-edit, collapsed gaps, matching against the original, every rejection, ENOENT, EACCES, not a file, symlinks, all of Pi's fuzzy cases (including line preservation), CRLF/LF/BOM, and every argument repair. Like Pi's tests, each success checks that the patch applied to the old file gives the new file. Snapshotted to __artifacts__/tools.edit.json.
  • Pi's "keeps the mutation queue locked until an aborted edit write settles", run over the gated provider now shared with the write test.

Known Pi behaviour, kept on purpose: overlapping occurrences ("aa" in "aaa") count as unique, and a whitespace-only final line the edit doesn't touch makes a fuzzy edit fail with "Cannot preserve unchanged lines…".

🤖 Generated with Claude Code

prem315 and others added 17 commits September 30, 2026 21:33
Rewrite `read` to match Pi's read tool: Pi's parameters, description,
prompt snippet and continuation notices; text read through `scanLines`
so only the shown window leaves the sandbox; images detected by magic
bytes (animated PNGs excluded), converted and resized with Photon on a
worker thread with EXIF orientation applied; binary files reported as
unsupported; macOS path spellings resolved.

Add `readBytes` to the sandbox filesystem contract, with a base64 shell
form for remote backends, and an optional `encodeDetails` on tool
definitions so a payload carried in content is not stored twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `write` matching Pi's write tool: Pi's parameters, description and
prompt text, `Successfully wrote to <path>`, parent directories created,
and the whole write held under the file's mutation lock.

Make `SandboxFileSystem.writeFile` uninterruptible: a started write
cannot be called back, so interrupting it only released the file's lock
while its bytes still landed over the next writer's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `edit` ported from Pi's edit tool: exact-text replacements matched
against the original file, exact first then fuzzy (NFKC, trailing
whitespace, smart quotes, dashes, special spaces) with untouched lines
kept byte for byte, CRLF and BOM preserved, Pi's error messages, and a
numbered display diff plus unified patch in `details`. The whole
read-match-write holds the file's mutation lock.

Add `prepareArguments` to tool definitions, run by the executor before
the arguments are validated, so `edit` repairs `edits` sent as a JSON
string, a single object, or a top-level oldText/newText pair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/harness/src/tool/executor.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/harness/src/plugin/builtin.ts
#	packages/harness/test/plugin.external.test.ts
#	packages/harness/test/plugin.git.live.test.ts
#	packages/plugin/src/sandbox/filesystem.ts
Base automatically changed from feature/write-tool to canary October 10, 2026 05:02
# Conflicts:
#	packages/harness/src/plugin/builtin.ts
#	packages/harness/test/plugin.external.test.ts
#	packages/harness/test/tools.write.test.ts
@sanchitrk
sanchitrk merged commit aaada19 into canary Oct 10, 2026
@sanchitrk
sanchitrk deleted the feature/edit-tool branch October 10, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants