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,715 @@
|
|
|
1
|
+
"""Team executor service - handles team execution business logic"""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Optional, List
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import structlog
|
|
6
|
+
import asyncio
|
|
7
|
+
import os
|
|
8
|
+
from temporalio import activity
|
|
9
|
+
|
|
10
|
+
from agno.agent import Agent
|
|
11
|
+
from agno.team import Team
|
|
12
|
+
from agno.models.litellm import LiteLLM
|
|
13
|
+
|
|
14
|
+
from control_plane_api.worker.control_plane_client import ControlPlaneClient
|
|
15
|
+
from control_plane_api.worker.services.session_service import SessionService
|
|
16
|
+
from control_plane_api.worker.services.cancellation_manager import CancellationManager
|
|
17
|
+
from control_plane_api.worker.services.skill_factory import SkillFactory
|
|
18
|
+
from control_plane_api.worker.utils.streaming_utils import StreamingHelper
|
|
19
|
+
from control_plane_api.worker.utils.parameter_validator import (
|
|
20
|
+
validate_tool_parameters,
|
|
21
|
+
ParameterValidationError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = structlog.get_logger()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TeamExecutorService:
|
|
28
|
+
"""
|
|
29
|
+
Service for executing teams with full session management and cancellation support.
|
|
30
|
+
|
|
31
|
+
This service orchestrates:
|
|
32
|
+
- Session loading and restoration
|
|
33
|
+
- Team and member agent creation with LiteLLM configuration
|
|
34
|
+
- Skill instantiation for team members
|
|
35
|
+
- Streaming execution with real-time updates
|
|
36
|
+
- Session persistence
|
|
37
|
+
- Cancellation support via CancellationManager
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
control_plane: ControlPlaneClient,
|
|
43
|
+
session_service: SessionService,
|
|
44
|
+
cancellation_manager: CancellationManager
|
|
45
|
+
):
|
|
46
|
+
self.control_plane = control_plane
|
|
47
|
+
self.session_service = session_service
|
|
48
|
+
self.cancellation_manager = cancellation_manager
|
|
49
|
+
|
|
50
|
+
async def execute(self, input: Any) -> Dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
Execute a team with full session management and streaming.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
input: TeamExecutionInput with execution details
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dict with response, usage, success flag, etc.
|
|
59
|
+
"""
|
|
60
|
+
execution_id = input.execution_id
|
|
61
|
+
|
|
62
|
+
logger.info(
|
|
63
|
+
"team_workflow_start",
|
|
64
|
+
execution_id=execution_id[:8],
|
|
65
|
+
team_id=input.team_id,
|
|
66
|
+
session_id=input.session_id,
|
|
67
|
+
agent_count=len(input.agents)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# STEP 1: Load session history
|
|
72
|
+
session_history = self.session_service.load_session(
|
|
73
|
+
execution_id=execution_id,
|
|
74
|
+
session_id=input.session_id
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if session_history:
|
|
78
|
+
print(f"ā
Loaded {len(session_history)} messages from previous session\n")
|
|
79
|
+
else:
|
|
80
|
+
print("ā¹ļø Starting new conversation\n")
|
|
81
|
+
|
|
82
|
+
# STEP 2: Build conversation context for Agno
|
|
83
|
+
conversation_context = self.session_service.build_conversation_context(session_history)
|
|
84
|
+
|
|
85
|
+
# STEP 3: Get LiteLLM configuration
|
|
86
|
+
litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
|
|
87
|
+
litellm_api_key = os.getenv("LITELLM_API_KEY")
|
|
88
|
+
|
|
89
|
+
if not litellm_api_key:
|
|
90
|
+
raise ValueError("LITELLM_API_KEY environment variable not set")
|
|
91
|
+
|
|
92
|
+
model = input.model_id or os.environ.get("LITELLM_DEFAULT_MODEL", "kubiya/claude-sonnet-4")
|
|
93
|
+
|
|
94
|
+
# STEP 4: Create member agents with skills
|
|
95
|
+
print(f"š„ Creating {len(input.agents)} member agents...\n")
|
|
96
|
+
|
|
97
|
+
team_members = []
|
|
98
|
+
for agent_data in input.agents:
|
|
99
|
+
agent_id = agent_data.get("id")
|
|
100
|
+
agent_name = agent_data.get("name", f"Agent {agent_id}")
|
|
101
|
+
agent_role = agent_data.get("role", "You are a helpful AI assistant")
|
|
102
|
+
|
|
103
|
+
print(f" š¤ Creating Agent: {agent_name}")
|
|
104
|
+
print(f" ID: {agent_id}")
|
|
105
|
+
print(f" Role: {agent_role[:80]}...")
|
|
106
|
+
|
|
107
|
+
# Fetch skills for this agent
|
|
108
|
+
skills = []
|
|
109
|
+
if agent_id:
|
|
110
|
+
try:
|
|
111
|
+
skill_configs = self.control_plane.get_skills(agent_id)
|
|
112
|
+
|
|
113
|
+
# AUTO-INCLUDE BUILT-IN SKILLS
|
|
114
|
+
if not skill_configs:
|
|
115
|
+
skill_configs = []
|
|
116
|
+
|
|
117
|
+
builtin_skill_types = {'context_graph_search'}
|
|
118
|
+
existing_skill_types = {cfg.get('type') for cfg in skill_configs}
|
|
119
|
+
|
|
120
|
+
for builtin_type in builtin_skill_types:
|
|
121
|
+
if builtin_type not in existing_skill_types:
|
|
122
|
+
builtin_config = {
|
|
123
|
+
'name': builtin_type,
|
|
124
|
+
'type': builtin_type,
|
|
125
|
+
'enabled': True,
|
|
126
|
+
'configuration': {}
|
|
127
|
+
}
|
|
128
|
+
skill_configs.append(builtin_config)
|
|
129
|
+
print(f" ā Auto-included built-in skill: {builtin_type}")
|
|
130
|
+
|
|
131
|
+
if skill_configs:
|
|
132
|
+
print(f" Skills: {len(skill_configs)}")
|
|
133
|
+
# Create SkillFactory instance for agno runtime
|
|
134
|
+
skill_factory = SkillFactory(runtime_type="agno")
|
|
135
|
+
skill_factory.initialize()
|
|
136
|
+
skills = skill_factory.create_skills_from_list(skill_configs, execution_id=execution_id)
|
|
137
|
+
if skills:
|
|
138
|
+
print(f" ā
Instantiated {len(skills)} skill(s)")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f" ā ļø Failed to fetch skills: {str(e)}")
|
|
141
|
+
logger.warning("skill_fetch_error_for_team_member", agent_id=agent_id, error=str(e))
|
|
142
|
+
|
|
143
|
+
# Create Agno Agent
|
|
144
|
+
member_agent = Agent(
|
|
145
|
+
name=agent_name,
|
|
146
|
+
role=agent_role,
|
|
147
|
+
model=LiteLLM(
|
|
148
|
+
id=f"openai/{model}",
|
|
149
|
+
api_base=litellm_api_base,
|
|
150
|
+
api_key=litellm_api_key,
|
|
151
|
+
),
|
|
152
|
+
tools=skills if skills else None,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
team_members.append(member_agent)
|
|
156
|
+
print(f" ā
Agent created\n")
|
|
157
|
+
|
|
158
|
+
if not team_members:
|
|
159
|
+
raise ValueError("No team members available for team execution")
|
|
160
|
+
|
|
161
|
+
# STEP 5: Create Agno Team with streaming helper
|
|
162
|
+
print(f"\nš Creating Agno Team:")
|
|
163
|
+
print(f" Team ID: {input.team_id}")
|
|
164
|
+
print(f" Members: {len(team_members)}")
|
|
165
|
+
print(f" Model: {model}")
|
|
166
|
+
|
|
167
|
+
# Create streaming helper for this execution
|
|
168
|
+
streaming_helper = StreamingHelper(
|
|
169
|
+
control_plane_client=self.control_plane,
|
|
170
|
+
execution_id=execution_id
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Create tool hook for real-time updates
|
|
174
|
+
def tool_hook(name: str = None, function_name: str = None, function=None, arguments: dict = None, **kwargs):
|
|
175
|
+
"""Hook to capture tool execution for real-time streaming"""
|
|
176
|
+
tool_name = name or function_name or "unknown"
|
|
177
|
+
tool_args = arguments or {}
|
|
178
|
+
|
|
179
|
+
# Generate unique tool execution ID
|
|
180
|
+
import time
|
|
181
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
182
|
+
|
|
183
|
+
print(f" š§ Tool Starting: {tool_name} (ID: {tool_execution_id})")
|
|
184
|
+
|
|
185
|
+
# Publish tool start event
|
|
186
|
+
streaming_helper.publish_tool_start(
|
|
187
|
+
tool_name=tool_name,
|
|
188
|
+
tool_execution_id=tool_execution_id,
|
|
189
|
+
tool_args=tool_args,
|
|
190
|
+
source="team"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Execute the tool
|
|
194
|
+
result = None
|
|
195
|
+
error = None
|
|
196
|
+
try:
|
|
197
|
+
if function and callable(function):
|
|
198
|
+
# Validate parameters before execution to catch mismatches early
|
|
199
|
+
try:
|
|
200
|
+
validate_tool_parameters(
|
|
201
|
+
function,
|
|
202
|
+
tool_args,
|
|
203
|
+
tool_name,
|
|
204
|
+
execution_id
|
|
205
|
+
)
|
|
206
|
+
except ParameterValidationError as ve:
|
|
207
|
+
# Log detailed error and fail fast
|
|
208
|
+
logger.error(
|
|
209
|
+
"tool_parameter_mismatch_detected",
|
|
210
|
+
tool_name=tool_name,
|
|
211
|
+
execution_id=execution_id,
|
|
212
|
+
validation_error=str(ve),
|
|
213
|
+
)
|
|
214
|
+
raise
|
|
215
|
+
|
|
216
|
+
result = function(**tool_args) if tool_args else function()
|
|
217
|
+
else:
|
|
218
|
+
raise ValueError(f"Function not callable: {function}")
|
|
219
|
+
|
|
220
|
+
status = "success"
|
|
221
|
+
print(f" ā
Tool Success: {tool_name}")
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
error = e
|
|
225
|
+
status = "failed"
|
|
226
|
+
print(f" ā Tool Failed: {tool_name} - {str(e)}")
|
|
227
|
+
|
|
228
|
+
# Publish tool completion event
|
|
229
|
+
streaming_helper.publish_tool_complete(
|
|
230
|
+
tool_name=tool_name,
|
|
231
|
+
tool_execution_id=tool_execution_id,
|
|
232
|
+
status=status,
|
|
233
|
+
output=str(result)[:1000] if result else None,
|
|
234
|
+
error=str(error) if error else None,
|
|
235
|
+
source="team"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if error:
|
|
239
|
+
raise error
|
|
240
|
+
|
|
241
|
+
return result
|
|
242
|
+
|
|
243
|
+
# Add tool hooks to all team members
|
|
244
|
+
for member in team_members:
|
|
245
|
+
if not hasattr(member, 'tool_hooks') or member.tool_hooks is None:
|
|
246
|
+
member.tool_hooks = []
|
|
247
|
+
member.tool_hooks.append(tool_hook)
|
|
248
|
+
|
|
249
|
+
# Create Agno Team
|
|
250
|
+
team = Team(
|
|
251
|
+
name=f"Team {input.team_id}",
|
|
252
|
+
members=team_members,
|
|
253
|
+
model=LiteLLM(
|
|
254
|
+
id=f"openai/{model}",
|
|
255
|
+
api_base=litellm_api_base,
|
|
256
|
+
api_key=litellm_api_key,
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# STEP 6: Register for cancellation
|
|
261
|
+
self.cancellation_manager.register(
|
|
262
|
+
execution_id=execution_id,
|
|
263
|
+
instance=team,
|
|
264
|
+
instance_type="team"
|
|
265
|
+
)
|
|
266
|
+
print(f"ā
Team registered for cancellation support\n")
|
|
267
|
+
|
|
268
|
+
# Cache execution metadata in Redis
|
|
269
|
+
self.control_plane.cache_metadata(execution_id, "TEAM")
|
|
270
|
+
|
|
271
|
+
# STEP 7: Execute with streaming
|
|
272
|
+
print("ā” Executing Team Run with Streaming...\n")
|
|
273
|
+
|
|
274
|
+
# Generate deterministic message IDs for this turn (matches V2 executor pattern)
|
|
275
|
+
# This ensures streaming and persisted messages have the SAME message_id
|
|
276
|
+
turn_number = len(session_history) // 2 + 1
|
|
277
|
+
user_message_id = f"{execution_id}_user_{turn_number}"
|
|
278
|
+
message_id = f"{execution_id}_assistant_{turn_number}"
|
|
279
|
+
|
|
280
|
+
# Publish user message to stream immediately so it appears in chronological order
|
|
281
|
+
from datetime import datetime, timezone
|
|
282
|
+
user_message_timestamp = datetime.now(timezone.utc).isoformat()
|
|
283
|
+
self.control_plane.publish_event(
|
|
284
|
+
execution_id=execution_id,
|
|
285
|
+
event_type="message",
|
|
286
|
+
data={
|
|
287
|
+
"role": "user",
|
|
288
|
+
"content": input.prompt,
|
|
289
|
+
"timestamp": user_message_timestamp,
|
|
290
|
+
"message_id": user_message_id,
|
|
291
|
+
"user_id": input.user_id,
|
|
292
|
+
"user_name": getattr(input, "user_name", None),
|
|
293
|
+
"user_email": getattr(input, "user_email", None),
|
|
294
|
+
"user_avatar": getattr(input, "user_avatar", None),
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
print(f" š¤ Published user message to stream (ID: {user_message_id})")
|
|
298
|
+
|
|
299
|
+
def stream_team_run():
|
|
300
|
+
"""Run team with streaming and collect response"""
|
|
301
|
+
# Create event loop for this thread (needed for async streaming)
|
|
302
|
+
loop = asyncio.new_event_loop()
|
|
303
|
+
asyncio.set_event_loop(loop)
|
|
304
|
+
|
|
305
|
+
async def _async_stream():
|
|
306
|
+
"""Async wrapper for streaming execution"""
|
|
307
|
+
import time as time_module
|
|
308
|
+
last_heartbeat_time = time_module.time()
|
|
309
|
+
last_persistence_time = time_module.time()
|
|
310
|
+
heartbeat_interval = 10 # Send heartbeat every 10 seconds
|
|
311
|
+
persistence_interval = 60 # Persist to database every 60 seconds
|
|
312
|
+
|
|
313
|
+
# Track tool_execution_ids for proper start/complete matching
|
|
314
|
+
# Key: "{member_name or 'leader'}_{tool_name}", Value: tool_execution_id
|
|
315
|
+
active_tool_executions: Dict[str, str] = {}
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
# Execute with conversation context
|
|
319
|
+
if conversation_context:
|
|
320
|
+
run_response = team.run(
|
|
321
|
+
input.prompt,
|
|
322
|
+
stream=True,
|
|
323
|
+
messages=conversation_context,
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
run_response = team.run(input.prompt, stream=True)
|
|
327
|
+
|
|
328
|
+
# Process streaming events (sync iteration in async context)
|
|
329
|
+
for event in run_response:
|
|
330
|
+
# Periodic maintenance: heartbeats and persistence
|
|
331
|
+
current_time = time_module.time()
|
|
332
|
+
|
|
333
|
+
# Send heartbeat every 10s (Temporal liveness)
|
|
334
|
+
if current_time - last_heartbeat_time >= heartbeat_interval:
|
|
335
|
+
current_response = streaming_helper.get_full_response()
|
|
336
|
+
activity.heartbeat({
|
|
337
|
+
"status": "Streaming in progress...",
|
|
338
|
+
"response_length": len(current_response),
|
|
339
|
+
"execution_id": execution_id,
|
|
340
|
+
})
|
|
341
|
+
last_heartbeat_time = current_time
|
|
342
|
+
|
|
343
|
+
# Persist snapshot every 60s (resilience against crashes)
|
|
344
|
+
if current_time - last_persistence_time >= persistence_interval:
|
|
345
|
+
current_response = streaming_helper.get_full_response()
|
|
346
|
+
if current_response:
|
|
347
|
+
print(f"\nš¾ Periodic persistence ({len(current_response)} chars)...")
|
|
348
|
+
snapshot_messages = session_history + [{
|
|
349
|
+
"role": "assistant",
|
|
350
|
+
"content": current_response,
|
|
351
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
352
|
+
"message_id": message_id, # Include deterministic message_id
|
|
353
|
+
}]
|
|
354
|
+
try:
|
|
355
|
+
# Best effort - don't fail execution if persistence fails
|
|
356
|
+
self.session_service.persist_session(
|
|
357
|
+
execution_id=execution_id,
|
|
358
|
+
session_id=input.session_id or execution_id,
|
|
359
|
+
user_id=input.user_id,
|
|
360
|
+
messages=snapshot_messages,
|
|
361
|
+
metadata={
|
|
362
|
+
"team_id": input.team_id,
|
|
363
|
+
"organization_id": input.organization_id,
|
|
364
|
+
"snapshot": True,
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
print(f" ā
Session snapshot persisted with message_id: {message_id}")
|
|
368
|
+
except Exception as e:
|
|
369
|
+
print(f" ā ļø Session persistence error: {str(e)} (non-fatal)")
|
|
370
|
+
last_persistence_time = current_time
|
|
371
|
+
|
|
372
|
+
# Handle run_id capture
|
|
373
|
+
streaming_helper.handle_run_id(
|
|
374
|
+
chunk=event,
|
|
375
|
+
on_run_id=lambda run_id: self.cancellation_manager.set_run_id(execution_id, run_id)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Get event type
|
|
379
|
+
event_type = getattr(event, 'event', None)
|
|
380
|
+
|
|
381
|
+
# Handle TEAM LEADER content chunks
|
|
382
|
+
if event_type == "TeamRunContent":
|
|
383
|
+
await streaming_helper.handle_content_chunk(
|
|
384
|
+
chunk=event,
|
|
385
|
+
message_id=message_id,
|
|
386
|
+
print_to_console=True
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Handle MEMBER content chunks (from team members)
|
|
390
|
+
elif event_type == "RunContent":
|
|
391
|
+
# Member agent content chunk
|
|
392
|
+
member_name = getattr(event, 'agent_name', getattr(event, 'member_name', 'Team Member'))
|
|
393
|
+
|
|
394
|
+
if hasattr(event, 'content') and event.content:
|
|
395
|
+
content = str(event.content)
|
|
396
|
+
streaming_helper.handle_member_content_chunk(
|
|
397
|
+
member_name=member_name,
|
|
398
|
+
content=content,
|
|
399
|
+
print_to_console=True
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Handle TEAM LEADER tool calls
|
|
403
|
+
elif event_type == "TeamToolCallStarted":
|
|
404
|
+
# Extract tool name properly
|
|
405
|
+
tool_obj = getattr(event, 'tool', None)
|
|
406
|
+
if tool_obj and hasattr(tool_obj, 'tool_name'):
|
|
407
|
+
tool_name = tool_obj.tool_name
|
|
408
|
+
tool_args = getattr(tool_obj, 'tool_args', {})
|
|
409
|
+
else:
|
|
410
|
+
tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
|
|
411
|
+
tool_args = {}
|
|
412
|
+
|
|
413
|
+
import time
|
|
414
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
415
|
+
|
|
416
|
+
# Store tool_execution_id for matching with completion event
|
|
417
|
+
tool_key = f"leader_{tool_name}"
|
|
418
|
+
active_tool_executions[tool_key] = tool_execution_id
|
|
419
|
+
|
|
420
|
+
print(f"\n š§ Tool Starting: {tool_name} (Team Leader) [ID: {tool_execution_id}]")
|
|
421
|
+
streaming_helper.publish_tool_start(
|
|
422
|
+
tool_name=tool_name,
|
|
423
|
+
tool_execution_id=tool_execution_id,
|
|
424
|
+
tool_args=tool_args,
|
|
425
|
+
source="team_leader",
|
|
426
|
+
member_name=None
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
elif event_type == "TeamToolCallCompleted":
|
|
430
|
+
# Extract tool name and output
|
|
431
|
+
tool_obj = getattr(event, 'tool', None)
|
|
432
|
+
if tool_obj and hasattr(tool_obj, 'tool_name'):
|
|
433
|
+
tool_name = tool_obj.tool_name
|
|
434
|
+
tool_output = getattr(tool_obj, 'result', None) or getattr(event, 'result', None)
|
|
435
|
+
else:
|
|
436
|
+
tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
|
|
437
|
+
tool_output = getattr(event, 'result', None)
|
|
438
|
+
|
|
439
|
+
# Retrieve stored tool_execution_id for matching
|
|
440
|
+
tool_key = f"leader_{tool_name}"
|
|
441
|
+
tool_execution_id = active_tool_executions.get(tool_key)
|
|
442
|
+
|
|
443
|
+
if not tool_execution_id:
|
|
444
|
+
# Fallback: generate new ID if not found (shouldn't happen)
|
|
445
|
+
import time
|
|
446
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
447
|
+
print(f"\n ā ļø Tool completion without matching start event: {tool_name}")
|
|
448
|
+
else:
|
|
449
|
+
# Remove from tracking dict
|
|
450
|
+
del active_tool_executions[tool_key]
|
|
451
|
+
|
|
452
|
+
print(f"\n ā
Tool Completed: {tool_name} (Team Leader) [ID: {tool_execution_id}]")
|
|
453
|
+
streaming_helper.publish_tool_complete(
|
|
454
|
+
tool_name=tool_name,
|
|
455
|
+
tool_execution_id=tool_execution_id,
|
|
456
|
+
status="success",
|
|
457
|
+
output=str(tool_output) if tool_output else None,
|
|
458
|
+
error=None,
|
|
459
|
+
source="team_leader",
|
|
460
|
+
member_name=None
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Handle MEMBER tool calls
|
|
464
|
+
elif event_type == "ToolCallStarted":
|
|
465
|
+
# Extract tool name properly
|
|
466
|
+
tool_obj = getattr(event, 'tool', None)
|
|
467
|
+
if tool_obj and hasattr(tool_obj, 'tool_name'):
|
|
468
|
+
tool_name = tool_obj.tool_name
|
|
469
|
+
tool_args = getattr(tool_obj, 'tool_args', {})
|
|
470
|
+
else:
|
|
471
|
+
tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
|
|
472
|
+
tool_args = {}
|
|
473
|
+
|
|
474
|
+
member_name = getattr(event, 'agent_name', getattr(event, 'member_name', 'Team Member'))
|
|
475
|
+
|
|
476
|
+
import time
|
|
477
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
478
|
+
|
|
479
|
+
# Store tool_execution_id for matching with completion event
|
|
480
|
+
tool_key = f"{member_name}_{tool_name}"
|
|
481
|
+
active_tool_executions[tool_key] = tool_execution_id
|
|
482
|
+
|
|
483
|
+
print(f"\n š§ Tool Starting: {tool_name} ({member_name}) [ID: {tool_execution_id}]")
|
|
484
|
+
streaming_helper.publish_tool_start(
|
|
485
|
+
tool_name=tool_name,
|
|
486
|
+
tool_execution_id=tool_execution_id,
|
|
487
|
+
tool_args=tool_args,
|
|
488
|
+
source="team_member",
|
|
489
|
+
member_name=member_name
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
elif event_type == "ToolCallCompleted":
|
|
493
|
+
# Extract tool name and output
|
|
494
|
+
tool_obj = getattr(event, 'tool', None)
|
|
495
|
+
if tool_obj and hasattr(tool_obj, 'tool_name'):
|
|
496
|
+
tool_name = tool_obj.tool_name
|
|
497
|
+
tool_output = getattr(tool_obj, 'result', None) or getattr(event, 'result', None)
|
|
498
|
+
else:
|
|
499
|
+
tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
|
|
500
|
+
tool_output = getattr(event, 'result', None)
|
|
501
|
+
|
|
502
|
+
member_name = getattr(event, 'agent_name', getattr(event, 'member_name', 'Team Member'))
|
|
503
|
+
|
|
504
|
+
# Retrieve stored tool_execution_id for matching
|
|
505
|
+
tool_key = f"{member_name}_{tool_name}"
|
|
506
|
+
tool_execution_id = active_tool_executions.get(tool_key)
|
|
507
|
+
|
|
508
|
+
if not tool_execution_id:
|
|
509
|
+
# Fallback: generate new ID if not found (shouldn't happen)
|
|
510
|
+
import time
|
|
511
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
512
|
+
print(f"\n ā ļø Tool completion without matching start event: {tool_name} ({member_name})")
|
|
513
|
+
else:
|
|
514
|
+
# Remove from tracking dict
|
|
515
|
+
del active_tool_executions[tool_key]
|
|
516
|
+
|
|
517
|
+
print(f"\n ā
Tool Completed: {tool_name} ({member_name}) [ID: {tool_execution_id}]")
|
|
518
|
+
streaming_helper.publish_tool_complete(
|
|
519
|
+
tool_name=tool_name,
|
|
520
|
+
tool_execution_id=tool_execution_id,
|
|
521
|
+
status="success",
|
|
522
|
+
output=str(tool_output) if tool_output else None,
|
|
523
|
+
error=None,
|
|
524
|
+
source="team_member",
|
|
525
|
+
member_name=member_name
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Finalize streaming (mark any active member as complete)
|
|
529
|
+
streaming_helper.finalize_streaming()
|
|
530
|
+
|
|
531
|
+
print() # New line after streaming
|
|
532
|
+
return run_response
|
|
533
|
+
|
|
534
|
+
except Exception as e:
|
|
535
|
+
print(f"\nā Streaming error: {str(e)}")
|
|
536
|
+
# Fall back to non-streaming
|
|
537
|
+
if conversation_context:
|
|
538
|
+
return team.run(input.prompt, stream=False, messages=conversation_context)
|
|
539
|
+
else:
|
|
540
|
+
return team.run(input.prompt, stream=False)
|
|
541
|
+
|
|
542
|
+
# Run the async function in the event loop
|
|
543
|
+
try:
|
|
544
|
+
return loop.run_until_complete(_async_stream())
|
|
545
|
+
finally:
|
|
546
|
+
loop.close()
|
|
547
|
+
|
|
548
|
+
# Execute in thread pool (no timeout - user controls via STOP button)
|
|
549
|
+
# Wrap in try-except to handle Temporal cancellation
|
|
550
|
+
try:
|
|
551
|
+
result = await asyncio.to_thread(stream_team_run)
|
|
552
|
+
except asyncio.CancelledError:
|
|
553
|
+
# Temporal cancelled the activity - cancel the running team
|
|
554
|
+
print("\nš Cancellation signal received - stopping team execution...")
|
|
555
|
+
cancel_result = self.cancellation_manager.cancel(execution_id)
|
|
556
|
+
if cancel_result["success"]:
|
|
557
|
+
print(f"ā
Team execution cancelled successfully")
|
|
558
|
+
else:
|
|
559
|
+
print(f"ā ļø Cancellation completed with warning: {cancel_result.get('error', 'Unknown')}")
|
|
560
|
+
# Re-raise to let Temporal know we're cancelled
|
|
561
|
+
raise
|
|
562
|
+
|
|
563
|
+
print("ā
Team Execution Completed!")
|
|
564
|
+
full_response = streaming_helper.get_full_response()
|
|
565
|
+
print(f" Response Length: {len(full_response)} chars\n")
|
|
566
|
+
|
|
567
|
+
logger.info(
|
|
568
|
+
"team_execution_completed",
|
|
569
|
+
execution_id=execution_id[:8],
|
|
570
|
+
response_length=len(full_response)
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
# Use the streamed response content
|
|
574
|
+
response_content = full_response if full_response else (result.content if hasattr(result, "content") else str(result))
|
|
575
|
+
|
|
576
|
+
# STEP 8: Extract usage metrics
|
|
577
|
+
usage = {}
|
|
578
|
+
if hasattr(result, "metrics") and result.metrics:
|
|
579
|
+
metrics = result.metrics
|
|
580
|
+
usage = {
|
|
581
|
+
"prompt_tokens": getattr(metrics, "input_tokens", 0),
|
|
582
|
+
"completion_tokens": getattr(metrics, "output_tokens", 0),
|
|
583
|
+
"total_tokens": getattr(metrics, "total_tokens", 0),
|
|
584
|
+
}
|
|
585
|
+
print(f"š Token Usage:")
|
|
586
|
+
print(f" Input: {usage.get('prompt_tokens', 0)}")
|
|
587
|
+
print(f" Output: {usage.get('completion_tokens', 0)}")
|
|
588
|
+
print(f" Total: {usage.get('total_tokens', 0)}\n")
|
|
589
|
+
|
|
590
|
+
# STEP 9: Persist complete session history
|
|
591
|
+
print("\nš¾ Persisting session history to Control Plane...")
|
|
592
|
+
|
|
593
|
+
# Build message_ids dict to pass to extract_messages_from_result
|
|
594
|
+
# This ensures persisted messages use the SAME IDs as streaming messages
|
|
595
|
+
message_ids_map = {
|
|
596
|
+
len(session_history): user_message_id, # User message index
|
|
597
|
+
len(session_history) + 1: message_id # Assistant message index
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
# Extract messages from Agno result to get accurate timestamps
|
|
601
|
+
# Agno tracks message timestamps as they're created (msg.created_at)
|
|
602
|
+
extracted_messages = self.session_service.extract_messages_from_result(
|
|
603
|
+
result=result,
|
|
604
|
+
user_id=input.user_id,
|
|
605
|
+
execution_id=execution_id,
|
|
606
|
+
message_ids=message_ids_map # Pass deterministic IDs
|
|
607
|
+
)
|
|
608
|
+
print(f" š Extracted {len(extracted_messages)} messages with deterministic IDs")
|
|
609
|
+
|
|
610
|
+
# Use extracted messages which have proper timestamps from Agno
|
|
611
|
+
# These include both user and assistant messages with accurate created_at times
|
|
612
|
+
new_messages = extracted_messages
|
|
613
|
+
|
|
614
|
+
# Fallback: if no messages extracted (shouldn't happen), create them manually
|
|
615
|
+
if not extracted_messages:
|
|
616
|
+
from datetime import datetime, timezone
|
|
617
|
+
current_timestamp = datetime.now(timezone.utc).isoformat()
|
|
618
|
+
print(" ā ļø No messages extracted from Agno result, creating manually")
|
|
619
|
+
new_messages = [
|
|
620
|
+
{
|
|
621
|
+
"role": "user",
|
|
622
|
+
"content": input.prompt,
|
|
623
|
+
"timestamp": current_timestamp,
|
|
624
|
+
"message_id": f"{execution_id}_user_{turn_number}",
|
|
625
|
+
"user_id": input.user_id,
|
|
626
|
+
"user_name": getattr(input, "user_name", None),
|
|
627
|
+
"user_email": getattr(input, "user_email", None),
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
"role": "assistant",
|
|
631
|
+
"content": response_content,
|
|
632
|
+
"timestamp": current_timestamp,
|
|
633
|
+
"message_id": message_id, # Use the same message_id as streaming
|
|
634
|
+
},
|
|
635
|
+
]
|
|
636
|
+
|
|
637
|
+
# Extract tool messages from streaming helper
|
|
638
|
+
tool_messages = streaming_helper.get_tool_messages()
|
|
639
|
+
print(f" š Collected {len(tool_messages)} tool messages during streaming")
|
|
640
|
+
|
|
641
|
+
# Combine with previous history: session_history + new_messages + tool_messages
|
|
642
|
+
complete_session = session_history + new_messages + tool_messages
|
|
643
|
+
|
|
644
|
+
# CRITICAL: Deduplicate messages by message_id AND content to prevent duplicates
|
|
645
|
+
# Use session_service.deduplicate_messages() which has enhanced two-level deduplication
|
|
646
|
+
original_count = len(complete_session)
|
|
647
|
+
complete_session = self.session_service.deduplicate_messages(complete_session)
|
|
648
|
+
|
|
649
|
+
# CRITICAL: Sort by timestamp to ensure chronological order
|
|
650
|
+
# Tool messages happen DURING streaming, so they need to be interleaved with user/assistant messages
|
|
651
|
+
complete_session.sort(key=lambda msg: msg.get("timestamp", ""))
|
|
652
|
+
print(f" ā
Messages deduplicated ({original_count} -> {len(complete_session)}) and sorted by timestamp")
|
|
653
|
+
|
|
654
|
+
if complete_session:
|
|
655
|
+
success = self.session_service.persist_session(
|
|
656
|
+
execution_id=execution_id,
|
|
657
|
+
session_id=input.session_id or execution_id,
|
|
658
|
+
user_id=input.user_id,
|
|
659
|
+
messages=complete_session,
|
|
660
|
+
metadata={
|
|
661
|
+
"team_id": input.team_id,
|
|
662
|
+
"organization_id": input.organization_id,
|
|
663
|
+
"turn_count": len(complete_session),
|
|
664
|
+
"member_count": len(team_members),
|
|
665
|
+
}
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
if success:
|
|
669
|
+
print(f" ā
Session persisted ({len(complete_session)} total messages)")
|
|
670
|
+
else:
|
|
671
|
+
print(f" ā ļø Session persistence failed")
|
|
672
|
+
else:
|
|
673
|
+
print(" ā¹ļø No messages to persist")
|
|
674
|
+
|
|
675
|
+
print("\n" + "="*80)
|
|
676
|
+
print("š TEAM EXECUTION END")
|
|
677
|
+
print("="*80 + "\n")
|
|
678
|
+
|
|
679
|
+
# STEP 10: Cleanup
|
|
680
|
+
self.cancellation_manager.unregister(execution_id)
|
|
681
|
+
|
|
682
|
+
from datetime import datetime, timezone
|
|
683
|
+
return {
|
|
684
|
+
"success": True,
|
|
685
|
+
"response": response_content,
|
|
686
|
+
"response_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
687
|
+
"usage": usage,
|
|
688
|
+
"model": model,
|
|
689
|
+
"finish_reason": "stop",
|
|
690
|
+
"team_member_count": len(team_members),
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
except Exception as e:
|
|
694
|
+
# Cleanup on error
|
|
695
|
+
self.cancellation_manager.unregister(execution_id)
|
|
696
|
+
|
|
697
|
+
print("\n" + "="*80)
|
|
698
|
+
print("ā TEAM EXECUTION FAILED")
|
|
699
|
+
print("="*80)
|
|
700
|
+
print(f"Error: {str(e)}")
|
|
701
|
+
print("="*80 + "\n")
|
|
702
|
+
|
|
703
|
+
logger.error(
|
|
704
|
+
"team_execution_failed",
|
|
705
|
+
execution_id=execution_id[:8],
|
|
706
|
+
error=str(e)
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
"success": False,
|
|
711
|
+
"error": str(e),
|
|
712
|
+
"model": input.model_id,
|
|
713
|
+
"usage": None,
|
|
714
|
+
"finish_reason": "error",
|
|
715
|
+
}
|