from __future__ import annotations from pathlib import Path from beaver.engine.session import SessionManager from beaver.memory.runs import RunMemoryStore, RunRecord from beaver.services.process_service import SessionProcessProjector from beaver.skills.specs import SkillActivationReceipt def test_process_projection_maps_task_team_events(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") run_store.append_run_record( RunRecord( run_id="sub-run", session_id="sub-session", task_id="task-1", attempt_index=1, task_text="sub task", started_at="2026-01-01T00:00:01+00:00", ended_at="2026-01-01T00:00:02+00:00", success=True, finish_reason="stop", ) ) run_store.append_run_record( RunRecord( run_id="main-run", session_id="web:test", task_id="task-1", attempt_index=1, task_text="main task", started_at="2026-01-01T00:00:03+00:00", ended_at="2026-01-01T00:00:04+00:00", success=True, finish_reason="stop", ) ) session.append_message( "web:test", role="system", event_type="task_execution_planned", event_payload={ "task_id": "task-1", "attempt_index": 1, "plan_mode": "team", "strategy": "sequence", "node_ids": ["research"], "skill_queries": ["research workflow"], "selected_skill_names": ["research-workflow"], "skill_resolution_report": [ { "node_id": "research", "skill_query": "research workflow", "selected_skill_names": ["research-workflow"], "ephemeral_guidance_id": None, "ephemeral_guidance_name": None, "ephemeral_used": False, "reason": "matched published skill", } ], "reason": "needs research", }, context_visible=False, ) session.append_message( "web:test", role="system", event_type="task_team_run_completed", event_payload={ "task_id": "task-1", "attempt_index": 1, "team_success": True, "team_run_ids": ["sub-run"], "node_results": [ { "node_id": "research", "success": True, "output_text": "evidence", "run_id": "sub-run", "skill_query": "research workflow", "selected_skill_names": ["research-workflow"], "ephemeral_skill_names": [], "ephemeral_guidance_id": None, "ephemeral_guidance_name": None, "ephemeral_used": False, "finish_reason": "stop", } ], }, context_visible=False, ) session.append_message( "web:test", role="system", event_type="task_synthesis_completed", event_payload={"task_id": "task-1", "attempt_index": 1, "main_run_id": "main-run"}, context_visible=False, ) session.append_message( "web:test", run_id="main-run", role="system", event_type="task_evidence_recorded", event_payload={ "task_id": "task-1", "attempt_index": 1, "evidence_status": "recorded", }, context_visible=False, ) session.append_message( "web:test", run_id="main-run", role="system", event_type="task_acceptance_recorded", event_payload={ "task_id": "task-1", "attempt_index": 1, "acceptance_type": "accept", }, context_visible=False, ) projection = SessionProcessProjector(session, run_store).project("web:test") run_ids = {run["run_id"] for run in projection["runs"]} assert "task:task-1:attempt:1" in run_ids assert "sub-run" in run_ids assert "main-run" in run_ids sub_run = next(run for run in projection["runs"] if run["run_id"] == "sub-run") assert sub_run["metadata"]["selected_skill_names"] == ["research-workflow"] assert sub_run["metadata"]["skill_query"] == "research workflow" assert sub_run["metadata"]["ephemeral_guidance_id"] is None assert any(event["actor_name"] == "Evidence" for event in projection["events"]) assert any(run["session_id"] == "web:test" for run in projection["runs"]) planned_event = next(event for event in projection["events"] if event["kind"] == "task_planned") assert planned_event["metadata"]["timeline_type"] == "plan" assert planned_event["metadata"]["plan_mode"] == "team" assert planned_event["metadata"]["strategy"] == "sequence" assert planned_event["metadata"]["selected_skill_names"] == ["research-workflow"] skill_event = next(event for event in projection["events"] if event["kind"] == "skill_selected") assert skill_event["metadata"]["timeline_type"] == "skill" assert skill_event["metadata"]["skill_names"] == ["research-workflow"] team_event = next(event for event in projection["events"] if event["kind"] == "agent_team_created") assert team_event["metadata"]["timeline_type"] == "agent_team" assert team_event["metadata"]["team_run_ids"] == ["sub-run"] node_event = next(event for event in projection["events"] if event["kind"] == "agent_finished") assert node_event["metadata"]["timeline_type"] == "agent_progress" assert "node_result" not in node_event["metadata"] evidence_event = next(event for event in projection["events"] if event["kind"] == "task_result_ready") assert evidence_event["metadata"]["timeline_type"] == "result" assert evidence_event["status"] == "done" acceptance_event = next(event for event in projection["events"] if event["kind"] == "task_acceptance_recorded") assert acceptance_event["metadata"]["timeline_type"] == "acceptance" def test_process_projection_maps_failed_task_team_events(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") run_store.append_run_record( RunRecord( run_id="failed-sub-run", session_id="failed-sub-session", task_id="task-1", attempt_index=1, task_text="failed sub task", started_at="2026-01-01T00:00:01+00:00", ended_at="2026-01-01T00:00:02+00:00", success=False, finish_reason="error", ) ) session.append_message( "web:test", role="system", event_type="task_team_run_failed", event_payload={ "task_id": "task-1", "attempt_index": 1, "team_success": False, "team_run_ids": ["failed-sub-run"], "error": "research node failed", "node_results": [ { "node_id": "research", "success": False, "error": "source unavailable", "run_id": "failed-sub-run", "finish_reason": "error", } ], }, context_visible=False, ) projection = SessionProcessProjector(session, run_store).project("web:test") team_event = next(event for event in projection["events"] if event["kind"] == "agent_team_created") assert team_event["status"] == "error" assert team_event["metadata"]["timeline_type"] == "agent_team" assert team_event["metadata"]["team_run_ids"] == ["failed-sub-run"] node_event = next(event for event in projection["events"] if event["kind"] == "agent_finished") assert node_event["status"] == "error" assert node_event["metadata"]["timeline_type"] == "agent_progress" def test_process_projection_uses_normalized_plan_metadata_defaults(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") session.append_message( "web:test", role="system", event_type="task_execution_planned", event_payload={ "task_id": "task-1", "attempt_index": 1, "plan_mode": None, "strategy": None, }, context_visible=False, ) projection = SessionProcessProjector(session, run_store).project("web:test") root_run = next(run for run in projection["runs"] if run["run_id"] == "task:task-1:attempt:1") assert root_run["metadata"]["plan_mode"] == "single" assert root_run["metadata"]["strategy"] == "single" planned_event = next(event for event in projection["events"] if event["kind"] == "task_planned") assert planned_event["metadata"]["plan_mode"] == "single" assert planned_event["metadata"]["strategy"] == "single" def test_process_projection_emits_skill_card_from_main_run_receipts(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") run_store.append_run_record( RunRecord( run_id="main-run", session_id="web:test", task_id="task-1", attempt_index=1, task_text="main task", started_at="2026-01-01T00:00:03+00:00", ended_at="2026-01-01T00:00:04+00:00", success=True, finish_reason="stop", activated_skills=[ SkillActivationReceipt( run_id="main-run", session_id="web:test", skill_name="web-operation", skill_version="1", content_hash="hash", activated_at="2026-01-01T00:00:03+00:00", activation_reason="Needs live web lookup.", ) ], ) ) session.append_message( "web:test", role="system", event_type="task_execution_planned", event_payload={ "task_id": "task-1", "attempt_index": 1, "plan_mode": "single", "strategy": "single", "selected_skill_names": [], }, context_visible=False, ) session.append_message( "web:test", role="system", event_type="task_synthesis_completed", event_payload={"task_id": "task-1", "attempt_index": 1, "main_run_id": "main-run"}, context_visible=False, ) projection = SessionProcessProjector(session, run_store).project("web:test") skill_events = [ event for event in projection["events"] if event["kind"] == "skill_selected" and event["run_id"] == "main-run" ] assert skill_events assert skill_events[0]["metadata"]["timeline_type"] == "skill" assert skill_events[0]["metadata"]["skill_names"] == ["web-operation"] def test_process_projection_emits_tool_cards_from_run_messages(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") run_store.append_run_record( RunRecord( run_id="main-run", session_id="web:test", task_id="task-1", attempt_index=1, task_text="main task", started_at="2026-01-01T00:00:03+00:00", ended_at="2026-01-01T00:00:04+00:00", success=True, finish_reason="stop", ) ) session.append_message( "web:test", role="system", event_type="task_execution_planned", event_payload={"task_id": "task-1", "attempt_index": 1}, context_visible=False, ) session.append_message( "web:test", run_id="main-run", role="assistant", event_type="assistant_message_added", event_payload={"task_id": "task-1"}, content="Searching", tool_calls=[ { "id": "call-1", "name": "multi_search", "arguments": {"query": "Macau cafe near Bóvia"}, } ], context_visible=False, ) session.append_message( "web:test", run_id="main-run", role="tool", event_type="tool_result_recorded", event_payload={"success": True, "error": None}, content="Found 3 restaurants", tool_name="multi_search", tool_call_id="call-1", context_visible=True, ) projection = SessionProcessProjector(session, run_store).project("web:test") tool_call = next(event for event in projection["events"] if event["kind"] == "tool_call_started") assert tool_call["metadata"]["timeline_type"] == "tool_call" assert tool_call["metadata"]["tool_name"] == "multi_search" assert tool_call["run_id"] == "main-run" tool_result = next(event for event in projection["events"] if event["kind"] == "tool_call_finished") assert tool_result["metadata"]["timeline_type"] == "tool_result" assert tool_result["metadata"]["tool_name"] == "multi_search" assert tool_result["metadata"]["success"] is True def test_process_projection_marks_root_done_when_result_is_ready(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") run_store.append_run_record( RunRecord( run_id="main-run", session_id="web:test", task_id="task-1", attempt_index=1, task_text="send email", started_at="2026-01-01T00:00:03+00:00", ended_at="2026-01-01T00:00:04+00:00", success=True, finish_reason="stop", ) ) session.append_message( "web:test", role="system", event_type="task_execution_planned", event_payload={"task_id": "task-1", "attempt_index": 1, "plan_mode": "single", "strategy": "single"}, context_visible=False, ) session.append_message( "web:test", role="system", event_type="task_synthesis_completed", event_payload={"task_id": "task-1", "attempt_index": 1, "main_run_id": "main-run"}, context_visible=False, ) session.append_message( "web:test", run_id="main-run", role="system", event_type="task_evidence_recorded", event_payload={"task_id": "task-1", "attempt_index": 1, "evidence_status": "recorded"}, context_visible=False, ) projection = SessionProcessProjector(session, run_store).project("web:test") root_run = next(run for run in projection["runs"] if run["run_id"] == "task:task-1:attempt:1") assert root_run["status"] == "done" assert root_run["finished_at"] is not None def test_process_projection_exposes_ephemeral_guidance_artifacts(tmp_path: Path) -> None: session = SessionManager(tmp_path) run_store = RunMemoryStore(tmp_path / "memory" / "runs") run_store.append_run_record( RunRecord( run_id="sub-run", session_id="sub-session", task_id="task-1", attempt_index=1, task_text="sub task", started_at="2026-01-01T00:00:01+00:00", ended_at="2026-01-01T00:00:02+00:00", success=True, finish_reason="stop", ) ) session.append_message( "web:test", role="system", event_type="task_execution_planned", event_payload={ "task_id": "task-1", "attempt_index": 1, "plan_mode": "team", "strategy": "sequence", "node_ids": ["research"], "ephemeral_guidance_ids": ["eg_123"], "skill_resolution_report": [ { "node_id": "research", "skill_query": "research workflow", "selected_skill_names": [], "ephemeral_guidance_id": "eg_123", "ephemeral_guidance_name": "research-workflow", "ephemeral_used": True, "reason": "generated ephemeral guidance", } ], }, context_visible=False, ) session.append_message( "web:test", role="system", event_type="task_team_run_completed", event_payload={ "task_id": "task-1", "attempt_index": 1, "team_success": True, "team_run_ids": ["sub-run"], "node_results": [ { "node_id": "research", "success": True, "output_text": "evidence", "run_id": "sub-run", "skill_query": "research workflow", "selected_skill_names": [], "ephemeral_skill_names": ["ephemeral:research-workflow"], "ephemeral_guidance_id": "eg_123", "ephemeral_guidance_name": "research-workflow", "ephemeral_used": True, "finish_reason": "stop", } ], }, context_visible=False, ) projection = SessionProcessProjector(session, run_store).project("web:test") sub_run = next(run for run in projection["runs"] if run["run_id"] == "sub-run") assert sub_run["metadata"]["ephemeral_guidance_id"] == "eg_123" assert projection["artifacts"][0]["artifact_id"] == "sub-run:ephemeral-guidance:eg_123" assert projection["artifacts"][0]["metadata"]["ephemeral_guidance_name"] == "research-workflow"