Files
codexpi/scripts/codexpi_host_agent.py

87 lines
3.6 KiB
Python
Raw Normal View History

2026-11-14 20:23:59 +01:00
#!/usr/bin/env python3
"""Private Unix-socket bridge for owner-approved CodexPi master sessions."""
from __future__ import annotations
import argparse
import hmac
import json
import os
import socket
import subprocess
from pathlib import Path
def receive(connection: socket.socket, limit: int = 100_000) -> dict:
chunks, size = [], 0
while True:
block = connection.recv(8192)
if not block:
break
size += len(block)
if size > limit:
raise ValueError("request too large")
chunks.append(block)
if b"\n" in block:
break
return json.loads(b"".join(chunks).split(b"\n", 1)[0])
def run_agent(codex: str, prompt: str, privileged: bool) -> tuple[bool, int, str]:
rules = (
"Tu es l’agent maître CodexPi sur l’hôte réel. Vérifie la cible avant toute écriture, protège les secrets, "
"utilise des commandes structurées, évite toute action destructive non explicitement demandée et rends compte des vérifications. "
"Les privilèges disponibles proviennent uniquement de l’identité de ce service et de la politique sudo de l’hôte."
)
if privileged:
rules += " Cette demande a reçu la confirmation privilégiée du propriétaire."
command = [codex, "--dangerously-bypass-approvals-and-sandbox", "exec", "--json", "--skip-git-repo-check", rules + "\n\nDEMANDE DU PROPRIÉTAIRE\n" + prompt]
process = subprocess.run(command, cwd="/", text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=3600, check=False)
answers = []
for line in process.stdout.splitlines():
try:
event = json.loads(line)
except ValueError:
continue
item = event.get("item") or {}
text = item.get("text") if item.get("type") in {"agent_message", "assistant_message"} else None
if text:
answers.append(str(text))
answer = answers[-1] if answers else "L’agent maître n’a produit aucun compte rendu exploitable."
return process.returncode == 0, process.returncode, answer
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--socket", required=True)
parser.add_argument("--token-file", required=True)
parser.add_argument("--codex", default="codex")
args = parser.parse_args()
socket_path, token_path = Path(args.socket), Path(args.token_file)
token = token_path.read_text(encoding="utf-8").strip()
if len(token) < 32 or token_path.stat().st_mode & 0o077:
raise SystemExit("unsafe host-agent token")
socket_path.parent.mkdir(parents=True, exist_ok=True)
if socket_path.exists():
socket_path.unlink()
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as server:
server.bind(str(socket_path))
os.chmod(socket_path, 0o660)
server.listen(4)
while True:
connection, _ = server.accept()
with connection:
try:
request = receive(connection)
if not hmac.compare_digest(str(request.get("token") or ""), token):
raise PermissionError("unauthorized")
ok, code, answer = run_agent(args.codex, str(request.get("prompt") or ""), bool(request.get("privileged")))
response = {"ok": ok, "exit_code": code, "answer": answer}
except Exception:
response = {"ok": False, "exit_code": 1, "answer": "Le canal hôte a refusé ou interrompu la demande."}
connection.sendall(json.dumps(response, ensure_ascii=False).encode() + b"\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())