emdash-core 0.1.33__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.
Files changed (67) hide show
  1. emdash_core/agent/agents.py +93 -23
  2. emdash_core/agent/background.py +481 -0
  3. emdash_core/agent/hooks.py +419 -0
  4. emdash_core/agent/inprocess_subagent.py +114 -10
  5. emdash_core/agent/mcp/config.py +78 -2
  6. emdash_core/agent/prompts/main_agent.py +88 -1
  7. emdash_core/agent/prompts/plan_mode.py +65 -44
  8. emdash_core/agent/prompts/subagents.py +96 -8
  9. emdash_core/agent/prompts/workflow.py +215 -50
  10. emdash_core/agent/providers/models.py +1 -1
  11. emdash_core/agent/providers/openai_provider.py +10 -0
  12. emdash_core/agent/research/researcher.py +154 -45
  13. emdash_core/agent/runner/agent_runner.py +157 -19
  14. emdash_core/agent/runner/context.py +28 -9
  15. emdash_core/agent/runner/sdk_runner.py +29 -2
  16. emdash_core/agent/skills.py +81 -1
  17. emdash_core/agent/toolkit.py +87 -11
  18. emdash_core/agent/toolkits/__init__.py +117 -18
  19. emdash_core/agent/toolkits/base.py +87 -2
  20. emdash_core/agent/toolkits/explore.py +18 -0
  21. emdash_core/agent/toolkits/plan.py +18 -0
  22. emdash_core/agent/tools/__init__.py +2 -0
  23. emdash_core/agent/tools/coding.py +344 -52
  24. emdash_core/agent/tools/lsp.py +361 -0
  25. emdash_core/agent/tools/skill.py +21 -1
  26. emdash_core/agent/tools/task.py +27 -23
  27. emdash_core/agent/tools/task_output.py +262 -32
  28. emdash_core/agent/verifier/__init__.py +11 -0
  29. emdash_core/agent/verifier/manager.py +295 -0
  30. emdash_core/agent/verifier/models.py +97 -0
  31. emdash_core/{swarm/worktree_manager.py → agent/worktree.py} +19 -1
  32. emdash_core/api/agent.py +451 -5
  33. emdash_core/api/research.py +3 -3
  34. emdash_core/api/router.py +0 -4
  35. emdash_core/context/longevity.py +197 -0
  36. emdash_core/context/providers/explored_areas.py +83 -39
  37. emdash_core/context/reranker.py +35 -144
  38. emdash_core/context/simple_reranker.py +500 -0
  39. emdash_core/context/tool_relevance.py +84 -0
  40. emdash_core/core/config.py +8 -0
  41. emdash_core/graph/__init__.py +8 -1
  42. emdash_core/graph/connection.py +24 -3
  43. emdash_core/graph/writer.py +7 -1
  44. emdash_core/ingestion/repository.py +17 -198
  45. emdash_core/models/agent.py +14 -0
  46. emdash_core/server.py +1 -6
  47. emdash_core/sse/stream.py +16 -1
  48. emdash_core/utils/__init__.py +0 -2
  49. emdash_core/utils/git.py +103 -0
  50. emdash_core/utils/image.py +147 -160
  51. {emdash_core-0.1.33.dist-info → emdash_core-0.1.60.dist-info}/METADATA +7 -5
  52. {emdash_core-0.1.33.dist-info → emdash_core-0.1.60.dist-info}/RECORD +54 -58
  53. emdash_core/api/swarm.py +0 -223
  54. emdash_core/db/__init__.py +0 -67
  55. emdash_core/db/auth.py +0 -134
  56. emdash_core/db/models.py +0 -91
  57. emdash_core/db/provider.py +0 -222
  58. emdash_core/db/providers/__init__.py +0 -5
  59. emdash_core/db/providers/supabase.py +0 -452
  60. emdash_core/swarm/__init__.py +0 -17
  61. emdash_core/swarm/merge_agent.py +0 -383
  62. emdash_core/swarm/session_manager.py +0 -274
  63. emdash_core/swarm/swarm_runner.py +0 -226
  64. emdash_core/swarm/task_definition.py +0 -137
  65. emdash_core/swarm/worker_spawner.py +0 -319
  66. {emdash_core-0.1.33.dist-info → emdash_core-0.1.60.dist-info}/WHEEL +0 -0
  67. {emdash_core-0.1.33.dist-info → emdash_core-0.1.60.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,9 @@
1
- """TaskOutput tool for retrieving sub-agent results.
1
+ """Task management tools for background tasks.
2
2
 
3
- Retrieves output from background sub-agents started with run_in_background=True.
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 sub-agent.
20
+ """Get output from a running or completed background task.
17
21
 
18
- Use this to check on sub-agents that were started with run_in_background=True.
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 sub-agents started with
25
- run_in_background=True. Can wait for completion or check immediately."""
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
- agent_id: str = "",
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 agent.
55
+ """Get output from a background task.
47
56
 
48
57
  Args:
49
- agent_id: Agent ID to get output from
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 agent output or status
63
+ ToolResult with task output or status
55
64
  """
56
- if not agent_id:
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
- "agent_id is required",
59
- suggestions=["Provide the agent_id from the task() call"],
70
+ "task_id is required",
71
+ suggestions=["Provide the task_id from execute_command() or task() call"],
60
72
  )
61
73
 
62
- output_file = self.agents_dir / f"{agent_id}.output"
63
- transcript_file = self.agents_dir / f"{agent_id}.jsonl"
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
- agent_id, output_file, transcript_file, timeout
87
+ task_id, output_file, transcript_file, timeout
68
88
  )
69
89
  else:
70
- return self._check_status(agent_id, output_file, transcript_file)
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={"agent_id": agent_id, "status": "completed"},
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
- "agent_id": agent_id,
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
- "agent_id": agent_id,
124
- "message": f"Agent did not complete within {timeout}s",
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
- "agent_id": agent_id,
213
+ "task_id": agent_id,
153
214
  },
154
215
  )
155
216
  else:
156
217
  return ToolResult.error_result(
157
- f"Agent {agent_id} not found",
158
- suggestions=["Check the agent_id is correct"],
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={"agent_id": agent_id, "status": "completed"},
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
- "agent_id": agent_id,
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
- "agent_id": {
249
+ "task_id": {
189
250
  "type": "string",
190
- "description": "Agent ID to get output from",
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=["agent_id"],
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
+ ]