emdash-core 0.1.37__py3-none-any.whl → 0.1.60__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.
- emdash_core/agent/agents.py +9 -0
- emdash_core/agent/background.py +481 -0
- emdash_core/agent/inprocess_subagent.py +70 -1
- emdash_core/agent/mcp/config.py +78 -2
- emdash_core/agent/prompts/main_agent.py +53 -1
- emdash_core/agent/prompts/plan_mode.py +65 -44
- emdash_core/agent/prompts/subagents.py +73 -1
- emdash_core/agent/prompts/workflow.py +179 -28
- emdash_core/agent/providers/models.py +1 -1
- emdash_core/agent/providers/openai_provider.py +10 -0
- emdash_core/agent/research/researcher.py +154 -45
- emdash_core/agent/runner/agent_runner.py +145 -19
- emdash_core/agent/runner/sdk_runner.py +29 -2
- emdash_core/agent/skills.py +81 -1
- emdash_core/agent/toolkit.py +87 -11
- emdash_core/agent/tools/__init__.py +2 -0
- emdash_core/agent/tools/coding.py +344 -52
- emdash_core/agent/tools/lsp.py +361 -0
- emdash_core/agent/tools/skill.py +21 -1
- emdash_core/agent/tools/task.py +16 -19
- emdash_core/agent/tools/task_output.py +262 -32
- emdash_core/agent/verifier/__init__.py +11 -0
- emdash_core/agent/verifier/manager.py +295 -0
- emdash_core/agent/verifier/models.py +97 -0
- emdash_core/{swarm/worktree_manager.py → agent/worktree.py} +19 -1
- emdash_core/api/agent.py +297 -2
- emdash_core/api/research.py +3 -3
- emdash_core/api/router.py +0 -4
- emdash_core/context/longevity.py +197 -0
- emdash_core/context/providers/explored_areas.py +83 -39
- emdash_core/context/reranker.py +35 -144
- emdash_core/context/simple_reranker.py +500 -0
- emdash_core/context/tool_relevance.py +84 -0
- emdash_core/core/config.py +8 -0
- emdash_core/graph/__init__.py +8 -1
- emdash_core/graph/connection.py +24 -3
- emdash_core/graph/writer.py +7 -1
- emdash_core/models/agent.py +10 -0
- emdash_core/server.py +1 -6
- emdash_core/sse/stream.py +16 -1
- emdash_core/utils/__init__.py +0 -2
- emdash_core/utils/git.py +103 -0
- emdash_core/utils/image.py +147 -160
- {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/METADATA +6 -6
- {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/RECORD +47 -52
- emdash_core/api/swarm.py +0 -223
- emdash_core/db/__init__.py +0 -67
- emdash_core/db/auth.py +0 -134
- emdash_core/db/models.py +0 -91
- emdash_core/db/provider.py +0 -222
- emdash_core/db/providers/__init__.py +0 -5
- emdash_core/db/providers/supabase.py +0 -452
- emdash_core/swarm/__init__.py +0 -17
- emdash_core/swarm/merge_agent.py +0 -383
- emdash_core/swarm/session_manager.py +0 -274
- emdash_core/swarm/swarm_runner.py +0 -226
- emdash_core/swarm/task_definition.py +0 -137
- emdash_core/swarm/worker_spawner.py +0 -319
- {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/WHEEL +0 -0
- {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Task management tools for background tasks.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Provides tools to:
|
|
4
|
+
- Get output from background tasks (shell commands and sub-agents)
|
|
5
|
+
- Kill running background tasks
|
|
6
|
+
- List all background tasks
|
|
4
7
|
"""
|
|
5
8
|
|
|
6
9
|
import json
|
|
@@ -9,20 +12,26 @@ from pathlib import Path
|
|
|
9
12
|
from typing import Optional
|
|
10
13
|
|
|
11
14
|
from .base import BaseTool, ToolResult, ToolCategory
|
|
15
|
+
from ..background import BackgroundTaskManager, TaskStatus, TaskType
|
|
12
16
|
from ...utils.logger import log
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
class TaskOutputTool(BaseTool):
|
|
16
|
-
"""Get output from a running or completed background
|
|
20
|
+
"""Get output from a running or completed background task.
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
Works with both shell commands (execute_command with run_in_background=true)
|
|
23
|
+
and sub-agents (task with run_in_background=true).
|
|
19
24
|
"""
|
|
20
25
|
|
|
21
26
|
name = "task_output"
|
|
22
|
-
description = """Get output from a background sub-agent.
|
|
27
|
+
description = """Get output from a background task (shell command or sub-agent).
|
|
23
28
|
|
|
24
|
-
Use this to check the status and results of
|
|
25
|
-
|
|
29
|
+
Use this to check the status and results of tasks started with run_in_background=true.
|
|
30
|
+
Can wait for completion or check immediately.
|
|
31
|
+
|
|
32
|
+
Works with:
|
|
33
|
+
- Shell commands: execute_command(..., run_in_background=true)
|
|
34
|
+
- Sub-agents: task(..., run_in_background=true)"""
|
|
26
35
|
category = ToolCategory.PLANNING
|
|
27
36
|
|
|
28
37
|
def __init__(self, repo_root: Path, connection=None):
|
|
@@ -38,36 +47,88 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
38
47
|
|
|
39
48
|
def execute(
|
|
40
49
|
self,
|
|
41
|
-
|
|
50
|
+
task_id: str = "",
|
|
42
51
|
block: bool = True,
|
|
43
52
|
timeout: int = 60,
|
|
44
53
|
**kwargs,
|
|
45
54
|
) -> ToolResult:
|
|
46
|
-
"""Get output from a background
|
|
55
|
+
"""Get output from a background task.
|
|
47
56
|
|
|
48
57
|
Args:
|
|
49
|
-
|
|
58
|
+
task_id: Task ID to get output from (shell_xxx or agent_xxx)
|
|
50
59
|
block: Whether to wait for completion
|
|
51
60
|
timeout: Max wait time in seconds (if blocking)
|
|
52
61
|
|
|
53
62
|
Returns:
|
|
54
|
-
ToolResult with
|
|
63
|
+
ToolResult with task output or status
|
|
55
64
|
"""
|
|
56
|
-
|
|
65
|
+
# Support both task_id and agent_id for backwards compatibility
|
|
66
|
+
task_id = task_id or kwargs.get("agent_id", "")
|
|
67
|
+
|
|
68
|
+
if not task_id:
|
|
57
69
|
return ToolResult.error_result(
|
|
58
|
-
"
|
|
59
|
-
suggestions=["Provide the
|
|
70
|
+
"task_id is required",
|
|
71
|
+
suggestions=["Provide the task_id from execute_command() or task() call"],
|
|
60
72
|
)
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
# Try the new BackgroundTaskManager first
|
|
75
|
+
manager = BackgroundTaskManager.get_instance()
|
|
76
|
+
task = manager.get_task(task_id)
|
|
77
|
+
|
|
78
|
+
if task:
|
|
79
|
+
return self._get_task_from_manager(task, block, timeout)
|
|
80
|
+
|
|
81
|
+
# Fall back to file-based lookup for legacy sub-agents
|
|
82
|
+
output_file = self.agents_dir / f"{task_id}.output"
|
|
83
|
+
transcript_file = self.agents_dir / f"{task_id}.jsonl"
|
|
64
84
|
|
|
65
85
|
if block:
|
|
66
86
|
return self._wait_for_completion(
|
|
67
|
-
|
|
87
|
+
task_id, output_file, transcript_file, timeout
|
|
68
88
|
)
|
|
69
89
|
else:
|
|
70
|
-
return self._check_status(
|
|
90
|
+
return self._check_status(task_id, output_file, transcript_file)
|
|
91
|
+
|
|
92
|
+
def _get_task_from_manager(
|
|
93
|
+
self,
|
|
94
|
+
task,
|
|
95
|
+
block: bool,
|
|
96
|
+
timeout: int,
|
|
97
|
+
) -> ToolResult:
|
|
98
|
+
"""Get task status from BackgroundTaskManager.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
task: BackgroundTask instance
|
|
102
|
+
block: Whether to wait for completion
|
|
103
|
+
timeout: Max wait time in seconds
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
ToolResult with task info
|
|
107
|
+
"""
|
|
108
|
+
if block and task.status == TaskStatus.RUNNING:
|
|
109
|
+
# Wait for completion
|
|
110
|
+
start_time = time.time()
|
|
111
|
+
while time.time() - start_time < timeout:
|
|
112
|
+
if task.status != TaskStatus.RUNNING:
|
|
113
|
+
break
|
|
114
|
+
time.sleep(0.5)
|
|
115
|
+
|
|
116
|
+
if task.status == TaskStatus.RUNNING:
|
|
117
|
+
return ToolResult.success_result(
|
|
118
|
+
data={
|
|
119
|
+
"task_id": task.task_id,
|
|
120
|
+
"status": "timeout",
|
|
121
|
+
"message": f"Task did not complete within {timeout}s",
|
|
122
|
+
"partial_stdout": task.stdout[-2000:] if task.stdout else "",
|
|
123
|
+
},
|
|
124
|
+
suggestions=["Use block=false to check status without waiting"],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Return full task info
|
|
128
|
+
return ToolResult.success_result(
|
|
129
|
+
data=task.to_dict(),
|
|
130
|
+
metadata={"task_id": task.task_id},
|
|
131
|
+
)
|
|
71
132
|
|
|
72
133
|
def _wait_for_completion(
|
|
73
134
|
self,
|
|
@@ -76,7 +137,7 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
76
137
|
transcript_file: Path,
|
|
77
138
|
timeout: int,
|
|
78
139
|
) -> ToolResult:
|
|
79
|
-
"""Wait for agent to complete.
|
|
140
|
+
"""Wait for legacy agent to complete (file-based).
|
|
80
141
|
|
|
81
142
|
Args:
|
|
82
143
|
agent_id: Agent ID
|
|
@@ -99,7 +160,7 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
99
160
|
if isinstance(data, dict) and "success" in data:
|
|
100
161
|
return ToolResult.success_result(
|
|
101
162
|
data=data,
|
|
102
|
-
metadata={"
|
|
163
|
+
metadata={"task_id": agent_id, "status": "completed"},
|
|
103
164
|
)
|
|
104
165
|
except json.JSONDecodeError:
|
|
105
166
|
pass
|
|
@@ -109,7 +170,7 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
109
170
|
return ToolResult.success_result(
|
|
110
171
|
data={
|
|
111
172
|
"status": "running",
|
|
112
|
-
"
|
|
173
|
+
"task_id": agent_id,
|
|
113
174
|
"partial_output": content[-2000:], # Last 2KB
|
|
114
175
|
},
|
|
115
176
|
)
|
|
@@ -120,8 +181,8 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
120
181
|
return ToolResult.success_result(
|
|
121
182
|
data={
|
|
122
183
|
"status": "timeout",
|
|
123
|
-
"
|
|
124
|
-
"message": f"
|
|
184
|
+
"task_id": agent_id,
|
|
185
|
+
"message": f"Task did not complete within {timeout}s",
|
|
125
186
|
},
|
|
126
187
|
suggestions=["Use block=false to check status without waiting"],
|
|
127
188
|
)
|
|
@@ -132,7 +193,7 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
132
193
|
output_file: Path,
|
|
133
194
|
transcript_file: Path,
|
|
134
195
|
) -> ToolResult:
|
|
135
|
-
"""Check agent status without waiting.
|
|
196
|
+
"""Check legacy agent status without waiting (file-based).
|
|
136
197
|
|
|
137
198
|
Args:
|
|
138
199
|
agent_id: Agent ID
|
|
@@ -149,13 +210,13 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
149
210
|
return ToolResult.success_result(
|
|
150
211
|
data={
|
|
151
212
|
"status": "running",
|
|
152
|
-
"
|
|
213
|
+
"task_id": agent_id,
|
|
153
214
|
},
|
|
154
215
|
)
|
|
155
216
|
else:
|
|
156
217
|
return ToolResult.error_result(
|
|
157
|
-
f"
|
|
158
|
-
suggestions=["Check the
|
|
218
|
+
f"Task {agent_id} not found",
|
|
219
|
+
suggestions=["Check the task_id is correct"],
|
|
159
220
|
)
|
|
160
221
|
|
|
161
222
|
# Output exists, check if complete
|
|
@@ -166,7 +227,7 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
166
227
|
if isinstance(data, dict) and "success" in data:
|
|
167
228
|
return ToolResult.success_result(
|
|
168
229
|
data=data,
|
|
169
|
-
metadata={"
|
|
230
|
+
metadata={"task_id": agent_id, "status": "completed"},
|
|
170
231
|
)
|
|
171
232
|
|
|
172
233
|
except json.JSONDecodeError:
|
|
@@ -176,7 +237,7 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
176
237
|
return ToolResult.success_result(
|
|
177
238
|
data={
|
|
178
239
|
"status": "running",
|
|
179
|
-
"
|
|
240
|
+
"task_id": agent_id,
|
|
180
241
|
"has_output": True,
|
|
181
242
|
},
|
|
182
243
|
)
|
|
@@ -185,9 +246,9 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
185
246
|
"""Get OpenAI function schema."""
|
|
186
247
|
return self._make_schema(
|
|
187
248
|
properties={
|
|
188
|
-
"
|
|
249
|
+
"task_id": {
|
|
189
250
|
"type": "string",
|
|
190
|
-
"description": "
|
|
251
|
+
"description": "Task ID to get output from (e.g., shell_abc123 or agent_xyz789)",
|
|
191
252
|
},
|
|
192
253
|
"block": {
|
|
193
254
|
"type": "boolean",
|
|
@@ -200,5 +261,174 @@ run_in_background=True. Can wait for completion or check immediately."""
|
|
|
200
261
|
"default": 60,
|
|
201
262
|
},
|
|
202
263
|
},
|
|
203
|
-
required=["
|
|
264
|
+
required=["task_id"],
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class KillTaskTool(BaseTool):
|
|
269
|
+
"""Kill a running background task."""
|
|
270
|
+
|
|
271
|
+
name = "kill_task"
|
|
272
|
+
description = """Kill a running background task (shell command or sub-agent).
|
|
273
|
+
|
|
274
|
+
Use this to terminate tasks that are no longer needed or are stuck.
|
|
275
|
+
Works with shell commands and sub-agents started with run_in_background=true."""
|
|
276
|
+
category = ToolCategory.PLANNING
|
|
277
|
+
|
|
278
|
+
def __init__(self, repo_root: Path, connection=None):
|
|
279
|
+
"""Initialize with repo root.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
repo_root: Root directory of the repository
|
|
283
|
+
connection: Optional connection (not used)
|
|
284
|
+
"""
|
|
285
|
+
self.repo_root = repo_root.resolve()
|
|
286
|
+
self.connection = connection
|
|
287
|
+
|
|
288
|
+
def execute(
|
|
289
|
+
self,
|
|
290
|
+
task_id: str = "",
|
|
291
|
+
**kwargs,
|
|
292
|
+
) -> ToolResult:
|
|
293
|
+
"""Kill a background task.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
task_id: Task ID to kill
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
ToolResult indicating success or failure
|
|
300
|
+
"""
|
|
301
|
+
if not task_id:
|
|
302
|
+
return ToolResult.error_result(
|
|
303
|
+
"task_id is required",
|
|
304
|
+
suggestions=["Provide the task_id to kill"],
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
manager = BackgroundTaskManager.get_instance()
|
|
308
|
+
task = manager.get_task(task_id)
|
|
309
|
+
|
|
310
|
+
if not task:
|
|
311
|
+
return ToolResult.error_result(
|
|
312
|
+
f"Task {task_id} not found",
|
|
313
|
+
suggestions=["Check the task_id is correct", "Use list_tasks to see all tasks"],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if task.status != TaskStatus.RUNNING:
|
|
317
|
+
return ToolResult.error_result(
|
|
318
|
+
f"Task {task_id} is not running (status: {task.status.value})",
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
success = manager.kill_task(task_id)
|
|
322
|
+
|
|
323
|
+
if success:
|
|
324
|
+
return ToolResult.success_result(
|
|
325
|
+
data={
|
|
326
|
+
"task_id": task_id,
|
|
327
|
+
"killed": True,
|
|
328
|
+
"message": f"Task {task_id} has been terminated",
|
|
329
|
+
},
|
|
330
|
+
)
|
|
331
|
+
else:
|
|
332
|
+
return ToolResult.error_result(
|
|
333
|
+
f"Failed to kill task {task_id}",
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def get_schema(self) -> dict:
|
|
337
|
+
"""Get OpenAI function schema."""
|
|
338
|
+
return self._make_schema(
|
|
339
|
+
properties={
|
|
340
|
+
"task_id": {
|
|
341
|
+
"type": "string",
|
|
342
|
+
"description": "Task ID to kill",
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
required=["task_id"],
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class ListTasksTool(BaseTool):
|
|
350
|
+
"""List all background tasks."""
|
|
351
|
+
|
|
352
|
+
name = "list_tasks"
|
|
353
|
+
description = """List all background tasks (running and completed).
|
|
354
|
+
|
|
355
|
+
Shows status of all shell commands and sub-agents started with run_in_background=true."""
|
|
356
|
+
category = ToolCategory.PLANNING
|
|
357
|
+
|
|
358
|
+
def __init__(self, repo_root: Path, connection=None):
|
|
359
|
+
"""Initialize with repo root.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
repo_root: Root directory of the repository
|
|
363
|
+
connection: Optional connection (not used)
|
|
364
|
+
"""
|
|
365
|
+
self.repo_root = repo_root.resolve()
|
|
366
|
+
self.connection = connection
|
|
367
|
+
|
|
368
|
+
def execute(
|
|
369
|
+
self,
|
|
370
|
+
status_filter: str = "",
|
|
371
|
+
**kwargs,
|
|
372
|
+
) -> ToolResult:
|
|
373
|
+
"""List all background tasks.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
status_filter: Optional filter by status (running, completed, failed)
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
ToolResult with task list
|
|
380
|
+
"""
|
|
381
|
+
manager = BackgroundTaskManager.get_instance()
|
|
382
|
+
tasks = manager.get_all_tasks()
|
|
383
|
+
|
|
384
|
+
# Apply filter if provided
|
|
385
|
+
if status_filter:
|
|
386
|
+
try:
|
|
387
|
+
filter_status = TaskStatus(status_filter)
|
|
388
|
+
tasks = [t for t in tasks if t.status == filter_status]
|
|
389
|
+
except ValueError:
|
|
390
|
+
return ToolResult.error_result(
|
|
391
|
+
f"Invalid status filter: {status_filter}",
|
|
392
|
+
suggestions=["Valid filters: running, completed, failed, killed"],
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Format task list
|
|
396
|
+
task_list = []
|
|
397
|
+
for task in tasks:
|
|
398
|
+
task_info = {
|
|
399
|
+
"task_id": task.task_id,
|
|
400
|
+
"type": task.task_type.value,
|
|
401
|
+
"status": task.status.value,
|
|
402
|
+
"description": task.description,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if task.task_type == TaskType.SHELL:
|
|
406
|
+
task_info["command"] = task.command[:50] + "..." if len(task.command or "") > 50 else task.command
|
|
407
|
+
else:
|
|
408
|
+
task_info["agent_type"] = task.agent_type
|
|
409
|
+
|
|
410
|
+
if task.exit_code is not None:
|
|
411
|
+
task_info["exit_code"] = task.exit_code
|
|
412
|
+
|
|
413
|
+
task_list.append(task_info)
|
|
414
|
+
|
|
415
|
+
return ToolResult.success_result(
|
|
416
|
+
data={
|
|
417
|
+
"tasks": task_list,
|
|
418
|
+
"total": len(task_list),
|
|
419
|
+
"running": len([t for t in tasks if t.status == TaskStatus.RUNNING]),
|
|
420
|
+
},
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def get_schema(self) -> dict:
|
|
424
|
+
"""Get OpenAI function schema."""
|
|
425
|
+
return self._make_schema(
|
|
426
|
+
properties={
|
|
427
|
+
"status_filter": {
|
|
428
|
+
"type": "string",
|
|
429
|
+
"enum": ["running", "completed", "failed", "killed"],
|
|
430
|
+
"description": "Filter by task status (optional)",
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
required=[],
|
|
204
434
|
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Verification system for validating agent output quality."""
|
|
2
|
+
|
|
3
|
+
from .models import VerifierConfig, VerifierResult, VerificationReport
|
|
4
|
+
from .manager import VerifierManager
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"VerifierConfig",
|
|
8
|
+
"VerifierResult",
|
|
9
|
+
"VerificationReport",
|
|
10
|
+
"VerifierManager",
|
|
11
|
+
]
|