kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.4__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.

Potentially problematic release.


This version of kubiya-control-plane-api might be problematic. Click here for more details.

Files changed (185) hide show
  1. control_plane_api/README.md +266 -0
  2. control_plane_api/__init__.py +0 -0
  3. control_plane_api/__version__.py +1 -0
  4. control_plane_api/alembic/README +1 -0
  5. control_plane_api/alembic/env.py +98 -0
  6. control_plane_api/alembic/script.py.mako +28 -0
  7. control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
  8. control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
  9. control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
  10. control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
  11. control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
  12. control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
  13. control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
  14. control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
  15. control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
  16. control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
  17. control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
  18. control_plane_api/alembic.ini +148 -0
  19. control_plane_api/api/index.py +12 -0
  20. control_plane_api/app/__init__.py +11 -0
  21. control_plane_api/app/activities/__init__.py +20 -0
  22. control_plane_api/app/activities/agent_activities.py +379 -0
  23. control_plane_api/app/activities/team_activities.py +410 -0
  24. control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
  25. control_plane_api/app/config/__init__.py +35 -0
  26. control_plane_api/app/config/api_config.py +354 -0
  27. control_plane_api/app/config/model_pricing.py +318 -0
  28. control_plane_api/app/config.py +95 -0
  29. control_plane_api/app/database.py +135 -0
  30. control_plane_api/app/exceptions.py +408 -0
  31. control_plane_api/app/lib/__init__.py +11 -0
  32. control_plane_api/app/lib/job_executor.py +312 -0
  33. control_plane_api/app/lib/kubiya_client.py +235 -0
  34. control_plane_api/app/lib/litellm_pricing.py +166 -0
  35. control_plane_api/app/lib/planning_tools/__init__.py +22 -0
  36. control_plane_api/app/lib/planning_tools/agents.py +155 -0
  37. control_plane_api/app/lib/planning_tools/base.py +189 -0
  38. control_plane_api/app/lib/planning_tools/environments.py +214 -0
  39. control_plane_api/app/lib/planning_tools/resources.py +240 -0
  40. control_plane_api/app/lib/planning_tools/teams.py +198 -0
  41. control_plane_api/app/lib/policy_enforcer_client.py +939 -0
  42. control_plane_api/app/lib/redis_client.py +436 -0
  43. control_plane_api/app/lib/supabase.py +71 -0
  44. control_plane_api/app/lib/temporal_client.py +138 -0
  45. control_plane_api/app/lib/validation/__init__.py +20 -0
  46. control_plane_api/app/lib/validation/runtime_validation.py +287 -0
  47. control_plane_api/app/main.py +128 -0
  48. control_plane_api/app/middleware/__init__.py +8 -0
  49. control_plane_api/app/middleware/auth.py +513 -0
  50. control_plane_api/app/middleware/exception_handler.py +267 -0
  51. control_plane_api/app/middleware/rate_limiting.py +384 -0
  52. control_plane_api/app/middleware/request_id.py +202 -0
  53. control_plane_api/app/models/__init__.py +27 -0
  54. control_plane_api/app/models/agent.py +79 -0
  55. control_plane_api/app/models/analytics.py +206 -0
  56. control_plane_api/app/models/associations.py +81 -0
  57. control_plane_api/app/models/environment.py +63 -0
  58. control_plane_api/app/models/execution.py +93 -0
  59. control_plane_api/app/models/job.py +179 -0
  60. control_plane_api/app/models/llm_model.py +75 -0
  61. control_plane_api/app/models/presence.py +49 -0
  62. control_plane_api/app/models/project.py +47 -0
  63. control_plane_api/app/models/session.py +38 -0
  64. control_plane_api/app/models/team.py +66 -0
  65. control_plane_api/app/models/workflow.py +55 -0
  66. control_plane_api/app/policies/README.md +121 -0
  67. control_plane_api/app/policies/approved_users.rego +62 -0
  68. control_plane_api/app/policies/business_hours.rego +51 -0
  69. control_plane_api/app/policies/rate_limiting.rego +100 -0
  70. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  71. control_plane_api/app/routers/__init__.py +4 -0
  72. control_plane_api/app/routers/agents.py +364 -0
  73. control_plane_api/app/routers/agents_v2.py +1260 -0
  74. control_plane_api/app/routers/analytics.py +1014 -0
  75. control_plane_api/app/routers/context_manager.py +562 -0
  76. control_plane_api/app/routers/environment_context.py +270 -0
  77. control_plane_api/app/routers/environments.py +715 -0
  78. control_plane_api/app/routers/execution_environment.py +517 -0
  79. control_plane_api/app/routers/executions.py +1911 -0
  80. control_plane_api/app/routers/health.py +92 -0
  81. control_plane_api/app/routers/health_v2.py +326 -0
  82. control_plane_api/app/routers/integrations.py +274 -0
  83. control_plane_api/app/routers/jobs.py +1344 -0
  84. control_plane_api/app/routers/models.py +82 -0
  85. control_plane_api/app/routers/models_v2.py +361 -0
  86. control_plane_api/app/routers/policies.py +639 -0
  87. control_plane_api/app/routers/presence.py +234 -0
  88. control_plane_api/app/routers/projects.py +902 -0
  89. control_plane_api/app/routers/runners.py +379 -0
  90. control_plane_api/app/routers/runtimes.py +172 -0
  91. control_plane_api/app/routers/secrets.py +155 -0
  92. control_plane_api/app/routers/skills.py +1001 -0
  93. control_plane_api/app/routers/skills_definitions.py +140 -0
  94. control_plane_api/app/routers/task_planning.py +1256 -0
  95. control_plane_api/app/routers/task_queues.py +654 -0
  96. control_plane_api/app/routers/team_context.py +270 -0
  97. control_plane_api/app/routers/teams.py +1400 -0
  98. control_plane_api/app/routers/worker_queues.py +1545 -0
  99. control_plane_api/app/routers/workers.py +935 -0
  100. control_plane_api/app/routers/workflows.py +204 -0
  101. control_plane_api/app/runtimes/__init__.py +6 -0
  102. control_plane_api/app/runtimes/validation.py +344 -0
  103. control_plane_api/app/schemas/job_schemas.py +295 -0
  104. control_plane_api/app/services/__init__.py +1 -0
  105. control_plane_api/app/services/agno_service.py +619 -0
  106. control_plane_api/app/services/litellm_service.py +190 -0
  107. control_plane_api/app/services/policy_service.py +525 -0
  108. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  109. control_plane_api/app/skills/__init__.py +44 -0
  110. control_plane_api/app/skills/base.py +229 -0
  111. control_plane_api/app/skills/business_intelligence.py +189 -0
  112. control_plane_api/app/skills/data_visualization.py +154 -0
  113. control_plane_api/app/skills/docker.py +104 -0
  114. control_plane_api/app/skills/file_generation.py +94 -0
  115. control_plane_api/app/skills/file_system.py +110 -0
  116. control_plane_api/app/skills/python.py +92 -0
  117. control_plane_api/app/skills/registry.py +65 -0
  118. control_plane_api/app/skills/shell.py +102 -0
  119. control_plane_api/app/skills/workflow_executor.py +469 -0
  120. control_plane_api/app/utils/workflow_executor.py +354 -0
  121. control_plane_api/app/workflows/__init__.py +11 -0
  122. control_plane_api/app/workflows/agent_execution.py +507 -0
  123. control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
  124. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  125. control_plane_api/app/workflows/team_execution.py +399 -0
  126. control_plane_api/scripts/seed_models.py +239 -0
  127. control_plane_api/worker/__init__.py +0 -0
  128. control_plane_api/worker/activities/__init__.py +0 -0
  129. control_plane_api/worker/activities/agent_activities.py +1241 -0
  130. control_plane_api/worker/activities/approval_activities.py +234 -0
  131. control_plane_api/worker/activities/runtime_activities.py +388 -0
  132. control_plane_api/worker/activities/skill_activities.py +267 -0
  133. control_plane_api/worker/activities/team_activities.py +1217 -0
  134. control_plane_api/worker/config/__init__.py +31 -0
  135. control_plane_api/worker/config/worker_config.py +275 -0
  136. control_plane_api/worker/control_plane_client.py +529 -0
  137. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  138. control_plane_api/worker/models/__init__.py +1 -0
  139. control_plane_api/worker/models/inputs.py +89 -0
  140. control_plane_api/worker/runtimes/__init__.py +31 -0
  141. control_plane_api/worker/runtimes/base.py +789 -0
  142. control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
  143. control_plane_api/worker/runtimes/default_runtime.py +617 -0
  144. control_plane_api/worker/runtimes/factory.py +173 -0
  145. control_plane_api/worker/runtimes/validation.py +93 -0
  146. control_plane_api/worker/services/__init__.py +1 -0
  147. control_plane_api/worker/services/agent_executor.py +422 -0
  148. control_plane_api/worker/services/agent_executor_v2.py +383 -0
  149. control_plane_api/worker/services/analytics_collector.py +457 -0
  150. control_plane_api/worker/services/analytics_service.py +464 -0
  151. control_plane_api/worker/services/approval_tools.py +310 -0
  152. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  153. control_plane_api/worker/services/cancellation_manager.py +177 -0
  154. control_plane_api/worker/services/data_visualization.py +827 -0
  155. control_plane_api/worker/services/jira_tools.py +257 -0
  156. control_plane_api/worker/services/runtime_analytics.py +328 -0
  157. control_plane_api/worker/services/session_service.py +194 -0
  158. control_plane_api/worker/services/skill_factory.py +175 -0
  159. control_plane_api/worker/services/team_executor.py +574 -0
  160. control_plane_api/worker/services/team_executor_v2.py +465 -0
  161. control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
  162. control_plane_api/worker/tests/__init__.py +1 -0
  163. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  164. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  165. control_plane_api/worker/tests/integration/__init__.py +0 -0
  166. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  167. control_plane_api/worker/tests/unit/__init__.py +0 -0
  168. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  169. control_plane_api/worker/utils/__init__.py +1 -0
  170. control_plane_api/worker/utils/chunk_batcher.py +305 -0
  171. control_plane_api/worker/utils/retry_utils.py +60 -0
  172. control_plane_api/worker/utils/streaming_utils.py +373 -0
  173. control_plane_api/worker/worker.py +753 -0
  174. control_plane_api/worker/workflows/__init__.py +0 -0
  175. control_plane_api/worker/workflows/agent_execution.py +589 -0
  176. control_plane_api/worker/workflows/team_execution.py +429 -0
  177. kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
  178. kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
  179. kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
  180. kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
  181. kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
  182. kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
  183. kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
  184. {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
  185. {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,102 @@
1
+ """
2
+ Shell Skill
3
+
4
+ Provides shell command execution capabilities with configurable restrictions.
5
+ """
6
+ from typing import Dict, Any, List
7
+ from .base import SkillDefinition, SkillType, SkillCategory, SkillVariant
8
+ from .registry import register_skill
9
+
10
+
11
+ class ShellSkill(SkillDefinition):
12
+ """Shell command execution skill"""
13
+
14
+ @property
15
+ def type(self) -> SkillType:
16
+ return SkillType.SHELL
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ return "Shell"
21
+
22
+ @property
23
+ def description(self) -> str:
24
+ return "Execute shell commands on the local system with configurable restrictions"
25
+
26
+ @property
27
+ def icon(self) -> str:
28
+ return "Terminal"
29
+
30
+ def get_variants(self) -> List[SkillVariant]:
31
+ return [
32
+ SkillVariant(
33
+ id="shell_safe_commands",
34
+ name="Shell - Safe Commands",
35
+ description="Execute read-only shell commands on the local system (ls, cat, grep, ps)",
36
+ category=SkillCategory.COMMON,
37
+ badge="Safe",
38
+ icon="Terminal",
39
+ configuration={
40
+ "allowed_commands": ["ls", "cat", "grep", "find", "ps", "top", "pwd", "echo", "head", "tail"],
41
+ "timeout": 30,
42
+ },
43
+ is_default=True,
44
+ ),
45
+ SkillVariant(
46
+ id="shell_full_access",
47
+ name="Shell - Full Access",
48
+ description="Unrestricted shell access to execute any command on local system",
49
+ category=SkillCategory.ADVANCED,
50
+ badge="Advanced",
51
+ icon="Terminal",
52
+ configuration={
53
+ "timeout": 300,
54
+ },
55
+ is_default=False,
56
+ ),
57
+ SkillVariant(
58
+ id="shell_read_only",
59
+ name="Shell - Read Only",
60
+ description="Maximum security: only non-destructive read commands allowed",
61
+ category=SkillCategory.SECURITY,
62
+ badge="Secure",
63
+ icon="ShieldCheck",
64
+ configuration={
65
+ "allowed_commands": ["ls", "cat", "head", "tail", "grep", "find", "pwd"],
66
+ "blocked_commands": ["rm", "mv", "cp", "chmod", "chown", "kill"],
67
+ "timeout": 15,
68
+ },
69
+ is_default=False,
70
+ ),
71
+ ]
72
+
73
+ def validate_configuration(self, config: Dict[str, Any]) -> Dict[str, Any]:
74
+ """Validate shell configuration"""
75
+ validated = {
76
+ "timeout": min(config.get("timeout", 30), 600), # Max 10 minutes
77
+ }
78
+
79
+ # Add allowed_commands if specified
80
+ if "allowed_commands" in config:
81
+ validated["allowed_commands"] = list(config["allowed_commands"])
82
+
83
+ # Add blocked_commands if specified
84
+ if "blocked_commands" in config:
85
+ validated["blocked_commands"] = list(config["blocked_commands"])
86
+
87
+ # Add working_directory if specified
88
+ if "working_directory" in config:
89
+ validated["working_directory"] = str(config["working_directory"])
90
+
91
+ return validated
92
+
93
+ def get_default_configuration(self) -> Dict[str, Any]:
94
+ """Default: safe commands only"""
95
+ return {
96
+ "allowed_commands": ["ls", "cat", "grep", "find", "ps", "top", "pwd", "echo", "head", "tail"],
97
+ "timeout": 30,
98
+ }
99
+
100
+
101
+ # Auto-register this skill
102
+ register_skill(ShellSkill())
@@ -0,0 +1,469 @@
1
+ """Workflow Executor Skill - Execute workflows defined via JSON or Python DSL."""
2
+ import json
3
+ import sys
4
+ import traceback
5
+ from io import StringIO
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from pydantic import BaseModel, Field, validator
9
+
10
+ from .base import SkillDefinition, SkillType, SkillCategory, SkillVariant, SkillRequirements
11
+
12
+
13
+ class WorkflowStepConfig(BaseModel):
14
+ """Configuration for a workflow step."""
15
+ name: str = Field(..., description="Step name")
16
+ description: Optional[str] = Field(None, description="Step description")
17
+ executor: Dict[str, Any] = Field(..., description="Executor configuration (type, config)")
18
+ depends_on: Optional[List[str]] = Field(None, description="Step dependencies")
19
+
20
+
21
+ class WorkflowTriggerConfig(BaseModel):
22
+ """Configuration for a workflow trigger."""
23
+ type: str = Field(..., description="Trigger type (manual, cron, webhook)")
24
+ config: Dict[str, Any] = Field(default_factory=dict, description="Trigger configuration")
25
+ runner: Optional[str] = Field(None, description="Runner/environment name")
26
+
27
+
28
+ class WorkflowDefinitionConfig(BaseModel):
29
+ """Workflow definition in JSON format."""
30
+ name: str = Field(..., description="Workflow name")
31
+ description: Optional[str] = Field(None, description="Workflow description")
32
+ steps: List[Dict[str, Any]] = Field(..., min_items=1, description="Workflow steps")
33
+ triggers: Optional[List[Dict[str, Any]]] = Field(None, description="Workflow triggers")
34
+ runner: Optional[str] = Field(None, description="Default runner/environment")
35
+ parameters: Optional[Dict[str, Any]] = Field(
36
+ None,
37
+ description="Parameter schema - defines available parameters (name, type, description). Agent fills values dynamically at runtime."
38
+ )
39
+
40
+ @validator('steps')
41
+ def validate_steps(cls, steps):
42
+ """Validate that steps have required fields."""
43
+ if not steps:
44
+ raise ValueError("Workflow must have at least one step")
45
+
46
+ step_names = set()
47
+ for step in steps:
48
+ if 'name' not in step:
49
+ raise ValueError("Each step must have a 'name' field")
50
+ if step['name'] in step_names:
51
+ raise ValueError(f"Duplicate step name: {step['name']}")
52
+ step_names.add(step['name'])
53
+
54
+ if 'executor' not in step:
55
+ raise ValueError(f"Step '{step['name']}' must have an 'executor' field")
56
+
57
+ executor = step['executor']
58
+ if not isinstance(executor, dict) or 'type' not in executor:
59
+ raise ValueError(f"Step '{step['name']}' executor must be a dict with 'type' field")
60
+
61
+ return steps
62
+
63
+
64
+ class WorkflowExecutorConfiguration(BaseModel):
65
+ """Configuration for the Workflow Executor skill."""
66
+
67
+ workflows: List[Dict[str, Any]] = Field(
68
+ default_factory=list,
69
+ description="Collection of workflow definitions. Each workflow becomes a separate tool."
70
+ )
71
+
72
+ validation_enabled: bool = Field(
73
+ True,
74
+ description="Enable workflow validation before saving"
75
+ )
76
+
77
+ default_runner: Optional[str] = Field(
78
+ None,
79
+ description="Default runner/environment for workflow execution"
80
+ )
81
+
82
+ timeout: int = Field(
83
+ 3600,
84
+ ge=30,
85
+ le=7200,
86
+ description="Maximum workflow execution timeout in seconds (30s - 2h)"
87
+ )
88
+
89
+ default_parameters: Optional[Dict[str, Any]] = Field(
90
+ None,
91
+ description="Default parameter values (optional). Used only if agent doesn't provide values at runtime."
92
+ )
93
+
94
+ # Legacy fields for backwards compatibility
95
+ workflow_type: Optional[str] = Field(
96
+ None,
97
+ description="LEGACY: Type of workflow definition: 'json' or 'python_dsl'"
98
+ )
99
+
100
+ workflow_definition: Optional[str] = Field(
101
+ None,
102
+ description="LEGACY: JSON workflow definition as a string"
103
+ )
104
+
105
+ python_dsl_code: Optional[str] = Field(
106
+ None,
107
+ description="LEGACY: Python DSL code for workflow definition"
108
+ )
109
+
110
+ @validator('workflows')
111
+ def validate_workflows(cls, v, values):
112
+ """Validate workflows collection."""
113
+ if not v:
114
+ # Check if using legacy fields
115
+ if values.get('workflow_definition') or values.get('python_dsl_code'):
116
+ return v # Allow empty if using legacy mode
117
+ raise ValueError("At least one workflow must be defined in 'workflows' array")
118
+
119
+ validation_enabled = values.get('validation_enabled', True)
120
+
121
+ workflow_names = set()
122
+ for idx, workflow in enumerate(v):
123
+ # Each workflow must have required fields
124
+ if 'name' not in workflow:
125
+ raise ValueError(f"Workflow at index {idx} missing 'name' field")
126
+
127
+ if 'type' not in workflow:
128
+ raise ValueError(f"Workflow '{workflow['name']}' missing 'type' field (json or python_dsl)")
129
+
130
+ # Check for duplicate names
131
+ name = workflow['name']
132
+ if name in workflow_names:
133
+ raise ValueError(f"Duplicate workflow name: {name}")
134
+ workflow_names.add(name)
135
+
136
+ # Validate based on type
137
+ wf_type = workflow['type']
138
+ if wf_type not in ['json', 'python_dsl']:
139
+ raise ValueError(f"Workflow '{name}' has invalid type: {wf_type}")
140
+
141
+ if wf_type == 'json':
142
+ if 'definition' not in workflow:
143
+ raise ValueError(f"JSON workflow '{name}' missing 'definition' field")
144
+
145
+ if validation_enabled:
146
+ try:
147
+ # Validate JSON structure
148
+ if isinstance(workflow['definition'], str):
149
+ workflow_data = json.loads(workflow['definition'])
150
+ else:
151
+ workflow_data = workflow['definition']
152
+
153
+ # Validate using WorkflowDefinitionConfig
154
+ WorkflowDefinitionConfig(**workflow_data)
155
+ except json.JSONDecodeError as e:
156
+ raise ValueError(f"Workflow '{name}' has invalid JSON: {str(e)}")
157
+ except Exception as e:
158
+ raise ValueError(f"Workflow '{name}' validation failed: {str(e)}")
159
+
160
+ elif wf_type == 'python_dsl':
161
+ if 'code' not in workflow:
162
+ raise ValueError(f"Python DSL workflow '{name}' missing 'code' field")
163
+
164
+ if validation_enabled:
165
+ try:
166
+ compile(workflow['code'], f'<workflow:{name}>', 'exec')
167
+ except SyntaxError as e:
168
+ raise ValueError(f"Workflow '{name}' has invalid Python syntax: {str(e)}")
169
+
170
+ return v
171
+
172
+ @validator('workflow_type')
173
+ def validate_workflow_type(cls, v):
174
+ """Validate workflow type (legacy)."""
175
+ if v and v not in ['json', 'python_dsl']:
176
+ raise ValueError("workflow_type must be 'json' or 'python_dsl'")
177
+ return v
178
+
179
+ @validator('workflow_definition')
180
+ def validate_workflow_definition(cls, v, values):
181
+ """Validate JSON workflow definition (legacy)."""
182
+ if v and values.get('validation_enabled', True):
183
+ try:
184
+ workflow_data = json.loads(v)
185
+ # Validate using WorkflowDefinitionConfig
186
+ WorkflowDefinitionConfig(**workflow_data)
187
+ except json.JSONDecodeError as e:
188
+ raise ValueError(f"Invalid JSON in workflow_definition: {str(e)}")
189
+ except Exception as e:
190
+ raise ValueError(f"Invalid workflow definition: {str(e)}")
191
+ return v
192
+
193
+ @validator('python_dsl_code')
194
+ def validate_python_dsl_code(cls, v, values):
195
+ """Validate Python DSL code (legacy)."""
196
+ if v and values.get('validation_enabled', True):
197
+ try:
198
+ compile(v, '<workflow>', 'exec')
199
+ except SyntaxError as e:
200
+ raise ValueError(f"Invalid Python syntax in DSL code: {str(e)}")
201
+ return v
202
+
203
+
204
+ class WorkflowExecutorSkill(SkillDefinition):
205
+ """Workflow Executor Skill - Execute workflows defined via JSON or Python DSL.
206
+
207
+ This skill allows defining and executing workflows using either:
208
+ 1. JSON workflow definitions with steps and executors
209
+ 2. Python DSL using the kubiya-sdk
210
+
211
+ Workflows are validated before saving and can be executed via the control plane.
212
+ """
213
+
214
+ @property
215
+ def type(self) -> SkillType:
216
+ """Return the skill type."""
217
+ return SkillType.WORKFLOW_EXECUTOR
218
+
219
+ @property
220
+ def name(self) -> str:
221
+ """Return the skill name."""
222
+ return "Workflow Executor"
223
+
224
+ @property
225
+ def description(self) -> str:
226
+ """Return the skill description."""
227
+ return (
228
+ "Execute workflows defined via JSON or Python DSL. "
229
+ "Supports complex multi-step workflows with dependencies, "
230
+ "conditional execution, and various executor types."
231
+ )
232
+
233
+ @property
234
+ def icon(self) -> str:
235
+ """Return the skill icon."""
236
+ return "Workflow"
237
+
238
+ @property
239
+ def icon_type(self) -> str:
240
+ """Return the icon type."""
241
+ return "lucide"
242
+
243
+ def get_variants(self) -> List[SkillVariant]:
244
+ """Return single workflow executor variant with empty configuration for user to fill."""
245
+ return [
246
+ SkillVariant(
247
+ id="workflow_executor",
248
+ name="Workflow Executor",
249
+ description="Execute workflows with dynamic parameters. Agent fills parameter values at runtime based on user requests.",
250
+ category=SkillCategory.ADVANCED,
251
+ configuration={
252
+ "workflows": [
253
+ {
254
+ "name": "deploy-app",
255
+ "type": "json",
256
+ "definition": {
257
+ "name": "deploy-app",
258
+ "description": "Deploy application to specified environment",
259
+ "runner": "kubiya-prod",
260
+ "parameters": {
261
+ "app_name": {
262
+ "type": "string",
263
+ "description": "Application name to deploy"
264
+ },
265
+ "environment": {
266
+ "type": "string",
267
+ "description": "Target environment (dev, staging, prod)"
268
+ },
269
+ "version": {
270
+ "type": "string",
271
+ "description": "Version to deploy"
272
+ }
273
+ },
274
+ "steps": [
275
+ {
276
+ "name": "deploy",
277
+ "description": "Deploy application",
278
+ "executor": {
279
+ "type": "shell",
280
+ "config": {
281
+ "command": "kubectl apply -f deploy.yaml --namespace={{environment}} && kubectl set image deployment/{{app_name}} app={{app_name}}:{{version}}"
282
+ }
283
+ }
284
+ }
285
+ ]
286
+ }
287
+ }
288
+ ],
289
+ "validation_enabled": True,
290
+ "default_runner": "kubiya-prod",
291
+ "timeout": 3600,
292
+ "default_parameters": {
293
+ "environment": "staging"
294
+ }
295
+ },
296
+ icon="Workflow",
297
+ is_default=True
298
+ )
299
+ ]
300
+
301
+ def validate_configuration(self, config: Dict[str, Any]) -> Dict[str, Any]:
302
+ """Validate the workflow executor configuration.
303
+
304
+ Args:
305
+ config: Configuration dictionary to validate
306
+
307
+ Returns:
308
+ Validated and normalized configuration
309
+
310
+ Raises:
311
+ ValueError: If configuration is invalid
312
+ """
313
+ try:
314
+ # Use Pydantic model for validation
315
+ validated = WorkflowExecutorConfiguration(**config)
316
+
317
+ # Additional validation for workflow execution
318
+ if validated.workflow_type == 'json' and validated.workflow_definition:
319
+ workflow_data = json.loads(validated.workflow_definition)
320
+ # Ensure step dependencies are valid
321
+ step_names = {step['name'] for step in workflow_data.get('steps', [])}
322
+ for step in workflow_data.get('steps', []):
323
+ depends_on = step.get('depends_on', [])
324
+ if depends_on:
325
+ for dep in depends_on:
326
+ if dep not in step_names:
327
+ raise ValueError(
328
+ f"Step '{step['name']}' depends on non-existent step: {dep}"
329
+ )
330
+
331
+ elif validated.workflow_type == 'python_dsl' and validated.python_dsl_code:
332
+ # Try to execute the DSL code to validate it can create a workflow
333
+ if validated.validation_enabled:
334
+ self._validate_python_dsl(validated.python_dsl_code)
335
+
336
+ return validated.dict()
337
+
338
+ except Exception as e:
339
+ raise ValueError(f"Workflow executor configuration validation failed: {str(e)}")
340
+
341
+ def _validate_python_dsl(self, dsl_code: str) -> None:
342
+ """Validate Python DSL code by attempting to compile and check imports.
343
+
344
+ Args:
345
+ dsl_code: Python DSL code to validate
346
+
347
+ Raises:
348
+ ValueError: If DSL code is invalid
349
+ """
350
+ try:
351
+ # First check if kubiya_sdk is available
352
+ try:
353
+ import kubiya_sdk
354
+ except ImportError:
355
+ raise ValueError(
356
+ "kubiya-sdk is not installed. Please install it to use Python DSL workflows."
357
+ )
358
+
359
+ # Compile the code to check for syntax errors
360
+ try:
361
+ compile(dsl_code, '<workflow>', 'exec')
362
+ except SyntaxError as e:
363
+ raise ValueError(f"Python syntax error in DSL code: {str(e)}")
364
+
365
+ # Create a restricted execution environment
366
+ namespace = {
367
+ '__builtins__': __builtins__,
368
+ }
369
+
370
+ # Import kubiya_sdk modules for validation
371
+ try:
372
+ from kubiya_sdk import StatefulWorkflow, Tool
373
+ from kubiya_sdk.workflows import step, tool_step
374
+ namespace.update({
375
+ 'StatefulWorkflow': StatefulWorkflow,
376
+ 'Tool': Tool,
377
+ 'step': step,
378
+ 'tool_step': tool_step,
379
+ })
380
+ except ImportError as e:
381
+ raise ValueError(f"Failed to import kubiya_sdk modules: {str(e)}")
382
+
383
+ # Capture stdout/stderr during validation
384
+ old_stdout = sys.stdout
385
+ old_stderr = sys.stderr
386
+ stdout_capture = StringIO()
387
+ stderr_capture = StringIO()
388
+
389
+ try:
390
+ sys.stdout = stdout_capture
391
+ sys.stderr = stderr_capture
392
+
393
+ # Execute the DSL code
394
+ exec(dsl_code, namespace)
395
+
396
+ # Check if a workflow was created
397
+ workflow_found = False
398
+ for name, obj in namespace.items():
399
+ if name.startswith('_'):
400
+ continue
401
+ # Check for StatefulWorkflow instance
402
+ if hasattr(obj, '__class__'):
403
+ class_name = obj.__class__.__name__
404
+ if 'StatefulWorkflow' in class_name or 'Workflow' in class_name:
405
+ workflow_found = True
406
+ break
407
+
408
+ if not workflow_found:
409
+ raise ValueError(
410
+ "Python DSL code must create a StatefulWorkflow instance. "
411
+ "Example: workflow = StatefulWorkflow(name='my-workflow', description='...')"
412
+ )
413
+
414
+ finally:
415
+ sys.stdout = old_stdout
416
+ sys.stderr = old_stderr
417
+
418
+ # Check for any errors in stderr
419
+ stderr_output = stderr_capture.getvalue()
420
+ if stderr_output and 'error' in stderr_output.lower():
421
+ raise ValueError(f"Python DSL execution produced errors: {stderr_output}")
422
+
423
+ except ValueError:
424
+ raise
425
+ except Exception as e:
426
+ raise ValueError(f"Python DSL validation failed: {str(e)}\n{traceback.format_exc()}")
427
+
428
+ def get_default_configuration(self) -> Dict[str, Any]:
429
+ """Return the default configuration."""
430
+ return {
431
+ "workflow_type": "json",
432
+ "validation_enabled": True,
433
+ "timeout": 3600,
434
+ "workflow_definition": json.dumps({
435
+ "name": "new-workflow",
436
+ "description": "New workflow",
437
+ "steps": [
438
+ {
439
+ "name": "step-1",
440
+ "description": "First step",
441
+ "executor": {
442
+ "type": "shell",
443
+ "config": {
444
+ "command": "echo 'Hello World'"
445
+ }
446
+ }
447
+ }
448
+ ]
449
+ }, indent=2)
450
+ }
451
+
452
+ def get_framework_class_name(self) -> str:
453
+ """Return the framework class name for this skill."""
454
+ return "WorkflowExecutorTool"
455
+
456
+ def get_requirements(self) -> SkillRequirements:
457
+ """Return the skill requirements."""
458
+ return SkillRequirements(
459
+ supported_os=["linux", "darwin", "win32"],
460
+ min_python_version="3.10",
461
+ python_packages=["kubiya-sdk"],
462
+ required_env_vars=[],
463
+ notes="Optional env vars: KUBIYA_API_KEY, KUBIYA_API_URL for workflow execution"
464
+ )
465
+
466
+
467
+ # Auto-register this skill
468
+ from .registry import register_skill
469
+ register_skill(WorkflowExecutorSkill())