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,370 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Runtime Server Manager
|
|
3
|
+
|
|
4
|
+
Manages the agent-runtime server process lifecycle.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import subprocess
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
import aiohttp
|
|
16
|
+
import structlog
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
logger = structlog.get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ServerConfig:
|
|
24
|
+
"""Configuration for agent-runtime server."""
|
|
25
|
+
grpc_port: int = 50052
|
|
26
|
+
http_port: int = 8082
|
|
27
|
+
health_port: int = 8083
|
|
28
|
+
config_dir: Path = Path.home() / ".kubiya"
|
|
29
|
+
log_level: str = "info"
|
|
30
|
+
database_url: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AgentRuntimeServer:
|
|
34
|
+
"""Manages agent-runtime server process."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, binary_path: Path, config: ServerConfig):
|
|
37
|
+
"""
|
|
38
|
+
Initialize server manager.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
binary_path: Path to agent-runtime binary
|
|
42
|
+
config: Server configuration
|
|
43
|
+
"""
|
|
44
|
+
self.binary_path = binary_path
|
|
45
|
+
self.config = config
|
|
46
|
+
self.process: Optional[subprocess.Popen] = None
|
|
47
|
+
self.pid_file = config.config_dir / "agent-runtime.pid"
|
|
48
|
+
self.log_file = config.config_dir / "logs" / "agent-runtime.log"
|
|
49
|
+
self.config_file = config.config_dir / "agent-runtime.yaml"
|
|
50
|
+
|
|
51
|
+
# Create directories
|
|
52
|
+
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
# GRPC address for clients
|
|
55
|
+
self.grpc_address = f"localhost:{config.grpc_port}"
|
|
56
|
+
|
|
57
|
+
async def start(self, wait_for_health: bool = True, timeout: int = 30) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Start agent-runtime server.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
wait_for_health: Whether to wait for health check
|
|
63
|
+
timeout: Health check timeout in seconds
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if started successfully
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
RuntimeError: If server fails to start
|
|
70
|
+
"""
|
|
71
|
+
logger.info("starting_agent_runtime_server",
|
|
72
|
+
grpc_port=self.config.grpc_port,
|
|
73
|
+
http_port=self.config.http_port)
|
|
74
|
+
|
|
75
|
+
# Check if already running
|
|
76
|
+
if self._is_running():
|
|
77
|
+
logger.warning("server_already_running", pid=self._get_pid())
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
# Generate configuration file
|
|
81
|
+
self._generate_config()
|
|
82
|
+
|
|
83
|
+
# Build command
|
|
84
|
+
cmd = [
|
|
85
|
+
str(self.binary_path),
|
|
86
|
+
"--grpc-port", str(self.config.grpc_port),
|
|
87
|
+
"--http-port", str(self.config.http_port),
|
|
88
|
+
"--health-port", str(self.config.health_port),
|
|
89
|
+
"--config", str(self.config_file),
|
|
90
|
+
"--log-level", self.config.log_level,
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Build environment
|
|
94
|
+
env = os.environ.copy()
|
|
95
|
+
env["AGENT_RUNTIME_BASE_DIR"] = str(self.config.config_dir)
|
|
96
|
+
|
|
97
|
+
if self.config.database_url:
|
|
98
|
+
env["DATABASE_URL"] = self.config.database_url
|
|
99
|
+
else:
|
|
100
|
+
# Default SQLite database
|
|
101
|
+
db_path = self.config.config_dir / "agent-runtime.db"
|
|
102
|
+
env["DATABASE_URL"] = f"sqlite:{db_path}"
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# Start process
|
|
106
|
+
with open(self.log_file, 'a') as log_f:
|
|
107
|
+
self.process = subprocess.Popen(
|
|
108
|
+
cmd,
|
|
109
|
+
env=env,
|
|
110
|
+
stdout=log_f,
|
|
111
|
+
stderr=subprocess.STDOUT,
|
|
112
|
+
start_new_session=True, # Detach from parent
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Save PID
|
|
116
|
+
self._save_pid(self.process.pid)
|
|
117
|
+
|
|
118
|
+
logger.info("server_started", pid=self.process.pid, log_file=str(self.log_file))
|
|
119
|
+
|
|
120
|
+
# Wait for health check
|
|
121
|
+
if wait_for_health:
|
|
122
|
+
if not await self.wait_for_health(timeout):
|
|
123
|
+
self.stop()
|
|
124
|
+
raise RuntimeError("Server failed health check")
|
|
125
|
+
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error("failed_to_start_server", error=str(e))
|
|
130
|
+
raise RuntimeError(f"Failed to start server: {e}")
|
|
131
|
+
|
|
132
|
+
def stop(self, timeout: int = 10) -> bool:
|
|
133
|
+
"""
|
|
134
|
+
Stop agent-runtime server gracefully.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
timeout: Graceful shutdown timeout in seconds
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if stopped successfully
|
|
141
|
+
"""
|
|
142
|
+
logger.info("stopping_agent_runtime_server")
|
|
143
|
+
|
|
144
|
+
pid = self._get_pid()
|
|
145
|
+
if not pid:
|
|
146
|
+
logger.warning("no_pid_found")
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
# Check if process exists
|
|
151
|
+
try:
|
|
152
|
+
os.kill(pid, 0)
|
|
153
|
+
except OSError:
|
|
154
|
+
logger.info("process_not_running", pid=pid)
|
|
155
|
+
self._cleanup_pid_file()
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
# Send SIGTERM for graceful shutdown
|
|
159
|
+
logger.info("sending_sigterm", pid=pid)
|
|
160
|
+
os.kill(pid, signal.SIGTERM)
|
|
161
|
+
|
|
162
|
+
# Wait for process to exit
|
|
163
|
+
start_time = time.time()
|
|
164
|
+
while time.time() - start_time < timeout:
|
|
165
|
+
try:
|
|
166
|
+
os.kill(pid, 0)
|
|
167
|
+
time.sleep(0.1)
|
|
168
|
+
except OSError:
|
|
169
|
+
logger.info("server_stopped_gracefully", pid=pid)
|
|
170
|
+
self._cleanup_pid_file()
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
# Force kill if still running
|
|
174
|
+
logger.warning("forcing_kill", pid=pid)
|
|
175
|
+
os.kill(pid, signal.SIGKILL)
|
|
176
|
+
time.sleep(0.5)
|
|
177
|
+
|
|
178
|
+
self._cleanup_pid_file()
|
|
179
|
+
logger.info("server_stopped_forcefully", pid=pid)
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error("error_stopping_server", error=str(e), pid=pid)
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
async def wait_for_health(self, timeout: int = 30) -> bool:
|
|
187
|
+
"""
|
|
188
|
+
Wait for server to become healthy.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
timeout: Maximum time to wait in seconds
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
True if healthy, False if timeout
|
|
195
|
+
"""
|
|
196
|
+
logger.info("waiting_for_health_check", timeout=timeout)
|
|
197
|
+
|
|
198
|
+
url = f"http://localhost:{self.config.http_port}/health"
|
|
199
|
+
start_time = time.time()
|
|
200
|
+
attempt = 0
|
|
201
|
+
|
|
202
|
+
while time.time() - start_time < timeout:
|
|
203
|
+
attempt += 1
|
|
204
|
+
try:
|
|
205
|
+
async with aiohttp.ClientSession() as session:
|
|
206
|
+
async with session.get(url, timeout=aiohttp.ClientTimeout(total=2)) as resp:
|
|
207
|
+
if resp.status == 200:
|
|
208
|
+
data = await resp.json()
|
|
209
|
+
if data.get("status") == "healthy":
|
|
210
|
+
logger.info("server_healthy", attempts=attempt)
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.debug("health_check_failed", attempt=attempt, error=str(e))
|
|
215
|
+
|
|
216
|
+
await asyncio.sleep(0.5)
|
|
217
|
+
|
|
218
|
+
logger.error("health_check_timeout", timeout=timeout, attempts=attempt)
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
async def health_check(self) -> bool:
|
|
222
|
+
"""
|
|
223
|
+
Check if server is currently healthy.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if healthy, False otherwise
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
url = f"http://localhost:{self.config.http_port}/health"
|
|
230
|
+
async with aiohttp.ClientSession() as session:
|
|
231
|
+
async with session.get(url, timeout=aiohttp.ClientTimeout(total=2)) as resp:
|
|
232
|
+
if resp.status == 200:
|
|
233
|
+
data = await resp.json()
|
|
234
|
+
return data.get("status") == "healthy"
|
|
235
|
+
return False
|
|
236
|
+
except Exception:
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
def _is_running(self) -> bool:
|
|
240
|
+
"""Check if server is running."""
|
|
241
|
+
pid = self._get_pid()
|
|
242
|
+
if not pid:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
os.kill(pid, 0)
|
|
247
|
+
return True
|
|
248
|
+
except OSError:
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def _get_pid(self) -> Optional[int]:
|
|
252
|
+
"""Get PID from file."""
|
|
253
|
+
if not self.pid_file.exists():
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
return int(self.pid_file.read_text().strip())
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error("failed_to_read_pid", error=str(e))
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
def _save_pid(self, pid: int):
|
|
263
|
+
"""Save PID to file."""
|
|
264
|
+
try:
|
|
265
|
+
self.pid_file.write_text(str(pid))
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error("failed_to_save_pid", error=str(e))
|
|
268
|
+
|
|
269
|
+
def _cleanup_pid_file(self):
|
|
270
|
+
"""Remove PID file."""
|
|
271
|
+
try:
|
|
272
|
+
if self.pid_file.exists():
|
|
273
|
+
self.pid_file.unlink()
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error("failed_to_cleanup_pid_file", error=str(e))
|
|
276
|
+
|
|
277
|
+
def _generate_config(self):
|
|
278
|
+
"""Generate agent-runtime configuration file."""
|
|
279
|
+
# Look for Claude Code binary (future: download/manage it too)
|
|
280
|
+
claude_code_path = self._find_claude_code_binary()
|
|
281
|
+
|
|
282
|
+
config = {
|
|
283
|
+
"server": {
|
|
284
|
+
"grpc_port": self.config.grpc_port,
|
|
285
|
+
"http_port": self.config.http_port,
|
|
286
|
+
"health_port": self.config.health_port,
|
|
287
|
+
},
|
|
288
|
+
"runtimes": [],
|
|
289
|
+
"logging": {
|
|
290
|
+
"level": self.config.log_level,
|
|
291
|
+
"format": "json",
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Add Claude Code runtime if found
|
|
296
|
+
if claude_code_path:
|
|
297
|
+
config["runtimes"].append({
|
|
298
|
+
"name": "claude-code",
|
|
299
|
+
"runtime_type": "binary",
|
|
300
|
+
"executable": str(claude_code_path),
|
|
301
|
+
"enabled": True,
|
|
302
|
+
})
|
|
303
|
+
else:
|
|
304
|
+
# Add Python example runtime as fallback
|
|
305
|
+
python_runtime_path = self.config.config_dir / "runtimes" / "python-example" / "main.py"
|
|
306
|
+
if python_runtime_path.exists():
|
|
307
|
+
config["runtimes"].append({
|
|
308
|
+
"name": "claude-code",
|
|
309
|
+
"runtime_type": "python",
|
|
310
|
+
"executable": str(python_runtime_path),
|
|
311
|
+
"enabled": True,
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
# Write config
|
|
315
|
+
with open(self.config_file, 'w') as f:
|
|
316
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
317
|
+
|
|
318
|
+
logger.info("config_generated", path=str(self.config_file), runtimes=len(config["runtimes"]))
|
|
319
|
+
|
|
320
|
+
def _find_claude_code_binary(self) -> Optional[Path]:
|
|
321
|
+
"""Try to find Claude Code binary."""
|
|
322
|
+
# Check common locations
|
|
323
|
+
possible_paths = [
|
|
324
|
+
Path.home() / ".kubiya" / "bin" / "claude-code",
|
|
325
|
+
Path("/usr/local/bin/claude-code"),
|
|
326
|
+
Path("/opt/claude-code/claude-code"),
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
for path in possible_paths:
|
|
330
|
+
if path.exists() and os.access(path, os.X_OK):
|
|
331
|
+
logger.info("found_claude_code_binary", path=str(path))
|
|
332
|
+
return path
|
|
333
|
+
|
|
334
|
+
logger.warning("claude_code_binary_not_found")
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
def get_status(self) -> dict:
|
|
338
|
+
"""Get server status information."""
|
|
339
|
+
pid = self._get_pid()
|
|
340
|
+
is_running = self._is_running()
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
"running": is_running,
|
|
344
|
+
"pid": pid if is_running else None,
|
|
345
|
+
"grpc_address": self.grpc_address,
|
|
346
|
+
"http_port": self.config.http_port,
|
|
347
|
+
"config_file": str(self.config_file),
|
|
348
|
+
"log_file": str(self.log_file),
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
def get_logs(self, lines: int = 50) -> str:
|
|
352
|
+
"""
|
|
353
|
+
Get recent log lines.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
lines: Number of lines to return
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Recent log content
|
|
360
|
+
"""
|
|
361
|
+
if not self.log_file.exists():
|
|
362
|
+
return ""
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
with open(self.log_file, 'r') as f:
|
|
366
|
+
all_lines = f.readlines()
|
|
367
|
+
return ''.join(all_lines[-lines:])
|
|
368
|
+
except Exception as e:
|
|
369
|
+
logger.error("failed_to_read_logs", error=str(e))
|
|
370
|
+
return ""
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Binary Manager for Agent Runtime
|
|
3
|
+
|
|
4
|
+
Handles downloading, verifying, and managing the agent-runtime binary.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import hashlib
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import shutil
|
|
12
|
+
import tarfile
|
|
13
|
+
import tempfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional, Tuple
|
|
16
|
+
import aiohttp
|
|
17
|
+
import structlog
|
|
18
|
+
from packaging import version
|
|
19
|
+
|
|
20
|
+
logger = structlog.get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
GITHUB_REPO = "kubiya-ai/agent-runtime"
|
|
23
|
+
GITHUB_API_BASE = f"https://api.github.com/repos/{GITHUB_REPO}"
|
|
24
|
+
GITHUB_RELEASES_BASE = f"https://github.com/{GITHUB_REPO}/releases/download"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BinaryManager:
|
|
28
|
+
"""Manages agent-runtime binary lifecycle."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, config_dir: Path):
|
|
31
|
+
"""
|
|
32
|
+
Initialize binary manager.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config_dir: Configuration directory (e.g., ~/.kubiya)
|
|
36
|
+
"""
|
|
37
|
+
self.config_dir = Path(config_dir)
|
|
38
|
+
self.binary_dir = self.config_dir / "bin"
|
|
39
|
+
self.agent_runtime_path = self.binary_dir / "agent-runtime"
|
|
40
|
+
self.version_file = self.binary_dir / ".agent-runtime-version"
|
|
41
|
+
|
|
42
|
+
# Create directories
|
|
43
|
+
self.binary_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
async def ensure_binary(self, version: str = "latest") -> Path:
|
|
46
|
+
"""
|
|
47
|
+
Ensure agent-runtime binary is available.
|
|
48
|
+
|
|
49
|
+
Downloads binary if missing or outdated.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
version: Version to ensure (e.g., "v0.1.0" or "latest")
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Path to agent-runtime binary
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RuntimeError: If binary cannot be obtained
|
|
59
|
+
"""
|
|
60
|
+
logger.info("ensuring_agent_runtime_binary", version=version, path=str(self.agent_runtime_path))
|
|
61
|
+
|
|
62
|
+
# Check if binary exists and is valid
|
|
63
|
+
if self._is_binary_valid():
|
|
64
|
+
current_version = self._get_current_version()
|
|
65
|
+
logger.info("binary_found", version=current_version)
|
|
66
|
+
|
|
67
|
+
# If requesting latest, check for updates
|
|
68
|
+
if version == "latest":
|
|
69
|
+
latest_version = await self._fetch_latest_version()
|
|
70
|
+
if latest_version and self._should_update(current_version, latest_version):
|
|
71
|
+
logger.info("update_available", current=current_version, latest=latest_version)
|
|
72
|
+
await self._download_binary(latest_version)
|
|
73
|
+
return self.agent_runtime_path
|
|
74
|
+
|
|
75
|
+
# If requesting specific version, check if we have it
|
|
76
|
+
if version != "latest" and current_version != version:
|
|
77
|
+
logger.info("different_version_requested", current=current_version, requested=version)
|
|
78
|
+
await self._download_binary(version)
|
|
79
|
+
|
|
80
|
+
return self.agent_runtime_path
|
|
81
|
+
|
|
82
|
+
# Binary doesn't exist or is invalid, download it
|
|
83
|
+
logger.info("binary_not_found", version=version)
|
|
84
|
+
if version == "latest":
|
|
85
|
+
version = await self._fetch_latest_version()
|
|
86
|
+
|
|
87
|
+
await self._download_binary(version)
|
|
88
|
+
return self.agent_runtime_path
|
|
89
|
+
|
|
90
|
+
def _is_binary_valid(self) -> bool:
|
|
91
|
+
"""Check if binary exists and is executable."""
|
|
92
|
+
if not self.agent_runtime_path.exists():
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
if not os.access(self.agent_runtime_path, os.X_OK):
|
|
96
|
+
logger.warning("binary_not_executable", path=str(self.agent_runtime_path))
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
def _get_current_version(self) -> Optional[str]:
|
|
102
|
+
"""Get currently installed version."""
|
|
103
|
+
if not self.version_file.exists():
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
return self.version_file.read_text().strip()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error("failed_to_read_version_file", error=str(e))
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def _save_version(self, ver: str):
|
|
113
|
+
"""Save installed version to file."""
|
|
114
|
+
try:
|
|
115
|
+
self.version_file.write_text(ver)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error("failed_to_save_version", error=str(e))
|
|
118
|
+
|
|
119
|
+
async def _fetch_latest_version(self) -> str:
|
|
120
|
+
"""Fetch latest version from GitHub."""
|
|
121
|
+
try:
|
|
122
|
+
async with aiohttp.ClientSession() as session:
|
|
123
|
+
url = f"{GITHUB_API_BASE}/releases/latest"
|
|
124
|
+
async with session.get(url) as resp:
|
|
125
|
+
if resp.status != 200:
|
|
126
|
+
logger.error("failed_to_fetch_latest_version", status=resp.status)
|
|
127
|
+
raise RuntimeError(f"Failed to fetch latest version: {resp.status}")
|
|
128
|
+
|
|
129
|
+
data = await resp.json()
|
|
130
|
+
latest = data["tag_name"]
|
|
131
|
+
logger.info("fetched_latest_version", version=latest)
|
|
132
|
+
return latest
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error("error_fetching_latest_version", error=str(e))
|
|
136
|
+
raise RuntimeError(f"Failed to fetch latest version: {e}")
|
|
137
|
+
|
|
138
|
+
def _should_update(self, current: Optional[str], latest: str) -> bool:
|
|
139
|
+
"""Check if should update to latest version."""
|
|
140
|
+
if not current:
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
# Remove 'v' prefix for comparison
|
|
145
|
+
current_ver = version.parse(current.lstrip('v'))
|
|
146
|
+
latest_ver = version.parse(latest.lstrip('v'))
|
|
147
|
+
return latest_ver > current_ver
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error("version_comparison_failed", error=str(e))
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
async def _download_binary(self, ver: str):
|
|
153
|
+
"""
|
|
154
|
+
Download and install binary for specified version.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
ver: Version to download (e.g., "v0.1.0")
|
|
158
|
+
"""
|
|
159
|
+
logger.info("downloading_binary", version=ver)
|
|
160
|
+
|
|
161
|
+
# Detect platform and architecture
|
|
162
|
+
platform_name, arch = self._detect_platform()
|
|
163
|
+
target = f"{arch}-{platform_name}"
|
|
164
|
+
|
|
165
|
+
# Construct download URL
|
|
166
|
+
filename = f"agent-runtime-{ver}-{target}.tar.gz"
|
|
167
|
+
download_url = f"{GITHUB_RELEASES_BASE}/{ver}/{filename}"
|
|
168
|
+
checksum_url = f"{download_url}.sha256"
|
|
169
|
+
|
|
170
|
+
logger.info("download_url", url=download_url)
|
|
171
|
+
|
|
172
|
+
# Download to temporary file
|
|
173
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
174
|
+
temp_path = Path(temp_dir)
|
|
175
|
+
archive_path = temp_path / filename
|
|
176
|
+
checksum_path = temp_path / f"{filename}.sha256"
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
# Download archive
|
|
180
|
+
await self._download_file(download_url, archive_path)
|
|
181
|
+
logger.info("downloaded_archive", size=archive_path.stat().st_size)
|
|
182
|
+
|
|
183
|
+
# Download checksum
|
|
184
|
+
await self._download_file(checksum_url, checksum_path)
|
|
185
|
+
|
|
186
|
+
# Verify checksum
|
|
187
|
+
if not self._verify_checksum(archive_path, checksum_path):
|
|
188
|
+
raise RuntimeError("Checksum verification failed")
|
|
189
|
+
|
|
190
|
+
# Extract binary
|
|
191
|
+
self._extract_binary(archive_path)
|
|
192
|
+
|
|
193
|
+
# Make executable
|
|
194
|
+
os.chmod(self.agent_runtime_path, 0o755)
|
|
195
|
+
|
|
196
|
+
# Save version
|
|
197
|
+
self._save_version(ver)
|
|
198
|
+
|
|
199
|
+
logger.info("binary_installed_successfully", version=ver, path=str(self.agent_runtime_path))
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error("binary_download_failed", error=str(e))
|
|
203
|
+
raise RuntimeError(f"Failed to download binary: {e}")
|
|
204
|
+
|
|
205
|
+
def _detect_platform(self) -> Tuple[str, str]:
|
|
206
|
+
"""
|
|
207
|
+
Detect current platform and architecture.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Tuple of (platform, architecture) for download URL
|
|
211
|
+
"""
|
|
212
|
+
system = platform.system().lower()
|
|
213
|
+
machine = platform.machine().lower()
|
|
214
|
+
|
|
215
|
+
# Map platform
|
|
216
|
+
if system == "darwin":
|
|
217
|
+
platform_name = "apple-darwin"
|
|
218
|
+
elif system == "linux":
|
|
219
|
+
platform_name = "unknown-linux-gnu"
|
|
220
|
+
else:
|
|
221
|
+
raise RuntimeError(f"Unsupported platform: {system}")
|
|
222
|
+
|
|
223
|
+
# Map architecture
|
|
224
|
+
if machine in ("x86_64", "amd64"):
|
|
225
|
+
arch = "x86_64"
|
|
226
|
+
elif machine in ("arm64", "aarch64"):
|
|
227
|
+
arch = "aarch64"
|
|
228
|
+
else:
|
|
229
|
+
raise RuntimeError(f"Unsupported architecture: {machine}")
|
|
230
|
+
|
|
231
|
+
logger.info("detected_platform", platform=platform_name, arch=arch)
|
|
232
|
+
return platform_name, arch
|
|
233
|
+
|
|
234
|
+
async def _download_file(self, url: str, dest: Path, chunk_size: int = 8192):
|
|
235
|
+
"""Download file with progress."""
|
|
236
|
+
async with aiohttp.ClientSession() as session:
|
|
237
|
+
async with session.get(url) as resp:
|
|
238
|
+
if resp.status != 200:
|
|
239
|
+
raise RuntimeError(f"Download failed: {resp.status}")
|
|
240
|
+
|
|
241
|
+
total_size = int(resp.headers.get('content-length', 0))
|
|
242
|
+
downloaded = 0
|
|
243
|
+
|
|
244
|
+
with open(dest, 'wb') as f:
|
|
245
|
+
async for chunk in resp.content.iter_chunked(chunk_size):
|
|
246
|
+
f.write(chunk)
|
|
247
|
+
downloaded += len(chunk)
|
|
248
|
+
|
|
249
|
+
if total_size > 0:
|
|
250
|
+
progress = (downloaded / total_size) * 100
|
|
251
|
+
logger.debug("download_progress", progress=f"{progress:.1f}%")
|
|
252
|
+
|
|
253
|
+
def _verify_checksum(self, archive_path: Path, checksum_path: Path) -> bool:
|
|
254
|
+
"""Verify archive checksum."""
|
|
255
|
+
try:
|
|
256
|
+
# Read expected checksum
|
|
257
|
+
checksum_content = checksum_path.read_text().strip()
|
|
258
|
+
expected_checksum = checksum_content.split()[0]
|
|
259
|
+
|
|
260
|
+
# Calculate actual checksum
|
|
261
|
+
sha256 = hashlib.sha256()
|
|
262
|
+
with open(archive_path, 'rb') as f:
|
|
263
|
+
for chunk in iter(lambda: f.read(8192), b''):
|
|
264
|
+
sha256.update(chunk)
|
|
265
|
+
|
|
266
|
+
actual_checksum = sha256.hexdigest()
|
|
267
|
+
|
|
268
|
+
if actual_checksum != expected_checksum:
|
|
269
|
+
logger.error("checksum_mismatch", expected=expected_checksum, actual=actual_checksum)
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
logger.info("checksum_verified")
|
|
273
|
+
return True
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.error("checksum_verification_failed", error=str(e))
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def _extract_binary(self, archive_path: Path):
|
|
280
|
+
"""Extract binary from tar.gz archive."""
|
|
281
|
+
try:
|
|
282
|
+
with tarfile.open(archive_path, 'r:gz') as tar:
|
|
283
|
+
# Extract all files
|
|
284
|
+
tar.extractall(path=self.binary_dir)
|
|
285
|
+
|
|
286
|
+
logger.info("binary_extracted", path=str(self.binary_dir))
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error("extraction_failed", error=str(e))
|
|
290
|
+
raise RuntimeError(f"Failed to extract archive: {e}")
|
|
291
|
+
|
|
292
|
+
async def check_for_updates(self) -> Optional[str]:
|
|
293
|
+
"""
|
|
294
|
+
Check if newer version is available.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
New version string if available, None otherwise
|
|
298
|
+
"""
|
|
299
|
+
current = self._get_current_version()
|
|
300
|
+
if not current:
|
|
301
|
+
return await self._fetch_latest_version()
|
|
302
|
+
|
|
303
|
+
latest = await self._fetch_latest_version()
|
|
304
|
+
if self._should_update(current, latest):
|
|
305
|
+
return latest
|
|
306
|
+
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
async def update_binary(self) -> bool:
|
|
310
|
+
"""
|
|
311
|
+
Update to latest version.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
True if updated, False if already on latest
|
|
315
|
+
"""
|
|
316
|
+
latest = await self.check_for_updates()
|
|
317
|
+
if not latest:
|
|
318
|
+
logger.info("already_on_latest_version")
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
logger.info("updating_to_latest", version=latest)
|
|
322
|
+
await self._download_binary(latest)
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
def get_binary_info(self) -> dict:
|
|
326
|
+
"""Get information about installed binary."""
|
|
327
|
+
return {
|
|
328
|
+
"path": str(self.agent_runtime_path),
|
|
329
|
+
"exists": self.agent_runtime_path.exists(),
|
|
330
|
+
"executable": os.access(self.agent_runtime_path, os.X_OK) if self.agent_runtime_path.exists() else False,
|
|
331
|
+
"version": self._get_current_version(),
|
|
332
|
+
"size": self.agent_runtime_path.stat().st_size if self.agent_runtime_path.exists() else 0,
|
|
333
|
+
}
|