88import os
99import sys
1010from contextlib import asynccontextmanager
11+ from datetime import datetime , timezone
1112from typing import Any , Dict , List , Optional
1213
1314from fastapi import FastAPI , HTTPException
1415from fastapi .middleware .cors import CORSMiddleware
15- from fastapi .responses import HTMLResponse , JSONResponse , Response
16+ from fastapi .responses import HTMLResponse , JSONResponse , RedirectResponse , Response
1617from pydantic import BaseModel
1718
1819# ---------------------------------------------------------------------------
2829# Logging — suppress noisy poll endpoints
2930# ---------------------------------------------------------------------------
3031class _PollFilter (logging .Filter ):
31- _SUPPRESSED = ("/state" , "/history" , "/results" , "/dashboard" , "/health" , "/favicon" , "/apple-touch-icon" )
32+ _SUPPRESSED = ("/state" , "/history" , "/results" , "/dashboard" , "/health" , "/logs" , "/ favicon" , "/apple-touch-icon" )
3233
3334 def filter (self , record : logging .LogRecord ) -> bool :
3435 msg = record .getMessage ()
@@ -48,6 +49,8 @@ def filter(self, record: logging.LogRecord) -> bool:
4849# ---------------------------------------------------------------------------
4950_env = MissionCtrlEnvironment ()
5051_completed_results : List [Dict [str , Any ]] = [] # Accumulated episode results across tiers
52+ HF_SPACE_URL = os .getenv ("HF_SPACE_URL" , "https://huggingface.co/spaces/Jit-fnc/missionctrl_env" )
53+ HF_SPACE_LOGS_URL = os .getenv ("HF_SPACE_LOGS_URL" , f"{ HF_SPACE_URL } ?logs=container" )
5154
5255# ---------------------------------------------------------------------------
5356# Request / Response models
@@ -89,6 +92,7 @@ async def lifespan(_: FastAPI):
8992 ║ GET /state → Current observation (read-only) ║
9093 ║ GET /dashboard → Live visualization UI ║
9194 ║ GET /history → Agent action history (JSON) ║
95+ ║ GET /logs → Redirect to HF container logs ║
9296 ║ ║
9397 ║ Tasks: easy, medium, hard, special ║
9498 ║ ║
@@ -98,8 +102,14 @@ async def lifespan(_: FastAPI):
98102 ║ ║
99103 ╚══════════════════════════════════════════════════════════════╝
100104 """
101- print (banner )
105+ # Use sys.stderr for HF Spaces container log capture
106+ sys .stderr .write (banner + "\n " )
107+ sys .stderr .flush ()
102108 log .info ("Server started — using persistent singleton environment" )
109+ log .info ("Boot time: %s" , datetime .now (timezone .utc ).strftime ("%Y-%m-%d %H:%M:%S UTC" ))
110+ log .info ("Health: http://0.0.0.0:8000/health" )
111+ log .info ("Dashboard: http://0.0.0.0:8000/dashboard" )
112+ sys .stderr .flush ()
103113 yield
104114
105115
@@ -123,13 +133,20 @@ async def lifespan(_: FastAPI):
123133# GET /
124134# ---------------------------------------------------------------------------
125135@app .get ("/" )
126- async def root () -> Dict [str , Any ]:
127- """Root endpoint — heartbeat for OpenEnv platform probes."""
136+ async def root ():
137+ """Root endpoint — redirect to dashboard for HF Spaces preview."""
138+ return RedirectResponse (url = "/dashboard" , status_code = 307 )
139+
140+
141+ @app .get ("/info" )
142+ async def info () -> Dict [str , Any ]:
143+ """Info endpoint — heartbeat for OpenEnv platform probes."""
128144 return {
129145 "status" : "ok" ,
130146 "name" : "missionctrl" ,
131147 "version" : "1.0.0" ,
132- "endpoints" : ["/health" , "/reset" , "/step" , "/state" , "/dashboard" , "/history" ],
148+ "endpoints" : ["/health" , "/reset" , "/step" , "/state" , "/dashboard" , "/history" , "/logs" ],
149+ "logs_url" : HF_SPACE_LOGS_URL ,
133150 }
134151
135152
@@ -228,6 +245,24 @@ async def dashboard() -> HTMLResponse:
228245 return HTMLResponse (content = f .read ())
229246
230247
248+ # ---------------------------------------------------------------------------
249+ # GET|HEAD /logs
250+ # ---------------------------------------------------------------------------
251+ @app .api_route ("/logs" , methods = ["GET" , "HEAD" ])
252+ async def logs (redirect : bool = False ):
253+ """Return logs URL info; optionally redirect to the HF container logs page."""
254+ if redirect :
255+ return RedirectResponse (url = HF_SPACE_LOGS_URL , status_code = 307 )
256+
257+ return JSONResponse (
258+ {
259+ "status" : "ok" ,
260+ "logs_url" : HF_SPACE_LOGS_URL ,
261+ "hint" : "Open logs_url in a browser, or call /logs?redirect=1 to auto-redirect." ,
262+ }
263+ )
264+
265+
231266# ---------------------------------------------------------------------------
232267# Favicon & Apple Touch Icon — suppress 404 noise
233268# ---------------------------------------------------------------------------
0 commit comments