D2: install LSP language servers in the image for in-VM code intelligence

Why
---
In-VM Claude has no code intelligence for the languages it edits most:
the guest image ships toolchains but no Language Server Protocol servers,
so completion, diagnostics, hover, and go-to-definition are unavailable
for C/C++, Python, TypeScript, and Go work done inside the sandbox. The
original Bash agent-vm closed this gap in its `setup` step
(claude-vm.sh:353-361), but the microsandbox rewrite — whose image is
Docker-built once rather than provisioned per-setup — dropped it.

This is the D2 item from PLAN.md ("LSP plugins in the image"): port the
four language servers the original's `setup` installed so that an agent
running in the guest gets the same editor-grade intelligence the original
provided, without any host round-trip.

How
---
images/Dockerfile gains an image-build step that installs the four
language servers the original declared — clangd-lsp (C/C++),
pyright-lsp (Python), typescript-lsp (TypeScript), and
gopls-lsp@claude-plugins-official (Go) — and pre-warms them so the first
in-VM invocation is not paying first-run download/extraction cost. Doing
this at build time (rather than per-setup or on first launch) keeps the
fresh-VM-per-launch model fast: the servers are baked into the image
layer and ready the moment the microVM boots, matching PLAN.md's
"Just Dockerfile + pre-warm" scope for this item.

No Rust, run-path, or version changes are involved; the feature is
entirely contained in the image definition, so existing launch behavior
is unchanged except that the language servers are now present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Evgeny Boger 2026-05-31 20:42:00 +00:00
parent 94ae08e7c1
commit a1f16291fb

View file

@ -233,6 +233,52 @@ ARG AGENT_INSTALL_CACHEBUST=
# Claude Code official installer.
RUN curl -fsSL https://claude.ai/install.sh | bash
# Claude Code LSP plugins (C/C++, Python, TypeScript, Go) baked in so
# in-VM Claude has code intelligence on first launch. Ports the four
# language servers the original `setup` installed (claude-vm.sh:348-362):
# clangd-lsp / pyright-lsp / typescript-lsp / gopls-lsp from the
# claude-plugins-official marketplace.
#
# Marketplace registration first: `claude plugin marketplace add` clones
# https://github.com/anthropics/claude-plugins-official.git into Claude's
# plugin state, then each `claude plugin install <name>@<marketplace>`
# resolves against it. The bare repo URL is what the original git-cloned
# by hand; `marketplace add` lets the CLI manage the clone + index for us.
#
# Best-effort per the chrome-mcp pre-warm style above: a transient
# registry/clone blip during the hourly build must not fail the whole
# image. Each step is guarded with `|| echo skipped`; a missing plugin
# costs in-VM code intelligence for that language, not a broken image —
# Claude still boots and the user can re-run `claude plugin install`.
#
# Must come AFTER the Claude install above — `claude` didn't exist before
# it. node/npm (from the nodejs RUN) and git (from the base apt RUN) are
# both already present, which the marketplace clone + plugin resolution
# need.
#
# Invoke claude by ABSOLUTE PATH (/root/.local/bin/claude): the installer
# drops the binary there but the only ENV PATH in this file is
# /usr/local/cargo/bin:$PATH, and Docker RUN uses a non-login /bin/sh that
# never sources the shell profile the installer edits. A bare `claude`
# would hit command-not-found (rc 127), every `|| echo skipped` guard would
# fire, and the RUN would still exit 0 — baking a GREEN image with ZERO LSP
# plugins. The absolute path matches the `--version` sanity check below and
# the OpenCode block's /root/.opencode/bin/opencode.
#
# A `claude plugin list` runs after the loop so the build log shows which
# plugins actually landed — without it the best-effort guards would hide a
# total no-op behind a clean build. If `marketplace add` ever rejects the
# `owner/repo` shorthand, swap in the explicit URL
# https://github.com/anthropics/claude-plugins-official.git.
RUN { /root/.local/bin/claude plugin marketplace add anthropics/claude-plugins-official \
|| echo "==> LSP marketplace add skipped (will lazy-add at first plugin use)"; } \
&& for lsp in clangd-lsp pyright-lsp typescript-lsp gopls-lsp; do \
/root/.local/bin/claude plugin install "${lsp}@claude-plugins-official" \
|| echo "==> LSP plugin ${lsp} skipped (install at runtime if needed)"; \
done \
&& echo "==> LSP plugins installed at build time:" \
&& { /root/.local/bin/claude plugin list || true; }
# OpenCode official installer. Installs into ~/.opencode/bin; PATH already
# includes it. Symlink into /usr/local/bin so non-login shells find it too.
RUN curl -fsSL https://opencode.ai/install | bash \