diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 5a84c2c2b..590bcdb9e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added `--no-lsp` to disable LSP tools, formatting, diagnostics, and warmup for a session + ## [4.2.1] - 2026-01-11 ### Added diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 60c3c1fba..692c0bb0a 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -31,6 +31,7 @@ export interface Args { models?: string[]; tools?: string[]; noTools?: boolean; + noLsp?: boolean; hooks?: string[]; extensions?: string[]; noExtensions?: boolean; @@ -100,6 +101,8 @@ export function parseArgs(args: string[], extensionFlags?: Map s.trim()); } else if (arg === "--no-tools") { result.noTools = true; + } else if (arg === "--no-lsp") { + result.noLsp = true; } else if (arg === "--tools" && i + 1 < args.length) { const toolNames = args[++i].split(",").map((s) => s.trim()); const validTools: string[] = []; @@ -196,6 +199,7 @@ ${chalk.bold("Options:")} --models Comma-separated model patterns for Ctrl+P cycling Supports globs (anthropic/*, *sonnet*) and fuzzy matching --no-tools Disable all built-in tools + --no-lsp Disable LSP tools, formatting, and diagnostics --tools Comma-separated list of tools to enable (default: read,bash,edit,write) Available: read, bash, edit, write, grep, find, ls --thinking Set thinking level: off, minimal, low, medium, high, xhigh diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index 47ad38671..59b68972a 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -153,6 +153,9 @@ export interface CreateAgentSessionOptions { /** Enable MCP server discovery from .mcp.json files. Default: true */ enableMCP?: boolean; + /** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */ + enableLsp?: boolean; + /** Tool names explicitly requested (enables disabled-by-default tools) */ toolNames?: string[]; @@ -667,9 +670,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} let agent: Agent; let session: AgentSession; + const enableLsp = options.enableLsp ?? true; + const toolSession: ToolSession = { cwd, hasUI: options.hasUI ?? false, + enableLsp, eventBus, outputSchema: options.outputSchema, requireCompleteTool: options.requireCompleteTool, @@ -991,7 +997,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // Warm up LSP servers (connects to detected servers) let lspServers: CreateAgentSessionResult["lspServers"]; - if (settingsManager.getLspDiagnosticsOnWrite()) { + if (enableLsp && settingsManager.getLspDiagnosticsOnWrite()) { try { const result = await warmupLspServers(cwd, { onConnecting: (serverNames) => { diff --git a/packages/coding-agent/src/core/tools/edit.ts b/packages/coding-agent/src/core/tools/edit.ts index 2ddf9897e..b771b0c2a 100644 --- a/packages/coding-agent/src/core/tools/edit.ts +++ b/packages/coding-agent/src/core/tools/edit.ts @@ -19,7 +19,7 @@ import { stripBom, } from "./edit-diff"; import type { ToolSession } from "./index"; -import { createLspWritethrough, type FileDiagnosticsResult } from "./lsp/index"; +import { createLspWritethrough, type FileDiagnosticsResult, writethroughNoop } from "./lsp/index"; import { resolveToCwd } from "./path-utils"; import { createToolUIKit, getDiffStats, shortenPath, truncateDiffByHunk } from "./render-utils"; @@ -43,9 +43,12 @@ export interface EditToolDetails { export function createEditTool(session: ToolSession): AgentTool { const allowFuzzy = session.settings?.getEditFuzzyMatch() ?? true; - const enableDiagnostics = session.settings?.getLspDiagnosticsOnEdit() ?? false; - const enableFormat = session.settings?.getLspFormatOnWrite() ?? true; - const writethrough = createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }); + const enableLsp = session.enableLsp ?? true; + const enableDiagnostics = enableLsp ? (session.settings?.getLspDiagnosticsOnEdit() ?? false) : false; + const enableFormat = enableLsp ? (session.settings?.getLspFormatOnWrite() ?? true) : false; + const writethrough = enableLsp + ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }) + : writethroughNoop; return { name: "edit", label: "Edit", diff --git a/packages/coding-agent/src/core/tools/index.test.ts b/packages/coding-agent/src/core/tools/index.test.ts index e1845b5b2..fbd6cd61e 100644 --- a/packages/coding-agent/src/core/tools/index.test.ts +++ b/packages/coding-agent/src/core/tools/index.test.ts @@ -34,6 +34,22 @@ describe("createTools", () => { expect(names).toContain("web_search"); }); + it("excludes lsp tool when session disables LSP", async () => { + const session = createTestSession({ enableLsp: false }); + const tools = await createTools(session, ["read", "lsp", "write"]); + const names = tools.map((t) => t.name); + + expect(names).toEqual(["read", "write"]); + }); + + it("excludes lsp tool when disabled", async () => { + const session = createTestSession({ enableLsp: false }); + const tools = await createTools(session); + const names = tools.map((t) => t.name); + + expect(names).not.toContain("lsp"); + }); + it("respects requested tool subset", async () => { const session = createTestSession(); const tools = await createTools(session, ["read", "write"]); diff --git a/packages/coding-agent/src/core/tools/index.ts b/packages/coding-agent/src/core/tools/index.ts index 8416d584b..7efb5814e 100644 --- a/packages/coding-agent/src/core/tools/index.ts +++ b/packages/coding-agent/src/core/tools/index.ts @@ -92,6 +92,8 @@ export interface ToolSession { cwd: string; /** Whether UI is available */ hasUI: boolean; + /** Whether LSP integrations are enabled */ + enableLsp?: boolean; /** Event bus for tool/extension communication */ eventBus?: EventBus; /** Output schema for structured completion (subagents) */ @@ -154,23 +156,28 @@ export type ToolName = keyof typeof BUILTIN_TOOLS; */ export async function createTools(session: ToolSession, toolNames?: string[]): Promise { const includeComplete = session.requireCompleteTool === true; + const enableLsp = session.enableLsp ?? true; const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined; const allTools: Record = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS }; + const isToolAllowed = (name: string) => (name === "lsp" ? enableLsp : true); if (includeComplete && requestedTools && !requestedTools.includes("complete")) { requestedTools.push("complete"); } - const entries = requestedTools - ? requestedTools.filter((name) => name in allTools).map((name) => [name, allTools[name]] as const) - : [ - ...Object.entries(BUILTIN_TOOLS), - ...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : []), - ]; + const filteredRequestedTools = requestedTools?.filter((name) => name in allTools && isToolAllowed(name)); + + const entries = + filteredRequestedTools !== undefined + ? filteredRequestedTools.map((name) => [name, allTools[name]] as const) + : [ + ...Object.entries(BUILTIN_TOOLS).filter(([name]) => isToolAllowed(name)), + ...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : []), + ]; const results = await Promise.all(entries.map(([, factory]) => factory(session))); const tools = results.filter((t): t is Tool => t !== null); - if (requestedTools) { - const allowed = new Set(requestedTools); + if (filteredRequestedTools !== undefined) { + const allowed = new Set(filteredRequestedTools); return tools.filter((tool) => allowed.has(tool.name)); } diff --git a/packages/coding-agent/src/core/tools/lsp/index.ts b/packages/coding-agent/src/core/tools/lsp/index.ts index 94276c7d8..ab09650a0 100644 --- a/packages/coding-agent/src/core/tools/lsp/index.ts +++ b/packages/coding-agent/src/core/tools/lsp/index.ts @@ -734,7 +734,10 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions } /** Create an LSP tool */ -export function createLspTool(session: ToolSession): AgentTool { +export function createLspTool(session: ToolSession): AgentTool | null { + if (session.enableLsp === false) { + return null; + } return { name: "lsp", label: "LSP", diff --git a/packages/coding-agent/src/core/tools/task/executor.ts b/packages/coding-agent/src/core/tools/task/executor.ts index fcb6c4bdb..6cf8ba2a8 100644 --- a/packages/coding-agent/src/core/tools/task/executor.ts +++ b/packages/coding-agent/src/core/tools/task/executor.ts @@ -31,6 +31,7 @@ export interface ExecutorOptions { context?: string; modelOverride?: string; outputSchema?: unknown; + enableLsp?: boolean; signal?: AbortSignal; onProgress?: (progress: AgentProgress) => void; sessionFile?: string | null; @@ -137,7 +138,8 @@ function getUsageTokens(usage: unknown): number { * Run a single agent in a worker. */ export async function runSubprocess(options: ExecutorOptions): Promise { - const { cwd, agent, task, index, taskId, context, modelOverride, outputSchema, signal, onProgress } = options; + const { cwd, agent, task, index, taskId, context, modelOverride, outputSchema, enableLsp, signal, onProgress } = + options; const startTime = Date.now(); // Initialize progress @@ -535,6 +537,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise { diff --git a/packages/coding-agent/src/core/tools/task/worker-protocol.ts b/packages/coding-agent/src/core/tools/task/worker-protocol.ts index 9a555b104..cbd61ec17 100644 --- a/packages/coding-agent/src/core/tools/task/worker-protocol.ts +++ b/packages/coding-agent/src/core/tools/task/worker-protocol.ts @@ -7,6 +7,7 @@ export interface SubagentWorkerStartPayload { model?: string; toolNames?: string[]; outputSchema?: unknown; + enableLsp?: boolean; sessionFile?: string | null; spawnsEnv?: string; } diff --git a/packages/coding-agent/src/core/tools/task/worker.ts b/packages/coding-agent/src/core/tools/task/worker.ts index 8eda728aa..b37d97b44 100644 --- a/packages/coding-agent/src/core/tools/task/worker.ts +++ b/packages/coding-agent/src/core/tools/task/worker.ts @@ -180,6 +180,7 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload): hasUI: false, // Pass spawn restrictions to nested tasks spawns: payload.spawnsEnv, + enableLsp: payload.enableLsp ?? true, }); runState.session = session; diff --git a/packages/coding-agent/src/core/tools/write.ts b/packages/coding-agent/src/core/tools/write.ts index f1e804ce0..3fd9a07ce 100644 --- a/packages/coding-agent/src/core/tools/write.ts +++ b/packages/coding-agent/src/core/tools/write.ts @@ -8,7 +8,7 @@ import type { RenderResultOptions } from "../custom-tools/types"; import { renderPromptTemplate } from "../prompt-templates"; import type { ToolSession } from "../sdk"; import { untilAborted } from "../utils"; -import { createLspWritethrough, type FileDiagnosticsResult } from "./lsp/index"; +import { createLspWritethrough, type FileDiagnosticsResult, writethroughNoop } from "./lsp/index"; import { resolveToCwd } from "./path-utils"; import { formatDiagnostics, replaceTabs, shortenPath } from "./render-utils"; @@ -23,9 +23,12 @@ export interface WriteToolDetails { } export function createWriteTool(session: ToolSession): AgentTool { - const enableFormat = session.settings?.getLspFormatOnWrite() ?? true; - const enableDiagnostics = session.settings?.getLspDiagnosticsOnWrite() ?? true; - const writethrough = createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }); + const enableLsp = session.enableLsp ?? true; + const enableFormat = enableLsp ? (session.settings?.getLspFormatOnWrite() ?? true) : false; + const enableDiagnostics = enableLsp ? (session.settings?.getLspDiagnosticsOnWrite() ?? true) : false; + const writethrough = enableLsp + ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }) + : writethroughNoop; return { name: "write", label: "Write", diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 8adee3e30..6f2547f42 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -359,6 +359,10 @@ async function buildSessionOptions( options.toolNames = parsed.tools; } + if (parsed.noLsp) { + options.enableLsp = false; + } + // Skills if (parsed.noSkills) { options.skills = []; diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts index 7287e5066..3b78bc94b 100644 --- a/packages/coding-agent/test/args.test.ts +++ b/packages/coding-agent/test/args.test.ts @@ -178,6 +178,13 @@ describe("parseArgs", () => { }); }); + describe("--no-lsp flag", () => { + test("parses --no-lsp flag", () => { + const result = parseArgs(["--no-lsp"]); + expect(result.noLsp).toBe(true); + }); + }); + describe("messages and file args", () => { test("parses plain text messages", () => { const result = parseArgs(["hello", "world"]);