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,1167 @@
|
|
|
1
|
+
"""Runtime-based execution activities for Temporal workflows.
|
|
2
|
+
|
|
3
|
+
This module provides activities that use the RuntimeFactory/RuntimeRegistry system
|
|
4
|
+
for agent execution, supporting multiple runtimes (Agno/Default, Claude Code, etc.)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional, List, Dict, Any
|
|
9
|
+
from temporalio import activity
|
|
10
|
+
from temporalio.exceptions import ApplicationError
|
|
11
|
+
import structlog
|
|
12
|
+
import os
|
|
13
|
+
import asyncio
|
|
14
|
+
import time
|
|
15
|
+
import httpx
|
|
16
|
+
from types import GeneratorType
|
|
17
|
+
|
|
18
|
+
from control_plane_api.worker.runtimes.base import (
|
|
19
|
+
RuntimeType,
|
|
20
|
+
RuntimeExecutionContext,
|
|
21
|
+
RuntimeExecutionResult,
|
|
22
|
+
)
|
|
23
|
+
from control_plane_api.worker.runtimes.factory import RuntimeFactory
|
|
24
|
+
from control_plane_api.worker.control_plane_client import get_control_plane_client
|
|
25
|
+
from control_plane_api.worker.services.cancellation_manager import CancellationManager
|
|
26
|
+
from control_plane_api.worker.services.runtime_analytics import submit_runtime_analytics
|
|
27
|
+
from control_plane_api.worker.services.analytics_service import AnalyticsService
|
|
28
|
+
from control_plane_api.worker.utils.logging_config import sanitize_value
|
|
29
|
+
|
|
30
|
+
logger = structlog.get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def serialize_tool_output(output: Any, max_length: int = 10000) -> Optional[str]:
|
|
34
|
+
"""
|
|
35
|
+
Safely serialize tool output for JSON encoding.
|
|
36
|
+
|
|
37
|
+
Handles:
|
|
38
|
+
- Generator objects (consumes and converts to string)
|
|
39
|
+
- Large strings (truncates with indication)
|
|
40
|
+
- None values
|
|
41
|
+
- Other types (converts to string)
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
output: Tool output to serialize
|
|
45
|
+
max_length: Maximum length for output string (default 10000)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Serialized string or None
|
|
49
|
+
"""
|
|
50
|
+
if output is None:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Check if it's a generator - consume it first
|
|
55
|
+
if isinstance(output, GeneratorType):
|
|
56
|
+
# Consume generator and join results
|
|
57
|
+
output = ''.join(str(item) for item in output)
|
|
58
|
+
|
|
59
|
+
# Convert to string
|
|
60
|
+
output_str = str(output)
|
|
61
|
+
|
|
62
|
+
# Truncate if too long
|
|
63
|
+
if len(output_str) > max_length:
|
|
64
|
+
return output_str[:max_length] + f"\n... (truncated, {len(output_str) - max_length} chars omitted)"
|
|
65
|
+
|
|
66
|
+
return output_str
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.warning("failed_to_serialize_tool_output", error=str(e))
|
|
70
|
+
return f"<Failed to serialize output: {str(e)}>"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def inject_env_vars_into_mcp_servers(
|
|
74
|
+
mcp_servers: Dict[str, Any],
|
|
75
|
+
agent_config: Optional[Dict[str, Any]] = None,
|
|
76
|
+
runtime_config: Optional[Dict[str, Any]] = None,
|
|
77
|
+
control_plane_client: Optional[Any] = None,
|
|
78
|
+
agent_id: Optional[str] = None,
|
|
79
|
+
team_id: Optional[str] = None,
|
|
80
|
+
) -> Dict[str, Any]:
|
|
81
|
+
"""
|
|
82
|
+
Inject environment variables into MCP server configurations (runtime-agnostic).
|
|
83
|
+
|
|
84
|
+
This ensures MCP servers have access to critical environment variables like:
|
|
85
|
+
- KUBIYA_API_KEY: For API authentication
|
|
86
|
+
- KUBIYA_API_BASE: For API base URL
|
|
87
|
+
- Agent/team-specific environment variables from agent_config
|
|
88
|
+
- Resolved environment (inherited vars, decrypted secrets, integration tokens)
|
|
89
|
+
|
|
90
|
+
This function is runtime-agnostic and can be used by any runtime (Default, Claude Code, etc.)
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
mcp_servers: Dictionary of MCP server configurations
|
|
94
|
+
agent_config: Optional agent configuration with env_vars
|
|
95
|
+
runtime_config: Optional runtime configuration with env vars
|
|
96
|
+
control_plane_client: Optional Control Plane API client for fetching resolved environment
|
|
97
|
+
agent_id: Optional agent ID for fetching agent-specific resolved environment
|
|
98
|
+
team_id: Optional team ID for fetching team-specific resolved environment
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Modified MCP server configurations with injected env vars
|
|
102
|
+
"""
|
|
103
|
+
if not mcp_servers:
|
|
104
|
+
return mcp_servers
|
|
105
|
+
|
|
106
|
+
# Collect environment variables to inject
|
|
107
|
+
env_vars_to_inject = {}
|
|
108
|
+
|
|
109
|
+
# Add Kubiya API credentials from OS environment
|
|
110
|
+
kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
|
|
111
|
+
if kubiya_api_key:
|
|
112
|
+
env_vars_to_inject["KUBIYA_API_KEY"] = kubiya_api_key
|
|
113
|
+
|
|
114
|
+
kubiya_api_base = os.environ.get("KUBIYA_API_BASE")
|
|
115
|
+
if kubiya_api_base:
|
|
116
|
+
env_vars_to_inject["KUBIYA_API_BASE"] = kubiya_api_base
|
|
117
|
+
|
|
118
|
+
# Layer 2: Fetch RESOLVED environment from Control Plane (includes inherited vars, secrets, tokens)
|
|
119
|
+
if control_plane_client and (agent_id or team_id):
|
|
120
|
+
try:
|
|
121
|
+
resolved_env = {}
|
|
122
|
+
if agent_id:
|
|
123
|
+
logger.info("fetching_resolved_agent_environment", agent_id=agent_id[:8])
|
|
124
|
+
resolved_env = control_plane_client.get_agent_execution_environment(agent_id)
|
|
125
|
+
elif team_id:
|
|
126
|
+
logger.info("fetching_resolved_team_environment", team_id=team_id[:8])
|
|
127
|
+
resolved_env = control_plane_client.get_team_execution_environment(team_id)
|
|
128
|
+
|
|
129
|
+
if resolved_env:
|
|
130
|
+
logger.info(
|
|
131
|
+
"resolved_environment_fetched",
|
|
132
|
+
entity_type="agent" if agent_id else "team",
|
|
133
|
+
entity_id=(agent_id or team_id)[:8],
|
|
134
|
+
env_var_count=len(resolved_env),
|
|
135
|
+
env_var_keys=list(resolved_env.keys()),
|
|
136
|
+
)
|
|
137
|
+
env_vars_to_inject.update(resolved_env)
|
|
138
|
+
else:
|
|
139
|
+
logger.warning("resolved_environment_empty", entity_id=(agent_id or team_id)[:8])
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(
|
|
142
|
+
"resolved_environment_fetch_failed",
|
|
143
|
+
entity_id=(agent_id or team_id)[:8] if (agent_id or team_id) else "unknown",
|
|
144
|
+
error=str(e),
|
|
145
|
+
error_type=type(e).__name__,
|
|
146
|
+
fallback_behavior="continuing_with_partial_environment",
|
|
147
|
+
exc_info=True,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Add any env vars from agent_config
|
|
151
|
+
if agent_config:
|
|
152
|
+
agent_env_vars = agent_config.get("env_vars", {})
|
|
153
|
+
if agent_env_vars and isinstance(agent_env_vars, dict):
|
|
154
|
+
env_vars_to_inject.update(agent_env_vars)
|
|
155
|
+
elif agent_env_vars:
|
|
156
|
+
logger.warning("agent_config.env_vars is not a dict, skipping", type=type(agent_env_vars).__name__)
|
|
157
|
+
|
|
158
|
+
# Also check runtime_config for env vars
|
|
159
|
+
if runtime_config:
|
|
160
|
+
runtime_env_vars = runtime_config.get("env", {})
|
|
161
|
+
if runtime_env_vars and isinstance(runtime_env_vars, dict):
|
|
162
|
+
env_vars_to_inject.update(runtime_env_vars)
|
|
163
|
+
elif runtime_env_vars:
|
|
164
|
+
logger.warning("runtime_config.env is not a dict, skipping", type=type(runtime_env_vars).__name__)
|
|
165
|
+
|
|
166
|
+
# ALSO inject collected env vars into runtime_config.env for SDK usage
|
|
167
|
+
# This ensures runtime SDK (Claude Code, Agno) has access to resolved environment
|
|
168
|
+
if runtime_config is not None:
|
|
169
|
+
if "env" not in runtime_config:
|
|
170
|
+
runtime_config["env"] = {}
|
|
171
|
+
# Merge all collected env vars into runtime_config.env (in-place mutation)
|
|
172
|
+
# Note: This happens after we've collected from runtime_config, so we're merging
|
|
173
|
+
# OS env + resolved env + agent_config env + runtime_config env back into runtime_config
|
|
174
|
+
runtime_config["env"].update(env_vars_to_inject)
|
|
175
|
+
logger.debug("updated_runtime_config_env", total_env_count=len(runtime_config["env"]))
|
|
176
|
+
|
|
177
|
+
if not env_vars_to_inject:
|
|
178
|
+
logger.debug("No environment variables to inject into MCP servers")
|
|
179
|
+
return mcp_servers
|
|
180
|
+
|
|
181
|
+
logger.info(
|
|
182
|
+
"Injecting environment variables into MCP servers",
|
|
183
|
+
server_count=len(mcp_servers),
|
|
184
|
+
env_var_keys=list(env_vars_to_inject.keys()),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def resolve_template_string(value: str, env_vars: Dict[str, str]) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Resolve template variables in a string.
|
|
190
|
+
Replaces {{VAR_NAME}} with the value from env_vars.
|
|
191
|
+
"""
|
|
192
|
+
import re
|
|
193
|
+
result = value
|
|
194
|
+
for var_name, var_value in env_vars.items():
|
|
195
|
+
# Match {{VAR_NAME}} patterns
|
|
196
|
+
pattern = r'\{\{' + re.escape(var_name) + r'\}\}'
|
|
197
|
+
result = re.sub(pattern, var_value, result)
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def resolve_templates_in_dict(data: Any, env_vars: Dict[str, str]) -> Any:
|
|
201
|
+
"""
|
|
202
|
+
Recursively resolve template variables in dictionaries, lists, and strings.
|
|
203
|
+
"""
|
|
204
|
+
if isinstance(data, dict):
|
|
205
|
+
return {k: resolve_templates_in_dict(v, env_vars) for k, v in data.items()}
|
|
206
|
+
elif isinstance(data, list):
|
|
207
|
+
return [resolve_templates_in_dict(item, env_vars) for item in data]
|
|
208
|
+
elif isinstance(data, str):
|
|
209
|
+
return resolve_template_string(data, env_vars)
|
|
210
|
+
else:
|
|
211
|
+
return data
|
|
212
|
+
|
|
213
|
+
# Inject env vars into each MCP server
|
|
214
|
+
modified_servers = {}
|
|
215
|
+
for server_name, server_config in mcp_servers.items():
|
|
216
|
+
try:
|
|
217
|
+
# Handle different MCP server configuration formats
|
|
218
|
+
if hasattr(server_config, 'env'):
|
|
219
|
+
# StdioServerParameters or similar object with env attribute
|
|
220
|
+
if server_config.env is None:
|
|
221
|
+
server_config.env = {}
|
|
222
|
+
# Merge env vars (don't override existing ones from server config)
|
|
223
|
+
server_config.env = {**env_vars_to_inject, **server_config.env}
|
|
224
|
+
logger.debug(
|
|
225
|
+
f"Injected env vars into MCP server '{server_name}' (object with env attribute)",
|
|
226
|
+
env_count=len(server_config.env),
|
|
227
|
+
)
|
|
228
|
+
elif isinstance(server_config, dict):
|
|
229
|
+
# Dictionary-based configuration
|
|
230
|
+
# First, resolve template variables in the entire config
|
|
231
|
+
server_config = resolve_templates_in_dict(server_config, env_vars_to_inject)
|
|
232
|
+
|
|
233
|
+
# Then add env vars to the env field
|
|
234
|
+
if 'env' not in server_config:
|
|
235
|
+
server_config['env'] = {}
|
|
236
|
+
# Merge env vars (don't override existing ones from server config)
|
|
237
|
+
server_config['env'] = {**env_vars_to_inject, **server_config['env']}
|
|
238
|
+
logger.debug(
|
|
239
|
+
f"Injected env vars and resolved templates in MCP server '{server_name}' (dict config)",
|
|
240
|
+
env_count=len(server_config['env']),
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
# Unknown format - try to set env attribute directly
|
|
244
|
+
try:
|
|
245
|
+
if not hasattr(server_config, 'env'):
|
|
246
|
+
setattr(server_config, 'env', {})
|
|
247
|
+
server_config.env = {**env_vars_to_inject, **getattr(server_config, 'env', {})}
|
|
248
|
+
logger.debug(
|
|
249
|
+
f"Injected env vars into MCP server '{server_name}' (setattr)",
|
|
250
|
+
env_count=len(server_config.env),
|
|
251
|
+
)
|
|
252
|
+
except Exception as attr_error:
|
|
253
|
+
logger.warning(
|
|
254
|
+
f"Could not inject env vars into MCP server '{server_name}' - unsupported format",
|
|
255
|
+
server_type=type(server_config).__name__,
|
|
256
|
+
error=str(attr_error),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
modified_servers[server_name] = server_config
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.error(
|
|
263
|
+
f"Error injecting env vars into MCP server '{server_name}'",
|
|
264
|
+
error=str(e),
|
|
265
|
+
exc_info=True,
|
|
266
|
+
)
|
|
267
|
+
# Keep original server config if injection fails
|
|
268
|
+
modified_servers[server_name] = server_config
|
|
269
|
+
|
|
270
|
+
logger.info(
|
|
271
|
+
"✅ Environment variables injected into MCP servers",
|
|
272
|
+
server_count=len(modified_servers),
|
|
273
|
+
env_vars_injected=list(env_vars_to_inject.keys()),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return modified_servers
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass
|
|
280
|
+
class ActivityRuntimeExecuteInput:
|
|
281
|
+
"""Input for runtime-based execution activity"""
|
|
282
|
+
execution_id: str
|
|
283
|
+
agent_id: str
|
|
284
|
+
organization_id: str
|
|
285
|
+
prompt: str
|
|
286
|
+
runtime_type: str = "default" # "default", "claude_code", etc.
|
|
287
|
+
system_prompt: Optional[str] = None
|
|
288
|
+
model_id: Optional[str] = None
|
|
289
|
+
model_config: Optional[Dict[str, Any]] = None
|
|
290
|
+
agent_config: Optional[Dict[str, Any]] = None
|
|
291
|
+
skills: Optional[List[Dict[str, Any]]] = None
|
|
292
|
+
mcp_servers: Optional[Dict[str, Any]] = None
|
|
293
|
+
conversation_history: Optional[List[Dict[str, Any]]] = None
|
|
294
|
+
user_metadata: Optional[Dict[str, Any]] = None
|
|
295
|
+
runtime_config: Optional[Dict[str, Any]] = None
|
|
296
|
+
stream: bool = False
|
|
297
|
+
conversation_turn: int = 1 # Track turn number for analytics
|
|
298
|
+
user_message_id: Optional[str] = None # Message ID from workflow signal for deduplication
|
|
299
|
+
user_id: Optional[str] = None # User who sent the message
|
|
300
|
+
user_name: Optional[str] = None
|
|
301
|
+
user_email: Optional[str] = None
|
|
302
|
+
user_avatar: Optional[str] = None
|
|
303
|
+
# Enforcement context fields
|
|
304
|
+
user_roles: Optional[List[str]] = None
|
|
305
|
+
team_id: Optional[str] = None
|
|
306
|
+
team_name: Optional[str] = None
|
|
307
|
+
environment: str = "production"
|
|
308
|
+
# NEW: Session ID for client pooling (enables client reuse across followups)
|
|
309
|
+
session_id: Optional[str] = None
|
|
310
|
+
|
|
311
|
+
def __post_init__(self):
|
|
312
|
+
if self.model_config is None:
|
|
313
|
+
self.model_config = {}
|
|
314
|
+
if self.agent_config is None:
|
|
315
|
+
self.agent_config = {}
|
|
316
|
+
if self.skills is None:
|
|
317
|
+
self.skills = []
|
|
318
|
+
if self.mcp_servers is None:
|
|
319
|
+
self.mcp_servers = {}
|
|
320
|
+
if self.conversation_history is None:
|
|
321
|
+
self.conversation_history = []
|
|
322
|
+
if self.user_metadata is None:
|
|
323
|
+
self.user_metadata = {}
|
|
324
|
+
if self.runtime_config is None:
|
|
325
|
+
self.runtime_config = {}
|
|
326
|
+
if self.user_roles is None:
|
|
327
|
+
self.user_roles = []
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@dataclass
|
|
331
|
+
class PublishUserMessageInput:
|
|
332
|
+
"""Input for publishing user message to stream"""
|
|
333
|
+
execution_id: str
|
|
334
|
+
prompt: str
|
|
335
|
+
timestamp: str
|
|
336
|
+
message_id: Optional[str] = None
|
|
337
|
+
user_id: Optional[str] = None
|
|
338
|
+
user_name: Optional[str] = None
|
|
339
|
+
user_email: Optional[str] = None
|
|
340
|
+
user_avatar: Optional[str] = None
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@activity.defn
|
|
344
|
+
async def publish_user_message(input: PublishUserMessageInput) -> Dict[str, Any]:
|
|
345
|
+
"""
|
|
346
|
+
Publish user message to SSE stream immediately.
|
|
347
|
+
|
|
348
|
+
This ensures the user message appears in chronological order in the UI,
|
|
349
|
+
before the assistant response starts streaming.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
input: User message details to publish
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Dict with success status
|
|
356
|
+
"""
|
|
357
|
+
activity.logger.info(
|
|
358
|
+
"Publishing user message to stream",
|
|
359
|
+
extra={
|
|
360
|
+
"execution_id": input.execution_id,
|
|
361
|
+
"message_id": input.message_id,
|
|
362
|
+
"has_user_metadata": bool(input.user_id),
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
# Get Control Plane client
|
|
368
|
+
control_plane = get_control_plane_client()
|
|
369
|
+
|
|
370
|
+
# Initialize event bus (Redis) for real-time streaming
|
|
371
|
+
await control_plane.initialize_event_bus()
|
|
372
|
+
|
|
373
|
+
# Publish user message event
|
|
374
|
+
control_plane.publish_event(
|
|
375
|
+
execution_id=input.execution_id,
|
|
376
|
+
event_type="message",
|
|
377
|
+
data={
|
|
378
|
+
"role": "user",
|
|
379
|
+
"content": input.prompt,
|
|
380
|
+
"timestamp": input.timestamp,
|
|
381
|
+
"message_id": input.message_id,
|
|
382
|
+
"user_id": input.user_id,
|
|
383
|
+
"user_name": input.user_name,
|
|
384
|
+
"user_email": input.user_email,
|
|
385
|
+
"user_avatar": input.user_avatar,
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
activity.logger.info(
|
|
390
|
+
"✅ User message published to stream",
|
|
391
|
+
extra={
|
|
392
|
+
"execution_id": input.execution_id,
|
|
393
|
+
"message_id": input.message_id,
|
|
394
|
+
}
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
return {"success": True, "message_id": input.message_id}
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
error_msg = str(e) or repr(e) or "Unknown error publishing user message"
|
|
401
|
+
activity.logger.error(
|
|
402
|
+
"Failed to publish user message",
|
|
403
|
+
extra={
|
|
404
|
+
"execution_id": input.execution_id,
|
|
405
|
+
"error": error_msg,
|
|
406
|
+
"error_type": type(e).__name__,
|
|
407
|
+
},
|
|
408
|
+
exc_info=True,
|
|
409
|
+
)
|
|
410
|
+
# Don't fail the workflow if publishing fails - this is non-critical
|
|
411
|
+
return {"success": False, "error": error_msg}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@activity.defn
|
|
415
|
+
async def execute_with_runtime(input: ActivityRuntimeExecuteInput) -> Dict[str, Any]:
|
|
416
|
+
"""
|
|
417
|
+
Execute agent using the RuntimeFactory/RuntimeRegistry system.
|
|
418
|
+
|
|
419
|
+
This activity:
|
|
420
|
+
1. Creates a runtime based on runtime_type (default, claude_code, etc.)
|
|
421
|
+
2. Builds execution context
|
|
422
|
+
3. Executes (streaming or non-streaming)
|
|
423
|
+
4. Returns results
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
input: Activity input with execution details and runtime_type
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Dict with response, usage, success flag, etc.
|
|
430
|
+
"""
|
|
431
|
+
logger.info(
|
|
432
|
+
"runtime_execution_initializing",
|
|
433
|
+
execution_id=input.execution_id,
|
|
434
|
+
agent_id=input.agent_id,
|
|
435
|
+
organization=input.organization_id,
|
|
436
|
+
runtime_type=input.runtime_type,
|
|
437
|
+
model=input.model_id or 'default',
|
|
438
|
+
stream=input.stream,
|
|
439
|
+
skills_count=len(input.skills),
|
|
440
|
+
mcp_servers_count=len(input.mcp_servers),
|
|
441
|
+
prompt_preview=input.prompt[:100] + "..." if len(input.prompt) > 100 else input.prompt
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
activity.logger.info(
|
|
445
|
+
"Executing with Runtime system",
|
|
446
|
+
extra={
|
|
447
|
+
"execution_id": input.execution_id,
|
|
448
|
+
"agent_id": input.agent_id,
|
|
449
|
+
"organization_id": input.organization_id,
|
|
450
|
+
"runtime_type": input.runtime_type,
|
|
451
|
+
"model_id": input.model_id,
|
|
452
|
+
"stream": input.stream,
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
# Track execution start time for analytics
|
|
458
|
+
turn_start_time = time.time()
|
|
459
|
+
|
|
460
|
+
# Get Control Plane client and cancellation manager
|
|
461
|
+
control_plane = get_control_plane_client()
|
|
462
|
+
|
|
463
|
+
# Initialize event bus (Redis) for real-time streaming
|
|
464
|
+
# This must be called in async context to establish connections
|
|
465
|
+
await control_plane.initialize_event_bus()
|
|
466
|
+
|
|
467
|
+
cancellation_manager = CancellationManager()
|
|
468
|
+
|
|
469
|
+
# STEP 0: Resolve execution environment (secrets, integrations, env vars)
|
|
470
|
+
# Call Control Plane API to get resolved execution environment
|
|
471
|
+
logger.info("resolving_execution_environment", agent_id=input.agent_id)
|
|
472
|
+
resolved_env_vars = {}
|
|
473
|
+
resolved_mcp_servers = {}
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
# Get Kubiya API token from environment
|
|
477
|
+
kubiya_token = os.environ.get("KUBIYA_API_KEY")
|
|
478
|
+
if not kubiya_token:
|
|
479
|
+
raise ValueError("KUBIYA_API_KEY environment variable not set")
|
|
480
|
+
|
|
481
|
+
# Get Control Plane URL
|
|
482
|
+
control_plane_url = os.environ.get("CONTROL_PLANE_URL", "https://control-plane.kubiya.ai")
|
|
483
|
+
|
|
484
|
+
# Call Control Plane API to resolve execution environment
|
|
485
|
+
api_url = f"{control_plane_url}/api/v1/execution-environment/agents/{input.agent_id}/resolved/full"
|
|
486
|
+
|
|
487
|
+
logger.debug("control_plane_api_call", api_url=api_url)
|
|
488
|
+
|
|
489
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
490
|
+
response = await client.get(
|
|
491
|
+
api_url,
|
|
492
|
+
headers={
|
|
493
|
+
"Authorization": f"UserKey {kubiya_token}",
|
|
494
|
+
"Accept": "application/json",
|
|
495
|
+
}
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
if response.status_code == 200:
|
|
499
|
+
resolved_env = response.json()
|
|
500
|
+
resolved_env_vars = resolved_env.get("env_vars", {})
|
|
501
|
+
resolved_mcp_servers = resolved_env.get("mcp_servers", {})
|
|
502
|
+
|
|
503
|
+
logger.info(
|
|
504
|
+
"execution_environment_resolved",
|
|
505
|
+
env_var_count=len(resolved_env_vars),
|
|
506
|
+
mcp_server_count=len(resolved_mcp_servers)
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
logger.debug(
|
|
510
|
+
"resolved_env_var_keys",
|
|
511
|
+
env_var_keys=list(resolved_env_vars.keys())
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
logger.debug(
|
|
515
|
+
"resolved_mcp_server_names",
|
|
516
|
+
mcp_server_names=list(resolved_mcp_servers.keys())
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# Log detailed env var info at DEBUG level with sanitization
|
|
520
|
+
for key, value in resolved_env_vars.items():
|
|
521
|
+
logger.debug(
|
|
522
|
+
"env_var_detail",
|
|
523
|
+
key=key,
|
|
524
|
+
value=sanitize_value(key, value),
|
|
525
|
+
value_length=len(str(value))
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
activity.logger.info(
|
|
529
|
+
"execution_environment_resolved_from_api",
|
|
530
|
+
extra={
|
|
531
|
+
"execution_id": input.execution_id,
|
|
532
|
+
"agent_id": input.agent_id,
|
|
533
|
+
"env_var_count": len(resolved_env_vars),
|
|
534
|
+
"mcp_server_count": len(resolved_mcp_servers),
|
|
535
|
+
}
|
|
536
|
+
)
|
|
537
|
+
else:
|
|
538
|
+
logger.warning(
|
|
539
|
+
"execution_environment_api_error",
|
|
540
|
+
status_code=response.status_code,
|
|
541
|
+
error_preview=response.text[:200]
|
|
542
|
+
)
|
|
543
|
+
activity.logger.warning(
|
|
544
|
+
"execution_environment_api_error",
|
|
545
|
+
extra={
|
|
546
|
+
"execution_id": input.execution_id,
|
|
547
|
+
"status_code": response.status_code,
|
|
548
|
+
"error": response.text[:500],
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
except Exception as e:
|
|
553
|
+
logger.error("execution_environment_resolution_error", error=str(e))
|
|
554
|
+
activity.logger.error(
|
|
555
|
+
"execution_environment_resolution_error",
|
|
556
|
+
extra={
|
|
557
|
+
"execution_id": input.execution_id,
|
|
558
|
+
"agent_id": input.agent_id,
|
|
559
|
+
"error": str(e),
|
|
560
|
+
},
|
|
561
|
+
exc_info=True,
|
|
562
|
+
)
|
|
563
|
+
# Continue with empty env vars - don't fail execution
|
|
564
|
+
|
|
565
|
+
# CRITICAL: Inject resolved env vars into os.environ so skills can access them
|
|
566
|
+
# This is needed because Python tools (via agno) use exec() which reads from os.environ
|
|
567
|
+
# Store original values to restore later (to avoid polluting the worker's environment)
|
|
568
|
+
original_env_values = {}
|
|
569
|
+
for key, value in resolved_env_vars.items():
|
|
570
|
+
if key in os.environ:
|
|
571
|
+
original_env_values[key] = os.environ[key]
|
|
572
|
+
os.environ[key] = str(value)
|
|
573
|
+
logger.debug(
|
|
574
|
+
"injected_env_var_to_os_environ",
|
|
575
|
+
key=key,
|
|
576
|
+
value_length=len(str(value)),
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if resolved_env_vars:
|
|
580
|
+
logger.info(
|
|
581
|
+
"env_vars_injected_to_process",
|
|
582
|
+
count=len(resolved_env_vars),
|
|
583
|
+
keys=list(resolved_env_vars.keys()),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Initialize analytics service for submission
|
|
587
|
+
analytics_service = AnalyticsService(
|
|
588
|
+
control_plane_url=control_plane.base_url if hasattr(control_plane, 'base_url') else "http://localhost:8000",
|
|
589
|
+
api_key=os.environ.get("KUBIYA_API_KEY", ""),
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
# Parse runtime type
|
|
593
|
+
try:
|
|
594
|
+
runtime_type_enum = RuntimeType(input.runtime_type)
|
|
595
|
+
except ValueError:
|
|
596
|
+
logger.error(f"Invalid runtime_type: {input.runtime_type}, falling back to DEFAULT")
|
|
597
|
+
runtime_type_enum = RuntimeType.DEFAULT
|
|
598
|
+
|
|
599
|
+
# Create runtime using factory
|
|
600
|
+
factory = RuntimeFactory()
|
|
601
|
+
runtime = factory.create_runtime(
|
|
602
|
+
runtime_type=runtime_type_enum,
|
|
603
|
+
control_plane_client=control_plane,
|
|
604
|
+
cancellation_manager=cancellation_manager,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
logger.info(
|
|
608
|
+
f"Created runtime",
|
|
609
|
+
extra={
|
|
610
|
+
"runtime_type": runtime_type_enum,
|
|
611
|
+
"runtime_class": runtime.__class__.__name__,
|
|
612
|
+
"capabilities": runtime.get_capabilities(),
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# Fetch and instantiate skills if runtime supports tools
|
|
617
|
+
skills = input.skills or []
|
|
618
|
+
if runtime.supports_tools():
|
|
619
|
+
logger.info("fetching_skills_from_control_plane", agent_id=input.agent_id)
|
|
620
|
+
try:
|
|
621
|
+
skill_configs = control_plane.get_skills(input.agent_id)
|
|
622
|
+
if skill_configs:
|
|
623
|
+
logger.info(
|
|
624
|
+
"skills_resolved",
|
|
625
|
+
skill_count=len(skill_configs),
|
|
626
|
+
types=[t.get('type') for t in skill_configs],
|
|
627
|
+
names=[t.get('name') for t in skill_configs],
|
|
628
|
+
enabled=[t.get('enabled', True) for t in skill_configs]
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# DEBUG: Show full config for workflow_executor skills
|
|
632
|
+
for cfg in skill_configs:
|
|
633
|
+
if cfg.get('type') in ['workflow_executor', 'workflow']:
|
|
634
|
+
logger.debug(
|
|
635
|
+
"workflow_executor_skill_config",
|
|
636
|
+
name=cfg.get('name'),
|
|
637
|
+
type=cfg.get('type'),
|
|
638
|
+
enabled=cfg.get('enabled', True),
|
|
639
|
+
config_keys=list(cfg.get('configuration', {}).keys())
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Import here to avoid circular dependency
|
|
643
|
+
from control_plane_api.worker.services.skill_factory import SkillFactory
|
|
644
|
+
|
|
645
|
+
logger.debug(
|
|
646
|
+
"before_skill_factory",
|
|
647
|
+
execution_id=input.execution_id,
|
|
648
|
+
execution_id_type=type(input.execution_id).__name__,
|
|
649
|
+
execution_id_bool=bool(input.execution_id),
|
|
650
|
+
skill_configs_count=len(skill_configs)
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
# Always include built-in context_graph_search skill
|
|
654
|
+
builtin_skill_types = {'context_graph_search'}
|
|
655
|
+
existing_skill_types = {cfg.get('type') for cfg in skill_configs}
|
|
656
|
+
|
|
657
|
+
for builtin_type in builtin_skill_types:
|
|
658
|
+
if builtin_type not in existing_skill_types:
|
|
659
|
+
builtin_config = {
|
|
660
|
+
'name': builtin_type,
|
|
661
|
+
'type': builtin_type,
|
|
662
|
+
'enabled': True,
|
|
663
|
+
'configuration': {}
|
|
664
|
+
}
|
|
665
|
+
skill_configs.append(builtin_config)
|
|
666
|
+
logger.info("auto_included_builtin_skill", skill_type=builtin_type)
|
|
667
|
+
|
|
668
|
+
# Determine runtime type from agent config or input
|
|
669
|
+
runtime_type = (input.agent_config or {}).get("runtime", input.runtime_type or "agno")
|
|
670
|
+
|
|
671
|
+
# Instantiate skills for all runtimes
|
|
672
|
+
# For Claude Code: custom skills will be converted to MCP servers by build_mcp_servers()
|
|
673
|
+
# For other runtimes: skills are used directly
|
|
674
|
+
skill_factory = SkillFactory(runtime_type=runtime_type)
|
|
675
|
+
skill_factory.initialize()
|
|
676
|
+
|
|
677
|
+
skills = skill_factory.create_skills_from_list(
|
|
678
|
+
skill_configs,
|
|
679
|
+
execution_id=input.execution_id # Pass execution_id for control plane streaming
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
if skills:
|
|
683
|
+
skill_types = [type(s).__name__ for s in skills]
|
|
684
|
+
logger.info(
|
|
685
|
+
"skills_instantiated",
|
|
686
|
+
skill_count=len(skills),
|
|
687
|
+
runtime_type=runtime_type,
|
|
688
|
+
skill_classes=skill_types
|
|
689
|
+
)
|
|
690
|
+
else:
|
|
691
|
+
logger.warning("no_skills_instantiated", runtime_type=runtime_type)
|
|
692
|
+
else:
|
|
693
|
+
logger.warning("no_skills_found", message="Using built-in skills only")
|
|
694
|
+
|
|
695
|
+
# Still include built-in skills even when no skills configured
|
|
696
|
+
from control_plane_api.worker.services.skill_factory import SkillFactory
|
|
697
|
+
|
|
698
|
+
builtin_skill_configs = [
|
|
699
|
+
{
|
|
700
|
+
'name': 'context_graph_search',
|
|
701
|
+
'type': 'context_graph_search',
|
|
702
|
+
'enabled': True,
|
|
703
|
+
'configuration': {}
|
|
704
|
+
}
|
|
705
|
+
]
|
|
706
|
+
|
|
707
|
+
runtime_type = (input.agent_config or {}).get("runtime", input.runtime_type or "agno")
|
|
708
|
+
|
|
709
|
+
# Instantiate builtin skills for ALL runtimes (including Claude Code)
|
|
710
|
+
# For Claude Code, these Toolkit objects will be converted to MCP servers by build_mcp_servers()
|
|
711
|
+
skill_factory = SkillFactory(runtime_type=runtime_type)
|
|
712
|
+
skill_factory.initialize()
|
|
713
|
+
skills = skill_factory.create_skills_from_list(
|
|
714
|
+
builtin_skill_configs,
|
|
715
|
+
execution_id=input.execution_id
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
if skills:
|
|
719
|
+
skill_types = [type(s).__name__ if hasattr(s, '__name__') else s.get('type', 'unknown') for s in skills]
|
|
720
|
+
logger.info(
|
|
721
|
+
"builtin_skills_instantiated",
|
|
722
|
+
skill_count=len(skills),
|
|
723
|
+
runtime_type=runtime_type,
|
|
724
|
+
skill_types=skill_types
|
|
725
|
+
)
|
|
726
|
+
except Exception as e:
|
|
727
|
+
logger.error("skill_fetch_error", error=str(e), exc_info=True)
|
|
728
|
+
|
|
729
|
+
# Merge MCP servers: resolved_mcp_servers (from DB with templates resolved) + input.mcp_servers (from workflow)
|
|
730
|
+
# Input MCP servers override resolved ones (allows runtime overrides)
|
|
731
|
+
merged_mcp_servers = {**resolved_mcp_servers, **(input.mcp_servers or {})}
|
|
732
|
+
|
|
733
|
+
logger.info(
|
|
734
|
+
"mcp_servers_merged",
|
|
735
|
+
from_execution_env=len(resolved_mcp_servers),
|
|
736
|
+
from_workflow_input=len(input.mcp_servers) if input.mcp_servers else 0,
|
|
737
|
+
total_merged=len(merged_mcp_servers),
|
|
738
|
+
server_names=list(merged_mcp_servers.keys()) if merged_mcp_servers else []
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
# Inject environment variables into MCP servers (runtime-agnostic)
|
|
742
|
+
# This ensures all MCP servers have access to KUBIYA_API_KEY, KUBIYA_API_BASE, etc.
|
|
743
|
+
# Also includes resolved_env_vars from execution environment (secrets, integrations)
|
|
744
|
+
agent_config_with_env = {
|
|
745
|
+
**(input.agent_config or {}),
|
|
746
|
+
"env_vars": {
|
|
747
|
+
**resolved_env_vars, # Include resolved secrets/integrations
|
|
748
|
+
**(input.agent_config or {}).get("env_vars", {}), # Override with explicit agent config
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
mcp_servers_with_env = inject_env_vars_into_mcp_servers(
|
|
753
|
+
mcp_servers=merged_mcp_servers,
|
|
754
|
+
agent_config=agent_config_with_env,
|
|
755
|
+
runtime_config=input.runtime_config,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
# Enrich user_metadata with additional fields for Langfuse tracking
|
|
759
|
+
enriched_user_metadata = dict(input.user_metadata or {})
|
|
760
|
+
|
|
761
|
+
# Add user_email if provided separately
|
|
762
|
+
if input.user_email and "user_email" not in enriched_user_metadata:
|
|
763
|
+
enriched_user_metadata["user_email"] = input.user_email
|
|
764
|
+
|
|
765
|
+
# Add user_id if provided separately
|
|
766
|
+
if input.user_id and "user_id" not in enriched_user_metadata:
|
|
767
|
+
enriched_user_metadata["user_id"] = input.user_id
|
|
768
|
+
|
|
769
|
+
# Add user_name if provided separately
|
|
770
|
+
if input.user_name and "user_name" not in enriched_user_metadata:
|
|
771
|
+
enriched_user_metadata["user_name"] = input.user_name
|
|
772
|
+
|
|
773
|
+
# Add session_id from runtime_config
|
|
774
|
+
if input.runtime_config and "session_id" in input.runtime_config:
|
|
775
|
+
enriched_user_metadata["session_id"] = input.runtime_config["session_id"]
|
|
776
|
+
elif "session_id" not in enriched_user_metadata:
|
|
777
|
+
# Default to execution_id for session tracking
|
|
778
|
+
enriched_user_metadata["session_id"] = input.execution_id
|
|
779
|
+
|
|
780
|
+
# Add agent_name if not already present (for generation_name in Langfuse)
|
|
781
|
+
if "agent_name" not in enriched_user_metadata:
|
|
782
|
+
# Try to get from agent_config or use agent_id
|
|
783
|
+
if input.agent_config and "name" in input.agent_config:
|
|
784
|
+
enriched_user_metadata["agent_name"] = input.agent_config["name"]
|
|
785
|
+
else:
|
|
786
|
+
enriched_user_metadata["agent_name"] = input.agent_id
|
|
787
|
+
|
|
788
|
+
logger.info(
|
|
789
|
+
"Enriched user_metadata for Langfuse tracking",
|
|
790
|
+
extra={
|
|
791
|
+
"execution_id": input.execution_id,
|
|
792
|
+
"has_user_email": "user_email" in enriched_user_metadata,
|
|
793
|
+
"has_session_id": "session_id" in enriched_user_metadata,
|
|
794
|
+
"has_agent_name": "agent_name" in enriched_user_metadata,
|
|
795
|
+
}
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# Build runtime config with resolved environment variables and session_id
|
|
799
|
+
# This includes secrets, integrations, custom env vars, and session_id for client pooling
|
|
800
|
+
runtime_config_with_env = {
|
|
801
|
+
**(input.runtime_config or {}),
|
|
802
|
+
"env": {
|
|
803
|
+
**resolved_env_vars, # Secrets, integrations, custom env vars
|
|
804
|
+
**(input.runtime_config or {}).get("env", {}), # Override with explicit runtime config
|
|
805
|
+
},
|
|
806
|
+
# NEW: Pass session_id for client pooling (enables reuse across followups)
|
|
807
|
+
"session_id": input.session_id or input.execution_id, # Fallback to execution_id
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
logger.info(
|
|
811
|
+
"runtime_config_session_id",
|
|
812
|
+
session_id=(input.session_id or input.execution_id)[:16],
|
|
813
|
+
execution_id=input.execution_id[:16],
|
|
814
|
+
is_reuse_enabled=bool(input.session_id),
|
|
815
|
+
note="Session ID enables client reuse for followup messages"
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
env_vars = runtime_config_with_env.get('env', {})
|
|
819
|
+
logger.info(
|
|
820
|
+
"environment_variables_passed_to_runtime",
|
|
821
|
+
total_env_vars=len(env_vars),
|
|
822
|
+
env_var_keys=list(env_vars.keys())
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
# Log detailed env var info at DEBUG level with sanitization
|
|
826
|
+
for key, value in env_vars.items():
|
|
827
|
+
logger.debug(
|
|
828
|
+
"runtime_env_var_detail",
|
|
829
|
+
key=key,
|
|
830
|
+
value=sanitize_value(key, str(value)),
|
|
831
|
+
value_length=len(str(value))
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
# Create execution workspace
|
|
835
|
+
from control_plane_api.worker.utils.workspace_manager import ensure_workspace
|
|
836
|
+
|
|
837
|
+
workspace_path = None
|
|
838
|
+
try:
|
|
839
|
+
workspace_path = ensure_workspace(input.execution_id)
|
|
840
|
+
|
|
841
|
+
logger.info(
|
|
842
|
+
"execution_workspace_created",
|
|
843
|
+
execution_id=input.execution_id[:8] if len(input.execution_id) >= 8 else input.execution_id,
|
|
844
|
+
path=str(workspace_path) if workspace_path else None,
|
|
845
|
+
)
|
|
846
|
+
except Exception as e:
|
|
847
|
+
logger.warning(
|
|
848
|
+
"execution_workspace_creation_failed",
|
|
849
|
+
execution_id=input.execution_id[:8] if len(input.execution_id) >= 8 else input.execution_id,
|
|
850
|
+
error=str(e),
|
|
851
|
+
error_type=type(e).__name__,
|
|
852
|
+
fallback="skills_and_runtime_will_use_defaults",
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
# Build execution context
|
|
856
|
+
context = RuntimeExecutionContext(
|
|
857
|
+
execution_id=input.execution_id,
|
|
858
|
+
agent_id=input.agent_id,
|
|
859
|
+
organization_id=input.organization_id,
|
|
860
|
+
prompt=input.prompt,
|
|
861
|
+
system_prompt=input.system_prompt,
|
|
862
|
+
conversation_history=input.conversation_history,
|
|
863
|
+
model_id=input.model_id,
|
|
864
|
+
model_config=input.model_config,
|
|
865
|
+
agent_config=input.agent_config,
|
|
866
|
+
skills=skills, # Use fetched skills
|
|
867
|
+
mcp_servers=mcp_servers_with_env, # Use MCP servers with injected env vars
|
|
868
|
+
user_metadata=enriched_user_metadata, # Use enriched metadata
|
|
869
|
+
runtime_config=runtime_config_with_env, # Include resolved env vars!
|
|
870
|
+
runtime_type=runtime_type_enum, # Runtime type for validation
|
|
871
|
+
# Enforcement context
|
|
872
|
+
user_email=input.user_email,
|
|
873
|
+
user_id=input.user_id,
|
|
874
|
+
user_roles=input.user_roles or [],
|
|
875
|
+
team_id=input.team_id,
|
|
876
|
+
team_name=input.team_name,
|
|
877
|
+
environment=input.environment,
|
|
878
|
+
workspace_directory=str(workspace_path) if workspace_path else None,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
# Execute based on streaming preference
|
|
882
|
+
if input.stream:
|
|
883
|
+
# Streaming execution
|
|
884
|
+
logger.info(
|
|
885
|
+
"🎬 Starting streaming execution",
|
|
886
|
+
execution_id=input.execution_id,
|
|
887
|
+
agent_id=input.agent_id
|
|
888
|
+
)
|
|
889
|
+
accumulated_response = ""
|
|
890
|
+
final_result = None
|
|
891
|
+
|
|
892
|
+
# Generate unique message ID for this turn (execution_id + timestamp)
|
|
893
|
+
message_id = f"{input.execution_id}_{int(time.time() * 1000000)}"
|
|
894
|
+
|
|
895
|
+
# Track tool events published
|
|
896
|
+
tool_events_published = {"start": 0, "complete": 0}
|
|
897
|
+
|
|
898
|
+
# Define event callback for publishing tool events to Control Plane
|
|
899
|
+
def event_callback(event: Dict):
|
|
900
|
+
"""Callback to publish events (tool start/complete, content chunks) to Control Plane SSE"""
|
|
901
|
+
event_type = event.get("type")
|
|
902
|
+
|
|
903
|
+
if event_type == "content_chunk":
|
|
904
|
+
# Content chunks are already handled below via result.response
|
|
905
|
+
pass
|
|
906
|
+
elif event_type == "tool_start":
|
|
907
|
+
# Publish tool start event (synchronous - this runs in async context via callback)
|
|
908
|
+
try:
|
|
909
|
+
logger.info(
|
|
910
|
+
"tool_start_event",
|
|
911
|
+
tool_name=event.get('tool_name'),
|
|
912
|
+
tool_execution_id=event.get('tool_execution_id')
|
|
913
|
+
)
|
|
914
|
+
control_plane.publish_event(
|
|
915
|
+
execution_id=input.execution_id,
|
|
916
|
+
event_type="tool_started", # Match default runtime event type
|
|
917
|
+
data={
|
|
918
|
+
"tool_name": event.get("tool_name"),
|
|
919
|
+
"tool_execution_id": event.get("tool_execution_id"),
|
|
920
|
+
"tool_arguments": event.get("tool_args", {}),
|
|
921
|
+
"message": f"🔧 Executing tool: {event.get('tool_name')}",
|
|
922
|
+
"source": "agent",
|
|
923
|
+
}
|
|
924
|
+
)
|
|
925
|
+
tool_events_published["start"] += 1
|
|
926
|
+
logger.debug(
|
|
927
|
+
"tool_started_event_published",
|
|
928
|
+
event_number=tool_events_published['start'],
|
|
929
|
+
tool_name=event.get('tool_name')
|
|
930
|
+
)
|
|
931
|
+
except Exception as e:
|
|
932
|
+
logger.error("tool_start_event_publish_failed", error=str(e), exc_info=True)
|
|
933
|
+
elif event_type == "tool_complete":
|
|
934
|
+
# Publish tool complete event
|
|
935
|
+
try:
|
|
936
|
+
status = event.get("status", "success")
|
|
937
|
+
logger.info(
|
|
938
|
+
"tool_complete_event",
|
|
939
|
+
tool_name=event.get('tool_name'),
|
|
940
|
+
status=status
|
|
941
|
+
)
|
|
942
|
+
control_plane.publish_event(
|
|
943
|
+
execution_id=input.execution_id,
|
|
944
|
+
event_type="tool_completed", # Match default runtime event type
|
|
945
|
+
data={
|
|
946
|
+
"tool_name": event.get("tool_name"),
|
|
947
|
+
"tool_execution_id": event.get("tool_execution_id"),
|
|
948
|
+
"status": status,
|
|
949
|
+
"tool_output": serialize_tool_output(event.get("output")), # Safely serialize output (handles generators)
|
|
950
|
+
"tool_error": event.get("error"),
|
|
951
|
+
"message": f"Tool {status}: {event.get('tool_name')}",
|
|
952
|
+
"source": "agent",
|
|
953
|
+
}
|
|
954
|
+
)
|
|
955
|
+
tool_events_published["complete"] += 1
|
|
956
|
+
logger.debug(
|
|
957
|
+
"tool_completed_event_published",
|
|
958
|
+
event_number=tool_events_published['complete'],
|
|
959
|
+
tool_name=event.get('tool_name')
|
|
960
|
+
)
|
|
961
|
+
except Exception as e:
|
|
962
|
+
logger.error("tool_complete_event_publish_failed", error=str(e), exc_info=True)
|
|
963
|
+
|
|
964
|
+
# Stream execution with event callback
|
|
965
|
+
# Note: AgnoRuntime publishes chunks via EventPublisher internally
|
|
966
|
+
# But ClaudeCodeRuntime needs us to publish chunks here
|
|
967
|
+
is_agno_runtime = runtime_type_enum == RuntimeType.DEFAULT
|
|
968
|
+
|
|
969
|
+
# Track last heartbeat time for periodic heartbeats during streaming
|
|
970
|
+
last_heartbeat = time.time()
|
|
971
|
+
chunk_count = 0
|
|
972
|
+
|
|
973
|
+
async for result in runtime.stream_execute(context, event_callback):
|
|
974
|
+
# Only process non-empty content (filter out empty strings and whitespace)
|
|
975
|
+
if result.response and result.response.strip():
|
|
976
|
+
accumulated_response += result.response
|
|
977
|
+
chunk_count += 1
|
|
978
|
+
|
|
979
|
+
# Publish chunks for non-Agno runtimes (e.g., Claude Code)
|
|
980
|
+
# AgnoRuntime publishes internally via EventPublisher to avoid duplicates
|
|
981
|
+
if not is_agno_runtime:
|
|
982
|
+
try:
|
|
983
|
+
await control_plane.publish_event_async(
|
|
984
|
+
execution_id=input.execution_id,
|
|
985
|
+
event_type="message_chunk",
|
|
986
|
+
data={
|
|
987
|
+
"role": "assistant",
|
|
988
|
+
"content": result.response,
|
|
989
|
+
"is_chunk": True,
|
|
990
|
+
"message_id": message_id,
|
|
991
|
+
}
|
|
992
|
+
)
|
|
993
|
+
except Exception as e:
|
|
994
|
+
logger.warning(f"Failed to publish streaming chunk: {e}")
|
|
995
|
+
|
|
996
|
+
# Send heartbeat every 10 seconds or every 50 chunks to detect hung executions
|
|
997
|
+
current_time = time.time()
|
|
998
|
+
if current_time - last_heartbeat > 10 or chunk_count % 50 == 0:
|
|
999
|
+
activity.heartbeat({
|
|
1000
|
+
"status": "streaming",
|
|
1001
|
+
"chunks_received": chunk_count,
|
|
1002
|
+
"response_length": len(accumulated_response),
|
|
1003
|
+
"elapsed_seconds": int(current_time - last_heartbeat)
|
|
1004
|
+
})
|
|
1005
|
+
last_heartbeat = current_time
|
|
1006
|
+
|
|
1007
|
+
if result.finish_reason:
|
|
1008
|
+
final_result = result
|
|
1009
|
+
break
|
|
1010
|
+
|
|
1011
|
+
if not final_result:
|
|
1012
|
+
raise RuntimeError("Streaming execution did not provide final result")
|
|
1013
|
+
|
|
1014
|
+
# Log tool event summary
|
|
1015
|
+
logger.info(
|
|
1016
|
+
"tool_events_summary",
|
|
1017
|
+
tool_started_events=tool_events_published['start'],
|
|
1018
|
+
tool_completed_events=tool_events_published['complete'],
|
|
1019
|
+
tool_messages_in_result=len(final_result.tool_messages or [])
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# Analytics now handled by separate Temporal activity in workflow
|
|
1023
|
+
# See: workflow calls submit_runtime_analytics_activity after this returns
|
|
1024
|
+
|
|
1025
|
+
# Log before return to verify we reach this point
|
|
1026
|
+
logger.info(
|
|
1027
|
+
"activity_about_to_return_streaming",
|
|
1028
|
+
execution_id=input.execution_id,
|
|
1029
|
+
turn_number=input.conversation_turn,
|
|
1030
|
+
note="About to return streaming activity result to Temporal"
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
"success": final_result.success,
|
|
1035
|
+
"response": accumulated_response,
|
|
1036
|
+
"usage": final_result.usage or {},
|
|
1037
|
+
"model": final_result.model,
|
|
1038
|
+
"finish_reason": final_result.finish_reason,
|
|
1039
|
+
"tool_messages": final_result.tool_messages or [],
|
|
1040
|
+
"metadata": final_result.metadata or {},
|
|
1041
|
+
"error": final_result.error,
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
else:
|
|
1045
|
+
# Non-streaming execution
|
|
1046
|
+
logger.info(
|
|
1047
|
+
"🎬 Starting non-streaming execution",
|
|
1048
|
+
execution_id=input.execution_id,
|
|
1049
|
+
agent_id=input.agent_id
|
|
1050
|
+
)
|
|
1051
|
+
result = await runtime.execute(context)
|
|
1052
|
+
|
|
1053
|
+
# Analytics now handled by separate Temporal activity in workflow
|
|
1054
|
+
# See: workflow calls submit_runtime_analytics_activity after this returns
|
|
1055
|
+
|
|
1056
|
+
# Log before return to verify we reach this point
|
|
1057
|
+
logger.info(
|
|
1058
|
+
"activity_about_to_return_non_streaming",
|
|
1059
|
+
execution_id=input.execution_id,
|
|
1060
|
+
turn_number=input.conversation_turn,
|
|
1061
|
+
note="About to return non-streaming activity result to Temporal"
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
return {
|
|
1065
|
+
"success": result.success,
|
|
1066
|
+
"response": result.response,
|
|
1067
|
+
"usage": result.usage or {},
|
|
1068
|
+
"model": result.model,
|
|
1069
|
+
"finish_reason": result.finish_reason,
|
|
1070
|
+
"tool_messages": result.tool_messages or [],
|
|
1071
|
+
"metadata": result.metadata or {},
|
|
1072
|
+
"error": result.error,
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
except asyncio.CancelledError as e:
|
|
1076
|
+
# DURABILITY FIX: Handle activity-level cancellation gracefully
|
|
1077
|
+
# This catches cancellations from Temporal (workflow cancellation, activity timeout, etc.)
|
|
1078
|
+
logger.warning(
|
|
1079
|
+
"Activity execution cancelled by Temporal",
|
|
1080
|
+
extra={
|
|
1081
|
+
"execution_id": input.execution_id,
|
|
1082
|
+
"runtime_type": input.runtime_type,
|
|
1083
|
+
"conversation_turn": input.conversation_turn,
|
|
1084
|
+
},
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
# Return a partial result instead of failing
|
|
1088
|
+
# This allows the workflow to handle the interruption and potentially resume
|
|
1089
|
+
return {
|
|
1090
|
+
"success": False, # Mark as failure since we couldn't complete
|
|
1091
|
+
"response": "",
|
|
1092
|
+
"usage": {},
|
|
1093
|
+
"model": input.model_id,
|
|
1094
|
+
"finish_reason": "cancelled",
|
|
1095
|
+
"tool_messages": [],
|
|
1096
|
+
"metadata": {
|
|
1097
|
+
"interrupted": True,
|
|
1098
|
+
"can_resume": False, # Activity-level cancellation can't resume easily
|
|
1099
|
+
"cancellation_source": "temporal_activity",
|
|
1100
|
+
},
|
|
1101
|
+
"error": "Execution was cancelled by Temporal",
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
except Exception as e:
|
|
1105
|
+
# Ensure error message is never empty
|
|
1106
|
+
error_msg = str(e) or repr(e) or f"{type(e).__name__}: No error details available"
|
|
1107
|
+
|
|
1108
|
+
logger.error(
|
|
1109
|
+
"Runtime execution failed",
|
|
1110
|
+
extra={
|
|
1111
|
+
"execution_id": input.execution_id,
|
|
1112
|
+
"runtime_type": input.runtime_type,
|
|
1113
|
+
"error": error_msg,
|
|
1114
|
+
"error_type": type(e).__name__,
|
|
1115
|
+
},
|
|
1116
|
+
exc_info=True,
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
# Publish error event to Control Plane for real-time UI updates
|
|
1120
|
+
try:
|
|
1121
|
+
from control_plane_api.worker.utils.error_publisher import (
|
|
1122
|
+
ErrorEventPublisher, ErrorSeverity, ErrorCategory
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
error_publisher = ErrorEventPublisher(control_plane)
|
|
1126
|
+
|
|
1127
|
+
# Determine error category based on error message
|
|
1128
|
+
error_str = error_msg.lower()
|
|
1129
|
+
category = ErrorCategory.UNKNOWN
|
|
1130
|
+
if "timeout" in error_str:
|
|
1131
|
+
category = ErrorCategory.TIMEOUT
|
|
1132
|
+
elif "import" in error_str or "module" in error_str:
|
|
1133
|
+
category = ErrorCategory.RUNTIME_INIT
|
|
1134
|
+
elif "api" in error_str or "model" in error_str or "anthropic" in error_str:
|
|
1135
|
+
category = ErrorCategory.MODEL_ERROR
|
|
1136
|
+
elif "network" in error_str or "connection" in error_str:
|
|
1137
|
+
category = ErrorCategory.NETWORK
|
|
1138
|
+
elif "auth" in error_str or "credential" in error_str:
|
|
1139
|
+
category = ErrorCategory.AUTHENTICATION
|
|
1140
|
+
|
|
1141
|
+
await error_publisher.publish_error(
|
|
1142
|
+
execution_id=input.execution_id,
|
|
1143
|
+
exception=e,
|
|
1144
|
+
severity=ErrorSeverity.CRITICAL,
|
|
1145
|
+
category=category,
|
|
1146
|
+
stage="execution",
|
|
1147
|
+
component=f"{input.runtime_type}_runtime",
|
|
1148
|
+
operation="agent_execution",
|
|
1149
|
+
metadata={
|
|
1150
|
+
"agent_id": input.agent_id,
|
|
1151
|
+
"model_id": input.model_id,
|
|
1152
|
+
"conversation_turn": input.conversation_turn,
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
except Exception as publish_error:
|
|
1156
|
+
# Never let error publishing break the main flow
|
|
1157
|
+
logger.warning(
|
|
1158
|
+
"failed_to_publish_error_event",
|
|
1159
|
+
error=str(publish_error)
|
|
1160
|
+
)
|
|
1161
|
+
|
|
1162
|
+
# Raise ApplicationError so Temporal marks the workflow as FAILED
|
|
1163
|
+
raise ApplicationError(
|
|
1164
|
+
f"Runtime execution failed: {error_msg}",
|
|
1165
|
+
non_retryable=False, # Allow retries per retry policy
|
|
1166
|
+
type=type(e).__name__
|
|
1167
|
+
)
|