45 lines
1.5 KiB
Python
45 lines
1.5 KiB
Python
from abc import ABC, abstractmethod
|
|
from datetime import timezone, datetime
|
|
import json, os, tempfile
|
|
|
|
|
|
class BaseAgent(ABC):
|
|
@abstractmethod
|
|
def run(self, issue_id: str):
|
|
pass # To be implemented by subclasses
|
|
|
|
REQUIRED_KEYS = {
|
|
"schema_version", "agent_id", "issue_id", "state",
|
|
"state_since", "run_id", "attempt", "counters", "max",
|
|
"branch_name", "events",
|
|
}
|
|
|
|
def write_state(agent_dir: str, issue_id: str, state: dict) -> None:
|
|
path = os.path.join(agent_dir, "state", f"{issue_id}.json")
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
state["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path), suffix=".tmp")
|
|
try:
|
|
with os.fdopen(fd, "w") as f:
|
|
json.dump(state, f, indent=2, sort_keys=False)
|
|
f.flush()
|
|
os.fsync(f.fileno())
|
|
os.replace(tmp, path) # atomic on POSIX and Windows
|
|
except Exception:
|
|
os.unlink(tmp)
|
|
raise
|
|
|
|
def read_state(agent_dir: str, issue_id: str) -> dict | None:
|
|
path = os.path.join(agent_dir, "state", f"{issue_id}.json")
|
|
if not os.path.exists(path):
|
|
return None
|
|
try:
|
|
with open(path) as f:
|
|
data = json.load(f)
|
|
except (json.JSONDecodeError, OSError):
|
|
return None # caller treats as corrupt → re-run
|
|
if data.get("schema_version") != 1:
|
|
return None # schema mismatch → re-run
|
|
if not REQUIRED_KEYS.issubset(data):
|
|
return None
|
|
return data |