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.
Files changed (60) hide show
  1. emdash_core/agent/agents.py +9 -0
  2. emdash_core/agent/background.py +481 -0
  3. emdash_core/agent/inprocess_subagent.py +70 -1
  4. emdash_core/agent/mcp/config.py +78 -2
  5. emdash_core/agent/prompts/main_agent.py +53 -1
  6. emdash_core/agent/prompts/plan_mode.py +65 -44
  7. emdash_core/agent/prompts/subagents.py +73 -1
  8. emdash_core/agent/prompts/workflow.py +179 -28
  9. emdash_core/agent/providers/models.py +1 -1
  10. emdash_core/agent/providers/openai_provider.py +10 -0
  11. emdash_core/agent/research/researcher.py +154 -45
  12. emdash_core/agent/runner/agent_runner.py +145 -19
  13. emdash_core/agent/runner/sdk_runner.py +29 -2
  14. emdash_core/agent/skills.py +81 -1
  15. emdash_core/agent/toolkit.py +87 -11
  16. emdash_core/agent/tools/__init__.py +2 -0
  17. emdash_core/agent/tools/coding.py +344 -52
  18. emdash_core/agent/tools/lsp.py +361 -0
  19. emdash_core/agent/tools/skill.py +21 -1
  20. emdash_core/agent/tools/task.py +16 -19
  21. emdash_core/agent/tools/task_output.py +262 -32
  22. emdash_core/agent/verifier/__init__.py +11 -0
  23. emdash_core/agent/verifier/manager.py +295 -0
  24. emdash_core/agent/verifier/models.py +97 -0
  25. emdash_core/{swarm/worktree_manager.py → agent/worktree.py} +19 -1
  26. emdash_core/api/agent.py +297 -2
  27. emdash_core/api/research.py +3 -3
  28. emdash_core/api/router.py +0 -4
  29. emdash_core/context/longevity.py +197 -0
  30. emdash_core/context/providers/explored_areas.py +83 -39
  31. emdash_core/context/reranker.py +35 -144
  32. emdash_core/context/simple_reranker.py +500 -0
  33. emdash_core/context/tool_relevance.py +84 -0
  34. emdash_core/core/config.py +8 -0
  35. emdash_core/graph/__init__.py +8 -1
  36. emdash_core/graph/connection.py +24 -3
  37. emdash_core/graph/writer.py +7 -1
  38. emdash_core/models/agent.py +10 -0
  39. emdash_core/server.py +1 -6
  40. emdash_core/sse/stream.py +16 -1
  41. emdash_core/utils/__init__.py +0 -2
  42. emdash_core/utils/git.py +103 -0
  43. emdash_core/utils/image.py +147 -160
  44. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/METADATA +6 -6
  45. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/RECORD +47 -52
  46. emdash_core/api/swarm.py +0 -223
  47. emdash_core/db/__init__.py +0 -67
  48. emdash_core/db/auth.py +0 -134
  49. emdash_core/db/models.py +0 -91
  50. emdash_core/db/provider.py +0 -222
  51. emdash_core/db/providers/__init__.py +0 -5
  52. emdash_core/db/providers/supabase.py +0 -452
  53. emdash_core/swarm/__init__.py +0 -17
  54. emdash_core/swarm/merge_agent.py +0 -383
  55. emdash_core/swarm/session_manager.py +0 -274
  56. emdash_core/swarm/swarm_runner.py +0 -226
  57. emdash_core/swarm/task_definition.py +0 -137
  58. emdash_core/swarm/worker_spawner.py +0 -319
  59. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/WHEEL +0 -0
  60. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/entry_points.txt +0 -0
emdash_core/db/auth.py DELETED
@@ -1,134 +0,0 @@
1
- """Supabase authentication with GitHub OAuth."""
2
-
3
- import os
4
- from dataclasses import dataclass
5
- from typing import Optional
6
-
7
- from supabase import create_client, Client
8
-
9
-
10
- @dataclass
11
- class User:
12
- """Authenticated user from Supabase Auth."""
13
-
14
- id: str
15
- email: Optional[str]
16
- github_handle: Optional[str]
17
- avatar_url: Optional[str]
18
- access_token: Optional[str] = None
19
-
20
-
21
- class AuthProvider:
22
- """Handle Supabase authentication with GitHub OAuth."""
23
-
24
- def __init__(self, url: Optional[str] = None, key: Optional[str] = None):
25
- self.url = url or os.getenv("SUPABASE_URL")
26
- self.key = key or os.getenv("SUPABASE_KEY")
27
-
28
- if not self.url or not self.key:
29
- raise ValueError("SUPABASE_URL and SUPABASE_KEY must be set")
30
-
31
- self.client: Client = create_client(self.url, self.key)
32
-
33
- def get_github_login_url(self, redirect_to: Optional[str] = None) -> str:
34
- """Get the GitHub OAuth login URL.
35
-
36
- Args:
37
- redirect_to: URL to redirect after login (for web apps)
38
-
39
- Returns:
40
- GitHub OAuth URL to redirect user to
41
- """
42
- options = {}
43
- if redirect_to:
44
- options["redirect_to"] = redirect_to
45
-
46
- response = self.client.auth.sign_in_with_oauth(
47
- {"provider": "github", "options": options}
48
- )
49
- return response.url
50
-
51
- def sign_in_with_github_token(self, access_token: str) -> Optional[User]:
52
- """Sign in with an existing GitHub access token.
53
-
54
- Args:
55
- access_token: GitHub OAuth access token
56
-
57
- Returns:
58
- User if successful, None otherwise
59
- """
60
- try:
61
- response = self.client.auth.sign_in_with_id_token(
62
- {"provider": "github", "token": access_token}
63
- )
64
- return self._session_to_user(response)
65
- except Exception:
66
- return None
67
-
68
- def get_session(self) -> Optional[User]:
69
- """Get the current authenticated user from session.
70
-
71
- Returns:
72
- User if authenticated, None otherwise
73
- """
74
- try:
75
- response = self.client.auth.get_session()
76
- if response and response.user:
77
- return self._response_to_user(response)
78
- return None
79
- except Exception:
80
- return None
81
-
82
- def sign_out(self) -> bool:
83
- """Sign out the current user.
84
-
85
- Returns:
86
- True if successful
87
- """
88
- try:
89
- self.client.auth.sign_out()
90
- return True
91
- except Exception:
92
- return False
93
-
94
- def _session_to_user(self, session) -> Optional[User]:
95
- """Convert Supabase session to User model."""
96
- if not session or not session.user:
97
- return None
98
-
99
- user_meta = session.user.user_metadata or {}
100
-
101
- return User(
102
- id=session.user.id,
103
- email=session.user.email,
104
- github_handle=user_meta.get("user_name") or user_meta.get("preferred_username"),
105
- avatar_url=user_meta.get("avatar_url"),
106
- access_token=session.access_token if hasattr(session, "access_token") else None,
107
- )
108
-
109
- def _response_to_user(self, response) -> Optional[User]:
110
- """Convert Supabase auth response to User model."""
111
- if not response or not response.user:
112
- return None
113
-
114
- user_meta = response.user.user_metadata or {}
115
-
116
- return User(
117
- id=response.user.id,
118
- email=response.user.email,
119
- github_handle=user_meta.get("user_name") or user_meta.get("preferred_username"),
120
- avatar_url=user_meta.get("avatar_url"),
121
- access_token=response.session.access_token if response.session else None,
122
- )
123
-
124
-
125
- # Singleton instance
126
- _auth_instance: Optional[AuthProvider] = None
127
-
128
-
129
- def get_auth() -> AuthProvider:
130
- """Get the auth provider instance."""
131
- global _auth_instance
132
- if _auth_instance is None:
133
- _auth_instance = AuthProvider()
134
- return _auth_instance
emdash_core/db/models.py DELETED
@@ -1,91 +0,0 @@
1
- """Data models for the database layer."""
2
-
3
- from dataclasses import dataclass, field
4
- from datetime import datetime
5
- from enum import Enum
6
- from typing import Optional
7
-
8
-
9
- class FeatureStatus(str, Enum):
10
- """Status of a feature in the workflow."""
11
-
12
- TODO = "todo"
13
- IN_DESIGN_REVIEW = "in_design_review"
14
- IN_PROGRESS = "in_progress"
15
- IN_PR = "in_pr"
16
- DONE = "done"
17
-
18
-
19
- class PRStatus(str, Enum):
20
- """Status of a pull request."""
21
-
22
- OPEN = "open"
23
- MERGED = "merged"
24
- CLOSED = "closed"
25
-
26
-
27
- @dataclass
28
- class Project:
29
- """A project in the system."""
30
-
31
- id: str
32
- name: str
33
- repo_url: Optional[str] = None
34
- owner_id: Optional[str] = None
35
- created_at: Optional[datetime] = None
36
- updated_at: Optional[datetime] = None
37
-
38
-
39
- @dataclass
40
- class TeamMember:
41
- """A team member belonging to a project."""
42
-
43
- id: str
44
- project_id: str
45
- name: str
46
- email: Optional[str] = None
47
- github_handle: Optional[str] = None
48
- role: Optional[str] = None
49
- user_id: Optional[str] = None # Links to auth.users
50
- created_at: Optional[datetime] = None
51
-
52
-
53
- @dataclass
54
- class Feature:
55
- """A feature belonging to a project."""
56
-
57
- id: str
58
- project_id: str
59
- title: str
60
- description: Optional[str] = None
61
- status: FeatureStatus = FeatureStatus.TODO
62
- spec: Optional[str] = None
63
- plan: Optional[str] = None
64
- tasks: Optional[str] = None
65
- created_at: Optional[datetime] = None
66
- updated_at: Optional[datetime] = None
67
- # Populated by joins
68
- assignees: list["TeamMember"] = field(default_factory=list)
69
- prs: list["FeaturePR"] = field(default_factory=list)
70
-
71
-
72
- @dataclass
73
- class FeatureAssignee:
74
- """Junction table for feature-assignee many-to-many relationship."""
75
-
76
- feature_id: str
77
- team_member_id: str
78
- assigned_at: Optional[datetime] = None
79
-
80
-
81
- @dataclass
82
- class FeaturePR:
83
- """A pull request linked to a feature."""
84
-
85
- id: str
86
- feature_id: str
87
- pr_url: str
88
- pr_number: int
89
- title: Optional[str] = None
90
- status: PRStatus = PRStatus.OPEN
91
- created_at: Optional[datetime] = None
@@ -1,222 +0,0 @@
1
- """Abstract database provider interface."""
2
-
3
- from abc import ABC, abstractmethod
4
- from typing import Optional
5
-
6
- from .models import (
7
- Feature,
8
- FeatureAssignee,
9
- FeaturePR,
10
- FeatureStatus,
11
- PRStatus,
12
- Project,
13
- TeamMember,
14
- )
15
-
16
-
17
- class DatabaseProvider(ABC):
18
- """Abstract base class for database providers.
19
-
20
- Implementations must provide all CRUD operations for the data models.
21
- """
22
-
23
- # -------------------------------------------------------------------------
24
- # Projects
25
- # -------------------------------------------------------------------------
26
-
27
- @abstractmethod
28
- async def create_project(
29
- self, name: str, repo_url: Optional[str] = None, owner_id: Optional[str] = None
30
- ) -> Project:
31
- """Create a new project.
32
-
33
- Args:
34
- name: Project name
35
- repo_url: GitHub repository URL
36
- owner_id: Auth user ID of the project owner
37
- """
38
- pass
39
-
40
- @abstractmethod
41
- async def get_project(self, project_id: str) -> Optional[Project]:
42
- """Get a project by ID."""
43
- pass
44
-
45
- @abstractmethod
46
- async def get_project_by_name(self, name: str) -> Optional[Project]:
47
- """Get a project by name."""
48
- pass
49
-
50
- @abstractmethod
51
- async def list_projects(self) -> list[Project]:
52
- """List all projects."""
53
- pass
54
-
55
- @abstractmethod
56
- async def update_project(
57
- self, project_id: str, name: Optional[str] = None, repo_url: Optional[str] = None
58
- ) -> Optional[Project]:
59
- """Update a project."""
60
- pass
61
-
62
- @abstractmethod
63
- async def delete_project(self, project_id: str) -> bool:
64
- """Delete a project and all related data."""
65
- pass
66
-
67
- # -------------------------------------------------------------------------
68
- # Team Members
69
- # -------------------------------------------------------------------------
70
-
71
- @abstractmethod
72
- async def create_team_member(
73
- self,
74
- project_id: str,
75
- name: str,
76
- email: Optional[str] = None,
77
- github_handle: Optional[str] = None,
78
- role: Optional[str] = None,
79
- user_id: Optional[str] = None,
80
- ) -> TeamMember:
81
- """Create a new team member.
82
-
83
- Args:
84
- project_id: Project this member belongs to
85
- name: Display name
86
- email: Email address
87
- github_handle: GitHub username
88
- role: Role in the project
89
- user_id: Auth user ID (links to Supabase auth.users)
90
- """
91
- pass
92
-
93
- @abstractmethod
94
- async def get_team_member(self, member_id: str) -> Optional[TeamMember]:
95
- """Get a team member by ID."""
96
- pass
97
-
98
- @abstractmethod
99
- async def list_team_members(self, project_id: str) -> list[TeamMember]:
100
- """List all team members for a project."""
101
- pass
102
-
103
- @abstractmethod
104
- async def update_team_member(
105
- self,
106
- member_id: str,
107
- name: Optional[str] = None,
108
- email: Optional[str] = None,
109
- github_handle: Optional[str] = None,
110
- role: Optional[str] = None,
111
- ) -> Optional[TeamMember]:
112
- """Update a team member."""
113
- pass
114
-
115
- @abstractmethod
116
- async def delete_team_member(self, member_id: str) -> bool:
117
- """Delete a team member."""
118
- pass
119
-
120
- # -------------------------------------------------------------------------
121
- # Features
122
- # -------------------------------------------------------------------------
123
-
124
- @abstractmethod
125
- async def create_feature(
126
- self,
127
- project_id: str,
128
- title: str,
129
- description: Optional[str] = None,
130
- status: FeatureStatus = FeatureStatus.TODO,
131
- spec: Optional[str] = None,
132
- plan: Optional[str] = None,
133
- tasks: Optional[str] = None,
134
- ) -> Feature:
135
- """Create a new feature."""
136
- pass
137
-
138
- @abstractmethod
139
- async def get_feature(self, feature_id: str, include_relations: bool = True) -> Optional[Feature]:
140
- """Get a feature by ID, optionally including assignees and PRs."""
141
- pass
142
-
143
- @abstractmethod
144
- async def list_features(
145
- self, project_id: str, status: Optional[FeatureStatus] = None, include_relations: bool = False
146
- ) -> list[Feature]:
147
- """List features for a project, optionally filtered by status."""
148
- pass
149
-
150
- @abstractmethod
151
- async def update_feature(
152
- self,
153
- feature_id: str,
154
- title: Optional[str] = None,
155
- description: Optional[str] = None,
156
- status: Optional[FeatureStatus] = None,
157
- spec: Optional[str] = None,
158
- plan: Optional[str] = None,
159
- tasks: Optional[str] = None,
160
- ) -> Optional[Feature]:
161
- """Update a feature."""
162
- pass
163
-
164
- @abstractmethod
165
- async def delete_feature(self, feature_id: str) -> bool:
166
- """Delete a feature and all related data."""
167
- pass
168
-
169
- # -------------------------------------------------------------------------
170
- # Feature Assignees
171
- # -------------------------------------------------------------------------
172
-
173
- @abstractmethod
174
- async def assign_feature(self, feature_id: str, team_member_id: str) -> FeatureAssignee:
175
- """Assign a team member to a feature."""
176
- pass
177
-
178
- @abstractmethod
179
- async def unassign_feature(self, feature_id: str, team_member_id: str) -> bool:
180
- """Remove a team member from a feature."""
181
- pass
182
-
183
- @abstractmethod
184
- async def get_feature_assignees(self, feature_id: str) -> list[TeamMember]:
185
- """Get all assignees for a feature."""
186
- pass
187
-
188
- # -------------------------------------------------------------------------
189
- # Feature PRs
190
- # -------------------------------------------------------------------------
191
-
192
- @abstractmethod
193
- async def add_feature_pr(
194
- self,
195
- feature_id: str,
196
- pr_url: str,
197
- pr_number: int,
198
- title: Optional[str] = None,
199
- status: PRStatus = PRStatus.OPEN,
200
- ) -> FeaturePR:
201
- """Add a PR to a feature."""
202
- pass
203
-
204
- @abstractmethod
205
- async def update_feature_pr(
206
- self,
207
- pr_id: str,
208
- title: Optional[str] = None,
209
- status: Optional[PRStatus] = None,
210
- ) -> Optional[FeaturePR]:
211
- """Update a feature PR."""
212
- pass
213
-
214
- @abstractmethod
215
- async def remove_feature_pr(self, pr_id: str) -> bool:
216
- """Remove a PR from a feature."""
217
- pass
218
-
219
- @abstractmethod
220
- async def get_feature_prs(self, feature_id: str) -> list[FeaturePR]:
221
- """Get all PRs for a feature."""
222
- pass
@@ -1,5 +0,0 @@
1
- """Database provider implementations."""
2
-
3
- from .supabase import SupabaseProvider
4
-
5
- __all__ = ["SupabaseProvider"]