steerdev 0.4.27__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.
- steerdev-0.4.27.dist-info/METADATA +224 -0
- steerdev-0.4.27.dist-info/RECORD +57 -0
- steerdev-0.4.27.dist-info/WHEEL +4 -0
- steerdev-0.4.27.dist-info/entry_points.txt +2 -0
- steerdev_agent/__init__.py +10 -0
- steerdev_agent/api/__init__.py +32 -0
- steerdev_agent/api/activity.py +278 -0
- steerdev_agent/api/agents.py +145 -0
- steerdev_agent/api/client.py +158 -0
- steerdev_agent/api/commands.py +399 -0
- steerdev_agent/api/configs.py +238 -0
- steerdev_agent/api/context.py +306 -0
- steerdev_agent/api/events.py +294 -0
- steerdev_agent/api/hooks.py +178 -0
- steerdev_agent/api/implementation_plan.py +408 -0
- steerdev_agent/api/messages.py +231 -0
- steerdev_agent/api/prd.py +281 -0
- steerdev_agent/api/runs.py +526 -0
- steerdev_agent/api/sessions.py +403 -0
- steerdev_agent/api/specs.py +321 -0
- steerdev_agent/api/tasks.py +659 -0
- steerdev_agent/api/workflow_runs.py +351 -0
- steerdev_agent/api/workflows.py +191 -0
- steerdev_agent/cli.py +2254 -0
- steerdev_agent/config/__init__.py +19 -0
- steerdev_agent/config/models.py +236 -0
- steerdev_agent/config/platform.py +272 -0
- steerdev_agent/config/settings.py +62 -0
- steerdev_agent/daemon.py +675 -0
- steerdev_agent/executor/__init__.py +64 -0
- steerdev_agent/executor/base.py +121 -0
- steerdev_agent/executor/claude.py +328 -0
- steerdev_agent/executor/stream.py +163 -0
- steerdev_agent/git/__init__.py +1 -0
- steerdev_agent/handlers/__init__.py +5 -0
- steerdev_agent/handlers/prd.py +533 -0
- steerdev_agent/integration.py +334 -0
- steerdev_agent/prompt/__init__.py +10 -0
- steerdev_agent/prompt/builder.py +263 -0
- steerdev_agent/prompt/templates.py +422 -0
- steerdev_agent/py.typed +0 -0
- steerdev_agent/runner.py +829 -0
- steerdev_agent/setup/__init__.py +5 -0
- steerdev_agent/setup/claude_setup.py +560 -0
- steerdev_agent/setup/templates/claude_md_section.md +140 -0
- steerdev_agent/setup/templates/settings.json +69 -0
- steerdev_agent/setup/templates/skills/activity/SKILL.md +160 -0
- steerdev_agent/setup/templates/skills/context/SKILL.md +122 -0
- steerdev_agent/setup/templates/skills/git-workflow/SKILL.md +218 -0
- steerdev_agent/setup/templates/skills/progress-logging/SKILL.md +211 -0
- steerdev_agent/setup/templates/skills/specs-management/SKILL.md +161 -0
- steerdev_agent/setup/templates/skills/task-management/SKILL.md +343 -0
- steerdev_agent/setup/templates/steerdev.yaml +51 -0
- steerdev_agent/version.py +149 -0
- steerdev_agent/workflow/__init__.py +10 -0
- steerdev_agent/workflow/executor.py +494 -0
- steerdev_agent/workflow/memory.py +185 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Configuration module for steerdev."""
|
|
2
|
+
|
|
3
|
+
from steerdev_agent.config.models import (
|
|
4
|
+
AgentConfig,
|
|
5
|
+
APIConfig,
|
|
6
|
+
EventsConfig,
|
|
7
|
+
SteerDevConfig,
|
|
8
|
+
WorktreeConfig,
|
|
9
|
+
)
|
|
10
|
+
from steerdev_agent.config.settings import Settings
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"APIConfig",
|
|
14
|
+
"AgentConfig",
|
|
15
|
+
"EventsConfig",
|
|
16
|
+
"Settings",
|
|
17
|
+
"SteerDevConfig",
|
|
18
|
+
"WorktreeConfig",
|
|
19
|
+
]
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Pydantic configuration models for steerdev."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WorktreeConfig(BaseModel):
|
|
11
|
+
"""Git worktree isolation configuration.
|
|
12
|
+
|
|
13
|
+
When enabled, the Claude CLI --worktree flag is used to run each task
|
|
14
|
+
in an isolated git worktree. Worktree lifecycle is managed by Claude CLI.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
enabled: Annotated[
|
|
18
|
+
bool,
|
|
19
|
+
Field(default=False, description="Enable Claude CLI --worktree isolation per task"),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AgentConfig(BaseModel):
|
|
24
|
+
"""Configuration for the CLI coding agent."""
|
|
25
|
+
|
|
26
|
+
model: Annotated[
|
|
27
|
+
str | None,
|
|
28
|
+
Field(
|
|
29
|
+
default=None,
|
|
30
|
+
description="Model to use (e.g., claude-sonnet-4-20250514)",
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
max_turns: Annotated[
|
|
34
|
+
int | None,
|
|
35
|
+
Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="Maximum number of agent turns",
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
timeout_seconds: Annotated[
|
|
41
|
+
int,
|
|
42
|
+
Field(default=3600, ge=60, description="Maximum execution time in seconds"),
|
|
43
|
+
]
|
|
44
|
+
workflow_id: Annotated[
|
|
45
|
+
str | None,
|
|
46
|
+
Field(
|
|
47
|
+
default=None,
|
|
48
|
+
description="Workflow ID for multi-phase task execution",
|
|
49
|
+
),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ExecutorConfig(BaseModel):
|
|
54
|
+
"""Configuration for the agent executor."""
|
|
55
|
+
|
|
56
|
+
type: Annotated[
|
|
57
|
+
str,
|
|
58
|
+
Field(
|
|
59
|
+
default="claude",
|
|
60
|
+
description="Executor type (claude, codex, aider)",
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
permission_mode: Annotated[
|
|
64
|
+
str,
|
|
65
|
+
Field(
|
|
66
|
+
default="dangerously-skip-permissions",
|
|
67
|
+
description="Permission mode for the executor",
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
allowed_tools: Annotated[
|
|
71
|
+
list[str],
|
|
72
|
+
Field(
|
|
73
|
+
default_factory=list,
|
|
74
|
+
description="List of tools to allow (empty = all allowed)",
|
|
75
|
+
),
|
|
76
|
+
]
|
|
77
|
+
disallowed_tools: Annotated[
|
|
78
|
+
list[str],
|
|
79
|
+
Field(
|
|
80
|
+
default_factory=list,
|
|
81
|
+
description="List of tools to disallow",
|
|
82
|
+
),
|
|
83
|
+
]
|
|
84
|
+
mcp_config: Annotated[
|
|
85
|
+
str | None,
|
|
86
|
+
Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="Path to MCP config file",
|
|
89
|
+
),
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class APIConfig(BaseModel):
|
|
94
|
+
"""Configuration for steerdev.com API."""
|
|
95
|
+
|
|
96
|
+
api_endpoint: Annotated[
|
|
97
|
+
str,
|
|
98
|
+
Field(
|
|
99
|
+
default="https://steerdev.com/api/v1",
|
|
100
|
+
description="API endpoint for steerdev.com",
|
|
101
|
+
),
|
|
102
|
+
]
|
|
103
|
+
api_key_env: Annotated[
|
|
104
|
+
str,
|
|
105
|
+
Field(
|
|
106
|
+
default="STEERDEV_API_KEY",
|
|
107
|
+
description="Environment variable name for API key",
|
|
108
|
+
),
|
|
109
|
+
]
|
|
110
|
+
project_id_env: Annotated[
|
|
111
|
+
str,
|
|
112
|
+
Field(
|
|
113
|
+
default="STEERDEV_PROJECT_ID",
|
|
114
|
+
description="Environment variable name for project ID",
|
|
115
|
+
),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class EventsConfig(BaseModel):
|
|
120
|
+
"""Configuration for event streaming to API."""
|
|
121
|
+
|
|
122
|
+
batch_size: Annotated[
|
|
123
|
+
int,
|
|
124
|
+
Field(default=10, ge=1, le=100, description="Number of events to batch before sending"),
|
|
125
|
+
]
|
|
126
|
+
flush_interval_seconds: Annotated[
|
|
127
|
+
float,
|
|
128
|
+
Field(default=5.0, ge=1.0, description="Maximum seconds between flushes"),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DaemonConfig(BaseModel):
|
|
133
|
+
"""Configuration for daemon (persistent agent) mode.
|
|
134
|
+
|
|
135
|
+
When running in daemon mode, the agent polls for commands from the API
|
|
136
|
+
and falls back to the task queue when idle.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
poll_interval_seconds: Annotated[
|
|
140
|
+
float,
|
|
141
|
+
Field(default=5.0, ge=1.0, description="Seconds between command queue polls when busy"),
|
|
142
|
+
]
|
|
143
|
+
heartbeat_interval_seconds: Annotated[
|
|
144
|
+
float,
|
|
145
|
+
Field(default=30.0, ge=10.0, description="Seconds between heartbeat updates"),
|
|
146
|
+
]
|
|
147
|
+
idle_poll_interval_seconds: Annotated[
|
|
148
|
+
float,
|
|
149
|
+
Field(default=10.0, ge=1.0, description="Seconds between polls when idle (no commands)"),
|
|
150
|
+
]
|
|
151
|
+
max_consecutive_errors: Annotated[
|
|
152
|
+
int,
|
|
153
|
+
Field(default=5, ge=1, description="Exit after this many consecutive errors"),
|
|
154
|
+
]
|
|
155
|
+
command_timeout_seconds: Annotated[
|
|
156
|
+
int,
|
|
157
|
+
Field(default=3600, ge=60, description="Timeout for individual command execution"),
|
|
158
|
+
]
|
|
159
|
+
auto_fetch_tasks: Annotated[
|
|
160
|
+
bool,
|
|
161
|
+
Field(default=True, description="Fall back to task queue when command queue is empty"),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class SteerDevConfig(BaseModel):
|
|
166
|
+
"""Main configuration for steerdev.
|
|
167
|
+
|
|
168
|
+
This config focuses on:
|
|
169
|
+
- Agent runtime settings (timeout, model)
|
|
170
|
+
- API connection settings
|
|
171
|
+
- Event streaming settings
|
|
172
|
+
- Executor settings
|
|
173
|
+
- Daemon mode settings
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
agent: Annotated[
|
|
177
|
+
AgentConfig,
|
|
178
|
+
Field(default_factory=AgentConfig, description="Agent configuration"),
|
|
179
|
+
]
|
|
180
|
+
api: Annotated[
|
|
181
|
+
APIConfig,
|
|
182
|
+
Field(default_factory=APIConfig, description="API configuration"),
|
|
183
|
+
]
|
|
184
|
+
events: Annotated[
|
|
185
|
+
EventsConfig,
|
|
186
|
+
Field(default_factory=EventsConfig, description="Event streaming configuration"),
|
|
187
|
+
]
|
|
188
|
+
executor: Annotated[
|
|
189
|
+
ExecutorConfig,
|
|
190
|
+
Field(default_factory=ExecutorConfig, description="Executor configuration"),
|
|
191
|
+
]
|
|
192
|
+
worktrees: Annotated[
|
|
193
|
+
WorktreeConfig,
|
|
194
|
+
Field(default_factory=WorktreeConfig, description="Git worktree isolation configuration"),
|
|
195
|
+
]
|
|
196
|
+
daemon: Annotated[
|
|
197
|
+
DaemonConfig,
|
|
198
|
+
Field(
|
|
199
|
+
default_factory=DaemonConfig, description="Daemon (persistent agent) mode configuration"
|
|
200
|
+
),
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def from_yaml(cls, path: str | Path) -> "SteerDevConfig":
|
|
205
|
+
"""Load configuration from a YAML file.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
path: Path to the YAML configuration file.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Parsed SteerDevConfig instance.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
FileNotFoundError: If the file doesn't exist.
|
|
215
|
+
yaml.YAMLError: If the YAML is invalid.
|
|
216
|
+
ValidationError: If the configuration is invalid.
|
|
217
|
+
"""
|
|
218
|
+
path = Path(path)
|
|
219
|
+
with path.open() as f:
|
|
220
|
+
data = yaml.safe_load(f)
|
|
221
|
+
return cls.model_validate(data or {})
|
|
222
|
+
|
|
223
|
+
def to_yaml(self, path: str | Path) -> None:
|
|
224
|
+
"""Save configuration to a YAML file.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
path: Path to save the YAML configuration file.
|
|
228
|
+
"""
|
|
229
|
+
path = Path(path)
|
|
230
|
+
with path.open("w") as f:
|
|
231
|
+
yaml.safe_dump(
|
|
232
|
+
self.model_dump(mode="json"),
|
|
233
|
+
f,
|
|
234
|
+
default_flow_style=False,
|
|
235
|
+
sort_keys=False,
|
|
236
|
+
)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Pydantic models for platform-synced configurations and workflows."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SystemPromptConfig(BaseModel):
|
|
9
|
+
"""System prompt configuration from platform."""
|
|
10
|
+
|
|
11
|
+
id: Annotated[str, Field(description="Config ID")]
|
|
12
|
+
name: Annotated[str, Field(description="Config name")]
|
|
13
|
+
description: Annotated[str | None, Field(default=None, description="Config description")]
|
|
14
|
+
content: Annotated[str, Field(description="System prompt content")]
|
|
15
|
+
version: Annotated[int, Field(default=1, description="Config version")]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SkillConfig(BaseModel):
|
|
19
|
+
"""Skill configuration from platform."""
|
|
20
|
+
|
|
21
|
+
id: Annotated[str, Field(description="Config ID")]
|
|
22
|
+
name: Annotated[str, Field(description="Skill name")]
|
|
23
|
+
description: Annotated[str | None, Field(default=None, description="Skill description")]
|
|
24
|
+
content: Annotated[
|
|
25
|
+
dict,
|
|
26
|
+
Field(
|
|
27
|
+
default_factory=dict,
|
|
28
|
+
description="Skill content (tool definitions, instructions, etc.)",
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
version: Annotated[int, Field(default=1, description="Config version")]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MCPConfig(BaseModel):
|
|
35
|
+
"""MCP server configuration from platform."""
|
|
36
|
+
|
|
37
|
+
id: Annotated[str, Field(description="Config ID")]
|
|
38
|
+
name: Annotated[str, Field(description="MCP server name")]
|
|
39
|
+
description: Annotated[str | None, Field(default=None, description="MCP description")]
|
|
40
|
+
content: Annotated[
|
|
41
|
+
dict,
|
|
42
|
+
Field(
|
|
43
|
+
default_factory=dict,
|
|
44
|
+
description="MCP configuration (server_url, tools, auth, etc.)",
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
version: Annotated[int, Field(default=1, description="Config version")]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class EnvVarsConfig(BaseModel):
|
|
51
|
+
"""Environment variables configuration from platform."""
|
|
52
|
+
|
|
53
|
+
id: Annotated[str, Field(description="Config ID")]
|
|
54
|
+
name: Annotated[str, Field(description="Config name")]
|
|
55
|
+
description: Annotated[str | None, Field(default=None, description="Config description")]
|
|
56
|
+
variables: Annotated[
|
|
57
|
+
dict[str, str],
|
|
58
|
+
Field(default_factory=dict, description="Environment variables key-value pairs"),
|
|
59
|
+
]
|
|
60
|
+
version: Annotated[int, Field(default=1, description="Config version")]
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_api_response(cls, data: dict) -> "EnvVarsConfig":
|
|
64
|
+
"""Create from API response format."""
|
|
65
|
+
content = data.get("content", {})
|
|
66
|
+
return cls(
|
|
67
|
+
id=data["id"],
|
|
68
|
+
name=data["name"],
|
|
69
|
+
description=data.get("description"),
|
|
70
|
+
variables=content.get("variables", {}),
|
|
71
|
+
version=data.get("version", 1),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class PlatformConfig(BaseModel):
|
|
76
|
+
"""Aggregated platform configurations synced from API."""
|
|
77
|
+
|
|
78
|
+
system_prompts: Annotated[
|
|
79
|
+
list[SystemPromptConfig],
|
|
80
|
+
Field(default_factory=list, description="System prompt configurations"),
|
|
81
|
+
]
|
|
82
|
+
skills: Annotated[
|
|
83
|
+
list[SkillConfig],
|
|
84
|
+
Field(default_factory=list, description="Skill configurations"),
|
|
85
|
+
]
|
|
86
|
+
mcps: Annotated[
|
|
87
|
+
list[MCPConfig],
|
|
88
|
+
Field(default_factory=list, description="MCP server configurations"),
|
|
89
|
+
]
|
|
90
|
+
env_vars: Annotated[
|
|
91
|
+
list[EnvVarsConfig],
|
|
92
|
+
Field(default_factory=list, description="Environment variable configurations"),
|
|
93
|
+
]
|
|
94
|
+
synced_at: Annotated[str, Field(description="Timestamp of last sync")]
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_api_response(cls, data: dict) -> "PlatformConfig":
|
|
98
|
+
"""Create from API sync response."""
|
|
99
|
+
return cls(
|
|
100
|
+
system_prompts=[
|
|
101
|
+
SystemPromptConfig(
|
|
102
|
+
id=sp["id"],
|
|
103
|
+
name=sp["name"],
|
|
104
|
+
description=sp.get("description"),
|
|
105
|
+
content=sp.get("content", {}).get("prompt", ""),
|
|
106
|
+
version=sp.get("version", 1),
|
|
107
|
+
)
|
|
108
|
+
for sp in data.get("system_prompts", [])
|
|
109
|
+
],
|
|
110
|
+
skills=[
|
|
111
|
+
SkillConfig(
|
|
112
|
+
id=sk["id"],
|
|
113
|
+
name=sk["name"],
|
|
114
|
+
description=sk.get("description"),
|
|
115
|
+
content=sk.get("content", {}),
|
|
116
|
+
version=sk.get("version", 1),
|
|
117
|
+
)
|
|
118
|
+
for sk in data.get("skills", [])
|
|
119
|
+
],
|
|
120
|
+
mcps=[
|
|
121
|
+
MCPConfig(
|
|
122
|
+
id=mcp["id"],
|
|
123
|
+
name=mcp["name"],
|
|
124
|
+
description=mcp.get("description"),
|
|
125
|
+
content=mcp.get("content", {}),
|
|
126
|
+
version=mcp.get("version", 1),
|
|
127
|
+
)
|
|
128
|
+
for mcp in data.get("mcps", [])
|
|
129
|
+
],
|
|
130
|
+
env_vars=[EnvVarsConfig.from_api_response(ev) for ev in data.get("env_vars", [])],
|
|
131
|
+
synced_at=data.get("synced_at", ""),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def get_combined_system_prompt(self) -> str | None:
|
|
135
|
+
"""Combine all system prompts into one."""
|
|
136
|
+
if not self.system_prompts:
|
|
137
|
+
return None
|
|
138
|
+
return "\n\n".join(sp.content for sp in self.system_prompts)
|
|
139
|
+
|
|
140
|
+
def get_combined_env_vars(self) -> dict[str, str]:
|
|
141
|
+
"""Combine all environment variables into one dict."""
|
|
142
|
+
combined: dict[str, str] = {}
|
|
143
|
+
for ev in self.env_vars:
|
|
144
|
+
combined.update(ev.variables)
|
|
145
|
+
return combined
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class WorkflowPhaseConfig(BaseModel):
|
|
149
|
+
"""Workflow phase configuration from platform."""
|
|
150
|
+
|
|
151
|
+
id: Annotated[str, Field(description="Phase ID")]
|
|
152
|
+
name: Annotated[str, Field(description="Phase name")]
|
|
153
|
+
phase_type: Annotated[
|
|
154
|
+
str,
|
|
155
|
+
Field(
|
|
156
|
+
default="custom",
|
|
157
|
+
description="Phase type: plan, implement, test, verify, deploy, custom",
|
|
158
|
+
),
|
|
159
|
+
]
|
|
160
|
+
phase_order: Annotated[int, Field(default=0, description="Execution order")]
|
|
161
|
+
prompt_template: Annotated[
|
|
162
|
+
str | None,
|
|
163
|
+
Field(default=None, description="Prompt template with variables"),
|
|
164
|
+
]
|
|
165
|
+
config_ids: Annotated[
|
|
166
|
+
list[str],
|
|
167
|
+
Field(default_factory=list, description="Config IDs to apply for this phase"),
|
|
168
|
+
]
|
|
169
|
+
required_for_completion: Annotated[
|
|
170
|
+
bool,
|
|
171
|
+
Field(default=True, description="Whether this phase must complete for workflow success"),
|
|
172
|
+
]
|
|
173
|
+
can_skip_on_failure: Annotated[
|
|
174
|
+
bool,
|
|
175
|
+
Field(default=False, description="Whether to skip this phase if it fails"),
|
|
176
|
+
]
|
|
177
|
+
entry_conditions: Annotated[
|
|
178
|
+
dict,
|
|
179
|
+
Field(default_factory=dict, description="Conditions to enter phase"),
|
|
180
|
+
]
|
|
181
|
+
exit_conditions: Annotated[
|
|
182
|
+
dict,
|
|
183
|
+
Field(default_factory=dict, description="Conditions to exit phase"),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
def render_prompt(
|
|
187
|
+
self,
|
|
188
|
+
task_title: str = "",
|
|
189
|
+
task_description: str = "",
|
|
190
|
+
context: dict | None = None,
|
|
191
|
+
) -> str | None:
|
|
192
|
+
"""Render prompt template with variables."""
|
|
193
|
+
if not self.prompt_template:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
prompt = self.prompt_template
|
|
197
|
+
prompt = prompt.replace("{task_title}", task_title)
|
|
198
|
+
prompt = prompt.replace("{task_description}", task_description)
|
|
199
|
+
|
|
200
|
+
if context:
|
|
201
|
+
prompt = prompt.replace("{context}", str(context))
|
|
202
|
+
|
|
203
|
+
return prompt
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class WorkflowConfig(BaseModel):
|
|
207
|
+
"""Workflow configuration from platform."""
|
|
208
|
+
|
|
209
|
+
id: Annotated[str, Field(description="Workflow ID")]
|
|
210
|
+
name: Annotated[str, Field(description="Workflow name")]
|
|
211
|
+
description: Annotated[str | None, Field(default=None, description="Workflow description")]
|
|
212
|
+
is_default: Annotated[
|
|
213
|
+
bool, Field(default=False, description="Whether this is the default workflow")
|
|
214
|
+
]
|
|
215
|
+
max_retries_per_phase: Annotated[int, Field(default=3, description="Max retries per phase")]
|
|
216
|
+
phase_timeout_seconds: Annotated[
|
|
217
|
+
int, Field(default=3600, description="Phase timeout in seconds")
|
|
218
|
+
]
|
|
219
|
+
phases: Annotated[
|
|
220
|
+
list[WorkflowPhaseConfig],
|
|
221
|
+
Field(default_factory=list, description="Ordered list of phases"),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def from_api_response(cls, data: dict) -> "WorkflowConfig":
|
|
226
|
+
"""Create from API response."""
|
|
227
|
+
return cls(
|
|
228
|
+
id=data["id"],
|
|
229
|
+
name=data["name"],
|
|
230
|
+
description=data.get("description"),
|
|
231
|
+
is_default=data.get("is_default", False),
|
|
232
|
+
max_retries_per_phase=data.get("max_retries_per_phase", 3),
|
|
233
|
+
phase_timeout_seconds=data.get("phase_timeout_seconds", 3600),
|
|
234
|
+
phases=[
|
|
235
|
+
WorkflowPhaseConfig(
|
|
236
|
+
id=p["id"],
|
|
237
|
+
name=p["name"],
|
|
238
|
+
phase_type=p.get("phase_type", "custom"),
|
|
239
|
+
phase_order=p.get("phase_order", 0),
|
|
240
|
+
prompt_template=p.get("prompt_template"),
|
|
241
|
+
config_ids=p.get("config_ids", []),
|
|
242
|
+
required_for_completion=p.get("required_for_completion", True),
|
|
243
|
+
can_skip_on_failure=p.get("can_skip_on_failure", False),
|
|
244
|
+
entry_conditions=p.get("entry_conditions", {}),
|
|
245
|
+
exit_conditions=p.get("exit_conditions", {}),
|
|
246
|
+
)
|
|
247
|
+
for p in data.get("phases", [])
|
|
248
|
+
],
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def get_phase_by_order(self, order: int) -> WorkflowPhaseConfig | None:
|
|
252
|
+
"""Get phase by order number."""
|
|
253
|
+
for phase in self.phases:
|
|
254
|
+
if phase.phase_order == order:
|
|
255
|
+
return phase
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
def get_phase_by_id(self, phase_id: str) -> WorkflowPhaseConfig | None:
|
|
259
|
+
"""Get phase by ID."""
|
|
260
|
+
for phase in self.phases:
|
|
261
|
+
if phase.id == phase_id:
|
|
262
|
+
return phase
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def get_next_phase(self, current_phase_id: str) -> WorkflowPhaseConfig | None:
|
|
266
|
+
"""Get the next phase after the current one."""
|
|
267
|
+
current_phase = self.get_phase_by_id(current_phase_id)
|
|
268
|
+
if not current_phase:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
next_order = current_phase.phase_order + 1
|
|
272
|
+
return self.get_phase_by_order(next_order)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Environment settings management for steerdev."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
from loguru import logger
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Settings(BaseSettings):
|
|
13
|
+
"""Application settings loaded from environment variables."""
|
|
14
|
+
|
|
15
|
+
model_config = SettingsConfigDict(
|
|
16
|
+
env_prefix="STEERDEV_",
|
|
17
|
+
env_file=".env",
|
|
18
|
+
env_file_encoding="utf-8",
|
|
19
|
+
extra="ignore",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# API Settings
|
|
23
|
+
api_key: Annotated[
|
|
24
|
+
str | None,
|
|
25
|
+
Field(default=None, description="API key for steerdev.com"),
|
|
26
|
+
]
|
|
27
|
+
api_endpoint: Annotated[
|
|
28
|
+
str,
|
|
29
|
+
Field(
|
|
30
|
+
default="https://steerdev.com/api/activity-report",
|
|
31
|
+
description="API endpoint for activity reports",
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
def get_env_value(self, env_var_name: str) -> str | None:
|
|
36
|
+
"""Get a value from environment by variable name.
|
|
37
|
+
|
|
38
|
+
This allows config to reference environment variables by name
|
|
39
|
+
rather than hardcoding values.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
env_var_name: Name of the environment variable.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The value if set, None otherwise.
|
|
46
|
+
"""
|
|
47
|
+
return os.environ.get(env_var_name)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_settings() -> Settings:
|
|
51
|
+
"""Get cached settings instance.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Singleton Settings instance.
|
|
55
|
+
"""
|
|
56
|
+
from dotenv import load_dotenv
|
|
57
|
+
|
|
58
|
+
logger.debug(f"Loading environment variables from {Path.cwd() / '.env'}")
|
|
59
|
+
loaded_envs = load_dotenv(Path.cwd() / ".env", override=True)
|
|
60
|
+
logger.debug(f"Environment variables loaded: {loaded_envs}")
|
|
61
|
+
|
|
62
|
+
return Settings()
|