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,1315 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execution Environment Controller - Centralized logic for resolving execution environments
|
|
3
|
+
|
|
4
|
+
This controller provides reusable logic for resolving execution environments for agents/teams.
|
|
5
|
+
It can be called from:
|
|
6
|
+
- API routes (for HTTP requests)
|
|
7
|
+
- Workers (for direct execution)
|
|
8
|
+
- Other internal services
|
|
9
|
+
|
|
10
|
+
The controller handles:
|
|
11
|
+
- Fetching execution environment configs from database
|
|
12
|
+
- Resolving secret names to actual values from Kubiya API
|
|
13
|
+
- Resolving integration IDs to actual tokens from Kubiya API
|
|
14
|
+
- Merging configs from environments + agent/team
|
|
15
|
+
- Template resolution in config fields
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
import os
|
|
20
|
+
import tempfile
|
|
21
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
22
|
+
from sqlalchemy.orm import Session
|
|
23
|
+
import structlog
|
|
24
|
+
|
|
25
|
+
from control_plane_api.app.models import (
|
|
26
|
+
Agent,
|
|
27
|
+
Team,
|
|
28
|
+
Environment,
|
|
29
|
+
AgentEnvironment,
|
|
30
|
+
TeamEnvironment,
|
|
31
|
+
)
|
|
32
|
+
from control_plane_api.app.models.custom_integration import CustomIntegration
|
|
33
|
+
from control_plane_api.app.lib.kubiya_client import KUBIYA_API_BASE
|
|
34
|
+
from control_plane_api.app.lib.templating import TemplateContext, resolve_templates
|
|
35
|
+
|
|
36
|
+
logger = structlog.get_logger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Integration type to environment variable name mapping
|
|
40
|
+
# Each integration can map to multiple env vars (e.g., AWS needs key + secret)
|
|
41
|
+
INTEGRATION_ENV_VAR_MAP = {
|
|
42
|
+
"github": ["GH_TOKEN", "GITHUB_TOKEN"],
|
|
43
|
+
"github_app": ["GITHUB_TOKEN"],
|
|
44
|
+
"jira": ["JIRA_TOKEN"],
|
|
45
|
+
"slack": ["SLACK_TOKEN", "SLACK_BOT_TOKEN"],
|
|
46
|
+
"aws": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_REGION"],
|
|
47
|
+
"aws-serviceaccount": ["AWS_ROLE_ARN", "AWS_REGION"],
|
|
48
|
+
"kubernetes": ["KUBECONFIG"],
|
|
49
|
+
"gcp": ["GCP_SERVICE_ACCOUNT_KEY", "GOOGLE_APPLICATION_CREDENTIALS"],
|
|
50
|
+
"azure": ["AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_TENANT_ID"],
|
|
51
|
+
"datadog": ["DD_API_KEY", "DD_APP_KEY", "DD_SITE"],
|
|
52
|
+
"pagerduty": ["PD_TOKEN"],
|
|
53
|
+
"gitlab": ["GITLAB_TOKEN"],
|
|
54
|
+
"bitbucket": ["BITBUCKET_TOKEN"],
|
|
55
|
+
"linear": ["LINEAR_API_KEY"],
|
|
56
|
+
"notion": ["NOTION_API_KEY"],
|
|
57
|
+
"openai": ["OPENAI_API_KEY"],
|
|
58
|
+
"anthropic": ["ANTHROPIC_API_KEY"],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ExecutionEnvironmentResolutionError(Exception):
|
|
63
|
+
"""Raised when execution environment resolution fails"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_aws_credentials_file(
|
|
68
|
+
access_key_id: str,
|
|
69
|
+
secret_access_key: str,
|
|
70
|
+
session_token: Optional[str] = None,
|
|
71
|
+
region: Optional[str] = None,
|
|
72
|
+
profile: str = "default",
|
|
73
|
+
) -> Tuple[str, str]:
|
|
74
|
+
"""
|
|
75
|
+
Create AWS credentials and config files for use with AWS SDKs/CLI.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
access_key_id: AWS access key ID
|
|
79
|
+
secret_access_key: AWS secret access key
|
|
80
|
+
session_token: Optional session token for temporary credentials
|
|
81
|
+
region: Optional AWS region
|
|
82
|
+
profile: Profile name (default: "default")
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Tuple of (credentials_file_path, config_file_path)
|
|
86
|
+
"""
|
|
87
|
+
# Create temp directory for AWS config
|
|
88
|
+
temp_dir = tempfile.mkdtemp(prefix="aws_")
|
|
89
|
+
|
|
90
|
+
# Create credentials file
|
|
91
|
+
credentials_path = os.path.join(temp_dir, "credentials")
|
|
92
|
+
credentials_content = f"""[{profile}]
|
|
93
|
+
aws_access_key_id = {access_key_id}
|
|
94
|
+
aws_secret_access_key = {secret_access_key}
|
|
95
|
+
"""
|
|
96
|
+
if session_token:
|
|
97
|
+
credentials_content += f"aws_session_token = {session_token}\n"
|
|
98
|
+
|
|
99
|
+
with open(credentials_path, "w") as f:
|
|
100
|
+
f.write(credentials_content)
|
|
101
|
+
|
|
102
|
+
# Create config file
|
|
103
|
+
config_path = os.path.join(temp_dir, "config")
|
|
104
|
+
config_content = f"""[{profile}]
|
|
105
|
+
"""
|
|
106
|
+
if region:
|
|
107
|
+
config_content += f"region = {region}\n"
|
|
108
|
+
else:
|
|
109
|
+
config_content += "region = us-east-1\n"
|
|
110
|
+
|
|
111
|
+
with open(config_path, "w") as f:
|
|
112
|
+
f.write(config_content)
|
|
113
|
+
|
|
114
|
+
logger.debug(
|
|
115
|
+
"aws_credentials_files_created",
|
|
116
|
+
credentials_path=credentials_path,
|
|
117
|
+
config_path=config_path,
|
|
118
|
+
profile=profile,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return credentials_path, config_path
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_kubeconfig_file(kubeconfig_content: str) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Create a kubeconfig file from content.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
kubeconfig_content: YAML content of kubeconfig
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Path to kubeconfig file
|
|
133
|
+
"""
|
|
134
|
+
temp_dir = tempfile.mkdtemp(prefix="kube_")
|
|
135
|
+
kubeconfig_path = os.path.join(temp_dir, "config")
|
|
136
|
+
|
|
137
|
+
with open(kubeconfig_path, "w") as f:
|
|
138
|
+
f.write(kubeconfig_content)
|
|
139
|
+
|
|
140
|
+
logger.debug("kubeconfig_file_created", kubeconfig_path=kubeconfig_path)
|
|
141
|
+
|
|
142
|
+
return kubeconfig_path
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def resolve_custom_integration(
|
|
146
|
+
custom_integration_id: str,
|
|
147
|
+
org_id: str,
|
|
148
|
+
db: Session,
|
|
149
|
+
kubiya_token: str,
|
|
150
|
+
auth_type: str = "UserKey",
|
|
151
|
+
) -> Dict[str, Any]:
|
|
152
|
+
"""
|
|
153
|
+
Resolve a custom integration to environment variables and files.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
custom_integration_id: Custom integration UUID
|
|
157
|
+
org_id: Organization ID
|
|
158
|
+
db: Database session
|
|
159
|
+
kubiya_token: Kubiya API token for secrets resolution
|
|
160
|
+
auth_type: Authorization type ("UserKey" for API keys, "Bearer" for JWT tokens)
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Dict with:
|
|
164
|
+
- env_vars: Dict of environment variables
|
|
165
|
+
- files: List of files to create
|
|
166
|
+
- context_prompt: Optional contextual prompt
|
|
167
|
+
"""
|
|
168
|
+
# Fetch custom integration from database
|
|
169
|
+
custom_int = db.query(CustomIntegration).filter(
|
|
170
|
+
CustomIntegration.id == custom_integration_id,
|
|
171
|
+
CustomIntegration.organization_id == org_id
|
|
172
|
+
).first()
|
|
173
|
+
|
|
174
|
+
if not custom_int:
|
|
175
|
+
logger.warning(
|
|
176
|
+
"custom_integration_not_found",
|
|
177
|
+
integration_id=custom_integration_id[:8],
|
|
178
|
+
org_id=org_id
|
|
179
|
+
)
|
|
180
|
+
return {"env_vars": {}, "files": [], "context_prompt": None}
|
|
181
|
+
|
|
182
|
+
config = custom_int.config or {}
|
|
183
|
+
resolved_env_vars = {}
|
|
184
|
+
files_to_create = []
|
|
185
|
+
|
|
186
|
+
# Add direct env vars
|
|
187
|
+
env_vars = config.get("env_vars", {})
|
|
188
|
+
resolved_env_vars.update(env_vars)
|
|
189
|
+
|
|
190
|
+
# Resolve secrets
|
|
191
|
+
secrets = config.get("secrets", [])
|
|
192
|
+
for secret_name in secrets:
|
|
193
|
+
try:
|
|
194
|
+
secret_value = await resolve_secret_value(secret_name, kubiya_token, org_id, auth_type)
|
|
195
|
+
resolved_env_vars[secret_name] = secret_value
|
|
196
|
+
logger.debug(
|
|
197
|
+
"custom_integration_secret_resolved",
|
|
198
|
+
integration_id=custom_integration_id[:8],
|
|
199
|
+
secret_name=secret_name[:20]
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error(
|
|
203
|
+
"custom_integration_secret_resolution_error",
|
|
204
|
+
integration_id=custom_integration_id[:8],
|
|
205
|
+
secret_name=secret_name[:20],
|
|
206
|
+
error=str(e)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Prepare files
|
|
210
|
+
files_config = config.get("files", [])
|
|
211
|
+
for file_config in files_config:
|
|
212
|
+
file_path = file_config.get("path")
|
|
213
|
+
content = file_config.get("content")
|
|
214
|
+
secret_ref = file_config.get("secret_ref")
|
|
215
|
+
mode = file_config.get("mode", "0644")
|
|
216
|
+
|
|
217
|
+
if not file_path:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
# Resolve content from secret if needed
|
|
221
|
+
if secret_ref and not content:
|
|
222
|
+
try:
|
|
223
|
+
content = await resolve_secret_value(secret_ref, kubiya_token, org_id, auth_type)
|
|
224
|
+
logger.debug(
|
|
225
|
+
"custom_integration_file_secret_resolved",
|
|
226
|
+
integration_id=custom_integration_id[:8],
|
|
227
|
+
file_path=file_path,
|
|
228
|
+
secret_ref=secret_ref[:20]
|
|
229
|
+
)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(
|
|
232
|
+
"custom_integration_file_secret_error",
|
|
233
|
+
integration_id=custom_integration_id[:8],
|
|
234
|
+
file_path=file_path,
|
|
235
|
+
error=str(e)
|
|
236
|
+
)
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
if content:
|
|
240
|
+
files_to_create.append({
|
|
241
|
+
"path": file_path,
|
|
242
|
+
"content": content,
|
|
243
|
+
"mode": mode,
|
|
244
|
+
"description": file_config.get("description")
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
context_prompt = config.get("context_prompt")
|
|
248
|
+
|
|
249
|
+
logger.info(
|
|
250
|
+
"custom_integration_resolved",
|
|
251
|
+
integration_id=custom_integration_id[:8],
|
|
252
|
+
integration_name=custom_int.name,
|
|
253
|
+
env_var_count=len(resolved_env_vars),
|
|
254
|
+
file_count=len(files_to_create),
|
|
255
|
+
has_context=bool(context_prompt)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"env_vars": resolved_env_vars,
|
|
260
|
+
"files": files_to_create,
|
|
261
|
+
"context_prompt": context_prompt,
|
|
262
|
+
"name": custom_int.name,
|
|
263
|
+
"integration_type": custom_int.integration_type
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def build_integration_context(
|
|
268
|
+
resolved_integrations: List[Dict[str, Any]],
|
|
269
|
+
) -> str:
|
|
270
|
+
"""
|
|
271
|
+
Build integration context information for injection into system prompt.
|
|
272
|
+
|
|
273
|
+
This provides the agent with awareness of available integrations and their
|
|
274
|
+
configuration without exposing credentials.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
resolved_integrations: List of resolved integration metadata
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Markdown-formatted context string for system prompt
|
|
281
|
+
"""
|
|
282
|
+
if not resolved_integrations:
|
|
283
|
+
return ""
|
|
284
|
+
|
|
285
|
+
context_parts = ["## Available Integrations\n"]
|
|
286
|
+
context_parts.append("The following integrations are configured and available for use:\n")
|
|
287
|
+
|
|
288
|
+
for integration in resolved_integrations:
|
|
289
|
+
integration_type = integration.get("integration_type", "unknown")
|
|
290
|
+
integration_name = integration.get("name", integration_type)
|
|
291
|
+
env_vars = integration.get("env_vars", [])
|
|
292
|
+
custom_context = integration.get("custom_context")
|
|
293
|
+
|
|
294
|
+
context_parts.append(f"\n### {integration_name} ({integration_type})")
|
|
295
|
+
|
|
296
|
+
if env_vars:
|
|
297
|
+
context_parts.append(f"- Available environment variables: {', '.join(env_vars)}")
|
|
298
|
+
|
|
299
|
+
# Add custom context if provided
|
|
300
|
+
if custom_context:
|
|
301
|
+
context_parts.append(f"- {custom_context}")
|
|
302
|
+
|
|
303
|
+
# Add integration-specific guidance
|
|
304
|
+
if integration_type in ["aws", "aws-serviceaccount"]:
|
|
305
|
+
context_parts.append("- AWS SDK and CLI are pre-configured with credentials")
|
|
306
|
+
context_parts.append("- Use environment variables or ~/.aws/credentials file")
|
|
307
|
+
elif integration_type == "kubernetes":
|
|
308
|
+
context_parts.append("- Kubernetes kubectl is pre-configured")
|
|
309
|
+
context_parts.append("- Use KUBECONFIG environment variable or ~/.kube/config")
|
|
310
|
+
elif integration_type in ["github", "github_app"]:
|
|
311
|
+
context_parts.append("- GitHub API access is available via GH_TOKEN or GITHUB_TOKEN")
|
|
312
|
+
elif integration_type == "jira":
|
|
313
|
+
context_parts.append("- Jira API access is available via JIRA_TOKEN")
|
|
314
|
+
elif integration_type == "slack":
|
|
315
|
+
context_parts.append("- Slack API access is available via SLACK_TOKEN")
|
|
316
|
+
|
|
317
|
+
return "\n".join(context_parts)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
async def resolve_secret_value(
|
|
321
|
+
secret_name: str,
|
|
322
|
+
token: str,
|
|
323
|
+
org_id: str,
|
|
324
|
+
auth_type: str = "UserKey",
|
|
325
|
+
) -> str:
|
|
326
|
+
"""
|
|
327
|
+
Resolve a secret name to its actual value from Kubiya API.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
secret_name: Name of the secret to resolve
|
|
331
|
+
token: Kubiya API token
|
|
332
|
+
org_id: Organization ID
|
|
333
|
+
auth_type: Authorization type ("UserKey" for API keys, "Bearer" for JWT tokens)
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Secret value as string
|
|
337
|
+
|
|
338
|
+
Raises:
|
|
339
|
+
ExecutionEnvironmentResolutionError: If secret resolution fails
|
|
340
|
+
"""
|
|
341
|
+
# Map auth types to what Kubiya API accepts
|
|
342
|
+
# "JWT" is used internally for Kubiya API keys that are JWTs - Kubiya API expects "UserKey" for these
|
|
343
|
+
kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
|
|
344
|
+
headers = {
|
|
345
|
+
"Authorization": f"{kubiya_auth_type} {token}",
|
|
346
|
+
"Accept": "application/json",
|
|
347
|
+
"Content-Type": "application/json",
|
|
348
|
+
"X-Kubiya-Client": "agent-control-plane",
|
|
349
|
+
"X-Organization-ID": org_id,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
353
|
+
response = await client.get(
|
|
354
|
+
f"{KUBIYA_API_BASE}/api/v2/secrets/get_value/{secret_name}",
|
|
355
|
+
headers=headers,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if response.status_code == 200:
|
|
359
|
+
return response.text
|
|
360
|
+
else:
|
|
361
|
+
logger.warning(
|
|
362
|
+
"secret_resolution_failed",
|
|
363
|
+
secret_name=secret_name[:20],
|
|
364
|
+
status=response.status_code,
|
|
365
|
+
)
|
|
366
|
+
raise ExecutionEnvironmentResolutionError(
|
|
367
|
+
f"Failed to resolve secret '{secret_name}': {response.text[:200]}"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
async def resolve_integration_token(
|
|
372
|
+
integration_id: str,
|
|
373
|
+
integration_type: str,
|
|
374
|
+
token: str,
|
|
375
|
+
org_id: str,
|
|
376
|
+
auth_type: str = "UserKey",
|
|
377
|
+
) -> Dict[str, str]:
|
|
378
|
+
"""
|
|
379
|
+
Resolve an integration ID to its credentials from Kubiya API.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
integration_id: Integration UUID
|
|
383
|
+
integration_type: Type of integration (github, jira, aws, etc.)
|
|
384
|
+
token: Kubiya API token
|
|
385
|
+
org_id: Organization ID
|
|
386
|
+
auth_type: Authorization type ("UserKey" for API keys, "Bearer" for JWT tokens)
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Dict with environment variable names mapped to their values.
|
|
390
|
+
May return multiple env vars for integrations like AWS (key + secret).
|
|
391
|
+
"""
|
|
392
|
+
# Map auth types to what Kubiya API accepts
|
|
393
|
+
kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
|
|
394
|
+
headers = {
|
|
395
|
+
"Authorization": f"{kubiya_auth_type} {token}",
|
|
396
|
+
"Accept": "application/json",
|
|
397
|
+
"Content-Type": "application/json",
|
|
398
|
+
"X-Kubiya-Client": "agent-control-plane",
|
|
399
|
+
"X-Organization-ID": org_id,
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
# Build token URL based on integration type
|
|
403
|
+
integration_type_lower = integration_type.lower()
|
|
404
|
+
|
|
405
|
+
# Map integration type to API endpoint
|
|
406
|
+
# NOTE: Kubiya API currently supports github, github_app, jira via /token endpoint
|
|
407
|
+
# For other types, we try the generic /creds endpoint used by SDK
|
|
408
|
+
|
|
409
|
+
if integration_type_lower == "github":
|
|
410
|
+
token_url = f"{KUBIYA_API_BASE}/api/v1/integration/github/token/{integration_id}"
|
|
411
|
+
elif integration_type_lower == "github_app":
|
|
412
|
+
token_url = f"{KUBIYA_API_BASE}/api/v1/integration/github_app/token/{integration_id}"
|
|
413
|
+
elif integration_type_lower == "jira":
|
|
414
|
+
token_url = f"{KUBIYA_API_BASE}/api/v1/integration/jira/token/{integration_id}"
|
|
415
|
+
elif integration_type_lower == "slack":
|
|
416
|
+
token_url = f"{KUBIYA_API_BASE}/api/v1/integration/slack/token/{integration_id}"
|
|
417
|
+
else:
|
|
418
|
+
# Use SDK-style generic endpoint: /api/v1/integrations/{vendor}/creds/{id}
|
|
419
|
+
# This supports AWS, Kubernetes, Azure, and other integrations
|
|
420
|
+
# Special integrations (jira, github_app) use "0" as ID per SDK convention
|
|
421
|
+
SPECIAL_INTEGRATIONS = {"jira", "github_app"}
|
|
422
|
+
actual_id = "0" if integration_type_lower in SPECIAL_INTEGRATIONS else integration_id
|
|
423
|
+
token_url = f"{KUBIYA_API_BASE}/api/v1/integrations/{integration_type_lower}/creds/{actual_id}"
|
|
424
|
+
logger.debug(
|
|
425
|
+
"using_sdk_creds_endpoint",
|
|
426
|
+
integration_type=integration_type,
|
|
427
|
+
integration_id=integration_id[:8],
|
|
428
|
+
actual_id=actual_id if actual_id == "0" else actual_id[:8],
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
432
|
+
response = await client.get(token_url, headers=headers)
|
|
433
|
+
|
|
434
|
+
if response.status_code == 200:
|
|
435
|
+
try:
|
|
436
|
+
# Try to parse as JSON first
|
|
437
|
+
credential_data = response.json()
|
|
438
|
+
|
|
439
|
+
# Handle different response formats based on integration type
|
|
440
|
+
if integration_type_lower in ["aws", "aws-serviceaccount"]:
|
|
441
|
+
# AWS returns structured credentials
|
|
442
|
+
env_vars = {}
|
|
443
|
+
if "access_key_id" in credential_data:
|
|
444
|
+
env_vars["AWS_ACCESS_KEY_ID"] = credential_data["access_key_id"]
|
|
445
|
+
if "secret_access_key" in credential_data:
|
|
446
|
+
env_vars["AWS_SECRET_ACCESS_KEY"] = credential_data["secret_access_key"]
|
|
447
|
+
if "session_token" in credential_data:
|
|
448
|
+
env_vars["AWS_SESSION_TOKEN"] = credential_data["session_token"]
|
|
449
|
+
if "region" in credential_data:
|
|
450
|
+
env_vars["AWS_REGION"] = credential_data["region"]
|
|
451
|
+
if "role_arn" in credential_data:
|
|
452
|
+
env_vars["AWS_ROLE_ARN"] = credential_data["role_arn"]
|
|
453
|
+
return env_vars
|
|
454
|
+
|
|
455
|
+
elif integration_type_lower == "kubernetes":
|
|
456
|
+
# Kubernetes returns kubeconfig content
|
|
457
|
+
kubeconfig_content = credential_data.get("kubeconfig", response.text)
|
|
458
|
+
return {"KUBECONFIG_CONTENT": kubeconfig_content}
|
|
459
|
+
|
|
460
|
+
elif integration_type_lower == "azure":
|
|
461
|
+
# Azure returns structured credentials
|
|
462
|
+
env_vars = {}
|
|
463
|
+
if "client_id" in credential_data:
|
|
464
|
+
env_vars["AZURE_CLIENT_ID"] = credential_data["client_id"]
|
|
465
|
+
if "client_secret" in credential_data:
|
|
466
|
+
env_vars["AZURE_CLIENT_SECRET"] = credential_data["client_secret"]
|
|
467
|
+
if "tenant_id" in credential_data:
|
|
468
|
+
env_vars["AZURE_TENANT_ID"] = credential_data["tenant_id"]
|
|
469
|
+
return env_vars
|
|
470
|
+
|
|
471
|
+
else:
|
|
472
|
+
# Generic token-based integration
|
|
473
|
+
token_value = credential_data.get("token", response.text)
|
|
474
|
+
|
|
475
|
+
# Map to standard env var names
|
|
476
|
+
env_var_names = INTEGRATION_ENV_VAR_MAP.get(
|
|
477
|
+
integration_type_lower, [f"{integration_type.upper()}_TOKEN"]
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
# Return token for first (primary) env var name
|
|
481
|
+
return {env_var_names[0]: token_value}
|
|
482
|
+
|
|
483
|
+
except Exception as e:
|
|
484
|
+
logger.debug(
|
|
485
|
+
"credential_json_parse_failed",
|
|
486
|
+
integration_id=integration_id[:8],
|
|
487
|
+
error=str(e),
|
|
488
|
+
note="Falling back to plain text"
|
|
489
|
+
)
|
|
490
|
+
# If not JSON, use plain text
|
|
491
|
+
token_value = response.text.strip()
|
|
492
|
+
env_var_names = INTEGRATION_ENV_VAR_MAP.get(
|
|
493
|
+
integration_type_lower, [f"{integration_type.upper()}_TOKEN"]
|
|
494
|
+
)
|
|
495
|
+
return {env_var_names[0]: token_value}
|
|
496
|
+
else:
|
|
497
|
+
logger.warning(
|
|
498
|
+
"integration_token_resolution_failed",
|
|
499
|
+
integration_id=integration_id[:8],
|
|
500
|
+
integration_type=integration_type,
|
|
501
|
+
status=response.status_code,
|
|
502
|
+
response_preview=response.text[:200],
|
|
503
|
+
)
|
|
504
|
+
# Don't fail the entire request for one integration
|
|
505
|
+
return {}
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
async def resolve_environment_configs(
|
|
509
|
+
environment_ids: List[str],
|
|
510
|
+
org_id: str,
|
|
511
|
+
db: Session,
|
|
512
|
+
) -> Dict[str, Any]:
|
|
513
|
+
"""
|
|
514
|
+
Resolve execution environment configs from a list of environment IDs.
|
|
515
|
+
Merges configs from all environments.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
environment_ids: List of environment IDs
|
|
519
|
+
org_id: Organization ID
|
|
520
|
+
db: Database session
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Merged execution environment dict with env_vars, secrets, integration_ids, mcp_servers
|
|
524
|
+
"""
|
|
525
|
+
if not environment_ids:
|
|
526
|
+
return {
|
|
527
|
+
"env_vars": {},
|
|
528
|
+
"secrets": [],
|
|
529
|
+
"integration_ids": [],
|
|
530
|
+
"mcp_servers": {},
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
# Fetch all environments
|
|
534
|
+
environments = (
|
|
535
|
+
db.query(Environment)
|
|
536
|
+
.filter(Environment.id.in_(environment_ids), Environment.organization_id == org_id)
|
|
537
|
+
.all()
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# Merge all environment configs
|
|
541
|
+
merged_env_vars = {}
|
|
542
|
+
merged_secrets = set()
|
|
543
|
+
merged_integration_ids = set()
|
|
544
|
+
merged_mcp_servers = {}
|
|
545
|
+
|
|
546
|
+
for env in environments:
|
|
547
|
+
env_config = env.execution_environment or {}
|
|
548
|
+
|
|
549
|
+
# Merge env vars (later environments override earlier ones)
|
|
550
|
+
merged_env_vars.update(env_config.get("env_vars", {}))
|
|
551
|
+
|
|
552
|
+
# Collect secrets (union)
|
|
553
|
+
merged_secrets.update(env_config.get("secrets", []))
|
|
554
|
+
|
|
555
|
+
# Collect integration IDs (union)
|
|
556
|
+
merged_integration_ids.update(env_config.get("integration_ids", []))
|
|
557
|
+
|
|
558
|
+
# Merge MCP servers (later environments override earlier ones)
|
|
559
|
+
merged_mcp_servers.update(env_config.get("mcp_servers", {}))
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
"env_vars": merged_env_vars,
|
|
563
|
+
"secrets": list(merged_secrets),
|
|
564
|
+
"integration_ids": list(merged_integration_ids),
|
|
565
|
+
"mcp_servers": merged_mcp_servers,
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def apply_template_resolution(
|
|
570
|
+
config: Dict[str, Any],
|
|
571
|
+
resolved_secrets: Dict[str, str],
|
|
572
|
+
resolved_env_vars: Dict[str, str],
|
|
573
|
+
) -> Dict[str, Any]:
|
|
574
|
+
"""
|
|
575
|
+
Apply template resolution to a configuration object.
|
|
576
|
+
|
|
577
|
+
Resolves all templates in the config using resolved secrets and env vars.
|
|
578
|
+
Templates are resolved recursively in all string fields.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
config: Configuration dict with potential templates
|
|
582
|
+
resolved_secrets: Map of secret names to resolved values
|
|
583
|
+
resolved_env_vars: Map of env var names to values
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Configuration with all templates resolved
|
|
587
|
+
"""
|
|
588
|
+
try:
|
|
589
|
+
# Build template context
|
|
590
|
+
# Include env_vars in variables for simple {{VAR}} syntax support
|
|
591
|
+
# This allows both {{.env.VAR}} and {{VAR}} to work for environment variables
|
|
592
|
+
context = TemplateContext(
|
|
593
|
+
variables=dict(resolved_env_vars), # Also expose env vars as simple variables
|
|
594
|
+
secrets=resolved_secrets,
|
|
595
|
+
env_vars=resolved_env_vars,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
# Apply template resolution recursively to entire config
|
|
599
|
+
resolved_config = resolve_templates(config, context, skip_on_error=True)
|
|
600
|
+
|
|
601
|
+
logger.debug(
|
|
602
|
+
"template_resolution_applied",
|
|
603
|
+
config_keys=list(config.keys()),
|
|
604
|
+
secrets_count=len(resolved_secrets),
|
|
605
|
+
env_vars_count=len(resolved_env_vars),
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
return resolved_config
|
|
609
|
+
|
|
610
|
+
except Exception as e:
|
|
611
|
+
logger.error(
|
|
612
|
+
"template_resolution_failed",
|
|
613
|
+
error=str(e),
|
|
614
|
+
config_keys=list(config.keys()),
|
|
615
|
+
)
|
|
616
|
+
# Return original config on error to avoid breaking execution
|
|
617
|
+
return config
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
async def resolve_agent_execution_environment(
|
|
621
|
+
agent_id: str,
|
|
622
|
+
org_id: str,
|
|
623
|
+
db: Session,
|
|
624
|
+
kubiya_token: str = None,
|
|
625
|
+
) -> Dict[str, Any]:
|
|
626
|
+
"""
|
|
627
|
+
Resolve complete execution environment for an agent.
|
|
628
|
+
|
|
629
|
+
This is the main controller function that:
|
|
630
|
+
1. Fetches agent config and associated environments from database
|
|
631
|
+
2. Merges environment configs (env vars, secrets, integrations, MCP servers)
|
|
632
|
+
3. Resolves secret names to actual values from Kubiya API
|
|
633
|
+
4. Resolves integration IDs to actual tokens from Kubiya API
|
|
634
|
+
5. Applies template resolution to all config fields
|
|
635
|
+
6. Returns complete resolved execution environment
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
agent_id: Agent UUID
|
|
639
|
+
org_id: Organization ID
|
|
640
|
+
db: Database session
|
|
641
|
+
kubiya_token: Kubiya API token for secret/integration resolution (optional, uses env var if not provided)
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
Dict with:
|
|
645
|
+
- env_vars: Resolved environment variables (dict)
|
|
646
|
+
- mcp_servers: MCP server configs with templates resolved (dict)
|
|
647
|
+
- system_prompt: Resolved system prompt (str)
|
|
648
|
+
- description: Resolved description (str)
|
|
649
|
+
- configuration: Resolved agent configuration (dict)
|
|
650
|
+
|
|
651
|
+
Raises:
|
|
652
|
+
ExecutionEnvironmentResolutionError: If agent not found or resolution fails
|
|
653
|
+
"""
|
|
654
|
+
try:
|
|
655
|
+
# Use environment KUBIYA_API_KEY if token not provided
|
|
656
|
+
# This is needed because the JWT bearer token from requests doesn't work with Kubiya secrets API
|
|
657
|
+
import os
|
|
658
|
+
if not kubiya_token:
|
|
659
|
+
kubiya_token = os.environ.get("KUBIYA_API_KEY")
|
|
660
|
+
if not kubiya_token:
|
|
661
|
+
logger.warning(
|
|
662
|
+
"kubiya_api_key_not_available",
|
|
663
|
+
agent_id=agent_id[:8],
|
|
664
|
+
note="Secrets and integrations will not be resolved"
|
|
665
|
+
)
|
|
666
|
+
# Continue without secret resolution
|
|
667
|
+
# Fetch agent with configuration fields
|
|
668
|
+
agent = (
|
|
669
|
+
db.query(Agent)
|
|
670
|
+
.filter(Agent.id == agent_id, Agent.organization_id == org_id)
|
|
671
|
+
.first()
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if not agent:
|
|
675
|
+
raise ExecutionEnvironmentResolutionError(
|
|
676
|
+
f"Agent {agent_id} not found in organization {org_id}"
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Get environment associations from join table
|
|
680
|
+
env_associations = (
|
|
681
|
+
db.query(AgentEnvironment)
|
|
682
|
+
.filter(AgentEnvironment.agent_id == agent_id)
|
|
683
|
+
.all()
|
|
684
|
+
)
|
|
685
|
+
environment_ids = [str(assoc.environment_id) for assoc in env_associations]
|
|
686
|
+
|
|
687
|
+
# Fetch environment names for dataset scoping
|
|
688
|
+
environment_names = []
|
|
689
|
+
if environment_ids:
|
|
690
|
+
environments = (
|
|
691
|
+
db.query(Environment)
|
|
692
|
+
.filter(Environment.id.in_(environment_ids))
|
|
693
|
+
.all()
|
|
694
|
+
)
|
|
695
|
+
environment_names = [env.name for env in environments]
|
|
696
|
+
|
|
697
|
+
# Resolve and merge environment configs
|
|
698
|
+
env_config = await resolve_environment_configs(environment_ids, org_id, db)
|
|
699
|
+
|
|
700
|
+
# Get agent-level config
|
|
701
|
+
agent_exec_env = agent.execution_environment or {}
|
|
702
|
+
agent_configuration = agent.configuration or {}
|
|
703
|
+
|
|
704
|
+
# Merge: environment config + agent config (agent overrides environment)
|
|
705
|
+
execution_environment = {
|
|
706
|
+
"env_vars": {
|
|
707
|
+
**env_config.get("env_vars", {}),
|
|
708
|
+
**agent_exec_env.get("env_vars", {}),
|
|
709
|
+
},
|
|
710
|
+
"secrets": list(
|
|
711
|
+
set(env_config.get("secrets", []) + agent_exec_env.get("secrets", []))
|
|
712
|
+
),
|
|
713
|
+
"integration_ids": list(
|
|
714
|
+
set(
|
|
715
|
+
env_config.get("integration_ids", [])
|
|
716
|
+
+ agent_exec_env.get("integration_ids", [])
|
|
717
|
+
)
|
|
718
|
+
),
|
|
719
|
+
"custom_integration_ids": list(
|
|
720
|
+
set(
|
|
721
|
+
env_config.get("custom_integration_ids", [])
|
|
722
|
+
+ agent_exec_env.get("custom_integration_ids", [])
|
|
723
|
+
)
|
|
724
|
+
),
|
|
725
|
+
"mcp_servers": {
|
|
726
|
+
**env_config.get("mcp_servers", {}),
|
|
727
|
+
**agent_exec_env.get("mcp_servers", {}),
|
|
728
|
+
},
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
# Start with custom env vars
|
|
732
|
+
resolved_env_vars = dict(execution_environment.get("env_vars", {}))
|
|
733
|
+
resolved_secrets = {}
|
|
734
|
+
resolved_integrations = [] # Track resolved integrations for context
|
|
735
|
+
|
|
736
|
+
# Add KUBIYA_API_KEY to resolved_env_vars for template resolution
|
|
737
|
+
# This allows MCP server configs to use {{KUBIYA_API_KEY}} templates
|
|
738
|
+
# Use the kubiya_token parameter (from request) since Control Plane doesn't have KUBIYA_API_KEY in env
|
|
739
|
+
if kubiya_token:
|
|
740
|
+
resolved_env_vars["KUBIYA_API_KEY"] = kubiya_token
|
|
741
|
+
|
|
742
|
+
# Resolve secrets
|
|
743
|
+
secrets = execution_environment.get("secrets", [])
|
|
744
|
+
for secret_name in secrets:
|
|
745
|
+
try:
|
|
746
|
+
secret_value = await resolve_secret_value(
|
|
747
|
+
secret_name, kubiya_token, org_id
|
|
748
|
+
)
|
|
749
|
+
resolved_env_vars[secret_name] = secret_value
|
|
750
|
+
resolved_secrets[secret_name] = secret_value
|
|
751
|
+
logger.debug(
|
|
752
|
+
"secret_resolved",
|
|
753
|
+
agent_id=agent_id[:8],
|
|
754
|
+
secret_name=secret_name[:20],
|
|
755
|
+
)
|
|
756
|
+
except Exception as e:
|
|
757
|
+
logger.error(
|
|
758
|
+
"secret_resolution_error",
|
|
759
|
+
agent_id=agent_id[:8],
|
|
760
|
+
secret_name=secret_name[:20],
|
|
761
|
+
error=str(e),
|
|
762
|
+
)
|
|
763
|
+
# Continue with other secrets even if one fails
|
|
764
|
+
|
|
765
|
+
# Resolve integrations
|
|
766
|
+
integration_ids = execution_environment.get("integration_ids", [])
|
|
767
|
+
if integration_ids:
|
|
768
|
+
headers = {
|
|
769
|
+
"Authorization": f"UserKey {kubiya_token}",
|
|
770
|
+
"Accept": "application/json",
|
|
771
|
+
"Content-Type": "application/json",
|
|
772
|
+
"X-Kubiya-Client": "agent-control-plane",
|
|
773
|
+
"X-Organization-ID": org_id,
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
777
|
+
response = await client.get(
|
|
778
|
+
f"{KUBIYA_API_BASE}/api/v2/integrations?full=true",
|
|
779
|
+
headers=headers,
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
if response.status_code == 200:
|
|
783
|
+
all_integrations = response.json()
|
|
784
|
+
|
|
785
|
+
for integration_id in integration_ids:
|
|
786
|
+
integration = next(
|
|
787
|
+
(
|
|
788
|
+
i
|
|
789
|
+
for i in all_integrations
|
|
790
|
+
if i.get("uuid") == integration_id
|
|
791
|
+
),
|
|
792
|
+
None,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
if integration:
|
|
796
|
+
integration_type = integration.get("integration_type", "")
|
|
797
|
+
integration_name = integration.get("name", integration_type)
|
|
798
|
+
try:
|
|
799
|
+
token_env_vars = await resolve_integration_token(
|
|
800
|
+
integration_id,
|
|
801
|
+
integration_type,
|
|
802
|
+
kubiya_token,
|
|
803
|
+
org_id,
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# Handle AWS credentials file creation
|
|
807
|
+
if integration_type in ["aws", "aws-serviceaccount"] and token_env_vars:
|
|
808
|
+
if "AWS_ACCESS_KEY_ID" in token_env_vars and "AWS_SECRET_ACCESS_KEY" in token_env_vars:
|
|
809
|
+
try:
|
|
810
|
+
creds_path, config_path = create_aws_credentials_file(
|
|
811
|
+
access_key_id=token_env_vars["AWS_ACCESS_KEY_ID"],
|
|
812
|
+
secret_access_key=token_env_vars["AWS_SECRET_ACCESS_KEY"],
|
|
813
|
+
session_token=token_env_vars.get("AWS_SESSION_TOKEN"),
|
|
814
|
+
region=token_env_vars.get("AWS_REGION"),
|
|
815
|
+
)
|
|
816
|
+
# Set AWS file locations
|
|
817
|
+
resolved_env_vars["AWS_SHARED_CREDENTIALS_FILE"] = creds_path
|
|
818
|
+
resolved_env_vars["AWS_CONFIG_FILE"] = config_path
|
|
819
|
+
except Exception as e:
|
|
820
|
+
logger.warning(
|
|
821
|
+
"aws_credentials_file_creation_failed",
|
|
822
|
+
error=str(e),
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
# Handle Kubernetes kubeconfig file creation
|
|
826
|
+
elif integration_type == "kubernetes" and "KUBECONFIG_CONTENT" in token_env_vars:
|
|
827
|
+
try:
|
|
828
|
+
kubeconfig_path = create_kubeconfig_file(
|
|
829
|
+
token_env_vars["KUBECONFIG_CONTENT"]
|
|
830
|
+
)
|
|
831
|
+
resolved_env_vars["KUBECONFIG"] = kubeconfig_path
|
|
832
|
+
# Remove content from env vars, keep only path
|
|
833
|
+
del token_env_vars["KUBECONFIG_CONTENT"]
|
|
834
|
+
except Exception as e:
|
|
835
|
+
logger.warning(
|
|
836
|
+
"kubeconfig_file_creation_failed",
|
|
837
|
+
error=str(e),
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Add all resolved env vars
|
|
841
|
+
resolved_env_vars.update(token_env_vars)
|
|
842
|
+
|
|
843
|
+
# Track integration for context
|
|
844
|
+
resolved_integrations.append({
|
|
845
|
+
"integration_type": integration_type,
|
|
846
|
+
"name": integration_name,
|
|
847
|
+
"env_vars": list(token_env_vars.keys()),
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
logger.debug(
|
|
851
|
+
"integration_resolved",
|
|
852
|
+
agent_id=agent_id[:8],
|
|
853
|
+
integration_id=integration_id[:8],
|
|
854
|
+
integration_type=integration_type,
|
|
855
|
+
env_var_count=len(token_env_vars),
|
|
856
|
+
)
|
|
857
|
+
except Exception as e:
|
|
858
|
+
logger.error(
|
|
859
|
+
"integration_resolution_error",
|
|
860
|
+
agent_id=agent_id[:8],
|
|
861
|
+
integration_id=integration_id[:8],
|
|
862
|
+
error=str(e),
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
# Resolve custom integrations
|
|
866
|
+
custom_integration_ids = execution_environment.get("custom_integration_ids", [])
|
|
867
|
+
custom_integration_files = [] # Track files to be created
|
|
868
|
+
|
|
869
|
+
for custom_integration_id in custom_integration_ids:
|
|
870
|
+
try:
|
|
871
|
+
result = await resolve_custom_integration(
|
|
872
|
+
custom_integration_id=custom_integration_id,
|
|
873
|
+
org_id=org_id,
|
|
874
|
+
db=db,
|
|
875
|
+
kubiya_token=kubiya_token,
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
# Add resolved env vars
|
|
879
|
+
resolved_env_vars.update(result["env_vars"])
|
|
880
|
+
|
|
881
|
+
# Track files to be created
|
|
882
|
+
custom_integration_files.extend(result["files"])
|
|
883
|
+
|
|
884
|
+
# Track integration for context
|
|
885
|
+
resolved_integrations.append({
|
|
886
|
+
"integration_type": result.get("integration_type", "custom"),
|
|
887
|
+
"name": result.get("name", "Custom Integration"),
|
|
888
|
+
"env_vars": list(result["env_vars"].keys()),
|
|
889
|
+
"custom_context": result.get("context_prompt"),
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
logger.debug(
|
|
893
|
+
"custom_integration_resolved",
|
|
894
|
+
agent_id=agent_id[:8],
|
|
895
|
+
custom_integration_id=custom_integration_id[:8],
|
|
896
|
+
env_var_count=len(result["env_vars"]),
|
|
897
|
+
file_count=len(result["files"]),
|
|
898
|
+
)
|
|
899
|
+
except Exception as e:
|
|
900
|
+
logger.error(
|
|
901
|
+
"custom_integration_resolution_error",
|
|
902
|
+
agent_id=agent_id[:8],
|
|
903
|
+
custom_integration_id=custom_integration_id[:8],
|
|
904
|
+
error=str(e),
|
|
905
|
+
)
|
|
906
|
+
# Continue with other custom integrations even if one fails
|
|
907
|
+
|
|
908
|
+
# Build complete config to resolve templates
|
|
909
|
+
complete_config = {
|
|
910
|
+
"system_prompt": agent_configuration.get("system_prompt"),
|
|
911
|
+
"description": agent.description,
|
|
912
|
+
"configuration": agent_configuration,
|
|
913
|
+
"mcp_servers": execution_environment.get("mcp_servers", {}),
|
|
914
|
+
"env_vars": execution_environment.get("env_vars", {}),
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
# Apply template resolution to ENTIRE config
|
|
918
|
+
resolved_config = apply_template_resolution(
|
|
919
|
+
complete_config, resolved_secrets, resolved_env_vars
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
mcp_servers_resolved = resolved_config.get("mcp_servers", {})
|
|
923
|
+
|
|
924
|
+
# Build integration context and inject into system prompt
|
|
925
|
+
integration_context = build_integration_context(resolved_integrations)
|
|
926
|
+
system_prompt = resolved_config.get("system_prompt", "")
|
|
927
|
+
|
|
928
|
+
if integration_context and system_prompt:
|
|
929
|
+
# Append integration context to system prompt
|
|
930
|
+
system_prompt = f"{system_prompt}\n\n{integration_context}"
|
|
931
|
+
|
|
932
|
+
# Add memory and context graph guidance to system prompt
|
|
933
|
+
memory_guidance = """
|
|
934
|
+
|
|
935
|
+
## Memory & Context Graph Tools
|
|
936
|
+
|
|
937
|
+
You have built-in persistent memory and organizational context awareness:
|
|
938
|
+
|
|
939
|
+
**Memory Tools** (use proactively):
|
|
940
|
+
- **store_memory(content, metadata)**: Store important facts, decisions, preferences, or context
|
|
941
|
+
- **recall_memory(query, limit)**: Retrieve stored memories using semantic search
|
|
942
|
+
|
|
943
|
+
**Context Graph** (discover organizational data):
|
|
944
|
+
- **search_nodes(label, property_name, property_value)**: Find resources by type and properties
|
|
945
|
+
- **search_by_text(property_name, search_text)**: Text search across the graph
|
|
946
|
+
- **get_node(node_id)**: Get detailed information about a specific resource
|
|
947
|
+
|
|
948
|
+
**Best Practices**:
|
|
949
|
+
- Store user preferences, task decisions, and important context as you learn them
|
|
950
|
+
- Use recall_memory at the start of tasks to check for relevant past context
|
|
951
|
+
- Search the graph to discover infrastructure, services, and relationships
|
|
952
|
+
- Add descriptive metadata when storing memories for better retrieval"""
|
|
953
|
+
|
|
954
|
+
if system_prompt:
|
|
955
|
+
system_prompt = f"{system_prompt}\n{memory_guidance}"
|
|
956
|
+
else:
|
|
957
|
+
system_prompt = memory_guidance.strip()
|
|
958
|
+
|
|
959
|
+
# Get context graph API URL from settings for memory tools
|
|
960
|
+
from control_plane_api.app.config import settings
|
|
961
|
+
graph_api_url = settings.context_graph_api_base
|
|
962
|
+
dataset_name = environment_names[0] if environment_names else "default"
|
|
963
|
+
|
|
964
|
+
# Inject memory dataset name into env vars
|
|
965
|
+
# Worker skills will fetch graph URL from control plane's /api/v1/client/config endpoint
|
|
966
|
+
resolved_env_vars["MEMORY_DATASET_NAME"] = dataset_name
|
|
967
|
+
|
|
968
|
+
logger.info(
|
|
969
|
+
"agent_execution_environment_resolved",
|
|
970
|
+
agent_id=agent_id[:8],
|
|
971
|
+
env_var_count=len(resolved_env_vars),
|
|
972
|
+
mcp_server_count=len(mcp_servers_resolved),
|
|
973
|
+
mcp_server_names=list(mcp_servers_resolved.keys()),
|
|
974
|
+
secrets_count=len(resolved_secrets),
|
|
975
|
+
integrations_count=len(resolved_integrations),
|
|
976
|
+
custom_integration_files_count=len(custom_integration_files),
|
|
977
|
+
graph_api_url=graph_api_url,
|
|
978
|
+
dataset_name=dataset_name,
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
return {
|
|
982
|
+
"env_vars": resolved_env_vars,
|
|
983
|
+
"mcp_servers": mcp_servers_resolved,
|
|
984
|
+
"system_prompt": system_prompt,
|
|
985
|
+
"description": resolved_config.get("description"),
|
|
986
|
+
"configuration": resolved_config.get("configuration", {}),
|
|
987
|
+
"files": custom_integration_files,
|
|
988
|
+
# Context graph configuration for memory tools
|
|
989
|
+
"graph_api_url": graph_api_url,
|
|
990
|
+
"dataset_name": dataset_name,
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
except ExecutionEnvironmentResolutionError:
|
|
994
|
+
raise
|
|
995
|
+
except Exception as e:
|
|
996
|
+
logger.error(
|
|
997
|
+
"agent_execution_environment_resolution_error",
|
|
998
|
+
agent_id=agent_id[:8],
|
|
999
|
+
error=str(e),
|
|
1000
|
+
exc_info=True,
|
|
1001
|
+
)
|
|
1002
|
+
raise ExecutionEnvironmentResolutionError(
|
|
1003
|
+
f"Failed to resolve execution environment for agent {agent_id}: {str(e)}"
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
async def resolve_team_execution_environment(
|
|
1008
|
+
team_id: str,
|
|
1009
|
+
org_id: str,
|
|
1010
|
+
db: Session,
|
|
1011
|
+
kubiya_token: str = None,
|
|
1012
|
+
) -> Dict[str, Any]:
|
|
1013
|
+
"""
|
|
1014
|
+
Resolve complete execution environment for a team.
|
|
1015
|
+
|
|
1016
|
+
Similar to resolve_agent_execution_environment but for teams.
|
|
1017
|
+
|
|
1018
|
+
Args:
|
|
1019
|
+
team_id: Team UUID
|
|
1020
|
+
org_id: Organization ID
|
|
1021
|
+
db: Database session
|
|
1022
|
+
kubiya_token: Kubiya API token for secret/integration resolution (optional, uses env var if not provided)
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
Dict with resolved execution environment
|
|
1026
|
+
|
|
1027
|
+
Raises:
|
|
1028
|
+
ExecutionEnvironmentResolutionError: If team not found or resolution fails
|
|
1029
|
+
"""
|
|
1030
|
+
try:
|
|
1031
|
+
# Use environment KUBIYA_API_KEY if token not provided
|
|
1032
|
+
import os
|
|
1033
|
+
if not kubiya_token:
|
|
1034
|
+
kubiya_token = os.environ.get("KUBIYA_API_KEY")
|
|
1035
|
+
if not kubiya_token:
|
|
1036
|
+
logger.warning(
|
|
1037
|
+
"kubiya_api_key_not_available",
|
|
1038
|
+
team_id=team_id[:8],
|
|
1039
|
+
note="Secrets and integrations will not be resolved"
|
|
1040
|
+
)
|
|
1041
|
+
# Fetch team with configuration fields
|
|
1042
|
+
team = (
|
|
1043
|
+
db.query(Team)
|
|
1044
|
+
.filter(Team.id == team_id, Team.organization_id == org_id)
|
|
1045
|
+
.first()
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
if not team:
|
|
1049
|
+
raise ExecutionEnvironmentResolutionError(
|
|
1050
|
+
f"Team {team_id} not found in organization {org_id}"
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
# Get environment-level configs
|
|
1054
|
+
environment_ids = team.environment_ids or []
|
|
1055
|
+
|
|
1056
|
+
# Fetch environment names for dataset scoping
|
|
1057
|
+
environment_names = []
|
|
1058
|
+
if environment_ids:
|
|
1059
|
+
environments = (
|
|
1060
|
+
db.query(Environment)
|
|
1061
|
+
.filter(Environment.id.in_(environment_ids))
|
|
1062
|
+
.all()
|
|
1063
|
+
)
|
|
1064
|
+
environment_names = [env.name for env in environments]
|
|
1065
|
+
|
|
1066
|
+
env_config = await resolve_environment_configs(environment_ids, org_id, db)
|
|
1067
|
+
|
|
1068
|
+
# Get team-level config
|
|
1069
|
+
team_exec_env = team.execution_environment or {}
|
|
1070
|
+
|
|
1071
|
+
# Merge: environment config + team config (team overrides environment)
|
|
1072
|
+
execution_environment = {
|
|
1073
|
+
"env_vars": {
|
|
1074
|
+
**env_config.get("env_vars", {}),
|
|
1075
|
+
**team_exec_env.get("env_vars", {}),
|
|
1076
|
+
},
|
|
1077
|
+
"secrets": list(
|
|
1078
|
+
set(env_config.get("secrets", []) + team_exec_env.get("secrets", []))
|
|
1079
|
+
),
|
|
1080
|
+
"integration_ids": list(
|
|
1081
|
+
set(
|
|
1082
|
+
env_config.get("integration_ids", [])
|
|
1083
|
+
+ team_exec_env.get("integration_ids", [])
|
|
1084
|
+
)
|
|
1085
|
+
),
|
|
1086
|
+
"mcp_servers": {
|
|
1087
|
+
**env_config.get("mcp_servers", {}),
|
|
1088
|
+
**team_exec_env.get("mcp_servers", {}),
|
|
1089
|
+
},
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
# Start with custom env vars
|
|
1093
|
+
resolved_env_vars = dict(execution_environment.get("env_vars", {}))
|
|
1094
|
+
resolved_secrets = {}
|
|
1095
|
+
resolved_integrations = [] # Track resolved integrations for context
|
|
1096
|
+
|
|
1097
|
+
# Add KUBIYA_API_KEY to resolved_env_vars for template resolution
|
|
1098
|
+
# This allows MCP server configs to use {{KUBIYA_API_KEY}} templates
|
|
1099
|
+
# Use the kubiya_token parameter (from request) since Control Plane doesn't have KUBIYA_API_KEY in env
|
|
1100
|
+
if kubiya_token:
|
|
1101
|
+
resolved_env_vars["KUBIYA_API_KEY"] = kubiya_token
|
|
1102
|
+
|
|
1103
|
+
# Resolve secrets
|
|
1104
|
+
secrets = execution_environment.get("secrets", [])
|
|
1105
|
+
for secret_name in secrets:
|
|
1106
|
+
try:
|
|
1107
|
+
secret_value = await resolve_secret_value(
|
|
1108
|
+
secret_name, kubiya_token, org_id
|
|
1109
|
+
)
|
|
1110
|
+
resolved_env_vars[secret_name] = secret_value
|
|
1111
|
+
resolved_secrets[secret_name] = secret_value
|
|
1112
|
+
logger.debug(
|
|
1113
|
+
"secret_resolved",
|
|
1114
|
+
team_id=team_id[:8],
|
|
1115
|
+
secret_name=secret_name[:20],
|
|
1116
|
+
)
|
|
1117
|
+
except Exception as e:
|
|
1118
|
+
logger.error(
|
|
1119
|
+
"secret_resolution_error",
|
|
1120
|
+
team_id=team_id[:8],
|
|
1121
|
+
secret_name=secret_name[:20],
|
|
1122
|
+
error=str(e),
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
# Resolve integrations
|
|
1126
|
+
integration_ids = execution_environment.get("integration_ids", [])
|
|
1127
|
+
if integration_ids:
|
|
1128
|
+
headers = {
|
|
1129
|
+
"Authorization": f"UserKey {kubiya_token}",
|
|
1130
|
+
"Accept": "application/json",
|
|
1131
|
+
"Content-Type": "application/json",
|
|
1132
|
+
"X-Kubiya-Client": "agent-control-plane",
|
|
1133
|
+
"X-Organization-ID": org_id,
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
1137
|
+
response = await client.get(
|
|
1138
|
+
f"{KUBIYA_API_BASE}/api/v2/integrations?full=true",
|
|
1139
|
+
headers=headers,
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
if response.status_code == 200:
|
|
1143
|
+
all_integrations = response.json()
|
|
1144
|
+
|
|
1145
|
+
for integration_id in integration_ids:
|
|
1146
|
+
integration = next(
|
|
1147
|
+
(
|
|
1148
|
+
i
|
|
1149
|
+
for i in all_integrations
|
|
1150
|
+
if i.get("uuid") == integration_id
|
|
1151
|
+
),
|
|
1152
|
+
None,
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
if integration:
|
|
1156
|
+
integration_type = integration.get("integration_type", "")
|
|
1157
|
+
integration_name = integration.get("name", integration_type)
|
|
1158
|
+
try:
|
|
1159
|
+
token_env_vars = await resolve_integration_token(
|
|
1160
|
+
integration_id,
|
|
1161
|
+
integration_type,
|
|
1162
|
+
kubiya_token,
|
|
1163
|
+
org_id,
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
# Handle AWS credentials file creation
|
|
1167
|
+
if integration_type in ["aws", "aws-serviceaccount"] and token_env_vars:
|
|
1168
|
+
if "AWS_ACCESS_KEY_ID" in token_env_vars and "AWS_SECRET_ACCESS_KEY" in token_env_vars:
|
|
1169
|
+
try:
|
|
1170
|
+
creds_path, config_path = create_aws_credentials_file(
|
|
1171
|
+
access_key_id=token_env_vars["AWS_ACCESS_KEY_ID"],
|
|
1172
|
+
secret_access_key=token_env_vars["AWS_SECRET_ACCESS_KEY"],
|
|
1173
|
+
session_token=token_env_vars.get("AWS_SESSION_TOKEN"),
|
|
1174
|
+
region=token_env_vars.get("AWS_REGION"),
|
|
1175
|
+
)
|
|
1176
|
+
resolved_env_vars["AWS_SHARED_CREDENTIALS_FILE"] = creds_path
|
|
1177
|
+
resolved_env_vars["AWS_CONFIG_FILE"] = config_path
|
|
1178
|
+
except Exception as e:
|
|
1179
|
+
logger.warning(
|
|
1180
|
+
"aws_credentials_file_creation_failed",
|
|
1181
|
+
error=str(e),
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
# Handle Kubernetes kubeconfig file creation
|
|
1185
|
+
elif integration_type == "kubernetes" and "KUBECONFIG_CONTENT" in token_env_vars:
|
|
1186
|
+
try:
|
|
1187
|
+
kubeconfig_path = create_kubeconfig_file(
|
|
1188
|
+
token_env_vars["KUBECONFIG_CONTENT"]
|
|
1189
|
+
)
|
|
1190
|
+
resolved_env_vars["KUBECONFIG"] = kubeconfig_path
|
|
1191
|
+
del token_env_vars["KUBECONFIG_CONTENT"]
|
|
1192
|
+
except Exception as e:
|
|
1193
|
+
logger.warning(
|
|
1194
|
+
"kubeconfig_file_creation_failed",
|
|
1195
|
+
error=str(e),
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
resolved_env_vars.update(token_env_vars)
|
|
1199
|
+
|
|
1200
|
+
# Track integration for context
|
|
1201
|
+
resolved_integrations.append({
|
|
1202
|
+
"integration_type": integration_type,
|
|
1203
|
+
"name": integration_name,
|
|
1204
|
+
"env_vars": list(token_env_vars.keys()),
|
|
1205
|
+
})
|
|
1206
|
+
|
|
1207
|
+
logger.debug(
|
|
1208
|
+
"integration_resolved",
|
|
1209
|
+
team_id=team_id[:8],
|
|
1210
|
+
integration_id=integration_id[:8],
|
|
1211
|
+
integration_type=integration_type,
|
|
1212
|
+
env_var_count=len(token_env_vars),
|
|
1213
|
+
)
|
|
1214
|
+
except Exception as e:
|
|
1215
|
+
logger.error(
|
|
1216
|
+
"integration_resolution_error",
|
|
1217
|
+
team_id=team_id[:8],
|
|
1218
|
+
integration_id=integration_id[:8],
|
|
1219
|
+
error=str(e),
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
# Build complete config to resolve templates
|
|
1223
|
+
complete_config = {
|
|
1224
|
+
"instructions": (
|
|
1225
|
+
team.configuration.get("instructions") if team.configuration else None
|
|
1226
|
+
),
|
|
1227
|
+
"description": team.description,
|
|
1228
|
+
"configuration": team.configuration or {},
|
|
1229
|
+
"mcp_servers": execution_environment.get("mcp_servers", {}),
|
|
1230
|
+
"env_vars": execution_environment.get("env_vars", {}),
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
# Apply template resolution to ENTIRE config
|
|
1234
|
+
resolved_config = apply_template_resolution(
|
|
1235
|
+
complete_config, resolved_secrets, resolved_env_vars
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
# Build integration context and inject into instructions
|
|
1239
|
+
integration_context = build_integration_context(resolved_integrations)
|
|
1240
|
+
instructions = resolved_config.get("instructions", "")
|
|
1241
|
+
|
|
1242
|
+
if integration_context and instructions:
|
|
1243
|
+
# Append integration context to instructions
|
|
1244
|
+
instructions = f"{instructions}\n\n{integration_context}"
|
|
1245
|
+
|
|
1246
|
+
# Add memory and context graph guidance to instructions
|
|
1247
|
+
memory_guidance = """
|
|
1248
|
+
|
|
1249
|
+
## Memory & Context Graph Tools
|
|
1250
|
+
|
|
1251
|
+
You have built-in persistent memory and organizational context awareness:
|
|
1252
|
+
|
|
1253
|
+
**Memory Tools** (use proactively):
|
|
1254
|
+
- **store_memory(content, metadata)**: Store important facts, decisions, preferences, or context
|
|
1255
|
+
- **recall_memory(query, limit)**: Retrieve stored memories using semantic search
|
|
1256
|
+
|
|
1257
|
+
**Context Graph** (discover organizational data):
|
|
1258
|
+
- **search_nodes(label, property_name, property_value)**: Find resources by type and properties
|
|
1259
|
+
- **search_by_text(property_name, search_text)**: Text search across the graph
|
|
1260
|
+
- **get_node(node_id)**: Get detailed information about a specific resource
|
|
1261
|
+
|
|
1262
|
+
**Best Practices**:
|
|
1263
|
+
- Store user preferences, task decisions, and important context as you learn them
|
|
1264
|
+
- Use recall_memory at the start of tasks to check for relevant past context
|
|
1265
|
+
- Search the graph to discover infrastructure, services, and relationships
|
|
1266
|
+
- Add descriptive metadata when storing memories for better retrieval"""
|
|
1267
|
+
|
|
1268
|
+
if instructions:
|
|
1269
|
+
instructions = f"{instructions}\n{memory_guidance}"
|
|
1270
|
+
else:
|
|
1271
|
+
instructions = memory_guidance.strip()
|
|
1272
|
+
|
|
1273
|
+
# Get context graph API URL from settings for memory tools
|
|
1274
|
+
from control_plane_api.app.config import settings
|
|
1275
|
+
graph_api_url = settings.context_graph_api_base
|
|
1276
|
+
dataset_name = environment_names[0] if environment_names else "default"
|
|
1277
|
+
|
|
1278
|
+
# Inject memory dataset name into env vars
|
|
1279
|
+
# Worker skills will fetch graph URL from control plane's /api/v1/client/config endpoint
|
|
1280
|
+
resolved_env_vars["MEMORY_DATASET_NAME"] = dataset_name
|
|
1281
|
+
|
|
1282
|
+
logger.info(
|
|
1283
|
+
"team_execution_environment_resolved",
|
|
1284
|
+
team_id=team_id[:8],
|
|
1285
|
+
env_var_count=len(resolved_env_vars),
|
|
1286
|
+
mcp_server_count=len(resolved_config.get("mcp_servers", {})),
|
|
1287
|
+
secrets_count=len(resolved_secrets),
|
|
1288
|
+
integrations_count=len(resolved_integrations),
|
|
1289
|
+
graph_api_url=graph_api_url,
|
|
1290
|
+
dataset_name=dataset_name,
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
return {
|
|
1294
|
+
"env_vars": resolved_env_vars,
|
|
1295
|
+
"mcp_servers": resolved_config.get("mcp_servers", {}),
|
|
1296
|
+
"instructions": instructions,
|
|
1297
|
+
"description": resolved_config.get("description"),
|
|
1298
|
+
"configuration": resolved_config.get("configuration", {}),
|
|
1299
|
+
# Context graph configuration for memory tools
|
|
1300
|
+
"graph_api_url": graph_api_url,
|
|
1301
|
+
"dataset_name": dataset_name,
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
except ExecutionEnvironmentResolutionError:
|
|
1305
|
+
raise
|
|
1306
|
+
except Exception as e:
|
|
1307
|
+
logger.error(
|
|
1308
|
+
"team_execution_environment_resolution_error",
|
|
1309
|
+
team_id=team_id[:8],
|
|
1310
|
+
error=str(e),
|
|
1311
|
+
exc_info=True,
|
|
1312
|
+
)
|
|
1313
|
+
raise ExecutionEnvironmentResolutionError(
|
|
1314
|
+
f"Failed to resolve execution environment for team {team_id}: {str(e)}"
|
|
1315
|
+
)
|