fix(coding-agent): decoupled lsp diagnostics from tool execution

- Offloaded slow LSP diagnostics to a deferred channel in the `write` tool to prevent blocking agent execution for the full 3-second poll window.
- Abstracted deferred diagnostic logic into a reusable `DeferredDiagnostics` class to standardize tracking and deduplication across tools.
- Updated `WriteTool` to support `beginDeferredDiagnosticsForPath` callbacks, surfacing diagnostics as an aside instead of stalling the tool result.
- Added a regression test validating that `write` completes immediately while diagnostics arrive asynchronously.
This commit is contained in:
can1357
2026-07-11 06:12:01 +02:00
parent a9cdaf427a
commit b0d98d9e2b
5 changed files with 150 additions and 78 deletions
+15 -2
View File
@@ -20,6 +20,7 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
import { InternalUrlRouter } from "../internal-urls";
import { parseInternalUrl } from "../internal-urls/parse";
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
import { DeferredDiagnostics } from "../lsp/deferred-diagnostics";
import { getDiagnosticsLedger } from "../lsp/diagnostics-ledger";
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
@@ -323,12 +324,15 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
}
readonly #writethrough: WritethroughCallback;
readonly #deferredDiagnostics: DeferredDiagnostics | undefined;
constructor(private readonly session: ToolSession) {
const enableLsp = session.enableLsp ?? true;
const enableFormat = enableLsp && session.settings.get("lsp.formatOnWrite");
const enableDiagnostics = enableLsp && session.settings.get("lsp.diagnosticsOnWrite");
const dedup = enableDiagnostics && session.settings.get("lsp.diagnosticsDeduplicate");
this.#deferredDiagnostics =
enableDiagnostics && session.queueDeferredDiagnostics ? new DeferredDiagnostics(session, dedup) : undefined;
this.#writethrough = enableLsp
? createLspWritethrough(session.cwd, {
enableFormat,
@@ -931,9 +935,18 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
};
}
const diagnostics = await this.#writethrough(absolutePath, cleanContent, signal, undefined, batchRequest);
const diagnostics = await this.#writethrough(
absolutePath,
cleanContent,
signal,
undefined,
batchRequest,
dst => this.#deferredDiagnostics?.begin(dst),
);
invalidateFsScanAfterWrite(absolutePath);
this.session.bumpFileMutationVersion?.(absolutePath);
if (!this.#deferredDiagnostics || batchRequest?.flush === false) {
this.session.bumpFileMutationVersion?.(absolutePath);
}
const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, cleanContent);
const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);