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,829 @@
1
+ """
2
+ Configuration builder for Claude Code runtime.
3
+
4
+ This module handles the construction of ClaudeAgentOptions from execution
5
+ context, including LiteLLM integration, MCP servers, and session management.
6
+
7
+ BUG FIX #4: Added session_id validation before use.
8
+ """
9
+
10
+ from typing import Dict, Any, Tuple, Optional, Callable, Set, List
11
+ import structlog
12
+ import os
13
+ import asyncio
14
+ import hashlib
15
+ import json
16
+ import time
17
+
18
+ from .tool_mapper import map_skills_to_tools, validate_tool_names
19
+ from .mcp_builder import build_mcp_servers
20
+ from .hooks import build_hooks
21
+ from .mcp_discovery import discover_all_mcp_resources
22
+ from control_plane_api.worker.services.system_prompt_enhancement import create_default_prompt_builder
23
+ from .litellm_proxy import (
24
+ get_proxy_base_url,
25
+ set_execution_context,
26
+ clear_execution_context,
27
+ )
28
+ from control_plane_api.worker.runtimes.model_utils import get_effective_model, is_model_override_active
29
+
30
+ logger = structlog.get_logger(__name__)
31
+
32
+ # Module-level singleton for system prompt enhancement
33
+ # NOTE: This is now created per-execution to support dynamic skill context
34
+ # _prompt_builder = create_default_prompt_builder()
35
+
36
+ # Note: Claude SDK handles MCP discovery automatically - we verify connection and log errors
37
+
38
+ # MCP Discovery Cache - Reduces process spawning by 50%
39
+ # Key: hash of MCP server configuration, Value: discovery results
40
+ _mcp_discovery_cache: Dict[str, Dict[str, Any]] = {}
41
+
42
+ # Options Cache - Reduces options rebuild overhead by ~80%
43
+ # Key: hash of execution configuration, Value: (options, timestamp)
44
+ # This cache stores built ClaudeAgentOptions to avoid rebuilding when config unchanged
45
+ _options_cache: Dict[str, Tuple[Any, float]] = {}
46
+ _options_cache_ttl = int(os.getenv("CLAUDE_CODE_OPTIONS_CACHE_TTL", "3600")) # 1 hour default
47
+
48
+
49
+ def _get_mcp_cache_key(mcp_servers: Dict[str, Any]) -> str:
50
+ """
51
+ Generate cache key for MCP server configuration.
52
+
53
+ Uses hash of server configs to detect when discovery needs to be re-run.
54
+ Only command/args/env matter for STDIO servers, URL for HTTP servers.
55
+ """
56
+ # Sort keys for consistent hashing
57
+ cache_input = {}
58
+ for server_name, server_config in sorted(mcp_servers.items()):
59
+ # Extract relevant fields for cache key
60
+ transport = server_config.get("transport", {})
61
+ transport_type = transport.get("type", "stdio")
62
+
63
+ if transport_type == "stdio":
64
+ # For stdio: command + args determine the process
65
+ cache_input[server_name] = {
66
+ "type": "stdio",
67
+ "command": transport.get("command", ""),
68
+ "args": transport.get("args", []),
69
+ "env": transport.get("env", {}),
70
+ }
71
+ elif transport_type in ["http", "sse"]:
72
+ # For HTTP/SSE: URL is what matters
73
+ cache_input[server_name] = {
74
+ "type": transport_type,
75
+ "url": transport.get("url", ""),
76
+ }
77
+
78
+ # Hash the configuration
79
+ config_json = json.dumps(cache_input, sort_keys=True)
80
+ return hashlib.sha256(config_json.encode()).hexdigest()[:16] # Short hash
81
+
82
+
83
+ def _get_options_cache_key(context: Any) -> str:
84
+ """
85
+ Generate cache key from execution context configuration.
86
+
87
+ This hash includes all configuration that affects options building:
88
+ - Model ID and system prompt
89
+ - Agent ID (affects permissions and config)
90
+ - Skills (determines tools and MCP servers)
91
+ - MCP servers configuration
92
+
93
+ Args:
94
+ context: RuntimeExecutionContext
95
+
96
+ Returns:
97
+ Cache key string (16-char hash)
98
+ """
99
+ # Extract skills as sorted list of names/types for consistent hashing
100
+ skill_identifiers = []
101
+ if hasattr(context, 'skills') and context.skills:
102
+ for skill in context.skills:
103
+ if isinstance(skill, dict):
104
+ skill_identifiers.append({
105
+ "name": skill.get("name", ""),
106
+ "type": skill.get("type", ""),
107
+ })
108
+ else:
109
+ # Toolkit object - use class name
110
+ skill_identifiers.append({"type": type(skill).__name__})
111
+
112
+ # Build cache input (only config that affects options)
113
+ cache_input = {
114
+ "model_id": context.model_id or "",
115
+ "system_prompt_length": len(context.system_prompt or ""), # Use length to avoid huge strings
116
+ "agent_id": context.agent_id,
117
+ "skill_identifiers": sorted(skill_identifiers, key=lambda x: (x.get("type", ""), x.get("name", ""))),
118
+ "mcp_server_names": sorted((context.mcp_servers or {}).keys()) if hasattr(context, 'mcp_servers') else [],
119
+ # Don't include execution_id or session_id - those vary per execution but don't affect config
120
+ }
121
+
122
+ config_json = json.dumps(cache_input, sort_keys=True)
123
+ return hashlib.sha256(config_json.encode()).hexdigest()[:16] # Short hash
124
+
125
+
126
+ def build_mcp_permission_handler(
127
+ mcp_servers: Dict[str, Any], allowed_tools: List[str]
128
+ ) -> Callable:
129
+ """
130
+ Build permission handler for MCP tools.
131
+
132
+ IMPORTANT: The SDK discovers MCP tools automatically, but does NOT
133
+ automatically grant permission to use them. We need to handle permissions
134
+ separately using the canUseTool callback.
135
+
136
+ This handler:
137
+ 1. Allows tools in the allowed_tools list (builtin tools)
138
+ 2. Auto-allows tools matching mcp__<server_name>__* for configured servers
139
+ 3. Denies everything else
140
+
141
+ Args:
142
+ mcp_servers: Dict of MCP server configurations
143
+ allowed_tools: List of allowed builtin tool names
144
+
145
+ Returns:
146
+ Async permission handler for canUseTool parameter
147
+ """
148
+ # Extract MCP server names for permission matching
149
+ mcp_server_names: Set[str] = set(mcp_servers.keys())
150
+
151
+ logger.info(
152
+ "building_mcp_permission_handler",
153
+ mcp_server_count=len(mcp_server_names),
154
+ mcp_server_names=list(mcp_server_names),
155
+ builtin_tools_count=len(allowed_tools),
156
+ note="SDK discovers tools, but we handle permissions via canUseTool"
157
+ )
158
+
159
+ async def permission_handler(
160
+ tool_name: str, input_data: dict, context: dict
161
+ ) -> Dict[str, Any]:
162
+ """
163
+ Permission handler that allows:
164
+ 1. Builtin tools from allowed_tools list
165
+ 2. MCP tools matching mcp__<server_name>__* pattern
166
+
167
+ Args:
168
+ tool_name: Tool being invoked
169
+ input_data: Tool input parameters
170
+ context: Execution context
171
+
172
+ Returns:
173
+ Permission decision: {"behavior": "allow"|"deny", "updatedInput": input_data}
174
+ """
175
+ # ALWAYS log permission checks for debugging
176
+ logger.info(
177
+ "permission_handler_called",
178
+ tool_name=tool_name,
179
+ is_builtin=tool_name in allowed_tools,
180
+ is_mcp=tool_name.startswith("mcp__"),
181
+ configured_servers=list(mcp_server_names),
182
+ )
183
+
184
+ # Allow builtin tools
185
+ if tool_name in allowed_tools:
186
+ logger.info("permission_granted_builtin", tool_name=tool_name)
187
+ return {"behavior": "allow", "updatedInput": input_data}
188
+
189
+ # Allow MCP tools from configured servers
190
+ # Pattern: mcp__<server_name>__<tool_name>
191
+ if tool_name.startswith("mcp__"):
192
+ parts = tool_name.split("__", 2)
193
+ if len(parts) >= 2:
194
+ server_name = parts[1]
195
+ logger.info(
196
+ "checking_mcp_permission",
197
+ tool_name=tool_name,
198
+ extracted_server=server_name,
199
+ configured_servers=list(mcp_server_names),
200
+ matches=server_name in mcp_server_names,
201
+ )
202
+ if server_name in mcp_server_names:
203
+ logger.info(
204
+ "permission_granted_mcp",
205
+ tool_name=tool_name,
206
+ server_name=server_name,
207
+ )
208
+ return {"behavior": "allow", "updatedInput": input_data}
209
+
210
+ # Deny unrecognized tools
211
+ logger.warning(
212
+ "tool_permission_denied",
213
+ tool_name=tool_name,
214
+ reason="not_in_allowed_tools_or_mcp_servers",
215
+ available_mcp_servers=list(mcp_server_names),
216
+ available_builtin_count=len(allowed_tools),
217
+ )
218
+ return {
219
+ "behavior": "deny",
220
+ "updatedInput": input_data,
221
+ "message": f"Tool '{tool_name}' not permitted. Not in allowed tools or MCP servers."
222
+ }
223
+
224
+ return permission_handler
225
+
226
+
227
+ def validate_session_id(session_id: Optional[str]) -> Optional[str]:
228
+ """
229
+ Validate session_id format before use.
230
+
231
+ BUG FIX #4: Ensures session_id is valid before storing for multi-turn.
232
+
233
+ Args:
234
+ session_id: Session ID to validate
235
+
236
+ Returns:
237
+ Valid session_id or None if invalid
238
+ """
239
+ if not session_id:
240
+ return None
241
+
242
+ if not isinstance(session_id, str) or len(session_id) < 10:
243
+ logger.warning(
244
+ "invalid_session_id_format",
245
+ session_id=session_id if isinstance(session_id, str) else None,
246
+ type=type(session_id).__name__,
247
+ length=len(session_id) if isinstance(session_id, str) else 0,
248
+ )
249
+ return None
250
+
251
+ return session_id
252
+
253
+
254
+ async def build_claude_options(
255
+ context: Any, # RuntimeExecutionContext
256
+ event_callback: Optional[Callable] = None,
257
+ runtime: Optional[Any] = None, # ClaudeCodeRuntime instance for caching
258
+ ) -> Tuple[Any, Dict[str, str], Set[str], Set[str]]:
259
+ """
260
+ Build ClaudeAgentOptions from execution context.
261
+
262
+ Args:
263
+ context: RuntimeExecutionContext with prompt, history, config
264
+ event_callback: Optional event callback for hooks
265
+ runtime: Optional ClaudeCodeRuntime instance for MCP discovery caching
266
+
267
+ Returns:
268
+ Tuple of (ClaudeAgentOptions instance, active_tools dict, started_tools set, completed_tools set)
269
+ """
270
+ from claude_agent_sdk import ClaudeAgentOptions
271
+
272
+ # Extract configuration
273
+ agent_config = context.agent_config or {}
274
+ runtime_config = context.runtime_config or {}
275
+
276
+ # Get LiteLLM configuration (same as DefaultRuntime/Agno)
277
+ litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
278
+ litellm_api_key = os.getenv("LITELLM_API_KEY")
279
+
280
+ if not litellm_api_key:
281
+ raise ValueError("LITELLM_API_KEY environment variable not set")
282
+
283
+ # Determine model (use LiteLLM format) with override support
284
+ # Priority: KUBIYA_MODEL_OVERRIDE > context.model_id > LITELLM_DEFAULT_MODEL > default
285
+ model = get_effective_model(
286
+ context_model_id=context.model_id,
287
+ log_context={"execution_id": context.execution_id[:8] if context.execution_id else "unknown"},
288
+ )
289
+
290
+ # Map skills to Claude Code tool names (built-in tools only)
291
+ allowed_tools = map_skills_to_tools(context.skills)
292
+
293
+ # Build MCP servers (both from context and custom skills)
294
+ # SDK will discover tools automatically - we just provide configs
295
+ mcp_servers, _ = build_mcp_servers(
296
+ context.skills, context.mcp_servers
297
+ )
298
+
299
+ # Verify MCP server connections and discover tools (with caching)
300
+ # This helps us detect configuration errors early
301
+ # Cache reduces process spawning by 50% by reusing discovery results
302
+ mcp_discovery_results = {}
303
+ if mcp_servers:
304
+ try:
305
+ # Check cache first
306
+ cache_key = _get_mcp_cache_key(mcp_servers)
307
+ if cache_key in _mcp_discovery_cache:
308
+ mcp_discovery_results = _mcp_discovery_cache[cache_key]
309
+ logger.info(
310
+ "using_cached_mcp_discovery",
311
+ server_count=len(mcp_servers),
312
+ server_names=list(mcp_servers.keys()),
313
+ cache_key=cache_key,
314
+ note="āœ… Using cached MCP discovery results (no processes spawned)"
315
+ )
316
+ else:
317
+ logger.info(
318
+ "verifying_mcp_server_connections",
319
+ server_count=len(mcp_servers),
320
+ server_names=list(mcp_servers.keys()),
321
+ cache_key=cache_key,
322
+ note="Attempting to connect and discover tools from all MCP servers"
323
+ )
324
+ mcp_discovery_results = await discover_all_mcp_resources(mcp_servers)
325
+ # Cache the results for future executions
326
+ _mcp_discovery_cache[cache_key] = mcp_discovery_results
327
+ logger.info(
328
+ "cached_mcp_discovery_results",
329
+ cache_key=cache_key,
330
+ note="Discovery results cached for future executions"
331
+ )
332
+
333
+ # Log results for each server
334
+ failed_servers = []
335
+ successful_servers = []
336
+ skipped_servers = []
337
+ for server_name, result in mcp_discovery_results.items():
338
+ # Check if server was skipped (HTTP servers use native SDK discovery)
339
+ if result.get("skipped"):
340
+ skipped_servers.append(server_name)
341
+ logger.info(
342
+ "mcp_server_skipped_native_discovery",
343
+ server_name=server_name,
344
+ status="⚔ HTTP - Using SDK native discovery",
345
+ note="Pre-discovery skipped for HTTP servers (SDK handles them natively)"
346
+ )
347
+ elif result["connected"]:
348
+ tool_count = len(result["tools"])
349
+ successful_servers.append(server_name)
350
+ if tool_count == 0:
351
+ logger.warning(
352
+ "mcp_server_connected_but_no_tools",
353
+ server_name=server_name,
354
+ message=f"MCP server '{server_name}' connected successfully but discovered 0 tools",
355
+ recommendation="Check server implementation - it may not be exposing any tools"
356
+ )
357
+ else:
358
+ logger.info(
359
+ "mcp_server_verified",
360
+ server_name=server_name,
361
+ tool_count=tool_count,
362
+ tool_names=result["tools"][:5] if tool_count <= 5 else [t["name"] for t in result["tools"][:5]] + [f"... and {tool_count - 5} more"],
363
+ status="āœ… Connected and discovered tools"
364
+ )
365
+ else:
366
+ failed_servers.append(server_name)
367
+ logger.error(
368
+ "mcp_server_connection_failed",
369
+ server_name=server_name,
370
+ error=result.get("error", "Unknown error"),
371
+ status="āŒ Failed to connect",
372
+ recommendation="Check server command, args, and environment variables in agent configuration"
373
+ )
374
+
375
+ # Summary log
376
+ if failed_servers:
377
+ logger.error(
378
+ "mcp_verification_summary",
379
+ total_servers=len(mcp_servers),
380
+ http_skipped=len(skipped_servers),
381
+ successful=len(successful_servers),
382
+ failed=len(failed_servers),
383
+ failed_server_names=failed_servers,
384
+ message=f"āš ļø {len(failed_servers)} MCP server(s) failed to connect - agent may not have access to expected tools"
385
+ )
386
+ else:
387
+ logger.info(
388
+ "mcp_verification_summary",
389
+ total_servers=len(mcp_servers),
390
+ http_skipped=len(skipped_servers),
391
+ successful=len(successful_servers),
392
+ total_tools_discovered=sum(len(r["tools"]) for r in mcp_discovery_results.values() if not r.get("skipped")),
393
+ message=f"āœ… All MCP servers ready: {len(skipped_servers)} HTTP (native SDK) + {len(successful_servers)} pre-discovered"
394
+ )
395
+
396
+ except Exception as discovery_error:
397
+ logger.error(
398
+ "mcp_discovery_process_failed",
399
+ error=str(discovery_error),
400
+ error_type=type(discovery_error).__name__,
401
+ message="Failed to verify MCP server connections - will proceed but tools may not be available",
402
+ exc_info=True
403
+ )
404
+
405
+ # BUG FIX #6: Validate built-in tool names before using
406
+ allowed_tools, invalid_tools = validate_tool_names(allowed_tools)
407
+
408
+ # Build permission handler for MCP tools
409
+ # IMPORTANT: SDK discovers tools, but we must permit them via canUseTool
410
+ permission_handler = None
411
+ if mcp_servers:
412
+ permission_handler = build_mcp_permission_handler(mcp_servers, allowed_tools)
413
+ logger.info(
414
+ "mcp_permission_handler_configured",
415
+ mcp_servers=list(mcp_servers.keys()),
416
+ note="Will auto-allow tools matching mcp__<server_name>__* pattern"
417
+ )
418
+
419
+ logger.info(
420
+ "claude_code_tools_configured",
421
+ builtin_tools_count=len(allowed_tools),
422
+ mcp_servers_count=len(mcp_servers),
423
+ mcp_server_names=list(mcp_servers.keys()) if mcp_servers else [],
424
+ builtin_tools=allowed_tools[:20], # Limit for readability
425
+ has_permission_handler=permission_handler is not None,
426
+ note="SDK discovers MCP tools automatically, we handle permissions via canUseTool"
427
+ )
428
+
429
+ # Create shared active_tools dict for tool name tracking
430
+ # This is populated in the stream when ToolUseBlock is received,
431
+ # and used in hooks to look up tool names
432
+ active_tools: Dict[str, str] = {}
433
+
434
+ # Create shared started_tools set for tracking published start events
435
+ # This prevents duplicate tool_start events from hooks
436
+ from typing import Set
437
+ started_tools: Set[str] = set()
438
+
439
+ # Create shared completed_tools set for tracking published completion events
440
+ # This prevents duplicate tool_complete events from hooks and ToolResultBlock
441
+ completed_tools: Set[str] = set()
442
+
443
+ # Initialize enforcement service for policy checks
444
+ enforcement_context = {
445
+ "organization_id": context.organization_id,
446
+ "user_email": context.user_email,
447
+ "user_id": context.user_id,
448
+ "user_roles": context.user_roles or [],
449
+ "team_id": context.team_id,
450
+ "team_name": context.team_name,
451
+ "agent_id": context.agent_id,
452
+ "environment": context.environment,
453
+ "model_id": context.model_id,
454
+ }
455
+
456
+ # Import enforcement dependencies
457
+ from control_plane_api.app.lib.policy_enforcer_client import create_policy_enforcer_client
458
+ from control_plane_api.worker.services.tool_enforcement import ToolEnforcementService
459
+
460
+ # Get enforcer client (using the same token as the control plane)
461
+ enforcer_client = None
462
+ enforcement_service = None
463
+
464
+ # Check if enforcement is enabled (opt-in via environment variable)
465
+ enforcement_enabled = os.environ.get("KUBIYA_ENFORCE_ENABLED", "").lower() in ("true", "1", "yes")
466
+
467
+ if not enforcement_enabled:
468
+ logger.info(
469
+ "policy_enforcement_disabled",
470
+ reason="KUBIYA_ENFORCE_ENABLED not set",
471
+ execution_id=context.execution_id[:8],
472
+ note="Set KUBIYA_ENFORCE_ENABLED=true to enable policy enforcement"
473
+ )
474
+ else:
475
+ try:
476
+ # Get API key from runtime (if available)
477
+ api_key = runtime.control_plane.api_key if runtime and hasattr(runtime, 'control_plane') else None
478
+ if api_key:
479
+ # Get enforcer URL - default to control plane enforcer proxy
480
+ enforcer_url = os.environ.get("ENFORCER_SERVICE_URL")
481
+ if not enforcer_url:
482
+ # Use control plane's enforcer proxy as default
483
+ control_plane_url = os.environ.get("CONTROL_PLANE_URL", "http://localhost:8000")
484
+ enforcer_url = f"{control_plane_url.rstrip('/')}/api/v1/enforcer"
485
+ logger.debug(
486
+ "using_control_plane_enforcer_proxy",
487
+ enforcer_url=enforcer_url,
488
+ execution_id=context.execution_id[:8],
489
+ )
490
+
491
+ # Use async context manager properly (we're in an async function)
492
+ enforcer_client_context = create_policy_enforcer_client(
493
+ enforcer_url=enforcer_url,
494
+ api_key=api_key,
495
+ auth_type="UserKey"
496
+ )
497
+ enforcer_client = await enforcer_client_context.__aenter__()
498
+ if enforcer_client:
499
+ enforcement_service = ToolEnforcementService(enforcer_client)
500
+ logger.info(
501
+ "policy_enforcement_enabled",
502
+ enforcer_url=enforcer_url,
503
+ execution_id=context.execution_id[:8],
504
+ )
505
+ else:
506
+ logger.debug(
507
+ "enforcement_service_skipped",
508
+ reason="no_api_key_available",
509
+ execution_id=context.execution_id[:8],
510
+ )
511
+ except Exception as e:
512
+ logger.warning(
513
+ "enforcement_service_init_failed",
514
+ error=str(e),
515
+ execution_id=context.execution_id[:8],
516
+ )
517
+
518
+ # Build hooks for tool execution monitoring with enforcement
519
+ hooks = (
520
+ build_hooks(
521
+ context.execution_id,
522
+ event_callback,
523
+ active_tools,
524
+ completed_tools,
525
+ started_tools,
526
+ enforcement_context=enforcement_context,
527
+ enforcement_service=enforcement_service,
528
+ )
529
+ if event_callback
530
+ else {}
531
+ )
532
+
533
+ # Build environment with LiteLLM configuration
534
+ env = runtime_config.get("env", {}).copy()
535
+
536
+ # Check if model override is active - if so, we'll bypass the internal proxy
537
+ model_override_active = is_model_override_active()
538
+ model_override_value = os.environ.get("KUBIYA_MODEL_OVERRIDE") if model_override_active else None
539
+
540
+ if model_override_active:
541
+ logger.info(
542
+ "model_override_detected_bypassing_internal_proxy",
543
+ model_override=model_override_value,
544
+ note="Internal LiteLLM proxy will be bypassed, using direct API configuration"
545
+ )
546
+
547
+ # Extract and validate secrets from skill configurations
548
+ # Skills may reference secrets (e.g., Slack skill with secret_name or secrets parameters)
549
+ # These secrets should be resolved and injected as environment variables by the execution environment controller
550
+ if context.skill_configs:
551
+ for skill_config in context.skill_configs:
552
+ config = skill_config.get("configuration", {})
553
+ skill_name = skill_config.get("name", "unknown")
554
+
555
+ # Check for secrets (list) or secret_name (single, deprecated)
556
+ secrets_list = []
557
+ if "secrets" in config:
558
+ secrets = config["secrets"]
559
+ if isinstance(secrets, list):
560
+ secrets_list = secrets
561
+ elif isinstance(secrets, str):
562
+ secrets_list = [s.strip() for s in secrets.split(",") if s.strip()]
563
+ elif "secret_name" in config and config["secret_name"]:
564
+ secrets_list = [config["secret_name"]]
565
+
566
+ # Validate that configured secrets are available in environment
567
+ for secret_name in secrets_list:
568
+ if secret_name not in env:
569
+ logger.warning(
570
+ "skill_secret_not_resolved",
571
+ skill_name=skill_name,
572
+ secret_name=secret_name,
573
+ note=f"Secret '{secret_name}' configured in skill '{skill_name}' but not found in execution environment. "
574
+ f"Ensure the secret is added to the execution environment's secrets list."
575
+ )
576
+
577
+ # LOG WHAT ENV VARS WE RECEIVED FROM RUNTIME CONFIG
578
+ print(f"\nšŸ” CLAUDE CODE CONFIG - ENV VARS RECEIVED:")
579
+ print(f" Received from runtime_config: {len(env)} variables")
580
+ print(f" Keys: {list(env.keys())}")
581
+ for key, value in env.items():
582
+ if any(s in key.upper() for s in ["TOKEN", "KEY", "SECRET", "PASSWORD"]):
583
+ masked = f"{value[:10]}...{value[-5:]}" if len(value) > 15 else "***"
584
+ print(f" {key}: {masked} (length: {len(value)})")
585
+ else:
586
+ print(f" {key}: {value}")
587
+ print()
588
+
589
+ # ALWAYS use internal proxy - it handles:
590
+ # 1. Model override (rewrites ALL model names including subagents)
591
+ # 2. Langfuse metadata injection
592
+ # 3. Request forwarding to real LiteLLM
593
+ try:
594
+ local_proxy_url = get_proxy_base_url()
595
+ logger.info(
596
+ "local_litellm_proxy_started",
597
+ proxy_url=local_proxy_url,
598
+ real_litellm_url=litellm_api_base,
599
+ execution_id=context.execution_id[:8],
600
+ model_override_active=model_override_active,
601
+ model_override=model_override_value if model_override_active else None,
602
+ note="Internal proxy handles model override for ALL requests including subagents"
603
+ )
604
+ except Exception as proxy_error:
605
+ logger.error(
606
+ "failed_to_start_local_proxy",
607
+ error=str(proxy_error),
608
+ execution_id=context.execution_id,
609
+ fallback="Using direct LiteLLM connection (no metadata injection or model override)",
610
+ )
611
+ # Fallback to direct connection if proxy fails
612
+ local_proxy_url = litellm_api_base
613
+
614
+ # Configure Claude Code SDK to use LOCAL proxy (which forwards to real LiteLLM)
615
+ env["ANTHROPIC_BASE_URL"] = local_proxy_url
616
+ env["ANTHROPIC_API_KEY"] = litellm_api_key
617
+
618
+ # Store execution context for metadata injection
619
+ execution_context = {}
620
+ if context.user_metadata:
621
+ user_id = context.user_metadata.get("user_email") or context.user_metadata.get("user_id")
622
+ session_id = context.user_metadata.get("session_id") or context.execution_id
623
+
624
+ execution_context = {
625
+ "user_id": user_id,
626
+ "session_id": session_id,
627
+ "organization_id": context.organization_id,
628
+ "agent_id": context.agent_id,
629
+ "agent_name": context.user_metadata.get("agent_name") or context.agent_id,
630
+ "model_id": model,
631
+ # Pass trace_name and generation_name from user_metadata to preserve plan grouping
632
+ "trace_name": context.user_metadata.get("trace_name"),
633
+ "generation_name": context.user_metadata.get("generation_name"),
634
+ }
635
+
636
+ # Store context for proxy to use
637
+ set_execution_context(context.execution_id, execution_context)
638
+
639
+ logger.info(
640
+ "execution_context_stored_for_proxy",
641
+ execution_id=context.execution_id[:8],
642
+ has_user_id=bool(user_id),
643
+ has_session_id=bool(session_id),
644
+ metadata_keys=list(execution_context.keys()),
645
+ )
646
+
647
+ # Pass Kubiya API credentials for workflow execution
648
+ kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
649
+ if kubiya_api_key:
650
+ env["KUBIYA_API_KEY"] = kubiya_api_key
651
+ logger.debug("added_kubiya_api_key_to_environment")
652
+
653
+ kubiya_api_base = os.environ.get("KUBIYA_API_BASE")
654
+ if kubiya_api_base:
655
+ env["KUBIYA_API_BASE"] = kubiya_api_base
656
+ logger.debug(
657
+ "added_kubiya_api_base_to_environment", kubiya_api_base=kubiya_api_base
658
+ )
659
+
660
+ # Get session_id from previous turn for conversation continuity
661
+ # BUG FIX #4: Validate session_id format before use
662
+ previous_session_id = None
663
+ if context.user_metadata:
664
+ raw_session_id = context.user_metadata.get("claude_code_session_id")
665
+ previous_session_id = validate_session_id(raw_session_id)
666
+
667
+ if raw_session_id and not previous_session_id:
668
+ logger.warning(
669
+ "invalid_session_id_from_user_metadata",
670
+ raw_session_id=raw_session_id,
671
+ )
672
+
673
+ logger.info(
674
+ "building_claude_code_options",
675
+ has_user_metadata=bool(context.user_metadata),
676
+ has_session_id_in_metadata=bool(previous_session_id),
677
+ previous_session_id_prefix=(
678
+ previous_session_id[:16] if previous_session_id else None
679
+ ),
680
+ will_resume=bool(previous_session_id),
681
+ )
682
+
683
+ # NEW: Support native subagents for team execution
684
+ sdk_agents = None
685
+ agents_config = agent_config.get('runtime_config', {}).get('agents')
686
+
687
+ if agents_config:
688
+ from claude_agent_sdk import AgentDefinition
689
+
690
+ sdk_agents = {}
691
+ for agent_id, agent_data in agents_config.items():
692
+ sdk_agents[agent_id] = AgentDefinition(
693
+ description=agent_data.get('description', ''),
694
+ prompt=agent_data.get('prompt', ''),
695
+ tools=agent_data.get('tools'),
696
+ model=agent_data.get('model', 'inherit'),
697
+ )
698
+
699
+ logger.info(
700
+ "native_subagents_configured",
701
+ execution_id=context.execution_id[:8] if context.execution_id else "unknown",
702
+ subagent_count=len(sdk_agents),
703
+ subagent_ids=list(sdk_agents.keys()),
704
+ subagent_models=[agent_data.get('model', 'inherit') for agent_data in agents_config.values()],
705
+ )
706
+
707
+ # Log detailed MCP server configuration for debugging
708
+ if mcp_servers:
709
+ logger.info(
710
+ "mcp_servers_being_passed_to_sdk",
711
+ server_count=len(mcp_servers),
712
+ server_names=list(mcp_servers.keys()),
713
+ server_configs={
714
+ name: {
715
+ "type": cfg.get("type", "stdio"),
716
+ "url": cfg.get("url", "N/A")[:50] if cfg.get("url") else "N/A",
717
+ "command": cfg.get("command", "N/A"),
718
+ "args": cfg.get("args", []), # Show args for debugging
719
+ "has_env": bool(cfg.get("env"))
720
+ }
721
+ for name, cfg in mcp_servers.items()
722
+ },
723
+ note="SDK should discover tools from these servers"
724
+ )
725
+
726
+ # Build options - SDK discovers tools, we handle permissions
727
+ # Enhance system prompt with runtime-specific additions
728
+ # Create per-execution prompt builder to support dynamic skill context and user context
729
+ from control_plane_api.worker.services.skill_context_enhancement import (
730
+ SkillContextEnhancement,
731
+ )
732
+
733
+ # Create prompt builder with user context
734
+ prompt_builder = create_default_prompt_builder(
735
+ user_metadata=context.user_metadata,
736
+ )
737
+
738
+ # Add skill context enhancement if enabled and skills are configured
739
+ skill_context_enabled = os.getenv("ENABLE_SKILL_CONTEXT_ENHANCEMENT", "true").lower() == "true"
740
+ if skill_context_enabled and context.skill_configs:
741
+ skill_context_enhancement = SkillContextEnhancement(context.skill_configs)
742
+ prompt_builder.add_enhancement(skill_context_enhancement)
743
+ logger.info(
744
+ "skill_context_enhancement_enabled",
745
+ skill_count=len(context.skill_configs),
746
+ execution_id=context.execution_id[:8] if context.execution_id else "unknown",
747
+ )
748
+
749
+ enhanced_system_prompt = prompt_builder.build(
750
+ base_prompt=context.system_prompt,
751
+ runtime_type="claude_code",
752
+ )
753
+
754
+ # LOG FINAL ENV VARS BEING PASSED TO CLAUDE SDK
755
+ print(f"\nšŸ” CLAUDE SDK OPTIONS - FINAL ENV VARS:")
756
+ print(f" Total env vars for SDK: {len(env)} variables")
757
+ print(f" Keys: {list(env.keys())}")
758
+ for key, value in env.items():
759
+ if any(s in key.upper() for s in ["TOKEN", "KEY", "SECRET", "PASSWORD"]):
760
+ masked = f"{value[:10]}...{value[-5:]}" if len(value) > 15 else "***"
761
+ print(f" {key}: {masked} (length: {len(value)})")
762
+ else:
763
+ print(f" {key}: {value}")
764
+ print()
765
+
766
+ # Determine working directory: user override > workspace > None (SDK default)
767
+ cwd_value = agent_config.get("cwd") or runtime_config.get("cwd")
768
+
769
+ if not cwd_value and context.workspace_directory:
770
+ cwd_value = context.workspace_directory
771
+
772
+ logger.info(
773
+ "claude_code_using_execution_workspace",
774
+ execution_id=context.execution_id[:8] if len(context.execution_id) >= 8 else context.execution_id,
775
+ workspace=cwd_value,
776
+ )
777
+
778
+ options_dict = {
779
+ "system_prompt": enhanced_system_prompt,
780
+ "allowed_tools": allowed_tools,
781
+ "mcp_servers": mcp_servers, # SDK discovers tools automatically
782
+ "agents": sdk_agents, # NEW: Native subagent support for teams
783
+ "permission_mode": runtime_config.get(
784
+ "permission_mode",
785
+ os.getenv("CLAUDE_CODE_PERMISSION_MODE", "bypassPermissions")
786
+ ),
787
+ "cwd": cwd_value,
788
+ "model": model,
789
+ "env": env, # ← ENVIRONMENT VARIABLES PASSED HERE!
790
+ "max_turns": runtime_config.get("max_turns", 50), # Default 50 turns to support complex multi-step workflows
791
+ "hooks": hooks,
792
+ "setting_sources": [], # Explicit: don't load filesystem settings
793
+ "include_partial_messages": True, # Enable character-by-character streaming
794
+ "resume": previous_session_id, # Resume previous conversation if available
795
+ }
796
+
797
+ # Extended thinking support - enables thinking/reasoning streaming
798
+ # Can be configured via runtime_config or environment variable
799
+ max_thinking_tokens = runtime_config.get(
800
+ "max_thinking_tokens",
801
+ int(os.getenv("CLAUDE_CODE_MAX_THINKING_TOKENS", "0"))
802
+ )
803
+ if max_thinking_tokens > 0:
804
+ options_dict["max_thinking_tokens"] = max_thinking_tokens
805
+ logger.info(
806
+ "extended_thinking_enabled",
807
+ max_thinking_tokens=max_thinking_tokens,
808
+ note="Extended thinking will stream thinking_start/thinking_delta/thinking_complete events"
809
+ )
810
+
811
+ # Add permission handler if we have MCP servers
812
+ # CRITICAL: SDK discovers tools but doesn't auto-permit them
813
+ if permission_handler:
814
+ options_dict["can_use_tool"] = permission_handler
815
+
816
+ options = ClaudeAgentOptions(**options_dict)
817
+
818
+ logger.info(
819
+ "claude_code_options_configured",
820
+ include_partial_messages=getattr(options, "include_partial_messages", "NOT SET"),
821
+ permission_mode=options.permission_mode,
822
+ model=options.model,
823
+ mcp_servers_configured=len(mcp_servers) if mcp_servers else 0,
824
+ has_can_use_tool=permission_handler is not None,
825
+ note="SDK discovers tools, canUseTool handler permits mcp__<server>__* pattern"
826
+ )
827
+
828
+ # Return options, active_tools dict, started_tools set, and completed_tools set for tracking
829
+ return options, active_tools, started_tools, completed_tools