121 lines
3.2 KiB
Python
Executable File
121 lines
3.2 KiB
Python
Executable File
"""
|
|
Debugger API — pause / resume / step an in-flight pipeline.
|
|
|
|
The engine is set by main.py on startup via `set_engine(...)`. If no
|
|
engine is registered (e.g. when running the mock server), endpoints
|
|
return a structured error rather than crashing.
|
|
"""
|
|
|
|
from fastapi import APIRouter
|
|
from typing import Any, Dict, Optional
|
|
import threading
|
|
|
|
router = APIRouter(prefix="/debugger", tags=["debugger"])
|
|
|
|
# F-15: guards for module-level mutable state used from async handlers.
|
|
# Multiple WS clients can call pause/resume/step concurrently.
|
|
_engine_lock = threading.Lock()
|
|
_state_lock = threading.Lock()
|
|
|
|
# Single in-memory engine reference (set by main.py)
|
|
_ENGINE: Optional[Any] = None
|
|
_DEBUG_STATE: Dict[str, Any] = {
|
|
"paused": False,
|
|
"current_step": 0,
|
|
"history": [],
|
|
}
|
|
|
|
|
|
def set_engine(engine: Any) -> None:
|
|
global _ENGINE
|
|
with _engine_lock:
|
|
_ENGINE = engine
|
|
|
|
|
|
def get_engine() -> Optional[Any]:
|
|
with _engine_lock:
|
|
return _ENGINE
|
|
|
|
|
|
# -----------------------------
|
|
# RESUME
|
|
# -----------------------------
|
|
@router.post("/resume")
|
|
def resume() -> Dict[str, Any]:
|
|
with _state_lock:
|
|
_DEBUG_STATE["paused"] = False
|
|
eng = get_engine()
|
|
if eng and hasattr(eng, "resume"):
|
|
eng.resume()
|
|
return {"state": "running"}
|
|
|
|
|
|
# -----------------------------
|
|
# PAUSE
|
|
# -----------------------------
|
|
@router.post("/pause")
|
|
def pause() -> Dict[str, Any]:
|
|
with _state_lock:
|
|
_DEBUG_STATE["paused"] = True
|
|
eng = get_engine()
|
|
if eng and hasattr(eng, "pause"):
|
|
eng.pause()
|
|
return {"state": "paused"}
|
|
|
|
|
|
# -----------------------------
|
|
# STEP
|
|
# -----------------------------
|
|
@router.post("/step")
|
|
def step() -> Dict[str, Any]:
|
|
eng = get_engine()
|
|
if eng and hasattr(eng, "step"):
|
|
eng.step()
|
|
with _state_lock:
|
|
_DEBUG_STATE["current_step"] += 1
|
|
step_n = _DEBUG_STATE["current_step"]
|
|
return {"state": "stepped", "step": step_n}
|
|
|
|
|
|
# -----------------------------
|
|
# STEP BACK (replay-only)
|
|
# -----------------------------
|
|
@router.post("/step-back")
|
|
def step_back() -> Dict[str, Any]:
|
|
with _state_lock:
|
|
if _DEBUG_STATE["current_step"] > 0:
|
|
_DEBUG_STATE["current_step"] -= 1
|
|
step_n = _DEBUG_STATE["current_step"]
|
|
return {"state": "stepped_back", "step": step_n}
|
|
|
|
|
|
# -----------------------------
|
|
# STATE
|
|
# -----------------------------
|
|
@router.get("/state")
|
|
def state() -> Dict[str, Any]:
|
|
eng = get_engine()
|
|
active = bool(eng and hasattr(eng, "is_active") and eng.is_active())
|
|
with _state_lock:
|
|
snapshot = {
|
|
"paused": _DEBUG_STATE["paused"],
|
|
"current_step": _DEBUG_STATE["current_step"],
|
|
"engine_attached": eng is not None,
|
|
"engine_active": active,
|
|
}
|
|
return snapshot
|
|
|
|
|
|
# -----------------------------
|
|
# Legacy wrapper for api.py facade
|
|
# -----------------------------
|
|
def debugger_endpoint(session_id: str, registry: Any = None, bus: Any = None) -> Dict[str, Any]:
|
|
"""Legacy — return current debugger state for a session."""
|
|
with _state_lock:
|
|
return {
|
|
"session_id": session_id,
|
|
"paused": _DEBUG_STATE["paused"],
|
|
"current_step": _DEBUG_STATE["current_step"],
|
|
"engine_attached": _ENGINE is not None,
|
|
}
|