From 0f00599e55947922db109215b03e98661374d4e0 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Tue, 19 May 2026 21:36:20 -0400 Subject: [PATCH] LSP Phase 8: feature gate + Channels struct refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related changes — splitting into separate commits would have required disentangling overlapping diffs in the same files. ## Refactor: build_channels returns Channels struct 10-tuple → `struct Channels { ... }`. Same fields, just named. build_channels returns Channels; main.rs destructures the same locals it already used. No behavior change, but the call site is no longer a positional 10-tuple unpacking that breaks every time we add a slot. Doc-noted in main.rs: `compile_lsp_commands` currently ignores the `extensions` field on per-server overrides. The claimed-extensions list lives in the static `builtin_servers()` registry; making it instance-overridable requires plumbing a per-session server set down through LspManager. Follow-up; users who need new extensions today must edit `server.rs`. ## Phase 8: feature gate Adds `feature = "lsp"` to Cargo.toml's default set. With it off: - `lsp` module is not compiled; `lsp-types` dep stays optional + skipped. - `Channels.lsp_manager` field gated out (also gated out of destructure in main.rs). - `build_agent` / `build_agent_inner` / `run_interactive` drop their `lsp_manager` arg via `#[cfg(feature = "lsp")]` on the param. - Read/Write/Edit tools drop their `lsp_manager` field + integration call. - LspTool not registered; `--no-lsp` CLI flag not exposed. - `LspConfig` / `LspServerConfig` types gated; `cfg.lsp` field gated. - ACP / slash sub-rebuilds / plan-switch all use `#[cfg(feature = "lsp")]` None args. Verified: - `cargo build --no-default-features --features 'loop git-worktree mcp'` → clean (no lsp deps pulled in). - `cargo build` (default) → clean, all 4 LSP servers wired. - `cargo test --no-default-features --features 'loop git-worktree mcp'` → 305 passing (LSP module's 121 tests correctly excluded). - `cargo test` (default) → 426 passing. --- Cargo.toml | 5 ++- src/agent/builder.rs | 6 ++- src/agent/tools/edit.rs | 29 +++++++++----- src/agent/tools/mod.rs | 2 + src/agent/tools/read.rs | 8 +++- src/agent/tools/write.rs | 16 +++++++- src/cli.rs | 2 + src/config/mod.rs | 6 ++- src/extras/acp/mod.rs | 1 + src/main.rs | 86 +++++++++++++++++++++++++--------------- src/provider.rs | 3 +- src/ui/mod.rs | 5 ++- src/ui/slash.rs | 7 ++++ 13 files changed, 125 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f939a151..8a2e2505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" keywords = ["ai", "cli", "dev"] [features] -default = ['loop', 'git-worktree', 'mcp'] +default = ['loop', 'git-worktree', 'mcp', 'lsp'] loop = [] git-worktree = [] mcp = [ @@ -26,6 +26,7 @@ semantic-ts = ["semantic", "dep:tree-sitter-typescript"] semantic-python = ["semantic", "dep:tree-sitter-python"] semantic-bash = ["semantic", "dep:tree-sitter-bash"] plugin = ["dep:janetrs"] +lsp = ["dep:lsp-types"] [dependencies] rig = { version = "0.37", features = ["rmcp"] } @@ -66,7 +67,7 @@ streaming-iterator = { version = "0.1", optional = true } janetrs = { version = "0.8", optional = true } html2text = "0.17" indexmap = "2" -lsp-types = "0.97" +lsp-types = { version = "0.97", optional = true } [dev-dependencies] tokio = { version = "1", features = ["test-util"] } diff --git a/src/agent/builder.rs b/src/agent/builder.rs index c3990b70..7c7a8a50 100644 --- a/src/agent/builder.rs +++ b/src/agent/builder.rs @@ -37,7 +37,7 @@ pub async fn build_agent_inner( question_tx: Option, plan_tx: Option, bg_store: Option, - lsp_manager: Option>, + #[cfg(feature = "lsp")] lsp_manager: Option>, sandbox: Sandbox, parent_model: Option, #[cfg(feature = "mcp")] mcp_manager: Option<&McpClientManager>, @@ -141,6 +141,7 @@ pub async fn build_agent_inner( permission.clone(), ask_tx.clone(), cache.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), )), Box::new(tools::WriteTool::with_cache( @@ -148,6 +149,7 @@ pub async fn build_agent_inner( ask_tx.clone(), plan_file.clone(), cache.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), )), Box::new(tools::EditTool::with_cache( @@ -155,6 +157,7 @@ pub async fn build_agent_inner( ask_tx.clone(), plan_file.clone(), cache.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), )), Box::new(tools::BashTool::with_cache( @@ -262,6 +265,7 @@ pub async fn build_agent_inner( builder = builder.tools(vec![task_tool, status_tool]); } + #[cfg(feature = "lsp")] if let Some(manager) = &lsp_manager { let cwd = std::env::current_dir().unwrap_or_else(|_| ".".into()); let lsp_tool = Box::new(tools::LspTool::new( diff --git a/src/agent/tools/edit.rs b/src/agent/tools/edit.rs index dc8f5129..c6f1540e 100644 --- a/src/agent/tools/edit.rs +++ b/src/agent/tools/edit.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +#[cfg(feature = "lsp")] use std::sync::Arc; use rig::completion::ToolDefinition; @@ -6,6 +7,7 @@ use rig::tool::Tool; use crate::agent::tools::cache::ToolCache; use crate::agent::tools::{AskSender, EditArgs, PermCheck, ToolError, check_perm_path}; +#[cfg(feature = "lsp")] use crate::lsp::manager::LspManager; pub struct EditTool { @@ -16,6 +18,7 @@ pub struct EditTool { /// When set, the tool touches the edited file on the LSP server and /// appends any diagnostic block to its output. `None` reproduces the /// pre-LSP behaviour. + #[cfg(feature = "lsp")] lsp_manager: Option>, } @@ -31,6 +34,7 @@ impl EditTool { ask_tx, plan_file, cache: None, + #[cfg(feature = "lsp")] lsp_manager: None, } } @@ -40,13 +44,14 @@ impl EditTool { ask_tx: Option, plan_file: Option, cache: ToolCache, - lsp_manager: Option>, + #[cfg(feature = "lsp")] lsp_manager: Option>, ) -> Self { EditTool { permission, ask_tx, plan_file, cache: Some(cache), + #[cfg(feature = "lsp")] lsp_manager, } } @@ -212,6 +217,7 @@ impl Tool for EditTool { new_content }; + #[cfg(feature = "lsp")] let write_at = std::time::Instant::now(); tokio::fs::write(&args.path, &output).await?; // File mutated → invalidate cached reads/greps/listings for this turn. @@ -236,15 +242,18 @@ impl Tool for EditTool { )); } - let path = std::path::Path::new(&args.path); - result.push_str( - &crate::agent::tools::write::append_lsp_block( - self.lsp_manager.as_ref(), - path, - write_at, - ) - .await, - ); + #[cfg(feature = "lsp")] + { + let path = std::path::Path::new(&args.path); + result.push_str( + &crate::agent::tools::write::append_lsp_block( + self.lsp_manager.as_ref(), + path, + write_at, + ) + .await, + ); + } Ok(result) } } diff --git a/src/agent/tools/mod.rs b/src/agent/tools/mod.rs index a1e2be58..5ad816a7 100644 --- a/src/agent/tools/mod.rs +++ b/src/agent/tools/mod.rs @@ -7,6 +7,7 @@ mod find_files; mod glob; mod grep; mod list_dir; +#[cfg(feature = "lsp")] mod lsp; mod memory; pub(crate) mod plan; @@ -30,6 +31,7 @@ pub use find_files::FindFilesTool; pub use glob::GlobTool; pub use grep::GrepTool; pub use list_dir::ListDirTool; +#[cfg(feature = "lsp")] pub use lsp::LspTool; pub use memory::MemoryTool; pub use plan::{PlanEnterTool, PlanExitTool}; diff --git a/src/agent/tools/read.rs b/src/agent/tools/read.rs index 2363e495..08cef63d 100644 --- a/src/agent/tools/read.rs +++ b/src/agent/tools/read.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "lsp")] use std::sync::Arc; use rig::completion::ToolDefinition; @@ -5,6 +6,7 @@ use rig::tool::Tool; use crate::agent::tools::cache::ToolCache; use crate::agent::tools::{AskSender, PermCheck, ReadArgs, ToolError, check_perm_path}; +#[cfg(feature = "lsp")] use crate::lsp::manager::{LspManager, TouchMode}; pub struct ReadTool { @@ -14,6 +16,7 @@ pub struct ReadTool { /// When set, the tool fires off a `touch_file` to warm the LSP server /// so subsequent edits surface diagnostics quickly. Fire-and-forget: /// the read tool does not wait or surface diagnostics in its output. + #[cfg(feature = "lsp")] pub lsp_manager: Option>, } @@ -24,6 +27,7 @@ impl ReadTool { permission, ask_tx, cache: None, + #[cfg(feature = "lsp")] lsp_manager: None, } } @@ -32,12 +36,13 @@ impl ReadTool { permission: Option, ask_tx: Option, cache: ToolCache, - lsp_manager: Option>, + #[cfg(feature = "lsp")] lsp_manager: Option>, ) -> Self { ReadTool { permission, ask_tx, cache: Some(cache), + #[cfg(feature = "lsp")] lsp_manager, } } @@ -122,6 +127,7 @@ impl Tool for ReadTool { // Fire-and-forget LSP warmup so the server already has the file // open by the time the agent edits it (and we can wait_for_push // quickly). No diagnostic surfacing on read. + #[cfg(feature = "lsp")] if let Some(manager) = self.lsp_manager.clone() { let path = std::path::PathBuf::from(&args.path); tokio::spawn(async move { diff --git a/src/agent/tools/write.rs b/src/agent/tools/write.rs index afeec9e9..517fff67 100644 --- a/src/agent/tools/write.rs +++ b/src/agent/tools/write.rs @@ -1,5 +1,7 @@ use std::path::{Path, PathBuf}; +#[cfg(feature = "lsp")] use std::sync::Arc; +#[cfg(feature = "lsp")] use std::time::{Duration, Instant}; use rig::completion::ToolDefinition; @@ -7,12 +9,15 @@ use rig::tool::Tool; use crate::agent::tools::cache::ToolCache; use crate::agent::tools::{AskSender, PermCheck, ToolError, WriteArgs, check_perm_path}; +#[cfg(feature = "lsp")] use crate::lsp::diagnostic; +#[cfg(feature = "lsp")] use crate::lsp::manager::{LspManager, TouchMode}; /// How long to wait for the LSP server to publish fresh diagnostics after /// a write. Matches opencode's `DIAGNOSTICS_FULL_WAIT_TIMEOUT_MS`. Bounded /// so a stuck server doesn't hold up the agent's turn. +#[cfg(feature = "lsp")] const DIAGNOSTIC_WAIT: Duration = Duration::from_secs(10); pub struct WriteTool { @@ -23,6 +28,7 @@ pub struct WriteTool { /// When set, the tool touches the file on the LSP server after writing /// and appends any resulting diagnostic block to its output. `None` /// reproduces the pre-LSP behaviour exactly. + #[cfg(feature = "lsp")] lsp_manager: Option>, } @@ -38,6 +44,7 @@ impl WriteTool { ask_tx, plan_file, cache: None, + #[cfg(feature = "lsp")] lsp_manager: None, } } @@ -47,13 +54,14 @@ impl WriteTool { ask_tx: Option, plan_file: Option, cache: ToolCache, - lsp_manager: Option>, + #[cfg(feature = "lsp")] lsp_manager: Option>, ) -> Self { WriteTool { permission, ask_tx, plan_file, cache: Some(cache), + #[cfg(feature = "lsp")] lsp_manager, } } @@ -106,6 +114,7 @@ impl Tool for WriteTool { tokio::fs::create_dir_all(parent).await?; } let bytes = args.content.len(); + #[cfg(feature = "lsp")] let write_at = Instant::now(); tokio::fs::write(path, &args.content).await?; // File mutated → invalidate cached reads/greps/listings for this turn. @@ -113,7 +122,9 @@ impl Tool for WriteTool { cache.clear(); } + #[allow(unused_mut)] let mut output = format!("Written {} bytes to {}", bytes, args.path); + #[cfg(feature = "lsp")] output.push_str(&append_lsp_block(self.lsp_manager.as_ref(), path, write_at).await); Ok(output) } @@ -124,6 +135,7 @@ impl Tool for WriteTool { /// Errors during touch/wait are intentionally swallowed — diagnostic /// surfacing is a side-effect; the write tool's primary contract is /// "wrote the file". +#[cfg(feature = "lsp")] pub(crate) async fn append_lsp_block( manager: Option<&Arc>, path: &Path, @@ -145,7 +157,7 @@ pub(crate) async fn append_lsp_block( diagnostic::build_report_block(path, &diagnostics) } -#[cfg(test)] +#[cfg(all(test, feature = "lsp"))] mod tests { use super::*; use crate::agent::tools::cache::ToolCache; diff --git a/src/cli.rs b/src/cli.rs index 872f3cda..de46b905 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -48,6 +48,7 @@ pub struct Cli { #[arg(long = "no-tools", help = "Disable all tools")] pub no_tools: bool, + #[cfg(feature = "lsp")] #[arg( long = "no-lsp", help = "Disable LSP integration (no diagnostics on edit/write, no `lsp` agent tool)" @@ -167,6 +168,7 @@ impl Cli { self.no_tools || cfg.no_tools.unwrap_or(false) } + #[cfg(feature = "lsp")] pub fn resolve_lsp_enabled(&self, cfg: &config::Config) -> bool { if self.no_lsp || self.no_tools { return false; diff --git a/src/config/mod.rs b/src/config/mod.rs index 5c75b7b0..927cc693 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -32,6 +32,7 @@ pub struct ToolsConfig { /// - `{ "disabled": true }` to turn off a built-in server entirely. /// - any subset of `{ command, extensions, env, initialization, disabled }` /// to override pieces of the default. +#[cfg(feature = "lsp")] #[derive(Debug, Default, Clone, Deserialize)] #[serde(default)] pub struct LspServerConfig { @@ -46,6 +47,7 @@ pub struct LspServerConfig { /// `lsp = false` → disable LSP entirely. /// `lsp = { server-id = { … } }` → enable defaults, overriding the named /// servers with the provided config. +#[cfg(feature = "lsp")] #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum LspConfig { @@ -53,6 +55,7 @@ pub enum LspConfig { Servers(HashMap), } +#[cfg(feature = "lsp")] impl LspConfig { /// `true` when LSP should be on. Defaults to enabled. pub fn is_enabled(&self) -> bool { @@ -102,6 +105,7 @@ pub struct Config { pub tool_result_max_chars: Option, pub default_prompt: Option, pub tools: Option, + #[cfg(feature = "lsp")] pub lsp: Option, #[cfg(feature = "mcp")] pub mcp_servers: Option>, @@ -194,7 +198,7 @@ pub fn load() -> Config { cfg } -#[cfg(test)] +#[cfg(all(test, feature = "lsp"))] mod tests { use super::*; diff --git a/src/extras/acp/mod.rs b/src/extras/acp/mod.rs index 55d66ee5..f0840efc 100644 --- a/src/extras/acp/mod.rs +++ b/src/extras/acp/mod.rs @@ -170,6 +170,7 @@ async fn run_prompt( None, None, None, + #[cfg(feature = "lsp")] None, sandbox, #[cfg(feature = "mcp")] diff --git a/src/main.rs b/src/main.rs index 5d81584e..261a1808 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod context; mod event; mod extras; mod image_util; +#[cfg(feature = "lsp")] mod lsp; mod permission; mod plugin; @@ -27,12 +28,34 @@ use session::MessageRole; use crate::agent::tools::background::{BackgroundStore, LifecycleReceiver}; use crate::agent::tools::plan::{PlanSwitchReceiver, PlanSwitchSender}; use crate::agent::tools::question::{QuestionReceiver, QuestionSender}; +#[cfg(feature = "lsp")] use crate::lsp::manager::LspManager; +#[cfg(feature = "lsp")] use crate::lsp::spawn::{ProcessCommand, ProcessSpawner}; -use crate::permission::ask::AskSender; +use crate::permission::ask::{AskReceiver, AskSender}; use crate::permission::checker::{PermCheck, PermissionChecker}; use crate::permission::{PermissionConfig, SecurityMode}; +/// Per-session channels and shared state, threaded through the agent build +/// chain in place of a ten-position tuple. Cloneable senders + shared state +/// (`bg_store`, `lsp_manager`, `permission`) survive being moved through +/// `build_agent`; the receivers (`ask_rx`, `question_rx`, `plan_rx`, +/// `lifecycle_rx`) are unique-owner and end up consumed by the UI loop. +#[derive(Default)] +struct Channels { + permission: Option, + ask_tx: Option, + ask_rx: Option, + question_tx: Option, + question_rx: Option, + plan_tx: Option, + plan_rx: Option, + bg_store: Option, + lifecycle_rx: Option, + #[cfg(feature = "lsp")] + lsp_manager: Option>, +} + fn resolve_mode(cli: &cli::Cli, cfg: &config::Config) -> SecurityMode { if cli.yolo || cfg.yolo.unwrap_or(false) { SecurityMode::Yolo @@ -52,24 +75,9 @@ fn resolve_mode(cli: &cli::Cli, cfg: &config::Config) -> SecurityMode { } } -fn build_channels( - cli: &cli::Cli, - cfg: &config::Config, -) -> ( - Option, - Option, - Option>, - Option, - Option, - Option, - Option, - Option, - Option, - Option>, -) { - let no_tools = cli.resolve_no_tools(cfg); - if no_tools { - return (None, None, None, None, None, None, None, None, None, None); +fn build_channels(cli: &cli::Cli, cfg: &config::Config) -> Channels { + if cli.resolve_no_tools(cfg) { + return Channels::default(); } let perm_config: PermissionConfig = cfg @@ -88,6 +96,7 @@ fn build_channels( let (lifecycle_tx, lifecycle_rx) = tokio::sync::mpsc::unbounded_channel(); let bg_store = BackgroundStore::with_ui_sink(lifecycle_tx); + #[cfg(feature = "lsp")] let lsp_manager = if cli.resolve_lsp_enabled(cfg) { let worktree = std::env::current_dir().unwrap_or_else(|_| ".".into()); let commands = compile_lsp_commands(cfg); @@ -97,23 +106,31 @@ fn build_channels( None }; - ( - Some(perm), - Some(ask_tx), - Some(ask_rx), - Some(question_tx), - Some(question_rx), - Some(plan_tx), - Some(plan_rx), - Some(bg_store), - Some(lifecycle_rx), + Channels { + permission: Some(perm), + ask_tx: Some(ask_tx), + ask_rx: Some(ask_rx), + question_tx: Some(question_tx), + question_rx: Some(question_rx), + plan_tx: Some(plan_tx), + plan_rx: Some(plan_rx), + bg_store: Some(bg_store), + lifecycle_rx: Some(lifecycle_rx), + #[cfg(feature = "lsp")] lsp_manager, - ) + } } /// Compile the spawn commands by starting from `ProcessSpawner::default_commands` /// and applying per-server overrides from user config. A `disabled = true` /// override removes the entry; any non-empty `command` replaces the default. +/// +/// Known limitation: `extensions` on the override is currently ignored. The +/// claimed-extensions list lives in the static `builtin_servers()` registry +/// (`lsp/server.rs`) — making it instance-overridable requires plumbing a +/// per-session server set down through `LspManager`. Follow-up; users who +/// need new extensions today must edit `server.rs`. +#[cfg(feature = "lsp")] fn compile_lsp_commands(cfg: &config::Config) -> std::collections::HashMap { let mut commands = ProcessSpawner::default_commands(); let Some(lsp_cfg) = &cfg.lsp else { @@ -334,7 +351,7 @@ async fn main() -> anyhow::Result<()> { } let sandbox = sandbox::Sandbox::new(cli.resolve_sandbox(&cfg)); - let ( + let Channels { permission, ask_tx, ask_rx, @@ -344,8 +361,9 @@ async fn main() -> anyhow::Result<()> { plan_rx, bg_store, lifecycle_rx, + #[cfg(feature = "lsp")] lsp_manager, - ) = build_channels(&cli, &cfg); + } = build_channels(&cli, &cfg); if let Some(perm) = &permission { let allowlist: Vec<(String, String)> = session @@ -371,6 +389,7 @@ async fn main() -> anyhow::Result<()> { question_tx.clone(), plan_tx.clone(), bg_store.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), sandbox.clone(), #[cfg(feature = "mcp")] @@ -402,6 +421,7 @@ async fn main() -> anyhow::Result<()> { question_tx.clone(), plan_tx.clone(), bg_store.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), sandbox.clone(), #[cfg(feature = "mcp")] @@ -423,6 +443,7 @@ async fn main() -> anyhow::Result<()> { question_tx.clone(), plan_tx.clone(), bg_store.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), sandbox.clone(), #[cfg(feature = "mcp")] @@ -482,6 +503,7 @@ async fn main() -> anyhow::Result<()> { plan_tx, bg_store, lifecycle_rx, + #[cfg(feature = "lsp")] lsp_manager, sandbox, #[cfg(feature = "mcp")] diff --git a/src/provider.rs b/src/provider.rs index f557385b..981ad4f0 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -439,7 +439,7 @@ pub async fn build_agent( question_tx: Option, plan_tx: Option, bg_store: Option, - lsp_manager: Option>, + #[cfg(feature = "lsp")] lsp_manager: Option>, sandbox: Sandbox, #[cfg(feature = "mcp")] mcp_manager: Option<&McpClientManager>, #[cfg(feature = "semantic")] semantic_manager: Option<&SemanticManager>, @@ -458,6 +458,7 @@ pub async fn build_agent( question_tx.clone(), plan_tx.clone(), bg_store.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), sandbox.clone(), Some(parent_model.clone()), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 98451a22..58a50cbf 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -153,7 +153,7 @@ pub async fn run_interactive( plan_tx: Option, bg_store: Option, mut lifecycle_rx: Option, - lsp_manager: Option>, + #[cfg(feature = "lsp")] lsp_manager: Option>, sandbox: Sandbox, #[cfg(feature = "mcp")] mcp_manager: Option<&McpClientManager>, #[cfg(feature = "semantic")] semantic_manager: Option<&SemanticManager>, @@ -805,6 +805,7 @@ pub async fn run_interactive( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] mcp_manager, @@ -1282,6 +1283,7 @@ pub async fn run_interactive( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] mcp_manager, @@ -1745,6 +1747,7 @@ pub async fn run_interactive( question_tx.clone(), plan_tx.clone(), bg_store.clone(), + #[cfg(feature = "lsp")] lsp_manager.clone(), sandbox.clone(), #[cfg(feature = "mcp")] diff --git a/src/ui/slash.rs b/src/ui/slash.rs index db07fef5..20bb3f0e 100644 --- a/src/ui/slash.rs +++ b/src/ui/slash.rs @@ -127,6 +127,7 @@ pub async fn handle_compress( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] @@ -189,6 +190,7 @@ pub async fn handle_slash( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] @@ -508,6 +510,7 @@ pub async fn handle_slash( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] @@ -619,6 +622,7 @@ pub async fn handle_slash( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] @@ -644,6 +648,7 @@ pub async fn handle_slash( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] @@ -696,6 +701,7 @@ pub async fn handle_slash( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")] @@ -849,6 +855,7 @@ pub async fn handle_slash( None, None, bg_store.clone(), + #[cfg(feature = "lsp")] None, sandbox.clone(), #[cfg(feature = "mcp")]