87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
#!/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())
|