agent-exec transport b11015860dac4bb8a1943889211374b7/0dd2eb729ad2
This commit is contained in:
@@ -5,11 +5,14 @@ does not contact providers or launch real agent runs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from scripts.agent_exec_config import (
|
||||
ALLOWED_PLACEHOLDERS,
|
||||
@@ -23,6 +26,34 @@ _PROBE_TIMEOUT_SECONDS = 5
|
||||
MAX_UNIT_PACKET_BYTES = 262_144
|
||||
UNIT_PACKET_DISPLAY_MARKER = "<unit-packet>"
|
||||
|
||||
TERMINAL_STATUSES = (
|
||||
"completed",
|
||||
"failed",
|
||||
"blocked",
|
||||
"cancelled",
|
||||
"failed-unknown",
|
||||
"scope-expansion",
|
||||
)
|
||||
TERMINAL_RECORD_SCHEMA: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"terminal_status",
|
||||
"summary",
|
||||
"changed_files",
|
||||
"evidence",
|
||||
"scope_expansion",
|
||||
],
|
||||
"properties": {
|
||||
"terminal_status": {"enum": list(TERMINAL_STATUSES)},
|
||||
"summary": {"type": "string"},
|
||||
"changed_files": {"type": "array", "items": {"type": "string"}},
|
||||
"evidence": {"type": "array", "items": {"type": "string"}},
|
||||
"scope_expansion": {"type": ["string", "null"]},
|
||||
},
|
||||
}
|
||||
# Bounds the backwards brace scan over one final message.
|
||||
_MAX_TERMINAL_TEXT_CHARS = 262_144
|
||||
|
||||
_PLACEHOLDER_MAP = {
|
||||
"${workspace}": "workspace",
|
||||
"${unit_packet}": "unit_packet",
|
||||
@@ -378,17 +409,177 @@ def _read_unit_packet_text(path: Path) -> str:
|
||||
|
||||
|
||||
def _build_bounded_unit_prompt(packet_text: str) -> str:
|
||||
schema = json.dumps(TERMINAL_RECORD_SCHEMA, separators=(",", ":"))
|
||||
return (
|
||||
"Implement exactly the supplied bounded implementation unit. "
|
||||
"Work only inside the current workspace. "
|
||||
"Leave all changes uncommitted. "
|
||||
"Do not expand scope beyond the unit packet. "
|
||||
"Return a structured final result matching the unit's required schema.\n\n"
|
||||
"--- TERMINAL RECORD ---\n\n"
|
||||
"End your final message with exactly one terminal record: a single-line "
|
||||
"JSON object that validates against this JSON Schema:\n"
|
||||
f"{schema}\n"
|
||||
"Use terminal_status completed only when the unit is fully implemented. "
|
||||
"Use blocked, cancelled, failed-unknown, failed, or scope-expansion "
|
||||
"otherwise. A missing or invalid terminal record fails the attempt.\n\n"
|
||||
"--- BOUNDED IMPLEMENTATION UNIT PACKET ---\n\n"
|
||||
f"{packet_text}"
|
||||
)
|
||||
|
||||
|
||||
def extract_terminal_record(text: str | None) -> dict[str, Any] | None:
|
||||
"""Return the last valid terminal record embedded in one final message."""
|
||||
if not isinstance(text, str) or not text:
|
||||
return None
|
||||
text = text[-_MAX_TERMINAL_TEXT_CHARS:]
|
||||
decoder = json.JSONDecoder()
|
||||
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
|
||||
pos = text.rfind("{", 0, pos)
|
||||
return None
|
||||
|
||||
|
||||
def _valid_terminal_record(obj: object) -> dict[str, Any] | None:
|
||||
if not isinstance(obj, dict):
|
||||
return None
|
||||
status = obj.get("terminal_status")
|
||||
if not isinstance(status, str):
|
||||
return None
|
||||
status = status.strip().replace("_", "-")
|
||||
if status not in TERMINAL_STATUSES:
|
||||
return None
|
||||
if not isinstance(obj.get("summary"), str):
|
||||
return None
|
||||
for key in ("changed_files", "evidence"):
|
||||
items = obj.get(key)
|
||||
if not isinstance(items, list) or any(not isinstance(i, str) for i in items):
|
||||
return None
|
||||
if "scope_expansion" not in obj or not (
|
||||
obj["scope_expansion"] is None or isinstance(obj["scope_expansion"], str)
|
||||
):
|
||||
return None
|
||||
record = dict(obj)
|
||||
record["terminal_status"] = status
|
||||
return record
|
||||
|
||||
|
||||
def decode_terminal_record(
|
||||
adapter: str,
|
||||
*,
|
||||
event_lines: Sequence[str | None],
|
||||
stdout_tail: str,
|
||||
result_text: str | None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Decode the worker terminal record from the adapter's real output.
|
||||
|
||||
``event_lines`` are recent complete stdout lines that may carry a terminal
|
||||
envelope, oldest first; ``None`` marks an envelope line too large to keep.
|
||||
Event-stream adapters are read only from their final assistant/result
|
||||
envelope, so prompt echoes and tool output never count as a record.
|
||||
"""
|
||||
if adapter in ("cursor", "claude"):
|
||||
return extract_terminal_record(_cursor_final_text(event_lines))
|
||||
if adapter == "antigravity":
|
||||
return extract_terminal_record(_agy_final_text(event_lines, stdout_tail))
|
||||
if adapter == "pi":
|
||||
return extract_terminal_record(_pi_final_text(event_lines))
|
||||
record = extract_terminal_record(result_text)
|
||||
if record is None:
|
||||
record = extract_terminal_record(stdout_tail)
|
||||
return record
|
||||
|
||||
|
||||
def _event_objects(event_lines: Sequence[str | None]) -> list[dict[str, Any] | None]:
|
||||
"""Newest-first parsed events; ``None`` keeps the oversized-envelope marker."""
|
||||
out: list[dict[str, Any] | None] = []
|
||||
for line in reversed(event_lines):
|
||||
if line is None:
|
||||
out.append(None)
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if isinstance(obj, dict):
|
||||
out.append(obj)
|
||||
return out
|
||||
|
||||
|
||||
def _cursor_final_text(event_lines: Sequence[str | None]) -> str | None:
|
||||
for obj in _event_objects(event_lines):
|
||||
if obj is None:
|
||||
return None
|
||||
if obj.get("type") != "result":
|
||||
continue
|
||||
if obj.get("is_error") is not False or obj.get("subtype") != "success":
|
||||
return None
|
||||
text = obj.get("result")
|
||||
return text if isinstance(text, str) else None
|
||||
return None
|
||||
|
||||
|
||||
def _agy_final_text(
|
||||
event_lines: Sequence[str | None], stdout_tail: str
|
||||
) -> str | None:
|
||||
candidates = _event_objects(event_lines)
|
||||
try:
|
||||
whole = json.loads(stdout_tail.strip())
|
||||
except json.JSONDecodeError:
|
||||
whole = None
|
||||
if isinstance(whole, dict):
|
||||
candidates.append(whole)
|
||||
for obj in candidates:
|
||||
if obj is None:
|
||||
return None
|
||||
if obj.get("event") == "result":
|
||||
payload = obj.get("result")
|
||||
elif "event" not in obj and "response" in obj:
|
||||
payload = obj
|
||||
else:
|
||||
continue
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
if payload.get("is_error") or payload.get("error"):
|
||||
return None
|
||||
text = payload.get("response")
|
||||
return text if isinstance(text, str) else None
|
||||
return None
|
||||
|
||||
|
||||
def _pi_final_text(event_lines: Sequence[str | None]) -> str | None:
|
||||
for obj in _event_objects(event_lines):
|
||||
if obj is None:
|
||||
return None
|
||||
if obj.get("type") != "message_end":
|
||||
continue
|
||||
message = obj.get("message")
|
||||
if not isinstance(message, dict) or message.get("role") != "assistant":
|
||||
continue
|
||||
if message.get("stopReason") in ("error", "aborted"):
|
||||
return None
|
||||
content = message.get("content")
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
if not isinstance(content, list):
|
||||
return None
|
||||
return "".join(
|
||||
block["text"]
|
||||
for block in content
|
||||
if isinstance(block, dict)
|
||||
and block.get("type") == "text"
|
||||
and isinstance(block.get("text"), str)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _expand_argv(
|
||||
items: tuple[str, ...],
|
||||
values: dict[str, str],
|
||||
@@ -480,11 +671,15 @@ def _sanitize_display(
|
||||
|
||||
__all__ = [
|
||||
"MAX_UNIT_PACKET_BYTES",
|
||||
"TERMINAL_RECORD_SCHEMA",
|
||||
"TERMINAL_STATUSES",
|
||||
"UNIT_PACKET_DISPLAY_MARKER",
|
||||
"AdapterProbe",
|
||||
"AdapterSpec",
|
||||
"BUILTIN_SPECS",
|
||||
"Invocation",
|
||||
"build_invocation",
|
||||
"decode_terminal_record",
|
||||
"extract_terminal_record",
|
||||
"probe_adapter",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user