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.
Files changed (57) hide show
  1. steerdev-0.4.27.dist-info/METADATA +224 -0
  2. steerdev-0.4.27.dist-info/RECORD +57 -0
  3. steerdev-0.4.27.dist-info/WHEEL +4 -0
  4. steerdev-0.4.27.dist-info/entry_points.txt +2 -0
  5. steerdev_agent/__init__.py +10 -0
  6. steerdev_agent/api/__init__.py +32 -0
  7. steerdev_agent/api/activity.py +278 -0
  8. steerdev_agent/api/agents.py +145 -0
  9. steerdev_agent/api/client.py +158 -0
  10. steerdev_agent/api/commands.py +399 -0
  11. steerdev_agent/api/configs.py +238 -0
  12. steerdev_agent/api/context.py +306 -0
  13. steerdev_agent/api/events.py +294 -0
  14. steerdev_agent/api/hooks.py +178 -0
  15. steerdev_agent/api/implementation_plan.py +408 -0
  16. steerdev_agent/api/messages.py +231 -0
  17. steerdev_agent/api/prd.py +281 -0
  18. steerdev_agent/api/runs.py +526 -0
  19. steerdev_agent/api/sessions.py +403 -0
  20. steerdev_agent/api/specs.py +321 -0
  21. steerdev_agent/api/tasks.py +659 -0
  22. steerdev_agent/api/workflow_runs.py +351 -0
  23. steerdev_agent/api/workflows.py +191 -0
  24. steerdev_agent/cli.py +2254 -0
  25. steerdev_agent/config/__init__.py +19 -0
  26. steerdev_agent/config/models.py +236 -0
  27. steerdev_agent/config/platform.py +272 -0
  28. steerdev_agent/config/settings.py +62 -0
  29. steerdev_agent/daemon.py +675 -0
  30. steerdev_agent/executor/__init__.py +64 -0
  31. steerdev_agent/executor/base.py +121 -0
  32. steerdev_agent/executor/claude.py +328 -0
  33. steerdev_agent/executor/stream.py +163 -0
  34. steerdev_agent/git/__init__.py +1 -0
  35. steerdev_agent/handlers/__init__.py +5 -0
  36. steerdev_agent/handlers/prd.py +533 -0
  37. steerdev_agent/integration.py +334 -0
  38. steerdev_agent/prompt/__init__.py +10 -0
  39. steerdev_agent/prompt/builder.py +263 -0
  40. steerdev_agent/prompt/templates.py +422 -0
  41. steerdev_agent/py.typed +0 -0
  42. steerdev_agent/runner.py +829 -0
  43. steerdev_agent/setup/__init__.py +5 -0
  44. steerdev_agent/setup/claude_setup.py +560 -0
  45. steerdev_agent/setup/templates/claude_md_section.md +140 -0
  46. steerdev_agent/setup/templates/settings.json +69 -0
  47. steerdev_agent/setup/templates/skills/activity/SKILL.md +160 -0
  48. steerdev_agent/setup/templates/skills/context/SKILL.md +122 -0
  49. steerdev_agent/setup/templates/skills/git-workflow/SKILL.md +218 -0
  50. steerdev_agent/setup/templates/skills/progress-logging/SKILL.md +211 -0
  51. steerdev_agent/setup/templates/skills/specs-management/SKILL.md +161 -0
  52. steerdev_agent/setup/templates/skills/task-management/SKILL.md +343 -0
  53. steerdev_agent/setup/templates/steerdev.yaml +51 -0
  54. steerdev_agent/version.py +149 -0
  55. steerdev_agent/workflow/__init__.py +10 -0
  56. steerdev_agent/workflow/executor.py +494 -0
  57. steerdev_agent/workflow/memory.py +185 -0
@@ -0,0 +1,403 @@
1
+ """Sessions management API client for agent execution sessions."""
2
+
3
+ from typing import Any, Literal, Self
4
+
5
+ import httpx
6
+ from loguru import logger
7
+ from pydantic import BaseModel, Field
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ from steerdev_agent.api.client import get_api_endpoint, get_api_key
13
+
14
+ console = Console()
15
+
16
+ SessionStatus = Literal["pending", "running", "completed", "failed", "cancelled"]
17
+
18
+ # Status display styles
19
+ STATUS_STYLES: dict[str, str] = {
20
+ "pending": "dim",
21
+ "running": "yellow",
22
+ "completed": "green",
23
+ "failed": "red",
24
+ "cancelled": "dim",
25
+ }
26
+
27
+
28
+ class SessionCreateRequest(BaseModel):
29
+ """Request model for creating a session."""
30
+
31
+ project_id: str = Field(description="Project ID")
32
+ task_id: str | None = Field(default=None, description="Task being executed")
33
+ agent_id: str | None = Field(default=None, description="Existing agent ID to link to")
34
+ agent_name: str | None = Field(default=None, description="Agent name (creates agent if needed)")
35
+ agent_type: str = Field(description="Agent type (claude, codex, aider)")
36
+ prompt: str = Field(description="Initial prompt sent to agent")
37
+ working_directory: str = Field(description="Working directory for execution")
38
+ metadata: dict[str, Any] | None = Field(default=None, description="Additional metadata")
39
+
40
+
41
+ class SessionResponse(BaseModel):
42
+ """Response model for session data."""
43
+
44
+ id: str
45
+ project_id: str
46
+ task_id: str | None = None
47
+ agent_type: str
48
+ agent_session_id: str | None = None # Native session ID for resume
49
+ status: SessionStatus
50
+ prompt: str
51
+ working_directory: str
52
+ created_at: str
53
+ updated_at: str
54
+ metadata: dict[str, Any] | None = None
55
+
56
+
57
+ class SessionListResponse(BaseModel):
58
+ """Response model for listing sessions."""
59
+
60
+ sessions: list[SessionResponse]
61
+ total: int
62
+ limit: int
63
+ offset: int
64
+
65
+
66
+ class SessionUpdateRequest(BaseModel):
67
+ """Request model for updating a session."""
68
+
69
+ status: SessionStatus | None = None
70
+ agent_session_id: str | None = None
71
+ metadata: dict[str, Any] | None = None
72
+
73
+
74
+ class SessionsClient:
75
+ """Async HTTP client for Sessions API.
76
+
77
+ Manages session lifecycle: create, get, update, list.
78
+ Sessions track agent execution and store the native session ID for resume.
79
+ """
80
+
81
+ def __init__(self, api_key: str | None = None, timeout: float = 30.0) -> None:
82
+ """Initialize the client.
83
+
84
+ Args:
85
+ api_key: API key for authentication. If not provided, reads from STEERDEV_API_KEY.
86
+ timeout: Request timeout in seconds.
87
+ """
88
+ self.api_key = api_key or get_api_key()
89
+ self.api_base = get_api_endpoint()
90
+ self.timeout = timeout
91
+ self._client: httpx.AsyncClient | None = None
92
+
93
+ @property
94
+ def headers(self) -> dict[str, str]:
95
+ """Get request headers with authentication."""
96
+ return {
97
+ "Authorization": f"Bearer {self.api_key}",
98
+ "Content-Type": "application/json",
99
+ }
100
+
101
+ async def _get_client(self) -> httpx.AsyncClient:
102
+ """Get or create async HTTP client."""
103
+ if self._client is None:
104
+ self._client = httpx.AsyncClient(timeout=self.timeout)
105
+ return self._client
106
+
107
+ async def close(self) -> None:
108
+ """Close the HTTP client."""
109
+ if self._client is not None:
110
+ await self._client.aclose()
111
+ self._client = None
112
+
113
+ async def __aenter__(self) -> Self:
114
+ """Enter async context manager."""
115
+ return self
116
+
117
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
118
+ """Exit async context manager."""
119
+ await self.close()
120
+
121
+ async def create_session(self, request: SessionCreateRequest) -> SessionResponse | None:
122
+ """Create a new session.
123
+
124
+ Args:
125
+ request: Session creation request.
126
+
127
+ Returns:
128
+ Created session data or None on failure.
129
+ """
130
+ client = await self._get_client()
131
+ logger.debug(f"Creating session at {self.api_base}/sessions")
132
+
133
+ try:
134
+ response = await client.post(
135
+ f"{self.api_base}/sessions",
136
+ headers=self.headers,
137
+ json=request.model_dump(exclude_none=True),
138
+ )
139
+
140
+ if response.status_code == 201:
141
+ return SessionResponse(**response.json())
142
+
143
+ logger.error(f"Failed to create session: {response.status_code} - {response.text}")
144
+ return None
145
+
146
+ except httpx.RequestError as e:
147
+ logger.error(f"Request error creating session: {e}")
148
+ return None
149
+
150
+ async def get_session(self, session_id: str) -> SessionResponse | None:
151
+ """Get a specific session by ID.
152
+
153
+ Args:
154
+ session_id: Session ID (UUID) to fetch.
155
+
156
+ Returns:
157
+ Session data or None if not found.
158
+ """
159
+ client = await self._get_client()
160
+ logger.debug(f"Fetching session {session_id}")
161
+
162
+ try:
163
+ response = await client.get(
164
+ f"{self.api_base}/sessions/{session_id}",
165
+ headers=self.headers,
166
+ )
167
+
168
+ if response.status_code == 200:
169
+ return SessionResponse(**response.json())
170
+ if response.status_code == 404:
171
+ return None
172
+
173
+ logger.error(f"Failed to get session: {response.status_code} - {response.text}")
174
+ return None
175
+
176
+ except httpx.RequestError as e:
177
+ logger.error(f"Request error getting session: {e}")
178
+ return None
179
+
180
+ async def update_session(
181
+ self,
182
+ session_id: str,
183
+ status: SessionStatus | None = None,
184
+ agent_session_id: str | None = None,
185
+ metadata: dict[str, Any] | None = None,
186
+ ) -> SessionResponse | None:
187
+ """Update a session.
188
+
189
+ Args:
190
+ session_id: Session ID to update.
191
+ status: New session status.
192
+ agent_session_id: Native agent session ID for resume.
193
+ metadata: Additional metadata to merge.
194
+
195
+ Returns:
196
+ Updated session data or None on failure.
197
+ """
198
+ client = await self._get_client()
199
+ logger.debug(f"Updating session {session_id}")
200
+
201
+ payload: dict[str, Any] = {}
202
+ if status is not None:
203
+ payload["status"] = status
204
+ if agent_session_id is not None:
205
+ payload["agent_session_id"] = agent_session_id
206
+ if metadata is not None:
207
+ payload["metadata"] = metadata
208
+
209
+ if not payload:
210
+ logger.warning("No fields to update")
211
+ return await self.get_session(session_id)
212
+
213
+ try:
214
+ response = await client.patch(
215
+ f"{self.api_base}/sessions/{session_id}",
216
+ headers=self.headers,
217
+ json=payload,
218
+ )
219
+
220
+ if response.status_code == 200:
221
+ return SessionResponse(**response.json())
222
+
223
+ if response.status_code == 404:
224
+ logger.warning(f"Session {session_id} not found")
225
+ return None
226
+
227
+ logger.error(f"Failed to update session: {response.status_code} - {response.text}")
228
+ return None
229
+
230
+ except httpx.RequestError as e:
231
+ logger.error(f"Request error updating session: {e}")
232
+ return None
233
+
234
+ async def list_sessions(
235
+ self,
236
+ project_id: str | None = None,
237
+ task_id: str | None = None,
238
+ status: SessionStatus | None = None,
239
+ limit: int = 50,
240
+ offset: int = 0,
241
+ ) -> SessionListResponse | None:
242
+ """List sessions with optional filters.
243
+
244
+ Args:
245
+ project_id: Filter by project ID.
246
+ task_id: Filter by task ID.
247
+ status: Filter by session status.
248
+ limit: Maximum number of sessions to return.
249
+ offset: Offset for pagination.
250
+
251
+ Returns:
252
+ Session list response or None on failure.
253
+ """
254
+ client = await self._get_client()
255
+ params: dict[str, str | int] = {"limit": limit, "offset": offset}
256
+ if project_id:
257
+ params["project_id"] = project_id
258
+ if task_id:
259
+ params["task_id"] = task_id
260
+ if status:
261
+ params["status"] = status
262
+
263
+ logger.debug(f"Listing sessions with params: {params}")
264
+
265
+ try:
266
+ response = await client.get(
267
+ f"{self.api_base}/sessions",
268
+ headers=self.headers,
269
+ params=params,
270
+ )
271
+
272
+ if response.status_code == 200:
273
+ return SessionListResponse(**response.json())
274
+
275
+ logger.error(f"Failed to list sessions: {response.status_code} - {response.text}")
276
+ return None
277
+
278
+ except httpx.RequestError as e:
279
+ logger.error(f"Request error listing sessions: {e}")
280
+ return None
281
+
282
+ async def mark_running(self, session_id: str) -> SessionResponse | None:
283
+ """Mark a session as running.
284
+
285
+ Args:
286
+ session_id: Session ID to update.
287
+
288
+ Returns:
289
+ Updated session data or None on failure.
290
+ """
291
+ return await self.update_session(session_id, status="running")
292
+
293
+ async def mark_completed(
294
+ self,
295
+ session_id: str,
296
+ agent_session_id: str | None = None,
297
+ ) -> SessionResponse | None:
298
+ """Mark a session as completed.
299
+
300
+ Args:
301
+ session_id: Session ID to update.
302
+ agent_session_id: Native agent session ID for resume.
303
+
304
+ Returns:
305
+ Updated session data or None on failure.
306
+ """
307
+ return await self.update_session(
308
+ session_id,
309
+ status="completed",
310
+ agent_session_id=agent_session_id,
311
+ )
312
+
313
+ async def mark_failed(
314
+ self,
315
+ session_id: str,
316
+ metadata: dict[str, Any] | None = None,
317
+ ) -> SessionResponse | None:
318
+ """Mark a session as failed.
319
+
320
+ Args:
321
+ session_id: Session ID to update.
322
+ metadata: Error details to store.
323
+
324
+ Returns:
325
+ Updated session data or None on failure.
326
+ """
327
+ return await self.update_session(session_id, status="failed", metadata=metadata)
328
+
329
+
330
+ def display_session(session: SessionResponse, title: str = "Session") -> None:
331
+ """Display a session in a formatted panel.
332
+
333
+ Args:
334
+ session: Session data.
335
+ title: Panel title.
336
+ """
337
+ status_style = STATUS_STYLES.get(session.status, "white")
338
+
339
+ session_info = (
340
+ f"[bold cyan]ID:[/bold cyan] {session.id}\n"
341
+ f"[bold cyan]Project ID:[/bold cyan] {session.project_id}\n"
342
+ f"[bold cyan]Agent Type:[/bold cyan] {session.agent_type}\n"
343
+ f"[bold cyan]Status:[/bold cyan] [{status_style}]{session.status}[/{status_style}]\n"
344
+ f"[bold cyan]Created:[/bold cyan] {session.created_at}\n"
345
+ )
346
+
347
+ if session.task_id:
348
+ session_info += f"[bold cyan]Task ID:[/bold cyan] {session.task_id}\n"
349
+
350
+ if session.agent_session_id:
351
+ session_info += f"[bold cyan]Agent Session ID:[/bold cyan] {session.agent_session_id}\n"
352
+
353
+ session_info += f"\n[bold cyan]Working Directory:[/bold cyan] {session.working_directory}"
354
+
355
+ if session.prompt:
356
+ # Truncate long prompts
357
+ prompt_display = (
358
+ session.prompt[:200] + "..." if len(session.prompt) > 200 else session.prompt
359
+ )
360
+ session_info += f"\n\n[bold cyan]Prompt:[/bold cyan]\n{prompt_display}"
361
+
362
+ console.print(Panel(session_info, title=title, border_style="green"))
363
+
364
+
365
+ def display_session_list(sessions: list[SessionResponse], full_ids: bool = True) -> None:
366
+ """Display a list of sessions in a formatted table.
367
+
368
+ Args:
369
+ sessions: List of session data.
370
+ full_ids: If True, show full UUIDs. If False, truncate.
371
+ """
372
+ if not sessions:
373
+ console.print("[yellow]No sessions found[/yellow]")
374
+ return
375
+
376
+ table = Table(title="Sessions")
377
+ table.add_column("ID", style="cyan", no_wrap=True)
378
+ table.add_column("Agent", style="white")
379
+ table.add_column("Status", style="magenta")
380
+ table.add_column("Task ID", style="yellow")
381
+ table.add_column("Created", style="green")
382
+
383
+ for session in sessions:
384
+ status_style = STATUS_STYLES.get(session.status, "white")
385
+
386
+ session_id = session.id
387
+ task_id = session.task_id or "-"
388
+ if not full_ids:
389
+ if len(session_id) > 8:
390
+ session_id = session_id[:8] + "..."
391
+ if task_id != "-" and len(task_id) > 8:
392
+ task_id = task_id[:8] + "..."
393
+
394
+ table.add_row(
395
+ session_id,
396
+ session.agent_type,
397
+ f"[{status_style}]{session.status}[/{status_style}]",
398
+ task_id,
399
+ session.created_at[:19] if session.created_at else "-",
400
+ )
401
+
402
+ console.print(table)
403
+ console.print(f"\n[dim]Total: {len(sessions)} sessions[/dim]")