"""Contextual sidebar scope schema and prompt block renderer. ContextualScope mirrors the TypeScript ContextualScope type sent by the Electron renderer when the user opens the side chat anchored to a specific view. The renderer ships camelCase keys; Pydantic's alias_generator maps them to snake_case Python attributes automatically. """ from __future__ import annotations from typing import Literal, Optional from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_camel PageType = Literal[ "timeline", "tasks", "projects-list", "project", "note", ] EntityType = Literal["project", "note", "task", "timeline_event"] class ContextualScope(BaseModel): """Scope payload sent by the Electron renderer for contextual chat. The renderer ships camelCase keys (entityType, entityId, ...). Pydantic's alias generator maps them to snake_case Python attrs. """ model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel) page: PageType entity_type: Optional[EntityType] = None entity_id: Optional[str] = None entity_name: Optional[str] = None project_id: Optional[str] = None char_count: Optional[int] = None counts: Optional[dict[str, int]] = None filters: Optional[dict] = None def render_scope_block(scope: ContextualScope) -> str: """Produce a single-paragraph human-readable summary of the current view for injection into the contextual agent system prompt. Never emits internal ids — only names. The LLM is told to use names in prose; ids travel through tool calls. """ if scope.entity_type == "project": c = scope.counts or {} return ( f"User is viewing the project {scope.entity_name!r}. " f"{c.get('tasks', 0)} tasks, " f"{c.get('notes', 0)} notes, " f"{c.get('milestones', 0)} milestones." ) if scope.entity_type == "note": return ( f"User is viewing the note {scope.entity_name!r} " f"({scope.char_count or 0} characters)." ) if scope.page == "tasks": return "User is viewing the global Tasks list (all projects)." if scope.page == "timeline": return "User is viewing the global Timeline view." if scope.page == "projects-list": return "User is viewing the Projects list." return f"User is on page {scope.page}."