From a3bfc956e75ab8565a00835442b06789bcc37701 Mon Sep 17 00:00:00 2001 From: alkatrazz Date: Wed, 3 Jun 2026 00:35:06 +0200 Subject: [PATCH] =?UTF-8?q?fix(hermes-auto-improve):=20trigger=20asynchron?= =?UTF-8?q?e=20=E2=80=94=20r=C3=A9pond=20en=20<10ms,=20script=20en=20backg?= =?UTF-8?q?round?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le run Hermes prend ~3min (5 appels LLM). Le serveur retourne maintenant {"started": true} immédiatement. n8n attend 5min via Wait node puis GET /status. Co-Authored-By: Claude Sonnet 4.6 --- tools/hermes-auto-improve/trigger-server.py | 64 +++++++++++---------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/tools/hermes-auto-improve/trigger-server.py b/tools/hermes-auto-improve/trigger-server.py index ad7b806..493dd8d 100644 --- a/tools/hermes-auto-improve/trigger-server.py +++ b/tools/hermes-auto-improve/trigger-server.py @@ -3,12 +3,16 @@ Serveur HTTP minimal pour déclencher hermes-auto-improve depuis n8n. Écoute sur 0.0.0.0:9095. -POST /trigger → lance auto-improve.py (run standard, MAX_FILES fichiers) -POST /trigger/all → lance avec --all -POST /trigger/subdir → body JSON {"subdir": "ia"} → lance sur un sous-dossier -POST /trigger/dry-run → analyse sans commit -GET /status → dernier état (state.json) -GET /health → {"ok": true} +Les POST /trigger* retournent immédiatement {"started": true}. +Le script tourne en background thread. +Polling du résultat via GET /status. + +POST /trigger → run standard (MAX_FILES fichiers) +POST /trigger/subdir → body JSON {"subdir": "ia"} +POST /trigger/dry-run → analyse sans commit/push +POST /trigger/daily-pr → crée PR GitHub + résumé journalier +GET /status → dernier résultat + état running +GET /health → {"ok": true} """ import json @@ -27,11 +31,11 @@ _running = False _last_result = None -def run_script(extra_args: list = None) -> dict: +def run_script(extra_args: list = None): global _running, _last_result with _lock: if _running: - return {"error": "already_running"} + return _running = True try: @@ -46,7 +50,6 @@ def run_script(extra_args: list = None) -> dict: if result.stderr: log.write(f"STDERR: {result.stderr}") - # Extraire le JSON du dernier bloc de stdout out = result.stdout start = out.rfind("{") end = out.rfind("}") + 1 @@ -54,21 +57,27 @@ def run_script(extra_args: list = None) -> dict: _last_result = json.loads(out[start:end]) else: _last_result = {"error": "no_json", "stdout": out[-500:]} - return _last_result except subprocess.TimeoutExpired: _last_result = {"error": "timeout"} - return _last_result except Exception as e: _last_result = {"error": str(e)} - return _last_result finally: _running = False +def trigger_async(extra_args: list = None) -> dict: + with _lock: + if _running: + return {"started": False, "error": "already_running"} + t = threading.Thread(target=run_script, args=(extra_args,), daemon=True) + t.start() + return {"started": True, "running": True} + + class Handler(BaseHTTPRequestHandler): def log_message(self, fmt, *args): - pass # silencer les logs HTTP par défaut + pass def send_json(self, code: int, data: dict): body = json.dumps(data, ensure_ascii=False, indent=2).encode() @@ -82,41 +91,36 @@ class Handler(BaseHTTPRequestHandler): if self.path == "/health": self.send_json(200, {"ok": True, "running": _running}) elif self.path == "/status": - if STATE_FILE.exists(): - state = json.loads(STATE_FILE.read_text()) - self.send_json(200, {"state": state, "last_result": _last_result, "running": _running}) - else: - self.send_json(200, {"state": None, "last_result": _last_result, "running": _running}) + state = json.loads(STATE_FILE.read_text()) if STATE_FILE.exists() else None + self.send_json(200, {"state": state, "last_result": _last_result, "running": _running}) else: self.send_json(404, {"error": "not_found"}) def do_POST(self): - length = int(self.headers.get("Content-Length", 0)) - body = json.loads(self.rfile.read(length)) if length > 0 else {} + length = int(self.headers.get("Content-Length", 0)) + body = json.loads(self.rfile.read(length)) if length > 0 else {} if self.path == "/trigger": - result = run_script() - self.send_json(200, result) + self.send_json(200, trigger_async()) elif self.path == "/trigger/all": - result = run_script(["--all"]) - self.send_json(200, result) + self.send_json(200, trigger_async(["--all"])) elif self.path == "/trigger/subdir": subdir = body.get("subdir") if not subdir: self.send_json(400, {"error": "missing 'subdir' in body"}) return - result = run_script(["--subdir", subdir]) - self.send_json(200, result) + self.send_json(200, trigger_async(["--subdir", subdir])) elif self.path == "/trigger/dry-run": - result = run_script(["--dry-run"]) - self.send_json(200, result) + self.send_json(200, trigger_async(["--dry-run"])) + + elif self.path == "/trigger/daily-pr": + self.send_json(200, trigger_async(["--daily-pr"])) elif self.path == "/trigger/reset": - result = run_script(["--reset"]) - self.send_json(200, result) + self.send_json(200, trigger_async(["--reset"])) else: self.send_json(404, {"error": "not_found"})