emdash-core 0.1.7__py3-none-any.whl → 0.1.33__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/__init__.py +6 -1
- emdash_core/agent/__init__.py +4 -0
- emdash_core/agent/events.py +52 -1
- emdash_core/agent/inprocess_subagent.py +123 -10
- emdash_core/agent/prompts/__init__.py +6 -0
- emdash_core/agent/prompts/main_agent.py +53 -3
- emdash_core/agent/prompts/plan_mode.py +255 -0
- emdash_core/agent/prompts/subagents.py +84 -16
- emdash_core/agent/prompts/workflow.py +270 -56
- emdash_core/agent/providers/base.py +4 -0
- emdash_core/agent/providers/factory.py +2 -2
- emdash_core/agent/providers/models.py +7 -0
- emdash_core/agent/providers/openai_provider.py +137 -13
- emdash_core/agent/runner/__init__.py +49 -0
- emdash_core/agent/runner/agent_runner.py +753 -0
- emdash_core/agent/runner/context.py +451 -0
- emdash_core/agent/runner/factory.py +108 -0
- emdash_core/agent/runner/plan.py +217 -0
- emdash_core/agent/runner/sdk_runner.py +324 -0
- emdash_core/agent/runner/utils.py +67 -0
- emdash_core/agent/skills.py +358 -0
- emdash_core/agent/toolkit.py +85 -5
- emdash_core/agent/toolkits/plan.py +9 -11
- emdash_core/agent/tools/__init__.py +3 -2
- emdash_core/agent/tools/coding.py +48 -4
- emdash_core/agent/tools/modes.py +207 -55
- emdash_core/agent/tools/search.py +4 -0
- emdash_core/agent/tools/skill.py +193 -0
- emdash_core/agent/tools/spec.py +61 -94
- emdash_core/agent/tools/task.py +41 -2
- emdash_core/agent/tools/tasks.py +15 -78
- emdash_core/api/agent.py +562 -8
- emdash_core/api/index.py +1 -1
- emdash_core/api/projectmd.py +4 -2
- emdash_core/api/router.py +2 -0
- emdash_core/api/skills.py +241 -0
- emdash_core/checkpoint/__init__.py +40 -0
- emdash_core/checkpoint/cli.py +175 -0
- emdash_core/checkpoint/git_operations.py +250 -0
- emdash_core/checkpoint/manager.py +231 -0
- emdash_core/checkpoint/models.py +107 -0
- emdash_core/checkpoint/storage.py +201 -0
- emdash_core/config.py +1 -1
- emdash_core/core/config.py +18 -2
- emdash_core/graph/schema.py +5 -5
- emdash_core/ingestion/orchestrator.py +19 -10
- emdash_core/models/agent.py +1 -1
- emdash_core/server.py +42 -0
- emdash_core/skills/frontend-design/SKILL.md +56 -0
- emdash_core/sse/stream.py +5 -0
- {emdash_core-0.1.7.dist-info → emdash_core-0.1.33.dist-info}/METADATA +2 -2
- {emdash_core-0.1.7.dist-info → emdash_core-0.1.33.dist-info}/RECORD +54 -37
- {emdash_core-0.1.7.dist-info → emdash_core-0.1.33.dist-info}/entry_points.txt +1 -0
- emdash_core/agent/runner.py +0 -601
- {emdash_core-0.1.7.dist-info → emdash_core-0.1.33.dist-info}/WHEEL +0 -0
emdash_core/__init__.py
CHANGED
emdash_core/agent/__init__.py
CHANGED
|
@@ -19,6 +19,9 @@ def __getattr__(name: str):
|
|
|
19
19
|
elif name == "AgentRunner":
|
|
20
20
|
from .runner import AgentRunner
|
|
21
21
|
return AgentRunner
|
|
22
|
+
elif name == "SafeJSONEncoder":
|
|
23
|
+
from .runner import SafeJSONEncoder
|
|
24
|
+
return SafeJSONEncoder
|
|
22
25
|
elif name == "ToolResult":
|
|
23
26
|
from .tools.base import ToolResult
|
|
24
27
|
return ToolResult
|
|
@@ -32,6 +35,7 @@ __all__ = [
|
|
|
32
35
|
"AgentToolkit",
|
|
33
36
|
"AgentSession",
|
|
34
37
|
"AgentRunner",
|
|
38
|
+
"SafeJSONEncoder",
|
|
35
39
|
"ToolResult",
|
|
36
40
|
"ToolCategory",
|
|
37
41
|
]
|
emdash_core/agent/events.py
CHANGED
|
@@ -17,6 +17,10 @@ class EventType(Enum):
|
|
|
17
17
|
TOOL_START = "tool_start"
|
|
18
18
|
TOOL_RESULT = "tool_result"
|
|
19
19
|
|
|
20
|
+
# Sub-agent lifecycle
|
|
21
|
+
SUBAGENT_START = "subagent_start"
|
|
22
|
+
SUBAGENT_END = "subagent_end"
|
|
23
|
+
|
|
20
24
|
# Agent thinking/progress
|
|
21
25
|
THINKING = "thinking"
|
|
22
26
|
PROGRESS = "progress"
|
|
@@ -24,10 +28,13 @@ class EventType(Enum):
|
|
|
24
28
|
# Output
|
|
25
29
|
RESPONSE = "response"
|
|
26
30
|
PARTIAL_RESPONSE = "partial_response"
|
|
31
|
+
ASSISTANT_TEXT = "assistant_text" # Intermediate text between tool calls
|
|
27
32
|
|
|
28
33
|
# Interaction
|
|
29
34
|
CLARIFICATION = "clarification"
|
|
30
35
|
CLARIFICATION_RESPONSE = "clarification_response"
|
|
36
|
+
PLAN_MODE_REQUESTED = "plan_mode_requested"
|
|
37
|
+
PLAN_SUBMITTED = "plan_submitted"
|
|
31
38
|
|
|
32
39
|
# Errors
|
|
33
40
|
ERROR = "error"
|
|
@@ -143,16 +150,23 @@ class AgentEventEmitter:
|
|
|
143
150
|
|
|
144
151
|
return event
|
|
145
152
|
|
|
146
|
-
def emit_tool_start(
|
|
153
|
+
def emit_tool_start(
|
|
154
|
+
self,
|
|
155
|
+
name: str,
|
|
156
|
+
args: dict[str, Any] | None = None,
|
|
157
|
+
tool_id: str | None = None,
|
|
158
|
+
) -> AgentEvent:
|
|
147
159
|
"""Convenience method to emit a tool start event.
|
|
148
160
|
|
|
149
161
|
Args:
|
|
150
162
|
name: Tool name
|
|
151
163
|
args: Tool arguments
|
|
164
|
+
tool_id: Unique ID for this tool call (for matching with result)
|
|
152
165
|
"""
|
|
153
166
|
return self.emit(EventType.TOOL_START, {
|
|
154
167
|
"name": name,
|
|
155
168
|
"args": args or {},
|
|
169
|
+
"tool_id": tool_id,
|
|
156
170
|
})
|
|
157
171
|
|
|
158
172
|
def emit_tool_result(
|
|
@@ -161,6 +175,7 @@ class AgentEventEmitter:
|
|
|
161
175
|
success: bool,
|
|
162
176
|
summary: str | None = None,
|
|
163
177
|
data: dict[str, Any] | None = None,
|
|
178
|
+
tool_id: str | None = None,
|
|
164
179
|
) -> AgentEvent:
|
|
165
180
|
"""Convenience method to emit a tool result event.
|
|
166
181
|
|
|
@@ -169,12 +184,14 @@ class AgentEventEmitter:
|
|
|
169
184
|
success: Whether the tool succeeded
|
|
170
185
|
summary: Brief summary of the result
|
|
171
186
|
data: Full result data (may be truncated by handlers)
|
|
187
|
+
tool_id: Unique ID for this tool call (for matching with start)
|
|
172
188
|
"""
|
|
173
189
|
return self.emit(EventType.TOOL_RESULT, {
|
|
174
190
|
"name": name,
|
|
175
191
|
"success": success,
|
|
176
192
|
"summary": summary,
|
|
177
193
|
"data": data,
|
|
194
|
+
"tool_id": tool_id,
|
|
178
195
|
})
|
|
179
196
|
|
|
180
197
|
def emit_thinking(self, message: str) -> AgentEvent:
|
|
@@ -207,6 +224,14 @@ class AgentEventEmitter:
|
|
|
207
224
|
event_type = EventType.RESPONSE if is_final else EventType.PARTIAL_RESPONSE
|
|
208
225
|
return self.emit(event_type, {"content": content})
|
|
209
226
|
|
|
227
|
+
def emit_assistant_text(self, content: str) -> AgentEvent:
|
|
228
|
+
"""Emit intermediate assistant text (shown between tool calls).
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
content: Text content from assistant (e.g., "Let me read the file...")
|
|
232
|
+
"""
|
|
233
|
+
return self.emit(EventType.ASSISTANT_TEXT, {"content": content})
|
|
234
|
+
|
|
210
235
|
def emit_clarification(
|
|
211
236
|
self,
|
|
212
237
|
question: str,
|
|
@@ -226,6 +251,32 @@ class AgentEventEmitter:
|
|
|
226
251
|
"options": options,
|
|
227
252
|
})
|
|
228
253
|
|
|
254
|
+
def emit_plan_mode_requested(
|
|
255
|
+
self,
|
|
256
|
+
reason: str,
|
|
257
|
+
) -> AgentEvent:
|
|
258
|
+
"""Convenience method to emit a plan mode request event.
|
|
259
|
+
|
|
260
|
+
This is emitted when the agent calls enter_plan_mode tool,
|
|
261
|
+
requesting user consent to enter plan mode.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
reason: Why the agent wants to enter plan mode
|
|
265
|
+
"""
|
|
266
|
+
return self.emit(EventType.PLAN_MODE_REQUESTED, {
|
|
267
|
+
"reason": reason,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
def emit_plan_submitted(self, plan: str) -> AgentEvent:
|
|
271
|
+
"""Convenience method to emit a plan submission event.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
plan: The implementation plan as markdown
|
|
275
|
+
"""
|
|
276
|
+
return self.emit(EventType.PLAN_SUBMITTED, {
|
|
277
|
+
"plan": plan,
|
|
278
|
+
})
|
|
279
|
+
|
|
229
280
|
def emit_error(self, message: str, details: str | None = None) -> AgentEvent:
|
|
230
281
|
"""Convenience method to emit an error.
|
|
231
282
|
|
|
@@ -62,6 +62,7 @@ class InProcessSubAgent:
|
|
|
62
62
|
model: Optional[str] = None,
|
|
63
63
|
max_turns: int = 10,
|
|
64
64
|
agent_id: Optional[str] = None,
|
|
65
|
+
thoroughness: str = "medium",
|
|
65
66
|
):
|
|
66
67
|
"""Initialize in-process sub-agent.
|
|
67
68
|
|
|
@@ -72,12 +73,14 @@ class InProcessSubAgent:
|
|
|
72
73
|
model: Model to use (defaults to fast model)
|
|
73
74
|
max_turns: Maximum iterations
|
|
74
75
|
agent_id: Optional agent ID (generated if not provided)
|
|
76
|
+
thoroughness: Search thoroughness level (quick, medium, thorough)
|
|
75
77
|
"""
|
|
76
78
|
self.subagent_type = subagent_type
|
|
77
79
|
self.repo_root = repo_root.resolve()
|
|
78
80
|
self.emitter = emitter
|
|
79
81
|
self.max_turns = max_turns
|
|
80
82
|
self.agent_id = agent_id or str(uuid.uuid4())[:8]
|
|
83
|
+
self.thoroughness = thoroughness
|
|
81
84
|
|
|
82
85
|
# Get toolkit for this agent type
|
|
83
86
|
self.toolkit = get_toolkit(subagent_type, repo_root)
|
|
@@ -86,13 +89,40 @@ class InProcessSubAgent:
|
|
|
86
89
|
model_name = model or DEFAULT_MODEL
|
|
87
90
|
self.provider = get_provider(model_name)
|
|
88
91
|
|
|
89
|
-
# Get system prompt
|
|
90
|
-
|
|
92
|
+
# Get system prompt and inject thoroughness level
|
|
93
|
+
base_prompt = get_subagent_prompt(subagent_type)
|
|
94
|
+
self.system_prompt = self._inject_thoroughness(base_prompt)
|
|
91
95
|
|
|
92
96
|
# Tracking
|
|
93
97
|
self.files_explored: set[str] = set()
|
|
94
98
|
self.tools_used: list[str] = []
|
|
95
99
|
|
|
100
|
+
def _inject_thoroughness(self, prompt: str) -> str:
|
|
101
|
+
"""Inject thoroughness level into the system prompt."""
|
|
102
|
+
thoroughness_guidance = {
|
|
103
|
+
"quick": """
|
|
104
|
+
## Thoroughness Level: QUICK
|
|
105
|
+
- Do basic searches only - find the most obvious matches first
|
|
106
|
+
- Stop after finding 2-3 relevant files
|
|
107
|
+
- Don't explore deeply - just locate the key files
|
|
108
|
+
- Prioritize speed over completeness""",
|
|
109
|
+
"medium": """
|
|
110
|
+
## Thoroughness Level: MEDIUM
|
|
111
|
+
- Do moderate exploration - check multiple locations
|
|
112
|
+
- Follow 1-2 levels of imports/references
|
|
113
|
+
- Balance speed with coverage
|
|
114
|
+
- Stop when you have reasonable confidence""",
|
|
115
|
+
"thorough": """
|
|
116
|
+
## Thoroughness Level: THOROUGH
|
|
117
|
+
- Do comprehensive analysis across the codebase
|
|
118
|
+
- Check multiple naming conventions and locations
|
|
119
|
+
- Follow import chains and cross-references deeply
|
|
120
|
+
- Explore edge cases and alternative implementations
|
|
121
|
+
- Only stop when you've exhausted relevant areas""",
|
|
122
|
+
}
|
|
123
|
+
guidance = thoroughness_guidance.get(self.thoroughness, thoroughness_guidance["medium"])
|
|
124
|
+
return prompt + "\n" + guidance
|
|
125
|
+
|
|
96
126
|
def _emit(self, event_type: str, **data) -> None:
|
|
97
127
|
"""Emit event with agent tagging.
|
|
98
128
|
|
|
@@ -115,6 +145,50 @@ class InProcessSubAgent:
|
|
|
115
145
|
if event_type in event_map:
|
|
116
146
|
self.emitter.emit(event_map[event_type], data)
|
|
117
147
|
|
|
148
|
+
def _get_project_context(self) -> str:
|
|
149
|
+
"""Get PROJECT.md and directory structure for context."""
|
|
150
|
+
context_parts = []
|
|
151
|
+
|
|
152
|
+
# Try to read PROJECT.md
|
|
153
|
+
project_md = self.repo_root / "PROJECT.md"
|
|
154
|
+
if project_md.exists():
|
|
155
|
+
try:
|
|
156
|
+
content = project_md.read_text()
|
|
157
|
+
# Truncate if too long
|
|
158
|
+
if len(content) > 8000:
|
|
159
|
+
content = content[:8000] + "\n...[truncated]"
|
|
160
|
+
context_parts.append(f"## PROJECT.md\n\n{content}")
|
|
161
|
+
except Exception as e:
|
|
162
|
+
log.debug(f"Could not read PROJECT.md: {e}")
|
|
163
|
+
|
|
164
|
+
# Get directory structure (top 2 levels)
|
|
165
|
+
try:
|
|
166
|
+
structure_lines = ["## Project Structure\n"]
|
|
167
|
+
for item in sorted(self.repo_root.iterdir()):
|
|
168
|
+
if item.name.startswith(".") and item.name not in (".emdash",):
|
|
169
|
+
continue
|
|
170
|
+
if item.name in ("node_modules", "__pycache__", ".git", "dist", "build", ".venv", "venv"):
|
|
171
|
+
continue
|
|
172
|
+
if item.is_dir():
|
|
173
|
+
structure_lines.append(f" {item.name}/")
|
|
174
|
+
# Show first level contents
|
|
175
|
+
try:
|
|
176
|
+
for subitem in sorted(item.iterdir())[:10]:
|
|
177
|
+
if not subitem.name.startswith("."):
|
|
178
|
+
suffix = "/" if subitem.is_dir() else ""
|
|
179
|
+
structure_lines.append(f" {subitem.name}{suffix}")
|
|
180
|
+
if len(list(item.iterdir())) > 10:
|
|
181
|
+
structure_lines.append(f" ...")
|
|
182
|
+
except PermissionError:
|
|
183
|
+
pass
|
|
184
|
+
else:
|
|
185
|
+
structure_lines.append(f" {item.name}")
|
|
186
|
+
context_parts.append("\n".join(structure_lines))
|
|
187
|
+
except Exception as e:
|
|
188
|
+
log.debug(f"Could not get directory structure: {e}")
|
|
189
|
+
|
|
190
|
+
return "\n\n".join(context_parts) if context_parts else ""
|
|
191
|
+
|
|
118
192
|
def run(self, prompt: str) -> SubAgentResult:
|
|
119
193
|
"""Execute the task and return results.
|
|
120
194
|
|
|
@@ -130,6 +204,19 @@ class InProcessSubAgent:
|
|
|
130
204
|
last_content = ""
|
|
131
205
|
error = None
|
|
132
206
|
|
|
207
|
+
# For Plan agents, inject project context
|
|
208
|
+
if self.subagent_type == "Plan":
|
|
209
|
+
context = self._get_project_context()
|
|
210
|
+
if context:
|
|
211
|
+
prompt = f"""Here is context about the project:
|
|
212
|
+
|
|
213
|
+
{context}
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
Now, your task:
|
|
218
|
+
{prompt}"""
|
|
219
|
+
|
|
133
220
|
# Add user message
|
|
134
221
|
messages.append({"role": "user", "content": prompt})
|
|
135
222
|
|
|
@@ -265,6 +352,7 @@ def run_subagent(
|
|
|
265
352
|
emitter=None,
|
|
266
353
|
model: Optional[str] = None,
|
|
267
354
|
max_turns: int = 10,
|
|
355
|
+
thoroughness: str = "medium",
|
|
268
356
|
) -> SubAgentResult:
|
|
269
357
|
"""Run a sub-agent synchronously.
|
|
270
358
|
|
|
@@ -275,18 +363,38 @@ def run_subagent(
|
|
|
275
363
|
emitter: Event emitter
|
|
276
364
|
model: Model to use
|
|
277
365
|
max_turns: Max iterations
|
|
366
|
+
thoroughness: Search thoroughness level (quick, medium, thorough)
|
|
278
367
|
|
|
279
368
|
Returns:
|
|
280
369
|
SubAgentResult
|
|
281
370
|
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
371
|
+
try:
|
|
372
|
+
agent = InProcessSubAgent(
|
|
373
|
+
subagent_type=subagent_type,
|
|
374
|
+
repo_root=repo_root,
|
|
375
|
+
emitter=emitter,
|
|
376
|
+
model=model,
|
|
377
|
+
max_turns=max_turns,
|
|
378
|
+
thoroughness=thoroughness,
|
|
379
|
+
)
|
|
380
|
+
return agent.run(prompt)
|
|
381
|
+
except Exception as e:
|
|
382
|
+
# Return a proper error result instead of letting the exception propagate
|
|
383
|
+
# This prevents 0.0s "silent" failures and gives clear error messages
|
|
384
|
+
log.error(f"Failed to create sub-agent: {e}")
|
|
385
|
+
return SubAgentResult(
|
|
386
|
+
success=False,
|
|
387
|
+
agent_type=subagent_type,
|
|
388
|
+
agent_id="init-failed",
|
|
389
|
+
task=prompt,
|
|
390
|
+
summary="",
|
|
391
|
+
files_explored=[],
|
|
392
|
+
findings=[],
|
|
393
|
+
iterations=0,
|
|
394
|
+
tools_used=[],
|
|
395
|
+
execution_time=0.0,
|
|
396
|
+
error=f"Sub-agent initialization failed: {e}",
|
|
397
|
+
)
|
|
290
398
|
|
|
291
399
|
|
|
292
400
|
def run_subagent_async(
|
|
@@ -296,6 +404,7 @@ def run_subagent_async(
|
|
|
296
404
|
emitter=None,
|
|
297
405
|
model: Optional[str] = None,
|
|
298
406
|
max_turns: int = 10,
|
|
407
|
+
thoroughness: str = "medium",
|
|
299
408
|
) -> Future[SubAgentResult]:
|
|
300
409
|
"""Run a sub-agent asynchronously (returns Future).
|
|
301
410
|
|
|
@@ -306,6 +415,7 @@ def run_subagent_async(
|
|
|
306
415
|
emitter: Event emitter
|
|
307
416
|
model: Model to use
|
|
308
417
|
max_turns: Max iterations
|
|
418
|
+
thoroughness: Search thoroughness level (quick, medium, thorough)
|
|
309
419
|
|
|
310
420
|
Returns:
|
|
311
421
|
Future[SubAgentResult] - call .result() to get result
|
|
@@ -319,6 +429,7 @@ def run_subagent_async(
|
|
|
319
429
|
emitter=emitter,
|
|
320
430
|
model=model,
|
|
321
431
|
max_turns=max_turns,
|
|
432
|
+
thoroughness=thoroughness,
|
|
322
433
|
)
|
|
323
434
|
|
|
324
435
|
|
|
@@ -335,6 +446,7 @@ def run_subagents_parallel(
|
|
|
335
446
|
- prompt: str
|
|
336
447
|
- model: str (optional)
|
|
337
448
|
- max_turns: int (optional)
|
|
449
|
+
- thoroughness: str (optional, default "medium")
|
|
338
450
|
repo_root: Repository root
|
|
339
451
|
emitter: Shared event emitter
|
|
340
452
|
|
|
@@ -350,6 +462,7 @@ def run_subagents_parallel(
|
|
|
350
462
|
emitter=emitter,
|
|
351
463
|
model=task.get("model"),
|
|
352
464
|
max_turns=task.get("max_turns", 10),
|
|
465
|
+
thoroughness=task.get("thoroughness", "medium"),
|
|
353
466
|
)
|
|
354
467
|
futures.append(future)
|
|
355
468
|
|
|
@@ -11,9 +11,12 @@ from .workflow import (
|
|
|
11
11
|
EXPLORATION_OUTPUT_FORMAT,
|
|
12
12
|
PLAN_TEMPLATE,
|
|
13
13
|
SIZING_GUIDELINES,
|
|
14
|
+
PARALLEL_EXECUTION,
|
|
14
15
|
)
|
|
15
16
|
from .main_agent import (
|
|
16
17
|
BASE_SYSTEM_PROMPT,
|
|
18
|
+
CODE_MODE_PROMPT,
|
|
19
|
+
PLAN_MODE_PROMPT,
|
|
17
20
|
build_system_prompt,
|
|
18
21
|
build_tools_section,
|
|
19
22
|
)
|
|
@@ -28,8 +31,11 @@ __all__ = [
|
|
|
28
31
|
"EXPLORATION_OUTPUT_FORMAT",
|
|
29
32
|
"PLAN_TEMPLATE",
|
|
30
33
|
"SIZING_GUIDELINES",
|
|
34
|
+
"PARALLEL_EXECUTION",
|
|
31
35
|
# Main agent
|
|
32
36
|
"BASE_SYSTEM_PROMPT",
|
|
37
|
+
"CODE_MODE_PROMPT",
|
|
38
|
+
"PLAN_MODE_PROMPT",
|
|
33
39
|
"build_system_prompt",
|
|
34
40
|
"build_tools_section",
|
|
35
41
|
# Sub-agents
|
|
@@ -8,13 +8,23 @@ from .workflow import (
|
|
|
8
8
|
WORKFLOW_PATTERNS,
|
|
9
9
|
EXPLORATION_STRATEGY,
|
|
10
10
|
OUTPUT_GUIDELINES,
|
|
11
|
+
PARALLEL_EXECUTION,
|
|
12
|
+
TODO_LIST_GUIDANCE,
|
|
11
13
|
)
|
|
12
14
|
|
|
13
15
|
# Base system prompt template with placeholder for tools
|
|
14
|
-
|
|
16
|
+
_BASE_PROMPT = """You are a code exploration and implementation assistant. You orchestrate focused sub-agents for exploration while maintaining the high-level view.
|
|
15
17
|
|
|
16
18
|
{tools_section}
|
|
17
|
-
"""
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Main agent system prompt - same for both code and plan modes
|
|
22
|
+
# Main agent is always an orchestrator that delegates to subagents
|
|
23
|
+
BASE_SYSTEM_PROMPT = _BASE_PROMPT + WORKFLOW_PATTERNS + PARALLEL_EXECUTION + EXPLORATION_STRATEGY + TODO_LIST_GUIDANCE + OUTPUT_GUIDELINES
|
|
24
|
+
|
|
25
|
+
# Legacy aliases
|
|
26
|
+
CODE_MODE_PROMPT = BASE_SYSTEM_PROMPT
|
|
27
|
+
PLAN_MODE_PROMPT = BASE_SYSTEM_PROMPT
|
|
18
28
|
|
|
19
29
|
|
|
20
30
|
def build_system_prompt(toolkit) -> str:
|
|
@@ -27,7 +37,47 @@ def build_system_prompt(toolkit) -> str:
|
|
|
27
37
|
Complete system prompt string
|
|
28
38
|
"""
|
|
29
39
|
tools_section = build_tools_section(toolkit)
|
|
30
|
-
|
|
40
|
+
skills_section = build_skills_section()
|
|
41
|
+
rules_section = build_rules_section()
|
|
42
|
+
|
|
43
|
+
# Main agent always uses the same prompt - it orchestrates and delegates
|
|
44
|
+
prompt = BASE_SYSTEM_PROMPT.format(tools_section=tools_section)
|
|
45
|
+
|
|
46
|
+
# Add rules section if there are rules defined
|
|
47
|
+
if rules_section:
|
|
48
|
+
prompt += "\n" + rules_section
|
|
49
|
+
|
|
50
|
+
# Add skills section if there are skills available
|
|
51
|
+
if skills_section:
|
|
52
|
+
prompt += "\n" + skills_section
|
|
53
|
+
|
|
54
|
+
return prompt
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_rules_section() -> str:
|
|
58
|
+
"""Build the rules section of the system prompt.
|
|
59
|
+
|
|
60
|
+
Loads rules from .emdash/rules/*.md files.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Formatted string with project rules, or empty string if none
|
|
64
|
+
"""
|
|
65
|
+
from ..rules import load_rules, format_rules_for_prompt
|
|
66
|
+
|
|
67
|
+
rules = load_rules()
|
|
68
|
+
return format_rules_for_prompt(rules)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_skills_section() -> str:
|
|
72
|
+
"""Build the skills section of the system prompt.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Formatted string with available skills, or empty string if none
|
|
76
|
+
"""
|
|
77
|
+
from ..skills import SkillRegistry
|
|
78
|
+
|
|
79
|
+
registry = SkillRegistry.get_instance()
|
|
80
|
+
return registry.get_skills_for_prompt()
|
|
31
81
|
|
|
32
82
|
|
|
33
83
|
def build_tools_section(toolkit) -> str:
|