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,237 @@
1
+ """
2
+ Integration test for ScheduledJobWrapperWorkflow.
3
+
4
+ This test verifies that the workflow correctly:
5
+ 1. Creates execution records via activities
6
+ 2. Executes child workflows (agent/team)
7
+ 3. Calculates duration properly (tests the fix for float.total_seconds() bug)
8
+ 4. Updates job execution status
9
+ """
10
+
11
+ import pytest
12
+ from unittest.mock import Mock, AsyncMock, patch
13
+ from datetime import timedelta
14
+ from temporalio.testing import WorkflowEnvironment
15
+ from temporalio.worker import Worker
16
+ from temporalio import activity
17
+
18
+ # Import the workflow and related classes
19
+ from control_plane_api.worker.workflows.scheduled_job_wrapper import (
20
+ ScheduledJobWrapperWorkflow,
21
+ ScheduledJobInput,
22
+ )
23
+ from control_plane_api.worker.workflows.agent_execution import (
24
+ AgentExecutionWorkflow,
25
+ )
26
+ from control_plane_api.worker.activities.job_activities import (
27
+ create_job_execution_record,
28
+ update_job_execution_status,
29
+ )
30
+
31
+
32
+ @pytest.mark.asyncio
33
+ async def test_scheduled_job_wrapper_workflow_duration_calculation():
34
+ """
35
+ Test that ScheduledJobWrapperWorkflow correctly calculates duration.
36
+
37
+ This specifically tests the fix for the bug where workflow.time() returns
38
+ a float, not a datetime, so we can't call .total_seconds() on the difference.
39
+ """
40
+
41
+ # Mock the activities
42
+ @activity.defn(name="create_job_execution_record")
43
+ async def mock_create_job_execution_record(input_data):
44
+ """Mock activity for creating job execution record"""
45
+ # input_data can be a dict or ActivityCreateJobExecutionInput object
46
+ execution_id = input_data.execution_id if hasattr(input_data, 'execution_id') else input_data.get('execution_id')
47
+ return {
48
+ "execution_id": execution_id,
49
+ "status": "created",
50
+ "created_at": "2024-01-01T00:00:00Z"
51
+ }
52
+
53
+ @activity.defn(name="update_job_execution_status")
54
+ async def mock_update_job_execution_status(
55
+ job_id: str,
56
+ execution_id: str,
57
+ status: str,
58
+ duration_ms: int = None,
59
+ error_message: str = None
60
+ ):
61
+ """Mock activity for updating job execution status"""
62
+ # This is the key assertion - duration_ms should be an integer
63
+ assert isinstance(duration_ms, int), f"duration_ms should be int, got {type(duration_ms)}"
64
+ assert duration_ms >= 0, f"duration_ms should be non-negative, got {duration_ms}"
65
+
66
+ return {
67
+ "job_id": job_id,
68
+ "execution_id": execution_id,
69
+ "status": "updated"
70
+ }
71
+
72
+ # Mock the child workflow
73
+ async def mock_agent_execution(input_data):
74
+ """Mock AgentExecutionWorkflow.run"""
75
+ # Simulate some execution time
76
+ import asyncio
77
+ await asyncio.sleep(0.1) # 100ms execution
78
+
79
+ return {
80
+ "status": "completed",
81
+ "execution_id": input_data.execution_id,
82
+ "response": "Test response"
83
+ }
84
+
85
+ # Create test environment
86
+ async with await WorkflowEnvironment.start_time_skipping() as env:
87
+ # Create worker with our workflow and mocked activities
88
+ worker = Worker(
89
+ env.client,
90
+ task_queue="test-task-queue",
91
+ workflows=[ScheduledJobWrapperWorkflow, AgentExecutionWorkflow],
92
+ activities=[
93
+ mock_create_job_execution_record,
94
+ mock_update_job_execution_status,
95
+ ],
96
+ )
97
+
98
+ async with worker:
99
+ # Prepare test input
100
+ test_input = ScheduledJobInput(
101
+ execution_id="test_exec_123",
102
+ agent_id="test_agent_456",
103
+ organization_id="test_org_789",
104
+ prompt="Test scheduled job prompt",
105
+ system_prompt="You are a test agent",
106
+ model_id="claude-3-5-sonnet-20241022",
107
+ model_config={},
108
+ agent_config={},
109
+ mcp_servers={},
110
+ user_metadata={
111
+ "job_id": "test_job_123",
112
+ "job_name": "Test Job",
113
+ "trigger_type": "cron"
114
+ },
115
+ runtime_type="default"
116
+ )
117
+
118
+ # Mock the child workflow execution
119
+ with patch.object(
120
+ AgentExecutionWorkflow,
121
+ 'run',
122
+ new=mock_agent_execution
123
+ ):
124
+ # Execute the workflow
125
+ result = await env.client.execute_workflow(
126
+ ScheduledJobWrapperWorkflow.run,
127
+ test_input,
128
+ id=f"test-scheduled-job-{test_input.execution_id}",
129
+ task_queue="test-task-queue",
130
+ )
131
+
132
+ # Verify the result
133
+ assert result["status"] == "completed"
134
+ assert result["execution_id"] == "test_exec_123"
135
+
136
+ print("✅ Test passed! Duration calculation works correctly.")
137
+
138
+
139
+ @pytest.mark.asyncio
140
+ async def test_scheduled_job_wrapper_workflow_with_failure():
141
+ """
142
+ Test that ScheduledJobWrapperWorkflow handles failures correctly
143
+ and still calculates duration.
144
+ """
145
+
146
+ @activity.defn(name="create_job_execution_record")
147
+ async def mock_create_job_execution_record(input_data):
148
+ # input_data can be a dict or ActivityCreateJobExecutionInput object
149
+ execution_id = input_data.execution_id if hasattr(input_data, 'execution_id') else input_data.get('execution_id')
150
+ return {
151
+ "execution_id": execution_id,
152
+ "status": "created",
153
+ "created_at": "2024-01-01T00:00:00Z"
154
+ }
155
+
156
+ @activity.defn(name="update_job_execution_status")
157
+ async def mock_update_job_execution_status(
158
+ job_id: str,
159
+ execution_id: str,
160
+ status: str,
161
+ duration_ms: int = None,
162
+ error_message: str = None
163
+ ):
164
+ # Verify we got duration even for failed execution
165
+ assert isinstance(duration_ms, int)
166
+ assert duration_ms >= 0
167
+ assert status == "failed"
168
+ assert error_message is not None
169
+
170
+ return {
171
+ "job_id": job_id,
172
+ "execution_id": execution_id,
173
+ "status": "updated"
174
+ }
175
+
176
+ async def mock_failing_agent_execution(input_data):
177
+ """Mock failing AgentExecutionWorkflow.run"""
178
+ import asyncio
179
+ await asyncio.sleep(0.05)
180
+ raise Exception("Test execution failure")
181
+
182
+ async with await WorkflowEnvironment.start_time_skipping() as env:
183
+ worker = Worker(
184
+ env.client,
185
+ task_queue="test-task-queue-fail",
186
+ workflows=[ScheduledJobWrapperWorkflow, AgentExecutionWorkflow],
187
+ activities=[
188
+ mock_create_job_execution_record,
189
+ mock_update_job_execution_status,
190
+ ],
191
+ )
192
+
193
+ async with worker:
194
+ test_input = ScheduledJobInput(
195
+ execution_id="test_exec_fail_456",
196
+ agent_id="test_agent_789",
197
+ organization_id="test_org_789",
198
+ prompt="Test failing job",
199
+ user_metadata={
200
+ "job_id": "test_job_fail_456",
201
+ "job_name": "Failing Test Job",
202
+ "trigger_type": "cron"
203
+ },
204
+ runtime_type="default"
205
+ )
206
+
207
+ with patch.object(
208
+ AgentExecutionWorkflow,
209
+ 'run',
210
+ new=mock_failing_agent_execution
211
+ ):
212
+ result = await env.client.execute_workflow(
213
+ ScheduledJobWrapperWorkflow.run,
214
+ test_input,
215
+ id=f"test-scheduled-job-fail-{test_input.execution_id}",
216
+ task_queue="test-task-queue-fail",
217
+ )
218
+
219
+ # Verify the failure was handled
220
+ assert result["status"] == "failed"
221
+ assert "error" in result
222
+
223
+ print("✅ Test passed! Duration calculation works correctly even on failure.")
224
+
225
+
226
+ if __name__ == "__main__":
227
+ import asyncio
228
+
229
+ print("Running ScheduledJobWrapperWorkflow integration tests...\n")
230
+
231
+ print("Test 1: Duration calculation with successful execution")
232
+ asyncio.run(test_scheduled_job_wrapper_workflow_duration_calculation())
233
+
234
+ print("\nTest 2: Duration calculation with failed execution")
235
+ asyncio.run(test_scheduled_job_wrapper_workflow_with_failure())
236
+
237
+ print("\n✅ All tests passed!")
@@ -0,0 +1,343 @@
1
+ """Integration tests for system prompt enhancement with runtimes."""
2
+ import pytest
3
+ import os
4
+ from unittest.mock import patch, MagicMock
5
+ from control_plane_api.worker.services.system_prompt_enhancement import (
6
+ create_default_prompt_builder,
7
+ TodoListEnhancement,
8
+ SystemPromptBuilder,
9
+ )
10
+ from control_plane_api.worker.runtimes.agno.config import build_agno_agent_config
11
+ from control_plane_api.worker.runtimes.base import RuntimeExecutionContext
12
+
13
+
14
+ class TestClaudeCodeRuntimeIntegration:
15
+ """Test integration with Claude Code runtime."""
16
+
17
+ def test_claude_code_receives_enhanced_prompt(self):
18
+ """Test that Claude Code runtime receives enhanced prompts with TODO instructions."""
19
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
20
+
21
+ base_prompt = "You are a helpful coding assistant."
22
+ enhanced = _prompt_builder.build(base_prompt, "claude_code")
23
+
24
+ # Should have base prompt
25
+ assert base_prompt in enhanced
26
+
27
+ # Should have TODO list enhancement
28
+ assert "TODO list" in enhanced
29
+ assert "Task Management" in enhanced
30
+ assert "multi step tasks" in enhanced
31
+
32
+ # Should be longer than base
33
+ assert len(enhanced) > len(base_prompt)
34
+
35
+ def test_claude_code_prompt_builder_singleton(self):
36
+ """Test that Claude Code runtime uses a singleton prompt builder."""
37
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
38
+
39
+ # Should be a SystemPromptBuilder instance
40
+ assert isinstance(_prompt_builder, SystemPromptBuilder)
41
+
42
+ # Should have at least one enhancement (TodoList)
43
+ assert len(_prompt_builder._enhancements) >= 1
44
+
45
+ # First enhancement should be TodoListEnhancement
46
+ assert isinstance(_prompt_builder._enhancements[0], TodoListEnhancement)
47
+
48
+ def test_claude_code_runtime_with_empty_prompt(self):
49
+ """Test Claude Code runtime with no base prompt."""
50
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
51
+
52
+ enhanced = _prompt_builder.build(None, "claude_code")
53
+
54
+ # Should still have enhancement
55
+ assert "TODO list" in enhanced
56
+ assert len(enhanced) > 0
57
+
58
+ def test_claude_code_runtime_with_long_prompt(self):
59
+ """Test Claude Code runtime with a long base prompt."""
60
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
61
+
62
+ base_prompt = "You are an expert software engineer.\n" * 50
63
+ enhanced = _prompt_builder.build(base_prompt, "claude_code")
64
+
65
+ # Should preserve base prompt
66
+ assert base_prompt in enhanced
67
+
68
+ # Should add enhancement at the end
69
+ assert enhanced.endswith("Task Management\n" +
70
+ "Where suitable for multi step tasks, always create a TODO list " +
71
+ "to decouple the task into subtasks. This helps you track progress " +
72
+ "and ensures no steps are missed.")
73
+
74
+
75
+ class TestAgnoRuntimeIntegration:
76
+ """Test integration with Agno runtime."""
77
+
78
+ def test_agno_does_not_receive_todo_enhancement(self):
79
+ """Test that Agno runtime does NOT receive TODO list enhancement."""
80
+ from control_plane_api.worker.runtimes.agno.config import _prompt_builder
81
+
82
+ base_prompt = "You are a helpful assistant."
83
+ enhanced = _prompt_builder.build(base_prompt, "agno")
84
+
85
+ # Should have base prompt unchanged
86
+ assert enhanced == base_prompt
87
+
88
+ # Should NOT have TODO list enhancement
89
+ assert "TODO list" not in enhanced
90
+
91
+ def test_agno_prompt_builder_singleton(self):
92
+ """Test that Agno runtime uses a singleton prompt builder."""
93
+ from control_plane_api.worker.runtimes.agno.config import _prompt_builder
94
+
95
+ # Should be a SystemPromptBuilder instance
96
+ assert isinstance(_prompt_builder, SystemPromptBuilder)
97
+
98
+ # Should have at least one enhancement (TodoList)
99
+ assert len(_prompt_builder._enhancements) >= 1
100
+
101
+ # Even though it has TodoList enhancement, it shouldn't apply to agno
102
+ base = "Test prompt"
103
+ enhanced = _prompt_builder.build(base, "agno")
104
+ assert enhanced == base
105
+
106
+ @patch.dict(os.environ, {"LITELLM_API_KEY": "test-key"})
107
+ def test_agno_agent_config_with_enhanced_prompt(self):
108
+ """Test that Agno agent config properly uses enhanced prompts."""
109
+ base_prompt = "You are a data analyst."
110
+
111
+ agent = build_agno_agent_config(
112
+ agent_id="test-agent",
113
+ system_prompt=base_prompt,
114
+ model_id="kubiya/claude-sonnet-4",
115
+ skills=[],
116
+ )
117
+
118
+ # Agent role should match the base prompt (no TODO enhancement)
119
+ assert agent.role == base_prompt
120
+ assert "TODO list" not in agent.role
121
+
122
+ @patch.dict(os.environ, {"LITELLM_API_KEY": "test-key"})
123
+ def test_agno_agent_config_with_no_prompt(self):
124
+ """Test Agno agent config with no system prompt."""
125
+ agent = build_agno_agent_config(
126
+ agent_id="test-agent",
127
+ system_prompt=None,
128
+ model_id="kubiya/claude-sonnet-4",
129
+ )
130
+
131
+ # Should use default prompt
132
+ assert agent.role == "You are a helpful AI assistant"
133
+ assert "TODO list" not in agent.role
134
+
135
+
136
+ class TestEnvironmentVariableConfiguration:
137
+ """Test that environment variables control enhancements."""
138
+
139
+ @patch.dict(os.environ, {"DISABLE_SYSTEM_PROMPT_ENHANCEMENTS": "true"})
140
+ def test_global_disable_affects_both_runtimes(self):
141
+ """Test that DISABLE_SYSTEM_PROMPT_ENHANCEMENTS disables for both runtimes."""
142
+ # Need to reimport to pick up env var
143
+ import importlib
144
+ from control_plane_api.worker.services import system_prompt_enhancement
145
+ importlib.reload(system_prompt_enhancement)
146
+
147
+ builder = system_prompt_enhancement.create_default_prompt_builder()
148
+
149
+ # Should not enhance for either runtime
150
+ claude_result = builder.build("Test", "claude_code")
151
+ assert "TODO list" not in claude_result
152
+ assert claude_result == "Test"
153
+
154
+ agno_result = builder.build("Test", "agno")
155
+ assert "TODO list" not in agno_result
156
+ assert agno_result == "Test"
157
+
158
+ @patch.dict(os.environ, {"ENABLE_TODO_LIST_ENHANCEMENT": "false"})
159
+ def test_disable_specific_enhancement(self):
160
+ """Test that ENABLE_TODO_LIST_ENHANCEMENT=false disables TODO enhancement."""
161
+ # Need to reimport to pick up env var
162
+ import importlib
163
+ from control_plane_api.worker.services import system_prompt_enhancement
164
+ importlib.reload(system_prompt_enhancement)
165
+
166
+ builder = system_prompt_enhancement.create_default_prompt_builder()
167
+
168
+ # Should not have TODO enhancement
169
+ result = builder.build("Test", "claude_code")
170
+ assert "TODO list" not in result
171
+ assert result == "Test"
172
+
173
+
174
+ class TestMultipleEnhancementsLayering:
175
+ """Test that multiple enhancements can be layered."""
176
+
177
+ def test_multiple_enhancements_stack(self):
178
+ """Test that multiple enhancements are applied in order."""
179
+ from control_plane_api.worker.services.system_prompt_enhancement import (
180
+ SystemPromptEnhancement,
181
+ RuntimeType,
182
+ )
183
+
184
+ class TestEnhancement1(SystemPromptEnhancement):
185
+ def __init__(self):
186
+ super().__init__(runtime_types=[RuntimeType.CLAUDE_CODE])
187
+
188
+ @property
189
+ def name(self):
190
+ return "test_1"
191
+
192
+ def enhance(self, base_prompt):
193
+ return (base_prompt or "") + "\n\nEnhancement 1"
194
+
195
+ class TestEnhancement2(SystemPromptEnhancement):
196
+ def __init__(self):
197
+ super().__init__(runtime_types=[RuntimeType.CLAUDE_CODE])
198
+
199
+ @property
200
+ def name(self):
201
+ return "test_2"
202
+
203
+ def enhance(self, base_prompt):
204
+ return (base_prompt or "") + "\n\nEnhancement 2"
205
+
206
+ builder = SystemPromptBuilder()
207
+ builder.add_enhancement(TestEnhancement1())
208
+ builder.add_enhancement(TestEnhancement2())
209
+
210
+ result = builder.build("Base", "claude_code")
211
+
212
+ # Should have base and both enhancements in order
213
+ assert "Base" in result
214
+ assert "Enhancement 1" in result
215
+ assert "Enhancement 2" in result
216
+
217
+ # Enhancements should be in order
218
+ idx_base = result.index("Base")
219
+ idx_e1 = result.index("Enhancement 1")
220
+ idx_e2 = result.index("Enhancement 2")
221
+ assert idx_base < idx_e1 < idx_e2
222
+
223
+ def test_different_runtimes_get_different_enhancements(self):
224
+ """Test that enhancements can be runtime-specific."""
225
+ from control_plane_api.worker.services.system_prompt_enhancement import (
226
+ SystemPromptEnhancement,
227
+ RuntimeType,
228
+ )
229
+
230
+ class ClaudeOnlyEnhancement(SystemPromptEnhancement):
231
+ def __init__(self):
232
+ super().__init__(runtime_types=[RuntimeType.CLAUDE_CODE])
233
+
234
+ @property
235
+ def name(self):
236
+ return "claude_only"
237
+
238
+ def enhance(self, base_prompt):
239
+ return (base_prompt or "") + "\nClaude Only"
240
+
241
+ class AgnoOnlyEnhancement(SystemPromptEnhancement):
242
+ def __init__(self):
243
+ super().__init__(runtime_types=[RuntimeType.AGNO])
244
+
245
+ @property
246
+ def name(self):
247
+ return "agno_only"
248
+
249
+ def enhance(self, base_prompt):
250
+ return (base_prompt or "") + "\nAgno Only"
251
+
252
+ builder = SystemPromptBuilder()
253
+ builder.add_enhancement(ClaudeOnlyEnhancement())
254
+ builder.add_enhancement(AgnoOnlyEnhancement())
255
+
256
+ # Claude Code should get Claude enhancement
257
+ claude_result = builder.build("Base", "claude_code")
258
+ assert "Claude Only" in claude_result
259
+ assert "Agno Only" not in claude_result
260
+
261
+ # Agno should get Agno enhancement
262
+ agno_result = builder.build("Base", "agno")
263
+ assert "Agno Only" in agno_result
264
+ assert "Claude Only" not in agno_result
265
+
266
+
267
+ class TestRuntimeConfigBuilders:
268
+ """Test integration with actual runtime config builders."""
269
+
270
+ @patch.dict(os.environ, {"LITELLM_API_KEY": "test-key"})
271
+ def test_agno_config_builder_integration(self):
272
+ """Test full integration with Agno config builder."""
273
+ agent = build_agno_agent_config(
274
+ agent_id="integration-test",
275
+ system_prompt="You are a DevOps expert.",
276
+ model_id="kubiya/claude-sonnet-4",
277
+ )
278
+
279
+ # Role should be the prompt without TODO enhancement
280
+ assert agent.role == "You are a DevOps expert."
281
+ assert "TODO list" not in agent.role
282
+
283
+ def test_enhancement_preserves_newlines_and_formatting(self):
284
+ """Test that enhancements preserve newlines and formatting in base prompt."""
285
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
286
+
287
+ base_prompt = """You are a helpful assistant.
288
+
289
+ Follow these rules:
290
+ 1. Be concise
291
+ 2. Be accurate
292
+ 3. Be helpful"""
293
+
294
+ enhanced = _prompt_builder.build(base_prompt, "claude_code")
295
+
296
+ # Should preserve base prompt formatting
297
+ assert base_prompt in enhanced
298
+
299
+ # Should have enhancement appended
300
+ assert "TODO list" in enhanced
301
+
302
+
303
+ class TestEdgeCases:
304
+ """Test edge cases and error handling."""
305
+
306
+ def test_enhancement_with_unicode_characters(self):
307
+ """Test that enhancements work with unicode characters."""
308
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
309
+
310
+ base_prompt = "You are a helpful assistant. 你好 🚀"
311
+ enhanced = _prompt_builder.build(base_prompt, "claude_code")
312
+
313
+ # Should preserve unicode
314
+ assert base_prompt in enhanced
315
+ assert "TODO list" in enhanced
316
+
317
+ def test_enhancement_with_very_long_prompt(self):
318
+ """Test that enhancements work with very long prompts."""
319
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
320
+
321
+ # Create a 10KB prompt
322
+ base_prompt = "You are a helpful assistant.\n" * 500
323
+ enhanced = _prompt_builder.build(base_prompt, "claude_code")
324
+
325
+ # Should preserve full base prompt
326
+ assert base_prompt in enhanced
327
+
328
+ # Should add enhancement
329
+ assert "TODO list" in enhanced
330
+
331
+ def test_enhancement_with_empty_string(self):
332
+ """Test enhancement with empty string (not None)."""
333
+ from control_plane_api.worker.runtimes.claude_code.config import _prompt_builder
334
+
335
+ enhanced = _prompt_builder.build("", "claude_code")
336
+
337
+ # Should add enhancement even for empty string
338
+ assert "TODO list" in enhanced
339
+
340
+
341
+ if __name__ == "__main__":
342
+ # Run tests
343
+ pytest.main([__file__, "-v", "-s"])
File without changes