@mrclrchtr/supi-code-intelligence 6.2.0 → 6.3.0
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/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/README.md +52 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +785 -129
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +660 -98
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +10 -4
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +6 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +106 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +4 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +348 -99
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +43 -15
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +23 -32
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +4 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +18 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/README.md +29 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs +99 -19
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js +94 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +14 -14
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +13 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs +97 -19
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts +28 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts.map +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts +28 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js +92 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +17 -17
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +2 -2
- package/package.json +5 -5
- package/src/session/health-refresh.ts +4 -2
- package/src/session/health-types.ts +3 -3
- package/src/substrate/lsp/settings.ts +2 -1
- package/src/tool/code_health/refresh-outcome.ts +37 -7
- package/src/tool/code_health/refresh-status.ts +58 -12
|
@@ -65,8 +65,8 @@ import {
|
|
|
65
65
|
} from "../diagnostics/workspace-sentinels.ts";
|
|
66
66
|
import { raceRequestControl } from "../session/readiness.ts";
|
|
67
67
|
import {
|
|
68
|
-
|
|
69
|
-
type
|
|
68
|
+
emptyProcessCrashRecoveryReport,
|
|
69
|
+
type ProcessCrashRecoveryReport,
|
|
70
70
|
type ScopeDecisionEntry,
|
|
71
71
|
type ScopeDecisionSummary,
|
|
72
72
|
type WorkspaceDiagnosticReport,
|
|
@@ -94,6 +94,11 @@ import {
|
|
|
94
94
|
syncClientFileAndGetDiagnostics,
|
|
95
95
|
} from "./manager-diagnostics.ts";
|
|
96
96
|
import { clientKey, rememberKnownRoot, resolveRootForFile } from "./manager-helpers.ts";
|
|
97
|
+
import {
|
|
98
|
+
boundProcessCrashFailureMessage,
|
|
99
|
+
buildProcessCrashRecoveryReport,
|
|
100
|
+
type ProcessCrashRecoveryRouteResult,
|
|
101
|
+
} from "./manager-process-crash-report.ts";
|
|
97
102
|
import { buildProjectServerInfo } from "./manager-project-info.ts";
|
|
98
103
|
import type {
|
|
99
104
|
ActiveCoverageSummaryEntry,
|
|
@@ -180,7 +185,7 @@ type FileClientRequestOptions = {
|
|
|
180
185
|
|
|
181
186
|
interface FileReadinessResult {
|
|
182
187
|
client: LspClient | null;
|
|
183
|
-
processCrashRecovery:
|
|
188
|
+
processCrashRecovery: ProcessCrashRecoveryReport;
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
interface ProcessCrashRecoveryState {
|
|
@@ -196,40 +201,56 @@ interface ProcessCrashRecoveryState {
|
|
|
196
201
|
pending: Promise<LspClient | null> | null;
|
|
197
202
|
/** Start time for the current replacement attempt, when one exists. */
|
|
198
203
|
attemptStartedAt?: number;
|
|
204
|
+
/** Bounded caught Error.message from a failed replacement startup. */
|
|
205
|
+
failureMessage?: string;
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
type ProcessCrashRecoveryOutcome = "attempt" | "success" | "failure" | "exhausted" | "cancelled";
|
|
202
|
-
|
|
203
208
|
type ProcessCrashDemandResult = {
|
|
204
|
-
/** Whether at least one
|
|
209
|
+
/** Whether at least one crashed route supports the operation. */
|
|
205
210
|
hasSupport: boolean;
|
|
211
|
+
/** Whether replacement work changed the route set and needs a fresh pass. */
|
|
212
|
+
requiresFreshEvidence: boolean;
|
|
206
213
|
/** Separate route-level result for this explicit process-crash demand. */
|
|
207
|
-
processCrashRecovery:
|
|
214
|
+
processCrashRecovery: ProcessCrashRecoveryReport;
|
|
208
215
|
/** Required routes that remain unavailable after shared recovery settles. */
|
|
209
216
|
failures: string[];
|
|
210
217
|
/** In-scope tracked files from required routes that remain unavailable. */
|
|
211
218
|
failedFiles: string[];
|
|
212
219
|
};
|
|
213
220
|
|
|
221
|
+
type ProcessCrashRecoverySnapshot = {
|
|
222
|
+
readonly key: string;
|
|
223
|
+
/** Mutable state handle used only to await the shared replacement. */
|
|
224
|
+
readonly recovery: ProcessCrashRecoveryState;
|
|
225
|
+
readonly failedClient: LspClient;
|
|
226
|
+
readonly name: string;
|
|
227
|
+
readonly root: string;
|
|
228
|
+
readonly statusReason: NonNullable<ProcessCrashRecoveryState["statusReason"]>;
|
|
229
|
+
readonly files: readonly string[];
|
|
230
|
+
};
|
|
231
|
+
|
|
214
232
|
type RequiredProcessCrashDemand = {
|
|
215
|
-
|
|
216
|
-
recovery: ProcessCrashRecoveryState;
|
|
233
|
+
snapshot: ProcessCrashRecoverySnapshot;
|
|
217
234
|
failedFiles: readonly string[];
|
|
218
235
|
};
|
|
219
236
|
|
|
220
|
-
type
|
|
237
|
+
type ImmediateProcessCrashDemand = {
|
|
238
|
+
route: ProcessCrashRecoveryRouteResult;
|
|
239
|
+
failedFiles: readonly string[];
|
|
240
|
+
reportOnly: boolean;
|
|
241
|
+
};
|
|
221
242
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
243
|
+
type ProcessCrashDemandSelection = {
|
|
244
|
+
immediate: ImmediateProcessCrashDemand[];
|
|
245
|
+
required: RequiredProcessCrashDemand[];
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
type ProcessCrashRecoveryTelemetryOutcome =
|
|
249
|
+
| "attempt"
|
|
250
|
+
| "success"
|
|
251
|
+
| "failure"
|
|
252
|
+
| "exhausted"
|
|
253
|
+
| "cancelled";
|
|
233
254
|
|
|
234
255
|
// ── LspManager ────────────────────────────────────────────────────────
|
|
235
256
|
export class LspManager {
|
|
@@ -292,20 +313,24 @@ export class LspManager {
|
|
|
292
313
|
}
|
|
293
314
|
|
|
294
315
|
/**
|
|
295
|
-
* Return
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* not reported as a new outcome. This is a passive read and never starts
|
|
299
|
-
* recovery.
|
|
316
|
+
* Return an exhausted process-crash outcome for a file readiness result.
|
|
317
|
+
* This is a passive read and never starts recovery. A crashed or pending
|
|
318
|
+
* route has no final result until an explicit operation observes it.
|
|
300
319
|
*/
|
|
301
|
-
|
|
320
|
+
getProcessCrashRecoveryReportForFile(filePath: string): ProcessCrashRecoveryReport | null {
|
|
302
321
|
const route = this.resolveFileRoute(resolveSessionPath(this.cwd, filePath));
|
|
303
322
|
const recovery = route ? this.processCrashRecoveries.get(route.key) : undefined;
|
|
304
|
-
if (!
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
323
|
+
if (!route || recovery?.statusReason !== "process-crash-recovery-exhausted") return null;
|
|
324
|
+
return buildProcessCrashRecoveryReport(
|
|
325
|
+
[
|
|
326
|
+
{
|
|
327
|
+
name: route.serverName,
|
|
328
|
+
root: route.root,
|
|
329
|
+
outcome: "recovery-exhausted",
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
this.cwd,
|
|
333
|
+
);
|
|
309
334
|
}
|
|
310
335
|
|
|
311
336
|
/**
|
|
@@ -509,6 +534,7 @@ export class LspManager {
|
|
|
509
534
|
failedClient: client,
|
|
510
535
|
files: this.trackedFilesForClient(client),
|
|
511
536
|
pending: null,
|
|
537
|
+
failureMessage: undefined,
|
|
512
538
|
});
|
|
513
539
|
}
|
|
514
540
|
|
|
@@ -547,66 +573,192 @@ export class LspManager {
|
|
|
547
573
|
/**
|
|
548
574
|
* Recover all crashed routes selected by one evidence-producing operation.
|
|
549
575
|
* Each route keeps its independent attempt budget and shared replacement.
|
|
576
|
+
* Selection uses one immutable route-fact snapshot so scope and retention
|
|
577
|
+
* facts cannot change halfway through the demand.
|
|
550
578
|
*/
|
|
551
579
|
private async recoverProcessCrashDemand(
|
|
552
|
-
selectFiles: (
|
|
580
|
+
selectFiles: (snapshot: ProcessCrashRecoverySnapshot) => readonly string[] | null,
|
|
553
581
|
control?: CodeRequestControl,
|
|
582
|
+
options: { skipWhenNoRetainedFile?: boolean } = {},
|
|
554
583
|
): Promise<ProcessCrashDemandResult> {
|
|
555
|
-
const required =
|
|
556
|
-
(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return failedFiles === null ? [] : [{ key, recovery, failedFiles }];
|
|
560
|
-
},
|
|
584
|
+
const { immediate, required } = this.selectProcessCrashDemandRoutes(
|
|
585
|
+
this.snapshotProcessCrashRoutes(),
|
|
586
|
+
selectFiles,
|
|
587
|
+
options,
|
|
561
588
|
);
|
|
589
|
+
const hasSupport = immediate.length > 0 || required.length > 0;
|
|
562
590
|
if (required.length === 0) {
|
|
591
|
+
const failures = immediate
|
|
592
|
+
.filter((entry) => !entry.reportOnly)
|
|
593
|
+
.map((entry) => this.formatProcessCrashDemandFailure(entry.route, entry.route.outcome));
|
|
563
594
|
return {
|
|
564
|
-
hasSupport
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
595
|
+
hasSupport,
|
|
596
|
+
requiresFreshEvidence: false,
|
|
597
|
+
processCrashRecovery: buildProcessCrashRecoveryReport(
|
|
598
|
+
immediate.map(({ route }) => route),
|
|
599
|
+
this.cwd,
|
|
600
|
+
),
|
|
601
|
+
failures,
|
|
602
|
+
failedFiles: Array.from(
|
|
603
|
+
new Set(
|
|
604
|
+
immediate
|
|
605
|
+
.filter((entry) => !entry.reportOnly)
|
|
606
|
+
.flatMap(({ failedFiles }) => failedFiles),
|
|
607
|
+
),
|
|
608
|
+
),
|
|
568
609
|
};
|
|
569
610
|
}
|
|
570
611
|
|
|
571
612
|
await Promise.all(
|
|
572
|
-
required.map(async ({
|
|
573
|
-
const serverConfig = this.config.servers[
|
|
613
|
+
required.map(async ({ snapshot }) => {
|
|
614
|
+
const serverConfig = this.config.servers[snapshot.name];
|
|
574
615
|
if (!serverConfig) return;
|
|
575
616
|
await this.acquireProcessCrashReplacement(
|
|
576
|
-
recovery,
|
|
617
|
+
snapshot.recovery,
|
|
577
618
|
{
|
|
578
|
-
serverName:
|
|
619
|
+
serverName: snapshot.name,
|
|
579
620
|
serverConfig,
|
|
580
|
-
root:
|
|
581
|
-
key,
|
|
621
|
+
root: snapshot.root,
|
|
622
|
+
key: snapshot.key,
|
|
582
623
|
},
|
|
583
624
|
control,
|
|
584
625
|
);
|
|
585
626
|
}),
|
|
586
627
|
);
|
|
587
628
|
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
({ recovery }): ProcessCrashRecoveryRouteOutcome =>
|
|
591
|
-
recovery.statusReason === undefined ? "recovered" : "failed",
|
|
629
|
+
const observedRequired = required.filter(
|
|
630
|
+
({ snapshot }) => !this.isProcessCrashDemandCancelled(snapshot),
|
|
592
631
|
);
|
|
632
|
+
const failedDemands = observedRequired.filter(
|
|
633
|
+
({ snapshot }) => snapshot.recovery.statusReason !== undefined,
|
|
634
|
+
);
|
|
635
|
+
const recoveredRoutes = observedRequired.map(
|
|
636
|
+
({ snapshot }): ProcessCrashRecoveryRouteResult => ({
|
|
637
|
+
name: snapshot.name,
|
|
638
|
+
root: snapshot.root,
|
|
639
|
+
outcome: snapshot.recovery.statusReason === undefined ? "recovered" : "recovery-failed",
|
|
640
|
+
...(snapshot.recovery.statusReason === undefined || !snapshot.recovery.failureMessage
|
|
641
|
+
? {}
|
|
642
|
+
: { failureMessage: snapshot.recovery.failureMessage }),
|
|
643
|
+
}),
|
|
644
|
+
);
|
|
645
|
+
const failedImmediate = immediate.filter((entry) => !entry.reportOnly);
|
|
593
646
|
return {
|
|
594
|
-
hasSupport
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
647
|
+
hasSupport,
|
|
648
|
+
requiresFreshEvidence: observedRequired.length > 0,
|
|
649
|
+
processCrashRecovery: buildProcessCrashRecoveryReport(
|
|
650
|
+
[...immediate.map(({ route }) => route), ...recoveredRoutes],
|
|
651
|
+
this.cwd,
|
|
652
|
+
),
|
|
653
|
+
failures: [
|
|
654
|
+
...failedImmediate.map((entry) =>
|
|
655
|
+
this.formatProcessCrashDemandFailure(entry.route, entry.route.outcome),
|
|
656
|
+
),
|
|
657
|
+
...failedDemands.map(({ snapshot }) =>
|
|
658
|
+
this.formatProcessCrashDemandFailure(snapshot.recovery, "recovery-failed"),
|
|
659
|
+
),
|
|
660
|
+
],
|
|
661
|
+
failedFiles: Array.from(
|
|
662
|
+
new Set([
|
|
663
|
+
...failedImmediate.flatMap(({ failedFiles }) => failedFiles),
|
|
664
|
+
...failedDemands.flatMap(({ failedFiles }) => failedFiles),
|
|
665
|
+
]),
|
|
666
|
+
),
|
|
598
667
|
};
|
|
599
668
|
}
|
|
600
669
|
|
|
601
|
-
private
|
|
602
|
-
|
|
670
|
+
private isProcessCrashDemandCancelled(snapshot: ProcessCrashRecoverySnapshot): boolean {
|
|
671
|
+
return (
|
|
672
|
+
snapshot.recovery.statusReason !== undefined &&
|
|
673
|
+
(this.shuttingDown || this.processCrashRecoveries.get(snapshot.key) !== snapshot.recovery)
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private selectProcessCrashDemandRoutes(
|
|
678
|
+
snapshots: readonly ProcessCrashRecoverySnapshot[],
|
|
679
|
+
selectFiles: (snapshot: ProcessCrashRecoverySnapshot) => readonly string[] | null,
|
|
680
|
+
options: { skipWhenNoRetainedFile?: boolean },
|
|
681
|
+
): ProcessCrashDemandSelection {
|
|
682
|
+
const immediate: ImmediateProcessCrashDemand[] = [];
|
|
683
|
+
const required: RequiredProcessCrashDemand[] = [];
|
|
684
|
+
|
|
685
|
+
for (const snapshot of snapshots) {
|
|
686
|
+
const failedFiles = selectFiles(snapshot);
|
|
687
|
+
if (failedFiles === null) continue;
|
|
688
|
+
|
|
689
|
+
const exhausted = snapshot.statusReason === "process-crash-recovery-exhausted";
|
|
690
|
+
const noRetainedFile = failedFiles.length === 0;
|
|
691
|
+
if (exhausted) {
|
|
692
|
+
immediate.push({
|
|
693
|
+
route: {
|
|
694
|
+
name: snapshot.name,
|
|
695
|
+
root: snapshot.root,
|
|
696
|
+
outcome: "recovery-exhausted",
|
|
697
|
+
},
|
|
698
|
+
failedFiles,
|
|
699
|
+
reportOnly: options.skipWhenNoRetainedFile === true && noRetainedFile,
|
|
700
|
+
});
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (
|
|
704
|
+
options.skipWhenNoRetainedFile === true &&
|
|
705
|
+
noRetainedFile &&
|
|
706
|
+
snapshot.statusReason === "process-crashed"
|
|
707
|
+
) {
|
|
708
|
+
immediate.push({
|
|
709
|
+
route: {
|
|
710
|
+
name: snapshot.name,
|
|
711
|
+
root: snapshot.root,
|
|
712
|
+
outcome: "skipped-no-retained-file",
|
|
713
|
+
},
|
|
714
|
+
failedFiles,
|
|
715
|
+
reportOnly: true,
|
|
716
|
+
});
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
required.push({ snapshot, failedFiles });
|
|
720
|
+
}
|
|
721
|
+
return { immediate, required };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
private snapshotProcessCrashRoutes(): ProcessCrashRecoverySnapshot[] {
|
|
725
|
+
return Array.from(this.processCrashRecoveries.entries()).flatMap(([key, recovery]) => {
|
|
726
|
+
if (recovery.statusReason === undefined) return [];
|
|
727
|
+
return [
|
|
728
|
+
{
|
|
729
|
+
key,
|
|
730
|
+
recovery,
|
|
731
|
+
failedClient: recovery.failedClient,
|
|
732
|
+
name: recovery.failedClient.name,
|
|
733
|
+
root: recovery.failedClient.root,
|
|
734
|
+
statusReason: recovery.statusReason,
|
|
735
|
+
files: [...recovery.files],
|
|
736
|
+
},
|
|
737
|
+
];
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
private formatProcessCrashDemandFailure(
|
|
742
|
+
recovery: ProcessCrashRecoveryState | ProcessCrashRecoveryRouteResult,
|
|
743
|
+
outcome?: ProcessCrashRecoveryRouteResult["outcome"],
|
|
744
|
+
): string {
|
|
745
|
+
const isRouteResult = "outcome" in recovery;
|
|
746
|
+
const name = isRouteResult ? recovery.name : recovery.failedClient.name;
|
|
747
|
+
const root = isRouteResult
|
|
748
|
+
? recovery.root
|
|
749
|
+
: (path.relative(this.cwd, recovery.failedClient.root) || ".").replaceAll("\\", "/");
|
|
750
|
+
const routeOutcome =
|
|
751
|
+
outcome ??
|
|
752
|
+
(isRouteResult
|
|
753
|
+
? recovery.outcome
|
|
754
|
+
: recovery.statusReason === "process-crash-recovery-exhausted"
|
|
755
|
+
? "recovery-exhausted"
|
|
756
|
+
: "recovery-failed");
|
|
603
757
|
const reason =
|
|
604
|
-
|
|
758
|
+
routeOutcome === "recovery-exhausted"
|
|
605
759
|
? "process recovery exhausted; reload required"
|
|
606
|
-
:
|
|
607
|
-
|
|
608
|
-
: "process crashed";
|
|
609
|
-
return `${recovery.failedClient.name} @ ${root} is unavailable — ${reason}.`;
|
|
760
|
+
: "process recovery failed";
|
|
761
|
+
return `${name} @ ${root} is unavailable — ${reason}.`;
|
|
610
762
|
}
|
|
611
763
|
|
|
612
764
|
private startProcessCrashRecovery(
|
|
@@ -670,6 +822,7 @@ export class LspManager {
|
|
|
670
822
|
|
|
671
823
|
this.unavailable.delete(key);
|
|
672
824
|
recovery.statusReason = undefined;
|
|
825
|
+
recovery.failureMessage = undefined;
|
|
673
826
|
this.publishLifecycle("recovery");
|
|
674
827
|
this.restoreTrackedDocuments(started, recovery.files);
|
|
675
828
|
this.recordProcessCrashRecoveryEvent(
|
|
@@ -716,7 +869,7 @@ export class LspManager {
|
|
|
716
869
|
// LspClient's initialize request already has the 30-second transport
|
|
717
870
|
// bound. Do not apply the 5-second diagnostic-restart bound here.
|
|
718
871
|
await replacement.start();
|
|
719
|
-
} catch {
|
|
872
|
+
} catch (error) {
|
|
720
873
|
if (this.isProcessCrashRecoveryCancelled(recovery, route.key)) {
|
|
721
874
|
await replacement.forceKill().catch(() => {});
|
|
722
875
|
this.recordProcessCrashRecoveryEvent(
|
|
@@ -728,7 +881,7 @@ export class LspManager {
|
|
|
728
881
|
return null;
|
|
729
882
|
}
|
|
730
883
|
if (recovery.statusReason !== "process-crash-recovery-exhausted") {
|
|
731
|
-
this.markProcessCrashRecoveryFailed(recovery, route.key, replacement);
|
|
884
|
+
this.markProcessCrashRecoveryFailed(recovery, route.key, replacement, error);
|
|
732
885
|
this.recordProcessCrashRecoveryEvent(
|
|
733
886
|
route.serverName,
|
|
734
887
|
route.root,
|
|
@@ -776,8 +929,13 @@ export class LspManager {
|
|
|
776
929
|
recovery: ProcessCrashRecoveryState,
|
|
777
930
|
key: string,
|
|
778
931
|
replacement: LspClient,
|
|
932
|
+
error: unknown,
|
|
779
933
|
): void {
|
|
780
934
|
recovery.statusReason = "process-crash-recovery-exhausted";
|
|
935
|
+
recovery.failureMessage =
|
|
936
|
+
error instanceof Error && error.message
|
|
937
|
+
? boundProcessCrashFailureMessage(error.message)
|
|
938
|
+
: undefined;
|
|
781
939
|
this.unavailable.set(key, "start-failed");
|
|
782
940
|
if (this.clients.get(key) === replacement) this.clients.delete(key);
|
|
783
941
|
// A failed initialize may still leave a child process alive. The
|
|
@@ -789,7 +947,7 @@ export class LspManager {
|
|
|
789
947
|
private recordProcessCrashRecoveryEvent(
|
|
790
948
|
serverName: string,
|
|
791
949
|
root: string,
|
|
792
|
-
outcome:
|
|
950
|
+
outcome: ProcessCrashRecoveryTelemetryOutcome,
|
|
793
951
|
startedAt?: number,
|
|
794
952
|
): void {
|
|
795
953
|
try {
|
|
@@ -1065,33 +1223,103 @@ export class LspManager {
|
|
|
1065
1223
|
async waitUntilFileReady(
|
|
1066
1224
|
filePath: string,
|
|
1067
1225
|
control?: CodeRequestControl,
|
|
1068
|
-
onProcessCrashRecovery?: (
|
|
1226
|
+
onProcessCrashRecovery?: (report: ProcessCrashRecoveryReport) => void,
|
|
1069
1227
|
): Promise<FileReadinessResult> {
|
|
1070
1228
|
throwIfCodeRequestInterrupted(control);
|
|
1071
1229
|
const resolvedPath = resolveSessionPath(this.cwd, filePath);
|
|
1072
1230
|
const route = this.resolveFileRoute(resolvedPath);
|
|
1073
1231
|
const recovery = route ? this.processCrashRecoveries.get(route.key) : undefined;
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
if (processCrashRecoveryRequested) {
|
|
1077
|
-
onProcessCrashRecovery?.(summarizeProcessCrashRecoveryRoutes(["pending"]));
|
|
1078
|
-
}
|
|
1232
|
+
const statusAtStart = recovery?.statusReason;
|
|
1233
|
+
const processCrashRecoveryRequested = statusAtStart !== undefined;
|
|
1079
1234
|
const client = await this.getClientForFile(resolvedPath, {
|
|
1080
1235
|
recoverProcessCrash: true,
|
|
1081
1236
|
control,
|
|
1082
1237
|
});
|
|
1083
|
-
const processCrashRecovery =
|
|
1084
|
-
|
|
1085
|
-
|
|
1238
|
+
const processCrashRecovery = this.buildFileProcessCrashRecoveryReport(
|
|
1239
|
+
route,
|
|
1240
|
+
recovery,
|
|
1241
|
+
statusAtStart,
|
|
1242
|
+
client,
|
|
1243
|
+
);
|
|
1244
|
+
if (processCrashRecoveryRequested) onProcessCrashRecovery?.(processCrashRecovery);
|
|
1086
1245
|
if (!client) return { client: null, processCrashRecovery };
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1246
|
+
try {
|
|
1247
|
+
await client.getReady(control);
|
|
1248
|
+
await this.warmSemanticProject(client, resolvedPath, true, control);
|
|
1249
|
+
throwIfCodeRequestInterrupted(control);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
const failedRecovery = this.buildFileProcessCrashFailureReport(
|
|
1252
|
+
route,
|
|
1253
|
+
recovery,
|
|
1254
|
+
statusAtStart,
|
|
1255
|
+
error,
|
|
1256
|
+
);
|
|
1257
|
+
if (failedRecovery) {
|
|
1258
|
+
onProcessCrashRecovery?.(failedRecovery);
|
|
1259
|
+
return { client: null, processCrashRecovery: failedRecovery };
|
|
1260
|
+
}
|
|
1261
|
+
throw error;
|
|
1262
|
+
}
|
|
1092
1263
|
return { client, processCrashRecovery };
|
|
1093
1264
|
}
|
|
1094
1265
|
|
|
1266
|
+
private buildFileProcessCrashRecoveryReport(
|
|
1267
|
+
route: FileRoute | null,
|
|
1268
|
+
recovery: ProcessCrashRecoveryState | undefined,
|
|
1269
|
+
statusAtStart: ProcessCrashRecoveryState["statusReason"],
|
|
1270
|
+
client: LspClient | null,
|
|
1271
|
+
): ProcessCrashRecoveryReport {
|
|
1272
|
+
if (statusAtStart === undefined) return emptyProcessCrashRecoveryReport();
|
|
1273
|
+
return buildProcessCrashRecoveryReport(
|
|
1274
|
+
[
|
|
1275
|
+
{
|
|
1276
|
+
name: route?.serverName ?? "unknown",
|
|
1277
|
+
root: route?.root ?? this.cwd,
|
|
1278
|
+
outcome:
|
|
1279
|
+
statusAtStart === "process-crash-recovery-exhausted"
|
|
1280
|
+
? "recovery-exhausted"
|
|
1281
|
+
: client
|
|
1282
|
+
? "recovered"
|
|
1283
|
+
: "recovery-failed",
|
|
1284
|
+
...(client || !recovery?.failureMessage
|
|
1285
|
+
? {}
|
|
1286
|
+
: { failureMessage: recovery.failureMessage }),
|
|
1287
|
+
},
|
|
1288
|
+
],
|
|
1289
|
+
this.cwd,
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
private buildFileProcessCrashFailureReport(
|
|
1294
|
+
route: FileRoute | null,
|
|
1295
|
+
recovery: ProcessCrashRecoveryState | undefined,
|
|
1296
|
+
statusAtStart: ProcessCrashRecoveryState["statusReason"],
|
|
1297
|
+
error: unknown,
|
|
1298
|
+
): ProcessCrashRecoveryReport | null {
|
|
1299
|
+
if (
|
|
1300
|
+
statusAtStart === undefined ||
|
|
1301
|
+
statusAtStart === "process-crash-recovery-exhausted" ||
|
|
1302
|
+
recovery?.statusReason !== "process-crash-recovery-exhausted"
|
|
1303
|
+
) {
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
return buildProcessCrashRecoveryReport(
|
|
1307
|
+
[
|
|
1308
|
+
{
|
|
1309
|
+
name: route?.serverName ?? "unknown",
|
|
1310
|
+
root: route?.root ?? this.cwd,
|
|
1311
|
+
outcome: "recovery-failed",
|
|
1312
|
+
...(error instanceof Error && error.message
|
|
1313
|
+
? { failureMessage: boundProcessCrashFailureMessage(error.message) }
|
|
1314
|
+
: recovery.failureMessage
|
|
1315
|
+
? { failureMessage: recovery.failureMessage }
|
|
1316
|
+
: {}),
|
|
1317
|
+
},
|
|
1318
|
+
],
|
|
1319
|
+
this.cwd,
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1095
1323
|
/**
|
|
1096
1324
|
* Wait until one started client is query-ready, then warm its project.
|
|
1097
1325
|
* Returns the number of concrete clients that are ready after the warm-up.
|
|
@@ -1245,18 +1473,26 @@ export class LspManager {
|
|
|
1245
1473
|
processCrashDemand?: { scopes?: readonly string[] };
|
|
1246
1474
|
control?: CodeRequestControl;
|
|
1247
1475
|
}): Promise<WorkspaceRecoveryResult> {
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1476
|
+
const processCrashDemand = options?.processCrashDemand;
|
|
1477
|
+
const demand = processCrashDemand
|
|
1478
|
+
? await this.recoverProcessCrashDemand(
|
|
1479
|
+
(snapshot) => {
|
|
1480
|
+
if (!this.processCrashDiagnosticRouteInScope(snapshot, processCrashDemand)) {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
return this.processCrashDiagnosticFiles(snapshot, processCrashDemand);
|
|
1484
|
+
},
|
|
1485
|
+
options?.control,
|
|
1486
|
+
{ skipWhenNoRetainedFile: true },
|
|
1487
|
+
)
|
|
1253
1488
|
: {
|
|
1254
1489
|
hasSupport: false,
|
|
1255
|
-
|
|
1490
|
+
requiresFreshEvidence: false,
|
|
1491
|
+
processCrashRecovery: emptyProcessCrashRecoveryReport(),
|
|
1256
1492
|
failures: [],
|
|
1257
1493
|
failedFiles: [],
|
|
1258
1494
|
};
|
|
1259
|
-
const recoveryOptions = demand.
|
|
1495
|
+
const recoveryOptions = demand.requiresFreshEvidence
|
|
1260
1496
|
? { ...options, initialEvidence: undefined }
|
|
1261
1497
|
: options;
|
|
1262
1498
|
const result = await recoverWorkspaceDiagnosticsImpl(this, recoveryOptions);
|
|
@@ -1266,21 +1502,34 @@ export class LspManager {
|
|
|
1266
1502
|
);
|
|
1267
1503
|
}
|
|
1268
1504
|
|
|
1505
|
+
private processCrashDiagnosticRouteInScope(
|
|
1506
|
+
snapshot: ProcessCrashRecoverySnapshot,
|
|
1507
|
+
demand: { scopes?: readonly string[] },
|
|
1508
|
+
): boolean {
|
|
1509
|
+
return this.processCrashDiagnosticScopes(demand).some(
|
|
1510
|
+
(scope) =>
|
|
1511
|
+
projectRoots.isWithinOrEqual(scope, snapshot.root) ||
|
|
1512
|
+
projectRoots.isWithinOrEqual(snapshot.root, scope),
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1269
1516
|
private processCrashDiagnosticFiles(
|
|
1270
|
-
|
|
1271
|
-
demand: { scopes?: readonly string[] }
|
|
1517
|
+
snapshot: ProcessCrashRecoverySnapshot,
|
|
1518
|
+
demand: { scopes?: readonly string[] },
|
|
1272
1519
|
): string[] {
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
return recovery.files.filter(
|
|
1520
|
+
const scopes = this.processCrashDiagnosticScopes(demand);
|
|
1521
|
+
return snapshot.files.filter(
|
|
1276
1522
|
(file) =>
|
|
1277
1523
|
this.isDiagnosticFile(file) &&
|
|
1278
|
-
(
|
|
1279
|
-
scopes.length === 0 ||
|
|
1280
|
-
scopes.some((scope) => projectRoots.isWithinOrEqual(scope, file))),
|
|
1524
|
+
scopes.some((scope) => projectRoots.isWithinOrEqual(scope, file)),
|
|
1281
1525
|
);
|
|
1282
1526
|
}
|
|
1283
1527
|
|
|
1528
|
+
private processCrashDiagnosticScopes(demand: { scopes?: readonly string[] }): string[] {
|
|
1529
|
+
const scopes = demand.scopes?.map((scope) => resolveSessionPath(this.cwd, scope));
|
|
1530
|
+
return scopes && scopes.length > 0 ? scopes : [this.cwd];
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1284
1533
|
private addProcessCrashDiagnosticEvidence(
|
|
1285
1534
|
result: WorkspaceRecoveryResult,
|
|
1286
1535
|
demand: ProcessCrashDemandResult,
|
|
@@ -1665,9 +1914,9 @@ export class LspManager {
|
|
|
1665
1914
|
);
|
|
1666
1915
|
}
|
|
1667
1916
|
const demand = await this.recoverProcessCrashDemand(
|
|
1668
|
-
(
|
|
1669
|
-
|
|
1670
|
-
routeMatches(
|
|
1917
|
+
(snapshot) =>
|
|
1918
|
+
snapshot.failedClient.serverCapabilities?.workspaceSymbolProvider &&
|
|
1919
|
+
routeMatches(snapshot.failedClient)
|
|
1671
1920
|
? []
|
|
1672
1921
|
: null,
|
|
1673
1922
|
control,
|