From 3e34843292164513b54070e9552f94c348e65a6f Mon Sep 17 00:00:00 2001 From: Anders Holck Date: Thu, 9 Apr 2026 09:16:07 +0200 Subject: [PATCH] Bug fixes --- config.py | 2 +- main.py | 16 ++++++++---- web.py | 73 ++++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/config.py b/config.py index 64af698..5800984 100644 --- a/config.py +++ b/config.py @@ -7,7 +7,7 @@ LLM backend settings and application constants. # LLM BACKEND CONFIGURATION — Edit these to match your setup # ============================================================ LLM_BACKEND = "ollama" # "ollama" or "vllm" -OLLAMA_URL = "http://turd.hem.holck.se:11434" +OLLAMA_URL = "http://localhost:11434" VLLM_URL = "http://localhost:8000" MODEL_NAME = "gemma4:e4b" diff --git a/main.py b/main.py index 3d3d171..b1a7bc8 100644 --- a/main.py +++ b/main.py @@ -106,9 +106,10 @@ def run(args: argparse.Namespace): llm = LLM(backend=args.backend, model=args.model) # Start web dashboard if requested + _web_server = None if args.web: from .web import start_web_server - start_web_server(args.web, workdir) + _web_server = start_web_server(args.web, workdir) print(f" \033[36m🌐 Web dashboard: http://localhost:{args.web}\033[0m") logger.log("startup", f"AutoDev started in {workdir}") @@ -352,6 +353,15 @@ def run(args: argparse.Namespace): print(f" Plan: see {config.PLAN_FILE}") logger.log("complete", "All steps executed successfully") + # Shut down web server + if _web_server: + try: + from .web import stop_web_server + stop_web_server() + _web_server.server_close() + except Exception: + pass + def main(): args = parse_args() @@ -359,18 +369,14 @@ def main(): run(args) except KeyboardInterrupt: print("\n\n Interrupted by user. State saved — restart to resume.") - sys.exit(130) except SandboxViolation as e: print(f"\n \033[31m✗ SANDBOX VIOLATION: {e}\033[0m") - sys.exit(1) except LLMError as e: print(f"\n \033[31m✗ LLM ERROR: {e}\033[0m") - sys.exit(1) except Exception as e: print(f"\n \033[31m✗ UNEXPECTED ERROR: {e}\033[0m") import traceback traceback.print_exc() - sys.exit(1) if __name__ == "__main__": diff --git a/web.py b/web.py index ed47a57..16f6d81 100644 --- a/web.py +++ b/web.py @@ -12,17 +12,25 @@ import threading import urllib.parse from . import config -# Global event queue — logger pushes events, SSE endpoint streams them -_event_queue: queue.Queue = queue.Queue(maxsize=5000) +# Global event system — broadcast to all connected SSE clients +_clients: list = [] # list of queue.Queue, one per connected client +_clients_lock = threading.Lock() +_event_history: list = [] # buffer of all past events for new clients _workdir: str = "" +_shutdown: bool = False +_server = None def push_event(event_type: str, data: dict): """Push an event to all connected web clients.""" - try: - _event_queue.put_nowait({"type": event_type, "data": data}) - except queue.Full: - pass + msg = {"type": event_type, "data": data} + _event_history.append(msg) + with _clients_lock: + for q in _clients: + try: + q.put_nowait(msg) + except queue.Full: + pass class WebHandler(http.server.BaseHTTPRequestHandler): @@ -62,19 +70,28 @@ class WebHandler(http.server.BaseHTTPRequestHandler): self.send_header("Connection", "keep-alive") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() + client_queue = queue.Queue(maxsize=5000) + # Replay event history for late-connecting clients + for past_event in _event_history: + client_queue.put_nowait(past_event) + with _clients_lock: + _clients.append(client_queue) try: - while True: + while not _shutdown: try: - event = _event_queue.get(timeout=1) + event = client_queue.get(timeout=1) line = f"data: {json.dumps(event)}\n\n" self.wfile.write(line.encode("utf-8")) self.wfile.flush() except queue.Empty: - # Send keepalive self.wfile.write(b": keepalive\n\n") self.wfile.flush() except (BrokenPipeError, ConnectionResetError, OSError): pass + finally: + with _clients_lock: + if client_queue in _clients: + _clients.remove(client_queue) def _serve_file_tree(self): files = [] @@ -115,14 +132,42 @@ class WebHandler(http.server.BaseHTTPRequestHandler): self.send_error(404, "File not found or not readable") +def stop_web_server(): + global _shutdown + _shutdown = True + if _server: + threading.Thread(target=_server.shutdown, daemon=True).start() + + def start_web_server(port: int, workdir: str): - """Start the web UI server in a background thread.""" + """Start the web UI server in a background daemon thread.""" global _workdir _workdir = workdir - server = http.server.HTTPServer(("0.0.0.0", port), WebHandler) - server.daemon_threads = True - thread = threading.Thread(target=server.serve_forever, daemon=True) - thread.start() + + class ThreadedHTTPServer(http.server.HTTPServer): + allow_reuse_address = True + daemon_threads = True + + def process_request(self, request, client_address): + t = threading.Thread(target=self._handle, args=(request, client_address), + daemon=True) + t.start() + + def _handle(self, request, client_address): + try: + self.finish_request(request, client_address) + except Exception: + pass + try: + self.shutdown_request(request) + except Exception: + pass + + server = ThreadedHTTPServer(("0.0.0.0", port), WebHandler) + global _server + _server = server + t = threading.Thread(target=server.serve_forever, daemon=True) + t.start() return server