Files
plane-agentd/agentd/store.py
T
jj.wang 00d42267e5 Harden transports and state files
- Executors and relay helpers get an environment without any configured
  Plane token / webhook secret (design 8: executors hold no Plane credentials).
- Windows runner: Set-Location -LiteralPath, and a failed cd writes exit=97
  instead of running the agent in the wrong directory.
- schtasks launch: escape apostrophes and pass the script path as \"...\" so
  paths with spaces survive PowerShell 5.1 native quoting.
- State and heartbeat files are written temp + fsync + rename.
- Tests for the above plus codex run/resume command shapes and unique run dirs.
2026-10-21 07:10:41 +00:00

68 lines
2.3 KiB
Python

"""JSON state store under state/: one file for all projects, an exclusive tick lock, heartbeat and log.
state/agentd-state.json:
{"projects": {PROJECT: {"issues": {issue_uuid: rec}}}, "paused_groups": {group: {...}}}
rec: seq, runs, thread_id, thread_on [terminal, executor], thread_rounds, tags [...], last_message,
seen_comments [...], last_comment_at, retries, retry {at, reason}, rebuild, inflight {...}"""
import contextlib
import fcntl
import json
import os
import time
from pathlib import Path
class Busy(Exception):
pass
class Store:
def __init__(self, state_dir, echo=True):
self.dir = Path(state_dir)
self.echo = echo
self.path = self.dir / "agentd-state.json"
def load(self):
if self.path.exists():
data = json.loads(self.path.read_text(encoding="utf-8"))
else:
data = {}
data.setdefault("projects", {})
data.setdefault("paused_groups", {})
return data
def _atomic_write(self, path, obj):
"""Write temp + fsync + rename, so a reader (or a crash) never sees half a file."""
self.dir.mkdir(parents=True, exist_ok=True)
tmp = path.with_suffix(".tmp")
with open(tmp, "w", encoding="utf-8") as fh:
fh.write(json.dumps(obj, ensure_ascii=False, indent=1))
fh.flush()
os.fsync(fh.fileno())
os.replace(tmp, path)
def save(self, data):
self._atomic_write(self.path, data)
@contextlib.contextmanager
def lock(self):
self.dir.mkdir(parents=True, exist_ok=True)
with open(self.dir / "agentd.lock", "w") as fh:
try:
fcntl.flock(fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
raise Busy("another tick holds %s" % (self.dir / "agentd.lock")) from None
yield
def heartbeat(self, info):
info = dict(info, ts=time.time(), at=time.strftime("%Y-%m-%dT%H:%M:%S"))
self._atomic_write(self.dir / "heartbeat.json", info)
def log(self, msg):
self.dir.mkdir(parents=True, exist_ok=True)
line = time.strftime("%Y-%m-%dT%H:%M:%S") + " " + msg
if self.echo:
print(line, flush=True)
with open(self.dir / "agentd.log", "a", encoding="utf-8") as fh:
fh.write(line + "\n")