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,345 @@
1
+ """
2
+ Unit tests for ControlPlaneClient job execution methods.
3
+
4
+ Tests the HTTP client methods that workers use to create and update job executions.
5
+ """
6
+ import pytest
7
+ from unittest.mock import Mock, AsyncMock, patch
8
+ import httpx
9
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
10
+
11
+
12
+ @pytest.fixture
13
+ def mock_async_client():
14
+ """Mock async HTTP client."""
15
+ mock_client = AsyncMock()
16
+ mock_response = Mock()
17
+ mock_response.status_code = 201
18
+ mock_response.json.return_value = {
19
+ "execution_id": "exec_test_123",
20
+ "status": "created",
21
+ "created_at": "2024-01-01T00:00:00Z"
22
+ }
23
+ mock_client.post = AsyncMock(return_value=mock_response)
24
+ return mock_client
25
+
26
+
27
+ @pytest.fixture
28
+ def control_plane_client(mock_async_client):
29
+ """Create ControlPlaneClient with mocked HTTP client."""
30
+ client = ControlPlaneClient(
31
+ base_url="http://test-control-plane",
32
+ api_key="test-api-key",
33
+ websocket_enabled=False
34
+ )
35
+ # Replace the async client with our mock
36
+ client._async_client = mock_async_client
37
+ return client
38
+
39
+
40
+ @pytest.mark.asyncio
41
+ class TestCreateJobExecutionRecord:
42
+ """Test create_job_execution_record method."""
43
+
44
+ async def test_create_job_execution_record_success(
45
+ self, control_plane_client, mock_async_client
46
+ ):
47
+ """Test successfully creating a job execution record."""
48
+ result = await control_plane_client.create_job_execution_record(
49
+ execution_id="exec_test_123",
50
+ job_id="job_test_456",
51
+ organization_id="org_test_789",
52
+ entity_type="agent",
53
+ entity_id="agent_test_111",
54
+ prompt="Test prompt",
55
+ trigger_type="cron",
56
+ trigger_metadata={"job_name": "Test Job"}
57
+ )
58
+
59
+ # Verify the HTTP call was made
60
+ mock_async_client.post.assert_called_once()
61
+ call_args = mock_async_client.post.call_args
62
+
63
+ # Verify URL
64
+ assert call_args[0][0] == "http://test-control-plane/api/v1/executions/create"
65
+
66
+ # Verify payload
67
+ payload = call_args.kwargs["json"]
68
+ assert payload["execution_id"] == "exec_test_123"
69
+ assert payload["job_id"] == "job_test_456"
70
+ assert payload["organization_id"] == "org_test_789"
71
+ assert payload["entity_type"] == "agent"
72
+ assert payload["entity_id"] == "agent_test_111"
73
+ assert payload["prompt"] == "Test prompt"
74
+ assert payload["trigger_type"] == "cron"
75
+ assert payload["trigger_metadata"]["job_name"] == "Test Job"
76
+
77
+ # Verify headers
78
+ assert call_args.kwargs["headers"]["Authorization"] == "UserKey test-api-key"
79
+
80
+ # Verify result
81
+ assert result["execution_id"] == "exec_test_123"
82
+ assert result["status"] == "created"
83
+
84
+ async def test_create_job_execution_record_without_job_id(
85
+ self, control_plane_client, mock_async_client
86
+ ):
87
+ """Test creating execution record without job_id."""
88
+ result = await control_plane_client.create_job_execution_record(
89
+ execution_id="exec_test_456",
90
+ job_id=None,
91
+ organization_id="org_test_789",
92
+ entity_type="team",
93
+ entity_id="team_test_222",
94
+ prompt="Manual execution",
95
+ trigger_type="manual",
96
+ trigger_metadata={"user_id": "user_123"}
97
+ )
98
+
99
+ # Verify the call
100
+ mock_async_client.post.assert_called_once()
101
+ payload = mock_async_client.post.call_args.kwargs["json"]
102
+ assert payload["job_id"] is None
103
+ assert payload["entity_type"] == "team"
104
+ assert payload["trigger_type"] == "manual"
105
+
106
+ async def test_create_job_execution_record_http_error(
107
+ self, control_plane_client, mock_async_client
108
+ ):
109
+ """Test handling HTTP error response."""
110
+ # Mock error response
111
+ error_response = Mock()
112
+ error_response.status_code = 500
113
+ error_response.text = "Internal server error"
114
+ mock_async_client.post = AsyncMock(return_value=error_response)
115
+
116
+ with pytest.raises(Exception) as exc_info:
117
+ await control_plane_client.create_job_execution_record(
118
+ execution_id="exec_error",
119
+ job_id="job_error",
120
+ organization_id="org_test",
121
+ entity_type="agent",
122
+ entity_id="agent_error",
123
+ prompt="Test",
124
+ trigger_type="cron",
125
+ trigger_metadata={}
126
+ )
127
+
128
+ assert "Failed to create execution record" in str(exc_info.value)
129
+ assert "HTTP 500" in str(exc_info.value)
130
+
131
+ async def test_create_job_execution_record_network_error(
132
+ self, control_plane_client, mock_async_client
133
+ ):
134
+ """Test handling network error."""
135
+ # Mock network error
136
+ mock_async_client.post = AsyncMock(
137
+ side_effect=httpx.ConnectError("Connection failed")
138
+ )
139
+
140
+ with pytest.raises(Exception):
141
+ await control_plane_client.create_job_execution_record(
142
+ execution_id="exec_network_error",
143
+ job_id="job_test",
144
+ organization_id="org_test",
145
+ entity_type="agent",
146
+ entity_id="agent_test",
147
+ prompt="Test",
148
+ trigger_type="cron",
149
+ trigger_metadata={}
150
+ )
151
+
152
+
153
+ @pytest.mark.asyncio
154
+ class TestUpdateJobExecutionStatus:
155
+ """Test update_job_execution_status method."""
156
+
157
+ async def test_update_job_execution_status_completed(
158
+ self, control_plane_client, mock_async_client
159
+ ):
160
+ """Test updating job execution status to completed."""
161
+ # Mock successful response
162
+ success_response = Mock()
163
+ success_response.status_code = 200
164
+ success_response.json.return_value = {
165
+ "job_id": "job_test_456",
166
+ "execution_id": "exec_test_123",
167
+ "status": "updated"
168
+ }
169
+ mock_async_client.post = AsyncMock(return_value=success_response)
170
+
171
+ result = await control_plane_client.update_job_execution_status(
172
+ execution_id="exec_test_123",
173
+ job_id="job_test_456",
174
+ status="completed",
175
+ duration_ms=5000,
176
+ error_message=None
177
+ )
178
+
179
+ # Verify the HTTP call
180
+ mock_async_client.post.assert_called_once()
181
+ call_args = mock_async_client.post.call_args
182
+
183
+ # Verify URL
184
+ expected_url = "http://test-control-plane/api/v1/executions/exec_test_123/job/job_test_456/status"
185
+ assert call_args[0][0] == expected_url
186
+
187
+ # Verify payload
188
+ payload = call_args.kwargs["json"]
189
+ assert payload["status"] == "completed"
190
+ assert payload["duration_ms"] == 5000
191
+ assert payload["error_message"] is None
192
+
193
+ # Verify result
194
+ assert result["job_id"] == "job_test_456"
195
+ assert result["execution_id"] == "exec_test_123"
196
+ assert result["status"] == "updated"
197
+
198
+ async def test_update_job_execution_status_failed(
199
+ self, control_plane_client, mock_async_client
200
+ ):
201
+ """Test updating job execution status to failed."""
202
+ # Mock successful response
203
+ success_response = Mock()
204
+ success_response.status_code = 200
205
+ success_response.json.return_value = {
206
+ "job_id": "job_test_789",
207
+ "execution_id": "exec_test_456",
208
+ "status": "updated"
209
+ }
210
+ mock_async_client.post = AsyncMock(return_value=success_response)
211
+
212
+ result = await control_plane_client.update_job_execution_status(
213
+ execution_id="exec_test_456",
214
+ job_id="job_test_789",
215
+ status="failed",
216
+ duration_ms=1500,
217
+ error_message="Test error occurred"
218
+ )
219
+
220
+ # Verify payload includes error message
221
+ payload = mock_async_client.post.call_args.kwargs["json"]
222
+ assert payload["status"] == "failed"
223
+ assert payload["duration_ms"] == 1500
224
+ assert payload["error_message"] == "Test error occurred"
225
+
226
+ async def test_update_job_execution_status_without_duration(
227
+ self, control_plane_client, mock_async_client
228
+ ):
229
+ """Test updating status without duration_ms."""
230
+ success_response = Mock()
231
+ success_response.status_code = 200
232
+ success_response.json.return_value = {
233
+ "job_id": "job_test",
234
+ "execution_id": "exec_test",
235
+ "status": "updated"
236
+ }
237
+ mock_async_client.post = AsyncMock(return_value=success_response)
238
+
239
+ result = await control_plane_client.update_job_execution_status(
240
+ execution_id="exec_test",
241
+ job_id="job_test",
242
+ status="completed"
243
+ )
244
+
245
+ # Verify payload
246
+ payload = mock_async_client.post.call_args.kwargs["json"]
247
+ assert payload["status"] == "completed"
248
+ assert payload["duration_ms"] is None
249
+ assert payload["error_message"] is None
250
+
251
+ async def test_update_job_execution_status_http_error(
252
+ self, control_plane_client, mock_async_client
253
+ ):
254
+ """Test handling HTTP error response."""
255
+ error_response = Mock()
256
+ error_response.status_code = 404
257
+ error_response.text = "Job execution not found"
258
+ mock_async_client.post = AsyncMock(return_value=error_response)
259
+
260
+ with pytest.raises(Exception) as exc_info:
261
+ await control_plane_client.update_job_execution_status(
262
+ execution_id="exec_notfound",
263
+ job_id="job_notfound",
264
+ status="completed"
265
+ )
266
+
267
+ assert "Failed to update job execution status" in str(exc_info.value)
268
+ assert "HTTP 404" in str(exc_info.value)
269
+
270
+ async def test_update_job_execution_status_timeout(
271
+ self, control_plane_client, mock_async_client
272
+ ):
273
+ """Test handling timeout error."""
274
+ mock_async_client.post = AsyncMock(
275
+ side_effect=httpx.TimeoutException("Request timeout")
276
+ )
277
+
278
+ with pytest.raises(Exception):
279
+ await control_plane_client.update_job_execution_status(
280
+ execution_id="exec_timeout",
281
+ job_id="job_timeout",
282
+ status="completed",
283
+ duration_ms=10000
284
+ )
285
+
286
+
287
+ @pytest.mark.asyncio
288
+ class TestControlPlaneClientIntegration:
289
+ """Integration tests for ControlPlaneClient job methods."""
290
+
291
+ async def test_full_job_execution_lifecycle(
292
+ self, control_plane_client, mock_async_client
293
+ ):
294
+ """Test complete lifecycle: create -> update status."""
295
+ # Mock create response
296
+ create_response = Mock()
297
+ create_response.status_code = 201
298
+ create_response.json.return_value = {
299
+ "execution_id": "exec_lifecycle_123",
300
+ "status": "created",
301
+ "created_at": "2024-01-01T00:00:00Z"
302
+ }
303
+
304
+ # Mock update response
305
+ update_response = Mock()
306
+ update_response.status_code = 200
307
+ update_response.json.return_value = {
308
+ "job_id": "job_lifecycle_456",
309
+ "execution_id": "exec_lifecycle_123",
310
+ "status": "updated"
311
+ }
312
+
313
+ # Configure mock to return different responses
314
+ mock_async_client.post = AsyncMock(
315
+ side_effect=[create_response, update_response]
316
+ )
317
+
318
+ # 1. Create execution record
319
+ create_result = await control_plane_client.create_job_execution_record(
320
+ execution_id="exec_lifecycle_123",
321
+ job_id="job_lifecycle_456",
322
+ organization_id="org_test",
323
+ entity_type="agent",
324
+ entity_id="agent_test",
325
+ prompt="Lifecycle test",
326
+ trigger_type="cron",
327
+ trigger_metadata={"job_name": "Lifecycle Job"}
328
+ )
329
+
330
+ assert create_result["execution_id"] == "exec_lifecycle_123"
331
+ assert create_result["status"] == "created"
332
+
333
+ # 2. Update execution status
334
+ update_result = await control_plane_client.update_job_execution_status(
335
+ execution_id="exec_lifecycle_123",
336
+ job_id="job_lifecycle_456",
337
+ status="completed",
338
+ duration_ms=3000
339
+ )
340
+
341
+ assert update_result["execution_id"] == "exec_lifecycle_123"
342
+ assert update_result["status"] == "updated"
343
+
344
+ # Verify both calls were made
345
+ assert mock_async_client.post.call_count == 2
@@ -0,0 +1,353 @@
1
+ """
2
+ Unit tests for refactored job activities.
3
+
4
+ Tests that job activities properly use ControlPlaneClient instead of direct Supabase access.
5
+ """
6
+ import pytest
7
+ from unittest.mock import Mock, AsyncMock, patch
8
+ from control_plane_api.worker.activities.job_activities import (
9
+ create_job_execution_record,
10
+ update_job_execution_status,
11
+ ActivityCreateJobExecutionInput,
12
+ )
13
+
14
+
15
+ @pytest.fixture
16
+ def mock_control_plane_client():
17
+ """Mock ControlPlaneClient for testing."""
18
+ mock_client = Mock()
19
+ mock_client.create_job_execution_record = AsyncMock()
20
+ mock_client.update_job_execution_status = AsyncMock()
21
+ return mock_client
22
+
23
+
24
+ @pytest.mark.asyncio
25
+ class TestCreateJobExecutionRecordActivity:
26
+ """Test create_job_execution_record activity."""
27
+
28
+ async def test_create_job_execution_record_with_job(
29
+ self, mock_control_plane_client
30
+ ):
31
+ """Test creating job execution record with job_id."""
32
+ # Mock successful response
33
+ mock_control_plane_client.create_job_execution_record.return_value = {
34
+ "execution_id": "exec_test_123",
35
+ "status": "created",
36
+ "created_at": "2024-01-01T00:00:00Z"
37
+ }
38
+
39
+ # Create input
40
+ input_data = ActivityCreateJobExecutionInput(
41
+ execution_id="exec_test_123",
42
+ job_id="job_test_456",
43
+ organization_id="org_test_789",
44
+ entity_type="agent",
45
+ entity_id="agent_test_111",
46
+ prompt="Test scheduled prompt",
47
+ trigger_type="cron",
48
+ trigger_metadata={
49
+ "job_name": "Test Job",
50
+ "user_id": "user_test_456",
51
+ }
52
+ )
53
+
54
+ # Patch get_control_plane_client
55
+ with patch(
56
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
57
+ return_value=mock_control_plane_client
58
+ ):
59
+ result = await create_job_execution_record(input_data)
60
+
61
+ # Verify client method was called
62
+ mock_control_plane_client.create_job_execution_record.assert_called_once_with(
63
+ execution_id="exec_test_123",
64
+ job_id="job_test_456",
65
+ organization_id="org_test_789",
66
+ entity_type="agent",
67
+ entity_id="agent_test_111",
68
+ prompt="Test scheduled prompt",
69
+ trigger_type="cron",
70
+ trigger_metadata={
71
+ "job_name": "Test Job",
72
+ "user_id": "user_test_456",
73
+ }
74
+ )
75
+
76
+ # Verify result
77
+ assert result["execution_id"] == "exec_test_123"
78
+ assert result["status"] == "created"
79
+
80
+ async def test_create_job_execution_record_without_job(
81
+ self, mock_control_plane_client
82
+ ):
83
+ """Test creating execution record without job_id."""
84
+ mock_control_plane_client.create_job_execution_record.return_value = {
85
+ "execution_id": "exec_test_456",
86
+ "status": "created",
87
+ "created_at": "2024-01-01T00:00:00Z"
88
+ }
89
+
90
+ input_data = ActivityCreateJobExecutionInput(
91
+ execution_id="exec_test_456",
92
+ job_id=None,
93
+ organization_id="org_test_789",
94
+ entity_type="team",
95
+ entity_id="team_test_222",
96
+ prompt="Manual execution",
97
+ trigger_type="manual",
98
+ trigger_metadata={"user_id": "user_test_456"}
99
+ )
100
+
101
+ with patch(
102
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
103
+ return_value=mock_control_plane_client
104
+ ):
105
+ result = await create_job_execution_record(input_data)
106
+
107
+ # Verify call
108
+ call_args = mock_control_plane_client.create_job_execution_record.call_args
109
+ assert call_args.kwargs["job_id"] is None
110
+ assert call_args.kwargs["entity_type"] == "team"
111
+ assert call_args.kwargs["trigger_type"] == "manual"
112
+
113
+ assert result["execution_id"] == "exec_test_456"
114
+
115
+ async def test_create_job_execution_record_webhook_trigger(
116
+ self, mock_control_plane_client
117
+ ):
118
+ """Test creating execution record for webhook trigger."""
119
+ mock_control_plane_client.create_job_execution_record.return_value = {
120
+ "execution_id": "exec_webhook_123",
121
+ "status": "created",
122
+ "created_at": "2024-01-01T00:00:00Z"
123
+ }
124
+
125
+ input_data = ActivityCreateJobExecutionInput(
126
+ execution_id="exec_webhook_123",
127
+ job_id="job_webhook_456",
128
+ organization_id="org_test_789",
129
+ entity_type="agent",
130
+ entity_id="agent_webhook_111",
131
+ prompt="Handle webhook {{payload}}",
132
+ trigger_type="webhook",
133
+ trigger_metadata={
134
+ "job_name": "Webhook Job",
135
+ "webhook_payload": {"alert": "test"},
136
+ "webhook_headers": {"x-source": "test"}
137
+ }
138
+ )
139
+
140
+ with patch(
141
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
142
+ return_value=mock_control_plane_client
143
+ ):
144
+ result = await create_job_execution_record(input_data)
145
+
146
+ # Verify webhook metadata was passed
147
+ call_args = mock_control_plane_client.create_job_execution_record.call_args
148
+ metadata = call_args.kwargs["trigger_metadata"]
149
+ assert metadata["job_name"] == "Webhook Job"
150
+ assert metadata["webhook_payload"]["alert"] == "test"
151
+ assert metadata["webhook_headers"]["x-source"] == "test"
152
+
153
+ async def test_create_job_execution_record_error_handling(
154
+ self, mock_control_plane_client
155
+ ):
156
+ """Test error handling when client call fails."""
157
+ # Mock error
158
+ mock_control_plane_client.create_job_execution_record.side_effect = Exception(
159
+ "HTTP 500 error"
160
+ )
161
+
162
+ input_data = ActivityCreateJobExecutionInput(
163
+ execution_id="exec_error",
164
+ job_id="job_error",
165
+ organization_id="org_test",
166
+ entity_type="agent",
167
+ entity_id="agent_error",
168
+ prompt="Test",
169
+ trigger_type="cron",
170
+ trigger_metadata={}
171
+ )
172
+
173
+ with patch(
174
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
175
+ return_value=mock_control_plane_client
176
+ ):
177
+ with pytest.raises(Exception) as exc_info:
178
+ await create_job_execution_record(input_data)
179
+
180
+ assert "HTTP 500 error" in str(exc_info.value)
181
+
182
+
183
+ @pytest.mark.asyncio
184
+ class TestUpdateJobExecutionStatusActivity:
185
+ """Test update_job_execution_status activity."""
186
+
187
+ async def test_update_job_execution_status_completed(
188
+ self, mock_control_plane_client
189
+ ):
190
+ """Test updating job execution status to completed."""
191
+ mock_control_plane_client.update_job_execution_status.return_value = {
192
+ "job_id": "job_test_456",
193
+ "execution_id": "exec_test_123",
194
+ "status": "updated"
195
+ }
196
+
197
+ with patch(
198
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
199
+ return_value=mock_control_plane_client
200
+ ):
201
+ result = await update_job_execution_status(
202
+ job_id="job_test_456",
203
+ execution_id="exec_test_123",
204
+ status="completed",
205
+ duration_ms=5000,
206
+ error_message=None
207
+ )
208
+
209
+ # Verify client method was called
210
+ mock_control_plane_client.update_job_execution_status.assert_called_once_with(
211
+ execution_id="exec_test_123",
212
+ job_id="job_test_456",
213
+ status="completed",
214
+ duration_ms=5000,
215
+ error_message=None
216
+ )
217
+
218
+ # Verify result
219
+ assert result["job_id"] == "job_test_456"
220
+ assert result["execution_id"] == "exec_test_123"
221
+ assert result["status"] == "updated"
222
+
223
+ async def test_update_job_execution_status_failed(
224
+ self, mock_control_plane_client
225
+ ):
226
+ """Test updating job execution status to failed."""
227
+ mock_control_plane_client.update_job_execution_status.return_value = {
228
+ "job_id": "job_test_789",
229
+ "execution_id": "exec_test_456",
230
+ "status": "updated"
231
+ }
232
+
233
+ with patch(
234
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
235
+ return_value=mock_control_plane_client
236
+ ):
237
+ result = await update_job_execution_status(
238
+ job_id="job_test_789",
239
+ execution_id="exec_test_456",
240
+ status="failed",
241
+ duration_ms=1500,
242
+ error_message="Test error occurred"
243
+ )
244
+
245
+ # Verify call includes error message
246
+ call_args = mock_control_plane_client.update_job_execution_status.call_args
247
+ assert call_args.kwargs["status"] == "failed"
248
+ assert call_args.kwargs["duration_ms"] == 1500
249
+ assert call_args.kwargs["error_message"] == "Test error occurred"
250
+
251
+ async def test_update_job_execution_status_without_duration(
252
+ self, mock_control_plane_client
253
+ ):
254
+ """Test updating status without duration_ms."""
255
+ mock_control_plane_client.update_job_execution_status.return_value = {
256
+ "job_id": "job_test",
257
+ "execution_id": "exec_test",
258
+ "status": "updated"
259
+ }
260
+
261
+ with patch(
262
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
263
+ return_value=mock_control_plane_client
264
+ ):
265
+ result = await update_job_execution_status(
266
+ job_id="job_test",
267
+ execution_id="exec_test",
268
+ status="completed"
269
+ )
270
+
271
+ # Verify call with None values
272
+ call_args = mock_control_plane_client.update_job_execution_status.call_args
273
+ assert call_args.kwargs["duration_ms"] is None
274
+ assert call_args.kwargs["error_message"] is None
275
+
276
+ async def test_update_job_execution_status_error_handling(
277
+ self, mock_control_plane_client
278
+ ):
279
+ """Test error handling when client call fails."""
280
+ mock_control_plane_client.update_job_execution_status.side_effect = Exception(
281
+ "HTTP 404 error"
282
+ )
283
+
284
+ with patch(
285
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
286
+ return_value=mock_control_plane_client
287
+ ):
288
+ with pytest.raises(Exception) as exc_info:
289
+ await update_job_execution_status(
290
+ job_id="job_notfound",
291
+ execution_id="exec_notfound",
292
+ status="completed"
293
+ )
294
+
295
+ assert "HTTP 404 error" in str(exc_info.value)
296
+
297
+
298
+ @pytest.mark.asyncio
299
+ class TestJobActivitiesIntegration:
300
+ """Integration tests for job activities workflow."""
301
+
302
+ async def test_full_activity_lifecycle(
303
+ self, mock_control_plane_client
304
+ ):
305
+ """Test complete lifecycle: create -> update."""
306
+ # Mock create response
307
+ mock_control_plane_client.create_job_execution_record.return_value = {
308
+ "execution_id": "exec_lifecycle_123",
309
+ "status": "created",
310
+ "created_at": "2024-01-01T00:00:00Z"
311
+ }
312
+
313
+ # Mock update response
314
+ mock_control_plane_client.update_job_execution_status.return_value = {
315
+ "job_id": "job_lifecycle_456",
316
+ "execution_id": "exec_lifecycle_123",
317
+ "status": "updated"
318
+ }
319
+
320
+ with patch(
321
+ "control_plane_api.worker.control_plane_client.get_control_plane_client",
322
+ return_value=mock_control_plane_client
323
+ ):
324
+ # 1. Create execution record
325
+ input_data = ActivityCreateJobExecutionInput(
326
+ execution_id="exec_lifecycle_123",
327
+ job_id="job_lifecycle_456",
328
+ organization_id="org_test",
329
+ entity_type="agent",
330
+ entity_id="agent_test",
331
+ prompt="Lifecycle test",
332
+ trigger_type="cron",
333
+ trigger_metadata={"job_name": "Lifecycle Job"}
334
+ )
335
+
336
+ create_result = await create_job_execution_record(input_data)
337
+ assert create_result["execution_id"] == "exec_lifecycle_123"
338
+ assert create_result["status"] == "created"
339
+
340
+ # 2. Update execution status
341
+ update_result = await update_job_execution_status(
342
+ job_id="job_lifecycle_456",
343
+ execution_id="exec_lifecycle_123",
344
+ status="completed",
345
+ duration_ms=3000
346
+ )
347
+
348
+ assert update_result["execution_id"] == "exec_lifecycle_123"
349
+ assert update_result["status"] == "updated"
350
+
351
+ # Verify both methods were called
352
+ assert mock_control_plane_client.create_job_execution_record.call_count == 1
353
+ assert mock_control_plane_client.update_job_execution_status.call_count == 1