* Restore Unix executable bits when extracting zip archives
- SafeZipExtractor._extract_member now chmods each extracted file with the
Unix mode stored in ZipInfo.external_attr (POSIX only, no-op when the
archive carries no Unix attributes, e.g. Windows-authored zips).
- stdlib zipfile never restores permission bits itself; a fix is tracked
upstream at https://github.com/python/cpython/pull/150061.
- Fixes archives with more than one executable losing their exec bit after
extraction, e.g. the bundled JBR inside the Kotlin Language Server
distribution (jbr/bin/java and native libs), which previously only had
its single top-level launcher script chmod'd by the language-server code.
Fixesoraios/serena#2100
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The AL adapter built a single executable path with a platform-specific
subdirectory (`bin/win32/...` on Windows) and raised "executable not found"
when it was absent. Compare the two VSIX packages from the marketplace with
the URL the adapter itself uses:
18.0.2242655 (Serena's pinned version): bin/{win32,linux,darwin}/<host>
18.0.2732683 (the build reported in #2069): bin/<host>, no platform subdirectories
decision(al): probe both layouts instead of making the path configurable, since the
extension build Serena downloads and the one the user has in VS Code differ by design.
constraint(al): keep the platform subdirectory first so existing behaviour is unchanged
for the pinned version; only a missing executable falls through.
learned(al): neither VSIX declares a targetPlatform, yet the newer package contains no unix or
darwin binaries at all; why Microsoft changed the packaging is upstream of this fix.
The error message now lists every candidate it tried, so the next layout change upstream is
identifiable from the failure alone rather than by reading the adapter.
Fixes#2069
Co-authored-by: sxh313 <sxh313@users.noreply.github.com>
* feat(solidlsp): add Astro language server support (T170)
* test(solidlsp): fix relative_paths type extraction in test_astro_basic
* refactor(solidlsp): replace asserts with explicit exceptions in Astro LSP
* docs(memories): document rule against assert in language server guide
* docs: add Astro language server documentation (README, docs, template, CHANGELOG)
* feat(solidlsp): implement dual-server architecture for Astro with companion TypeScript server (#2085)
- Implement companion AstroTypeScriptServer using @astrojs/ts-plugin for cross-file code intelligence between .ts and .astro components
- Route .ts/.js document symbols, definitions, references, and renames through companion TypeScript server
- Support two-way buffer edit forwarding between Astro LS and companion TS server
- Replace all assert statements with explicit SolidLSPException and FileNotFoundError
- Handle *.customData configuration requests for Volar HTML service
- Register .astro extension in src/serena/hooks.py
- Add test fixtures and tests for symbol retrieval and cross-file references from TypeScript to Astro components
- Update documentation and CHANGELOG
* fix(solidlsp): narrow astro_ls type to AstroLanguageServer in companion server test
* fix(solidlsp): prevent caching empty document symbols and poll initial compilation in Nextflow tests
TypeScriptLanguageServer._find_representative_source_file scanned files directly
adjacent to tsconfig.json before checking a src/ subdirectory, so a same-level tool
config that the tsconfig excludes (vitest.config.ts, jest.config.ts, etc.) could be
picked as the file used to trigger project activation for an additional workspace
folder. The wrong inferred TypeScript project then loads, and cross-package
find_referencing_symbols queries silently return {} instead of raising or warning.
Reorder the scan to walk the src/ subtree first (recursively, respecting the usual
ignored-directory rules) and only fall back to a same-level file when no src/
directory exists, matching the fallback the reporter's issue proposed.
Fixes#2090
Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
* fix(csharp): fold newly created files into the loaded Roslyn project
Roslyn only learns which files belong to a project from the
solution/open and project/open notifications _open_solution_and_projects
sends once at startup. The generic didChangeWatchedFiles/open-close cycle
that poll_and_notify sends for every backend on file creation does not
make it re-evaluate the project, so a .cs file created after the project
was already indexed stayed a standalone Miscellaneous Files document,
producing phantom diagnostics (e.g. an incorrect "using directive is
unnecessary") instead of the real ones.
Add an overridable SolidLanguageServer.notify_files_created hook, called
by LanguageServerFileChangeNotifier.poll_and_notify before it opens newly
created files. CSharpLanguageServer overrides it to resend the same
solution/project notifications and wait for Roslyn's own reload-complete
log line before the file is opened; every other backend keeps its current
behavior via the no-op default.
Fixes#1961
* fix(test): use get_language_server_manager_or_raise in csharp fold test
project.language_server_manager is typed LanguageServerManager | None, so
calling .iter_language_servers() on it directly failed ty's unresolved-attribute
check. Agent already exposes a non-Optional accessor for the same value.
_wait_for_cross_file_references_if_needed latched after the first cross-file
query and never waited again, even when a later query opened a file from a
project tsserver had not loaded yet and started a fresh $/progress cycle.
find_referencing_symbols and find_references would then return whatever
tsserver had indexed so far, silently partial, with nothing distinguishing it
from a complete answer.
The latch still covers the first query's start-grace wait unchanged. A later
query now also checks whether a progress token is currently active and, if
so, waits for it to drain before returning.
Fixes#1937
search_for_pattern resolved each match's line number by rescanning the
file from the beginning (TextUtils.get_line_from_index walks a
TextStepper from index 0), making coordinate resolution O(n) per match
and O(n*m) for m matches. search_text now precomputes line start
offsets once per file (O(n)) and resolves each match via binary search
(O(log n) per match).
The precomputed table mirrors TextStepper line semantics exactly:
\n, \r\n and bare \r are line separators, and an index pointing at the
\n of a \r\n pair resolves to (next line, 0) — the same rule
TextUtils.get_line_col_from_index implements, now pinned by regression
tests (including the mixed line-ending edge cases from the
insert_text_at_position fix).
On a synthetic 12k-line file (494 KB) with 723 matches, match-coordinate
resolution drops from ~1764 ms to ~2 ms (~1000x); the one-off
precompute costs ~1.1 ms. Small files regress nothing (100 lines, 5
matches: 70 µs -> 11 µs). Results are byte-for-byte identical to the
previous implementation.
Note: compatible with PR #1899 (interruptible agent regexes): that PR
touches the re.compile call site in search_text and the ContentReplacer
helpers, this PR touches the post-match line-coordinate resolution; the
two changes are complementary and rebase cleanly onto each other.
* perf(search_text): add reproducible benchmark script
scripts/profile_search_text.py regenerates the synthetic benchmark
content with a fixed seed (no real-world code), asserts both resolution
paths produce identical results, and prints the before/after timings
quoted in the PR description. Default: 12k lines / ~1830 matches;
optional CLI args scale the file down for quick runs.
* refactor(ls): encapsulate cached text coordinates in TextCoordinates abstraction
Address review feedback on the search_text line-lookup PR: the previously
added module-level helpers duplicated TextStepper's line-ending logic and
exposed raw offsets as a low-level data structure shared between two
functions, violating the project's software design principles (each
concern in exactly one home; dataclasses instead of tuples for simple
data storage).
Introduce in solidlsp/ls_utils.py:
- LineCol: frozen, kw-only dataclass for a 0-based line/column position
- TextCoordinates: encapsulates the cached table of line start offsets,
built via TextStepper (step_line + line_start_idx), with a single
public method line_col_at_index() resolving an index via binary
search, including the CRLF rule (an index pointing at the '\n' of a
'\r\n' pair denotes the beginning of the next line, column 0) and
InvalidTextLocationError for out-of-range indices
search_text now instantiates TextCoordinates once per file and resolves
each match's coordinates through the public method; the private helpers
(_compute_line_starts, _line_col_at_index) are removed.
Equivalence with TextUtils.get_line_col_from_index verified across
mixed line endings, empty content and boundary indices (0 mismatches).
Tests are behaviour-anchored: the implementation-coupled parametrized
test importing the private helpers is replaced by public-contract tests
of TextCoordinates (test/solidlsp/test_ls_utils.py) and absolute line
assertions through the public search_text API. The benchmark script
uses the public API as well.
Benchmark (synthetic 12k lines, ~723 matches, best of 5): coordinate
resolution ~1764 ms -> ~2 ms; precompute ~1.1 ms; small files unchanged.
Semantics are unchanged; results are identical to the previously
proposed implementation and to the upstream reference implementation.
* fix(test): rename test_ls_utils to test_text_coordinates to avoid basename collision
The new test file collided with the pre-existing upstream
test/solidlsp/util/test_ls_utils.py: pytest (prepend import mode, no
__init__.py in the test dirs) requires unique basenames, and collecting
both modules produced an import-file-mismatch error that aborted
collection with exit code 2 across every CI matrix job.
Rename to test_text_coordinates.py (unique, and matches the class under
test). Full-suite collection verified locally (2252 tests, 0 errors).
The Dart analysis server treats rootUri as an additional analysis root on top of
workspaceFolders, with no de-duplication, so on a monorepo root the whole tree is
analysed and the server burns CPU at idle. Always omit rootUri/rootPath and rely on
workspaceFolders alone.
Rename propagation
* `rename_memory_and_propagate_references` enumerated `get_full_list()`, which
includes the memories matched by `read_only_memory_patterns`.
* Writing one of those raises `PermissionError` in `_check_write_access`, so a
rename of a memory that was referenced from a read-only memory failed *after*
`move_memory` had already applied the rename.
* The memory graph was then half-updated: the renamed memory existed under its
new name while the writable referrers still held `mem:OLD_NAME`, and retrying
the rename failed with "Memory not found".
Fix
* In a tool context, propagate only into the memories that accept writes, sorted
to keep the enumeration order that `get_full_list()` provided.
* Outside a tool context nothing changes, so read-only memories are still updated
when the user renames through the CLI.
* A reference which thereby remains in a read-only memory is still reported as
stale by `validate_referential_integrity`, so it is not hidden.
Documentation
* `docs/02-usage/045_memories.md` promised that the tool rewrites every reference across all
memories. That is now only true for the memories the agent may write, so the caveat is stated
where the promise is made.
Tests
* Add a regression pair: the tool-context rename completes and rewrites both
occurrences in a writable referrer, while the CLI context additionally rewrites
the read-only one.
* fix(tools): expand unmatched-group $!N backreferences to the empty string
A $!N backreference in a regex-mode replacement template refers to the Nth
group of the search expression. When the group exists but did not participate
in the match (e.g. it sits inside an optional construct that was skipped), the
expansion emitted the literal template text instead of the empty string.
Observed in practice when an agent replaced 15 occurrences in an MQL header
with a template containing EA_INPUT$!1(...): the group was optional and never
participated, so every site came out containing the literal EA_INPUT$!1(...).
A reference to a group the expression does not define at all now raises a
clear ValueError instead of a raw IndexError - which also crashed
literal-mode replacements whose template contained $!N, since literal mode
compiles an escaped pattern without any groups.
* chore(ci): re-trigger test workflow
The previous run failed in native (macos-latest) on
test_cpp_basic.py::test_get_document_symbols[cpp_ccls] ("Expected 'main'
in document symbols, got: []"), a ccls language-server startup/indexing
flake unrelated to this PR's changes (text_utils.py and its tests only).
* fix(tools): pass literal-mode replacement through verbatim (no $!N expansion)
ContentReplacer ran the $!N backreference expansion on the replacement
template in both modes, so a literal-mode replacement whose template
contained a $!N sequence (e.g. documenting the convention itself in a
memory) failed with a backreference error instead of writing the text.
This contradicted the tools' documented contract ('the replacement string
(verbatim)' in literal mode) and the behavior of the dry-run path, where
MultiFileContentReplacer.find_occurrences already gated the expansion on
regex mode.
The expansion is now gated on the mode, mirroring the dry-run path; the
ambiguity validation still applies in both modes. The literal-mode test
that asserted the crash is inverted accordingly and the changelog entry
is corrected.
* fix(csharp): stop tuple-typed properties losing their name to the method branch
_extract_base_name_and_type split Roslyn's "Name : Type" property names by
checking for a literal '(' anywhere in the raw string. A C# tuple type is
written with parentheses ("(int X, string Y)"), so a tuple-typed property
tripped that guard and fell into the method branch instead, which kept the
trailing " :" as part of the reported name (e.g. "Position :"). Since
find_symbol defaults to exact name-path matching, such a property becomes
unfindable by its real name.
The guard now only checks for '(' in the identifier segment before the
first " : ", not the whole string, so a parenthesis inside the type
annotation no longer affects branch selection.
* fix(csharp): bump the high-level symbol cache fingerprint
_normalize_symbol_name's output changed in this PR, and
_document_symbols_cache_fingerprint gates the cache that stores its
result; leaving the version at 1 would keep serving the old, corrupted
names to anyone with an existing on-disk cache.
The previous default, 262.9593.0, is a JetBrains EAP-style build that has
expired: intellij-server exits at startup with "This build of intellij-server
has expired", so every Kotlin symbolic tool fails. 263.4702.0 is the newest
release on Kotlin/kotlin-lsp and starts normally.
Uses the same archive layout and CDN path as 262.9593.0. Hashes regenerated
with scripts/update_downloaded_dependency_hashes.py.
Fixes#2008
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Previously, Tool.apply_ex derived a session ID from the MCP session object (or
"global" with no context) and injected it into any apply() method that declared
a session_id parameter. This is being changed because:
* the new MCP SDK v2 no longer provides session identifiers
* handling it internally is more robust anyway, since clients did not
consistently use sessions
Session handling
- Remove implicit session ID injection from Tool.apply_ex, along with the
supporting _is_session_aware property and SESSION_ID_PARAM_NAME skip logic
- SerenaAgent.create_system_prompt() now creates the session itself and reports
its id, instead of receiving session_id as an argument
Tool signatures
- InitialInstructionsTool.apply() and ActivateProjectTool.apply() now declare
session_id explicitly and rely on the LLM to pass it, rather than having it
injected
- Rename SerenaReplTool.apply()'s session parameter to session_id for consistency
with the other tools
Resolves#2061
* Many case differentiations in the agent code were replaced by
method calls in the newly introduced LanguageBackend abstraction
* The registry allows new backends to be added dynamically
(via Python packages that implement a specific entrypoint)
- Require a valid Bearer token on heartbeat and project-query requests.
- Send the configured auth_secret from clients and the query-project tool.
- Test accepted and rejected credentials, including real HTTP client requests.
- Generate a random UUID when auth_secret is missing, null, or empty.
- Persist generated secrets and preserve configured values across reloads.
- Document the shared secret and test generation, persistence, and direct construction.
- Rename the read-only context to read_project_context and add writable project_context.
- Dispatch LSP-backed edits remotely and enforce read-only access in the calling context.
- Allow project-server facade calls independently of the target project's API restrictions.
- Update dispatch coverage for both backends and access modes; remove obsolete rejection assertions.
Facade availability:
* facades can be optional (`Facade.from_api(..., is_optional=True)`), mirroring optional tools
* `Facade.is_enabled()` is derived: a facade is available iff it has at least one enabled method
* `ApiScope.is_facade_enabled` is thereby obsolete and removed
Opt-in rule (uniform for optional facades, excluded facades and optional methods):
* methods of a facade which is not included and optional methods require explicit inclusion
* all other methods are enabled unless explicitly excluded
Application:
* the `ext` facade is optional; the query-projects mode includes it (`included_apis: [ext]`)
Godot's GDScript parser can report a symbol's end column one column past the
line-end convention every other language server follows (closing a node's range from
the next lookahead token instead of the last consumed one, when that lookahead is a
synthesized newline); `replace_symbol_body` on the last function in a file silently
consumed the separating blank line as a result. `GodotLanguageServer` now corrects this
specific, measured overshoot when building its high-level document symbols (#1974)
---------
Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
The project registry in serena_config.yml can be added to from the CLI
('project create', 'project index') and from the MCP side
('activate_project'), but it could only be removed from on the MCP side,
via the 'remove_project' tool. From a terminal, hand-editing the file was
the only way to drop an entry.
'serena project remove <PROJECT_NAME|PROJECT_PATH>' takes the same
argument type as 'project index', so a project can be addressed by name
or by path. Only the registry entry is removed; the project's own files,
including its project configuration, are left on disk.
Adds SerenaConfig.remove_registered_project, which mirrors
add_registered_project and removes the given entry rather than the first
entry whose name matches. Resolving a path to an entry and then deleting
by name would remove the wrong project when two registered directories
carry the same project_name.
Refs #2029
`serena.analytics` imported `anthropic.types` at module level for two type
annotations, so every `import serena.cli` / `import serena.agent` paid for
loading the whole anthropic package (2-4 s on the reporter's Windows machine)
even with the default CHAR_COUNT token estimator. That delay can push MCP
startup past a client's initial tool-discovery window.
The import now lives behind `TYPE_CHECKING`; `AnthropicTokenCount` already
imported `anthropic` lazily in `__init__`, and the request body is a plain
dict (what `MessageParam` produces at runtime anyway).
Tests: importing serena.cli / serena.agent / serena.analytics in a fresh
interpreter leaves `anthropic` out of `sys.modules`; the Anthropic estimator
still sends the expected count_tokens request.
Fixes#2012
* fix(tools): save edited source files atomically
Issue #1958 lists three direct open(path, "w") writes that can destroy a
file if the process stops partway through: two in MemoryManager and
CodeEditor._save_edited_file. The memory ones were fixed in #1969, which
is also where write_file_atomic comes from; this is the source-file half
the issue proposes as a follow-up.
A plain open(path, "w") truncates the file before the new content is
complete, so a crash, an OOM kill or a full disk during the write leaves
the file holding neither the old content nor the new one.
The issue asks for symlink and metadata behaviour to be confirmed before
source files use the helper. Measured: the executable bit already
survived, but a bare os.replace onto a symlink path swapped the link out
for a regular file and left its target holding stale content, which
open(path, "w") does not do. write_file_atomic now resolves the path
first, so it follows symlinks the way the call it replaces did, and the
temporary file lands in the destination's real directory.
Refs #1958
* test(tools): make the atomic-write tests platform-independent
Two of the new tests failed on the Windows CI job. Both were the tests,
not the change under test.
- The executable-bit test asserted 0o755 and read back 0o666: Windows
does not model POSIX permission bits, and chmod there only toggles the
read-only flag. Skipped on win32.
- The encoding test left newline unset, which is LineEnding.NATIVE and
therefore newline=None, under which Python translates "\n" to
os.linesep on write; it got b"caf\xe9\r\n". The line ending is now
pinned so the test covers the encoding alone, matching the sibling
test that already pins "\r\n" and passes on Windows.
Also covers three paths the first round missed, all of which pass both
with and without the change and are there to show it does not break
them: a file in a subdirectory, a symlinked parent directory (the path
is resolved in full, so the temp file lands in the destination's real
directory), and a non-ASCII filename. The last two skip where the
platform cannot create symlinks or represent the name.
Refs #1958
trusted_project_path_patterns is matched against the project's root path itself
(SerenaConfig.is_trusted_project_path, called from Project.is_trusted with project_root).
A '<project root>/**' entry therefore matches only paths below the root and trusts no
project at all, while reading like the natural way to name one project. The template's
example showed only parent-directory globs, so there was nothing to copy for the
single-project case.
Adds the bare-root form to the example, states which path a pattern is matched against,
and pins all three cases in tests so the documented example cannot silently go stale.
Requested by a maintainer in #2001.
* _activate_project: a project's backend override switches the backend in REPL mode (background
modes, facades, prompt parameters and backend initialisation are recomputed upon activation)
* the tool interface keeps rejecting the mismatch (fixed toolset); the error now mentions the REPL
as a workaround
* single-project sessions: in the REPL tool's description (fixed at registration)
* otherwise: in the project activation message (facades depend on the activated project)
* REPL tool description wording adjusted (listing location, notebook semantics)
Peripheral changes:
* is_tool_function_available: in REPL mode, active tools without facade counterparts count as available
Introduce the ext facade with list_projects and project_context: within
`with s.ext.project_context(name):`, the facades operate read-only on the given external project.
The agent's active project is switched temporarily, and methods marked with
`@facade_method(uses_project_server=True)` (all lsp methods) are executed in the project server when
the LSP backend is active (with JetBrains, the IDE serves all projects, so they run locally). The
project server gains a /call_facade_method route executing a facade method on its own REPL facades
and returning the pickled result; arguments are transferred as JSON. Editing methods are refused,
and contexts cannot be nested. This replaces the query_project tools in the REPL.
Result objects are now self-contained (picklable): renderers no longer hold the agent (only the
default length limit; the constructor interface is unchanged), LSP results carry eagerly retrieved
symbol info and reference contexts instead of renderers accessing the language server or project.
Code is now executed at module level in the session's namespace (statements executed, a trailing
expression evaluated as the result), replacing the function wrapper with global declarations that
persistence had required. This yields notebook semantics throughout: top-level bindings persist by
construction, functions resolve names from the session namespace, and `return` is no longer used
(a top-level `return` yields a syntax error with a hint). Tool description and tests updated.
Code executions of a session now share a namespace (SerenaSession.repl_namespace), which serves as
the globals of the executions: variables, functions, classes and imports bound at the top level of
the submitted code persist across calls, like the cells of a notebook. The code is wrapped in a
function at the AST level (preserving line numbers for error reporting), with the top-level names
declared global. The entrypoint `s` is re-bound before every execution, such that persisted
functions always use the current entrypoint (e.g. after a project switch). `s.vars()` lists the
persisted items, `s.clear()` removes them; the tool description explains the persistence.
The namespace is tied to the session's lifetime; the session registry additionally evicts sessions
which have been idle for too long, releasing their namespaces.
Facades now discover all user-defined classes reachable through the annotations of their methods and
of the members of reachable types (transitively) and make them documentable via s.info, without the
types having to be declared. Explicit ReferencedType declarations serve curation only (member
whitelists, flags). TypedDicts are documented with their keys (e.g. the JetBrains symbol DTOs and
the LSP diagnostic structures), enums with their members. SymbolKind no longer needs declaring.
Referenced types are now also discovered through the parameter annotations of type members and
facade methods (not only through return types), such that e.g. SymbolKind (a parameter type of
LanguageServerSymbol.iter_ancestors) is documented along with LanguageServerSymbol. Enums are
documented with their members and values. The lsp facade declares SymbolKind as a referenced type.
Type documentation now transitively includes the declared types a type's members reference, such
that e.g. LspSymbolCollection's documentation includes LanguageServerSymbol. To avoid repeating widely
shared types, the REPL tracks per session which types have been documented: a contained type
documented earlier in the session is only pointed to (an explicit request always yields it in full).
Since MCP provides no reliable session identification and clients keep a stdio server across
conversations, the REPL's session identity is supplied by the LLM: the instructions (system prompt)
establish a SerenaSession and state its id, and the REPL tool takes a required session_id parameter.
Sessions are managed by a SessionRegistry (creation on demand, LRU eviction). Tools which may be
called before the instructions have been read (activate_project, initial_instructions) are unchanged.
The REPL overview (tier 0, part of the tool description) now names the result type of methods
returning objects that can be processed in code (e.g. `find_symbol -> LspSymbolCollection`), such
that type documentation can be requested alongside a facade in one turn.
The facade description (tier 1) documents the common operations in full, summarises methods marked
as niche (new flag of facade_method; not yet set anywhere) with a pointer to their documentation,
and lists result types by name only. Type documentation (tier 2) is provided on request only;
provide_info_with_facade remains available but is no longer set for any type.
The REPL tool description states this protocol, in particular that facade descriptions do not
include result types and that type documentation should only be requested for processing results in
code.
Facades declare the types their methods return or contain as ReferencedTypes (with an optional
whitelist of members for curation, e.g. for LanguageServerSymbol, and a flag for inclusion in the
facade description). Type documentation is generated from class docstrings, attribute annotations,
properties and method signatures/docstrings, and is available via s.info("<facade>.<Type>") or by
bare type name; method documentation points to the documentation of a referenced return type.
Annotations are rendered without module paths, such that the names in signatures are the names by
which types can be looked up. s.info accepts several items at once, reporting unknown items inline.
All APIs declare their result types; result classes declare their attributes at class level.
Introduce ConfigApi, exposing Serena's configuration and session state: get_current_config and
open_dashboard (both non-editing). The corresponding tools delegate to the API via ConfigApiMixin.
As on the tool side, opening the dashboard is only offered if the dashboard is enabled and not
opened automatically; the condition is shared (_is_dashboard_openable) and applied to the API scope
via a NamedApiInclusionDefinition, the API-side counterpart of NamedToolInclusionDefinition.
Prompt parameters (available tools/markers and the tool name mapping) are now computed per agent
interface (SerenaAgent.PromptParams). In REPL mode, the available tools comprise the exposed tools
and the enabled facade methods, and the tool name mapping resolves tool names to the qualified names
of the corresponding facade methods (e.g. `lsp.find_symbol`), such that prompts refer to operations
as they are used in REPL code. The parameters are re-computed whenever the active tools change.
FacadeMethod refers to its parent facade and provides facade_name and qualified_name; its name is
taken from its info. Prompt templates use the tool name mapping consistently (fixing a template
syntax error in the editing mode) and use interface-neutral wording.
Introduce AgentInterface (tools/REPL) as a setting in the global configuration, overridable per
project (resolution: project > global > default "tools") and via the CLI option --agent-interface.
The interface is fixed for the session, like the language backend.
In REPL mode, the set of exposed tools is fixed: the REPL tool, initial_instructions and (unless in a
single-project session) activate_project. Tool inclusion/exclusion definitions do not apply in REPL
mode, as they pertain to the tool interface; the REPL is configured via API inclusions/exclusions.
The REPL tool is optional in tool mode.
SerenaAgent.is_single_project records whether the session is a single-project session.
FacadeMethodInfo gains corresponding_tool (the tool class offering the same functionality) and
get_corresponding_tool_name, and every API method with a tool counterpart names it in its
facade_method decorator. This enables applying tool-level exclusions to API methods and rendering
prompt conditions based on tools.
Since the API modules now refer to the tool classes, the tools import the APIs locally (in the
mixins' _api methods), and the symbol tools' symbol_dict_grouper attributes become properties.
Introduce ShellApi with the single editing operation execute_shell_command, which returns a
ShellCommandOutput exposing stdout, stderr, return code and working directory to code and rendering
as JSON as before. Keeping shell command execution in its own facade makes it a natural unit for
exclusion. The shell command tool delegates to the API via ShellApiMixin; the shell facade is always
part of the REPL.
Introduce FsApi, exposing operations on the project's files as units: read_file, create_text_file,
list_dir, find_file and search_for_pattern. The boundary to the edit facade, which modifies content
within existing files, is stated in the facade description. Results expose their data to code
(FileContent, DirectoryListing, PatternMatches) and render as before; the pattern search's
shortening ladder moves from the tool into PatternMatchesRenderer.
The file tools delegate to the API via FsApiMixin. The fs facade is always part of the REPL.
Introduce MemoryApi, exposing the memory operations list_memories, read_memory, write_memory,
edit_memory, rename_memory and delete_memory (the latter four marked as editing operations) as well
as onboarding, which provides the onboarding instructions. list_memories returns a MemoryList, which
exposes the (writable and read-only) memory names to code and renders as JSON.
The memory tools and the onboarding tool delegate to the API via MemoryApiMixin; the onboarding tool
retains its check for the availability of the memory writing tool. The mem facade is always part of
the REPL.
The modes no-onboarding, no-memories and benchmark exclude the corresponding APIs (mem.onboarding,
or the mem facade as a whole), mirroring their tool exclusions.
Introduce EditApi as the facade for editing operations which depend
only on the code editor abstraction and thus work with any language
backend: create_text_file, replace_content, replace_in_files, the
optional line-level operations delete_lines, replace_lines and
insert_at_line, and the symbol-level operations replace_symbol_body,
insert_after_symbol and insert_before_symbol (moved from LspApi, which
retains the LSP-specific rename_symbol and safe_delete_symbol).
replace_in_files builds on MultiFileReplacement: a dry run returns a
ReplacementPreview, which exposes the occurrences to code and renders
the listing of prospective changes; rejections raise a ValueError
carrying the listing where applicable.
FacadeApi gains a backend-agnostic _create_code_editor; the LSP API's
retriever-based factory is renamed to _create_ls_code_editor. The
editing tools (file and symbol level) delegate to EditApi via
EditApiMixin, retaining only the diagnostics context. The edit facade
is always part of the REPL.
Facade methods are now declared explicitly via the facade_method
decorator, which attaches FacadeMethodInfo (optional, beta, can_edit)
mirroring the tool markers; undecorated methods are never exposed.
All LspApi and JetBrainsApi methods are decorated accordingly
(JetBrains methods are non-optional, as the facade only exists with
the JetBrains backend).
Which facades/methods are enabled is determined by an ApiScope, which
is built by applying ApiInclusionDefinitions (included_apis,
excluded_apis, referencing facades or facade methods such as "lsp" or
"lsp.find_symbol") from the global configuration, the context, the
active modes and the project configuration, in that order. Optional
methods and all methods of an excluded facade must be included
explicitly; other methods are enabled unless excluded. For read-only
projects, editing methods are excluded. The entrypoint omits facades
which are not enabled.
The REPL is re-created whenever the active tools are updated (mode
switch, project activation), as it depends on the same configuration.
The new settings are read from project.yml and documented in all
configuration templates.