kubiya-control-plane-api 0.9.15__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.
- control_plane_api/LICENSE +676 -0
- control_plane_api/README.md +350 -0
- control_plane_api/__init__.py +4 -0
- control_plane_api/__version__.py +8 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +121 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
- control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
- control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
- control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
- control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
- control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
- control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
- control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
- control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
- control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
- control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
- control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
- control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
- control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
- control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
- control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
- control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
- control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +384 -0
- control_plane_api/app/activities/plan_generation_activities.py +499 -0
- control_plane_api/app/activities/team_activities.py +424 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +469 -0
- control_plane_api/app/config/config_loader.py +224 -0
- control_plane_api/app/config/model_pricing.py +323 -0
- control_plane_api/app/config/storage_config.py +159 -0
- control_plane_api/app/config.py +115 -0
- control_plane_api/app/controllers/__init__.py +0 -0
- control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/environment.py +65 -0
- control_plane_api/app/lib/event_bus/__init__.py +17 -0
- control_plane_api/app/lib/event_bus/base.py +136 -0
- control_plane_api/app/lib/event_bus/manager.py +335 -0
- control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
- control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
- control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
- control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
- control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
- control_plane_api/app/lib/job_executor.py +330 -0
- control_plane_api/app/lib/kubiya_client.py +293 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/mcp_validation.py +163 -0
- control_plane_api/app/lib/nats/__init__.py +13 -0
- control_plane_api/app/lib/nats/credentials_manager.py +288 -0
- control_plane_api/app/lib/nats/listener.py +374 -0
- control_plane_api/app/lib/planning_prompt_builder.py +153 -0
- control_plane_api/app/lib/planning_tools/__init__.py +41 -0
- control_plane_api/app/lib/planning_tools/agents.py +409 -0
- control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
- control_plane_api/app/lib/planning_tools/base.py +119 -0
- control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
- control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
- control_plane_api/app/lib/planning_tools/environments.py +218 -0
- control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
- control_plane_api/app/lib/planning_tools/models.py +93 -0
- control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
- control_plane_api/app/lib/planning_tools/resources.py +242 -0
- control_plane_api/app/lib/planning_tools/teams.py +334 -0
- control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
- control_plane_api/app/lib/redis_client.py +803 -0
- control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
- control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
- control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
- control_plane_api/app/lib/storage/__init__.py +20 -0
- control_plane_api/app/lib/storage/base_provider.py +274 -0
- control_plane_api/app/lib/storage/provider_factory.py +157 -0
- control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/supabase_utils.py +138 -0
- control_plane_api/app/lib/task_planning/__init__.py +138 -0
- control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
- control_plane_api/app/lib/task_planning/agents.py +389 -0
- control_plane_api/app/lib/task_planning/cache.py +218 -0
- control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
- control_plane_api/app/lib/task_planning/helpers.py +293 -0
- control_plane_api/app/lib/task_planning/hooks.py +474 -0
- control_plane_api/app/lib/task_planning/models.py +503 -0
- control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
- control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
- control_plane_api/app/lib/task_planning/runner.py +656 -0
- control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
- control_plane_api/app/lib/task_planning/workflow.py +424 -0
- control_plane_api/app/lib/templating/__init__.py +88 -0
- control_plane_api/app/lib/templating/compiler.py +278 -0
- control_plane_api/app/lib/templating/engine.py +178 -0
- control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
- control_plane_api/app/lib/templating/parsers/base.py +96 -0
- control_plane_api/app/lib/templating/parsers/env.py +85 -0
- control_plane_api/app/lib/templating/parsers/graph.py +112 -0
- control_plane_api/app/lib/templating/parsers/secret.py +87 -0
- control_plane_api/app/lib/templating/parsers/simple.py +81 -0
- control_plane_api/app/lib/templating/resolver.py +366 -0
- control_plane_api/app/lib/templating/types.py +214 -0
- control_plane_api/app/lib/templating/validator.py +201 -0
- control_plane_api/app/lib/temporal_client.py +232 -0
- control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
- control_plane_api/app/lib/temporal_credentials_service.py +203 -0
- control_plane_api/app/lib/validation/__init__.py +24 -0
- control_plane_api/app/lib/validation/runtime_validation.py +388 -0
- control_plane_api/app/main.py +531 -0
- control_plane_api/app/middleware/__init__.py +10 -0
- control_plane_api/app/middleware/auth.py +645 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/prometheus_middleware.py +173 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +40 -0
- control_plane_api/app/models/agent.py +90 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +107 -0
- control_plane_api/app/models/auth_user.py +73 -0
- control_plane_api/app/models/context.py +161 -0
- control_plane_api/app/models/custom_integration.py +99 -0
- control_plane_api/app/models/environment.py +64 -0
- control_plane_api/app/models/execution.py +125 -0
- control_plane_api/app/models/execution_transition.py +50 -0
- control_plane_api/app/models/job.py +159 -0
- control_plane_api/app/models/llm_model.py +78 -0
- control_plane_api/app/models/orchestration.py +66 -0
- control_plane_api/app/models/plan_execution.py +102 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +61 -0
- control_plane_api/app/models/project_management.py +85 -0
- control_plane_api/app/models/session.py +29 -0
- control_plane_api/app/models/skill.py +155 -0
- control_plane_api/app/models/system_tables.py +43 -0
- control_plane_api/app/models/task_planning.py +372 -0
- control_plane_api/app/models/team.py +86 -0
- control_plane_api/app/models/trace.py +257 -0
- control_plane_api/app/models/user_profile.py +54 -0
- control_plane_api/app/models/worker.py +221 -0
- control_plane_api/app/models/workflow.py +161 -0
- control_plane_api/app/models/workspace.py +50 -0
- control_plane_api/app/observability/__init__.py +177 -0
- control_plane_api/app/observability/context_logging.py +475 -0
- control_plane_api/app/observability/decorators.py +337 -0
- control_plane_api/app/observability/local_span_processor.py +702 -0
- control_plane_api/app/observability/metrics.py +303 -0
- control_plane_api/app/observability/middleware.py +246 -0
- control_plane_api/app/observability/optional.py +115 -0
- control_plane_api/app/observability/tracing.py +382 -0
- control_plane_api/app/policies/README.md +149 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_enforcement/README.md +336 -0
- control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
- control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
- control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
- control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
- control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +382 -0
- control_plane_api/app/routers/agents_v2.py +1598 -0
- control_plane_api/app/routers/analytics.py +1310 -0
- control_plane_api/app/routers/auth.py +59 -0
- control_plane_api/app/routers/client_config.py +57 -0
- control_plane_api/app/routers/context_graph.py +561 -0
- control_plane_api/app/routers/context_manager.py +577 -0
- control_plane_api/app/routers/custom_integrations.py +490 -0
- control_plane_api/app/routers/enforcer.py +132 -0
- control_plane_api/app/routers/environment_context.py +252 -0
- control_plane_api/app/routers/environments.py +761 -0
- control_plane_api/app/routers/execution_environment.py +847 -0
- control_plane_api/app/routers/executions/__init__.py +28 -0
- control_plane_api/app/routers/executions/router.py +286 -0
- control_plane_api/app/routers/executions/services/__init__.py +22 -0
- control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
- control_plane_api/app/routers/executions/services/status_service.py +420 -0
- control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
- control_plane_api/app/routers/executions/services/worker_health.py +514 -0
- control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
- control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
- control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
- control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
- control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
- control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
- control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
- control_plane_api/app/routers/executions.py +4888 -0
- control_plane_api/app/routers/health.py +165 -0
- control_plane_api/app/routers/health_v2.py +394 -0
- control_plane_api/app/routers/integration_templates.py +496 -0
- control_plane_api/app/routers/integrations.py +287 -0
- control_plane_api/app/routers/jobs.py +1809 -0
- control_plane_api/app/routers/metrics.py +517 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +628 -0
- control_plane_api/app/routers/plan_executions.py +1481 -0
- control_plane_api/app/routers/plan_generation_async.py +304 -0
- control_plane_api/app/routers/policies.py +669 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +987 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +171 -0
- control_plane_api/app/routers/skills.py +1010 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/storage.py +456 -0
- control_plane_api/app/routers/task_planning.py +611 -0
- control_plane_api/app/routers/task_queues.py +650 -0
- control_plane_api/app/routers/team_context.py +274 -0
- control_plane_api/app/routers/teams.py +1747 -0
- control_plane_api/app/routers/templates.py +248 -0
- control_plane_api/app/routers/traces.py +571 -0
- control_plane_api/app/routers/websocket_client.py +479 -0
- control_plane_api/app/routers/websocket_executions_status.py +437 -0
- control_plane_api/app/routers/websocket_gateway.py +323 -0
- control_plane_api/app/routers/websocket_traces.py +576 -0
- control_plane_api/app/routers/worker_queues.py +2555 -0
- control_plane_api/app/routers/worker_websocket.py +419 -0
- control_plane_api/app/routers/workers.py +1004 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/__init__.py +1 -0
- control_plane_api/app/schemas/job_schemas.py +302 -0
- control_plane_api/app/schemas/mcp_schemas.py +311 -0
- control_plane_api/app/schemas/template_schemas.py +133 -0
- control_plane_api/app/schemas/trace_schemas.py +168 -0
- control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_planning_strategy.py +233 -0
- control_plane_api/app/services/agno_service.py +838 -0
- control_plane_api/app/services/claude_code_planning_service.py +203 -0
- control_plane_api/app/services/context_graph_client.py +224 -0
- control_plane_api/app/services/custom_integration_service.py +415 -0
- control_plane_api/app/services/integration_resolution_service.py +345 -0
- control_plane_api/app/services/litellm_service.py +394 -0
- control_plane_api/app/services/plan_generator.py +79 -0
- control_plane_api/app/services/planning_strategy.py +66 -0
- control_plane_api/app/services/planning_strategy_factory.py +118 -0
- control_plane_api/app/services/policy_service.py +615 -0
- control_plane_api/app/services/state_transition_service.py +755 -0
- control_plane_api/app/services/storage_service.py +593 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
- control_plane_api/app/services/trace_retention.py +354 -0
- control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
- control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
- control_plane_api/app/services/workflow_operations_service.py +611 -0
- control_plane_api/app/skills/__init__.py +100 -0
- control_plane_api/app/skills/base.py +239 -0
- control_plane_api/app/skills/builtin/__init__.py +37 -0
- control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
- control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
- control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
- control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
- control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
- control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
- control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
- control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
- control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
- control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
- control_plane_api/app/skills/builtin/docker/skill.py +104 -0
- control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
- control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
- control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
- control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
- control_plane_api/app/skills/builtin/python/__init__.py +4 -0
- control_plane_api/app/skills/builtin/python/skill.py +92 -0
- control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
- control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
- control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
- control_plane_api/app/skills/builtin/shell/skill.py +161 -0
- control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/app/skills/builtin/slack/skill.py +302 -0
- control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
- control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/config.py +63 -0
- control_plane_api/app/skills/loaders/__init__.py +14 -0
- control_plane_api/app/skills/loaders/base.py +73 -0
- control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
- control_plane_api/app/skills/registry.py +125 -0
- control_plane_api/app/utils/helpers.py +12 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +520 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/plan_generation.py +254 -0
- control_plane_api/app/workflows/team_execution.py +442 -0
- control_plane_api/scripts/seed_models.py +240 -0
- control_plane_api/scripts/validate_existing_tool_names.py +492 -0
- control_plane_api/shared/__init__.py +8 -0
- control_plane_api/shared/version.py +17 -0
- control_plane_api/test_deduplication.py +274 -0
- control_plane_api/test_executor_deduplication_e2e.py +309 -0
- control_plane_api/test_job_execution_e2e.py +283 -0
- control_plane_api/test_real_integration.py +193 -0
- control_plane_api/version.py +38 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1585 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/job_activities.py +199 -0
- control_plane_api/worker/activities/runtime_activities.py +1167 -0
- control_plane_api/worker/activities/skill_activities.py +282 -0
- control_plane_api/worker/activities/team_activities.py +479 -0
- control_plane_api/worker/agent_runtime_server.py +370 -0
- control_plane_api/worker/binary_manager.py +333 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +273 -0
- control_plane_api/worker/control_plane_client.py +1491 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/health_monitor.py +159 -0
- control_plane_api/worker/metrics.py +237 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/error_events.py +105 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +35 -0
- control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
- control_plane_api/worker/runtimes/agno/__init__.py +34 -0
- control_plane_api/worker/runtimes/agno/config.py +248 -0
- control_plane_api/worker/runtimes/agno/hooks.py +385 -0
- control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
- control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
- control_plane_api/worker/runtimes/agno/utils.py +163 -0
- control_plane_api/worker/runtimes/base.py +979 -0
- control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
- control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
- control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
- control_plane_api/worker/runtimes/claude_code/config.py +829 -0
- control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
- control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
- control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
- control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
- control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
- control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
- control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/model_utils.py +107 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_communication_tools.py +908 -0
- control_plane_api/worker/services/agent_executor.py +485 -0
- control_plane_api/worker/services/agent_executor_v2.py +793 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/code_ingestion_tools.py +465 -0
- control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
- control_plane_api/worker/services/data_visualization.py +834 -0
- control_plane_api/worker/services/event_publisher.py +531 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +365 -0
- control_plane_api/worker/services/skill_context_enhancement.py +181 -0
- control_plane_api/worker/services/skill_factory.py +471 -0
- control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
- control_plane_api/worker/services/team_executor.py +715 -0
- control_plane_api/worker/services/team_executor_v2.py +1866 -0
- control_plane_api/worker/services/tool_enforcement.py +254 -0
- control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
- control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
- control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
- control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
- control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
- control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/models.py +142 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
- control_plane_api/worker/skills/__init__.py +12 -0
- control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
- control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
- control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
- control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
- control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
- control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
- control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
- control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
- control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
- control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
- control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
- control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
- control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
- control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
- control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
- control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
- control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
- control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
- control_plane_api/worker/skills/loaders/__init__.py +5 -0
- control_plane_api/worker/skills/loaders/base.py +23 -0
- control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
- control_plane_api/worker/skills/registry.py +208 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/conftest.py +12 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
- control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
- control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
- control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
- control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
- control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
- control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
- control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
- control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
- control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
- control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
- control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +330 -0
- control_plane_api/worker/utils/environment.py +65 -0
- control_plane_api/worker/utils/error_publisher.py +260 -0
- control_plane_api/worker/utils/event_batcher.py +256 -0
- control_plane_api/worker/utils/logging_config.py +335 -0
- control_plane_api/worker/utils/logging_helper.py +326 -0
- control_plane_api/worker/utils/parameter_validator.py +120 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +665 -0
- control_plane_api/worker/utils/tool_validation.py +332 -0
- control_plane_api/worker/utils/workspace_manager.py +163 -0
- control_plane_api/worker/websocket_client.py +393 -0
- control_plane_api/worker/worker.py +1297 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +909 -0
- control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
- control_plane_api/worker/workflows/team_execution.py +611 -0
- kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
- kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
- kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
- kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
- scripts/__init__.py +1 -0
- scripts/migrations.py +39 -0
- scripts/seed_worker_queues.py +128 -0
- scripts/setup_agent_runtime.py +142 -0
- worker_internal/__init__.py +1 -0
- worker_internal/planner/__init__.py +1 -0
- worker_internal/planner/activities.py +1499 -0
- worker_internal/planner/agent_tools.py +197 -0
- worker_internal/planner/event_models.py +148 -0
- worker_internal/planner/event_publisher.py +67 -0
- worker_internal/planner/models.py +199 -0
- worker_internal/planner/retry_logic.py +134 -0
- worker_internal/planner/worker.py +300 -0
- worker_internal/planner/workflows.py +970 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket endpoint for real-time execution status updates across all executions.
|
|
3
|
+
|
|
4
|
+
Allows the kanban board to receive instant status changes without polling.
|
|
5
|
+
This is a lightweight endpoint that only streams status changes, not full
|
|
6
|
+
execution content (messages, tool calls, etc.).
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Long-running connection (no timeout)
|
|
10
|
+
- Authentication via JWT tokens (same as websocket_client.py)
|
|
11
|
+
- Subscribes to organization-wide status updates
|
|
12
|
+
- Heartbeat/keepalive mechanism
|
|
13
|
+
|
|
14
|
+
Architecture:
|
|
15
|
+
Browser → WebSocket → Control Plane API → Redis Pub/Sub → Status Events
|
|
16
|
+
|
|
17
|
+
Events sent:
|
|
18
|
+
- execution_status: { execution_id, status, updated_at }
|
|
19
|
+
- execution_created: { execution_id, status, entity_type, entity_name, prompt_preview }
|
|
20
|
+
- execution_deleted: { execution_id }
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, status
|
|
24
|
+
from typing import Optional, Dict, Any, Set
|
|
25
|
+
import structlog
|
|
26
|
+
import json
|
|
27
|
+
import asyncio
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
|
|
30
|
+
from control_plane_api.app.lib.redis_client import get_redis_client
|
|
31
|
+
|
|
32
|
+
logger = structlog.get_logger()
|
|
33
|
+
|
|
34
|
+
router = APIRouter()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StatusConnectionManager:
|
|
38
|
+
"""
|
|
39
|
+
Manages WebSocket connections for organization-wide status updates.
|
|
40
|
+
|
|
41
|
+
Features:
|
|
42
|
+
- Per-organization connection tracking
|
|
43
|
+
- Connection statistics
|
|
44
|
+
- Cleanup on disconnect
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
# connection_id -> WebSocket
|
|
49
|
+
self._connections: Dict[str, WebSocket] = {}
|
|
50
|
+
|
|
51
|
+
# organization_id -> Set[connection_id]
|
|
52
|
+
self._org_connections: Dict[str, Set[str]] = {}
|
|
53
|
+
|
|
54
|
+
# Connection statistics
|
|
55
|
+
self._stats = {
|
|
56
|
+
"total_connections": 0,
|
|
57
|
+
"active_connections": 0,
|
|
58
|
+
"messages_sent": 0,
|
|
59
|
+
"errors": 0,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Connection counter for unique IDs
|
|
63
|
+
self._connection_counter = 0
|
|
64
|
+
|
|
65
|
+
def _generate_connection_id(self) -> str:
|
|
66
|
+
"""Generate unique connection ID."""
|
|
67
|
+
self._connection_counter += 1
|
|
68
|
+
return f"status_conn_{self._connection_counter}_{int(datetime.now(timezone.utc).timestamp() * 1000)}"
|
|
69
|
+
|
|
70
|
+
async def connect(
|
|
71
|
+
self,
|
|
72
|
+
organization_id: str,
|
|
73
|
+
websocket: WebSocket,
|
|
74
|
+
) -> str:
|
|
75
|
+
"""Register a new status WebSocket connection."""
|
|
76
|
+
connection_id = self._generate_connection_id()
|
|
77
|
+
|
|
78
|
+
# Track connection
|
|
79
|
+
self._connections[connection_id] = websocket
|
|
80
|
+
|
|
81
|
+
if organization_id not in self._org_connections:
|
|
82
|
+
self._org_connections[organization_id] = set()
|
|
83
|
+
self._org_connections[organization_id].add(connection_id)
|
|
84
|
+
|
|
85
|
+
# Update stats
|
|
86
|
+
self._stats["total_connections"] += 1
|
|
87
|
+
self._stats["active_connections"] = len(self._connections)
|
|
88
|
+
|
|
89
|
+
logger.info(
|
|
90
|
+
"status_websocket_connected",
|
|
91
|
+
connection_id=connection_id,
|
|
92
|
+
organization_id=organization_id[:8] if len(organization_id) > 8 else organization_id,
|
|
93
|
+
active_connections=self._stats["active_connections"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return connection_id
|
|
97
|
+
|
|
98
|
+
async def disconnect(self, connection_id: str, organization_id: str) -> None:
|
|
99
|
+
"""Unregister a status WebSocket connection."""
|
|
100
|
+
if connection_id in self._connections:
|
|
101
|
+
del self._connections[connection_id]
|
|
102
|
+
|
|
103
|
+
if organization_id in self._org_connections:
|
|
104
|
+
self._org_connections[organization_id].discard(connection_id)
|
|
105
|
+
if not self._org_connections[organization_id]:
|
|
106
|
+
del self._org_connections[organization_id]
|
|
107
|
+
|
|
108
|
+
self._stats["active_connections"] = len(self._connections)
|
|
109
|
+
|
|
110
|
+
logger.info(
|
|
111
|
+
"status_websocket_disconnected",
|
|
112
|
+
connection_id=connection_id,
|
|
113
|
+
organization_id=organization_id[:8] if len(organization_id) > 8 else organization_id,
|
|
114
|
+
active_connections=self._stats["active_connections"],
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
118
|
+
"""Get connection statistics."""
|
|
119
|
+
return {
|
|
120
|
+
**self._stats,
|
|
121
|
+
"connections_by_org": {
|
|
122
|
+
org_id[:8] if len(org_id) > 8 else org_id: len(conns)
|
|
123
|
+
for org_id, conns in self._org_connections.items()
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Global connection manager
|
|
129
|
+
status_manager = StatusConnectionManager()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def send_json(websocket: WebSocket, event_type: str, data: Dict[str, Any]) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Send JSON message via WebSocket.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
websocket: WebSocket connection
|
|
138
|
+
event_type: Event type (e.g., 'execution_status', 'connected')
|
|
139
|
+
data: Event data payload
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
message = {
|
|
143
|
+
"type": event_type,
|
|
144
|
+
**data
|
|
145
|
+
}
|
|
146
|
+
await websocket.send_text(json.dumps(message))
|
|
147
|
+
status_manager._stats["messages_sent"] += 1
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error("failed_to_send_status_message", error=str(e), event_type=event_type)
|
|
150
|
+
status_manager._stats["errors"] += 1
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def handle_auth(websocket: WebSocket, token: str) -> Optional[str]:
|
|
155
|
+
"""
|
|
156
|
+
Handle authentication message.
|
|
157
|
+
|
|
158
|
+
Supports multiple authentication methods:
|
|
159
|
+
1. JWT tokens with org_id claim (Auth0 tokens)
|
|
160
|
+
2. Kubiya API keys (validated via Kubiya API)
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
websocket: WebSocket connection
|
|
164
|
+
token: Authentication token (JWT or API key)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
organization_id if authentication successful, None otherwise
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
# Import auth utilities
|
|
171
|
+
from control_plane_api.app.middleware.auth import (
|
|
172
|
+
decode_jwt_token,
|
|
173
|
+
validate_kubiya_api_key,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
organization_id = None
|
|
177
|
+
user_id = None
|
|
178
|
+
|
|
179
|
+
# First, try to decode as JWT
|
|
180
|
+
decoded = decode_jwt_token(token)
|
|
181
|
+
|
|
182
|
+
if decoded:
|
|
183
|
+
# Check for org_id in various claim locations
|
|
184
|
+
organization_id = (
|
|
185
|
+
decoded.get('https://kubiya.ai/org_id') or
|
|
186
|
+
decoded.get('org_id') or
|
|
187
|
+
decoded.get('organization_id') or
|
|
188
|
+
decoded.get('organization') # Kubiya API keys use 'organization' field
|
|
189
|
+
)
|
|
190
|
+
user_id = decoded.get('sub') or decoded.get('user_id') or decoded.get('email')
|
|
191
|
+
|
|
192
|
+
# If JWT decode failed or org_id not found, try Kubiya API validation
|
|
193
|
+
if not organization_id:
|
|
194
|
+
logger.debug("status_ws_jwt_org_id_not_found_trying_api_validation")
|
|
195
|
+
|
|
196
|
+
# Try UserKey format first (for CLI API keys)
|
|
197
|
+
user_data = await validate_kubiya_api_key(f"UserKey {token}", use_userkey=True)
|
|
198
|
+
|
|
199
|
+
if not user_data:
|
|
200
|
+
# Try Bearer format
|
|
201
|
+
user_data = await validate_kubiya_api_key(f"Bearer {token}", use_userkey=False)
|
|
202
|
+
|
|
203
|
+
if user_data:
|
|
204
|
+
organization_id = user_data.get('org')
|
|
205
|
+
user_id = user_data.get('uuid') or user_data.get('email')
|
|
206
|
+
logger.info("status_websocket_auth_via_kubiya_api", org=organization_id)
|
|
207
|
+
|
|
208
|
+
if not organization_id:
|
|
209
|
+
logger.error("status_ws_org_id_missing", decoded_claims=list(decoded.keys()) if decoded else [])
|
|
210
|
+
await send_json(websocket, "auth_error", {
|
|
211
|
+
"error": "Organization ID not found in token",
|
|
212
|
+
"code": "ORG_ID_MISSING",
|
|
213
|
+
})
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
logger.info(
|
|
217
|
+
"status_websocket_authenticated",
|
|
218
|
+
organization_id=organization_id[:8] if len(organization_id) > 8 else organization_id,
|
|
219
|
+
user_id=user_id[:8] if user_id and len(user_id) > 8 else user_id,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Send auth success
|
|
223
|
+
await send_json(websocket, "auth_success", {
|
|
224
|
+
"organization_id": organization_id,
|
|
225
|
+
"user_id": user_id,
|
|
226
|
+
"authenticated_at": datetime.now(timezone.utc).isoformat(),
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return organization_id
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error("status_authentication_failed", error=str(e), error_type=type(e).__name__)
|
|
233
|
+
await send_json(websocket, "auth_error", {
|
|
234
|
+
"error": "Authentication failed",
|
|
235
|
+
"code": "AUTH_FAILED",
|
|
236
|
+
})
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@router.websocket("/ws/executions/status")
|
|
241
|
+
async def websocket_execution_status_stream(
|
|
242
|
+
websocket: WebSocket,
|
|
243
|
+
):
|
|
244
|
+
"""
|
|
245
|
+
WebSocket endpoint for organization-wide execution status updates.
|
|
246
|
+
|
|
247
|
+
Streams lightweight status events for ALL executions in the organization,
|
|
248
|
+
enabling real-time kanban board updates without polling.
|
|
249
|
+
|
|
250
|
+
Protocol:
|
|
251
|
+
1. Client connects
|
|
252
|
+
2. Client sends auth message: {"type": "auth", "token": "jwt..."}
|
|
253
|
+
3. Server authenticates and sends 'auth_success'
|
|
254
|
+
4. Server sends 'connected' event
|
|
255
|
+
5. Server subscribes to Redis pub/sub for organization status events
|
|
256
|
+
6. Server streams events until client disconnects
|
|
257
|
+
7. Client sends ping, server responds with pong (keepalive)
|
|
258
|
+
|
|
259
|
+
Events sent to client:
|
|
260
|
+
- auth_success: Authentication successful
|
|
261
|
+
- auth_error: Authentication failed
|
|
262
|
+
- connected: Connection established
|
|
263
|
+
- execution_status: { execution_id, status, updated_at }
|
|
264
|
+
- pong: Response to ping (keepalive)
|
|
265
|
+
- error: Error occurred
|
|
266
|
+
|
|
267
|
+
Client messages:
|
|
268
|
+
- {"type": "auth", "token": "..."}: Authenticate
|
|
269
|
+
- {"type": "ping"}: Keepalive ping
|
|
270
|
+
"""
|
|
271
|
+
organization_id: Optional[str] = None
|
|
272
|
+
connection_id: Optional[str] = None
|
|
273
|
+
pubsub = None
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
# Accept connection
|
|
277
|
+
await websocket.accept()
|
|
278
|
+
|
|
279
|
+
logger.info("status_websocket_connection_started")
|
|
280
|
+
|
|
281
|
+
# Wait for authentication message (timeout after 5 seconds)
|
|
282
|
+
try:
|
|
283
|
+
auth_message = await asyncio.wait_for(websocket.receive_text(), timeout=5.0)
|
|
284
|
+
auth_data = json.loads(auth_message)
|
|
285
|
+
|
|
286
|
+
if auth_data.get("type") == "auth" and "token" in auth_data:
|
|
287
|
+
organization_id = await handle_auth(websocket, auth_data["token"])
|
|
288
|
+
if not organization_id:
|
|
289
|
+
await websocket.close(code=4001, reason="Authentication failed")
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# Register connection
|
|
293
|
+
connection_id = await status_manager.connect(organization_id, websocket)
|
|
294
|
+
|
|
295
|
+
except asyncio.TimeoutError:
|
|
296
|
+
logger.error("status_ws_auth_timeout")
|
|
297
|
+
await websocket.close(code=4002, reason="Authentication timeout")
|
|
298
|
+
return
|
|
299
|
+
except json.JSONDecodeError:
|
|
300
|
+
logger.error("status_ws_invalid_auth_message")
|
|
301
|
+
await websocket.close(code=4003, reason="Invalid authentication message")
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
# Send connected event
|
|
305
|
+
await send_json(websocket, "connected", {
|
|
306
|
+
"connection_id": connection_id,
|
|
307
|
+
"organization_id": organization_id,
|
|
308
|
+
"connected_at": datetime.now(timezone.utc).isoformat(),
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
# Get Redis client and subscribe to status channel
|
|
312
|
+
redis_client = get_redis_client()
|
|
313
|
+
if not redis_client:
|
|
314
|
+
logger.error("status_ws_redis_not_available")
|
|
315
|
+
await send_json(websocket, "error", {"error": "Redis not available"})
|
|
316
|
+
await websocket.close(code=1011, reason="Redis not available")
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
# Subscribe to organization-wide status channel
|
|
320
|
+
status_channel = f"organization:{organization_id}:execution_status"
|
|
321
|
+
pubsub = redis_client.pubsub()
|
|
322
|
+
await pubsub.subscribe(status_channel)
|
|
323
|
+
|
|
324
|
+
logger.info(
|
|
325
|
+
"status_websocket_subscribed",
|
|
326
|
+
channel=status_channel,
|
|
327
|
+
organization_id=organization_id[:8] if len(organization_id) > 8 else organization_id,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Listen for both Redis events and client messages
|
|
331
|
+
async def listen_redis():
|
|
332
|
+
"""Listen for Redis pub/sub status events."""
|
|
333
|
+
try:
|
|
334
|
+
async for message in pubsub.listen():
|
|
335
|
+
if message["type"] == "message":
|
|
336
|
+
try:
|
|
337
|
+
data = json.loads(message["data"])
|
|
338
|
+
event_type = data.get("event_type", "execution_status")
|
|
339
|
+
|
|
340
|
+
# Forward the event to WebSocket
|
|
341
|
+
await send_json(websocket, event_type, {
|
|
342
|
+
"execution_id": data.get("execution_id"),
|
|
343
|
+
"status": data.get("status"),
|
|
344
|
+
"updated_at": data.get("updated_at") or datetime.now(timezone.utc).isoformat(),
|
|
345
|
+
"entity_type": data.get("entity_type"),
|
|
346
|
+
"entity_name": data.get("entity_name"),
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
logger.debug(
|
|
350
|
+
"status_event_forwarded",
|
|
351
|
+
execution_id=data.get("execution_id", "")[:8],
|
|
352
|
+
status=data.get("status"),
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
except json.JSONDecodeError as e:
|
|
356
|
+
logger.warning("status_ws_invalid_redis_message", error=str(e))
|
|
357
|
+
|
|
358
|
+
except asyncio.CancelledError:
|
|
359
|
+
pass
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.error("status_redis_listener_error", error=str(e))
|
|
362
|
+
|
|
363
|
+
async def listen_client():
|
|
364
|
+
"""Listen for client messages (ping, etc.)."""
|
|
365
|
+
try:
|
|
366
|
+
while True:
|
|
367
|
+
message = await websocket.receive_text()
|
|
368
|
+
data = json.loads(message)
|
|
369
|
+
|
|
370
|
+
if data.get("type") == "ping":
|
|
371
|
+
await send_json(websocket, "pong", {
|
|
372
|
+
"timestamp": int(datetime.now(timezone.utc).timestamp() * 1000)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
except WebSocketDisconnect:
|
|
376
|
+
pass
|
|
377
|
+
except asyncio.CancelledError:
|
|
378
|
+
pass
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error("status_client_listener_error", error=str(e))
|
|
381
|
+
|
|
382
|
+
# Run both listeners concurrently
|
|
383
|
+
redis_task = asyncio.create_task(listen_redis())
|
|
384
|
+
client_task = asyncio.create_task(listen_client())
|
|
385
|
+
|
|
386
|
+
# Wait for either task to complete (client disconnect or error)
|
|
387
|
+
done, pending = await asyncio.wait(
|
|
388
|
+
{redis_task, client_task},
|
|
389
|
+
return_when=asyncio.FIRST_COMPLETED
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Cancel pending tasks
|
|
393
|
+
for task in pending:
|
|
394
|
+
task.cancel()
|
|
395
|
+
try:
|
|
396
|
+
await task
|
|
397
|
+
except asyncio.CancelledError:
|
|
398
|
+
pass
|
|
399
|
+
|
|
400
|
+
except WebSocketDisconnect:
|
|
401
|
+
logger.info("status_websocket_client_disconnected", connection_id=connection_id)
|
|
402
|
+
|
|
403
|
+
except Exception as e:
|
|
404
|
+
logger.error(
|
|
405
|
+
"status_websocket_error",
|
|
406
|
+
error=str(e),
|
|
407
|
+
error_type=type(e).__name__,
|
|
408
|
+
connection_id=connection_id,
|
|
409
|
+
)
|
|
410
|
+
try:
|
|
411
|
+
await send_json(websocket, "error", {"error": str(e)})
|
|
412
|
+
except:
|
|
413
|
+
pass
|
|
414
|
+
|
|
415
|
+
finally:
|
|
416
|
+
# Cleanup
|
|
417
|
+
if pubsub:
|
|
418
|
+
try:
|
|
419
|
+
await pubsub.unsubscribe()
|
|
420
|
+
await pubsub.close()
|
|
421
|
+
except:
|
|
422
|
+
pass
|
|
423
|
+
|
|
424
|
+
if connection_id and organization_id:
|
|
425
|
+
await status_manager.disconnect(connection_id, organization_id)
|
|
426
|
+
|
|
427
|
+
logger.info("status_websocket_closed", connection_id=connection_id)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@router.get("/ws/executions/status/stats")
|
|
431
|
+
async def websocket_status_stats():
|
|
432
|
+
"""
|
|
433
|
+
Get WebSocket status connection statistics.
|
|
434
|
+
|
|
435
|
+
Returns statistics about active connections and message throughput.
|
|
436
|
+
"""
|
|
437
|
+
return status_manager.get_stats()
|