Skip to content

harness: str_replace — show target file and expandable old→new diff #368

Description

@btipling

Summary

When the agent calls str_replace, the operator needs to see which file was edited and, on expand, what changed (old vs new). Today the tool-run surface mostly shows a one-liner like path + replacement count + bytes — not enough to audit the edit without re-running or digging elsewhere.

Current behavior (baseline)

Layer What happens today
Tool result (lib/agent/tools.ts) str_replace ${path}: ok replacements=N bytes=Mno old_string / new_string / patch body
L1 brief (salientToolBits) e.g. path · N replacements · M B
L2 preview (buildToolPreview) Only whatever is in that short result text — usually nothing expandable beyond the one-liner

So expand is useless for surgical edits: you can’t see the diff that actually landed.

Desired UX

Collapsed / L1 (always visible)

  • Clear file path (workspace-relative as resolved)
  • Short status: ok/fail, replacement count (and fail reason when ERROR)
  • Example: str_replace · ok · src/ui.zig · 1 replacement

Expanded / L2

  • A readable diff between what was matched (old_string) and what replaced it (new_string):
    • Prefer a unified diff-style block (redacted, bounded) — same spirit as existing markdown/diff fence paint in the harness
    • Or a two-block before / after layout if unified is awkward for multi-replace / replace_all
  • Include enough context that a human can approve the edit at a glance
  • Failures: show path + ERROR line; if the tool rejected (not unique, missing read, etc.), expand with the error detail (already in result text)

Implementation sketch (not locked)

  1. Backend tool result — when str_replace succeeds, include a bounded, redacted representation of the edit, e.g.:
    • path, replacements, bytes (keep)
    • plus old_string / new_string (truncated) or a prebuilt unified-diff snippet
    • Must go through existing secret redaction; never dump unbounded file bodies
  2. Stream summary + previewsummarizeToolLine / buildToolPreview treat that body as real L2 (path stays in L1 bits)
  3. Wasm paint — L2 already uses mono for str_replace / multi-line detail; optionally route through existing diff/patch fence painting if the payload is a fenced diff
  4. Caps — reuse tool-run preview budget (TOOL_RUN_PREVIEW_*, group encode budget); huge replaces clip with explicit

Acceptance

  • Successful str_replace L1 shows which file was edited (and count/status)
  • Expanding the item shows a diff (or before/after) of old vs new content, redacted + bounded
  • Failed str_replace still surfaces path + error; expand not a blank duplicate of L1
  • No secrets leaked; stays under existing tool-run / msg size budgets
  • Docs: docs/harness-limits.md / agent-stream tool-result notes if the result shape changes

Related

Out of scope

  • Reverting edits from the UI
  • Live file watcher / external editor
  • Changing str_replace tool semantics (unique match, freshness gate) — display only unless result shape must grow

Related (2026-08-15)

Operator-visible diff stays this issue. Model-facing error window on a failed replace is #564. Do not merge those payloads.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestharnessHarness / agent UIui

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions