Fixes + Cleanup

This commit is contained in:
Renn F
2025-12-18 03:29:08 +01:00
parent 9f008e5362
commit eed29cdf4a
19 changed files with 805 additions and 617 deletions
+2 -2
View File
@@ -117,8 +117,8 @@ def create_app() -> FastAPI:
title="RoboCo API",
description="AI Agents Company - Messaging and Task Management API",
version=settings.app_version,
docs_url="/docs", # if settings.debug else None,
redoc_url="/redoc", # if settings.debug else None,
docs_url="/docs", # if settings.debug else None,
redoc_url="/redoc", # if settings.debug else None,
lifespan=lifespan,
)
+26 -2
View File
@@ -4,9 +4,10 @@ API Dependencies
Shared dependencies for FastAPI routes.
"""
from __future__ import annotations
import contextlib
from collections.abc import Callable, Coroutine
from typing import Annotated, Any, cast
from typing import TYPE_CHECKING, Annotated, Any, cast
from uuid import UUID
from fastapi import Depends, Header, HTTPException, status
@@ -16,8 +17,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
from roboco.db.base import get_db
from roboco.db.tables import AgentTable
from roboco.models import AgentRole, Team
from roboco.runtime import AgentOrchestrator
from roboco.services.permissions import AgentContext, PermissionService
if TYPE_CHECKING:
from collections.abc import Callable, Coroutine
# Type alias for database session dependency
DbSession = Annotated[AsyncSession, Depends(get_db)]
@@ -61,6 +66,7 @@ class _ServiceHolder:
"""Holder for singleton service instances."""
permission_service: PermissionService | None = None
orchestrator: AgentOrchestrator | None = None
def get_permission_service() -> PermissionService:
@@ -73,6 +79,24 @@ def get_permission_service() -> PermissionService:
PermissionServiceDep = Annotated[PermissionService, Depends(get_permission_service)]
def set_orchestrator(orchestrator: AgentOrchestrator) -> None:
"""Set the global orchestrator instance."""
_ServiceHolder.orchestrator = orchestrator
def get_orchestrator() -> AgentOrchestrator:
"""Get the global orchestrator instance."""
if _ServiceHolder.orchestrator is None:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Orchestrator not initialized",
)
return _ServiceHolder.orchestrator
OrchestratorDep = Annotated[AgentOrchestrator, Depends(get_orchestrator)]
async def get_current_agent_id(
db: DbSession,
x_agent_id: Annotated[str | None, Header()] = None,
+3 -22
View File
@@ -8,31 +8,16 @@ from typing import cast
from uuid import UUID
from fastapi import APIRouter, HTTPException, Query, status
from pydantic import BaseModel
from sqlalchemy import select
from roboco.api.deps import DbSession
from roboco.api.schemas.agents import AgentResponse
from roboco.db.tables import AgentTable
from roboco.models import AgentRole, Team
router = APIRouter()
class AgentResponse(BaseModel):
"""Response model for agent information."""
id: UUID
name: str
slug: str
role: AgentRole
team: Team | None
class Config:
"""Pydantic config."""
from_attributes = True
@router.get("")
async def list_agents(
db: DbSession,
@@ -96,14 +81,10 @@ async def get_agent(
# Try to parse as UUID first
try:
uuid = UUID(agent_id)
result = await db.execute(
select(AgentTable).where(AgentTable.id == uuid)
)
result = await db.execute(select(AgentTable).where(AgentTable.id == uuid))
except ValueError:
# Not a UUID, try slug lookup
result = await db.execute(
select(AgentTable).where(AgentTable.slug == agent_id)
)
result = await db.execute(select(AgentTable).where(AgentTable.slug == agent_id))
agent = result.scalar_one_or_none()
+10 -59
View File
@@ -18,6 +18,9 @@ from roboco.api.schemas.channels import (
ChannelResponse,
GroupResponse,
ListChannelsQuery,
apply_channel_updates,
get_channel_or_404,
require_channel_admin,
)
from roboco.db.tables import ChannelTable
from roboco.models import AgentRole, ChannelCreate, ChannelUpdate
@@ -25,37 +28,6 @@ from roboco.utils.converters import require_uuid, to_python_uuid
router = APIRouter()
# Roles authorized to manage channels
CHANNEL_ADMIN_ROLES = frozenset(
{AgentRole.CEO, AgentRole.PRODUCT_OWNER, AgentRole.MAIN_PM}
)
# =============================================================================
# Helpers
# =============================================================================
def _require_channel_admin(agent: CurrentAgentContext) -> None:
"""Raise 403 if agent is not authorized to manage channels."""
if agent.role not in CHANNEL_ADMIN_ROLES:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to manage channels",
)
async def _get_channel_or_404(db: DbSession, channel_id: UUID) -> ChannelTable:
"""Get channel by ID or raise 404."""
result = await db.execute(select(ChannelTable).where(ChannelTable.id == channel_id))
channel = result.scalar_one_or_none()
if not channel:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Channel not found",
)
return channel
# =============================================================================
# Routes
@@ -300,27 +272,6 @@ async def create_channel(
)
# Fields that can be updated on a channel
_CHANNEL_UPDATE_FIELDS = (
"name",
"description",
"topic",
"is_archived",
"allow_threads",
"allow_reactions",
"message_retention_days",
"max_message_length",
)
def _apply_channel_updates(channel: ChannelTable, data: ChannelUpdate) -> None:
"""Apply updates to channel fields."""
for field in _CHANNEL_UPDATE_FIELDS:
value = getattr(data, field, None)
if value is not None:
setattr(channel, field, value)
@router.patch(
"/{channel_id}",
response_model=ChannelResponse,
@@ -335,9 +286,9 @@ async def update_channel(
data: ChannelUpdate,
) -> ChannelResponse:
"""Update channel settings."""
_require_channel_admin(agent)
channel = await _get_channel_or_404(db, channel_id)
_apply_channel_updates(channel, data)
require_channel_admin(agent)
channel = await get_channel_or_404(db, ChannelTable, channel_id)
apply_channel_updates(channel, data)
await db.flush()
return ChannelResponse(
@@ -370,8 +321,8 @@ async def add_member(
can_write: bool = Query(True),
) -> None:
"""Add a member to the channel."""
_require_channel_admin(agent)
channel = await _get_channel_or_404(db, channel_id)
require_channel_admin(agent)
channel = await get_channel_or_404(db, ChannelTable, channel_id)
# Add to members if not already present
if member_id not in channel.members:
@@ -397,8 +348,8 @@ async def remove_member(
member_id: UUID,
) -> None:
"""Remove a member from the channel."""
_require_channel_admin(agent)
channel = await _get_channel_or_404(db, channel_id)
require_channel_admin(agent)
channel = await get_channel_or_404(db, ChannelTable, channel_id)
# Remove from members and writers
channel.members = [m for m in channel.members if m != member_id]
+3 -26
View File
@@ -4,38 +4,15 @@ Health Check Routes
Endpoints for monitoring application health and readiness.
"""
import redis.asyncio as redis
from fastapi import APIRouter, status
from sqlalchemy import text
from roboco.api.schemas.health import HealthResponse, ReadinessResponse
from roboco.config import settings
from roboco.db.base import get_db_context
from roboco.services.health import check_database, check_redis
router = APIRouter()
async def _check_database() -> tuple[str, bool]:
"""Check database connectivity."""
try:
async with get_db_context() as session:
await session.execute(text("SELECT 1"))
return "ok", True
except Exception as e:
return str(e), False
async def _check_redis() -> tuple[str, bool]:
"""Check Redis connectivity."""
try:
client = redis.from_url(settings.redis_url)
await client.ping()
await client.close()
return "ok", True
except Exception as e:
return str(e), False
@router.get(
"/health",
response_model=HealthResponse,
@@ -61,8 +38,8 @@ async def health_check() -> HealthResponse:
)
async def readiness_check() -> ReadinessResponse:
"""Check if all dependencies are ready."""
db_status, db_ok = await _check_database()
redis_status, redis_ok = await _check_redis()
db_status, db_ok = await check_database()
redis_status, redis_ok = await check_redis()
overall = "ok" if (db_ok and redis_ok) else "degraded"
+17 -149
View File
@@ -18,13 +18,18 @@ from roboco.api.schemas.notifications import (
NotificationCreateRequest,
NotificationListResponse,
NotificationResponse,
build_notification_query,
notification_to_response,
)
from roboco.db.tables import AgentTable, NotificationTable
from roboco.enforcement import (
NotificationPermissionError,
validate_notification_permission,
)
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
from roboco.services.notification import (
get_notification_or_404,
require_notification_recipient,
)
router = APIRouter()
@@ -34,49 +39,6 @@ router = APIRouter()
# =============================================================================
def _build_notification_query(
agent_id: UUID,
params: ListNotificationsParams,
) -> Any:
"""Build the notification query with filters."""
query = select(NotificationTable).where(
NotificationTable.to_agents.contains([agent_id])
)
if params.unread_only:
query = query.where(~NotificationTable.read_by.contains([agent_id]))
if params.pending_ack_only:
query = query.where(
NotificationTable.requires_ack.is_(True),
~NotificationTable.acked_by.contains([agent_id]),
)
if params.type_filter:
query = query.where(NotificationTable.type == params.type_filter)
return query.order_by(NotificationTable.timestamp.desc()).limit(params.limit)
def _notification_to_response(
n: NotificationTable,
agent_id: UUID,
) -> NotificationResponse:
"""Convert a notification to response format."""
return NotificationResponse(
id=require_uuid(n.id),
type=n.type,
priority=n.priority,
from_agent=require_uuid(n.from_agent),
to_agents=to_python_uuid_list(n.to_agents),
subject=n.subject,
body=n.body,
requires_ack=n.requires_ack,
is_acknowledged=agent_id in n.acked_by,
is_fully_acknowledged=all(a in n.acked_by for a in n.to_agents),
is_read=agent_id in n.read_by,
related_task_id=to_python_uuid(n.related_task_id),
timestamp=n.timestamp,
expires_at=n.expires_at,
)
@router.get(
"",
response_model=NotificationListResponse,
@@ -89,7 +51,7 @@ async def list_notifications(
params: Annotated[ListNotificationsParams, Depends()],
) -> NotificationListResponse:
"""List notifications for the agent."""
query = _build_notification_query(agent_id, params)
query = build_notification_query(NotificationTable, agent_id, params)
result: Any = await db.execute(query)
notifications = result.scalars().all()
@@ -97,7 +59,7 @@ async def list_notifications(
pending_ack_count = sum(
1 for n in notifications if n.requires_ack and agent_id not in n.acked_by
)
items = [_notification_to_response(n, agent_id) for n in notifications]
items = [notification_to_response(n, agent_id) for n in notifications]
return NotificationListResponse(
items=items,
@@ -119,47 +81,15 @@ async def get_notification(
notification_id: UUID,
) -> NotificationResponse:
"""Get a notification."""
result = await db.execute(
select(NotificationTable).where(NotificationTable.id == notification_id)
)
notification = result.scalar_one_or_none()
if not notification:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found",
)
# Check if agent is a recipient
if agent_id not in notification.to_agents:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not a recipient of this notification",
)
notification = await get_notification_or_404(db, notification_id)
require_notification_recipient(notification, agent_id)
# Mark as read
if agent_id not in notification.read_by:
notification.read_by = [*notification.read_by, agent_id]
await db.flush()
return NotificationResponse(
id=require_uuid(notification.id),
type=notification.type,
priority=notification.priority,
from_agent=require_uuid(notification.from_agent),
to_agents=to_python_uuid_list(notification.to_agents),
subject=notification.subject,
body=notification.body,
requires_ack=notification.requires_ack,
is_acknowledged=agent_id in notification.acked_by,
is_fully_acknowledged=all(
a in notification.acked_by for a in notification.to_agents
),
is_read=True,
related_task_id=to_python_uuid(notification.related_task_id),
timestamp=notification.timestamp,
expires_at=notification.expires_at,
)
return notification_to_response(notification, agent_id)
@router.post(
@@ -229,22 +159,7 @@ async def send_notification(
db.add(notification)
await db.flush()
return NotificationResponse(
id=require_uuid(notification.id),
type=notification.type,
priority=notification.priority,
from_agent=require_uuid(notification.from_agent),
to_agents=to_python_uuid_list(notification.to_agents),
subject=notification.subject,
body=notification.body,
requires_ack=notification.requires_ack,
is_acknowledged=False,
is_fully_acknowledged=False,
is_read=False,
related_task_id=to_python_uuid(notification.related_task_id),
timestamp=notification.timestamp,
expires_at=notification.expires_at,
)
return notification_to_response(notification, agent_id)
@router.post(
@@ -259,23 +174,8 @@ async def acknowledge_notification(
notification_id: UUID,
) -> NotificationResponse:
"""Acknowledge a notification."""
result = await db.execute(
select(NotificationTable).where(NotificationTable.id == notification_id)
)
notification = result.scalar_one_or_none()
if not notification:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found",
)
# Check if agent is a recipient
if agent_id not in notification.to_agents:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not a recipient of this notification",
)
notification = await get_notification_or_404(db, notification_id)
require_notification_recipient(notification, agent_id)
if not notification.requires_ack:
raise HTTPException(
@@ -296,25 +196,7 @@ async def acknowledge_notification(
notification.read_by = [*notification.read_by, agent_id]
await db.flush()
return NotificationResponse(
id=require_uuid(notification.id),
type=notification.type,
priority=notification.priority,
from_agent=require_uuid(notification.from_agent),
to_agents=to_python_uuid_list(notification.to_agents),
subject=notification.subject,
body=notification.body,
requires_ack=notification.requires_ack,
is_acknowledged=True,
is_fully_acknowledged=all(
a in notification.acked_by for a in notification.to_agents
),
is_read=True,
related_task_id=to_python_uuid(notification.related_task_id),
timestamp=notification.timestamp,
expires_at=notification.expires_at,
)
return notification_to_response(notification, agent_id)
@router.post(
@@ -329,22 +211,8 @@ async def mark_as_read(
notification_id: UUID,
) -> None:
"""Mark a notification as read."""
result = await db.execute(
select(NotificationTable).where(NotificationTable.id == notification_id)
)
notification = result.scalar_one_or_none()
if not notification:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found",
)
if agent_id not in notification.to_agents:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not a recipient of this notification",
)
notification = await get_notification_or_404(db, notification_id)
require_notification_recipient(notification, agent_id)
if agent_id not in notification.read_by:
notification.read_by = [*notification.read_by, agent_id]
+3 -21
View File
@@ -8,6 +8,7 @@ from datetime import datetime
from fastapi import APIRouter, HTTPException, status
from roboco.api.deps import get_orchestrator, set_orchestrator
from roboco.api.schemas.orchestrator import (
AgentStatusResponse,
OrchestratorStatusResponse,
@@ -15,30 +16,11 @@ from roboco.api.schemas.orchestrator import (
SpawnAgentRequest,
WaitingAgentResponse,
)
from roboco.runtime import AgentOrchestrator
router = APIRouter()
class _OrchestratorHolder:
"""Holder for orchestrator instance (set by bootstrap)."""
instance: AgentOrchestrator | None = None
def set_orchestrator(orchestrator: AgentOrchestrator) -> None:
"""Set the global orchestrator instance."""
_OrchestratorHolder.instance = orchestrator
def get_orchestrator() -> AgentOrchestrator:
"""Get the global orchestrator instance."""
if _OrchestratorHolder.instance is None:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Orchestrator not initialized",
)
return _OrchestratorHolder.instance
# Re-export set_orchestrator for bootstrap code
__all__ = ["router", "set_orchestrator"]
# =============================================================================
+45 -166
View File
@@ -17,163 +17,29 @@ from roboco.api.deps import (
)
from roboco.api.schemas.tasks import (
CheckpointRequest,
CheckpointResponse,
ClaimRequest,
CommitRefResponse,
CommitRequest,
ListTasksQuery,
ProgressRequest,
ProgressUpdateResponse,
QANotes,
SubTaskResponse,
TaskCountResponse,
TaskPlanResponse,
TaskResponse,
TaskUpdate,
TeamTasksQuery,
task_list_to_response,
task_to_response,
transform_update_data,
)
from roboco.db.tables import AgentTable, TaskTable
from roboco.db.tables import AgentTable
from roboco.models.base import TaskStatus, Team
from roboco.models.task import TaskCreate
from roboco.services.audit import get_audit_service
from roboco.services.permissions import TaskAction
from roboco.services.task import TaskCreateRequest, get_task_service
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
router = APIRouter()
def _convert_plan(plan_data: dict | None) -> TaskPlanResponse | None:
"""Convert plan JSON dict to TaskPlanResponse."""
if not plan_data:
return None
sub_tasks = []
for st in plan_data.get("sub_tasks", []):
sub_tasks.append(
SubTaskResponse(
id=st.get("id"),
title=st.get("title", ""),
description=st.get("description"),
completed=st.get("completed", False),
order=st.get("order", 0),
estimated_hours=st.get("estimated_hours"),
notes=st.get("notes"),
)
)
return TaskPlanResponse(
approach=plan_data.get("approach", ""),
sub_tasks=sub_tasks,
technical_considerations=plan_data.get("technical_considerations", []),
risks=plan_data.get("risks", []),
open_questions=plan_data.get("open_questions", []),
)
def _convert_checkpoints(checkpoints_data: list | None) -> list[CheckpointResponse]:
"""Convert checkpoints JSON list to CheckpointResponse list."""
if not checkpoints_data:
return []
result = []
for cp in checkpoints_data:
result.append(
CheckpointResponse(
id=cp.get("id"),
timestamp=cp.get("timestamp"),
agent_id=cp.get("agent_id"),
state_summary=cp.get("state_summary", ""),
remaining_work=cp.get("remaining_work", []),
notes=cp.get("notes"),
)
)
return result
def _convert_progress_updates(
updates_data: list | None,
) -> list[ProgressUpdateResponse]:
"""Convert progress_updates JSON list to ProgressUpdateResponse list."""
if not updates_data:
return []
result = []
for pu in updates_data:
result.append(
ProgressUpdateResponse(
timestamp=pu.get("timestamp"),
agent_id=pu.get("agent_id"),
message=pu.get("message", ""),
percentage=pu.get("percentage"),
)
)
return result
def _convert_commits(commits_data: list | None) -> list[CommitRefResponse]:
"""Convert commits JSON list to CommitRefResponse list."""
if not commits_data:
return []
result = []
for cm in commits_data:
result.append(
CommitRefResponse(
hash=cm.get("hash", ""),
message=cm.get("message", ""),
timestamp=cm.get("timestamp"),
author_agent_id=cm.get("author_agent_id"),
)
)
return result
def _to_response(task: TaskTable) -> TaskResponse:
"""Convert TaskTable to TaskResponse with proper UUID conversion."""
return TaskResponse(
id=require_uuid(task.id),
title=task.title,
description=task.description,
acceptance_criteria=task.acceptance_criteria or [],
status=task.status,
priority=task.priority,
team=task.team,
created_by=require_uuid(task.created_by),
assigned_to=to_python_uuid(task.assigned_to),
parent_task_id=to_python_uuid(task.parent_task_id),
dependency_ids=to_python_uuid_list(task.dependency_ids),
blocker_ids=to_python_uuid_list(task.blocker_ids),
created_at=task.created_at,
updated_at=task.updated_at,
claimed_at=task.claimed_at,
started_at=task.started_at,
completed_at=task.completed_at,
target_date=task.target_date,
estimated_complexity=task.estimated_complexity,
# Planning
plan=_convert_plan(task.plan),
# Execution
checkpoints=_convert_checkpoints(task.checkpoints),
progress_updates=_convert_progress_updates(task.progress_updates),
# Artifacts
commits=_convert_commits(task.commits),
# Documentation
dev_notes=task.dev_notes,
qa_notes=task.qa_notes,
auditor_notes=task.auditor_notes,
quick_context=task.quick_context,
# Review Status
self_verified=task.self_verified,
qa_verified=task.qa_verified,
)
def _to_response_list(tasks: list[TaskTable]) -> list[TaskResponse]:
"""Convert list of TaskTable to list of TaskResponse."""
return [_to_response(t) for t in tasks]
# =============================================================================
# CRUD ENDPOINTS
# =============================================================================
@@ -217,7 +83,7 @@ async def create_task(
)
task = await service.create(req)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.get("", response_model=list[TaskResponse])
@@ -258,7 +124,7 @@ async def list_tasks(
else:
tasks = await service.list_all(params.limit, params.offset)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/my", response_model=list[TaskResponse])
@@ -270,7 +136,7 @@ async def get_my_tasks(
"""Get tasks assigned to the current agent."""
service = get_task_service(db)
tasks = await service.list_by_assignee(agent.agent_id, status)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/pending", response_model=list[TaskResponse])
@@ -288,7 +154,7 @@ async def get_pending_tasks(
effective_team = team if can_view_all else agent.team
tasks = await service.list_pending(effective_team)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/blocked", response_model=list[TaskResponse])
@@ -306,7 +172,7 @@ async def get_blocked_tasks(
effective_team = team if can_view_all else agent.team
tasks = await service.list_blocked(effective_team)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/awaiting-qa", response_model=list[TaskResponse])
@@ -324,7 +190,7 @@ async def get_awaiting_qa_tasks(
effective_team = team if can_view_all else agent.team
tasks = await service.list_awaiting_qa(effective_team)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/awaiting-docs", response_model=list[TaskResponse])
@@ -342,7 +208,7 @@ async def get_awaiting_docs_tasks(
effective_team = team if can_view_all else agent.team
tasks = await service.list_awaiting_docs(effective_team)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/team/{team}", response_model=list[TaskResponse])
@@ -366,7 +232,7 @@ async def get_team_tasks(
service = get_task_service(db)
tasks = await service.list_by_team(team, params.task_status, params.limit)
return _to_response_list(tasks)
return task_list_to_response(tasks)
@router.get("/stats", response_model=TaskCountResponse)
@@ -419,7 +285,7 @@ async def get_task(
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
)
return _to_response(task)
return task_to_response(task)
@router.put("/{task_id}", response_model=TaskResponse)
@@ -431,7 +297,17 @@ async def update_task(
agent: CurrentAgentContext,
permissions: PermissionServiceDep,
) -> TaskResponse:
"""Update a task. Supports both PUT and PATCH for partial updates."""
"""Update a task. Supports both PUT and PATCH for partial updates.
CEO and privileged roles can update any field including:
- Basic info (title, description, acceptance_criteria, priority, etc.)
- Ownership (team, assigned_to)
- Relationships (parent_task_id, dependency_ids, blocker_ids)
- Planning (plan with sub_tasks, risks, open_questions)
- Execution tracking (progress_updates, checkpoints)
- Artifacts (commits)
- Notes (dev_notes, qa_notes, auditor_notes, quick_context)
"""
service = get_task_service(db)
task = await service.get(task_id)
if not task:
@@ -455,14 +331,17 @@ async def update_task(
detail="Not authorized to update this task",
)
task = await service.update(task_id, **data.model_dump(exclude_unset=True))
# Transform input data for database storage
updates = transform_update_data(data)
task = await service.update(task_id, **updates)
if not task:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Task update failed unexpectedly",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
@@ -504,7 +383,7 @@ async def get_subtasks(
"""Get subtasks of a task."""
service = get_task_service(db)
tasks = await service.get_subtasks(task_id)
return _to_response_list(tasks)
return task_list_to_response(tasks)
# =============================================================================
@@ -573,7 +452,7 @@ async def claim_task(
detail="Cannot claim task - not pending",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/start", response_model=TaskResponse)
@@ -604,7 +483,7 @@ async def start_task(
detail="Cannot start task - invalid status",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/block", response_model=TaskResponse)
@@ -639,7 +518,7 @@ async def block_task(
detail="Task block failed unexpectedly",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/unblock", response_model=TaskResponse)
@@ -673,7 +552,7 @@ async def unblock_task(
detail="Cannot unblock task - not blocked",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/pause", response_model=TaskResponse)
@@ -704,7 +583,7 @@ async def pause_task(
detail="Cannot pause task - not in progress",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/resume", response_model=TaskResponse)
@@ -735,7 +614,7 @@ async def resume_task(
detail="Cannot resume task - not paused",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/verify", response_model=TaskResponse)
@@ -766,7 +645,7 @@ async def submit_for_verification(
detail="Cannot verify task - not in progress",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/submit-qa", response_model=TaskResponse)
@@ -797,7 +676,7 @@ async def submit_for_qa(
detail="Cannot submit for QA - not verifying",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/pass-qa", response_model=TaskResponse)
@@ -853,7 +732,7 @@ async def pass_qa(
detail="Cannot pass QA - not awaiting QA",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/fail-qa", response_model=TaskResponse)
@@ -892,7 +771,7 @@ async def fail_qa(
detail="Cannot fail QA - not awaiting QA",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/complete", response_model=TaskResponse)
@@ -927,7 +806,7 @@ async def complete_task(
detail="Cannot complete task - invalid status",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/cancel", response_model=TaskResponse)
@@ -962,7 +841,7 @@ async def cancel_task(
detail="Task cancel failed unexpectedly",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
# =============================================================================
@@ -1001,7 +880,7 @@ async def add_progress(
detail="Add progress failed unexpectedly",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/checkpoint", response_model=TaskResponse)
@@ -1039,7 +918,7 @@ async def add_checkpoint(
detail="Add checkpoint failed unexpectedly",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
@router.post("/{task_id}/commit", response_model=TaskResponse)
@@ -1071,4 +950,4 @@ async def add_commit(
detail="Add commit failed unexpectedly",
)
await db.commit()
return _to_response(task)
return task_to_response(task)
+26
View File
@@ -0,0 +1,26 @@
"""
Agents API Schemas
Request/response models for agent endpoints.
"""
from uuid import UUID
from pydantic import BaseModel
from roboco.models import AgentRole, Team
class AgentResponse(BaseModel):
"""Response model for agent information."""
id: UUID
name: str
slug: str
role: AgentRole
team: Team | None
class Config:
"""Pydantic config."""
from_attributes = True
+66 -1
View File
@@ -4,11 +4,20 @@ Channels API Schemas
Request/response models for channel endpoints.
"""
from typing import TYPE_CHECKING
from uuid import UUID
from fastapi import HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import select
from roboco.models import ChannelType
from roboco.models import AgentRole, ChannelType, ChannelUpdate
if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession
from roboco.db.tables import ChannelTable
from roboco.services.permissions import AgentContext
class ChannelResponse(BaseModel):
@@ -60,3 +69,59 @@ class ListChannelsQuery(BaseModel):
page: int = Field(1, ge=1)
page_size: int = Field(20, ge=1, le=100)
include_archived: bool = False
# =============================================================================
# HELPERS AND UTILITIES
# =============================================================================
# Roles authorized to manage channels
CHANNEL_ADMIN_ROLES = frozenset(
{AgentRole.CEO, AgentRole.PRODUCT_OWNER, AgentRole.MAIN_PM}
)
# Fields that can be updated on a channel
CHANNEL_UPDATE_FIELDS = (
"name",
"description",
"topic",
"is_archived",
"allow_threads",
"allow_reactions",
"message_retention_days",
"max_message_length",
)
def require_channel_admin(agent: "AgentContext") -> None:
"""Raise 403 if agent is not authorized to manage channels."""
if agent.role not in CHANNEL_ADMIN_ROLES:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to manage channels",
)
async def get_channel_or_404(
db: "AsyncSession",
channel_table: type["ChannelTable"],
channel_id: UUID,
) -> "ChannelTable":
"""Get channel by ID or raise 404."""
query = select(channel_table).where(channel_table.id == channel_id)
result = await db.execute(query)
channel = result.scalar_one_or_none()
if not channel:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Channel not found",
)
return channel
def apply_channel_updates(channel: "ChannelTable", data: ChannelUpdate) -> None:
"""Apply updates to channel fields."""
for field in CHANNEL_UPDATE_FIELDS:
value = getattr(data, field, None)
if value is not None:
setattr(channel, field, value)
+55
View File
@@ -5,11 +5,17 @@ Request/response models for the notification system.
"""
from datetime import datetime
from typing import TYPE_CHECKING, Any
from uuid import UUID
from pydantic import BaseModel, Field
from sqlalchemy import select
from roboco.models import NotificationPriority, NotificationType
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
if TYPE_CHECKING:
from roboco.db.tables import NotificationTable
class ListNotificationsParams(BaseModel):
@@ -60,3 +66,52 @@ class NotificationCreateRequest(BaseModel):
requires_ack: bool = True
related_task_id: UUID | None = None
expires_at: datetime | None = None
# =============================================================================
# CONVERTERS AND QUERY BUILDERS
# =============================================================================
def build_notification_query(
notification_table: type["NotificationTable"],
agent_id: UUID,
params: ListNotificationsParams,
) -> Any:
"""Build the notification query with filters."""
query = select(notification_table).where(
notification_table.to_agents.contains([agent_id])
)
if params.unread_only:
query = query.where(~notification_table.read_by.contains([agent_id]))
if params.pending_ack_only:
query = query.where(
notification_table.requires_ack.is_(True),
~notification_table.acked_by.contains([agent_id]),
)
if params.type_filter:
query = query.where(notification_table.type == params.type_filter)
return query.order_by(notification_table.timestamp.desc()).limit(params.limit)
def notification_to_response(
n: "NotificationTable",
agent_id: UUID,
) -> NotificationResponse:
"""Convert a notification to response format."""
return NotificationResponse(
id=require_uuid(n.id),
type=n.type,
priority=n.priority,
from_agent=require_uuid(n.from_agent),
to_agents=to_python_uuid_list(n.to_agents),
subject=n.subject,
body=n.body,
requires_ack=n.requires_ack,
is_acknowledged=agent_id in n.acked_by,
is_fully_acknowledged=all(a in n.acked_by for a in n.to_agents),
is_read=agent_id in n.read_by,
related_task_id=to_python_uuid(n.related_task_id),
timestamp=n.timestamp,
expires_at=n.expires_at,
)
+266 -2
View File
@@ -5,12 +5,16 @@ Request/response models for task endpoints.
"""
from datetime import datetime
from typing import Any
from typing import TYPE_CHECKING, Any
from uuid import UUID
from pydantic import BaseModel, Field
from roboco.models.base import Complexity, TaskStatus, Team
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
if TYPE_CHECKING:
from roboco.db.tables import TaskTable
# =============================================================================
# NESTED RESPONSE MODELS
@@ -68,21 +72,104 @@ class TaskPlanResponse(BaseModel):
open_questions: list[dict[str, Any]] = []
# =============================================================================
# INPUT MODELS (for creating/updating nested data)
# =============================================================================
class SubTaskInput(BaseModel):
"""Input for creating/updating a sub-task."""
id: str # Client-generated ID
title: str
description: str | None = None
completed: bool = False
order: int
estimated_hours: float | None = None
notes: str | None = None
class TaskPlanInput(BaseModel):
"""Input for creating/updating a task plan."""
approach: str
sub_tasks: list[SubTaskInput] = []
technical_considerations: list[str] = []
risks: list[dict[str, Any]] = []
open_questions: list[dict[str, Any]] = []
class ProgressUpdateInput(BaseModel):
"""Input for adding a progress update."""
timestamp: datetime
agent_id: str # Can be agent slug or "CEO"
message: str
percentage: int | None = None
class CheckpointInput(BaseModel):
"""Input for adding a checkpoint."""
id: str # Client-generated ID
timestamp: datetime
agent_id: str # Can be agent slug or "CEO"
state_summary: str
remaining_work: list[str] = []
notes: str | None = None
class CommitRefInput(BaseModel):
"""Input for linking a commit."""
hash: str
message: str
timestamp: datetime
author_agent_id: str | None = None # Can be agent slug or "CEO"
# =============================================================================
# REQUEST MODELS
# =============================================================================
class TaskUpdate(BaseModel):
"""Request to update a task."""
"""Request to update a task.
CEO can update any field. All fields are optional for partial updates.
"""
# Basic info
title: str | None = None
description: str | None = None
acceptance_criteria: list[str] | None = None
priority: int | None = Field(default=None, ge=0, le=3)
target_date: datetime | None = None
estimated_complexity: Complexity | None = None
# Ownership & assignment
team: Team | None = None
assigned_to: str | None = None # UUID string or null to unassign
# Relationships
parent_task_id: str | None = None # UUID string or null
dependency_ids: list[str] | None = None # List of UUID strings
blocker_ids: list[str] | None = None # List of UUID strings
# Planning
plan: TaskPlanInput | None = None
# Execution tracking
progress_updates: list[ProgressUpdateInput] | None = None
checkpoints: list[CheckpointInput] | None = None
# Artifacts
commits: list[CommitRefInput] | None = None
# Notes
dev_notes: str | None = None
qa_notes: str | None = None
auditor_notes: str | None = None
quick_context: str | None = None
@@ -222,3 +309,180 @@ class TeamTasksQuery(BaseModel):
task_status: TaskStatus | None = None
limit: int = Field(100, ge=1, le=500)
# =============================================================================
# CONVERTERS (from database/dict to response models)
# =============================================================================
def convert_plan(plan_data: dict | None) -> TaskPlanResponse | None:
"""Convert plan JSON dict to TaskPlanResponse."""
if not plan_data:
return None
sub_tasks = []
for st in plan_data.get("sub_tasks", []):
sub_tasks.append(
SubTaskResponse(
id=st.get("id"),
title=st.get("title", ""),
description=st.get("description"),
completed=st.get("completed", False),
order=st.get("order", 0),
estimated_hours=st.get("estimated_hours"),
notes=st.get("notes"),
)
)
return TaskPlanResponse(
approach=plan_data.get("approach", ""),
sub_tasks=sub_tasks,
technical_considerations=plan_data.get("technical_considerations", []),
risks=plan_data.get("risks", []),
open_questions=plan_data.get("open_questions", []),
)
def convert_checkpoints(checkpoints_data: list | None) -> list[CheckpointResponse]:
"""Convert checkpoints JSON list to CheckpointResponse list."""
if not checkpoints_data:
return []
result = []
for cp in checkpoints_data:
result.append(
CheckpointResponse(
id=cp.get("id"),
timestamp=cp.get("timestamp"),
agent_id=cp.get("agent_id"),
state_summary=cp.get("state_summary", ""),
remaining_work=cp.get("remaining_work", []),
notes=cp.get("notes"),
)
)
return result
def convert_progress_updates(
updates_data: list | None,
) -> list[ProgressUpdateResponse]:
"""Convert progress_updates JSON list to ProgressUpdateResponse list."""
if not updates_data:
return []
result = []
for pu in updates_data:
result.append(
ProgressUpdateResponse(
timestamp=pu.get("timestamp"),
agent_id=pu.get("agent_id"),
message=pu.get("message", ""),
percentage=pu.get("percentage"),
)
)
return result
def convert_commits(commits_data: list | None) -> list[CommitRefResponse]:
"""Convert commits JSON list to CommitRefResponse list."""
if not commits_data:
return []
result = []
for cm in commits_data:
result.append(
CommitRefResponse(
hash=cm.get("hash", ""),
message=cm.get("message", ""),
timestamp=cm.get("timestamp"),
author_agent_id=cm.get("author_agent_id"),
)
)
return result
def task_to_response(task: "TaskTable") -> TaskResponse:
"""Convert TaskTable to TaskResponse with proper UUID conversion."""
return TaskResponse(
id=require_uuid(task.id),
title=task.title,
description=task.description,
acceptance_criteria=task.acceptance_criteria or [],
status=task.status,
priority=task.priority,
team=task.team,
created_by=require_uuid(task.created_by),
assigned_to=to_python_uuid(task.assigned_to),
parent_task_id=to_python_uuid(task.parent_task_id),
dependency_ids=to_python_uuid_list(task.dependency_ids),
blocker_ids=to_python_uuid_list(task.blocker_ids),
created_at=task.created_at,
updated_at=task.updated_at,
claimed_at=task.claimed_at,
started_at=task.started_at,
completed_at=task.completed_at,
target_date=task.target_date,
estimated_complexity=task.estimated_complexity,
# Planning
plan=convert_plan(task.plan),
# Execution
checkpoints=convert_checkpoints(task.checkpoints),
progress_updates=convert_progress_updates(task.progress_updates),
# Artifacts
commits=convert_commits(task.commits),
# Documentation
dev_notes=task.dev_notes,
qa_notes=task.qa_notes,
auditor_notes=task.auditor_notes,
quick_context=task.quick_context,
# Review Status
self_verified=task.self_verified,
qa_verified=task.qa_verified,
)
def task_list_to_response(tasks: list["TaskTable"]) -> list[TaskResponse]:
"""Convert list of TaskTable to list of TaskResponse."""
return [task_to_response(t) for t in tasks]
def parse_uuid_or_none(value: str | None) -> UUID | None:
"""Parse a string to UUID, returning None if empty or None."""
if not value:
return None
try:
return UUID(value)
except ValueError:
return None
def _parse_uuid_list(id_strings: list[str] | None) -> list[UUID]:
"""Parse a list of UUID strings to UUID objects, filtering empty values."""
if not id_strings:
return []
return [UUID(id_str) for id_str in id_strings if id_str]
# Fields that need UUID parsing (single value)
_SINGLE_UUID_FIELDS = ("assigned_to", "parent_task_id")
# Fields that need UUID list parsing
_UUID_LIST_FIELDS = ("dependency_ids", "blocker_ids")
def transform_update_data(data: TaskUpdate) -> dict:
"""Transform TaskUpdate input to format suitable for database storage."""
updates = data.model_dump(exclude_unset=True)
# Convert single UUID fields
for field in _SINGLE_UUID_FIELDS:
if field in updates:
updates[field] = parse_uuid_or_none(updates[field])
# Convert UUID list fields
for field in _UUID_LIST_FIELDS:
if field in updates:
updates[field] = _parse_uuid_list(updates[field])
return updates
+1 -1
View File
@@ -12,7 +12,7 @@ import structlog
import uvicorn
from roboco.agents import set_reasoning_stream_callback
from roboco.api.routes.orchestrator import set_orchestrator
from roboco.api.deps import set_orchestrator
from roboco.api.websocket import broadcast_agent_chunk
from roboco.config import settings
from roboco.db import bootstrap_database
+1 -1
View File
@@ -67,7 +67,7 @@ class Settings(BaseSettings):
"""
if self.api_url:
return f"{self.api_url.rstrip('/')}/api/v1"
connect_host = "127.0.0.1" if self.host == "0.0.0.0" else self.host
connect_host = "127.0.0.1" if self.host == "0.0.0.0" else self.host # nosec B104
return f"http://{connect_host}:{self.port}/api/v1"
# ==========================================================================
+93 -62
View File
@@ -222,7 +222,78 @@ async def _handle_channel_list(agent_id: str) -> dict[str, Any]:
}
async def _handle_channel_history( # noqa: PLR0911
async def _get_channel_by_slug(
client: httpx.AsyncClient,
channel_slug: str,
headers: dict[str, str],
) -> str | dict[str, Any]:
"""Get channel ID by slug. Returns channel_id or error dict."""
resp = await client.get(
f"{settings.internal_api_url}/channels",
params={"slug": channel_slug},
headers=headers,
)
if resp.status_code != status.HTTP_200_OK:
return _format_error_response("API_ERROR", "Failed to fetch channels")
data = resp.json()
items = data.get("items", data)
if not items:
return _format_error_response("NOT_FOUND", f"Channel #{channel_slug} not found")
channel = items[0] if isinstance(items, list) else items
return str(channel["id"])
async def _get_sessions_for_group(
client: httpx.AsyncClient,
group_id: str,
headers: dict[str, str],
) -> list | dict[str, Any]:
"""Get sessions for a group. Returns session list or error dict."""
resp = await client.get(
f"{settings.internal_api_url}/sessions",
params={"group_id": group_id, "limit": 5},
headers=headers,
)
if resp.status_code != status.HTTP_200_OK:
return _format_error_response("API_ERROR", "Failed to fetch sessions")
items: list = resp.json().get("items", [])
return items
async def _fetch_messages_from_sessions(
client: httpx.AsyncClient,
sessions: list,
since: datetime,
limit: int,
headers: dict[str, str],
) -> list:
"""Fetch messages from multiple sessions."""
all_messages: list = []
for session in sessions:
resp = await client.get(
f"{settings.internal_api_url}/messages",
params={
"session_id": session["id"],
"after": since.isoformat(),
"limit": limit,
},
headers=headers,
)
if resp.status_code == status.HTTP_200_OK:
all_messages.extend(resp.json().get("items", []))
if len(all_messages) >= limit:
break
all_messages.sort(key=lambda m: m.get("timestamp", ""), reverse=True)
return all_messages[:limit]
async def _handle_channel_history(
agent_id: str,
channel_slug: str,
limit: int,
@@ -236,48 +307,28 @@ async def _handle_channel_history( # noqa: PLR0911
limit = min(limit, 100)
since = datetime.now(UTC) - timedelta(hours=hours_back)
headers = _get_agent_headers(agent_id)
async with httpx.AsyncClient() as client:
# Get channel by slug
channels_resp = await client.get(
f"{settings.internal_api_url}/channels",
params={"slug": channel_slug},
headers=headers,
)
# Get channel
channel_result = await _get_channel_by_slug(client, channel_slug, headers)
if isinstance(channel_result, dict):
return channel_result
channel_id = channel_result
if channels_resp.status_code != status.HTTP_200_OK:
return _format_error_response("API_ERROR", "Failed to fetch channels")
channels_data = channels_resp.json()
items = channels_data.get("items", channels_data)
if not items:
return _format_error_response(
"NOT_FOUND", f"Channel #{channel_slug} not found"
)
channel = items[0] if isinstance(items, list) else items
channel_id = channel["id"]
# Get groups for this channel
# Get group
group_result = await _get_default_group(client, channel_id, headers)
if isinstance(group_result, dict):
return group_result # Error response
return group_result
group_id = group_result
# Get sessions for this group
sessions_resp = await client.get(
f"{settings.internal_api_url}/sessions",
params={"group_id": group_id, "limit": 5},
headers=headers,
)
if sessions_resp.status_code != status.HTTP_200_OK:
return _format_error_response("API_ERROR", "Failed to fetch sessions")
sessions_data = sessions_resp.json()
sessions = sessions_data.get("items", [])
# Get sessions
sessions_result = await _get_sessions_for_group(client, group_id, headers)
if isinstance(sessions_result, dict):
return sessions_result
sessions = sessions_result
# Early return for no sessions
if not sessions:
return {
"channel": channel_slug,
@@ -287,36 +338,16 @@ async def _handle_channel_history( # noqa: PLR0911
"since": since.isoformat(),
}
# Get messages from all recent sessions
all_messages = []
for session in sessions:
session_id = session["id"]
messages_resp = await client.get(
f"{settings.internal_api_url}/messages",
params={
"session_id": session_id,
"after": since.isoformat(),
"limit": limit,
},
headers=headers,
)
if messages_resp.status_code == status.HTTP_200_OK:
msg_data = messages_resp.json()
all_messages.extend(msg_data.get("items", []))
if len(all_messages) >= limit:
break
# Sort by timestamp descending and limit
all_messages.sort(key=lambda m: m.get("timestamp", ""), reverse=True)
all_messages = all_messages[:limit]
# Fetch messages
messages = await _fetch_messages_from_sessions(
client, sessions, since, limit, headers
)
return {
"channel": channel_slug,
"messages": all_messages,
"total": len(all_messages),
"has_more": len(all_messages) >= limit,
"messages": messages,
"total": len(messages),
"has_more": len(messages) >= limit,
"since": since.isoformat(),
}
+5 -1
View File
@@ -190,7 +190,11 @@ CHANNEL_MEMBERSHIPS = {
"doc-all": ["be-doc", "fe-doc", "ux-doc", CEO_AGENT_ID],
# Management channels + CEO
"main-pm-board": [
"main-pm", "product-owner", "head-marketing", "auditor", CEO_AGENT_ID
"main-pm",
"product-owner",
"head-marketing",
"auditor",
CEO_AGENT_ID,
],
"board-private": ["product-owner", "head-marketing", "auditor", CEO_AGENT_ID],
# Broadcast channels - everyone (CEO included via DEFAULT_AGENTS)
+32
View File
@@ -0,0 +1,32 @@
"""
Health Check Service
Infrastructure health check functions.
"""
import redis.asyncio as redis
from sqlalchemy import text
from roboco.config import settings
from roboco.db.base import get_db_context
async def check_database() -> tuple[str, bool]:
"""Check database connectivity."""
try:
async with get_db_context() as session:
await session.execute(text("SELECT 1"))
return "ok", True
except Exception as e:
return str(e), False
async def check_redis() -> tuple[str, bool]:
"""Check Redis connectivity."""
try:
client = redis.from_url(settings.redis_url)
await client.ping()
await client.close()
return "ok", True
except Exception as e:
return str(e), False
+46
View File
@@ -4,12 +4,24 @@ Notification Service
Sends notifications through the API with proper enforcement.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import structlog
from fastapi import HTTPException, status
from sqlalchemy import select
from roboco.db.base import get_db_context
from roboco.db.tables import NotificationTable
from roboco.models import NotificationPriority, NotificationType
from roboco.models.notification import CreateNotificationParams
if TYPE_CHECKING:
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
logger = structlog.get_logger()
@@ -189,3 +201,37 @@ class NotificationService:
notification_id=str(notification.id),
type=params.notification_type.value,
)
# =============================================================================
# Route Helpers
# =============================================================================
async def get_notification_or_404(
db: AsyncSession,
notification_id: UUID,
) -> NotificationTable:
"""Fetch notification by ID or raise 404."""
result = await db.execute(
select(NotificationTable).where(NotificationTable.id == notification_id)
)
notification = result.scalar_one_or_none()
if not notification:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found",
)
return notification
def require_notification_recipient(
notification: NotificationTable,
agent_id: UUID,
) -> None:
"""Raise 403 if agent is not a recipient."""
if agent_id not in notification.to_agents:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not a recipient of this notification",
)
Generated
+105 -102
View File
@@ -838,19 +838,19 @@ wheels = [
[[package]]
name = "faker"
version = "38.2.0"
version = "39.0.0"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "tzdata" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/64/27/022d4dbd4c20567b4c294f79a133cc2f05240ea61e0d515ead18c995c249/faker-38.2.0.tar.gz", hash = "sha256:20672803db9c7cb97f9b56c18c54b915b6f1d8991f63d1d673642dc43f5ce7ab", size = 1941469, upload-time = "2025-11-19T16:37:31.892Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/30/b9/0897fb5888ddda099dc0f314a8a9afb5faa7e52eaf6865c00686dfb394db/faker-39.0.0.tar.gz", hash = "sha256:ddae46d3b27e01cea7894651d687b33bcbe19a45ef044042c721ceac6d3da0ff", size = 1941757, upload-time = "2025-12-17T19:19:04.762Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/17/93/00c94d45f55c336434a15f98d906387e87ce28f9918e4444829a8fda432d/faker-38.2.0-py3-none-any.whl", hash = "sha256:35fe4a0a79dee0dc4103a6083ee9224941e7d3594811a50e3969e547b0d2ee65", size = 1980505, upload-time = "2025-11-19T16:37:30.208Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/eb/5a/26cdb1b10a55ac6eb11a738cea14865fa753606c4897d7be0f5dc230df00/faker-39.0.0-py3-none-any.whl", hash = "sha256:c72f1fca8f1a24b8da10fcaa45739135a19772218ddd61b86b7ea1b8c790dce7", size = 1980775, upload-time = "2025-12-17T19:19:02.926Z" },
]
[[package]]
name = "fastapi"
version = "0.124.4"
version = "0.125.0"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "annotated-doc" },
@@ -858,18 +858,18 @@ dependencies = [
{ name = "starlette" },
{ name = "typing-extensions" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cd/21/ade3ff6745a82ea8ad88552b4139d27941549e4f19125879f848ac8f3c3d/fastapi-0.124.4.tar.gz", hash = "sha256:0e9422e8d6b797515f33f500309f6e1c98ee4e85563ba0f2debb282df6343763", size = 378460, upload-time = "2025-12-12T15:00:43.891Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/17/71/2df15009fb4bdd522a069d2fbca6007c6c5487fce5cb965be00fc335f1d1/fastapi-0.125.0.tar.gz", hash = "sha256:16b532691a33e2c5dee1dac32feb31dc6eb41a3dd4ff29a95f9487cb21c054c0", size = 370550, upload-time = "2025-12-17T21:41:44.15Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3e/57/aa70121b5008f44031be645a61a7c4abc24e0e888ad3fc8fda916f4d188e/fastapi-0.124.4-py3-none-any.whl", hash = "sha256:6d1e703698443ccb89e50abe4893f3c84d9d6689c0cf1ca4fad6d3c15cf69f15", size = 113281, upload-time = "2025-12-12T15:00:42.44Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/34/2f/ff2fcc98f500713368d8b650e1bbc4a0b3ebcdd3e050dcdaad5f5a13fd7e/fastapi-0.125.0-py3-none-any.whl", hash = "sha256:2570ec4f3aecf5cca8f0428aed2398b774fcdfee6c2116f86e80513f2f86a7a1", size = 112888, upload-time = "2025-12-17T21:41:41.286Z" },
]
[[package]]
name = "filelock"
version = "3.20.0"
version = "3.20.1"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" },
]
[[package]]
@@ -1295,11 +1295,11 @@ wheels = [
[[package]]
name = "joblib"
version = "1.5.2"
version = "1.5.3"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
]
[[package]]
@@ -1331,19 +1331,19 @@ wheels = [
[[package]]
name = "lance-namespace"
version = "0.3.1"
version = "0.3.2"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "lance-namespace-urllib3-client" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c9/36/1c926adfe4bf5cd43fb488f7b9f61bb0acb6f057f4e22c74809818106f46/lance_namespace-0.3.1.tar.gz", hash = "sha256:ad8408570bd3d8403cfe6558aae1ab99371c892c2c0d8471c2ab8a50a679a3d8", size = 6826, upload-time = "2025-12-11T06:45:41.042Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/4d/44/946ca6033997820623906d84cb9830af89768940bbc9f824aadec6136254/lance_namespace-0.3.2.tar.gz", hash = "sha256:51eb30f8a9f073bba15d1824460bf6e9fa7f867e224e73ee64520ed254f0c140", size = 6833, upload-time = "2025-12-15T18:28:23.012Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ad/6e/f603cf28c41f79cb3135444ac78e6318759c0365b4257354daa16966a9ee/lance_namespace-0.3.1-py3-none-any.whl", hash = "sha256:2e303f780286a3a80416c140a9c18c8cbdef1f4e0f9a5a2f1ec5292625a65107", size = 8328, upload-time = "2025-12-11T06:45:39.931Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/98/d2/947eedf16c59e1269c9cf7a2dc3c4522a3915cec664a9ffe8a7d1a0e2fcd/lance_namespace-0.3.2-py3-none-any.whl", hash = "sha256:794249bec15fb6e34d2b8d9f9698f11ae191179eccd9cd879743d8fb3c666ca0", size = 8335, upload-time = "2025-12-15T18:28:24.701Z" },
]
[[package]]
name = "lance-namespace-urllib3-client"
version = "0.3.1"
version = "0.3.2"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "pydantic" },
@@ -1351,14 +1351,14 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "urllib3" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bd/16/9830da3893f4d5e71072c33fbbee91a950362f9f8f8d1992e64a57d0c424/lance_namespace_urllib3_client-0.3.1.tar.gz", hash = "sha256:4b68684cb9b96b9da5bec895f9d1199784ef925052bea85ae1667d073e104c4d", size = 151787, upload-time = "2025-12-11T06:45:41.823Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e7/17/56d98ad4a969e59d08d6e7157f9a680383f1fe5fd2916b75a42826ad0b52/lance_namespace_urllib3_client-0.3.2.tar.gz", hash = "sha256:1474e8a16a3547faeb5be56270b8903bd2c9ce10ae04d09245f3870ede3a5c4d", size = 151790, upload-time = "2025-12-15T18:28:23.867Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/81/de/cc3f5c5a513913f0dfdfe54bdd5872922ad8949263233f9f6e9d5873abee/lance_namespace_urllib3_client-0.3.1-py3-none-any.whl", hash = "sha256:7f9d2be67a65c68faed3b4771a4665590ac1441451d7f609acb29bf300ba8303", size = 256820, upload-time = "2025-12-11T06:45:43.259Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1e/8c/40ac725fb6fb7a4a13295fa2bc3b6ff877be1538d0a95ecf939ef0ceb562/lance_namespace_urllib3_client-0.3.2-py3-none-any.whl", hash = "sha256:bc73668b1086ef96c279870b019902bb293d15a6271ea8cf8eb429a57ab6a6ab", size = 256823, upload-time = "2025-12-15T18:28:25.603Z" },
]
[[package]]
name = "lancedb"
version = "0.25.3"
version = "0.26.0"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "deprecation" },
@@ -1370,54 +1370,53 @@ dependencies = [
{ name = "tqdm" },
]
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d7/62/a149b47dc4ccf3c569eba722b805cbba1b90566976ff1d459f20f7f00ebc/lancedb-0.25.3-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:1cfa4dd97b33ca8f73288aa4b1baaddc9545ce0d3c8e5d06fba8feb77f42363f", size = 38425074, upload-time = "2025-11-07T05:58:15.763Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b2/94/ae3e74bb27dcca321ccf1e7a32ccab09b1062ddf54f96376221ca8610e7c/lancedb-0.25.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8a7bfe0cb2146f6e78e9f376673ed2f906b93dab84df97dad2ba9fa52f97e152", size = 34506539, upload-time = "2025-11-07T05:14:04.901Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/6a/07/b580d0e002eaaa3d5216699fb9f19186c37861c3fa11ac3be991fa7d6d03/lancedb-0.25.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a395d07d31da1e13e2631fd9911b15e6d4fb903d34358cea0bd450006364e3", size = 36149261, upload-time = "2025-11-07T05:23:13.002Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c1/95/32ddb779a01cd0d349f391e7d5f4218d045f9848c1d757f5a8ace4c63b09/lancedb-0.25.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500beac161f73e3e6826a711efb1d24397d892d07dfdce2c9fb1da73f8de506c", size = 39145675, upload-time = "2025-11-07T05:24:40.813Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f4/33/fdaff64a111f86dbb99f3ff09136df93b441e350f4953884a9fc21c49283/lancedb-0.25.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2d0fce4187582e48b69430d204665e164002f1b49b03e67747ca8ec2c3083481", size = 36165492, upload-time = "2025-11-07T05:27:13.394Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ab/15/f0d69acc5e06892d19e09c127cd928cf20f5d2966a069e93693fc389b132/lancedb-0.25.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3035665fb8e4aaff8dff2602747cc77aeba6bc39f1a95345abc3275c97a044cb", size = 39191458, upload-time = "2025-11-07T05:24:38.047Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bc/dc/3c5785cee0f0abaa5046ff817f3d64275909067500d6a317da0aeb9141b8/lancedb-0.25.3-cp39-abi3-win_amd64.whl", hash = "sha256:8c153d976bec79358d328e4c8a287a7b9c918b35b3912fff6864ced6b2a15943", size = 42080029, upload-time = "2025-11-07T05:46:32.639Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a8/91/fe585b2181bd61efc65e1da410ae8ab7b29a26f156e4ca7d7d616b1234de/lancedb-0.26.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3a0d435fff1392f056c173f695f71d495c691c555daa9802c056ea23f6a3900e", size = 41174270, upload-time = "2025-12-16T17:16:30.699Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ce/fc/e47e092f4fc97a8810b37dbee07996689bca42f0817f3f3c38d7fb51dd9d/lancedb-0.26.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2206320fd0f33c01e264960afd768987646133cf152c4d3a8b7faf81b3017bf", size = 42936720, upload-time = "2025-12-16T17:24:43.527Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b5/d7/323897d22a7c00ef1dc4f5b76df1a11df549fe887d8e05d689c2224e47b8/lancedb-0.26.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca0322cb4b62d526748f6f29e5b43cce4251c7f693e111897eb1f77e7f1ec2b", size = 45846184, upload-time = "2025-12-16T17:27:33.802Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3a/0b/7671c94b27a5aa267b9f1d6db759c9e08070cb8f783828ade04da9dc7d79/lancedb-0.26.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7f2b8d69a647265b8753576b501354333c3edfd47d12ec9f47e665e8574c92fe", size = 42954293, upload-time = "2025-12-16T17:24:30.335Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/52/2e/9f720d6ae7bd3a94d096f320a0ec2f277735423af9d16cf5c61c4a70e6ca/lancedb-0.26.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:8e5cc334686a389cf2f28d1c239d13a205098ed98f3914226d3966858e58b957", size = 45896935, upload-time = "2025-12-16T17:27:30.156Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/00/0e/4b292c24a9e25ee2cd081d2da930fcdc672ee0eea531fc453c19c73addb5/lancedb-0.26.0-cp39-abi3-win_amd64.whl", hash = "sha256:2fc9b48a11f526de87388002eb3838329db7279241eefb3166c1c6c3b194a3cf", size = 50615000, upload-time = "2025-12-16T17:53:34.409Z" },
]
[[package]]
name = "librt"
version = "0.7.3"
version = "0.7.4"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" },
]
[[package]]
@@ -1692,7 +1691,7 @@ wheels = [
[[package]]
name = "mcp"
version = "1.16.0"
version = "1.23.3"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "anyio" },
@@ -1701,15 +1700,18 @@ dependencies = [
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "python-multipart" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3d/a1/b1f328da3b153683d2ec34f849b4b6eac2790fb240e3aef06ff2fab3df9d/mcp-1.16.0.tar.gz", hash = "sha256:39b8ca25460c578ee2cdad33feeea122694cfdf73eef58bee76c42f6ef0589df", size = 472918, upload-time = "2025-10-02T16:58:20.631Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c9/0e/7cebc88e17daf94ebe28c95633af595ccb2864dc2ee7abd75542d98495cc/mcp-1.16.0-py3-none-any.whl", hash = "sha256:ec917be9a5d31b09ba331e1768aa576e0af45470d657a0319996a20a57d7d633", size = 167266, upload-time = "2025-10-02T16:58:19.039Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" },
]
[[package]]
@@ -1921,29 +1923,29 @@ wheels = [
[[package]]
name = "mypy"
version = "1.19.0"
version = "1.19.1"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "librt" },
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "typing-extensions" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" },
]
[[package]]
@@ -2197,7 +2199,7 @@ wheels = [
[[package]]
name = "openai"
version = "2.11.0"
version = "2.13.0"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "anyio" },
@@ -2209,9 +2211,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f4/8c/aa6aea6072f985ace9d6515046b9088ff00c157f9654da0c7b1e129d9506/openai-2.11.0.tar.gz", hash = "sha256:b3da01d92eda31524930b6ec9d7167c535e843918d7ba8a76b1c38f1104f321e", size = 624540, upload-time = "2025-12-11T19:11:58.539Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/0f/39/8e347e9fda125324d253084bb1b82407e5e3c7777a03dc398f79b2d95626/openai-2.13.0.tar.gz", hash = "sha256:9ff633b07a19469ec476b1e2b5b26c5ef700886524a7a72f65e6f0b5203142d5", size = 626583, upload-time = "2025-12-16T18:19:44.387Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e5/f1/d9251b565fce9f8daeb45611e3e0d2f7f248429e40908dcee3b6fe1b5944/openai-2.11.0-py3-none-any.whl", hash = "sha256:21189da44d2e3d027b08c7a920ba4454b8b7d6d30ae7e64d9de11dbe946d4faa", size = 1064131, upload-time = "2025-12-11T19:11:56.816Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bb/d5/eb52edff49d3d5ea116e225538c118699ddeb7c29fa17ec28af14bc10033/openai-2.13.0-py3-none-any.whl", hash = "sha256:746521065fed68df2f9c2d85613bb50844343ea81f60009b60e6a600c9352c79", size = 1066837, upload-time = "2025-12-16T18:19:43.124Z" },
]
[[package]]
@@ -2904,15 +2906,15 @@ crypto = [
[[package]]
name = "pymdown-extensions"
version = "10.19"
version = "10.19.1"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "markdown" },
{ name = "pyyaml" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1f/4e/e73e88f4f2d0b26cbd2e100074107470984f0a6055869805fc181b847ac7/pymdown_extensions-10.19.tar.gz", hash = "sha256:01bb917ea231f9ce14456fa9092cdb95ac3e5bd32202a3ee61dbd5ad2dd9ef9b", size = 847701, upload-time = "2025-12-11T18:20:46.093Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/72/2d/9f30cee56d4d6d222430d401e85b0a6a1ae229819362f5786943d1a8c03b/pymdown_extensions-10.19.1.tar.gz", hash = "sha256:4969c691009a389fb1f9712dd8e7bd70dcc418d15a0faf70acb5117d022f7de8", size = 847839, upload-time = "2025-12-14T17:25:24.42Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d4/56/fa9edaceb3805e03ac9faf68ca1ddc660a75b49aee5accb493511005fef5/pymdown_extensions-10.19-py3-none-any.whl", hash = "sha256:dc5f249fc3a1b6d8a6de4634ba8336b88d0942cee75e92b18ac79eaf3503bf7c", size = 266670, upload-time = "2025-12-11T18:20:44.736Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fb/35/b763e8fbcd51968329b9adc52d188fc97859f85f2ee15fe9f379987d99c5/pymdown_extensions-10.19.1-py3-none-any.whl", hash = "sha256:e8698a66055b1dc0dca2a7f2c9d0ea6f5faa7834a9c432e3535ab96c0c4e509b", size = 266693, upload-time = "2025-12-14T17:25:22.999Z" },
]
[[package]]
@@ -3038,11 +3040,11 @@ cryptography = [
[[package]]
name = "python-multipart"
version = "0.0.20"
version = "0.0.21"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" },
]
[[package]]
@@ -3697,7 +3699,7 @@ wheels = [
[[package]]
name = "semgrep"
version = "1.145.2"
version = "1.146.0"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "attrs" },
@@ -3725,13 +3727,13 @@ dependencies = [
{ name = "urllib3" },
{ name = "wcmatch" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/2a/08/379a699032f63695b0d339f14ba1deac4f7354242b1611d9fdc132ca1ef5/semgrep-1.145.2.tar.gz", hash = "sha256:52e837152fc0d0536a9870e949f73a750aaddec39cd16d5de03382ff820cb23c", size = 42375275, upload-time = "2025-12-13T01:53:47.901Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e6/7d/5f712a254c0d34acc465876dcea0904cb729d881238fd0f7c9dde32ef916/semgrep-1.146.0.tar.gz", hash = "sha256:a6f665a9ff9c5184bbc89677f7e6953b0f298a3a9bc7d1ab0b75f368c64c9f82", size = 42404719, upload-time = "2025-12-17T20:44:43.159Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8a/1d/86fea11eb85dd5eb2e6ffb5ab48d5096ce5256876992725d6d41c11c72ae/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:64bce21fac450e9db64d7ba51e4354160a84efed4c85ca693ba1e5f1cdf98f0a", size = 35051470, upload-time = "2025-12-13T01:53:32.44Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d9/ff/56d2caa49e41e636f886e2fca0d307f60357bbca2772af6c958eff42eaf0/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:4f212bc7b58dea2f6d99e108b76a3eb6919696dafbb3de66f79035e5020f810f", size = 39953168, upload-time = "2025-12-13T01:53:35.722Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/31/de/fe69e8d5cc797ff7a8f40d657352e8d4fa8875b33c9931b3c896a03322f8/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f9ca6c5024cd9c92a717d2dfe46123e22c98ff545543c8578235f874f1bb3", size = 54321315, upload-time = "2025-12-13T01:53:38.805Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b6/e1/86d206f5f97945ccc9b5b2fd9f84ae743a2d035cd485ea809d3c0cd89d56/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5633950d57bb596fef545f694c6131371301c5807740f3b0e46801091c9120a0", size = 50438864, upload-time = "2025-12-13T01:53:41.909Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f5/1f/e7997d959c115bef967a27ddb8355afc817433a41eb13561f8ccdfbce1b4/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:57b6433a8620bacaf05c48c8a5ce8f6ae11f0e96e852492c931d877b0534c8a5", size = 42997312, upload-time = "2025-12-13T01:53:44.621Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/88/56/ce2f158819fc7b1fed249930469472ec7a0583a078c12aa956cb36405b8d/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:d79328f245bc106ef95b80d2569c804551850a0fd29256a99d734175429c15ae", size = 35070245, upload-time = "2025-12-17T20:44:23.913Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bf/76/77e79b401d4371a9c9554877d74b7a999ffceeb3679b0b1050650a592ce0/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:a2d0efba16bafef120031c6bafad40a8288b24f70248e1119881bc4eb26390a2", size = 39967238, upload-time = "2025-12-17T20:44:27.874Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/53/df/67f6f310a88c64e798654766710f0901a7d5f42deadffc5646463c186c4d/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9335bf8579834aada5663338bf07eb83d9c51ab55e7470d39bdaeb095a0bdab", size = 54436507, upload-time = "2025-12-17T20:44:31.967Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/db/c873b2c9f7d2ca49d8a9e536991066a513d2cabde15c587034e03f7ab2ed/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8eaae8191c3dd5b85c7b40cfaeca6db623fe387c85f8938176d112855cca829", size = 50525908, upload-time = "2025-12-17T20:44:35.947Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/28/e9/4d6f31e21803c571a54cfeeeaaa6c3a7909645a2421e4d247a2ca44543fd/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:57e2590ada20fba58376fe541d6babaae9fb95763f0fe132df0729563af4ce4c", size = 43021046, upload-time = "2025-12-17T20:44:39.742Z" },
]
[[package]]
@@ -3856,14 +3858,15 @@ asyncio = [
[[package]]
name = "sse-starlette"
version = "3.0.3"
version = "3.0.4"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [
{ name = "anyio" },
{ name = "starlette" },
]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/db/3c/fa6517610dc641262b77cc7bf994ecd17465812c1b0585fe33e11be758ab/sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971", size = 21943, upload-time = "2025-10-30T18:44:20.117Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/17/8b/54651ad49bce99a50fd61a7f19c2b6a79fbb072e693101fbb1194c362054/sse_starlette-3.0.4.tar.gz", hash = "sha256:5e34286862e96ead0eb70f5ddd0bd21ab1f6473a8f44419dd267f431611383dd", size = 22576, upload-time = "2025-12-14T16:22:52.493Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431", size = 11765, upload-time = "2025-10-30T18:44:18.834Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/71/22/8ab1066358601163e1ac732837adba3672f703818f693e179b24e0d3b65c/sse_starlette-3.0.4-py3-none-any.whl", hash = "sha256:32c80ef0d04506ced4b0b6ab8fe300925edc37d26f666afb1874c754895f5dc3", size = 11764, upload-time = "2025-12-14T16:22:51.453Z" },
]
[[package]]
@@ -4279,11 +4282,11 @@ wheels = [
[[package]]
name = "tzdata"
version = "2025.2"
version = "2025.3"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
]
[[package]]