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,294 @@
|
|
|
1
|
+
"""Events streaming API client with batching support."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from steerdev_agent.api.client import get_api_endpoint, get_api_key
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EventData(BaseModel):
|
|
16
|
+
"""Model for a single event to be sent to the API."""
|
|
17
|
+
|
|
18
|
+
event_type: str = Field(description="Type of event (e.g., assistant, user, system, tool_use)")
|
|
19
|
+
event_timestamp: str = Field(description="ISO timestamp of when the event occurred")
|
|
20
|
+
data: dict[str, Any] = Field(description="Structured event data")
|
|
21
|
+
raw_json: str = Field(description="Original raw JSON from the agent")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EventBatchRequest(BaseModel):
|
|
25
|
+
"""Request model for batch inserting events."""
|
|
26
|
+
|
|
27
|
+
events: list[EventData] = Field(description="List of events to insert")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EventResponse(BaseModel):
|
|
31
|
+
"""Response model for an event."""
|
|
32
|
+
|
|
33
|
+
id: str
|
|
34
|
+
session_id: str
|
|
35
|
+
event_type: str
|
|
36
|
+
event_timestamp: str
|
|
37
|
+
data: dict[str, Any]
|
|
38
|
+
raw_json: str
|
|
39
|
+
created_at: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EventListResponse(BaseModel):
|
|
43
|
+
"""Response model for listing events."""
|
|
44
|
+
|
|
45
|
+
events: list[EventResponse]
|
|
46
|
+
total: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EventsClient:
|
|
50
|
+
"""Async HTTP client for Events API with batching support.
|
|
51
|
+
|
|
52
|
+
Buffers events and sends them in batches to reduce API calls.
|
|
53
|
+
Configurable batch size and flush interval.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
session_id: str,
|
|
59
|
+
api_key: str | None = None,
|
|
60
|
+
timeout: float = 30.0,
|
|
61
|
+
batch_size: int = 10,
|
|
62
|
+
flush_interval_seconds: float = 5.0,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Initialize the client.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
session_id: Session ID to associate events with.
|
|
68
|
+
api_key: API key for authentication. If not provided, reads from STEERDEV_API_KEY.
|
|
69
|
+
timeout: Request timeout in seconds.
|
|
70
|
+
batch_size: Number of events to buffer before auto-flushing.
|
|
71
|
+
flush_interval_seconds: Maximum time to wait before flushing events.
|
|
72
|
+
"""
|
|
73
|
+
self.session_id = session_id
|
|
74
|
+
self.api_key = api_key or get_api_key()
|
|
75
|
+
self.api_base = get_api_endpoint()
|
|
76
|
+
self.timeout = timeout
|
|
77
|
+
self.batch_size = batch_size
|
|
78
|
+
self.flush_interval_seconds = flush_interval_seconds
|
|
79
|
+
|
|
80
|
+
self._client: httpx.AsyncClient | None = None
|
|
81
|
+
self._buffer: list[EventData] = []
|
|
82
|
+
self._buffer_lock = asyncio.Lock()
|
|
83
|
+
self._flush_task: asyncio.Task[None] | None = None
|
|
84
|
+
self._running = False
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def headers(self) -> dict[str, str]:
|
|
88
|
+
"""Get request headers with authentication."""
|
|
89
|
+
return {
|
|
90
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
95
|
+
"""Get or create async HTTP client."""
|
|
96
|
+
if self._client is None:
|
|
97
|
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
|
98
|
+
return self._client
|
|
99
|
+
|
|
100
|
+
async def start(self) -> None:
|
|
101
|
+
"""Start the background flush task."""
|
|
102
|
+
if self._running:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
self._running = True
|
|
106
|
+
self._flush_task = asyncio.create_task(self._flush_loop())
|
|
107
|
+
logger.debug(f"EventsClient started for session {self.session_id}")
|
|
108
|
+
|
|
109
|
+
async def stop(self) -> None:
|
|
110
|
+
"""Stop the background flush task and flush remaining events."""
|
|
111
|
+
self._running = False
|
|
112
|
+
|
|
113
|
+
if self._flush_task:
|
|
114
|
+
self._flush_task.cancel()
|
|
115
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
116
|
+
await self._flush_task
|
|
117
|
+
self._flush_task = None
|
|
118
|
+
|
|
119
|
+
# Final flush
|
|
120
|
+
await self.flush()
|
|
121
|
+
logger.debug(f"EventsClient stopped for session {self.session_id}")
|
|
122
|
+
|
|
123
|
+
async def close(self) -> None:
|
|
124
|
+
"""Stop and close the HTTP client."""
|
|
125
|
+
await self.stop()
|
|
126
|
+
if self._client is not None:
|
|
127
|
+
await self._client.aclose()
|
|
128
|
+
self._client = None
|
|
129
|
+
|
|
130
|
+
async def __aenter__(self) -> Self:
|
|
131
|
+
"""Enter async context manager."""
|
|
132
|
+
await self.start()
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
136
|
+
"""Exit async context manager."""
|
|
137
|
+
await self.close()
|
|
138
|
+
|
|
139
|
+
async def _flush_loop(self) -> None:
|
|
140
|
+
"""Background task that periodically flushes events."""
|
|
141
|
+
while self._running:
|
|
142
|
+
try:
|
|
143
|
+
await asyncio.sleep(self.flush_interval_seconds)
|
|
144
|
+
await self.flush()
|
|
145
|
+
except asyncio.CancelledError:
|
|
146
|
+
break
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Error in flush loop: {e}")
|
|
149
|
+
|
|
150
|
+
async def add_event(
|
|
151
|
+
self,
|
|
152
|
+
event_type: str,
|
|
153
|
+
data: dict[str, Any],
|
|
154
|
+
raw_json: str,
|
|
155
|
+
timestamp: datetime | None = None,
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Add an event to the buffer.
|
|
158
|
+
|
|
159
|
+
Events are automatically flushed when the batch size is reached
|
|
160
|
+
or when the flush interval expires.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
event_type: Type of event (e.g., assistant, user, system).
|
|
164
|
+
data: Structured event data.
|
|
165
|
+
raw_json: Original raw JSON string.
|
|
166
|
+
timestamp: Event timestamp. Defaults to now.
|
|
167
|
+
"""
|
|
168
|
+
if timestamp is None:
|
|
169
|
+
timestamp = datetime.now(UTC)
|
|
170
|
+
|
|
171
|
+
event = EventData(
|
|
172
|
+
event_type=event_type,
|
|
173
|
+
event_timestamp=timestamp.isoformat(),
|
|
174
|
+
data=data,
|
|
175
|
+
raw_json=raw_json,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async with self._buffer_lock:
|
|
179
|
+
self._buffer.append(event)
|
|
180
|
+
buffer_size = len(self._buffer)
|
|
181
|
+
|
|
182
|
+
# Auto-flush if batch size reached
|
|
183
|
+
if buffer_size >= self.batch_size:
|
|
184
|
+
await self.flush()
|
|
185
|
+
|
|
186
|
+
async def flush(self) -> bool:
|
|
187
|
+
"""Flush buffered events to the API.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
True if flush succeeded or no events to flush, False on error.
|
|
191
|
+
"""
|
|
192
|
+
async with self._buffer_lock:
|
|
193
|
+
if not self._buffer:
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
events_to_send = self._buffer.copy()
|
|
197
|
+
self._buffer.clear()
|
|
198
|
+
|
|
199
|
+
logger.debug(f"Flushing {len(events_to_send)} events for session {self.session_id}")
|
|
200
|
+
|
|
201
|
+
client = await self._get_client()
|
|
202
|
+
request = EventBatchRequest(events=events_to_send)
|
|
203
|
+
url = f"{self.api_base}/sessions/{self.session_id}/events"
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
logger.debug(f"POST {url}")
|
|
207
|
+
response = await client.post(
|
|
208
|
+
url,
|
|
209
|
+
headers=self.headers,
|
|
210
|
+
json=request.model_dump(),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if response.status_code in (200, 201):
|
|
214
|
+
logger.debug(f"Successfully flushed {len(events_to_send)} events")
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
logger.error(f"Failed to flush events to {url}: {response.status_code} - {response.text[:200]}")
|
|
218
|
+
# Re-add events to buffer on failure
|
|
219
|
+
async with self._buffer_lock:
|
|
220
|
+
self._buffer = events_to_send + self._buffer
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
except httpx.RequestError as e:
|
|
224
|
+
logger.error(f"Request error flushing events to {url}: {e}")
|
|
225
|
+
# Re-add events to buffer on failure
|
|
226
|
+
async with self._buffer_lock:
|
|
227
|
+
self._buffer = events_to_send + self._buffer
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
async def get_events(
|
|
231
|
+
self,
|
|
232
|
+
limit: int = 100,
|
|
233
|
+
offset: int = 0,
|
|
234
|
+
) -> EventListResponse | None:
|
|
235
|
+
"""Get events for the session (for debugging).
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
limit: Maximum number of events to return.
|
|
239
|
+
offset: Offset for pagination.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Event list response or None on failure.
|
|
243
|
+
"""
|
|
244
|
+
client = await self._get_client()
|
|
245
|
+
params: dict[str, int] = {"limit": limit, "offset": offset}
|
|
246
|
+
|
|
247
|
+
logger.debug(f"Getting events for session {self.session_id}")
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
response = await client.get(
|
|
251
|
+
f"{self.api_base}/sessions/{self.session_id}/events",
|
|
252
|
+
headers=self.headers,
|
|
253
|
+
params=params,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if response.status_code == 200:
|
|
257
|
+
return EventListResponse(**response.json())
|
|
258
|
+
|
|
259
|
+
logger.error(f"Failed to get events: {response.status_code} - {response.text}")
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
except httpx.RequestError as e:
|
|
263
|
+
logger.error(f"Request error getting events: {e}")
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def pending_events(self) -> int:
|
|
268
|
+
"""Return the number of events pending in the buffer."""
|
|
269
|
+
return len(self._buffer)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def create_event_from_stream(
|
|
273
|
+
event_type: str,
|
|
274
|
+
message_data: dict[str, Any],
|
|
275
|
+
raw_json: str,
|
|
276
|
+
) -> EventData:
|
|
277
|
+
"""Create an EventData from stream output.
|
|
278
|
+
|
|
279
|
+
Helper function to create events from Claude's stream-json output.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
event_type: The type from Claude's stream (assistant, user, system, etc.).
|
|
283
|
+
message_data: The parsed message data.
|
|
284
|
+
raw_json: The original raw JSON line.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
EventData ready to be added to the client.
|
|
288
|
+
"""
|
|
289
|
+
return EventData(
|
|
290
|
+
event_type=event_type,
|
|
291
|
+
event_timestamp=datetime.now(UTC).isoformat(),
|
|
292
|
+
data=message_data,
|
|
293
|
+
raw_json=raw_json,
|
|
294
|
+
)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Claude Code hooks API client for activity reporting."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from steerdev_agent.api.client import get_agent_name, get_api_key
|
|
12
|
+
|
|
13
|
+
# Default activity API endpoint
|
|
14
|
+
DEFAULT_ACTIVITY_ENDPOINT = "https://steerdev.com/api/v1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_activity_endpoint() -> str:
|
|
18
|
+
"""Get activity API endpoint from environment or use default."""
|
|
19
|
+
return os.environ.get("STEERDEV_API_ENDPOINT", DEFAULT_ACTIVITY_ENDPOINT)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HooksClient:
|
|
23
|
+
"""Client for reporting hook events to SteerDev activity API.
|
|
24
|
+
|
|
25
|
+
Hooks read JSON input from stdin (provided by Claude Code) and
|
|
26
|
+
report activity events to the SteerDev platform.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, timeout: float = 5.0) -> None:
|
|
30
|
+
"""Initialize the hooks client.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
timeout: Request timeout in seconds (short for hooks).
|
|
34
|
+
"""
|
|
35
|
+
self.api_key = get_api_key()
|
|
36
|
+
self.api_endpoint = get_activity_endpoint()
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
self.agent_name = get_agent_name()
|
|
39
|
+
|
|
40
|
+
def read_stdin_input(self) -> dict[str, Any] | None:
|
|
41
|
+
"""Read JSON input from stdin.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Parsed JSON dict or None on failure.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
return json.load(sys.stdin)
|
|
48
|
+
except json.JSONDecodeError:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def report_event(
|
|
52
|
+
self,
|
|
53
|
+
event_type: str,
|
|
54
|
+
input_data: dict[str, Any],
|
|
55
|
+
extra_metadata: dict[str, Any] | None = None,
|
|
56
|
+
) -> bool:
|
|
57
|
+
"""Report an activity event to the SteerDev API.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
event_type: Type of event (e.g., "session_start", "agent_stopped").
|
|
61
|
+
input_data: Input data from Claude Code hook.
|
|
62
|
+
extra_metadata: Additional metadata to include.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if the event was reported successfully.
|
|
66
|
+
"""
|
|
67
|
+
if not self.api_key:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
# Build base metadata
|
|
71
|
+
metadata: dict[str, Any] = {
|
|
72
|
+
"cwd": input_data.get("cwd"),
|
|
73
|
+
"transcript_path": input_data.get("transcript_path"),
|
|
74
|
+
"permission_mode": input_data.get("permission_mode"),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Add extra metadata
|
|
78
|
+
if extra_metadata:
|
|
79
|
+
metadata.update(extra_metadata)
|
|
80
|
+
|
|
81
|
+
# Build event payload
|
|
82
|
+
event = {
|
|
83
|
+
"event_type": event_type,
|
|
84
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
85
|
+
"session_name": input_data.get("session_id"),
|
|
86
|
+
"metadata": metadata,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Build request body
|
|
90
|
+
request_body = {
|
|
91
|
+
"agent_name": self.agent_name,
|
|
92
|
+
"application": "claude_code",
|
|
93
|
+
"events": [event],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Send to API (non-blocking)
|
|
97
|
+
try:
|
|
98
|
+
httpx.post(
|
|
99
|
+
f"{self.api_endpoint}/activity/report",
|
|
100
|
+
json=request_body,
|
|
101
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
102
|
+
timeout=self.timeout,
|
|
103
|
+
)
|
|
104
|
+
return True
|
|
105
|
+
except Exception:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
def session_start(self) -> None:
|
|
109
|
+
"""Process SessionStart hook event.
|
|
110
|
+
|
|
111
|
+
Reads JSON from stdin and reports session_start event.
|
|
112
|
+
"""
|
|
113
|
+
input_data = self.read_stdin_input()
|
|
114
|
+
if not input_data:
|
|
115
|
+
sys.exit(0)
|
|
116
|
+
|
|
117
|
+
self.report_event(
|
|
118
|
+
event_type="session_start",
|
|
119
|
+
input_data=input_data,
|
|
120
|
+
extra_metadata={
|
|
121
|
+
"source": input_data.get("source"), # startup, resume, clear, compact
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
sys.exit(0)
|
|
125
|
+
|
|
126
|
+
def session_end(self) -> None:
|
|
127
|
+
"""Process SessionEnd hook event.
|
|
128
|
+
|
|
129
|
+
Reads JSON from stdin and reports session_end event.
|
|
130
|
+
"""
|
|
131
|
+
input_data = self.read_stdin_input()
|
|
132
|
+
if not input_data:
|
|
133
|
+
sys.exit(0)
|
|
134
|
+
|
|
135
|
+
self.report_event(
|
|
136
|
+
event_type="session_end",
|
|
137
|
+
input_data=input_data,
|
|
138
|
+
extra_metadata={
|
|
139
|
+
"reason": input_data.get("reason"), # clear, logout, prompt_input_exit, other
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
sys.exit(0)
|
|
143
|
+
|
|
144
|
+
def agent_stop(self) -> None:
|
|
145
|
+
"""Process Stop hook event (main agent).
|
|
146
|
+
|
|
147
|
+
Reads JSON from stdin and reports agent_stopped event.
|
|
148
|
+
"""
|
|
149
|
+
input_data = self.read_stdin_input()
|
|
150
|
+
if not input_data:
|
|
151
|
+
sys.exit(0)
|
|
152
|
+
|
|
153
|
+
self.report_event(
|
|
154
|
+
event_type="agent_stopped",
|
|
155
|
+
input_data=input_data,
|
|
156
|
+
extra_metadata={
|
|
157
|
+
"stop_hook_active": input_data.get("stop_hook_active"),
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
sys.exit(0)
|
|
161
|
+
|
|
162
|
+
def subagent_stop(self) -> None:
|
|
163
|
+
"""Process SubagentStop hook event.
|
|
164
|
+
|
|
165
|
+
Reads JSON from stdin and reports subagent_stopped event.
|
|
166
|
+
"""
|
|
167
|
+
input_data = self.read_stdin_input()
|
|
168
|
+
if not input_data:
|
|
169
|
+
sys.exit(0)
|
|
170
|
+
|
|
171
|
+
self.report_event(
|
|
172
|
+
event_type="subagent_stopped",
|
|
173
|
+
input_data=input_data,
|
|
174
|
+
extra_metadata={
|
|
175
|
+
"stop_hook_active": input_data.get("stop_hook_active"),
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
sys.exit(0)
|