fix(bridge): approval flow robustness — pending cleanup, MERGE dedup, false positive filter, auto_resolve, 30min timeout
This commit is contained in:
63
bridge.py
63
bridge.py
@@ -68,15 +68,46 @@ class BridgeProtocol:
|
||||
for d in [self.pending_dir, self.response_dir, self.commands_dir]:
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Startup cleanup: purge stale pending files (> 5 min old)
|
||||
self._cleanup_stale_pending()
|
||||
|
||||
logger.info(f"Bridge protocol initialized: {self.bridge_dir}")
|
||||
|
||||
def get_pending_requests(self) -> list[ApprovalRequest]:
|
||||
"""Read all pending approval requests."""
|
||||
requests = []
|
||||
fields = {f.name for f in ApprovalRequest.__dataclass_fields__.values()}
|
||||
def _cleanup_stale_pending(self, max_age_seconds: int = 300):
|
||||
"""Remove pending files older than max_age_seconds on startup."""
|
||||
now = time.time()
|
||||
cleaned = 0
|
||||
for f in self.pending_dir.glob("*.json"):
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding="utf-8-sig"))
|
||||
ts = data.get("timestamp", 0)
|
||||
if now - ts > max_age_seconds:
|
||||
f.unlink()
|
||||
cleaned += 1
|
||||
except (json.JSONDecodeError, OSError):
|
||||
f.unlink() # corrupt file, remove
|
||||
cleaned += 1
|
||||
if cleaned:
|
||||
logger.info(f"Startup cleanup: removed {cleaned} stale pending files")
|
||||
|
||||
def get_pending_requests(self) -> list[ApprovalRequest]:
|
||||
"""Read all pending approval requests. Skips files older than 5 minutes."""
|
||||
requests = []
|
||||
fields = {f.name for f in ApprovalRequest.__dataclass_fields__.values()}
|
||||
now = time.time()
|
||||
MAX_AGE = 300 # 5 minutes
|
||||
for f in self.pending_dir.glob("*.json"):
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding="utf-8-sig"))
|
||||
ts = data.get("timestamp", 0)
|
||||
if now - ts > MAX_AGE:
|
||||
# Too old — mark expired and skip
|
||||
data["status"] = "expired"
|
||||
f.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
continue
|
||||
if data.get("status") == "pending":
|
||||
# Filter to known fields only
|
||||
filtered = {k: v for k, v in data.items() if k in fields}
|
||||
@@ -85,6 +116,19 @@ class BridgeProtocol:
|
||||
logger.warning(f"Bad pending request {f.name}: {e}")
|
||||
return requests
|
||||
|
||||
def read_pending_request(self, request_id: str) -> ApprovalRequest | None:
|
||||
"""Re-read a specific pending request (to get merged data)."""
|
||||
f = self.pending_dir / f"{request_id}.json"
|
||||
if not f.exists():
|
||||
return None
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding="utf-8-sig"))
|
||||
fields = {fn.name for fn in ApprovalRequest.__dataclass_fields__.values()}
|
||||
filtered = {k: v for k, v in data.items() if k in fields}
|
||||
return ApprovalRequest(**filtered)
|
||||
except (json.JSONDecodeError, TypeError, OSError):
|
||||
return None
|
||||
|
||||
def write_response(self, response: UserResponse):
|
||||
"""Write a user response to the response directory."""
|
||||
response.timestamp = time.time()
|
||||
@@ -97,17 +141,12 @@ class BridgeProtocol:
|
||||
)
|
||||
logger.info(f"Response written: {filename} (approved={response.approved})")
|
||||
|
||||
# Mark pending request as processed
|
||||
# Delete pending file after processing (prevents re-processing and accumulation)
|
||||
pending_file = self.pending_dir / filename
|
||||
if pending_file.exists():
|
||||
try:
|
||||
data = json.loads(pending_file.read_text(encoding="utf-8"))
|
||||
data["status"] = "approved" if response.approved else "rejected"
|
||||
pending_file.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8"
|
||||
)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pending_file.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def write_command(self, conversation_id: str, text: str, *, project_name: str = ""):
|
||||
|
||||
Reference in New Issue
Block a user