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
@@ -2,10 +2,13 @@
2
2
 
3
3
  Provides specialized toolkits for different agent types.
4
4
  Each toolkit contains a curated set of tools appropriate for the agent's purpose.
5
+
6
+ Custom agents from .emdash/agents/*.md are also supported and use the Explore toolkit
7
+ by default (unless they specify different tools in their frontmatter).
5
8
  """
6
9
 
7
10
  from pathlib import Path
8
- from typing import TYPE_CHECKING, Dict, Type
11
+ from typing import TYPE_CHECKING, Dict, Type, Optional
9
12
 
10
13
  if TYPE_CHECKING:
11
14
  from .base import BaseToolkit
@@ -20,45 +23,141 @@ TOOLKIT_REGISTRY: Dict[str, str] = {
20
23
  }
21
24
 
22
25
 
26
+ def _get_custom_agents(repo_root: Optional[Path] = None) -> dict:
27
+ """Load custom agents from .emdash/agents/ directory.
28
+
29
+ Args:
30
+ repo_root: Repository root (defaults to cwd)
31
+
32
+ Returns:
33
+ Dict mapping agent name to CustomAgent
34
+ """
35
+ from ..agents import load_agents
36
+ from ...utils.logger import log
37
+
38
+ agents_dir = (repo_root or Path.cwd()) / ".emdash" / "agents"
39
+ log.debug(f"Loading custom agents from: {agents_dir} (exists={agents_dir.exists()})")
40
+ agents = load_agents(agents_dir)
41
+ log.debug(f"Loaded custom agents: {list(agents.keys())}")
42
+ return agents
43
+
44
+
23
45
  def get_toolkit(subagent_type: str, repo_root: Path) -> "BaseToolkit":
24
46
  """Get toolkit for agent type.
25
47
 
26
48
  Args:
27
- subagent_type: Type of agent (e.g., "Explore", "Plan")
49
+ subagent_type: Type of agent (e.g., "Explore", "Plan", or custom agent name)
28
50
  repo_root: Root directory of the repository
29
51
 
30
52
  Returns:
31
53
  Toolkit instance
32
54
 
33
55
  Raises:
34
- ValueError: If agent type is not registered
56
+ ValueError: If agent type is not registered or found
57
+ """
58
+ # Check built-in agents first
59
+ if subagent_type in TOOLKIT_REGISTRY:
60
+ import importlib
61
+ module_path, class_name = TOOLKIT_REGISTRY[subagent_type].rsplit(":", 1)
62
+ module = importlib.import_module(module_path)
63
+ toolkit_class = getattr(module, class_name)
64
+ return toolkit_class(repo_root)
65
+
66
+ # Check custom agents
67
+ custom_agents = _get_custom_agents(repo_root)
68
+ if subagent_type in custom_agents:
69
+ # Custom agents use Explore toolkit by default (read-only, safe)
70
+ # This gives them: glob, grep, read_file, list_files, semantic_search
71
+ # Plus any MCP servers defined in the agent's frontmatter
72
+ import importlib
73
+ from ...utils.logger import log
74
+
75
+ custom_agent = custom_agents[subagent_type]
76
+ module_path, class_name = TOOLKIT_REGISTRY["Explore"].rsplit(":", 1)
77
+ module = importlib.import_module(module_path)
78
+ toolkit_class = getattr(module, class_name)
79
+
80
+ # Pass MCP servers if defined
81
+ mcp_servers = custom_agent.mcp_servers if custom_agent.mcp_servers else None
82
+ if mcp_servers:
83
+ log.info(f"Custom agent '{subagent_type}' has {len(mcp_servers)} MCP servers")
84
+
85
+ return toolkit_class(repo_root, mcp_servers=mcp_servers)
86
+
87
+ # Not found
88
+ available = list_agent_types(repo_root)
89
+ raise ValueError(
90
+ f"Unknown agent type: {subagent_type}. Available: {available}"
91
+ )
92
+
93
+
94
+ def list_agent_types(repo_root: Optional[Path] = None) -> list[str]:
95
+ """List all available agent types (built-in + custom).
96
+
97
+ Args:
98
+ repo_root: Repository root for finding custom agents
99
+
100
+ Returns:
101
+ List of agent type names
35
102
  """
36
- if subagent_type not in TOOLKIT_REGISTRY:
37
- available = list(TOOLKIT_REGISTRY.keys())
38
- raise ValueError(
39
- f"Unknown agent type: {subagent_type}. Available: {available}"
40
- )
103
+ # Start with built-in agents
104
+ types = list(TOOLKIT_REGISTRY.keys())
41
105
 
42
- # Import lazily to avoid circular imports
43
- import importlib
106
+ # Add custom agents
107
+ custom_agents = _get_custom_agents(repo_root)
108
+ for name in custom_agents.keys():
109
+ if name not in types:
110
+ types.append(name)
44
111
 
45
- module_path, class_name = TOOLKIT_REGISTRY[subagent_type].rsplit(":", 1)
46
- module = importlib.import_module(module_path)
47
- toolkit_class = getattr(module, class_name)
48
- return toolkit_class(repo_root)
112
+ return types
49
113
 
50
114
 
51
- def list_agent_types() -> list[str]:
52
- """List all available agent types.
115
+ def get_agents_with_descriptions(repo_root: Optional[Path] = None) -> list[dict]:
116
+ """Get all agents with their names and descriptions.
117
+
118
+ Args:
119
+ repo_root: Repository root for finding custom agents
53
120
 
54
121
  Returns:
55
- List of agent type names
122
+ List of dicts with 'name' and 'description' keys
123
+ """
124
+ from ..prompts.subagents import BUILTIN_AGENTS
125
+
126
+ agents = []
127
+
128
+ # Built-in agents
129
+ for name, description in BUILTIN_AGENTS.items():
130
+ agents.append({"name": name, "description": description})
131
+
132
+ # Custom agents
133
+ custom_agents = _get_custom_agents(repo_root)
134
+ for name, agent in custom_agents.items():
135
+ agents.append({
136
+ "name": name,
137
+ "description": agent.description or "Custom agent"
138
+ })
139
+
140
+ return agents
141
+
142
+
143
+ def get_custom_agent(name: str, repo_root: Optional[Path] = None):
144
+ """Get a specific custom agent by name.
145
+
146
+ Args:
147
+ name: Agent name
148
+ repo_root: Repository root
149
+
150
+ Returns:
151
+ CustomAgent or None
56
152
  """
57
- return list(TOOLKIT_REGISTRY.keys())
153
+ custom_agents = _get_custom_agents(repo_root)
154
+ return custom_agents.get(name)
58
155
 
59
156
 
60
157
  __all__ = [
61
158
  "get_toolkit",
62
159
  "list_agent_types",
160
+ "get_agents_with_descriptions",
161
+ "get_custom_agent",
63
162
  "TOOLKIT_REGISTRY",
64
163
  ]
@@ -2,10 +2,14 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from pathlib import Path
5
- from typing import Optional
5
+ from typing import TYPE_CHECKING, Optional
6
6
 
7
7
  from ..tools.base import BaseTool, ToolResult
8
8
 
9
+ if TYPE_CHECKING:
10
+ from ..agents import AgentMCPServerConfig
11
+ from ..mcp.manager import MCPServerManager
12
+
9
13
 
10
14
  class BaseToolkit(ABC):
11
15
  """Abstract base class for sub-agent toolkits.
@@ -15,20 +19,30 @@ class BaseToolkit(ABC):
15
19
  - Registering appropriate tools
16
20
  - Providing OpenAI function schemas
17
21
  - Executing tools by name
22
+ - Managing per-agent MCP servers (optional)
18
23
  """
19
24
 
20
25
  # List of tool names this toolkit provides (for documentation)
21
26
  TOOLS: list[str] = []
22
27
 
23
- def __init__(self, repo_root: Path):
28
+ def __init__(
29
+ self,
30
+ repo_root: Path,
31
+ mcp_servers: Optional[list["AgentMCPServerConfig"]] = None,
32
+ ):
24
33
  """Initialize the toolkit.
25
34
 
26
35
  Args:
27
36
  repo_root: Root directory of the repository
37
+ mcp_servers: Optional list of MCP server configurations for this agent
28
38
  """
29
39
  self.repo_root = repo_root.resolve()
30
40
  self._tools: dict[str, BaseTool] = {}
41
+ self._mcp_manager: Optional["MCPServerManager"] = None
42
+ self._mcp_servers_config = mcp_servers or []
43
+
31
44
  self._register_tools()
45
+ self._init_mcp_servers()
32
46
 
33
47
  @abstractmethod
34
48
  def _register_tools(self) -> None:
@@ -38,6 +52,77 @@ class BaseToolkit(ABC):
38
52
  """
39
53
  pass
40
54
 
55
+ def _init_mcp_servers(self) -> None:
56
+ """Initialize MCP servers for this agent if configured.
57
+
58
+ Creates an MCPServerManager with the agent's MCP server configs
59
+ and registers the tools from those servers. Only enabled servers
60
+ are started.
61
+ """
62
+ if not self._mcp_servers_config:
63
+ return
64
+
65
+ # Filter to only enabled servers
66
+ enabled_servers = [s for s in self._mcp_servers_config if s.enabled]
67
+ if not enabled_servers:
68
+ return
69
+
70
+ from ..mcp.config import MCPServerConfig, MCPConfigFile
71
+ from ..mcp.manager import MCPServerManager
72
+ from ..mcp.tool_factory import create_tools_from_mcp
73
+ from ...utils.logger import log
74
+
75
+ log.info(f"Initializing {len(enabled_servers)} MCP servers for agent")
76
+
77
+ # Create a temporary config file object with our servers
78
+ config = MCPConfigFile()
79
+ for server_cfg in enabled_servers:
80
+ mcp_config = MCPServerConfig(
81
+ name=server_cfg.name,
82
+ command=server_cfg.command,
83
+ args=server_cfg.args,
84
+ env=server_cfg.env,
85
+ enabled=True,
86
+ timeout=server_cfg.timeout,
87
+ )
88
+ config.add_server(mcp_config)
89
+
90
+ # Create manager with in-memory config (not from file)
91
+ self._mcp_manager = MCPServerManager(repo_root=self.repo_root)
92
+ self._mcp_manager._config = config # Inject our config directly
93
+
94
+ # Start all servers and register tools
95
+ try:
96
+ started = self._mcp_manager.start_all_enabled()
97
+ log.info(f"Started MCP servers for agent: {started}")
98
+
99
+ # Create tool wrappers and register them
100
+ mcp_tools = create_tools_from_mcp(self._mcp_manager)
101
+ for tool in mcp_tools:
102
+ self.register_tool(tool)
103
+ log.debug(f"Registered MCP tool: {tool.name}")
104
+
105
+ except Exception as e:
106
+ log.warning(f"Failed to initialize MCP servers for agent: {e}")
107
+
108
+ def shutdown(self) -> None:
109
+ """Shutdown the toolkit and cleanup resources.
110
+
111
+ Stops any running MCP servers.
112
+ """
113
+ if self._mcp_manager:
114
+ from ...utils.logger import log
115
+ log.info("Shutting down agent MCP servers")
116
+ self._mcp_manager.shutdown_all()
117
+ self._mcp_manager = None
118
+
119
+ def __del__(self):
120
+ """Cleanup on garbage collection."""
121
+ try:
122
+ self.shutdown()
123
+ except Exception:
124
+ pass
125
+
41
126
  def register_tool(self, tool: BaseTool) -> None:
42
127
  """Register a tool.
43
128
 
@@ -1,12 +1,16 @@
1
1
  """Explorer toolkit - read-only tools for fast codebase exploration."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING, Optional
4
5
 
5
6
  from .base import BaseToolkit
6
7
  from ..tools.coding import ReadFileTool, ListFilesTool
7
8
  from ..tools.search import SemanticSearchTool, GrepTool, GlobTool
8
9
  from ...utils.logger import log
9
10
 
11
+ if TYPE_CHECKING:
12
+ from ..agents import AgentMCPServerConfig
13
+
10
14
 
11
15
  class ExploreToolkit(BaseToolkit):
12
16
  """Read-only toolkit for fast codebase exploration.
@@ -16,6 +20,7 @@ class ExploreToolkit(BaseToolkit):
16
20
  - Listing directory contents
17
21
  - Searching with patterns (grep, glob)
18
22
  - Semantic code search
23
+ - MCP server tools (if configured)
19
24
 
20
25
  All tools are read-only - no file modifications allowed.
21
26
  """
@@ -28,6 +33,19 @@ class ExploreToolkit(BaseToolkit):
28
33
  "semantic_search",
29
34
  ]
30
35
 
36
+ def __init__(
37
+ self,
38
+ repo_root: Path,
39
+ mcp_servers: Optional[list["AgentMCPServerConfig"]] = None,
40
+ ):
41
+ """Initialize the explore toolkit.
42
+
43
+ Args:
44
+ repo_root: Root directory of the repository
45
+ mcp_servers: Optional MCP server configurations for this agent
46
+ """
47
+ super().__init__(repo_root, mcp_servers=mcp_servers)
48
+
31
49
  def _register_tools(self) -> None:
32
50
  """Register read-only exploration tools."""
33
51
  # File reading
@@ -5,12 +5,16 @@ The main agent (in plan mode) writes the plan to .emdash/<feature>.md.
5
5
  """
6
6
 
7
7
  from pathlib import Path
8
+ from typing import TYPE_CHECKING, Optional
8
9
 
9
10
  from .base import BaseToolkit
10
11
  from ..tools.coding import ReadFileTool, ListFilesTool
11
12
  from ..tools.search import SemanticSearchTool, GrepTool, GlobTool
12
13
  from ...utils.logger import log
13
14
 
15
+ if TYPE_CHECKING:
16
+ from ..agents import AgentMCPServerConfig
17
+
14
18
 
15
19
  class PlanToolkit(BaseToolkit):
16
20
  """Read-only toolkit for Plan subagent.
@@ -24,6 +28,7 @@ class PlanToolkit(BaseToolkit):
24
28
  - glob: Find files by pattern
25
29
  - grep: Search file contents
26
30
  - semantic_search: AI-powered code search
31
+ - MCP server tools (if configured)
27
32
  """
28
33
 
29
34
  TOOLS = [
@@ -34,6 +39,19 @@ class PlanToolkit(BaseToolkit):
34
39
  "semantic_search",
35
40
  ]
36
41
 
42
+ def __init__(
43
+ self,
44
+ repo_root: Path,
45
+ mcp_servers: Optional[list["AgentMCPServerConfig"]] = None,
46
+ ):
47
+ """Initialize the plan toolkit.
48
+
49
+ Args:
50
+ repo_root: Root directory of the repository
51
+ mcp_servers: Optional MCP server configurations for this agent
52
+ """
53
+ super().__init__(repo_root, mcp_servers=mcp_servers)
54
+
37
55
  def _register_tools(self) -> None:
38
56
  """Register read-only exploration tools."""
39
57
  # All read-only exploration tools
@@ -56,6 +56,7 @@ from .coding import (
56
56
  CodingTool,
57
57
  ReadFileTool,
58
58
  WriteToFileTool,
59
+ EditFileTool,
59
60
  ApplyDiffTool,
60
61
  DeleteFileTool,
61
62
  ListFilesTool,
@@ -124,6 +125,7 @@ __all__ = [
124
125
  "CodingTool",
125
126
  "ReadFileTool",
126
127
  "WriteToFileTool",
128
+ "EditFileTool",
127
129
  "ApplyDiffTool",
128
130
  "DeleteFileTool",
129
131
  "ListFilesTool",