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,611 @@
1
+ """
2
+ Task Planning Router - AI-powered task analysis and planning using Claude Code SDK or Agno
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from fastapi import APIRouter, HTTPException, status, Depends, Request
8
+ from fastapi.responses import StreamingResponse
9
+ from typing import AsyncIterator, Optional
10
+ from sqlalchemy.orm import Session
11
+ import structlog
12
+ import os
13
+ import traceback
14
+ import json
15
+ import asyncio
16
+ import re
17
+ import time
18
+
19
+ from control_plane_api.app.database import get_db
20
+ from control_plane_api.app.lib.litellm_pricing import get_litellm_pricing
21
+
22
+ # Import OpenTelemetry for rich tracing
23
+ from control_plane_api.app.observability import (
24
+ create_span_with_context,
25
+ add_span_event,
26
+ add_span_error,
27
+ get_current_trace_id,
28
+ )
29
+
30
+ # Import all models from the new models package
31
+ from control_plane_api.app.models.task_planning import (
32
+ TaskPlanRequest,
33
+ TaskPlanResponse,
34
+ )
35
+
36
+ # Import public functions from task_planning library
37
+ from control_plane_api.app.lib.task_planning import format_sse_message
38
+ from control_plane_api.app.lib.task_planning.planning_workflow import (
39
+ create_planning_workflow as create_multistep_workflow,
40
+ run_planning_workflow_stream,
41
+ )
42
+
43
+ # Import private helper functions directly from helpers module
44
+ from control_plane_api.app.lib.task_planning.helpers import (
45
+ _extract_organization_id_from_token,
46
+ _get_organization_id_fallback,
47
+ )
48
+
49
+ # Import planning strategy factory
50
+ from control_plane_api.app.services.planning_strategy_factory import get_planning_strategy
51
+
52
+ # Import entity resolver for converting entity names to UUIDs
53
+ from control_plane_api.app.lib.task_planning.entity_resolver import resolve_plan_entities
54
+
55
+ router = APIRouter()
56
+ logger = structlog.get_logger()
57
+
58
+ # Planning timeout configuration (in seconds)
59
+ PLANNING_TIMEOUT = int(os.getenv("PLANNING_TIMEOUT_SECONDS", "180")) # Default: 3 minutes
60
+
61
+ # Planning retry configuration
62
+ MAX_PLANNING_RETRIES = int(os.getenv("MAX_PLANNING_RETRIES", "2")) # Default: 2 retries (3 total attempts)
63
+
64
+ # Planning Strategy Selection (defaults to "agno")
65
+ # Options: "claude_code_sdk", "agno"
66
+ # Like choosing transportation: train, walk, or flight!
67
+ PLANNING_STRATEGY = os.getenv("PLANNING_STRATEGY", "agno").lower()
68
+
69
+
70
+ @router.post("/tasks/plan")
71
+ async def plan_task(task_request: TaskPlanRequest, http_request: Request, db: Session = Depends(get_db)):
72
+ """
73
+ Generate an AI-powered task plan (non-streaming)
74
+
75
+ Uses the same 2-step workflow as /tasks/plan/stream but returns the final plan directly.
76
+ This endpoint is used by the CLI for fast planning mode.
77
+
78
+ The 2-step workflow:
79
+ - Step 1: Analysis & Resource Selection (discovers agents/teams, selects best match)
80
+ - Step 2: Full Plan Generation (creates TaskPlanResponse with costs, risks, etc.)
81
+
82
+ Benefits:
83
+ - Faster than old 4-step workflow (45-55s vs 119s)
84
+ - Consistent behavior with streaming endpoint
85
+ - Smart pre-fetching of top 20 resources
86
+ """
87
+ # Extract API token from Authorization header
88
+ auth_header = http_request.headers.get("authorization", "")
89
+ api_token = auth_header.replace("UserKey ", "").replace("Bearer ", "") if auth_header else None
90
+
91
+ # Extract organization ID from token (needed for entity resolution)
92
+ organization_id = None
93
+ if not api_token:
94
+ logger.error("no_api_token_provided")
95
+ raise HTTPException(
96
+ status_code=status.HTTP_401_UNAUTHORIZED,
97
+ detail="Authentication required: No API token provided. Please configure your API token."
98
+ )
99
+
100
+ try:
101
+ organization_id = _extract_organization_id_from_token(api_token)
102
+ if not organization_id:
103
+ raise ValueError("Token does not contain organization_id")
104
+ logger.info("extracted_organization_id", organization_id=organization_id)
105
+ except Exception as e:
106
+ logger.error("failed_to_extract_organization_id", error=str(e))
107
+ raise HTTPException(
108
+ status_code=status.HTTP_401_UNAUTHORIZED,
109
+ detail=f"Authentication failed: Could not extract organization from token. Error: {str(e)}"
110
+ )
111
+
112
+ # Create custom span for task planning business logic
113
+ with create_span_with_context(
114
+ "task_planning.generate_plan",
115
+ organization_id=organization_id,
116
+ attributes={
117
+ "task.description": task_request.description[:200],
118
+ "task.priority": task_request.priority,
119
+ "task.quick_mode": task_request.quick_mode,
120
+ }
121
+ ) as plan_span:
122
+ try:
123
+ trace_id = get_current_trace_id()
124
+
125
+ add_span_event("Task planning request received", {
126
+ "description_length": len(task_request.description),
127
+ "quick_mode": str(task_request.quick_mode),
128
+ "trace_id": trace_id,
129
+ })
130
+
131
+ logger.info(
132
+ "task_planning_requested_nonstreaming",
133
+ description=task_request.description[:100],
134
+ quick_mode=task_request.quick_mode,
135
+ organization_id=organization_id,
136
+ trace_id=trace_id,
137
+ )
138
+
139
+ # Let the planner fetch everything from DB (no outer_context needed)
140
+ outer_context = None
141
+
142
+ add_span_event("Creating 2-step planning workflow", {
143
+ "workflow_type": "unified_2step",
144
+ "quick_mode": str(task_request.quick_mode),
145
+ })
146
+
147
+ # Create the unified 2-step workflow
148
+ logger.info(
149
+ "using_unified_2step_workflow_nonstreaming",
150
+ message="Unified 2-step workflow (same as streaming endpoint)",
151
+ quick_mode=task_request.quick_mode,
152
+ trace_id=trace_id,
153
+ )
154
+ workflow = create_multistep_workflow(
155
+ db=db,
156
+ organization_id=organization_id,
157
+ api_token=api_token,
158
+ quick_mode=task_request.quick_mode,
159
+ outer_context=outer_context
160
+ )
161
+
162
+ # Run workflow without streaming (collect all events internally)
163
+ add_span_event("Executing planning workflow", {"workflow_mode": "non_streaming"})
164
+ logger.info("executing_workflow_nonstreaming", trace_id=trace_id)
165
+
166
+ # Create a simple event collector (we don't need to stream events to client)
167
+ events_collected = []
168
+ def collect_event(event):
169
+ events_collected.append(event)
170
+ # Also log workflow events to span
171
+ if isinstance(event, dict) and event.get("type"):
172
+ add_span_event(f"Workflow: {event.get('type')}", {"event_data": str(event)[:200]})
173
+
174
+ # Run the workflow
175
+ start_time = time.time()
176
+ plan = run_planning_workflow_stream(
177
+ workflow,
178
+ task_request,
179
+ collect_event, # Internal event collector
180
+ task_request.quick_mode
181
+ )
182
+ workflow_duration = time.time() - start_time
183
+
184
+ add_span_event("Workflow completed", {
185
+ "duration_seconds": f"{workflow_duration:.2f}",
186
+ "events_collected": len(events_collected),
187
+ })
188
+
189
+ # Resolve entity names to UUIDs before returning the plan
190
+ add_span_event("Resolving plan entities", {"organization_id": organization_id})
191
+ await resolve_plan_entities(
192
+ plan_response=plan,
193
+ organization_id=organization_id,
194
+ db=db
195
+ )
196
+ add_span_event("Plan entities resolved", {"plan_title": plan.title})
197
+ logger.info("plan_entities_resolved_nonstreaming", plan_title=plan.title, trace_id=trace_id)
198
+
199
+ plan_span.set_attribute("plan.title", plan.title)
200
+ plan_span.set_attribute("plan.workflow_duration_seconds", workflow_duration)
201
+ plan_span.set_attribute("plan.success", True)
202
+
203
+ logger.info("task_plan_generated_nonstreaming", title=plan.title, trace_id=trace_id)
204
+ return {"plan": plan}
205
+
206
+ except ValueError as e:
207
+ # Validation errors should return 422 (Unprocessable Entity)
208
+ add_span_error(e, {"error_type": "validation"})
209
+ error_msg = str(e)
210
+ if "validation" in error_msg.lower() or "does NOT exist" in error_msg or "does not exist" in error_msg:
211
+ logger.error("task_planning_validation_error", error=error_msg, trace_id=trace_id, traceback=traceback.format_exc())
212
+ raise HTTPException(
213
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
214
+ detail={
215
+ "error": "validation_failed",
216
+ "message": "The task planner generated invalid output that failed validation",
217
+ "details": error_msg,
218
+ "suggestion": "This usually means the AI tried to use non-existent agents/teams. Please try again or check your available resources."
219
+ }
220
+ )
221
+ else:
222
+ # Other ValueError issues
223
+ logger.error("task_planning_value_error", error=str(e), traceback=traceback.format_exc())
224
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Task planning failed: {str(e)}")
225
+
226
+ except Exception as e:
227
+ add_span_error(e, {"error_type": "general"})
228
+ logger.error("task_planning_error", error=str(e), trace_id=trace_id, traceback=traceback.format_exc())
229
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Task planning failed: {str(e)}")
230
+
231
+
232
+ async def generate_task_plan_stream(
233
+ request: TaskPlanRequest, db: Session, api_token: Optional[str] = None
234
+ ) -> AsyncIterator[str]:
235
+ """
236
+ Generate task plan using unified 2-step Agno workflow
237
+
238
+ This implementation uses a streamlined 2-step workflow with validated outputs:
239
+ - Step 1: Task Analysis & Resource Selection (AnalysisAndSelectionOutput schema)
240
+ - Step 2: Full Plan Generation with Costs (TaskPlanResponse schema)
241
+
242
+ Benefits:
243
+ - Faster than old 4-step workflow (45-55s vs 119s)
244
+ - Type-safe communication between agents (no text parsing)
245
+ - Validated outputs at each step (eliminates hallucination risk)
246
+ - Smart pre-fetching of top 20 resources (limits context window)
247
+ - Real-time progress updates via SSE
248
+ - Single unified workflow for all cases (no fast/slow path split)
249
+ """
250
+ try:
251
+ # Yield initial progress with informative message
252
+ yield format_sse_message(
253
+ "progress", {"stage": "initializing", "message": "🚀 Initializing AI Task Planner - preparing to discover available agents, teams, and resources...", "progress": 10}
254
+ )
255
+
256
+ logger.info(
257
+ "task_planning_stream_v2_requested",
258
+ description=request.description[:100],
259
+ priority=request.priority,
260
+ )
261
+
262
+ # Extract organization ID from token (REQUIRED for entity resolution)
263
+ organization_id = None
264
+ if not api_token:
265
+ logger.error("no_api_token_provided", message="API token is required for task planning")
266
+ yield format_sse_message("error", {
267
+ "message": "Authentication required: No API token provided. Please configure your API token."
268
+ })
269
+ return
270
+
271
+ try:
272
+ organization_id = _extract_organization_id_from_token(api_token)
273
+ if not organization_id:
274
+ raise ValueError("Token does not contain organization_id")
275
+ logger.info("extracted_organization_id", organization_id=organization_id)
276
+ except Exception as e:
277
+ logger.error("failed_to_extract_organization_id", error=str(e))
278
+ yield format_sse_message("error", {
279
+ "message": f"Authentication failed: Could not extract organization from token. Error: {str(e)}"
280
+ })
281
+ return
282
+
283
+ # SIMPLIFIED: Let the planner fetch everything from DB
284
+ # CLI no longer needs to fetch agents/teams/environments/queues
285
+ # This centralizes data fetching in the API where it belongs
286
+ outer_context = None
287
+ logger.info(
288
+ "planner_will_fetch_resources",
289
+ message="Planner will fetch all resources from database (agents, teams, environments, queues)"
290
+ )
291
+
292
+ # UNIFIED WORKFLOW: Always use the 2-step workflow (fast enough for all cases)
293
+ # quick_mode is passed as an optimization hint but same workflow is used
294
+ logger.info(
295
+ "using_unified_2step_workflow",
296
+ message="Unified 2-step workflow (fast enough for all cases)",
297
+ quick_mode=request.quick_mode,
298
+ has_outer_context=bool(outer_context)
299
+ )
300
+ workflow = create_multistep_workflow(
301
+ db=db,
302
+ organization_id=organization_id,
303
+ api_token=api_token,
304
+ quick_mode=request.quick_mode, # Passed as optimization hint
305
+ outer_context=outer_context
306
+ )
307
+
308
+ # Set up event queue for streaming progress updates
309
+ event_queue = asyncio.Queue()
310
+
311
+ # Capture the event loop before starting background work
312
+ loop = asyncio.get_event_loop()
313
+
314
+ def publish_event(event_dict):
315
+ """Publish event to the queue (thread-safe)"""
316
+ try:
317
+ loop.call_soon_threadsafe(event_queue.put_nowait, event_dict)
318
+ except Exception as e:
319
+ logger.error("failed_to_publish_event", error=str(e), event=event_dict)
320
+
321
+ # Run workflow in executor (blocking operation)
322
+ logger.info("running_workflow_in_executor")
323
+
324
+ workflow_complete = False
325
+ workflow_result = None
326
+ workflow_error = None
327
+
328
+ async def run_workflow_async():
329
+ """Run the workflow in a thread pool"""
330
+ nonlocal workflow_complete, workflow_result, workflow_error
331
+ try:
332
+ # Run blocking workflow in thread pool
333
+ result = await asyncio.to_thread(
334
+ run_planning_workflow_stream,
335
+ workflow,
336
+ request,
337
+ publish_event,
338
+ request.quick_mode # Pass quick_mode to skip verbose reasoning
339
+ )
340
+ workflow_result = result
341
+ except Exception as e:
342
+ logger.error("workflow_execution_failed", error=str(e), exc_info=True)
343
+ workflow_error = e
344
+ finally:
345
+ workflow_complete = True
346
+
347
+ # Start workflow task
348
+ workflow_task = asyncio.create_task(run_workflow_async())
349
+
350
+ # Stream events from queue as they arrive
351
+ while not workflow_complete:
352
+ try:
353
+ # Try to get event from queue (with short timeout for responsive UI)
354
+ # 0.5s provides good balance between responsiveness and CPU usage
355
+ event = await asyncio.wait_for(event_queue.get(), timeout=0.5)
356
+
357
+ event_type = event.get("event")
358
+ event_data = event.get("data", {})
359
+
360
+ # Map workflow events to UI-compatible events for backward compatibility
361
+ if event_type == "step_started":
362
+ # Map to progress event with step description
363
+ yield format_sse_message("progress", {
364
+ "message": event_data.get("step_description", "Processing..."),
365
+ "progress": event_data.get("progress", 0)
366
+ })
367
+ elif event_type == "step_completed":
368
+ # Map to progress event showing completion
369
+ yield format_sse_message("progress", {
370
+ "message": f"{event_data.get('step_name', 'Step')} completed",
371
+ "progress": event_data.get("progress", 0)
372
+ })
373
+ elif event_type == "tool_call":
374
+ # Pass through tool_call events for elegant UI display
375
+ # CLI will render these with nice formatting
376
+ yield format_sse_message("tool_call", {
377
+ "tool_name": event_data.get("tool_name", "unknown"),
378
+ "tool_id": event_data.get("tool_id"),
379
+ "step": event_data.get("step"),
380
+ "timestamp": event_data.get("timestamp"),
381
+ })
382
+ elif event_type == "tool_result":
383
+ # Pass through tool_result for completion feedback
384
+ yield format_sse_message("tool_result", {
385
+ "tool_name": event_data.get("tool_name", "unknown"),
386
+ "tool_id": event_data.get("tool_id"),
387
+ "status": event_data.get("status", "success"),
388
+ "duration": event_data.get("duration"),
389
+ "step": event_data.get("step"),
390
+ })
391
+ elif event_type == "validation_error":
392
+ # Map to error event
393
+ yield format_sse_message("error", {
394
+ "message": f"Validation error: {event_data.get('error', 'Unknown error')}"
395
+ })
396
+ else:
397
+ # Pass through other events as-is (progress, error, etc.)
398
+ yield format_sse_message(event_type, event_data)
399
+
400
+ await asyncio.sleep(0) # Flush immediately
401
+
402
+ except asyncio.TimeoutError:
403
+ # No event in queue, check if workflow is done
404
+ if workflow_task.done():
405
+ workflow_complete = True
406
+ break
407
+ # Otherwise continue waiting
408
+ continue
409
+
410
+ # Drain any remaining events (with same mapping)
411
+ while not event_queue.empty():
412
+ try:
413
+ event = event_queue.get_nowait()
414
+ event_type = event.get("event")
415
+ event_data = event.get("data", {})
416
+
417
+ # Apply same event mapping as main loop
418
+ if event_type == "step_started":
419
+ yield format_sse_message("progress", {
420
+ "message": event_data.get("step_description", "Processing..."),
421
+ "progress": event_data.get("progress", 0)
422
+ })
423
+ elif event_type == "step_completed":
424
+ yield format_sse_message("progress", {
425
+ "message": f"{event_data.get('step_name', 'Step')} completed",
426
+ "progress": event_data.get("progress", 0)
427
+ })
428
+ elif event_type == "tool_call":
429
+ yield format_sse_message("tool_call", {
430
+ "tool_name": event_data.get("tool_name", "unknown"),
431
+ "tool_id": event_data.get("tool_id"),
432
+ "step": event_data.get("step"),
433
+ "timestamp": event_data.get("timestamp"),
434
+ })
435
+ elif event_type == "tool_result":
436
+ yield format_sse_message("tool_result", {
437
+ "tool_name": event_data.get("tool_name", "unknown"),
438
+ "tool_id": event_data.get("tool_id"),
439
+ "status": event_data.get("status", "success"),
440
+ "duration": event_data.get("duration"),
441
+ "step": event_data.get("step"),
442
+ })
443
+ elif event_type == "validation_error":
444
+ yield format_sse_message("error", {
445
+ "message": f"Validation error: {event_data.get('error', 'Unknown error')}"
446
+ })
447
+ else:
448
+ yield format_sse_message(event_type, event_data)
449
+
450
+ await asyncio.sleep(0)
451
+ except asyncio.QueueEmpty:
452
+ break
453
+
454
+ # Check for workflow errors
455
+ if workflow_error:
456
+ logger.error("workflow_failed", error=str(workflow_error))
457
+ yield format_sse_message("error", {"message": f"Workflow failed: {str(workflow_error)}"})
458
+ raise workflow_error
459
+
460
+ # Validate result
461
+ if not workflow_result:
462
+ error_msg = "Workflow completed but returned no result"
463
+ logger.error("workflow_no_result")
464
+ yield format_sse_message("error", {"message": error_msg})
465
+ raise ValueError(error_msg)
466
+
467
+ # Validate result (always expecting TaskPlanResponse from 2-step workflow)
468
+ from pydantic import ValidationError
469
+ try:
470
+ if isinstance(workflow_result, TaskPlanResponse):
471
+ plan = workflow_result
472
+ elif isinstance(workflow_result, dict):
473
+ plan = TaskPlanResponse.model_validate(workflow_result)
474
+ else:
475
+ raise ValueError(f"Unexpected result type: {type(workflow_result)}")
476
+ except ValidationError as e:
477
+ logger.error("plan_validation_failed", errors=e.errors())
478
+ yield format_sse_message("error", {"message": f"Invalid plan structure: {str(e)}"})
479
+ raise
480
+
481
+ # Resolve entity names to UUIDs before sending final plan
482
+ # This is CRITICAL - without proper UUIDs, execution will fail
483
+ # Note: organization_id was already extracted earlier in this function (line ~268)
484
+ if not organization_id:
485
+ # If no organization_id, fail early with clear error
486
+ error_msg = "Failed to resolve entity IDs: No organization context available"
487
+ logger.error("no_organization_id_for_entity_resolution_stream")
488
+ yield format_sse_message("error", {"message": error_msg})
489
+ return # Stop here - don't send incomplete plan
490
+
491
+ await resolve_plan_entities(
492
+ plan_response=plan,
493
+ organization_id=organization_id,
494
+ db=db
495
+ )
496
+ logger.info("plan_entities_resolved_stream", plan_title=plan.title)
497
+
498
+ # Yield final plan event with "complete" type (CLI expects this event type)
499
+ logger.info("workflow_completed_successfully", title=plan.title)
500
+ yield format_sse_message(
501
+ "complete",
502
+ {
503
+ "plan": plan.model_dump(),
504
+ "progress": 100,
505
+ "message": "✅ Plan generated successfully!"
506
+ }
507
+ )
508
+
509
+ except ValueError as e:
510
+ # Validation errors - return structured error message in stream
511
+ error_msg = str(e)
512
+ error_type = "validation_error" if ("validation" in error_msg.lower() or "does NOT exist" in error_msg or "does not exist" in error_msg) else "value_error"
513
+
514
+ logger.error(f"task_planning_stream_{error_type}", error=error_msg, exc_info=True)
515
+
516
+ yield format_sse_message(
517
+ "error",
518
+ {
519
+ "error": error_type,
520
+ "message": "The task planner generated invalid output that failed validation" if error_type == "validation_error" else "Task planning failed",
521
+ "details": error_msg,
522
+ "suggestion": "This usually means the AI tried to use non-existent agents/teams. Please try again or check your available resources." if error_type == "validation_error" else None
523
+ }
524
+ )
525
+
526
+ except Exception as e:
527
+ from sqlalchemy.exc import OperationalError, DisconnectionError
528
+ from control_plane_api.app.database import dispose_engine, IS_SERVERLESS
529
+
530
+ error_type = type(e).__name__
531
+ logger.error("task_planning_stream_v2_error", error=str(e), error_type=error_type, exc_info=True)
532
+
533
+ # Specific handling for database connection errors
534
+ if isinstance(e, (OperationalError, DisconnectionError)):
535
+ error_msg = "Database connection lost. Please try again."
536
+ if IS_SERVERLESS:
537
+ dispose_engine()
538
+ else:
539
+ error_msg = f"Task planning failed: {str(e)}"
540
+
541
+ yield format_sse_message("error", {"message": error_msg})
542
+ finally:
543
+ # Cleanup database connections in serverless
544
+ from control_plane_api.app.database import dispose_engine, IS_SERVERLESS
545
+ if IS_SERVERLESS:
546
+ logger.info("cleaning_up_serverless_connections")
547
+ dispose_engine()
548
+
549
+
550
+ @router.post("/tasks/plan/stream")
551
+ async def plan_task_stream(task_request: TaskPlanRequest, http_request: Request, db: Session = Depends(get_db)):
552
+ """
553
+ Generate an AI-powered task plan with streaming
554
+
555
+ Uses a single intelligent agent with tool streaming for real-time progress updates.
556
+ Streams: tool calls → thinking → plan generation → complete
557
+
558
+ The agent has access to context graph tools for intelligent resource discovery.
559
+ """
560
+ # Extract API token from Authorization header
561
+ auth_header = http_request.headers.get("authorization", "")
562
+ api_token = auth_header.replace("UserKey ", "").replace("Bearer ", "") if auth_header else None
563
+
564
+ logger.info("task_planning_stream_requested")
565
+
566
+ # Use the robust 4-step workflow implementation
567
+ return StreamingResponse(
568
+ generate_task_plan_stream(task_request, db, api_token),
569
+ media_type="text/event-stream",
570
+ headers={
571
+ "Cache-Control": "no-cache",
572
+ "Connection": "keep-alive",
573
+ "X-Accel-Buffering": "no",
574
+ },
575
+ )
576
+
577
+
578
+ @router.get("/tasks/plan/health")
579
+ async def planning_health():
580
+ """Health check for task planning endpoint with strategy availability info"""
581
+ available_strategies = []
582
+
583
+ # Check Agno availability (should always be available)
584
+ try:
585
+ from control_plane_api.app.services.agno_planning_strategy import AgnoPlanningStrategy
586
+ available_strategies.append("agno")
587
+ except ImportError:
588
+ pass
589
+
590
+ # Check Claude Code SDK availability
591
+ try:
592
+ from claude_agent_sdk import ClaudeSDKClient
593
+ # Check if Claude CLI binary is available
594
+ import shutil
595
+ if shutil.which("claude"):
596
+ available_strategies.append("claude_code_sdk")
597
+ except ImportError:
598
+ pass
599
+
600
+ current_strategy = os.getenv("PLANNING_STRATEGY", "agno")
601
+ is_healthy = current_strategy in available_strategies
602
+ environment_type = "serverless" if (os.getenv("VERCEL") or os.getenv("AWS_LAMBDA_FUNCTION_NAME")) else "standard"
603
+
604
+ return {
605
+ "status": "healthy" if is_healthy else "degraded",
606
+ "service": "task_planning",
607
+ "current_strategy": current_strategy,
608
+ "available_strategies": available_strategies,
609
+ "environment": environment_type,
610
+ "recommended_strategy": "agno" if environment_type == "serverless" else current_strategy,
611
+ }