Merge branch 'main' into fix/1871-nextflow-scan-flush-flag

This commit is contained in:
Dr. Dominik Jain authored and GitHub committed 2026-09-02 13:06:49 +02:00
commit f41f33c904
12 files changed
+395 -6

No files matched your search

+3 -2
View File
@@ -40,8 +40,9 @@ class PromptFactoryBase:
return self._prompt_collection.get_prompt_list(prompt_name, self.lang_code)
def autogenerate_prompt_factory_module(prompts_dir: str, target_module_path: str, interprompt_library_package: str = "interprompt",
class_name: str = "PromptFactory") -> None:
def autogenerate_prompt_factory_module(
prompts_dir: str, target_module_path: str, interprompt_library_package: str = "interprompt", class_name: str = "PromptFactory"
) -> None:
"""
Auto-generates a prompt factory module for the given prompt directory.
The generated `PromptFactory` class is meant to be the central entry class for retrieving and rendering prompt templates and prompt
+4
View File
@@ -794,6 +794,7 @@ class SerenaAgent:
# 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.
apply_read_only = False
if is_single_project:
assert project is not None
log.info(
@@ -807,9 +808,12 @@ class SerenaAgent:
)
)
tool_inclusion_definitions.append(project.project_config)
apply_read_only = project.project_config.read_only
# compute the resulting tool set
base_toolset = ToolSet.default().apply(*tool_inclusion_definitions)
if apply_read_only:
base_toolset = base_toolset.without_editing_tools()
log.info(f"Number of exposed tools: {len(base_toolset)}")
return base_toolset
+3
View File
@@ -144,6 +144,9 @@ class LanguageServerManager:
language_servers[thread.ls_id] = thread.language_server
# If any server failed to start up, raise an exception and stop all started language servers.
# A server whose own thread raised has already stopped its own process, since
# SolidLanguageServer.start() cleans up after itself on failure; only the servers that
# started successfully (and are therefore absent from `exceptions`) still need stopping.
# We intentionally fail fast here. The user's intention is to work with all the specified languages,
# so if any of them is not available, it is better to make symbolic tool calls fail, bringing the issue to the
# user's attention instead of silently continuing with a subset of the language servers and potentially
@@ -325,6 +325,12 @@ class ClojureLSP(SolidLanguageServer):
"workspace": {
"applyEdit": True,
"workspaceEdit": {"documentChanges": True},
# Serena notifies language servers about files changed outside its own
# edit tools (git checkout, another editor, a build step) via
# workspace/didChangeWatchedFiles; see LanguageServerManager.poll_and_notify.
# Without declaring the capability, clojure-lsp is not told the client
# sends those notifications and may keep answering from its stale analysis.
"didChangeWatchedFiles": {"dynamicRegistration": True},
"symbol": {"symbolKind": {"valueSet": list(range(1, 27))}},
"workspaceFolders": True,
},
@@ -4,6 +4,7 @@ Provides TypeScript specific instantiation of the LanguageServer class. Contains
import logging
import os
import re
import shutil
import threading
import time
@@ -15,7 +16,9 @@ from sensai.util.logging import LogTime
from solidlsp import ls_types
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import MessageType
from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
@@ -65,6 +68,21 @@ def prefer_non_node_modules_definition(definitions: list[ls_types.Location]) ->
return definitions[0]
class TypeScriptServerCrashedError(SolidLSPException):
"""Raised when tsserver reported its own abnormal exit via window/logMessage.
typescript-language-server sends a $/progress "end" event for the in-flight
token as part of tearing its connection down after tsserver dies, which is
otherwise indistinguishable from a normal indexing completion.
"""
# Matches typescript-language-server's window/logMessage notification for an abnormal
# tsserver exit, e.g. "[lspserver] [tsclient] [tsserver] Exited. Code: null. Signal: SIGABRT".
# A clean shutdown does not produce this message.
_TSSERVER_EXITED_PATTERN = re.compile(r"\[tsserver\]\s+Exited\b", re.IGNORECASE)
class TypeScriptLanguageServer(SolidLanguageServer):
"""
Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
@@ -114,14 +132,36 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self._active_progress_tokens: set[str] = set()
self._indexing_complete = threading.Event()
self._indexing_complete.set() # Initially set (no active work)
# set from window/logMessage when tsserver reports its own abnormal exit;
# a crash mid-indexing still drains _active_progress_tokens via a $/progress
# "end" event, so that alone cannot distinguish a crash from real completion
self._crash_message: str | None = None
def _raise_if_crashed(self) -> None:
if self._crash_message is not None:
raise TypeScriptServerCrashedError(self._crash_message)
@staticmethod
def _tsserver_exit_message(msg: dict) -> str | None:
""":return: the log text if ``msg`` is tsserver reporting its own abnormal exit, else None."""
if msg.get("type") != MessageType.Error:
return None
message_text = str(msg.get("message", ""))
if _TSSERVER_EXITED_PATTERN.search(message_text):
return message_text
return None
def wait_for_indexing(self, timeout: float) -> bool:
"""Block until all $/progress tokens complete.
:param timeout: Maximum seconds to wait.
:return: True if indexing completed, False on timeout.
:raises TypeScriptServerCrashedError: if tsserver reported an abnormal exit.
"""
return self._indexing_complete.wait(timeout=timeout)
result = self._indexing_complete.wait(timeout=timeout)
if result:
self._raise_if_crashed()
return result
def _wait_for_indexing_start_or_completion(self, timeout: float, start_grace: float | None = None) -> bool:
"""Wait until TypeScript indexing has started and drained, or provably never started.
@@ -129,6 +169,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
:param timeout: Maximum seconds to wait once active indexing progress is observed.
:param start_grace: Maximum seconds to wait for progress to begin after opening files.
:return: True if indexing completed or no progress began within the grace period, False on timeout.
:raises TypeScriptServerCrashedError: if tsserver reported an abnormal exit.
"""
grace = self.INDEXING_START_GRACE if start_grace is None else start_grace
@@ -139,6 +180,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
if self._active_progress_tokens:
break
if self._indexing_complete.is_set():
self._raise_if_crashed()
return True
time.sleep(0.05)
@@ -147,6 +189,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
has_active_progress = bool(self._active_progress_tokens)
if not has_active_progress:
self._indexing_complete.set()
self._raise_if_crashed()
return True
# wait for active progress to drain
@@ -382,6 +425,10 @@ class TypeScriptLanguageServer(SolidLanguageServer):
def window_log_message(msg: dict) -> None:
log.info(f"LSP: window/logMessage: {msg}")
crash_text = self._tsserver_exit_message(msg)
if crash_text is not None:
log.warning(f"tsserver reported an abnormal exit: {crash_text}")
self._crash_message = f"tsserver exited abnormally: {crash_text}"
def handle_typescript_version(params: dict) -> None:
"""
+11 -1
View File
@@ -3179,7 +3179,17 @@ class SolidLanguageServer(ABC):
"""
log.info(f"Starting language server {self.language_server.ls_id} for {self.language_server.repository_root_path}")
self.server_started = True
self._start_server()
try:
self._start_server()
except Exception:
# `_start_server()` may raise after already spawning the underlying process (a
# capability assertion or an initialize() timeout firing post-spawn). Stop it here
# so a failed start never leaves an orphaned process behind, regardless of caller.
if self.is_running():
self.stop()
else:
self.server_started = False
raise
return self
def stop(self, shutdown_timeout: float = 2.0) -> None: