| """CLI event handling for a single queued node (transcript + session + errors).""" |
|
|
| from collections.abc import Awaitable, Callable |
| from typing import Any |
|
|
| from loguru import logger |
|
|
| from free_claude_code.core.trace import trace_event |
|
|
| from .cli_event_constants import TRANSCRIPT_EVENT_TYPES, get_status_for_event |
| from .managed_protocols import ManagedClaudeSessionManagerProtocol |
| from .safe_diagnostics import text_len_hint |
| from .transcript import TranscriptBuffer |
| from .trees import NodeClaim |
|
|
| RecordSession = Callable[[str], Awaitable[None]] |
| CompleteClaim = Callable[[str | None], Awaitable[None]] |
| FailClaim = Callable[[str, str], Awaitable[None]] |
|
|
|
|
| async def handle_session_info_event( |
| event_data: dict[str, Any], |
| claim: NodeClaim, |
| captured_session_id: str | None, |
| temp_session_id: str | None, |
| *, |
| cli_manager: ManagedClaudeSessionManagerProtocol, |
| record_session: RecordSession, |
| ) -> tuple[str | None, str | None]: |
| """Handle session_info event; return updated (captured_session_id, temp_session_id).""" |
| if event_data.get("type") != "session_info": |
| return captured_session_id, temp_session_id |
|
|
| real_session_id = event_data.get("session_id") |
| if not real_session_id or not temp_session_id: |
| return captured_session_id, temp_session_id |
|
|
| registered = await cli_manager.register_real_session_id( |
| temp_session_id, |
| real_session_id, |
| ) |
| if not registered: |
| raise RuntimeError("Managed Claude session registration failed.") |
| trace_event( |
| stage="claude_cli", |
| event="claude_cli.session.registered", |
| source="claude_cli", |
| node_id=claim.node.node_id, |
| temp_session_id=temp_session_id, |
| real_session_id=real_session_id, |
| tree_root_id=claim.identity.root_id, |
| ) |
| await record_session(real_session_id) |
|
|
| return real_session_id, None |
|
|
|
|
| async def process_parsed_cli_event( |
| parsed: dict[str, Any], |
| transcript: TranscriptBuffer, |
| update_ui: Callable[..., Awaitable[None]], |
| last_status: str | None, |
| had_transcript_events: bool, |
| claim: NodeClaim, |
| captured_session_id: str | None, |
| *, |
| format_status: Callable[..., str], |
| complete_claim: CompleteClaim, |
| fail_claim: FailClaim, |
| log_messaging_error_details: bool = False, |
| ) -> tuple[str | None, bool]: |
| """Process a single parsed CLI event. Returns (last_status, had_transcript_events).""" |
| ptype = parsed.get("type") or "" |
|
|
| if ptype in TRANSCRIPT_EVENT_TYPES: |
| transcript.apply(parsed) |
| had_transcript_events = True |
|
|
| status = get_status_for_event(ptype, parsed, format_status) |
| if status is not None: |
| await update_ui(status) |
| last_status = status |
| elif ptype == "block_stop": |
| await update_ui(last_status, force=True) |
| elif ptype == "complete": |
| if parsed.get("status") != "success": |
| return last_status, had_transcript_events |
| if not had_transcript_events: |
| transcript.apply({"type": "text_chunk", "text": "Done."}) |
| trace_event( |
| stage="claude_cli", |
| event="turn.completed", |
| source="cli_event", |
| node_id=claim.node.node_id, |
| claude_session_id=captured_session_id, |
| ) |
| await update_ui(format_status("✅", "Complete"), force=True) |
| await complete_claim(captured_session_id) |
| elif ptype == "error": |
| error_msg = parsed.get("message", "Unknown error") |
| em = error_msg if isinstance(error_msg, str) else str(error_msg) |
| trace_event( |
| stage="claude_cli", |
| event="turn.failed", |
| source="cli_event", |
| node_id=claim.node.node_id, |
| claude_session_id=captured_session_id, |
| cli_error_message=em, |
| ) |
| if log_messaging_error_details: |
| logger.error("HANDLER: Error event received: {}", error_msg) |
| else: |
| logger.error( |
| "HANDLER: Error event received: message_chars={}", |
| text_len_hint(em), |
| ) |
| await update_ui(format_status("❌", "Error"), force=True) |
| await fail_claim(em, "Parent task failed") |
|
|
| return last_status, had_transcript_events |
|
|