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=M — no 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)
- 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
- Stream summary +
preview — summarizeToolLine / buildToolPreview treat that body as real L2 (path stays in L1 bits)
- 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
- Caps — reuse tool-run preview budget (
TOOL_RUN_PREVIEW_*, group encode budget); huge replaces clip with explicit …
Acceptance
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.
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)
lib/agent/tools.ts)str_replace ${path}: ok replacements=N bytes=M— noold_string/new_string/ patch bodysalientToolBits)path · N replacements · M BbuildToolPreview)So expand is useless for surgical edits: you can’t see the diff that actually landed.
Desired UX
Collapsed / L1 (always visible)
str_replace · ok · src/ui.zig · 1 replacementExpanded / L2
old_string) and what replaced it (new_string):replace_allImplementation sketch (not locked)
str_replacesucceeds, include a bounded, redacted representation of the edit, e.g.:old_string/new_string(truncated) or a prebuilt unified-diff snippetpreview—summarizeToolLine/buildToolPreviewtreat that body as real L2 (path stays in L1 bits)str_replace/ multi-line detail; optionally route through existing diff/patch fence painting if the payload is a fenced diffTOOL_RUN_PREVIEW_*, group encode budget); huge replaces clip with explicit…Acceptance
str_replaceL1 shows which file was edited (and count/status)str_replacestill surfaces path + error; expand not a blank duplicate of L1docs/harness-limits.md/ agent-stream tool-result notes if the result shape changesRelated
Out of scope
str_replacetool semantics (unique match, freshness gate) — display only unless result shape must growRelated (2026-08-15)
Operator-visible diff stays this issue. Model-facing error window on a failed replace is #564. Do not merge those payloads.