diff --git a/.github/workflows/runner-full-stack-e2e.yml b/.github/workflows/runner-full-stack-e2e.yml index 69ddfd9c9fc..b82f2c2605e 100644 --- a/.github/workflows/runner-full-stack-e2e.yml +++ b/.github/workflows/runner-full-stack-e2e.yml @@ -353,9 +353,12 @@ jobs: python3 <<'PY' import json import os + import select import shutil import subprocess import sys + import tempfile + import time def snapshot(path): descriptor = os.memfd_create("paperclip-verified-launch") @@ -374,34 +377,94 @@ jobs: environment["PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT"] = os.path.abspath( "packages/paperclip-runner" ) - request = { - "protocolVersion": 2, - "id": 1, - "command": "initialize", - "params": {"agent": "codex", "model": "gpt-5.6-sol"}, - } + environment["PAPERCLIP_VERIFIED_RUNTIME_EXECUTABLE"] = ( + f"/proc/self/fd/{node_descriptor}" + ) + environment["OPENAI_API_KEY"] = "sk-paperclip-descriptor-probe-not-real" loader = r'''const fs=require("node:fs");const Module=require("node:module");const filename=process.argv[1];const source=fs.readFileSync(filename,"utf8").replace(/^#![^\r\n]*(?:\r?\n|$)/,"");const artifact=new Module(filename);artifact.filename=filename;artifact.paths=[];artifact._compile(source,filename);''' - result = subprocess.run( + process = subprocess.Popen( [ f"/proc/self/fd/{node_descriptor}", "--eval", loader, f"/proc/self/fd/{sidecar_descriptor}", ], - input=json.dumps(request) + "\n", text=True, - capture_output=True, - timeout=15, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, pass_fds=(node_descriptor, sidecar_descriptor), env=environment, ) - sys.stdout.write(result.stdout) - sys.stderr.write(result.stderr) - if result.returncode != 0: - raise SystemExit(result.returncode) - response = json.loads(result.stdout.strip().splitlines()[0]) - if response.get("ok") is not True: + + def request(request_id, command, params): + process.stdin.write(json.dumps({ + "protocolVersion": 2, + "id": request_id, + "command": command, + "params": params, + }) + "\n") + process.stdin.flush() + deadline = time.monotonic() + 20 + while time.monotonic() < deadline: + ready, _, _ = select.select([process.stdout], [], [], 1) + if not ready: + continue + line = process.stdout.readline() + if not line: + raise SystemExit("descriptor-safe ACPX sidecar closed stdout") + frame = json.loads(line) + if frame.get("id") == request_id: + return frame + raise SystemExit(f"descriptor-safe ACPX {command} timed out") + + initialized = request( + 1, + "initialize", + {"agent": "codex", "model": "gpt-5.6-sol"}, + ) + if initialized.get("ok") is not True: raise SystemExit("descriptor-safe ACPX initialize failed") + with tempfile.TemporaryDirectory() as runtime_directory: + working_directory = os.path.join(runtime_directory, "workspace") + os.mkdir(working_directory) + opened = request( + 2, + "session.open", + { + "runtimeDirectory": runtime_directory, + "normalizedSessionId": "descriptor-probe-session", + "workingDirectory": working_directory, + "agent": "codex", + "model": "gpt-5.6-sol", + "permissionMode": "approve-all", + "permissionModePinned": True, + "systemInstructions": "", + "runtimeContext": None, + "tools": [], + }, + ) + if opened.get("ok") is not True: + message = str(opened.get("error", {}).get("message", "")) + probes = [ + "ENOENT", "EACCES", "EPERM", "memfd", "deleted", "lstat", + "guardian", "ownership", "handshake", "credential", "fence", + "spawn", "executable", "package", "model", "auth", "401", + "timed out", "exited", "failed to start", + ] + print(json.dumps({ + "descriptorSessionOpen": "rejected", + "code": opened.get("error", {}).get("code"), + "messageDigest": __import__("hashlib").sha256( + message.encode("utf8") + ).hexdigest(), + "classifiers": [ + probe for probe in probes if probe.lower() in message.lower() + ], + })) + raise SystemExit("descriptor-safe ACPX session.open failed") + process.stdin.close() + process.wait(timeout=10) PY - name: Qualify local provider Node interpreter