fix(file-ops): a V4A patch keeps terminal escape bytes on lines it never touched
read_file_raw feeds the V4A write-back, and it ran the whole file through the terminal fence-leak cleanup, which deletes every OSC sequence and BEL byte. A prompt script's title escape or a beep line was silently rewritten on apply, and the diff and post-write hash both started from the stripped text, so nothing showed it. A leaked wrapper always carries the fence marker: clean only those lines when the content is going to be written back.
This commit is contained in:
committed by
Austin Pickett
parent
ac2ffe60d0
commit
0a8c4f8540
@@ -371,6 +371,26 @@ class TestBomHandling:
|
||||
assert b"print('world')" in raw
|
||||
|
||||
|
||||
def test_v4a_update_keeps_terminal_escape_bytes_on_untouched_lines(self, ops, tmp_path: Path):
|
||||
# read_file_raw feeds the V4A write-back; its leak cleanup must not eat the file's own
|
||||
# OSC title escapes and BEL bytes on lines the patch never touched.
|
||||
target = tmp_path / "prompt.sh"
|
||||
original = (b'set_title() { printf "\x1b]0;%s\x07" "$1"; }\n'
|
||||
b'beep() { printf "\x07"; }\n'
|
||||
b'VERSION=1\n')
|
||||
target.write_bytes(original)
|
||||
patch = (
|
||||
"*** Begin Patch\n"
|
||||
f"*** Update File: {target}\n"
|
||||
"@@\n"
|
||||
"-VERSION=1\n"
|
||||
"+VERSION=2\n"
|
||||
"*** End Patch"
|
||||
)
|
||||
res = ops.patch_v4a(patch)
|
||||
assert res.success, res.error
|
||||
assert target.read_bytes() == original.replace(b"VERSION=1", b"VERSION=2")
|
||||
|
||||
|
||||
class TestProtectedInstructionFiles:
|
||||
"""Writes to agent-instruction files ALWAYS require approval.
|
||||
|
||||
@@ -1085,7 +1085,8 @@ class ShellFileOperations(LintMixin, SearchMixin, FileOperations):
|
||||
return ReadResult(error=f"Failed to read file: {cat_result.stdout}")
|
||||
# Strip a leading BOM (a phantom U+FEFF defeats an exact first-line match);
|
||||
# write_file re-probes disk and restores it.
|
||||
raw_content, _ = _strip_bom(_strip_terminal_fence_leaks(cat_result.stdout))
|
||||
# V4A writes this back, so only lines carrying a leaked fence are cleaned.
|
||||
raw_content, _ = _strip_bom(_strip_terminal_fence_leaks(cat_result.stdout, fenced_lines_only=True))
|
||||
return ReadResult(content=raw_content, file_size=file_size)
|
||||
|
||||
def read_file_bytes(self, path: str, max_bytes: Optional[int] = None) -> ReadResult:
|
||||
|
||||
@@ -204,13 +204,20 @@ def count_conflict_blocks(formatted_content: str) -> int:
|
||||
return min(opens, len(_CONFLICT_CLOSE.findall(formatted_content))) if opens else 0
|
||||
|
||||
|
||||
def _strip_terminal_fence_leaks(text: str) -> str:
|
||||
def _strip_terminal_fence_leaks(text: str, *, fenced_lines_only: bool = False) -> str:
|
||||
"""Strip leaked terminal fence wrappers (OSC sequences, fence markers) from
|
||||
command output; drops lines that were nothing but wrapper."""
|
||||
command output; drops lines that were nothing but wrapper.
|
||||
|
||||
``fenced_lines_only`` is for file content that gets written back: a leak always carries the
|
||||
fence marker, so a line without one is the file's own bytes (a prompt script's ``\x1b]0;``
|
||||
title escape, a BEL) and is kept verbatim."""
|
||||
if not text:
|
||||
return text
|
||||
cleaned_lines: List[str] = []
|
||||
for line in text.splitlines(keepends=True):
|
||||
if fenced_lines_only and "__HERMES_FENCE_" not in line:
|
||||
cleaned_lines.append(line)
|
||||
continue
|
||||
had_terminal_wrapper = "__HERMES_FENCE_" in line or "\x1b]" in line
|
||||
cleaned = _FENCE_MARKER_RE.sub("", _OSC_SEQUENCE_RE.sub("", line)).replace("\x07", "")
|
||||
if had_terminal_wrapper and cleaned.strip("'\r\n\t ") == "":
|
||||
|
||||
Reference in New Issue
Block a user