跳到主要内容

语言服务器协议 (LSP)

Hermes 将完整的语言服务器(如 pyright、gopls、rust-analyzer、typescript-language-server、clangd 以及约 20 多种其他服务器)作为后台子进程运行,并将它们的语义诊断信息输入到 write_filepatch 使用的写后 lint 检查中。当 agent 编辑文件时,它会看到该编辑引入的确切错误——不仅是语法错误,还包括语言服务器检测到的类型错误、未定义名称、缺失导入和项目范围的语义问题

这与顶级编码 agent 使用的架构相同。Hermes 将其以自包含方式提供:无需编辑器宿主,无需安装插件,也无需管理单独的守护进程。

LSP 运行时机

LSP 的运行受 git 工作区检测 控制。当 agent 的工作目录(或正在编辑的文件)位于 git 仓库内时,LSP 会针对该工作区运行。如果两者都不在 git 仓库中,LSP 将保持休眠状态——这对于消息网关非常有用,因为其当前工作目录是用户的主目录,且没有需要诊断的项目。

检查是分层的:首先进行进程内语法检查(微秒级),然后在语法无误时进行 LSP 诊断。不稳定或缺失的语言服务器永远不会导致写入失败——所有 LSP 失败路径都会静默回退到仅语法检查的结果。

具体来说,在每次成功的 write_filepatch 操作中:

  1. Hermes 捕获该文件当前诊断信息的基线。
  2. 执行写入操作。
  3. 重新查询语言服务器,过滤掉基线中已存在的诊断信息,仅显示新的诊断信息。

Agent 看到的输出如下:

{
"bytes_written": 42,
"dirs_created": false,
"lint": {"status": "ok", "output": ""},
"lsp_diagnostics": "LSP diagnostics introduced by this edit:\n<diagnostics file=\"/path/to/foo.py\">\nERROR [42:5] Cannot find name 'foo' [reportUndefinedVariable] (Pyright)\nERROR [50:1] Argument of type \"str\" is not assignable to \"int\" [reportArgumentType] (Pyright)\n</diagnostics>"
}

lint 字段携带语法检查结果(通过 ast.parsejson.loads 等进行微秒级进程内解析);lsp_diagnostics 字段携带来自真实语言服务器的语义诊断信息。这是两个独立的信号通道——agent 会将语法正确但存在语义问题的文件视为 lint: ok 加上填充了内容的 lsp_diagnostics

支持的语言

语言服务器自动安装
Pythonpyright-langservernpm
TypeScript / JavaScript / JSX / TSXtypescript-language-servernpm
Vue@vue/language-servernpm
Sveltesvelte-language-servernpm
Astro@astrojs/language-servernpm
Gogoplsgo install
Rustrust-analyzer手动 (rustup)
C / C++clangd手动 (LLVM)
Bash / Zshbash-language-servernpm
YAMLyaml-language-servernpm
Lualua-language-server手动 (GitHub releases)
PHPintelephensenpm
OCamlocaml-lsp手动 (opam)
Dockerfiledockerfile-language-server-nodejsnpm
Terraformterraform-ls手动
Dartdart language-server手动 (dart sdk)
Haskellhaskell-language-server手动 (ghcup)
Juliajulia + LanguageServer.jl手动
Clojureclojure-lsp手动
Nixnixd手动
Zigzls手动
Gleamgleam lsp手动 (gleam install)
Elixirelixir-ls手动
Prismaprisma language-server手动
Kotlinkotlin-language-server手动
Javajdtls手动

对于“手动”条目,请通过适合该语言的任何工具链管理器(如 rustup、ghcup、opam、brew 等)安装服务器。Hermes 会自动检测 PATH 中或 <HERMES_HOME>/lsp/bin/ 中的二进制文件。

少数服务器需要安装一个 npm 不会自动拉取的对等依赖项。目前的情况是 typescript-language-server,它要求从同一 node_modules 树中可以导入 typescript SDK——当你运行 hermes lsp install typescript 或在首次使用时触发自动安装时,Hermes 会同时安装这两个包。

CLI

hermes lsp status          # service state + per-server install status
hermes lsp list # registry, optionally --installed-only
hermes lsp install <id> # eagerly install one server
hermes lsp install-all # try every server with a known recipe
hermes lsp restart # tear down running clients
hermes lsp which <id> # print resolved binary path

hermes lsp status 是最好的起点——它显示哪些语言今天将获得语义诊断,以及哪些需要安装二进制文件。

配置

默认设置适用于典型场景;如果二进制文件已在 PATH 中,则无需进行任何设置。

# config.yaml
lsp:
# Master toggle. Disabling skips the entire subsystem — no servers
# spawn, no background event loop runs.
enabled: true

# How long to wait for diagnostics after each write.
wait_mode: document # "document" or "full"
wait_timeout: 5.0

# How to handle missing server binaries.
# auto — install via npm/pip/go install into <HERMES_HOME>/lsp/bin
# manual — only use binaries already on PATH
install_strategy: auto

# Per-server overrides (all optional).
servers:
pyright:
disabled: false
command: ["/abs/path/to/pyright-langserver", "--stdio"]
env: { PYRIGHT_LOG_LEVEL: "info" }
initialization_options:
python:
analysis:
typeCheckingMode: "strict"
typescript:
disabled: true # skip TS even when its extensions match

每个服务器的键

  • disabled: true — 即使其扩展名匹配文件,也完全跳过此服务器。
  • command: [bin, ...args] — 指定自定义二进制文件路径。绕过自动安装。
  • env: {KEY: value} — 传递给生成进程的额外环境变量。
  • initialization_options: {...} — 合并到 initialize 握手期间发送的 LSP initializationOptions 负载中。特定于服务器;请参阅语言服务器的文档。

安装位置

install_strategy: auto 时,Hermes 将二进制文件安装到 <HERMES_HOME>/lsp/bin/ 中。NPM 包存放在 <HERMES_HOME>/lsp/node_modules/ 中,bin 符号链接位于上一级目录。Go 二进制文件来自 go install,其中 GOBIN 指向暂存目录。

任何内容都不会安装到 /usr/local/~/.local/ 或任何其他共享位置——暂存目录完全由 Hermes 拥有,并在你重置配置文件时被移除。

性能特征

LSP 服务器在首次使用时懒启动(lazy-spawned)。在一个从未处理过 .py 文件的项目中编辑 Python 文件会启动 pyright;大多数服务器的启动耗时为 1-3 秒(rust-analyzer 在冷启动项目中可能需要 10 秒以上)。同一工作区中的后续编辑将复用正在运行的服务器。

当未发出诊断信息时,LSP 层会在干净写入(clean writes)时增加几毫秒的开销。当发出诊断信息时,等待预算为 wait_timeout 秒——通常 pyright/tsserver 的服务器响应时间为几十毫秒,而 rust-analyzer 在索引中期可能需要几秒钟。

服务器在 Hermes 进程的整个生命周期内保持活跃。没有空闲超时回收机制——因为每次写入都重新启动服务器索引的成本远高于保持守护进程运行的成本。

禁用

config.yaml 中设置 lsp.enabled: false 以禁用整个子系统。写后检查将回退到进程内的语法检查(Python 使用 ast.parse,JSON 使用 json.loads 等),这与早期版本中的实现保持不变。

要在不禁用整个层的情况下禁用单一语言:

lsp:
servers:
rust-analyzer:
disabled: true

故障排除

hermes lsp status 显示服务器状态为 "missing"

二进制文件不在 PATH 中,也不在 <HERMES_HOME>/lsp/bin/ 中。运行 hermes lsp install <server_id> 尝试自动安装,或通过该语言的常规工具链手动安装二进制文件。

hermes lsp status 中的 Backend warnings 部分

某些服务器作为外部 CLI 的薄包装层提供实际诊断功能——它们能正常启动并接受请求,但当侧车二进制文件(sidecar binary)缺失时永远不会发出错误。最常见的情况是 bash-language-server,它将诊断委托给 shellcheck。当 hermes lsp status 显示 Backend warnings 部分时,请通过操作系统的包管理器安装指定的工具:

apt install shellcheck      # Debian / Ubuntu
brew install shellcheck # macOS
scoop install shellcheck # Windows

相同的警告也会在服务器启动时记录一次到 ~/.hermes/logs/agent.log 中。

服务器已启动但从未返回诊断信息

检查 ~/.hermes/logs/agent.log 中的 [agent.lsp.client] 条目——来自语言服务器的 stderr 输出和协议错误都会记录在此处。某些服务器(尤其是 rust-analyzer)需要在发出每个文件的诊断之前完成项目范围的索引;服务器启动后的第一次编辑可能不会返回诊断信息,后续编辑才会获取到。

服务器崩溃

崩溃的服务器会被加入损坏集合(broken-set),并且在剩余会话期间不会重试。运行 hermes lsp restart 以清除该集合;下一次编辑将重新启动服务器。

编辑不在任何 git 仓库中的文件

根据设计,LSP 仅在 git 仓库内部运行。如果项目尚未初始化,请运行 git init 以启用 LSP 诊断。否则,将应用仅进行语法检查的进程内回退机制。