from __future__ import annotations import os import pwd import subprocess from pathlib import Path from .config import Settings class AiderAgent: def __init__(self, settings: Settings) -> None: self.settings = settings def run(self, workspace: Path, task: str) -> str: task_file = workspace / ".git" / "failure-task.txt" task_file.write_text(task) task_file.chmod(0o600) command = [ self.settings.agent_command, "--model", self.settings.agent_model, "--message-file", str(task_file), "--yes", "--no-auto-commits", "--no-dirty-commits", "--no-stream", ] coder = pwd.getpwnam("coder") os.chown(task_file, coder.pw_uid, coder.pw_gid) for path in workspace.rglob("*"): if ".git" not in path.parts: os.chown(path, coder.pw_uid, coder.pw_gid, follow_symlinks=False) os.chown(workspace, coder.pw_uid, coder.pw_gid, follow_symlinks=False) environment = { "HOME": coder.pw_dir, "PATH": os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin"), } for name in ("MISTRAL_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"): if os.getenv(name): environment[name] = os.environ[name] def drop_privileges() -> None: os.setgroups([]) os.setgid(coder.pw_gid) os.setuid(coder.pw_uid) result = subprocess.run( command, cwd=workspace, env=environment, text=True, capture_output=True, check=False, preexec_fn=drop_privileges, timeout=3600, ) if result.returncode != 0: raise RuntimeError(f"Aider failed ({result.returncode}): {result.stderr[-4000:]}") return result.stdout[-12_000:]