kubiya-control-plane-api 0.9.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. control_plane_api/LICENSE +676 -0
  2. control_plane_api/README.md +350 -0
  3. control_plane_api/__init__.py +4 -0
  4. control_plane_api/__version__.py +8 -0
  5. control_plane_api/alembic/README +1 -0
  6. control_plane_api/alembic/env.py +121 -0
  7. control_plane_api/alembic/script.py.mako +28 -0
  8. control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
  9. control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
  10. control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
  11. control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
  12. control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
  13. control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
  14. control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
  15. control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
  16. control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
  17. control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
  18. control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
  19. control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
  20. control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
  21. control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
  22. control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
  23. control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
  24. control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
  25. control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
  26. control_plane_api/alembic.ini +148 -0
  27. control_plane_api/api/index.py +12 -0
  28. control_plane_api/app/__init__.py +11 -0
  29. control_plane_api/app/activities/__init__.py +20 -0
  30. control_plane_api/app/activities/agent_activities.py +384 -0
  31. control_plane_api/app/activities/plan_generation_activities.py +499 -0
  32. control_plane_api/app/activities/team_activities.py +424 -0
  33. control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
  34. control_plane_api/app/config/__init__.py +35 -0
  35. control_plane_api/app/config/api_config.py +469 -0
  36. control_plane_api/app/config/config_loader.py +224 -0
  37. control_plane_api/app/config/model_pricing.py +323 -0
  38. control_plane_api/app/config/storage_config.py +159 -0
  39. control_plane_api/app/config.py +115 -0
  40. control_plane_api/app/controllers/__init__.py +0 -0
  41. control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
  42. control_plane_api/app/database.py +135 -0
  43. control_plane_api/app/exceptions.py +408 -0
  44. control_plane_api/app/lib/__init__.py +11 -0
  45. control_plane_api/app/lib/environment.py +65 -0
  46. control_plane_api/app/lib/event_bus/__init__.py +17 -0
  47. control_plane_api/app/lib/event_bus/base.py +136 -0
  48. control_plane_api/app/lib/event_bus/manager.py +335 -0
  49. control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
  50. control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
  51. control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
  52. control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
  53. control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
  54. control_plane_api/app/lib/job_executor.py +330 -0
  55. control_plane_api/app/lib/kubiya_client.py +293 -0
  56. control_plane_api/app/lib/litellm_pricing.py +166 -0
  57. control_plane_api/app/lib/mcp_validation.py +163 -0
  58. control_plane_api/app/lib/nats/__init__.py +13 -0
  59. control_plane_api/app/lib/nats/credentials_manager.py +288 -0
  60. control_plane_api/app/lib/nats/listener.py +374 -0
  61. control_plane_api/app/lib/planning_prompt_builder.py +153 -0
  62. control_plane_api/app/lib/planning_tools/__init__.py +41 -0
  63. control_plane_api/app/lib/planning_tools/agents.py +409 -0
  64. control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
  65. control_plane_api/app/lib/planning_tools/base.py +119 -0
  66. control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
  67. control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
  68. control_plane_api/app/lib/planning_tools/environments.py +218 -0
  69. control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
  70. control_plane_api/app/lib/planning_tools/models.py +93 -0
  71. control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
  72. control_plane_api/app/lib/planning_tools/resources.py +242 -0
  73. control_plane_api/app/lib/planning_tools/teams.py +334 -0
  74. control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
  75. control_plane_api/app/lib/redis_client.py +803 -0
  76. control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
  77. control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
  78. control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
  79. control_plane_api/app/lib/storage/__init__.py +20 -0
  80. control_plane_api/app/lib/storage/base_provider.py +274 -0
  81. control_plane_api/app/lib/storage/provider_factory.py +157 -0
  82. control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
  83. control_plane_api/app/lib/supabase.py +71 -0
  84. control_plane_api/app/lib/supabase_utils.py +138 -0
  85. control_plane_api/app/lib/task_planning/__init__.py +138 -0
  86. control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
  87. control_plane_api/app/lib/task_planning/agents.py +389 -0
  88. control_plane_api/app/lib/task_planning/cache.py +218 -0
  89. control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
  90. control_plane_api/app/lib/task_planning/helpers.py +293 -0
  91. control_plane_api/app/lib/task_planning/hooks.py +474 -0
  92. control_plane_api/app/lib/task_planning/models.py +503 -0
  93. control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
  94. control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
  95. control_plane_api/app/lib/task_planning/runner.py +656 -0
  96. control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
  97. control_plane_api/app/lib/task_planning/workflow.py +424 -0
  98. control_plane_api/app/lib/templating/__init__.py +88 -0
  99. control_plane_api/app/lib/templating/compiler.py +278 -0
  100. control_plane_api/app/lib/templating/engine.py +178 -0
  101. control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
  102. control_plane_api/app/lib/templating/parsers/base.py +96 -0
  103. control_plane_api/app/lib/templating/parsers/env.py +85 -0
  104. control_plane_api/app/lib/templating/parsers/graph.py +112 -0
  105. control_plane_api/app/lib/templating/parsers/secret.py +87 -0
  106. control_plane_api/app/lib/templating/parsers/simple.py +81 -0
  107. control_plane_api/app/lib/templating/resolver.py +366 -0
  108. control_plane_api/app/lib/templating/types.py +214 -0
  109. control_plane_api/app/lib/templating/validator.py +201 -0
  110. control_plane_api/app/lib/temporal_client.py +232 -0
  111. control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
  112. control_plane_api/app/lib/temporal_credentials_service.py +203 -0
  113. control_plane_api/app/lib/validation/__init__.py +24 -0
  114. control_plane_api/app/lib/validation/runtime_validation.py +388 -0
  115. control_plane_api/app/main.py +531 -0
  116. control_plane_api/app/middleware/__init__.py +10 -0
  117. control_plane_api/app/middleware/auth.py +645 -0
  118. control_plane_api/app/middleware/exception_handler.py +267 -0
  119. control_plane_api/app/middleware/prometheus_middleware.py +173 -0
  120. control_plane_api/app/middleware/rate_limiting.py +384 -0
  121. control_plane_api/app/middleware/request_id.py +202 -0
  122. control_plane_api/app/models/__init__.py +40 -0
  123. control_plane_api/app/models/agent.py +90 -0
  124. control_plane_api/app/models/analytics.py +206 -0
  125. control_plane_api/app/models/associations.py +107 -0
  126. control_plane_api/app/models/auth_user.py +73 -0
  127. control_plane_api/app/models/context.py +161 -0
  128. control_plane_api/app/models/custom_integration.py +99 -0
  129. control_plane_api/app/models/environment.py +64 -0
  130. control_plane_api/app/models/execution.py +125 -0
  131. control_plane_api/app/models/execution_transition.py +50 -0
  132. control_plane_api/app/models/job.py +159 -0
  133. control_plane_api/app/models/llm_model.py +78 -0
  134. control_plane_api/app/models/orchestration.py +66 -0
  135. control_plane_api/app/models/plan_execution.py +102 -0
  136. control_plane_api/app/models/presence.py +49 -0
  137. control_plane_api/app/models/project.py +61 -0
  138. control_plane_api/app/models/project_management.py +85 -0
  139. control_plane_api/app/models/session.py +29 -0
  140. control_plane_api/app/models/skill.py +155 -0
  141. control_plane_api/app/models/system_tables.py +43 -0
  142. control_plane_api/app/models/task_planning.py +372 -0
  143. control_plane_api/app/models/team.py +86 -0
  144. control_plane_api/app/models/trace.py +257 -0
  145. control_plane_api/app/models/user_profile.py +54 -0
  146. control_plane_api/app/models/worker.py +221 -0
  147. control_plane_api/app/models/workflow.py +161 -0
  148. control_plane_api/app/models/workspace.py +50 -0
  149. control_plane_api/app/observability/__init__.py +177 -0
  150. control_plane_api/app/observability/context_logging.py +475 -0
  151. control_plane_api/app/observability/decorators.py +337 -0
  152. control_plane_api/app/observability/local_span_processor.py +702 -0
  153. control_plane_api/app/observability/metrics.py +303 -0
  154. control_plane_api/app/observability/middleware.py +246 -0
  155. control_plane_api/app/observability/optional.py +115 -0
  156. control_plane_api/app/observability/tracing.py +382 -0
  157. control_plane_api/app/policies/README.md +149 -0
  158. control_plane_api/app/policies/approved_users.rego +62 -0
  159. control_plane_api/app/policies/business_hours.rego +51 -0
  160. control_plane_api/app/policies/rate_limiting.rego +100 -0
  161. control_plane_api/app/policies/tool_enforcement/README.md +336 -0
  162. control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
  163. control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
  164. control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
  165. control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
  166. control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
  167. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  168. control_plane_api/app/routers/__init__.py +4 -0
  169. control_plane_api/app/routers/agents.py +382 -0
  170. control_plane_api/app/routers/agents_v2.py +1598 -0
  171. control_plane_api/app/routers/analytics.py +1310 -0
  172. control_plane_api/app/routers/auth.py +59 -0
  173. control_plane_api/app/routers/client_config.py +57 -0
  174. control_plane_api/app/routers/context_graph.py +561 -0
  175. control_plane_api/app/routers/context_manager.py +577 -0
  176. control_plane_api/app/routers/custom_integrations.py +490 -0
  177. control_plane_api/app/routers/enforcer.py +132 -0
  178. control_plane_api/app/routers/environment_context.py +252 -0
  179. control_plane_api/app/routers/environments.py +761 -0
  180. control_plane_api/app/routers/execution_environment.py +847 -0
  181. control_plane_api/app/routers/executions/__init__.py +28 -0
  182. control_plane_api/app/routers/executions/router.py +286 -0
  183. control_plane_api/app/routers/executions/services/__init__.py +22 -0
  184. control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
  185. control_plane_api/app/routers/executions/services/status_service.py +420 -0
  186. control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
  187. control_plane_api/app/routers/executions/services/worker_health.py +514 -0
  188. control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
  189. control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
  190. control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
  191. control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
  192. control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
  193. control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
  194. control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
  195. control_plane_api/app/routers/executions.py +4888 -0
  196. control_plane_api/app/routers/health.py +165 -0
  197. control_plane_api/app/routers/health_v2.py +394 -0
  198. control_plane_api/app/routers/integration_templates.py +496 -0
  199. control_plane_api/app/routers/integrations.py +287 -0
  200. control_plane_api/app/routers/jobs.py +1809 -0
  201. control_plane_api/app/routers/metrics.py +517 -0
  202. control_plane_api/app/routers/models.py +82 -0
  203. control_plane_api/app/routers/models_v2.py +628 -0
  204. control_plane_api/app/routers/plan_executions.py +1481 -0
  205. control_plane_api/app/routers/plan_generation_async.py +304 -0
  206. control_plane_api/app/routers/policies.py +669 -0
  207. control_plane_api/app/routers/presence.py +234 -0
  208. control_plane_api/app/routers/projects.py +987 -0
  209. control_plane_api/app/routers/runners.py +379 -0
  210. control_plane_api/app/routers/runtimes.py +172 -0
  211. control_plane_api/app/routers/secrets.py +171 -0
  212. control_plane_api/app/routers/skills.py +1010 -0
  213. control_plane_api/app/routers/skills_definitions.py +140 -0
  214. control_plane_api/app/routers/storage.py +456 -0
  215. control_plane_api/app/routers/task_planning.py +611 -0
  216. control_plane_api/app/routers/task_queues.py +650 -0
  217. control_plane_api/app/routers/team_context.py +274 -0
  218. control_plane_api/app/routers/teams.py +1747 -0
  219. control_plane_api/app/routers/templates.py +248 -0
  220. control_plane_api/app/routers/traces.py +571 -0
  221. control_plane_api/app/routers/websocket_client.py +479 -0
  222. control_plane_api/app/routers/websocket_executions_status.py +437 -0
  223. control_plane_api/app/routers/websocket_gateway.py +323 -0
  224. control_plane_api/app/routers/websocket_traces.py +576 -0
  225. control_plane_api/app/routers/worker_queues.py +2555 -0
  226. control_plane_api/app/routers/worker_websocket.py +419 -0
  227. control_plane_api/app/routers/workers.py +1004 -0
  228. control_plane_api/app/routers/workflows.py +204 -0
  229. control_plane_api/app/runtimes/__init__.py +6 -0
  230. control_plane_api/app/runtimes/validation.py +344 -0
  231. control_plane_api/app/schemas/__init__.py +1 -0
  232. control_plane_api/app/schemas/job_schemas.py +302 -0
  233. control_plane_api/app/schemas/mcp_schemas.py +311 -0
  234. control_plane_api/app/schemas/template_schemas.py +133 -0
  235. control_plane_api/app/schemas/trace_schemas.py +168 -0
  236. control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
  237. control_plane_api/app/services/__init__.py +1 -0
  238. control_plane_api/app/services/agno_planning_strategy.py +233 -0
  239. control_plane_api/app/services/agno_service.py +838 -0
  240. control_plane_api/app/services/claude_code_planning_service.py +203 -0
  241. control_plane_api/app/services/context_graph_client.py +224 -0
  242. control_plane_api/app/services/custom_integration_service.py +415 -0
  243. control_plane_api/app/services/integration_resolution_service.py +345 -0
  244. control_plane_api/app/services/litellm_service.py +394 -0
  245. control_plane_api/app/services/plan_generator.py +79 -0
  246. control_plane_api/app/services/planning_strategy.py +66 -0
  247. control_plane_api/app/services/planning_strategy_factory.py +118 -0
  248. control_plane_api/app/services/policy_service.py +615 -0
  249. control_plane_api/app/services/state_transition_service.py +755 -0
  250. control_plane_api/app/services/storage_service.py +593 -0
  251. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  252. control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
  253. control_plane_api/app/services/trace_retention.py +354 -0
  254. control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
  255. control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
  256. control_plane_api/app/services/workflow_operations_service.py +611 -0
  257. control_plane_api/app/skills/__init__.py +100 -0
  258. control_plane_api/app/skills/base.py +239 -0
  259. control_plane_api/app/skills/builtin/__init__.py +37 -0
  260. control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
  261. control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
  262. control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
  263. control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
  264. control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
  265. control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
  266. control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
  267. control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
  268. control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
  269. control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
  270. control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
  271. control_plane_api/app/skills/builtin/docker/skill.py +104 -0
  272. control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
  273. control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
  274. control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
  275. control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
  276. control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
  277. control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
  278. control_plane_api/app/skills/builtin/python/__init__.py +4 -0
  279. control_plane_api/app/skills/builtin/python/skill.py +92 -0
  280. control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
  281. control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
  282. control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
  283. control_plane_api/app/skills/builtin/shell/skill.py +161 -0
  284. control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
  285. control_plane_api/app/skills/builtin/slack/skill.py +302 -0
  286. control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
  287. control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
  288. control_plane_api/app/skills/business_intelligence.py +189 -0
  289. control_plane_api/app/skills/config.py +63 -0
  290. control_plane_api/app/skills/loaders/__init__.py +14 -0
  291. control_plane_api/app/skills/loaders/base.py +73 -0
  292. control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
  293. control_plane_api/app/skills/registry.py +125 -0
  294. control_plane_api/app/utils/helpers.py +12 -0
  295. control_plane_api/app/utils/workflow_executor.py +354 -0
  296. control_plane_api/app/workflows/__init__.py +11 -0
  297. control_plane_api/app/workflows/agent_execution.py +520 -0
  298. control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
  299. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  300. control_plane_api/app/workflows/plan_generation.py +254 -0
  301. control_plane_api/app/workflows/team_execution.py +442 -0
  302. control_plane_api/scripts/seed_models.py +240 -0
  303. control_plane_api/scripts/validate_existing_tool_names.py +492 -0
  304. control_plane_api/shared/__init__.py +8 -0
  305. control_plane_api/shared/version.py +17 -0
  306. control_plane_api/test_deduplication.py +274 -0
  307. control_plane_api/test_executor_deduplication_e2e.py +309 -0
  308. control_plane_api/test_job_execution_e2e.py +283 -0
  309. control_plane_api/test_real_integration.py +193 -0
  310. control_plane_api/version.py +38 -0
  311. control_plane_api/worker/__init__.py +0 -0
  312. control_plane_api/worker/activities/__init__.py +0 -0
  313. control_plane_api/worker/activities/agent_activities.py +1585 -0
  314. control_plane_api/worker/activities/approval_activities.py +234 -0
  315. control_plane_api/worker/activities/job_activities.py +199 -0
  316. control_plane_api/worker/activities/runtime_activities.py +1167 -0
  317. control_plane_api/worker/activities/skill_activities.py +282 -0
  318. control_plane_api/worker/activities/team_activities.py +479 -0
  319. control_plane_api/worker/agent_runtime_server.py +370 -0
  320. control_plane_api/worker/binary_manager.py +333 -0
  321. control_plane_api/worker/config/__init__.py +31 -0
  322. control_plane_api/worker/config/worker_config.py +273 -0
  323. control_plane_api/worker/control_plane_client.py +1491 -0
  324. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  325. control_plane_api/worker/health_monitor.py +159 -0
  326. control_plane_api/worker/metrics.py +237 -0
  327. control_plane_api/worker/models/__init__.py +1 -0
  328. control_plane_api/worker/models/error_events.py +105 -0
  329. control_plane_api/worker/models/inputs.py +89 -0
  330. control_plane_api/worker/runtimes/__init__.py +35 -0
  331. control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
  332. control_plane_api/worker/runtimes/agno/__init__.py +34 -0
  333. control_plane_api/worker/runtimes/agno/config.py +248 -0
  334. control_plane_api/worker/runtimes/agno/hooks.py +385 -0
  335. control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
  336. control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
  337. control_plane_api/worker/runtimes/agno/utils.py +163 -0
  338. control_plane_api/worker/runtimes/base.py +979 -0
  339. control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
  340. control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
  341. control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
  342. control_plane_api/worker/runtimes/claude_code/config.py +829 -0
  343. control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
  344. control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
  345. control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
  346. control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
  347. control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
  348. control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
  349. control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
  350. control_plane_api/worker/runtimes/factory.py +173 -0
  351. control_plane_api/worker/runtimes/model_utils.py +107 -0
  352. control_plane_api/worker/runtimes/validation.py +93 -0
  353. control_plane_api/worker/services/__init__.py +1 -0
  354. control_plane_api/worker/services/agent_communication_tools.py +908 -0
  355. control_plane_api/worker/services/agent_executor.py +485 -0
  356. control_plane_api/worker/services/agent_executor_v2.py +793 -0
  357. control_plane_api/worker/services/analytics_collector.py +457 -0
  358. control_plane_api/worker/services/analytics_service.py +464 -0
  359. control_plane_api/worker/services/approval_tools.py +310 -0
  360. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  361. control_plane_api/worker/services/cancellation_manager.py +177 -0
  362. control_plane_api/worker/services/code_ingestion_tools.py +465 -0
  363. control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
  364. control_plane_api/worker/services/data_visualization.py +834 -0
  365. control_plane_api/worker/services/event_publisher.py +531 -0
  366. control_plane_api/worker/services/jira_tools.py +257 -0
  367. control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
  368. control_plane_api/worker/services/runtime_analytics.py +328 -0
  369. control_plane_api/worker/services/session_service.py +365 -0
  370. control_plane_api/worker/services/skill_context_enhancement.py +181 -0
  371. control_plane_api/worker/services/skill_factory.py +471 -0
  372. control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
  373. control_plane_api/worker/services/team_executor.py +715 -0
  374. control_plane_api/worker/services/team_executor_v2.py +1866 -0
  375. control_plane_api/worker/services/tool_enforcement.py +254 -0
  376. control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
  377. control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
  378. control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
  379. control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
  380. control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
  381. control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
  382. control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
  383. control_plane_api/worker/services/workflow_executor/models.py +142 -0
  384. control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
  385. control_plane_api/worker/skills/__init__.py +12 -0
  386. control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
  387. control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
  388. control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
  389. control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
  390. control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
  391. control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
  392. control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
  393. control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
  394. control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
  395. control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
  396. control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
  397. control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
  398. control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
  399. control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
  400. control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
  401. control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
  402. control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
  403. control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
  404. control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
  405. control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
  406. control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
  407. control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
  408. control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
  409. control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
  410. control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
  411. control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
  412. control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
  413. control_plane_api/worker/skills/loaders/__init__.py +5 -0
  414. control_plane_api/worker/skills/loaders/base.py +23 -0
  415. control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
  416. control_plane_api/worker/skills/registry.py +208 -0
  417. control_plane_api/worker/tests/__init__.py +1 -0
  418. control_plane_api/worker/tests/conftest.py +12 -0
  419. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  420. control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
  421. control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
  422. control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
  423. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  424. control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
  425. control_plane_api/worker/tests/integration/__init__.py +0 -0
  426. control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
  427. control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
  428. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  429. control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
  430. control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
  431. control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
  432. control_plane_api/worker/tests/unit/__init__.py +0 -0
  433. control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
  434. control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
  435. control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
  436. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  437. control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
  438. control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
  439. control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
  440. control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
  441. control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
  442. control_plane_api/worker/utils/__init__.py +1 -0
  443. control_plane_api/worker/utils/chunk_batcher.py +330 -0
  444. control_plane_api/worker/utils/environment.py +65 -0
  445. control_plane_api/worker/utils/error_publisher.py +260 -0
  446. control_plane_api/worker/utils/event_batcher.py +256 -0
  447. control_plane_api/worker/utils/logging_config.py +335 -0
  448. control_plane_api/worker/utils/logging_helper.py +326 -0
  449. control_plane_api/worker/utils/parameter_validator.py +120 -0
  450. control_plane_api/worker/utils/retry_utils.py +60 -0
  451. control_plane_api/worker/utils/streaming_utils.py +665 -0
  452. control_plane_api/worker/utils/tool_validation.py +332 -0
  453. control_plane_api/worker/utils/workspace_manager.py +163 -0
  454. control_plane_api/worker/websocket_client.py +393 -0
  455. control_plane_api/worker/worker.py +1297 -0
  456. control_plane_api/worker/workflows/__init__.py +0 -0
  457. control_plane_api/worker/workflows/agent_execution.py +909 -0
  458. control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
  459. control_plane_api/worker/workflows/team_execution.py +611 -0
  460. kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
  461. kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
  462. kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
  463. kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
  464. kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
  465. kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
  466. scripts/__init__.py +1 -0
  467. scripts/migrations.py +39 -0
  468. scripts/seed_worker_queues.py +128 -0
  469. scripts/setup_agent_runtime.py +142 -0
  470. worker_internal/__init__.py +1 -0
  471. worker_internal/planner/__init__.py +1 -0
  472. worker_internal/planner/activities.py +1499 -0
  473. worker_internal/planner/agent_tools.py +197 -0
  474. worker_internal/planner/event_models.py +148 -0
  475. worker_internal/planner/event_publisher.py +67 -0
  476. worker_internal/planner/models.py +199 -0
  477. worker_internal/planner/retry_logic.py +134 -0
  478. worker_internal/planner/worker.py +300 -0
  479. worker_internal/planner/workflows.py +970 -0
@@ -0,0 +1,611 @@
1
+ """
2
+ Workflow Operations Service.
3
+
4
+ This service provides business logic for workflow/task operations including
5
+ listing workflows, getting workflow details, and terminating workflows.
6
+ """
7
+
8
+ import structlog
9
+ from datetime import datetime, timezone
10
+ from typing import Optional, Dict, List
11
+ from sqlalchemy.orm import Session
12
+ from temporalio.client import Client
13
+ from temporalio.api.enums.v1 import EventType
14
+
15
+ from control_plane_api.app.models.worker import WorkerQueue
16
+ from control_plane_api.app.models.execution import Execution
17
+ from control_plane_api.app.models.orchestration import TemporalNamespace
18
+ from control_plane_api.app.lib.temporal_client import get_temporal_client_for_org
19
+ from control_plane_api.app.lib.temporal_credentials_service import get_temporal_credentials_for_org
20
+ from control_plane_api.app.schemas.worker_queue_observability_schemas import (
21
+ WorkflowListItem,
22
+ WorkflowsListResponse,
23
+ WorkflowDetailsResponse,
24
+ WorkflowTrace,
25
+ ActivityExecution,
26
+ WorkflowEvent,
27
+ TerminateWorkflowResponse
28
+ )
29
+
30
+ logger = structlog.get_logger()
31
+
32
+
33
+ class WorkflowOperationsService:
34
+ """Service for workflow/task operations"""
35
+
36
+ def __init__(self, db: Session):
37
+ self.db = db
38
+
39
+ async def list_queue_workflows(
40
+ self,
41
+ queue_id: str,
42
+ organization_id: str,
43
+ status_filter: Optional[str] = None,
44
+ limit: int = 100
45
+ ) -> WorkflowsListResponse:
46
+ """
47
+ List workflows/tasks for a worker queue.
48
+
49
+ Args:
50
+ queue_id: Worker queue UUID
51
+ organization_id: Organization ID
52
+ status_filter: Optional status filter (running, completed, failed, cancelled)
53
+ limit: Maximum number of workflows to return
54
+
55
+ Returns:
56
+ WorkflowsListResponse with workflow list and counts
57
+
58
+ Raises:
59
+ ValueError: If queue not found
60
+ """
61
+ # Verify queue exists
62
+ queue = self.db.query(WorkerQueue).filter(
63
+ WorkerQueue.id == queue_id,
64
+ WorkerQueue.organization_id == organization_id
65
+ ).first()
66
+
67
+ if not queue:
68
+ raise ValueError("Worker queue not found")
69
+
70
+ # Query executions for this queue
71
+ query = self.db.query(Execution).filter(
72
+ Execution.worker_queue_id == queue_id
73
+ )
74
+
75
+ if status_filter:
76
+ query = query.filter(Execution.status == status_filter)
77
+
78
+ # Order by most recent first
79
+ query = query.order_by(Execution.created_at.desc()).limit(limit)
80
+ executions = query.all()
81
+
82
+ # Get status counts
83
+ pending_count = self.db.query(Execution).filter(
84
+ Execution.worker_queue_id == queue_id,
85
+ Execution.status == "pending"
86
+ ).count()
87
+
88
+ running_count = self.db.query(Execution).filter(
89
+ Execution.worker_queue_id == queue_id,
90
+ Execution.status == "running"
91
+ ).count()
92
+
93
+ completed_count = self.db.query(Execution).filter(
94
+ Execution.worker_queue_id == queue_id,
95
+ Execution.status == "completed"
96
+ ).count()
97
+
98
+ failed_count = self.db.query(Execution).filter(
99
+ Execution.worker_queue_id == queue_id,
100
+ Execution.status == "failed"
101
+ ).count()
102
+
103
+ # Convert to workflow list items
104
+ workflows = []
105
+ for execution in executions:
106
+ workflows.append(WorkflowListItem(
107
+ workflow_id=execution.temporal_workflow_id or "",
108
+ run_id=execution.temporal_run_id or "",
109
+ task_queue=execution.task_queue_name or "",
110
+ worker_id=None, # Worker ID not tracked at execution level
111
+ status=execution.status or "unknown",
112
+ execution_id=str(execution.id),
113
+ started_at=execution.started_at,
114
+ close_time=execution.completed_at,
115
+ workflow_type="agent-execution-workflow",
116
+ attempt=1,
117
+ history_length=0
118
+ ))
119
+
120
+ logger.info(
121
+ "workflows_listed",
122
+ queue_id=queue_id,
123
+ total=len(workflows),
124
+ pending=pending_count,
125
+ running=running_count,
126
+ completed=completed_count,
127
+ failed=failed_count
128
+ )
129
+
130
+ return WorkflowsListResponse(
131
+ workflows=workflows,
132
+ total=len(workflows),
133
+ pending_count=pending_count,
134
+ running_count=running_count,
135
+ completed_count=completed_count,
136
+ failed_count=failed_count
137
+ )
138
+
139
+ async def get_workflow_details(
140
+ self,
141
+ workflow_id: str,
142
+ organization_id: str,
143
+ token: str
144
+ ) -> WorkflowDetailsResponse:
145
+ """
146
+ Get detailed information about a workflow execution.
147
+
148
+ Args:
149
+ workflow_id: Temporal workflow ID
150
+ organization_id: Organization ID
151
+ token: Kubiya auth token for Temporal credentials
152
+
153
+ Returns:
154
+ WorkflowDetailsResponse with full workflow details
155
+
156
+ Raises:
157
+ ValueError: If workflow not found or access denied
158
+ RuntimeError: If Temporal client unavailable
159
+ """
160
+ # Find execution by temporal_workflow_id or derived from workflow_id pattern
161
+ execution = self.db.query(Execution).filter(
162
+ Execution.temporal_workflow_id == workflow_id
163
+ ).first()
164
+
165
+ if not execution:
166
+ # Try to extract execution ID from derived workflow ID patterns
167
+ # Pattern: agent-execution-{uuid} or team-execution-{uuid}
168
+ execution_id = None
169
+ if workflow_id.startswith("agent-execution-"):
170
+ execution_id = workflow_id.replace("agent-execution-", "")
171
+ elif workflow_id.startswith("team-execution-"):
172
+ execution_id = workflow_id.replace("team-execution-", "")
173
+
174
+ if execution_id:
175
+ execution = self.db.query(Execution).filter(
176
+ Execution.id == execution_id
177
+ ).first()
178
+
179
+ if not execution:
180
+ raise ValueError("Workflow not found")
181
+
182
+ # Verify organization access
183
+ if execution.organization_id != organization_id:
184
+ raise ValueError("Access denied")
185
+
186
+ # Get Temporal client
187
+ temporal_credentials = await get_temporal_credentials_for_org(
188
+ org_id=organization_id,
189
+ token=token,
190
+ use_fallback=True
191
+ )
192
+
193
+ temporal_client = await get_temporal_client_for_org(
194
+ namespace=temporal_credentials["namespace"],
195
+ api_key=temporal_credentials["api_key"],
196
+ host=temporal_credentials["host"],
197
+ )
198
+
199
+ if not temporal_client:
200
+ raise RuntimeError("Temporal client unavailable")
201
+
202
+ try:
203
+ # Get workflow handle
204
+ workflow_handle = temporal_client.get_workflow_handle(
205
+ workflow_id=workflow_id,
206
+ run_id=execution.temporal_run_id
207
+ )
208
+
209
+ # Describe workflow
210
+ description = await workflow_handle.describe()
211
+
212
+ # Fetch workflow history and parse activities, input, and events
213
+ trace, workflow_input, recent_events, history_length = await self._parse_workflow_history(workflow_handle)
214
+
215
+ # Calculate duration
216
+ if execution.started_at and execution.completed_at:
217
+ duration_ms = (execution.completed_at - execution.started_at).total_seconds() * 1000
218
+ elif execution.started_at:
219
+ duration_ms = (datetime.now(timezone.utc) - execution.started_at).total_seconds() * 1000
220
+ else:
221
+ duration_ms = 0
222
+
223
+ # Generate Temporal Web UI URL (internal use only, not exposed to UI)
224
+ temporal_web_url = f"https://cloud.temporal.io/namespaces/{temporal_credentials['namespace']}/workflows/{workflow_id}/{execution.temporal_run_id}"
225
+
226
+ logger.info(
227
+ "workflow_details_retrieved",
228
+ workflow_id=workflow_id,
229
+ execution_id=str(execution.id),
230
+ history_length=history_length,
231
+ has_input=workflow_input is not None
232
+ )
233
+
234
+ return WorkflowDetailsResponse(
235
+ workflow_id=workflow_id,
236
+ run_id=execution.temporal_run_id or "",
237
+ status=description.status.name,
238
+ execution_id=str(execution.id),
239
+ execution_status=execution.status or "unknown",
240
+ start_time=execution.started_at,
241
+ close_time=execution.completed_at,
242
+ execution_duration_ms=duration_ms,
243
+ task_queue=description.task_queue,
244
+ workflow_type=description.workflow_type,
245
+ attempt=1,
246
+ history_length=history_length,
247
+ history_size_bytes=0,
248
+ input=workflow_input,
249
+ temporal_web_url=temporal_web_url,
250
+ recent_events=recent_events,
251
+ trace=trace
252
+ )
253
+
254
+ except Exception as e:
255
+ logger.error(
256
+ "workflow_details_fetch_failed",
257
+ error=str(e),
258
+ workflow_id=workflow_id
259
+ )
260
+ raise RuntimeError(f"Failed to get workflow details: {str(e)}")
261
+
262
+ async def terminate_workflow(
263
+ self,
264
+ workflow_id: str,
265
+ organization_id: str,
266
+ token: str,
267
+ reason: str
268
+ ) -> TerminateWorkflowResponse:
269
+ """
270
+ Terminate a running workflow.
271
+
272
+ Args:
273
+ workflow_id: Temporal workflow ID
274
+ organization_id: Organization ID
275
+ token: Kubiya auth token
276
+ reason: Termination reason
277
+
278
+ Returns:
279
+ TerminateWorkflowResponse with success status
280
+
281
+ Raises:
282
+ ValueError: If workflow not found, access denied, or not running
283
+ RuntimeError: If termination fails
284
+ """
285
+ # Find execution by temporal_workflow_id or derived from workflow_id pattern
286
+ execution = self.db.query(Execution).filter(
287
+ Execution.temporal_workflow_id == workflow_id
288
+ ).first()
289
+
290
+ if not execution:
291
+ # Try to extract execution ID from derived workflow ID patterns
292
+ # Pattern: agent-execution-{uuid} or team-execution-{uuid}
293
+ execution_id = None
294
+ if workflow_id.startswith("agent-execution-"):
295
+ execution_id = workflow_id.replace("agent-execution-", "")
296
+ elif workflow_id.startswith("team-execution-"):
297
+ execution_id = workflow_id.replace("team-execution-", "")
298
+
299
+ if execution_id:
300
+ execution = self.db.query(Execution).filter(
301
+ Execution.id == execution_id
302
+ ).first()
303
+
304
+ if not execution:
305
+ raise ValueError("Workflow not found")
306
+
307
+ # Verify organization access
308
+ if execution.organization_id != organization_id:
309
+ raise ValueError("Access denied")
310
+
311
+ # Check if workflow is running
312
+ if execution.status not in ["running", "pending"]:
313
+ raise ValueError(f"Cannot terminate workflow in status: {execution.status}")
314
+
315
+ # Get Temporal client
316
+ temporal_credentials = await get_temporal_credentials_for_org(
317
+ org_id=organization_id,
318
+ token=token,
319
+ use_fallback=True
320
+ )
321
+
322
+ temporal_client = await get_temporal_client_for_org(
323
+ namespace=temporal_credentials["namespace"],
324
+ api_key=temporal_credentials["api_key"],
325
+ host=temporal_credentials["host"],
326
+ )
327
+
328
+ if not temporal_client:
329
+ raise RuntimeError("Temporal client unavailable")
330
+
331
+ try:
332
+ # Get workflow handle
333
+ workflow_handle = temporal_client.get_workflow_handle(
334
+ workflow_id=workflow_id,
335
+ run_id=execution.temporal_run_id
336
+ )
337
+
338
+ # Cancel the workflow
339
+ await workflow_handle.cancel()
340
+
341
+ # Update execution status in DB
342
+ now = datetime.now(timezone.utc)
343
+ execution.status = "cancelled"
344
+ execution.completed_at = now
345
+ self.db.commit()
346
+
347
+ logger.info(
348
+ "workflow_terminated",
349
+ workflow_id=workflow_id,
350
+ execution_id=str(execution.id),
351
+ reason=reason
352
+ )
353
+
354
+ return TerminateWorkflowResponse(
355
+ success=True,
356
+ workflow_id=workflow_id,
357
+ terminated_at=now
358
+ )
359
+
360
+ except Exception as e:
361
+ self.db.rollback()
362
+ logger.error(
363
+ "workflow_termination_failed",
364
+ error=str(e),
365
+ workflow_id=workflow_id
366
+ )
367
+ raise RuntimeError(f"Failed to terminate workflow: {str(e)}")
368
+
369
+ async def _parse_workflow_history(self, workflow_handle) -> tuple:
370
+ """
371
+ Parse workflow history to extract activity executions, timeline, input, and events.
372
+
373
+ Args:
374
+ workflow_handle: Temporal workflow handle
375
+
376
+ Returns:
377
+ Tuple of (WorkflowTrace, input_data, recent_events, history_length)
378
+ """
379
+ activities_map: Dict[str, ActivityExecution] = {}
380
+ timeline_events: List[Dict] = []
381
+ recent_events: List[WorkflowEvent] = []
382
+ workflow_input: Optional[Dict] = None
383
+ history_length: int = 0
384
+
385
+ # Map to track scheduled_event_id -> activity_id for linking events
386
+ scheduled_event_map: Dict[int, str] = {}
387
+
388
+ try:
389
+ # Use fetch_history_events for async iteration
390
+ async for event in workflow_handle.fetch_history_events():
391
+ history_length += 1
392
+ event_type = event.event_type
393
+ event_time = event.event_time.ToDatetime() if hasattr(event.event_time, 'ToDatetime') else event.event_time
394
+
395
+ # Capture recent events (last 20)
396
+ event_type_name = str(event_type).replace("EVENT_TYPE_", "") if hasattr(event_type, 'name') else str(event_type)
397
+ if len(recent_events) < 20:
398
+ recent_events.append(WorkflowEvent(
399
+ event_id=event.event_id,
400
+ event_type=event_type_name,
401
+ event_time=event_time,
402
+ details={}
403
+ ))
404
+
405
+ # Parse WorkflowExecutionStarted to get input
406
+ # Compare as integer since event.event_type might be int or enum
407
+ if event_type == EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED or event_type == 1:
408
+ logger.debug("found_workflow_execution_started", event_type=str(event_type), event_id=event.event_id)
409
+ try:
410
+ attrs = event.workflow_execution_started_event_attributes
411
+ if attrs.input and attrs.input.payloads:
412
+ import json
413
+ logger.debug("workflow_input_payloads_found", count=len(attrs.input.payloads))
414
+ # Try to decode the first payload as JSON
415
+ for payload in attrs.input.payloads:
416
+ try:
417
+ data = payload.data.decode('utf-8') if payload.data else None
418
+ if data:
419
+ workflow_input = json.loads(data)
420
+ logger.info("workflow_input_parsed", has_input=True)
421
+ break
422
+ except (json.JSONDecodeError, UnicodeDecodeError):
423
+ # If not JSON, store as string
424
+ workflow_input = {"raw": payload.data.decode('utf-8', errors='replace') if payload.data else None}
425
+ logger.info("workflow_input_raw", has_input=True)
426
+ else:
427
+ logger.debug("workflow_input_not_found", has_input=attrs.input is not None)
428
+ except Exception as e:
429
+ logger.warning("failed_to_parse_workflow_input", error=str(e))
430
+
431
+ # Parse activity scheduled events
432
+ elif event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED:
433
+ attrs = event.activity_task_scheduled_event_attributes
434
+ activity_id = attrs.activity_id or str(event.event_id)
435
+
436
+ # Store mapping from event_id to activity_id for linking
437
+ scheduled_event_map[event.event_id] = activity_id
438
+
439
+ activities_map[activity_id] = ActivityExecution(
440
+ activity_id=activity_id,
441
+ activity_type=attrs.activity_type.name if attrs.activity_type else "unknown",
442
+ status="scheduled",
443
+ scheduled_time=event_time,
444
+ attempt=1
445
+ )
446
+
447
+ timeline_events.append({
448
+ "type": "activity_scheduled",
449
+ "event_id": event.event_id,
450
+ "activity_id": activity_id,
451
+ "activity_type": attrs.activity_type.name if attrs.activity_type else "unknown",
452
+ "timestamp": event_time.isoformat() if hasattr(event_time, 'isoformat') else str(event_time)
453
+ })
454
+
455
+ # Parse activity started events
456
+ elif event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_STARTED:
457
+ attrs = event.activity_task_started_event_attributes
458
+ # Get activity_id from the scheduled_event_id reference
459
+ scheduled_event_id = attrs.scheduled_event_id
460
+ activity_id = scheduled_event_map.get(scheduled_event_id)
461
+
462
+ if activity_id and activity_id in activities_map:
463
+ activities_map[activity_id].status = "started"
464
+ activities_map[activity_id].started_time = event_time
465
+ activities_map[activity_id].worker_identity = attrs.identity
466
+ activities_map[activity_id].attempt = attrs.attempt
467
+
468
+ timeline_events.append({
469
+ "type": "activity_started",
470
+ "event_id": event.event_id,
471
+ "activity_id": activity_id,
472
+ "worker": attrs.identity,
473
+ "attempt": attrs.attempt,
474
+ "timestamp": event_time.isoformat() if hasattr(event_time, 'isoformat') else str(event_time)
475
+ })
476
+
477
+ # Parse activity completed events
478
+ elif event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_COMPLETED:
479
+ attrs = event.activity_task_completed_event_attributes
480
+ scheduled_event_id = attrs.scheduled_event_id
481
+ activity_id = scheduled_event_map.get(scheduled_event_id)
482
+
483
+ if activity_id and activity_id in activities_map:
484
+ activity = activities_map[activity_id]
485
+ activity.status = "completed"
486
+ activity.completed_time = event_time
487
+
488
+ if activity.started_time:
489
+ started = activity.started_time
490
+ if hasattr(started, 'timestamp'):
491
+ started = started.timestamp()
492
+ elif hasattr(started, 'ToDatetime'):
493
+ started = started.ToDatetime().timestamp()
494
+ else:
495
+ started = started.timestamp() if hasattr(started, 'timestamp') else 0
496
+
497
+ completed = event_time
498
+ if hasattr(completed, 'timestamp'):
499
+ completed = completed.timestamp()
500
+ elif hasattr(completed, 'ToDatetime'):
501
+ completed = completed.ToDatetime().timestamp()
502
+ else:
503
+ completed = completed.timestamp() if hasattr(completed, 'timestamp') else 0
504
+
505
+ activity.duration_ms = (completed - started) * 1000
506
+
507
+ timeline_events.append({
508
+ "type": "activity_completed",
509
+ "event_id": event.event_id,
510
+ "activity_id": activity_id,
511
+ "duration_ms": activity.duration_ms,
512
+ "timestamp": event_time.isoformat() if hasattr(event_time, 'isoformat') else str(event_time)
513
+ })
514
+
515
+ # Parse activity failed events
516
+ elif event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_FAILED:
517
+ attrs = event.activity_task_failed_event_attributes
518
+ scheduled_event_id = attrs.scheduled_event_id
519
+ activity_id = scheduled_event_map.get(scheduled_event_id)
520
+
521
+ if activity_id and activity_id in activities_map:
522
+ activity = activities_map[activity_id]
523
+ activity.status = "failed"
524
+ activity.completed_time = event_time
525
+
526
+ # Extract failure message
527
+ if attrs.failure:
528
+ activity.failure_message = attrs.failure.message
529
+
530
+ if activity.started_time:
531
+ started = activity.started_time
532
+ if hasattr(started, 'timestamp'):
533
+ started = started.timestamp()
534
+ elif hasattr(started, 'ToDatetime'):
535
+ started = started.ToDatetime().timestamp()
536
+ else:
537
+ started = 0
538
+
539
+ completed = event_time
540
+ if hasattr(completed, 'timestamp'):
541
+ completed = completed.timestamp()
542
+ elif hasattr(completed, 'ToDatetime'):
543
+ completed = completed.ToDatetime().timestamp()
544
+ else:
545
+ completed = 0
546
+
547
+ activity.duration_ms = (completed - started) * 1000
548
+
549
+ timeline_events.append({
550
+ "type": "activity_failed",
551
+ "event_id": event.event_id,
552
+ "activity_id": activity_id,
553
+ "error": activity.failure_message,
554
+ "retry_state": attrs.retry_state if hasattr(attrs, 'retry_state') else None,
555
+ "timestamp": event_time.isoformat() if hasattr(event_time, 'isoformat') else str(event_time)
556
+ })
557
+
558
+ # Parse activity timed out events
559
+ elif event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT:
560
+ attrs = event.activity_task_timed_out_event_attributes
561
+ scheduled_event_id = attrs.scheduled_event_id
562
+ activity_id = scheduled_event_map.get(scheduled_event_id)
563
+
564
+ if activity_id and activity_id in activities_map:
565
+ activities_map[activity_id].status = "timed_out"
566
+ activities_map[activity_id].completed_time = event_time
567
+
568
+ timeline_events.append({
569
+ "type": "activity_timed_out",
570
+ "event_id": event.event_id,
571
+ "activity_id": activity_id,
572
+ "timestamp": event_time.isoformat() if hasattr(event_time, 'isoformat') else str(event_time)
573
+ })
574
+
575
+ # Parse activity cancelled events
576
+ elif event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_CANCELED:
577
+ attrs = event.activity_task_canceled_event_attributes
578
+ scheduled_event_id = attrs.scheduled_event_id
579
+ activity_id = scheduled_event_map.get(scheduled_event_id)
580
+
581
+ if activity_id and activity_id in activities_map:
582
+ activities_map[activity_id].status = "cancelled"
583
+ activities_map[activity_id].completed_time = event_time
584
+
585
+ timeline_events.append({
586
+ "type": "activity_cancelled",
587
+ "event_id": event.event_id,
588
+ "activity_id": activity_id,
589
+ "timestamp": event_time.isoformat() if hasattr(event_time, 'isoformat') else str(event_time)
590
+ })
591
+
592
+ except Exception as e:
593
+ logger.warning(
594
+ "workflow_history_parse_error",
595
+ error=str(e),
596
+ exc_info=True
597
+ )
598
+ # Continue with what we have
599
+
600
+ # Build trace object
601
+ activities_list = list(activities_map.values())
602
+
603
+ trace = WorkflowTrace(
604
+ activities=activities_list,
605
+ timeline=timeline_events,
606
+ total_activities=len(activities_list),
607
+ completed_activities=sum(1 for a in activities_list if a.status == "completed"),
608
+ failed_activities=sum(1 for a in activities_list if a.status == "failed")
609
+ )
610
+
611
+ return trace, workflow_input, recent_events, history_length