mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-03-22 05:13:09 +00:00
Compare commits
9 Commits
472064c3da
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fca1f6a5e2 | ||
|
|
b15f45c094 | ||
|
|
f999e1937d | ||
|
|
8b2251331d | ||
|
|
7f875c3bbd | ||
|
|
e26ca3761b | ||
|
|
5d3c04a3c7 | ||
|
|
df23a978cb | ||
|
|
41c1a14ae3 |
@@ -65,7 +65,7 @@ python autonomous_agent_demo.py --project-dir my-app --yolo
|
|||||||
# Parallel mode: run multiple agents concurrently (1-5 agents)
|
# Parallel mode: run multiple agents concurrently (1-5 agents)
|
||||||
python autonomous_agent_demo.py --project-dir my-app --parallel --max-concurrency 3
|
python autonomous_agent_demo.py --project-dir my-app --parallel --max-concurrency 3
|
||||||
|
|
||||||
# Batch mode: implement multiple features per agent session (1-3)
|
# Batch mode: implement multiple features per agent session (1-15)
|
||||||
python autonomous_agent_demo.py --project-dir my-app --batch-size 3
|
python autonomous_agent_demo.py --project-dir my-app --batch-size 3
|
||||||
|
|
||||||
# Batch specific features by ID
|
# Batch specific features by ID
|
||||||
@@ -496,9 +496,9 @@ The orchestrator enforces strict bounds on concurrent processes:
|
|||||||
|
|
||||||
### Multi-Feature Batching
|
### Multi-Feature Batching
|
||||||
|
|
||||||
Agents can implement multiple features per session using `--batch-size` (1-3, default: 3):
|
Agents can implement multiple features per session using `--batch-size` (1-15, default: 3):
|
||||||
- `--batch-size N` - Max features per coding agent batch
|
- `--batch-size N` - Max features per coding agent batch
|
||||||
- `--testing-batch-size N` - Features per testing batch (1-5, default: 3)
|
- `--testing-batch-size N` - Features per testing batch (1-15, default: 3)
|
||||||
- `--batch-features 1,2,3` - Specific feature IDs for batch implementation
|
- `--batch-features 1,2,3` - Specific feature IDs for batch implementation
|
||||||
- `--testing-batch-features 1,2,3` - Specific feature IDs for batch regression testing
|
- `--testing-batch-features 1,2,3` - Specific feature IDs for batch regression testing
|
||||||
- `prompts.py` provides `get_batch_feature_prompt()` for multi-feature prompt generation
|
- `prompts.py` provides `get_batch_feature_prompt()` for multi-feature prompt generation
|
||||||
|
|||||||
@@ -176,14 +176,14 @@ Authentication:
|
|||||||
"--testing-batch-size",
|
"--testing-batch-size",
|
||||||
type=int,
|
type=int,
|
||||||
default=3,
|
default=3,
|
||||||
help="Number of features per testing batch (1-5, default: 3)",
|
help="Number of features per testing batch (1-15, default: 3)",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--batch-size",
|
"--batch-size",
|
||||||
type=int,
|
type=int,
|
||||||
default=3,
|
default=3,
|
||||||
help="Max features per coding agent batch (1-3, default: 3)",
|
help="Max features per coding agent batch (1-15, default: 3)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "autoforge-ai",
|
"name": "autoforge-ai",
|
||||||
"version": "0.1.15",
|
"version": "0.1.17",
|
||||||
"description": "Autonomous coding agent with web UI - build complete apps with AI",
|
"description": "Autonomous coding agent with web UI - build complete apps with AI",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ def _dump_database_state(feature_dicts: list[dict], label: str = ""):
|
|||||||
MAX_PARALLEL_AGENTS = 5
|
MAX_PARALLEL_AGENTS = 5
|
||||||
MAX_TOTAL_AGENTS = 10
|
MAX_TOTAL_AGENTS = 10
|
||||||
DEFAULT_CONCURRENCY = 3
|
DEFAULT_CONCURRENCY = 3
|
||||||
DEFAULT_TESTING_BATCH_SIZE = 3 # Number of features per testing batch (1-5)
|
DEFAULT_TESTING_BATCH_SIZE = 3 # Number of features per testing batch (1-15)
|
||||||
POLL_INTERVAL = 5 # seconds between checking for ready features
|
POLL_INTERVAL = 5 # seconds between checking for ready features
|
||||||
MAX_FEATURE_RETRIES = 3 # Maximum times to retry a failed feature
|
MAX_FEATURE_RETRIES = 3 # Maximum times to retry a failed feature
|
||||||
INITIALIZER_TIMEOUT = 1800 # 30 minutes timeout for initializer
|
INITIALIZER_TIMEOUT = 1800 # 30 minutes timeout for initializer
|
||||||
@@ -168,7 +168,7 @@ class ParallelOrchestrator:
|
|||||||
yolo_mode: Whether to run in YOLO mode (skip testing agents entirely)
|
yolo_mode: Whether to run in YOLO mode (skip testing agents entirely)
|
||||||
testing_agent_ratio: Number of regression testing agents to maintain (0-3).
|
testing_agent_ratio: Number of regression testing agents to maintain (0-3).
|
||||||
0 = disabled, 1-3 = maintain that many testing agents running independently.
|
0 = disabled, 1-3 = maintain that many testing agents running independently.
|
||||||
testing_batch_size: Number of features to include per testing session (1-5).
|
testing_batch_size: Number of features to include per testing session (1-15).
|
||||||
Each testing agent receives this many features to regression test.
|
Each testing agent receives this many features to regression test.
|
||||||
on_output: Callback for agent output (feature_id, line)
|
on_output: Callback for agent output (feature_id, line)
|
||||||
on_status: Callback for agent status changes (feature_id, status)
|
on_status: Callback for agent status changes (feature_id, status)
|
||||||
@@ -178,8 +178,8 @@ class ParallelOrchestrator:
|
|||||||
self.model = model
|
self.model = model
|
||||||
self.yolo_mode = yolo_mode
|
self.yolo_mode = yolo_mode
|
||||||
self.testing_agent_ratio = min(max(testing_agent_ratio, 0), 3) # Clamp 0-3
|
self.testing_agent_ratio = min(max(testing_agent_ratio, 0), 3) # Clamp 0-3
|
||||||
self.testing_batch_size = min(max(testing_batch_size, 1), 5) # Clamp 1-5
|
self.testing_batch_size = min(max(testing_batch_size, 1), 15) # Clamp 1-15
|
||||||
self.batch_size = min(max(batch_size, 1), 3) # Clamp 1-3
|
self.batch_size = min(max(batch_size, 1), 15) # Clamp 1-15
|
||||||
self.on_output = on_output
|
self.on_output = on_output
|
||||||
self.on_status = on_status
|
self.on_status = on_status
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from .routers import (
|
|||||||
features_router,
|
features_router,
|
||||||
filesystem_router,
|
filesystem_router,
|
||||||
projects_router,
|
projects_router,
|
||||||
|
scaffold_router,
|
||||||
schedules_router,
|
schedules_router,
|
||||||
settings_router,
|
settings_router,
|
||||||
spec_creation_router,
|
spec_creation_router,
|
||||||
@@ -169,6 +170,7 @@ app.include_router(filesystem_router)
|
|||||||
app.include_router(assistant_chat_router)
|
app.include_router(assistant_chat_router)
|
||||||
app.include_router(settings_router)
|
app.include_router(settings_router)
|
||||||
app.include_router(terminal_router)
|
app.include_router(terminal_router)
|
||||||
|
app.include_router(scaffold_router)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from .expand_project import router as expand_project_router
|
|||||||
from .features import router as features_router
|
from .features import router as features_router
|
||||||
from .filesystem import router as filesystem_router
|
from .filesystem import router as filesystem_router
|
||||||
from .projects import router as projects_router
|
from .projects import router as projects_router
|
||||||
|
from .scaffold import router as scaffold_router
|
||||||
from .schedules import router as schedules_router
|
from .schedules import router as schedules_router
|
||||||
from .settings import router as settings_router
|
from .settings import router as settings_router
|
||||||
from .spec_creation import router as spec_creation_router
|
from .spec_creation import router as spec_creation_router
|
||||||
@@ -29,4 +30,5 @@ __all__ = [
|
|||||||
"assistant_chat_router",
|
"assistant_chat_router",
|
||||||
"settings_router",
|
"settings_router",
|
||||||
"terminal_router",
|
"terminal_router",
|
||||||
|
"scaffold_router",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ from ..utils.project_helpers import get_project_path as _get_project_path
|
|||||||
from ..utils.validation import validate_project_name
|
from ..utils.validation import validate_project_name
|
||||||
|
|
||||||
|
|
||||||
def _get_settings_defaults() -> tuple[bool, str, int, bool, int]:
|
def _get_settings_defaults() -> tuple[bool, str, int, bool, int, int]:
|
||||||
"""Get defaults from global settings.
|
"""Get defaults from global settings.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size)
|
Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size, testing_batch_size)
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
root = Path(__file__).parent.parent.parent
|
root = Path(__file__).parent.parent.parent
|
||||||
@@ -47,7 +47,12 @@ def _get_settings_defaults() -> tuple[bool, str, int, bool, int]:
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
batch_size = 3
|
batch_size = 3
|
||||||
|
|
||||||
return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size
|
try:
|
||||||
|
testing_batch_size = int(settings.get("testing_batch_size", "3"))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
testing_batch_size = 3
|
||||||
|
|
||||||
|
return yolo_mode, model, testing_agent_ratio, playwright_headless, batch_size, testing_batch_size
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
|
router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
|
||||||
@@ -96,7 +101,7 @@ async def start_agent(
|
|||||||
manager = get_project_manager(project_name)
|
manager = get_project_manager(project_name)
|
||||||
|
|
||||||
# Get defaults from global settings if not provided in request
|
# Get defaults from global settings if not provided in request
|
||||||
default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size = _get_settings_defaults()
|
default_yolo, default_model, default_testing_ratio, playwright_headless, default_batch_size, default_testing_batch_size = _get_settings_defaults()
|
||||||
|
|
||||||
yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo
|
yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo
|
||||||
model = request.model if request.model else default_model
|
model = request.model if request.model else default_model
|
||||||
@@ -104,6 +109,7 @@ async def start_agent(
|
|||||||
testing_agent_ratio = request.testing_agent_ratio if request.testing_agent_ratio is not None else default_testing_ratio
|
testing_agent_ratio = request.testing_agent_ratio if request.testing_agent_ratio is not None else default_testing_ratio
|
||||||
|
|
||||||
batch_size = default_batch_size
|
batch_size = default_batch_size
|
||||||
|
testing_batch_size = default_testing_batch_size
|
||||||
|
|
||||||
success, message = await manager.start(
|
success, message = await manager.start(
|
||||||
yolo_mode=yolo_mode,
|
yolo_mode=yolo_mode,
|
||||||
@@ -112,6 +118,7 @@ async def start_agent(
|
|||||||
testing_agent_ratio=testing_agent_ratio,
|
testing_agent_ratio=testing_agent_ratio,
|
||||||
playwright_headless=playwright_headless,
|
playwright_headless=playwright_headless,
|
||||||
batch_size=batch_size,
|
batch_size=batch_size,
|
||||||
|
testing_batch_size=testing_batch_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Notify scheduler of manual start (to prevent auto-stop during scheduled window)
|
# Notify scheduler of manual start (to prevent auto-stop during scheduled window)
|
||||||
|
|||||||
136
server/routers/scaffold.py
Normal file
136
server/routers/scaffold.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
"""
|
||||||
|
Scaffold Router
|
||||||
|
================
|
||||||
|
|
||||||
|
SSE streaming endpoint for running project scaffold commands.
|
||||||
|
Supports templated project creation (e.g., Next.js agentic starter).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .filesystem import is_path_blocked
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/scaffold", tags=["scaffold"])
|
||||||
|
|
||||||
|
# Hardcoded templates — no arbitrary commands allowed
|
||||||
|
TEMPLATES: dict[str, list[str]] = {
|
||||||
|
"agentic-starter": ["npx", "create-agentic-app@latest", ".", "-y", "-p", "npm", "--skip-git"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ScaffoldRequest(BaseModel):
|
||||||
|
template: str
|
||||||
|
target_path: str
|
||||||
|
|
||||||
|
|
||||||
|
def _sse_event(data: dict) -> str:
|
||||||
|
"""Format a dict as an SSE data line."""
|
||||||
|
return f"data: {json.dumps(data)}\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
async def _stream_scaffold(template: str, target_path: str, request: Request):
|
||||||
|
"""Run the scaffold command and yield SSE events."""
|
||||||
|
# Validate template
|
||||||
|
if template not in TEMPLATES:
|
||||||
|
yield _sse_event({"type": "error", "message": f"Unknown template: {template}"})
|
||||||
|
return
|
||||||
|
|
||||||
|
# Validate path
|
||||||
|
path = Path(target_path)
|
||||||
|
try:
|
||||||
|
path = path.resolve()
|
||||||
|
except (OSError, ValueError) as e:
|
||||||
|
yield _sse_event({"type": "error", "message": f"Invalid path: {e}"})
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_path_blocked(path):
|
||||||
|
yield _sse_event({"type": "error", "message": "Access to this directory is not allowed"})
|
||||||
|
return
|
||||||
|
|
||||||
|
if not path.exists() or not path.is_dir():
|
||||||
|
yield _sse_event({"type": "error", "message": "Target directory does not exist"})
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check npx is available
|
||||||
|
npx_name = "npx"
|
||||||
|
if sys.platform == "win32":
|
||||||
|
npx_name = "npx.cmd"
|
||||||
|
|
||||||
|
if not shutil.which(npx_name):
|
||||||
|
yield _sse_event({"type": "error", "message": "npx is not available. Please install Node.js."})
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
argv = list(TEMPLATES[template])
|
||||||
|
if sys.platform == "win32" and not argv[0].lower().endswith(".cmd"):
|
||||||
|
argv[0] = argv[0] + ".cmd"
|
||||||
|
|
||||||
|
process = None
|
||||||
|
try:
|
||||||
|
popen_kwargs: dict = {
|
||||||
|
"stdout": subprocess.PIPE,
|
||||||
|
"stderr": subprocess.STDOUT,
|
||||||
|
"stdin": subprocess.DEVNULL,
|
||||||
|
"cwd": str(path),
|
||||||
|
}
|
||||||
|
if sys.platform == "win32":
|
||||||
|
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
||||||
|
|
||||||
|
process = subprocess.Popen(argv, **popen_kwargs)
|
||||||
|
logger.info("Scaffold process started: pid=%s, template=%s, path=%s", process.pid, template, target_path)
|
||||||
|
|
||||||
|
# Stream stdout lines
|
||||||
|
assert process.stdout is not None
|
||||||
|
for raw_line in iter(process.stdout.readline, b""):
|
||||||
|
# Check if client disconnected
|
||||||
|
if await request.is_disconnected():
|
||||||
|
logger.info("Client disconnected during scaffold, terminating process")
|
||||||
|
break
|
||||||
|
|
||||||
|
line = raw_line.decode("utf-8", errors="replace").rstrip("\n\r")
|
||||||
|
yield _sse_event({"type": "output", "line": line})
|
||||||
|
# Yield control to event loop so disconnect checks work
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
exit_code = process.returncode
|
||||||
|
success = exit_code == 0
|
||||||
|
logger.info("Scaffold process completed: exit_code=%s, template=%s", exit_code, template)
|
||||||
|
yield _sse_event({"type": "complete", "success": success, "exit_code": exit_code})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Scaffold error: %s", e)
|
||||||
|
yield _sse_event({"type": "error", "message": str(e)})
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if process and process.poll() is None:
|
||||||
|
try:
|
||||||
|
process.terminate()
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except Exception:
|
||||||
|
process.kill()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/run")
|
||||||
|
async def run_scaffold(body: ScaffoldRequest, request: Request):
|
||||||
|
"""Run a scaffold template command with SSE streaming output."""
|
||||||
|
return StreamingResponse(
|
||||||
|
_stream_scaffold(body.template, body.target_path, request),
|
||||||
|
media_type="text/event-stream",
|
||||||
|
headers={
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"X-Accel-Buffering": "no",
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -113,6 +113,7 @@ async def get_settings():
|
|||||||
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
|
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
|
||||||
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
|
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
|
||||||
batch_size=_parse_int(all_settings.get("batch_size"), 3),
|
batch_size=_parse_int(all_settings.get("batch_size"), 3),
|
||||||
|
testing_batch_size=_parse_int(all_settings.get("testing_batch_size"), 3),
|
||||||
api_provider=api_provider,
|
api_provider=api_provider,
|
||||||
api_base_url=all_settings.get("api_base_url"),
|
api_base_url=all_settings.get("api_base_url"),
|
||||||
api_has_auth_token=bool(all_settings.get("api_auth_token")),
|
api_has_auth_token=bool(all_settings.get("api_auth_token")),
|
||||||
@@ -138,6 +139,9 @@ async def update_settings(update: SettingsUpdate):
|
|||||||
if update.batch_size is not None:
|
if update.batch_size is not None:
|
||||||
set_setting("batch_size", str(update.batch_size))
|
set_setting("batch_size", str(update.batch_size))
|
||||||
|
|
||||||
|
if update.testing_batch_size is not None:
|
||||||
|
set_setting("testing_batch_size", str(update.testing_batch_size))
|
||||||
|
|
||||||
# API provider settings
|
# API provider settings
|
||||||
if update.api_provider is not None:
|
if update.api_provider is not None:
|
||||||
old_provider = get_setting("api_provider", "claude")
|
old_provider = get_setting("api_provider", "claude")
|
||||||
@@ -177,6 +181,7 @@ async def update_settings(update: SettingsUpdate):
|
|||||||
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
|
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
|
||||||
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
|
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
|
||||||
batch_size=_parse_int(all_settings.get("batch_size"), 3),
|
batch_size=_parse_int(all_settings.get("batch_size"), 3),
|
||||||
|
testing_batch_size=_parse_int(all_settings.get("testing_batch_size"), 3),
|
||||||
api_provider=api_provider,
|
api_provider=api_provider,
|
||||||
api_base_url=all_settings.get("api_base_url"),
|
api_base_url=all_settings.get("api_base_url"),
|
||||||
api_has_auth_token=bool(all_settings.get("api_auth_token")),
|
api_has_auth_token=bool(all_settings.get("api_auth_token")),
|
||||||
|
|||||||
@@ -444,7 +444,8 @@ class SettingsResponse(BaseModel):
|
|||||||
ollama_mode: bool = False # True when api_provider is "ollama"
|
ollama_mode: bool = False # True when api_provider is "ollama"
|
||||||
testing_agent_ratio: int = 1 # Regression testing agents (0-3)
|
testing_agent_ratio: int = 1 # Regression testing agents (0-3)
|
||||||
playwright_headless: bool = True
|
playwright_headless: bool = True
|
||||||
batch_size: int = 3 # Features per coding agent batch (1-3)
|
batch_size: int = 3 # Features per coding agent batch (1-15)
|
||||||
|
testing_batch_size: int = 3 # Features per testing agent batch (1-15)
|
||||||
api_provider: str = "claude"
|
api_provider: str = "claude"
|
||||||
api_base_url: str | None = None
|
api_base_url: str | None = None
|
||||||
api_has_auth_token: bool = False # Never expose actual token
|
api_has_auth_token: bool = False # Never expose actual token
|
||||||
@@ -463,7 +464,8 @@ class SettingsUpdate(BaseModel):
|
|||||||
model: str | None = None
|
model: str | None = None
|
||||||
testing_agent_ratio: int | None = None # 0-3
|
testing_agent_ratio: int | None = None # 0-3
|
||||||
playwright_headless: bool | None = None
|
playwright_headless: bool | None = None
|
||||||
batch_size: int | None = None # Features per agent batch (1-3)
|
batch_size: int | None = None # Features per agent batch (1-15)
|
||||||
|
testing_batch_size: int | None = None # Features per testing agent batch (1-15)
|
||||||
api_provider: str | None = None
|
api_provider: str | None = None
|
||||||
api_base_url: str | None = Field(None, max_length=500)
|
api_base_url: str | None = Field(None, max_length=500)
|
||||||
api_auth_token: str | None = Field(None, max_length=500) # Write-only, never returned
|
api_auth_token: str | None = Field(None, max_length=500) # Write-only, never returned
|
||||||
@@ -500,8 +502,15 @@ class SettingsUpdate(BaseModel):
|
|||||||
@field_validator('batch_size')
|
@field_validator('batch_size')
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_batch_size(cls, v: int | None) -> int | None:
|
def validate_batch_size(cls, v: int | None) -> int | None:
|
||||||
if v is not None and (v < 1 or v > 3):
|
if v is not None and (v < 1 or v > 15):
|
||||||
raise ValueError("batch_size must be between 1 and 3")
|
raise ValueError("batch_size must be between 1 and 15")
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator('testing_batch_size')
|
||||||
|
@classmethod
|
||||||
|
def validate_testing_batch_size(cls, v: int | None) -> int | None:
|
||||||
|
if v is not None and (v < 1 or v > 15):
|
||||||
|
raise ValueError("testing_batch_size must be between 1 and 15")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ class AgentProcessManager:
|
|||||||
testing_agent_ratio: int = 1,
|
testing_agent_ratio: int = 1,
|
||||||
playwright_headless: bool = True,
|
playwright_headless: bool = True,
|
||||||
batch_size: int = 3,
|
batch_size: int = 3,
|
||||||
|
testing_batch_size: int = 3,
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Start the agent as a subprocess.
|
Start the agent as a subprocess.
|
||||||
@@ -440,6 +441,9 @@ class AgentProcessManager:
|
|||||||
# Add --batch-size flag for multi-feature batching
|
# Add --batch-size flag for multi-feature batching
|
||||||
cmd.extend(["--batch-size", str(batch_size)])
|
cmd.extend(["--batch-size", str(batch_size)])
|
||||||
|
|
||||||
|
# Add --testing-batch-size flag for testing agent batching
|
||||||
|
cmd.extend(["--testing-batch-size", str(testing_batch_size)])
|
||||||
|
|
||||||
# Apply headless setting to .playwright/cli.config.json so playwright-cli
|
# Apply headless setting to .playwright/cli.config.json so playwright-cli
|
||||||
# picks it up (the only mechanism it supports for headless control)
|
# picks it up (the only mechanism it supports for headless control)
|
||||||
self._apply_playwright_headless(playwright_headless)
|
self._apply_playwright_headless(playwright_headless)
|
||||||
|
|||||||
276
ui/package-lock.json
generated
276
ui/package-lock.json
generated
@@ -56,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
"..": {
|
"..": {
|
||||||
"name": "autoforge-ai",
|
"name": "autoforge-ai",
|
||||||
"version": "0.1.15",
|
"version": "0.1.17",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"autoforge": "bin/autoforge.js"
|
"autoforge": "bin/autoforge.js"
|
||||||
@@ -1991,9 +1991,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||||
"integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
|
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2005,9 +2005,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
|
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2019,9 +2019,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
|
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2033,9 +2033,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
|
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2047,9 +2047,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
|
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2061,9 +2061,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
|
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2075,9 +2075,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
|
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2089,9 +2089,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
|
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2103,9 +2103,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
|
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2117,9 +2117,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
|
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2131,9 +2131,23 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
|
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||||
|
"version": "4.59.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||||
|
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -2145,9 +2159,23 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
|
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||||
|
"version": "4.59.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||||
|
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -2159,9 +2187,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
|
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -2173,9 +2201,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
|
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -2187,9 +2215,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
|
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -2201,9 +2229,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
|
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2215,9 +2243,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
|
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2228,10 +2256,24 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||||
|
"version": "4.59.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||||
|
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
|
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2243,9 +2285,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
|
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2257,9 +2299,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
|
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -2271,9 +2313,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
|
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2285,9 +2327,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
|
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3042,24 +3084,37 @@
|
|||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
"version": "2.0.2",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
|
||||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.8.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-reYkDYtj/b19TeqbNZCV4q9t+Yxylf/rYBsLb42SXJatTv4/ylq5lEiAmhA/IToxO7NI2UzNMghHoHuaqDkAjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^5.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
@@ -3227,9 +3282,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4757,9 +4812,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
@@ -5664,9 +5719,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6150,9 +6205,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.54.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||||
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
|
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6166,28 +6221,31 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.54.0",
|
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||||
"@rollup/rollup-android-arm64": "4.54.0",
|
"@rollup/rollup-android-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.54.0",
|
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.54.0",
|
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.54.0",
|
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-x64": "4.54.0",
|
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.54.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.54.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.54.0",
|
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.54.0",
|
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.54.0",
|
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.54.0",
|
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.54.0",
|
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.54.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.54.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.54.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.54.0",
|
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.54.0",
|
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.54.0",
|
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.54.0",
|
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.54.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||||
|
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
* Multi-step modal for creating new projects:
|
* Multi-step modal for creating new projects:
|
||||||
* 1. Enter project name
|
* 1. Enter project name
|
||||||
* 2. Select project folder
|
* 2. Select project folder
|
||||||
* 3. Choose spec method (Claude or manual)
|
* 3. Choose project template (blank or agentic starter)
|
||||||
* 4a. If Claude: Show SpecCreationChat
|
* 4. Choose spec method (Claude or manual)
|
||||||
* 4b. If manual: Create project and close
|
* 5a. If Claude: Show SpecCreationChat
|
||||||
|
* 5b. If manual: Create project and close
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Bot, FileEdit, ArrowRight, ArrowLeft, Loader2, CheckCircle2, Folder } from 'lucide-react'
|
import { Bot, FileEdit, ArrowRight, ArrowLeft, Loader2, CheckCircle2, Folder, Zap, FileCode2, AlertCircle, RotateCcw } from 'lucide-react'
|
||||||
import { useCreateProject } from '../hooks/useProjects'
|
import { useCreateProject } from '../hooks/useProjects'
|
||||||
import { SpecCreationChat } from './SpecCreationChat'
|
import { SpecCreationChat } from './SpecCreationChat'
|
||||||
import { FolderBrowser } from './FolderBrowser'
|
import { FolderBrowser } from './FolderBrowser'
|
||||||
@@ -32,8 +33,9 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { Card, CardContent } from '@/components/ui/card'
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
|
|
||||||
type InitializerStatus = 'idle' | 'starting' | 'error'
|
type InitializerStatus = 'idle' | 'starting' | 'error'
|
||||||
|
type ScaffoldStatus = 'idle' | 'running' | 'success' | 'error'
|
||||||
|
|
||||||
type Step = 'name' | 'folder' | 'method' | 'chat' | 'complete'
|
type Step = 'name' | 'folder' | 'template' | 'method' | 'chat' | 'complete'
|
||||||
type SpecMethod = 'claude' | 'manual'
|
type SpecMethod = 'claude' | 'manual'
|
||||||
|
|
||||||
interface NewProjectModalProps {
|
interface NewProjectModalProps {
|
||||||
@@ -57,6 +59,10 @@ export function NewProjectModal({
|
|||||||
const [initializerStatus, setInitializerStatus] = useState<InitializerStatus>('idle')
|
const [initializerStatus, setInitializerStatus] = useState<InitializerStatus>('idle')
|
||||||
const [initializerError, setInitializerError] = useState<string | null>(null)
|
const [initializerError, setInitializerError] = useState<string | null>(null)
|
||||||
const [yoloModeSelected, setYoloModeSelected] = useState(false)
|
const [yoloModeSelected, setYoloModeSelected] = useState(false)
|
||||||
|
const [scaffoldStatus, setScaffoldStatus] = useState<ScaffoldStatus>('idle')
|
||||||
|
const [scaffoldOutput, setScaffoldOutput] = useState<string[]>([])
|
||||||
|
const [scaffoldError, setScaffoldError] = useState<string | null>(null)
|
||||||
|
const scaffoldLogRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
// Suppress unused variable warning - specMethod may be used in future
|
// Suppress unused variable warning - specMethod may be used in future
|
||||||
void _specMethod
|
void _specMethod
|
||||||
@@ -91,13 +97,84 @@ export function NewProjectModal({
|
|||||||
|
|
||||||
const handleFolderSelect = (path: string) => {
|
const handleFolderSelect = (path: string) => {
|
||||||
setProjectPath(path)
|
setProjectPath(path)
|
||||||
changeStep('method')
|
changeStep('template')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFolderCancel = () => {
|
const handleFolderCancel = () => {
|
||||||
changeStep('name')
|
changeStep('name')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTemplateSelect = async (choice: 'blank' | 'agentic-starter') => {
|
||||||
|
if (choice === 'blank') {
|
||||||
|
changeStep('method')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projectPath) return
|
||||||
|
|
||||||
|
setScaffoldStatus('running')
|
||||||
|
setScaffoldOutput([])
|
||||||
|
setScaffoldError(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/scaffold/run', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ template: 'agentic-starter', target_path: projectPath }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok || !res.body) {
|
||||||
|
setScaffoldStatus('error')
|
||||||
|
setScaffoldError(`Server error: ${res.status}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = res.body.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let buffer = ''
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
const lines = buffer.split('\n')
|
||||||
|
buffer = lines.pop() || ''
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!line.startsWith('data: ')) continue
|
||||||
|
try {
|
||||||
|
const event = JSON.parse(line.slice(6))
|
||||||
|
if (event.type === 'output') {
|
||||||
|
setScaffoldOutput(prev => {
|
||||||
|
const next = [...prev, event.line]
|
||||||
|
return next.length > 100 ? next.slice(-100) : next
|
||||||
|
})
|
||||||
|
// Auto-scroll
|
||||||
|
setTimeout(() => scaffoldLogRef.current?.scrollTo(0, scaffoldLogRef.current.scrollHeight), 0)
|
||||||
|
} else if (event.type === 'complete') {
|
||||||
|
if (event.success) {
|
||||||
|
setScaffoldStatus('success')
|
||||||
|
setTimeout(() => changeStep('method'), 1200)
|
||||||
|
} else {
|
||||||
|
setScaffoldStatus('error')
|
||||||
|
setScaffoldError(`Scaffold exited with code ${event.exit_code}`)
|
||||||
|
}
|
||||||
|
} else if (event.type === 'error') {
|
||||||
|
setScaffoldStatus('error')
|
||||||
|
setScaffoldError(event.message)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// skip malformed SSE lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setScaffoldStatus('error')
|
||||||
|
setScaffoldError(err instanceof Error ? err.message : 'Failed to run scaffold')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleMethodSelect = async (method: SpecMethod) => {
|
const handleMethodSelect = async (method: SpecMethod) => {
|
||||||
setSpecMethod(method)
|
setSpecMethod(method)
|
||||||
|
|
||||||
@@ -188,13 +265,21 @@ export function NewProjectModal({
|
|||||||
setInitializerStatus('idle')
|
setInitializerStatus('idle')
|
||||||
setInitializerError(null)
|
setInitializerError(null)
|
||||||
setYoloModeSelected(false)
|
setYoloModeSelected(false)
|
||||||
|
setScaffoldStatus('idle')
|
||||||
|
setScaffoldOutput([])
|
||||||
|
setScaffoldError(null)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
if (step === 'method') {
|
if (step === 'method') {
|
||||||
changeStep('folder')
|
changeStep('template')
|
||||||
setSpecMethod(null)
|
setSpecMethod(null)
|
||||||
|
} else if (step === 'template') {
|
||||||
|
changeStep('folder')
|
||||||
|
setScaffoldStatus('idle')
|
||||||
|
setScaffoldOutput([])
|
||||||
|
setScaffoldError(null)
|
||||||
} else if (step === 'folder') {
|
} else if (step === 'folder') {
|
||||||
changeStep('name')
|
changeStep('name')
|
||||||
setProjectPath(null)
|
setProjectPath(null)
|
||||||
@@ -255,6 +340,7 @@ export function NewProjectModal({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{step === 'name' && 'Create New Project'}
|
{step === 'name' && 'Create New Project'}
|
||||||
|
{step === 'template' && 'Choose Project Template'}
|
||||||
{step === 'method' && 'Choose Setup Method'}
|
{step === 'method' && 'Choose Setup Method'}
|
||||||
{step === 'complete' && 'Project Created!'}
|
{step === 'complete' && 'Project Created!'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
@@ -294,7 +380,127 @@ export function NewProjectModal({
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Step 2: Spec Method */}
|
{/* Step 2: Project Template */}
|
||||||
|
{step === 'template' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{scaffoldStatus === 'idle' && (
|
||||||
|
<>
|
||||||
|
<DialogDescription>
|
||||||
|
Start with a blank project or use a pre-configured template.
|
||||||
|
</DialogDescription>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Card
|
||||||
|
className="cursor-pointer hover:border-primary transition-colors"
|
||||||
|
onClick={() => handleTemplateSelect('blank')}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-2 bg-secondary rounded-lg">
|
||||||
|
<FileCode2 size={24} className="text-secondary-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="font-semibold">Blank Project</span>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Start from scratch. AutoForge will scaffold your app based on the spec you define.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
className="cursor-pointer hover:border-primary transition-colors"
|
||||||
|
onClick={() => handleTemplateSelect('agentic-starter')}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-2 bg-primary/10 rounded-lg">
|
||||||
|
<Zap size={24} className="text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-semibold">Agentic Starter</span>
|
||||||
|
<Badge variant="secondary">Next.js</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Pre-configured Next.js app with BetterAuth, Drizzle ORM, Postgres, and AI capabilities.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className="sm:justify-start">
|
||||||
|
<Button variant="ghost" onClick={handleBack}>
|
||||||
|
<ArrowLeft size={16} />
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{scaffoldStatus === 'running' && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
|
<span className="font-medium">Setting up Agentic Starter...</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={scaffoldLogRef}
|
||||||
|
className="bg-muted rounded-lg p-3 max-h-60 overflow-y-auto font-mono text-xs leading-relaxed"
|
||||||
|
>
|
||||||
|
{scaffoldOutput.map((line, i) => (
|
||||||
|
<div key={i} className="whitespace-pre-wrap break-all">{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{scaffoldStatus === 'success' && (
|
||||||
|
<div className="text-center py-6">
|
||||||
|
<div className="inline-flex items-center justify-center w-12 h-12 bg-primary/10 rounded-full mb-3">
|
||||||
|
<CheckCircle2 size={24} className="text-primary" />
|
||||||
|
</div>
|
||||||
|
<p className="font-medium">Template ready!</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">Proceeding to setup method...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{scaffoldStatus === 'error' && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertCircle size={16} />
|
||||||
|
<AlertDescription>
|
||||||
|
{scaffoldError || 'An unknown error occurred'}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{scaffoldOutput.length > 0 && (
|
||||||
|
<div className="bg-muted rounded-lg p-3 max-h-40 overflow-y-auto font-mono text-xs leading-relaxed">
|
||||||
|
{scaffoldOutput.slice(-10).map((line, i) => (
|
||||||
|
<div key={i} className="whitespace-pre-wrap break-all">{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DialogFooter className="sm:justify-start gap-2">
|
||||||
|
<Button variant="ghost" onClick={handleBack}>
|
||||||
|
<ArrowLeft size={16} />
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => handleTemplateSelect('agentic-starter')}>
|
||||||
|
<RotateCcw size={16} />
|
||||||
|
Retry
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 3: Spec Method */}
|
||||||
{step === 'method' && (
|
{step === 'method' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
|
import { Slider } from '@/components/ui/slider'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -63,6 +64,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTestingBatchSizeChange = (size: number) => {
|
||||||
|
if (!updateSettings.isPending) {
|
||||||
|
updateSettings.mutate({ testing_batch_size: size })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleProviderChange = (providerId: string) => {
|
const handleProviderChange = (providerId: string) => {
|
||||||
if (!updateSettings.isPending) {
|
if (!updateSettings.isPending) {
|
||||||
updateSettings.mutate({ api_provider: providerId })
|
updateSettings.mutate({ api_provider: providerId })
|
||||||
@@ -432,28 +439,34 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Features per Agent */}
|
{/* Features per Coding Agent */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="font-medium">Features per Agent</Label>
|
<Label className="font-medium">Features per Coding Agent</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Number of features assigned to each coding agent
|
Number of features assigned to each coding agent session
|
||||||
</p>
|
</p>
|
||||||
<div className="flex rounded-lg border overflow-hidden">
|
<Slider
|
||||||
{[1, 2, 3].map((size) => (
|
min={1}
|
||||||
<button
|
max={15}
|
||||||
key={size}
|
value={settings.batch_size ?? 3}
|
||||||
onClick={() => handleBatchSizeChange(size)}
|
onChange={handleBatchSizeChange}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className={`flex-1 py-2 px-3 text-sm font-medium transition-colors ${
|
/>
|
||||||
(settings.batch_size ?? 1) === size
|
</div>
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'bg-background text-foreground hover:bg-muted'
|
{/* Features per Testing Agent */}
|
||||||
} ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
|
<div className="space-y-2">
|
||||||
>
|
<Label className="font-medium">Features per Testing Agent</Label>
|
||||||
{size}
|
<p className="text-sm text-muted-foreground">
|
||||||
</button>
|
Number of features assigned to each testing agent session
|
||||||
))}
|
</p>
|
||||||
</div>
|
<Slider
|
||||||
|
min={1}
|
||||||
|
max={15}
|
||||||
|
value={settings.testing_batch_size ?? 3}
|
||||||
|
onChange={handleTestingBatchSizeChange}
|
||||||
|
disabled={isSaving}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Update Error */}
|
{/* Update Error */}
|
||||||
|
|||||||
44
ui/src/components/ui/slider.tsx
Normal file
44
ui/src/components/ui/slider.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
||||||
|
min: number
|
||||||
|
max: number
|
||||||
|
value: number
|
||||||
|
onChange: (value: number) => void
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Slider({
|
||||||
|
className,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
}: SliderProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn("flex items-center gap-3", className)}>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(Number(e.target.value))}
|
||||||
|
disabled={disabled}
|
||||||
|
className={cn(
|
||||||
|
"slider-input h-2 w-full cursor-pointer appearance-none rounded-full bg-input transition-colors",
|
||||||
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
|
disabled && "cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<span className="min-w-[2ch] text-center text-sm font-semibold tabular-nums">
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Slider }
|
||||||
@@ -302,6 +302,7 @@ const DEFAULT_SETTINGS: Settings = {
|
|||||||
testing_agent_ratio: 1,
|
testing_agent_ratio: 1,
|
||||||
playwright_headless: true,
|
playwright_headless: true,
|
||||||
batch_size: 3,
|
batch_size: 3,
|
||||||
|
testing_batch_size: 3,
|
||||||
api_provider: 'claude',
|
api_provider: 'claude',
|
||||||
api_base_url: null,
|
api_base_url: null,
|
||||||
api_has_auth_token: false,
|
api_has_auth_token: false,
|
||||||
|
|||||||
@@ -579,7 +579,8 @@ export interface Settings {
|
|||||||
ollama_mode: boolean
|
ollama_mode: boolean
|
||||||
testing_agent_ratio: number // Regression testing agents (0-3)
|
testing_agent_ratio: number // Regression testing agents (0-3)
|
||||||
playwright_headless: boolean
|
playwright_headless: boolean
|
||||||
batch_size: number // Features per coding agent batch (1-3)
|
batch_size: number // Features per coding agent batch (1-15)
|
||||||
|
testing_batch_size: number // Features per testing agent batch (1-15)
|
||||||
api_provider: string
|
api_provider: string
|
||||||
api_base_url: string | null
|
api_base_url: string | null
|
||||||
api_has_auth_token: boolean
|
api_has_auth_token: boolean
|
||||||
@@ -592,6 +593,7 @@ export interface SettingsUpdate {
|
|||||||
testing_agent_ratio?: number
|
testing_agent_ratio?: number
|
||||||
playwright_headless?: boolean
|
playwright_headless?: boolean
|
||||||
batch_size?: number
|
batch_size?: number
|
||||||
|
testing_batch_size?: number
|
||||||
api_provider?: string
|
api_provider?: string
|
||||||
api_base_url?: string
|
api_base_url?: string
|
||||||
api_auth_token?: string
|
api_auth_token?: string
|
||||||
|
|||||||
@@ -1472,3 +1472,53 @@
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--muted-foreground);
|
background: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Slider (range input) styling
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.slider-input::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary);
|
||||||
|
border: 2px solid var(--primary-foreground);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 150ms, box-shadow 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input::-moz-range-thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary);
|
||||||
|
border: 2px solid var(--primary-foreground);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 150ms, box-shadow 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input::-moz-range-thumb:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input::-webkit-slider-runnable-track {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--input);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-input::-moz-range-track {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--input);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user