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,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Approval workflow tools for human-in-the-loop approval gates.
|
|
3
|
+
|
|
4
|
+
Provides tools for workflows to request approval from authorized users
|
|
5
|
+
and wait for approval/rejection before continuing.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
import asyncio
|
|
10
|
+
import httpx
|
|
11
|
+
import structlog
|
|
12
|
+
from typing import List, Optional, Dict, Any
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
|
|
15
|
+
logger = structlog.get_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ApprovalTools:
|
|
19
|
+
"""
|
|
20
|
+
Approval workflow tools for human-in-the-loop gates.
|
|
21
|
+
|
|
22
|
+
Provides a temporal-native way to wait for approval from authorized users.
|
|
23
|
+
The workflow pauses execution and polls the control plane for approval status.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
control_plane_url: str,
|
|
29
|
+
api_key: str,
|
|
30
|
+
execution_id: str,
|
|
31
|
+
organization_id: str,
|
|
32
|
+
config: Optional[Dict[str, Any]] = None,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Initialize approval tools.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
control_plane_url: Control plane API base URL
|
|
39
|
+
api_key: API key for authentication
|
|
40
|
+
execution_id: Current execution ID
|
|
41
|
+
organization_id: Organization ID
|
|
42
|
+
config: Optional configuration (timeout, require_reason, etc.)
|
|
43
|
+
"""
|
|
44
|
+
self.control_plane_url = control_plane_url.rstrip("/")
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.execution_id = execution_id
|
|
47
|
+
self.organization_id = organization_id
|
|
48
|
+
self.config = config or {}
|
|
49
|
+
|
|
50
|
+
# Configuration
|
|
51
|
+
self.timeout_minutes = self.config.get("timeout_minutes", 1440) # 24 hours default
|
|
52
|
+
self.require_approval_reason = self.config.get("require_approval_reason", False)
|
|
53
|
+
self.poll_interval_seconds = self.config.get("poll_interval_seconds", 5) # Poll every 5 seconds
|
|
54
|
+
|
|
55
|
+
self.client = httpx.AsyncClient(
|
|
56
|
+
base_url=self.control_plane_url,
|
|
57
|
+
headers={
|
|
58
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
},
|
|
61
|
+
timeout=30.0,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
async def wait_for_approval(
|
|
65
|
+
self,
|
|
66
|
+
title: str,
|
|
67
|
+
message: Optional[str] = None,
|
|
68
|
+
approver_user_ids: Optional[List[str]] = None,
|
|
69
|
+
approver_user_emails: Optional[List[str]] = None,
|
|
70
|
+
approver_group_id: Optional[str] = None,
|
|
71
|
+
context: Optional[Dict[str, Any]] = None,
|
|
72
|
+
) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
Wait for approval from authorized users.
|
|
75
|
+
|
|
76
|
+
This function creates an approval request and polls the control plane
|
|
77
|
+
until the request is approved, rejected, or times out.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
title: Brief title for the approval request
|
|
81
|
+
message: Detailed message or reason for approval
|
|
82
|
+
approver_user_ids: List of user IDs who can approve (optional)
|
|
83
|
+
approver_user_emails: List of user emails who can approve (optional)
|
|
84
|
+
approver_group_id: Group ID that can approve (optional)
|
|
85
|
+
context: Additional context data (optional)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with approval result:
|
|
89
|
+
{
|
|
90
|
+
"approved": bool,
|
|
91
|
+
"status": "approved" | "rejected" | "expired",
|
|
92
|
+
"approval_id": str,
|
|
93
|
+
"approved_by_email": str (if approved),
|
|
94
|
+
"rejection_reason": str (if rejected),
|
|
95
|
+
"resolved_at": str (ISO timestamp)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
Exception: If approval request creation fails or times out
|
|
100
|
+
"""
|
|
101
|
+
logger.info(
|
|
102
|
+
"wait_for_approval_started",
|
|
103
|
+
title=title,
|
|
104
|
+
execution_id=self.execution_id,
|
|
105
|
+
approver_emails=approver_user_emails,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Validate at least one approver is specified
|
|
109
|
+
if not approver_user_ids and not approver_user_emails and not approver_group_id:
|
|
110
|
+
raise ValueError("At least one of approver_user_ids, approver_user_emails, or approver_group_id must be provided")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
# Create approval request via control plane API
|
|
114
|
+
approval_request = {
|
|
115
|
+
"execution_id": self.execution_id,
|
|
116
|
+
"title": title,
|
|
117
|
+
"message": message,
|
|
118
|
+
"approver_user_ids": approver_user_ids or [],
|
|
119
|
+
"approver_user_emails": approver_user_emails or [],
|
|
120
|
+
"approver_group_id": approver_group_id,
|
|
121
|
+
"timeout_minutes": self.timeout_minutes,
|
|
122
|
+
"context": context or {},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
response = await self.client.post(
|
|
126
|
+
"/api/v1/approvals",
|
|
127
|
+
json=approval_request,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if response.status_code != 201:
|
|
131
|
+
error_detail = response.text
|
|
132
|
+
logger.error(
|
|
133
|
+
"approval_request_creation_failed",
|
|
134
|
+
status_code=response.status_code,
|
|
135
|
+
error=error_detail
|
|
136
|
+
)
|
|
137
|
+
raise Exception(f"Failed to create approval request: {error_detail}")
|
|
138
|
+
|
|
139
|
+
approval_data = response.json()
|
|
140
|
+
approval_id = approval_data["id"]
|
|
141
|
+
|
|
142
|
+
logger.info(
|
|
143
|
+
"approval_request_created",
|
|
144
|
+
approval_id=approval_id,
|
|
145
|
+
title=title,
|
|
146
|
+
execution_id=self.execution_id,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Calculate timeout
|
|
150
|
+
start_time = time.time()
|
|
151
|
+
timeout_seconds = self.timeout_minutes * 60
|
|
152
|
+
expires_at = time.time() + timeout_seconds
|
|
153
|
+
|
|
154
|
+
# Poll for approval status
|
|
155
|
+
poll_count = 0
|
|
156
|
+
while time.time() < expires_at:
|
|
157
|
+
poll_count += 1
|
|
158
|
+
|
|
159
|
+
# Get approval status
|
|
160
|
+
try:
|
|
161
|
+
status_response = await self.client.get(
|
|
162
|
+
f"/api/v1/approvals/{approval_id}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if status_response.status_code == 200:
|
|
166
|
+
approval_status = status_response.json()
|
|
167
|
+
|
|
168
|
+
if approval_status["status"] == "approved":
|
|
169
|
+
elapsed_minutes = (time.time() - start_time) / 60
|
|
170
|
+
logger.info(
|
|
171
|
+
"approval_granted",
|
|
172
|
+
approval_id=approval_id,
|
|
173
|
+
approved_by=approval_status.get("approved_by_email"),
|
|
174
|
+
elapsed_minutes=round(elapsed_minutes, 2),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
"approved": True,
|
|
179
|
+
"status": "approved",
|
|
180
|
+
"approval_id": approval_id,
|
|
181
|
+
"approved_by_email": approval_status.get("approved_by_email"),
|
|
182
|
+
"approved_by_name": approval_status.get("approved_by_name"),
|
|
183
|
+
"resolved_at": approval_status.get("resolved_at"),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
elif approval_status["status"] == "rejected":
|
|
187
|
+
elapsed_minutes = (time.time() - start_time) / 60
|
|
188
|
+
logger.info(
|
|
189
|
+
"approval_rejected",
|
|
190
|
+
approval_id=approval_id,
|
|
191
|
+
rejected_by=approval_status.get("approved_by_email"),
|
|
192
|
+
reason=approval_status.get("rejection_reason"),
|
|
193
|
+
elapsed_minutes=round(elapsed_minutes, 2),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"approved": False,
|
|
198
|
+
"status": "rejected",
|
|
199
|
+
"approval_id": approval_id,
|
|
200
|
+
"rejected_by_email": approval_status.get("approved_by_email"),
|
|
201
|
+
"rejected_by_name": approval_status.get("approved_by_name"),
|
|
202
|
+
"rejection_reason": approval_status.get("rejection_reason"),
|
|
203
|
+
"resolved_at": approval_status.get("resolved_at"),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
elif approval_status["status"] == "expired":
|
|
207
|
+
logger.warning(
|
|
208
|
+
"approval_expired",
|
|
209
|
+
approval_id=approval_id,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
"approved": False,
|
|
214
|
+
"status": "expired",
|
|
215
|
+
"approval_id": approval_id,
|
|
216
|
+
"resolved_at": approval_status.get("resolved_at"),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# Still pending, continue polling
|
|
220
|
+
if poll_count % 12 == 0: # Log every minute (12 * 5 seconds)
|
|
221
|
+
elapsed_minutes = (time.time() - start_time) / 60
|
|
222
|
+
remaining_minutes = (expires_at - time.time()) / 60
|
|
223
|
+
logger.debug(
|
|
224
|
+
"waiting_for_approval",
|
|
225
|
+
approval_id=approval_id,
|
|
226
|
+
status=approval_status["status"],
|
|
227
|
+
elapsed_minutes=round(elapsed_minutes, 2),
|
|
228
|
+
remaining_minutes=round(remaining_minutes, 2),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(
|
|
233
|
+
"approval_status_check_failed",
|
|
234
|
+
approval_id=approval_id,
|
|
235
|
+
status_code=status_response.status_code,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
except Exception as poll_error:
|
|
239
|
+
logger.warning(
|
|
240
|
+
"approval_poll_error",
|
|
241
|
+
approval_id=approval_id,
|
|
242
|
+
error=str(poll_error),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Wait before next poll
|
|
246
|
+
await asyncio.sleep(self.poll_interval_seconds)
|
|
247
|
+
|
|
248
|
+
# Timeout reached
|
|
249
|
+
logger.warning(
|
|
250
|
+
"approval_timeout",
|
|
251
|
+
approval_id=approval_id,
|
|
252
|
+
timeout_minutes=self.timeout_minutes,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
"approved": False,
|
|
257
|
+
"status": "expired",
|
|
258
|
+
"approval_id": approval_id,
|
|
259
|
+
"resolved_at": datetime.utcnow().isoformat(),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.error(
|
|
264
|
+
"wait_for_approval_failed",
|
|
265
|
+
title=title,
|
|
266
|
+
execution_id=self.execution_id,
|
|
267
|
+
error=str(e),
|
|
268
|
+
)
|
|
269
|
+
raise
|
|
270
|
+
|
|
271
|
+
finally:
|
|
272
|
+
await self.client.aclose()
|
|
273
|
+
|
|
274
|
+
def get_tools_schema(self) -> List[Dict[str, Any]]:
|
|
275
|
+
"""
|
|
276
|
+
Get the tool schema for LLM function calling.
|
|
277
|
+
|
|
278
|
+
Returns list of tool definitions that can be provided to LLMs.
|
|
279
|
+
"""
|
|
280
|
+
return [
|
|
281
|
+
{
|
|
282
|
+
"name": "wait_for_approval",
|
|
283
|
+
"description": "Pause workflow execution and wait for approval from authorized users before continuing. "
|
|
284
|
+
"Use this when you need human approval for sensitive operations, decisions, or actions. "
|
|
285
|
+
"The workflow will pause until an authorized user approves or rejects the request.",
|
|
286
|
+
"input_schema": {
|
|
287
|
+
"type": "object",
|
|
288
|
+
"properties": {
|
|
289
|
+
"title": {
|
|
290
|
+
"type": "string",
|
|
291
|
+
"description": "Brief title for the approval request (e.g., 'Deploy to Production', 'Delete Database')"
|
|
292
|
+
},
|
|
293
|
+
"message": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"description": "Detailed message explaining why approval is needed and what will happen if approved"
|
|
296
|
+
},
|
|
297
|
+
"approver_user_emails": {
|
|
298
|
+
"type": "array",
|
|
299
|
+
"items": {"type": "string"},
|
|
300
|
+
"description": "List of user email addresses who can approve this request"
|
|
301
|
+
},
|
|
302
|
+
"context": {
|
|
303
|
+
"type": "object",
|
|
304
|
+
"description": "Additional context data to help approvers make a decision"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"required": ["title", "approver_user_emails"]
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agno-compatible approval workflow tools.
|
|
3
|
+
|
|
4
|
+
Provides wait_for_approval tool as an Agno Tool for seamless integration
|
|
5
|
+
with Agno agent runtimes.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import List, Optional, Dict, Any
|
|
9
|
+
import structlog
|
|
10
|
+
from control_plane_api.worker.services.approval_tools import ApprovalTools
|
|
11
|
+
|
|
12
|
+
logger = structlog.get_logger()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ApprovalToolkit:
|
|
16
|
+
"""
|
|
17
|
+
Agno toolkit for approval workflow (human-in-the-loop gates).
|
|
18
|
+
|
|
19
|
+
Provides tools for workflows to request approval from authorized users
|
|
20
|
+
and wait for approval/rejection before continuing.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
control_plane_client: Any,
|
|
26
|
+
config: Optional[Dict[str, Any]] = None,
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Initialize approval toolkit.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
control_plane_client: Control plane client (provides URL and credentials)
|
|
33
|
+
config: Optional configuration (timeout, etc.)
|
|
34
|
+
"""
|
|
35
|
+
self.control_plane_client = control_plane_client
|
|
36
|
+
self.config = config or {}
|
|
37
|
+
|
|
38
|
+
# Store environment variable names - will be accessed at tool execution time
|
|
39
|
+
self.control_plane_url = None
|
|
40
|
+
self.api_key = None
|
|
41
|
+
|
|
42
|
+
# execution_id and organization_id will be set at runtime
|
|
43
|
+
self.execution_id = None
|
|
44
|
+
self.organization_id = None
|
|
45
|
+
|
|
46
|
+
logger.info("approval_toolkit_initialized")
|
|
47
|
+
|
|
48
|
+
def set_execution_context(self, execution_id: str, organization_id: str):
|
|
49
|
+
"""
|
|
50
|
+
Set execution context (called by runtime before tool execution).
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
execution_id: Current execution ID
|
|
54
|
+
organization_id: Organization ID
|
|
55
|
+
"""
|
|
56
|
+
self.execution_id = execution_id
|
|
57
|
+
self.organization_id = organization_id
|
|
58
|
+
|
|
59
|
+
async def wait_for_approval(
|
|
60
|
+
self,
|
|
61
|
+
title: str,
|
|
62
|
+
approver_user_emails: Optional[List[str]] = None,
|
|
63
|
+
approver_group_id: Optional[str] = None,
|
|
64
|
+
message: Optional[str] = None,
|
|
65
|
+
context: Optional[Dict[str, Any]] = None,
|
|
66
|
+
) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Wait for approval from authorized users or groups before continuing.
|
|
69
|
+
|
|
70
|
+
This tool creates an approval request and pauses the workflow until
|
|
71
|
+
an authorized user approves or rejects the request. Use this for:
|
|
72
|
+
- Deployments to production
|
|
73
|
+
- Destructive operations (deletions, etc.)
|
|
74
|
+
- High-value transactions
|
|
75
|
+
- Policy-gated actions
|
|
76
|
+
|
|
77
|
+
APPROVERS: You can specify approvers in three ways:
|
|
78
|
+
1. Pass approver_user_emails parameter (list of emails)
|
|
79
|
+
2. Pass approver_group_id parameter (group UUID)
|
|
80
|
+
3. Use defaults from skill configuration (if configured)
|
|
81
|
+
|
|
82
|
+
If you don't specify approvers, the tool will use the default approvers
|
|
83
|
+
configured in the skill settings.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
title: Brief title for the approval request (e.g., "Deploy to Production")
|
|
87
|
+
approver_user_emails: List of user email addresses who can approve (optional, uses config default if not provided)
|
|
88
|
+
approver_group_id: Group UUID whose members can approve (optional, uses config default if not provided)
|
|
89
|
+
message: Detailed message explaining why approval is needed (optional)
|
|
90
|
+
context: Additional context data to help approvers decide (optional)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
str: Approval result message
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
```python
|
|
97
|
+
# Approve by specific users
|
|
98
|
+
result = await wait_for_approval(
|
|
99
|
+
title="Deploy to Production",
|
|
100
|
+
message="Deploy version 2.0.0 to production environment",
|
|
101
|
+
approver_user_emails=["ops-lead@company.com", "cto@company.com"],
|
|
102
|
+
context={"version": "2.0.0", "environment": "production"}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Approve by group
|
|
106
|
+
result = await wait_for_approval(
|
|
107
|
+
title="Delete Customer Data",
|
|
108
|
+
message="Permanently delete customer data for GDPR request",
|
|
109
|
+
approver_group_id="550e8400-e29b-41d4-a716-446655440000", # Admin group UUID
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Approve by group OR specific users
|
|
113
|
+
result = await wait_for_approval(
|
|
114
|
+
title="Emergency Hotfix",
|
|
115
|
+
message="Deploy critical security patch",
|
|
116
|
+
approver_user_emails=["security-lead@company.com"],
|
|
117
|
+
approver_group_id="550e8400-e29b-41d4-a716-446655440000", # Ops group UUID
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
"""
|
|
121
|
+
if not self.execution_id or not self.organization_id:
|
|
122
|
+
return "❌ Error: Approval toolkit not initialized with execution context"
|
|
123
|
+
|
|
124
|
+
# Get control plane URL and API key from environment at execution time
|
|
125
|
+
self.control_plane_url = os.getenv("CONTROL_PLANE_URL")
|
|
126
|
+
self.api_key = os.getenv("KUBIYA_API_KEY")
|
|
127
|
+
|
|
128
|
+
if not self.control_plane_url or not self.api_key:
|
|
129
|
+
return "❌ Error: CONTROL_PLANE_URL and KUBIYA_API_KEY environment variables must be set"
|
|
130
|
+
|
|
131
|
+
# Use config defaults if no approvers specified
|
|
132
|
+
if not approver_user_emails and not approver_group_id:
|
|
133
|
+
# Try to get defaults from config
|
|
134
|
+
approver_user_emails = self.config.get("default_approver_emails")
|
|
135
|
+
approver_group_id = self.config.get("default_approver_group_id")
|
|
136
|
+
|
|
137
|
+
# Still no approvers? Error
|
|
138
|
+
if not approver_user_emails and not approver_group_id:
|
|
139
|
+
return (
|
|
140
|
+
"❌ Error: No approvers specified. Either:\n"
|
|
141
|
+
"1. Pass approver_user_emails or approver_group_id to this tool call, OR\n"
|
|
142
|
+
"2. Configure default approvers in the skill configuration"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
logger.info(
|
|
146
|
+
"wait_for_approval_tool_called",
|
|
147
|
+
title=title,
|
|
148
|
+
approver_emails=approver_user_emails,
|
|
149
|
+
approver_group_id=approver_group_id,
|
|
150
|
+
execution_id=self.execution_id,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
# Create approval tools instance for this execution
|
|
155
|
+
approval_tools = ApprovalTools(
|
|
156
|
+
control_plane_url=self.control_plane_url,
|
|
157
|
+
api_key=self.api_key,
|
|
158
|
+
execution_id=self.execution_id,
|
|
159
|
+
organization_id=self.organization_id,
|
|
160
|
+
config=self.config,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Call underlying approval tools
|
|
164
|
+
result = await approval_tools.wait_for_approval(
|
|
165
|
+
title=title,
|
|
166
|
+
message=message,
|
|
167
|
+
approver_user_emails=approver_user_emails or [],
|
|
168
|
+
approver_group_id=approver_group_id,
|
|
169
|
+
context=context,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if result["approved"]:
|
|
173
|
+
approved_by = result.get("approved_by_email", "unknown")
|
|
174
|
+
return (
|
|
175
|
+
f"✅ Approval granted by {approved_by}. "
|
|
176
|
+
f"You may proceed with '{title}'."
|
|
177
|
+
)
|
|
178
|
+
elif result["status"] == "rejected":
|
|
179
|
+
rejected_by = result.get("rejected_by_email", "unknown")
|
|
180
|
+
reason = result.get("rejection_reason", "No reason provided")
|
|
181
|
+
return (
|
|
182
|
+
f"❌ Request rejected by {rejected_by}. "
|
|
183
|
+
f"Reason: {reason}. "
|
|
184
|
+
f"You must not proceed with '{title}'."
|
|
185
|
+
)
|
|
186
|
+
elif result["status"] == "expired":
|
|
187
|
+
return (
|
|
188
|
+
f"⏱️ Approval request expired without response. "
|
|
189
|
+
f"You must not proceed with '{title}' without approval."
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
return (
|
|
193
|
+
f"⚠️ Approval request ended with status: {result['status']}. "
|
|
194
|
+
f"You must not proceed with '{title}' without explicit approval."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(
|
|
199
|
+
"wait_for_approval_tool_error",
|
|
200
|
+
error=str(e),
|
|
201
|
+
title=title,
|
|
202
|
+
execution_id=self.execution_id,
|
|
203
|
+
)
|
|
204
|
+
return (
|
|
205
|
+
f"❌ Failed to request approval: {str(e)}. "
|
|
206
|
+
f"You must not proceed with '{title}' due to approval system error."
|
|
207
|
+
)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Cancellation manager - handles agent/team registry and cancellation"""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
logger = structlog.get_logger()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CancellationManager:
|
|
11
|
+
"""
|
|
12
|
+
Manages active agent/team instances for cancellation support.
|
|
13
|
+
|
|
14
|
+
Provides a centralized registry and cancellation logic that works
|
|
15
|
+
with Agno's cancel_run() API.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
# Key: execution_id, Value: {agent/team, run_id, started_at}
|
|
20
|
+
self._registry: Dict[str, Dict[str, Any]] = {}
|
|
21
|
+
|
|
22
|
+
def register(
|
|
23
|
+
self,
|
|
24
|
+
execution_id: str,
|
|
25
|
+
instance: Any, # Agent or Team
|
|
26
|
+
instance_type: str = "agent"
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Register an agent or team for cancellation support.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
execution_id: Unique execution ID
|
|
33
|
+
instance: Agno Agent or Team instance
|
|
34
|
+
instance_type: "agent" or "team"
|
|
35
|
+
"""
|
|
36
|
+
self._registry[execution_id] = {
|
|
37
|
+
"instance": instance,
|
|
38
|
+
"instance_type": instance_type,
|
|
39
|
+
"run_id": None, # Set when run starts
|
|
40
|
+
"started_at": datetime.now(timezone.utc).isoformat(),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.info(
|
|
44
|
+
f"{instance_type}_registered_for_cancellation",
|
|
45
|
+
execution_id=execution_id[:8],
|
|
46
|
+
instance_type=instance_type
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def set_run_id(self, execution_id: str, run_id: str) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Set the Agno run_id for an execution (captured from first streaming chunk).
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
execution_id: Execution ID
|
|
55
|
+
run_id: Agno run_id from streaming response
|
|
56
|
+
"""
|
|
57
|
+
if execution_id in self._registry:
|
|
58
|
+
self._registry[execution_id]["run_id"] = run_id
|
|
59
|
+
logger.info(
|
|
60
|
+
"run_id_captured",
|
|
61
|
+
execution_id=execution_id[:8],
|
|
62
|
+
run_id=run_id[:16]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def cancel(self, execution_id: str) -> Dict[str, Any]:
|
|
66
|
+
"""
|
|
67
|
+
Cancel an active execution using Agno's cancel_run API.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
execution_id: Execution to cancel
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict with success status and details
|
|
74
|
+
"""
|
|
75
|
+
# Check if execution exists in registry
|
|
76
|
+
if execution_id not in self._registry:
|
|
77
|
+
logger.warning(
|
|
78
|
+
"cancel_execution_not_found",
|
|
79
|
+
execution_id=execution_id[:8]
|
|
80
|
+
)
|
|
81
|
+
return {
|
|
82
|
+
"success": False,
|
|
83
|
+
"error": "Execution not found or already completed",
|
|
84
|
+
"execution_id": execution_id,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
entry = self._registry[execution_id]
|
|
88
|
+
instance = entry["instance"]
|
|
89
|
+
run_id = entry.get("run_id")
|
|
90
|
+
instance_type = entry.get("instance_type", "agent")
|
|
91
|
+
|
|
92
|
+
# Check if run has started
|
|
93
|
+
if not run_id:
|
|
94
|
+
logger.warning(
|
|
95
|
+
"cancel_no_run_id",
|
|
96
|
+
execution_id=execution_id[:8]
|
|
97
|
+
)
|
|
98
|
+
return {
|
|
99
|
+
"success": False,
|
|
100
|
+
"error": "Execution not started yet",
|
|
101
|
+
"execution_id": execution_id,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
logger.info(
|
|
105
|
+
f"cancelling_{instance_type}_run",
|
|
106
|
+
execution_id=execution_id[:8],
|
|
107
|
+
run_id=run_id[:16]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Call Agno's cancel_run API
|
|
112
|
+
success = instance.cancel_run(run_id)
|
|
113
|
+
|
|
114
|
+
if success:
|
|
115
|
+
logger.info(
|
|
116
|
+
f"{instance_type}_run_cancelled",
|
|
117
|
+
execution_id=execution_id[:8],
|
|
118
|
+
run_id=run_id[:16]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Clean up registry
|
|
122
|
+
del self._registry[execution_id]
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"success": True,
|
|
126
|
+
"execution_id": execution_id,
|
|
127
|
+
"run_id": run_id,
|
|
128
|
+
"instance_type": instance_type,
|
|
129
|
+
"cancelled_at": datetime.now(timezone.utc).isoformat(),
|
|
130
|
+
}
|
|
131
|
+
else:
|
|
132
|
+
logger.warning(
|
|
133
|
+
f"{instance_type}_cancel_failed",
|
|
134
|
+
execution_id=execution_id[:8],
|
|
135
|
+
run_id=run_id[:16]
|
|
136
|
+
)
|
|
137
|
+
return {
|
|
138
|
+
"success": False,
|
|
139
|
+
"error": "Cancel failed - run may be completed",
|
|
140
|
+
"execution_id": execution_id,
|
|
141
|
+
"run_id": run_id,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(
|
|
146
|
+
f"{instance_type}_cancel_error",
|
|
147
|
+
execution_id=execution_id[:8],
|
|
148
|
+
error=str(e)
|
|
149
|
+
)
|
|
150
|
+
return {
|
|
151
|
+
"success": False,
|
|
152
|
+
"error": str(e),
|
|
153
|
+
"execution_id": execution_id,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def unregister(self, execution_id: str) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Remove an execution from the registry (called on completion).
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
execution_id: Execution to unregister
|
|
162
|
+
"""
|
|
163
|
+
if execution_id in self._registry:
|
|
164
|
+
instance_type = self._registry[execution_id].get("instance_type", "agent")
|
|
165
|
+
del self._registry[execution_id]
|
|
166
|
+
logger.info(
|
|
167
|
+
f"{instance_type}_unregistered",
|
|
168
|
+
execution_id=execution_id[:8]
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def get_active_count(self) -> int:
|
|
172
|
+
"""Get number of active executions in registry."""
|
|
173
|
+
return len(self._registry)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Global singleton instance
|
|
177
|
+
cancellation_manager = CancellationManager()
|