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.
Files changed (479) hide show
  1. control_plane_api/LICENSE +676 -0
  2. control_plane_api/README.md +350 -0
  3. control_plane_api/__init__.py +4 -0
  4. control_plane_api/__version__.py +8 -0
  5. control_plane_api/alembic/README +1 -0
  6. control_plane_api/alembic/env.py +121 -0
  7. control_plane_api/alembic/script.py.mako +28 -0
  8. control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
  9. control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
  10. control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
  11. control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
  12. control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
  13. control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
  14. control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
  15. control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
  16. control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
  17. control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
  18. control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
  19. control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
  20. control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
  21. control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
  22. control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
  23. control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
  24. control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
  25. control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
  26. control_plane_api/alembic.ini +148 -0
  27. control_plane_api/api/index.py +12 -0
  28. control_plane_api/app/__init__.py +11 -0
  29. control_plane_api/app/activities/__init__.py +20 -0
  30. control_plane_api/app/activities/agent_activities.py +384 -0
  31. control_plane_api/app/activities/plan_generation_activities.py +499 -0
  32. control_plane_api/app/activities/team_activities.py +424 -0
  33. control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
  34. control_plane_api/app/config/__init__.py +35 -0
  35. control_plane_api/app/config/api_config.py +469 -0
  36. control_plane_api/app/config/config_loader.py +224 -0
  37. control_plane_api/app/config/model_pricing.py +323 -0
  38. control_plane_api/app/config/storage_config.py +159 -0
  39. control_plane_api/app/config.py +115 -0
  40. control_plane_api/app/controllers/__init__.py +0 -0
  41. control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
  42. control_plane_api/app/database.py +135 -0
  43. control_plane_api/app/exceptions.py +408 -0
  44. control_plane_api/app/lib/__init__.py +11 -0
  45. control_plane_api/app/lib/environment.py +65 -0
  46. control_plane_api/app/lib/event_bus/__init__.py +17 -0
  47. control_plane_api/app/lib/event_bus/base.py +136 -0
  48. control_plane_api/app/lib/event_bus/manager.py +335 -0
  49. control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
  50. control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
  51. control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
  52. control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
  53. control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
  54. control_plane_api/app/lib/job_executor.py +330 -0
  55. control_plane_api/app/lib/kubiya_client.py +293 -0
  56. control_plane_api/app/lib/litellm_pricing.py +166 -0
  57. control_plane_api/app/lib/mcp_validation.py +163 -0
  58. control_plane_api/app/lib/nats/__init__.py +13 -0
  59. control_plane_api/app/lib/nats/credentials_manager.py +288 -0
  60. control_plane_api/app/lib/nats/listener.py +374 -0
  61. control_plane_api/app/lib/planning_prompt_builder.py +153 -0
  62. control_plane_api/app/lib/planning_tools/__init__.py +41 -0
  63. control_plane_api/app/lib/planning_tools/agents.py +409 -0
  64. control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
  65. control_plane_api/app/lib/planning_tools/base.py +119 -0
  66. control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
  67. control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
  68. control_plane_api/app/lib/planning_tools/environments.py +218 -0
  69. control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
  70. control_plane_api/app/lib/planning_tools/models.py +93 -0
  71. control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
  72. control_plane_api/app/lib/planning_tools/resources.py +242 -0
  73. control_plane_api/app/lib/planning_tools/teams.py +334 -0
  74. control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
  75. control_plane_api/app/lib/redis_client.py +803 -0
  76. control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
  77. control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
  78. control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
  79. control_plane_api/app/lib/storage/__init__.py +20 -0
  80. control_plane_api/app/lib/storage/base_provider.py +274 -0
  81. control_plane_api/app/lib/storage/provider_factory.py +157 -0
  82. control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
  83. control_plane_api/app/lib/supabase.py +71 -0
  84. control_plane_api/app/lib/supabase_utils.py +138 -0
  85. control_plane_api/app/lib/task_planning/__init__.py +138 -0
  86. control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
  87. control_plane_api/app/lib/task_planning/agents.py +389 -0
  88. control_plane_api/app/lib/task_planning/cache.py +218 -0
  89. control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
  90. control_plane_api/app/lib/task_planning/helpers.py +293 -0
  91. control_plane_api/app/lib/task_planning/hooks.py +474 -0
  92. control_plane_api/app/lib/task_planning/models.py +503 -0
  93. control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
  94. control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
  95. control_plane_api/app/lib/task_planning/runner.py +656 -0
  96. control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
  97. control_plane_api/app/lib/task_planning/workflow.py +424 -0
  98. control_plane_api/app/lib/templating/__init__.py +88 -0
  99. control_plane_api/app/lib/templating/compiler.py +278 -0
  100. control_plane_api/app/lib/templating/engine.py +178 -0
  101. control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
  102. control_plane_api/app/lib/templating/parsers/base.py +96 -0
  103. control_plane_api/app/lib/templating/parsers/env.py +85 -0
  104. control_plane_api/app/lib/templating/parsers/graph.py +112 -0
  105. control_plane_api/app/lib/templating/parsers/secret.py +87 -0
  106. control_plane_api/app/lib/templating/parsers/simple.py +81 -0
  107. control_plane_api/app/lib/templating/resolver.py +366 -0
  108. control_plane_api/app/lib/templating/types.py +214 -0
  109. control_plane_api/app/lib/templating/validator.py +201 -0
  110. control_plane_api/app/lib/temporal_client.py +232 -0
  111. control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
  112. control_plane_api/app/lib/temporal_credentials_service.py +203 -0
  113. control_plane_api/app/lib/validation/__init__.py +24 -0
  114. control_plane_api/app/lib/validation/runtime_validation.py +388 -0
  115. control_plane_api/app/main.py +531 -0
  116. control_plane_api/app/middleware/__init__.py +10 -0
  117. control_plane_api/app/middleware/auth.py +645 -0
  118. control_plane_api/app/middleware/exception_handler.py +267 -0
  119. control_plane_api/app/middleware/prometheus_middleware.py +173 -0
  120. control_plane_api/app/middleware/rate_limiting.py +384 -0
  121. control_plane_api/app/middleware/request_id.py +202 -0
  122. control_plane_api/app/models/__init__.py +40 -0
  123. control_plane_api/app/models/agent.py +90 -0
  124. control_plane_api/app/models/analytics.py +206 -0
  125. control_plane_api/app/models/associations.py +107 -0
  126. control_plane_api/app/models/auth_user.py +73 -0
  127. control_plane_api/app/models/context.py +161 -0
  128. control_plane_api/app/models/custom_integration.py +99 -0
  129. control_plane_api/app/models/environment.py +64 -0
  130. control_plane_api/app/models/execution.py +125 -0
  131. control_plane_api/app/models/execution_transition.py +50 -0
  132. control_plane_api/app/models/job.py +159 -0
  133. control_plane_api/app/models/llm_model.py +78 -0
  134. control_plane_api/app/models/orchestration.py +66 -0
  135. control_plane_api/app/models/plan_execution.py +102 -0
  136. control_plane_api/app/models/presence.py +49 -0
  137. control_plane_api/app/models/project.py +61 -0
  138. control_plane_api/app/models/project_management.py +85 -0
  139. control_plane_api/app/models/session.py +29 -0
  140. control_plane_api/app/models/skill.py +155 -0
  141. control_plane_api/app/models/system_tables.py +43 -0
  142. control_plane_api/app/models/task_planning.py +372 -0
  143. control_plane_api/app/models/team.py +86 -0
  144. control_plane_api/app/models/trace.py +257 -0
  145. control_plane_api/app/models/user_profile.py +54 -0
  146. control_plane_api/app/models/worker.py +221 -0
  147. control_plane_api/app/models/workflow.py +161 -0
  148. control_plane_api/app/models/workspace.py +50 -0
  149. control_plane_api/app/observability/__init__.py +177 -0
  150. control_plane_api/app/observability/context_logging.py +475 -0
  151. control_plane_api/app/observability/decorators.py +337 -0
  152. control_plane_api/app/observability/local_span_processor.py +702 -0
  153. control_plane_api/app/observability/metrics.py +303 -0
  154. control_plane_api/app/observability/middleware.py +246 -0
  155. control_plane_api/app/observability/optional.py +115 -0
  156. control_plane_api/app/observability/tracing.py +382 -0
  157. control_plane_api/app/policies/README.md +149 -0
  158. control_plane_api/app/policies/approved_users.rego +62 -0
  159. control_plane_api/app/policies/business_hours.rego +51 -0
  160. control_plane_api/app/policies/rate_limiting.rego +100 -0
  161. control_plane_api/app/policies/tool_enforcement/README.md +336 -0
  162. control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
  163. control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
  164. control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
  165. control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
  166. control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
  167. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  168. control_plane_api/app/routers/__init__.py +4 -0
  169. control_plane_api/app/routers/agents.py +382 -0
  170. control_plane_api/app/routers/agents_v2.py +1598 -0
  171. control_plane_api/app/routers/analytics.py +1310 -0
  172. control_plane_api/app/routers/auth.py +59 -0
  173. control_plane_api/app/routers/client_config.py +57 -0
  174. control_plane_api/app/routers/context_graph.py +561 -0
  175. control_plane_api/app/routers/context_manager.py +577 -0
  176. control_plane_api/app/routers/custom_integrations.py +490 -0
  177. control_plane_api/app/routers/enforcer.py +132 -0
  178. control_plane_api/app/routers/environment_context.py +252 -0
  179. control_plane_api/app/routers/environments.py +761 -0
  180. control_plane_api/app/routers/execution_environment.py +847 -0
  181. control_plane_api/app/routers/executions/__init__.py +28 -0
  182. control_plane_api/app/routers/executions/router.py +286 -0
  183. control_plane_api/app/routers/executions/services/__init__.py +22 -0
  184. control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
  185. control_plane_api/app/routers/executions/services/status_service.py +420 -0
  186. control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
  187. control_plane_api/app/routers/executions/services/worker_health.py +514 -0
  188. control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
  189. control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
  190. control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
  191. control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
  192. control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
  193. control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
  194. control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
  195. control_plane_api/app/routers/executions.py +4888 -0
  196. control_plane_api/app/routers/health.py +165 -0
  197. control_plane_api/app/routers/health_v2.py +394 -0
  198. control_plane_api/app/routers/integration_templates.py +496 -0
  199. control_plane_api/app/routers/integrations.py +287 -0
  200. control_plane_api/app/routers/jobs.py +1809 -0
  201. control_plane_api/app/routers/metrics.py +517 -0
  202. control_plane_api/app/routers/models.py +82 -0
  203. control_plane_api/app/routers/models_v2.py +628 -0
  204. control_plane_api/app/routers/plan_executions.py +1481 -0
  205. control_plane_api/app/routers/plan_generation_async.py +304 -0
  206. control_plane_api/app/routers/policies.py +669 -0
  207. control_plane_api/app/routers/presence.py +234 -0
  208. control_plane_api/app/routers/projects.py +987 -0
  209. control_plane_api/app/routers/runners.py +379 -0
  210. control_plane_api/app/routers/runtimes.py +172 -0
  211. control_plane_api/app/routers/secrets.py +171 -0
  212. control_plane_api/app/routers/skills.py +1010 -0
  213. control_plane_api/app/routers/skills_definitions.py +140 -0
  214. control_plane_api/app/routers/storage.py +456 -0
  215. control_plane_api/app/routers/task_planning.py +611 -0
  216. control_plane_api/app/routers/task_queues.py +650 -0
  217. control_plane_api/app/routers/team_context.py +274 -0
  218. control_plane_api/app/routers/teams.py +1747 -0
  219. control_plane_api/app/routers/templates.py +248 -0
  220. control_plane_api/app/routers/traces.py +571 -0
  221. control_plane_api/app/routers/websocket_client.py +479 -0
  222. control_plane_api/app/routers/websocket_executions_status.py +437 -0
  223. control_plane_api/app/routers/websocket_gateway.py +323 -0
  224. control_plane_api/app/routers/websocket_traces.py +576 -0
  225. control_plane_api/app/routers/worker_queues.py +2555 -0
  226. control_plane_api/app/routers/worker_websocket.py +419 -0
  227. control_plane_api/app/routers/workers.py +1004 -0
  228. control_plane_api/app/routers/workflows.py +204 -0
  229. control_plane_api/app/runtimes/__init__.py +6 -0
  230. control_plane_api/app/runtimes/validation.py +344 -0
  231. control_plane_api/app/schemas/__init__.py +1 -0
  232. control_plane_api/app/schemas/job_schemas.py +302 -0
  233. control_plane_api/app/schemas/mcp_schemas.py +311 -0
  234. control_plane_api/app/schemas/template_schemas.py +133 -0
  235. control_plane_api/app/schemas/trace_schemas.py +168 -0
  236. control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
  237. control_plane_api/app/services/__init__.py +1 -0
  238. control_plane_api/app/services/agno_planning_strategy.py +233 -0
  239. control_plane_api/app/services/agno_service.py +838 -0
  240. control_plane_api/app/services/claude_code_planning_service.py +203 -0
  241. control_plane_api/app/services/context_graph_client.py +224 -0
  242. control_plane_api/app/services/custom_integration_service.py +415 -0
  243. control_plane_api/app/services/integration_resolution_service.py +345 -0
  244. control_plane_api/app/services/litellm_service.py +394 -0
  245. control_plane_api/app/services/plan_generator.py +79 -0
  246. control_plane_api/app/services/planning_strategy.py +66 -0
  247. control_plane_api/app/services/planning_strategy_factory.py +118 -0
  248. control_plane_api/app/services/policy_service.py +615 -0
  249. control_plane_api/app/services/state_transition_service.py +755 -0
  250. control_plane_api/app/services/storage_service.py +593 -0
  251. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  252. control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
  253. control_plane_api/app/services/trace_retention.py +354 -0
  254. control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
  255. control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
  256. control_plane_api/app/services/workflow_operations_service.py +611 -0
  257. control_plane_api/app/skills/__init__.py +100 -0
  258. control_plane_api/app/skills/base.py +239 -0
  259. control_plane_api/app/skills/builtin/__init__.py +37 -0
  260. control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
  261. control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
  262. control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
  263. control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
  264. control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
  265. control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
  266. control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
  267. control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
  268. control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
  269. control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
  270. control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
  271. control_plane_api/app/skills/builtin/docker/skill.py +104 -0
  272. control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
  273. control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
  274. control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
  275. control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
  276. control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
  277. control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
  278. control_plane_api/app/skills/builtin/python/__init__.py +4 -0
  279. control_plane_api/app/skills/builtin/python/skill.py +92 -0
  280. control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
  281. control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
  282. control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
  283. control_plane_api/app/skills/builtin/shell/skill.py +161 -0
  284. control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
  285. control_plane_api/app/skills/builtin/slack/skill.py +302 -0
  286. control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
  287. control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
  288. control_plane_api/app/skills/business_intelligence.py +189 -0
  289. control_plane_api/app/skills/config.py +63 -0
  290. control_plane_api/app/skills/loaders/__init__.py +14 -0
  291. control_plane_api/app/skills/loaders/base.py +73 -0
  292. control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
  293. control_plane_api/app/skills/registry.py +125 -0
  294. control_plane_api/app/utils/helpers.py +12 -0
  295. control_plane_api/app/utils/workflow_executor.py +354 -0
  296. control_plane_api/app/workflows/__init__.py +11 -0
  297. control_plane_api/app/workflows/agent_execution.py +520 -0
  298. control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
  299. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  300. control_plane_api/app/workflows/plan_generation.py +254 -0
  301. control_plane_api/app/workflows/team_execution.py +442 -0
  302. control_plane_api/scripts/seed_models.py +240 -0
  303. control_plane_api/scripts/validate_existing_tool_names.py +492 -0
  304. control_plane_api/shared/__init__.py +8 -0
  305. control_plane_api/shared/version.py +17 -0
  306. control_plane_api/test_deduplication.py +274 -0
  307. control_plane_api/test_executor_deduplication_e2e.py +309 -0
  308. control_plane_api/test_job_execution_e2e.py +283 -0
  309. control_plane_api/test_real_integration.py +193 -0
  310. control_plane_api/version.py +38 -0
  311. control_plane_api/worker/__init__.py +0 -0
  312. control_plane_api/worker/activities/__init__.py +0 -0
  313. control_plane_api/worker/activities/agent_activities.py +1585 -0
  314. control_plane_api/worker/activities/approval_activities.py +234 -0
  315. control_plane_api/worker/activities/job_activities.py +199 -0
  316. control_plane_api/worker/activities/runtime_activities.py +1167 -0
  317. control_plane_api/worker/activities/skill_activities.py +282 -0
  318. control_plane_api/worker/activities/team_activities.py +479 -0
  319. control_plane_api/worker/agent_runtime_server.py +370 -0
  320. control_plane_api/worker/binary_manager.py +333 -0
  321. control_plane_api/worker/config/__init__.py +31 -0
  322. control_plane_api/worker/config/worker_config.py +273 -0
  323. control_plane_api/worker/control_plane_client.py +1491 -0
  324. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  325. control_plane_api/worker/health_monitor.py +159 -0
  326. control_plane_api/worker/metrics.py +237 -0
  327. control_plane_api/worker/models/__init__.py +1 -0
  328. control_plane_api/worker/models/error_events.py +105 -0
  329. control_plane_api/worker/models/inputs.py +89 -0
  330. control_plane_api/worker/runtimes/__init__.py +35 -0
  331. control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
  332. control_plane_api/worker/runtimes/agno/__init__.py +34 -0
  333. control_plane_api/worker/runtimes/agno/config.py +248 -0
  334. control_plane_api/worker/runtimes/agno/hooks.py +385 -0
  335. control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
  336. control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
  337. control_plane_api/worker/runtimes/agno/utils.py +163 -0
  338. control_plane_api/worker/runtimes/base.py +979 -0
  339. control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
  340. control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
  341. control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
  342. control_plane_api/worker/runtimes/claude_code/config.py +829 -0
  343. control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
  344. control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
  345. control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
  346. control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
  347. control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
  348. control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
  349. control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
  350. control_plane_api/worker/runtimes/factory.py +173 -0
  351. control_plane_api/worker/runtimes/model_utils.py +107 -0
  352. control_plane_api/worker/runtimes/validation.py +93 -0
  353. control_plane_api/worker/services/__init__.py +1 -0
  354. control_plane_api/worker/services/agent_communication_tools.py +908 -0
  355. control_plane_api/worker/services/agent_executor.py +485 -0
  356. control_plane_api/worker/services/agent_executor_v2.py +793 -0
  357. control_plane_api/worker/services/analytics_collector.py +457 -0
  358. control_plane_api/worker/services/analytics_service.py +464 -0
  359. control_plane_api/worker/services/approval_tools.py +310 -0
  360. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  361. control_plane_api/worker/services/cancellation_manager.py +177 -0
  362. control_plane_api/worker/services/code_ingestion_tools.py +465 -0
  363. control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
  364. control_plane_api/worker/services/data_visualization.py +834 -0
  365. control_plane_api/worker/services/event_publisher.py +531 -0
  366. control_plane_api/worker/services/jira_tools.py +257 -0
  367. control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
  368. control_plane_api/worker/services/runtime_analytics.py +328 -0
  369. control_plane_api/worker/services/session_service.py +365 -0
  370. control_plane_api/worker/services/skill_context_enhancement.py +181 -0
  371. control_plane_api/worker/services/skill_factory.py +471 -0
  372. control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
  373. control_plane_api/worker/services/team_executor.py +715 -0
  374. control_plane_api/worker/services/team_executor_v2.py +1866 -0
  375. control_plane_api/worker/services/tool_enforcement.py +254 -0
  376. control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
  377. control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
  378. control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
  379. control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
  380. control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
  381. control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
  382. control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
  383. control_plane_api/worker/services/workflow_executor/models.py +142 -0
  384. control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
  385. control_plane_api/worker/skills/__init__.py +12 -0
  386. control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
  387. control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
  388. control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
  389. control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
  390. control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
  391. control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
  392. control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
  393. control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
  394. control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
  395. control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
  396. control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
  397. control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
  398. control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
  399. control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
  400. control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
  401. control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
  402. control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
  403. control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
  404. control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
  405. control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
  406. control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
  407. control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
  408. control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
  409. control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
  410. control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
  411. control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
  412. control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
  413. control_plane_api/worker/skills/loaders/__init__.py +5 -0
  414. control_plane_api/worker/skills/loaders/base.py +23 -0
  415. control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
  416. control_plane_api/worker/skills/registry.py +208 -0
  417. control_plane_api/worker/tests/__init__.py +1 -0
  418. control_plane_api/worker/tests/conftest.py +12 -0
  419. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  420. control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
  421. control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
  422. control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
  423. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  424. control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
  425. control_plane_api/worker/tests/integration/__init__.py +0 -0
  426. control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
  427. control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
  428. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  429. control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
  430. control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
  431. control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
  432. control_plane_api/worker/tests/unit/__init__.py +0 -0
  433. control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
  434. control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
  435. control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
  436. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  437. control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
  438. control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
  439. control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
  440. control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
  441. control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
  442. control_plane_api/worker/utils/__init__.py +1 -0
  443. control_plane_api/worker/utils/chunk_batcher.py +330 -0
  444. control_plane_api/worker/utils/environment.py +65 -0
  445. control_plane_api/worker/utils/error_publisher.py +260 -0
  446. control_plane_api/worker/utils/event_batcher.py +256 -0
  447. control_plane_api/worker/utils/logging_config.py +335 -0
  448. control_plane_api/worker/utils/logging_helper.py +326 -0
  449. control_plane_api/worker/utils/parameter_validator.py +120 -0
  450. control_plane_api/worker/utils/retry_utils.py +60 -0
  451. control_plane_api/worker/utils/streaming_utils.py +665 -0
  452. control_plane_api/worker/utils/tool_validation.py +332 -0
  453. control_plane_api/worker/utils/workspace_manager.py +163 -0
  454. control_plane_api/worker/websocket_client.py +393 -0
  455. control_plane_api/worker/worker.py +1297 -0
  456. control_plane_api/worker/workflows/__init__.py +0 -0
  457. control_plane_api/worker/workflows/agent_execution.py +909 -0
  458. control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
  459. control_plane_api/worker/workflows/team_execution.py +611 -0
  460. kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
  461. kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
  462. kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
  463. kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
  464. kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
  465. kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
  466. scripts/__init__.py +1 -0
  467. scripts/migrations.py +39 -0
  468. scripts/seed_worker_queues.py +128 -0
  469. scripts/setup_agent_runtime.py +142 -0
  470. worker_internal/__init__.py +1 -0
  471. worker_internal/planner/__init__.py +1 -0
  472. worker_internal/planner/activities.py +1499 -0
  473. worker_internal/planner/agent_tools.py +197 -0
  474. worker_internal/planner/event_models.py +148 -0
  475. worker_internal/planner/event_publisher.py +67 -0
  476. worker_internal/planner/models.py +199 -0
  477. worker_internal/planner/retry_logic.py +134 -0
  478. worker_internal/planner/worker.py +300 -0
  479. 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
+ }