Show interface, backend and REPL function availability in the dashboard

Configuration endpoint:
* ResponseConfigOverview: agent_interface, language_backend and facades (None in tool mode)
* facades report each facade and its methods with their enabled state (SerenaReplEntrypoint.get_facade_availability_info)

Dashboard UI:
* new rows "Interface" and "Backend"; "Languages" is shown for the LSP backend only
* new collapsible section "Active Functions" listing the facades and their methods, dimming disabled ones
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-15 12:50:44 +02:00
1 parent 323e855625
commit 988f20d5f5
5 files changed
+141 -12

No files matched your search

+30
View File
@@ -61,11 +61,25 @@ class ResponseToolStats(BaseModel):
stats: dict[str, dict[str, int]]
class ResponseFacadeMethod(BaseModel):
name: str
is_enabled: bool
class ResponseFacade(BaseModel):
name: str
is_enabled: bool
methods: list[ResponseFacadeMethod]
class ResponseConfigOverview(BaseModel):
active_project: dict[str, str | None]
context: dict[str, str]
modes: list[dict[str, str]]
active_tools: list[str]
agent_interface: str
language_backend: str
facades: list[ResponseFacade] | None
tool_stats_summary: dict[str, dict[str, int]]
registered_projects: list[dict[str, str | bool]]
available_tools: list[dict[str, str | bool]]
@@ -608,6 +622,19 @@ class SerenaDashboardAPI:
if self._agent.is_tool_function_available(ReadMemoryTool) and project is not None:
available_memories = project.memory_manager.list_memories().get_full_list()
# Get the availability of the REPL's facades and their methods (REPL interface only)
facades = None
if self._agent.get_agent_interface().is_repl():
availability_info = self._agent.get_repl().entrypoint.get_facade_availability_info()
facades = [
ResponseFacade(
name=facade_info.name,
is_enabled=facade_info.is_enabled,
methods=[ResponseFacadeMethod(name=m.name, is_enabled=m.is_enabled) for m in facade_info.methods],
)
for facade_info in availability_info.facades
]
# Get list of languages for the active project
ls_ids = []
if project is not None:
@@ -623,6 +650,9 @@ class SerenaDashboardAPI:
context=context_info,
modes=modes_info,
active_tools=active_tools,
agent_interface=self._agent.get_agent_interface().value,
language_backend=self._agent.get_language_backend().value,
facades=facades,
tool_stats_summary=tool_stats_summary,
registered_projects=registered_projects,
available_tools=available_tools,
+6
View File
@@ -643,6 +643,12 @@ class Facade:
"""
return [m for m in self._methods.values() if m.enabled]
def get_methods(self) -> list[FacadeMethod]:
"""
:return: the list of all methods, regardless of whether they are enabled (e.g. for changing their enabled state)
"""
return list(self._methods.values())
def get_types(self) -> list[ReferencedType]:
"""
:return: the types referenced by the facade's methods
+44 -8
View File
@@ -8,6 +8,7 @@ import ast
import logging
import re
import traceback
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from ..session import SerenaSession
@@ -21,6 +22,35 @@ if TYPE_CHECKING:
log = logging.getLogger(__name__)
class FacadeAvailabilityInfo:
"""
Represents information on the availability of facades and the methods therein
"""
@dataclass
class FacadeInfo:
name: str
is_enabled: bool
methods: list["FacadeAvailabilityInfo.MethodInfo"]
@dataclass
class MethodInfo:
name: str
is_enabled: bool
def __init__(self):
self.facades: list[FacadeAvailabilityInfo.FacadeInfo] = []
def add_facade(self, facade: Facade, is_enabled: bool):
def is_method_enabled(m: FacadeMethod) -> bool:
return is_enabled and m.enabled
methods_info = [
FacadeAvailabilityInfo.MethodInfo(name=method.name, is_enabled=is_method_enabled(method)) for method in facade.get_methods()
]
self.facades.append(FacadeAvailabilityInfo.FacadeInfo(name=facade.name, is_enabled=is_enabled, methods=methods_info))
class SerenaReplEntrypoint:
"""
Represents the entrypoint object for the REPL. It holds the configured facades as attributes
@@ -35,13 +65,25 @@ class SerenaReplEntrypoint:
self._facades: dict[str, Facade] = {}
self._current_session: SerenaSession | None = None
self._current_namespace: dict[str, Any] | None = None
self._facade_availability_info = FacadeAvailabilityInfo()
registered_facade_names = []
for facade in facades:
if api_scope.is_facade_enabled(facade.name):
self._register(facade)
is_facade_enabled = api_scope.is_facade_enabled(facade.name)
self._facade_availability_info.add_facade(facade, is_facade_enabled)
if is_facade_enabled:
if facade.name in self._facades:
raise ValueError(f"Duplicate facade name: {facade.name}")
self._facades[facade.name] = facade
setattr(self, facade.name, facade)
registered_facade_names.append(facade.name)
log.info("Registered %d/%d facades: %s", len(registered_facade_names), len(facades), registered_facade_names)
def get_facade_availability_info(self) -> FacadeAvailabilityInfo:
"""
:return: the availability of all facades and their methods (enabled or disabled)
"""
return self._facade_availability_info
def get_enabled_methods(self) -> list[FacadeMethod]:
"""
:return: the list of all enabled methods across all facades
@@ -117,12 +159,6 @@ class SerenaReplEntrypoint:
del self._current_namespace[name]
return f"Removed {len(items)} persisted item(s)."
def _register(self, facade: Facade) -> None:
if facade.name in self._facades:
raise ValueError(f"Duplicate facade name: {facade.name}")
self._facades[facade.name] = facade
setattr(self, facade.name, facade)
def _get_facade(self, name: str) -> Facade:
if name not in self._facades:
raise ValueError(f"Unknown facade '{name}'. Available facades: {list(self._facades)}")
@@ -603,6 +603,23 @@ code, pre, kbd, samp,
cursor: default;
}
.facade-block {
margin-bottom: 12px;
}
.facade-name {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
}
.facade-name.disabled,
.tool-item.disabled {
color: var(--text-secondary);
opacity: 0.5;
}
/* Projects List */
.project-item {
padding: 10px 12px;
+44 -4
View File
@@ -614,6 +614,7 @@ class Dashboard {
const $existingToolsContent = $('#tools-content');
const $existingMemoriesContent = $('#memories-content');
const wasToolsExpanded = $existingToolsContent.is(':visible');
const wasFunctionsExpanded = $('#functions-content').is(':visible');
const wasMemoriesExpanded = $existingMemoriesContent.is(':visible');
let html = '<div class="config-grid">';
@@ -634,10 +635,14 @@ class Dashboard {
html += '<div class="config-value">' + (config.active_project.name || 'None') + '</div>';
}
html += '<div class="config-label">Languages:</div>';
if (this.jetbrainsMode) {
html += '<div class="config-value">Using JetBrains backend</div>';
} else {
html += '<div class="config-label">Interface:</div>';
html += '<div class="config-value">' + config.agent_interface + '</div>';
html += '<div class="config-label">Backend:</div>';
html += '<div class="config-value">' + config.language_backend + '</div>';
if (!this.jetbrainsMode) {
html += '<div class="config-label">Languages:</div>';
html += '<div class="config-value">';
if (config.languages && config.languages.length > 0) {
html += '<div class="languages-container">';
@@ -705,6 +710,32 @@ class Dashboard {
html += '</div>';
html += '</div>';
// Active functions of the REPL's facades - collapsible (REPL interface only)
if (config.facades) {
const enabledMethodCount = config.facades.reduce(function (count, facade) {
return count + facade.methods.filter(function (method) { return method.is_enabled; }).length;
}, 0);
html += '<div style="margin-top: 20px;">';
html += '<h3 class="collapsible-header" id="functions-header" style="font-size: 16px; margin: 0;">';
html += '<span>Active Functions (' + enabledMethodCount + ')</span>';
html += '<span class="toggle-icon' + (wasFunctionsExpanded ? ' expanded' : '') + '">▼</span>';
html += '</h3>';
html += '<div class="collapsible-content" id="functions-content" style="' + (wasFunctionsExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
config.facades.forEach(function (facade) {
html += '<div class="facade-block">';
html += '<div class="facade-name' + (facade.is_enabled ? '' : ' disabled') + '">s.' + facade.name + '</div>';
html += '<div class="tools-grid">';
facade.methods.forEach(function (method) {
const title = facade.name + '.' + method.name + (method.is_enabled ? '' : ' (disabled)');
html += '<div class="tool-item' + (method.is_enabled ? '' : ' disabled') + '" title="' + title + '">' + method.name + '</div>';
});
html += '</div>';
html += '</div>';
});
html += '</div>';
html += '</div>';
}
// Available memories - collapsible (show if memories exist or if project exists)
if (config.active_project && config.active_project.name) {
html += '<div style="margin-top: 20px;">';
@@ -773,6 +804,15 @@ class Dashboard {
$('#create-memory-btn').click(this.openCreateMemoryModal.bind(this));
// Re-attach collapsible handler for the newly created tools header
$('#functions-header').click(function () {
const $header = $(this);
const $content = $('#functions-content');
const $icon = $header.find('.toggle-icon');
$content.slideToggle(300);
$icon.toggleClass('expanded');
});
$('#tools-header').click(function () {
const $header = $(this);
const $content = $('#tools-content');