fix(api): /executions incluye también las ejecuciones HITL pendientes
`GET /executions` solo leía `executions.jsonl`, donde nunca se escribe una ejecución en `awaiting_approval` (esas viven solo en el checkpointer). La página de Aprobaciones del dashboard hace `[e for e in list_executions() if e.status=="awaiting_approval"]`, así que nunca encontraba aprobaciones pendientes. Ahora `list_executions` reconstruye las no-terminales desde `execution_index.json` + `orchestrator.snapshot()` y las fusiona con las del JSONL. Añade `test_list_executions_incluye_la_awaiting`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -147,8 +147,47 @@ async def invoke_agent(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[AgentExecutionSummary])
|
@router.get("", response_model=list[AgentExecutionSummary])
|
||||||
def list_executions(settings: SettingsDep) -> list[AgentExecutionSummary]:
|
async def list_executions(
|
||||||
return read_execution_summaries(settings.data_dir)
|
registry: RegistryDep,
|
||||||
|
policies: PolicyStoreDep,
|
||||||
|
orchestrator: OrchestratorDep,
|
||||||
|
settings: SettingsDep,
|
||||||
|
) -> list[AgentExecutionSummary]:
|
||||||
|
"""Resumen de ejecuciones: las terminales del JSONL más las pausadas en HITL.
|
||||||
|
|
||||||
|
Una ejecución en ``awaiting_approval`` no se escribe en el log append-only
|
||||||
|
(`executions.jsonl`); solo vive en el checkpointer. Para que la página de
|
||||||
|
aprobaciones del dashboard la encuentre, aquí se reconstruyen esas desde el
|
||||||
|
índice `execution_index.json` + el checkpointer.
|
||||||
|
"""
|
||||||
|
summaries = read_execution_summaries(settings.data_dir)
|
||||||
|
seen = {str(s.trace_id) for s in summaries}
|
||||||
|
for trace_id, meta in _load_index(settings.data_dir).items():
|
||||||
|
if trace_id in seen:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
agent_def = registry.get_version(meta["agent_name"], meta["version"])
|
||||||
|
policy = policies.get_policy(agent_def.guardrails[0])
|
||||||
|
execution = await orchestrator.snapshot(
|
||||||
|
agent_def=agent_def, policy=policy, trace_id=UUID(trace_id)
|
||||||
|
)
|
||||||
|
except (FileNotFoundError, IndexError, KeyError, ValueError):
|
||||||
|
continue
|
||||||
|
if execution is None:
|
||||||
|
continue
|
||||||
|
summaries.append(
|
||||||
|
AgentExecutionSummary(
|
||||||
|
trace_id=execution.trace_id,
|
||||||
|
agent_name=execution.agent_name,
|
||||||
|
agent_version=execution.agent_version,
|
||||||
|
status=execution.status,
|
||||||
|
started_at=execution.started_at,
|
||||||
|
finished_at=execution.finished_at,
|
||||||
|
n_violations=len(execution.violations),
|
||||||
|
n_proposed_actions=len(execution.proposed_actions),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return summaries
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{trace_id}", response_model=AgentExecution)
|
@router.get("/{trace_id}", response_model=AgentExecution)
|
||||||
|
|||||||
@@ -81,6 +81,18 @@ def test_list_executions_incluye_la_completada(client: TestClient) -> None:
|
|||||||
assert rows[0]["n_proposed_actions"] == 1
|
assert rows[0]["n_proposed_actions"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_executions_incluye_la_awaiting(client: TestClient) -> None:
|
||||||
|
"""Una ejecución pausada en HITL no se escribe en el JSONL; aún así debe listarse
|
||||||
|
(la página de Aprobaciones del dashboard depende de ello)."""
|
||||||
|
trace_id = client.post(
|
||||||
|
"/agents/incident_analyzer/invoke", json={"input": "caída registros sip"}
|
||||||
|
).json()["trace_id"]
|
||||||
|
rows = client.get("/executions").json()
|
||||||
|
awaiting = [r for r in rows if r["trace_id"] == trace_id]
|
||||||
|
assert len(awaiting) == 1
|
||||||
|
assert awaiting[0]["status"] == "awaiting_approval"
|
||||||
|
|
||||||
|
|
||||||
def test_approve_resume_completa_ejecucion(client: TestClient) -> None:
|
def test_approve_resume_completa_ejecucion(client: TestClient) -> None:
|
||||||
invoked = client.post(
|
invoked = client.post(
|
||||||
"/agents/incident_analyzer/invoke", json={"input": "caída registros sip"}
|
"/agents/incident_analyzer/invoke", json={"input": "caída registros sip"}
|
||||||
|
|||||||
Reference in New Issue
Block a user