emdash-core 0.1.7__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 +3 -0
- emdash_core/agent/__init__.py +37 -0
- emdash_core/agent/agents.py +225 -0
- emdash_core/agent/code_reviewer.py +476 -0
- emdash_core/agent/compaction.py +143 -0
- emdash_core/agent/context_manager.py +140 -0
- emdash_core/agent/events.py +338 -0
- emdash_core/agent/handlers.py +224 -0
- emdash_core/agent/inprocess_subagent.py +377 -0
- emdash_core/agent/mcp/__init__.py +50 -0
- emdash_core/agent/mcp/client.py +346 -0
- emdash_core/agent/mcp/config.py +302 -0
- emdash_core/agent/mcp/manager.py +496 -0
- emdash_core/agent/mcp/tool_factory.py +213 -0
- emdash_core/agent/prompts/__init__.py +38 -0
- emdash_core/agent/prompts/main_agent.py +104 -0
- emdash_core/agent/prompts/subagents.py +131 -0
- emdash_core/agent/prompts/workflow.py +136 -0
- emdash_core/agent/providers/__init__.py +34 -0
- emdash_core/agent/providers/base.py +143 -0
- emdash_core/agent/providers/factory.py +80 -0
- emdash_core/agent/providers/models.py +220 -0
- emdash_core/agent/providers/openai_provider.py +463 -0
- emdash_core/agent/providers/transformers_provider.py +217 -0
- emdash_core/agent/research/__init__.py +81 -0
- emdash_core/agent/research/agent.py +143 -0
- emdash_core/agent/research/controller.py +254 -0
- emdash_core/agent/research/critic.py +428 -0
- emdash_core/agent/research/macros.py +469 -0
- emdash_core/agent/research/planner.py +449 -0
- emdash_core/agent/research/researcher.py +436 -0
- emdash_core/agent/research/state.py +523 -0
- emdash_core/agent/research/synthesizer.py +594 -0
- emdash_core/agent/reviewer_profile.py +475 -0
- emdash_core/agent/rules.py +123 -0
- emdash_core/agent/runner.py +601 -0
- emdash_core/agent/session.py +262 -0
- emdash_core/agent/spec_schema.py +66 -0
- emdash_core/agent/specification.py +479 -0
- emdash_core/agent/subagent.py +397 -0
- emdash_core/agent/subagent_prompts.py +13 -0
- emdash_core/agent/toolkit.py +482 -0
- emdash_core/agent/toolkits/__init__.py +64 -0
- emdash_core/agent/toolkits/base.py +96 -0
- emdash_core/agent/toolkits/explore.py +47 -0
- emdash_core/agent/toolkits/plan.py +55 -0
- emdash_core/agent/tools/__init__.py +141 -0
- emdash_core/agent/tools/analytics.py +436 -0
- emdash_core/agent/tools/base.py +131 -0
- emdash_core/agent/tools/coding.py +484 -0
- emdash_core/agent/tools/github_mcp.py +592 -0
- emdash_core/agent/tools/history.py +13 -0
- emdash_core/agent/tools/modes.py +153 -0
- emdash_core/agent/tools/plan.py +206 -0
- emdash_core/agent/tools/plan_write.py +135 -0
- emdash_core/agent/tools/search.py +412 -0
- emdash_core/agent/tools/spec.py +341 -0
- emdash_core/agent/tools/task.py +262 -0
- emdash_core/agent/tools/task_output.py +204 -0
- emdash_core/agent/tools/tasks.py +454 -0
- emdash_core/agent/tools/traversal.py +588 -0
- emdash_core/agent/tools/web.py +179 -0
- emdash_core/analytics/__init__.py +5 -0
- emdash_core/analytics/engine.py +1286 -0
- emdash_core/api/__init__.py +5 -0
- emdash_core/api/agent.py +308 -0
- emdash_core/api/agents.py +154 -0
- emdash_core/api/analyze.py +264 -0
- emdash_core/api/auth.py +173 -0
- emdash_core/api/context.py +77 -0
- emdash_core/api/db.py +121 -0
- emdash_core/api/embed.py +131 -0
- emdash_core/api/feature.py +143 -0
- emdash_core/api/health.py +93 -0
- emdash_core/api/index.py +162 -0
- emdash_core/api/plan.py +110 -0
- emdash_core/api/projectmd.py +210 -0
- emdash_core/api/query.py +320 -0
- emdash_core/api/research.py +122 -0
- emdash_core/api/review.py +161 -0
- emdash_core/api/router.py +76 -0
- emdash_core/api/rules.py +116 -0
- emdash_core/api/search.py +119 -0
- emdash_core/api/spec.py +99 -0
- emdash_core/api/swarm.py +223 -0
- emdash_core/api/tasks.py +109 -0
- emdash_core/api/team.py +120 -0
- emdash_core/auth/__init__.py +17 -0
- emdash_core/auth/github.py +389 -0
- emdash_core/config.py +74 -0
- emdash_core/context/__init__.py +52 -0
- emdash_core/context/models.py +50 -0
- emdash_core/context/providers/__init__.py +11 -0
- emdash_core/context/providers/base.py +74 -0
- emdash_core/context/providers/explored_areas.py +183 -0
- emdash_core/context/providers/touched_areas.py +360 -0
- emdash_core/context/registry.py +73 -0
- emdash_core/context/reranker.py +199 -0
- emdash_core/context/service.py +260 -0
- emdash_core/context/session.py +352 -0
- emdash_core/core/__init__.py +104 -0
- emdash_core/core/config.py +454 -0
- emdash_core/core/exceptions.py +55 -0
- emdash_core/core/models.py +265 -0
- emdash_core/core/review_config.py +57 -0
- emdash_core/db/__init__.py +67 -0
- emdash_core/db/auth.py +134 -0
- emdash_core/db/models.py +91 -0
- emdash_core/db/provider.py +222 -0
- emdash_core/db/providers/__init__.py +5 -0
- emdash_core/db/providers/supabase.py +452 -0
- emdash_core/embeddings/__init__.py +24 -0
- emdash_core/embeddings/indexer.py +534 -0
- emdash_core/embeddings/models.py +192 -0
- emdash_core/embeddings/providers/__init__.py +7 -0
- emdash_core/embeddings/providers/base.py +112 -0
- emdash_core/embeddings/providers/fireworks.py +141 -0
- emdash_core/embeddings/providers/openai.py +104 -0
- emdash_core/embeddings/registry.py +146 -0
- emdash_core/embeddings/service.py +215 -0
- emdash_core/graph/__init__.py +26 -0
- emdash_core/graph/builder.py +134 -0
- emdash_core/graph/connection.py +692 -0
- emdash_core/graph/schema.py +416 -0
- emdash_core/graph/writer.py +667 -0
- emdash_core/ingestion/__init__.py +7 -0
- emdash_core/ingestion/change_detector.py +150 -0
- emdash_core/ingestion/git/__init__.py +5 -0
- emdash_core/ingestion/git/commit_analyzer.py +196 -0
- emdash_core/ingestion/github/__init__.py +6 -0
- emdash_core/ingestion/github/pr_fetcher.py +296 -0
- emdash_core/ingestion/github/task_extractor.py +100 -0
- emdash_core/ingestion/orchestrator.py +540 -0
- emdash_core/ingestion/parsers/__init__.py +10 -0
- emdash_core/ingestion/parsers/base_parser.py +66 -0
- emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
- emdash_core/ingestion/parsers/class_extractor.py +154 -0
- emdash_core/ingestion/parsers/function_extractor.py +202 -0
- emdash_core/ingestion/parsers/import_analyzer.py +119 -0
- emdash_core/ingestion/parsers/python_parser.py +123 -0
- emdash_core/ingestion/parsers/registry.py +72 -0
- emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
- emdash_core/ingestion/parsers/typescript_parser.py +278 -0
- emdash_core/ingestion/repository.py +346 -0
- emdash_core/models/__init__.py +38 -0
- emdash_core/models/agent.py +68 -0
- emdash_core/models/index.py +77 -0
- emdash_core/models/query.py +113 -0
- emdash_core/planning/__init__.py +7 -0
- emdash_core/planning/agent_api.py +413 -0
- emdash_core/planning/context_builder.py +265 -0
- emdash_core/planning/feature_context.py +232 -0
- emdash_core/planning/feature_expander.py +646 -0
- emdash_core/planning/llm_explainer.py +198 -0
- emdash_core/planning/similarity.py +509 -0
- emdash_core/planning/team_focus.py +821 -0
- emdash_core/server.py +153 -0
- emdash_core/sse/__init__.py +5 -0
- emdash_core/sse/stream.py +196 -0
- emdash_core/swarm/__init__.py +17 -0
- emdash_core/swarm/merge_agent.py +383 -0
- emdash_core/swarm/session_manager.py +274 -0
- emdash_core/swarm/swarm_runner.py +226 -0
- emdash_core/swarm/task_definition.py +137 -0
- emdash_core/swarm/worker_spawner.py +319 -0
- emdash_core/swarm/worktree_manager.py +278 -0
- emdash_core/templates/__init__.py +10 -0
- emdash_core/templates/defaults/agent-builder.md.template +82 -0
- emdash_core/templates/defaults/focus.md.template +115 -0
- emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
- emdash_core/templates/defaults/pr-review.md.template +80 -0
- emdash_core/templates/defaults/project.md.template +85 -0
- emdash_core/templates/defaults/research_critic.md.template +112 -0
- emdash_core/templates/defaults/research_planner.md.template +85 -0
- emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
- emdash_core/templates/defaults/reviewer.md.template +81 -0
- emdash_core/templates/defaults/spec.md.template +41 -0
- emdash_core/templates/defaults/tasks.md.template +78 -0
- emdash_core/templates/loader.py +296 -0
- emdash_core/utils/__init__.py +45 -0
- emdash_core/utils/git.py +84 -0
- emdash_core/utils/image.py +502 -0
- emdash_core/utils/logger.py +51 -0
- emdash_core-0.1.7.dist-info/METADATA +35 -0
- emdash_core-0.1.7.dist-info/RECORD +187 -0
- emdash_core-0.1.7.dist-info/WHEEL +4 -0
- emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
emdash_core/server.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""FastAPI server entry point for emdash-core."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import uvicorn
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
from fastapi import FastAPI
|
|
14
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
15
|
+
|
|
16
|
+
from .api import api_router
|
|
17
|
+
from .config import get_config, set_config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load_env_files(repo_root: Optional[str] = None):
|
|
21
|
+
"""Load .env files from repo root and user config.
|
|
22
|
+
|
|
23
|
+
Uses override=True so .env file takes precedence over shell environment.
|
|
24
|
+
"""
|
|
25
|
+
# Also try parent directories to find .env (load first, lower priority)
|
|
26
|
+
current = Path(__file__).parent
|
|
27
|
+
for _ in range(5):
|
|
28
|
+
current = current.parent
|
|
29
|
+
env_path = current / ".env"
|
|
30
|
+
if env_path.exists():
|
|
31
|
+
load_dotenv(env_path, override=True)
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
# Load from repo root if provided (highest priority)
|
|
35
|
+
if repo_root:
|
|
36
|
+
env_path = Path(repo_root) / ".env"
|
|
37
|
+
if env_path.exists():
|
|
38
|
+
load_dotenv(env_path, override=True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@asynccontextmanager
|
|
42
|
+
async def lifespan(app: FastAPI):
|
|
43
|
+
"""Application lifespan handler."""
|
|
44
|
+
config = get_config()
|
|
45
|
+
|
|
46
|
+
# Startup
|
|
47
|
+
print(f"EmDash Core starting on http://{config.host}:{config.port}")
|
|
48
|
+
if config.repo_root:
|
|
49
|
+
print(f"Repository root: {config.repo_root}")
|
|
50
|
+
|
|
51
|
+
# Write port file for CLI/Electron discovery
|
|
52
|
+
port_file = Path.home() / ".emdash" / "server.port"
|
|
53
|
+
port_file.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
port_file.write_text(str(config.port))
|
|
55
|
+
|
|
56
|
+
yield
|
|
57
|
+
|
|
58
|
+
# Shutdown
|
|
59
|
+
print("EmDash Core shutting down...")
|
|
60
|
+
if port_file.exists():
|
|
61
|
+
port_file.unlink()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def create_app(
|
|
65
|
+
repo_root: Optional[str] = None,
|
|
66
|
+
host: str = "127.0.0.1",
|
|
67
|
+
port: int = 8765,
|
|
68
|
+
) -> FastAPI:
|
|
69
|
+
"""Create and configure the FastAPI application."""
|
|
70
|
+
# Load environment variables from .env files
|
|
71
|
+
_load_env_files(repo_root)
|
|
72
|
+
|
|
73
|
+
# Set configuration
|
|
74
|
+
set_config(
|
|
75
|
+
host=host,
|
|
76
|
+
port=port,
|
|
77
|
+
repo_root=repo_root,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
app = FastAPI(
|
|
81
|
+
title="EmDash Core",
|
|
82
|
+
description="FastAPI server for code intelligence",
|
|
83
|
+
version="0.1.0",
|
|
84
|
+
lifespan=lifespan,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# CORS middleware for Electron app
|
|
88
|
+
app.add_middleware(
|
|
89
|
+
CORSMiddleware,
|
|
90
|
+
allow_origins=["*"], # Allow all origins for local development
|
|
91
|
+
allow_credentials=True,
|
|
92
|
+
allow_methods=["*"],
|
|
93
|
+
allow_headers=["*"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Include API routes
|
|
97
|
+
app.include_router(api_router)
|
|
98
|
+
|
|
99
|
+
return app
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main():
|
|
103
|
+
"""Main entry point for the server."""
|
|
104
|
+
parser = argparse.ArgumentParser(description="EmDash Core Server")
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"--host",
|
|
107
|
+
default="127.0.0.1",
|
|
108
|
+
help="Host to bind to (default: 127.0.0.1)"
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--port",
|
|
112
|
+
type=int,
|
|
113
|
+
default=8765,
|
|
114
|
+
help="Port to bind to (default: 8765)"
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"--repo-root",
|
|
118
|
+
help="Repository root path"
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--reload",
|
|
122
|
+
action="store_true",
|
|
123
|
+
help="Enable auto-reload for development"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
args = parser.parse_args()
|
|
127
|
+
|
|
128
|
+
# Handle graceful shutdown
|
|
129
|
+
def signal_handler(signum, frame):
|
|
130
|
+
print("\nReceived shutdown signal...")
|
|
131
|
+
sys.exit(0)
|
|
132
|
+
|
|
133
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
134
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
135
|
+
|
|
136
|
+
# Create app
|
|
137
|
+
app = create_app(
|
|
138
|
+
repo_root=args.repo_root,
|
|
139
|
+
host=args.host,
|
|
140
|
+
port=args.port,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Run server
|
|
144
|
+
uvicorn.run(
|
|
145
|
+
app,
|
|
146
|
+
host=args.host,
|
|
147
|
+
port=args.port,
|
|
148
|
+
reload=args.reload,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
main()
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""SSE streaming infrastructure for agent events."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, AsyncIterator
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EventType(str, Enum):
|
|
13
|
+
"""Types of events emitted by agents (matches emdash.agent.events.EventType)."""
|
|
14
|
+
|
|
15
|
+
# Tool lifecycle
|
|
16
|
+
TOOL_START = "tool_start"
|
|
17
|
+
TOOL_RESULT = "tool_result"
|
|
18
|
+
|
|
19
|
+
# Agent thinking/progress
|
|
20
|
+
THINKING = "thinking"
|
|
21
|
+
PROGRESS = "progress"
|
|
22
|
+
|
|
23
|
+
# Output
|
|
24
|
+
RESPONSE = "response"
|
|
25
|
+
PARTIAL_RESPONSE = "partial_response"
|
|
26
|
+
|
|
27
|
+
# Interaction
|
|
28
|
+
CLARIFICATION = "clarification"
|
|
29
|
+
CLARIFICATION_RESPONSE = "clarification_response"
|
|
30
|
+
|
|
31
|
+
# Errors
|
|
32
|
+
ERROR = "error"
|
|
33
|
+
WARNING = "warning"
|
|
34
|
+
|
|
35
|
+
# Session
|
|
36
|
+
SESSION_START = "session_start"
|
|
37
|
+
SESSION_END = "session_end"
|
|
38
|
+
|
|
39
|
+
# Context
|
|
40
|
+
CONTEXT_FRAME = "context_frame"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SSEEvent(BaseModel):
|
|
44
|
+
"""An SSE event for streaming to clients."""
|
|
45
|
+
|
|
46
|
+
type: EventType
|
|
47
|
+
data: dict[str, Any]
|
|
48
|
+
timestamp: datetime
|
|
49
|
+
agent_name: str | None = None
|
|
50
|
+
|
|
51
|
+
def to_sse(self) -> str:
|
|
52
|
+
"""Format as SSE wire protocol.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
SSE formatted string: "event: {type}\ndata: {json}\n\n"
|
|
56
|
+
"""
|
|
57
|
+
payload = {
|
|
58
|
+
**self.data,
|
|
59
|
+
"timestamp": self.timestamp.isoformat(),
|
|
60
|
+
}
|
|
61
|
+
if self.agent_name:
|
|
62
|
+
payload["agent_name"] = self.agent_name
|
|
63
|
+
|
|
64
|
+
data_json = json.dumps(payload, default=str)
|
|
65
|
+
return f"event: {self.type.value}\ndata: {data_json}\n\n"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SSEHandler:
|
|
69
|
+
"""Event handler that queues events for SSE streaming.
|
|
70
|
+
|
|
71
|
+
This handler implements the EventHandler protocol and collects
|
|
72
|
+
events into an async queue for streaming to HTTP clients.
|
|
73
|
+
|
|
74
|
+
Thread-safe: can be used from both async context and thread pools.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, agent_name: str | None = None):
|
|
78
|
+
"""Initialize the handler.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
agent_name: Optional name to tag events
|
|
82
|
+
"""
|
|
83
|
+
# Use thread-safe queue for cross-thread communication
|
|
84
|
+
import queue
|
|
85
|
+
self._queue: queue.Queue[SSEEvent | None] = queue.Queue()
|
|
86
|
+
self._agent_name = agent_name
|
|
87
|
+
self._closed = False
|
|
88
|
+
|
|
89
|
+
def handle(self, event: Any) -> None:
|
|
90
|
+
"""Handle an event from the agent.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
event: AgentEvent from the agent system
|
|
94
|
+
"""
|
|
95
|
+
if self._closed:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# Convert to SSEEvent
|
|
99
|
+
sse_event = SSEEvent(
|
|
100
|
+
type=EventType(event.type.value),
|
|
101
|
+
data=event.data,
|
|
102
|
+
timestamp=event.timestamp,
|
|
103
|
+
agent_name=event.agent_name or self._agent_name,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Put in queue (thread-safe)
|
|
107
|
+
self._queue.put(sse_event)
|
|
108
|
+
|
|
109
|
+
def emit(self, event_type: EventType, data: dict[str, Any]) -> None:
|
|
110
|
+
"""Emit an event directly (thread-safe).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
event_type: Type of event
|
|
114
|
+
data: Event data
|
|
115
|
+
"""
|
|
116
|
+
if self._closed:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
sse_event = SSEEvent(
|
|
120
|
+
type=event_type,
|
|
121
|
+
data=data,
|
|
122
|
+
timestamp=datetime.now(),
|
|
123
|
+
agent_name=self._agent_name,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
self._queue.put(sse_event)
|
|
127
|
+
|
|
128
|
+
async def emit_async(self, event_type: EventType, data: dict[str, Any]) -> None:
|
|
129
|
+
"""Emit an event asynchronously.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
event_type: Type of event
|
|
133
|
+
data: Event data
|
|
134
|
+
"""
|
|
135
|
+
if self._closed:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
sse_event = SSEEvent(
|
|
139
|
+
type=event_type,
|
|
140
|
+
data=data,
|
|
141
|
+
timestamp=datetime.now(),
|
|
142
|
+
agent_name=self._agent_name,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self._queue.put(sse_event)
|
|
146
|
+
|
|
147
|
+
def close(self) -> None:
|
|
148
|
+
"""Close the handler and signal end of stream."""
|
|
149
|
+
self._closed = True
|
|
150
|
+
self._queue.put(None) # Sentinel to end iteration
|
|
151
|
+
|
|
152
|
+
async def __aiter__(self) -> AsyncIterator[str]:
|
|
153
|
+
"""Iterate over SSE-formatted events.
|
|
154
|
+
|
|
155
|
+
Yields:
|
|
156
|
+
SSE formatted strings ready for HTTP streaming
|
|
157
|
+
"""
|
|
158
|
+
import queue
|
|
159
|
+
|
|
160
|
+
loop = asyncio.get_event_loop()
|
|
161
|
+
ping_counter = 0
|
|
162
|
+
|
|
163
|
+
while True:
|
|
164
|
+
try:
|
|
165
|
+
# Run blocking queue.get in executor to not block event loop
|
|
166
|
+
try:
|
|
167
|
+
event = await loop.run_in_executor(
|
|
168
|
+
None, # Default executor
|
|
169
|
+
lambda: self._queue.get(timeout=1.0)
|
|
170
|
+
)
|
|
171
|
+
if event is None:
|
|
172
|
+
break
|
|
173
|
+
yield event.to_sse()
|
|
174
|
+
ping_counter = 0
|
|
175
|
+
except queue.Empty:
|
|
176
|
+
ping_counter += 1
|
|
177
|
+
# Send keep-alive ping every 30 seconds
|
|
178
|
+
if ping_counter >= 30:
|
|
179
|
+
yield ": ping\n\n"
|
|
180
|
+
ping_counter = 0
|
|
181
|
+
continue
|
|
182
|
+
except Exception:
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def create_sse_stream(handler: SSEHandler) -> AsyncIterator[str]:
|
|
187
|
+
"""Create an SSE stream from a handler.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
handler: SSE handler to stream from
|
|
191
|
+
|
|
192
|
+
Yields:
|
|
193
|
+
SSE formatted event strings
|
|
194
|
+
"""
|
|
195
|
+
async for event in handler:
|
|
196
|
+
yield event
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Multi-agent swarm execution with git worktrees."""
|
|
2
|
+
|
|
3
|
+
from .task_definition import SwarmTask, SwarmState, TaskStatus
|
|
4
|
+
from .worktree_manager import WorktreeManager, WorktreeInfo, WorktreeError
|
|
5
|
+
from .swarm_runner import SwarmRunner
|
|
6
|
+
from .session_manager import SessionManager
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"SwarmTask",
|
|
10
|
+
"SwarmState",
|
|
11
|
+
"TaskStatus",
|
|
12
|
+
"WorktreeManager",
|
|
13
|
+
"WorktreeInfo",
|
|
14
|
+
"WorktreeError",
|
|
15
|
+
"SwarmRunner",
|
|
16
|
+
"SessionManager",
|
|
17
|
+
]
|