mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 11:04:43 +02:00
fix(hermes-auto-improve): trigger asynchrone — répond en <10ms, script en background
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 <noreply@anthropic.com>
This commit is contained in:
parent
5d76ff1f4b
commit
a3bfc956e7
1 changed files with 34 additions and 30 deletions
|
|
@ -3,12 +3,16 @@
|
||||||
Serveur HTTP minimal pour déclencher hermes-auto-improve depuis n8n.
|
Serveur HTTP minimal pour déclencher hermes-auto-improve depuis n8n.
|
||||||
Écoute sur 0.0.0.0:9095.
|
Écoute sur 0.0.0.0:9095.
|
||||||
|
|
||||||
POST /trigger → lance auto-improve.py (run standard, MAX_FILES fichiers)
|
Les POST /trigger* retournent immédiatement {"started": true}.
|
||||||
POST /trigger/all → lance avec --all
|
Le script tourne en background thread.
|
||||||
POST /trigger/subdir → body JSON {"subdir": "ia"} → lance sur un sous-dossier
|
Polling du résultat via GET /status.
|
||||||
POST /trigger/dry-run → analyse sans commit
|
|
||||||
GET /status → dernier état (state.json)
|
POST /trigger → run standard (MAX_FILES fichiers)
|
||||||
GET /health → {"ok": true}
|
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
|
import json
|
||||||
|
|
@ -27,11 +31,11 @@ _running = False
|
||||||
_last_result = None
|
_last_result = None
|
||||||
|
|
||||||
|
|
||||||
def run_script(extra_args: list = None) -> dict:
|
def run_script(extra_args: list = None):
|
||||||
global _running, _last_result
|
global _running, _last_result
|
||||||
with _lock:
|
with _lock:
|
||||||
if _running:
|
if _running:
|
||||||
return {"error": "already_running"}
|
return
|
||||||
_running = True
|
_running = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -46,7 +50,6 @@ def run_script(extra_args: list = None) -> dict:
|
||||||
if result.stderr:
|
if result.stderr:
|
||||||
log.write(f"STDERR: {result.stderr}")
|
log.write(f"STDERR: {result.stderr}")
|
||||||
|
|
||||||
# Extraire le JSON du dernier bloc de stdout
|
|
||||||
out = result.stdout
|
out = result.stdout
|
||||||
start = out.rfind("{")
|
start = out.rfind("{")
|
||||||
end = out.rfind("}") + 1
|
end = out.rfind("}") + 1
|
||||||
|
|
@ -54,21 +57,27 @@ def run_script(extra_args: list = None) -> dict:
|
||||||
_last_result = json.loads(out[start:end])
|
_last_result = json.loads(out[start:end])
|
||||||
else:
|
else:
|
||||||
_last_result = {"error": "no_json", "stdout": out[-500:]}
|
_last_result = {"error": "no_json", "stdout": out[-500:]}
|
||||||
return _last_result
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
_last_result = {"error": "timeout"}
|
_last_result = {"error": "timeout"}
|
||||||
return _last_result
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_last_result = {"error": str(e)}
|
_last_result = {"error": str(e)}
|
||||||
return _last_result
|
|
||||||
finally:
|
finally:
|
||||||
_running = False
|
_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):
|
class Handler(BaseHTTPRequestHandler):
|
||||||
def log_message(self, fmt, *args):
|
def log_message(self, fmt, *args):
|
||||||
pass # silencer les logs HTTP par défaut
|
pass
|
||||||
|
|
||||||
def send_json(self, code: int, data: dict):
|
def send_json(self, code: int, data: dict):
|
||||||
body = json.dumps(data, ensure_ascii=False, indent=2).encode()
|
body = json.dumps(data, ensure_ascii=False, indent=2).encode()
|
||||||
|
|
@ -82,41 +91,36 @@ class Handler(BaseHTTPRequestHandler):
|
||||||
if self.path == "/health":
|
if self.path == "/health":
|
||||||
self.send_json(200, {"ok": True, "running": _running})
|
self.send_json(200, {"ok": True, "running": _running})
|
||||||
elif self.path == "/status":
|
elif self.path == "/status":
|
||||||
if STATE_FILE.exists():
|
state = json.loads(STATE_FILE.read_text()) if STATE_FILE.exists() else None
|
||||||
state = json.loads(STATE_FILE.read_text())
|
self.send_json(200, {"state": state, "last_result": _last_result, "running": _running})
|
||||||
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})
|
|
||||||
else:
|
else:
|
||||||
self.send_json(404, {"error": "not_found"})
|
self.send_json(404, {"error": "not_found"})
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
length = int(self.headers.get("Content-Length", 0))
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
body = json.loads(self.rfile.read(length)) if length > 0 else {}
|
body = json.loads(self.rfile.read(length)) if length > 0 else {}
|
||||||
|
|
||||||
if self.path == "/trigger":
|
if self.path == "/trigger":
|
||||||
result = run_script()
|
self.send_json(200, trigger_async())
|
||||||
self.send_json(200, result)
|
|
||||||
|
|
||||||
elif self.path == "/trigger/all":
|
elif self.path == "/trigger/all":
|
||||||
result = run_script(["--all"])
|
self.send_json(200, trigger_async(["--all"]))
|
||||||
self.send_json(200, result)
|
|
||||||
|
|
||||||
elif self.path == "/trigger/subdir":
|
elif self.path == "/trigger/subdir":
|
||||||
subdir = body.get("subdir")
|
subdir = body.get("subdir")
|
||||||
if not subdir:
|
if not subdir:
|
||||||
self.send_json(400, {"error": "missing 'subdir' in body"})
|
self.send_json(400, {"error": "missing 'subdir' in body"})
|
||||||
return
|
return
|
||||||
result = run_script(["--subdir", subdir])
|
self.send_json(200, trigger_async(["--subdir", subdir]))
|
||||||
self.send_json(200, result)
|
|
||||||
|
|
||||||
elif self.path == "/trigger/dry-run":
|
elif self.path == "/trigger/dry-run":
|
||||||
result = run_script(["--dry-run"])
|
self.send_json(200, trigger_async(["--dry-run"]))
|
||||||
self.send_json(200, result)
|
|
||||||
|
elif self.path == "/trigger/daily-pr":
|
||||||
|
self.send_json(200, trigger_async(["--daily-pr"]))
|
||||||
|
|
||||||
elif self.path == "/trigger/reset":
|
elif self.path == "/trigger/reset":
|
||||||
result = run_script(["--reset"])
|
self.send_json(200, trigger_async(["--reset"]))
|
||||||
self.send_json(200, result)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.send_json(404, {"error": "not_found"})
|
self.send_json(404, {"error": "not_found"})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue