mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Many fixes to Mentor, Query RAG, etc
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## IMPORTANT NOTES
|
||||
|
||||
**IGNORING != FIXING**
|
||||
**`# noqa` & `# type: ignore` != FIXING**
|
||||
|
||||
## Project Overview
|
||||
|
||||
**RoboCo** is an AI Agentic Company - a virtual organization of 18 AI agents + 1 human CEO, designed to operate as a complete software development workforce. The system implements a structured organizational hierarchy with formal communication protocols, task management, and quality controls.
|
||||
|
||||
@@ -30,7 +30,31 @@ Claim → read full description → plan breakdown → start → journal decisio
|
||||
Create session for YOUR task with `roboco_session_create_for_tasks()`. Subtasks inherit it automatically.
|
||||
|
||||
### 4. SUBTASKS
|
||||
Create with `roboco_task_create()`. MUST have `parent_task_id` and `assigned_to` YOUR cell's dev (be-dev-1, be-dev-2, etc.).
|
||||
|
||||
**CRITICAL: Always set `parent_task_id` to YOUR task ID.** Without this, you create orphan tasks, not subtasks.
|
||||
|
||||
```python
|
||||
# Get YOUR task ID first
|
||||
my_task = roboco_task_get(task_id)
|
||||
|
||||
# Create SUBTASK with parent_task_id
|
||||
roboco_task_create(
|
||||
title="Implement user auth endpoint",
|
||||
parent_task_id=my_task["id"], # REQUIRED - links to your task
|
||||
assigned_to="be-dev-1", # USE SLUG
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
**Your cell's agent slugs:**
|
||||
- Backend: `be-dev-1`, `be-dev-2`, `be-qa`, `be-doc`
|
||||
- Frontend: `fe-dev-1`, `fe-dev-2`, `fe-qa`, `fe-doc`
|
||||
- UX/UI: `ux-dev-1`, `ux-dev-2`, `ux-qa`, `ux-doc`
|
||||
|
||||
**Without `parent_task_id`:**
|
||||
- Task becomes a sibling (wrong!)
|
||||
- Completion tracking breaks
|
||||
- Your task can't complete
|
||||
|
||||
### 5. CREATE BRANCH (Git Tasks)
|
||||
**For tasks with `requires_git=True`:**
|
||||
|
||||
@@ -41,7 +41,31 @@ roboco_git_create_branch(project_slug, task_id, branch_type, "main")
|
||||
Use `roboco_group_create()` in each relevant cell channel. Cell PMs need groups to create sessions.
|
||||
|
||||
### 5. CREATE CELL TASKS
|
||||
Use `roboco_task_create()` with `parent_task_id`, `team`, and `assigned_to` Cell PM (be-pm, fe-pm, ux-pm).
|
||||
|
||||
**CRITICAL: Always set `parent_task_id` to YOUR task ID.** Without this, you create orphan tasks, not subtasks.
|
||||
|
||||
```python
|
||||
# Get YOUR task ID first
|
||||
my_task = roboco_task_get(task_id)
|
||||
|
||||
# Create SUBTASK with parent_task_id
|
||||
roboco_task_create(
|
||||
title="Backend: Implement feature X",
|
||||
parent_task_id=my_task["id"], # REQUIRED - links to your task
|
||||
team="backend",
|
||||
assigned_to="be-pm", # USE SLUG
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
**Agent slugs:**
|
||||
- `be-pm`, `fe-pm`, `ux-pm` - Cell PMs
|
||||
|
||||
**Without `parent_task_id`:**
|
||||
- Task becomes a sibling (wrong!)
|
||||
- Completion tracking breaks
|
||||
- Your task can't complete
|
||||
|
||||
- Set `project_id` and `branch_name` for git tasks
|
||||
- Cell PMs will create subtask branches from your parent branch
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Agent UUIDs Reference
|
||||
|
||||
**ALWAYS use SLUGS when assigning tasks.** The system resolves slugs to UUIDs automatically.
|
||||
|
||||
```python
|
||||
# CORRECT - Use slug
|
||||
roboco_task_create(assigned_to="be-dev-1", ...)
|
||||
|
||||
# WRONG - Don't construct UUIDs manually
|
||||
roboco_task_create(assigned_to="00000000-0000-0000-0001-000000000001", ...)
|
||||
```
|
||||
|
||||
## UUID Scheme (Reference Only)
|
||||
|
||||
```
|
||||
00000000-0000-0000-{CELL}-00000000000{N}
|
||||
```
|
||||
|
||||
| Cell Code | Team |
|
||||
|-----------|------|
|
||||
| `0000` | CEO |
|
||||
| `0001` | Backend |
|
||||
| `0002` | Frontend |
|
||||
| `0003` | UX/UI |
|
||||
| `0004` | Board/Management |
|
||||
|
||||
**NO OTHER CELL CODES EXIST.** Do not construct UUIDs with codes like `0005`, `0006`, etc.
|
||||
|
||||
## Backend Cell (0001)
|
||||
|
||||
| Slug | UUID |
|
||||
|------|------|
|
||||
| `be-dev-1` | `00000000-0000-0000-0001-000000000001` |
|
||||
| `be-dev-2` | `00000000-0000-0000-0001-000000000002` |
|
||||
| `be-qa` | `00000000-0000-0000-0001-000000000003` |
|
||||
| `be-pm` | `00000000-0000-0000-0001-000000000004` |
|
||||
| `be-doc` | `00000000-0000-0000-0001-000000000005` |
|
||||
|
||||
## Frontend Cell (0002)
|
||||
|
||||
| Slug | UUID |
|
||||
|------|------|
|
||||
| `fe-dev-1` | `00000000-0000-0000-0002-000000000001` |
|
||||
| `fe-dev-2` | `00000000-0000-0000-0002-000000000002` |
|
||||
| `fe-qa` | `00000000-0000-0000-0002-000000000003` |
|
||||
| `fe-pm` | `00000000-0000-0000-0002-000000000004` |
|
||||
| `fe-doc` | `00000000-0000-0000-0002-000000000005` |
|
||||
|
||||
## UX/UI Cell (0003)
|
||||
|
||||
| Slug | UUID |
|
||||
|------|------|
|
||||
| `ux-dev-1` | `00000000-0000-0000-0003-000000000001` |
|
||||
| `ux-dev-2` | `00000000-0000-0000-0003-000000000002` |
|
||||
| `ux-qa` | `00000000-0000-0000-0003-000000000003` |
|
||||
| `ux-pm` | `00000000-0000-0000-0003-000000000004` |
|
||||
| `ux-doc` | `00000000-0000-0000-0003-000000000005` |
|
||||
|
||||
## Board/Management (0004)
|
||||
|
||||
| Slug | UUID |
|
||||
|------|------|
|
||||
| `main-pm` | `00000000-0000-0000-0004-000000000001` |
|
||||
| `product-owner` | `00000000-0000-0000-0004-000000000002` |
|
||||
| `head-marketing` | `00000000-0000-0000-0004-000000000003` |
|
||||
| `auditor` | `00000000-0000-0000-0004-000000000004` |
|
||||
|
||||
## CEO (0000)
|
||||
|
||||
| Slug | UUID |
|
||||
|------|------|
|
||||
| `ceo` | `00000000-0000-0000-0000-000000000001` |
|
||||
|
||||
## Usage
|
||||
|
||||
Most tools accept either slug or UUID:
|
||||
```python
|
||||
roboco_task_claim(task_id) # task_id is UUID
|
||||
roboco_journal_read_team("be-dev-1") # slug works
|
||||
roboco_journal_read_team("00000000-0000-0000-0001-000000000001") # UUID works
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
# Channel Reference
|
||||
|
||||
All available channels with their slugs and access rules.
|
||||
|
||||
## Cell Channels
|
||||
|
||||
| Slug | Name | Members |
|
||||
|------|------|---------|
|
||||
| `backend-cell` | Backend Cell | be-dev-1, be-dev-2, be-qa, be-pm, be-doc |
|
||||
| `frontend-cell` | Frontend Cell | fe-dev-1, fe-dev-2, fe-qa, fe-pm, fe-doc |
|
||||
| `uxui-cell` | UX/UI Cell | ux-dev-1, ux-dev-2, ux-qa, ux-pm, ux-doc |
|
||||
|
||||
## Cross-Cell Channels
|
||||
|
||||
| Slug | Name | Members |
|
||||
|------|------|---------|
|
||||
| `dev-all` | All Developers | All 6 developers |
|
||||
| `qa-all` | All QA | be-qa, fe-qa, ux-qa |
|
||||
| `pm-all` | All PMs | be-pm, fe-pm, ux-pm, main-pm |
|
||||
| `doc-all` | All Documenters | be-doc, fe-doc, ux-doc |
|
||||
|
||||
## Management Channels
|
||||
|
||||
| Slug | Name | Members |
|
||||
|------|------|---------|
|
||||
| `main-pm-board` | Main PM & Board | main-pm, product-owner, head-marketing, auditor |
|
||||
| `board-private` | Board Private | product-owner, head-marketing, auditor, ceo |
|
||||
|
||||
## Special Channels
|
||||
|
||||
| Slug | Name | Read | Write |
|
||||
|------|------|------|-------|
|
||||
| `announcements` | Announcements | Everyone | PM/Board only |
|
||||
| `all-hands` | All Hands | Everyone | Everyone |
|
||||
|
||||
## Auditor Silent Access
|
||||
|
||||
Auditor has silent read access to:
|
||||
- `backend-cell`
|
||||
- `frontend-cell`
|
||||
- `uxui-cell`
|
||||
- `dev-all`
|
||||
- `qa-all`
|
||||
- `pm-all`
|
||||
- `doc-all`
|
||||
|
||||
Auditor does NOT appear in member lists but CAN read.
|
||||
|
||||
## Privileged Access
|
||||
|
||||
These roles bypass normal membership checks:
|
||||
- **CEO**: Full access everywhere
|
||||
- **Auditor**: Silent read everywhere
|
||||
- **Main PM**: Read access to all cell channels
|
||||
|
||||
## Using Channels
|
||||
|
||||
```python
|
||||
# Send message to your cell
|
||||
roboco_message_send({
|
||||
channel: "backend-cell",
|
||||
content: "Starting work on task",
|
||||
task_id: task_id
|
||||
})
|
||||
|
||||
# Read channel history
|
||||
roboco_channel_history("backend-cell", limit=50)
|
||||
|
||||
# List available channels
|
||||
roboco_channel_list()
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
# Configuration Reference
|
||||
|
||||
Environment variables for RoboCo (prefix: `ROBOCO_`).
|
||||
|
||||
## API Server
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_HOST` | `127.0.0.1` | API host (0.0.0.0 for containers) |
|
||||
| `ROBOCO_PORT` | `8000` | API port |
|
||||
| `ROBOCO_DEBUG` | `false` | Debug mode |
|
||||
| `ROBOCO_ENVIRONMENT` | `development` | development/staging/production |
|
||||
|
||||
## Database
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_DATABASE_HOST` | `localhost` | PostgreSQL host |
|
||||
| `ROBOCO_DATABASE_PORT` | `5432` | PostgreSQL port |
|
||||
| `ROBOCO_DATABASE_USER` | `roboco` | Database user |
|
||||
| `ROBOCO_DATABASE_PASSWORD` | `roboco` | Database password |
|
||||
| `ROBOCO_DATABASE_NAME` | `roboco` | Database name |
|
||||
|
||||
## Redis
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_REDIS_HOST` | `localhost` | Redis host |
|
||||
| `ROBOCO_REDIS_PORT` | `6379` | Redis port |
|
||||
| `ROBOCO_REDIS_DB` | `0` | Redis database |
|
||||
|
||||
## Workspaces
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_WORKSPACES_ROOT` | `/data/workspaces` | Root for agent workspaces |
|
||||
| `ROBOCO_WORKSPACE_AUTO_CLONE` | `true` | Auto-clone on first access |
|
||||
| `ROBOCO_WORKSPACE_CLONE_TIMEOUT` | `300` | Clone timeout (seconds) |
|
||||
|
||||
## RAG/Embeddings
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_DEFAULT_EMBEDDING_MODEL` | `embeddinggemma:300m` | Embedding model |
|
||||
| `ROBOCO_EMBEDDING_DIMENSIONS` | `768` | Embedding dimensions |
|
||||
| `ROBOCO_RAG_CHUNK_STRATEGY` | `fixed` | fixed/semantic/hierarchical/contextual |
|
||||
| `ROBOCO_RAG_CHUNK_SIZE` | `512` | Base chunk size |
|
||||
| `ROBOCO_RAG_CHUNK_SIZE_DOCS` | `1536` | Chunk size for docs |
|
||||
| `ROBOCO_RAG_CHUNK_SIZE_JOURNALS` | `1024` | Chunk size for journals |
|
||||
| `ROBOCO_RAG_CHUNK_OVERLAP` | `128` | Chunk overlap |
|
||||
| `ROBOCO_RAG_USE_HYDE` | `true` | Use HyDE for queries |
|
||||
| `ROBOCO_RAG_USE_HYBRID_SEARCH` | `true` | BM25 + vector search |
|
||||
| `ROBOCO_RAG_USE_CROSS_ENCODER` | `true` | Neural reranking |
|
||||
| `ROBOCO_RAG_AUTO_UPDATE_ENABLED` | `true` | Auto-update indexes |
|
||||
| `ROBOCO_RAG_AUTO_UPDATE_INTERVAL` | `300` | Update interval (seconds) |
|
||||
|
||||
## LLM
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_LOCAL_LLM_MODEL` | `glm-4.6:cloud` | Local LLM for RAG |
|
||||
| `ROBOCO_LOCAL_LLM_BASE_URL` | `http://roboco-ollama:11434/v1` | OpenAI-compat API |
|
||||
| `ROBOCO_OLLAMA_BASE_URL` | `http://roboco-ollama:11434` | Native Ollama API |
|
||||
|
||||
## Security
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_SECRET_KEY` | (required) | JWT signing key (32+ chars) |
|
||||
| `ROBOCO_ACCESS_TOKEN_EXPIRE_MINUTES` | `1440` | Token expiry (24 hours) |
|
||||
|
||||
## Logging
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_LOG_LEVEL` | `INFO` | DEBUG/INFO/WARNING/ERROR/CRITICAL |
|
||||
| `ROBOCO_LOG_FORMAT` | `json` | json or console |
|
||||
|
||||
## Sessions
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ROBOCO_SESSION_DEFAULT_TIMEOUT_SECONDS` | `300` | Session timeout |
|
||||
| `ROBOCO_SESSION_MAX_MESSAGE_COUNT` | `100` | Max messages per session |
|
||||
| `ROBOCO_MESSAGE_MAX_LENGTH` | `10000` | Max message length |
|
||||
@@ -0,0 +1,73 @@
|
||||
# Escalation Chain Reference
|
||||
|
||||
Complete escalation mapping for all agents.
|
||||
|
||||
## Chain Overview
|
||||
|
||||
```
|
||||
Cell Members → Cell PM → Main PM → Product Owner → CEO
|
||||
```
|
||||
|
||||
## Full Mapping
|
||||
|
||||
| Agent | Escalates To |
|
||||
|-------|--------------|
|
||||
| `be-dev-1` | `be-pm` |
|
||||
| `be-dev-2` | `be-pm` |
|
||||
| `be-qa` | `be-pm` |
|
||||
| `be-doc` | `be-pm` |
|
||||
| `fe-dev-1` | `fe-pm` |
|
||||
| `fe-dev-2` | `fe-pm` |
|
||||
| `fe-qa` | `fe-pm` |
|
||||
| `fe-doc` | `fe-pm` |
|
||||
| `ux-dev-1` | `ux-pm` |
|
||||
| `ux-dev-2` | `ux-pm` |
|
||||
| `ux-qa` | `ux-pm` |
|
||||
| `ux-doc` | `ux-pm` |
|
||||
| `be-pm` | `main-pm` |
|
||||
| `fe-pm` | `main-pm` |
|
||||
| `ux-pm` | `main-pm` |
|
||||
| `main-pm` | `product-owner` |
|
||||
| `product-owner` | `ceo` |
|
||||
| `head-marketing` | `ceo` |
|
||||
| `auditor` | `ceo` |
|
||||
|
||||
## Cell PM for Team
|
||||
|
||||
| Team | Cell PM |
|
||||
|------|---------|
|
||||
| `backend` | `be-pm` |
|
||||
| `frontend` | `fe-pm` |
|
||||
| `ux_ui` | `ux-pm` |
|
||||
|
||||
## Escalation Tool
|
||||
|
||||
```python
|
||||
roboco_task_escalate(
|
||||
task_id="uuid-here",
|
||||
reason="Need clarification on requirements"
|
||||
)
|
||||
```
|
||||
|
||||
Auto-routes to your escalation target. You CANNOT choose a different target.
|
||||
|
||||
## CEO Escalation (PM Only)
|
||||
|
||||
```python
|
||||
roboco_task_escalate_to_ceo(
|
||||
task_id="uuid-here",
|
||||
notes="Major feature ready for approval"
|
||||
)
|
||||
```
|
||||
|
||||
Requirements:
|
||||
- Task in `awaiting_pm_review`
|
||||
- PR exists (for git tasks)
|
||||
- Only PMs can call this
|
||||
|
||||
## Cannot Skip Levels
|
||||
|
||||
System enforces chain:
|
||||
- Developer CANNOT escalate directly to Main PM
|
||||
- Cell PM CANNOT escalate directly to CEO
|
||||
- Each level must acknowledge and decide
|
||||
@@ -0,0 +1,88 @@
|
||||
# Permissions Reference
|
||||
|
||||
What each role can do in the system.
|
||||
|
||||
## Permission Levels
|
||||
|
||||
| Level | Roles |
|
||||
|-------|-------|
|
||||
| CEO | ceo, system |
|
||||
| BOARD | product_owner, head_marketing |
|
||||
| AUDITOR | auditor |
|
||||
| MAIN_PM | main_pm |
|
||||
| CELL_PM | cell_pm |
|
||||
| CELL_MEMBER | developer, qa, documenter |
|
||||
|
||||
## Task Permissions
|
||||
|
||||
| Action | CEO | Board | Auditor | Main PM | Cell PM | Dev | QA | Doc |
|
||||
|--------|-----|-------|---------|---------|---------|-----|----|----|
|
||||
| View All | Yes | Yes | Yes | Yes | - | - | - | - |
|
||||
| View Own | - | - | - | - | Yes | Yes | Yes | Yes |
|
||||
| Create | Yes | Yes | Yes | Yes | Yes | - | - | - |
|
||||
| Assign | Yes | Yes | Yes | Yes | Yes | - | - | - |
|
||||
| Cancel | - | Yes | - | Yes | Yes | - | - | - |
|
||||
| Close | Yes | Yes | Yes | Yes | Yes | Yes | - | Yes |
|
||||
| Claim | - | - | - | Yes | Yes | Yes | Yes | Yes |
|
||||
| Pass QA | - | - | - | - | - | - | Yes | - |
|
||||
| Fail QA | - | - | - | - | - | - | Yes | - |
|
||||
| Docs Complete | - | - | - | - | - | - | - | Yes |
|
||||
|
||||
Note: CEO and Auditor CANNOT cancel (by design - observe/approve only).
|
||||
|
||||
## Index Permissions
|
||||
|
||||
| Action | CEO | Board | Auditor | Main PM | Cell PM | Dev | QA | Doc |
|
||||
|--------|-----|-------|---------|---------|---------|-----|----|----|
|
||||
| Index Code | Yes | - | - | Yes | Yes | Yes | - | - |
|
||||
| Index Docs | Yes | Yes | - | Yes | Yes | Yes | - | Yes |
|
||||
| Search/Query | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| View Stats | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Clear Index | Yes | - | - | Yes | - | - | - | - |
|
||||
| Refresh Index | Yes | - | - | Yes | - | - | - | - |
|
||||
|
||||
Note: Board (Product Owner, Head Marketing) can only index docs, not code.
|
||||
|
||||
## Notification Permissions
|
||||
|
||||
| Role | Can Send | Scope |
|
||||
|------|----------|-------|
|
||||
| ceo | Yes | All |
|
||||
| product_owner | Yes | Management chain |
|
||||
| head_marketing | Yes | Management chain |
|
||||
| auditor | Yes | All |
|
||||
| main_pm | Yes | All |
|
||||
| cell_pm | Yes | Own cell |
|
||||
| developer | No | - |
|
||||
| qa | No | - |
|
||||
| documenter | No | - |
|
||||
|
||||
## PM-Capable Roles
|
||||
|
||||
These roles can create/assign tasks:
|
||||
- `ceo`
|
||||
- `product_owner`
|
||||
- `head_marketing`
|
||||
- `main_pm`
|
||||
- `cell_pm`
|
||||
|
||||
## Cancellation Roles
|
||||
|
||||
These roles can cancel tasks:
|
||||
- `product_owner`
|
||||
- `head_marketing`
|
||||
- `main_pm`
|
||||
- `cell_pm`
|
||||
|
||||
Note: CEO and Auditor CANNOT cancel (observe/approve only).
|
||||
|
||||
## View Scope
|
||||
|
||||
| Role | Can View |
|
||||
|------|----------|
|
||||
| CEO | All tasks |
|
||||
| Board | All tasks |
|
||||
| Auditor | All tasks (silent) |
|
||||
| Main PM | All tasks |
|
||||
| Cell PM | Own cell + cross-cell |
|
||||
| Cell Member | Own cell |
|
||||
@@ -0,0 +1,104 @@
|
||||
# API Endpoints Reference
|
||||
|
||||
Base URL: `http://{host}:{port}/api/v1`
|
||||
|
||||
## Tasks
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/tasks` | List tasks (filtered) |
|
||||
| GET | `/tasks/my` | My assigned tasks |
|
||||
| GET | `/tasks/pending` | Pending tasks |
|
||||
| GET | `/tasks/blocked` | Blocked tasks |
|
||||
| GET | `/tasks/awaiting-qa` | Tasks awaiting QA |
|
||||
| GET | `/tasks/awaiting-docs` | Tasks awaiting docs |
|
||||
| GET | `/tasks/awaiting-pm-review` | Tasks awaiting PM |
|
||||
| GET | `/tasks/awaiting-ceo-approval` | Tasks for CEO |
|
||||
| GET | `/tasks/team/{team}` | Tasks by team |
|
||||
| GET | `/tasks/{id}` | Get task details |
|
||||
| GET | `/tasks/{id}/subtasks` | Get subtasks |
|
||||
| POST | `/tasks` | Create task |
|
||||
| PATCH | `/tasks/{id}` | Update task |
|
||||
| DELETE | `/tasks/{id}` | Delete task |
|
||||
| POST | `/tasks/{id}/claim` | Claim task |
|
||||
| POST | `/tasks/{id}/start` | Start work |
|
||||
| POST | `/tasks/{id}/pause` | Pause work |
|
||||
| POST | `/tasks/{id}/resume` | Resume work |
|
||||
| POST | `/tasks/{id}/block` | Block on task |
|
||||
| POST | `/tasks/{id}/soft-block` | Block on external |
|
||||
| POST | `/tasks/{id}/unblock` | Unblock task |
|
||||
| POST | `/tasks/{id}/verify` | Submit verification |
|
||||
| POST | `/tasks/{id}/submit-qa` | Submit for QA |
|
||||
| POST | `/tasks/{id}/pass-qa` | Pass QA |
|
||||
| POST | `/tasks/{id}/fail-qa` | Fail QA |
|
||||
| POST | `/tasks/{id}/docs-complete` | Complete docs |
|
||||
| POST | `/tasks/{id}/submit-pm-review` | Submit to PM |
|
||||
| POST | `/tasks/{id}/complete` | Complete task |
|
||||
| POST | `/tasks/{id}/cancel` | Cancel task |
|
||||
| POST | `/tasks/{id}/activate` | Activate task |
|
||||
| POST | `/tasks/{id}/escalate` | Escalate task |
|
||||
| POST | `/tasks/{id}/escalate-to-ceo` | Escalate to CEO |
|
||||
| POST | `/tasks/{id}/ceo-approve` | CEO approve |
|
||||
| POST | `/tasks/{id}/ceo-reject` | CEO reject |
|
||||
| POST | `/tasks/{id}/substitute` | Substitute agent |
|
||||
| POST | `/tasks/{id}/progress` | Update progress |
|
||||
|
||||
## Git
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/git/status` | Git status |
|
||||
| GET | `/git/log` | Commit history |
|
||||
| GET | `/git/branches` | List branches |
|
||||
| GET | `/git/diff` | View diff |
|
||||
| POST | `/git/commit` | Create commit |
|
||||
| POST | `/git/push` | Push to remote |
|
||||
| POST | `/git/branch/create` | Create branch |
|
||||
| POST | `/git/checkout` | Checkout branch |
|
||||
| POST | `/git/pr/create` | Create PR |
|
||||
| POST | `/git/pr/merge` | Merge PR |
|
||||
|
||||
## Channels & Messages
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/channels` | List channels |
|
||||
| GET | `/channels/{slug}/history` | Channel history |
|
||||
| POST | `/messages` | Send message |
|
||||
| GET | `/messages/{id}` | Get message |
|
||||
|
||||
## Notifications
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/notifications` | List notifications |
|
||||
| GET | `/notifications/{id}` | Get notification |
|
||||
| POST | `/notifications` | Send notification |
|
||||
| POST | `/notifications/{id}/ack` | Acknowledge |
|
||||
|
||||
## Journals
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/journals/me/entries` | My entries |
|
||||
| POST | `/journals/me/entries` | Create entry |
|
||||
| GET | `/journals/me/stats` | My stats |
|
||||
| GET | `/journals/{agent}/entries` | Read team journal |
|
||||
|
||||
## Knowledge Base
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/optimal/search` | Semantic search |
|
||||
| POST | `/optimal/query` | RAG query |
|
||||
| POST | `/optimal/mentor/ask` | Ask mentor |
|
||||
| GET | `/optimal/stats` | KB stats |
|
||||
| POST | `/optimal/index/code` | Index code |
|
||||
| POST | `/optimal/index/docs` | Index docs |
|
||||
|
||||
## Health
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/health` | Health check |
|
||||
| GET | `/health/ready` | Readiness check |
|
||||
@@ -67,10 +67,17 @@ roboco_task_complete(task_id)
|
||||
## PM Operations
|
||||
|
||||
```python
|
||||
# Create task
|
||||
# Create SUBTASK (most common)
|
||||
roboco_task_create({
|
||||
title: "Implement auth endpoint",
|
||||
parent_task_id: my_task_id, # REQUIRED for subtasks
|
||||
team: "backend",
|
||||
assigned_to: "be-dev-1" # Use SLUG
|
||||
})
|
||||
|
||||
# Create standalone task (rare)
|
||||
roboco_task_create({
|
||||
title: "...",
|
||||
description: "...",
|
||||
team: "backend",
|
||||
status: "backlog"
|
||||
})
|
||||
@@ -83,8 +90,15 @@ roboco_task_cancel(task_id, reason)
|
||||
|
||||
# Plan
|
||||
roboco_task_plan(task_id, approach, steps)
|
||||
|
||||
# Escalate to CEO (parent tasks only)
|
||||
roboco_task_escalate_to_ceo(task_id, notes)
|
||||
```
|
||||
|
||||
**CRITICAL**: When creating subtasks, ALWAYS include `parent_task_id`. Without it, you create orphan sibling tasks instead of linked subtasks.
|
||||
|
||||
**Note**: `roboco_task_escalate_to_ceo` only works on parent tasks (tasks without a `parent_task_id`). Subtasks must have their parent task escalated instead.
|
||||
|
||||
## Progress Updates
|
||||
|
||||
```python
|
||||
|
||||
@@ -49,11 +49,30 @@
|
||||
|
||||
## Self-Review Prevented
|
||||
|
||||
**Error**: "Cannot review own work"
|
||||
**Error**: "Cannot review own work" or "SELF_REVIEW_NOT_ALLOWED"
|
||||
|
||||
**Cause**: QA/Documenter trying to claim task they developed
|
||||
**Cause**: QA trying to claim, pass, or fail a task they originally developed
|
||||
|
||||
**Solution**: Another QA/Documenter must handle this task
|
||||
**Solution**: Another QA must handle this task. Self-review prevention applies to:
|
||||
- Claiming the task
|
||||
- Passing QA (`roboco_task_qa_pass`)
|
||||
- Failing QA (`roboco_task_qa_fail`)
|
||||
|
||||
## Cannot Escalate Subtask to CEO
|
||||
|
||||
**Error**: "Cannot escalate subtask to CEO - only parent tasks can be escalated"
|
||||
|
||||
**Cause**: Attempting to escalate a task that has a `parent_task_id`
|
||||
|
||||
**Solution**: Escalate the parent task instead:
|
||||
```python
|
||||
# Get the parent task ID
|
||||
task = roboco_task_get(subtask_id)
|
||||
parent_id = task.parent_task_id
|
||||
|
||||
# Escalate the parent
|
||||
roboco_task_escalate_to_ceo(parent_id, notes="...")
|
||||
```
|
||||
|
||||
## Git Task: No Branch
|
||||
|
||||
@@ -65,3 +84,24 @@
|
||||
```python
|
||||
roboco_git_create_branch(project_slug, task_id, "feature")
|
||||
```
|
||||
|
||||
## Task Has Incomplete Subtasks
|
||||
|
||||
**Error**: "Cannot complete task - subtasks not finished" with list of task IDs
|
||||
|
||||
**Cause**: Trying to complete a parent task while subtasks are still in progress
|
||||
|
||||
**Solution**: The error message includes which subtask IDs are blocking. Either:
|
||||
1. Complete the blocking subtasks first
|
||||
2. Cancel them if no longer needed: `roboco_task_cancel(subtask_id, reason)`
|
||||
|
||||
## Invalid Task Status for Operation
|
||||
|
||||
**Error**: "Task is in [status], expected [expected_status]"
|
||||
|
||||
**Cause**: Attempting an operation that's not valid for the current task state
|
||||
|
||||
**Solution**: Check the task's current status and follow the correct workflow:
|
||||
- QA operations require `awaiting_qa` status
|
||||
- Documentation operations require `awaiting_documentation` status
|
||||
- PM completion requires `awaiting_pm_review` status
|
||||
|
||||
@@ -73,6 +73,9 @@ Requirements:
|
||||
- Task must be in `awaiting_pm_review`
|
||||
- PR must exist (for git tasks)
|
||||
- Only PMs can do this
|
||||
- **PARENT TASKS ONLY** - Subtasks cannot be escalated to CEO
|
||||
|
||||
If you need to escalate a subtask, escalate the parent task instead. The CEO reviews the complete feature, not individual components.
|
||||
|
||||
## Good Escalation Format
|
||||
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
# Knowledge Base Search
|
||||
|
||||
**ALL agents have access to KB/RAG tools.** These are automatically available.
|
||||
|
||||
## Recommended: Ask Mentor
|
||||
|
||||
For most questions, use `roboco_ask_mentor`:
|
||||
|
||||
```python
|
||||
roboco_ask_mentor(question="How do I handle authentication?")
|
||||
```
|
||||
|
||||
It searches ALL knowledge sources and supports follow-up questions.
|
||||
|
||||
## Search Types
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `roboco_kb_search` | Semantic search across indexes |
|
||||
| `roboco_rag_query` | AI-synthesized answer |
|
||||
| `roboco_ask_mentor` | Conversational help |
|
||||
| Tool | Purpose | Best For |
|
||||
|------|---------|----------|
|
||||
| `roboco_ask_mentor` | Conversational help | **Most questions** |
|
||||
| `roboco_kb_search` | Semantic search | Browsing, exploration |
|
||||
| `roboco_rag_query` | AI-synthesized answer | Quick answers |
|
||||
|
||||
## Semantic Search
|
||||
|
||||
|
||||
@@ -85,4 +85,9 @@ roboco_journal_reflect({
|
||||
|
||||
QA CANNOT review tasks they originally developed.
|
||||
|
||||
System tracks `original_developer` in `quick_context`. If QA == original_developer, claim is FORBIDDEN.
|
||||
System tracks `original_developer` in `quick_context`. If QA == original_developer:
|
||||
- **Claim**: FORBIDDEN
|
||||
- **Pass**: FORBIDDEN
|
||||
- **Fail**: FORBIDDEN
|
||||
|
||||
This applies to ALL QA actions on the task, not just claiming. The system enforces this at both the API and MCP tool level.
|
||||
|
||||
@@ -62,8 +62,17 @@ backlog → pending (via roboco_task_activate)
|
||||
| `awaiting_qa → needs_revision` | qa only |
|
||||
| `awaiting_documentation → awaiting_pm_review` | documenter only |
|
||||
| `awaiting_pm_review → completed` | cell_pm, main_pm |
|
||||
| `awaiting_pm_review → awaiting_ceo_approval` | cell_pm, main_pm (parent tasks only) |
|
||||
| `awaiting_ceo_approval → completed` | ceo only |
|
||||
| `awaiting_ceo_approval → needs_revision` | ceo only |
|
||||
| `any → cancelled` | cell_pm, main_pm |
|
||||
|
||||
## CEO Approval Notes
|
||||
|
||||
- Only **parent tasks** (no `parent_task_id`) can be escalated to CEO
|
||||
- Subtasks are completed by their Cell PM, not the CEO
|
||||
- The CEO reviews the complete feature via the parent task
|
||||
|
||||
## Checking State
|
||||
|
||||
```python
|
||||
|
||||
@@ -482,40 +482,16 @@ async def check_staleness(
|
||||
return await service.check_index_staleness()
|
||||
|
||||
|
||||
@router.get("/health", response_model=RAGHealthResponse)
|
||||
async def rag_health_check() -> RAGHealthResponse:
|
||||
"""
|
||||
Check RAG system health.
|
||||
|
||||
Tests connectivity to:
|
||||
- Embedding model (sentence-transformers)
|
||||
- LLM (Ollama for HyDE)
|
||||
- Vector store (PostgreSQL/pgvector)
|
||||
|
||||
Each test has a 10-second timeout to prevent hanging.
|
||||
"""
|
||||
async def _check_embedding_health(details: dict[str, Any], timeout: float) -> bool:
|
||||
"""Test embedding model connectivity."""
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
|
||||
from roboco.config import settings
|
||||
|
||||
details: dict[str, Any] = {}
|
||||
embedding_ok = False
|
||||
llm_ok = False
|
||||
vector_ok = False
|
||||
|
||||
health_timeout = 10.0 # seconds
|
||||
|
||||
# Test embedding model with timeout
|
||||
from roboco.services.optimal_brain.shared_embedder import (
|
||||
get_shared_embedder,
|
||||
)
|
||||
from roboco.services.optimal_brain.shared_embedder import get_shared_embedder
|
||||
|
||||
try:
|
||||
async with asyncio.timeout(health_timeout):
|
||||
async with asyncio.timeout(timeout):
|
||||
embedder = await get_shared_embedder(model=settings.default_embedding_model)
|
||||
# Use async method if available (OllamaEmbedder), else run sync in thread
|
||||
if hasattr(embedder, "aembed_query"):
|
||||
test_embedding = await embedder.aembed_query("health check")
|
||||
else:
|
||||
@@ -523,17 +499,24 @@ async def rag_health_check() -> RAGHealthResponse:
|
||||
embedder.embed_query, "health check"
|
||||
)
|
||||
if test_embedding and len(test_embedding) == settings.embedding_dimensions:
|
||||
embedding_ok = True
|
||||
details["embedding_model"] = settings.default_embedding_model
|
||||
details["embedding_dimensions"] = len(test_embedding)
|
||||
return True
|
||||
except TimeoutError:
|
||||
details["embedding_error"] = f"Timeout after {health_timeout}s"
|
||||
details["embedding_error"] = f"Timeout after {timeout}s"
|
||||
except Exception as e:
|
||||
details["embedding_error"] = str(e)
|
||||
return False
|
||||
|
||||
|
||||
async def _check_llm_health(details: dict[str, Any], timeout: float) -> bool:
|
||||
"""Test LLM (Ollama) connectivity."""
|
||||
import httpx
|
||||
|
||||
from roboco.config import settings
|
||||
|
||||
# Test LLM (Ollama) - already has timeout via httpx
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=health_timeout) as client:
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
resp = await client.post(
|
||||
f"{settings.local_llm_base_url}/chat/completions",
|
||||
json={
|
||||
@@ -543,39 +526,60 @@ async def rag_health_check() -> RAGHealthResponse:
|
||||
},
|
||||
)
|
||||
if resp.is_success:
|
||||
llm_ok = True
|
||||
details["llm_model"] = settings.local_llm_model
|
||||
details["llm_base_url"] = settings.local_llm_base_url
|
||||
return True
|
||||
except Exception as e:
|
||||
details["llm_error"] = str(e)
|
||||
return False
|
||||
|
||||
|
||||
async def _check_vector_store_health(details: dict[str, Any], timeout: float) -> bool:
|
||||
"""Test vector store connectivity and index health."""
|
||||
import asyncio
|
||||
|
||||
# Test vector store with timeout
|
||||
try:
|
||||
async with asyncio.timeout(health_timeout):
|
||||
async with asyncio.timeout(timeout):
|
||||
service = await get_optimal_service()
|
||||
stats = await service.get_stats()
|
||||
if stats.get("initialized"):
|
||||
vector_ok = True
|
||||
details["vector_store"] = "connected"
|
||||
if not stats.get("initialized"):
|
||||
return False
|
||||
details["vector_store"] = "connected"
|
||||
|
||||
# Test actual search capability per index
|
||||
index_health: dict[str, str] = {}
|
||||
for index_type, plugin in service._plugins.items():
|
||||
try:
|
||||
outcome = await plugin.search("test", top_k=1)
|
||||
if outcome.success:
|
||||
index_health[index_type.value] = "ok"
|
||||
else:
|
||||
err_msg = outcome.error_message or "unknown"
|
||||
index_health[index_type.value] = f"error: {err_msg}"
|
||||
index_health[index_type.value] = (
|
||||
"ok"
|
||||
if outcome.success
|
||||
else f"error: {outcome.error_message or 'unknown'}"
|
||||
)
|
||||
except Exception as idx_e:
|
||||
index_health[index_type.value] = f"error: {idx_e}"
|
||||
details["index_health"] = index_health
|
||||
|
||||
return True
|
||||
except TimeoutError:
|
||||
details["vector_store_error"] = f"Timeout after {health_timeout}s"
|
||||
details["vector_store_error"] = f"Timeout after {timeout}s"
|
||||
except Exception as e:
|
||||
details["vector_store_error"] = str(e)
|
||||
return False
|
||||
|
||||
|
||||
@router.get("/health", response_model=RAGHealthResponse)
|
||||
async def rag_health_check() -> RAGHealthResponse:
|
||||
"""
|
||||
Check RAG system health.
|
||||
|
||||
Tests connectivity to embedding model, LLM, and vector store.
|
||||
Each test has a 10-second timeout.
|
||||
"""
|
||||
details: dict[str, Any] = {}
|
||||
timeout = 10.0
|
||||
|
||||
embedding_ok = await _check_embedding_health(details, timeout)
|
||||
llm_ok = await _check_llm_health(details, timeout)
|
||||
vector_ok = await _check_vector_store_health(details, timeout)
|
||||
|
||||
return RAGHealthResponse(
|
||||
healthy=embedding_ok and llm_ok and vector_ok,
|
||||
|
||||
@@ -1212,6 +1212,9 @@ async def complete_task(
|
||||
detail="force_with_cancelled requires justification",
|
||||
)
|
||||
|
||||
# Store original task info for error reporting
|
||||
original_status = task.status.value if task.status else "unknown"
|
||||
|
||||
task = await service.complete(
|
||||
task_id,
|
||||
agent_id=agent.agent_id,
|
||||
@@ -1219,10 +1222,41 @@ async def complete_task(
|
||||
justification=justification,
|
||||
)
|
||||
if not task:
|
||||
# Provide specific error based on what blocked completion
|
||||
refetch = await service.get(task_id)
|
||||
if refetch:
|
||||
# Check for incomplete descendants
|
||||
descendants = await service.get_all_descendants(task_id)
|
||||
incomplete = [
|
||||
str(d.id)[:8]
|
||||
for d in descendants
|
||||
if d.status.value not in ("completed", "cancelled")
|
||||
]
|
||||
max_shown = 5
|
||||
if incomplete:
|
||||
shown = ", ".join(incomplete[:max_shown])
|
||||
extra = (
|
||||
f" (+{len(incomplete) - max_shown} more)"
|
||||
if len(incomplete) > max_shown
|
||||
else ""
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Cannot complete task - {len(incomplete)} subtask(s) "
|
||||
f"still in progress: {shown}{extra}. "
|
||||
"Monitor and help unblock stuck tasks.",
|
||||
)
|
||||
# Check for status issue
|
||||
if original_status not in ("awaiting_pm_review", "in_progress"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Cannot complete - status is '{original_status}'. "
|
||||
"Must be 'awaiting_pm_review' or 'in_progress'.",
|
||||
)
|
||||
# Fallback generic error
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Cannot complete task - all subtasks must be in terminal states "
|
||||
"(completed or cancelled). Monitor and help unblock stuck tasks.",
|
||||
detail="Cannot complete task - check task status and subtasks.",
|
||||
)
|
||||
await db.commit()
|
||||
return task_to_response(task)
|
||||
|
||||
@@ -5,18 +5,17 @@ Request/response models for task endpoints.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
|
||||
from roboco.db.tables import ProjectTable, TaskTable, WorkSessionTable
|
||||
from roboco.models.base import Complexity, TaskNature, TaskStatus, TaskType, Team
|
||||
from roboco.models.session import SessionScope
|
||||
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
|
||||
# =============================================================================
|
||||
@@ -601,10 +600,6 @@ async def enrich_task_with_context(
|
||||
|
||||
Call this when full traceability context is needed.
|
||||
"""
|
||||
from sqlalchemy import select # noqa: PLC0415
|
||||
|
||||
from roboco.db.tables import ProjectTable, WorkSessionTable # noqa: PLC0415
|
||||
|
||||
task_dict = task_response.model_dump()
|
||||
|
||||
# Get project info if task has project_id
|
||||
|
||||
@@ -149,9 +149,10 @@ def _register_developer_tools(mcp: FastMCP, client: ApiClient, agent_id: str) ->
|
||||
Returns:
|
||||
Commit details with hash and files changed
|
||||
"""
|
||||
return await handle_git_commit(
|
||||
client, project_slug, message, task_id, files, agent_id
|
||||
)
|
||||
from roboco.mcp.git.handlers import GitContext
|
||||
|
||||
ctx = GitContext(client=client, project_slug=project_slug, agent_id=agent_id)
|
||||
return await handle_git_commit(ctx, message, task_id, files)
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_git_push(
|
||||
@@ -200,9 +201,10 @@ def _register_developer_tools(mcp: FastMCP, client: ApiClient, agent_id: str) ->
|
||||
Returns:
|
||||
PR details with URL and number
|
||||
"""
|
||||
return await handle_git_create_pr(
|
||||
client, project_slug, task_id, title, body, agent_id
|
||||
)
|
||||
from roboco.mcp.git.handlers import GitContext
|
||||
|
||||
ctx = GitContext(client=client, project_slug=project_slug, agent_id=agent_id)
|
||||
return await handle_git_create_pr(ctx, task_id, title, body)
|
||||
|
||||
|
||||
def _register_pm_branch_tools(mcp: FastMCP, client: ApiClient, agent_id: str) -> None:
|
||||
@@ -234,9 +236,10 @@ def _register_pm_branch_tools(mcp: FastMCP, client: ApiClient, agent_id: str) ->
|
||||
Returns:
|
||||
Created branch info with checkout instructions
|
||||
"""
|
||||
return await handle_git_create_branch(
|
||||
client, project_slug, task_id, branch_type, parent_branch, agent_id
|
||||
)
|
||||
from roboco.mcp.git.handlers import GitContext
|
||||
|
||||
ctx = GitContext(client=client, project_slug=project_slug, agent_id=agent_id)
|
||||
return await handle_git_create_branch(ctx, task_id, branch_type, parent_branch)
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_git_checkout(
|
||||
@@ -279,9 +282,10 @@ def _register_pm_branch_tools(mcp: FastMCP, client: ApiClient, agent_id: str) ->
|
||||
Returns:
|
||||
Merge result with final commit hash
|
||||
"""
|
||||
return await handle_git_merge_pr(
|
||||
client, project_slug, pr_number, task_id, merge_method, agent_id
|
||||
)
|
||||
from roboco.mcp.git.handlers import GitContext
|
||||
|
||||
ctx = GitContext(client=client, project_slug=project_slug, agent_id=agent_id)
|
||||
return await handle_git_merge_pr(ctx, pr_number, task_id, merge_method)
|
||||
|
||||
|
||||
def create_git_mcp_server(agent_id: str) -> FastMCP:
|
||||
|
||||
+31
-28
@@ -7,6 +7,7 @@ Handler functions for git operations. Each handler:
|
||||
3. Returns formatted response with guidance
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from roboco.mcp.utils import (
|
||||
@@ -15,6 +16,16 @@ from roboco.mcp.utils import (
|
||||
format_success_response,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GitContext:
|
||||
"""Common context for git operations."""
|
||||
|
||||
client: ApiClient
|
||||
project_slug: str
|
||||
agent_id: str
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# READ-ONLY HANDLERS
|
||||
# =============================================================================
|
||||
@@ -172,25 +183,23 @@ async def handle_git_diff(
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def handle_git_commit( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
async def handle_git_commit(
|
||||
ctx: GitContext,
|
||||
message: str,
|
||||
task_id: str,
|
||||
files: list[str] | None,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle git commit request."""
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"project_slug": ctx.project_slug,
|
||||
"message": message,
|
||||
"task_id": task_id,
|
||||
"agent_id": agent_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
}
|
||||
if files:
|
||||
payload["files"] = files
|
||||
|
||||
resp = await client.post("/git/commit", json=payload)
|
||||
resp = await ctx.client.post("/git/commit", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"GIT_COMMIT_FAILED",
|
||||
@@ -247,24 +256,22 @@ async def handle_git_push(
|
||||
)
|
||||
|
||||
|
||||
async def handle_git_create_pr( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
async def handle_git_create_pr(
|
||||
ctx: GitContext,
|
||||
task_id: str,
|
||||
title: str,
|
||||
body: str,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle PR creation request."""
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"project_slug": ctx.project_slug,
|
||||
"task_id": task_id,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"agent_id": agent_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
}
|
||||
|
||||
resp = await client.post("/git/pr/create", json=payload)
|
||||
resp = await ctx.client.post("/git/pr/create", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"PR_CREATE_FAILED",
|
||||
@@ -290,13 +297,11 @@ async def handle_git_create_pr( # noqa: PLR0913
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def handle_git_create_branch( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
async def handle_git_create_branch(
|
||||
ctx: GitContext,
|
||||
task_id: str,
|
||||
branch_type: str,
|
||||
parent_branch: str | None,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle branch creation request (PM only)."""
|
||||
valid_types = {"feature", "bug", "chore", "docs", "hotfix"}
|
||||
@@ -308,15 +313,15 @@ async def handle_git_create_branch( # noqa: PLR0913
|
||||
)
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"project_slug": ctx.project_slug,
|
||||
"task_id": task_id,
|
||||
"branch_type": branch_type,
|
||||
"agent_id": agent_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
}
|
||||
if parent_branch:
|
||||
payload["parent_branch"] = parent_branch
|
||||
|
||||
resp = await client.post("/git/branch/create", json=payload)
|
||||
resp = await ctx.client.post("/git/branch/create", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"BRANCH_CREATE_FAILED",
|
||||
@@ -363,13 +368,11 @@ async def handle_git_checkout(
|
||||
)
|
||||
|
||||
|
||||
async def handle_git_merge_pr( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
async def handle_git_merge_pr(
|
||||
ctx: GitContext,
|
||||
pr_number: int,
|
||||
task_id: str,
|
||||
merge_method: str,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle PR merge request (PM only)."""
|
||||
valid_methods = {"merge", "squash", "rebase"}
|
||||
@@ -381,14 +384,14 @@ async def handle_git_merge_pr( # noqa: PLR0913
|
||||
)
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"project_slug": ctx.project_slug,
|
||||
"pr_number": pr_number,
|
||||
"task_id": task_id,
|
||||
"merge_method": merge_method,
|
||||
"agent_id": agent_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
}
|
||||
|
||||
resp = await client.post("/git/pr/merge", json=payload)
|
||||
resp = await ctx.client.post("/git/pr/merge", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"PR_MERGE_FAILED",
|
||||
|
||||
@@ -279,6 +279,19 @@ async def handle_escalate_to_ceo(
|
||||
{"current_status": current_status},
|
||||
)
|
||||
|
||||
# Only parent tasks can be escalated to CEO (not subtasks)
|
||||
if task.get("parent_task_id"):
|
||||
return format_error_response(
|
||||
"IS_SUBTASK",
|
||||
"Cannot escalate subtask to CEO - only parent tasks allowed.",
|
||||
{
|
||||
"task_id": task_id,
|
||||
"parent_task_id": task.get("parent_task_id"),
|
||||
"guidance": "Escalate the parent task instead.",
|
||||
},
|
||||
hint="roboco_kb_search('parent task escalation')",
|
||||
)
|
||||
|
||||
payload = {}
|
||||
if notes:
|
||||
payload["notes"] = notes
|
||||
|
||||
@@ -258,6 +258,31 @@ def _validate_qa_issues(issues: list[str]) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
|
||||
async def _validate_qa_fail_request(
|
||||
client: ApiClient, task_id: str, issues: list[str], agent_id: str
|
||||
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
|
||||
"""Validate QA fail request. Returns (task, None) or (None, error)."""
|
||||
if error := _validate_qa_role(agent_id, "fail"):
|
||||
return None, error
|
||||
|
||||
if error := _validate_qa_issues(issues):
|
||||
return None, error
|
||||
|
||||
task, error = await fetch_task_or_error(client, task_id)
|
||||
if error:
|
||||
return None, error
|
||||
assert task is not None
|
||||
|
||||
if error := validate_task_status_in(task, QA_WORKFLOW_STATUSES, "fail QA on"):
|
||||
return None, error
|
||||
|
||||
# Security: prevent QA from reviewing their own work
|
||||
if error := await _check_self_review(task, agent_id, client):
|
||||
return None, error
|
||||
|
||||
return task, None
|
||||
|
||||
|
||||
async def handle_task_qa_fail(
|
||||
client: ApiClient,
|
||||
task_id: str,
|
||||
@@ -266,20 +291,11 @@ async def handle_task_qa_fail(
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle task QA failure."""
|
||||
if error := _validate_qa_role(agent_id, "fail"):
|
||||
return error
|
||||
|
||||
if error := _validate_qa_issues(issues):
|
||||
return error
|
||||
|
||||
task, error = await fetch_task_or_error(client, task_id)
|
||||
task, error = await _validate_qa_fail_request(client, task_id, issues, agent_id)
|
||||
if error:
|
||||
return error
|
||||
assert task is not None
|
||||
|
||||
if error := validate_task_status_in(task, QA_WORKFLOW_STATUSES, "fail QA on"):
|
||||
return error
|
||||
|
||||
full_notes = f"{qa_notes}\n\nIssues:\n" + "\n".join(f"- {i}" for i in issues)
|
||||
fail_resp = await client.post(
|
||||
f"/tasks/{task_id}/fail-qa", json={"notes": full_notes}
|
||||
|
||||
+30
-27
@@ -7,6 +7,7 @@ Handler functions for test/CI operations. Each handler:
|
||||
3. Returns formatted response with results and guidance
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from roboco.mcp.utils import (
|
||||
@@ -15,6 +16,17 @@ from roboco.mcp.utils import (
|
||||
format_success_response,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestContext:
|
||||
"""Common context for test operations."""
|
||||
|
||||
client: ApiClient
|
||||
project_slug: str
|
||||
task_id: str
|
||||
agent_id: str
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# READ-ONLY HANDLERS
|
||||
# =============================================================================
|
||||
@@ -54,25 +66,22 @@ async def handle_test_status(
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def handle_test_run( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
task_id: str,
|
||||
async def handle_test_run(
|
||||
ctx: TestContext,
|
||||
test_path: str | None,
|
||||
verbose: bool,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle test run request."""
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"task_id": task_id,
|
||||
"agent_id": agent_id,
|
||||
"project_slug": ctx.project_slug,
|
||||
"task_id": ctx.task_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
"verbose": verbose,
|
||||
}
|
||||
if test_path:
|
||||
payload["test_path"] = test_path
|
||||
|
||||
resp = await client.post("/test/run", json=payload)
|
||||
resp = await ctx.client.post("/test/run", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"TEST_RUN_FAILED",
|
||||
@@ -97,25 +106,22 @@ async def handle_test_run( # noqa: PLR0913
|
||||
return format_success_response(data, guidance=guidance, next_step=next_step)
|
||||
|
||||
|
||||
async def handle_test_lint( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
task_id: str,
|
||||
async def handle_test_lint(
|
||||
ctx: TestContext,
|
||||
fix: bool,
|
||||
path: str | None,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle lint request."""
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"task_id": task_id,
|
||||
"agent_id": agent_id,
|
||||
"project_slug": ctx.project_slug,
|
||||
"task_id": ctx.task_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
"fix": fix,
|
||||
}
|
||||
if path:
|
||||
payload["path"] = path
|
||||
|
||||
resp = await client.post("/test/lint", json=payload)
|
||||
resp = await ctx.client.post("/test/lint", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"LINT_FAILED",
|
||||
@@ -145,25 +151,22 @@ async def handle_test_lint( # noqa: PLR0913
|
||||
)
|
||||
|
||||
|
||||
async def handle_test_format( # noqa: PLR0913
|
||||
client: ApiClient,
|
||||
project_slug: str,
|
||||
task_id: str,
|
||||
async def handle_test_format(
|
||||
ctx: TestContext,
|
||||
check_only: bool,
|
||||
path: str | None,
|
||||
agent_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Handle format request."""
|
||||
payload: dict[str, Any] = {
|
||||
"project_slug": project_slug,
|
||||
"task_id": task_id,
|
||||
"agent_id": agent_id,
|
||||
"project_slug": ctx.project_slug,
|
||||
"task_id": ctx.task_id,
|
||||
"agent_id": ctx.agent_id,
|
||||
"check_only": check_only,
|
||||
}
|
||||
if path:
|
||||
payload["path"] = path
|
||||
|
||||
resp = await client.post("/test/format", json=payload)
|
||||
resp = await ctx.client.post("/test/format", json=payload)
|
||||
if not resp.ok:
|
||||
return format_error_response(
|
||||
"FORMAT_FAILED",
|
||||
|
||||
@@ -76,9 +76,12 @@ def _register_test_tools(mcp: FastMCP, client: ApiClient, agent_id: str) -> None
|
||||
Returns:
|
||||
Test results with pass/fail counts and output
|
||||
"""
|
||||
return await handle_test_run(
|
||||
client, project_slug, task_id, test_path, verbose, agent_id
|
||||
from roboco.mcp.test.handlers import TestContext
|
||||
|
||||
ctx = TestContext(
|
||||
client=client, project_slug=project_slug, task_id=task_id, agent_id=agent_id
|
||||
)
|
||||
return await handle_test_run(ctx, test_path, verbose)
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_test_lint(
|
||||
@@ -101,9 +104,12 @@ def _register_test_tools(mcp: FastMCP, client: ApiClient, agent_id: str) -> None
|
||||
Returns:
|
||||
Lint results with issues found
|
||||
"""
|
||||
return await handle_test_lint(
|
||||
client, project_slug, task_id, fix, path, agent_id
|
||||
from roboco.mcp.test.handlers import TestContext
|
||||
|
||||
ctx = TestContext(
|
||||
client=client, project_slug=project_slug, task_id=task_id, agent_id=agent_id
|
||||
)
|
||||
return await handle_test_lint(ctx, fix, path)
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_test_format(
|
||||
@@ -126,9 +132,12 @@ def _register_test_tools(mcp: FastMCP, client: ApiClient, agent_id: str) -> None
|
||||
Returns:
|
||||
Format results with files modified
|
||||
"""
|
||||
return await handle_test_format(
|
||||
client, project_slug, task_id, check_only, path, agent_id
|
||||
from roboco.mcp.test.handlers import TestContext
|
||||
|
||||
ctx = TestContext(
|
||||
client=client, project_slug=project_slug, task_id=task_id, agent_id=agent_id
|
||||
)
|
||||
return await handle_test_format(ctx, check_only, path)
|
||||
|
||||
@mcp.tool()
|
||||
async def roboco_test_typecheck(
|
||||
|
||||
@@ -281,6 +281,10 @@ class AgentOrchestrator:
|
||||
# Git - branch management, commits, PRs
|
||||
# Role-based permissions enforced at handler level
|
||||
"mcp__roboco-git__*",
|
||||
# Agent-to-Agent protocol - cross-cell coordination
|
||||
"mcp__roboco-a2a__*",
|
||||
# Test tools - run tests, lint, format
|
||||
"mcp__roboco-test__*",
|
||||
# File operations for documenters and developers
|
||||
# Note: // prefix = absolute path (container paths like /app/docs)
|
||||
"Write(//app/docs/**)",
|
||||
@@ -577,11 +581,17 @@ class AgentOrchestrator:
|
||||
agent_id: str,
|
||||
git_context: SpawnGitContext | None = None,
|
||||
) -> Path:
|
||||
"""Generate role-aware MCP config for an agent.
|
||||
"""Generate MCP config for an agent.
|
||||
|
||||
Different roles get different MCP server access:
|
||||
- All agents: task, message, journal
|
||||
- PMs only: notify (for sending notifications)
|
||||
All agents get access to these MCP servers:
|
||||
- roboco-task: Task management
|
||||
- roboco-message: Channel messaging
|
||||
- roboco-journal: Personal journaling
|
||||
- roboco-notify: Notifications (read for all, send for PMs)
|
||||
- roboco-optimal: Knowledge base, RAG, semantic search
|
||||
- roboco-git: Git operations (role-based at handler level)
|
||||
- roboco-a2a: Agent-to-Agent protocol
|
||||
- roboco-test: Test/lint/format tools
|
||||
|
||||
Git context is passed to MCP servers so git tools can use defaults.
|
||||
"""
|
||||
@@ -679,6 +689,34 @@ class AgentOrchestrator:
|
||||
"env": mcp_env,
|
||||
}
|
||||
|
||||
# A2A server - Agent-to-Agent protocol for cross-cell coordination
|
||||
# All agents can discover and request help from other agents
|
||||
mcp_servers["roboco-a2a"] = {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"python",
|
||||
"-m",
|
||||
"roboco.mcp.a2a_server",
|
||||
agent_id,
|
||||
],
|
||||
"env": mcp_env,
|
||||
}
|
||||
|
||||
# Test server - run tests, lint, format, typecheck, build
|
||||
# Role-based permissions enforced at handler level
|
||||
mcp_servers["roboco-test"] = {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"python",
|
||||
"-m",
|
||||
"roboco.mcp.test.test_server",
|
||||
agent_id,
|
||||
],
|
||||
"env": mcp_env,
|
||||
}
|
||||
|
||||
config: dict[str, Any] = {"mcpServers": mcp_servers}
|
||||
|
||||
# Write to shared config directory (mounted in both orchestrator and agents)
|
||||
|
||||
@@ -159,6 +159,9 @@ class OptimalService:
|
||||
self._plugins: dict[IndexType, BaseIndexPlugin] = {}
|
||||
self._prompt_templates: dict[str, dict[str, Any]] = {}
|
||||
self._indexing_task: Any = None # Background indexing task
|
||||
self._periodic_update_task: Any = None # Periodic update task
|
||||
self._file_mtimes: dict[str, float] = {} # Track file modification times
|
||||
self._docs_root: Path | None = None # Cached docs root path
|
||||
|
||||
async def initialize(self) -> None:
|
||||
"""
|
||||
@@ -258,6 +261,7 @@ class OptimalService:
|
||||
|
||||
Runs auto-indexing in background without blocking API startup.
|
||||
Logs errors but doesn't crash the service if Ollama is unavailable.
|
||||
After startup indexing, starts periodic update task if enabled.
|
||||
"""
|
||||
try:
|
||||
report = await self._auto_index_on_startup()
|
||||
@@ -280,6 +284,9 @@ class OptimalService:
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
# Start periodic update task if enabled
|
||||
await self._start_periodic_update()
|
||||
|
||||
async def _auto_index_on_startup(self, force: bool = False) -> AutoIndexReport:
|
||||
"""
|
||||
Auto-index documentation on startup.
|
||||
@@ -331,6 +338,7 @@ class OptimalService:
|
||||
for path in possible_docs_roots:
|
||||
if path.exists() and path.is_dir():
|
||||
docs_root = path
|
||||
self._docs_root = path # Cache for periodic updates
|
||||
break
|
||||
|
||||
if docs_root is None:
|
||||
@@ -391,6 +399,12 @@ class OptimalService:
|
||||
await self.index_documentation([str(md_file)])
|
||||
report.successful += 1
|
||||
|
||||
# Track mtime for periodic update detection
|
||||
import contextlib
|
||||
|
||||
with contextlib.suppress(OSError):
|
||||
self._file_mtimes[str(md_file)] = md_file.stat().st_mtime
|
||||
|
||||
logger.debug(f"Indexed {name} file", file=str(md_file))
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
@@ -410,8 +424,129 @@ class OptimalService:
|
||||
)
|
||||
return report
|
||||
|
||||
# =========================================================================
|
||||
# PERIODIC UPDATE (File Change Detection)
|
||||
# =========================================================================
|
||||
|
||||
async def _start_periodic_update(self) -> None:
|
||||
"""Start periodic update task if enabled in config."""
|
||||
import asyncio
|
||||
|
||||
from roboco.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
if not settings.rag_auto_update_enabled:
|
||||
logger.info("RAG auto-update disabled in config")
|
||||
return
|
||||
|
||||
interval = settings.rag_auto_update_interval
|
||||
logger.info(
|
||||
"Starting RAG periodic update task",
|
||||
interval_seconds=interval,
|
||||
)
|
||||
self._periodic_update_task = asyncio.create_task(
|
||||
self._periodic_update_loop(interval)
|
||||
)
|
||||
|
||||
async def _periodic_update_loop(self, interval: int) -> None:
|
||||
"""Background loop that checks for file changes periodically."""
|
||||
import asyncio
|
||||
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(interval)
|
||||
await self._check_for_updates()
|
||||
except asyncio.CancelledError:
|
||||
logger.info("Periodic update task cancelled")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error("Periodic update check failed", error=str(e))
|
||||
# Continue running despite errors
|
||||
|
||||
def _resolve_docs_root(self) -> Path | None:
|
||||
"""Resolve and cache the docs root directory."""
|
||||
if self._docs_root is not None:
|
||||
return self._docs_root
|
||||
|
||||
possible_docs_roots = [
|
||||
Path("/app/docs"),
|
||||
Path(__file__).parent.parent.parent / "docs",
|
||||
Path.cwd() / "docs",
|
||||
]
|
||||
for path in possible_docs_roots:
|
||||
if path.exists() and path.is_dir():
|
||||
self._docs_root = path
|
||||
return path
|
||||
return None
|
||||
|
||||
async def _check_for_updates(self) -> None:
|
||||
"""Scan for new or modified files and index them."""
|
||||
docs_root = self._resolve_docs_root()
|
||||
if docs_root is None:
|
||||
return
|
||||
|
||||
rag_dir = docs_root / "rag"
|
||||
if not rag_dir.exists():
|
||||
return
|
||||
|
||||
new_files: list[Path] = []
|
||||
modified_files: list[Path] = []
|
||||
|
||||
for md_file in rag_dir.rglob("*.md"):
|
||||
file_path = str(md_file)
|
||||
try:
|
||||
current_mtime = md_file.stat().st_mtime
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
if file_path not in self._file_mtimes:
|
||||
new_files.append(md_file)
|
||||
self._file_mtimes[file_path] = current_mtime
|
||||
elif current_mtime > self._file_mtimes[file_path]:
|
||||
modified_files.append(md_file)
|
||||
self._file_mtimes[file_path] = current_mtime
|
||||
|
||||
files_to_index = new_files + modified_files
|
||||
if not files_to_index:
|
||||
return
|
||||
|
||||
logger.info(
|
||||
"Detected file changes, re-indexing",
|
||||
new_count=len(new_files),
|
||||
modified_count=len(modified_files),
|
||||
)
|
||||
|
||||
indexed = 0
|
||||
for md_file in files_to_index:
|
||||
try:
|
||||
await self.index_documentation([str(md_file)])
|
||||
indexed += 1
|
||||
logger.debug("Re-indexed file", file=str(md_file))
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to re-index file",
|
||||
file=str(md_file),
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
if indexed > 0:
|
||||
logger.info(
|
||||
"Periodic update complete",
|
||||
indexed=indexed,
|
||||
total_files=len(files_to_index),
|
||||
)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Cleanup resources."""
|
||||
import asyncio
|
||||
import contextlib
|
||||
|
||||
# Cancel periodic update task
|
||||
if self._periodic_update_task and not self._periodic_update_task.done():
|
||||
self._periodic_update_task.cancel()
|
||||
with contextlib.suppress(asyncio.CancelledError):
|
||||
await self._periodic_update_task
|
||||
|
||||
for plugin in self._plugins.values():
|
||||
await plugin.close()
|
||||
self._plugins.clear()
|
||||
|
||||
@@ -76,11 +76,12 @@ class ConversationsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list:
|
||||
"""Search conversations in a specific channel."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"channel_id": str(channel_id)},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
async def search_by_session(
|
||||
self,
|
||||
@@ -89,8 +90,9 @@ class ConversationsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list:
|
||||
"""Search conversations in a specific session."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"session_id": str(session_id)},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
@@ -125,10 +125,10 @@ class DecisionsIndexPlugin(BaseIndexPlugin):
|
||||
Returns:
|
||||
List of similar past decisions
|
||||
"""
|
||||
results = await self.search(query=topic, top_k=top_k)
|
||||
outcome = await self.search(query=topic, top_k=top_k)
|
||||
|
||||
decisions = []
|
||||
for result in results:
|
||||
for result in outcome.results:
|
||||
if result.score >= threshold:
|
||||
decisions.append(
|
||||
Decision(
|
||||
@@ -176,11 +176,12 @@ class DecisionsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list[SearchResult]:
|
||||
"""Search decisions by scope (team or org)."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"scope": scope},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
async def search_by_agent(
|
||||
self,
|
||||
@@ -189,8 +190,9 @@ class DecisionsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list[SearchResult]:
|
||||
"""Search decisions made by a specific agent."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"agent_id": str(agent_id)},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
@@ -113,7 +113,8 @@ class ErrorsIndexPlugin(BaseIndexPlugin):
|
||||
if context:
|
||||
query += f" Context: {context}"
|
||||
|
||||
results = await self.search(query=query, top_k=top_k)
|
||||
outcome = await self.search(query=query, top_k=top_k)
|
||||
results = outcome.results
|
||||
|
||||
# Boost results where worked=True
|
||||
for result in results:
|
||||
@@ -129,8 +130,9 @@ class ErrorsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list[SearchResult]:
|
||||
"""Search errors from a specific team."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"team": team},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
@@ -72,11 +72,12 @@ class JournalsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list:
|
||||
"""Search journal entries for a specific agent."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"agent_id": str(agent_id)},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
async def search_by_type(
|
||||
self,
|
||||
@@ -85,8 +86,9 @@ class JournalsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list:
|
||||
"""Search journal entries of a specific type."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"entry_type": entry_type},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
@@ -146,7 +146,8 @@ class LearningsIndexPlugin(BaseIndexPlugin):
|
||||
if shareable_only:
|
||||
filters["shareable"] = True
|
||||
|
||||
return await self.search(query=query, top_k=top_k, filters=filters)
|
||||
outcome = await self.search(query=query, top_k=top_k, filters=filters)
|
||||
return outcome.results
|
||||
|
||||
async def get_learnings_by_category(
|
||||
self,
|
||||
@@ -154,11 +155,12 @@ class LearningsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 20,
|
||||
) -> list[SearchResult]:
|
||||
"""Get all learnings in a category."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=f"learnings about {category}",
|
||||
top_k=top_k,
|
||||
filters={"category": category},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
async def get_learnings_by_role(
|
||||
self,
|
||||
@@ -166,11 +168,12 @@ class LearningsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 20,
|
||||
) -> list[SearchResult]:
|
||||
"""Get learnings from agents with a specific role."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=f"learnings from {agent_role}",
|
||||
top_k=top_k,
|
||||
filters={"agent_role": agent_role},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
async def get_team_learnings(
|
||||
self,
|
||||
@@ -178,8 +181,9 @@ class LearningsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 20,
|
||||
) -> list[SearchResult]:
|
||||
"""Get learnings from a specific team."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query="team learnings",
|
||||
top_k=top_k,
|
||||
filters={"team": team},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
@@ -159,7 +159,8 @@ class ReviewsIndexPlugin(BaseIndexPlugin):
|
||||
query = f"Review for file: {file_path}"
|
||||
|
||||
# Search without filters first
|
||||
results = await self.search(query=query, top_k=top_k * 2)
|
||||
outcome = await self.search(query=query, top_k=top_k * 2)
|
||||
results = outcome.results
|
||||
|
||||
# Filter by pattern similarity
|
||||
filtered = []
|
||||
@@ -181,11 +182,12 @@ class ReviewsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list[SearchResult]:
|
||||
"""Search reviews by type (code, security, performance)."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"review_type": review_type},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
async def search_by_severity(
|
||||
self,
|
||||
@@ -194,8 +196,9 @@ class ReviewsIndexPlugin(BaseIndexPlugin):
|
||||
top_k: int = 5,
|
||||
) -> list[SearchResult]:
|
||||
"""Search reviews by severity."""
|
||||
return await self.search(
|
||||
outcome = await self.search(
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
filters={"severity": severity},
|
||||
)
|
||||
return outcome.results
|
||||
|
||||
@@ -265,7 +265,8 @@ class StandardsIndexPlugin(BaseIndexPlugin):
|
||||
if severity:
|
||||
filters["severity"] = severity
|
||||
|
||||
return await self.search(query=query, top_k=top_k, filters=filters)
|
||||
outcome = await self.search(query=query, top_k=top_k, filters=filters)
|
||||
return outcome.results
|
||||
|
||||
async def validate_against_standards(
|
||||
self,
|
||||
|
||||
@@ -454,18 +454,14 @@ class OllamaEmbedder:
|
||||
f"{self.base_url}/api/embed",
|
||||
json={"model": self.model, "input": batch},
|
||||
)
|
||||
return self._handle_embed_response(
|
||||
response, input_count=len(batch)
|
||||
)
|
||||
return self._handle_embed_response(response, input_count=len(batch))
|
||||
|
||||
except httpx.ConnectError as e:
|
||||
last_error = OllamaConnectionError(
|
||||
f"Cannot connect to Ollama at {self.base_url}: {e}"
|
||||
)
|
||||
except httpx.TimeoutException as e:
|
||||
last_error = OllamaConnectionError(
|
||||
f"Ollama request timed out: {e}"
|
||||
)
|
||||
last_error = OllamaConnectionError(f"Ollama request timed out: {e}")
|
||||
except (OllamaModelError, OllamaEmbedderError):
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -653,9 +649,7 @@ class OllamaEmbedder:
|
||||
f"Cannot connect to Ollama at {self.base_url}: {e}"
|
||||
)
|
||||
except httpx.TimeoutException as e:
|
||||
last_error = OllamaConnectionError(
|
||||
f"Ollama request timed out: {e}"
|
||||
)
|
||||
last_error = OllamaConnectionError(f"Ollama request timed out: {e}")
|
||||
except (OllamaModelError, OllamaEmbedderError):
|
||||
raise
|
||||
except Exception as e:
|
||||
|
||||
+129
-154
@@ -1722,8 +1722,10 @@ class TaskService(BaseService):
|
||||
)
|
||||
|
||||
if ready_for_pm:
|
||||
# Both conditions met - transition to PM review
|
||||
task.status = TaskStatus.AWAITING_PM_REVIEW
|
||||
# Both conditions met - transition to PM review (validated)
|
||||
self._validate_and_set_status(
|
||||
task, TaskStatus.AWAITING_PM_REVIEW, "documenter"
|
||||
)
|
||||
# Clear assignment so PM can claim the task for review
|
||||
task.assigned_to = None
|
||||
self.log.info(
|
||||
@@ -1908,178 +1910,89 @@ class TaskService(BaseService):
|
||||
)
|
||||
return task
|
||||
|
||||
async def complete( # noqa: PLR0911, PLR0912, PLR0915
|
||||
self,
|
||||
task_id: UUID,
|
||||
agent_id: UUID | None = None,
|
||||
force_with_cancelled: bool = False,
|
||||
justification: str | None = None,
|
||||
) -> TaskTable | None:
|
||||
"""
|
||||
Mark task as completed (PM only).
|
||||
|
||||
Approval hierarchy:
|
||||
1. Cell PM reviews → reassigns to Main PM (same awaiting_pm_review state)
|
||||
2. Main PM reviews leaf task → completes
|
||||
3. Main PM reviews parent task (all descendants terminal) → escalates to CEO
|
||||
|
||||
PM Override for cancelled subtasks:
|
||||
Use force_with_cancelled=True with justification to complete despite
|
||||
cancelled subtasks. Only works if ALL non-completed children are cancelled.
|
||||
|
||||
Args:
|
||||
task_id: The task to complete
|
||||
agent_id: Optional agent UUID - if provided, allows PM to complete
|
||||
their own in_progress tasks
|
||||
force_with_cancelled: Override cancelled subtask check
|
||||
justification: Required when force_with_cancelled=True
|
||||
|
||||
Returns:
|
||||
The completed task or None if completion not allowed
|
||||
"""
|
||||
task = await self.get(task_id)
|
||||
if not task:
|
||||
async def _get_completing_agent_role(self, agent_id: UUID | None) -> str | None:
|
||||
"""Get the role of the completing agent."""
|
||||
if not agent_id:
|
||||
return None
|
||||
agent_result = await self.session.execute(
|
||||
select(AgentTable).where(AgentTable.id == agent_id)
|
||||
)
|
||||
agent = agent_result.scalar_one_or_none()
|
||||
if agent and agent.role:
|
||||
return agent.role.value if hasattr(agent.role, "value") else str(agent.role)
|
||||
return None
|
||||
|
||||
# Get the completing agent's role
|
||||
completing_agent_role = None
|
||||
if agent_id:
|
||||
agent_result = await self.session.execute(
|
||||
select(AgentTable).where(AgentTable.id == agent_id)
|
||||
)
|
||||
completing_agent = agent_result.scalar_one_or_none()
|
||||
if completing_agent and completing_agent.role:
|
||||
completing_agent_role = (
|
||||
completing_agent.role.value
|
||||
if hasattr(completing_agent.role, "value")
|
||||
else str(completing_agent.role)
|
||||
)
|
||||
|
||||
# Check if PM is completing their own task (assigned to them)
|
||||
is_own_task = agent_id and task.assigned_to == agent_id
|
||||
|
||||
# Two valid completion paths:
|
||||
# 1. Normal workflow: task in awaiting_pm_review (dev → QA → docs → PM)
|
||||
# 2. PM's own work: task in in_progress AND assigned to this PM
|
||||
def _is_valid_completion_status(
|
||||
self, task: TaskTable, agent_id: UUID | None
|
||||
) -> bool:
|
||||
"""Check if task is in a valid status for completion."""
|
||||
if task.status == TaskStatus.AWAITING_PM_REVIEW:
|
||||
pass # Normal completion of developer work
|
||||
elif task.status == TaskStatus.IN_PROGRESS and is_own_task:
|
||||
pass # PM completing their own task
|
||||
else:
|
||||
return True
|
||||
is_own_task = agent_id and task.assigned_to == agent_id
|
||||
return task.status == TaskStatus.IN_PROGRESS and bool(is_own_task)
|
||||
|
||||
async def _handle_cell_pm_escalation(
|
||||
self, task: TaskTable, task_id: UUID, agent_id: UUID | None
|
||||
) -> TaskTable | None:
|
||||
"""Handle Cell PM escalation to Main PM. Returns task if escalated."""
|
||||
main_pm_result = await self.session.execute(
|
||||
select(AgentTable).where(AgentTable.role == AgentRole.MAIN_PM)
|
||||
)
|
||||
main_pm = main_pm_result.scalar_one_or_none()
|
||||
if not main_pm:
|
||||
self.log.warning(
|
||||
"Cannot complete task - invalid status for completion",
|
||||
task_id=str(task_id),
|
||||
current_status=task.status.value,
|
||||
is_own_task=is_own_task,
|
||||
"No Main PM found - proceeding with completion", task_id=str(task_id)
|
||||
)
|
||||
return None
|
||||
|
||||
# Check ALL descendants (recursive - children, grandchildren, etc.)
|
||||
task.assigned_to = cast("Any", main_pm.id)
|
||||
await self.session.flush()
|
||||
await self._emit_task_event(
|
||||
EventType.TASK_ESCALATED_TO_MAIN_PM,
|
||||
task_id,
|
||||
{
|
||||
"main_pm_id": str(main_pm.id),
|
||||
"cell_pm_id": str(agent_id) if agent_id else None,
|
||||
},
|
||||
)
|
||||
self.log.info(
|
||||
"Cell PM approved - escalating to Main PM",
|
||||
task_id=str(task_id),
|
||||
main_pm_id=str(main_pm.id),
|
||||
)
|
||||
return task
|
||||
|
||||
async def _validate_completion_prerequisites(
|
||||
self, task: TaskTable, task_id: UUID, agent_id: UUID | None
|
||||
) -> list[TaskTable] | None:
|
||||
"""Validate task can be completed. Returns descendants or None."""
|
||||
if not self._is_valid_completion_status(task, agent_id):
|
||||
self.log.warning("Cannot complete - invalid status", task_id=str(task_id))
|
||||
return None
|
||||
|
||||
all_descendants = await self.get_all_descendants(task_id)
|
||||
incomplete_descendants = [
|
||||
incomplete = [
|
||||
st
|
||||
for st in all_descendants
|
||||
if st.status not in (TaskStatus.COMPLETED, TaskStatus.CANCELLED)
|
||||
]
|
||||
|
||||
if incomplete_descendants:
|
||||
# Block completion - some descendants are still in progress
|
||||
if incomplete:
|
||||
self.log.warning(
|
||||
"Cannot complete task - incomplete descendants exist",
|
||||
task_id=str(task_id),
|
||||
incomplete_count=len(incomplete_descendants),
|
||||
incomplete_ids=[str(st.id) for st in incomplete_descendants[:5]],
|
||||
"Cannot complete - incomplete descendants", task_id=str(task_id)
|
||||
)
|
||||
return None
|
||||
return all_descendants
|
||||
|
||||
# APPROVAL HIERARCHY: Cell PM → Main PM → CEO
|
||||
if task.status == TaskStatus.AWAITING_PM_REVIEW:
|
||||
# Cell PM reviewed - escalate to Main PM
|
||||
if completing_agent_role == "cell_pm":
|
||||
main_pm_result = await self.session.execute(
|
||||
select(AgentTable).where(AgentTable.role == AgentRole.MAIN_PM)
|
||||
)
|
||||
main_pm = main_pm_result.scalar_one_or_none()
|
||||
if main_pm:
|
||||
task.assigned_to = cast("Any", main_pm.id)
|
||||
await self.session.flush()
|
||||
|
||||
# Emit event for Main PM notification
|
||||
await self._emit_task_event(
|
||||
EventType.TASK_ESCALATED_TO_MAIN_PM,
|
||||
task_id,
|
||||
{
|
||||
"main_pm_id": str(main_pm.id),
|
||||
"cell_pm_id": str(agent_id) if agent_id else None,
|
||||
},
|
||||
)
|
||||
|
||||
self.log.info(
|
||||
"Cell PM approved - escalating to Main PM",
|
||||
task_id=str(task_id),
|
||||
main_pm_id=str(main_pm.id),
|
||||
)
|
||||
return task # Return without completing - Main PM needs to review
|
||||
else:
|
||||
self.log.warning(
|
||||
"No Main PM found - proceeding with completion",
|
||||
task_id=str(task_id),
|
||||
)
|
||||
|
||||
# Main PM reviewed - check if parent task needs CEO approval
|
||||
if completing_agent_role == "main_pm" and all_descendants:
|
||||
# Parent task with descendants - needs CEO approval
|
||||
self.log.info(
|
||||
"Main PM approved parent task - escalating to CEO",
|
||||
task_id=str(task_id),
|
||||
descendant_count=len(all_descendants),
|
||||
)
|
||||
return await self.escalate_to_ceo(task_id, "main_pm")
|
||||
|
||||
# Check for cancelled descendants (only matters if force override requested)
|
||||
cancelled_descendants = [
|
||||
st for st in all_descendants if st.status == TaskStatus.CANCELLED
|
||||
]
|
||||
|
||||
if cancelled_descendants and not force_with_cancelled:
|
||||
self.log.warning(
|
||||
"Cannot complete - cancelled descendants exist",
|
||||
task_id=str(task_id),
|
||||
cancelled_count=len(cancelled_descendants),
|
||||
hint="use force_with_cancelled (CEO only)",
|
||||
)
|
||||
return None
|
||||
|
||||
if force_with_cancelled and cancelled_descendants:
|
||||
if not justification:
|
||||
self.log.warning(
|
||||
"Cannot force complete - justification required",
|
||||
task_id=str(task_id),
|
||||
)
|
||||
return None
|
||||
# Log the CEO override
|
||||
self.log.info(
|
||||
"CEO override: completing task with cancelled descendants",
|
||||
task_id=str(task_id),
|
||||
agent_id=str(agent_id) if agent_id else None,
|
||||
justification=justification,
|
||||
cancelled_descendant_ids=[str(st.id) for st in cancelled_descendants],
|
||||
)
|
||||
|
||||
task.completed_at = datetime.now(UTC)
|
||||
# Validate transition with PM role requirement
|
||||
self._validate_and_set_status(task, TaskStatus.COMPLETED, "cell_pm")
|
||||
await self.session.flush()
|
||||
|
||||
# RAG auto-indexing hooks (fire-and-forget)
|
||||
# 1. Extract completion learnings
|
||||
async def _trigger_completion_hooks(
|
||||
self, task: TaskTable, agent_id: UUID | None
|
||||
) -> None:
|
||||
"""Trigger background RAG indexing hooks after completion."""
|
||||
bg_task = asyncio.create_task(
|
||||
self._extract_completion_learnings(task, agent_id)
|
||||
)
|
||||
self._background_tasks.add(bg_task)
|
||||
bg_task.add_done_callback(self._background_tasks.discard)
|
||||
|
||||
# 2. Index code changes from commits
|
||||
if task.commits:
|
||||
code_task = asyncio.create_task(
|
||||
self._index_code_changes_background(
|
||||
@@ -2091,7 +2004,6 @@ class TaskService(BaseService):
|
||||
self._background_tasks.add(code_task)
|
||||
code_task.add_done_callback(self._background_tasks.discard)
|
||||
|
||||
# 3. Detect and index decisions from notes
|
||||
if task.dev_notes:
|
||||
decision_task = asyncio.create_task(
|
||||
self._index_decisions_background(
|
||||
@@ -2105,7 +2017,61 @@ class TaskService(BaseService):
|
||||
self._background_tasks.add(decision_task)
|
||||
decision_task.add_done_callback(self._background_tasks.discard)
|
||||
|
||||
# Unblock any tasks waiting on this one
|
||||
async def complete(
|
||||
self,
|
||||
task_id: UUID,
|
||||
agent_id: UUID | None = None,
|
||||
force_with_cancelled: bool = False,
|
||||
justification: str | None = None,
|
||||
) -> TaskTable | None:
|
||||
"""
|
||||
Mark task as completed (PM only).
|
||||
|
||||
Approval hierarchy:
|
||||
1. Cell PM reviews → reassigns to Main PM (same awaiting_pm_review state)
|
||||
2. Main PM reviews leaf task → completes
|
||||
3. Main PM reviews parent task (all descendants terminal) → escalates to CEO
|
||||
"""
|
||||
task = await self.get(task_id)
|
||||
if not task:
|
||||
return None
|
||||
|
||||
completing_agent_role = await self._get_completing_agent_role(agent_id)
|
||||
all_descendants = await self._validate_completion_prerequisites(
|
||||
task, task_id, agent_id
|
||||
)
|
||||
if all_descendants is None:
|
||||
return None
|
||||
|
||||
# APPROVAL HIERARCHY: Cell PM → Main PM → CEO
|
||||
if task.status == TaskStatus.AWAITING_PM_REVIEW:
|
||||
if completing_agent_role == "cell_pm":
|
||||
escalated = await self._handle_cell_pm_escalation(
|
||||
task, task_id, agent_id
|
||||
)
|
||||
if escalated:
|
||||
return escalated
|
||||
if completing_agent_role == "main_pm" and all_descendants:
|
||||
self.log.info(
|
||||
"Main PM approved parent - escalating to CEO", task_id=str(task_id)
|
||||
)
|
||||
return await self.escalate_to_ceo(task_id, "main_pm")
|
||||
|
||||
# Handle cancelled descendants - require force flag and justification
|
||||
cancelled = [st for st in all_descendants if st.status == TaskStatus.CANCELLED]
|
||||
if cancelled and (not force_with_cancelled or not justification):
|
||||
self.log.warning(
|
||||
"Cannot complete - cancelled descendants", task_id=str(task_id)
|
||||
)
|
||||
return None
|
||||
|
||||
task.completed_at = datetime.now(UTC)
|
||||
self._validate_and_set_status(
|
||||
task, TaskStatus.COMPLETED, completing_agent_role or "cell_pm"
|
||||
)
|
||||
await self.session.flush()
|
||||
|
||||
await self._trigger_completion_hooks(task, agent_id)
|
||||
await self._unblock_dependents(task_id)
|
||||
return task
|
||||
|
||||
@@ -2148,6 +2114,15 @@ class TaskService(BaseService):
|
||||
)
|
||||
return None
|
||||
|
||||
# Only parent tasks can be escalated to CEO (not subtasks)
|
||||
if task.parent_task_id:
|
||||
self.log.warning(
|
||||
"Cannot escalate subtask to CEO - only parent tasks allowed",
|
||||
task_id=str(task_id),
|
||||
parent_task_id=str(task.parent_task_id),
|
||||
)
|
||||
return None
|
||||
|
||||
# Store escalation notes
|
||||
if notes:
|
||||
existing_context = task.quick_context or ""
|
||||
|
||||
@@ -874,11 +874,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.20.1"
|
||||
version = "3.20.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/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" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1339,19 +1339,19 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lance-namespace"
|
||||
version = "0.4.0"
|
||||
version = "0.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "lance-namespace-urllib3-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/8d/b117539252afc81b0fb94301e5543516af8594a70242ef247bc88c03cbdc/lance_namespace-0.4.0.tar.gz", hash = "sha256:aedfb5f4413ead9c5f0d2a351fe47b0b68a1dec0dd4331a88f54bce3491f630f", size = 9827, upload-time = "2025-12-21T16:07:51.349Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/8d/1e6f2e32e7c782938583c3ceaea301f85b6a2aff005b43a5b3e95f876e3e/lance_namespace-0.4.3.tar.gz", hash = "sha256:c24fc810d967b59b42894b1b4282a964331807f38172d574d5d61ffef77f5520", size = 9827, upload-time = "2026-01-01T07:54:35.502Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/fe/edbeb9ae7408685e90b2f0609c2f84bc3ef2f65d82bb4dce394de6d9c317/lance_namespace-0.4.0-py3-none-any.whl", hash = "sha256:7d91ee199a9864535ea17bd41787726c06b7ec8efbf06f7275bc54ea9998264f", size = 11701, upload-time = "2025-12-21T16:07:50.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/cf/31d478e291ca879e846e67f0cc0700df5fe63373d3897374ee4f5a035221/lance_namespace-0.4.3-py3-none-any.whl", hash = "sha256:27dfb93181673b9fdf3b48a60e8075de43429946c91d913a889964c6d2d01f00", size = 11701, upload-time = "2026-01-01T07:54:36.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lance-namespace-urllib3-client"
|
||||
version = "0.4.0"
|
||||
version = "0.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
@@ -1359,14 +1359,14 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/a2/53643e7ea756cd8c4275219f555a554db340d1e4e7366df39a79d9bd092d/lance_namespace_urllib3_client-0.4.0.tar.gz", hash = "sha256:896bf9336f5b14f5acc0d45ca956e291e0fcc2a0e56c1efe52723c23ae3a3296", size = 154577, upload-time = "2025-12-21T16:07:53.443Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c1/68/17502c6fde1d758d98903551fe88d73a55e5fc9a68c605829206f9611bbb/lance_namespace_urllib3_client-0.4.3.tar.gz", hash = "sha256:4cea0c78692debf5722f953671503178aa7fc0e72a80bea28243a9c093c68944", size = 157358, upload-time = "2026-01-01T07:54:33.115Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/1f/050c1ed613b0ec017fa3b85d35d52658ead1158d95a092c1b83578d39ab5/lance_namespace_urllib3_client-0.4.0-py3-none-any.whl", hash = "sha256:858b44b4b34b4ae8f4d905e10a89e4b14f08213dca9dd6751be09cfa03a7dbdc", size = 261516, upload-time = "2025-12-21T16:07:51.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/bc/f30dd5812642a0720092723029170b660d9fd0a6018476927714694c97a9/lance_namespace_urllib3_client-0.4.3-py3-none-any.whl", hash = "sha256:bc32e80e6cc92b12fa9287632d776dadd488363938d117f815c9c4450e482ad6", size = 268625, upload-time = "2026-01-01T07:54:34.341Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lancedb"
|
||||
version = "0.26.0"
|
||||
version = "0.26.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
@@ -1378,53 +1378,53 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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://files.pythonhosted.org/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://files.pythonhosted.org/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://files.pythonhosted.org/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://files.pythonhosted.org/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" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/b5/110651418ceb1fa4ff2eb74ce4bad911ecf49dc765b134f0201d5564aab8/lancedb-0.26.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:b1c4389134ede49e4be0497b9719f573f447e627426bb9e6fc1b642db11fb22d", size = 43416143, upload-time = "2026-01-02T17:57:07.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/8a/b48a14281d7875e5bfccf22d911d9e1fa019c1fe7b805d290a4449e3cf60/lancedb-0.26.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07abd18e0aa4730442d0361bab4491ad469de14f9087c3542e56ca6d7fcda473", size = 45302392, upload-time = "2026-01-02T18:04:55.963Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/d0/8f6bc531f290206c7a0061236928710506598a2591ff1fcaea477fc52e7f/lancedb-0.26.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df8eb631519c6ede9975099bea187ea25a09e4617a421fe19e5e1613651cd62f", size = 48372676, upload-time = "2026-01-02T18:08:12.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/13/d8db83335ddf28afe1fb814ca995da7f67826f337d547e54471d7d425dd1/lancedb-0.26.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2941c9f8aa22244002307c4da5d19f12ab77dcb0569eb4f8a48b60e9c4fdee79", size = 45318771, upload-time = "2026-01-02T18:04:26.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/94/10e9d4b5ba49eeba72024d310dc42e0c24feb8d5676f48e989198121a8a0/lancedb-0.26.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0d5f125b98836a49095c492085f5ecf3a78906fafcab59c367d9347eb372a4cc", size = 48425627, upload-time = "2026-01-02T18:11:57.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/5d/d7a834ce8dd9c5e6ef7a0e308c7de5f87bb8f04c0944a1bea617d9d42dc7/lancedb-0.26.1-cp39-abi3-win_amd64.whl", hash = "sha256:9338d34c6e7472c97e49fd6b2638b29d3d087e8b002d92cafdbb46a8b0b1480e", size = 53214501, upload-time = "2026-01-02T22:34:06.836Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librt"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/8a/071f6628363d83e803d4783e0cd24fb9c5b798164300fcfaaa47c30659c0/librt-0.7.5.tar.gz", hash = "sha256:de4221a1181fa9c8c4b5f35506ed6f298948f44003d84d2a8b9885d7e01e6cfa", size = 145868, upload-time = "2025-12-25T03:53:16.039Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/29/47f29026ca17f35cf299290292d5f8331f5077364974b7675a353179afa2/librt-0.7.7.tar.gz", hash = "sha256:81d957b069fed1890953c3b9c3895c7689960f233eea9a1d9607f71ce7f00b2c", size = 145910, upload-time = "2026-01-01T23:52:22.87Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/9a/8f61e16de0ff76590af893cfb5b1aa5fa8b13e5e54433d0809c7033f59ed/librt-0.7.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b1795c4b2789b458fa290059062c2f5a297ddb28c31e704d27e161386469691a", size = 55750, upload-time = "2025-12-25T03:52:26.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/7c/a8a883804851a066f301e0bad22b462260b965d5c9e7fe3c5de04e6f91f8/librt-0.7.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2fcbf2e135c11f721193aa5f42ba112bb1046afafbffd407cbc81d8d735c74d0", size = 57170, upload-time = "2025-12-25T03:52:27.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/5d/b3b47facf5945be294cf8a835b03589f70ee0e791522f99ec6782ed738b3/librt-0.7.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c039bbf79a9a2498404d1ae7e29a6c175e63678d7a54013a97397c40aee026c5", size = 165834, upload-time = "2025-12-25T03:52:29.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/b6/b26910cd0a4e43e5d02aacaaea0db0d2a52e87660dca08293067ee05601a/librt-0.7.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3919c9407faeeee35430ae135e3a78acd4ecaaaa73767529e2c15ca1d73ba325", size = 174820, upload-time = "2025-12-25T03:52:30.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/a3/81feddd345d4c869b7a693135a462ae275f964fcbbe793d01ea56a84c2ee/librt-0.7.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26b46620e1e0e45af510d9848ea0915e7040605dd2ae94ebefb6c962cbb6f7ec", size = 189609, upload-time = "2025-12-25T03:52:31.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/a9/31310796ef4157d1d37648bf4a3b84555319f14cee3e9bad7bdd7bfd9a35/librt-0.7.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9bbb8facc5375476d392990dd6a71f97e4cb42e2ac66f32e860f6e47299d5e89", size = 184589, upload-time = "2025-12-25T03:52:32.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/22/da3900544cb0ac6ab7a2857850158a0a093b86f92b264aa6c4a4f2355ff3/librt-0.7.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e9e9c988b5ffde7be02180f864cbd17c0b0c1231c235748912ab2afa05789c25", size = 178251, upload-time = "2025-12-25T03:52:33.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/77/78e02609846e78b9b8c8e361753b3dbac9a07e6d5b567fe518de9e074ab0/librt-0.7.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:edf6b465306215b19dbe6c3fb63cf374a8f3e1ad77f3b4c16544b83033bbb67b", size = 199852, upload-time = "2025-12-25T03:52:34.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/25/05706f6b346429c951582f1b3561f4d5e1418d0d7ba1a0c181237cd77b3b/librt-0.7.5-cp313-cp313-win32.whl", hash = "sha256:060bde69c3604f694bd8ae21a780fe8be46bb3dbb863642e8dfc75c931ca8eee", size = 43250, upload-time = "2025-12-25T03:52:35.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/59/c38677278ac0b9ae1afc611382ef6c9ea87f52ad257bd3d8d65f0eacdc6a/librt-0.7.5-cp313-cp313-win_amd64.whl", hash = "sha256:a82d5a0ee43aeae2116d7292c77cc8038f4841830ade8aa922e098933b468b9e", size = 49421, upload-time = "2025-12-25T03:52:36.895Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/47/1d71113df4a81de5fdfbd3d7244e05d3d67e89f25455c3380ca50b92741e/librt-0.7.5-cp313-cp313-win_arm64.whl", hash = "sha256:3c98a8d0ac9e2a7cb8ff8c53e5d6e8d82bfb2839abf144fdeaaa832f2a12aa45", size = 42827, upload-time = "2025-12-25T03:52:37.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/ae/8635b4efdc784220f1378be640d8b1a794332f7f6ea81bb4859bf9d18aa7/librt-0.7.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9937574e6d842f359b8585903d04f5b4ab62277a091a93e02058158074dc52f2", size = 55191, upload-time = "2025-12-25T03:52:38.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/11/ed7ef6955dc2032af37db9b0b31cd5486a138aa792e1bb9e64f0f4950e27/librt-0.7.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5cd3afd71e9bc146203b6c8141921e738364158d4aa7cdb9a874e2505163770f", size = 56894, upload-time = "2025-12-25T03:52:39.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/f1/02921d4a66a1b5dcd0493b89ce76e2762b98c459fe2ad04b67b2ea6fdd39/librt-0.7.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cffa3ef0af29687455161cb446eff059bf27607f95163d6a37e27bcb37180f6", size = 163726, upload-time = "2025-12-25T03:52:40.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/87/27df46d2756fcb7a82fa7f6ca038a0c6064c3e93ba65b0b86fbf6a4f76a2/librt-0.7.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82f3f088482e2229387eadf8215c03f7726d56f69cce8c0c40f0795aebc9b361", size = 172470, upload-time = "2025-12-25T03:52:42.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/a9/e65a35e5d423639f4f3d8e17301ff13cc41c2ff97677fe9c361c26dbfbb7/librt-0.7.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7aa33153a5bb0bac783d2c57885889b1162823384e8313d47800a0e10d0070e", size = 186807, upload-time = "2025-12-25T03:52:43.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/b0/ac68aa582a996b1241773bd419823290c42a13dc9f494704a12a17ddd7b6/librt-0.7.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:265729b551a2dd329cc47b323a182fb7961af42abf21e913c9dd7d3331b2f3c2", size = 181810, upload-time = "2025-12-25T03:52:45.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/c1/03f6717677f20acd2d690813ec2bbe12a2de305f32c61479c53f7b9413bc/librt-0.7.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:168e04663e126416ba712114050f413ac306759a1791d87b7c11d4428ba75760", size = 175599, upload-time = "2025-12-25T03:52:46.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d7/f976ff4c07c59b69bb5eec7e5886d43243075bbef834428124b073471c86/librt-0.7.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:553dc58987d1d853adda8aeadf4db8e29749f0b11877afcc429a9ad892818ae2", size = 196506, upload-time = "2025-12-25T03:52:47.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/74/004f068b8888e61b454568b5479f88018fceb14e511ac0609cccee7dd227/librt-0.7.5-cp314-cp314-win32.whl", hash = "sha256:263f4fae9eba277513357c871275b18d14de93fd49bf5e43dc60a97b81ad5eb8", size = 39747, upload-time = "2025-12-25T03:52:48.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/b1/ea3ec8fcf5f0a00df21f08972af77ad799604a306db58587308067d27af8/librt-0.7.5-cp314-cp314-win_amd64.whl", hash = "sha256:85f485b7471571e99fab4f44eeb327dc0e1f814ada575f3fa85e698417d8a54e", size = 45970, upload-time = "2025-12-25T03:52:49.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/30/5e3fb7ac4614a50fc67e6954926137d50ebc27f36419c9963a94f931f649/librt-0.7.5-cp314-cp314-win_arm64.whl", hash = "sha256:49c596cd18e90e58b7caa4d7ca7606049c1802125fcff96b8af73fa5c3870e4d", size = 39075, upload-time = "2025-12-25T03:52:50.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/7f/0af0a9306a06c2aabee3a790f5aa560c50ec0a486ab818a572dd3db6c851/librt-0.7.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:54d2aef0b0f5056f130981ad45081b278602ff3657fe16c88529f5058038e802", size = 57375, upload-time = "2025-12-25T03:52:51.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/1f/c85e510baf6572a3d6ef40c742eacedc02973ed2acdb5dba2658751d9af8/librt-0.7.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0b4791202296ad51ac09a3ff58eb49d9da8e3a4009167a6d76ac418a974e5fd4", size = 59234, upload-time = "2025-12-25T03:52:52.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b1/bb6535e4250cd18b88d6b18257575a0239fa1609ebba925f55f51ae08e8e/librt-0.7.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e860909fea75baef941ee6436e0453612505883b9d0d87924d4fda27865b9a2", size = 183873, upload-time = "2025-12-25T03:52:53.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/49/ad4a138cca46cdaa7f0e15fa912ce3ccb4cc0d4090bfeb8ccc35766fa6d5/librt-0.7.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f02c4337bf271c4f06637f5ff254fad2238c0b8e32a3a480ebb2fc5e26f754a5", size = 194609, upload-time = "2025-12-25T03:52:54.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/2d/3b3cb933092d94bb2c1d3c9b503d8775f08d806588c19a91ee4d1495c2a8/librt-0.7.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f51ffe59f4556243d3cc82d827bde74765f594fa3ceb80ec4de0c13ccd3416", size = 206777, upload-time = "2025-12-25T03:52:55.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/52/6e7611d3d1347812233dabc44abca4c8065ee97b83c9790d7ecc3f782bc8/librt-0.7.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b7f080ba30601dfa3e3deed3160352273e1b9bc92e652f51103c3e9298f7899", size = 203208, upload-time = "2025-12-25T03:52:57.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/aa/466ae4654bd2d45903fbf180815d41e3ae8903e5a1861f319f73c960a843/librt-0.7.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb565b4219abc8ea2402e61c7ba648a62903831059ed3564fa1245cc245d58d7", size = 196698, upload-time = "2025-12-25T03:52:58.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/8f/424f7e4525bb26fe0d3e984d1c0810ced95e53be4fd867ad5916776e18a3/librt-0.7.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a3cfb15961e7333ea6ef033dc574af75153b5c230d5ad25fbcd55198f21e0cf", size = 217194, upload-time = "2025-12-25T03:52:59.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/33/13a4cb798a171b173f3c94db23adaf13a417130e1493933dc0df0d7fb439/librt-0.7.5-cp314-cp314t-win32.whl", hash = "sha256:118716de5ad6726332db1801bc90fa6d94194cd2e07c1a7822cebf12c496714d", size = 40282, upload-time = "2025-12-25T03:53:01.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/f1/62b136301796399d65dad73b580f4509bcbd347dff885a450bff08e80cb6/librt-0.7.5-cp314-cp314t-win_amd64.whl", hash = "sha256:3dd58f7ce20360c6ce0c04f7bd9081c7f9c19fc6129a3c705d0c5a35439f201d", size = 46764, upload-time = "2025-12-25T03:53:02.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/cb/940431d9410fda74f941f5cd7f0e5a22c63be7b0c10fa98b2b7022b48cb1/librt-0.7.5-cp314-cp314t-win_arm64.whl", hash = "sha256:08153ea537609d11f774d2bfe84af39d50d5c9ca3a4d061d946e0c9d8bce04a1", size = 39728, upload-time = "2025-12-25T03:53:03.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/5e/d979ccb0a81407ec47c14ea68fb217ff4315521730033e1dd9faa4f3e2c1/librt-0.7.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f4a0b0a3c86ba9193a8e23bb18f100d647bf192390ae195d84dfa0a10fb6244", size = 55746, upload-time = "2026-01-01T23:51:29.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/2c/3b65861fb32f802c3783d6ac66fc5589564d07452a47a8cf9980d531cad3/librt-0.7.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5335890fea9f9e6c4fdf8683061b9ccdcbe47c6dc03ab8e9b68c10acf78be78d", size = 57174, upload-time = "2026-01-01T23:51:31.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/df/030b50614b29e443607220097ebaf438531ea218c7a9a3e21ea862a919cd/librt-0.7.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b4346b1225be26def3ccc6c965751c74868f0578cbcba293c8ae9168483d811", size = 165834, upload-time = "2026-01-01T23:51:32.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/e1/bd8d1eacacb24be26a47f157719553bbd1b3fe812c30dddf121c0436fd0b/librt-0.7.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a10b8eebdaca6e9fdbaf88b5aefc0e324b763a5f40b1266532590d5afb268a4c", size = 174819, upload-time = "2026-01-01T23:51:33.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/7d/91d6c3372acf54a019c1ad8da4c9ecf4fc27d039708880bf95f48dbe426a/librt-0.7.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:067be973d90d9e319e6eb4ee2a9b9307f0ecd648b8a9002fa237289a4a07a9e7", size = 189607, upload-time = "2026-01-01T23:51:34.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/ac/44604d6d3886f791fbd1c6ae12d5a782a8f4aca927484731979f5e92c200/librt-0.7.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:23d2299ed007812cccc1ecef018db7d922733382561230de1f3954db28433977", size = 184586, upload-time = "2026-01-01T23:51:35.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/26/d8a6e4c17117b7f9b83301319d9a9de862ae56b133efb4bad8b3aa0808c9/librt-0.7.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b6f8ea465524aa4c7420c7cc4ca7d46fe00981de8debc67b1cc2e9957bb5b9d", size = 178251, upload-time = "2026-01-01T23:51:37.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/ab/98d857e254376f8e2f668e807daccc1f445e4b4fc2f6f9c1cc08866b0227/librt-0.7.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8df32a99cc46eb0ee90afd9ada113ae2cafe7e8d673686cf03ec53e49635439", size = 199853, upload-time = "2026-01-01T23:51:38.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/55/4523210d6ae5134a5da959900be43ad8bab2e4206687b6620befddb5b5fd/librt-0.7.7-cp313-cp313-win32.whl", hash = "sha256:86f86b3b785487c7760247bcdac0b11aa8bf13245a13ed05206286135877564b", size = 43247, upload-time = "2026-01-01T23:51:39.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/40/3ec0fed5e8e9297b1cf1a3836fb589d3de55f9930e3aba988d379e8ef67c/librt-0.7.7-cp313-cp313-win_amd64.whl", hash = "sha256:4862cb2c702b1f905c0503b72d9d4daf65a7fdf5a9e84560e563471e57a56949", size = 49419, upload-time = "2026-01-01T23:51:40.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/7a/aab5f0fb122822e2acbc776addf8b9abfb4944a9056c00c393e46e543177/librt-0.7.7-cp313-cp313-win_arm64.whl", hash = "sha256:0996c83b1cb43c00e8c87835a284f9057bc647abd42b5871e5f941d30010c832", size = 42828, upload-time = "2026-01-01T23:51:41.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/9c/228a5c1224bd23809a635490a162e9cbdc68d99f0eeb4a696f07886b8206/librt-0.7.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:23daa1ab0512bafdd677eb1bfc9611d8ffbe2e328895671e64cb34166bc1b8c8", size = 55188, upload-time = "2026-01-01T23:51:43.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/c2/0e7c6067e2b32a156308205e5728f4ed6478c501947e9142f525afbc6bd2/librt-0.7.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:558a9e5a6f3cc1e20b3168fb1dc802d0d8fa40731f6e9932dcc52bbcfbd37111", size = 56895, upload-time = "2026-01-01T23:51:44.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/77/de50ff70c80855eb79d1d74035ef06f664dd073fb7fb9d9fb4429651b8eb/librt-0.7.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2567cb48dc03e5b246927ab35cbb343376e24501260a9b5e30b8e255dca0d1d2", size = 163724, upload-time = "2026-01-01T23:51:45.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/19/f8e4bf537899bdef9e0bb9f0e4b18912c2d0f858ad02091b6019864c9a6d/librt-0.7.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6066c638cdf85ff92fc6f932d2d73c93a0e03492cdfa8778e6d58c489a3d7259", size = 172470, upload-time = "2026-01-01T23:51:46.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/4c/dcc575b69d99076768e8dd6141d9aecd4234cba7f0e09217937f52edb6ed/librt-0.7.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a609849aca463074c17de9cda173c276eb8fee9e441053529e7b9e249dc8b8ee", size = 186806, upload-time = "2026-01-01T23:51:48.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/f8/4094a2b7816c88de81239a83ede6e87f1138477d7ee956c30f136009eb29/librt-0.7.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:add4e0a000858fe9bb39ed55f31085506a5c38363e6eb4a1e5943a10c2bfc3d1", size = 181809, upload-time = "2026-01-01T23:51:49.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/ac/821b7c0ab1b5a6cd9aee7ace8309c91545a2607185101827f79122219a7e/librt-0.7.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a3bfe73a32bd0bdb9a87d586b05a23c0a1729205d79df66dee65bb2e40d671ba", size = 175597, upload-time = "2026-01-01T23:51:50.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/f9/27f6bfbcc764805864c04211c6ed636fe1d58f57a7b68d1f4ae5ed74e0e0/librt-0.7.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0ecce0544d3db91a40f8b57ae26928c02130a997b540f908cefd4d279d6c5848", size = 196506, upload-time = "2026-01-01T23:51:52.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/ba/c9b9c6fc931dd7ea856c573174ccaf48714905b1a7499904db2552e3bbaf/librt-0.7.7-cp314-cp314-win32.whl", hash = "sha256:8f7a74cf3a80f0c3b0ec75b0c650b2f0a894a2cec57ef75f6f72c1e82cdac61d", size = 39747, upload-time = "2026-01-01T23:51:53.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/69/cd1269337c4cde3ee70176ee611ab0058aa42fc8ce5c9dce55f48facfcd8/librt-0.7.7-cp314-cp314-win_amd64.whl", hash = "sha256:3d1fe2e8df3268dd6734dba33ededae72ad5c3a859b9577bc00b715759c5aaab", size = 45971, upload-time = "2026-01-01T23:51:54.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/fd/e0844794423f5583108c5991313c15e2b400995f44f6ec6871f8aaf8243c/librt-0.7.7-cp314-cp314-win_arm64.whl", hash = "sha256:2987cf827011907d3dfd109f1be0d61e173d68b1270107bb0e89f2fca7f2ed6b", size = 39075, upload-time = "2026-01-01T23:51:55.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/02/211fd8f7c381e7b2a11d0fdfcd410f409e89967be2e705983f7c6342209a/librt-0.7.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8e92c8de62b40bfce91d5e12c6e8b15434da268979b1af1a6589463549d491e6", size = 57368, upload-time = "2026-01-01T23:51:56.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/b6/aca257affae73ece26041ae76032153266d110453173f67d7603058e708c/librt-0.7.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f683dcd49e2494a7535e30f779aa1ad6e3732a019d80abe1309ea91ccd3230e3", size = 59238, upload-time = "2026-01-01T23:51:58.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/47/7383a507d8e0c11c78ca34c9d36eab9000db5989d446a2f05dc40e76c64f/librt-0.7.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b15e5d17812d4d629ff576699954f74e2cc24a02a4fc401882dd94f81daba45", size = 183870, upload-time = "2026-01-01T23:51:59.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/b8/50f3d8eec8efdaf79443963624175c92cec0ba84827a66b7fcfa78598e51/librt-0.7.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c084841b879c4d9b9fa34e5d5263994f21aea7fd9c6add29194dbb41a6210536", size = 194608, upload-time = "2026-01-01T23:52:00.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/d9/1b6520793aadb59d891e3b98ee057a75de7f737e4a8b4b37fdbecb10d60f/librt-0.7.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c8fb9966f84737115513fecbaf257f9553d067a7dd45a69c2c7e5339e6a8dc", size = 206776, upload-time = "2026-01-01T23:52:01.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/db/331edc3bba929d2756fa335bfcf736f36eff4efcb4f2600b545a35c2ae58/librt-0.7.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5fb1ecb2c35362eab2dbd354fd1efa5a8440d3e73a68be11921042a0edc0ff", size = 203206, upload-time = "2026-01-01T23:52:03.315Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/e1/6af79ec77204e85f6f2294fc171a30a91bb0e35d78493532ed680f5d98be/librt-0.7.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d1454899909d63cc9199a89fcc4f81bdd9004aef577d4ffc022e600c412d57f3", size = 196697, upload-time = "2026-01-01T23:52:04.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/46/de55ecce4b2796d6d243295c221082ca3a944dc2fb3a52dcc8660ce7727d/librt-0.7.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7ef28f2e7a016b29792fe0a2dd04dec75725b32a1264e390c366103f834a9c3a", size = 217193, upload-time = "2026-01-01T23:52:06.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/61/33063e271949787a2f8dd33c5260357e3d512a114fc82ca7890b65a76e2d/librt-0.7.7-cp314-cp314t-win32.whl", hash = "sha256:5e419e0db70991b6ba037b70c1d5bbe92b20ddf82f31ad01d77a347ed9781398", size = 40277, upload-time = "2026-01-01T23:52:07.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/21/1abd972349f83a696ea73159ac964e63e2d14086fdd9bc7ca878c25fced4/librt-0.7.7-cp314-cp314t-win_amd64.whl", hash = "sha256:d6b7d93657332c817b8d674ef6bf1ab7796b4f7ce05e420fd45bd258a72ac804", size = 46765, upload-time = "2026-01-01T23:52:08.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/0e/b756c7708143a63fca65a51ca07990fa647db2cc8fcd65177b9e96680255/librt-0.7.7-cp314-cp314t-win_arm64.whl", hash = "sha256:142c2cd91794b79fd0ce113bd658993b7ede0fe93057668c2f98a45ca00b7e91", size = 39724, upload-time = "2026-01-01T23:52:09.745Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2499,60 +2499,60 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.0.0"
|
||||
version = "12.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2912,15 +2912,15 @@ crypto = [
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.19.1"
|
||||
version = "10.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/2d/9f30cee56d4d6d222430d401e85b0a6a1ae229819362f5786943d1a8c03b/pymdown_extensions-10.19.1.tar.gz", hash = "sha256:4969c691009a389fb1f9712dd8e7bd70dcc418d15a0faf70acb5117d022f7de8", size = 847839, upload-time = "2025-12-14T17:25:24.42Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/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" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3492,14 +3492,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruamel-yaml"
|
||||
version = "0.18.16"
|
||||
version = "0.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/73/bb1bc2529f852e7bf64a2dec885e89ff9f5cc7bbf6c9340eed30ff2c69c5/ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba", size = 119858, upload-time = "2025-10-22T17:53:59.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3820,16 +3817,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "speechrecognition"
|
||||
version = "3.14.4"
|
||||
version = "3.14.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "audioop-lts" },
|
||||
{ name = "standard-aifc" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/05/ca38458e353991676fff9c8ad027a516bc8483b5c11ff7521c19e4ddddc2/speechrecognition-3.14.4.tar.gz", hash = "sha256:e698248b611589b5ba4f760fcf6ec9bc3a8a25a87a2e0e88ec926c63539b6a6f", size = 32859616, upload-time = "2025-11-19T12:14:02.273Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/ab/bb1c60e7bfd6b7a736f76439b78ebbfb5e92a81b626b6e94a87e166f2ea4/speechrecognition-3.14.5.tar.gz", hash = "sha256:2d185192986b9b67a1502825a330e971f59a2cae0262f727a19ad1f6b586d00a", size = 32859817, upload-time = "2025-12-31T11:25:46.518Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/44/9645d4eccf04508c9677e4f75816c977a1ce306cf39e6f258f9d9deee8f9/speechrecognition-3.14.4-py3-none-any.whl", hash = "sha256:8b09d99a6ed31f994ed6b1749d6f921717e41179aa6387a79e8d514d30d98577", size = 32856066, upload-time = "2025-11-19T12:13:57.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/a7/903429719d39ac2c42aa37086c90e816d883560f13c87d51f09a2962e021/speechrecognition-3.14.5-py3-none-any.whl", hash = "sha256:0c496d74e9f29b1daadb0d96f5660f47563e42bf09316dacdd57094c5095977e", size = 32856308, upload-time = "2025-12-31T11:25:41.161Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3868,15 +3865,15 @@ asyncio = [
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "3.1.1"
|
||||
version = "3.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/62/08/8f554b0e5bad3e4e880521a1686d96c05198471eed860b0eb89b57ea3636/sse_starlette-3.1.1.tar.gz", hash = "sha256:bffa531420c1793ab224f63648c059bcadc412bf9fdb1301ac8de1cf9a67b7fb", size = 24306, upload-time = "2025-12-26T15:22:53.836Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/da/34/f5df66cb383efdbf4f2db23cabb27f51b1dcb737efaf8a558f6f1d195134/sse_starlette-3.1.2.tar.gz", hash = "sha256:55eff034207a83a0eb86de9a68099bd0157838f0b8b999a1b742005c71e33618", size = 26303, upload-time = "2025-12-31T08:02:20.023Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/31/4c281581a0f8de137b710a07f65518b34bcf333b201cfa06cfda9af05f8a/sse_starlette-3.1.1-py3-none-any.whl", hash = "sha256:bb38f71ae74cfd86b529907a9fda5632195dfa6ae120f214ea4c890c7ee9d436", size = 12442, upload-time = "2025-12-26T15:22:52.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/95/8c4b76eec9ae574474e5d2997557cebf764bcd3586458956c30631ae08f4/sse_starlette-3.1.2-py3-none-any.whl", hash = "sha256:cd800dd349f4521b317b9391d3796fa97b71748a4da9b9e00aafab32dda375c8", size = 12484, upload-time = "2025-12-31T08:02:18.894Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user