From d87b316c519a17015e54b3a196985f859c6f5c4f Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 19 Apr 2026 14:54:31 +0200 Subject: [PATCH] Make dashboard interface mode configurable --- src/serena/agent.py | 58 ++++++++++++++++--- src/serena/config/serena_config.py | 1 + .../resources/serena_config.template.yml | 13 +++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index efec89eb..c37d3b04 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -252,10 +252,11 @@ class DashboardManager: """ Open the dashboard in the default browser; supported on all platforms. """ - PYWEBVIEW = "webview" + WEBVIEW = "app" """ - Open the dashboard via a native window (using pywebview) which minimises to the tray; supported on Windows only - (on macOS, tray apps for multiple instances accumulate in the top bar, which users do not want) + Open the dashboard via a native window (using pywebview) which minimises to the tray; + supported on Windows and macOS (but on macOS, tray apps for multiple instances accumulate + in the top bar, which users may not want) """ TRAY_MANAGER = "tray_manager" """ @@ -267,15 +268,52 @@ class DashboardManager: def from_platform(cls) -> "DashboardManager.Mode": match platform.system(): case "Windows": - return cls.PYWEBVIEW + return cls.WEBVIEW case "Darwin": return cls.TRAY_MANAGER case _: return cls.BROWSER - def __init__(self, port: int, host_listen_address: str, open_dashboard_on_launch: bool, active_project: Project | None = None): + def is_supported(self) -> bool: + """ + :return: whether the mode is supported on the current platform + """ + if self == DashboardManager.Mode.WEBVIEW: + return SerenaDashboardViewer.is_current_platform_supported() + elif self == DashboardManager.Mode.TRAY_MANAGER: + return SerenaDashboardTrayManager.is_current_platform_supported() + else: + return True + + def __init__( + self, + port: int, + host_listen_address: str, + open_dashboard_on_launch: bool, + active_project: Project | None = None, + mode_str: str | None = None, + ): + # determine requested mode + if mode_str is not None: + try: + mode = self.Mode(mode_str) + except ValueError: + mode = self.Mode.from_platform() + log.warning(f"Invalid dashboard interface mode '{mode_str}' specified; falling back to platform default '{mode.value}'.") + else: + mode = self.Mode.from_platform() + + # check for mode compatibility + if not mode.is_supported(): + fallback_mode = self.Mode.from_platform() + log.warning( + f"Dashboard interface mode '{mode.value}' is not supported on the current platform; " + "falling back to '{fallback_mode.value}'." + ) + mode = fallback_mode + self._port = port - # self._mode = self.Mode.from_platform() + self._mode = mode self._mode = self.Mode.TRAY_MANAGER self._dashboard_viewer_process: multiprocessing.Process | None = None self._tray_manager_lock = threading.Lock() @@ -287,7 +325,7 @@ class DashboardManager: # handle startup match self._mode: - case self.Mode.PYWEBVIEW: + case self.Mode.WEBVIEW: self._start_dashboard_viewer(minimized=not open_dashboard_on_launch) case self.Mode.TRAY_MANAGER: init_fn = lambda: self._tray_manager_register(open_on_launch=open_dashboard_on_launch, active_project=active_project) @@ -529,7 +567,11 @@ class SerenaAgent: get_memory_log_handler(), tool_names, agent=self, tool_usage_stats=self._tool_usage_stats ).run_in_thread(host=self.serena_config.web_dashboard_listen_address) self._dashboard_manager = DashboardManager( - port, self.serena_config.web_dashboard_listen_address, self.serena_config.web_dashboard_open_on_launch, self._active_project + port, + self.serena_config.web_dashboard_listen_address, + self.serena_config.web_dashboard_open_on_launch, + self._active_project, + mode_str=self.serena_config.web_dashboard_interface, ) log.info("Serena web dashboard started at %s", self._dashboard_manager.url) # inform the GUI window (if any) diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 26815061..2f787f6d 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -682,6 +682,7 @@ class SerenaConfig(SharedConfig): trace_lsp_communication: bool = False web_dashboard: bool = True web_dashboard_open_on_launch: bool = True + web_dashboard_interface: str | None = None web_dashboard_listen_address: str = "127.0.0.1" jetbrains_plugin_server_address: str = "127.0.0.1" tool_timeout: float = DEFAULT_TOOL_TIMEOUT diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml index a823c743..5eb7ca3b 100644 --- a/src/serena/resources/serena_config.template.yml +++ b/src/serena/resources/serena_config.template.yml @@ -46,6 +46,19 @@ web_dashboard: True # See also: https://oraios.github.io/serena/02-usage/060_dashboard.html web_dashboard_open_on_launch: True +# defines the interface (application mode) used for the web dashboard (if enabled). +# If empty/null, use platform-specific default. Otherwise, possible values: +# * browser: the dashboard is opened in the default browser (if `web_dashboard_open_on_launch` is true) +# This is supported on all platforms. +# * app: the dashboard is opened in a separate native-like app window with accompanying tray icon, whose +# lifecycle is tied to the Serena process. +# If `web_dashboard_open_on_launch` is false, the dashboard can be conveniently accessed via the tray icon. +# This is supported on Windows and macOS. +# * tray_manager: use a global tray icon to provide access to the dashboards of all running Serena instances, +# opening the dashboard in browser tabs when selected from the tray menu. +# This is supported on Windows and macOS. +web_dashboard_interface: + # the address the web dashboard will listen on (bind address). web_dashboard_listen_address: 127.0.0.1