Merge branch 'dashboard-monitor-parent' into v1.1-develop

This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-04-10 13:50:34 +02:00
commit c7b4fb49cb
3 files changed
+50 -10

No files matched your search

+5 -1
View File
@@ -4,11 +4,15 @@ Status of the `main` branch. Changes prior to the next official version change w
* General:
- Added `serena init` command
* Language Servers:
- Added Crystal language support (uses [Crystalline](https://github.com/elbywan/crystalline) language server)
- `JetBrainsFindReferencingSymbolTool`: Include context lines (when using plugin version 2023.2.15+)
* Dashboard:
- Add version display
- Add version display
- Dashboard viewer (Windows): Add a parent monitoring thread to ensure termination.
Some clients would terminate the MCP server in a way that did not ensure proper termination.
* Fixes:
- Fix reactivation of the same project restarting language servers #1280
+3 -3
View File
@@ -554,12 +554,12 @@ class SerenaAgent:
return self._dashboard_url
@staticmethod
def _start_dashboard_viewer_process_function(url: str, minimized: bool) -> None:
def _start_dashboard_viewer_process_function(url: str, minimized: bool, parent_process_id: int) -> None:
"""
Main function of the subprocess for starting the dashboard viewer
"""
try:
SerenaDashboardViewer(url, start_minimized=minimized).run()
SerenaDashboardViewer(url, start_minimized=minimized, parent_process_id=parent_process_id).run()
except webview.errors.WebViewException as e:
log.warning(f"Could not open Serena Dashboard viewer. Cause:\n{e}")
# Fall back to opening the browser window if the window was supposed to be shown directly
@@ -582,7 +582,7 @@ class SerenaAgent:
assert url is not None
if SerenaDashboardViewer.is_current_platform_supported():
self._dashboard_viewer_process = multiprocessing.Process(
target=self._start_dashboard_viewer_process_function, args=(url, minimized), daemon=True
target=self._start_dashboard_viewer_process_function, args=(url, minimized, os.getpid()), daemon=True
)
self._dashboard_viewer_process.start()
else:
+42 -6
View File
@@ -3,11 +3,13 @@ import os
import socket
import sys
import threading
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import TYPE_CHECKING, Any, Self
import psutil
import webview
from flask import Flask, Response, redirect, request, send_from_directory
from PIL import Image
@@ -743,6 +745,8 @@ class SerenaDashboardViewer:
Minimal pywebview wrapper with optional system tray.
"""
DEBUG = False
def __init__(
self,
url: str,
@@ -750,6 +754,7 @@ class SerenaDashboardViewer:
start_minimized: bool = False,
width: int = 1400,
height: int = 900,
parent_process_id: int | None = None,
):
self.url = url
# Use system tray to allow hiding to tray and intercepting close to hide instead of quit
@@ -758,6 +763,7 @@ class SerenaDashboardViewer:
self.width = width
self.height = height
self.start_minimized = start_minimized
self.parent_process_id = parent_process_id
self.window: webview.Window
self._tray_icon: Any
@@ -786,6 +792,10 @@ class SerenaDashboardViewer:
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("oraios.serena")
if self.DEBUG:
logging.configure(level=logging.DEBUG)
logging.add_file_logger(SerenaPaths().get_next_log_file_path("dashboard-viewer"))
dashboard_path = Path(SERENA_DASHBOARD_DIR)
# .ico is Windows-only; macOS expects a PNG for the window/dock icon.
icon_filename = "serena.ico" if sys.platform == "win32" else "serena-icon-1024-mac.png"
@@ -815,8 +825,27 @@ class SerenaDashboardViewer:
else:
self._show_window()
# start a thread to monitor the parent process
if self.parent_process_id is not None:
threading.Thread(target=self._monitor_parent_process, daemon=True).start()
webview.start(_start_callback, icon=icon_path)
def _monitor_parent_process(self) -> None:
"""
Monitors the parent process and shuts down the dashboard viewer if the parent process exits.
"""
pid = self.parent_process_id
log.info("Starting parent process monitor thread (parent_pid=%d, pid=%s)", pid, os.getpid())
try:
parent_process = psutil.Process(pid)
while parent_process.is_running():
time.sleep(1)
except psutil.NoSuchProcess:
pass # Parent process already exited
log.info("Parent process (pid=%d) has exited, shutting down dashboard viewer", pid)
self._terminate()
def _show_window(self) -> None:
if not self.window:
return
@@ -885,6 +914,18 @@ class SerenaDashboardViewer:
self._hide_window()
return False # prevent the window from actually closing
def _terminate(self) -> None:
"""
Terminates the viewer application and the tray icon
"""
self._quitting = True
try:
if self._tray_icon:
self._tray_icon.stop()
finally:
if self.window:
self.window.destroy()
def _start_tray(self) -> None:
# import pystray locally, because the import fails when there is no display!
import pystray
@@ -905,12 +946,7 @@ class SerenaDashboardViewer:
self._hide_window()
def quit_app(_icon: TrayIcon, _item: Item) -> None:
self._quitting = True
try:
_icon.stop()
finally:
if self.window:
self.window.destroy()
self._terminate()
menu = pystray.Menu(
Item("Open", show, default=True),