From 00726f5fdc293788f75170f3aaded90495f7f368 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 14 Apr 2026 14:43:17 +0200 Subject: [PATCH 1/5] Add date to changelog headings, add 'v' prefix Fixes #1337 --- CHANGELOG.md | 10 +++++----- scripts/bump_version.py | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4542aa564..92a95e736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ JetBrains: - Add mSL (mIRC Scripting Language) support (custom pygls-based language server; symbols, references, definitions) - Fix initialisation issues in Vue language server #1333 -# 1.1.1 +# v1.1.1 (2026-04-12) * General: - Enable cert verification for HTTPS request to oraios-software.de #1320 @@ -30,7 +30,7 @@ JetBrains: - Fix Dart LSP returning only symbol name as body instead of full method body. -# 1.1.0 +# v1.1.0 (2026-04-11) * General: - **Major**: Add commands for hooks and documentation of recommended setup. Consider setting up the [recommended hooks](https://oraios.github.io/serena/02-usage/030_clients.html) ! @@ -53,7 +53,7 @@ JetBrains: Some clients would terminate the MCP server in a way that did not ensure proper termination. - Fix: Manual server shutdown triggered by GUI tool/dashboard not cleaning everything up. -# 1.0.0 +# v1.0.0 (2026-04-03) * General: * Add monorepo/multi-language support @@ -134,7 +134,7 @@ JetBrains: * **C/C++ alternate LS (ccls)**: Add experimental, opt-in support for ccls as an alternative backend to clangd. Enable via `cpp_ccls` in project configuration. Requires `ccls` installed and ideally a `compile_commands.json` at repo root. * **Add support for Solidity** via the Nomic Foundation `@nomicfoundation/solidity-language-server` (automatically installed via npm) -# 0.1.4 +# v0.1.4 (2025-08-15) ## Summary @@ -178,7 +178,7 @@ Fixes: default shell reconfiguration imposed by Claude Code) * Additional wait for initialization in C# language server before requesting references, allowing cross-file references to be found. -# 0.1.3 +# v0.1.3 (2025-07-22) ## Summary diff --git a/scripts/bump_version.py b/scripts/bump_version.py index e3426baa7..7647da78a 100644 --- a/scripts/bump_version.py +++ b/scripts/bump_version.py @@ -3,6 +3,7 @@ import logging import os import re +from datetime import datetime from pathlib import Path from typing import Literal @@ -185,7 +186,9 @@ def update_changelog(changelog_text: str, new_version: str) -> str: unreleased_body = unreleased_section[len(_UNRELEASED_HEADER) :] intro, unreleased_entries = split_unreleased_body(unreleased_body) - updated_section = _UNRELEASED_HEADER + intro + f"# {new_version}\n" + date_str = datetime.now().strftime("%Y-%m-%d") + updated_section = _UNRELEASED_HEADER + intro + f"# v{new_version} ({date_str})\n" + if unreleased_entries.strip(): updated_section += "\n" + unreleased_entries.lstrip("\n") else: From b2729c2a3efcd214d4c1fdd8d3497ba7be1719ce Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 14 Apr 2026 14:56:42 +0200 Subject: [PATCH 2/5] SerenaAgentMode: Fix print_overview --- src/serena/config/context_mode.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/serena/config/context_mode.py b/src/serena/config/context_mode.py index e417fe32d..053f54ee2 100644 --- a/src/serena/config/context_mode.py +++ b/src/serena/config/context_mode.py @@ -53,7 +53,13 @@ def print_overview(self) -> None: """Print an overview of the mode.""" print(f"{self.name}:\n {self.description}") if self.excluded_tools: - print(" excluded tools:\n " + ", ".join(sorted(self.excluded_tools))) + print(" excluded tools:\n " + ", ".join(sorted(self.excluded_tools))) + if self.included_optional_tools: + print(" included optional tools:\n " + ", ".join(sorted(self.included_optional_tools))) + if self.fixed_tools: + print(" fixed tools:\n " + ", ".join(sorted(self.fixed_tools))) + if self.prompt: + print(" defines initial prompt") @classmethod def from_yaml(cls, yaml_path: str | Path) -> Self: From 505d520c80055bd18ad7d029a897f18f78186e52 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 14 Apr 2026 14:59:56 +0200 Subject: [PATCH 3/5] SerenaAgent._create_base_toolset: Account for mode activation no longer being dynamic Fully apply exclusions when in a single-project context Fixes #1336 --- CHANGELOG.md | 2 ++ src/serena/agent.py | 36 ++++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a95e736..3ce066966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Status of the `main` branch. Changes prior to the next official version change w * General: - Support environment variable `SERENA_USAGE_REPORTING` (set to `false` to disable usage reporting) - Extended the list of always ignored directories (by language servers) with common cases. + - Improve exposed toolset: With mode switching no longer being a feature, we now fully apply tool exclusions + defined by modes when in a single-project context (limiting exposed tools to a minimum) - Fix: When scanning for `.gitignore` files, the presence of files that could not be made relative to the project root would cause the scan to fail. #1317 diff --git a/src/serena/agent.py b/src/serena/agent.py index 322db7c86..30ef7cc53 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -453,31 +453,35 @@ def _create_base_toolset( tool_inclusion_definitions.append(serena_config) tool_inclusion_definitions.append(context) + # determine whether we are operating in a single-project context + # (i.e. the project that is activated at startup is the only project that will be worked with throughout the session) + is_single_project = context.single_project and project is not None + # consider modes - # Since modes can be dynamically turned on and off, we don't include their definitions directly, - # For the initially active dynamic modes, we make sure that the tools they enable are included. - for mode in modes.get_default_modes(): - tool_inclusion_definitions.append( - NamedToolInclusionDefinition( - name=f"InitialDynamicModeInclusions[{mode.name}]", included_optional_tools=mode.included_optional_tools - ) - ) - # For the base modes, we also apply the tool exclusions, since they apply throughout the entire session + # * base modes: These cannot be changed, so they are fully applied for base_mode in modes.get_base_modes(): - tool_inclusion_definitions.append( - NamedToolInclusionDefinition( - name=f"BaseMode[{base_mode.name}]", - included_optional_tools=base_mode.included_optional_tools, - excluded_tools=base_mode.excluded_tools, + tool_inclusion_definitions.append(base_mode) + # * default modes: When not in a single-project context, these modes are dynamic (can later be turned off), + # so we consider only their inclusions (but not their exclusions, because these must not be hard) + for mode in modes.get_default_modes(): + if is_single_project: + tool_inclusion_definitions.append(mode) + else: + # Since modes can be dynamically turned on and off, we don't include their definitions directly, + # For the initially active dynamic modes, we make sure that the tools they enable are included. + tool_inclusion_definitions.append( + NamedToolInclusionDefinition( + name=f"InitialDynamicModeInclusions[{mode.name}]", included_optional_tools=mode.included_optional_tools + ) ) - ) # When in a single-project context, the agent is assumed to work on a single project, and we thus # want to apply that project's tool exclusions/inclusions from the get-go, limiting the set # of tools that will be exposed to the client. # Furthermore, we disable tools that are only relevant for project activation. # So if the project exists, we apply all the aforementioned exclusions. - if context.single_project and project is not None: + if is_single_project: + assert project is not None log.info( "Applying tool inclusion/exclusion definitions for single-project context based on project '%s'", project.project_name, From e1b29b8c28aeb232859edd876b80cb435f45db16 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 14 Apr 2026 15:36:03 +0200 Subject: [PATCH 4/5] Dashboard: Improve handling of read news (saving each id individually) Fixes #1338 --- CHANGELOG.md | 3 ++ src/serena/config/serena_config.py | 6 ++- src/serena/dashboard.py | 68 ++++++++++++++++++++++++------ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce066966..ee9434b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Status of the `main` branch. Changes prior to the next official version change w - Fix: When scanning for `.gitignore` files, the presence of files that could not be made relative to the project root would cause the scan to fail. #1317 +Dashboard: + - Fix handling of read news, saving each read news entry separately #1338 + JetBrains: - Improve handling of `relative_path` parameter - Improve its documentation to avoid usage errors diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 987d8cf67..a9cefd3b1 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -83,7 +83,11 @@ def __init__(self) -> None: If a name of a mode matches a name of a mode in SERENAS_OWN_MODES_YAML_DIR, the user mode will override the default mode definition. """ - self.news_snippet_id_file: str = os.path.join(self.serena_user_home_dir, "last_read_news_snippet_id.txt") + self.news_legacy_last_read_id_file: str = os.path.join(self.serena_user_home_dir, "last_read_news_snippet_id.txt") + """ + file containing the ID of the last read news snippet + """ + self.news_read_items_file: str = os.path.join(self.serena_user_home_dir, "news_read.pkl") """ file containing the ID of the last read news snippet """ diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 3c33f1a29..8ed27a5ff 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -15,6 +15,7 @@ from PIL import Image from pydantic import BaseModel from sensai.util import logging +from sensai.util.pickle import dump_pickle, load_pickle from serena.analytics import ToolUsageStats from serena.config.serena_config import SerenaConfig, SerenaPaths @@ -132,6 +133,54 @@ def from_task_info(cls, task_info: TaskExecutor.TaskInfo) -> Self: ) +class ReadNews: + def __init__(self, read_ids: list[str], legacy_last_read_id: str | None = None): + self._read_ids = set(read_ids) + self._legacy_last_read_id = legacy_last_read_id + + @staticmethod + def load() -> "ReadNews": + read_news_path = SerenaPaths().news_read_items_file + legacy_last_read_id_path = SerenaPaths().news_legacy_last_read_id_file + + def load_legacy_last_read_id() -> str | None: + if not os.path.exists(legacy_last_read_id_path): + return None + with open(legacy_last_read_id_path, encoding="utf-8") as f: + last_read_news_id = f.read().strip() + if last_read_news_id == "20262103": + last_read_news_id = "20260321" # fix originally misnamed news id + return last_read_news_id + + if os.path.exists(read_news_path): + return load_pickle(read_news_path) + else: + instance = ReadNews(read_ids=[], legacy_last_read_id=load_legacy_last_read_id()) + instance._save() + try: + os.unlink(legacy_last_read_id_path) + except: + pass + return instance + + def _save(self) -> None: + dump_pickle(self, SerenaPaths().news_read_items_file) + + def is_read(self, identifier: str) -> bool: + if identifier in self._read_ids: + return True + if self._legacy_last_read_id is not None and identifier <= self._legacy_last_read_id: + return True + return False + + def mark_read(self, identifier: str) -> None: + """ + Marks the given news snippet as read, saving the new state to disk + """ + self._read_ids.add(identifier) + self._save() + + class SerenaDashboardAPI: log = logging.getLogger(__qualname__) @@ -150,6 +199,7 @@ def __init__( self._loaded_news: dict[str, str] = {} self._news_ready = threading.Event() self._setup_routes() + self._read_news = ReadNews.load() # Fetch remote news in background on startup (non-blocking) threading.Thread(target=self._fetch_news, daemon=True).start() @@ -362,7 +412,6 @@ def _fetch_unread_news() -> dict[str, str]: self._news_ready.wait() all_news = self._loaded_news - # Filter news items by installation date serena_config_creation_date = SerenaConfig.get_config_file_creation_date() if serena_config_creation_date is None: # should not normally happen, since config file should exist when the dashboard is started @@ -370,17 +419,12 @@ def _fetch_unread_news() -> dict[str, str]: log.error("Serena config file not found when starting the dashboard") return {} serena_config_creation_date = serena_config_creation_date.strftime("%Y%m%d") - # Only include news items published on or after the installation date + + # filter for news after the installation date post_installation_news = {k: v for k, v in all_news.items() if k >= serena_config_creation_date} - news_snippet_id_file = SerenaPaths().news_snippet_id_file - if not os.path.exists(news_snippet_id_file): - return post_installation_news - with open(news_snippet_id_file, encoding="utf-8") as f: - last_read_news_id = f.read().strip() - if last_read_news_id == "20262103": - last_read_news_id = "20260321" # fix originally misnamed news id - return {k: v for k, v in post_installation_news.items() if k > last_read_news_id} + # read unread news + return {k: v for k, v in post_installation_news.items() if not self._read_news.is_read(k)} try: unread_news = _fetch_unread_news() @@ -393,9 +437,7 @@ def mark_news_snippet_as_read() -> dict[str, str]: try: request_data = request.get_json() news_snippet_id = str(request_data.get("news_snippet_id")) - news_snippet_id_file = SerenaPaths().news_snippet_id_file - with open(news_snippet_id_file, "w", encoding="utf-8") as f: - f.write(news_snippet_id) + self._read_news.mark_read(news_snippet_id) return {"status": "success", "message": f"Marked news snippet {news_snippet_id} as read"} except Exception as e: return {"status": "error", "message": str(e)} From de2f75673b71c5071373f3fcc1bd8312969bf529 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 14 Apr 2026 17:40:24 +0200 Subject: [PATCH 5/5] Add news item on evaluation and 1.1.2 release --- news/20260414.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 news/20260414.html diff --git a/news/20260414.html b/news/20260414.html new file mode 100644 index 000000000..5e2a0d46d --- /dev/null +++ b/news/20260414.html @@ -0,0 +1,15 @@ +
+

Agents Evaluating Serena for Themselves & Maintenance Update

+

April 14, 2026

+

+ Evaluation. + We had our actual "end users", i.e. coding agents, evaluate the added value of Serena's tools based on their own empirical insights. + Read what the agents had to say in our evaluation report.
+ This represents the first systematic evaluation we ever conducted. +

+

+ Maintenance Update. The new v1.1.2 release adds several improvements and fixes.
+ If you have already switched to the uv tool installation of Serena, upgrade as follows:
+ uv tool upgrade serena-agent --prerelease=allow +

+
\ No newline at end of file