agent-exec transport 35d469bccb3546fa95ccf1db49e5a7c2/79ece004c6be
This commit is contained in:
@@ -429,22 +429,39 @@ def _build_bounded_unit_prompt(packet_text: str) -> str:
|
||||
|
||||
|
||||
def extract_terminal_record(text: str | None) -> dict[str, Any] | None:
|
||||
"""Return the last valid terminal record embedded in one final message."""
|
||||
"""Return the sole terminal record that ends one final message.
|
||||
|
||||
Only whitespace may follow the record, and no other valid record may
|
||||
precede it; anything else fails closed.
|
||||
"""
|
||||
if not isinstance(text, str) or not text:
|
||||
return None
|
||||
text = text[-_MAX_TERMINAL_TEXT_CHARS:]
|
||||
text = text[-_MAX_TERMINAL_TEXT_CHARS:].rstrip()
|
||||
decoder = json.JSONDecoder()
|
||||
|
||||
def decode_at(pos: int) -> tuple[object, int]:
|
||||
try:
|
||||
return decoder.raw_decode(text, pos)
|
||||
except json.JSONDecodeError:
|
||||
return None, -1
|
||||
|
||||
pos = text.rfind("{")
|
||||
while pos != -1:
|
||||
try:
|
||||
candidate, _ = decoder.raw_decode(text, pos)
|
||||
except json.JSONDecodeError:
|
||||
candidate = None
|
||||
record = _valid_terminal_record(candidate)
|
||||
if record is not None:
|
||||
return record
|
||||
candidate, end = decode_at(pos)
|
||||
if end == len(text) and isinstance(candidate, dict):
|
||||
break
|
||||
pos = text.rfind("{", 0, pos)
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
record = _valid_terminal_record(candidate)
|
||||
if record is None:
|
||||
return None
|
||||
earlier = text.rfind("{", 0, pos)
|
||||
while earlier != -1:
|
||||
if _valid_terminal_record(decode_at(earlier)[0]) is not None:
|
||||
return None
|
||||
earlier = text.rfind("{", 0, earlier)
|
||||
return record
|
||||
|
||||
|
||||
def _valid_terminal_record(obj: object) -> dict[str, Any] | None:
|
||||
|
||||
@@ -2325,7 +2325,7 @@ class AgentExecControllerTests(unittest.TestCase):
|
||||
"pi",
|
||||
_pi_help(),
|
||||
_pi_json_events(
|
||||
"ok\n```json\n" + _terminal_json() + "\n```",
|
||||
"ok\n" + _terminal_json() + "\n",
|
||||
agent_end_padding=2 * agent_exec.TERMINAL_LINE_MAX_BYTES,
|
||||
),
|
||||
"success",
|
||||
@@ -2411,12 +2411,35 @@ class TerminalRecordDecodingTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_cursor_stream_json_result_is_decoded(self) -> None:
|
||||
lines = _cursor_stream_events("Done.\n" + _terminal_json() + "\nThanks.")
|
||||
lines = _cursor_stream_events("Done.\n" + _terminal_json() + "\n \n")
|
||||
record = self._decode("cursor", lines)
|
||||
assert record is not None
|
||||
self.assertEqual(record["terminal_status"], "completed")
|
||||
self.assertEqual(record["changed_files"], ["src/marker.txt"])
|
||||
|
||||
def test_terminal_record_must_be_sole_final_content(self) -> None:
|
||||
invalid = json.dumps({"terminal_status": "done", "summary": "x"})
|
||||
texts = {
|
||||
"trailing-prose": "Done.\n" + _terminal_json() + "\nThanks.",
|
||||
"trailing-invalid-object": _terminal_json() + "\n" + invalid,
|
||||
"trailing-non-terminal-object": _terminal_json() + ' {"note":"x"}',
|
||||
"truncated-terminal-object": _terminal_json() + '\n{"terminal_status":',
|
||||
"two-records": _terminal_json() + "\n" + _terminal_json(),
|
||||
"conflicting-records": _terminal_json() + "\n" + _terminal_json("blocked"),
|
||||
}
|
||||
for name, text in texts.items():
|
||||
for adapter, lines in (
|
||||
("cursor", _cursor_stream_events(text)),
|
||||
("antigravity", _agy_stream_events(text)),
|
||||
("pi", _pi_json_events(text)),
|
||||
):
|
||||
with self.subTest(case=name, adapter=adapter):
|
||||
self.assertIsNone(self._decode(adapter, lines))
|
||||
with self.subTest(case=name, adapter="custom"):
|
||||
self.assertIsNone(
|
||||
self._decode("custom", [], stdout_tail="", result_text=text)
|
||||
)
|
||||
|
||||
def test_cursor_user_echo_and_error_result_are_not_records(self) -> None:
|
||||
lines = _cursor_stream_events("prose only")
|
||||
echo = json.loads(lines[1])
|
||||
|
||||
Reference in New Issue
Block a user