diff --git a/scripts/x/elevate-adapter-superpowerd b/scripts/x/elevate-adapter-superpowerd index 2ec9ccb..42e6b0b 100755 --- a/scripts/x/elevate-adapter-superpowerd +++ b/scripts/x/elevate-adapter-superpowerd @@ -29,7 +29,6 @@ import os import signal import socket import struct -import subprocess import threading _token_lock = threading.Lock() @@ -40,27 +39,74 @@ def _log(msg): print(f"{_LOG_PREFIX}: {msg}", file=sys.stderr, flush=True) +def _read_master_token(): + """Read the superpowerd master token from the standard location.""" + path = os.environ.get("SUPERPOWERD_MASTER_TOKEN_FILE") or \ + os.path.join(os.path.expanduser("~"), "Private", "superpowerd_master.token") + try: + return open(path).read().strip() + except OSError as e: + _log(f"cannot read master token at {path}: {e}") + return None + + def refresh_token(): - """Obtain a fresh session token from superpowerd via run-session. + """Create a fresh session directly on superpowerd via SEQPACKET. + + Connects to superpowerd and calls CreateSession with the master token. + Because WE are the connecting process, the session PID = our PID, + so pidAlive() stays true for our entire lifetime. Returns the new token string, or None on failure. Updates os.environ so subsequent calls use the fresh token. """ - _log("refreshing session token") + _log("refreshing session token via direct CreateSession") + master_token = _read_master_token() + if not master_token: + _log("refresh failed: cannot read master token") + return None + + sp_path = sp_socket_path() try: - result = subprocess.run( - ["superpowers", "run-session", "--child-session", "--", - "sh", "-c", "echo $SUPERPOWERD_SESSION_TOKEN"], - capture_output=True, text=True, timeout=10, - ) - token = result.stdout.strip() - if token and result.returncode == 0: + sp = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sp.connect(sp_path) + except OSError as e: + _log(f"refresh failed: connect {sp_path}: {e}") + return None + + try: + req = json.dumps({ + "jsonrpc": "2.0", + "method": "CreateSession", + "id": 1, + "params": { + "auth": {"master_token": master_token}, + "env": dict(os.environ), + "working_dir": os.getcwd(), + }, + }).encode() + sp.send(req) + resp_data = sp.recv(1024 * 1024) + sp.close() + + resp = json.loads(resp_data) + if "error" in resp and resp["error"]: + msg = resp["error"].get("message", str(resp["error"])) + _log(f"refresh failed: superpowerd error: {msg}") + return None + + token = resp.get("result", {}).get("token", "") + if token: os.environ["SUPERPOWERD_SESSION_TOKEN"] = token _log("session token refreshed successfully") return token - _log(f"token refresh returned exit={result.returncode} stdout={result.stdout!r:.80}") - except (subprocess.TimeoutExpired, OSError) as e: - _log(f"token refresh failed: {e}") + _log(f"refresh failed: no token in response: {resp_data!r:.200}") + except (OSError, json.JSONDecodeError, ValueError) as e: + _log(f"refresh failed: {e}") + try: + sp.close() + except OSError: + pass return None @@ -101,7 +147,7 @@ def _is_auth_error(sp_data): err = resp.get("error") if isinstance(err, dict): msg = err.get("message", "").lower() - return "invalid session token" in msg or "session not found" in msg + return "invalid session token" in msg or "session not found" in msg or "session expired" in msg or "parent session expired" in msg except (json.JSONDecodeError, ValueError): pass return False @@ -302,8 +348,12 @@ def _finish_response(conn, sp_data): def main(): if "SUPERPOWERD_SESSION_TOKEN" not in os.environ: - os.execvp("superpowers", ["superpowers", "run-session", "--", - sys.executable, __file__] + sys.argv[1:]) + # Get a token directly from superpowerd instead of os.execvp through + # superpowers run-session (which fails on Gentoo's python-exec2c). + token = refresh_token() + if not token: + _log("fatal: cannot obtain session token") + sys.exit(1) sock_path = os.environ.get("OLLIE_ELEVATE_SOCKET") or default_socket_path() sock_path = os.path.expanduser(sock_path)