M5: LSP pool and diagnostics wiring
Adds the DiagnosticsSource seam trait in harness-core::lsp (kept out of harness-tools so tools never link the LSP crate directly), and implements it in harness-lsp as a pool that lazily spawns one language server per file extension (rust-analyzer, typescript-language-server, gopls, pyright-langserver) only when the binary is on PATH. Adds harness-tools::diagnostics and wires diagnostics into edit/write/bash/glob/grep output.
This commit is contained in:
@@ -434,6 +434,7 @@ mod tests {
|
||||
spawner: None,
|
||||
job_board: None,
|
||||
context_reporter: None,
|
||||
diagnostics: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ pub struct StepContext {
|
||||
/// Present in subagent sessions: reports read files to this session's job on the parent
|
||||
/// board. `None` for root sessions (nothing to report to).
|
||||
pub context_reporter: Option<Arc<dyn ContextReporter>>,
|
||||
/// Language-server diagnostics source shared by the session's edit/write tool calls.
|
||||
pub diagnostics: Option<Arc<dyn crate::lsp::DiagnosticsSource>>,
|
||||
}
|
||||
|
||||
struct FlushTracker {
|
||||
@@ -522,6 +524,7 @@ impl<'a> Run<'a> {
|
||||
metadata: metadata_sink,
|
||||
spawner: self.ctx.spawner.clone(),
|
||||
context_reporter: self.ctx.context_reporter.clone(),
|
||||
diagnostics: self.ctx.diagnostics.clone(),
|
||||
};
|
||||
|
||||
let result = tokio::select! {
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod config;
|
||||
pub mod engine;
|
||||
pub mod event;
|
||||
pub mod llm;
|
||||
pub mod lsp;
|
||||
pub mod permission;
|
||||
pub mod store;
|
||||
pub mod tool;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//! Seam trait for language-server diagnostics, implemented by `harness-lsp` and consumed by
|
||||
//! the edit/write tools. Kept in core so `harness-tools` never links the LSP crate directly.
|
||||
//! See `docs/09-integrations.md`.
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Severity {
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
Hint,
|
||||
}
|
||||
|
||||
/// One diagnostic reported by a language server. Line/character are 1-based for display.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Diagnostic {
|
||||
pub line: u32,
|
||||
pub character: u32,
|
||||
pub severity: Severity,
|
||||
pub message: String,
|
||||
pub source: Option<String>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// `{file}: L{line}: {message}` — the one-line form appended to tool output.
|
||||
pub fn display_line(&self, file: &str) -> String {
|
||||
format!("{file}: L{}: {}", self.line, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
/// A source of file diagnostics (an LSP pool). All methods are best-effort: failures are
|
||||
/// swallowed (logged) so a broken language server never fails a tool call.
|
||||
#[async_trait]
|
||||
pub trait DiagnosticsSource: Send + Sync {
|
||||
/// Ensure a server for `path`'s language is running and told about the file's current
|
||||
/// contents (spawn-if-needed + didOpen/didChange).
|
||||
async fn touch(&self, path: &Path);
|
||||
|
||||
/// Diagnostics for `path`, waiting up to `wait` for the server to (re)publish after a
|
||||
/// change. Returns whatever is known on timeout.
|
||||
async fn diagnostics(&self, path: &Path, wait: Duration) -> Vec<Diagnostic>;
|
||||
}
|
||||
@@ -195,6 +195,9 @@ pub struct ToolCtx {
|
||||
pub spawner: Option<Arc<dyn SubagentSpawner>>,
|
||||
/// Present in subagent sessions: lets the read tool report files to the job board.
|
||||
pub context_reporter: Option<Arc<dyn ContextReporter>>,
|
||||
/// Language-server diagnostics source (edit/write surface errors after a change). `None`
|
||||
/// disables LSP integration.
|
||||
pub diagnostics: Option<Arc<dyn crate::lsp::DiagnosticsSource>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
Reference in New Issue
Block a user