PraisonAI 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Queue Scheduler for PraisonAI.
|
|
3
|
+
|
|
4
|
+
Priority-based FIFO scheduler with concurrency limits.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, Set
|
|
13
|
+
|
|
14
|
+
from .models import QueuedRun, RunState, RunPriority, QueueConfig, QueueEvent
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QueueFullError(Exception):
|
|
20
|
+
"""Raised when queue is at capacity."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RunNotFoundError(Exception):
|
|
25
|
+
"""Raised when a run is not found."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class QueueScheduler:
|
|
30
|
+
"""
|
|
31
|
+
Priority-based FIFO scheduler with concurrency limits.
|
|
32
|
+
|
|
33
|
+
Runs are organized by priority (URGENT > HIGH > NORMAL > LOW),
|
|
34
|
+
with FIFO ordering within each priority level.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: Optional[QueueConfig] = None):
|
|
38
|
+
"""
|
|
39
|
+
Initialize scheduler.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: Queue configuration. Uses defaults if not provided.
|
|
43
|
+
"""
|
|
44
|
+
self.config = config or QueueConfig()
|
|
45
|
+
|
|
46
|
+
# Priority queues: priority -> list of runs (FIFO order)
|
|
47
|
+
self._queues: Dict[RunPriority, List[QueuedRun]] = {
|
|
48
|
+
p: [] for p in RunPriority
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Running runs: run_id -> run
|
|
52
|
+
self._running: Dict[str, QueuedRun] = {}
|
|
53
|
+
|
|
54
|
+
# All runs by ID for quick lookup
|
|
55
|
+
self._all_runs: Dict[str, QueuedRun] = {}
|
|
56
|
+
|
|
57
|
+
# Lock for thread safety
|
|
58
|
+
self._lock = asyncio.Lock()
|
|
59
|
+
|
|
60
|
+
# Event callbacks
|
|
61
|
+
self._event_callbacks: List[Callable[[QueueEvent], None]] = []
|
|
62
|
+
|
|
63
|
+
# Cancellation tokens
|
|
64
|
+
self._cancel_tokens: Set[str] = set()
|
|
65
|
+
|
|
66
|
+
def add_event_callback(self, callback: Callable[[QueueEvent], None]) -> None:
|
|
67
|
+
"""Add a callback for queue events."""
|
|
68
|
+
self._event_callbacks.append(callback)
|
|
69
|
+
|
|
70
|
+
def remove_event_callback(self, callback: Callable[[QueueEvent], None]) -> None:
|
|
71
|
+
"""Remove an event callback."""
|
|
72
|
+
if callback in self._event_callbacks:
|
|
73
|
+
self._event_callbacks.remove(callback)
|
|
74
|
+
|
|
75
|
+
def _emit_event(self, event: QueueEvent) -> None:
|
|
76
|
+
"""Emit an event to all callbacks."""
|
|
77
|
+
for callback in self._event_callbacks:
|
|
78
|
+
try:
|
|
79
|
+
callback(event)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Error in event callback: {e}")
|
|
82
|
+
|
|
83
|
+
async def submit(
|
|
84
|
+
self,
|
|
85
|
+
run: QueuedRun,
|
|
86
|
+
check_duplicate: bool = True,
|
|
87
|
+
) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Submit a run to the queue.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
run: The run to submit.
|
|
93
|
+
check_duplicate: If True, reject duplicate run_ids.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The run_id.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
QueueFullError: If queue is at capacity.
|
|
100
|
+
ValueError: If run_id already exists (when check_duplicate=True).
|
|
101
|
+
"""
|
|
102
|
+
async with self._lock:
|
|
103
|
+
# Check for duplicates
|
|
104
|
+
if check_duplicate and run.run_id in self._all_runs:
|
|
105
|
+
raise ValueError(f"Run {run.run_id} already exists")
|
|
106
|
+
|
|
107
|
+
# Check queue capacity
|
|
108
|
+
total_queued = sum(len(q) for q in self._queues.values())
|
|
109
|
+
if total_queued >= self.config.max_queue_size:
|
|
110
|
+
raise QueueFullError(
|
|
111
|
+
f"Queue is full ({total_queued}/{self.config.max_queue_size})"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Ensure state is QUEUED
|
|
115
|
+
run.state = RunState.QUEUED
|
|
116
|
+
|
|
117
|
+
# Add to appropriate priority queue
|
|
118
|
+
self._queues[run.priority].append(run)
|
|
119
|
+
self._all_runs[run.run_id] = run
|
|
120
|
+
|
|
121
|
+
logger.debug(f"Submitted run {run.run_id} with priority {run.priority.name}")
|
|
122
|
+
|
|
123
|
+
self._emit_event(QueueEvent(
|
|
124
|
+
event_type="run_submitted",
|
|
125
|
+
run_id=run.run_id,
|
|
126
|
+
data={"priority": run.priority.name, "agent": run.agent_name}
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
return run.run_id
|
|
130
|
+
|
|
131
|
+
async def next(self) -> Optional[QueuedRun]:
|
|
132
|
+
"""
|
|
133
|
+
Get the next run to execute, respecting priority and concurrency limits.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
The next run, or None if no run is available.
|
|
137
|
+
"""
|
|
138
|
+
async with self._lock:
|
|
139
|
+
if not self._can_start_new():
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
# Check priorities in order (URGENT first = highest value)
|
|
143
|
+
for priority in reversed(list(RunPriority)):
|
|
144
|
+
queue = self._queues[priority]
|
|
145
|
+
|
|
146
|
+
for i, run in enumerate(queue):
|
|
147
|
+
if self._can_run(run):
|
|
148
|
+
# Remove from queue
|
|
149
|
+
queue.pop(i)
|
|
150
|
+
|
|
151
|
+
# Update state
|
|
152
|
+
run.state = RunState.RUNNING
|
|
153
|
+
run.started_at = time.time()
|
|
154
|
+
|
|
155
|
+
# Add to running
|
|
156
|
+
self._running[run.run_id] = run
|
|
157
|
+
|
|
158
|
+
logger.debug(f"Starting run {run.run_id}")
|
|
159
|
+
|
|
160
|
+
self._emit_event(QueueEvent(
|
|
161
|
+
event_type="run_started",
|
|
162
|
+
run_id=run.run_id,
|
|
163
|
+
data={"agent": run.agent_name}
|
|
164
|
+
))
|
|
165
|
+
|
|
166
|
+
return run
|
|
167
|
+
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
async def complete(
|
|
171
|
+
self,
|
|
172
|
+
run_id: str,
|
|
173
|
+
output: Optional[str] = None,
|
|
174
|
+
metrics: Optional[Dict[str, Any]] = None,
|
|
175
|
+
) -> Optional[QueuedRun]:
|
|
176
|
+
"""
|
|
177
|
+
Mark a run as completed successfully.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
run_id: The run ID.
|
|
181
|
+
output: The output content.
|
|
182
|
+
metrics: Optional metrics.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
The completed run, or None if not found.
|
|
186
|
+
"""
|
|
187
|
+
async with self._lock:
|
|
188
|
+
run = self._running.pop(run_id, None)
|
|
189
|
+
if run is None:
|
|
190
|
+
logger.warning(f"Run {run_id} not found in running")
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
run.state = RunState.SUCCEEDED
|
|
194
|
+
run.ended_at = time.time()
|
|
195
|
+
run.output_content = output
|
|
196
|
+
if metrics:
|
|
197
|
+
run.metrics.update(metrics)
|
|
198
|
+
|
|
199
|
+
logger.debug(f"Completed run {run_id}")
|
|
200
|
+
|
|
201
|
+
self._emit_event(QueueEvent(
|
|
202
|
+
event_type="run_completed",
|
|
203
|
+
run_id=run_id,
|
|
204
|
+
data={"duration": run.duration_seconds}
|
|
205
|
+
))
|
|
206
|
+
|
|
207
|
+
return run
|
|
208
|
+
|
|
209
|
+
async def fail(
|
|
210
|
+
self,
|
|
211
|
+
run_id: str,
|
|
212
|
+
error: str,
|
|
213
|
+
metrics: Optional[Dict[str, Any]] = None,
|
|
214
|
+
) -> Optional[QueuedRun]:
|
|
215
|
+
"""
|
|
216
|
+
Mark a run as failed.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
run_id: The run ID.
|
|
220
|
+
error: The error message.
|
|
221
|
+
metrics: Optional metrics.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
The failed run, or None if not found.
|
|
225
|
+
"""
|
|
226
|
+
async with self._lock:
|
|
227
|
+
run = self._running.pop(run_id, None)
|
|
228
|
+
if run is None:
|
|
229
|
+
logger.warning(f"Run {run_id} not found in running")
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
run.state = RunState.FAILED
|
|
233
|
+
run.ended_at = time.time()
|
|
234
|
+
run.error = error
|
|
235
|
+
if metrics:
|
|
236
|
+
run.metrics.update(metrics)
|
|
237
|
+
|
|
238
|
+
logger.debug(f"Failed run {run_id}: {error}")
|
|
239
|
+
|
|
240
|
+
self._emit_event(QueueEvent(
|
|
241
|
+
event_type="run_failed",
|
|
242
|
+
run_id=run_id,
|
|
243
|
+
data={"error": error}
|
|
244
|
+
))
|
|
245
|
+
|
|
246
|
+
return run
|
|
247
|
+
|
|
248
|
+
async def cancel(self, run_id: str) -> bool:
|
|
249
|
+
"""
|
|
250
|
+
Cancel a queued or running run.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
run_id: The run ID to cancel.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
True if cancelled, False if not found.
|
|
257
|
+
"""
|
|
258
|
+
async with self._lock:
|
|
259
|
+
# Check if running
|
|
260
|
+
if run_id in self._running:
|
|
261
|
+
run = self._running.pop(run_id)
|
|
262
|
+
run.state = RunState.CANCELLED
|
|
263
|
+
run.ended_at = time.time()
|
|
264
|
+
self._cancel_tokens.add(run_id)
|
|
265
|
+
|
|
266
|
+
logger.debug(f"Cancelled running run {run_id}")
|
|
267
|
+
|
|
268
|
+
self._emit_event(QueueEvent(
|
|
269
|
+
event_type="run_cancelled",
|
|
270
|
+
run_id=run_id,
|
|
271
|
+
data={"was_running": True}
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
return True
|
|
275
|
+
|
|
276
|
+
# Check if queued
|
|
277
|
+
for priority in RunPriority:
|
|
278
|
+
queue = self._queues[priority]
|
|
279
|
+
for i, run in enumerate(queue):
|
|
280
|
+
if run.run_id == run_id:
|
|
281
|
+
queue.pop(i)
|
|
282
|
+
run.state = RunState.CANCELLED
|
|
283
|
+
run.ended_at = time.time()
|
|
284
|
+
|
|
285
|
+
logger.debug(f"Cancelled queued run {run_id}")
|
|
286
|
+
|
|
287
|
+
self._emit_event(QueueEvent(
|
|
288
|
+
event_type="run_cancelled",
|
|
289
|
+
run_id=run_id,
|
|
290
|
+
data={"was_running": False}
|
|
291
|
+
))
|
|
292
|
+
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
def is_cancelled(self, run_id: str) -> bool:
|
|
298
|
+
"""Check if a run has been cancelled."""
|
|
299
|
+
return run_id in self._cancel_tokens
|
|
300
|
+
|
|
301
|
+
def clear_cancel_token(self, run_id: str) -> None:
|
|
302
|
+
"""Clear a cancellation token."""
|
|
303
|
+
self._cancel_tokens.discard(run_id)
|
|
304
|
+
|
|
305
|
+
async def retry(self, run_id: str) -> Optional[str]:
|
|
306
|
+
"""
|
|
307
|
+
Retry a failed run.
|
|
308
|
+
|
|
309
|
+
Creates a new run with incremented retry count and link to parent.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
run_id: The run ID to retry.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
The new run_id, or None if retry not allowed.
|
|
316
|
+
"""
|
|
317
|
+
async with self._lock:
|
|
318
|
+
# Find the original run
|
|
319
|
+
original = self._all_runs.get(run_id)
|
|
320
|
+
if original is None:
|
|
321
|
+
logger.warning(f"Run {run_id} not found for retry")
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
if not original.can_retry():
|
|
325
|
+
logger.warning(
|
|
326
|
+
f"Run {run_id} cannot be retried "
|
|
327
|
+
f"(state={original.state}, retries={original.retry_count}/{original.max_retries})"
|
|
328
|
+
)
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
# Create new run
|
|
332
|
+
new_run = QueuedRun(
|
|
333
|
+
run_id=str(uuid.uuid4())[:8],
|
|
334
|
+
agent_name=original.agent_name,
|
|
335
|
+
input_content=original.input_content,
|
|
336
|
+
state=RunState.QUEUED,
|
|
337
|
+
priority=original.priority,
|
|
338
|
+
session_id=original.session_id,
|
|
339
|
+
trace_id=original.trace_id,
|
|
340
|
+
workspace=original.workspace,
|
|
341
|
+
user_id=original.user_id,
|
|
342
|
+
retry_count=original.retry_count + 1,
|
|
343
|
+
max_retries=original.max_retries,
|
|
344
|
+
parent_run_id=original.run_id,
|
|
345
|
+
config=original.config.copy(),
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Submit outside lock to avoid deadlock
|
|
349
|
+
await self.submit(new_run, check_duplicate=True)
|
|
350
|
+
|
|
351
|
+
logger.debug(f"Retrying run {run_id} as {new_run.run_id}")
|
|
352
|
+
|
|
353
|
+
self._emit_event(QueueEvent(
|
|
354
|
+
event_type="run_retried",
|
|
355
|
+
run_id=new_run.run_id,
|
|
356
|
+
data={"parent_run_id": run_id, "retry_count": new_run.retry_count}
|
|
357
|
+
))
|
|
358
|
+
|
|
359
|
+
return new_run.run_id
|
|
360
|
+
|
|
361
|
+
async def pause(self, run_id: str) -> bool:
|
|
362
|
+
"""
|
|
363
|
+
Pause a running run.
|
|
364
|
+
|
|
365
|
+
Note: The worker must check for pause state and handle accordingly.
|
|
366
|
+
"""
|
|
367
|
+
async with self._lock:
|
|
368
|
+
if run_id not in self._running:
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
run = self._running[run_id]
|
|
372
|
+
run.state = RunState.PAUSED
|
|
373
|
+
|
|
374
|
+
self._emit_event(QueueEvent(
|
|
375
|
+
event_type="run_paused",
|
|
376
|
+
run_id=run_id,
|
|
377
|
+
))
|
|
378
|
+
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
async def resume(self, run_id: str) -> bool:
|
|
382
|
+
"""Resume a paused run."""
|
|
383
|
+
async with self._lock:
|
|
384
|
+
if run_id not in self._running:
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
run = self._running[run_id]
|
|
388
|
+
if run.state != RunState.PAUSED:
|
|
389
|
+
return False
|
|
390
|
+
|
|
391
|
+
run.state = RunState.RUNNING
|
|
392
|
+
|
|
393
|
+
self._emit_event(QueueEvent(
|
|
394
|
+
event_type="run_resumed",
|
|
395
|
+
run_id=run_id,
|
|
396
|
+
))
|
|
397
|
+
|
|
398
|
+
return True
|
|
399
|
+
|
|
400
|
+
def get_run(self, run_id: str) -> Optional[QueuedRun]:
|
|
401
|
+
"""Get a run by ID."""
|
|
402
|
+
return self._all_runs.get(run_id)
|
|
403
|
+
|
|
404
|
+
def get_queued(self) -> List[QueuedRun]:
|
|
405
|
+
"""Get all queued runs in priority order."""
|
|
406
|
+
result = []
|
|
407
|
+
for priority in reversed(list(RunPriority)):
|
|
408
|
+
result.extend(self._queues[priority])
|
|
409
|
+
return result
|
|
410
|
+
|
|
411
|
+
def get_running(self) -> List[QueuedRun]:
|
|
412
|
+
"""Get all running runs."""
|
|
413
|
+
return list(self._running.values())
|
|
414
|
+
|
|
415
|
+
def get_all(self) -> List[QueuedRun]:
|
|
416
|
+
"""Get all runs."""
|
|
417
|
+
return list(self._all_runs.values())
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def queued_count(self) -> int:
|
|
421
|
+
"""Number of queued runs."""
|
|
422
|
+
return sum(len(q) for q in self._queues.values())
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def running_count(self) -> int:
|
|
426
|
+
"""Number of running runs."""
|
|
427
|
+
return len(self._running)
|
|
428
|
+
|
|
429
|
+
def _can_start_new(self) -> bool:
|
|
430
|
+
"""Check if we can start a new run (global limit)."""
|
|
431
|
+
return len(self._running) < self.config.max_concurrent_global
|
|
432
|
+
|
|
433
|
+
def _can_run(self, run: QueuedRun) -> bool:
|
|
434
|
+
"""Check if a specific run can start (per-agent and per-workspace limits)."""
|
|
435
|
+
# Count running by agent
|
|
436
|
+
agent_count = sum(
|
|
437
|
+
1 for r in self._running.values()
|
|
438
|
+
if r.agent_name == run.agent_name
|
|
439
|
+
)
|
|
440
|
+
if agent_count >= self.config.max_concurrent_per_agent:
|
|
441
|
+
return False
|
|
442
|
+
|
|
443
|
+
# Count running by workspace
|
|
444
|
+
if run.workspace:
|
|
445
|
+
workspace_count = sum(
|
|
446
|
+
1 for r in self._running.values()
|
|
447
|
+
if r.workspace == run.workspace
|
|
448
|
+
)
|
|
449
|
+
if workspace_count >= self.config.max_concurrent_per_workspace:
|
|
450
|
+
return False
|
|
451
|
+
|
|
452
|
+
return True
|
|
453
|
+
|
|
454
|
+
async def clear_queue(self) -> int:
|
|
455
|
+
"""Clear all queued (not running) runs."""
|
|
456
|
+
async with self._lock:
|
|
457
|
+
count = 0
|
|
458
|
+
for priority in RunPriority:
|
|
459
|
+
count += len(self._queues[priority])
|
|
460
|
+
for run in self._queues[priority]:
|
|
461
|
+
run.state = RunState.CANCELLED
|
|
462
|
+
del self._all_runs[run.run_id]
|
|
463
|
+
self._queues[priority] = []
|
|
464
|
+
|
|
465
|
+
return count
|
|
466
|
+
|
|
467
|
+
def load_runs(self, runs: List[QueuedRun]) -> None:
|
|
468
|
+
"""
|
|
469
|
+
Load runs (e.g., from persistence after restart).
|
|
470
|
+
|
|
471
|
+
Queued runs are added to queues, running runs are re-queued.
|
|
472
|
+
"""
|
|
473
|
+
for run in runs:
|
|
474
|
+
if run.state == RunState.RUNNING:
|
|
475
|
+
# Re-queue running runs (they were interrupted)
|
|
476
|
+
run.state = RunState.QUEUED
|
|
477
|
+
run.started_at = None
|
|
478
|
+
|
|
479
|
+
if run.state == RunState.QUEUED:
|
|
480
|
+
self._queues[run.priority].append(run)
|
|
481
|
+
|
|
482
|
+
self._all_runs[run.run_id] = run
|
|
483
|
+
|
|
484
|
+
logger.info(f"Loaded {len(runs)} runs from persistence")
|