Refine runtime git delivery scaffolding

This commit is contained in:
elipitc
2026-06-03 12:44:07 +08:00
parent bf1508e49c
commit 880139ab3e
+194
View File
@@ -3,9 +3,14 @@
import asyncio
import json
import logging
import os
import re
import shutil
import subprocess
import tempfile
import uuid
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Any, Optional
from sqlalchemy.orm import Session
@@ -60,6 +65,7 @@ class SwarmOrchestrator:
self.callback: Optional[CallbackDeliveryClient] = None
self.correlation_id: Optional[str] = None
self.k8s_manager: Optional[K8sManager] = None
self._git_workspace_dir: Optional[Path] = None
async def initialize(self) -> bool:
"""
@@ -201,6 +207,7 @@ class SwarmOrchestrator:
result = await self._execute_hybrid()
artifacts = self._ensure_result_artifacts(result)
artifacts = self._attach_git_delivery_refs(artifacts)
# Update swarm status
self.swarm.status = SwarmStatus.COMPLETED
@@ -700,6 +707,11 @@ class SwarmOrchestrator:
for client in self.agents.values():
await client.close()
self.agents.clear()
if self._git_workspace_dir:
workspace_root = self._git_workspace_dir.parent
if workspace_root.exists():
shutil.rmtree(workspace_root, ignore_errors=True)
self._git_workspace_dir = None
def _callback_context(self) -> Dict[str, Any]:
"""Return callback context stored on the swarm."""
@@ -1288,6 +1300,188 @@ class SwarmOrchestrator:
logger.warning("Failed to persist runtime project artifact %s: %s", artifact_id, e)
return None
def _git_context(self) -> Optional[Dict[str, Any]]:
"""Return repository context when Runtime should write delivery branches."""
context = self._callback_context()
repo_url = context.get("repo_url")
if not repo_url:
return None
return {
"repo_url": repo_url,
"base_branch": context.get("branch") or "main",
"git_binding_id": context.get("git_binding_id"),
}
def _git_credentials(self) -> Optional[tuple[str, str]]:
"""Resolve git credentials from runtime environment."""
username = os.getenv("GITEE_USERNAME") or ""
token = os.getenv("GITEE_TOKEN") or ""
password = os.getenv("GITEE_PASSWORD") or ""
if token and token != "your-gitee-token":
return username or "oauth2", token
if password and password != "your-gitee-password":
return username or "git", password
return None
def _inject_git_credentials(self, repo_url: str, username: str, password: str) -> str:
"""Inject credentials into an HTTP(S) git URL without persisting them."""
if "://" not in repo_url:
return repo_url
proto, rest = repo_url.split("://", 1)
if "@" in rest:
rest = rest.split("@", 1)[1]
return f"{proto}://{username}:{password}@{rest}"
def _run_git(self, args: List[str], cwd: Path, *, timeout: int = 120) -> subprocess.CompletedProcess:
"""Run a git command and raise on failure."""
result = subprocess.run(
args,
cwd=str(cwd),
capture_output=True,
text=True,
timeout=timeout,
)
if result.returncode != 0:
raise RuntimeError(result.stderr.strip() or result.stdout.strip() or f"git command failed: {' '.join(args)}")
return result
def _ensure_git_workspace(self) -> Optional[Path]:
"""Clone the target repository once and reuse it for role and delivery branches."""
git_context = self._git_context()
credentials = self._git_credentials()
if not git_context or not credentials:
return None
if self._git_workspace_dir and self._git_workspace_dir.exists():
return self._git_workspace_dir
username, password = credentials
repo_url = git_context["repo_url"]
auth_url = self._inject_git_credentials(repo_url, username, password)
workspace = Path(tempfile.mkdtemp(prefix=f"swarm_git_{self.swarm_id}_"))
repo_dir = workspace / "repo"
self._run_git(["git", "clone", "--branch", git_context["base_branch"], auth_url, str(repo_dir)], workspace, timeout=180)
self._run_git(["git", "remote", "set-url", "origin", repo_url], repo_dir)
self._run_git(["git", "config", "user.name", "heicode-agent"], repo_dir)
self._run_git(["git", "config", "user.email", "bot@heicode.local"], repo_dir)
self._git_workspace_dir = repo_dir
return repo_dir
def _write_project_files_to_repo(self, repo_dir: Path, files: Dict[str, str]) -> None:
"""Materialize project artifact files into a git workspace."""
for relative_path, content in files.items():
file_path = repo_dir / relative_path
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(content, encoding="utf-8")
def _git_repo_files_from_artifact(self, artifact: Dict[str, Any]) -> Dict[str, str]:
"""Read back stored project artifact files for git delivery."""
artifact_id = artifact.get("artifact_id")
project = store = None
from .artifact_store import load_runtime_project_artifact
project = load_runtime_project_artifact(self.swarm_id, artifact_id)
if not project:
return {}
files = {}
root_dir = project.artifact_dir / project.root_dir
for file_path in sorted(root_dir.rglob("*")):
if file_path.is_file():
rel = file_path.relative_to(root_dir).as_posix()
files[rel] = file_path.read_text(encoding="utf-8", errors="replace")
return files
def _attach_git_delivery_refs(self, artifacts: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Best-effort git branch/commit materialization for project-folder artifacts."""
repo_dir = self._ensure_git_workspace()
if not repo_dir:
return artifacts
git_context = self._git_context() or {}
credentials = self._git_credentials()
if not credentials:
return artifacts
username, password = credentials
origin_url = self._run_git(["git", "remote", "get-url", "origin"], repo_dir).stdout.strip()
auth_url = self._inject_git_credentials(origin_url, username, password)
base_branch = git_context["base_branch"]
role_branches = []
updated_artifacts = []
base_git_ref = self._git_ref_metadata() or {}
for artifact in artifacts:
metadata = artifact.get("metadata") or {}
if metadata.get("artifact_layout") != "project_folder":
updated_artifacts.append(artifact)
continue
role = metadata.get("source_agent_role") or metadata.get("agent_role") or "worker"
role_branch = f"agent/{role}/{self.swarm_id}"
self._run_git(["git", "checkout", base_branch], repo_dir)
self._run_git(["git", "checkout", "-B", role_branch], repo_dir)
files = self._git_repo_files_from_artifact(artifact)
if not files:
updated_artifacts.append(artifact)
continue
self._write_project_files_to_repo(repo_dir, files)
self._run_git(["git", "add", "-A"], repo_dir)
commit_message = f"sub-mode {role} delivery for {self.swarm_id}"
commit_result = subprocess.run(
["git", "commit", "-m", commit_message],
cwd=str(repo_dir),
capture_output=True,
text=True,
timeout=120,
)
if commit_result.returncode != 0 and "nothing to commit" not in (commit_result.stdout + commit_result.stderr).lower():
raise RuntimeError(commit_result.stderr.strip() or commit_result.stdout.strip() or "git commit failed")
self._run_git(["git", "push", auth_url, role_branch], repo_dir, timeout=180)
commit_sha = self._run_git(["git", "rev-parse", "HEAD"], repo_dir).stdout.strip()
metadata["git_ref"] = {
"provider": metadata.get("git_ref", {}).get("provider") or base_git_ref.get("provider") or "git",
"repo_url": origin_url,
"base_branch": base_branch,
"branch": role_branch,
"commit_sha": commit_sha,
"git_binding_id": git_context.get("git_binding_id"),
}
artifact["metadata"] = metadata
updated_artifacts.append(artifact)
role_branches.append(role_branch)
if role_branches:
delivery_branch = f"delivery/{self.swarm_id}"
self._run_git(["git", "checkout", base_branch], repo_dir)
self._run_git(["git", "checkout", "-B", delivery_branch], repo_dir)
for role_branch in role_branches:
merge_result = subprocess.run(
["git", "merge", "--no-ff", "--no-edit", role_branch],
cwd=str(repo_dir),
capture_output=True,
text=True,
timeout=180,
)
if merge_result.returncode != 0:
raise RuntimeError(merge_result.stderr.strip() or merge_result.stdout.strip() or f"git merge failed for {role_branch}")
self._run_git(["git", "push", auth_url, delivery_branch], repo_dir, timeout=180)
delivery_sha = self._run_git(["git", "rev-parse", "HEAD"], repo_dir).stdout.strip()
if self.swarm:
context = self._callback_context()
context["delivery_branch"] = delivery_branch
context["delivery_commit_sha"] = delivery_sha
self.swarm.project_context = context
self.db.commit()
for artifact in updated_artifacts:
metadata = artifact.get("metadata") or {}
if metadata.get("artifact_layout") == "project_folder":
metadata["delivery_ref"] = {
"kind": "git_branch",
"branch": delivery_branch,
"commit_sha": delivery_sha,
"project_revision": metadata.get("project_revision", 1),
}
artifact["metadata"] = metadata
return updated_artifacts
def _project_files_from_content(self, role: str, content: str) -> tuple[Dict[str, str], str]:
"""Convert a code-oriented response into a minimal project-folder file set."""
root_dir = f"{role}-delivery"