Skip to content

feat: Language Server Protocol (LSP) integration#3016

Open
TheArchitectit wants to merge 16 commits into
ultraworkers:mainfrom
TheArchitectit:feat/lsp-v2-proper
Open

feat: Language Server Protocol (LSP) integration#3016
TheArchitectit wants to merge 16 commits into
ultraworkers:mainfrom
TheArchitectit:feat/lsp-v2-proper

Conversation

@TheArchitectit
Copy link
Copy Markdown

@TheArchitectit TheArchitectit commented May 10, 2026

No description provided.

TheArchitectit and others added 16 commits May 10, 2026 21:42
When preserve_recent_messages == 0, raw_keep_from equals messages.len(),
causing index out of bounds when accessing session.messages[k].

Added k >= session.messages.len() check to prevent panic.

Reason: Compaction with preserve_recent_messages=0 triggered OOB access
when checking for tool-use/tool-result pair preservation at boundary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The final streaming chunk from some providers contains only finish_reason
and usage, with no delta field. Made it optional to prevent parse errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Some providers (GLM, DeepSeek) emit reasoning tokens in `reasoning_content`
or nested `thinking.content` fields instead of `content`. Added support
for these fields so reasoning models work correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a provider returns a JSON error (e.g., {"error":{"message":"..."}})
without SSE framing (no "data:" prefix), the SSE parser was silently
ignoring it and hanging. Now detects and surfaces these errors.

Also handles HTML responses that lack SSE framing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a provider returns HTML (e.g., error page, wrong endpoint) instead
of JSON in an SSE stream, provide a clear error message instead of
hanging or failing with a cryptic parse error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds scripts/install.sh that builds the release binary and links it
to ~/.local/bin/claw. Run after code changes to update the CLI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Some OpenAI-compatible providers (e.g., GLM-5) omit the `id` field in
streaming and non-streaming responses. Adding #[serde(default)] allows
the parser to accept these responses instead of failing with
"missing field `id`".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement complete LSP support for code intelligence tools:

- lsp_transport.rs: JSON-RPC 2.0 transport over stdio with Content-Length
  framing, async request/response handling, and graceful shutdown

- lsp_process.rs: LSP process manager with initialize handshake, and methods
  for hover, goto_definition, references, document_symbols, completion, format

- lsp_discovery.rs: Auto-discovery of installed LSP servers (rust-analyzer,
  clangd, gopls, pyright, typescript-language-server, etc.) with PATH lookup

- lsp_client.rs: Rewired LspRegistry to use real LSP processes instead of
  placeholder JSON, with lazy-start on first dispatch call

- config.rs: Added LspServerConfig for user-configured LSP servers

- config_validate.rs: Validation for lsp config section

- main.rs: CLI integration with server discovery at startup, /lsp slash
  command for status/start/stop/restart, and graceful shutdown on exit

- commands/src/lib.rs: Added SlashCommand::Lsp variant

The LSP tool is now available to the agent for hover, definition, references,
symbols, completion, and diagnostics queries. Servers are auto-discovered at
REPL startup and lazily started on first use.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rust-analyzer installed through rustup exits non-zero on --version
("Unknown binary in official toolchain"), which caused discovery
to skip it. Changed command_exists_on_path to treat any successful
spawn as "found", regardless of exit code — only a failure to
spawn (command not found) means the server isn't available.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…chment

Wire LSP into the Read/Edit/Write tool flow so the agent automatically
gets diagnostics after file operations:

- lsp_transport: Add LspServerMessage enum, read_message() for handling
  both responses and server-initiated notifications, notification queue
  with drain_notifications(), send_request now handles interleaved
  publishDiagnostics without breaking

- lsp_process: Add did_open(), did_change(), drain_diagnostics(),
  open file tracking (HashSet) and version counters for didChange,
  language_id_for_path() and severity_name() helpers

- lsp_client: Add notify_file_open(), notify_file_change(),
  fetch_diagnostics_for_file() with best-effort graceful fallback,
  registry-level open file tracking, diagnostic caching

- tools: Enrich run_read_file with didOpen + diagnostics, run_write_file
  and run_edit_file with didChange + diagnostics, format_diagnostic_appendix()
  for readable diagnostic output appended to tool results

All enrichment is non-blocking: if no LSP server is available, tools work
exactly as before. No errors propagate from the LSP layer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Split the three large LSP files into module directories with sub-files:

lsp_transport/ (was 560 lines):
  - mod.rs (425) — types + LspTransport impl
  - tests.rs (134) — test module

lsp_process/ (was 929 lines):
  - mod.rs (436) — LspProcess struct + public methods + error types
  - parse.rs (311) — helper functions and LSP response parsers
  - tests.rs (194) — test module

lsp_client/ (was 1338 lines):
  - mod.rs (466) — LspRegistry struct + impl, re-exports from types
  - types.rs (103) — LspAction, LspDiagnostic, LspServerStatus, etc.
  - dispatch.rs (224) — LspRegistry::dispatch() method
  - tests.rs (273) — core registry tests
  - tests_lifecycle.rs (294) — lifecycle and integration tests

All files under 500 lines. All 501 runtime tests pass. Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…transport modules

- Add lsp_auto_start field to RuntimeFeatureConfig (default: true)
- Add lspAutoStart bool field validation in config_validate
- Parse lspAutoStart from config JSON
- Auto-start discovered LSP servers on REPL init when enabled
- Add /lsp toggle command to enable/disable auto-start at runtime
- Remove lsp_client.rs, lsp_process.rs, lsp_transport.rs (2831 lines)
  — functionality consolidated into discovery-based auto-start
- Show auto-start status in /lsp status output
- Add distro-aware install prompt system: detects Ubuntu/Debian/Fedora/
  Arch/openSUSE/Alpine/Void/NixOS/macOS and suggests the right install
  command for each missing LSP server at startup
- Add 6 new language servers: HTML, CSS, JSON, Bash, YAML, GDScript
- Add didClose notifications for proper file lifecycle
- Add code_action support (quick fixes, refactors)
- Add rename support (workspace-wide symbol renaming)
- Add signature_help (function signatures + parameter hints)
- Add code_lens (inline actionable hints)
- Add workspace_symbols (project-wide symbol search)
- Add workspaceFolders support in initialize handshake
- Advertise full capability set (code actions, rename, signatures,
  code lens, workspace symbols) to LSP servers
- Fix panic in lsp_discovery test when rust-analyzer is a rustup
  proxy stub for an uninstalled component

💘 Generated with Crush

Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Godot LSP runs as a TCP server on localhost:6008 when the editor is
open — it doesn't speak LSP over stdio like other servers. Added
connect_tcp() to LspTransport which uses socat (or nc fallback) as
a stdio↔TCP bridge, reusing the existing Content-Length framing.
lsp_process detects tcp:// URIs and routes to TCP transport.

LSP startup now gracefully handles servers that fail to start
(gdscript without a running Godot editor) without blocking other
servers from initializing.

💘 Generated with Crush

Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
…vers

Set NODE_NO_WARNINGS=1 when spawning LSP server processes to suppress
noisy punycode deprecation warnings from bash-language-server,
yaml-language-server, vscode-* servers, etc.

💘 Generated with Crush

Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
- Startup now shows "Loading LSP servers..." then ✓/✗ per server
- When auto-start is on: shows disable hint (toggle or settings.json)
- When auto-start is off: shows available servers with how to start

💘 Generated with Crush

Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant