fix(agent): separate continuation fragments so joined text does not glue

truncated_response_parts were joined with no separator at both the
ceiling exit and the success path, so a fragment ending mid-word ran
straight into the next one (#78577). insert a newline only when the
previous fragment ends non-whitespace and the next starts
non-whitespace, so existing separators are not doubled.
This commit is contained in:
Erosika
2026-08-07 11:23:43 -04:00
committed by kshitij
parent c8cf8bfdb6
commit 4cc3ea01f6
2 changed files with 33 additions and 2 deletions
+12 -2
View File
@@ -182,6 +182,16 @@ _API_CALL_MODULES = frozenset({
})
def _join_truncated_parts(parts: List[str]) -> str:
"""Join continuation fragments, adding a newline where two would glue together (#78577)."""
joined = ""
for part in parts:
if joined and not joined[-1].isspace() and part and not part[0].isspace():
joined += "\n"
joined += part
return joined
def _apply_active_turn_redirect(agent: Any, messages: List[Dict[str, Any]], text: str) -> None:
"""Append a provider-safe checkpoint and correction to the live turn.
@@ -3291,7 +3301,7 @@ def run_conversation(
_retry.restart_with_length_continuation = True
break
partial_response = agent._strip_think_blocks("".join(truncated_response_parts)).strip()
partial_response = agent._strip_think_blocks(_join_truncated_parts(truncated_response_parts)).strip()
if partial_response:
agent._vprint(
f"{agent.log_prefix}⚠️ Response still truncated "
@@ -7199,7 +7209,7 @@ def run_conversation(
codex_ack_continuations = 0
if truncated_response_parts:
final_response = "".join(truncated_response_parts) + final_response
final_response = _join_truncated_parts([*truncated_response_parts, final_response])
truncated_response_parts = []
length_continue_retries = 0
# The continuation recovered, so the fragments stay in the transcript.
@@ -229,3 +229,24 @@ class TestContinuationCeilingWedge:
)
assert "second turn partial" in result2["final_response"]
assert "and the rest." in result2["final_response"]
class TestTruncatedPartJoining:
"""#78577 — parts joined with no separator glued text together."""
def test_glued_parts_get_a_newline(self):
from agent.conversation_loop import _join_truncated_parts
assert _join_truncated_parts(
["Edited index.html", "Review the 5 changes"]
) == "Edited index.html\nReview the 5 changes"
def test_existing_whitespace_is_not_doubled(self):
from agent.conversation_loop import _join_truncated_parts
assert _join_truncated_parts(["line one\n", "line two"]) == "line one\nline two"
assert _join_truncated_parts(["word", " next"]) == "word next"
def test_degenerate_inputs(self):
from agent.conversation_loop import _join_truncated_parts
assert _join_truncated_parts([]) == ""
assert _join_truncated_parts(["only"]) == "only"
assert _join_truncated_parts(["a", "", "b"]) == "a\nb"