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,761 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environments router - Clean API for environment management.
|
|
3
|
+
|
|
4
|
+
This router provides /environments endpoints that map to the environments table.
|
|
5
|
+
The naming "environments" is internal - externally we call them "environments".
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
from sqlalchemy.orm import Session, joinedload
|
|
13
|
+
from sqlalchemy.exc import IntegrityError
|
|
14
|
+
import structlog
|
|
15
|
+
import uuid
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from control_plane_api.app.middleware.auth import get_current_organization
|
|
19
|
+
from control_plane_api.app.database import get_db
|
|
20
|
+
from control_plane_api.app.models.environment import Environment
|
|
21
|
+
from control_plane_api.app.models.skill import Skill, SkillAssociation
|
|
22
|
+
from control_plane_api.app.lib.temporal_client import get_temporal_client
|
|
23
|
+
|
|
24
|
+
logger = structlog.get_logger()
|
|
25
|
+
|
|
26
|
+
router = APIRouter()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Execution Environment Model (shared with agents/teams)
|
|
30
|
+
class ExecutionEnvironment(BaseModel):
|
|
31
|
+
"""
|
|
32
|
+
Execution environment configuration - env vars, secrets, integration credentials, and MCP servers.
|
|
33
|
+
|
|
34
|
+
All string fields in mcp_servers support template syntax:
|
|
35
|
+
- {{variable}} - Simple variables
|
|
36
|
+
- {{.secret.name}} - Secrets from vault
|
|
37
|
+
- {{.env.VAR}} - Environment variables
|
|
38
|
+
"""
|
|
39
|
+
env_vars: dict[str, str] = Field(default_factory=dict, description="Environment variables (key-value pairs)")
|
|
40
|
+
secrets: list[str] = Field(default_factory=list, description="Secret names from Kubiya vault")
|
|
41
|
+
integration_ids: list[str] = Field(default_factory=list, description="Integration UUIDs for delegated credentials")
|
|
42
|
+
mcp_servers: dict[str, dict] = Field(
|
|
43
|
+
default_factory=dict,
|
|
44
|
+
description="MCP (Model Context Protocol) server configurations. Supports stdio, HTTP, and SSE transports. All string values support template syntax."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Pydantic schemas
|
|
49
|
+
class EnvironmentCreate(BaseModel):
|
|
50
|
+
name: str = Field(..., description="Environment name (e.g., default, production)", min_length=2, max_length=100)
|
|
51
|
+
display_name: str | None = Field(None, description="User-friendly display name")
|
|
52
|
+
description: str | None = Field(None, description="Environment description")
|
|
53
|
+
tags: List[str] = Field(default_factory=list, description="Tags for categorization")
|
|
54
|
+
settings: dict = Field(default_factory=dict, description="Environment settings")
|
|
55
|
+
execution_environment: ExecutionEnvironment | None = Field(None, description="Execution environment configuration")
|
|
56
|
+
# Note: priority and policy_ids not supported by environments table
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EnvironmentUpdate(BaseModel):
|
|
60
|
+
name: str | None = None
|
|
61
|
+
display_name: str | None = None
|
|
62
|
+
description: str | None = None
|
|
63
|
+
tags: List[str] | None = None
|
|
64
|
+
settings: dict | None = None
|
|
65
|
+
status: str | None = None
|
|
66
|
+
execution_environment: ExecutionEnvironment | None = None
|
|
67
|
+
# Note: priority and policy_ids not supported by environments table
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class EnvironmentResponse(BaseModel):
|
|
71
|
+
id: str
|
|
72
|
+
organization_id: str
|
|
73
|
+
name: str
|
|
74
|
+
display_name: str | None
|
|
75
|
+
description: str | None
|
|
76
|
+
tags: List[str]
|
|
77
|
+
settings: dict
|
|
78
|
+
status: str
|
|
79
|
+
created_at: str
|
|
80
|
+
updated_at: str
|
|
81
|
+
created_by: str | None
|
|
82
|
+
|
|
83
|
+
# Temporal Cloud provisioning fields
|
|
84
|
+
worker_token: str | None = None
|
|
85
|
+
provisioning_workflow_id: str | None = None
|
|
86
|
+
provisioned_at: str | None = None
|
|
87
|
+
error_message: str | None = None
|
|
88
|
+
temporal_namespace_id: str | None = None
|
|
89
|
+
|
|
90
|
+
# Worker metrics (deprecated at environment level, use worker_queues)
|
|
91
|
+
active_workers: int = 0
|
|
92
|
+
idle_workers: int = 0
|
|
93
|
+
busy_workers: int = 0
|
|
94
|
+
|
|
95
|
+
# Skills (populated from associations)
|
|
96
|
+
skill_ids: List[str] = []
|
|
97
|
+
skills: List[dict] = []
|
|
98
|
+
|
|
99
|
+
# Execution environment configuration
|
|
100
|
+
execution_environment: dict = {}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class WorkerCommandResponse(BaseModel):
|
|
104
|
+
"""Response with worker registration command"""
|
|
105
|
+
worker_token: str
|
|
106
|
+
environment_name: str
|
|
107
|
+
command: str
|
|
108
|
+
command_parts: dict
|
|
109
|
+
namespace_status: str
|
|
110
|
+
can_register: bool
|
|
111
|
+
provisioning_workflow_id: str | None = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def ensure_default_environment(db: Session, organization: dict) -> Optional[Environment]:
|
|
115
|
+
"""
|
|
116
|
+
Ensure the organization has a default environment.
|
|
117
|
+
Creates one if it doesn't exist.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
# Check if default environment exists
|
|
121
|
+
existing = db.query(Environment).filter(
|
|
122
|
+
Environment.organization_id == organization["id"],
|
|
123
|
+
Environment.name == "default"
|
|
124
|
+
).first()
|
|
125
|
+
|
|
126
|
+
if existing:
|
|
127
|
+
return existing
|
|
128
|
+
|
|
129
|
+
# Create default environment
|
|
130
|
+
default_env = Environment(
|
|
131
|
+
id=uuid.uuid4(),
|
|
132
|
+
organization_id=organization["id"],
|
|
133
|
+
name="default",
|
|
134
|
+
display_name="Default Environment",
|
|
135
|
+
description="Default environment for all workers",
|
|
136
|
+
tags=[],
|
|
137
|
+
settings={},
|
|
138
|
+
status="active",
|
|
139
|
+
created_at=datetime.utcnow(),
|
|
140
|
+
updated_at=datetime.utcnow(),
|
|
141
|
+
created_by=organization.get("user_id"),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
db.add(default_env)
|
|
145
|
+
db.commit()
|
|
146
|
+
db.refresh(default_env)
|
|
147
|
+
|
|
148
|
+
logger.info(
|
|
149
|
+
"default_environment_created",
|
|
150
|
+
environment_id=str(default_env.id),
|
|
151
|
+
org_id=organization["id"],
|
|
152
|
+
)
|
|
153
|
+
return default_env
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
db.rollback()
|
|
157
|
+
logger.error("ensure_default_environment_failed", error=str(e), org_id=organization.get("id"))
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_environment_skills(db: Session, organization_id: str, environment_id: str) -> tuple[List[str], List[dict]]:
|
|
162
|
+
"""Get skills associated with an environment"""
|
|
163
|
+
try:
|
|
164
|
+
# Get associations with full skill data
|
|
165
|
+
associations = db.query(SkillAssociation).options(
|
|
166
|
+
joinedload(SkillAssociation.skill)
|
|
167
|
+
).filter(
|
|
168
|
+
SkillAssociation.organization_id == organization_id,
|
|
169
|
+
SkillAssociation.entity_type == "environment",
|
|
170
|
+
SkillAssociation.entity_id == environment_id
|
|
171
|
+
).all()
|
|
172
|
+
|
|
173
|
+
skill_ids = []
|
|
174
|
+
skills = []
|
|
175
|
+
|
|
176
|
+
for assoc in associations:
|
|
177
|
+
skill_data = assoc.skill
|
|
178
|
+
if skill_data:
|
|
179
|
+
skill_ids.append(str(skill_data.id))
|
|
180
|
+
|
|
181
|
+
# Merge configuration with override
|
|
182
|
+
config = skill_data.configuration or {}
|
|
183
|
+
override = assoc.configuration_override
|
|
184
|
+
if override:
|
|
185
|
+
config = {**config, **override}
|
|
186
|
+
|
|
187
|
+
skills.append({
|
|
188
|
+
"id": str(skill_data.id),
|
|
189
|
+
"name": skill_data.name,
|
|
190
|
+
"type": skill_data.skill_type,
|
|
191
|
+
"description": skill_data.description,
|
|
192
|
+
"enabled": skill_data.enabled if skill_data.enabled is not None else True,
|
|
193
|
+
"configuration": config,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return skill_ids, skills
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
logger.error("get_environment_skills_failed", error=str(e), environment_id=environment_id)
|
|
200
|
+
return [], []
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@router.post("", response_model=EnvironmentResponse, status_code=status.HTTP_201_CREATED)
|
|
204
|
+
async def create_environment(
|
|
205
|
+
env_data: EnvironmentCreate,
|
|
206
|
+
request: Request,
|
|
207
|
+
organization: dict = Depends(get_current_organization),
|
|
208
|
+
db: Session = Depends(get_db),
|
|
209
|
+
):
|
|
210
|
+
"""
|
|
211
|
+
Create a new environment.
|
|
212
|
+
|
|
213
|
+
If this is the first environment for the organization, it will trigger
|
|
214
|
+
Temporal Cloud namespace provisioning workflow.
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
# Check if environment name already exists
|
|
218
|
+
existing = db.query(Environment).filter(
|
|
219
|
+
Environment.organization_id == organization["id"],
|
|
220
|
+
Environment.name == env_data.name
|
|
221
|
+
).first()
|
|
222
|
+
|
|
223
|
+
if existing:
|
|
224
|
+
raise HTTPException(
|
|
225
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
226
|
+
detail=f"Environment with name '{env_data.name}' already exists"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Check if this is the first environment
|
|
230
|
+
env_count = db.query(Environment).filter(
|
|
231
|
+
Environment.organization_id == organization["id"]
|
|
232
|
+
).count()
|
|
233
|
+
is_first_env = env_count == 0
|
|
234
|
+
|
|
235
|
+
# Check if namespace already exists (temporal_namespaces table check)
|
|
236
|
+
# Note: We need to check if the table exists first
|
|
237
|
+
has_namespace = False
|
|
238
|
+
try:
|
|
239
|
+
from sqlalchemy import inspect
|
|
240
|
+
inspector = inspect(db.bind)
|
|
241
|
+
if 'temporal_namespaces' in inspector.get_table_names():
|
|
242
|
+
from sqlalchemy import text
|
|
243
|
+
result = db.execute(
|
|
244
|
+
text("SELECT COUNT(*) FROM temporal_namespaces WHERE organization_id = :org_id"),
|
|
245
|
+
{"org_id": organization["id"]}
|
|
246
|
+
)
|
|
247
|
+
has_namespace = result.scalar() > 0
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
needs_provisioning = is_first_env and not has_namespace
|
|
252
|
+
|
|
253
|
+
# Set initial status
|
|
254
|
+
initial_status = "provisioning" if needs_provisioning else "ready"
|
|
255
|
+
|
|
256
|
+
env_obj = Environment(
|
|
257
|
+
id=uuid.uuid4(),
|
|
258
|
+
organization_id=organization["id"],
|
|
259
|
+
name=env_data.name,
|
|
260
|
+
display_name=env_data.display_name or env_data.name,
|
|
261
|
+
description=env_data.description,
|
|
262
|
+
tags=env_data.tags,
|
|
263
|
+
settings=env_data.settings,
|
|
264
|
+
status=initial_status,
|
|
265
|
+
created_at=datetime.utcnow(),
|
|
266
|
+
updated_at=datetime.utcnow(),
|
|
267
|
+
created_by=organization.get("user_id"),
|
|
268
|
+
worker_token=uuid.uuid4(),
|
|
269
|
+
execution_environment=env_data.execution_environment.model_dump() if env_data.execution_environment else {},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
db.add(env_obj)
|
|
273
|
+
db.commit()
|
|
274
|
+
db.refresh(env_obj)
|
|
275
|
+
|
|
276
|
+
env_id = str(env_obj.id)
|
|
277
|
+
|
|
278
|
+
# Trigger namespace provisioning if needed
|
|
279
|
+
if needs_provisioning:
|
|
280
|
+
try:
|
|
281
|
+
from control_plane_api.app.workflows.namespace_provisioning import (
|
|
282
|
+
ProvisionTemporalNamespaceWorkflow,
|
|
283
|
+
ProvisionNamespaceInput,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
temporal_client = await get_temporal_client()
|
|
287
|
+
account_id = os.environ.get("TEMPORAL_CLOUD_ACCOUNT_ID", "default-account")
|
|
288
|
+
|
|
289
|
+
workflow_input = ProvisionNamespaceInput(
|
|
290
|
+
organization_id=organization["id"],
|
|
291
|
+
organization_name=organization.get("name", organization["id"]),
|
|
292
|
+
task_queue_id=env_id,
|
|
293
|
+
account_id=account_id,
|
|
294
|
+
region=os.environ.get("TEMPORAL_CLOUD_REGION", "aws-us-east-1"),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
workflow_handle = await temporal_client.start_workflow(
|
|
298
|
+
ProvisionTemporalNamespaceWorkflow.run,
|
|
299
|
+
workflow_input,
|
|
300
|
+
id=f"provision-namespace-{organization['id']}",
|
|
301
|
+
task_queue="agent-control-plane",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
env_obj.provisioning_workflow_id = workflow_handle.id
|
|
305
|
+
env_obj.updated_at = datetime.utcnow()
|
|
306
|
+
db.commit()
|
|
307
|
+
db.refresh(env_obj)
|
|
308
|
+
|
|
309
|
+
logger.info(
|
|
310
|
+
"namespace_provisioning_workflow_started",
|
|
311
|
+
workflow_id=workflow_handle.id,
|
|
312
|
+
environment_id=env_id,
|
|
313
|
+
org_id=organization["id"],
|
|
314
|
+
)
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(
|
|
317
|
+
"failed_to_start_provisioning_workflow",
|
|
318
|
+
error=str(e),
|
|
319
|
+
environment_id=env_id,
|
|
320
|
+
org_id=organization["id"],
|
|
321
|
+
)
|
|
322
|
+
env_obj.status = "error"
|
|
323
|
+
env_obj.error_message = f"Failed to start provisioning: {str(e)}"
|
|
324
|
+
env_obj.updated_at = datetime.utcnow()
|
|
325
|
+
db.commit()
|
|
326
|
+
db.refresh(env_obj)
|
|
327
|
+
|
|
328
|
+
logger.info(
|
|
329
|
+
"environment_created",
|
|
330
|
+
environment_id=env_id,
|
|
331
|
+
environment_name=env_obj.name,
|
|
332
|
+
org_id=organization["id"],
|
|
333
|
+
needs_provisioning=needs_provisioning,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Convert SQLAlchemy model to dict
|
|
337
|
+
environment_dict = {
|
|
338
|
+
"id": str(env_obj.id),
|
|
339
|
+
"organization_id": env_obj.organization_id,
|
|
340
|
+
"name": env_obj.name,
|
|
341
|
+
"display_name": env_obj.display_name,
|
|
342
|
+
"description": env_obj.description,
|
|
343
|
+
"tags": env_obj.tags or [],
|
|
344
|
+
"settings": env_obj.settings or {},
|
|
345
|
+
"status": env_obj.status,
|
|
346
|
+
"created_at": env_obj.created_at.isoformat() if env_obj.created_at else None,
|
|
347
|
+
"updated_at": env_obj.updated_at.isoformat() if env_obj.updated_at else None,
|
|
348
|
+
"created_by": env_obj.created_by,
|
|
349
|
+
"worker_token": str(env_obj.worker_token) if env_obj.worker_token else None,
|
|
350
|
+
"provisioning_workflow_id": env_obj.provisioning_workflow_id,
|
|
351
|
+
"provisioned_at": env_obj.provisioned_at.isoformat() if env_obj.provisioned_at else None,
|
|
352
|
+
"error_message": env_obj.error_message,
|
|
353
|
+
"temporal_namespace_id": str(env_obj.temporal_namespace_id) if env_obj.temporal_namespace_id else None,
|
|
354
|
+
"execution_environment": env_obj.execution_environment or {},
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return EnvironmentResponse(
|
|
358
|
+
**environment_dict,
|
|
359
|
+
active_workers=0,
|
|
360
|
+
idle_workers=0,
|
|
361
|
+
busy_workers=0,
|
|
362
|
+
skill_ids=[],
|
|
363
|
+
skills=[],
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
except HTTPException:
|
|
367
|
+
raise
|
|
368
|
+
except Exception as e:
|
|
369
|
+
db.rollback()
|
|
370
|
+
logger.error("environment_creation_failed", error=str(e), org_id=organization["id"])
|
|
371
|
+
raise HTTPException(
|
|
372
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
373
|
+
detail=f"Failed to create environment: {str(e)}"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@router.get("", response_model=List[EnvironmentResponse])
|
|
378
|
+
async def list_environments(
|
|
379
|
+
request: Request,
|
|
380
|
+
status_filter: str | None = None,
|
|
381
|
+
organization: dict = Depends(get_current_organization),
|
|
382
|
+
db: Session = Depends(get_db),
|
|
383
|
+
):
|
|
384
|
+
"""List all environments in the organization"""
|
|
385
|
+
try:
|
|
386
|
+
# Ensure default environment exists
|
|
387
|
+
ensure_default_environment(db, organization)
|
|
388
|
+
|
|
389
|
+
# Query environments
|
|
390
|
+
query = db.query(Environment).filter(
|
|
391
|
+
Environment.organization_id == organization["id"]
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if status_filter:
|
|
395
|
+
query = query.filter(Environment.status == status_filter)
|
|
396
|
+
|
|
397
|
+
query = query.order_by(Environment.created_at.asc())
|
|
398
|
+
env_objects = query.all()
|
|
399
|
+
|
|
400
|
+
if not env_objects:
|
|
401
|
+
return []
|
|
402
|
+
|
|
403
|
+
# BATCH FETCH: Get all skills for all environments in one query
|
|
404
|
+
environment_ids = [str(env.id) for env in env_objects]
|
|
405
|
+
skills_associations = db.query(SkillAssociation).options(
|
|
406
|
+
joinedload(SkillAssociation.skill)
|
|
407
|
+
).filter(
|
|
408
|
+
SkillAssociation.organization_id == organization["id"],
|
|
409
|
+
SkillAssociation.entity_type == "environment",
|
|
410
|
+
SkillAssociation.entity_id.in_(environment_ids)
|
|
411
|
+
).all()
|
|
412
|
+
|
|
413
|
+
# Group skills by environment_id
|
|
414
|
+
skills_by_env = {}
|
|
415
|
+
for assoc in skills_associations:
|
|
416
|
+
env_id = str(assoc.entity_id)
|
|
417
|
+
skill_data = assoc.skill
|
|
418
|
+
if skill_data:
|
|
419
|
+
if env_id not in skills_by_env:
|
|
420
|
+
skills_by_env[env_id] = {"ids": [], "data": []}
|
|
421
|
+
|
|
422
|
+
# Merge configuration with override
|
|
423
|
+
config = skill_data.configuration or {}
|
|
424
|
+
override = assoc.configuration_override
|
|
425
|
+
if override:
|
|
426
|
+
config = {**config, **override}
|
|
427
|
+
|
|
428
|
+
skills_by_env[env_id]["ids"].append(str(skill_data.id))
|
|
429
|
+
skills_by_env[env_id]["data"].append({
|
|
430
|
+
"id": str(skill_data.id),
|
|
431
|
+
"name": skill_data.name,
|
|
432
|
+
"type": skill_data.skill_type,
|
|
433
|
+
"description": skill_data.description,
|
|
434
|
+
"enabled": skill_data.enabled if skill_data.enabled is not None else True,
|
|
435
|
+
"configuration": config,
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
# Build environment responses
|
|
439
|
+
environments = []
|
|
440
|
+
for env in env_objects:
|
|
441
|
+
env_id = str(env.id)
|
|
442
|
+
env_skills = skills_by_env.get(env_id, {"ids": [], "data": []})
|
|
443
|
+
|
|
444
|
+
env_dict = {
|
|
445
|
+
"id": env_id,
|
|
446
|
+
"organization_id": env.organization_id,
|
|
447
|
+
"name": env.name,
|
|
448
|
+
"display_name": env.display_name,
|
|
449
|
+
"description": env.description,
|
|
450
|
+
"tags": env.tags or [],
|
|
451
|
+
"settings": env.settings or {},
|
|
452
|
+
"status": env.status,
|
|
453
|
+
"created_at": env.created_at.isoformat() if env.created_at else None,
|
|
454
|
+
"updated_at": env.updated_at.isoformat() if env.updated_at else None,
|
|
455
|
+
"created_by": env.created_by,
|
|
456
|
+
"worker_token": str(env.worker_token) if env.worker_token else None,
|
|
457
|
+
"provisioning_workflow_id": env.provisioning_workflow_id,
|
|
458
|
+
"provisioned_at": env.provisioned_at.isoformat() if env.provisioned_at else None,
|
|
459
|
+
"error_message": env.error_message,
|
|
460
|
+
"temporal_namespace_id": str(env.temporal_namespace_id) if env.temporal_namespace_id else None,
|
|
461
|
+
"execution_environment": env.execution_environment or {},
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
environments.append(
|
|
465
|
+
EnvironmentResponse(
|
|
466
|
+
**env_dict,
|
|
467
|
+
active_workers=0,
|
|
468
|
+
idle_workers=0,
|
|
469
|
+
busy_workers=0,
|
|
470
|
+
skill_ids=env_skills["ids"],
|
|
471
|
+
skills=env_skills["data"],
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
logger.info(
|
|
476
|
+
"environments_listed",
|
|
477
|
+
count=len(environments),
|
|
478
|
+
org_id=organization["id"],
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
return environments
|
|
482
|
+
|
|
483
|
+
except Exception as e:
|
|
484
|
+
logger.error("environments_list_failed", error=str(e), org_id=organization["id"])
|
|
485
|
+
raise HTTPException(
|
|
486
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
487
|
+
detail=f"Failed to list environments: {str(e)}"
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@router.get("/{environment_id}", response_model=EnvironmentResponse)
|
|
492
|
+
async def get_environment(
|
|
493
|
+
environment_id: str,
|
|
494
|
+
request: Request,
|
|
495
|
+
organization: dict = Depends(get_current_organization),
|
|
496
|
+
db: Session = Depends(get_db),
|
|
497
|
+
):
|
|
498
|
+
"""Get a specific environment by ID"""
|
|
499
|
+
try:
|
|
500
|
+
env = db.query(Environment).filter(
|
|
501
|
+
Environment.id == environment_id,
|
|
502
|
+
Environment.organization_id == organization["id"]
|
|
503
|
+
).first()
|
|
504
|
+
|
|
505
|
+
if not env:
|
|
506
|
+
raise HTTPException(status_code=404, detail="Environment not found")
|
|
507
|
+
|
|
508
|
+
# Get skills
|
|
509
|
+
skill_ids, skills = get_environment_skills(db, organization["id"], environment_id)
|
|
510
|
+
|
|
511
|
+
env_dict = {
|
|
512
|
+
"id": str(env.id),
|
|
513
|
+
"organization_id": env.organization_id,
|
|
514
|
+
"name": env.name,
|
|
515
|
+
"display_name": env.display_name,
|
|
516
|
+
"description": env.description,
|
|
517
|
+
"tags": env.tags or [],
|
|
518
|
+
"settings": env.settings or {},
|
|
519
|
+
"status": env.status,
|
|
520
|
+
"created_at": env.created_at.isoformat() if env.created_at else None,
|
|
521
|
+
"updated_at": env.updated_at.isoformat() if env.updated_at else None,
|
|
522
|
+
"created_by": env.created_by,
|
|
523
|
+
"worker_token": str(env.worker_token) if env.worker_token else None,
|
|
524
|
+
"provisioning_workflow_id": env.provisioning_workflow_id,
|
|
525
|
+
"provisioned_at": env.provisioned_at.isoformat() if env.provisioned_at else None,
|
|
526
|
+
"error_message": env.error_message,
|
|
527
|
+
"temporal_namespace_id": str(env.temporal_namespace_id) if env.temporal_namespace_id else None,
|
|
528
|
+
"execution_environment": env.execution_environment or {},
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return EnvironmentResponse(
|
|
532
|
+
**env_dict,
|
|
533
|
+
active_workers=0,
|
|
534
|
+
idle_workers=0,
|
|
535
|
+
busy_workers=0,
|
|
536
|
+
skill_ids=skill_ids,
|
|
537
|
+
skills=skills,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
except HTTPException:
|
|
541
|
+
raise
|
|
542
|
+
except Exception as e:
|
|
543
|
+
logger.error("environment_get_failed", error=str(e), environment_id=environment_id)
|
|
544
|
+
raise HTTPException(
|
|
545
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
546
|
+
detail=f"Failed to get environment: {str(e)}"
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
@router.patch("/{environment_id}", response_model=EnvironmentResponse)
|
|
551
|
+
async def update_environment(
|
|
552
|
+
environment_id: str,
|
|
553
|
+
env_data: EnvironmentUpdate,
|
|
554
|
+
request: Request,
|
|
555
|
+
organization: dict = Depends(get_current_organization),
|
|
556
|
+
db: Session = Depends(get_db),
|
|
557
|
+
):
|
|
558
|
+
"""Update an environment"""
|
|
559
|
+
try:
|
|
560
|
+
# Check if environment exists
|
|
561
|
+
env = db.query(Environment).filter(
|
|
562
|
+
Environment.id == environment_id,
|
|
563
|
+
Environment.organization_id == organization["id"]
|
|
564
|
+
).first()
|
|
565
|
+
|
|
566
|
+
if not env:
|
|
567
|
+
raise HTTPException(status_code=404, detail="Environment not found")
|
|
568
|
+
|
|
569
|
+
# Build update dict
|
|
570
|
+
update_data = env_data.model_dump(exclude_unset=True)
|
|
571
|
+
|
|
572
|
+
# Convert execution_environment Pydantic model to dict if present
|
|
573
|
+
if "execution_environment" in update_data and update_data["execution_environment"]:
|
|
574
|
+
if hasattr(update_data["execution_environment"], "model_dump"):
|
|
575
|
+
update_data["execution_environment"] = update_data["execution_environment"].model_dump()
|
|
576
|
+
|
|
577
|
+
# Update fields
|
|
578
|
+
for field, value in update_data.items():
|
|
579
|
+
if hasattr(env, field):
|
|
580
|
+
setattr(env, field, value)
|
|
581
|
+
|
|
582
|
+
env.updated_at = datetime.utcnow()
|
|
583
|
+
|
|
584
|
+
db.commit()
|
|
585
|
+
db.refresh(env)
|
|
586
|
+
|
|
587
|
+
# Get skills
|
|
588
|
+
skill_ids, skills = get_environment_skills(db, organization["id"], environment_id)
|
|
589
|
+
|
|
590
|
+
logger.info(
|
|
591
|
+
"environment_updated",
|
|
592
|
+
environment_id=environment_id,
|
|
593
|
+
org_id=organization["id"],
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
env_dict = {
|
|
597
|
+
"id": str(env.id),
|
|
598
|
+
"organization_id": env.organization_id,
|
|
599
|
+
"name": env.name,
|
|
600
|
+
"display_name": env.display_name,
|
|
601
|
+
"description": env.description,
|
|
602
|
+
"tags": env.tags or [],
|
|
603
|
+
"settings": env.settings or {},
|
|
604
|
+
"status": env.status,
|
|
605
|
+
"created_at": env.created_at.isoformat() if env.created_at else None,
|
|
606
|
+
"updated_at": env.updated_at.isoformat() if env.updated_at else None,
|
|
607
|
+
"created_by": env.created_by,
|
|
608
|
+
"worker_token": str(env.worker_token) if env.worker_token else None,
|
|
609
|
+
"provisioning_workflow_id": env.provisioning_workflow_id,
|
|
610
|
+
"provisioned_at": env.provisioned_at.isoformat() if env.provisioned_at else None,
|
|
611
|
+
"error_message": env.error_message,
|
|
612
|
+
"temporal_namespace_id": str(env.temporal_namespace_id) if env.temporal_namespace_id else None,
|
|
613
|
+
"execution_environment": env.execution_environment or {},
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return EnvironmentResponse(
|
|
617
|
+
**env_dict,
|
|
618
|
+
active_workers=0,
|
|
619
|
+
idle_workers=0,
|
|
620
|
+
busy_workers=0,
|
|
621
|
+
skill_ids=skill_ids,
|
|
622
|
+
skills=skills,
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
except HTTPException:
|
|
626
|
+
raise
|
|
627
|
+
except Exception as e:
|
|
628
|
+
db.rollback()
|
|
629
|
+
logger.error("environment_update_failed", error=str(e), environment_id=environment_id)
|
|
630
|
+
raise HTTPException(
|
|
631
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
632
|
+
detail=f"Failed to update environment: {str(e)}"
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
@router.delete("/{environment_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
637
|
+
async def delete_environment(
|
|
638
|
+
environment_id: str,
|
|
639
|
+
request: Request,
|
|
640
|
+
organization: dict = Depends(get_current_organization),
|
|
641
|
+
db: Session = Depends(get_db),
|
|
642
|
+
):
|
|
643
|
+
"""Delete an environment"""
|
|
644
|
+
try:
|
|
645
|
+
# Prevent deleting default environment
|
|
646
|
+
env = db.query(Environment).filter(
|
|
647
|
+
Environment.id == environment_id,
|
|
648
|
+
Environment.organization_id == organization["id"]
|
|
649
|
+
).first()
|
|
650
|
+
|
|
651
|
+
if not env:
|
|
652
|
+
raise HTTPException(status_code=404, detail="Environment not found")
|
|
653
|
+
|
|
654
|
+
if env.name == "default":
|
|
655
|
+
raise HTTPException(
|
|
656
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
657
|
+
detail="Cannot delete the default environment"
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
db.delete(env)
|
|
661
|
+
db.commit()
|
|
662
|
+
|
|
663
|
+
logger.info("environment_deleted", environment_id=environment_id, org_id=organization["id"])
|
|
664
|
+
|
|
665
|
+
return None
|
|
666
|
+
|
|
667
|
+
except HTTPException:
|
|
668
|
+
raise
|
|
669
|
+
except Exception as e:
|
|
670
|
+
db.rollback()
|
|
671
|
+
logger.error("environment_delete_failed", error=str(e), environment_id=environment_id)
|
|
672
|
+
raise HTTPException(
|
|
673
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
674
|
+
detail=f"Failed to delete environment: {str(e)}"
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
@router.get("/{environment_id}/worker-command", response_model=WorkerCommandResponse)
|
|
679
|
+
async def get_worker_registration_command(
|
|
680
|
+
environment_id: str,
|
|
681
|
+
request: Request,
|
|
682
|
+
organization: dict = Depends(get_current_organization),
|
|
683
|
+
db: Session = Depends(get_db),
|
|
684
|
+
):
|
|
685
|
+
"""
|
|
686
|
+
Get the worker registration command for an environment.
|
|
687
|
+
|
|
688
|
+
Returns the kubiya worker start command with the worker token.
|
|
689
|
+
"""
|
|
690
|
+
try:
|
|
691
|
+
# Get environment
|
|
692
|
+
env = db.query(Environment).filter(
|
|
693
|
+
Environment.id == environment_id,
|
|
694
|
+
Environment.organization_id == organization["id"]
|
|
695
|
+
).first()
|
|
696
|
+
|
|
697
|
+
if not env:
|
|
698
|
+
raise HTTPException(status_code=404, detail="Environment not found")
|
|
699
|
+
|
|
700
|
+
worker_token = str(env.worker_token) if env.worker_token else None
|
|
701
|
+
|
|
702
|
+
# Generate worker_token if it doesn't exist
|
|
703
|
+
if not worker_token:
|
|
704
|
+
env.worker_token = uuid.uuid4()
|
|
705
|
+
env.updated_at = datetime.utcnow()
|
|
706
|
+
db.commit()
|
|
707
|
+
db.refresh(env)
|
|
708
|
+
worker_token = str(env.worker_token)
|
|
709
|
+
|
|
710
|
+
logger.info(
|
|
711
|
+
"worker_token_generated",
|
|
712
|
+
environment_id=environment_id,
|
|
713
|
+
org_id=organization["id"],
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
environment_name = env.name
|
|
717
|
+
namespace_status = env.status or "unknown"
|
|
718
|
+
provisioning_workflow_id = env.provisioning_workflow_id
|
|
719
|
+
|
|
720
|
+
# Check if namespace is ready
|
|
721
|
+
can_register = namespace_status in ["ready", "active"]
|
|
722
|
+
|
|
723
|
+
# Build command
|
|
724
|
+
command = f"kubiya worker start --token {worker_token} --environment {environment_name}"
|
|
725
|
+
|
|
726
|
+
command_parts = {
|
|
727
|
+
"binary": "kubiya",
|
|
728
|
+
"subcommand": "worker start",
|
|
729
|
+
"flags": {
|
|
730
|
+
"--token": worker_token,
|
|
731
|
+
"--environment": environment_name,
|
|
732
|
+
},
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
logger.info(
|
|
736
|
+
"worker_command_retrieved",
|
|
737
|
+
environment_id=environment_id,
|
|
738
|
+
can_register=can_register,
|
|
739
|
+
status=namespace_status,
|
|
740
|
+
org_id=organization["id"],
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
return WorkerCommandResponse(
|
|
744
|
+
worker_token=worker_token,
|
|
745
|
+
environment_name=environment_name,
|
|
746
|
+
command=command,
|
|
747
|
+
command_parts=command_parts,
|
|
748
|
+
namespace_status=namespace_status,
|
|
749
|
+
can_register=can_register,
|
|
750
|
+
provisioning_workflow_id=provisioning_workflow_id,
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
except HTTPException:
|
|
754
|
+
raise
|
|
755
|
+
except Exception as e:
|
|
756
|
+
db.rollback()
|
|
757
|
+
logger.error("worker_command_get_failed", error=str(e), environment_id=environment_id)
|
|
758
|
+
raise HTTPException(
|
|
759
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
760
|
+
detail=f"Failed to get worker command: {str(e)}"
|
|
761
|
+
)
|