diff --git a/core/src/agentforge_core/api/executions.py b/core/src/agentforge_core/api/executions.py index c49f204..41524c3 100644 --- a/core/src/agentforge_core/api/executions.py +++ b/core/src/agentforge_core/api/executions.py @@ -147,8 +147,47 @@ async def invoke_agent( @router.get("", response_model=list[AgentExecutionSummary]) -def list_executions(settings: SettingsDep) -> list[AgentExecutionSummary]: - return read_execution_summaries(settings.data_dir) +async def list_executions( + 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) diff --git a/tests/unit/test_api_executions.py b/tests/unit/test_api_executions.py index 7d80638..3bb5e32 100644 --- a/tests/unit/test_api_executions.py +++ b/tests/unit/test_api_executions.py @@ -81,6 +81,18 @@ def test_list_executions_incluye_la_completada(client: TestClient) -> None: 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: invoked = client.post( "/agents/incident_analyzer/invoke", json={"input": "caída registros sip"}