diff --git a/resources/scripts/napseer_mcp_server.py b/resources/scripts/napseer_mcp_server.py index f278b68..6c3d7ec 100755 --- a/resources/scripts/napseer_mcp_server.py +++ b/resources/scripts/napseer_mcp_server.py @@ -4773,6 +4773,7 @@ def gateway_relay_loop(lane_id="lane-1"): try: lane_index = int(str(lane_id).rsplit("-", 1)[-1]) if str(lane_id).rsplit("-", 1)[-1].isdigit() else 1 if gateway_remote_should_run() and lane_index <= gateway_listener_target_lanes(): + refresh_public_auth_state() ensure_gateway_worker_capability(refresh=True) project_id = current_project_id() agent_id = AUTH.get("agent_id") @@ -4786,13 +4787,29 @@ def gateway_relay_loop(lane_id="lane-1"): lane_id=lane_id, target_lanes=gateway_listener_target_lanes(), ) - listener = ws_connect( - gateway_listener_ws_url(project_id, agent_id), - extra_headers={ - "Authorization": f"Bearer {TOKEN}", - "X-Napseer-Gateway-Lane-Id": lane_id, - }, - ) + try: + listener = ws_connect( + gateway_listener_ws_url(project_id, agent_id), + extra_headers={ + "Authorization": f"Bearer {TOKEN}", + "X-Napseer-Gateway-Lane-Id": lane_id, + }, + ) + except RuntimeError as exc: + if " 401 " not in str(exc): + raise + gateway_log("relay_listener_auth_retry", level="warn", project_id=project_id, agent_id=agent_id, lane_id=lane_id) + renew_auth() + refresh_public_auth_state() + project_id = current_project_id() + agent_id = AUTH.get("agent_id") + listener = ws_connect( + gateway_listener_ws_url(project_id, agent_id), + extra_headers={ + "Authorization": f"Bearer {TOKEN}", + "X-Napseer-Gateway-Lane-Id": lane_id, + }, + ) connected_at = time.time() with GATEWAY_LISTENER_STABILITY_LOCK: GATEWAY_LISTENER_ACTIVE_LANES.add(lane_id) @@ -6288,6 +6305,17 @@ def save_auth(updates): TOKEN_EXPIRES_AT = AUTH["token_expires_at"] +def refresh_public_auth_state(): + global AUTH, BASE_URL, TOKEN, DEFAULT_PROJECT_ID, TOKEN_EXPIRES_AT + public = load_public_auth_file() + AUTH = load_auth(public_override=public, secret_override=VAULT_SECRETS if gateway_is_unlocked() else {}) + BASE_URL = AUTH["base_url"].rstrip("/") + TOKEN = AUTH["token"] + DEFAULT_PROJECT_ID = AUTH["project_id"] + TOKEN_EXPIRES_AT = AUTH["token_expires_at"] + return AUTH + + def ensure_local_files(): AUTH_DIR.mkdir(exist_ok=True) chmod_best_effort(AUTH_DIR, 0o700) diff --git a/tests/test_gateway_locked_auth_refresh.py b/tests/test_gateway_locked_auth_refresh.py index caf2314..f45575a 100644 --- a/tests/test_gateway_locked_auth_refresh.py +++ b/tests/test_gateway_locked_auth_refresh.py @@ -57,6 +57,20 @@ def run(): assert mod.TOKEN == "fresh" assert mod.TOKEN_EXPIRES_AT == "new" + mod.load_public_auth_file = lambda: { + "base_url": "https://api.example.test", + "project_id": "project_1", + "worker_name": "agent", + "device_fingerprint": "device", + "root_path": "/repo", + "worker_capabilities": {"local_mcp": True, "gateway": True}, + "token": "newer", + "token_expires_at": "later", + } + mod.refresh_public_auth_state() + assert mod.TOKEN == "newer" + assert mod.TOKEN_EXPIRES_AT == "later" + try: mod.save_auth({"gateway_default_command": "bash -l"}) except RuntimeError as exc: