字节笔记本
2026年6月21日
hermes教程-LSP — 语义诊断
语言服务器协议 (LSP)
Hermes 将完整的语言服务器(如 pyright、gopls、rust-analyzer、typescript-language-server、clangd 以及约 20 个其他服务器)作为后台子进程运行,并将其语义诊断结果输入到 write_file 和 patch 使用的写入后 lint 检查中。当代理编辑文件时,它能够精确看到该编辑引入的错误——不仅仅是语法错误,还包括语言服务器检测到的类型错误、未定义名称、缺失导入以及项目范围的语义问题。
这是顶级编码代理所使用的相同架构。Hermes 以自包含方式提供:无需编辑器宿主、无需安装插件、无需管理单独的守护进程。
LSP 何时运行
LSP 基于 git 工作区检测 进行门控。当代理的工作目录(或正在编辑的文件)位于 git 仓库内时,LSP 会针对该工作区运行。当两者都不在 git 仓库中时,LSP 保持休眠状态——这对于当前工作目录是用户主目录且没有项目需要诊断的消息网关场景非常有用。
检查是分层的:首先进行进程内语法检查(微秒级),当语法检查通过后再进行 LSP 诊断。一个不稳定或缺失的语言服务器永远不会破坏写入操作——每个 LSP 失败路径都会静默回退到仅语法检查的结果。
具体来说,在每次成功的 write_file 或 patch 操作中:
- Hermes 捕获该文件当前诊断的基线。
- 执行写入操作。
- 重新查询语言服务器,过滤掉基线中已存在的诊断,仅显示新增的诊断。
代理会看到类似如下的输出:
{
"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 字段携带来自真实语言服务器的语义诊断。两个通道,独立信号——代理会看到一个语法正确但存在语义问题的文件,表现为 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,它要求 typescript SDK 可以从同一个 node_modules 树中导入——当你运行 hermes lsp install typescript 或首次使用时自动安装触发时,Hermes 会同时安装这两个包。
CLI
hermes lsp status # 服务状态 + 每个服务器的安装状态
hermes lsp list # 注册表,可选 --installed-only
hermes lsp install <id> # 主动安装一个服务器
hermes lsp install-all # 尝试安装所有已知配方的服务器
hermes lsp restart # 关闭正在运行的客户端
hermes lsp which <id> # 打印解析后的二进制路径hermes lsp status 是最好的起点——它会显示哪些语言今天可以获得语义诊断,哪些需要安装二进制文件。
配置
默认设置适用于典型配置;如果二进制文件已在 PATH 中,则无需设置任何内容。
## config.yaml
lsp:
## 主开关。禁用将跳过整个子系统——不会启动任何服务器,不会运行后台事件循环。
enabled: true
## 每次写入后等待诊断的时间。
wait_mode: document # "document" 或 "full"
wait_timeout: 5.0
## 如何处理缺失的服务器二进制文件。
## auto — 通过 npm/pip/go install 安装到 <HERMES_HOME>/lsp/bin
## manual — 仅使用已在 PATH 中的二进制文件
install_strategy: auto
## 每个服务器的覆盖配置(全部可选)。
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 # 即使其扩展名匹配也跳过 TS每个服务器的键
disabled: true— 即使其扩展名匹配文件,也完全跳过此服务器。command: [bin, ...args]— 指定自定义二进制路径。绕过自动安装。env: {KEY: value}— 传递给生成进程的额外环境变量。initialization_options: {...}— 合并到 LSPinitialize握手中发送的initializationOptions负载中。特定于服务器;请查阅语言服务器的文档。
安装位置
当 install_strategy: auto 时,Hermes 将二进制文件安装到 <HERMES_HOME>/lsp/bin/。NPM 包会安装到 <HERMES_HOME>/lsp/node_modules/,并在上一级创建二进制符号链接。Go 二进制文件来自 go install,其中 GOBIN 指向暂存目录。
任何内容都不会安装到 /usr/local/、~/.local/ 或其他共享位置——暂存目录完全由 Hermes 拥有,并在重置配置文件时被删除。
性能特性
LSP 服务器在首次使用时延迟生成。在一个从未处理过 .py 文件的项目中编辑 Python 文件会生成 pyright;对于大多数服务器,生成过程需要 1-3 秒(rust-analyzer 在冷项目上可能需要 10 秒以上)。同一工作区中的后续编辑会重用正在运行的服务器。
当没有发出诊断时,LSP 层为干净的写入增加几毫秒。当发出诊断时,等待预算为 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 显示服务器为“缺失”
该二进制文件不在 PATH 上,也不在 <HERMES_HOME>/lsp/bin/ 中。运行 hermes lsp install <server_id> 尝试自动安装,或通过该语言的正常工具链手动安装二进制文件。
hermes lsp status 中的 Backend warnings 部分
某些服务器作为实际诊断的外部 CLI 的薄包装器提供——它们正常生成并接受请求,但当侧车二进制文件缺失时从不发出错误。最常见的情况是 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)需要完成项目范围的索引后才能发出每个文件的诊断;服务器启动后的第一次编辑可能完成时没有诊断,后续编辑才会获取到。
服务器崩溃
崩溃的服务器会被添加到损坏集合中,并且在会话的剩余时间内不会重试。运行 hermes lsp restart 以清除该集合;下一次编辑会重新生成。
在 git 仓库之外编辑文件
根据设计,LSP 仅在 git 仓库内运行。如果项目尚未初始化,请运行 git init 以启用 LSP 诊断。否则,将应用进程内仅语法检查的回退。