Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/share-server-instances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ian-pascoe/pi-lsp": minor
---

Share Server Instances among the Pi sessions in one Pi process, so a main session and its in-process subagents or Advisor sessions no longer each start and load their own copy of every language server. Sessions whose Server Definition resolves to the same command, arguments, environment, `initializationOptions`, `settings`, `lsp.timeouts`, and `maxConcurrentDiagnostics` for the same workspace root lease one process; any difference, such as a project override only one session trusts, still gets its own. A session takes its lease on first use and releases it at shutdown or when it disables the Server Definition, and the process stops when the last lease is released. A crash makes the instance unavailable in every session using it, and `/lsp stop` or `lsp_restart` from any of them stops it for all, each starting it again on next use. `lsp_status` and `/lsp` add `shared with N other sessions` to a running instance other sessions also lease, with a matching `shared_with_sessions` structured field. A server-initiated edit becomes a preview only when one session uses the instance; otherwise it is declined because it cannot be attributed to a session. Server stderr files now live in a private temporary directory per Server Instance, removed when it stops, instead of in the session directory.
5 changes: 3 additions & 2 deletions packages/pi-lsp/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
- **Result Spill** — the complete LSP operation output referenced when the model-visible result reaches Pi's standard output limit (it holds the complete text), or when a structured result exceeds its 1 MiB cap and is bounded (it holds the complete structured data as JSON).
- **Structured Result** — the JSON `structuredContent` of an LSP tool result, which programmatic callers such as codemode scripts receive. It is kept out of model context, adds flat one-based `path`, `line`, and `character` to locations, symbols, and hierarchy items and a `kind_name` to symbols so a script can pass them straight to a position tool, is capped at 1 MiB like Pi's built-in `bash` tool, and is bounded deterministically (longest strings and array tails cut, identifying fields kept, `structured_truncated: true`) when larger. `truncated` also covers a cut of only the model-visible text.
- **Server Definition** — a configured language-server command, language mapping, workspace-root policy (the nearest root marker by default, or the nearest workspace root marker such as `pnpm-workspace.yaml` so one Server Instance covers a monorepo), Activation Gate, and protocol settings identified by a stable server ID. A project Server Definition replaces a global definition with the same ID; an invalid project replacement shadows the global definition and is quarantined.
- **Server Instance** — one running language-server process for a Server Definition and a detected workspace root.
- **Server Instance** — one running language-server process for a Server Definition's effective process configuration and a detected workspace root, shared by every Pi session in the process that resolves the same pair.
- **Server Instance Lease** — a Pi session's claim on a shared Server Instance, taken on first use and released at session shutdown, when the session disables the Server Definition, or when the instance is stopped; the instance runs while it has at least one.
- **Stop** — ending a Server Instance without preventing a later lazy start for its Server Definition.
- **Disabled Server Definition** — a Server Definition that is ineligible to start Server Instances until it is enabled.
- **Server Enablement Override** — an explicit enabled or disabled choice for a Server Definition at session, project, or global scope. A session-scoped choice belongs to the selected session history and does not apply when navigating before it.
Expand All @@ -33,7 +34,7 @@ A tool call discards the Workspace Edit Previews it created when its returned re

Language-server documents are valid UTF-8 text. A result position is converted only against the text of the file it lies in; positions in a file without readable text are approximated and named in a warning, never converted against another file's text. A position that disagrees with a readable file's current text never fails the server's result: a character past the line end is clamped to it without a warning, as the LSP specification says; a line past the document end keeps the position, a character inside a Unicode character snaps to its start, and a warning names the file, because it changed since the server read it or the server sent an invalid position. Content edits follow existing symlinks and identify the canonical target in the Mutation Manifest; resource operations act on the named directory entry. Conflicting or non-file workspace edits are rejected before they become applicable previews. A code action whose edit fails this validation is listed as not applicable, with the reason, while the server's other actions remain.

Server Definitions come only from the `lsp` key in Pi's global and trusted project settings. Pi's standard reload lifecycle reloads configuration. Server Instances start lazily, are reused within the Pi session, and retain failure state until explicit recovery. Read operations and code-action listing may query several matching Server Instances; a rename or formatting preview must identify one when several match. References and rename name the root of the Server Instance they searched and warn when other roots of the same Server Definition exist, because files under those roots may be missing from the result. A directory counts as another root only when its files route to a different Server Instance; packages inside a searched workspace root do not. Because a language server searches only packages it has loaded, references and rename in a workspace root also name its packages (directories holding a root marker) where the Server Instance has no synchronized document; before a references or rename request, the extension opens one representative file in each unloaded package, capped by count and time, and names the packages still unloaded (ADR-0004). Workspace root selection never uses the home directory or anything above it unless the working directory is there.
Server Definitions come only from the `lsp` key in Pi's global and trusted project settings. Pi's standard reload lifecycle reloads configuration. Server Instances start lazily, are shared by the Pi sessions in one Pi process that resolve the same Server Definition configuration and root, and retain failure state until a Stop or restart from any of them. Read operations and code-action listing may query several matching Server Instances; a rename or formatting preview must identify one when several match. References and rename name the root of the Server Instance they searched and warn when other roots of the same Server Definition exist, because files under those roots may be missing from the result. A directory counts as another root only when its files route to a different Server Instance; packages inside a searched workspace root do not. Because a language server searches only packages it has loaded, references and rename in a workspace root also name its packages (directories holding a root marker) where the Server Instance has no synchronized document; before a references or rename request, the extension opens one representative file in each unloaded package, capped by count and time, and names the packages still unloaded (ADR-0004). Workspace root selection never uses the home directory or anything above it unless the working directory is there.

An Activation Gate is evaluated independently for every candidate file. A Server Definition that
does not pass its gate is excluded from automatic routing without warning. Changes to root markers
Expand Down
70 changes: 54 additions & 16 deletions packages/pi-lsp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,11 @@ Invalid server definitions, timeout fields, and `includeHintDiagnostics` are qua
individually and remain visible through `status`; unrelated valid settings continue to work. An invalid project server replacement
still shadows the global definition. Untrusted project settings are ignored.

Pi's `/reload` reloads configuration. Servers start lazily on first use and live for one Pi session.
After a process or protocol failure, recovery requires stopping the affected Instance, disabling
then enabling its Definition, using the existing `restart` tool operation, or `/reload`.
Pi's `/reload` reloads configuration. Servers start lazily on first use and are shared by the Pi
sessions in one Pi process, such as a main session and its in-process subagents, that resolve the
same Server Definition configuration and root (see [Limits and lifecycle](#limits-and-lifecycle)).
After a process or protocol failure, recovery requires stopping the affected Instance, using the
existing `restart` tool operation, or ending every session that uses it.

## `/lsp` command

Expand All @@ -141,13 +143,18 @@ roots and the effective enablement scope; opening it never starts a server. Text
/lsp enable typescript --global
```

- **Stop** ends one known Server Instance and clears its failure state. The next matching request
can start it again. Omit the root when only one is known; otherwise choose a root in the picker
or supply its path, relative to Pi's working directory or absolute.
- **Disable** prevents automatic and explicit startup of the entire Server Definition, stops all
its Instances in this session, and clears their failed runtime state.
- **Stop** ends one known Server Instance and clears its failure state, for every session sharing
it. The next matching request in any of them can start it again. Omit the root when only one is
known; otherwise choose a root in the picker or supply its path, relative to Pi's working
directory or absolute.
- **Disable** prevents automatic and explicit startup of the entire Server Definition in this
session and releases this session's leases on its Instances. An Instance stops only when no
other session still uses it; other sessions keep using it.
- **Enable** permits lazy startup without launching a process.

The status and picker rows of a running Instance that other sessions also use end with
`shared with N other sessions`.

Unflagged enable/disable choices are custom session entries. They survive `/reload` and saved-session
resume, but follow the selected branch: navigating before a choice rolls it back, and forks inherit
choices on their selected ancestry. A newly created session starts without session overrides. Pi
Expand Down Expand Up @@ -305,6 +312,9 @@ Diagnostics, hover, status, code actions, and apply results are compact text too
documentation; servers that only answer plaintext render as sent.
- `lsp_status` lists one `server_id state [root] language(extensions,...) [error: ...]` line per
Server Instance (running, starting, unavailable, or stopped) and per Disabled Server Definition.
A running Instance that other Pi sessions in the process also lease ends its line with
`shared with N other sessions`, and its structured entry has a matching `shared_with_sessions`.
An Instance this session holds no lease on reads `stopped`, even while other sessions use it.
One `+N configured, not started (pass all: true to list)` line counts the other Server
Definitions, followed by settings warnings. Pass `all: true` to list every configured Server
Definition. Its structured result applies the same filter, reports the omitted count as
Expand Down Expand Up @@ -653,14 +663,42 @@ excluded from model context; the model sees diagnostics only in the original mut
## Limits and lifecycle

Every LSP tool uses Pi's 2,000-line/50-KB output limit for model-visible text. Complete truncated text is saved as a
Result Spill and the result names its path. Each server's latest 1 MB of stderr is saved in the
session temp directory and failure messages name that path. With `--no-session`, Pi provides no
session directory, so these files use a private directory under the OS temporary directory instead.
Normal session teardown removes it; forced termination may leave temporary files behind.

Documents must be valid UTF-8. Each server keeps at most 100 synchronized documents and closes
least-recently-used documents. Session shutdown requests a graceful LSP shutdown, then terminates
the process within the configured timeout.
Result Spill and the result names its path. Result Spills are saved in the session temp directory.
With `--no-session`, Pi provides no session directory, so they use a private directory under the
OS temporary directory instead. Normal session teardown removes it; forced termination may leave
temporary files behind. Each server's latest 1 MB of stderr is saved in a private directory of its
own under the OS temporary directory, and failure messages name that path. It belongs to the
Server Instance, not to a session, so it outlives the session that started the server and is
removed when the Instance stops.

Server Instances are shared by every Pi session in one Pi process (main sessions, Minimal Subagents
children, Advisor sessions) whose Server Definition resolves to the same command, arguments,
environment, `initializationOptions`, `settings`, `lsp.timeouts`, and `maxConcurrentDiagnostics`
for the same workspace root, with the same pi-lsp version. Sessions that differ in any of these,
for example because only one trusts the project's settings, get their own Instances. There is no
opt-out, and Pi processes never share Instances.

- A session takes a lease on an Instance the first time it routes a request to it, never at
startup, so a session that never touches code starts nothing. Sessions that need an Instance
that is still starting share its one startup, which continues while any of them waits for it.
- A session releases its leases when it shuts down or disables the Server Definition, and every
lease on an Instance ends when the Instance stops. The Instance stops as soon as its last lease
is released.
- A failure makes the Instance unavailable to every session using it. `/lsp stop` or `lsp_restart`
from any of them stops the process for all of them and clears the failure; each starts it again
lazily on its next use.
- Each Instance has one set of synchronized documents, diagnostics caches, capabilities, and
`maxConcurrentDiagnostics` limit, shared by its sessions. Workspace Edit Previews, Result
Spills, enablement overrides, Post-edit Diagnostics, and the roots that references and rename
warn about stay per session.
- A server-initiated edit (`workspace/applyEdit`) becomes a held Workspace Edit Preview in the
session using the Instance when only one session does. When two or more do, the edit cannot be
attributed to one of them, so the server is told it was not applied and no preview is created.

Documents must be valid UTF-8. Each Server Instance keeps at most 100 synchronized documents, for
all the sessions sharing it, and closes least-recently-used documents, so one session's activity
can close another's documents, which only costs a re-open. Stopping an Instance requests a
graceful LSP shutdown, then terminates the process within the configured timeout.

## Security

Expand Down
Loading
Loading