agentic-ai-engineering/trigger_webhook.py

121 lines
4.1 KiB
Python

"""ADW ZTE Engine — Webhook trigger + pipeline orchestrator.
Built on the factory infrastructure (25 containers, /deploy endpoint, RSI cron)."""
import hashlib
import hmac
import json
import logging
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
import uuid
from datetime import datetime
from pathlib import Path
BASE = Path(__file__).parent
TREES = BASE / "trees"
MODULES = BASE / "adw_modules"
DEPLOY_URL = os.environ.get("DEPLOY_URL", "http://localhost:8099/deploy")
DEPLOY_TOKEN = os.environ.get("DEPLOY_TOKEN", "factory-deploy-token-2026")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("adw")
def signed_payload(action, target, actor="adw", payload_hash="none"):
data = {
"timestamp": int(time.time()),
"nonce": hashlib.sha256(f"{time.time()}:{target}:{action}".encode()).hexdigest()[:16],
"actor": actor,
"action": action,
"target": target,
"payload_hash": payload_hash,
}
body = json.dumps(data, sort_keys=True, separators=(",", ":")).encode()
data["signature"] = hmac.new(DEPLOY_TOKEN.encode(), body, hashlib.sha256).hexdigest()
return data
def post_deploy(payload):
req = urllib.request.Request(
DEPLOY_URL,
data=json.dumps(payload).encode(),
headers={"Authorization": f"Bearer {DEPLOY_TOKEN}", "Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
return r.read().decode()[:200]
except urllib.error.HTTPError as e:
return e.read().decode()[:200]
except Exception as e:
return str(e)[:200]
class ADW:
def __init__(self, issue=0, title="", body="", zte=False):
self.id = f"adw-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:6]}"
self.issue = issue
self.title = title or f"ADW {self.id}"
self.body = body
self.zte = zte # uppercase ZTE = auto-ship
self.phase_results = []
def classify(self):
t = (self.title + " " + self.body).lower()
return "patch" if any(w in t for w in ["bug", "fix", "crash"]) else "feature"
def run_phase(self, name):
script = MODULES / f"adw_{name}_iso.py"
if not script.exists():
return {"phase": name, "status": "skipped"}
log.info(f"Phase: {name}")
r = subprocess.run([
"python3",
str(script),
"--adw-id",
self.id,
"--issue",
str(self.issue),
], capture_output=True, text=True, timeout=300)
return {"phase": name, "status": "ok" if r.returncode == 0 else "fail", "exit": r.returncode}
def ship(self):
payload = signed_payload(
"patch_skill_from_pr",
self.id,
payload_hash=hashlib.sha256(f"{self.id}:{self.issue}:{self.title}".encode()).hexdigest()[:16],
)
return post_deploy(payload)
def run(self):
log.info(f"ADW {self.id} | Issue #{self.issue} | ZTE={'yes' if self.zte else 'no'}")
_ = self.classify()
phases = ["plan", "build", "test", "review", "document"]
for p in phases:
r = self.run_phase(p)
self.phase_results.append(r)
if r["status"] == "fail" and not self.zte:
break
if r["status"] == "fail" and self.zte:
break
ship_result = None
all_ok = all(r["status"] == "ok" for r in self.phase_results)
if all_ok and (self.zte or input("Ship? y/N: ") == "y"):
ship_result = self.ship()
return {"id": self.id, "issue": self.issue, "zte": self.zte, "type": self.classify(),
"phases": self.phase_results, "ship": ship_result}
if __name__ == "__main__":
import argparse
a = argparse.ArgumentParser()
a.add_argument("--issue", type=int, default=0)
a.add_argument("--title", default="")
a.add_argument("--zte", action="store_true")
args = a.parse_args()
adw = ADW(issue=args.issue, title=args.title, zte=args.zte)
print(json.dumps(adw.run(), indent=2))