Files
codex-mmo/libexec/mmo_app_server.py
T

2340 lines
97 KiB
Python
Raw Normal View History

2026-08-24 08:11:59 -07:00
#!/usr/bin/env python3
"""Codex app-server transport and persistent-thread lifecycle primitives.
Root runs and detached workers each own one client through this boundary. MMO
callers control workers through the authenticated Agent-MCP kernel rather than
connecting to a worker's private app-server process directly.
"""
from __future__ import annotations
import base64
import contextlib
import datetime as dt
import hashlib
import json
import os
import random
import re
import socket
import struct
import subprocess
import tempfile
import threading
import time
from collections import deque
from collections.abc import Callable, Mapping, Sequence
from pathlib import Path
from typing import Any
from mmo_util import (
append_jsonl,
atomic_write_text,
bounded_text,
package_version,
strict_json_loads,
terminate_process_group,
utc_now,
)
from mmo_version import APP_SERVER_PROTOCOL_CODEX_VERSION
class AppServerError(RuntimeError):
"""Codex app-server transport or protocol failure."""
class ControlDeliveryUnknown(AppServerError):
"""A worker control request may have been applied before transport failed."""
class ControlRequestRejected(AppServerError):
"""A worker explicitly rejected a delivered control request."""
# Initialization is local, while thread lifecycle requests may start or resume
# required MCP servers. Keep the latter generous enough for slow external
# integrations without turning individual model turns into wall-clock timers.
APP_SERVER_INITIALIZE_TIMEOUT_SECONDS = 60.0
APP_SERVER_LIFECYCLE_TIMEOUT_SECONDS = 1200.0
APP_SERVER_RECOVERY_DELAYS_SECONDS = (1.0, 5.0, 15.0)
APP_SERVER_GOAL_OBJECTIVE_MAX_CHARS = 4000
APP_SERVER_DYNAMIC_TOOL_TIMEOUT_SECONDS = APP_SERVER_LIFECYCLE_TIMEOUT_SECONDS + 60.0
PARTIAL_EVENT_WINDOW_BYTES = 32 * 1024 * 1024
APP_SERVER_PROTOCOL_FILE_COUNT = 401
APP_SERVER_PROTOCOL_SHA256 = "fcfeaf23728b96ab73916a21302eb7a16629e67ee99f7ee47b60fad6b6e5ee1a"
APP_SERVER_OVERLOAD_RETRY_DELAYS_SECONDS = (0.1, 0.2, 0.4, 0.8)
MAX_WEBSOCKET_MESSAGE_BYTES = 64 * 1024 * 1024
MAX_CONTROL_MESSAGE_BYTES = 4 * 1024 * 1024
APPROVAL_REQUEST_METHODS = frozenset(
{
"item/commandExecution/requestApproval",
"item/fileChange/requestApproval",
"item/permissions/requestApproval",
"applyPatchApproval",
"execCommandApproval",
}
)
PENDING_SERVER_REQUEST_METHODS = frozenset(
{
"item/tool/requestUserInput",
"mcpServer/elicitation/request",
*APPROVAL_REQUEST_METHODS,
}
)
UNSUPPORTED_SERVER_REQUEST_METHODS = frozenset(
{
"account/chatgptAuthTokens/refresh",
"attestation/generate",
}
)
_RESPONSES_TOOL_NAME_PATTERN = re.compile(r"[^a-zA-Z0-9_-]+")
_DYNAMIC_MCP_TOOL_PREFIX = "mmo_mcp__"
def _responses_tool_name(value: str) -> str:
"""Return a non-empty Responses-compatible identifier component."""
normalized = _RESPONSES_TOOL_NAME_PATTERN.sub("_", value).strip("_")
return normalized or "tool"
def _flat_mcp_dynamic_tool_name(server: str, tool: str, *, salt: str = "") -> str:
"""Build the flat compatibility name for one namespaced MCP function."""
# Codex reserves the native ``mcp__`` namespace and rejects dynamic tool
# declarations using it. Keep the shim visibly MMO-owned while preserving
# the server/tool split in the model-facing name.
base = f"{_DYNAMIC_MCP_TOOL_PREFIX}{_responses_tool_name(server)}__{_responses_tool_name(tool)}"
if len(base) <= 128 and not salt:
return base
digest = hashlib.sha256(f"{server}\0{tool}\0{salt}".encode()).hexdigest()[:12]
suffix = f"__{digest}"
return base[: 128 - len(suffix)].rstrip("_") + suffix
def _dynamic_tool_content_items(result: Mapping[str, Any]) -> list[dict[str, Any]]:
"""Translate one MCP call result into Codex dynamic-tool content items."""
items: list[dict[str, Any]] = []
decoded_text_values: list[Any] = []
content = result.get("content")
if isinstance(content, list):
for block in content:
if not isinstance(block, Mapping):
items.append(
{
"type": "inputText",
"text": json.dumps(block, ensure_ascii=False, allow_nan=False),
}
)
continue
block_type = block.get("type")
if block_type == "text" and isinstance(block.get("text"), str):
items.append({"type": "inputText", "text": block["text"]})
with contextlib.suppress(json.JSONDecodeError, ValueError):
decoded_text_values.append(strict_json_loads(block["text"]))
elif (
block_type == "image"
and isinstance(block.get("data"), str)
and isinstance(block.get("mimeType"), str)
):
items.append(
{
"type": "inputImage",
"imageUrl": f"data:{block['mimeType']};base64,{block['data']}",
}
)
elif (
block_type == "audio"
and isinstance(block.get("data"), str)
and isinstance(block.get("mimeType"), str)
):
items.append(
{
"type": "inputAudio",
"audioUrl": f"data:{block['mimeType']};base64,{block['data']}",
}
)
else:
items.append(
{
"type": "inputText",
"text": json.dumps(
dict(block),
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
),
}
)
structured = result.get("structuredContent")
structured_already_present = structured in decoded_text_values
if isinstance(structured, Mapping) and len(structured) == 1:
structured_already_present = (
structured_already_present or next(iter(structured.values())) in decoded_text_values
)
if structured is not None and not structured_already_present:
items.append(
{
"type": "inputText",
"text": "structuredContent="
+ json.dumps(
structured,
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
),
}
)
if not items:
items.append({"type": "inputText", "text": ""})
return items
_PROVIDER_LIMIT_PATTERN = re.compile(
r"(?:usage\s+limit|limit\s+(?:reached|exhausted)|quota|rate\s+limit)",
re.IGNORECASE,
)
_PROVIDER_RESET_PATTERN = re.compile(
r"(?:limit\s+will\s+reset|reset)\s+at\s+"
r"(?P<value>\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2})"
r"(?:\s*(?P<zone>Z|UTC|[+-]\d{2}:?\d{2}))?",
re.IGNORECASE,
)
_RETRYABLE_CODEX_TRANSPORT_ERRORS = frozenset(
{
"serverOverloaded",
"internalServerError",
"httpConnectionFailed",
"responseStreamConnectionFailed",
"responseStreamDisconnected",
"responseTooManyFailedAttempts",
}
)
def normalize_turn_failure(turn: Mapping[str, Any]) -> dict[str, Any] | None:
"""Translate one authoritative failed turn into durable lifecycle evidence.
Codex providers do not currently expose a common quota error enum. Preserve
the complete error object, prefer its structured fields, and use the human
message only to distinguish a provider limit from a generic failed turn.
"""
if turn.get("status") != "failed":
return None
raw_error = turn.get("error")
error = dict(raw_error) if isinstance(raw_error, Mapping) else {}
message_value = error.get("message") if error else raw_error
message = str(message_value or "app-server turn failed")
details = error.get("additionalDetails")
searchable = "\n".join(
value for value in (message, str(details) if details is not None else "") if value
)
codex_error_info = error.get("codexErrorInfo")
codex_error_kind = (
codex_error_info
if isinstance(codex_error_info, str)
else next(iter(codex_error_info), None)
if isinstance(codex_error_info, Mapping)
else None
)
kind = "turn_failed"
retryable = False
if codex_error_kind == "usageLimitExceeded" or _PROVIDER_LIMIT_PATTERN.search(searchable):
kind = "provider_usage_limited"
retryable = True
elif "failed to parse tool call arguments" in searchable.lower():
kind = "malformed_tool_arguments"
retryable = True
elif (
"stream disconnected" in searchable.lower()
or codex_error_kind in _RETRYABLE_CODEX_TRANSPORT_ERRORS
):
kind = "provider_transport"
retryable = True
retry_at: str | None = None
retry_at_raw: str | None = None
retry_at_timezone: str | None = None
match = _PROVIDER_RESET_PATTERN.search(searchable)
if match is not None:
value = match.group("value")
zone = match.group("zone")
retry_at_raw = value + (f" {zone}" if zone else "")
if zone is not None:
retry_at_timezone = zone
normalized_zone = "+00:00" if zone.upper() in {"Z", "UTC"} else zone
if len(normalized_zone) == 5 and normalized_zone[3] != ":":
normalized_zone = normalized_zone[:3] + ":" + normalized_zone[3:]
with contextlib.suppress(ValueError):
retry_at = dt.datetime.fromisoformat(
value.replace(" ", "T") + normalized_zone
).isoformat()
result: dict[str, Any] = {
"kind": kind,
"source": "turn/completed",
"turn_id": turn.get("id"),
"turn_status": "failed",
"message": message,
"codex_error_info": codex_error_info,
"raw_error": raw_error,
"retryable": retryable,
"observed_at": utc_now(),
}
if retry_at_raw is not None:
result["retry_at_raw"] = retry_at_raw
if retry_at is not None and retry_at_timezone is not None:
result["retry_at"] = retry_at
result["retry_at_timezone"] = retry_at_timezone
return result
def app_server_message_thread_id(message: Mapping[str, Any]) -> str | None:
"""Return the thread identity carried by a notification or server request."""
raw_params = message.get("params")
if not isinstance(raw_params, Mapping):
return None
for key in ("threadId", "conversationId"):
value = raw_params.get(key)
if isinstance(value, str):
return value
goal = raw_params.get("goal")
if isinstance(goal, Mapping) and isinstance(goal.get("threadId"), str):
return str(goal["threadId"])
thread = raw_params.get("thread")
if isinstance(thread, Mapping) and isinstance(thread.get("id"), str):
return str(thread["id"])
return None
def app_server_socket_path(identity: str) -> Path:
"""Return a stable, private, portable AF_UNIX path for one logical host."""
runtime_root = os.environ.get("XDG_RUNTIME_DIR")
if runtime_root:
base = Path(runtime_root).expanduser().resolve() / "codex-mmo"
else:
owner = hashlib.sha256(f"{os.getuid()}:{Path.home().resolve()}".encode()).hexdigest()[:12]
base = Path(tempfile.gettempdir()).resolve() / f"codex-mmo-{owner}"
if base.exists() and (base.is_symlink() or not base.is_dir()):
raise AppServerError(f"app-server socket root is unsafe: {base}")
base.mkdir(parents=True, exist_ok=True, mode=0o700)
with contextlib.suppress(OSError):
base.chmod(0o700)
name = hashlib.sha256(identity.encode("utf-8")).hexdigest()[:32] + ".sock"
path = base / name
if len(os.fsencode(path)) >= 104:
raise AppServerError("app-server runtime directory is too long for AF_UNIX")
return path
def app_server_listen_command(
binary: str,
command_flags: Sequence[str],
socket_path: Path,
) -> list[str]:
return [
binary,
*command_flags,
"app-server",
"--listen",
f"unix://{socket_path}",
]
def app_server_protocol_status(binary: str | None) -> dict[str, Any]:
"""Require the reviewed Codex 0.149 experimental app-server contract exactly."""
if not binary:
return {
"passed": False,
"expected_codex_version": APP_SERVER_PROTOCOL_CODEX_VERSION,
"expected_schema_sha256": APP_SERVER_PROTOCOL_SHA256,
"error": "Codex binary not found",
}
try:
version_result = subprocess.run(
[binary, "--version"],
text=True,
capture_output=True,
timeout=10,
check=False,
)
version_text = version_result.stdout.strip()
observed_version = version_text.removeprefix("codex-cli ").strip()
with tempfile.TemporaryDirectory(prefix="codex-mmo-app-schema-") as temporary:
result = subprocess.run(
[
binary,
"app-server",
"generate-json-schema",
"--experimental",
"--out",
temporary,
],
text=True,
capture_output=True,
timeout=30,
check=False,
)
schema_root = Path(temporary)
files = sorted(path for path in schema_root.rglob("*") if path.is_file())
digest = hashlib.sha256()
for path in files:
digest.update(path.relative_to(schema_root).as_posix().encode("utf-8"))
digest.update(b"\0")
digest.update(path.read_bytes())
observed_sha256 = digest.hexdigest()
version_matches = (
version_result.returncode == 0 and observed_version == APP_SERVER_PROTOCOL_CODEX_VERSION
)
schema_matches = (
result.returncode == 0
and len(files) == APP_SERVER_PROTOCOL_FILE_COUNT
and observed_sha256 == APP_SERVER_PROTOCOL_SHA256
)
return {
"passed": version_matches and schema_matches,
"expected_codex_version": APP_SERVER_PROTOCOL_CODEX_VERSION,
"observed_codex_version": observed_version,
"expected_schema_files": APP_SERVER_PROTOCOL_FILE_COUNT,
"observed_schema_files": len(files),
"expected_schema_sha256": APP_SERVER_PROTOCOL_SHA256,
"observed_schema_sha256": observed_sha256,
"version_matches": version_matches,
"schema_matches": schema_matches,
"schema_exit_code": result.returncode,
"stderr": (version_result.stderr + result.stderr)[-2000:],
}
except (OSError, subprocess.SubprocessError) as exc:
return {
"passed": False,
"expected_codex_version": APP_SERVER_PROTOCOL_CODEX_VERSION,
"expected_schema_sha256": APP_SERVER_PROTOCOL_SHA256,
"error": f"{type(exc).__name__}: {exc}",
}
def require_app_server_codex_version(binary: str) -> None:
"""Reject session admission unless the executable is the reviewed Codex release."""
try:
result = subprocess.run(
[binary, "--version"],
text=True,
capture_output=True,
timeout=10,
check=False,
)
except (OSError, subprocess.SubprocessError) as exc:
raise AppServerError(f"unable to verify Codex app-server version: {exc}") from exc
observed = result.stdout.strip().removeprefix("codex-cli ").strip()
if result.returncode != 0 or observed != APP_SERVER_PROTOCOL_CODEX_VERSION:
raise AppServerError(
"Codex app-server protocol version mismatch: expected "
f"{APP_SERVER_PROTOCOL_CODEX_VERSION}, observed {observed or 'unavailable'}"
)
def _exact_keys(
value: Mapping[str, Any], *, required: set[str], allowed: set[str], label: str
) -> None:
missing = sorted(required - set(value))
unexpected = sorted(set(value) - allowed)
if missing or unexpected:
details: list[str] = []
if missing:
details.append("missing " + ", ".join(missing))
if unexpected:
details.append("unexpected " + ", ".join(unexpected))
raise ValueError(f"{label} has invalid fields: {'; '.join(details)}")
def _network_policy_amendment(value: Any, label: str) -> None:
if not isinstance(value, Mapping):
raise ValueError(f"{label} must be an object")
_exact_keys(value, required={"action", "host"}, allowed={"action", "host"}, label=label)
if value["action"] not in {"allow", "deny"} or not isinstance(value["host"], str):
raise ValueError(f"{label} must contain an allow/deny action and string host")
def _command_approval_decision(value: Any) -> None:
if isinstance(value, str):
if value not in {"accept", "acceptForSession", "decline", "cancel"}:
raise ValueError("command approval decision is invalid")
return
if not isinstance(value, Mapping) or len(value) != 1:
raise ValueError("command approval decision must be a supported string or object")
if "acceptWithExecpolicyAmendment" in value:
amendment = value["acceptWithExecpolicyAmendment"]
if not isinstance(amendment, Mapping):
raise ValueError("exec-policy amendment must be an object")
_exact_keys(
amendment,
required={"execpolicy_amendment"},
allowed={"execpolicy_amendment"},
label="exec-policy amendment",
)
rules = amendment["execpolicy_amendment"]
if not isinstance(rules, list) or not all(isinstance(item, str) for item in rules):
raise ValueError("execpolicy_amendment must be an array of strings")
return
if "applyNetworkPolicyAmendment" in value:
amendment = value["applyNetworkPolicyAmendment"]
if not isinstance(amendment, Mapping):
raise ValueError("network-policy amendment wrapper must be an object")
_exact_keys(
amendment,
required={"network_policy_amendment"},
allowed={"network_policy_amendment"},
label="network-policy amendment wrapper",
)
_network_policy_amendment(amendment["network_policy_amendment"], "network-policy amendment")
return
raise ValueError("command approval decision object is invalid")
def _legacy_approval_decision(value: Any) -> None:
if isinstance(value, str):
if value not in {
"approved",
"approved_for_session",
"approved_mcp_policy_amendment",
"timed_out",
"abort",
}:
raise ValueError("legacy approval decision is invalid")
return
if not isinstance(value, Mapping) or len(value) != 1:
raise ValueError("legacy approval decision must be a supported string or object")
if "approved_execpolicy_amendment" in value:
amendment = value["approved_execpolicy_amendment"]
if not isinstance(amendment, Mapping):
raise ValueError("approved exec-policy amendment must be an object")
_exact_keys(
amendment,
required={"proposed_execpolicy_amendment"},
allowed={"proposed_execpolicy_amendment"},
label="approved exec-policy amendment",
)
rules = amendment["proposed_execpolicy_amendment"]
if not isinstance(rules, list) or not all(isinstance(item, str) for item in rules):
raise ValueError("proposed_execpolicy_amendment must be an array of strings")
return
if "network_policy_amendment" in value:
wrapper = value["network_policy_amendment"]
if not isinstance(wrapper, Mapping):
raise ValueError("legacy network-policy amendment wrapper must be an object")
_exact_keys(
wrapper,
required={"network_policy_amendment"},
allowed={"network_policy_amendment"},
label="legacy network-policy amendment wrapper",
)
_network_policy_amendment(
wrapper["network_policy_amendment"], "legacy network-policy amendment"
)
return
if "denied" in value:
denied = value["denied"]
if not isinstance(denied, Mapping):
raise ValueError("denied approval decision must be an object")
_exact_keys(
denied,
required={"rejection"},
allowed={"rejection"},
label="denied approval decision",
)
if not isinstance(denied["rejection"], str):
raise ValueError("approval rejection must be a string")
return
raise ValueError("legacy approval decision object is invalid")
def _permission_path(value: Any) -> None:
if not isinstance(value, Mapping) or not isinstance(value.get("type"), str):
raise ValueError("permission path must be a tagged object")
path_type = value["type"]
if path_type == "path":
required, allowed, field = {"type", "path"}, {"type", "path"}, "path"
elif path_type == "glob_pattern":
required, allowed, field = {"type", "pattern"}, {"type", "pattern"}, "pattern"
elif path_type == "special":
required, allowed, field = {"type", "value"}, {"type", "value"}, "value"
else:
raise ValueError("permission path type is invalid")
_exact_keys(value, required=required, allowed=allowed, label="permission path")
if path_type != "special":
if not isinstance(value[field], str):
raise ValueError(f"permission path {field} must be a string")
return
special = value[field]
if not isinstance(special, Mapping) or not isinstance(special.get("kind"), str):
raise ValueError("special permission path must be a tagged object")
kind = special["kind"]
if kind in {"root", "minimal", "tmpdir", "slash_tmp"}:
_exact_keys(special, required={"kind"}, allowed={"kind"}, label="special path")
elif kind == "project_roots":
_exact_keys(
special,
required={"kind"},
allowed={"kind", "subpath"},
label="project-roots special path",
)
if (
"subpath" in special
and special["subpath"] is not None
and not isinstance(special["subpath"], str)
):
raise ValueError("project-roots subpath must be a string or null")
elif kind == "unknown":
_exact_keys(
special,
required={"kind", "path"},
allowed={"kind", "path", "subpath"},
label="unknown special path",
)
if not isinstance(special["path"], str) or (
"subpath" in special
and special["subpath"] is not None
and not isinstance(special["subpath"], str)
):
raise ValueError("unknown special path values are invalid")
else:
raise ValueError("special permission path kind is invalid")
def _permission_profile(value: Any) -> None:
if not isinstance(value, Mapping):
raise ValueError("permissions must be an object")
_exact_keys(
value,
required=set(),
allowed={"fileSystem", "network"},
label="permissions",
)
filesystem = value.get("fileSystem")
if filesystem is not None:
if not isinstance(filesystem, Mapping):
raise ValueError("permissions.fileSystem must be an object or null")
_exact_keys(
filesystem,
required=set(),
allowed={"entries", "globScanMaxDepth", "read", "write"},
label="permissions.fileSystem",
)
entries = filesystem.get("entries")
if entries is not None:
if not isinstance(entries, list):
raise ValueError("permissions.fileSystem.entries must be an array or null")
for entry in entries:
if not isinstance(entry, Mapping):
raise ValueError("filesystem permission entry must be an object")
_exact_keys(
entry,
required={"access", "path"},
allowed={"access", "path"},
label="filesystem permission entry",
)
if entry["access"] not in {"read", "write", "deny"}:
raise ValueError("filesystem permission access is invalid")
_permission_path(entry["path"])
depth = filesystem.get("globScanMaxDepth")
if depth is not None and (
not isinstance(depth, int) or isinstance(depth, bool) or depth < 1
):
raise ValueError("globScanMaxDepth must be a positive integer or null")
for field in ("read", "write"):
paths = filesystem.get(field)
if paths is not None and (
not isinstance(paths, list) or not all(isinstance(item, str) for item in paths)
):
raise ValueError(f"permissions.fileSystem.{field} must be strings or null")
network = value.get("network")
if network is not None:
if not isinstance(network, Mapping):
raise ValueError("permissions.network must be an object or null")
_exact_keys(
network,
required=set(),
allowed={"enabled"},
label="permissions.network",
)
if network.get("enabled") is not None and not isinstance(network["enabled"], bool):
raise ValueError("permissions.network.enabled must be a boolean or null")
def validate_server_request_response(method: str, response: Mapping[str, Any]) -> None:
"""Validate one controller response against Codex app-server v2's method shape."""
if method == "item/tool/requestUserInput":
_exact_keys(response, required={"answers"}, allowed={"answers"}, label=method)
answers = response["answers"]
if not isinstance(answers, Mapping) or not all(isinstance(key, str) for key in answers):
raise ValueError("user-input answers must map string question IDs to answer objects")
for answer in answers.values():
if not isinstance(answer, Mapping):
raise ValueError("each user-input answer must be an object")
_exact_keys(
answer,
required={"answers"},
allowed={"answers"},
label="user-input answer",
)
values = answer["answers"]
if not isinstance(values, list) or not all(isinstance(item, str) for item in values):
raise ValueError("each user-input answer must contain an array of strings")
return
if method == "mcpServer/elicitation/request":
_exact_keys(
response,
required={"action"},
allowed={"action", "content", "_meta"},
label=method,
)
if response["action"] not in {"accept", "decline", "cancel"}:
raise ValueError("MCP elicitation action is invalid")
return
if method in {
"item/commandExecution/requestApproval",
"item/fileChange/requestApproval",
"applyPatchApproval",
"execCommandApproval",
}:
_exact_keys(response, required={"decision"}, allowed={"decision"}, label=method)
if method == "item/commandExecution/requestApproval":
_command_approval_decision(response["decision"])
elif method == "item/fileChange/requestApproval":
if response["decision"] not in {"accept", "acceptForSession", "decline", "cancel"}:
raise ValueError("file-change approval decision is invalid")
else:
_legacy_approval_decision(response["decision"])
return
if method == "item/permissions/requestApproval":
_exact_keys(
response,
required={"permissions"},
allowed={"permissions", "scope", "strictAutoReview"},
label=method,
)
_permission_profile(response["permissions"])
if response.get("scope", "turn") not in {"turn", "session"}:
raise ValueError("permission grant scope is invalid")
if response.get("strictAutoReview") is not None and not isinstance(
response["strictAutoReview"], bool
):
raise ValueError("strictAutoReview must be a boolean or null")
return
raise ValueError(f"unsupported pending app-server request method: {method}")
class UnixWebSocket:
"""Minimal RFC 6455 client for Codex's Unix-domain app-server listener."""
def __init__(self, path: Path, *, timeout: float) -> None:
if not path.is_absolute():
raise AppServerError("app-server Unix socket path must be absolute")
if len(os.fsencode(path)) >= 104:
raise AppServerError("app-server Unix socket path exceeds the portable AF_UNIX limit")
self.path = path
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._socket.settimeout(timeout)
self._buffer = bytearray()
self._send_lock = threading.Lock()
self._closed = False
try:
self._socket.connect(str(path))
self._handshake()
self._socket.settimeout(None)
except BaseException:
self._socket.close()
self._closed = True
raise
@property
def closed(self) -> bool:
return self._closed
def _handshake(self) -> None:
key = base64.b64encode(os.urandom(16)).decode("ascii")
request = (
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {key}\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n"
).encode("ascii")
self._socket.sendall(request)
response = bytearray()
marker = b"\r\n\r\n"
while marker not in response:
chunk = self._socket.recv(4096)
if not chunk:
raise AppServerError("app-server closed during WebSocket handshake")
response.extend(chunk)
if len(response) > 64 * 1024:
raise AppServerError("app-server WebSocket handshake exceeds 64 KiB")
raw_headers, trailing = bytes(response).split(marker, 1)
lines = raw_headers.decode("iso-8859-1").split("\r\n")
status = lines[0].split(" ", 2) if lines else []
if len(status) < 2 or status[0] != "HTTP/1.1" or status[1] != "101":
raise AppServerError(
f"app-server rejected WebSocket upgrade: {lines[0] if lines else ''}"
)
headers: dict[str, list[str]] = {}
for line in lines[1:]:
if ":" not in line:
raise AppServerError("app-server returned a malformed WebSocket header")
name, value = line.split(":", 1)
name = name.strip().lower()
if not name:
raise AppServerError("app-server returned an empty WebSocket header name")
headers.setdefault(name, []).append(value.strip())
expected = base64.b64encode(
hashlib.sha1( # noqa: S324 - mandated by RFC 6455, not used for security
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode("ascii")
).digest()
).decode("ascii")
if headers.get("sec-websocket-accept") != [expected]:
raise AppServerError("app-server returned an invalid WebSocket accept digest")
upgrade_tokens = {
token.strip().casefold()
for value in headers.get("upgrade", [])
for token in value.split(",")
if token.strip()
}
connection_tokens = {
token.strip().casefold()
for value in headers.get("connection", [])
for token in value.split(",")
if token.strip()
}
if "websocket" not in upgrade_tokens:
raise AppServerError("app-server WebSocket upgrade header is missing")
if "upgrade" not in connection_tokens:
raise AppServerError("app-server WebSocket connection upgrade token is missing")
if "sec-websocket-extensions" in headers or "sec-websocket-protocol" in headers:
raise AppServerError("app-server negotiated an unrequested WebSocket feature")
self._buffer.extend(trailing)
def _recv_exact(self, size: int) -> bytes:
while len(self._buffer) < size:
chunk = self._socket.recv(max(4096, size - len(self._buffer)))
if not chunk:
raise AppServerError("app-server WebSocket transport closed")
self._buffer.extend(chunk)
value = bytes(self._buffer[:size])
del self._buffer[:size]
return value
@staticmethod
def _frame(opcode: int, payload: bytes) -> bytes:
if len(payload) > MAX_WEBSOCKET_MESSAGE_BYTES:
raise AppServerError("app-server WebSocket message exceeds 64 MiB")
if opcode >= 0x8 and len(payload) > 125:
raise AppServerError("app-server WebSocket control frame exceeds 125 bytes")
if opcode == 0x8 and len(payload) == 1:
raise AppServerError("app-server WebSocket close payload is malformed")
first = 0x80 | opcode
length = len(payload)
if length < 126:
header = bytes((first, 0x80 | length))
elif length <= 0xFFFF:
header = bytes((first, 0x80 | 126)) + struct.pack("!H", length)
else:
header = bytes((first, 0x80 | 127)) + struct.pack("!Q", length)
mask = os.urandom(4)
masked = bytes(value ^ mask[index & 3] for index, value in enumerate(payload))
return header + mask + masked
def send_text(self, value: str) -> None:
encoded = value.encode("utf-8")
with self._send_lock:
if self._closed:
raise AppServerError("app-server WebSocket transport is closed")
try:
self._socket.sendall(self._frame(0x1, encoded))
except OSError as exc:
raise AppServerError(f"app-server WebSocket write failed: {exc}") from exc
def _send_control(self, opcode: int, payload: bytes = b"") -> None:
with self._send_lock:
if self._closed:
return
self._socket.sendall(self._frame(opcode, payload))
def _close_socket(self) -> None:
if self._closed:
return
self._closed = True
with contextlib.suppress(OSError):
self._socket.shutdown(socket.SHUT_RDWR)
with contextlib.suppress(OSError):
self._socket.close()
def _protocol_failure(self, message: str, *, close_code: int = 1002) -> None:
with contextlib.suppress(OSError, AppServerError):
self._send_control(0x8, struct.pack("!H", close_code))
self._close_socket()
raise AppServerError(message)
@staticmethod
def _valid_close_code(code: int) -> bool:
return (
code
in {
1000,
1001,
1002,
1003,
1007,
1008,
1009,
1010,
1011,
1012,
1013,
1014,
}
or 3000 <= code <= 4999
)
def receive_text(self) -> str:
fragments = bytearray()
fragmented = False
while True:
first, second = self._recv_exact(2)
final = bool(first & 0x80)
if first & 0x70:
self._protocol_failure("app-server WebSocket frame has unsupported RSV bits")
opcode = first & 0x0F
masked = bool(second & 0x80)
if masked:
self._protocol_failure("app-server sent a masked WebSocket server frame")
length = second & 0x7F
control = opcode >= 0x8
if control and (not final or length > 125):
self._protocol_failure("app-server sent a malformed WebSocket control frame")
if length == 126:
length = struct.unpack("!H", self._recv_exact(2))[0]
if length < 126:
self._protocol_failure("app-server used a non-minimal WebSocket frame length")
elif length == 127:
encoded_length = self._recv_exact(8)
if encoded_length[0] & 0x80:
self._protocol_failure("app-server WebSocket frame length has its high bit set")
length = struct.unpack("!Q", encoded_length)[0]
if length <= 0xFFFF:
self._protocol_failure("app-server used a non-minimal WebSocket frame length")
if length > MAX_WEBSOCKET_MESSAGE_BYTES:
self._protocol_failure(
"app-server WebSocket message exceeds 64 MiB", close_code=1009
)
payload = self._recv_exact(length)
if opcode == 0x8:
if len(payload) == 1:
self._protocol_failure("app-server sent a malformed WebSocket close payload")
if payload:
close_code = struct.unpack("!H", payload[:2])[0]
if not self._valid_close_code(close_code):
self._protocol_failure("app-server sent an invalid WebSocket close code")
try:
payload[2:].decode("utf-8")
except UnicodeDecodeError:
self._protocol_failure(
"app-server sent a non-UTF-8 WebSocket close reason",
close_code=1007,
)
with contextlib.suppress(OSError, AppServerError):
self._send_control(0x8, payload)
self._close_socket()
raise AppServerError("app-server WebSocket transport closed")
if opcode == 0x9:
self._send_control(0xA, payload)
continue
if opcode == 0xA:
continue
if opcode == 0x1:
if fragmented:
self._protocol_failure("app-server started a nested WebSocket message")
fragments.extend(payload)
fragmented = not final
elif opcode == 0x0 and fragmented:
fragments.extend(payload)
fragmented = not final
elif opcode == 0x2:
self._protocol_failure(
"app-server emitted unsupported WebSocket binary data", close_code=1003
)
else:
self._protocol_failure(f"unsupported app-server WebSocket opcode: {opcode}")
if len(fragments) > MAX_WEBSOCKET_MESSAGE_BYTES:
self._protocol_failure(
"app-server WebSocket message exceeds 64 MiB", close_code=1009
)
if not fragmented:
try:
return fragments.decode("utf-8")
except UnicodeDecodeError as exc:
with contextlib.suppress(OSError, AppServerError):
self._send_control(0x8, struct.pack("!H", 1007))
self._close_socket()
raise AppServerError("app-server emitted non-UTF-8 WebSocket text") from exc
def close(self) -> None:
if self._closed:
return
with contextlib.suppress(OSError, AppServerError):
self._send_control(0x8, struct.pack("!H", 1000))
self._close_socket()
class AppServerClient:
"""One protocol client connected to an isolated Unix app-server host."""
def __init__(
self,
*,
socket_path: Path,
cwd: Path,
env: Mapping[str, str],
events_path: Path,
stderr_path: Path,
approval_policy: str,
command: Sequence[str] | None = None,
on_message: Callable[[dict[str, Any]], None] | None = None,
) -> None:
self.command = list(command) if command is not None else None
self.socket_path = socket_path.expanduser().resolve()
self.cwd = cwd
self.env = dict(env)
self.events_path = events_path
self.stderr_path = stderr_path
self.approval_policy = approval_policy
self.on_message = on_message
self.process: subprocess.Popen[bytes] | None = None
self.transport: UnixWebSocket | None = None
self.initialize_result: dict[str, Any] | None = None
self._stderr: Any = None
self._reader: threading.Thread | None = None
self._write_lock = threading.Lock()
self._condition = threading.Condition()
self._responses: dict[int, dict[str, Any]] = {}
self._server_requests: dict[str, dict[str, Any]] = {}
self._dynamic_mcp_tools: dict[str, tuple[str, str]] = {}
self._dynamic_call_threads: set[threading.Thread] = set()
self._dynamic_call_lock = threading.Lock()
self._next_id = 1
self._closed = False
self._reader_error: str | None = None
@property
def pid(self) -> int | None:
return self.process.pid if self.process is not None else None
@property
def alive(self) -> bool:
process_alive = self.process is None or self.process.poll() is None
return bool(
process_alive
and self.transport is not None
and not self.transport.closed
and not self._closed
)
@property
def pending_server_requests(self) -> list[dict[str, Any]]:
with self._condition:
return [dict(value) for value in self._server_requests.values()]
def pending_server_requests_for_thread(self, thread_id: str) -> list[dict[str, Any]]:
"""Return only pending requests owned by one thread on a shared host."""
return [
request
for request in self.pending_server_requests
if app_server_message_thread_id(request) == thread_id
]
def install_switchyard_mcp_bridge(
self,
*,
timeout: float = APP_SERVER_LIFECYCLE_TIMEOUT_SECONDS,
) -> list[dict[str, Any]]:
"""Expose configured MCP tools as flat app-server dynamic functions.
Codex 0.149 emits MCP tools as Responses namespace entries. Switchyard
0.2.0 accepts ordinary function tools but drops namespace entries while
translating requests for OpenAI-compatible third-party providers. The
app-server dynamic-tool protocol gives those providers an equivalent
flat function surface without changing Codex or bypassing its MCP
connection, allowlist, approval, and result handling.
"""
cursor: str | None = None
seen_cursors: set[str] = set()
rows: list[Mapping[str, Any]] = []
while True:
params: dict[str, Any] = {
"detail": "toolsAndAuthOnly",
"limit": 100,
}
if cursor is not None:
params["cursor"] = cursor
response = self.request("mcpServerStatus/list", params, timeout=timeout)
data = response.get("data") if isinstance(response, Mapping) else None
if not isinstance(data, list) or not all(isinstance(row, Mapping) for row in data):
raise AppServerError("mcpServerStatus/list returned invalid server data")
rows.extend(data)
next_cursor = response.get("nextCursor")
if next_cursor is None:
break
if not isinstance(next_cursor, str) or next_cursor in seen_cursors:
raise AppServerError("mcpServerStatus/list returned an invalid cursor sequence")
seen_cursors.add(next_cursor)
cursor = next_cursor
routes: dict[str, tuple[str, str]] = {}
specs: list[dict[str, Any]] = []
for row in rows:
server = row.get("name")
tools = row.get("tools")
if not isinstance(server, str) or not isinstance(tools, Mapping):
raise AppServerError("mcpServerStatus/list returned an invalid server row")
for listed_name, raw_tool in sorted(tools.items(), key=lambda item: str(item[0])):
if not isinstance(listed_name, str) or not isinstance(raw_tool, Mapping):
raise AppServerError("mcpServerStatus/list returned an invalid tool row")
tool = raw_tool.get("name", listed_name)
if not isinstance(tool, str) or not tool:
raise AppServerError("mcpServerStatus/list returned an invalid tool name")
name = _flat_mcp_dynamic_tool_name(server, tool)
if name in routes and routes[name] != (server, tool):
name = _flat_mcp_dynamic_tool_name(server, tool, salt=listed_name)
if name in routes:
raise AppServerError(f"flat MCP dynamic tool name collision: {name}")
input_schema = raw_tool.get("inputSchema", {"type": "object"})
if not isinstance(input_schema, Mapping):
raise AppServerError(f"MCP tool {server}.{tool} has an invalid input schema")
description = raw_tool.get("description")
specs.append(
{
"type": "function",
"name": name,
"description": (
str(description)
if isinstance(description, str)
else f"Call {tool} on the {server} MCP server."
),
"inputSchema": dict(input_schema),
}
)
routes[name] = (server, tool)
self._dynamic_mcp_tools = routes
return specs
def _run_dynamic_mcp_tool_call(
self,
request_id: int | str,
message: Mapping[str, Any],
) -> None:
"""Route one flat dynamic function through Codex's own MCP API."""
try:
params = message.get("params")
if not isinstance(params, Mapping):
raise AppServerError("dynamic tool request params are invalid")
thread_id = params.get("threadId")
namespace = params.get("namespace")
flat_name = params.get("tool")
if not isinstance(thread_id, str) or not thread_id:
raise AppServerError("dynamic tool request has no thread identity")
if namespace is not None:
raise AppServerError("flat MCP bridge received a namespaced dynamic tool call")
if not isinstance(flat_name, str) or flat_name not in self._dynamic_mcp_tools:
raise AppServerError(f"unknown flat MCP dynamic tool: {flat_name!r}")
server, tool = self._dynamic_mcp_tools[flat_name]
call_params: dict[str, Any] = {
"threadId": thread_id,
"server": server,
"tool": tool,
}
if "arguments" in params:
call_params["arguments"] = params["arguments"]
result = self.request(
"mcpServer/tool/call",
call_params,
timeout=APP_SERVER_DYNAMIC_TOOL_TIMEOUT_SECONDS,
)
if not isinstance(result, Mapping):
raise AppServerError("mcpServer/tool/call returned a non-object result")
self.respond(
request_id,
{
"contentItems": _dynamic_tool_content_items(result),
"success": result.get("isError") is not True,
},
)
except Exception as exc:
with contextlib.suppress(Exception):
self.respond(
request_id,
{
"contentItems": [
{
"type": "inputText",
"text": f"MCP bridge error: {type(exc).__name__}: {exc}",
}
],
"success": False,
},
)
finally:
with self._dynamic_call_lock:
self._dynamic_call_threads.discard(threading.current_thread())
def _dispatch_dynamic_mcp_tool_call(
self,
request_id: int | str,
message: Mapping[str, Any],
) -> None:
worker = threading.Thread(
target=self._run_dynamic_mcp_tool_call,
args=(request_id, dict(message)),
name=f"codex-app-server-dynamic-tool-{request_id}",
daemon=True,
)
with self._dynamic_call_lock:
self._dynamic_call_threads.add(worker)
worker.start()
@staticmethod
def _server_request_key(request_id: int | str) -> str:
prefix = "integer" if isinstance(request_id, int) else "string"
return f"{prefix}:{request_id}"
def start(
self,
*,
timeout: float = 30.0,
on_started: Callable[[int], None] | None = None,
) -> dict[str, Any]:
if self.transport is not None or self.process is not None:
raise AppServerError("app-server client has already been started")
self.stderr_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
self.socket_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
if self.command is not None:
if self.socket_path.exists() or self.socket_path.is_symlink():
raise AppServerError(
f"refusing to replace an existing app-server socket: {self.socket_path}"
)
self._stderr = self.stderr_path.open("ab", buffering=0)
try:
self.process = subprocess.Popen(
self.command,
stdin=subprocess.DEVNULL,
stdout=self._stderr,
stderr=self._stderr,
cwd=self.cwd,
env=self.env,
start_new_session=True,
close_fds=True,
)
except BaseException:
self._stderr.close()
self._stderr = None
raise
try:
# Publish the isolated process-group identity before performing any
# potentially slow protocol or MCP initialization. This closes the
# bootstrap interval in which an operator stop could otherwise
# lose ownership of the newly spawned app-server descendants.
if on_started is not None and self.process is not None:
on_started(self.process.pid)
deadline = time.monotonic() + timeout
last_error = "socket not ready"
while self.transport is None:
if self.process is not None and self.process.poll() is not None:
raise AppServerError(
f"app-server exited before accepting connections: {self.process.returncode}"
)
remaining = deadline - time.monotonic()
if remaining <= 0:
raise AppServerError(
f"app-server Unix socket did not become ready: {last_error}"
)
try:
self.transport = UnixWebSocket(
self.socket_path,
timeout=min(1.0, remaining),
)
except (AppServerError, OSError) as exc:
last_error = f"{type(exc).__name__}: {exc}"
time.sleep(min(0.05, remaining))
self._reader = threading.Thread(
target=self._read_loop,
name=f"codex-app-server-{self.pid or self.socket_path.name}",
daemon=True,
)
self._reader.start()
result = self.request(
"initialize",
{
"clientInfo": {
"name": "codex-mmo",
"title": "Codex Multi-Model Orchestrator",
"version": package_version(),
},
"capabilities": {"experimentalApi": True},
},
timeout=timeout,
)
except BaseException:
self.close()
raise
if not isinstance(result, dict):
self.close()
raise AppServerError("app-server initialize returned a non-object result")
self.initialize_result = result
self.notify("initialized", {})
return result
def _record(self, direction: str, message: Mapping[str, Any]) -> None:
append_jsonl(
self.events_path,
{
"recorded_at": utc_now(),
"direction": direction,
"message": dict(message),
},
)
def _read_loop(self) -> None:
transport = self.transport
if transport is None:
raise AppServerError("app-server WebSocket transport was not initialized")
try:
while True:
line = transport.receive_text()
try:
value = strict_json_loads(line)
except (json.JSONDecodeError, ValueError) as exc:
raise AppServerError(f"invalid app-server JSON: {exc}") from exc
if not isinstance(value, dict):
raise AppServerError("app-server emitted a non-object protocol message")
if "jsonrpc" in value:
raise AppServerError("app-server emitted an unexpected JSON-RPC version field")
self._record("received", value)
message_id = value.get("id")
if message_id is not None and ("result" in value or "error" in value):
if not isinstance(message_id, int) or isinstance(message_id, bool):
raise AppServerError("app-server response id is not an integer")
if ("result" in value) == ("error" in value):
raise AppServerError(
"app-server response must contain exactly one of result or error"
)
if "error" in value:
error = value["error"]
if not isinstance(error, Mapping):
raise AppServerError("app-server response error is not an object")
code = error.get("code")
if not isinstance(code, int) or isinstance(code, bool):
raise AppServerError("app-server response error code is not an integer")
if not isinstance(error.get("message"), str):
raise AppServerError(
"app-server response error message is not a string"
)
with self._condition:
self._responses[message_id] = value
self._condition.notify_all()
elif message_id is not None and isinstance(value.get("method"), str):
if not (
isinstance(message_id, str)
or (isinstance(message_id, int) and not isinstance(message_id, bool))
):
raise AppServerError("app-server request id must be an integer or string")
key = self._server_request_key(message_id)
method = str(value["method"])
if method == "currentTime/read":
self.respond(message_id, {"currentTimeAt": int(time.time())})
elif method == "item/tool/call" and self._dynamic_mcp_tools:
# A bridge call must run off the reader thread: its
# mcpServer/tool/call request is answered by this same
# reader, so handling it inline would deadlock.
self._dispatch_dynamic_mcp_tool_call(message_id, value)
if self.on_message is not None:
with contextlib.suppress(Exception):
self.on_message(value)
elif method in APPROVAL_REQUEST_METHODS and self.approval_policy == "never":
self._deny_approval(message_id, method)
elif method in PENDING_SERVER_REQUEST_METHODS:
with self._condition:
self._server_requests[key] = value
self._condition.notify_all()
if self.on_message is not None:
with contextlib.suppress(Exception):
self.on_message(value)
elif method in UNSUPPORTED_SERVER_REQUEST_METHODS:
self.respond_error(
message_id,
-32601,
f"{method} is deliberately not provided by Codex MMO",
)
else:
self.respond_error(
message_id,
-32601,
f"unsupported app-server request method: {method}",
)
elif value.get("method") == "serverRequest/resolved":
params = value.get("params")
request_id = params.get("requestId") if isinstance(params, Mapping) else None
if isinstance(request_id, str) or (
isinstance(request_id, int) and not isinstance(request_id, bool)
):
with self._condition:
self._server_requests.pop(self._server_request_key(request_id), None)
self._condition.notify_all()
if self.on_message is not None:
with contextlib.suppress(Exception):
self.on_message(value)
elif self.on_message is not None:
with contextlib.suppress(Exception):
self.on_message(value)
except Exception as exc:
self._reader_error = f"{type(exc).__name__}: {exc}"
finally:
with self._condition:
self._closed = True
self._condition.notify_all()
def _deny_approval(self, request_id: Any, method: str) -> None:
if method in {
"item/commandExecution/requestApproval",
"item/fileChange/requestApproval",
}:
result: dict[str, Any] = {"decision": "decline"}
elif method == "item/permissions/requestApproval":
self.respond_error(request_id, -32001, "permission escalation denied by MMO policy")
return
else:
result = {
"decision": {
"denied": {
"rejection": "denied by Codex MMO approval_policy=never",
}
}
}
self.respond(request_id, result)
def _send(self, message: Mapping[str, Any]) -> None:
transport = self.transport
if transport is None or transport.closed or self._closed:
raise AppServerError("app-server is not running")
encoded = json.dumps(
dict(message), ensure_ascii=False, separators=(",", ":"), allow_nan=False
)
with self._write_lock:
# Keep the durable trace in the exact order bytes are serialized
# onto the app-server transport. Recording before this lock
# allowed concurrent control/approval writers to publish an order
# different from the wire order.
self._record("sent", message)
try:
transport.send_text(encoded)
except (AppServerError, OSError) as exc:
raise AppServerError(f"app-server write failed: {exc}") from exc
def request(self, method: str, params: Mapping[str, Any], *, timeout: float = 30.0) -> Any:
deadline = time.monotonic() + timeout
for attempt in range(len(APP_SERVER_OVERLOAD_RETRY_DELAYS_SECONDS) + 1):
with self._condition:
request_id = self._next_id
self._next_id += 1
self._send({"id": request_id, "method": method, "params": dict(params)})
with self._condition:
while request_id not in self._responses:
if self._closed:
detail = self._reader_error or "app-server transport closed"
raise AppServerError(detail)
remaining = deadline - time.monotonic()
if remaining <= 0:
self._responses.pop(request_id, None)
raise AppServerError(f"app-server request timed out: {method}")
self._condition.wait(timeout=remaining)
response = self._responses.pop(request_id)
if "error" not in response:
return response.get("result")
error = response["error"]
code = int(error["code"])
message = str(error["message"])
overload = code == -32001 and message == "Server overloaded; retry later."
if overload and attempt < len(APP_SERVER_OVERLOAD_RETRY_DELAYS_SECONDS):
base_delay = APP_SERVER_OVERLOAD_RETRY_DELAYS_SECONDS[attempt]
delay = base_delay * random.uniform(0.5, 1.0)
if delay < deadline - time.monotonic():
time.sleep(delay)
continue
raise AppServerError(f"app-server {method} failed ({code}): {message}")
raise AssertionError("app-server retry loop exhausted without returning")
def notify(self, method: str, params: Mapping[str, Any]) -> None:
self._send({"method": method, "params": dict(params)})
def respond(self, request_id: Any, result: Mapping[str, Any]) -> None:
self._send({"id": request_id, "result": dict(result)})
if isinstance(request_id, str) or (
isinstance(request_id, int) and not isinstance(request_id, bool)
):
with self._condition:
self._server_requests.pop(self._server_request_key(request_id), None)
def respond_error(self, request_id: Any, code: int, message: str) -> None:
self._send(
{
"id": request_id,
"error": {"code": int(code), "message": message},
}
)
if isinstance(request_id, str) or (
isinstance(request_id, int) and not isinstance(request_id, bool)
):
with self._condition:
self._server_requests.pop(self._server_request_key(request_id), None)
def close(self) -> None:
"""Detach this client without changing the app-server host or its goal."""
if self.transport is not None:
self.transport.close()
if self._reader is not None:
self._reader.join(timeout=2.0)
if self._stderr is not None:
with contextlib.suppress(Exception):
self._stderr.close()
with self._condition:
self._closed = True
self._condition.notify_all()
def stop_host(self, *, grace_seconds: float = 8.0) -> None:
"""Detach and retire the isolated app-server process this client started."""
process = self.process
self.close()
if process is not None:
terminate_process_group(process.pid, grace_seconds=grace_seconds)
with contextlib.suppress(Exception):
process.wait(timeout=2.0)
if self.socket_path.is_socket():
with contextlib.suppress(OSError):
self.socket_path.unlink()
def last_agent_message(turn: Any) -> str:
if not isinstance(turn, Mapping):
return ""
messages = [
str(item.get("text", ""))
for item in turn.get("items", [])
if isinstance(item, Mapping) and item.get("type") == "agentMessage"
]
return next((value for value in reversed(messages) if value.strip()), "")
def bounded_goal_objective(value: str) -> str:
"""Return one deterministic app-server goal objective within its hard limit.
The complete delegated or root prompt remains the first turn input. Long
objectives retain a recognizable prefix plus a digest that lets operators
correlate the bounded goal with those full instructions.
"""
objective = value.strip()
if not objective:
raise ValueError("goal objective must be a non-empty string")
if len(objective) <= APP_SERVER_GOAL_OBJECTIVE_MAX_CHARS:
return objective
digest = hashlib.sha256(objective.encode("utf-8")).hexdigest()
suffix = f"\n\n[Full instructions remain in the initial turn; sha256={digest}]"
prefix_chars = APP_SERVER_GOAL_OBJECTIVE_MAX_CHARS - len(suffix)
return objective[:prefix_chars].rstrip() + suffix
def completed_turn_presentable_text(events_path: Path, turn_id: str) -> str:
"""Recover the last completed user-visible item for one exact root turn."""
if not events_path.is_file() or events_path.is_symlink():
return ""
result = ""
with events_path.open("r", encoding="utf-8", errors="replace") as handle:
for raw in handle:
try:
record = strict_json_loads(raw)
except (json.JSONDecodeError, ValueError):
continue
if not isinstance(record, Mapping) or record.get("direction") != "received":
continue
message = record.get("message")
if not isinstance(message, Mapping) or message.get("method") != "item/completed":
continue
params = message.get("params")
if not isinstance(params, Mapping) or params.get("turnId") != turn_id:
continue
item = params.get("item")
if not isinstance(item, Mapping) or item.get("type") not in {
"agentMessage",
"plan",
}:
continue
text = item.get("text")
if isinstance(text, str) and text.strip():
result = text
return result
def turn_input(prompt: str, attachments: Sequence[str]) -> list[dict[str, Any]]:
items: list[dict[str, Any]] = [{"type": "text", "text": prompt}]
items.extend(
{"type": "localImage", "path": path}
for path in attachments
if Path(path).suffix.lower() in {".png", ".jpg", ".jpeg", ".webp", ".gif"}
)
return items
def resumed_turns(
thread: Mapping[str, Any],
prior_turn_id: Any,
*,
turn_start_pending: bool = False,
) -> tuple[dict[str, Any] | None, str | None]:
"""Return an exact recovered terminal turn and any remaining active turn."""
turns = [turn for turn in thread.get("turns", []) if isinstance(turn, dict)]
prior_index = next(
(
index
for index in range(len(turns) - 1, -1, -1)
if isinstance(prior_turn_id, str) and turns[index].get("id") == prior_turn_id
),
None,
)
if turn_start_pending:
appended = turns[(prior_index + 1) if isinstance(prior_index, int) else 0 :]
candidate = appended[-1] if appended else None
else:
candidate = turns[prior_index] if isinstance(prior_index, int) else None
terminal = (
candidate
if isinstance(candidate, dict) and candidate.get("status") != "inProgress"
else None
)
active = next(
(turn for turn in reversed(turns) if turn.get("status") == "inProgress"),
None,
)
active_id = active.get("id") if isinstance(active, dict) else None
return terminal, str(active_id) if isinstance(active_id, str) else None
class PersistentThreadHost:
"""Shared durable thread/turn state for root and delegated app-server hosts."""
def __init__(
self,
*,
state: dict[str, Any] | None = None,
state_lock: threading.RLock | None = None,
on_state_change: Callable[[dict[str, Any], Mapping[str, Any]], None] | None = None,
) -> None:
self.state = state if state is not None else {}
self.state_lock = state_lock or threading.RLock()
self.on_state_change = on_state_change
self.state.setdefault("client", None)
self.state.setdefault("thread_id", None)
self.state.setdefault("active_turn_id", None)
self.state.setdefault("last_turn_id", None)
self.state.setdefault("turn_start_pending", False)
self.state.setdefault("completed_turn", None)
self.state.setdefault("turn_failure", None)
self.state.setdefault("thread_status", None)
self.state.setdefault("goal", None)
self.state.setdefault("token_usage", None)
self.state.setdefault("last_item", None)
self.state.setdefault("turn_event", threading.Event())
@property
def client(self) -> AppServerClient:
with self.state_lock:
client = self.state.get("client")
if not isinstance(client, AppServerClient):
raise AppServerError("app-server host has no connected client")
return client
@property
def thread_id(self) -> str | None:
with self.state_lock:
value = self.state.get("thread_id")
return str(value) if isinstance(value, str) else None
@property
def active_turn_id(self) -> str | None:
with self.state_lock:
value = self.state.get("active_turn_id")
return str(value) if isinstance(value, str) else None
@property
def last_turn_id(self) -> str | None:
with self.state_lock:
value = self.state.get("last_turn_id")
return str(value) if isinstance(value, str) else None
@property
def completed_turn(self) -> dict[str, Any] | None:
with self.state_lock:
value = self.state.get("completed_turn")
return dict(value) if isinstance(value, Mapping) else None
@property
def turn_event(self) -> threading.Event:
value = self.state["turn_event"]
if not isinstance(value, threading.Event):
raise AppServerError("app-server host turn event is invalid")
return value
def attach_client(self, client: AppServerClient) -> None:
with self.state_lock:
self.state["client"] = client
def detach_client(self, client: AppServerClient | None = None) -> None:
with self.state_lock:
if client is None or self.state.get("client") is client:
self.state["client"] = None
def on_message(self, message: dict[str, Any]) -> None:
method = message.get("method")
raw_params = message.get("params")
params: Mapping[str, Any] = raw_params if isinstance(raw_params, Mapping) else {}
changes: dict[str, Any] = {}
with self.state_lock:
expected_thread_id = self.state.get("thread_id")
message_thread_id = app_server_message_thread_id(message)
if method == "thread/started":
thread = params.get("thread")
if isinstance(thread, Mapping) and isinstance(thread.get("id"), str):
changes["thread_started"] = dict(thread)
elif (
isinstance(expected_thread_id, str)
and isinstance(message_thread_id, str)
and message_thread_id != expected_thread_id
):
return
elif method == "turn/started":
turn = params.get("turn")
if isinstance(turn, Mapping) and isinstance(turn.get("id"), str):
self.state["active_turn_id"] = turn["id"]
self.state["last_turn_id"] = turn["id"]
self.state["turn_start_pending"] = False
self.state["turn_failure"] = None
changes.update(
active_turn_id=turn["id"],
last_turn_id=turn["id"],
turn_start_pending=False,
turn_failure=None,
)
elif method == "turn/completed":
turn = params.get("turn")
if isinstance(turn, Mapping):
current_turn_id = self.state.get("active_turn_id")
completed_turn_id = turn.get("id")
if not isinstance(current_turn_id, str) or completed_turn_id == current_turn_id:
self.state["completed_turn"] = dict(turn)
self.state["active_turn_id"] = None
if isinstance(completed_turn_id, str):
self.state["last_turn_id"] = completed_turn_id
self.state["turn_start_pending"] = False
failure = normalize_turn_failure(turn)
self.state["turn_failure"] = failure
changes.update(
active_turn_id=None,
last_turn_id=(
completed_turn_id
if isinstance(completed_turn_id, str)
else self.state.get("last_turn_id")
),
turn_start_pending=False,
last_turn_status=turn.get("status"),
turn_failure=failure,
)
self.turn_event.set()
elif method == "thread/status/changed":
status = params.get("status")
self.state["thread_status"] = status
changes["thread_status"] = status
elif method == "thread/tokenUsage/updated":
usage = params.get("tokenUsage")
self.state["token_usage"] = usage
changes["token_usage"] = usage
elif method == "thread/goal/updated":
goal = params.get("goal")
if isinstance(goal, Mapping):
self.state["goal"] = dict(goal)
changes["goal"] = dict(goal)
elif method == "thread/goal/cleared":
self.state["goal"] = None
changes["goal"] = None
elif method in {"item/started", "item/completed"}:
item = params.get("item")
if isinstance(item, Mapping):
self.state["last_item"] = dict(item)
changes["last_item"] = dict(item)
changes["last_item_event"] = method
elif method in {
"turn/diff/updated",
"turn/plan/updated",
"mcpServer/startupStatus/updated",
"model/rerouted",
"model/verification",
"warning",
"error",
"account/rateLimits/updated",
}:
changes["last_observability_event"] = method
changes["last_observability_params"] = dict(params)
elif (
message.get("id") is not None
and method
in {
"item/tool/requestUserInput",
"mcpServer/elicitation/request",
}
| APPROVAL_REQUEST_METHODS
):
client = self.state.get("client")
changes["pending_request_count"] = len(
client.pending_server_requests_for_thread(str(expected_thread_id))
if isinstance(client, AppServerClient) and isinstance(expected_thread_id, str)
else []
)
elif method == "serverRequest/resolved":
client = self.state.get("client")
changes["pending_request_count"] = len(
client.pending_server_requests_for_thread(str(expected_thread_id))
if isinstance(client, AppServerClient) and isinstance(expected_thread_id, str)
else []
)
if self.on_state_change is not None:
self.on_state_change(changes, message)
def adopt_thread(self, thread: Mapping[str, Any]) -> None:
"""Switch the tracked logical root after its durable owner adopts a successor."""
thread_id = thread.get("id")
if not isinstance(thread_id, str) or not thread_id:
raise AppServerError("root successor has no thread identity")
terminal, active_turn_id = resumed_turns(thread, None)
turns = [value for value in thread.get("turns", []) if isinstance(value, Mapping)]
last_turn = turns[-1] if turns else None
with self.state_lock:
self.state["thread_id"] = thread_id
self.state["thread_status"] = thread.get("status")
goal = thread.get("goal")
self.state["goal"] = dict(goal) if isinstance(goal, Mapping) else None
self.state["turn_start_pending"] = False
self.state["completed_turn"] = terminal
self.state["turn_failure"] = (
normalize_turn_failure(terminal) if isinstance(terminal, Mapping) else None
)
self.state["active_turn_id"] = active_turn_id
self.state["last_turn_id"] = (
terminal.get("id")
if isinstance(terminal, Mapping) and isinstance(terminal.get("id"), str)
else active_turn_id
or (
last_turn.get("id")
if isinstance(last_turn, Mapping) and isinstance(last_turn.get("id"), str)
else None
)
)
if terminal is not None:
self.turn_event.set()
else:
self.turn_event.clear()
def open_thread(
self,
mode: str,
params: Mapping[str, Any],
*,
timeout: float,
prior_turn_id: Any = None,
expected_thread_id: str | None = None,
interrupt_stale: bool = False,
) -> dict[str, Any]:
method = {
"start": "thread/start",
"resume": "thread/resume",
"fork": "thread/fork",
}.get(mode)
if method is None:
raise ValueError(f"unsupported app-server thread mode: {mode}")
response = self.client.request(method, params, timeout=timeout)
thread = response.get("thread") if isinstance(response, Mapping) else None
if not isinstance(thread, Mapping) or not isinstance(thread.get("id"), str):
raise AppServerError("thread start/resume returned no thread identifier")
thread_id = str(thread["id"])
if expected_thread_id is not None and thread_id != expected_thread_id:
raise AppServerError("thread/resume returned the wrong thread identity")
with self.state_lock:
turn_start_pending = bool(self.state.get("turn_start_pending"))
terminal, active_turn_id = resumed_turns(
thread,
prior_turn_id,
turn_start_pending=turn_start_pending,
)
with self.state_lock:
self.state["thread_id"] = thread_id
self.state["thread_status"] = thread.get("status")
goal = thread.get("goal")
self.state["goal"] = dict(goal) if isinstance(goal, Mapping) else None
if terminal is not None:
self.state["completed_turn"] = terminal
self.state["turn_failure"] = normalize_turn_failure(terminal)
self.state["active_turn_id"] = None
self.state["last_turn_id"] = terminal.get("id")
self.turn_event.set()
else:
self.state["turn_failure"] = None
self.state["active_turn_id"] = active_turn_id
if active_turn_id is not None:
self.state["last_turn_id"] = active_turn_id
self.state["turn_start_pending"] = False
if terminal is None and interrupt_stale and active_turn_id is not None:
interrupt_error: AppServerError | None = None
try:
self.client.request(
"turn/interrupt",
{"threadId": thread_id, "turnId": active_turn_id},
)
except AppServerError as exc:
# The turn may have terminalized after thread/resume but before
# the interrupt was admitted. Its completion notification, not
# the racing request error, is authoritative.
interrupt_error = exc
if not self.turn_event.wait(
timeout=min(timeout, APP_SERVER_INITIALIZE_TIMEOUT_SECONDS)
):
if interrupt_error is not None:
raise interrupt_error
raise AppServerError("persisted active turn did not settle after interruption")
with self.state_lock:
settled = self.state.get("completed_turn")
if not isinstance(settled, Mapping) or settled.get("id") != active_turn_id:
raise AppServerError("persisted active turn settled with the wrong identity")
if settled.get("status") != "interrupted":
return dict(thread)
with self.state_lock:
self.state["active_turn_id"] = None
self.state["completed_turn"] = None
self.state["turn_failure"] = None
self.turn_event.clear()
elif (
interrupt_stale
and isinstance(terminal, Mapping)
and terminal.get("status") == "interrupted"
):
with self.state_lock:
self.state["active_turn_id"] = None
self.state["completed_turn"] = None
self.state["turn_failure"] = None
self.turn_event.clear()
return dict(thread)
def set_goal(
self,
*,
objective: str | None = None,
status: str | None = None,
token_budget: int | None = None,
timeout: float = 60.0,
) -> dict[str, Any]:
thread_id = self.thread_id
if thread_id is None:
raise AppServerError("app-server host has no persistent thread")
params: dict[str, Any] = {"threadId": thread_id}
if objective is not None:
params["objective"] = objective
if status is not None:
params["status"] = status
if token_budget is not None:
params["tokenBudget"] = token_budget
response = self.client.request("thread/goal/set", params, timeout=timeout)
goal = response.get("goal") if isinstance(response, Mapping) else None
if not isinstance(goal, Mapping):
raise AppServerError("thread/goal/set returned no goal")
with self.state_lock:
self.state["goal"] = dict(goal)
return dict(goal)
def read_thread(self, *, include_turns: bool = False, timeout: float = 60.0) -> dict[str, Any]:
thread_id = self.thread_id
if thread_id is None:
raise AppServerError("app-server host has no persistent thread")
response = self.client.request(
"thread/read",
{"threadId": thread_id, "includeTurns": include_turns},
timeout=timeout,
)
thread = response.get("thread") if isinstance(response, Mapping) else None
if not isinstance(thread, Mapping) or thread.get("id") != thread_id:
raise AppServerError("thread/read returned the wrong thread")
return dict(thread)
def complete_history(self, *, timeout: float = 60.0) -> list[dict[str, Any]]:
"""Read every persisted turn through the authoritative paginated API."""
thread_id = self.thread_id
if thread_id is None:
raise AppServerError("app-server host has no persistent thread")
cursor: str | None = None
turns: list[dict[str, Any]] = []
seen_cursors: set[str] = set()
while True:
params: dict[str, Any] = {
"threadId": thread_id,
"limit": 100,
"sortDirection": "asc",
"itemsView": "full",
}
if cursor is not None:
params["cursor"] = cursor
response = self.client.request("thread/turns/list", params, timeout=timeout)
data = response.get("data") if isinstance(response, Mapping) else None
if not isinstance(data, list) or not all(isinstance(item, Mapping) for item in data):
raise AppServerError("thread/turns/list returned invalid turn data")
turns.extend(dict(item) for item in data)
next_cursor = response.get("nextCursor")
if next_cursor is None:
return turns
if not isinstance(next_cursor, str) or next_cursor in seen_cursors:
raise AppServerError("thread/turns/list returned an invalid cursor sequence")
seen_cursors.add(next_cursor)
cursor = next_cursor
def start_turn(
self,
input_items: Sequence[Mapping[str, Any]],
*,
effort: Any,
output_schema: Mapping[str, Any] | None = None,
timeout: float = 60.0,
) -> str:
thread_id = self.thread_id
if thread_id is None:
raise AppServerError("app-server host has no persistent thread")
params: dict[str, Any] = {
"threadId": thread_id,
"input": [dict(item) for item in input_items],
"effort": effort,
}
if output_schema is not None:
params["outputSchema"] = dict(output_schema)
with self.state_lock:
self.state["active_turn_id"] = None
self.state["turn_start_pending"] = True
self.state["completed_turn"] = None
self.state["turn_failure"] = None
self.turn_event.clear()
if self.on_state_change is not None:
self.on_state_change(
{"active_turn_id": None, "turn_start_pending": True},
{"method": "mmo/turn/reset", "params": {"threadId": thread_id}},
)
response = self.client.request("turn/start", params, timeout=timeout)
turn = response.get("turn") if isinstance(response, Mapping) else None
if not isinstance(turn, Mapping) or not isinstance(turn.get("id"), str):
raise AppServerError("turn/start returned no turn identifier")
turn_id = str(turn["id"])
with self.state_lock:
completed_turn = self.state.get("completed_turn")
self.state["last_turn_id"] = turn_id
self.state["turn_start_pending"] = False
if isinstance(completed_turn, Mapping) and completed_turn.get("id") == turn_id:
self.state["active_turn_id"] = None
changes = {
"active_turn_id": None,
"last_turn_id": turn_id,
"turn_start_pending": False,
}
else:
self.state["active_turn_id"] = turn_id
changes = {
"active_turn_id": turn_id,
"last_turn_id": turn_id,
"turn_start_pending": False,
}
if isinstance(completed_turn, Mapping):
self.state["completed_turn"] = None
self.turn_event.clear()
if self.on_state_change is not None:
self.on_state_change(
changes,
{"method": "mmo/turn/accepted", "params": {"threadId": thread_id}},
)
return turn_id
def take_completed_turn(self) -> dict[str, Any] | None:
with self.state_lock:
value = self.state.get("completed_turn")
self.state["completed_turn"] = None
self.turn_event.clear()
return dict(value) if isinstance(value, Mapping) else None
def retain_partial_evidence(
data: Mapping[str, Any],
directory: Path,
*,
reason: str,
events_filename: str = "events.jsonl",
result_filename: str = "result.md",
partial_filename: str = "partial-result.md",
title: str = "Partial agent result",
) -> dict[str, Any]:
"""Materialize bounded readable evidence from an interrupted app-server job."""
for filename in (events_filename, result_filename, partial_filename):
if Path(filename).name != filename:
raise ValueError("partial-evidence filenames must be plain child names")
partial_path = directory / partial_filename
if partial_path.is_symlink():
raise RuntimeError("partial-evidence output cannot traverse a symlink")
messages: deque[str] = deque(maxlen=100)
observations: deque[str] = deque(maxlen=100)
trace_window_truncated = False
# Runtime state records include these paths for observability, but evidence
# recovery uses only the canonical job/session children. A corrupted state
# file must never turn partial-result recovery into an arbitrary file read.
events_path = directory / events_filename
if events_path.is_file() and not events_path.is_symlink():
with events_path.open("rb") as handle:
size = events_path.stat().st_size
start = max(0, size - PARTIAL_EVENT_WINDOW_BYTES)
if start:
trace_window_truncated = True
handle.seek(start - 1)
if handle.read(1) != b"\n":
handle.readline()
for raw in handle:
try:
envelope = strict_json_loads(raw)
except (json.JSONDecodeError, ValueError):
continue
pending: list[Any] = [envelope]
visited = 0
while pending and visited < 20_000:
visited += 1
value = pending.pop()
if isinstance(value, Mapping):
if value.get("type") == "agentMessage" and isinstance(
value.get("text"), str
):
message, _truncated = bounded_text(str(value["text"]).strip(), 10_000)
if message and message not in messages:
messages.append(message)
item_type = value.get("type")
if isinstance(item_type, str) and item_type in {
"commandExecution",
"mcpToolCall",
}:
observation = json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)[:4000]
if observation not in observations:
observations.append(observation)
pending.extend(value.values())
elif isinstance(value, list):
pending.extend(value)
result_path = directory / result_filename
if result_path.is_file() and not result_path.is_symlink():
candidate, _truncated = bounded_text(
result_path.read_text(encoding="utf-8", errors="replace").strip(), 100_000
)
if candidate and candidate not in messages:
messages.append(candidate)
sections = [
f"# {title}",
"",
f"Terminal reason: {reason}",
"",
"This retained evidence is not a contract-valid final result. Review it directly or "
"continue the persisted thread; it cannot be accepted or integrated as a completed result.",
]
if messages:
sections.extend(["", "## Completed assistant messages", "", "\n\n".join(messages)])
if observations:
sections.extend(
[
"",
"## Completed tool observations",
"",
"\n".join(f"- `{item}`" for item in observations),
]
)
if trace_window_truncated:
sections.extend(
[
"",
"Older event records are omitted from this bounded summary; the complete durable "
"JSONL trace remains available.",
]
)
text = "\n".join(sections).strip() + "\n"
atomic_write_text(partial_path, text, 0o600)
return {
"partial_result_path": str(partial_path),
"partial_result_sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
"partial_message_count": len(messages),
"partial_tool_observation_count": len(observations),
"partial_trace_window_truncated": trace_window_truncated,
"result_kind": "partial",
"result_state": "unread",
}
def receive_control_request(connection: socket.socket) -> dict[str, Any]:
"""Read one bounded newline-framed local control request."""
raw = bytearray()
while b"\n" not in raw:
chunk = connection.recv(65536)
if not chunk:
break
raw.extend(chunk)
if len(raw) > MAX_CONTROL_MESSAGE_BYTES:
raise ValueError("control request exceeds 4 MiB")
request = strict_json_loads(bytes(raw).split(b"\n", 1)[0])
if not isinstance(request, dict):
raise ValueError("control request must be an object")
return request
def send_control_response(connection: socket.socket, value: Mapping[str, Any]) -> None:
"""Write one strict newline-framed local control response."""
payload = (
json.dumps(
dict(value),
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
+ b"\n"
)
connection.sendall(payload)
def serve_control_socket(
socket_path: Path,
*,
stop_event: threading.Event,
handler: Callable[[Mapping[str, Any]], Any],
on_ready: Callable[[], None],
backlog: int,
connection_timeout: float | None,
) -> None:
"""Own the shared local control framing while a runner owns action semantics."""
if socket_path.exists() or socket_path.is_symlink():
if socket_path.is_symlink() or not socket_path.is_socket():
raise RuntimeError("control socket path is unsafe")
socket_path.unlink()
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as server:
server.bind(str(socket_path))
os.chmod(socket_path, 0o600)
server.listen(backlog)
server.settimeout(0.5)
on_ready()
while not stop_event.is_set():
try:
connection, _ = server.accept()
except TimeoutError:
continue
with connection:
if connection_timeout is not None:
connection.settimeout(connection_timeout)
try:
result = handler(receive_control_request(connection))
send_control_response(connection, {"ok": True, "result": result})
except Exception as exc:
with contextlib.suppress(OSError):
send_control_response(
connection,
{"ok": False, "error": f"{type(exc).__name__}: {exc}"},
)
finally:
with contextlib.suppress(OSError):
socket_path.unlink()
def send_control_request(
socket_path: Path,
request: Mapping[str, Any],
*,
timeout: float = 30.0,
) -> dict[str, Any]:
"""Send one bounded request to a worker-owned Unix control socket."""
encoded = (
json.dumps(
dict(request), ensure_ascii=False, separators=(",", ":"), allow_nan=False
).encode("utf-8")
+ b"\n"
)
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
client.settimeout(timeout)
# A connect failure proves that the request was not delivered. Once the
# socket is connected, a send/receive failure is ambiguous: the worker
# may already have applied the mutation before its reply was lost.
client.connect(str(socket_path))
try:
client.sendall(encoded)
chunks: list[bytes] = []
total = 0
while True:
chunk = client.recv(65536)
if not chunk:
break
chunks.append(chunk)
total += len(chunk)
if total > MAX_CONTROL_MESSAGE_BYTES:
raise ControlDeliveryUnknown("worker control response exceeds 4 MiB")
if b"\n" in chunk:
break
except ControlDeliveryUnknown:
raise
except OSError as exc:
raise ControlDeliveryUnknown(
f"worker control response was lost after delivery began: {exc}"
) from exc
line = b"".join(chunks).split(b"\n", 1)[0]
if not line:
raise ControlDeliveryUnknown("worker control socket returned no response")
try:
value = strict_json_loads(line)
except (json.JSONDecodeError, UnicodeDecodeError, ValueError) as exc:
raise ControlDeliveryUnknown(f"worker control socket returned invalid JSON: {exc}") from exc
if not isinstance(value, dict):
raise ControlDeliveryUnknown("worker control socket returned a non-object response")
if value.get("ok") is not True:
raise ControlRequestRejected(str(value.get("error") or "worker control request failed"))
return value