autoforge-ai 0.1.0
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.
- package/.claude/commands/check-code.md +32 -0
- package/.claude/commands/checkpoint.md +40 -0
- package/.claude/commands/create-spec.md +613 -0
- package/.claude/commands/expand-project.md +234 -0
- package/.claude/commands/gsd-to-autoforge-spec.md +10 -0
- package/.claude/commands/review-pr.md +75 -0
- package/.claude/templates/app_spec.template.txt +331 -0
- package/.claude/templates/coding_prompt.template.md +265 -0
- package/.claude/templates/initializer_prompt.template.md +354 -0
- package/.claude/templates/testing_prompt.template.md +146 -0
- package/.env.example +64 -0
- package/LICENSE.md +676 -0
- package/README.md +423 -0
- package/agent.py +444 -0
- package/api/__init__.py +10 -0
- package/api/database.py +536 -0
- package/api/dependency_resolver.py +449 -0
- package/api/migration.py +156 -0
- package/auth.py +83 -0
- package/autoforge_paths.py +315 -0
- package/autonomous_agent_demo.py +293 -0
- package/bin/autoforge.js +3 -0
- package/client.py +607 -0
- package/env_constants.py +27 -0
- package/examples/OPTIMIZE_CONFIG.md +230 -0
- package/examples/README.md +531 -0
- package/examples/org_config.yaml +172 -0
- package/examples/project_allowed_commands.yaml +139 -0
- package/lib/cli.js +791 -0
- package/mcp_server/__init__.py +1 -0
- package/mcp_server/feature_mcp.py +988 -0
- package/package.json +53 -0
- package/parallel_orchestrator.py +1800 -0
- package/progress.py +247 -0
- package/prompts.py +427 -0
- package/pyproject.toml +17 -0
- package/rate_limit_utils.py +132 -0
- package/registry.py +614 -0
- package/requirements-prod.txt +14 -0
- package/security.py +959 -0
- package/server/__init__.py +17 -0
- package/server/main.py +261 -0
- package/server/routers/__init__.py +32 -0
- package/server/routers/agent.py +177 -0
- package/server/routers/assistant_chat.py +327 -0
- package/server/routers/devserver.py +309 -0
- package/server/routers/expand_project.py +239 -0
- package/server/routers/features.py +746 -0
- package/server/routers/filesystem.py +514 -0
- package/server/routers/projects.py +524 -0
- package/server/routers/schedules.py +356 -0
- package/server/routers/settings.py +127 -0
- package/server/routers/spec_creation.py +357 -0
- package/server/routers/terminal.py +453 -0
- package/server/schemas.py +593 -0
- package/server/services/__init__.py +36 -0
- package/server/services/assistant_chat_session.py +496 -0
- package/server/services/assistant_database.py +304 -0
- package/server/services/chat_constants.py +57 -0
- package/server/services/dev_server_manager.py +557 -0
- package/server/services/expand_chat_session.py +399 -0
- package/server/services/process_manager.py +657 -0
- package/server/services/project_config.py +475 -0
- package/server/services/scheduler_service.py +683 -0
- package/server/services/spec_chat_session.py +502 -0
- package/server/services/terminal_manager.py +756 -0
- package/server/utils/__init__.py +1 -0
- package/server/utils/process_utils.py +134 -0
- package/server/utils/project_helpers.py +32 -0
- package/server/utils/validation.py +54 -0
- package/server/websocket.py +903 -0
- package/start.py +456 -0
- package/ui/dist/assets/index-8W_wmZzz.js +168 -0
- package/ui/dist/assets/index-B47Ubhox.css +1 -0
- package/ui/dist/assets/vendor-flow-CVNK-_lx.js +7 -0
- package/ui/dist/assets/vendor-query-BUABzP5o.js +1 -0
- package/ui/dist/assets/vendor-radix-DTNNCg2d.js +45 -0
- package/ui/dist/assets/vendor-react-qkC6yhPU.js +1 -0
- package/ui/dist/assets/vendor-utils-COeKbHgx.js +2 -0
- package/ui/dist/assets/vendor-xterm-DP_gxef0.js +16 -0
- package/ui/dist/index.html +23 -0
- package/ui/dist/ollama.png +0 -0
- package/ui/dist/vite.svg +6 -0
- package/ui/package.json +57 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Backend Server
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
Web UI server for the Autonomous Coding Agent.
|
|
6
|
+
Provides REST API and WebSocket endpoints for project management,
|
|
7
|
+
feature tracking, and agent control.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Fix Windows asyncio subprocess support - MUST be before any other imports
|
|
11
|
+
# that might create an event loop
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
if sys.platform == "win32":
|
|
15
|
+
import asyncio
|
|
16
|
+
|
|
17
|
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
package/server/main.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Main Application
|
|
3
|
+
========================
|
|
4
|
+
|
|
5
|
+
Main entry point for the Autonomous Coding UI server.
|
|
6
|
+
Provides REST API, WebSocket, and static file serving.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
from contextlib import asynccontextmanager
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Fix for Windows subprocess support in asyncio
|
|
18
|
+
if sys.platform == "win32":
|
|
19
|
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
20
|
+
|
|
21
|
+
from dotenv import load_dotenv
|
|
22
|
+
|
|
23
|
+
# Load environment variables from .env file if present
|
|
24
|
+
load_dotenv()
|
|
25
|
+
|
|
26
|
+
from fastapi import FastAPI, HTTPException, Request, WebSocket
|
|
27
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
28
|
+
from fastapi.responses import FileResponse
|
|
29
|
+
from fastapi.staticfiles import StaticFiles
|
|
30
|
+
|
|
31
|
+
from .routers import (
|
|
32
|
+
agent_router,
|
|
33
|
+
assistant_chat_router,
|
|
34
|
+
devserver_router,
|
|
35
|
+
expand_project_router,
|
|
36
|
+
features_router,
|
|
37
|
+
filesystem_router,
|
|
38
|
+
projects_router,
|
|
39
|
+
schedules_router,
|
|
40
|
+
settings_router,
|
|
41
|
+
spec_creation_router,
|
|
42
|
+
terminal_router,
|
|
43
|
+
)
|
|
44
|
+
from .schemas import SetupStatus
|
|
45
|
+
from .services.assistant_chat_session import cleanup_all_sessions as cleanup_assistant_sessions
|
|
46
|
+
from .services.chat_constants import ROOT_DIR
|
|
47
|
+
from .services.dev_server_manager import (
|
|
48
|
+
cleanup_all_devservers,
|
|
49
|
+
cleanup_orphaned_devserver_locks,
|
|
50
|
+
)
|
|
51
|
+
from .services.expand_chat_session import cleanup_all_expand_sessions
|
|
52
|
+
from .services.process_manager import cleanup_all_managers, cleanup_orphaned_locks
|
|
53
|
+
from .services.scheduler_service import cleanup_scheduler, get_scheduler
|
|
54
|
+
from .services.terminal_manager import cleanup_all_terminals
|
|
55
|
+
from .websocket import project_websocket
|
|
56
|
+
|
|
57
|
+
# Paths
|
|
58
|
+
UI_DIST_DIR = ROOT_DIR / "ui" / "dist"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@asynccontextmanager
|
|
62
|
+
async def lifespan(app: FastAPI):
|
|
63
|
+
"""Lifespan context manager for startup and shutdown."""
|
|
64
|
+
# Startup - clean up orphaned lock files from previous runs
|
|
65
|
+
cleanup_orphaned_locks()
|
|
66
|
+
cleanup_orphaned_devserver_locks()
|
|
67
|
+
|
|
68
|
+
# Start the scheduler service
|
|
69
|
+
scheduler = get_scheduler()
|
|
70
|
+
await scheduler.start()
|
|
71
|
+
|
|
72
|
+
yield
|
|
73
|
+
|
|
74
|
+
# Shutdown - cleanup scheduler first to stop triggering new starts
|
|
75
|
+
await cleanup_scheduler()
|
|
76
|
+
# Then cleanup all running agents, sessions, terminals, and dev servers
|
|
77
|
+
await cleanup_all_managers()
|
|
78
|
+
await cleanup_assistant_sessions()
|
|
79
|
+
await cleanup_all_expand_sessions()
|
|
80
|
+
await cleanup_all_terminals()
|
|
81
|
+
await cleanup_all_devservers()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Create FastAPI app
|
|
85
|
+
app = FastAPI(
|
|
86
|
+
title="Autonomous Coding UI",
|
|
87
|
+
description="Web UI for the Autonomous Coding Agent",
|
|
88
|
+
version="1.0.0",
|
|
89
|
+
lifespan=lifespan,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Module logger
|
|
93
|
+
logger = logging.getLogger(__name__)
|
|
94
|
+
|
|
95
|
+
# Check if remote access is enabled via environment variable
|
|
96
|
+
# Set by start_ui.py when --host is not 127.0.0.1
|
|
97
|
+
ALLOW_REMOTE = os.environ.get("AUTOFORGE_ALLOW_REMOTE", "").lower() in ("1", "true", "yes")
|
|
98
|
+
|
|
99
|
+
if ALLOW_REMOTE:
|
|
100
|
+
logger.warning(
|
|
101
|
+
"ALLOW_REMOTE is enabled. Terminal WebSocket is exposed without sandboxing. "
|
|
102
|
+
"Only use this in trusted network environments."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# CORS - allow all origins when remote access is enabled, otherwise localhost only
|
|
106
|
+
if ALLOW_REMOTE:
|
|
107
|
+
app.add_middleware(
|
|
108
|
+
CORSMiddleware,
|
|
109
|
+
allow_origins=["*"], # Allow all origins for remote access
|
|
110
|
+
allow_credentials=True,
|
|
111
|
+
allow_methods=["*"],
|
|
112
|
+
allow_headers=["*"],
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
app.add_middleware(
|
|
116
|
+
CORSMiddleware,
|
|
117
|
+
allow_origins=[
|
|
118
|
+
"http://localhost:5173", # Vite dev server
|
|
119
|
+
"http://127.0.0.1:5173",
|
|
120
|
+
"http://localhost:8888", # Production
|
|
121
|
+
"http://127.0.0.1:8888",
|
|
122
|
+
],
|
|
123
|
+
allow_credentials=True,
|
|
124
|
+
allow_methods=["*"],
|
|
125
|
+
allow_headers=["*"],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ============================================================================
|
|
130
|
+
# Security Middleware
|
|
131
|
+
# ============================================================================
|
|
132
|
+
|
|
133
|
+
if not ALLOW_REMOTE:
|
|
134
|
+
@app.middleware("http")
|
|
135
|
+
async def require_localhost(request: Request, call_next):
|
|
136
|
+
"""Only allow requests from localhost (disabled when AUTOFORGE_ALLOW_REMOTE=1)."""
|
|
137
|
+
client_host = request.client.host if request.client else None
|
|
138
|
+
|
|
139
|
+
# Allow localhost connections
|
|
140
|
+
if client_host not in ("127.0.0.1", "::1", "localhost", None):
|
|
141
|
+
raise HTTPException(status_code=403, detail="Localhost access only")
|
|
142
|
+
|
|
143
|
+
return await call_next(request)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ============================================================================
|
|
147
|
+
# Include Routers
|
|
148
|
+
# ============================================================================
|
|
149
|
+
|
|
150
|
+
app.include_router(projects_router)
|
|
151
|
+
app.include_router(features_router)
|
|
152
|
+
app.include_router(agent_router)
|
|
153
|
+
app.include_router(schedules_router)
|
|
154
|
+
app.include_router(devserver_router)
|
|
155
|
+
app.include_router(spec_creation_router)
|
|
156
|
+
app.include_router(expand_project_router)
|
|
157
|
+
app.include_router(filesystem_router)
|
|
158
|
+
app.include_router(assistant_chat_router)
|
|
159
|
+
app.include_router(settings_router)
|
|
160
|
+
app.include_router(terminal_router)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ============================================================================
|
|
164
|
+
# WebSocket Endpoint
|
|
165
|
+
# ============================================================================
|
|
166
|
+
|
|
167
|
+
@app.websocket("/ws/projects/{project_name}")
|
|
168
|
+
async def websocket_endpoint(websocket: WebSocket, project_name: str):
|
|
169
|
+
"""WebSocket endpoint for real-time project updates."""
|
|
170
|
+
await project_websocket(websocket, project_name)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ============================================================================
|
|
174
|
+
# Setup & Health Endpoints
|
|
175
|
+
# ============================================================================
|
|
176
|
+
|
|
177
|
+
@app.get("/api/health")
|
|
178
|
+
async def health_check():
|
|
179
|
+
"""Health check endpoint."""
|
|
180
|
+
return {"status": "healthy"}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@app.get("/api/setup/status", response_model=SetupStatus)
|
|
184
|
+
async def setup_status():
|
|
185
|
+
"""Check system setup status."""
|
|
186
|
+
# Check for Claude CLI
|
|
187
|
+
claude_cli = shutil.which("claude") is not None
|
|
188
|
+
|
|
189
|
+
# Check for CLI configuration directory
|
|
190
|
+
# Note: CLI no longer stores credentials in ~/.claude/.credentials.json
|
|
191
|
+
# The existence of ~/.claude indicates the CLI has been configured
|
|
192
|
+
claude_dir = Path.home() / ".claude"
|
|
193
|
+
has_claude_config = claude_dir.exists() and claude_dir.is_dir()
|
|
194
|
+
|
|
195
|
+
# If GLM mode is configured via .env, we have alternative credentials
|
|
196
|
+
glm_configured = bool(os.getenv("ANTHROPIC_BASE_URL") and os.getenv("ANTHROPIC_AUTH_TOKEN"))
|
|
197
|
+
credentials = has_claude_config or glm_configured
|
|
198
|
+
|
|
199
|
+
# Check for Node.js and npm
|
|
200
|
+
node = shutil.which("node") is not None
|
|
201
|
+
npm = shutil.which("npm") is not None
|
|
202
|
+
|
|
203
|
+
return SetupStatus(
|
|
204
|
+
claude_cli=claude_cli,
|
|
205
|
+
credentials=credentials,
|
|
206
|
+
node=node,
|
|
207
|
+
npm=npm,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ============================================================================
|
|
212
|
+
# Static File Serving (Production)
|
|
213
|
+
# ============================================================================
|
|
214
|
+
|
|
215
|
+
# Serve React build files if they exist
|
|
216
|
+
if UI_DIST_DIR.exists():
|
|
217
|
+
# Mount static assets
|
|
218
|
+
app.mount("/assets", StaticFiles(directory=UI_DIST_DIR / "assets"), name="assets")
|
|
219
|
+
|
|
220
|
+
@app.get("/")
|
|
221
|
+
async def serve_index():
|
|
222
|
+
"""Serve the React app index.html."""
|
|
223
|
+
return FileResponse(UI_DIST_DIR / "index.html")
|
|
224
|
+
|
|
225
|
+
@app.get("/{path:path}")
|
|
226
|
+
async def serve_spa(path: str):
|
|
227
|
+
"""
|
|
228
|
+
Serve static files or fall back to index.html for SPA routing.
|
|
229
|
+
"""
|
|
230
|
+
# Check if the path is an API route (shouldn't hit this due to router ordering)
|
|
231
|
+
if path.startswith("api/") or path.startswith("ws/"):
|
|
232
|
+
raise HTTPException(status_code=404)
|
|
233
|
+
|
|
234
|
+
# Try to serve the file directly
|
|
235
|
+
file_path = (UI_DIST_DIR / path).resolve()
|
|
236
|
+
|
|
237
|
+
# Ensure resolved path is within UI_DIST_DIR (prevent path traversal)
|
|
238
|
+
try:
|
|
239
|
+
file_path.relative_to(UI_DIST_DIR.resolve())
|
|
240
|
+
except ValueError:
|
|
241
|
+
raise HTTPException(status_code=404)
|
|
242
|
+
|
|
243
|
+
if file_path.exists() and file_path.is_file():
|
|
244
|
+
return FileResponse(file_path)
|
|
245
|
+
|
|
246
|
+
# Fall back to index.html for SPA routing
|
|
247
|
+
return FileResponse(UI_DIST_DIR / "index.html")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ============================================================================
|
|
251
|
+
# Main Entry Point
|
|
252
|
+
# ============================================================================
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
import uvicorn
|
|
256
|
+
uvicorn.run(
|
|
257
|
+
"server.main:app",
|
|
258
|
+
host="127.0.0.1", # Localhost only for security
|
|
259
|
+
port=8888,
|
|
260
|
+
reload=True,
|
|
261
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Routers
|
|
3
|
+
===========
|
|
4
|
+
|
|
5
|
+
FastAPI routers for different API endpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .agent import router as agent_router
|
|
9
|
+
from .assistant_chat import router as assistant_chat_router
|
|
10
|
+
from .devserver import router as devserver_router
|
|
11
|
+
from .expand_project import router as expand_project_router
|
|
12
|
+
from .features import router as features_router
|
|
13
|
+
from .filesystem import router as filesystem_router
|
|
14
|
+
from .projects import router as projects_router
|
|
15
|
+
from .schedules import router as schedules_router
|
|
16
|
+
from .settings import router as settings_router
|
|
17
|
+
from .spec_creation import router as spec_creation_router
|
|
18
|
+
from .terminal import router as terminal_router
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"projects_router",
|
|
22
|
+
"features_router",
|
|
23
|
+
"agent_router",
|
|
24
|
+
"schedules_router",
|
|
25
|
+
"devserver_router",
|
|
26
|
+
"spec_creation_router",
|
|
27
|
+
"expand_project_router",
|
|
28
|
+
"filesystem_router",
|
|
29
|
+
"assistant_chat_router",
|
|
30
|
+
"settings_router",
|
|
31
|
+
"terminal_router",
|
|
32
|
+
]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Router
|
|
3
|
+
============
|
|
4
|
+
|
|
5
|
+
API endpoints for agent control (start/stop/pause/resume).
|
|
6
|
+
Uses project registry for path lookups.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, HTTPException
|
|
12
|
+
|
|
13
|
+
from ..schemas import AgentActionResponse, AgentStartRequest, AgentStatus
|
|
14
|
+
from ..services.chat_constants import ROOT_DIR
|
|
15
|
+
from ..services.process_manager import get_manager
|
|
16
|
+
from ..utils.project_helpers import get_project_path as _get_project_path
|
|
17
|
+
from ..utils.validation import validate_project_name
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_settings_defaults() -> tuple[bool, str, int, bool, int]:
|
|
21
|
+
"""Get defaults from global settings.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size)
|
|
25
|
+
"""
|
|
26
|
+
import sys
|
|
27
|
+
root = Path(__file__).parent.parent.parent
|
|
28
|
+
if str(root) not in sys.path:
|
|
29
|
+
sys.path.insert(0, str(root))
|
|
30
|
+
|
|
31
|
+
from registry import DEFAULT_MODEL, get_all_settings
|
|
32
|
+
|
|
33
|
+
settings = get_all_settings()
|
|
34
|
+
yolo_mode = (settings.get("yolo_mode") or "false").lower() == "true"
|
|
35
|
+
model = settings.get("model", DEFAULT_MODEL)
|
|
36
|
+
|
|
37
|
+
# Parse testing agent settings with defaults
|
|
38
|
+
try:
|
|
39
|
+
testing_agent_ratio = int(settings.get("testing_agent_ratio", "1"))
|
|
40
|
+
except (ValueError, TypeError):
|
|
41
|
+
testing_agent_ratio = 1
|
|
42
|
+
|
|
43
|
+
playwright_headless = (settings.get("playwright_headless") or "true").lower() == "true"
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
batch_size = int(settings.get("batch_size", "3"))
|
|
47
|
+
except (ValueError, TypeError):
|
|
48
|
+
batch_size = 3
|
|
49
|
+
|
|
50
|
+
return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_project_manager(project_name: str):
|
|
57
|
+
"""Get the process manager for a project."""
|
|
58
|
+
project_name = validate_project_name(project_name)
|
|
59
|
+
project_dir = _get_project_path(project_name)
|
|
60
|
+
|
|
61
|
+
if not project_dir:
|
|
62
|
+
raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found in registry")
|
|
63
|
+
|
|
64
|
+
if not project_dir.exists():
|
|
65
|
+
raise HTTPException(status_code=404, detail=f"Project directory not found: {project_dir}")
|
|
66
|
+
|
|
67
|
+
return get_manager(project_name, project_dir, ROOT_DIR)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.get("/status", response_model=AgentStatus)
|
|
71
|
+
async def get_agent_status(project_name: str):
|
|
72
|
+
"""Get the current status of the agent for a project."""
|
|
73
|
+
manager = get_project_manager(project_name)
|
|
74
|
+
|
|
75
|
+
# Run healthcheck to detect crashed processes
|
|
76
|
+
await manager.healthcheck()
|
|
77
|
+
|
|
78
|
+
return AgentStatus(
|
|
79
|
+
status=manager.status,
|
|
80
|
+
pid=manager.pid,
|
|
81
|
+
started_at=manager.started_at.isoformat() if manager.started_at else None,
|
|
82
|
+
yolo_mode=manager.yolo_mode,
|
|
83
|
+
model=manager.model,
|
|
84
|
+
parallel_mode=manager.parallel_mode,
|
|
85
|
+
max_concurrency=manager.max_concurrency,
|
|
86
|
+
testing_agent_ratio=manager.testing_agent_ratio,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@router.post("/start", response_model=AgentActionResponse)
|
|
91
|
+
async def start_agent(
|
|
92
|
+
project_name: str,
|
|
93
|
+
request: AgentStartRequest = AgentStartRequest(),
|
|
94
|
+
):
|
|
95
|
+
"""Start the agent for a project."""
|
|
96
|
+
manager = get_project_manager(project_name)
|
|
97
|
+
|
|
98
|
+
# Get defaults from global settings if not provided in request
|
|
99
|
+
default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size = _get_settings_defaults()
|
|
100
|
+
|
|
101
|
+
yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo
|
|
102
|
+
model = request.model if request.model else default_model
|
|
103
|
+
max_concurrency = request.max_concurrency or 1
|
|
104
|
+
testing_agent_ratio = request.testing_agent_ratio if request.testing_agent_ratio is not None else default_testing_ratio
|
|
105
|
+
|
|
106
|
+
batch_size = default_batch_size
|
|
107
|
+
|
|
108
|
+
success, message = await manager.start(
|
|
109
|
+
yolo_mode=yolo_mode,
|
|
110
|
+
model=model,
|
|
111
|
+
max_concurrency=max_concurrency,
|
|
112
|
+
testing_agent_ratio=testing_agent_ratio,
|
|
113
|
+
playwright_headless=playwright_headless,
|
|
114
|
+
batch_size=batch_size,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Notify scheduler of manual start (to prevent auto-stop during scheduled window)
|
|
118
|
+
if success:
|
|
119
|
+
from ..services.scheduler_service import get_scheduler
|
|
120
|
+
project_dir = _get_project_path(project_name)
|
|
121
|
+
if project_dir:
|
|
122
|
+
get_scheduler().notify_manual_start(project_name, project_dir)
|
|
123
|
+
|
|
124
|
+
return AgentActionResponse(
|
|
125
|
+
success=success,
|
|
126
|
+
status=manager.status,
|
|
127
|
+
message=message,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@router.post("/stop", response_model=AgentActionResponse)
|
|
132
|
+
async def stop_agent(project_name: str):
|
|
133
|
+
"""Stop the agent for a project."""
|
|
134
|
+
manager = get_project_manager(project_name)
|
|
135
|
+
|
|
136
|
+
success, message = await manager.stop()
|
|
137
|
+
|
|
138
|
+
# Notify scheduler of manual stop (to prevent auto-start during scheduled window)
|
|
139
|
+
if success:
|
|
140
|
+
from ..services.scheduler_service import get_scheduler
|
|
141
|
+
project_dir = _get_project_path(project_name)
|
|
142
|
+
if project_dir:
|
|
143
|
+
get_scheduler().notify_manual_stop(project_name, project_dir)
|
|
144
|
+
|
|
145
|
+
return AgentActionResponse(
|
|
146
|
+
success=success,
|
|
147
|
+
status=manager.status,
|
|
148
|
+
message=message,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@router.post("/pause", response_model=AgentActionResponse)
|
|
153
|
+
async def pause_agent(project_name: str):
|
|
154
|
+
"""Pause the agent for a project."""
|
|
155
|
+
manager = get_project_manager(project_name)
|
|
156
|
+
|
|
157
|
+
success, message = await manager.pause()
|
|
158
|
+
|
|
159
|
+
return AgentActionResponse(
|
|
160
|
+
success=success,
|
|
161
|
+
status=manager.status,
|
|
162
|
+
message=message,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@router.post("/resume", response_model=AgentActionResponse)
|
|
167
|
+
async def resume_agent(project_name: str):
|
|
168
|
+
"""Resume a paused agent."""
|
|
169
|
+
manager = get_project_manager(project_name)
|
|
170
|
+
|
|
171
|
+
success, message = await manager.resume()
|
|
172
|
+
|
|
173
|
+
return AgentActionResponse(
|
|
174
|
+
success=success,
|
|
175
|
+
status=manager.status,
|
|
176
|
+
message=message,
|
|
177
|
+
)
|