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,715 @@
1
+ """Team executor service - handles team execution business logic"""
2
+
3
+ from typing import Dict, Any, Optional, List
4
+ from datetime import datetime, timezone
5
+ import structlog
6
+ import asyncio
7
+ import os
8
+ from temporalio import activity
9
+
10
+ from agno.agent import Agent
11
+ from agno.team import Team
12
+ from agno.models.litellm import LiteLLM
13
+
14
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
15
+ from control_plane_api.worker.services.session_service import SessionService
16
+ from control_plane_api.worker.services.cancellation_manager import CancellationManager
17
+ from control_plane_api.worker.services.skill_factory import SkillFactory
18
+ from control_plane_api.worker.utils.streaming_utils import StreamingHelper
19
+ from control_plane_api.worker.utils.parameter_validator import (
20
+ validate_tool_parameters,
21
+ ParameterValidationError,
22
+ )
23
+
24
+ logger = structlog.get_logger()
25
+
26
+
27
+ class TeamExecutorService:
28
+ """
29
+ Service for executing teams with full session management and cancellation support.
30
+
31
+ This service orchestrates:
32
+ - Session loading and restoration
33
+ - Team and member agent creation with LiteLLM configuration
34
+ - Skill instantiation for team members
35
+ - Streaming execution with real-time updates
36
+ - Session persistence
37
+ - Cancellation support via CancellationManager
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ control_plane: ControlPlaneClient,
43
+ session_service: SessionService,
44
+ cancellation_manager: CancellationManager
45
+ ):
46
+ self.control_plane = control_plane
47
+ self.session_service = session_service
48
+ self.cancellation_manager = cancellation_manager
49
+
50
+ async def execute(self, input: Any) -> Dict[str, Any]:
51
+ """
52
+ Execute a team with full session management and streaming.
53
+
54
+ Args:
55
+ input: TeamExecutionInput with execution details
56
+
57
+ Returns:
58
+ Dict with response, usage, success flag, etc.
59
+ """
60
+ execution_id = input.execution_id
61
+
62
+ logger.info(
63
+ "team_workflow_start",
64
+ execution_id=execution_id[:8],
65
+ team_id=input.team_id,
66
+ session_id=input.session_id,
67
+ agent_count=len(input.agents)
68
+ )
69
+
70
+ try:
71
+ # STEP 1: Load session history
72
+ session_history = self.session_service.load_session(
73
+ execution_id=execution_id,
74
+ session_id=input.session_id
75
+ )
76
+
77
+ if session_history:
78
+ print(f"āœ… Loaded {len(session_history)} messages from previous session\n")
79
+ else:
80
+ print("ā„¹ļø Starting new conversation\n")
81
+
82
+ # STEP 2: Build conversation context for Agno
83
+ conversation_context = self.session_service.build_conversation_context(session_history)
84
+
85
+ # STEP 3: Get LiteLLM configuration
86
+ litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
87
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
88
+
89
+ if not litellm_api_key:
90
+ raise ValueError("LITELLM_API_KEY environment variable not set")
91
+
92
+ model = input.model_id or os.environ.get("LITELLM_DEFAULT_MODEL", "kubiya/claude-sonnet-4")
93
+
94
+ # STEP 4: Create member agents with skills
95
+ print(f"šŸ‘„ Creating {len(input.agents)} member agents...\n")
96
+
97
+ team_members = []
98
+ for agent_data in input.agents:
99
+ agent_id = agent_data.get("id")
100
+ agent_name = agent_data.get("name", f"Agent {agent_id}")
101
+ agent_role = agent_data.get("role", "You are a helpful AI assistant")
102
+
103
+ print(f" šŸ¤– Creating Agent: {agent_name}")
104
+ print(f" ID: {agent_id}")
105
+ print(f" Role: {agent_role[:80]}...")
106
+
107
+ # Fetch skills for this agent
108
+ skills = []
109
+ if agent_id:
110
+ try:
111
+ skill_configs = self.control_plane.get_skills(agent_id)
112
+
113
+ # AUTO-INCLUDE BUILT-IN SKILLS
114
+ if not skill_configs:
115
+ skill_configs = []
116
+
117
+ builtin_skill_types = {'context_graph_search'}
118
+ existing_skill_types = {cfg.get('type') for cfg in skill_configs}
119
+
120
+ for builtin_type in builtin_skill_types:
121
+ if builtin_type not in existing_skill_types:
122
+ builtin_config = {
123
+ 'name': builtin_type,
124
+ 'type': builtin_type,
125
+ 'enabled': True,
126
+ 'configuration': {}
127
+ }
128
+ skill_configs.append(builtin_config)
129
+ print(f" āž• Auto-included built-in skill: {builtin_type}")
130
+
131
+ if skill_configs:
132
+ print(f" Skills: {len(skill_configs)}")
133
+ # Create SkillFactory instance for agno runtime
134
+ skill_factory = SkillFactory(runtime_type="agno")
135
+ skill_factory.initialize()
136
+ skills = skill_factory.create_skills_from_list(skill_configs, execution_id=execution_id)
137
+ if skills:
138
+ print(f" āœ… Instantiated {len(skills)} skill(s)")
139
+ except Exception as e:
140
+ print(f" āš ļø Failed to fetch skills: {str(e)}")
141
+ logger.warning("skill_fetch_error_for_team_member", agent_id=agent_id, error=str(e))
142
+
143
+ # Create Agno Agent
144
+ member_agent = Agent(
145
+ name=agent_name,
146
+ role=agent_role,
147
+ model=LiteLLM(
148
+ id=f"openai/{model}",
149
+ api_base=litellm_api_base,
150
+ api_key=litellm_api_key,
151
+ ),
152
+ tools=skills if skills else None,
153
+ )
154
+
155
+ team_members.append(member_agent)
156
+ print(f" āœ… Agent created\n")
157
+
158
+ if not team_members:
159
+ raise ValueError("No team members available for team execution")
160
+
161
+ # STEP 5: Create Agno Team with streaming helper
162
+ print(f"\nšŸš€ Creating Agno Team:")
163
+ print(f" Team ID: {input.team_id}")
164
+ print(f" Members: {len(team_members)}")
165
+ print(f" Model: {model}")
166
+
167
+ # Create streaming helper for this execution
168
+ streaming_helper = StreamingHelper(
169
+ control_plane_client=self.control_plane,
170
+ execution_id=execution_id
171
+ )
172
+
173
+ # Create tool hook for real-time updates
174
+ def tool_hook(name: str = None, function_name: str = None, function=None, arguments: dict = None, **kwargs):
175
+ """Hook to capture tool execution for real-time streaming"""
176
+ tool_name = name or function_name or "unknown"
177
+ tool_args = arguments or {}
178
+
179
+ # Generate unique tool execution ID
180
+ import time
181
+ tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
182
+
183
+ print(f" šŸ”§ Tool Starting: {tool_name} (ID: {tool_execution_id})")
184
+
185
+ # Publish tool start event
186
+ streaming_helper.publish_tool_start(
187
+ tool_name=tool_name,
188
+ tool_execution_id=tool_execution_id,
189
+ tool_args=tool_args,
190
+ source="team"
191
+ )
192
+
193
+ # Execute the tool
194
+ result = None
195
+ error = None
196
+ try:
197
+ if function and callable(function):
198
+ # Validate parameters before execution to catch mismatches early
199
+ try:
200
+ validate_tool_parameters(
201
+ function,
202
+ tool_args,
203
+ tool_name,
204
+ execution_id
205
+ )
206
+ except ParameterValidationError as ve:
207
+ # Log detailed error and fail fast
208
+ logger.error(
209
+ "tool_parameter_mismatch_detected",
210
+ tool_name=tool_name,
211
+ execution_id=execution_id,
212
+ validation_error=str(ve),
213
+ )
214
+ raise
215
+
216
+ result = function(**tool_args) if tool_args else function()
217
+ else:
218
+ raise ValueError(f"Function not callable: {function}")
219
+
220
+ status = "success"
221
+ print(f" āœ… Tool Success: {tool_name}")
222
+
223
+ except Exception as e:
224
+ error = e
225
+ status = "failed"
226
+ print(f" āŒ Tool Failed: {tool_name} - {str(e)}")
227
+
228
+ # Publish tool completion event
229
+ streaming_helper.publish_tool_complete(
230
+ tool_name=tool_name,
231
+ tool_execution_id=tool_execution_id,
232
+ status=status,
233
+ output=str(result)[:1000] if result else None,
234
+ error=str(error) if error else None,
235
+ source="team"
236
+ )
237
+
238
+ if error:
239
+ raise error
240
+
241
+ return result
242
+
243
+ # Add tool hooks to all team members
244
+ for member in team_members:
245
+ if not hasattr(member, 'tool_hooks') or member.tool_hooks is None:
246
+ member.tool_hooks = []
247
+ member.tool_hooks.append(tool_hook)
248
+
249
+ # Create Agno Team
250
+ team = Team(
251
+ name=f"Team {input.team_id}",
252
+ members=team_members,
253
+ model=LiteLLM(
254
+ id=f"openai/{model}",
255
+ api_base=litellm_api_base,
256
+ api_key=litellm_api_key,
257
+ ),
258
+ )
259
+
260
+ # STEP 6: Register for cancellation
261
+ self.cancellation_manager.register(
262
+ execution_id=execution_id,
263
+ instance=team,
264
+ instance_type="team"
265
+ )
266
+ print(f"āœ… Team registered for cancellation support\n")
267
+
268
+ # Cache execution metadata in Redis
269
+ self.control_plane.cache_metadata(execution_id, "TEAM")
270
+
271
+ # STEP 7: Execute with streaming
272
+ print("⚔ Executing Team Run with Streaming...\n")
273
+
274
+ # Generate deterministic message IDs for this turn (matches V2 executor pattern)
275
+ # This ensures streaming and persisted messages have the SAME message_id
276
+ turn_number = len(session_history) // 2 + 1
277
+ user_message_id = f"{execution_id}_user_{turn_number}"
278
+ message_id = f"{execution_id}_assistant_{turn_number}"
279
+
280
+ # Publish user message to stream immediately so it appears in chronological order
281
+ from datetime import datetime, timezone
282
+ user_message_timestamp = datetime.now(timezone.utc).isoformat()
283
+ self.control_plane.publish_event(
284
+ execution_id=execution_id,
285
+ event_type="message",
286
+ data={
287
+ "role": "user",
288
+ "content": input.prompt,
289
+ "timestamp": user_message_timestamp,
290
+ "message_id": user_message_id,
291
+ "user_id": input.user_id,
292
+ "user_name": getattr(input, "user_name", None),
293
+ "user_email": getattr(input, "user_email", None),
294
+ "user_avatar": getattr(input, "user_avatar", None),
295
+ }
296
+ )
297
+ print(f" šŸ“¤ Published user message to stream (ID: {user_message_id})")
298
+
299
+ def stream_team_run():
300
+ """Run team with streaming and collect response"""
301
+ # Create event loop for this thread (needed for async streaming)
302
+ loop = asyncio.new_event_loop()
303
+ asyncio.set_event_loop(loop)
304
+
305
+ async def _async_stream():
306
+ """Async wrapper for streaming execution"""
307
+ import time as time_module
308
+ last_heartbeat_time = time_module.time()
309
+ last_persistence_time = time_module.time()
310
+ heartbeat_interval = 10 # Send heartbeat every 10 seconds
311
+ persistence_interval = 60 # Persist to database every 60 seconds
312
+
313
+ # Track tool_execution_ids for proper start/complete matching
314
+ # Key: "{member_name or 'leader'}_{tool_name}", Value: tool_execution_id
315
+ active_tool_executions: Dict[str, str] = {}
316
+
317
+ try:
318
+ # Execute with conversation context
319
+ if conversation_context:
320
+ run_response = team.run(
321
+ input.prompt,
322
+ stream=True,
323
+ messages=conversation_context,
324
+ )
325
+ else:
326
+ run_response = team.run(input.prompt, stream=True)
327
+
328
+ # Process streaming events (sync iteration in async context)
329
+ for event in run_response:
330
+ # Periodic maintenance: heartbeats and persistence
331
+ current_time = time_module.time()
332
+
333
+ # Send heartbeat every 10s (Temporal liveness)
334
+ if current_time - last_heartbeat_time >= heartbeat_interval:
335
+ current_response = streaming_helper.get_full_response()
336
+ activity.heartbeat({
337
+ "status": "Streaming in progress...",
338
+ "response_length": len(current_response),
339
+ "execution_id": execution_id,
340
+ })
341
+ last_heartbeat_time = current_time
342
+
343
+ # Persist snapshot every 60s (resilience against crashes)
344
+ if current_time - last_persistence_time >= persistence_interval:
345
+ current_response = streaming_helper.get_full_response()
346
+ if current_response:
347
+ print(f"\nšŸ’¾ Periodic persistence ({len(current_response)} chars)...")
348
+ snapshot_messages = session_history + [{
349
+ "role": "assistant",
350
+ "content": current_response,
351
+ "timestamp": datetime.now(timezone.utc).isoformat(),
352
+ "message_id": message_id, # Include deterministic message_id
353
+ }]
354
+ try:
355
+ # Best effort - don't fail execution if persistence fails
356
+ self.session_service.persist_session(
357
+ execution_id=execution_id,
358
+ session_id=input.session_id or execution_id,
359
+ user_id=input.user_id,
360
+ messages=snapshot_messages,
361
+ metadata={
362
+ "team_id": input.team_id,
363
+ "organization_id": input.organization_id,
364
+ "snapshot": True,
365
+ }
366
+ )
367
+ print(f" āœ… Session snapshot persisted with message_id: {message_id}")
368
+ except Exception as e:
369
+ print(f" āš ļø Session persistence error: {str(e)} (non-fatal)")
370
+ last_persistence_time = current_time
371
+
372
+ # Handle run_id capture
373
+ streaming_helper.handle_run_id(
374
+ chunk=event,
375
+ on_run_id=lambda run_id: self.cancellation_manager.set_run_id(execution_id, run_id)
376
+ )
377
+
378
+ # Get event type
379
+ event_type = getattr(event, 'event', None)
380
+
381
+ # Handle TEAM LEADER content chunks
382
+ if event_type == "TeamRunContent":
383
+ await streaming_helper.handle_content_chunk(
384
+ chunk=event,
385
+ message_id=message_id,
386
+ print_to_console=True
387
+ )
388
+
389
+ # Handle MEMBER content chunks (from team members)
390
+ elif event_type == "RunContent":
391
+ # Member agent content chunk
392
+ member_name = getattr(event, 'agent_name', getattr(event, 'member_name', 'Team Member'))
393
+
394
+ if hasattr(event, 'content') and event.content:
395
+ content = str(event.content)
396
+ streaming_helper.handle_member_content_chunk(
397
+ member_name=member_name,
398
+ content=content,
399
+ print_to_console=True
400
+ )
401
+
402
+ # Handle TEAM LEADER tool calls
403
+ elif event_type == "TeamToolCallStarted":
404
+ # Extract tool name properly
405
+ tool_obj = getattr(event, 'tool', None)
406
+ if tool_obj and hasattr(tool_obj, 'tool_name'):
407
+ tool_name = tool_obj.tool_name
408
+ tool_args = getattr(tool_obj, 'tool_args', {})
409
+ else:
410
+ tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
411
+ tool_args = {}
412
+
413
+ import time
414
+ tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
415
+
416
+ # Store tool_execution_id for matching with completion event
417
+ tool_key = f"leader_{tool_name}"
418
+ active_tool_executions[tool_key] = tool_execution_id
419
+
420
+ print(f"\n šŸ”§ Tool Starting: {tool_name} (Team Leader) [ID: {tool_execution_id}]")
421
+ streaming_helper.publish_tool_start(
422
+ tool_name=tool_name,
423
+ tool_execution_id=tool_execution_id,
424
+ tool_args=tool_args,
425
+ source="team_leader",
426
+ member_name=None
427
+ )
428
+
429
+ elif event_type == "TeamToolCallCompleted":
430
+ # Extract tool name and output
431
+ tool_obj = getattr(event, 'tool', None)
432
+ if tool_obj and hasattr(tool_obj, 'tool_name'):
433
+ tool_name = tool_obj.tool_name
434
+ tool_output = getattr(tool_obj, 'result', None) or getattr(event, 'result', None)
435
+ else:
436
+ tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
437
+ tool_output = getattr(event, 'result', None)
438
+
439
+ # Retrieve stored tool_execution_id for matching
440
+ tool_key = f"leader_{tool_name}"
441
+ tool_execution_id = active_tool_executions.get(tool_key)
442
+
443
+ if not tool_execution_id:
444
+ # Fallback: generate new ID if not found (shouldn't happen)
445
+ import time
446
+ tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
447
+ print(f"\n āš ļø Tool completion without matching start event: {tool_name}")
448
+ else:
449
+ # Remove from tracking dict
450
+ del active_tool_executions[tool_key]
451
+
452
+ print(f"\n āœ… Tool Completed: {tool_name} (Team Leader) [ID: {tool_execution_id}]")
453
+ streaming_helper.publish_tool_complete(
454
+ tool_name=tool_name,
455
+ tool_execution_id=tool_execution_id,
456
+ status="success",
457
+ output=str(tool_output) if tool_output else None,
458
+ error=None,
459
+ source="team_leader",
460
+ member_name=None
461
+ )
462
+
463
+ # Handle MEMBER tool calls
464
+ elif event_type == "ToolCallStarted":
465
+ # Extract tool name properly
466
+ tool_obj = getattr(event, 'tool', None)
467
+ if tool_obj and hasattr(tool_obj, 'tool_name'):
468
+ tool_name = tool_obj.tool_name
469
+ tool_args = getattr(tool_obj, 'tool_args', {})
470
+ else:
471
+ tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
472
+ tool_args = {}
473
+
474
+ member_name = getattr(event, 'agent_name', getattr(event, 'member_name', 'Team Member'))
475
+
476
+ import time
477
+ tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
478
+
479
+ # Store tool_execution_id for matching with completion event
480
+ tool_key = f"{member_name}_{tool_name}"
481
+ active_tool_executions[tool_key] = tool_execution_id
482
+
483
+ print(f"\n šŸ”§ Tool Starting: {tool_name} ({member_name}) [ID: {tool_execution_id}]")
484
+ streaming_helper.publish_tool_start(
485
+ tool_name=tool_name,
486
+ tool_execution_id=tool_execution_id,
487
+ tool_args=tool_args,
488
+ source="team_member",
489
+ member_name=member_name
490
+ )
491
+
492
+ elif event_type == "ToolCallCompleted":
493
+ # Extract tool name and output
494
+ tool_obj = getattr(event, 'tool', None)
495
+ if tool_obj and hasattr(tool_obj, 'tool_name'):
496
+ tool_name = tool_obj.tool_name
497
+ tool_output = getattr(tool_obj, 'result', None) or getattr(event, 'result', None)
498
+ else:
499
+ tool_name = str(tool_obj) if tool_obj else getattr(event, 'tool_name', 'unknown')
500
+ tool_output = getattr(event, 'result', None)
501
+
502
+ member_name = getattr(event, 'agent_name', getattr(event, 'member_name', 'Team Member'))
503
+
504
+ # Retrieve stored tool_execution_id for matching
505
+ tool_key = f"{member_name}_{tool_name}"
506
+ tool_execution_id = active_tool_executions.get(tool_key)
507
+
508
+ if not tool_execution_id:
509
+ # Fallback: generate new ID if not found (shouldn't happen)
510
+ import time
511
+ tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
512
+ print(f"\n āš ļø Tool completion without matching start event: {tool_name} ({member_name})")
513
+ else:
514
+ # Remove from tracking dict
515
+ del active_tool_executions[tool_key]
516
+
517
+ print(f"\n āœ… Tool Completed: {tool_name} ({member_name}) [ID: {tool_execution_id}]")
518
+ streaming_helper.publish_tool_complete(
519
+ tool_name=tool_name,
520
+ tool_execution_id=tool_execution_id,
521
+ status="success",
522
+ output=str(tool_output) if tool_output else None,
523
+ error=None,
524
+ source="team_member",
525
+ member_name=member_name
526
+ )
527
+
528
+ # Finalize streaming (mark any active member as complete)
529
+ streaming_helper.finalize_streaming()
530
+
531
+ print() # New line after streaming
532
+ return run_response
533
+
534
+ except Exception as e:
535
+ print(f"\nāŒ Streaming error: {str(e)}")
536
+ # Fall back to non-streaming
537
+ if conversation_context:
538
+ return team.run(input.prompt, stream=False, messages=conversation_context)
539
+ else:
540
+ return team.run(input.prompt, stream=False)
541
+
542
+ # Run the async function in the event loop
543
+ try:
544
+ return loop.run_until_complete(_async_stream())
545
+ finally:
546
+ loop.close()
547
+
548
+ # Execute in thread pool (no timeout - user controls via STOP button)
549
+ # Wrap in try-except to handle Temporal cancellation
550
+ try:
551
+ result = await asyncio.to_thread(stream_team_run)
552
+ except asyncio.CancelledError:
553
+ # Temporal cancelled the activity - cancel the running team
554
+ print("\nšŸ›‘ Cancellation signal received - stopping team execution...")
555
+ cancel_result = self.cancellation_manager.cancel(execution_id)
556
+ if cancel_result["success"]:
557
+ print(f"āœ… Team execution cancelled successfully")
558
+ else:
559
+ print(f"āš ļø Cancellation completed with warning: {cancel_result.get('error', 'Unknown')}")
560
+ # Re-raise to let Temporal know we're cancelled
561
+ raise
562
+
563
+ print("āœ… Team Execution Completed!")
564
+ full_response = streaming_helper.get_full_response()
565
+ print(f" Response Length: {len(full_response)} chars\n")
566
+
567
+ logger.info(
568
+ "team_execution_completed",
569
+ execution_id=execution_id[:8],
570
+ response_length=len(full_response)
571
+ )
572
+
573
+ # Use the streamed response content
574
+ response_content = full_response if full_response else (result.content if hasattr(result, "content") else str(result))
575
+
576
+ # STEP 8: Extract usage metrics
577
+ usage = {}
578
+ if hasattr(result, "metrics") and result.metrics:
579
+ metrics = result.metrics
580
+ usage = {
581
+ "prompt_tokens": getattr(metrics, "input_tokens", 0),
582
+ "completion_tokens": getattr(metrics, "output_tokens", 0),
583
+ "total_tokens": getattr(metrics, "total_tokens", 0),
584
+ }
585
+ print(f"šŸ“Š Token Usage:")
586
+ print(f" Input: {usage.get('prompt_tokens', 0)}")
587
+ print(f" Output: {usage.get('completion_tokens', 0)}")
588
+ print(f" Total: {usage.get('total_tokens', 0)}\n")
589
+
590
+ # STEP 9: Persist complete session history
591
+ print("\nšŸ’¾ Persisting session history to Control Plane...")
592
+
593
+ # Build message_ids dict to pass to extract_messages_from_result
594
+ # This ensures persisted messages use the SAME IDs as streaming messages
595
+ message_ids_map = {
596
+ len(session_history): user_message_id, # User message index
597
+ len(session_history) + 1: message_id # Assistant message index
598
+ }
599
+
600
+ # Extract messages from Agno result to get accurate timestamps
601
+ # Agno tracks message timestamps as they're created (msg.created_at)
602
+ extracted_messages = self.session_service.extract_messages_from_result(
603
+ result=result,
604
+ user_id=input.user_id,
605
+ execution_id=execution_id,
606
+ message_ids=message_ids_map # Pass deterministic IDs
607
+ )
608
+ print(f" šŸ“Š Extracted {len(extracted_messages)} messages with deterministic IDs")
609
+
610
+ # Use extracted messages which have proper timestamps from Agno
611
+ # These include both user and assistant messages with accurate created_at times
612
+ new_messages = extracted_messages
613
+
614
+ # Fallback: if no messages extracted (shouldn't happen), create them manually
615
+ if not extracted_messages:
616
+ from datetime import datetime, timezone
617
+ current_timestamp = datetime.now(timezone.utc).isoformat()
618
+ print(" āš ļø No messages extracted from Agno result, creating manually")
619
+ new_messages = [
620
+ {
621
+ "role": "user",
622
+ "content": input.prompt,
623
+ "timestamp": current_timestamp,
624
+ "message_id": f"{execution_id}_user_{turn_number}",
625
+ "user_id": input.user_id,
626
+ "user_name": getattr(input, "user_name", None),
627
+ "user_email": getattr(input, "user_email", None),
628
+ },
629
+ {
630
+ "role": "assistant",
631
+ "content": response_content,
632
+ "timestamp": current_timestamp,
633
+ "message_id": message_id, # Use the same message_id as streaming
634
+ },
635
+ ]
636
+
637
+ # Extract tool messages from streaming helper
638
+ tool_messages = streaming_helper.get_tool_messages()
639
+ print(f" šŸ“Š Collected {len(tool_messages)} tool messages during streaming")
640
+
641
+ # Combine with previous history: session_history + new_messages + tool_messages
642
+ complete_session = session_history + new_messages + tool_messages
643
+
644
+ # CRITICAL: Deduplicate messages by message_id AND content to prevent duplicates
645
+ # Use session_service.deduplicate_messages() which has enhanced two-level deduplication
646
+ original_count = len(complete_session)
647
+ complete_session = self.session_service.deduplicate_messages(complete_session)
648
+
649
+ # CRITICAL: Sort by timestamp to ensure chronological order
650
+ # Tool messages happen DURING streaming, so they need to be interleaved with user/assistant messages
651
+ complete_session.sort(key=lambda msg: msg.get("timestamp", ""))
652
+ print(f" āœ… Messages deduplicated ({original_count} -> {len(complete_session)}) and sorted by timestamp")
653
+
654
+ if complete_session:
655
+ success = self.session_service.persist_session(
656
+ execution_id=execution_id,
657
+ session_id=input.session_id or execution_id,
658
+ user_id=input.user_id,
659
+ messages=complete_session,
660
+ metadata={
661
+ "team_id": input.team_id,
662
+ "organization_id": input.organization_id,
663
+ "turn_count": len(complete_session),
664
+ "member_count": len(team_members),
665
+ }
666
+ )
667
+
668
+ if success:
669
+ print(f" āœ… Session persisted ({len(complete_session)} total messages)")
670
+ else:
671
+ print(f" āš ļø Session persistence failed")
672
+ else:
673
+ print(" ā„¹ļø No messages to persist")
674
+
675
+ print("\n" + "="*80)
676
+ print("šŸ TEAM EXECUTION END")
677
+ print("="*80 + "\n")
678
+
679
+ # STEP 10: Cleanup
680
+ self.cancellation_manager.unregister(execution_id)
681
+
682
+ from datetime import datetime, timezone
683
+ return {
684
+ "success": True,
685
+ "response": response_content,
686
+ "response_timestamp": datetime.now(timezone.utc).isoformat(),
687
+ "usage": usage,
688
+ "model": model,
689
+ "finish_reason": "stop",
690
+ "team_member_count": len(team_members),
691
+ }
692
+
693
+ except Exception as e:
694
+ # Cleanup on error
695
+ self.cancellation_manager.unregister(execution_id)
696
+
697
+ print("\n" + "="*80)
698
+ print("āŒ TEAM EXECUTION FAILED")
699
+ print("="*80)
700
+ print(f"Error: {str(e)}")
701
+ print("="*80 + "\n")
702
+
703
+ logger.error(
704
+ "team_execution_failed",
705
+ execution_id=execution_id[:8],
706
+ error=str(e)
707
+ )
708
+
709
+ return {
710
+ "success": False,
711
+ "error": str(e),
712
+ "model": input.model_id,
713
+ "usage": None,
714
+ "finish_reason": "error",
715
+ }