mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 04:15:11 +00:00
Merge pull request #938 from oraios/open_dashboard_param_in_cli
Open dashboard param in cli and client info in dashboard
This commit is contained in:
7 files changed
+136
-29
No files matched your search
@@ -12,6 +12,9 @@ Serena comes with built-in tools for monitoring and managing the current session
|
||||
By default, it will be accessible at `http://localhost:24282/dashboard/index.html`,
|
||||
but a higher port may be used if the default port is unavailable/multiple instances are running.
|
||||
|
||||
**We recommend always enabling the dashboard**. If you don't want the browser to open automatically,
|
||||
you can disable it while still keeping the dashboard running in the background (see below).
|
||||
|
||||
* the **GUI tool** (disabled by default)
|
||||
|
||||
The GUI tool is a native application window which displays logs.
|
||||
@@ -26,7 +29,8 @@ For the dashboard, this can be disabled if desired (see below).
|
||||
## Disabling Automatic Browser Opening
|
||||
|
||||
If you prefer not to have the dashboard open automatically (e.g., to avoid focus stealing), you can disable it
|
||||
by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml`.
|
||||
by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml` or by passing `--open-web-dashboard False`
|
||||
to `start-mcp-server` CLI command.
|
||||
|
||||
When automatic opening is disabled, you can still access the dashboard by:
|
||||
* asking the LLM to "open the Serena dashboard", which will open the dashboard in your default browser
|
||||
|
||||
+83
-24
@@ -36,6 +36,8 @@ from solidlsp.util.subprocess_util import subprocess_kwargs
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_MAX_CONTENT_WIDTH = 100
|
||||
|
||||
|
||||
def find_project_root(root: str | Path | None = None) -> str:
|
||||
"""Find project root by walking up from CWD.
|
||||
@@ -138,7 +140,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
super().__init__(name="serena", help="Serena CLI commands. You can run `<command> --help` for more info on each command.")
|
||||
|
||||
@staticmethod
|
||||
@click.command("start-mcp-server", help="Starts the Serena MCP server.")
|
||||
@click.command("start-mcp-server", help="Starts the Serena MCP server.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.option("--project", "project", type=PROJECT_TYPE, default=None, help="Path or name of project to activate at startup.")
|
||||
@click.option("--project-file", "project", type=PROJECT_TYPE, default=None, help="[DEPRECATED] Use --project instead.")
|
||||
@click.argument("project_file_arg", type=PROJECT_TYPE, required=False, default=None, metavar="")
|
||||
@@ -177,8 +179,29 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
@click.option(
|
||||
"--port", type=int, default=8000, show_default=True, help="Listen port for the MCP server (when using corresponding transport)."
|
||||
)
|
||||
@click.option("--enable-web-dashboard", type=bool, is_flag=False, default=None, help="Override dashboard setting in config.")
|
||||
@click.option("--enable-gui-log-window", type=bool, is_flag=False, default=None, help="Override GUI log window setting in config.")
|
||||
@click.option(
|
||||
"--enable-web-dashboard",
|
||||
type=bool,
|
||||
is_flag=False,
|
||||
default=None,
|
||||
help="Enable the web dashboard (overriding the setting in Serena's config). "
|
||||
"It is recommended to always enable the dashboard. If you don't want the browser to open on startup, set open-web-dashboard to False. "
|
||||
"For more information, see\nhttps://oraios.github.io/serena/02-usage/060_dashboard.html",
|
||||
)
|
||||
@click.option(
|
||||
"--enable-gui-log-window",
|
||||
type=bool,
|
||||
is_flag=False,
|
||||
default=None,
|
||||
help="Enable the gui log window (currently only displays logs; overriding the setting in Serena's config).",
|
||||
)
|
||||
@click.option(
|
||||
"--open-web-dashboard",
|
||||
type=bool,
|
||||
is_flag=False,
|
||||
default=None,
|
||||
help="Open Serena's dashboard in your browser after MCP server startup (overriding the setting in Serena's config).",
|
||||
)
|
||||
@click.option(
|
||||
"--log-level",
|
||||
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
|
||||
@@ -204,6 +227,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
host: str,
|
||||
port: int,
|
||||
enable_web_dashboard: bool | None,
|
||||
open_web_dashboard: bool | None,
|
||||
enable_gui_log_window: bool | None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None,
|
||||
trace_lsp_communication: bool | None,
|
||||
@@ -244,6 +268,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
modes=modes,
|
||||
language_backend=LanguageBackend.from_str(language_backend) if language_backend else None,
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
open_web_dashboard=open_web_dashboard,
|
||||
enable_gui_log_window=enable_gui_log_window,
|
||||
log_level=log_level,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
@@ -258,7 +283,9 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
server.run(transport=transport)
|
||||
|
||||
@staticmethod
|
||||
@click.command("print-system-prompt", help="Print the system prompt for a project.")
|
||||
@click.command(
|
||||
"print-system-prompt", help="Print the system prompt for a project.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option(
|
||||
"--log-level",
|
||||
@@ -309,7 +336,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
super().__init__(name="mode", help="Manage Serena modes. You can run `mode <command> --help` for more info on each command.")
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="List available modes.")
|
||||
@click.command("list", help="List available modes.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
def list() -> None:
|
||||
mode_names = SerenaAgentMode.list_registered_mode_names()
|
||||
max_len_name = max(len(name) for name in mode_names) if mode_names else 20
|
||||
@@ -321,7 +348,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
click.echo(name_descr_string)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new mode or copy an internal one.")
|
||||
@click.command("create", help="Create a new mode or copy an internal one.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.option(
|
||||
"--name",
|
||||
"-n",
|
||||
@@ -350,7 +377,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(dest)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit", help="Edit a custom mode YAML file.")
|
||||
@click.command("edit", help="Edit a custom mode YAML file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("mode_name")
|
||||
def edit(mode_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
@@ -366,7 +393,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete", help="Delete a custom mode file.")
|
||||
@click.command("delete", help="Delete a custom mode file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("mode_name")
|
||||
def delete(mode_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
@@ -386,7 +413,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="List available contexts.")
|
||||
@click.command("list", help="List available contexts.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
def list() -> None:
|
||||
context_names = SerenaAgentContext.list_registered_context_names()
|
||||
max_len_name = max(len(name) for name in context_names) if context_names else 20
|
||||
@@ -398,7 +425,9 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
click.echo(name_descr_string)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new context or copy an internal one.")
|
||||
@click.command(
|
||||
"create", help="Create a new context or copy an internal one.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.option(
|
||||
"--name",
|
||||
"-n",
|
||||
@@ -427,7 +456,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(dest)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit", help="Edit a custom context YAML file.")
|
||||
@click.command("edit", help="Edit a custom context YAML file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("context_name")
|
||||
def edit(context_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
|
||||
@@ -443,7 +472,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete", help="Delete a custom context file.")
|
||||
@click.command("delete", help="Delete a custom context file.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("context_name")
|
||||
def delete(context_name: str) -> None:
|
||||
path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
|
||||
@@ -462,7 +491,9 @@ class SerenaConfigCommands(AutoRegisteringGroup):
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"edit", help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found."
|
||||
"edit",
|
||||
help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
def edit() -> None:
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
@@ -512,7 +543,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
return generated_conf
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new Serena project configuration.")
|
||||
@click.command("create", help="Create a new Serena project configuration.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("project_path", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
|
||||
@click.option("--name", type=str, default=None, help="Project name; defaults to directory name if not specified.")
|
||||
@click.option(
|
||||
@@ -538,7 +569,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
raise click.ClickException(str(e))
|
||||
|
||||
@staticmethod
|
||||
@click.command("index", help="Index a project by saving symbols to the LSP cache. Auto-creates project.yml if it doesn't exist.")
|
||||
@click.command(
|
||||
"index",
|
||||
help="Index a project by saving symbols to the LSP cache. Auto-creates project.yml if it doesn't exist.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option("--name", type=str, default=None, help="Project name (only used if auto-creating project.yml).")
|
||||
@click.option(
|
||||
@@ -570,7 +605,9 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ProjectCommands._index_project(project, log_level, timeout=timeout)
|
||||
|
||||
@staticmethod
|
||||
@click.command("index-deprecated", help="Deprecated alias for 'serena project index'.")
|
||||
@click.command(
|
||||
"index-deprecated", help="Deprecated alias for 'serena project index'.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option("--log-level", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="WARNING")
|
||||
@click.option("--timeout", type=float, default=10, help="Timeout for indexing a single file.")
|
||||
@@ -622,7 +659,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ls_mgr.stop_all()
|
||||
|
||||
@staticmethod
|
||||
@click.command("is_ignored_path", help="Check if a path is ignored by the project configuration.")
|
||||
@click.command(
|
||||
"is_ignored_path",
|
||||
help="Check if a path is ignored by the project configuration.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("path", type=click.Path(exists=False, file_okay=True, dir_okay=True))
|
||||
@click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
|
||||
def is_ignored_path(path: str, project: str) -> None:
|
||||
@@ -639,7 +680,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
click.echo(f"Path '{path}' IS {'ignored' if is_ignored else 'IS NOT ignored'} by the project configuration.")
|
||||
|
||||
@staticmethod
|
||||
@click.command("index-file", help="Index a single file by saving its symbols to the LSP cache.")
|
||||
@click.command(
|
||||
"index-file",
|
||||
help="Index a single file by saving its symbols to the LSP cache.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
||||
@click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
|
||||
@click.option("--verbose", "-v", is_flag=True, help="Print detailed information about the indexed symbols.")
|
||||
@@ -672,7 +717,11 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ls_mgr.stop_all()
|
||||
|
||||
@staticmethod
|
||||
@click.command("health-check", help="Perform a comprehensive health check of the project's tools and language server.")
|
||||
@click.command(
|
||||
"health-check",
|
||||
help="Perform a comprehensive health check of the project's tools and language server.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
|
||||
def health_check(project: str) -> None:
|
||||
"""
|
||||
@@ -827,6 +876,7 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
@click.command(
|
||||
"list",
|
||||
help="Prints an overview of the tools that are active by default (not just the active ones for your project). For viewing all tools, pass `--all / -a`",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.option("--quiet", "-q", is_flag=True)
|
||||
@click.option("--all", "-a", "include_optional", is_flag=True, help="List all tools, including those not enabled by default.")
|
||||
@@ -849,6 +899,7 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
@click.command(
|
||||
"description",
|
||||
help="Print the description of a tool, optionally with a specific context (the latter may modify the default description).",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("tool_name", type=str)
|
||||
@click.option("--context", type=str, default=None, help="Context name or path to context file.")
|
||||
@@ -879,7 +930,9 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
return os.path.join(templates_dir, prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="Lists yamls that are used for defining prompts.")
|
||||
@click.command(
|
||||
"list", help="Lists yamls that are used for defining prompts.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
def list() -> None:
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
for prompt_yaml_name in serena_prompt_yaml_names:
|
||||
@@ -890,7 +943,11 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
click.echo(prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create-override", help="Create an override of an internal prompts yaml for customizing Serena's prompts")
|
||||
@click.command(
|
||||
"create-override",
|
||||
help="Create an override of an internal prompts yaml for customizing Serena's prompts",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("prompt_yaml_name")
|
||||
def create_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
@@ -908,7 +965,9 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit-override", help="Edit an existing prompt override file")
|
||||
@click.command(
|
||||
"edit-override", help="Edit an existing prompt override file", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||
)
|
||||
@click.argument("prompt_yaml_name")
|
||||
def edit_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
@@ -925,7 +984,7 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list-overrides", help="List existing prompt override files")
|
||||
@click.command("list-overrides", help="List existing prompt override files", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
def list_overrides() -> None:
|
||||
user_templates_dir = SerenaPaths().user_prompt_templates_dir
|
||||
os.makedirs(user_templates_dir, exist_ok=True)
|
||||
@@ -936,7 +995,7 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
click.echo(file_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete-override", help="Delete a prompt override file")
|
||||
@click.command("delete-override", help="Delete a prompt override file", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@click.argument("prompt_yaml_name")
|
||||
def delete_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -56,6 +56,7 @@ class ResponseConfigOverview(BaseModel):
|
||||
jetbrains_mode: bool
|
||||
languages: list[str]
|
||||
encoding: str | None
|
||||
current_client: str | None
|
||||
|
||||
|
||||
class ResponseAvailableLanguages(BaseModel):
|
||||
@@ -370,6 +371,7 @@ class SerenaDashboardAPI:
|
||||
|
||||
def _get_config_overview(self) -> ResponseConfigOverview:
|
||||
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.tools.tools_base import Tool
|
||||
|
||||
# Get active project info
|
||||
project = self._agent.get_active_project()
|
||||
@@ -491,6 +493,7 @@ class SerenaDashboardAPI:
|
||||
jetbrains_mode=self._agent.serena_config.language_backend == LanguageBackend.JETBRAINS,
|
||||
languages=languages,
|
||||
encoding=encoding,
|
||||
current_client=Tool.get_last_tool_call_client_str(),
|
||||
)
|
||||
|
||||
def _shutdown(self) -> None:
|
||||
|
||||
+8
-1
@@ -239,7 +239,9 @@ class SerenaMCPFactory:
|
||||
parameters=parameters,
|
||||
fn_metadata=func_arg_metadata,
|
||||
is_async=is_async,
|
||||
context_kwarg=None,
|
||||
# keep the value in sync with the kwarg name in Tool.apply_ex. The mcp sdk uses reflection to infer this
|
||||
# when the tool is constructed via from_function (which is a bit crazy IMO, but well...)
|
||||
context_kwarg="mcp_ctx",
|
||||
annotations=annotations,
|
||||
title=tool_title,
|
||||
)
|
||||
@@ -274,6 +276,7 @@ class SerenaMCPFactory:
|
||||
language_backend: LanguageBackend | None = None,
|
||||
enable_web_dashboard: bool | None = None,
|
||||
enable_gui_log_window: bool | None = None,
|
||||
open_web_dashboard: bool | None = None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
|
||||
trace_lsp_communication: bool | None = None,
|
||||
tool_timeout: float | None = None,
|
||||
@@ -288,6 +291,8 @@ class SerenaMCPFactory:
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param open_web_dashboard: Whether to open the web dashboard on launch.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param log_level: Log level. If not specified, will take the value from the serena configuration.
|
||||
:param trace_lsp_communication: Whether to trace the communication between Serena and the language servers.
|
||||
This is useful for debugging language server issues.
|
||||
@@ -301,6 +306,8 @@ class SerenaMCPFactory:
|
||||
config.web_dashboard = enable_web_dashboard
|
||||
if enable_gui_log_window is not None:
|
||||
config.gui_log_window_enabled = enable_gui_log_window
|
||||
if open_web_dashboard is not None:
|
||||
config.web_dashboard_open_on_launch = open_web_dashboard
|
||||
if log_level is not None:
|
||||
log_level = cast(Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], log_level.upper())
|
||||
config.log_level = logging.getLevelNamesMapping()[log_level]
|
||||
|
||||
@@ -619,6 +619,10 @@ class Dashboard {
|
||||
html += '<div class="config-label">File Encoding:</div>';
|
||||
html += '<div class="config-value">' + (config.encoding || 'N/A') + '</div>';
|
||||
|
||||
// Current Client info
|
||||
html += '<div class="config-label">Current Client:</div>';
|
||||
html += '<div class="config-value">' + (config.current_client || 'None') + '</div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Active tools - collapsible
|
||||
|
||||
@@ -27,6 +27,12 @@ web_dashboard: True
|
||||
# whether to open the Serena web dashboard (which will be accessible through your web browser) that
|
||||
# provides access to Serena's configuration and state as well as the current session logs.
|
||||
# The web dashboard is supported on all platforms.
|
||||
# We strongly recommend to always enable this option, since the dashboard provides important information
|
||||
# about the current state of Serena, including the configuration, the logs and tool usage statistics.
|
||||
# If you don't want the browser window to pop up automatically, set the `web_dashboard_open_on_launch` to False
|
||||
# (either here or through the corresponding flag in the `start-mcp-server` CLI command).
|
||||
# You can then open the dashboard by asking your agent to do so (e.g., by saying "open the dashboard"), Serena provides
|
||||
# a tool for this.
|
||||
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
|
||||
web_dashboard_listen_address: 127.0.0.1
|
||||
@@ -34,7 +40,7 @@ web_dashboard_listen_address: 127.0.0.1
|
||||
|
||||
web_dashboard_open_on_launch: True
|
||||
# whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard
|
||||
# is enabled).
|
||||
# is enabled). See also the web_dashboard option.
|
||||
# If set to false, you can still open the dashboard manually by
|
||||
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
|
||||
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
|
||||
|
||||
@@ -4,8 +4,10 @@ from abc import ABC
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, Protocol, Self, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, Protocol, Self, TypeVar, cast
|
||||
|
||||
from mcp import Implementation
|
||||
from mcp.server.fastmcp import Context
|
||||
from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata, func_metadata
|
||||
from sensai.util import logging
|
||||
from sensai.util.string import dict_string
|
||||
@@ -115,6 +117,17 @@ class Tool(Component):
|
||||
# (which is use by the LLM, so a good description is important)
|
||||
# and to validate the tool call arguments.
|
||||
|
||||
_last_tool_call_client_str: str | None = None
|
||||
"""We can only get the client info from within a tool call. Each tool call will update this variable."""
|
||||
|
||||
@classmethod
|
||||
def set_last_tool_call_client_str(cls, client_str: str | None) -> None:
|
||||
cls._last_tool_call_client_str = client_str
|
||||
|
||||
@classmethod
|
||||
def get_last_tool_call_client_str(cls) -> str | None:
|
||||
return cls._last_tool_call_client_str
|
||||
|
||||
@classmethod
|
||||
def get_name_from_cls(cls) -> str:
|
||||
name = cls.__name__
|
||||
@@ -219,10 +232,21 @@ class Tool(Component):
|
||||
def is_active(self) -> bool:
|
||||
return self.agent.tool_is_active(self.__class__)
|
||||
|
||||
def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, **kwargs) -> str: # type: ignore
|
||||
def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, mcp_ctx: Context | None = None, **kwargs) -> str: # type: ignore
|
||||
"""
|
||||
Applies the tool with logging and exception handling, using the given keyword arguments
|
||||
"""
|
||||
if mcp_ctx is not None:
|
||||
try:
|
||||
client_params = mcp_ctx.session.client_params
|
||||
if client_params is not None:
|
||||
client_info = cast(Implementation, client_params.clientInfo)
|
||||
client_str = client_info.title if client_info.title else client_info.name + " " + client_info.version
|
||||
if client_str != self.get_last_tool_call_client_str():
|
||||
log.debug(f"Updating client info: {client_info}")
|
||||
self.set_last_tool_call_client_str(client_str)
|
||||
except BaseException as e:
|
||||
log.info(f"Failed to get client info: {e}.")
|
||||
|
||||
def task() -> str:
|
||||
apply_fn = self.get_apply_fn()
|
||||
|
||||
Reference in new issue
Block a user