@herbertgao/pi-extensions 2026.9.1 → 2026.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@narumitw/pi-btw/README.md +57 -141
- package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
- package/node_modules/@narumitw/pi-btw/package.json +2 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
- package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
- package/node_modules/pi-lens/CHANGELOG.md +84 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -1
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -6
- package/node_modules/pi-lens/dist/clients/bootstrap.js +4 -4
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -5
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +1 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +1 -6
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +2 -2
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
- package/node_modules/pi-lens/dist/clients/config-core/index.js +11 -8
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +1 -1
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +2 -9
- package/node_modules/pi-lens/dist/clients/config-core/records.js +1 -1
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +3 -5
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +1 -1
- package/node_modules/pi-lens/dist/clients/config-resolve.js +4 -4
- package/node_modules/pi-lens/dist/clients/config-schema.js +39 -2
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -4
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -26
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +1 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +1 -64
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -0
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
- package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
- package/node_modules/pi-lens/dist/clients/file-time.js +1 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-service.js +0 -1
- package/node_modules/pi-lens/dist/clients/formatters.js +110 -3
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +3 -4
- package/node_modules/pi-lens/dist/clients/git-guard.js +5 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
- package/node_modules/pi-lens/dist/clients/installer/index.js +220 -9
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +6 -6
- package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
- package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +5 -7
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +19 -0
- package/node_modules/pi-lens/dist/clients/lens-engine.js +3 -4
- package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +18 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -36
- package/node_modules/pi-lens/dist/clients/lsp/client.js +4 -3
- package/node_modules/pi-lens/dist/clients/lsp/config.js +1 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +3 -3
- package/node_modules/pi-lens/dist/clients/lsp/index.js +216 -25
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -8
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -2
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +5 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +1 -1
- package/node_modules/pi-lens/dist/clients/module-report.js +1 -1
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +3 -3
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +3 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -4
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +9 -9
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +2 -6
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +23 -2
- package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/pipeline.js +2 -2
- package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +46 -1
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
- package/node_modules/pi-lens/dist/clients/python-provenance.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +5 -2
- package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +3 -5
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +1 -1
- package/node_modules/pi-lens/dist/clients/runtime-session.js +45 -10
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +11 -1
- package/node_modules/pi-lens/dist/clients/sanitize.js +0 -11
- package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
- package/node_modules/pi-lens/dist/clients/sgconfig.js +1 -5
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
- package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +77 -72
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +1 -52
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
- package/node_modules/pi-lens/dist/clients/user-notify.js +0 -4
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/word-index-store.js +3 -7
- package/node_modules/pi-lens/dist/clients/word-index.js +9 -16
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +1 -1
- package/node_modules/pi-lens/dist/index.js +1062 -538
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +1 -1
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +1 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +1 -1
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +37 -6
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
- package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
- package/node_modules/pi-lens/docs/globalconfig.md +20 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +6 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -1
- package/node_modules/pi-lens/docs/release-qa-baseline.md +12 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
- package/node_modules/pi-lens/docs/word-index.md +1 -1
- package/node_modules/pi-lens/package.json +6 -15
- package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +1 -1
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
- package/package.json +3 -3
|
@@ -25,6 +25,7 @@ import { isLspSpawnAllowedByTrust, assertInstallAllowed, projectTrustDenialReaso
|
|
|
25
25
|
import { shouldPreferPullOnlyDiagnostics } from "../lsp-budget.js";
|
|
26
26
|
import { sampleProcessTreeCpuPercent } from "../resource-sampler.js";
|
|
27
27
|
import { bounded, withDeadline, withTimeout } from "../deadline-utils.js";
|
|
28
|
+
import { HOOK_WALL_BUDGET_MS } from "../hook-budgets.js";
|
|
28
29
|
import { getAmbientAbortSignal } from "../safe-spawn.js";
|
|
29
30
|
import { abortDeferredLspWork } from "../deferred-lsp-work.js";
|
|
30
31
|
import { acquireWorkspaceSweepHold, clearWorkspaceSweepHoldForSessionStart, } from "./workspace-sweep-hold.js";
|
|
@@ -646,6 +647,12 @@ export class LSPService {
|
|
|
646
647
|
/** Server/root pairs that already emitted unavailable for the current occurrence. */
|
|
647
648
|
unavailableLogged = new Set();
|
|
648
649
|
optionalDisabled = new Set();
|
|
650
|
+
/**
|
|
651
|
+
* One first-contact diagnostic wait per custom server in this service
|
|
652
|
+
* generation. Concurrent touches share the probe; a hook cutoff never arms
|
|
653
|
+
* the navigation-only latch, so the next touch can try again.
|
|
654
|
+
*/
|
|
655
|
+
firstContactWaits = new Map();
|
|
649
656
|
/**
|
|
650
657
|
* #1934 review F1: what the last COMPLETED `spawnClient` call for a
|
|
651
658
|
* (server, root) key decided, written by that call at every point it
|
|
@@ -833,6 +840,8 @@ export class LSPService {
|
|
|
833
840
|
clientSpawnGate = Promise.resolve();
|
|
834
841
|
/** True after shutdown() has been called; blocks new operations */
|
|
835
842
|
isDestroyed = false;
|
|
843
|
+
/** Aborts bounded in-flight service work when this generation is retired. */
|
|
844
|
+
shutdownController = new AbortController();
|
|
836
845
|
/**
|
|
837
846
|
* #850: teardown completion for every singleton generation retired before
|
|
838
847
|
* this service was published. Only replacement services receive one; direct
|
|
@@ -852,6 +861,8 @@ export class LSPService {
|
|
|
852
861
|
clientSpawnedAt: new Map(),
|
|
853
862
|
demonstratedReady: new Set(),
|
|
854
863
|
demonstratedCold: new Set(),
|
|
864
|
+
diagnosticsPublished: new Set(),
|
|
865
|
+
diagnosticsUnsupported: new Set(),
|
|
855
866
|
};
|
|
856
867
|
}
|
|
857
868
|
/**
|
|
@@ -3117,6 +3128,12 @@ export class LSPService {
|
|
|
3117
3128
|
}
|
|
3118
3129
|
const override = getServerInitOverride(server.id, filePath);
|
|
3119
3130
|
const mergedInit = mergeInitializationOptions(spawned.initialization, override?.initializationOptions);
|
|
3131
|
+
// A replacement process is a new first-contact identity even when the
|
|
3132
|
+
// configured server id is unchanged. Re-arm both capability latches before
|
|
3133
|
+
// the replacement can publish a snapshot.
|
|
3134
|
+
this.state.diagnosticsPublished.delete(server.id);
|
|
3135
|
+
this.state.diagnosticsUnsupported.delete(server.id);
|
|
3136
|
+
this.firstContactWaits.delete(server.id);
|
|
3120
3137
|
const client = await createLSPClient({
|
|
3121
3138
|
serverId: server.id,
|
|
3122
3139
|
process: spawned.process,
|
|
@@ -3125,6 +3142,9 @@ export class LSPService {
|
|
|
3125
3142
|
initialization: mergedInit,
|
|
3126
3143
|
initializeTimeoutMs: server.initializeTimeoutMs,
|
|
3127
3144
|
launchVariant: spawned.launchVariant,
|
|
3145
|
+
onDiagnosticsPublished: (serverId) => {
|
|
3146
|
+
this.state.diagnosticsPublished.add(serverId);
|
|
3147
|
+
},
|
|
3128
3148
|
});
|
|
3129
3149
|
// Guard 2: service was shut down while we were completing the initialize
|
|
3130
3150
|
// handshake. Shut down the live client best-effort and do not register it.
|
|
@@ -3286,6 +3306,11 @@ export class LSPService {
|
|
|
3286
3306
|
return;
|
|
3287
3307
|
}
|
|
3288
3308
|
const startedAt = Date.now();
|
|
3309
|
+
const hookDeadlineAt = options.hook !== undefined &&
|
|
3310
|
+
Object.hasOwn(HOOK_WALL_BUDGET_MS, options.hook)
|
|
3311
|
+
? startedAt +
|
|
3312
|
+
HOOK_WALL_BUDGET_MS[options.hook]
|
|
3313
|
+
: undefined;
|
|
3289
3314
|
const normalizedPath = normalizeMapKey(filePath);
|
|
3290
3315
|
const outsideRoot = await this.findOutsideProjectRoot(filePath);
|
|
3291
3316
|
if (outsideRoot) {
|
|
@@ -3394,6 +3419,19 @@ export class LSPService {
|
|
|
3394
3419
|
if (!leaseKeys) {
|
|
3395
3420
|
// An idle eviction won between selection and lease admission. Resolve the
|
|
3396
3421
|
// now-current client set and replay; no notification targets the retiree.
|
|
3422
|
+
if (this.checkDestroyed()) {
|
|
3423
|
+
const primaryServerIds = spawned
|
|
3424
|
+
.filter((entry) => entry.info.role !== "auxiliary")
|
|
3425
|
+
.map((entry) => entry.info.id);
|
|
3426
|
+
return {
|
|
3427
|
+
diags: [],
|
|
3428
|
+
inconclusive: true,
|
|
3429
|
+
...(primaryServerIds.length > 0 && {
|
|
3430
|
+
inconclusiveServerIds: primaryServerIds,
|
|
3431
|
+
}),
|
|
3432
|
+
inconclusiveReason: "diagnostics-wait",
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3397
3435
|
return this.touchFile(filePath, content, options);
|
|
3398
3436
|
}
|
|
3399
3437
|
try {
|
|
@@ -3791,6 +3829,7 @@ export class LSPService {
|
|
|
3791
3829
|
// fallback below may still fill it in on a timed-out empty result).
|
|
3792
3830
|
let tsserverSyncEligible = false;
|
|
3793
3831
|
let tsserverSyncConfirmed;
|
|
3832
|
+
let diagnosticsUnsupportedServerIds = [];
|
|
3794
3833
|
if (diagnosticsMode !== "none") {
|
|
3795
3834
|
// Resolution: env wins so users can tune the cap without rebuilding.
|
|
3796
3835
|
// Otherwise, on the single-server hot path (primary scope), use that
|
|
@@ -3861,6 +3900,9 @@ export class LSPService {
|
|
|
3861
3900
|
root: entry.client.root,
|
|
3862
3901
|
operationSupport: entry.client.getOperationSupport(),
|
|
3863
3902
|
workspaceDiagnosticsSupport: entry.client.getWorkspaceDiagnosticsSupport(),
|
|
3903
|
+
customServer: entry.info.custom,
|
|
3904
|
+
diagnosticsPublished: this.state.diagnosticsPublished.has(entry.info.id),
|
|
3905
|
+
diagnosticsUnsupported: this.state.diagnosticsUnsupported.has(entry.info.id),
|
|
3864
3906
|
advertisedCommands: entry.client.getAdvertisedCommands(),
|
|
3865
3907
|
rawCapabilityKeys: entry.client.getRawCapabilityKeys?.() ?? [],
|
|
3866
3908
|
launchVariant: entry.client.getLaunchVariant?.(),
|
|
@@ -4004,6 +4046,29 @@ export class LSPService {
|
|
|
4004
4046
|
const hasTouchAuxiliaries = clientScope === "with-auxiliary" &&
|
|
4005
4047
|
options.collectDiagnostics === true &&
|
|
4006
4048
|
spawned.some((e) => e.info.role === "auxiliary");
|
|
4049
|
+
if (spawned.some((entry) => entry.info.custom === true)) {
|
|
4050
|
+
try {
|
|
4051
|
+
const snapshotHook = options.hook ?? "tool_result_edit";
|
|
4052
|
+
const snapshotBudget = hookDeadlineAt === undefined
|
|
4053
|
+
? HOOK_WALL_BUDGET_MS.tool_result_edit
|
|
4054
|
+
: Math.max(0, hookDeadlineAt - Date.now());
|
|
4055
|
+
const snapshots = await bounded(this.getCapabilitySnapshots(filePath), {
|
|
4056
|
+
ms: snapshotBudget,
|
|
4057
|
+
signal: getAmbientAbortSignal(),
|
|
4058
|
+
shutdownSignal: this.shutdownController.signal,
|
|
4059
|
+
hook: snapshotHook,
|
|
4060
|
+
label: `getCapabilitySnapshots:${filePath}`,
|
|
4061
|
+
});
|
|
4062
|
+
if (snapshots !== undefined) {
|
|
4063
|
+
diagnosticsUnsupportedServerIds = spawned
|
|
4064
|
+
.filter((entry) => classifyServerWaitTier(entry.info.id, snapshots.find((s) => s.serverId === entry.info.id)) === "diagnostics-unsupported")
|
|
4065
|
+
.map((entry) => entry.info.id);
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
catch {
|
|
4069
|
+
// Capability uncertainty fails closed and retains the wait.
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4007
4072
|
// Per-server wait promises (each already bounded by its own
|
|
4008
4073
|
// perServerTimeout — unchanged from before R8).
|
|
4009
4074
|
let pressureSnapshots = [];
|
|
@@ -4028,12 +4093,33 @@ export class LSPService {
|
|
|
4028
4093
|
? auxWaitBudgetMs(entry.client.serverId, true, configuredAuxCeilingMs, declaredServerTimeout)
|
|
4029
4094
|
: perServerDeclaredTimeouts[entryIndex];
|
|
4030
4095
|
});
|
|
4096
|
+
const firstContactCutOffServerIds = new Set();
|
|
4097
|
+
const firstContactErroredServerIds = new Set();
|
|
4098
|
+
const answeredForThisTouch = (entry) => {
|
|
4099
|
+
const baseline = diagnosticBaselines.get(entry.client);
|
|
4100
|
+
const currentPathVersion = readPathVersion(entry.client);
|
|
4101
|
+
if (Number.isFinite(baseline) &&
|
|
4102
|
+
currentPathVersion !== undefined &&
|
|
4103
|
+
currentPathVersion > baseline) {
|
|
4104
|
+
return true;
|
|
4105
|
+
}
|
|
4106
|
+
try {
|
|
4107
|
+
return (entry.client.getAllDiagnostics?.().has(normalizedPath) === true);
|
|
4108
|
+
}
|
|
4109
|
+
catch {
|
|
4110
|
+
return false;
|
|
4111
|
+
}
|
|
4112
|
+
};
|
|
4031
4113
|
const perServerRaceBudgets = spawned.map((entry, entryIndex) => {
|
|
4032
4114
|
return hasTouchAuxiliaries && entry.info.role === "auxiliary"
|
|
4033
4115
|
? auxWaitBudgetMs(entry.client.serverId, coldAuxiliaryServerIds.has(entry.client.serverId), configuredAuxCeilingMs, perServerDeclaredTimeouts[entryIndex])
|
|
4034
4116
|
: perServerDeclaredTimeouts[entryIndex];
|
|
4035
4117
|
});
|
|
4036
4118
|
const perServerWaits = spawned.map((entry, entryIndex) => {
|
|
4119
|
+
if (this.state.diagnosticsUnsupported.has(entry.info.id) ||
|
|
4120
|
+
diagnosticsUnsupportedServerIds.includes(entry.info.id)) {
|
|
4121
|
+
return Promise.resolve(undefined);
|
|
4122
|
+
}
|
|
4037
4123
|
// #1459: a DEFERRED server never received this content, so its version
|
|
4038
4124
|
// can never advance past the baseline — waiting on it burns its whole
|
|
4039
4125
|
// budget and would flip the touch to `inconclusive`, discarding a
|
|
@@ -4063,7 +4149,7 @@ export class LSPService {
|
|
|
4063
4149
|
const isWarmupTouch = source === "lsp_sweep_warmup";
|
|
4064
4150
|
// #743: per-server — a server we DID push to still gets the
|
|
4065
4151
|
// version-baseline wait even when a sibling was debounced away.
|
|
4066
|
-
const
|
|
4152
|
+
const waitForDiagnostics = () => !notifySkippedServerIds.has(entry.info.id) &&
|
|
4067
4153
|
Number.isFinite(baseline)
|
|
4068
4154
|
? entry.client.waitForDiagnostics(filePath, serverTimeout, {
|
|
4069
4155
|
minVersion: baseline,
|
|
@@ -4080,7 +4166,73 @@ export class LSPService {
|
|
|
4080
4166
|
pullSettleSource: "pull-warmup",
|
|
4081
4167
|
})
|
|
4082
4168
|
: entry.client.waitForDiagnostics(filePath, serverTimeout);
|
|
4083
|
-
|
|
4169
|
+
const firstContact = entry.info.custom === true &&
|
|
4170
|
+
!this.state.diagnosticsPublished.has(entry.info.id) &&
|
|
4171
|
+
!this.state.diagnosticsUnsupported.has(entry.info.id) &&
|
|
4172
|
+
entry.client.getWorkspaceDiagnosticsSupport().mode !== "pull";
|
|
4173
|
+
if (!firstContact)
|
|
4174
|
+
return waitForDiagnostics().catch(() => undefined);
|
|
4175
|
+
let shared = this.firstContactWaits.get(entry.info.id);
|
|
4176
|
+
if (!shared) {
|
|
4177
|
+
let removeShutdownListener;
|
|
4178
|
+
const shutdown = new Promise((resolve) => {
|
|
4179
|
+
const onShutdown = () => resolve("shutdown");
|
|
4180
|
+
if (this.shutdownController.signal.aborted) {
|
|
4181
|
+
onShutdown();
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
this.shutdownController.signal.addEventListener("abort", onShutdown, {
|
|
4185
|
+
once: true,
|
|
4186
|
+
});
|
|
4187
|
+
removeShutdownListener = () => this.shutdownController.signal.removeEventListener("abort", onShutdown);
|
|
4188
|
+
});
|
|
4189
|
+
const probe = Promise.race([
|
|
4190
|
+
waitForDiagnostics().then(() => "completed", () => "errored"),
|
|
4191
|
+
shutdown,
|
|
4192
|
+
]);
|
|
4193
|
+
const completed = withDeadline(probe, {
|
|
4194
|
+
ms: serverTimeout,
|
|
4195
|
+
onTimeout: "undefined",
|
|
4196
|
+
});
|
|
4197
|
+
shared = completed
|
|
4198
|
+
.then((outcome) => {
|
|
4199
|
+
if (outcome === "errored" || outcome === "shutdown") {
|
|
4200
|
+
return "errored";
|
|
4201
|
+
}
|
|
4202
|
+
if (answeredForThisTouch(entry))
|
|
4203
|
+
return "answered";
|
|
4204
|
+
this.state.diagnosticsUnsupported.add(entry.info.id);
|
|
4205
|
+
recordDegradationOnce({
|
|
4206
|
+
kind: "lsp-diagnostics-unsupported",
|
|
4207
|
+
subject: entry.info.id,
|
|
4208
|
+
reason: "custom server has no pull provider and no publish evidence after first contact wait",
|
|
4209
|
+
});
|
|
4210
|
+
return "silent";
|
|
4211
|
+
})
|
|
4212
|
+
.catch(() => "errored")
|
|
4213
|
+
.finally(() => {
|
|
4214
|
+
removeShutdownListener?.();
|
|
4215
|
+
if (this.firstContactWaits.get(entry.info.id) === shared) {
|
|
4216
|
+
this.firstContactWaits.delete(entry.info.id);
|
|
4217
|
+
}
|
|
4218
|
+
});
|
|
4219
|
+
this.firstContactWaits.set(entry.info.id, shared);
|
|
4220
|
+
}
|
|
4221
|
+
const callerWait = hookDeadlineAt === undefined
|
|
4222
|
+
? shared
|
|
4223
|
+
: withDeadline(shared, {
|
|
4224
|
+
ms: Math.max(0, hookDeadlineAt - Date.now()),
|
|
4225
|
+
onTimeout: "undefined",
|
|
4226
|
+
});
|
|
4227
|
+
return callerWait.then((outcome) => {
|
|
4228
|
+
if (outcome === undefined) {
|
|
4229
|
+
firstContactCutOffServerIds.add(entry.info.id);
|
|
4230
|
+
}
|
|
4231
|
+
else if (outcome === "errored") {
|
|
4232
|
+
firstContactErroredServerIds.add(entry.info.id);
|
|
4233
|
+
}
|
|
4234
|
+
return undefined;
|
|
4235
|
+
});
|
|
4084
4236
|
});
|
|
4085
4237
|
// The push wait — same per-server budget composition as before #707;
|
|
4086
4238
|
// only the awaiting changed (assigned so it can be raced below).
|
|
@@ -4257,9 +4409,14 @@ export class LSPService {
|
|
|
4257
4409
|
});
|
|
4258
4410
|
})()
|
|
4259
4411
|
: Promise.all(perServerWaits).then(() => { });
|
|
4260
|
-
pushWait
|
|
4412
|
+
// Mark on BOTH paths: a rejected `pushWait` is settled too, and a
|
|
4413
|
+
// resolve-only `.then` left a derived promise that rejected unhandled
|
|
4414
|
+
// whenever a per-server wait failed (oxlint no-floating-promises,
|
|
4415
|
+
// 2026-09-07). The rejection itself still reaches the awaiters below.
|
|
4416
|
+
const markPushWaitSettled = () => {
|
|
4261
4417
|
pushWaitSettled = true;
|
|
4262
|
-
}
|
|
4418
|
+
};
|
|
4419
|
+
void pushWait.then(markPushWaitSettled, markPushWaitSettled);
|
|
4263
4420
|
if (tsserverSyncEligible) {
|
|
4264
4421
|
// #707 racing variant: rather than burning the full push-wait budget
|
|
4265
4422
|
// on a silent-on-clean server (which by definition never answers on a
|
|
@@ -4331,6 +4488,35 @@ export class LSPService {
|
|
|
4331
4488
|
await pushWait;
|
|
4332
4489
|
}
|
|
4333
4490
|
const waitedMs = Date.now() - waitStartedAt;
|
|
4491
|
+
const firstContactCustomServerIds = spawned
|
|
4492
|
+
.filter((entry) => entry.info.custom === true &&
|
|
4493
|
+
!this.state.diagnosticsPublished.has(entry.info.id) &&
|
|
4494
|
+
!this.state.diagnosticsUnsupported.has(entry.info.id) &&
|
|
4495
|
+
!firstContactCutOffServerIds.has(entry.info.id) &&
|
|
4496
|
+
!firstContactErroredServerIds.has(entry.info.id) &&
|
|
4497
|
+
entry.client.getWorkspaceDiagnosticsSupport().mode !== "pull")
|
|
4498
|
+
.map((entry) => entry.info.id);
|
|
4499
|
+
for (const serverId of firstContactCustomServerIds) {
|
|
4500
|
+
const entry = spawned.find((candidate) => candidate.info.id === serverId);
|
|
4501
|
+
if (!entry || answeredForThisTouch(entry))
|
|
4502
|
+
continue;
|
|
4503
|
+
this.state.diagnosticsUnsupported.add(serverId);
|
|
4504
|
+
diagnosticsUnsupportedServerIds.push(serverId);
|
|
4505
|
+
recordDegradationOnce({
|
|
4506
|
+
kind: "lsp-diagnostics-unsupported",
|
|
4507
|
+
subject: serverId,
|
|
4508
|
+
reason: "custom server has no pull provider and no publish evidence after first contact wait",
|
|
4509
|
+
});
|
|
4510
|
+
}
|
|
4511
|
+
for (const entry of spawned) {
|
|
4512
|
+
if (entry.info.custom === true &&
|
|
4513
|
+
this.state.diagnosticsUnsupported.has(entry.info.id)) {
|
|
4514
|
+
diagnosticsUnsupportedServerIds.push(entry.info.id);
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
diagnosticsUnsupportedServerIds = [
|
|
4518
|
+
...new Set(diagnosticsUnsupportedServerIds),
|
|
4519
|
+
];
|
|
4334
4520
|
// #1533: the same auxiliary coverage evidence for a collecting touch that
|
|
4335
4521
|
// did NOT enter the aux-grace wait — in practice `clientScope: "all"`, the
|
|
4336
4522
|
// batch/directory scan surface. Auxiliaries ARE spawned on that scope
|
|
@@ -4454,7 +4640,8 @@ export class LSPService {
|
|
|
4454
4640
|
},
|
|
4455
4641
|
});
|
|
4456
4642
|
}
|
|
4457
|
-
else if (waitedMs + 20 >= timeoutMs
|
|
4643
|
+
else if (waitedMs + 20 >= timeoutMs ||
|
|
4644
|
+
firstContactErroredServerIds.size > 0) {
|
|
4458
4645
|
// Within ~20 ms of the configured budget we treat it as a timeout;
|
|
4459
4646
|
// the LSP didn't beat the cap. Diagnostics that arrive late still
|
|
4460
4647
|
// land in the client's cache and surface on the next edit.
|
|
@@ -4479,25 +4666,10 @@ export class LSPService {
|
|
|
4479
4666
|
// reads as unanswered, which for a primary is exactly the pre-#1549
|
|
4480
4667
|
// verdict. This block can therefore only ever NARROW an inconclusive
|
|
4481
4668
|
// touch, never create one.
|
|
4482
|
-
const answeredForThisTouch = (entry) => {
|
|
4483
|
-
const baseline = diagnosticBaselines.get(entry.client);
|
|
4484
|
-
const currentPathVersion = readPathVersion(entry.client);
|
|
4485
|
-
if (Number.isFinite(baseline) &&
|
|
4486
|
-
currentPathVersion !== undefined &&
|
|
4487
|
-
currentPathVersion > baseline) {
|
|
4488
|
-
return true;
|
|
4489
|
-
}
|
|
4490
|
-
try {
|
|
4491
|
-
return (entry.client.getAllDiagnostics?.().has(normalizedPath) === true);
|
|
4492
|
-
}
|
|
4493
|
-
catch {
|
|
4494
|
-
// Fail closed: an unreadable cache is not evidence of an answer.
|
|
4495
|
-
return false;
|
|
4496
|
-
}
|
|
4497
|
-
};
|
|
4498
4669
|
// A deferred server was never sent this content and is not waited on, so
|
|
4499
4670
|
// it cannot have "timed out" — it is already reported as a coverage gap.
|
|
4500
|
-
const waited = spawned.filter((entry) => !deferredResyncServerIds.has(entry.info.id)
|
|
4671
|
+
const waited = spawned.filter((entry) => !deferredResyncServerIds.has(entry.info.id) &&
|
|
4672
|
+
!diagnosticsUnsupportedServerIds.includes(entry.info.id));
|
|
4501
4673
|
const unanswered = waited.filter((entry) => !answeredForThisTouch(entry));
|
|
4502
4674
|
diagnosticsUnansweredServerIds = unanswered.map((e) => e.info.id);
|
|
4503
4675
|
diagnosticsUnansweredPrimaryServerIds = unanswered
|
|
@@ -4507,8 +4679,9 @@ export class LSPService {
|
|
|
4507
4679
|
// preserve, so it keeps the pre-#1549 touch-wide verdict rather than
|
|
4508
4680
|
// absolving itself on an auxiliary's evidence.
|
|
4509
4681
|
const hasWaitedPrimary = waited.some((entry) => entry.info.role !== "auxiliary");
|
|
4682
|
+
const hasUnsupportedPrimary = diagnosticsUnsupportedServerIds.some((serverId) => spawned.some((entry) => entry.info.id === serverId && entry.info.role !== "auxiliary"));
|
|
4510
4683
|
diagnosticsTimedOut =
|
|
4511
|
-
!hasWaitedPrimary ||
|
|
4684
|
+
(!hasWaitedPrimary && !hasUnsupportedPrimary) ||
|
|
4512
4685
|
diagnosticsUnansweredPrimaryServerIds.length > 0;
|
|
4513
4686
|
for (const entry of unanswered) {
|
|
4514
4687
|
incrementDegradationCount({
|
|
@@ -5061,6 +5234,11 @@ export class LSPService {
|
|
|
5061
5234
|
// collect, `binding` only for a collecting touch — a non-collecting touch
|
|
5062
5235
|
// keeps resolving `{ diags: [] }`, no flags.
|
|
5063
5236
|
const result = { diags: collected ?? [] };
|
|
5237
|
+
const primaryDiagnosticsUnsupported = diagnosticsUnsupportedServerIds.some((serverId) => spawned.some((entry) => entry.info.id === serverId && entry.info.role !== "auxiliary"));
|
|
5238
|
+
if (diagnosticsUnsupportedServerIds.length > 0) {
|
|
5239
|
+
result.diagnosticsUnsupportedServerIds =
|
|
5240
|
+
diagnosticsUnsupportedServerIds;
|
|
5241
|
+
}
|
|
5064
5242
|
if (collected !== undefined && inconclusive) {
|
|
5065
5243
|
result.inconclusive = true;
|
|
5066
5244
|
// #1549: name the primary that produced the verdict and which deadline it
|
|
@@ -5073,6 +5251,11 @@ export class LSPService {
|
|
|
5073
5251
|
}
|
|
5074
5252
|
result.inconclusiveReason = verdict.inconclusiveReason;
|
|
5075
5253
|
}
|
|
5254
|
+
else if (collected !== undefined && primaryDiagnosticsUnsupported) {
|
|
5255
|
+
// A navigation-only primary has no diagnostic confirmation to report.
|
|
5256
|
+
// Auxiliary coverage cannot turn that capability boundary into a clean
|
|
5257
|
+
// or partial diagnostic verdict.
|
|
5258
|
+
}
|
|
5076
5259
|
else if (collected !== undefined && coverageGap) {
|
|
5077
5260
|
// #1470/#1493: narrowed, not collapsed. Reached for EITHER no-answer
|
|
5078
5261
|
// shape — a cut-off auxiliary or a silent one with nothing published for
|
|
@@ -5083,7 +5266,7 @@ export class LSPService {
|
|
|
5083
5266
|
result.confirmation = "partial";
|
|
5084
5267
|
result.unconfirmedServerIds = [...unconfirmedServerIds];
|
|
5085
5268
|
}
|
|
5086
|
-
else if (collected !== undefined) {
|
|
5269
|
+
else if (collected !== undefined && !primaryDiagnosticsUnsupported) {
|
|
5087
5270
|
// Preserve the lower-level affirmative result across consumers. In
|
|
5088
5271
|
// particular, the silent-clean gates above clear diagnosticsTimedOut only
|
|
5089
5272
|
// after a successful notify and capability-confirmed wait; reclassifying
|
|
@@ -5854,6 +6037,9 @@ export class LSPService {
|
|
|
5854
6037
|
root,
|
|
5855
6038
|
operationSupport: client.getOperationSupport(),
|
|
5856
6039
|
workspaceDiagnosticsSupport: client.getWorkspaceDiagnosticsSupport(),
|
|
6040
|
+
customServer: server.custom,
|
|
6041
|
+
diagnosticsPublished: this.state.diagnosticsPublished.has(server.id),
|
|
6042
|
+
diagnosticsUnsupported: this.state.diagnosticsUnsupported.has(server.id),
|
|
5857
6043
|
advertisedCommands: client.getAdvertisedCommands(),
|
|
5858
6044
|
rawCapabilityKeys: client.getRawCapabilityKeys?.() ?? [],
|
|
5859
6045
|
launchVariant: client.getLaunchVariant?.(),
|
|
@@ -5871,6 +6057,9 @@ export class LSPService {
|
|
|
5871
6057
|
root: client.root,
|
|
5872
6058
|
operationSupport: client.getOperationSupport(),
|
|
5873
6059
|
workspaceDiagnosticsSupport: client.getWorkspaceDiagnosticsSupport(),
|
|
6060
|
+
customServer: this.state.servers.get(serverId)?.custom,
|
|
6061
|
+
diagnosticsPublished: this.state.diagnosticsPublished.has(serverId),
|
|
6062
|
+
diagnosticsUnsupported: this.state.diagnosticsUnsupported.has(serverId),
|
|
5874
6063
|
advertisedCommands: client.getAdvertisedCommands(),
|
|
5875
6064
|
rawCapabilityKeys: client.getRawCapabilityKeys?.() ?? [],
|
|
5876
6065
|
launchVariant: client.getLaunchVariant?.(),
|
|
@@ -7042,7 +7231,7 @@ export class LSPService {
|
|
|
7042
7231
|
// ONE bracket for the whole fan-out, not one per file (#2272 review F1).
|
|
7043
7232
|
// Per-file bracketing was the first shape and it was inert for exactly
|
|
7044
7233
|
// the case above: `phaseFinished` pushes onto a ring capped at
|
|
7045
|
-
// `
|
|
7234
|
+
// `RECENT_PHASE_CAP` (5, clients/latency-logger.ts), while `turn_end`
|
|
7046
7235
|
// reads `getPhaseForWindow` AFTER the sweep returns. A 225-file sweep
|
|
7047
7236
|
// therefore evicted the blocking file's own bracket unless the block
|
|
7048
7237
|
// landed in the last five files, and the survivors were then rejected by
|
|
@@ -7515,6 +7704,7 @@ export class LSPService {
|
|
|
7515
7704
|
if (this.checkDestroyed())
|
|
7516
7705
|
return;
|
|
7517
7706
|
this.isDestroyed = true;
|
|
7707
|
+
this.shutdownController.abort();
|
|
7518
7708
|
for (const [key, token] of this.outstandingAuxNotifyWrites) {
|
|
7519
7709
|
this.releaseOutstandingAuxNotifyWrite(key, token);
|
|
7520
7710
|
}
|
|
@@ -7563,6 +7753,7 @@ export class LSPService {
|
|
|
7563
7753
|
});
|
|
7564
7754
|
this.state.clients.clear();
|
|
7565
7755
|
this.state.broken.clear();
|
|
7756
|
+
this.firstContactWaits.clear();
|
|
7566
7757
|
// #1934 review F1: map hygiene alongside the breaker it sits next to.
|
|
7567
7758
|
// Not load-bearing — every read follows its own attempt's write — but a
|
|
7568
7759
|
// verdict for a client generation that no longer exists is dead weight.
|
|
@@ -55,7 +55,7 @@ const TYPESCRIPT_DIAGNOSTIC_SOURCES = new Set(["typescript", "ts", "tsserver"]);
|
|
|
55
55
|
export function isTsProjectFile(filePath) {
|
|
56
56
|
return TS_PROJECT_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
function isTypeScriptSourcedDiagnostic(d) {
|
|
59
59
|
return TYPESCRIPT_DIAGNOSTIC_SOURCES.has((d.source ?? "").toLowerCase());
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
@@ -61,11 +61,3 @@ export function getLanguageId(filePath) {
|
|
|
61
61
|
LANGUAGE_EXTENSIONS[base.toLowerCase()] ??
|
|
62
62
|
getBasenameLanguageId(base));
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Get all extensions for a language ID
|
|
66
|
-
*/
|
|
67
|
-
export function getExtensionsForLanguage(languageId) {
|
|
68
|
-
return Object.entries(LANGUAGE_EXTENSIONS)
|
|
69
|
-
.filter(([, id]) => id === languageId)
|
|
70
|
-
.map(([ext]) => ext);
|
|
71
|
-
}
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Re-export from shared path-utils.
|
|
3
3
|
* Kept as a local module for LSP imports that use relative paths.
|
|
4
4
|
*/
|
|
5
|
-
export { isUnderDir,
|
|
5
|
+
export { isUnderDir, normalizeEphemeralMapKey, normalizeMapKey, pathsEqual, uriToDiskPath, uriToPath, } from "../path-utils.js";
|
|
@@ -187,8 +187,6 @@ export function drainPendingAuxiliaryCoverage() {
|
|
|
187
187
|
export function pendingAuxiliaryCoverageSize() {
|
|
188
188
|
return pending.size;
|
|
189
189
|
}
|
|
190
|
-
/** Test-only alias for the store-size assertion seam. */
|
|
191
|
-
export const pendingAuxiliaryCoverageSizeForTests = pendingAuxiliaryCoverageSize;
|
|
192
190
|
/**
|
|
193
191
|
* Drain (read and reset) the cap-eviction count accumulated since the last
|
|
194
192
|
* drain. Mirrors {@link drainPendingAuxiliaryCoverage}'s consume-once shape
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
* always sent, so an unrecognized/absent value never regresses `Full`/`None`
|
|
17
17
|
* behavior.
|
|
18
18
|
*/
|
|
19
|
-
export const TEXT_DOCUMENT_SYNC_KIND_NONE = 0;
|
|
20
19
|
export const TEXT_DOCUMENT_SYNC_KIND_FULL = 1;
|
|
21
20
|
export const TEXT_DOCUMENT_SYNC_KIND_INCREMENTAL = 2;
|
|
22
21
|
function isSyncKind(value) {
|
|
@@ -37,7 +37,7 @@ import { normalizeMapKey } from "../path-utils.js";
|
|
|
37
37
|
// ---------------------------------------------------------------------------
|
|
38
38
|
// Constants
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
40
|
+
const TSSERVER_REQUEST_COMMAND = "typescript.tsserverRequest";
|
|
41
41
|
/**
|
|
42
42
|
* tsserver's synthetic inferred-project name, as its own source builds it:
|
|
43
43
|
* `"/dev/null/inferredProject" + counter + "*"`. `/dev/null` is a deliberate
|
|
@@ -219,13 +219,13 @@ export async function fetchTsserverProjectIdentity(svc, file) {
|
|
|
219
219
|
// ---------------------------------------------------------------------------
|
|
220
220
|
// Helpers
|
|
221
221
|
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
222
|
+
function isTsserverSyncRawDiagnostic(value) {
|
|
223
223
|
if (!value || typeof value !== "object")
|
|
224
224
|
return false;
|
|
225
225
|
const v = value;
|
|
226
226
|
return typeof v.message === "string" && typeof v.category === "string";
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
function tsserverSeverityFromCategory(category) {
|
|
229
229
|
switch (category) {
|
|
230
230
|
case "error":
|
|
231
231
|
return 1;
|
|
@@ -242,7 +242,7 @@ export function tsserverSeverityFromCategory(category) {
|
|
|
242
242
|
* `LSPDiagnostic`. Both `line`/`offset` are 1-based in tsserver's protocol
|
|
243
243
|
* and 0-based in LSP — this conversion handles that.
|
|
244
244
|
*/
|
|
245
|
-
|
|
245
|
+
function tsserverSyncDiagnosticToLsp(d) {
|
|
246
246
|
const startLine = Math.max(0, (d.startLocation?.line ?? 1) - 1);
|
|
247
247
|
const startChar = Math.max(0, (d.startLocation?.offset ?? 1) - 1);
|
|
248
248
|
const endLine = Math.max(0, (d.endLocation?.line ?? d.startLocation?.line ?? 1) - 1);
|
|
@@ -265,7 +265,7 @@ export function tsserverSyncDiagnosticToLsp(d) {
|
|
|
265
265
|
* executed, the response envelope isn't `{success:true, body:[...]}`, or
|
|
266
266
|
* any error is thrown.
|
|
267
267
|
*/
|
|
268
|
-
|
|
268
|
+
async function runTsserverSyncCommand(svc, file, command) {
|
|
269
269
|
if (typeof svc.executeCommand !== "function")
|
|
270
270
|
return undefined;
|
|
271
271
|
const outcome = await svc.executeCommand(file, TSSERVER_REQUEST_COMMAND, [
|
|
@@ -24,6 +24,12 @@ export function classifyServerWaitTier(serverId, snapshot) {
|
|
|
24
24
|
const mode = snapshot.workspaceDiagnosticsSupport?.mode;
|
|
25
25
|
if (mode === "pull")
|
|
26
26
|
return "pull-capable";
|
|
27
|
+
if (snapshot.customServer === true &&
|
|
28
|
+
mode === "push-only" &&
|
|
29
|
+
snapshot.diagnosticsUnsupported === true &&
|
|
30
|
+
snapshot.diagnosticsPublished !== true) {
|
|
31
|
+
return "diagnostics-unsupported";
|
|
32
|
+
}
|
|
27
33
|
if (mode !== "push-only")
|
|
28
34
|
return "waits";
|
|
29
35
|
const strategy = getStrategy(serverId, snapshot.launchVariant);
|
|
@@ -73,6 +73,7 @@ export function resolveAstGrepNativeExe(platform = process.platform, arch = proc
|
|
|
73
73
|
return undefined;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
/** @public — loaded by scripts/probe-clean-signal.mjs through a dynamic import wrapper knip cannot see. */
|
|
76
77
|
export const SERVER_DIAGNOSTIC_STRATEGIES = {
|
|
77
78
|
typescript: {
|
|
78
79
|
seedFirstPush: true,
|
|
@@ -287,7 +288,7 @@ const NATIVE_TS7_DIAGNOSTIC_STRATEGY = {
|
|
|
287
288
|
silentOnClean: false,
|
|
288
289
|
};
|
|
289
290
|
/** Fallback for unknown servers. Conservative defaults. */
|
|
290
|
-
|
|
291
|
+
const DEFAULT_STRATEGY = {
|
|
291
292
|
seedFirstPush: false,
|
|
292
293
|
pullRetryBudgetMs: 250,
|
|
293
294
|
debounceMs: 150,
|
|
@@ -2,7 +2,7 @@ import { getLSPService } from "./lsp/index.js";
|
|
|
2
2
|
// Grounded against this repo's workspace-native TypeScript 7.0.2 server
|
|
3
3
|
// (`tsc --lsp --stdio`) on 2026-07-30: 20 already-open documentSymbol calls
|
|
4
4
|
// measured p50 3.70 ms / p95 5.28 ms. Keep generous headroom for slower hosts.
|
|
5
|
-
|
|
5
|
+
const LSP_DOCUMENT_SYMBOL_TIMEOUT_MS = 150;
|
|
6
6
|
export const SYMBOL_KIND_NAMES = {
|
|
7
7
|
1: "file",
|
|
8
8
|
2: "module",
|