feat: surface L2 agent pool and escalations on supervisor screen

- routing-service: GET /escalations (list recent escalation attempts)
- supervisor UI: new panel showing the real agent pool (status, level,
  tenant, skills, calls handled) fed by GET /agents, with a form to add
  operators to the pool
- supervisor UI: new live escalation feed (AI->L2 handoffs, status,
  assigned operator), auto-refreshed every 5s alongside live calls
This commit is contained in:
Hermes Agent
2026-08-29 13:28:14 +05:00
parent 2243f305b8
commit 1ad4e1ec6e
4 changed files with 332 additions and 2 deletions
+39 -1
View File
@@ -12,6 +12,7 @@ from services.shared.models import (
AgentCreate,
AgentPoolOut,
AgentStatusUpdateIn,
EscalationOut,
HealthResponse,
QueueCreate,
QueueOut,
@@ -20,7 +21,7 @@ from services.shared.models import (
)
from services.shared.security import require_roles
from services.shared.sql_init import init_sql_schema
from services.shared.sql_models import AgentRow, IvrSessionRow, Queue, RoutingCounter
from services.shared.sql_models import AgentRow, EscalationRow, IvrSessionRow, Queue, RoutingCounter
app = FastAPI(title="routing-service", version="1.1.0")
@@ -328,6 +329,43 @@ def update_agent_status(
session.close()
def _escalation_to_out(row: EscalationRow) -> EscalationOut:
return EscalationOut(
escalation_id=row.escalation_id,
call_id=row.call_id,
tenant_id=row.tenant_id,
from_level=row.from_level,
to_level=row.to_level,
reason_code=row.reason_code,
required_skills=routing_engine.parse_list_json(row.required_skills_json),
priority=row.priority,
topic=row.topic,
summary=row.summary,
status=row.status,
assigned_agent_id=row.assigned_agent_id,
requested_at=row.requested_at,
connected_at=row.connected_at,
completed_at=row.completed_at,
)
@app.get("/escalations", response_model=list[EscalationOut])
def list_escalations(
status: str | None = None,
limit: int = 50,
_: dict = Depends(require_roles(Role.ADMIN, Role.SUPERVISOR)),
) -> list[EscalationOut]:
session = get_session()
try:
stmt = select(EscalationRow).order_by(EscalationRow.id.desc()).limit(max(1, min(limit, 200)))
if status:
stmt = stmt.where(EscalationRow.status == status)
rows = session.execute(stmt).scalars().all()
return [_escalation_to_out(r) for r in rows]
finally:
session.close()
@app.post("/internal/routing/reserve-agent", response_model=RoutingAgentReserveOut)
def reserve_agent_endpoint(
payload: RoutingAgentReserveIn,