mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 06:15:43 +00:00
Changed dashboard listen address to 127.0.0.1
Also minor refactoring in SerenaConfig that allows not repeating the defaults (as field defaults and in `from_config_file`)
This commit is contained in:
1 parent
904a25053e
commit
7bef5a6ee3
4 files changed
+51
-20
No files matched your search
+6
-2
@@ -271,8 +271,12 @@ class SerenaAgent:
|
||||
if self.serena_config.web_dashboard:
|
||||
self._dashboard_thread, port = SerenaDashboardAPI(
|
||||
get_memory_log_handler(), tool_names, agent=self, tool_usage_stats=self._tool_usage_stats
|
||||
).run_in_thread()
|
||||
dashboard_url = f"http://127.0.0.1:{port}/dashboard/index.html"
|
||||
).run_in_thread(host=self.serena_config.web_dashboard_host)
|
||||
host_for_url = self.serena_config.web_dashboard_host
|
||||
# this usually means we are in docker, so we use localhost for URL (the user has to port-forward then)
|
||||
if host_for_url == "0.0.0.0":
|
||||
host_for_url = "127.0.0.1"
|
||||
dashboard_url = f"http://{self.serena_config.web_dashboard_host}:{port}/dashboard/index.html"
|
||||
log.info("Serena web dashboard started at %s", dashboard_url)
|
||||
if self.serena_config.web_dashboard_open_on_launch:
|
||||
# open the dashboard URL in the default web browser (using a separate process to control
|
||||
|
||||
@@ -28,7 +28,7 @@ from serena.constants import (
|
||||
SERENA_MANAGED_DIR_IN_HOME,
|
||||
SERENA_MANAGED_DIR_NAME,
|
||||
)
|
||||
from serena.util.general import load_yaml, save_yaml
|
||||
from serena.util.general import get_dataclass_default, load_yaml, save_yaml
|
||||
from serena.util.inspection import determine_programming_language_composition
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
@@ -342,6 +342,8 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
trace_lsp_communication: bool = False
|
||||
web_dashboard: bool = True
|
||||
web_dashboard_open_on_launch: bool = True
|
||||
web_dashboard_host: str = "127.0.0.1"
|
||||
"""Will be automatically set to '0.0.0.0' when running in Docker. Not loaded from config file."""
|
||||
tool_timeout: float = DEFAULT_TOOL_TIMEOUT
|
||||
loaded_commented_yaml: CommentedMap | None = None
|
||||
config_file_path: str | None = None
|
||||
@@ -373,6 +375,10 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
CONFIG_FILE = "serena_config.yml"
|
||||
CONFIG_FILE_DOCKER = "serena_config.docker.yml" # Docker-specific config file; auto-generated if missing, mounted via docker-compose for user customization
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if is_running_in_docker():
|
||||
self.web_dashboard_host = "0.0.0.0"
|
||||
|
||||
def _tostring_includes(self) -> list[str]:
|
||||
return ["config_file_path"]
|
||||
|
||||
@@ -456,23 +462,24 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
instance.projects.append(project)
|
||||
|
||||
# set other configuration parameters
|
||||
def get_value_or_default(field_name: str) -> Any:
|
||||
return loaded_commented_yaml.get(field_name, get_dataclass_default(SerenaConfig, field_name))
|
||||
|
||||
if is_running_in_docker():
|
||||
instance.gui_log_window_enabled = False # not supported in Docker
|
||||
else:
|
||||
instance.gui_log_window_enabled = loaded_commented_yaml.get("gui_log_window", False)
|
||||
instance.gui_log_window_enabled = get_value_or_default("gui_log_window_enabled")
|
||||
instance.log_level = loaded_commented_yaml.get("log_level", loaded_commented_yaml.get("gui_log_level", logging.INFO))
|
||||
instance.web_dashboard = loaded_commented_yaml.get("web_dashboard", True)
|
||||
instance.web_dashboard_open_on_launch = loaded_commented_yaml.get("web_dashboard_open_on_launch", True)
|
||||
instance.tool_timeout = loaded_commented_yaml.get("tool_timeout", DEFAULT_TOOL_TIMEOUT)
|
||||
instance.trace_lsp_communication = loaded_commented_yaml.get("trace_lsp_communication", False)
|
||||
instance.excluded_tools = loaded_commented_yaml.get("excluded_tools", [])
|
||||
instance.included_optional_tools = loaded_commented_yaml.get("included_optional_tools", [])
|
||||
instance.jetbrains = loaded_commented_yaml.get("jetbrains", False)
|
||||
instance.token_count_estimator = loaded_commented_yaml.get(
|
||||
"token_count_estimator", RegisteredTokenCountEstimator.TIKTOKEN_GPT4O.name
|
||||
)
|
||||
instance.default_max_tool_answer_chars = loaded_commented_yaml.get("default_max_tool_answer_chars", 150_000)
|
||||
instance.ls_specific_settings = loaded_commented_yaml.get("ls_specific_settings", {})
|
||||
instance.web_dashboard = get_value_or_default("web_dashboard")
|
||||
instance.web_dashboard_open_on_launch = get_value_or_default("web_dashboard_open_on_launch")
|
||||
instance.tool_timeout = get_value_or_default("tool_timeout")
|
||||
instance.trace_lsp_communication = get_value_or_default("trace_lsp_communication")
|
||||
instance.excluded_tools = get_value_or_default("excluded_tools")
|
||||
instance.included_optional_tools = get_value_or_default("included_optional_tools")
|
||||
instance.jetbrains = get_value_or_default("jetbrains")
|
||||
instance.token_count_estimator = get_value_or_default("token_count_estimator")
|
||||
instance.default_max_tool_answer_chars = get_value_or_default("default_max_tool_answer_chars")
|
||||
instance.ls_specific_settings = get_value_or_default("ls_specific_settings")
|
||||
|
||||
# re-save the configuration file if any migrations were performed
|
||||
if num_project_migrations > 0:
|
||||
|
||||
@@ -560,7 +560,7 @@ class SerenaDashboardAPI:
|
||||
|
||||
raise RuntimeError(f"No free ports found starting from {start_port}")
|
||||
|
||||
def run(self, host: str = "0.0.0.0", port: int = 0x5EDA) -> int:
|
||||
def run(self, host: str, port: int) -> int:
|
||||
"""
|
||||
Runs the dashboard on the given host and port and returns the port number.
|
||||
"""
|
||||
@@ -572,8 +572,8 @@ class SerenaDashboardAPI:
|
||||
self._app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
|
||||
return port
|
||||
|
||||
def run_in_thread(self) -> tuple[threading.Thread, int]:
|
||||
def run_in_thread(self, host: str) -> tuple[threading.Thread, int]:
|
||||
port = self._find_first_free_port(0x5EDA)
|
||||
thread = threading.Thread(target=lambda: self.run(port=port), daemon=True)
|
||||
thread = threading.Thread(target=lambda: self.run(host=host, port=port), daemon=True)
|
||||
thread.start()
|
||||
return thread, port
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
from typing import Literal, overload
|
||||
from dataclasses import MISSING, Field
|
||||
from typing import Any, Literal, cast, overload
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
from ruamel.yaml.comments import CommentedMap
|
||||
@@ -32,3 +33,22 @@ def save_yaml(path: str, data: dict | CommentedMap, preserve_comments: bool = Fa
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding=SERENA_FILE_ENCODING) as f:
|
||||
yaml.dump(data, f)
|
||||
|
||||
|
||||
def get_dataclass_default(cls: type, field_name: str) -> Any:
|
||||
"""
|
||||
Gets the default value of a dataclass field.
|
||||
|
||||
:param cls: The dataclass type.
|
||||
:param field_name: The name of the field.
|
||||
:return: The default value of the field (either from default or default_factory).
|
||||
"""
|
||||
field = cast(Field, cls.__dataclass_fields__[field_name]) # type: ignore[attr-defined]
|
||||
|
||||
if field.default is not MISSING:
|
||||
return field.default
|
||||
|
||||
if field.default_factory is not MISSING: # default_factory is a function
|
||||
return field.default_factory()
|
||||
|
||||
raise AttributeError(f"{field_name} has no default")
|
||||
Reference in new issue
Block a user