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,482 @@
1
+ """
2
+ Hook system for Claude Code runtime tool execution monitoring.
3
+
4
+ This module provides pre-tool and post-tool hooks for real-time event
5
+ publishing and monitoring of tool execution.
6
+
7
+ Hooks Implemented:
8
+ - PreToolUse: Called before tool execution (can block/modify)
9
+ - PostToolUse: Called after tool execution completes
10
+ - SubagentStop: Called when subagent (Task tool) completes
11
+
12
+ Why SubagentStop is Needed:
13
+ According to Claude SDK documentation, Task tools spawn subagents that execute
14
+ in separate contexts. PostToolUse hooks don't fire for subagent tools because
15
+ they run in isolated sessions. SubagentStop captures these completions.
16
+
17
+ Built-in Tool Behavior:
18
+ Some built-in tools (TodoWrite, Bash, Read, Write, Edit, Glob, Grep) may execute
19
+ through optimized SDK paths that skip hooks. This is expected behavior. The runtime's
20
+ fallback mechanism (runtime.py lines 1032-1105) publishes synthetic completion events
21
+ for these tools to ensure the frontend receives proper notifications.
22
+
23
+ Includes policy enforcement for tool executions.
24
+
25
+ BUG FIX #2: Replaced all print() statements with structured logging.
26
+ BUG FIX #8: Added SubagentStop hook to capture Task tool completions.
27
+ """
28
+
29
+ from typing import Dict, Any, Callable, Optional, TYPE_CHECKING
30
+ import structlog
31
+ import os
32
+
33
+ if TYPE_CHECKING:
34
+ from control_plane_api.worker.services.tool_enforcement import ToolEnforcementService
35
+
36
+ logger = structlog.get_logger(__name__)
37
+
38
+ # Check if verbose debug logging is enabled
39
+ # Support legacy CLAUDE_CODE_DEBUG for backward compatibility
40
+ if os.getenv("CLAUDE_CODE_DEBUG"):
41
+ logger.warning(
42
+ "deprecated_env_var",
43
+ old_var="CLAUDE_CODE_DEBUG",
44
+ new_var="KUBIYA_CLI_LOG_LEVEL",
45
+ message="CLAUDE_CODE_DEBUG is deprecated. Please use KUBIYA_CLI_LOG_LEVEL=DEBUG instead."
46
+ )
47
+ DEBUG_MODE = os.getenv("CLAUDE_CODE_DEBUG", "false").lower() == "true"
48
+ else:
49
+ DEBUG_MODE = os.getenv("KUBIYA_CLI_LOG_LEVEL", "INFO").upper() == "DEBUG"
50
+
51
+
52
+ def build_hooks(
53
+ execution_id: str,
54
+ event_callback: Optional[Callable[[Dict], None]],
55
+ active_tools: Dict[str, str],
56
+ completed_tools: set,
57
+ started_tools: set,
58
+ enforcement_context: Optional[Dict[str, Any]] = None,
59
+ enforcement_service: Optional["ToolEnforcementService"] = None,
60
+ ) -> Dict[str, Any]:
61
+ """
62
+ Build hooks for tool execution monitoring with policy enforcement.
63
+
64
+ Hooks intercept events like PreToolUse and PostToolUse to provide
65
+ real-time feedback and monitoring. Both hooks and ToolResultBlock
66
+ can publish tool_complete events, so we use completed_tools set
67
+ for deduplication. Uses started_tools set to prevent duplicate
68
+ tool_start events. Includes policy enforcement checks.
69
+
70
+ Args:
71
+ execution_id: Execution ID for event tracking
72
+ event_callback: Callback for publishing events
73
+ active_tools: Shared dict mapping tool_use_id -> tool_name
74
+ completed_tools: Shared set of tool_use_ids that completed
75
+ started_tools: Shared set of tool_use_ids that started (prevents duplicate tool_start events)
76
+ enforcement_context: Optional context for policy enforcement
77
+ enforcement_service: Optional enforcement service for policy checks
78
+
79
+ Returns:
80
+ Dict of hook configurations
81
+ """
82
+ from claude_agent_sdk import HookMatcher
83
+
84
+ async def pre_tool_hook(input_data, tool_use_id, tool_context):
85
+ """
86
+ Hook called before tool execution.
87
+
88
+ BUG FIX #2: Uses logger.debug() instead of print().
89
+ """
90
+ # BUG FIX #2: Use structured logging instead of print
91
+ if DEBUG_MODE:
92
+ logger.debug(
93
+ "pre_tool_hook_called",
94
+ tool_use_id=tool_use_id,
95
+ input_data_type=type(input_data).__name__,
96
+ input_data_keys=(
97
+ list(input_data.keys()) if isinstance(input_data, dict) else None
98
+ ),
99
+ has_tool_context=bool(tool_context),
100
+ )
101
+
102
+ # Try to extract tool name from input_data
103
+ tool_name = "unknown"
104
+ tool_args = {}
105
+
106
+ if isinstance(input_data, dict):
107
+ # Check if input_data has tool_name like output_data does
108
+ tool_name = input_data.get("tool_name", "unknown")
109
+ tool_args = input_data.get("tool_input", {})
110
+
111
+ # Always log MCP tool calls (not just in debug mode)
112
+ if tool_name.startswith("mcp__"):
113
+ logger.info(
114
+ "🔧 mcp_tool_starting",
115
+ tool_name=tool_name,
116
+ tool_use_id=tool_use_id[:12],
117
+ args=tool_args,
118
+ message=f"▶️ Executing {tool_name}"
119
+ )
120
+
121
+ if DEBUG_MODE:
122
+ if tool_name == "unknown":
123
+ logger.debug(
124
+ "pre_tool_hook_no_tool_name",
125
+ tool_use_id=tool_use_id,
126
+ input_data_keys=list(input_data.keys()),
127
+ )
128
+ else:
129
+ logger.debug(
130
+ "pre_tool_hook_found_tool_name",
131
+ tool_use_id=tool_use_id,
132
+ tool_name=tool_name,
133
+ )
134
+
135
+ # Enforcement check (non-blocking, async)
136
+ enforcement_allowed = True
137
+ enforcement_violation = None
138
+ enforcement_metadata = {}
139
+
140
+ if enforcement_service and enforcement_context and tool_name != "unknown":
141
+ try:
142
+ enforcement_allowed, enforcement_violation, enforcement_metadata = (
143
+ await enforcement_service.enforce_tool_execution(
144
+ tool_name=tool_name,
145
+ tool_args=tool_args,
146
+ enforcement_context={
147
+ **enforcement_context,
148
+ "execution_id": execution_id,
149
+ "tool_use_id": tool_use_id,
150
+ },
151
+ )
152
+ )
153
+
154
+ # Store enforcement result in tool context for post-hook
155
+ if not enforcement_allowed:
156
+ tool_context["enforcement_violation"] = enforcement_violation
157
+ tool_context["enforcement_metadata"] = enforcement_metadata
158
+
159
+ except Exception as e:
160
+ logger.error(
161
+ "enforcement_check_failed",
162
+ tool_name=tool_name,
163
+ error=str(e),
164
+ )
165
+ # Fail open - allow execution
166
+ enforcement_metadata = {"enforcer": "error", "error": str(e)}
167
+
168
+ # Publish tool_start event with enforcement metadata (with deduplication)
169
+ # Check if already started to prevent duplicate events
170
+ if event_callback and tool_name != "unknown" and tool_use_id not in started_tools:
171
+ try:
172
+ event_callback(
173
+ {
174
+ "type": "tool_start",
175
+ "tool_name": tool_name,
176
+ "tool_args": tool_args,
177
+ "tool_execution_id": tool_use_id,
178
+ "execution_id": execution_id,
179
+ "enforcement": enforcement_metadata, # Add enforcement metadata
180
+ }
181
+ )
182
+ # Mark as started to prevent duplicate events
183
+ started_tools.add(tool_use_id)
184
+ if DEBUG_MODE:
185
+ logger.debug(
186
+ "pre_tool_hook_published_tool_start",
187
+ tool_use_id=tool_use_id,
188
+ tool_name=tool_name,
189
+ )
190
+ except Exception as e:
191
+ logger.error(
192
+ "failed_to_publish_tool_start",
193
+ tool_name=tool_name,
194
+ tool_use_id=tool_use_id,
195
+ error=str(e),
196
+ exc_info=True,
197
+ )
198
+ elif tool_use_id in started_tools:
199
+ if DEBUG_MODE:
200
+ logger.debug(
201
+ "tool_start_already_published",
202
+ tool_use_id=tool_use_id,
203
+ tool_name=tool_name,
204
+ )
205
+
206
+ return {}
207
+
208
+ async def post_tool_hook(output_data, tool_use_id, tool_context):
209
+ """
210
+ Hook called after tool execution.
211
+
212
+ BUG FIX #2: Uses logger.debug() instead of print().
213
+ """
214
+ # Extract tool name from output_data (provided by Claude Code SDK)
215
+ tool_name = "unknown"
216
+ if isinstance(output_data, dict):
217
+ # Claude SDK provides tool_name directly in output_data
218
+ tool_name = output_data.get("tool_name", "unknown")
219
+
220
+ # Check for errors from multiple sources:
221
+ # 1. tool_context.is_error (set by SDK)
222
+ # 2. output_data.isError (returned by tool wrapper when exception occurs)
223
+ is_error = (
224
+ (tool_context.get("is_error", False) if tool_context else False) or
225
+ (output_data.get("isError", False) if isinstance(output_data, dict) else False)
226
+ )
227
+
228
+ # Check for enforcement violation and inject into output
229
+ enforcement_violation = tool_context.get("enforcement_violation") if tool_context else None
230
+ enforcement_metadata = tool_context.get("enforcement_metadata", {}) if tool_context else {}
231
+
232
+ if enforcement_violation:
233
+ # Inject violation into output_data
234
+ violation_message = (
235
+ f"\n{'='*60}\n"
236
+ f"⛔ POLICY VIOLATION DETECTED\n"
237
+ f"{'='*60}\n"
238
+ f"{enforcement_violation}\n"
239
+ f"{'='*60}\n\n"
240
+ )
241
+
242
+ if isinstance(output_data, dict):
243
+ existing_output = output_data.get("output", "")
244
+ output_data["output"] = violation_message + str(existing_output)
245
+ output_data["enforcement_violated"] = True
246
+
247
+ logger.warning(
248
+ "tool_execution_policy_violation",
249
+ tool_name=tool_name,
250
+ tool_use_id=tool_use_id,
251
+ enforcement_metadata=enforcement_metadata,
252
+ )
253
+
254
+ # Always log MCP tool results (not just in debug mode)
255
+ if tool_name.startswith("mcp__"):
256
+ # Extract result/error from output_data
257
+ result_preview = None
258
+ if isinstance(output_data, dict):
259
+ result = output_data.get("result", output_data.get("output", output_data))
260
+ result_str = str(result)
261
+ result_preview = result_str[:500] + "..." if len(result_str) > 500 else result_str
262
+
263
+ if is_error:
264
+ logger.error(
265
+ "❌ mcp_tool_failed",
266
+ tool_name=tool_name,
267
+ tool_use_id=tool_use_id[:12],
268
+ error=result_preview,
269
+ message=f"Failed: {tool_name}"
270
+ )
271
+ else:
272
+ logger.info(
273
+ "✅ mcp_tool_completed",
274
+ tool_name=tool_name,
275
+ tool_use_id=tool_use_id[:12],
276
+ result_preview=result_preview,
277
+ result_length=len(str(output_data)),
278
+ message=f"Completed: {tool_name}"
279
+ )
280
+
281
+ # BUG FIX #2: Use structured logging instead of print
282
+ if DEBUG_MODE:
283
+ logger.debug(
284
+ "post_tool_hook_called",
285
+ tool_use_id=tool_use_id,
286
+ tool_name=tool_name,
287
+ is_error=is_error,
288
+ status="failed" if is_error else "success",
289
+ )
290
+
291
+ # Publish tool_complete event (with deduplication)
292
+ # Both hooks and ToolResultBlock can publish, so check if already published
293
+ if event_callback and tool_use_id not in completed_tools:
294
+ try:
295
+ event_callback(
296
+ {
297
+ "type": "tool_complete",
298
+ "tool_name": tool_name,
299
+ "tool_execution_id": tool_use_id,
300
+ "status": "failed" if is_error else "success",
301
+ "output": str(output_data)[:1000] if output_data else None,
302
+ "error": str(output_data) if is_error else None,
303
+ "execution_id": execution_id,
304
+ "enforcement": enforcement_metadata, # Add enforcement metadata
305
+ }
306
+ )
307
+ # Mark as completed to prevent duplicate from ToolResultBlock
308
+ completed_tools.add(tool_use_id)
309
+ if DEBUG_MODE:
310
+ logger.debug(
311
+ "post_tool_hook_published_tool_complete",
312
+ tool_use_id=tool_use_id,
313
+ tool_name=tool_name,
314
+ is_error=is_error,
315
+ )
316
+ except Exception as e:
317
+ logger.error(
318
+ "failed_to_publish_tool_complete",
319
+ tool_name=tool_name,
320
+ tool_use_id=tool_use_id,
321
+ error=str(e),
322
+ exc_info=True,
323
+ )
324
+ elif tool_use_id in completed_tools:
325
+ if DEBUG_MODE:
326
+ logger.debug(
327
+ "tool_complete_already_published_via_stream",
328
+ tool_use_id=tool_use_id,
329
+ tool_name=tool_name,
330
+ )
331
+
332
+ return {}
333
+
334
+ async def subagent_stop_hook(input_data, tool_use_id, tool_context):
335
+ """
336
+ Hook called when a subagent (Task tool) completes.
337
+
338
+ This captures completions for Task tools that spawn subagents,
339
+ which don't fire PostToolUse hooks. According to Claude SDK docs,
340
+ subagent execution happens in separate contexts, so we need this
341
+ dedicated hook to track their completion.
342
+
343
+ Args:
344
+ input_data: SubagentStopHookInput dict containing:
345
+ - session_id: Session ID for the subagent
346
+ - transcript_path: Path to subagent transcript
347
+ - cwd: Working directory
348
+ - stop_hook_active: Whether hook is active
349
+ - hook_event_name: Name of the hook event
350
+ tool_use_id: Unique identifier for this tool execution
351
+ tool_context: Additional context from SDK
352
+
353
+ Returns:
354
+ Empty dict (SDK requirement)
355
+ """
356
+ tool_name = "Task" # Subagents are spawned by Task tool
357
+
358
+ # CRITICAL: Validate input_data is a dict (SDK passes SubagentStopHookInput)
359
+ if not isinstance(input_data, dict):
360
+ logger.error(
361
+ "subagent_stop_hook_invalid_input_type",
362
+ input_type=type(input_data).__name__,
363
+ note="Expected dict with session_id, transcript_path, etc."
364
+ )
365
+ return {}
366
+
367
+ # Defensive: Validate tool_use_id is present and valid
368
+ if not tool_use_id:
369
+ logger.error(
370
+ "subagent_stop_hook_missing_tool_use_id",
371
+ has_input_data=bool(input_data),
372
+ note="Cannot track completion without tool_use_id"
373
+ )
374
+ return {}
375
+
376
+ # Defensive: Ensure tool_use_id is string for string operations
377
+ if not isinstance(tool_use_id, str):
378
+ logger.warning(
379
+ "subagent_stop_hook_invalid_tool_use_id_type",
380
+ tool_use_id_type=type(tool_use_id).__name__,
381
+ note="Converting to string for safety"
382
+ )
383
+ try:
384
+ tool_use_id = str(tool_use_id)
385
+ except Exception as e:
386
+ logger.error(
387
+ "subagent_stop_hook_tool_use_id_conversion_failed",
388
+ error=str(e),
389
+ note="Cannot proceed without valid tool_use_id"
390
+ )
391
+ return {}
392
+
393
+ # Safe slicing for logging (check length)
394
+ tool_use_id_short = tool_use_id[:12] if len(tool_use_id) >= 12 else tool_use_id
395
+
396
+ if DEBUG_MODE:
397
+ logger.debug(
398
+ "subagent_stop_hook_called",
399
+ tool_use_id=tool_use_id_short,
400
+ has_input_data=bool(input_data),
401
+ input_data_type=type(input_data).__name__,
402
+ input_data_keys=list(input_data.keys()) if isinstance(input_data, dict) else None,
403
+ )
404
+
405
+ # Always log subagent completions (not just in debug mode)
406
+ logger.info(
407
+ "subagent_completed",
408
+ tool_use_id=tool_use_id_short,
409
+ tool_name=tool_name,
410
+ message=f"✅ Subagent (Task) completed: {tool_use_id_short}"
411
+ )
412
+
413
+ # Check if already completed (deduplicate with PostToolUse, though unlikely)
414
+ if event_callback and tool_use_id not in completed_tools:
415
+ try:
416
+ # Extract meaningful info from SubagentStopHookInput
417
+ output_str = None
418
+ if input_data:
419
+ try:
420
+ session_id = input_data.get("session_id", "")
421
+ transcript_path = input_data.get("transcript_path", "")
422
+ output_str = f"Subagent completed (session: {session_id[:16] if session_id else 'unknown'})"
423
+
424
+ if transcript_path:
425
+ output_str += f" - transcript: {transcript_path}"
426
+ except Exception as conv_error:
427
+ logger.warning(
428
+ "subagent_input_extraction_failed",
429
+ tool_use_id=tool_use_id_short,
430
+ error=str(conv_error),
431
+ note="Using fallback output representation"
432
+ )
433
+ output_str = f"<Subagent completed - extraction failed: {type(input_data).__name__}>"
434
+
435
+ event_callback(
436
+ {
437
+ "type": "tool_complete",
438
+ "tool_name": tool_name,
439
+ "tool_execution_id": tool_use_id,
440
+ "status": "success", # Subagent completed successfully
441
+ "output": output_str,
442
+ "error": None,
443
+ "execution_id": execution_id,
444
+ }
445
+ )
446
+ completed_tools.add(tool_use_id)
447
+
448
+ if DEBUG_MODE:
449
+ logger.debug(
450
+ "subagent_completion_published",
451
+ tool_use_id=tool_use_id_short,
452
+ tool_name=tool_name,
453
+ )
454
+ except Exception as e:
455
+ # Non-fatal: Log but don't crash if event publishing fails
456
+ logger.error(
457
+ "failed_to_publish_subagent_completion",
458
+ tool_use_id=tool_use_id_short,
459
+ error=str(e),
460
+ error_type=type(e).__name__,
461
+ exc_info=True,
462
+ note="Completion tracking failed but execution succeeded"
463
+ )
464
+ elif tool_use_id in completed_tools:
465
+ if DEBUG_MODE:
466
+ logger.debug(
467
+ "subagent_completion_already_published",
468
+ tool_use_id=tool_use_id_short,
469
+ tool_name=tool_name,
470
+ note="Deduplicated - PostToolUse already published this"
471
+ )
472
+
473
+ return {}
474
+
475
+ # Build hook configuration
476
+ hooks = {
477
+ "PreToolUse": [HookMatcher(hooks=[pre_tool_hook])],
478
+ "PostToolUse": [HookMatcher(hooks=[post_tool_hook])],
479
+ "SubagentStop": [HookMatcher(hooks=[subagent_stop_hook])], # NEW: Track subagent (Task) completions
480
+ }
481
+
482
+ return hooks