|
|
|
|
@@ -29,6 +29,9 @@ except ImportError:
|
|
|
|
|
class AgentOrchestrator:
|
|
|
|
|
"""Orchestrates the software generation process with full audit trail."""
|
|
|
|
|
|
|
|
|
|
REMOTE_READY_REPOSITORY_MODES = {'project', 'onboarded'}
|
|
|
|
|
REMOTE_READY_REPOSITORY_STATUSES = {'created', 'exists', 'ready', 'onboarded'}
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
project_id: str,
|
|
|
|
|
@@ -80,6 +83,7 @@ class AgentOrchestrator:
|
|
|
|
|
self.branch_name = self._build_pr_branch_name(project_id)
|
|
|
|
|
self.active_pull_request = None
|
|
|
|
|
self._gitea_username: str | None = None
|
|
|
|
|
existing_repository: dict | None = None
|
|
|
|
|
hinted_issue_number = (related_issue_hint or {}).get('number') if related_issue_hint else None
|
|
|
|
|
self.related_issue_number = hinted_issue_number if hinted_issue_number is not None else self._extract_issue_number(prompt_text)
|
|
|
|
|
self.related_issue: dict | None = DatabaseManager._normalize_issue(related_issue_hint)
|
|
|
|
|
@@ -110,9 +114,12 @@ class AgentOrchestrator:
|
|
|
|
|
latest_ui = self.db_manager._get_latest_ui_snapshot_data(self.history.id)
|
|
|
|
|
repository = latest_ui.get('repository') if isinstance(latest_ui, dict) else None
|
|
|
|
|
if isinstance(repository, dict) and repository:
|
|
|
|
|
existing_repository = dict(repository)
|
|
|
|
|
self.repo_owner = repository.get('owner') or self.repo_owner
|
|
|
|
|
self.repo_name = repository.get('name') or self.repo_name
|
|
|
|
|
self.repo_url = repository.get('url') or self.repo_url
|
|
|
|
|
git_state = latest_ui.get('git') if isinstance(latest_ui.get('git'), dict) else {}
|
|
|
|
|
self.branch_name = git_state.get('active_branch') or self.branch_name
|
|
|
|
|
if self.prompt_text:
|
|
|
|
|
self.prompt_audit = self.db_manager.log_prompt_submission(
|
|
|
|
|
history_id=self.history.id,
|
|
|
|
|
@@ -129,18 +136,26 @@ class AgentOrchestrator:
|
|
|
|
|
self.ui_manager.ui_data["project_root"] = str(self.project_root)
|
|
|
|
|
self.ui_manager.ui_data["features"] = list(self.features)
|
|
|
|
|
self.ui_manager.ui_data["tech_stack"] = list(self.tech_stack)
|
|
|
|
|
self.ui_manager.ui_data["repository"] = {
|
|
|
|
|
repository_ui = {
|
|
|
|
|
"owner": self.repo_owner,
|
|
|
|
|
"name": self.repo_name,
|
|
|
|
|
"mode": "project" if settings.use_project_repositories else "shared",
|
|
|
|
|
"status": "pending" if settings.use_project_repositories else "shared",
|
|
|
|
|
"provider": "gitea",
|
|
|
|
|
}
|
|
|
|
|
if existing_repository:
|
|
|
|
|
repository_ui.update(existing_repository)
|
|
|
|
|
self.ui_manager.ui_data["repository"] = repository_ui
|
|
|
|
|
if self.related_issue:
|
|
|
|
|
self.ui_manager.ui_data["related_issue"] = self.related_issue
|
|
|
|
|
if self.active_pull_request:
|
|
|
|
|
self.ui_manager.ui_data["pull_request"] = self.active_pull_request
|
|
|
|
|
|
|
|
|
|
def _repository_supports_remote_delivery(self, repository: dict | None = None) -> bool:
|
|
|
|
|
"""Return whether repository metadata supports git push and PR delivery."""
|
|
|
|
|
repo = repository or self.ui_manager.ui_data.get('repository') or {}
|
|
|
|
|
return repo.get('mode') in self.REMOTE_READY_REPOSITORY_MODES and repo.get('status') in self.REMOTE_READY_REPOSITORY_STATUSES
|
|
|
|
|
|
|
|
|
|
def _static_files(self) -> dict[str, str]:
|
|
|
|
|
"""Files that do not need prompt-specific generation."""
|
|
|
|
|
return {
|
|
|
|
|
@@ -309,6 +324,14 @@ class AgentOrchestrator:
|
|
|
|
|
self.db_manager.attach_issue_to_prompt(self.prompt_audit.id, self.related_issue)
|
|
|
|
|
|
|
|
|
|
async def _ensure_remote_repository(self) -> None:
|
|
|
|
|
repository = self.ui_manager.ui_data.get("repository") or {}
|
|
|
|
|
if self._repository_supports_remote_delivery(repository):
|
|
|
|
|
repository.setdefault("provider", "gitea")
|
|
|
|
|
repository.setdefault("status", "ready")
|
|
|
|
|
if repository.get("url"):
|
|
|
|
|
self.repo_url = repository.get("url")
|
|
|
|
|
self.ui_manager.ui_data["repository"] = repository
|
|
|
|
|
return
|
|
|
|
|
if not settings.use_project_repositories:
|
|
|
|
|
self.ui_manager.ui_data["repository"]["status"] = "shared"
|
|
|
|
|
if settings.gitea_repo:
|
|
|
|
|
@@ -400,9 +423,7 @@ class AgentOrchestrator:
|
|
|
|
|
async def _push_branch(self, branch: str) -> dict | None:
|
|
|
|
|
"""Push a branch to the configured project repository when available."""
|
|
|
|
|
repository = self.ui_manager.ui_data.get('repository') or {}
|
|
|
|
|
if repository.get('mode') != 'project':
|
|
|
|
|
return None
|
|
|
|
|
if repository.get('status') not in {'created', 'exists', 'ready'}:
|
|
|
|
|
if not self._repository_supports_remote_delivery(repository):
|
|
|
|
|
return None
|
|
|
|
|
if not settings.gitea_token or not self.repo_owner or not self.repo_name:
|
|
|
|
|
return None
|
|
|
|
|
@@ -449,7 +470,7 @@ class AgentOrchestrator:
|
|
|
|
|
self.ui_manager.ui_data['pull_request'] = self.active_pull_request
|
|
|
|
|
return self.active_pull_request
|
|
|
|
|
repository = self.ui_manager.ui_data.get('repository') or {}
|
|
|
|
|
if repository.get('mode') != 'project' or repository.get('status') not in {'created', 'exists', 'ready'}:
|
|
|
|
|
if not self._repository_supports_remote_delivery(repository):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
title = f"AI delivery for {self.project_name}"
|
|
|
|
|
@@ -490,9 +511,7 @@ class AgentOrchestrator:
|
|
|
|
|
async def _push_remote_commit(self, commit_hash: str, commit_message: str, changed_files: list[str], base_commit: str | None) -> dict | None:
|
|
|
|
|
"""Push the local commit to the provisioned Gitea repository and build browser links."""
|
|
|
|
|
repository = self.ui_manager.ui_data.get("repository") or {}
|
|
|
|
|
if repository.get("mode") != "project":
|
|
|
|
|
return None
|
|
|
|
|
if repository.get("status") not in {"created", "exists", "ready"}:
|
|
|
|
|
if not self._repository_supports_remote_delivery(repository):
|
|
|
|
|
return None
|
|
|
|
|
push_result = await self._push_branch(self.branch_name)
|
|
|
|
|
if push_result is None:
|
|
|
|
|
|