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,345 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for ControlPlaneClient job execution methods.
|
|
3
|
+
|
|
4
|
+
Tests the HTTP client methods that workers use to create and update job executions.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
from unittest.mock import Mock, AsyncMock, patch
|
|
8
|
+
import httpx
|
|
9
|
+
from control_plane_api.worker.control_plane_client import ControlPlaneClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def mock_async_client():
|
|
14
|
+
"""Mock async HTTP client."""
|
|
15
|
+
mock_client = AsyncMock()
|
|
16
|
+
mock_response = Mock()
|
|
17
|
+
mock_response.status_code = 201
|
|
18
|
+
mock_response.json.return_value = {
|
|
19
|
+
"execution_id": "exec_test_123",
|
|
20
|
+
"status": "created",
|
|
21
|
+
"created_at": "2024-01-01T00:00:00Z"
|
|
22
|
+
}
|
|
23
|
+
mock_client.post = AsyncMock(return_value=mock_response)
|
|
24
|
+
return mock_client
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def control_plane_client(mock_async_client):
|
|
29
|
+
"""Create ControlPlaneClient with mocked HTTP client."""
|
|
30
|
+
client = ControlPlaneClient(
|
|
31
|
+
base_url="http://test-control-plane",
|
|
32
|
+
api_key="test-api-key",
|
|
33
|
+
websocket_enabled=False
|
|
34
|
+
)
|
|
35
|
+
# Replace the async client with our mock
|
|
36
|
+
client._async_client = mock_async_client
|
|
37
|
+
return client
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.asyncio
|
|
41
|
+
class TestCreateJobExecutionRecord:
|
|
42
|
+
"""Test create_job_execution_record method."""
|
|
43
|
+
|
|
44
|
+
async def test_create_job_execution_record_success(
|
|
45
|
+
self, control_plane_client, mock_async_client
|
|
46
|
+
):
|
|
47
|
+
"""Test successfully creating a job execution record."""
|
|
48
|
+
result = await control_plane_client.create_job_execution_record(
|
|
49
|
+
execution_id="exec_test_123",
|
|
50
|
+
job_id="job_test_456",
|
|
51
|
+
organization_id="org_test_789",
|
|
52
|
+
entity_type="agent",
|
|
53
|
+
entity_id="agent_test_111",
|
|
54
|
+
prompt="Test prompt",
|
|
55
|
+
trigger_type="cron",
|
|
56
|
+
trigger_metadata={"job_name": "Test Job"}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Verify the HTTP call was made
|
|
60
|
+
mock_async_client.post.assert_called_once()
|
|
61
|
+
call_args = mock_async_client.post.call_args
|
|
62
|
+
|
|
63
|
+
# Verify URL
|
|
64
|
+
assert call_args[0][0] == "http://test-control-plane/api/v1/executions/create"
|
|
65
|
+
|
|
66
|
+
# Verify payload
|
|
67
|
+
payload = call_args.kwargs["json"]
|
|
68
|
+
assert payload["execution_id"] == "exec_test_123"
|
|
69
|
+
assert payload["job_id"] == "job_test_456"
|
|
70
|
+
assert payload["organization_id"] == "org_test_789"
|
|
71
|
+
assert payload["entity_type"] == "agent"
|
|
72
|
+
assert payload["entity_id"] == "agent_test_111"
|
|
73
|
+
assert payload["prompt"] == "Test prompt"
|
|
74
|
+
assert payload["trigger_type"] == "cron"
|
|
75
|
+
assert payload["trigger_metadata"]["job_name"] == "Test Job"
|
|
76
|
+
|
|
77
|
+
# Verify headers
|
|
78
|
+
assert call_args.kwargs["headers"]["Authorization"] == "UserKey test-api-key"
|
|
79
|
+
|
|
80
|
+
# Verify result
|
|
81
|
+
assert result["execution_id"] == "exec_test_123"
|
|
82
|
+
assert result["status"] == "created"
|
|
83
|
+
|
|
84
|
+
async def test_create_job_execution_record_without_job_id(
|
|
85
|
+
self, control_plane_client, mock_async_client
|
|
86
|
+
):
|
|
87
|
+
"""Test creating execution record without job_id."""
|
|
88
|
+
result = await control_plane_client.create_job_execution_record(
|
|
89
|
+
execution_id="exec_test_456",
|
|
90
|
+
job_id=None,
|
|
91
|
+
organization_id="org_test_789",
|
|
92
|
+
entity_type="team",
|
|
93
|
+
entity_id="team_test_222",
|
|
94
|
+
prompt="Manual execution",
|
|
95
|
+
trigger_type="manual",
|
|
96
|
+
trigger_metadata={"user_id": "user_123"}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Verify the call
|
|
100
|
+
mock_async_client.post.assert_called_once()
|
|
101
|
+
payload = mock_async_client.post.call_args.kwargs["json"]
|
|
102
|
+
assert payload["job_id"] is None
|
|
103
|
+
assert payload["entity_type"] == "team"
|
|
104
|
+
assert payload["trigger_type"] == "manual"
|
|
105
|
+
|
|
106
|
+
async def test_create_job_execution_record_http_error(
|
|
107
|
+
self, control_plane_client, mock_async_client
|
|
108
|
+
):
|
|
109
|
+
"""Test handling HTTP error response."""
|
|
110
|
+
# Mock error response
|
|
111
|
+
error_response = Mock()
|
|
112
|
+
error_response.status_code = 500
|
|
113
|
+
error_response.text = "Internal server error"
|
|
114
|
+
mock_async_client.post = AsyncMock(return_value=error_response)
|
|
115
|
+
|
|
116
|
+
with pytest.raises(Exception) as exc_info:
|
|
117
|
+
await control_plane_client.create_job_execution_record(
|
|
118
|
+
execution_id="exec_error",
|
|
119
|
+
job_id="job_error",
|
|
120
|
+
organization_id="org_test",
|
|
121
|
+
entity_type="agent",
|
|
122
|
+
entity_id="agent_error",
|
|
123
|
+
prompt="Test",
|
|
124
|
+
trigger_type="cron",
|
|
125
|
+
trigger_metadata={}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
assert "Failed to create execution record" in str(exc_info.value)
|
|
129
|
+
assert "HTTP 500" in str(exc_info.value)
|
|
130
|
+
|
|
131
|
+
async def test_create_job_execution_record_network_error(
|
|
132
|
+
self, control_plane_client, mock_async_client
|
|
133
|
+
):
|
|
134
|
+
"""Test handling network error."""
|
|
135
|
+
# Mock network error
|
|
136
|
+
mock_async_client.post = AsyncMock(
|
|
137
|
+
side_effect=httpx.ConnectError("Connection failed")
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
with pytest.raises(Exception):
|
|
141
|
+
await control_plane_client.create_job_execution_record(
|
|
142
|
+
execution_id="exec_network_error",
|
|
143
|
+
job_id="job_test",
|
|
144
|
+
organization_id="org_test",
|
|
145
|
+
entity_type="agent",
|
|
146
|
+
entity_id="agent_test",
|
|
147
|
+
prompt="Test",
|
|
148
|
+
trigger_type="cron",
|
|
149
|
+
trigger_metadata={}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.asyncio
|
|
154
|
+
class TestUpdateJobExecutionStatus:
|
|
155
|
+
"""Test update_job_execution_status method."""
|
|
156
|
+
|
|
157
|
+
async def test_update_job_execution_status_completed(
|
|
158
|
+
self, control_plane_client, mock_async_client
|
|
159
|
+
):
|
|
160
|
+
"""Test updating job execution status to completed."""
|
|
161
|
+
# Mock successful response
|
|
162
|
+
success_response = Mock()
|
|
163
|
+
success_response.status_code = 200
|
|
164
|
+
success_response.json.return_value = {
|
|
165
|
+
"job_id": "job_test_456",
|
|
166
|
+
"execution_id": "exec_test_123",
|
|
167
|
+
"status": "updated"
|
|
168
|
+
}
|
|
169
|
+
mock_async_client.post = AsyncMock(return_value=success_response)
|
|
170
|
+
|
|
171
|
+
result = await control_plane_client.update_job_execution_status(
|
|
172
|
+
execution_id="exec_test_123",
|
|
173
|
+
job_id="job_test_456",
|
|
174
|
+
status="completed",
|
|
175
|
+
duration_ms=5000,
|
|
176
|
+
error_message=None
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Verify the HTTP call
|
|
180
|
+
mock_async_client.post.assert_called_once()
|
|
181
|
+
call_args = mock_async_client.post.call_args
|
|
182
|
+
|
|
183
|
+
# Verify URL
|
|
184
|
+
expected_url = "http://test-control-plane/api/v1/executions/exec_test_123/job/job_test_456/status"
|
|
185
|
+
assert call_args[0][0] == expected_url
|
|
186
|
+
|
|
187
|
+
# Verify payload
|
|
188
|
+
payload = call_args.kwargs["json"]
|
|
189
|
+
assert payload["status"] == "completed"
|
|
190
|
+
assert payload["duration_ms"] == 5000
|
|
191
|
+
assert payload["error_message"] is None
|
|
192
|
+
|
|
193
|
+
# Verify result
|
|
194
|
+
assert result["job_id"] == "job_test_456"
|
|
195
|
+
assert result["execution_id"] == "exec_test_123"
|
|
196
|
+
assert result["status"] == "updated"
|
|
197
|
+
|
|
198
|
+
async def test_update_job_execution_status_failed(
|
|
199
|
+
self, control_plane_client, mock_async_client
|
|
200
|
+
):
|
|
201
|
+
"""Test updating job execution status to failed."""
|
|
202
|
+
# Mock successful response
|
|
203
|
+
success_response = Mock()
|
|
204
|
+
success_response.status_code = 200
|
|
205
|
+
success_response.json.return_value = {
|
|
206
|
+
"job_id": "job_test_789",
|
|
207
|
+
"execution_id": "exec_test_456",
|
|
208
|
+
"status": "updated"
|
|
209
|
+
}
|
|
210
|
+
mock_async_client.post = AsyncMock(return_value=success_response)
|
|
211
|
+
|
|
212
|
+
result = await control_plane_client.update_job_execution_status(
|
|
213
|
+
execution_id="exec_test_456",
|
|
214
|
+
job_id="job_test_789",
|
|
215
|
+
status="failed",
|
|
216
|
+
duration_ms=1500,
|
|
217
|
+
error_message="Test error occurred"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Verify payload includes error message
|
|
221
|
+
payload = mock_async_client.post.call_args.kwargs["json"]
|
|
222
|
+
assert payload["status"] == "failed"
|
|
223
|
+
assert payload["duration_ms"] == 1500
|
|
224
|
+
assert payload["error_message"] == "Test error occurred"
|
|
225
|
+
|
|
226
|
+
async def test_update_job_execution_status_without_duration(
|
|
227
|
+
self, control_plane_client, mock_async_client
|
|
228
|
+
):
|
|
229
|
+
"""Test updating status without duration_ms."""
|
|
230
|
+
success_response = Mock()
|
|
231
|
+
success_response.status_code = 200
|
|
232
|
+
success_response.json.return_value = {
|
|
233
|
+
"job_id": "job_test",
|
|
234
|
+
"execution_id": "exec_test",
|
|
235
|
+
"status": "updated"
|
|
236
|
+
}
|
|
237
|
+
mock_async_client.post = AsyncMock(return_value=success_response)
|
|
238
|
+
|
|
239
|
+
result = await control_plane_client.update_job_execution_status(
|
|
240
|
+
execution_id="exec_test",
|
|
241
|
+
job_id="job_test",
|
|
242
|
+
status="completed"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Verify payload
|
|
246
|
+
payload = mock_async_client.post.call_args.kwargs["json"]
|
|
247
|
+
assert payload["status"] == "completed"
|
|
248
|
+
assert payload["duration_ms"] is None
|
|
249
|
+
assert payload["error_message"] is None
|
|
250
|
+
|
|
251
|
+
async def test_update_job_execution_status_http_error(
|
|
252
|
+
self, control_plane_client, mock_async_client
|
|
253
|
+
):
|
|
254
|
+
"""Test handling HTTP error response."""
|
|
255
|
+
error_response = Mock()
|
|
256
|
+
error_response.status_code = 404
|
|
257
|
+
error_response.text = "Job execution not found"
|
|
258
|
+
mock_async_client.post = AsyncMock(return_value=error_response)
|
|
259
|
+
|
|
260
|
+
with pytest.raises(Exception) as exc_info:
|
|
261
|
+
await control_plane_client.update_job_execution_status(
|
|
262
|
+
execution_id="exec_notfound",
|
|
263
|
+
job_id="job_notfound",
|
|
264
|
+
status="completed"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
assert "Failed to update job execution status" in str(exc_info.value)
|
|
268
|
+
assert "HTTP 404" in str(exc_info.value)
|
|
269
|
+
|
|
270
|
+
async def test_update_job_execution_status_timeout(
|
|
271
|
+
self, control_plane_client, mock_async_client
|
|
272
|
+
):
|
|
273
|
+
"""Test handling timeout error."""
|
|
274
|
+
mock_async_client.post = AsyncMock(
|
|
275
|
+
side_effect=httpx.TimeoutException("Request timeout")
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
with pytest.raises(Exception):
|
|
279
|
+
await control_plane_client.update_job_execution_status(
|
|
280
|
+
execution_id="exec_timeout",
|
|
281
|
+
job_id="job_timeout",
|
|
282
|
+
status="completed",
|
|
283
|
+
duration_ms=10000
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@pytest.mark.asyncio
|
|
288
|
+
class TestControlPlaneClientIntegration:
|
|
289
|
+
"""Integration tests for ControlPlaneClient job methods."""
|
|
290
|
+
|
|
291
|
+
async def test_full_job_execution_lifecycle(
|
|
292
|
+
self, control_plane_client, mock_async_client
|
|
293
|
+
):
|
|
294
|
+
"""Test complete lifecycle: create -> update status."""
|
|
295
|
+
# Mock create response
|
|
296
|
+
create_response = Mock()
|
|
297
|
+
create_response.status_code = 201
|
|
298
|
+
create_response.json.return_value = {
|
|
299
|
+
"execution_id": "exec_lifecycle_123",
|
|
300
|
+
"status": "created",
|
|
301
|
+
"created_at": "2024-01-01T00:00:00Z"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# Mock update response
|
|
305
|
+
update_response = Mock()
|
|
306
|
+
update_response.status_code = 200
|
|
307
|
+
update_response.json.return_value = {
|
|
308
|
+
"job_id": "job_lifecycle_456",
|
|
309
|
+
"execution_id": "exec_lifecycle_123",
|
|
310
|
+
"status": "updated"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# Configure mock to return different responses
|
|
314
|
+
mock_async_client.post = AsyncMock(
|
|
315
|
+
side_effect=[create_response, update_response]
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# 1. Create execution record
|
|
319
|
+
create_result = await control_plane_client.create_job_execution_record(
|
|
320
|
+
execution_id="exec_lifecycle_123",
|
|
321
|
+
job_id="job_lifecycle_456",
|
|
322
|
+
organization_id="org_test",
|
|
323
|
+
entity_type="agent",
|
|
324
|
+
entity_id="agent_test",
|
|
325
|
+
prompt="Lifecycle test",
|
|
326
|
+
trigger_type="cron",
|
|
327
|
+
trigger_metadata={"job_name": "Lifecycle Job"}
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
assert create_result["execution_id"] == "exec_lifecycle_123"
|
|
331
|
+
assert create_result["status"] == "created"
|
|
332
|
+
|
|
333
|
+
# 2. Update execution status
|
|
334
|
+
update_result = await control_plane_client.update_job_execution_status(
|
|
335
|
+
execution_id="exec_lifecycle_123",
|
|
336
|
+
job_id="job_lifecycle_456",
|
|
337
|
+
status="completed",
|
|
338
|
+
duration_ms=3000
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
assert update_result["execution_id"] == "exec_lifecycle_123"
|
|
342
|
+
assert update_result["status"] == "updated"
|
|
343
|
+
|
|
344
|
+
# Verify both calls were made
|
|
345
|
+
assert mock_async_client.post.call_count == 2
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for refactored job activities.
|
|
3
|
+
|
|
4
|
+
Tests that job activities properly use ControlPlaneClient instead of direct Supabase access.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
from unittest.mock import Mock, AsyncMock, patch
|
|
8
|
+
from control_plane_api.worker.activities.job_activities import (
|
|
9
|
+
create_job_execution_record,
|
|
10
|
+
update_job_execution_status,
|
|
11
|
+
ActivityCreateJobExecutionInput,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def mock_control_plane_client():
|
|
17
|
+
"""Mock ControlPlaneClient for testing."""
|
|
18
|
+
mock_client = Mock()
|
|
19
|
+
mock_client.create_job_execution_record = AsyncMock()
|
|
20
|
+
mock_client.update_job_execution_status = AsyncMock()
|
|
21
|
+
return mock_client
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.asyncio
|
|
25
|
+
class TestCreateJobExecutionRecordActivity:
|
|
26
|
+
"""Test create_job_execution_record activity."""
|
|
27
|
+
|
|
28
|
+
async def test_create_job_execution_record_with_job(
|
|
29
|
+
self, mock_control_plane_client
|
|
30
|
+
):
|
|
31
|
+
"""Test creating job execution record with job_id."""
|
|
32
|
+
# Mock successful response
|
|
33
|
+
mock_control_plane_client.create_job_execution_record.return_value = {
|
|
34
|
+
"execution_id": "exec_test_123",
|
|
35
|
+
"status": "created",
|
|
36
|
+
"created_at": "2024-01-01T00:00:00Z"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Create input
|
|
40
|
+
input_data = ActivityCreateJobExecutionInput(
|
|
41
|
+
execution_id="exec_test_123",
|
|
42
|
+
job_id="job_test_456",
|
|
43
|
+
organization_id="org_test_789",
|
|
44
|
+
entity_type="agent",
|
|
45
|
+
entity_id="agent_test_111",
|
|
46
|
+
prompt="Test scheduled prompt",
|
|
47
|
+
trigger_type="cron",
|
|
48
|
+
trigger_metadata={
|
|
49
|
+
"job_name": "Test Job",
|
|
50
|
+
"user_id": "user_test_456",
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Patch get_control_plane_client
|
|
55
|
+
with patch(
|
|
56
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
57
|
+
return_value=mock_control_plane_client
|
|
58
|
+
):
|
|
59
|
+
result = await create_job_execution_record(input_data)
|
|
60
|
+
|
|
61
|
+
# Verify client method was called
|
|
62
|
+
mock_control_plane_client.create_job_execution_record.assert_called_once_with(
|
|
63
|
+
execution_id="exec_test_123",
|
|
64
|
+
job_id="job_test_456",
|
|
65
|
+
organization_id="org_test_789",
|
|
66
|
+
entity_type="agent",
|
|
67
|
+
entity_id="agent_test_111",
|
|
68
|
+
prompt="Test scheduled prompt",
|
|
69
|
+
trigger_type="cron",
|
|
70
|
+
trigger_metadata={
|
|
71
|
+
"job_name": "Test Job",
|
|
72
|
+
"user_id": "user_test_456",
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Verify result
|
|
77
|
+
assert result["execution_id"] == "exec_test_123"
|
|
78
|
+
assert result["status"] == "created"
|
|
79
|
+
|
|
80
|
+
async def test_create_job_execution_record_without_job(
|
|
81
|
+
self, mock_control_plane_client
|
|
82
|
+
):
|
|
83
|
+
"""Test creating execution record without job_id."""
|
|
84
|
+
mock_control_plane_client.create_job_execution_record.return_value = {
|
|
85
|
+
"execution_id": "exec_test_456",
|
|
86
|
+
"status": "created",
|
|
87
|
+
"created_at": "2024-01-01T00:00:00Z"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
input_data = ActivityCreateJobExecutionInput(
|
|
91
|
+
execution_id="exec_test_456",
|
|
92
|
+
job_id=None,
|
|
93
|
+
organization_id="org_test_789",
|
|
94
|
+
entity_type="team",
|
|
95
|
+
entity_id="team_test_222",
|
|
96
|
+
prompt="Manual execution",
|
|
97
|
+
trigger_type="manual",
|
|
98
|
+
trigger_metadata={"user_id": "user_test_456"}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
with patch(
|
|
102
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
103
|
+
return_value=mock_control_plane_client
|
|
104
|
+
):
|
|
105
|
+
result = await create_job_execution_record(input_data)
|
|
106
|
+
|
|
107
|
+
# Verify call
|
|
108
|
+
call_args = mock_control_plane_client.create_job_execution_record.call_args
|
|
109
|
+
assert call_args.kwargs["job_id"] is None
|
|
110
|
+
assert call_args.kwargs["entity_type"] == "team"
|
|
111
|
+
assert call_args.kwargs["trigger_type"] == "manual"
|
|
112
|
+
|
|
113
|
+
assert result["execution_id"] == "exec_test_456"
|
|
114
|
+
|
|
115
|
+
async def test_create_job_execution_record_webhook_trigger(
|
|
116
|
+
self, mock_control_plane_client
|
|
117
|
+
):
|
|
118
|
+
"""Test creating execution record for webhook trigger."""
|
|
119
|
+
mock_control_plane_client.create_job_execution_record.return_value = {
|
|
120
|
+
"execution_id": "exec_webhook_123",
|
|
121
|
+
"status": "created",
|
|
122
|
+
"created_at": "2024-01-01T00:00:00Z"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
input_data = ActivityCreateJobExecutionInput(
|
|
126
|
+
execution_id="exec_webhook_123",
|
|
127
|
+
job_id="job_webhook_456",
|
|
128
|
+
organization_id="org_test_789",
|
|
129
|
+
entity_type="agent",
|
|
130
|
+
entity_id="agent_webhook_111",
|
|
131
|
+
prompt="Handle webhook {{payload}}",
|
|
132
|
+
trigger_type="webhook",
|
|
133
|
+
trigger_metadata={
|
|
134
|
+
"job_name": "Webhook Job",
|
|
135
|
+
"webhook_payload": {"alert": "test"},
|
|
136
|
+
"webhook_headers": {"x-source": "test"}
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
with patch(
|
|
141
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
142
|
+
return_value=mock_control_plane_client
|
|
143
|
+
):
|
|
144
|
+
result = await create_job_execution_record(input_data)
|
|
145
|
+
|
|
146
|
+
# Verify webhook metadata was passed
|
|
147
|
+
call_args = mock_control_plane_client.create_job_execution_record.call_args
|
|
148
|
+
metadata = call_args.kwargs["trigger_metadata"]
|
|
149
|
+
assert metadata["job_name"] == "Webhook Job"
|
|
150
|
+
assert metadata["webhook_payload"]["alert"] == "test"
|
|
151
|
+
assert metadata["webhook_headers"]["x-source"] == "test"
|
|
152
|
+
|
|
153
|
+
async def test_create_job_execution_record_error_handling(
|
|
154
|
+
self, mock_control_plane_client
|
|
155
|
+
):
|
|
156
|
+
"""Test error handling when client call fails."""
|
|
157
|
+
# Mock error
|
|
158
|
+
mock_control_plane_client.create_job_execution_record.side_effect = Exception(
|
|
159
|
+
"HTTP 500 error"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
input_data = ActivityCreateJobExecutionInput(
|
|
163
|
+
execution_id="exec_error",
|
|
164
|
+
job_id="job_error",
|
|
165
|
+
organization_id="org_test",
|
|
166
|
+
entity_type="agent",
|
|
167
|
+
entity_id="agent_error",
|
|
168
|
+
prompt="Test",
|
|
169
|
+
trigger_type="cron",
|
|
170
|
+
trigger_metadata={}
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
with patch(
|
|
174
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
175
|
+
return_value=mock_control_plane_client
|
|
176
|
+
):
|
|
177
|
+
with pytest.raises(Exception) as exc_info:
|
|
178
|
+
await create_job_execution_record(input_data)
|
|
179
|
+
|
|
180
|
+
assert "HTTP 500 error" in str(exc_info.value)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@pytest.mark.asyncio
|
|
184
|
+
class TestUpdateJobExecutionStatusActivity:
|
|
185
|
+
"""Test update_job_execution_status activity."""
|
|
186
|
+
|
|
187
|
+
async def test_update_job_execution_status_completed(
|
|
188
|
+
self, mock_control_plane_client
|
|
189
|
+
):
|
|
190
|
+
"""Test updating job execution status to completed."""
|
|
191
|
+
mock_control_plane_client.update_job_execution_status.return_value = {
|
|
192
|
+
"job_id": "job_test_456",
|
|
193
|
+
"execution_id": "exec_test_123",
|
|
194
|
+
"status": "updated"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
with patch(
|
|
198
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
199
|
+
return_value=mock_control_plane_client
|
|
200
|
+
):
|
|
201
|
+
result = await update_job_execution_status(
|
|
202
|
+
job_id="job_test_456",
|
|
203
|
+
execution_id="exec_test_123",
|
|
204
|
+
status="completed",
|
|
205
|
+
duration_ms=5000,
|
|
206
|
+
error_message=None
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Verify client method was called
|
|
210
|
+
mock_control_plane_client.update_job_execution_status.assert_called_once_with(
|
|
211
|
+
execution_id="exec_test_123",
|
|
212
|
+
job_id="job_test_456",
|
|
213
|
+
status="completed",
|
|
214
|
+
duration_ms=5000,
|
|
215
|
+
error_message=None
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Verify result
|
|
219
|
+
assert result["job_id"] == "job_test_456"
|
|
220
|
+
assert result["execution_id"] == "exec_test_123"
|
|
221
|
+
assert result["status"] == "updated"
|
|
222
|
+
|
|
223
|
+
async def test_update_job_execution_status_failed(
|
|
224
|
+
self, mock_control_plane_client
|
|
225
|
+
):
|
|
226
|
+
"""Test updating job execution status to failed."""
|
|
227
|
+
mock_control_plane_client.update_job_execution_status.return_value = {
|
|
228
|
+
"job_id": "job_test_789",
|
|
229
|
+
"execution_id": "exec_test_456",
|
|
230
|
+
"status": "updated"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
with patch(
|
|
234
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
235
|
+
return_value=mock_control_plane_client
|
|
236
|
+
):
|
|
237
|
+
result = await update_job_execution_status(
|
|
238
|
+
job_id="job_test_789",
|
|
239
|
+
execution_id="exec_test_456",
|
|
240
|
+
status="failed",
|
|
241
|
+
duration_ms=1500,
|
|
242
|
+
error_message="Test error occurred"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Verify call includes error message
|
|
246
|
+
call_args = mock_control_plane_client.update_job_execution_status.call_args
|
|
247
|
+
assert call_args.kwargs["status"] == "failed"
|
|
248
|
+
assert call_args.kwargs["duration_ms"] == 1500
|
|
249
|
+
assert call_args.kwargs["error_message"] == "Test error occurred"
|
|
250
|
+
|
|
251
|
+
async def test_update_job_execution_status_without_duration(
|
|
252
|
+
self, mock_control_plane_client
|
|
253
|
+
):
|
|
254
|
+
"""Test updating status without duration_ms."""
|
|
255
|
+
mock_control_plane_client.update_job_execution_status.return_value = {
|
|
256
|
+
"job_id": "job_test",
|
|
257
|
+
"execution_id": "exec_test",
|
|
258
|
+
"status": "updated"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
with patch(
|
|
262
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
263
|
+
return_value=mock_control_plane_client
|
|
264
|
+
):
|
|
265
|
+
result = await update_job_execution_status(
|
|
266
|
+
job_id="job_test",
|
|
267
|
+
execution_id="exec_test",
|
|
268
|
+
status="completed"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Verify call with None values
|
|
272
|
+
call_args = mock_control_plane_client.update_job_execution_status.call_args
|
|
273
|
+
assert call_args.kwargs["duration_ms"] is None
|
|
274
|
+
assert call_args.kwargs["error_message"] is None
|
|
275
|
+
|
|
276
|
+
async def test_update_job_execution_status_error_handling(
|
|
277
|
+
self, mock_control_plane_client
|
|
278
|
+
):
|
|
279
|
+
"""Test error handling when client call fails."""
|
|
280
|
+
mock_control_plane_client.update_job_execution_status.side_effect = Exception(
|
|
281
|
+
"HTTP 404 error"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
with patch(
|
|
285
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
286
|
+
return_value=mock_control_plane_client
|
|
287
|
+
):
|
|
288
|
+
with pytest.raises(Exception) as exc_info:
|
|
289
|
+
await update_job_execution_status(
|
|
290
|
+
job_id="job_notfound",
|
|
291
|
+
execution_id="exec_notfound",
|
|
292
|
+
status="completed"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
assert "HTTP 404 error" in str(exc_info.value)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@pytest.mark.asyncio
|
|
299
|
+
class TestJobActivitiesIntegration:
|
|
300
|
+
"""Integration tests for job activities workflow."""
|
|
301
|
+
|
|
302
|
+
async def test_full_activity_lifecycle(
|
|
303
|
+
self, mock_control_plane_client
|
|
304
|
+
):
|
|
305
|
+
"""Test complete lifecycle: create -> update."""
|
|
306
|
+
# Mock create response
|
|
307
|
+
mock_control_plane_client.create_job_execution_record.return_value = {
|
|
308
|
+
"execution_id": "exec_lifecycle_123",
|
|
309
|
+
"status": "created",
|
|
310
|
+
"created_at": "2024-01-01T00:00:00Z"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# Mock update response
|
|
314
|
+
mock_control_plane_client.update_job_execution_status.return_value = {
|
|
315
|
+
"job_id": "job_lifecycle_456",
|
|
316
|
+
"execution_id": "exec_lifecycle_123",
|
|
317
|
+
"status": "updated"
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
with patch(
|
|
321
|
+
"control_plane_api.worker.control_plane_client.get_control_plane_client",
|
|
322
|
+
return_value=mock_control_plane_client
|
|
323
|
+
):
|
|
324
|
+
# 1. Create execution record
|
|
325
|
+
input_data = ActivityCreateJobExecutionInput(
|
|
326
|
+
execution_id="exec_lifecycle_123",
|
|
327
|
+
job_id="job_lifecycle_456",
|
|
328
|
+
organization_id="org_test",
|
|
329
|
+
entity_type="agent",
|
|
330
|
+
entity_id="agent_test",
|
|
331
|
+
prompt="Lifecycle test",
|
|
332
|
+
trigger_type="cron",
|
|
333
|
+
trigger_metadata={"job_name": "Lifecycle Job"}
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
create_result = await create_job_execution_record(input_data)
|
|
337
|
+
assert create_result["execution_id"] == "exec_lifecycle_123"
|
|
338
|
+
assert create_result["status"] == "created"
|
|
339
|
+
|
|
340
|
+
# 2. Update execution status
|
|
341
|
+
update_result = await update_job_execution_status(
|
|
342
|
+
job_id="job_lifecycle_456",
|
|
343
|
+
execution_id="exec_lifecycle_123",
|
|
344
|
+
status="completed",
|
|
345
|
+
duration_ms=3000
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
assert update_result["execution_id"] == "exec_lifecycle_123"
|
|
349
|
+
assert update_result["status"] == "updated"
|
|
350
|
+
|
|
351
|
+
# Verify both methods were called
|
|
352
|
+
assert mock_control_plane_client.create_job_execution_record.call_count == 1
|
|
353
|
+
assert mock_control_plane_client.update_job_execution_status.call_count == 1
|