devduck 0.1.0__py3-none-any.whl → 0.1.1766644714__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 devduck might be problematic. Click here for more details.
- devduck/__init__.py +1439 -483
- devduck/__main__.py +7 -0
- devduck/_version.py +34 -0
- devduck/agentcore_handler.py +76 -0
- devduck/test_redduck.py +0 -1
- devduck/tools/__init__.py +47 -0
- devduck/tools/_ambient_input.py +423 -0
- devduck/tools/_tray_app.py +530 -0
- devduck/tools/agentcore_agents.py +197 -0
- devduck/tools/agentcore_config.py +441 -0
- devduck/tools/agentcore_invoke.py +423 -0
- devduck/tools/agentcore_logs.py +320 -0
- devduck/tools/ambient.py +157 -0
- devduck/tools/create_subagent.py +659 -0
- devduck/tools/fetch_github_tool.py +201 -0
- devduck/tools/install_tools.py +409 -0
- devduck/tools/ipc.py +546 -0
- devduck/tools/mcp_server.py +600 -0
- devduck/tools/scraper.py +935 -0
- devduck/tools/speech_to_speech.py +850 -0
- devduck/tools/state_manager.py +292 -0
- devduck/tools/store_in_kb.py +187 -0
- devduck/tools/system_prompt.py +608 -0
- devduck/tools/tcp.py +263 -94
- devduck/tools/tray.py +247 -0
- devduck/tools/use_github.py +438 -0
- devduck/tools/websocket.py +498 -0
- devduck-0.1.1766644714.dist-info/METADATA +717 -0
- devduck-0.1.1766644714.dist-info/RECORD +33 -0
- {devduck-0.1.0.dist-info → devduck-0.1.1766644714.dist-info}/entry_points.txt +1 -0
- devduck-0.1.1766644714.dist-info/licenses/LICENSE +201 -0
- devduck/install.sh +0 -42
- devduck-0.1.0.dist-info/METADATA +0 -106
- devduck-0.1.0.dist-info/RECORD +0 -11
- devduck-0.1.0.dist-info/licenses/LICENSE +0 -21
- {devduck-0.1.0.dist-info → devduck-0.1.1766644714.dist-info}/WHEEL +0 -0
- {devduck-0.1.0.dist-info → devduck-0.1.1766644714.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"""Sub-agent creation and management tool for Strands Agents.
|
|
2
|
+
|
|
3
|
+
This module provides a tool for creating and managing sub-agents by triggering GitHub Actions
|
|
4
|
+
workflows. It enables task delegation and parallel processing by handing off tasks to
|
|
5
|
+
specialized agents running in separate GitHub Actions workflows.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
1. Create sub-agents by triggering GitHub Actions workflows
|
|
9
|
+
2. Pass context and tasks to sub-agents
|
|
10
|
+
3. Configure model selection (model ID, provider, max tokens) for granular control
|
|
11
|
+
4. Configure tool selection with comma-separated tool names
|
|
12
|
+
5. Prevent throttling by using different models for different sub-agents
|
|
13
|
+
6. Track sub-agent status and results
|
|
14
|
+
7. Set specific system prompts for sub-agents
|
|
15
|
+
|
|
16
|
+
Model Selection:
|
|
17
|
+
- Use us.anthropic.claude-sonnet-4-20250514-v1:0 for tasks
|
|
18
|
+
- Use us.anthropic.claude-opus-4-20250514-v1:0 for advanced reasoning tasks
|
|
19
|
+
- Specify provider (bedrock, anthropic, openai) and max_tokens for fine-tuned control
|
|
20
|
+
|
|
21
|
+
Tool Selection:
|
|
22
|
+
- Pass comma-separated tool names to control which tools the sub-agent has access to
|
|
23
|
+
- Available tools include: current_time,editor,environment,file_read,file_write,http_request,load_tool,
|
|
24
|
+
python_repl,retrieve,shell,slack,think,use_agent,calculator,use_aws,generate_image,image_reader,memory,swarm,workflow
|
|
25
|
+
- Examples: "file_read,file_write,shell" or "python_repl,calculator,http_request"
|
|
26
|
+
|
|
27
|
+
Usage Examples:
|
|
28
|
+
```python
|
|
29
|
+
from strands import Agent
|
|
30
|
+
from strands_action import create_subagent
|
|
31
|
+
|
|
32
|
+
agent = Agent(tools=[create_subagent])
|
|
33
|
+
|
|
34
|
+
# Create a sub-agent with specific model and tools for data analysis
|
|
35
|
+
result = agent.tool.create_subagent(
|
|
36
|
+
repository="owner/repo",
|
|
37
|
+
workflow_id="agent.yml",
|
|
38
|
+
task="Analyze this dataset and provide insights",
|
|
39
|
+
model="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
40
|
+
provider="bedrock",
|
|
41
|
+
max_tokens=60000,
|
|
42
|
+
tools="file_read,use_github,create_subagent,system_prompt,python_repl,calculator",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Create a sub-agent with powerful model for complex reasoning with limited tools
|
|
46
|
+
result = agent.tool.create_subagent(
|
|
47
|
+
repository="owner/repo",
|
|
48
|
+
workflow_id="agent.yml",
|
|
49
|
+
task="Complex multi-step analysis requiring deep reasoning",
|
|
50
|
+
model="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
51
|
+
provider="bedrock",
|
|
52
|
+
max_tokens=60000,
|
|
53
|
+
tools="think,use_agent,retrieve,use_github,scraper",
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import os
|
|
59
|
+
import re
|
|
60
|
+
from typing import Any
|
|
61
|
+
|
|
62
|
+
import requests
|
|
63
|
+
from strands import tool
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_github_token() -> str:
|
|
67
|
+
"""Get GitHub token from environment variable."""
|
|
68
|
+
return os.environ.get("GITHUB_TOKEN", "")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dispatch_workflow(
|
|
72
|
+
repository: str,
|
|
73
|
+
workflow_id: str,
|
|
74
|
+
inputs: dict[str, str],
|
|
75
|
+
token: str,
|
|
76
|
+
branch: str = "main",
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
"""Dispatch a GitHub Actions workflow with inputs.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
repository: The repository in format "owner/repo"
|
|
82
|
+
workflow_id: The workflow file name or ID
|
|
83
|
+
inputs: Dictionary of input parameters to pass to the workflow
|
|
84
|
+
token: GitHub token
|
|
85
|
+
branch: The branch to run the workflow on (default: "main")
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dictionary with success status and message
|
|
89
|
+
"""
|
|
90
|
+
# GitHub API endpoint for workflow dispatch
|
|
91
|
+
url = f"https://api.github.com/repos/{repository}/actions/workflows/{workflow_id}/dispatches"
|
|
92
|
+
|
|
93
|
+
headers = {
|
|
94
|
+
"Accept": "application/vnd.github+json",
|
|
95
|
+
"Authorization": f"Bearer {token}",
|
|
96
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Prepare payload
|
|
100
|
+
data = {"ref": branch, "inputs": inputs}
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
response = requests.post(url, headers=headers, json=data, timeout=30)
|
|
104
|
+
|
|
105
|
+
if response.status_code == 204:
|
|
106
|
+
return {
|
|
107
|
+
"success": True,
|
|
108
|
+
"message": f"Workflow {workflow_id} dispatched successfully",
|
|
109
|
+
}
|
|
110
|
+
else:
|
|
111
|
+
error_message = (
|
|
112
|
+
f"Failed to dispatch workflow: {response.status_code} - {response.text}"
|
|
113
|
+
)
|
|
114
|
+
return {"success": False, "message": error_message}
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return {"success": False, "message": f"Error dispatching workflow: {e!s}"}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _check_workflow_run_status(
|
|
120
|
+
repository: str,
|
|
121
|
+
run_id: str,
|
|
122
|
+
token: str,
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
"""Check the status of a specific workflow run.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
repository: The repository in format "owner/repo"
|
|
128
|
+
run_id: The run ID of the specific workflow execution
|
|
129
|
+
token: GitHub token
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Dictionary with status information
|
|
133
|
+
"""
|
|
134
|
+
# GitHub API endpoint for workflow run
|
|
135
|
+
url = f"https://api.github.com/repos/{repository}/actions/runs/{run_id}"
|
|
136
|
+
|
|
137
|
+
headers = {
|
|
138
|
+
"Accept": "application/vnd.github+json",
|
|
139
|
+
"Authorization": f"Bearer {token}",
|
|
140
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
145
|
+
|
|
146
|
+
if response.status_code == 200:
|
|
147
|
+
data = response.json()
|
|
148
|
+
return {
|
|
149
|
+
"success": True,
|
|
150
|
+
"status": data.get("status"),
|
|
151
|
+
"conclusion": data.get("conclusion"),
|
|
152
|
+
"html_url": data.get("html_url"),
|
|
153
|
+
"name": data.get("name"),
|
|
154
|
+
"workflow_id": data.get("workflow_id"),
|
|
155
|
+
"created_at": data.get("created_at"),
|
|
156
|
+
"updated_at": data.get("updated_at"),
|
|
157
|
+
}
|
|
158
|
+
else:
|
|
159
|
+
error_message = f"Failed to check workflow run status: {response.status_code} - {response.text}"
|
|
160
|
+
return {"success": False, "message": error_message}
|
|
161
|
+
except Exception as e:
|
|
162
|
+
return {"success": False, "message": f"Error checking workflow run: {e!s}"}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _get_workflow_run_logs(
|
|
166
|
+
repository: str,
|
|
167
|
+
run_id: str,
|
|
168
|
+
token: str,
|
|
169
|
+
) -> dict[str, Any]:
|
|
170
|
+
"""Get logs for a workflow run.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
repository: The repository in format "owner/repo"
|
|
174
|
+
run_id: The run ID of the specific workflow execution
|
|
175
|
+
token: GitHub token
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Dictionary with logs information
|
|
179
|
+
"""
|
|
180
|
+
# GitHub API endpoint for workflow run logs
|
|
181
|
+
url = f"https://api.github.com/repos/{repository}/actions/runs/{run_id}/logs"
|
|
182
|
+
|
|
183
|
+
headers = {
|
|
184
|
+
"Accept": "application/vnd.github+json",
|
|
185
|
+
"Authorization": f"Bearer {token}",
|
|
186
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# This endpoint returns a redirect to download the logs
|
|
191
|
+
response = requests.get(url, headers=headers, allow_redirects=False, timeout=30)
|
|
192
|
+
|
|
193
|
+
if response.status_code == 302:
|
|
194
|
+
download_url = response.headers.get("Location")
|
|
195
|
+
if download_url:
|
|
196
|
+
# Follow redirect to get logs
|
|
197
|
+
log_response = requests.get(download_url, timeout=30)
|
|
198
|
+
if log_response.status_code == 200:
|
|
199
|
+
return {"success": True, "logs": log_response.text}
|
|
200
|
+
else:
|
|
201
|
+
return {
|
|
202
|
+
"success": False,
|
|
203
|
+
"message": f"Failed to download logs: {log_response.status_code}",
|
|
204
|
+
}
|
|
205
|
+
else:
|
|
206
|
+
return {
|
|
207
|
+
"success": False,
|
|
208
|
+
"message": "No download URL found in response",
|
|
209
|
+
}
|
|
210
|
+
else:
|
|
211
|
+
error_message = f"Failed to get workflow run logs: {response.status_code} - {response.text}"
|
|
212
|
+
return {"success": False, "message": error_message}
|
|
213
|
+
except Exception as e:
|
|
214
|
+
return {"success": False, "message": f"Error getting workflow run logs: {e!s}"}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _parse_agent_responses_from_logs(logs: str) -> list[dict[str, str]]:
|
|
218
|
+
"""Parse agent responses from workflow logs.
|
|
219
|
+
|
|
220
|
+
This function extracts the actual agent responses from the log output
|
|
221
|
+
by looking for specially formatted output blocks.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
logs: The raw workflow logs text
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
List of dictionaries containing agent responses
|
|
228
|
+
"""
|
|
229
|
+
responses = []
|
|
230
|
+
|
|
231
|
+
# Look for patterns like "Event: <message>" and "python -c" command execution
|
|
232
|
+
# This is a simplified pattern that may need adjustment based on actual log format
|
|
233
|
+
pattern = r"Event:\s*(.*?)\n.*?python -c"
|
|
234
|
+
message_matches = re.findall(pattern, logs, re.DOTALL)
|
|
235
|
+
|
|
236
|
+
if message_matches:
|
|
237
|
+
for message in message_matches:
|
|
238
|
+
responses.append({"prompt": message.strip()})
|
|
239
|
+
|
|
240
|
+
return responses
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _list_workflow_runs(
|
|
244
|
+
repository: str,
|
|
245
|
+
workflow_id: str,
|
|
246
|
+
token: str,
|
|
247
|
+
per_page: int = 5,
|
|
248
|
+
) -> dict[str, Any]:
|
|
249
|
+
"""List runs of a specific workflow.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
repository: The repository in format "owner/repo"
|
|
253
|
+
workflow_id: The workflow file name or ID
|
|
254
|
+
token: GitHub token
|
|
255
|
+
per_page: Number of runs to return (default: 5)
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Dictionary with list of workflow runs
|
|
259
|
+
"""
|
|
260
|
+
# GitHub API endpoint for workflow runs
|
|
261
|
+
url = f"https://api.github.com/repos/{repository}/actions/workflows/{workflow_id}/runs"
|
|
262
|
+
|
|
263
|
+
headers = {
|
|
264
|
+
"Accept": "application/vnd.github+json",
|
|
265
|
+
"Authorization": f"Bearer {token}",
|
|
266
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
params = {"per_page": per_page}
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
response = requests.get(url, headers=headers, params=params, timeout=30)
|
|
273
|
+
|
|
274
|
+
if response.status_code == 200:
|
|
275
|
+
data = response.json()
|
|
276
|
+
runs = []
|
|
277
|
+
|
|
278
|
+
for run in data.get("workflow_runs", []):
|
|
279
|
+
runs.append(
|
|
280
|
+
{
|
|
281
|
+
"id": run.get("id"),
|
|
282
|
+
"name": run.get("name"),
|
|
283
|
+
"status": run.get("status"),
|
|
284
|
+
"conclusion": run.get("conclusion"),
|
|
285
|
+
"created_at": run.get("created_at"),
|
|
286
|
+
"updated_at": run.get("updated_at"),
|
|
287
|
+
"html_url": run.get("html_url"),
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
"success": True,
|
|
293
|
+
"runs": runs,
|
|
294
|
+
"total_count": data.get("total_count", 0),
|
|
295
|
+
}
|
|
296
|
+
else:
|
|
297
|
+
error_message = f"Failed to list workflow runs: {response.status_code} - {response.text}"
|
|
298
|
+
return {"success": False, "message": error_message}
|
|
299
|
+
except Exception as e:
|
|
300
|
+
return {"success": False, "message": f"Error listing workflow runs: {e!s}"}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@tool
|
|
304
|
+
def create_subagent(
|
|
305
|
+
repository: str,
|
|
306
|
+
workflow_id: str,
|
|
307
|
+
task: str | None = None,
|
|
308
|
+
system_prompt: str | None = None,
|
|
309
|
+
context: str | None = None,
|
|
310
|
+
branch: str = "main",
|
|
311
|
+
action: str = "create",
|
|
312
|
+
run_id: str | None = None,
|
|
313
|
+
model: str | None = None,
|
|
314
|
+
provider: str | None = None,
|
|
315
|
+
max_tokens: int | None = None,
|
|
316
|
+
tools: str | None = None,
|
|
317
|
+
agent_runner: str | None = None,
|
|
318
|
+
) -> dict[str, Any]:
|
|
319
|
+
"""Create and manage sub-agents via GitHub Actions workflows.
|
|
320
|
+
|
|
321
|
+
This tool allows creating and managing sub-agents by triggering GitHub Actions
|
|
322
|
+
workflows. It helps with task delegation and parallel processing by handing off
|
|
323
|
+
tasks to specialized agents running in separate GitHub Actions workflows.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
repository: GitHub repository in format "owner/repo" where the workflow lives
|
|
327
|
+
workflow_id: The workflow file name (e.g., "agent.yml") or ID
|
|
328
|
+
task: The task to delegate to the sub-agent (required only for "create" action)
|
|
329
|
+
system_prompt: Optional system prompt to set for the sub-agent
|
|
330
|
+
context: Optional context information to provide to the sub-agent
|
|
331
|
+
branch: The branch to run the workflow on (default: "main")
|
|
332
|
+
action: The action to perform. One of:
|
|
333
|
+
- "create": Create a new sub-agent (trigger workflow)
|
|
334
|
+
- "status": Check status of a sub-agent run
|
|
335
|
+
- "list": List recent sub-agent runs
|
|
336
|
+
run_id: The run ID when checking status of a specific run
|
|
337
|
+
model: The model ID to use for the sub-agent. Available models:
|
|
338
|
+
- "us.anthropic.claude-3-7-sonnet-20250219-v1:0" (max 32768 tokens) - 10000 ideal
|
|
339
|
+
- "us.anthropic.claude-sonnet-4-20250514-v1:0" (max 65536 tokens) - 10000 ideal
|
|
340
|
+
provider: The model provider to use (e.g., "bedrock" - only bedrock is enabled)
|
|
341
|
+
max_tokens: Maximum number of tokens for the sub-agent model.
|
|
342
|
+
tools: Comma-separated list of tools to enable for the sub-agent. Available tools:
|
|
343
|
+
current_time,editor,environment,file_read,file_write,http_request,load_tool,
|
|
344
|
+
python_repl,retrieve,shell,slack,think,use_llm,calculator,use_aws,generate_image,
|
|
345
|
+
image_reader,memory,swarm,workflow. Example: "file_read,python_repl,calculator"
|
|
346
|
+
agent_runner: Optional custom agent runner script URL to use for the sub-agent.
|
|
347
|
+
Falls back to repository variable AGENT_RUNNER, then default gist if not provided.
|
|
348
|
+
Example: "https://gist.githubusercontent.com/myuser/custom-agent.py"
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
A dictionary with the operation status and information
|
|
352
|
+
|
|
353
|
+
Example:
|
|
354
|
+
```python
|
|
355
|
+
# Create a sub-agent with Claude Opus 4 for complex reasoning (smartest)
|
|
356
|
+
result = create_subagent(
|
|
357
|
+
repository="owner/repo",
|
|
358
|
+
workflow_id="agent.yml",
|
|
359
|
+
task="Complex multi-step analysis requiring deep reasoning",
|
|
360
|
+
model="us.anthropic.claude-opus-4-20250514-v1:0",
|
|
361
|
+
provider="bedrock",
|
|
362
|
+
max_tokens=10000,
|
|
363
|
+
tools="think,use_agent,retrieve,shell,retrieve",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Create a sub-agent with Claude Sonnet 4 for balanced performance (suggested)
|
|
367
|
+
result = create_subagent(
|
|
368
|
+
repository="owner/repo",
|
|
369
|
+
workflow_id="agent.yml",
|
|
370
|
+
task="Analyze this dataset and provide comprehensive insights",
|
|
371
|
+
model="us.anthropic.claude-sonnet-4-20250514-v1:0",
|
|
372
|
+
provider="bedrock",
|
|
373
|
+
max_tokens=10000,
|
|
374
|
+
tools="file_read,python_repl,calculator,http_request,retrieve",
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Create a specialized development sub-agent
|
|
378
|
+
result = create_subagent(
|
|
379
|
+
repository="owner/repo",
|
|
380
|
+
workflow_id="agent.yml",
|
|
381
|
+
task="Review code and suggest improvements",
|
|
382
|
+
tools="file_read,editor,python_repl,shell",
|
|
383
|
+
system_prompt="You are a senior code reviewer focused on best practices",
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# Create a sub-agent with custom agent runner
|
|
388
|
+
result = create_subagent(
|
|
389
|
+
repository="owner/repo",
|
|
390
|
+
workflow_id="agent.yml",
|
|
391
|
+
task="Use custom agent logic for this task",
|
|
392
|
+
agent_runner="https://gist.githubusercontent.com/myuser/custom-agent.py",
|
|
393
|
+
tools="file_read,python_repl",
|
|
394
|
+
)
|
|
395
|
+
# Check status of a specific run
|
|
396
|
+
result = create_subagent(
|
|
397
|
+
action="status",
|
|
398
|
+
repository="owner/repo",
|
|
399
|
+
workflow_id="agent.yml",
|
|
400
|
+
run_id="12345678",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# List recent runs
|
|
404
|
+
result = create_subagent(
|
|
405
|
+
action="list",
|
|
406
|
+
repository="owner/repo",
|
|
407
|
+
workflow_id="agent.yml",
|
|
408
|
+
)
|
|
409
|
+
```
|
|
410
|
+
"""
|
|
411
|
+
try:
|
|
412
|
+
token = _get_github_token()
|
|
413
|
+
if not token:
|
|
414
|
+
return {
|
|
415
|
+
"status": "error",
|
|
416
|
+
"content": [
|
|
417
|
+
{
|
|
418
|
+
"text": "Error: GitHub token not available. Cannot create or manage sub-agents."
|
|
419
|
+
}
|
|
420
|
+
],
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if action == "create":
|
|
424
|
+
# Validate that task is provided for create action
|
|
425
|
+
if not task:
|
|
426
|
+
return {
|
|
427
|
+
"status": "error",
|
|
428
|
+
"content": [
|
|
429
|
+
{
|
|
430
|
+
"text": "Error: 'task' parameter is required for the create action"
|
|
431
|
+
}
|
|
432
|
+
],
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# Prepare inputs for the workflow - only include valid inputs based on workflow definition
|
|
436
|
+
inputs = {}
|
|
437
|
+
|
|
438
|
+
# Create task context combining task and context
|
|
439
|
+
task_context = task
|
|
440
|
+
if context is not None:
|
|
441
|
+
task_context += f"\nContext: {context}"
|
|
442
|
+
inputs["task"] = task_context
|
|
443
|
+
|
|
444
|
+
# Only add system_prompt if it's provided
|
|
445
|
+
if system_prompt is not None:
|
|
446
|
+
inputs["system_prompt"] = str(
|
|
447
|
+
system_prompt
|
|
448
|
+
) # Explicitly convert to string
|
|
449
|
+
|
|
450
|
+
# Add model configuration parameters if provided
|
|
451
|
+
if model is not None:
|
|
452
|
+
inputs["model"] = str(model)
|
|
453
|
+
|
|
454
|
+
if provider is not None:
|
|
455
|
+
inputs["provider"] = str(provider)
|
|
456
|
+
|
|
457
|
+
if max_tokens is not None:
|
|
458
|
+
inputs["max_tokens"] = str(
|
|
459
|
+
max_tokens
|
|
460
|
+
) # Convert to string for GitHub Actions input
|
|
461
|
+
|
|
462
|
+
# Add tools configuration if provided
|
|
463
|
+
if tools is not None:
|
|
464
|
+
inputs["tools"] = str(tools) # Pass comma-separated tools string
|
|
465
|
+
# Add agent_runner configuration if provided
|
|
466
|
+
if agent_runner is not None:
|
|
467
|
+
inputs["agent_runner"] = str(agent_runner)
|
|
468
|
+
|
|
469
|
+
# Dispatch the workflow
|
|
470
|
+
result = _dispatch_workflow(
|
|
471
|
+
repository=repository,
|
|
472
|
+
workflow_id=workflow_id,
|
|
473
|
+
inputs=inputs,
|
|
474
|
+
token=token,
|
|
475
|
+
branch=branch,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
if result["success"]:
|
|
479
|
+
content = [
|
|
480
|
+
{"text": f"Sub-agent created successfully: {result['message']}"},
|
|
481
|
+
{"text": f"Task delegated: {task}"},
|
|
482
|
+
{"text": f"Repository: {repository}"},
|
|
483
|
+
{"text": f"Workflow: {workflow_id}"},
|
|
484
|
+
{"text": f"Branch: {branch}"},
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
# Add model configuration info if provided
|
|
488
|
+
if model is not None:
|
|
489
|
+
content.append({"text": f"Model: {model}"})
|
|
490
|
+
if provider is not None:
|
|
491
|
+
content.append({"text": f"Provider: {provider}"})
|
|
492
|
+
if max_tokens is not None:
|
|
493
|
+
content.append({"text": f"Max Tokens: {max_tokens}"})
|
|
494
|
+
if tools is not None:
|
|
495
|
+
content.append({"text": f"Tools: {tools}"})
|
|
496
|
+
if agent_runner is not None:
|
|
497
|
+
content.append({"text": f"Agent Runner: {agent_runner}"})
|
|
498
|
+
|
|
499
|
+
content.append(
|
|
500
|
+
{
|
|
501
|
+
"text": "To check status, use the 'list' action to find the run ID, then use 'status' action."
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
"status": "success",
|
|
507
|
+
"content": content,
|
|
508
|
+
}
|
|
509
|
+
else:
|
|
510
|
+
return {
|
|
511
|
+
"status": "error",
|
|
512
|
+
"content": [
|
|
513
|
+
{"text": f"Failed to create sub-agent: {result['message']}"}
|
|
514
|
+
],
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
elif action == "status":
|
|
518
|
+
if not run_id:
|
|
519
|
+
return {
|
|
520
|
+
"status": "error",
|
|
521
|
+
"content": [
|
|
522
|
+
{
|
|
523
|
+
"text": "Error: run_id parameter is required for the status action"
|
|
524
|
+
}
|
|
525
|
+
],
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
result = _check_workflow_run_status(
|
|
529
|
+
repository=repository, run_id=run_id, token=token
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
if result["success"]:
|
|
533
|
+
status_text = f"Status: {result.get('status')}"
|
|
534
|
+
if result.get("conclusion"):
|
|
535
|
+
status_text += f", Conclusion: {result.get('conclusion')}"
|
|
536
|
+
|
|
537
|
+
content = [
|
|
538
|
+
{"text": f"Sub-agent run status: {status_text}"},
|
|
539
|
+
{"text": f"Name: {result.get('name', 'N/A')}"},
|
|
540
|
+
{"text": f"Created: {result.get('created_at', 'N/A')}"},
|
|
541
|
+
{"text": f"Updated: {result.get('updated_at', 'N/A')}"},
|
|
542
|
+
{"text": f"Run URL: {result.get('html_url', 'N/A')}"},
|
|
543
|
+
]
|
|
544
|
+
|
|
545
|
+
# If the run is complete, try to get logs and extract meaningful information
|
|
546
|
+
if result.get("status") == "completed":
|
|
547
|
+
logs_result = _get_workflow_run_logs(
|
|
548
|
+
repository=repository, run_id=run_id, token=token
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
if logs_result["success"]:
|
|
552
|
+
logs = logs_result.get("logs", "")
|
|
553
|
+
|
|
554
|
+
# Try to extract agent responses from logs
|
|
555
|
+
agent_responses = _parse_agent_responses_from_logs(logs)
|
|
556
|
+
|
|
557
|
+
if agent_responses:
|
|
558
|
+
content.append({"text": "Agent Tasks:"})
|
|
559
|
+
for idx, response in enumerate(agent_responses, 1):
|
|
560
|
+
content.append(
|
|
561
|
+
{
|
|
562
|
+
"text": f"Task {idx}: {response.get('prompt', 'N/A')}"
|
|
563
|
+
}
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# Extract event output message
|
|
567
|
+
event_pattern = r"Event:\s*(.*?)(?:\n|\r\n)"
|
|
568
|
+
event_match = re.search(event_pattern, logs)
|
|
569
|
+
if event_match:
|
|
570
|
+
content.append(
|
|
571
|
+
{"text": f"Event: {event_match.group(1).strip()}"}
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# Also show a truncated version of the logs for debugging
|
|
575
|
+
if len(logs) > 1000:
|
|
576
|
+
logs_excerpt = (
|
|
577
|
+
logs[:1000] + "...\n[Logs truncated due to length]"
|
|
578
|
+
)
|
|
579
|
+
else:
|
|
580
|
+
logs_excerpt = logs
|
|
581
|
+
|
|
582
|
+
content.append(
|
|
583
|
+
{"text": f"Run Logs (excerpt):\n```\n{logs_excerpt}\n```"}
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
return {"status": "success", "content": content}
|
|
587
|
+
else:
|
|
588
|
+
return {
|
|
589
|
+
"status": "error",
|
|
590
|
+
"content": [
|
|
591
|
+
{
|
|
592
|
+
"text": f"Failed to check sub-agent status: {result.get('message')}"
|
|
593
|
+
}
|
|
594
|
+
],
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
elif action == "list":
|
|
598
|
+
result = _list_workflow_runs(
|
|
599
|
+
repository=repository, workflow_id=workflow_id, token=token
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
if result["success"]:
|
|
603
|
+
runs = result.get("runs", [])
|
|
604
|
+
total_count = result.get("total_count", 0)
|
|
605
|
+
|
|
606
|
+
if not runs:
|
|
607
|
+
return {
|
|
608
|
+
"status": "success",
|
|
609
|
+
"content": [{"text": "No recent sub-agent runs found"}],
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
content = [
|
|
613
|
+
{
|
|
614
|
+
"text": f"Recent sub-agent runs (showing {len(runs)} of {total_count}):"
|
|
615
|
+
}
|
|
616
|
+
]
|
|
617
|
+
for run in runs:
|
|
618
|
+
run_info = (
|
|
619
|
+
f"Run ID: {run.get('id')}\n"
|
|
620
|
+
f"Name: {run.get('name', 'N/A')}\n"
|
|
621
|
+
f"Status: {run.get('status')}\n"
|
|
622
|
+
f"Conclusion: {run.get('conclusion', 'N/A')}\n"
|
|
623
|
+
f"Created: {run.get('created_at')}\n"
|
|
624
|
+
f"Updated: {run.get('updated_at', 'N/A')}\n"
|
|
625
|
+
f"URL: {run.get('html_url')}\n"
|
|
626
|
+
)
|
|
627
|
+
content.append({"text": run_info})
|
|
628
|
+
|
|
629
|
+
content.append(
|
|
630
|
+
{
|
|
631
|
+
"text": "To check the status of a specific run, use: "
|
|
632
|
+
'create_subagent(action="status", repository="owner/repo", '
|
|
633
|
+
'workflow_id="workflow.yml", run_id="RUN_ID")'
|
|
634
|
+
}
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
return {"status": "success", "content": content}
|
|
638
|
+
else:
|
|
639
|
+
return {
|
|
640
|
+
"status": "error",
|
|
641
|
+
"content": [
|
|
642
|
+
{
|
|
643
|
+
"text": f"Failed to list sub-agent runs: {result.get('message')}"
|
|
644
|
+
}
|
|
645
|
+
],
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
else:
|
|
649
|
+
return {
|
|
650
|
+
"status": "error",
|
|
651
|
+
"content": [
|
|
652
|
+
{
|
|
653
|
+
"text": f"Error: Unknown action '{action}'. Valid actions are create, status, list"
|
|
654
|
+
}
|
|
655
|
+
],
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
except Exception as e:
|
|
659
|
+
return {"status": "error", "content": [{"text": f"Error: {e!s}"}]}
|