語言服務器協議 (LSP)
Hermes 將完整的語言服務器(如 pyright、gopls、rust-analyzer、typescript-language-server、clangd 以及約 20 多種其他服務器)作為後臺子進程運行,並將它們的語義診斷信息輸入到 write_file 和 patch 使用的寫後 lint 檢查中。當 agent 編輯文件時,它會看到該編輯引入的確切錯誤——不僅是語法錯誤,還包括語言服務器檢測到的類型錯誤、未定義名稱、缺失導入和項目範圍的語義問題。
這與頂級編碼 agent 使用的架構相同。Hermes 將其以自包含方式提供:無需編輯器宿主,無需安裝插件,也無需管理單獨的守護進程。
LSP 運行時機
LSP 的運行受 git 工作區檢測 控制。當 agent 的工作目錄(或正在編輯的文件)位於 git 倉庫內時,LSP 會針對該工作區運行。如果兩者都不在 git 倉庫中,LSP 將保持休眠狀態——這對於消息網關非常有用,因為其當前工作目錄是用戶的主目錄,且沒有需要診斷的項目。
檢查是分層的:首先進行進程內語法檢查(微秒級),然後在語法無誤時進行 LSP 診斷。不穩定或缺失的語言服務器永遠不會導致寫入失敗——所有 LSP 失敗路徑都會靜默回退到僅語法檢查的結果。
具體來說,在每次成功的 write_file 或 patch 操作中:
- Hermes 捕獲該文件當前診斷信息的基線。
- 執行寫入操作。
- 重新查詢語言服務器,過濾掉基線中已存在的診斷信息,僅顯示新的診斷信息。
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.parse、json.loads 等進行微秒級進程內解析);lsp_diagnostics 字段攜帶來自真實語言服務器的語義診斷信息。這是兩個獨立的信號通道——agent 會將語法正確但存在語義問題的文件視為 lint: ok 加上填充了內容的 lsp_diagnostics。
支持的語言
| 語言 | 服務器 | 自動安裝 |
|---|---|---|
| Python | pyright-langserver | npm |
| TypeScript / JavaScript / JSX / TSX | typescript-language-server | npm |
| Vue | @vue/language-server | npm |
| Svelte | svelte-language-server | npm |
| Astro | @astrojs/language-server | npm |
| Go | gopls | go install |
| Rust | rust-analyzer | 手動 (rustup) |
| C / C++ | clangd | 手動 (LLVM) |
| Bash / Zsh | bash-language-server | npm |
| YAML | yaml-language-server | npm |
| Lua | lua-language-server | 手動 (GitHub releases) |
| PHP | intelephense | npm |
| OCaml | ocaml-lsp | 手動 (opam) |
| Dockerfile | dockerfile-language-server-nodejs | npm |
| Terraform | terraform-ls | 手動 |
| Dart | dart language-server | 手動 (dart sdk) |
| Haskell | haskell-language-server | 手動 (ghcup) |
| Julia | julia + LanguageServer.jl | 手動 |
| Clojure | clojure-lsp | 手動 |
| Nix | nixd | 手動 |
| Zig | zls | 手動 |
| Gleam | gleam lsp | 手動 (gleam install) |
| Elixir | elixir-ls | 手動 |
| Prisma | prisma language-server | 手動 |
| Kotlin | kotlin-language-server | 手動 |
| Java | jdtls | 手動 |
對於“手動”條目,請通過適合該語言的任何工具鏈管理器(如 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握手期間發送的 LSPinitializationOptions負載中。特定於服務器;請參閱語言服務器的文檔。
安裝位置
當 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 診斷。否則,將應用僅進行語法檢查的進程內回退機制。