@mrclrchtr/supi-code-intelligence 6.4.0 → 7.0.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 +164 -82
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
- 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-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +6 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +608 -45
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +19 -3
- 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-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- 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/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/package.json +1 -1
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +1 -2
- package/src/analysis/health/file-scope.ts +4 -1
- package/src/analysis/health/server-status.ts +1 -1
- package/src/analysis/relations/callees.ts +17 -8
- package/src/analysis/relations/implementations.ts +1 -2
- package/src/analysis/relations/references.ts +8 -4
- package/src/analysis/relations/types.ts +6 -30
- package/src/analysis/target/anchored.ts +9 -1
- package/src/analysis/target/symbol.ts +29 -11
- package/src/analysis/target/types.ts +14 -6
- package/src/api.ts +2 -0
- package/src/session/graph/collect.ts +18 -3
- package/src/session/health-refresh.ts +12 -1
- package/src/session/health-types.ts +18 -6
- package/src/session/health-workflow.ts +21 -19
- package/src/session/orientation-types.ts +5 -6
- package/src/session/orientation-workflow.ts +10 -2
- package/src/session/target-workflow.ts +9 -6
- package/src/substrate/lsp/source-tracking.ts +11 -6
- package/src/tool/code_find/guidance.ts +2 -2
- package/src/tool/code_find/spec.ts +5 -4
- package/src/tool/code_find/tui.ts +7 -1
- package/src/tool/code_graph/details.ts +32 -0
- package/src/tool/code_graph/display.ts +54 -0
- package/src/tool/code_graph/format.ts +60 -0
- package/src/tool/code_graph/guidance.ts +1 -1
- package/src/tool/code_graph/markdown.ts +76 -198
- package/src/tool/code_graph/read-next.ts +65 -0
- package/src/tool/code_graph/result.ts +118 -83
- package/src/tool/code_graph/tui.ts +216 -54
- package/src/tool/code_health/guidance.ts +2 -4
- package/src/tool/code_health/markdown.ts +75 -58
- package/src/tool/code_health/refresh-outcome.ts +41 -1
- package/src/tool/code_health/refresh-status.ts +114 -48
- package/src/tool/code_health/scope.ts +78 -0
- package/src/tool/code_health/spec.ts +1 -1
- package/src/tool/code_health/tui.ts +25 -38
- package/src/tool/code_inspect/tui.ts +7 -1
- package/src/tool/code_orientation/result.ts +31 -8
- package/src/tool/code_orientation/tui.ts +26 -5
- package/src/tool/code_refactor_apply/guidance.ts +1 -1
- package/src/tool/code_refactor_apply/tui.ts +7 -3
- package/src/tool/code_refactor_plan/guidance.ts +1 -1
- package/src/tool/code_refactor_plan/tui.ts +7 -1
- package/src/tool/code_resolve/guidance.ts +2 -4
- package/src/tool/code_resolve/result.ts +13 -10
- package/src/tool/code_resolve/tui.ts +18 -1
- package/src/tool/infra/truncate.ts +39 -13
- package/src/tool/result/candidate-row.ts +27 -0
- package/src/tool/result/errors.ts +21 -6
- package/src/tool/result/types.ts +7 -3
- package/src/tool/schemas.ts +5 -0
- package/src/types/execution.ts +2 -0
- package/src/types/index.ts +2 -0
- package/src/ui/tui/candidate-legacy-body.ts +69 -0
- package/src/ui/tui/candidate-selection.ts +366 -0
- package/src/ui/tui/common.ts +26 -31
- package/src/ui/tui/execution-error.ts +92 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type CodeQueryResult,
|
|
5
5
|
type CodeRequestControl,
|
|
6
6
|
completedCodeQuery,
|
|
7
|
+
isCodeRequestInterruption,
|
|
7
8
|
throwIfCodeRequestInterrupted,
|
|
8
9
|
unavailableCodeQuery,
|
|
9
10
|
} from "@mrclrchtr/supi-code-runtime/api";
|
|
@@ -17,22 +18,24 @@ import {
|
|
|
17
18
|
type DiagnosticCacheEntry,
|
|
18
19
|
type DiagnosticSynchronization,
|
|
19
20
|
hasCurrentEvidence,
|
|
20
|
-
|
|
21
|
+
hasFreshEvidence,
|
|
22
|
+
incompleteDiagnosticResult,
|
|
21
23
|
isCurrentSynchronization,
|
|
22
24
|
isValidPublishDiagnosticsParams,
|
|
23
25
|
nextDocumentVersion,
|
|
24
26
|
} from "./client-diagnostic-evidence.ts";
|
|
25
27
|
import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
|
|
28
|
+
import { DiagnosticPublicationTracker } from "./client-diagnostic-publication.ts";
|
|
29
|
+
import { startDiagnosticEvidenceFromAdapter } from "./client-diagnostic-pull.ts";
|
|
26
30
|
import {
|
|
27
|
-
DiagnosticPublicationTracker,
|
|
28
|
-
trackerFileIdentityFor,
|
|
29
|
-
} from "./client-diagnostic-publication.ts";
|
|
30
|
-
import {
|
|
31
|
-
pullClientDiagnosticEvidenceFromHost,
|
|
32
31
|
refreshClientOpenDiagnostics,
|
|
33
32
|
sendDidCloseNotification,
|
|
34
33
|
} from "./client-diagnostic-refresh.ts";
|
|
35
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
DiagnosticRequestInvalidatedError,
|
|
36
|
+
DiagnosticRequestScheduler,
|
|
37
|
+
} from "./client-diagnostic-request.ts";
|
|
38
|
+
import { DiagnosticObserver } from "./client-diagnostic-timing.ts";
|
|
36
39
|
import { DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
|
|
37
40
|
import {
|
|
38
41
|
type ClientDiagnosticSnapshot,
|
|
@@ -41,21 +44,54 @@ import {
|
|
|
41
44
|
hasConfirmedDiagnosticEvidence,
|
|
42
45
|
type OpenDocumentState,
|
|
43
46
|
} from "./client-document-state.ts";
|
|
44
|
-
import {
|
|
45
|
-
clearTrackedDocumentState,
|
|
46
|
-
reopenDocument,
|
|
47
|
-
synchronizeTrackedDocument,
|
|
48
|
-
} from "./client-document-sync.ts";
|
|
47
|
+
import { clearTrackedDocumentState, synchronizeTrackedDocument } from "./client-document-sync.ts";
|
|
49
48
|
import { getDiagnosticFileState } from "./client-file-state.ts";
|
|
49
|
+
import { SemanticInputRequestRetryGuard } from "./client-request-enrollment.ts";
|
|
50
|
+
import {
|
|
51
|
+
SemanticInputBarrier,
|
|
52
|
+
type SemanticInputDocument,
|
|
53
|
+
type SemanticInputSnapshot,
|
|
54
|
+
type SemanticInputUpdate,
|
|
55
|
+
} from "./client-semantic-input-barrier.ts";
|
|
56
|
+
import type {
|
|
57
|
+
SemanticInputChangeKind,
|
|
58
|
+
SemanticInputSynchronizationError,
|
|
59
|
+
} from "./client-semantic-input-errors.ts";
|
|
60
|
+
import { isSemanticInputEnrollmentError } from "./client-semantic-input-errors.ts";
|
|
50
61
|
|
|
51
62
|
const DIAGNOSTIC_WAIT_MS = 3_000;
|
|
52
|
-
/**
|
|
53
|
-
const
|
|
63
|
+
/** Bound rejoin attempts for one synchronization pass, separate from request retries. */
|
|
64
|
+
const MAX_SEMANTIC_INPUT_SYNCHRONIZATION_RETRIES = 1;
|
|
65
|
+
|
|
66
|
+
/** Bound abandoned adapter work without binding it to one caller's deadline. */
|
|
67
|
+
const DIAGNOSTIC_REQUEST_OWNER_TIMEOUT_MS = 30_000;
|
|
68
|
+
|
|
69
|
+
interface ServerDiagnosticRefreshWork {
|
|
70
|
+
promise: Promise<DiagnosticEvidenceSummary>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type DiagnosticEvidenceCandidate = DiagnosticSynchronization & {
|
|
74
|
+
readonly evidenceRevision: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
interface DiagnosticQueryAttemptState {
|
|
78
|
+
inputRevision: number | undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function unavailableAfterEnrollmentRetry(error: unknown): CodeQueryResult<Diagnostic[]> {
|
|
82
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
83
|
+
return unavailableCodeQuery(`Diagnostic collection failed closed: ${detail}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
54
86
|
/** Own one client's document and diagnostic evidence; revisions prevent stale reuse. */
|
|
55
87
|
export class ClientDiagnostics {
|
|
56
88
|
readonly #openDocs = new Map<string, OpenDocumentState>();
|
|
57
89
|
readonly #diagnosticStore = new Map<string, DiagnosticCacheEntry>();
|
|
58
90
|
readonly #waiters = new DiagnosticWaitRegistry();
|
|
91
|
+
/** One shared, bounded request engine for this client route. */
|
|
92
|
+
readonly #requestScheduler = new DiagnosticRequestScheduler();
|
|
93
|
+
/** Invalidation signals for active per-file adapter collections. */
|
|
94
|
+
readonly #requestControllers = new Map<string, AbortController>();
|
|
59
95
|
readonly #versionHistory = new Map<string, number>();
|
|
60
96
|
readonly #failedUris = new Set<string>();
|
|
61
97
|
/** Bounded push-publication telemetry for one client's diagnostic state. */
|
|
@@ -64,16 +100,160 @@ export class ClientDiagnostics {
|
|
|
64
100
|
readonly #unversionedPushSyncMoments = new Map<string, number>();
|
|
65
101
|
/** URIs closed by a lifecycle operation: versioned pushes stay fail-closed. */
|
|
66
102
|
readonly #closedVersionedBarrier = new Set<string>();
|
|
103
|
+
readonly #inputBarrier: SemanticInputBarrier;
|
|
67
104
|
#evidenceRevision = 0;
|
|
68
105
|
#nextSynchronizationId = 0;
|
|
106
|
+
/** One active server refresh plus one coalesced newer demand generation. */
|
|
107
|
+
#serverRefreshGeneration = 0;
|
|
108
|
+
#serverRefreshWork: ServerDiagnosticRefreshWork | undefined;
|
|
69
109
|
|
|
70
110
|
constructor(private readonly host: ClientDiagnosticsHost) {
|
|
71
|
-
this.#publications = new DiagnosticPublicationTracker(
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
this.#publications = new DiagnosticPublicationTracker({
|
|
112
|
+
server: host.server,
|
|
113
|
+
cwd: host.cwd,
|
|
114
|
+
});
|
|
115
|
+
this.#inputBarrier = new SemanticInputBarrier({
|
|
116
|
+
isOperational: () => this.host.isOperational(),
|
|
117
|
+
getOpenDocuments: () => this.#getSemanticInputDocuments(),
|
|
118
|
+
applyDocumentUpdates: (updates) => this.#applySemanticInputUpdates(updates),
|
|
119
|
+
closeMissingDocument: (filePath) => this.didClose(filePath),
|
|
120
|
+
markUnreadableDocument: (filePath) => this.markFailedFile(filePath),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Synchronize all open document inputs, rejoining once after a new enrollment. */
|
|
125
|
+
async synchronizeSemanticInputs(
|
|
126
|
+
control?: CodeRequestControl,
|
|
127
|
+
contentOverrides?: ReadonlyMap<string, string>,
|
|
128
|
+
): Promise<SemanticInputSnapshot> {
|
|
129
|
+
throwIfCodeRequestInterrupted(control);
|
|
130
|
+
for (let synchronizationRetry = 0; ; synchronizationRetry++) {
|
|
131
|
+
try {
|
|
132
|
+
return await this.#inputBarrier.synchronize(control, contentOverrides);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (
|
|
135
|
+
synchronizationRetry >= MAX_SEMANTIC_INPUT_SYNCHRONIZATION_RETRIES ||
|
|
136
|
+
!isSemanticInputEnrollmentError(error) ||
|
|
137
|
+
!this.host.isOperational()
|
|
138
|
+
) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
throwIfCodeRequestInterrupted(control);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Return the current typed input change after a request revision, when present. */
|
|
147
|
+
getSemanticInputChangeSince(revision: number): SemanticInputSynchronizationError | undefined {
|
|
148
|
+
return this.#inputBarrier.getChangeSince(
|
|
149
|
+
revision,
|
|
150
|
+
"Semantic input changed while the request was running.",
|
|
74
151
|
);
|
|
75
152
|
}
|
|
76
153
|
|
|
154
|
+
/** Fail closed when a semantic request observes a changed input generation. */
|
|
155
|
+
assertSemanticInputsCurrent(
|
|
156
|
+
snapshot: SemanticInputSnapshot,
|
|
157
|
+
control?: CodeRequestControl,
|
|
158
|
+
contentOverrides?: ReadonlyMap<string, string>,
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
throwIfCodeRequestInterrupted(control);
|
|
161
|
+
return this.#inputBarrier.assertCurrent(snapshot, control, contentOverrides);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#getSemanticInputDocuments(): SemanticInputDocument[] {
|
|
165
|
+
return Array.from(this.#openDocs, ([uri, document]) => ({
|
|
166
|
+
uri,
|
|
167
|
+
filePath: uriToFile(uri),
|
|
168
|
+
content: document.content,
|
|
169
|
+
contentFingerprint: document.contentFingerprint,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#applySemanticInputUpdates(updates: readonly SemanticInputUpdate[]): void {
|
|
174
|
+
if (updates.length === 0) return;
|
|
175
|
+
this.#advanceEvidenceRevision();
|
|
176
|
+
for (const update of updates) {
|
|
177
|
+
const document = this.#openDocs.get(update.document.uri);
|
|
178
|
+
if (!document) throw new Error("Semantic input changed while synchronization was running.");
|
|
179
|
+
this.#synchronizeTrackedDocument(
|
|
180
|
+
update.document.uri,
|
|
181
|
+
update.document.filePath,
|
|
182
|
+
update.content,
|
|
183
|
+
document,
|
|
184
|
+
);
|
|
185
|
+
this.#failedUris.delete(update.document.uri);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Apply one tracked-document change with the shared client state. */
|
|
190
|
+
#synchronizeTrackedDocument(
|
|
191
|
+
uri: string,
|
|
192
|
+
filePath: string,
|
|
193
|
+
content: string,
|
|
194
|
+
document: OpenDocumentState,
|
|
195
|
+
): void {
|
|
196
|
+
synchronizeTrackedDocument({
|
|
197
|
+
uri,
|
|
198
|
+
content,
|
|
199
|
+
document,
|
|
200
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
201
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
202
|
+
evidenceRevision: this.#evidenceRevision,
|
|
203
|
+
incrementalSync: this.host.usesIncrementalDocumentSync(),
|
|
204
|
+
waiters: this.#waiters,
|
|
205
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
206
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
207
|
+
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
208
|
+
open: () => this.didOpen(filePath, content),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#initializeDocumentContent(uri: string, content: string): void {
|
|
213
|
+
this.#inputBarrier.initializeDocumentContent(uri, content);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#observeDiskContent(uri: string, content: string): void {
|
|
217
|
+
this.#inputBarrier.observeDiskContent(uri, content);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#forgetDocumentContent(uri: string): void {
|
|
221
|
+
this.#inputBarrier.forgetDocumentContent(uri);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#ensureExplicitDocumentContent(
|
|
225
|
+
filePath: string,
|
|
226
|
+
content: string,
|
|
227
|
+
contentIsAuthoritative: boolean,
|
|
228
|
+
): void {
|
|
229
|
+
const uri = fileToUri(filePath);
|
|
230
|
+
const document = this.#openDocs.get(uri);
|
|
231
|
+
if (!document) {
|
|
232
|
+
// An untracked file needs an initial protocol document before its barrier
|
|
233
|
+
// read can verify the manager's content.
|
|
234
|
+
this.didOpen(filePath, content);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Manager reads are not authoritative: an already tracked document can
|
|
238
|
+
// change between that read and the barrier's verified read.
|
|
239
|
+
if (
|
|
240
|
+
!contentIsAuthoritative ||
|
|
241
|
+
document.contentFingerprint === fingerprintDocumentContent(content)
|
|
242
|
+
) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
this.#cancelDiagnosticRequest(uri);
|
|
246
|
+
this.didChange(filePath, content);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#getDiagnosticContentOverrides(
|
|
250
|
+
uri: string,
|
|
251
|
+
content: string,
|
|
252
|
+
contentIsAuthoritative: boolean,
|
|
253
|
+
): ReadonlyMap<string, string> | undefined {
|
|
254
|
+
return contentIsAuthoritative ? new Map([[uri, content]]) : undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
77
257
|
get openFiles(): string[] {
|
|
78
258
|
return Array.from(this.#openDocs.keys()).map(uriToFile);
|
|
79
259
|
}
|
|
@@ -91,10 +271,12 @@ export class ClientDiagnostics {
|
|
|
91
271
|
this.#openDocs.clear();
|
|
92
272
|
this.#diagnosticStore.clear();
|
|
93
273
|
this.#versionHistory.clear();
|
|
274
|
+
this.#inputBarrier.clear();
|
|
94
275
|
this.#evidenceRevision++;
|
|
95
276
|
this.#unversionedPushSyncMoments.clear();
|
|
96
277
|
this.#waiters.releaseAll();
|
|
97
278
|
this.#waiters.cancelSettle();
|
|
279
|
+
this.#cancelAllDiagnosticRequests();
|
|
98
280
|
}
|
|
99
281
|
|
|
100
282
|
didOpen(filePath: string, content: string): void {
|
|
@@ -103,9 +285,14 @@ export class ClientDiagnostics {
|
|
|
103
285
|
const uri = fileToUri(filePath);
|
|
104
286
|
this.#failedUris.delete(uri);
|
|
105
287
|
if (this.#openDocs.has(uri)) {
|
|
288
|
+
// An equivalent duplicate open is a no-op. A real content change is
|
|
289
|
+
// cancelled by didChange before it advances the synchronization.
|
|
106
290
|
this.didChange(filePath, content);
|
|
107
291
|
return;
|
|
108
292
|
}
|
|
293
|
+
this.#initializeDocumentContent(uri, content);
|
|
294
|
+
this.#cancelDiagnosticRequest(uri);
|
|
295
|
+
this.#advanceInputRevision(false, false, "enrollment");
|
|
109
296
|
|
|
110
297
|
const languageId = detectLanguageId(filePath);
|
|
111
298
|
this.#waiters.cancelSettle();
|
|
@@ -142,29 +329,25 @@ export class ClientDiagnostics {
|
|
|
142
329
|
}
|
|
143
330
|
const nextFingerprint = fingerprintDocumentContent(content);
|
|
144
331
|
if (doc.contentFingerprint === nextFingerprint) return;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
content,
|
|
148
|
-
document: doc,
|
|
149
|
-
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
150
|
-
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
151
|
-
evidenceRevision: this.#evidenceRevision,
|
|
152
|
-
incrementalSync: this.host.usesIncrementalDocumentSync(),
|
|
153
|
-
waiters: this.#waiters,
|
|
154
|
-
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
155
|
-
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
156
|
-
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
157
|
-
open: () => this.didOpen(filePath, content),
|
|
158
|
-
});
|
|
332
|
+
this.#advanceInputRevision();
|
|
333
|
+
this.#synchronizeTrackedDocument(uri, filePath, content, doc);
|
|
159
334
|
}
|
|
160
335
|
|
|
161
336
|
didClose(filePath: string): void {
|
|
162
337
|
const uri = fileToUri(filePath);
|
|
163
338
|
const wasOpen = this.#openDocs.has(uri);
|
|
339
|
+
const hadState =
|
|
340
|
+
wasOpen ||
|
|
341
|
+
this.#diagnosticStore.has(uri) ||
|
|
342
|
+
this.#failedUris.has(uri) ||
|
|
343
|
+
this.#versionHistory.has(uri);
|
|
344
|
+
if (hadState) this.#advanceInputRevision(true, true, "close");
|
|
164
345
|
this.#failedUris.delete(uri);
|
|
346
|
+
this.#forgetDocumentContent(uri);
|
|
165
347
|
this.#unversionedPushSyncMoments.delete(uri);
|
|
166
348
|
this.#closedVersionedBarrier.add(uri);
|
|
167
349
|
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
350
|
+
this.#cancelDiagnosticRequest(uri);
|
|
168
351
|
|
|
169
352
|
if (wasOpen && this.host.isOperational()) {
|
|
170
353
|
sendDidCloseNotification(this.host, uri);
|
|
@@ -184,9 +367,12 @@ export class ClientDiagnostics {
|
|
|
184
367
|
if (getDiagnosticFileState(filePath) !== "removed") continue;
|
|
185
368
|
|
|
186
369
|
const wasOpen = this.#openDocs.has(uri);
|
|
370
|
+
this.#advanceInputRevision(true, true, "close");
|
|
187
371
|
this.#failedUris.delete(uri);
|
|
372
|
+
this.#forgetDocumentContent(uri);
|
|
188
373
|
this.#unversionedPushSyncMoments.delete(uri);
|
|
189
374
|
this.#closedVersionedBarrier.add(uri);
|
|
375
|
+
this.#cancelDiagnosticRequest(uri);
|
|
190
376
|
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
191
377
|
removedFiles.push(filePath);
|
|
192
378
|
if (wasOpen && this.host.isOperational()) sendDidCloseNotification(this.host, uri);
|
|
@@ -197,6 +383,7 @@ export class ClientDiagnostics {
|
|
|
197
383
|
/** Retain a failed document outcome when a replacement cannot reopen it. */
|
|
198
384
|
markFailedFile(filePath: string): void {
|
|
199
385
|
const uri = fileToUri(filePath);
|
|
386
|
+
if (!this.#failedUris.has(uri)) this.#advanceInputRevision(true, true, "failure");
|
|
200
387
|
this.#failedUris.add(uri);
|
|
201
388
|
this.#unversionedPushSyncMoments.delete(uri);
|
|
202
389
|
this.#closedVersionedBarrier.add(uri);
|
|
@@ -224,7 +411,8 @@ export class ClientDiagnostics {
|
|
|
224
411
|
}
|
|
225
412
|
/** Invalidate cache proof while retaining its data as partial fallback. */
|
|
226
413
|
invalidateCachedEvidence(): void {
|
|
227
|
-
this.#
|
|
414
|
+
this.#advanceInputRevision();
|
|
415
|
+
this.#requestScheduler.clearPending();
|
|
228
416
|
const knownUris = new Set([
|
|
229
417
|
...this.#openDocs.keys(),
|
|
230
418
|
...this.#diagnosticStore.keys(),
|
|
@@ -236,6 +424,7 @@ export class ClientDiagnostics {
|
|
|
236
424
|
// pushes the server sends after the change can become fresh evidence.
|
|
237
425
|
const moment = Date.now();
|
|
238
426
|
for (const uri of knownUris) {
|
|
427
|
+
this.#cancelDiagnosticRequest(uri);
|
|
239
428
|
this.#unversionedPushSyncMoments.set(uri, moment);
|
|
240
429
|
this.#closedVersionedBarrier.add(uri);
|
|
241
430
|
}
|
|
@@ -243,6 +432,7 @@ export class ClientDiagnostics {
|
|
|
243
432
|
|
|
244
433
|
handlePublishDiagnostics(params: unknown): void {
|
|
245
434
|
if (!isValidPublishDiagnosticsParams(params)) return;
|
|
435
|
+
const previousEntry = this.#diagnosticStore.get(params.uri);
|
|
246
436
|
const result = applyPushDiagnostics({
|
|
247
437
|
store: this.#diagnosticStore,
|
|
248
438
|
openDocuments: this.#openDocs,
|
|
@@ -253,39 +443,74 @@ export class ClientDiagnostics {
|
|
|
253
443
|
});
|
|
254
444
|
if (!result.accepted) return;
|
|
255
445
|
const entry = this.#diagnosticStore.get(params.uri);
|
|
256
|
-
if (
|
|
446
|
+
if (
|
|
447
|
+
entry !== previousEntry &&
|
|
448
|
+
entry?.source === "push" &&
|
|
449
|
+
entry.synchronizationId !== undefined &&
|
|
450
|
+
entry.evidenceRevision !== undefined
|
|
451
|
+
) {
|
|
257
452
|
this.#publications.record(
|
|
258
453
|
params.uri,
|
|
259
454
|
entry.synchronizationId,
|
|
260
455
|
entry.evidenceRevision,
|
|
261
456
|
entry.receivedAt,
|
|
262
457
|
);
|
|
263
|
-
if (result.promoted) {
|
|
264
|
-
this.#publications.promoted(
|
|
265
|
-
params.uri,
|
|
266
|
-
entry.synchronizationId,
|
|
267
|
-
entry.evidenceRevision,
|
|
268
|
-
entry.receivedAt,
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
458
|
}
|
|
272
|
-
this.#waiters.releaseFile(params.uri, "
|
|
459
|
+
this.#waiters.releaseFile(params.uri, "observed");
|
|
273
460
|
this.#waiters.notifySettle();
|
|
274
461
|
}
|
|
275
462
|
async refreshOpenDiagnostics(
|
|
276
463
|
options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl = {},
|
|
277
464
|
): Promise<DiagnosticEvidenceSummary> {
|
|
278
|
-
|
|
465
|
+
// The refresh helper owns its read, classification, and budget pass. Do
|
|
466
|
+
// not run the query barrier first: one changed file could then invalidate
|
|
467
|
+
// the whole maintenance generation and resend unchanged text.
|
|
468
|
+
return this.#refreshOpenDiagnostics(options);
|
|
279
469
|
}
|
|
280
470
|
|
|
281
|
-
/**
|
|
282
|
-
|
|
283
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Refresh evidence after a server request without pretending input changed.
|
|
473
|
+
*
|
|
474
|
+
* A newer request invalidates the active evidence and joins the same owned
|
|
475
|
+
* work. The next pass starts only after active diagnostic transport settles.
|
|
476
|
+
*/
|
|
477
|
+
refreshForServerRequest(): Promise<DiagnosticEvidenceSummary> {
|
|
478
|
+
this.#serverRefreshGeneration++;
|
|
479
|
+
this.#invalidateDiagnosticEvidenceForServerRequest();
|
|
480
|
+
const active = this.#serverRefreshWork;
|
|
481
|
+
if (active) return active.promise;
|
|
482
|
+
|
|
483
|
+
const work: ServerDiagnosticRefreshWork = {
|
|
484
|
+
promise: undefined as unknown as Promise<DiagnosticEvidenceSummary>,
|
|
485
|
+
};
|
|
486
|
+
this.#serverRefreshWork = work;
|
|
487
|
+
work.promise = this.#runServerDiagnosticRefresh(work);
|
|
488
|
+
void work.promise.catch(() => {});
|
|
489
|
+
return work.promise;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async #runServerDiagnosticRefresh(
|
|
493
|
+
work: ServerDiagnosticRefreshWork,
|
|
494
|
+
): Promise<DiagnosticEvidenceSummary> {
|
|
495
|
+
for (;;) {
|
|
496
|
+
const generation = this.#serverRefreshGeneration;
|
|
497
|
+
try {
|
|
498
|
+
const evidence = await this.#refreshOpenDiagnostics({});
|
|
499
|
+
if (generation === this.#serverRefreshGeneration) {
|
|
500
|
+
if (this.#serverRefreshWork === work) this.#serverRefreshWork = undefined;
|
|
501
|
+
return evidence;
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
if (generation === this.#serverRefreshGeneration) {
|
|
505
|
+
if (this.#serverRefreshWork === work) this.#serverRefreshWork = undefined;
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
284
510
|
}
|
|
285
511
|
|
|
286
512
|
async #refreshOpenDiagnostics(
|
|
287
513
|
options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl,
|
|
288
|
-
forceResynchronize: boolean,
|
|
289
514
|
): Promise<DiagnosticEvidenceSummary> {
|
|
290
515
|
const requestedFiles = Array.from(
|
|
291
516
|
new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys(), ...this.#failedUris]),
|
|
@@ -301,20 +526,45 @@ export class ClientDiagnostics {
|
|
|
301
526
|
failedFiles: () => new Set(Array.from(this.#failedUris).map(uriToFile)),
|
|
302
527
|
isRelatedUriTracked: (uri) => this.#openDocs.has(uri) || this.#versionHistory.has(uri),
|
|
303
528
|
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
529
|
+
noteInputContentChange: () => this.#noteInputContentChange(),
|
|
530
|
+
observeDiskContent: (uri, content) => this.#observeDiskContent(uri, content),
|
|
304
531
|
invalidateEvidence: (uri) => {
|
|
532
|
+
this.#advanceInputRevision(false, true, "failure");
|
|
533
|
+
this.#cancelDiagnosticRequest(uri);
|
|
305
534
|
this.#failedUris.add(uri);
|
|
535
|
+
// Keep old diagnostics as partial data, but do not let unchanged
|
|
536
|
+
// recovery restore their former confirmation generation.
|
|
537
|
+
const entry = this.#diagnosticStore.get(uri);
|
|
538
|
+
if (entry) entry.evidenceRevision = -1;
|
|
306
539
|
const document = this.#openDocs.get(uri);
|
|
307
540
|
if (document) document.evidenceRevision = -1;
|
|
308
541
|
},
|
|
309
542
|
clearFile: (uri) => {
|
|
543
|
+
this.#advanceInputRevision(false, true, "close");
|
|
544
|
+
this.#cancelDiagnosticRequest(uri);
|
|
545
|
+
this.#forgetDocumentContent(uri);
|
|
310
546
|
this.#failedUris.delete(uri);
|
|
311
547
|
this.#unversionedPushSyncMoments.delete(uri);
|
|
312
548
|
this.#closedVersionedBarrier.add(uri);
|
|
313
549
|
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
314
550
|
},
|
|
315
|
-
markUnversionedSyncMoment: (uri) =>
|
|
551
|
+
markUnversionedSyncMoment: (uri) => {
|
|
552
|
+
this.#cancelDiagnosticRequest(uri);
|
|
553
|
+
this.#unversionedPushSyncMoments.set(uri, Date.now());
|
|
554
|
+
},
|
|
316
555
|
clearFailedFile: (uri) => this.#failedUris.delete(uri),
|
|
317
|
-
|
|
556
|
+
requestDiagnostics: (requestOptions) =>
|
|
557
|
+
this.#collectDiagnosticRequestEvidence({
|
|
558
|
+
request: requestOptions.request,
|
|
559
|
+
timeoutMs: requestOptions.timeoutMs,
|
|
560
|
+
control: {
|
|
561
|
+
signal: requestOptions.signal,
|
|
562
|
+
deadline: requestOptions.deadline,
|
|
563
|
+
operationId: requestOptions.operationId,
|
|
564
|
+
},
|
|
565
|
+
deadline: requestOptions.deadline,
|
|
566
|
+
operationId: requestOptions.operationId,
|
|
567
|
+
}),
|
|
318
568
|
options,
|
|
319
569
|
publications: {
|
|
320
570
|
emitSummary: (summaryOptions) =>
|
|
@@ -334,192 +584,282 @@ export class ClientDiagnostics {
|
|
|
334
584
|
filePath: string,
|
|
335
585
|
content: string,
|
|
336
586
|
control?: CodeRequestControl,
|
|
587
|
+
options: { contentIsAuthoritative?: boolean } = {},
|
|
337
588
|
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
338
589
|
// Reject immediately when the request was already cancelled: no document
|
|
339
590
|
// synchronization or protocol traffic may start for a caller that no
|
|
340
591
|
// longer awaits a result.
|
|
341
592
|
throwIfCodeRequestInterrupted(control);
|
|
342
|
-
const
|
|
343
|
-
const
|
|
593
|
+
const uri = fileToUri(filePath);
|
|
594
|
+
const contentIsAuthoritative = options.contentIsAuthoritative !== false;
|
|
595
|
+
this.#ensureExplicitDocumentContent(filePath, content, contentIsAuthoritative);
|
|
596
|
+
const contentOverrides = this.#getDiagnosticContentOverrides(
|
|
597
|
+
uri,
|
|
598
|
+
content,
|
|
599
|
+
contentIsAuthoritative,
|
|
600
|
+
);
|
|
601
|
+
return this.#runDiagnosticQueryWithEnrollmentRetry({
|
|
602
|
+
filePath,
|
|
603
|
+
content,
|
|
604
|
+
uri,
|
|
605
|
+
control,
|
|
606
|
+
contentOverrides,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async #runDiagnosticQueryWithEnrollmentRetry(options: {
|
|
611
|
+
filePath: string;
|
|
612
|
+
content: string;
|
|
613
|
+
uri: string;
|
|
614
|
+
control?: CodeRequestControl;
|
|
615
|
+
contentOverrides: ReadonlyMap<string, string> | undefined;
|
|
616
|
+
}): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
617
|
+
const retryGuard = new SemanticInputRequestRetryGuard((revision) =>
|
|
618
|
+
this.getSemanticInputChangeSince(revision),
|
|
619
|
+
);
|
|
620
|
+
for (;;) {
|
|
621
|
+
const attempt: DiagnosticQueryAttemptState = { inputRevision: undefined };
|
|
622
|
+
try {
|
|
623
|
+
retryGuard.assertCurrent();
|
|
624
|
+
return await this.#runDiagnosticQueryAttempt({
|
|
625
|
+
...options,
|
|
626
|
+
retryGuard,
|
|
627
|
+
reuseCachedEvidence: !retryGuard.hasRetried,
|
|
628
|
+
attempt,
|
|
629
|
+
});
|
|
630
|
+
} catch (error) {
|
|
631
|
+
throwIfCodeRequestInterrupted(options.control);
|
|
632
|
+
if (isSemanticInputEnrollmentError(error)) {
|
|
633
|
+
// Enrollment can change cross-file diagnostics. Invalidate only the
|
|
634
|
+
// evidence generation; the new request key queues behind owned
|
|
635
|
+
// transport instead of joining an active obsolete request.
|
|
636
|
+
this.#invalidateDiagnosticEvidenceAfterEnrollment();
|
|
637
|
+
}
|
|
638
|
+
const decision = retryGuard.decide(error, attempt.inputRevision);
|
|
639
|
+
if (decision.kind === "retry") {
|
|
640
|
+
throwIfCodeRequestInterrupted(options.control);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
return unavailableAfterEnrollmentRetry(decision.error);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** Run one diagnostic synchronization and evidence attempt. */
|
|
649
|
+
async #runDiagnosticQueryAttempt(options: {
|
|
650
|
+
filePath: string;
|
|
651
|
+
content: string;
|
|
652
|
+
uri: string;
|
|
653
|
+
control?: CodeRequestControl;
|
|
654
|
+
contentOverrides: ReadonlyMap<string, string> | undefined;
|
|
655
|
+
reuseCachedEvidence: boolean;
|
|
656
|
+
retryGuard: SemanticInputRequestRetryGuard;
|
|
657
|
+
attempt: DiagnosticQueryAttemptState;
|
|
658
|
+
}): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
659
|
+
const {
|
|
660
|
+
filePath,
|
|
661
|
+
content,
|
|
662
|
+
uri,
|
|
663
|
+
control,
|
|
664
|
+
contentOverrides,
|
|
665
|
+
reuseCachedEvidence,
|
|
666
|
+
retryGuard,
|
|
667
|
+
attempt,
|
|
668
|
+
} = options;
|
|
669
|
+
retryGuard.assertCurrent();
|
|
670
|
+
const synchronized = await this.#synchronizeDiagnosticInputs(uri, control, contentOverrides);
|
|
671
|
+
if ("kind" in synchronized) return synchronized;
|
|
672
|
+
const inputSnapshot = synchronized;
|
|
673
|
+
attempt.inputRevision = inputSnapshot.revision;
|
|
674
|
+
throwIfCodeRequestInterrupted(control);
|
|
675
|
+
retryGuard.assertCurrent();
|
|
676
|
+
const inputRevision = inputSnapshot.revision;
|
|
677
|
+
const requestAdapter = this.host.diagnosticRequestAdapter.supports(uri)
|
|
678
|
+
? this.host.diagnosticRequestAdapter
|
|
679
|
+
: undefined;
|
|
680
|
+
const observer = new DiagnosticObserver("sync-file", requestAdapter !== undefined, control, {
|
|
344
681
|
server: this.host.server,
|
|
345
682
|
cwd: this.host.cwd,
|
|
346
683
|
file: relativeDiagnosticFile(this.host.cwd, filePath),
|
|
347
684
|
});
|
|
348
|
-
const uri = fileToUri(filePath);
|
|
349
685
|
const cached = this.#diagnosticStore.get(uri);
|
|
350
686
|
const cachedDiagnostics = cached ? [...cached.diagnostics] : null;
|
|
351
687
|
const syncStart = Date.now();
|
|
352
688
|
const openDocument = this.#openDocs.get(uri);
|
|
353
689
|
const contentUnchanged =
|
|
354
690
|
openDocument?.contentFingerprint === fingerprintDocumentContent(content);
|
|
355
|
-
const synchronizationCurrent = openDocument?.evidenceRevision === this.#evidenceRevision;
|
|
356
|
-
// Equivalent content joins the current synchronization. Cached evidence
|
|
357
|
-
// cannot force a no-op didChange that cancels an in-flight push or pull.
|
|
358
|
-
if (!contentUnchanged || !synchronizationCurrent) {
|
|
359
|
-
synchronizeTrackedDocument({
|
|
360
|
-
uri,
|
|
361
|
-
content,
|
|
362
|
-
document: this.#openDocs.get(uri),
|
|
363
|
-
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
364
|
-
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
365
|
-
evidenceRevision: this.#evidenceRevision,
|
|
366
|
-
incrementalSync: this.host.usesIncrementalDocumentSync(),
|
|
367
|
-
waiters: this.#waiters,
|
|
368
|
-
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
369
|
-
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
370
|
-
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
371
|
-
open: () => this.didOpen(filePath, content),
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
691
|
const synchronization = this.#openDocs.get(uri);
|
|
375
692
|
observer.synchronized();
|
|
376
693
|
if (!synchronization) {
|
|
377
|
-
return unavailableCodeQuery(
|
|
694
|
+
return unavailableCodeQuery(
|
|
695
|
+
this.#closedVersionedBarrier.has(uri)
|
|
696
|
+
? "Diagnostic collection ended before the current document synchronization was confirmed."
|
|
697
|
+
: "The document could not be synchronized for diagnostics.",
|
|
698
|
+
);
|
|
378
699
|
}
|
|
379
700
|
if (
|
|
701
|
+
reuseCachedEvidence &&
|
|
380
702
|
contentUnchanged &&
|
|
381
703
|
hasConfirmedDiagnosticEvidence(synchronization, cached, this.#evidenceRevision)
|
|
382
704
|
) {
|
|
383
705
|
observer.cacheReused(1);
|
|
384
|
-
return
|
|
706
|
+
return this.#verifyDiagnosticInputs({
|
|
707
|
+
result: completedCodeQuery(cachedDiagnostics ?? []),
|
|
708
|
+
snapshot: inputSnapshot,
|
|
709
|
+
control,
|
|
710
|
+
contentOverrides,
|
|
711
|
+
uri,
|
|
712
|
+
diagnosticEvidence: this.#captureDiagnosticEvidence({
|
|
713
|
+
uri,
|
|
714
|
+
synchronizationId: synchronization.synchronizationId,
|
|
715
|
+
evidenceRevision: synchronization.evidenceRevision,
|
|
716
|
+
}),
|
|
717
|
+
});
|
|
385
718
|
}
|
|
719
|
+
const requestEvidenceRevision =
|
|
720
|
+
synchronization.evidenceRevision === this.#evidenceRevision
|
|
721
|
+
? synchronization.evidenceRevision
|
|
722
|
+
: undefined;
|
|
386
723
|
const request = {
|
|
387
724
|
uri,
|
|
388
725
|
synchronizationId: synchronization.synchronizationId,
|
|
389
|
-
|
|
726
|
+
...(requestEvidenceRevision !== undefined
|
|
727
|
+
? { evidenceRevision: requestEvidenceRevision }
|
|
728
|
+
: {}),
|
|
390
729
|
};
|
|
391
|
-
return this.#
|
|
730
|
+
return this.#collectFileDiagnostics(
|
|
392
731
|
{
|
|
393
732
|
filePath,
|
|
394
|
-
content,
|
|
395
733
|
uri,
|
|
396
|
-
|
|
734
|
+
requestAdapter,
|
|
397
735
|
syncStart,
|
|
398
736
|
request,
|
|
399
737
|
cachedDiagnostics,
|
|
400
738
|
observer,
|
|
739
|
+
inputRevision,
|
|
740
|
+
contentOverrides,
|
|
401
741
|
},
|
|
402
742
|
control,
|
|
403
743
|
);
|
|
404
744
|
}
|
|
405
745
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
746
|
+
async #synchronizeDiagnosticInputs(
|
|
747
|
+
uri: string,
|
|
748
|
+
control: CodeRequestControl | undefined,
|
|
749
|
+
contentOverrides: ReadonlyMap<string, string> | undefined,
|
|
750
|
+
): Promise<SemanticInputSnapshot | CodeQueryResult<Diagnostic[]>> {
|
|
751
|
+
try {
|
|
752
|
+
return await this.synchronizeSemanticInputs(control, contentOverrides);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (isCodeRequestInterruption(error, control)) throw error;
|
|
755
|
+
if (this.#closedVersionedBarrier.has(uri)) {
|
|
756
|
+
return unavailableCodeQuery(
|
|
757
|
+
"Diagnostic collection ended before the current document synchronization was confirmed.",
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
761
|
+
return unavailableCodeQuery(`Semantic input synchronization failed: ${detail}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async #verifyDiagnosticInputs(options: {
|
|
766
|
+
result: CodeQueryResult<Diagnostic[]>;
|
|
767
|
+
snapshot: SemanticInputSnapshot;
|
|
768
|
+
control: CodeRequestControl | undefined;
|
|
769
|
+
contentOverrides: ReadonlyMap<string, string> | undefined;
|
|
770
|
+
uri: string;
|
|
771
|
+
diagnosticEvidence?: DiagnosticEvidenceCandidate;
|
|
772
|
+
}): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
773
|
+
try {
|
|
774
|
+
await this.assertSemanticInputsCurrent(
|
|
775
|
+
options.snapshot,
|
|
776
|
+
options.control,
|
|
777
|
+
options.contentOverrides,
|
|
778
|
+
);
|
|
779
|
+
if (
|
|
780
|
+
options.result.kind === "completed" &&
|
|
781
|
+
(!options.diagnosticEvidence ||
|
|
782
|
+
!this.#isDiagnosticEvidenceCurrent(options.diagnosticEvidence))
|
|
783
|
+
) {
|
|
784
|
+
return incompleteDiagnosticResult(options.result.data, "timed-out");
|
|
785
|
+
}
|
|
786
|
+
return options.result;
|
|
787
|
+
} catch (error) {
|
|
788
|
+
if (isCodeRequestInterruption(error, options.control)) throw error;
|
|
789
|
+
if (isSemanticInputEnrollmentError(error)) throw error;
|
|
790
|
+
if (options.result.kind === "unavailable" && this.#closedVersionedBarrier.has(options.uri)) {
|
|
791
|
+
return options.result;
|
|
792
|
+
}
|
|
793
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
794
|
+
return unavailableCodeQuery(`Diagnostic collection failed closed: ${detail}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** Collect diagnostics for one synchronized document. */
|
|
799
|
+
async #collectFileDiagnostics(
|
|
411
800
|
options: {
|
|
412
801
|
filePath: string;
|
|
413
|
-
content: string;
|
|
414
802
|
uri: string;
|
|
415
|
-
|
|
803
|
+
requestAdapter: ClientDiagnosticsHost["diagnosticRequestAdapter"] | undefined;
|
|
416
804
|
syncStart: number;
|
|
417
805
|
request: DiagnosticSynchronization;
|
|
418
806
|
cachedDiagnostics: Diagnostic[] | null;
|
|
419
807
|
observer: DiagnosticObserver;
|
|
808
|
+
inputRevision: number;
|
|
809
|
+
contentOverrides: ReadonlyMap<string, string> | undefined;
|
|
420
810
|
},
|
|
421
811
|
control?: CodeRequestControl,
|
|
422
812
|
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
423
|
-
const {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
observer,
|
|
432
|
-
} = options;
|
|
433
|
-
let pushOutcome: DiagnosticPushWaitOutcome | undefined;
|
|
434
|
-
let attemptRequest = request;
|
|
435
|
-
let attemptSyncStart = syncStart;
|
|
436
|
-
let attemptMaxWaitMs = DIAGNOSTIC_WAIT_MS;
|
|
437
|
-
let reopenedOnce = false;
|
|
438
|
-
let result: CodeQueryResult<Diagnostic[]>;
|
|
439
|
-
for (;;) {
|
|
440
|
-
result = await collectSynchronizedFileDiagnostics(
|
|
441
|
-
{
|
|
442
|
-
supportsPull,
|
|
443
|
-
syncStart: attemptSyncStart,
|
|
444
|
-
maxWaitMs: attemptMaxWaitMs,
|
|
445
|
-
request: attemptRequest,
|
|
446
|
-
cachedDiagnostics,
|
|
447
|
-
observer,
|
|
448
|
-
waiters: this.#waiters,
|
|
449
|
-
current: () => isCurrentSynchronization(this.#openDocs, attemptRequest),
|
|
450
|
-
freshPush: () =>
|
|
451
|
-
hasFreshPush(this.#diagnosticStore, attemptRequest, this.#evidenceRevision),
|
|
452
|
-
currentPushReceivedAt: () => {
|
|
453
|
-
const entry = this.#diagnosticStore.get(attemptRequest.uri);
|
|
454
|
-
return entry?.source === "push" &&
|
|
455
|
-
hasCurrentEvidence(this.#diagnosticStore, attemptRequest, this.#evidenceRevision)
|
|
456
|
-
? entry.receivedAt
|
|
457
|
-
: undefined;
|
|
458
|
-
},
|
|
459
|
-
diagnostics: () => this.getDiagnostics(filePath),
|
|
460
|
-
pullDiagnostics: (timeoutMs, signal) =>
|
|
461
|
-
pullClientDiagnosticEvidenceFromHost({
|
|
462
|
-
host: this.host,
|
|
463
|
-
store: this.#diagnosticStore,
|
|
464
|
-
openDocuments: this.#openDocs,
|
|
465
|
-
currentEvidenceRevision: () => this.#evidenceRevision,
|
|
466
|
-
isRelatedUriTracked: (relatedUri) =>
|
|
467
|
-
this.#openDocs.has(relatedUri) || this.#versionHistory.has(relatedUri),
|
|
468
|
-
request: {
|
|
469
|
-
uri,
|
|
813
|
+
const { filePath, uri, requestAdapter, syncStart, request, cachedDiagnostics, observer } =
|
|
814
|
+
options;
|
|
815
|
+
const collected = await collectSynchronizedFileDiagnostics(
|
|
816
|
+
{
|
|
817
|
+
requestDiagnostics: requestAdapter
|
|
818
|
+
? (timeoutMs, deadline, requestControl) =>
|
|
819
|
+
this.#collectDiagnosticRequestEvidence({
|
|
820
|
+
request,
|
|
470
821
|
timeoutMs,
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
signal,
|
|
474
|
-
deadline: control?.deadline,
|
|
822
|
+
control: requestControl,
|
|
823
|
+
deadline,
|
|
475
824
|
operationId: control?.operationId,
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Reopen-resync fallback (R2): on push-only routes a document that
|
|
485
|
-
// timed out with no push stays unconfirmed — a clean file gets no
|
|
486
|
-
// push on didChange at all. Close and reopen it over the protocol so
|
|
487
|
-
// the server publishes on didOpen, then wait once more with a bounded
|
|
488
|
-
// budget. The cache entry and version history survive the reopen.
|
|
489
|
-
const document = this.#openDocs.get(uri);
|
|
490
|
-
if (
|
|
491
|
-
supportsPull ||
|
|
492
|
-
reopenedOnce ||
|
|
493
|
-
result.kind === "completed" ||
|
|
494
|
-
pushOutcome !== "timed-out" ||
|
|
495
|
-
!document ||
|
|
496
|
-
!isCurrentSynchronization(this.#openDocs, attemptRequest)
|
|
497
|
-
) {
|
|
498
|
-
break;
|
|
499
|
-
}
|
|
500
|
-
reopenDocument({
|
|
501
|
-
uri,
|
|
502
|
-
content,
|
|
503
|
-
document,
|
|
504
|
-
languageId: detectLanguageId(filePath),
|
|
505
|
-
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
506
|
-
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
507
|
-
evidenceRevision: this.#evidenceRevision,
|
|
825
|
+
})
|
|
826
|
+
: undefined,
|
|
827
|
+
requestSource: requestAdapter?.sourceFor(uri),
|
|
828
|
+
syncStart,
|
|
829
|
+
maxWaitMs: DIAGNOSTIC_WAIT_MS,
|
|
830
|
+
request,
|
|
831
|
+
cachedDiagnostics,
|
|
832
|
+
observer,
|
|
508
833
|
waiters: this.#waiters,
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
834
|
+
current: () => isCurrentSynchronization(this.#openDocs, request),
|
|
835
|
+
currentPushObservation: () => {
|
|
836
|
+
const entry = this.#diagnosticStore.get(request.uri);
|
|
837
|
+
return entry?.source === "push" &&
|
|
838
|
+
hasCurrentEvidence(this.#diagnosticStore, request, this.#evidenceRevision)
|
|
839
|
+
? { receivedAt: entry.receivedAt, hasDiagnostics: entry.diagnostics.length > 0 }
|
|
840
|
+
: undefined;
|
|
841
|
+
},
|
|
842
|
+
diagnostics: () => this.getDiagnostics(filePath),
|
|
843
|
+
},
|
|
844
|
+
control,
|
|
845
|
+
);
|
|
846
|
+
const result = await this.#verifyDiagnosticInputs({
|
|
847
|
+
result: collected,
|
|
848
|
+
snapshot: { revision: options.inputRevision },
|
|
849
|
+
control,
|
|
850
|
+
contentOverrides: options.contentOverrides,
|
|
851
|
+
uri,
|
|
852
|
+
// Capture the candidate before the final input barrier can await a read.
|
|
853
|
+
// The candidate must not follow the mutable document revision.
|
|
854
|
+
diagnosticEvidence:
|
|
855
|
+
collected.kind === "completed"
|
|
856
|
+
? this.#captureDiagnosticEvidence({
|
|
857
|
+
uri: request.uri,
|
|
858
|
+
synchronizationId: request.synchronizationId,
|
|
859
|
+
evidenceRevision: request.evidenceRevision ?? this.#evidenceRevision,
|
|
860
|
+
})
|
|
861
|
+
: undefined,
|
|
862
|
+
});
|
|
523
863
|
this.#publications.emitSummary({
|
|
524
864
|
operation: "sync-file",
|
|
525
865
|
identity: {
|
|
@@ -530,8 +870,8 @@ export class ClientDiagnostics {
|
|
|
530
870
|
synchronizations: [
|
|
531
871
|
{
|
|
532
872
|
uri,
|
|
533
|
-
synchronizationId:
|
|
534
|
-
evidenceRevision:
|
|
873
|
+
synchronizationId: request.synchronizationId,
|
|
874
|
+
evidenceRevision: request.evidenceRevision ?? this.#evidenceRevision,
|
|
535
875
|
confirmed: result.kind === "completed",
|
|
536
876
|
},
|
|
537
877
|
],
|
|
@@ -539,6 +879,168 @@ export class ClientDiagnostics {
|
|
|
539
879
|
});
|
|
540
880
|
return result;
|
|
541
881
|
}
|
|
882
|
+
|
|
883
|
+
/** Capture immutable identity for one request-confirmed diagnostic result. */
|
|
884
|
+
#captureDiagnosticEvidence(options: {
|
|
885
|
+
uri: string;
|
|
886
|
+
synchronizationId: number;
|
|
887
|
+
evidenceRevision: number;
|
|
888
|
+
}): DiagnosticEvidenceCandidate | undefined {
|
|
889
|
+
const entry = this.#diagnosticStore.get(options.uri);
|
|
890
|
+
if (
|
|
891
|
+
!entry ||
|
|
892
|
+
entry.source === "push" ||
|
|
893
|
+
entry.synchronizationId !== options.synchronizationId ||
|
|
894
|
+
entry.evidenceRevision !== options.evidenceRevision ||
|
|
895
|
+
options.evidenceRevision !== this.#evidenceRevision
|
|
896
|
+
) {
|
|
897
|
+
return undefined;
|
|
898
|
+
}
|
|
899
|
+
return {
|
|
900
|
+
uri: options.uri,
|
|
901
|
+
synchronizationId: options.synchronizationId,
|
|
902
|
+
evidenceRevision: options.evidenceRevision,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/** Check a captured diagnostic result against the current live evidence. */
|
|
907
|
+
#isDiagnosticEvidenceCurrent(candidate: DiagnosticEvidenceCandidate): boolean {
|
|
908
|
+
return (
|
|
909
|
+
candidate.evidenceRevision === this.#evidenceRevision &&
|
|
910
|
+
isCurrentSynchronization(this.#openDocs, candidate) &&
|
|
911
|
+
hasFreshEvidence(this.#diagnosticStore, candidate, this.#evidenceRevision)
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/** Start one shared request and apply its report through the evidence gate. */
|
|
916
|
+
#collectDiagnosticRequestEvidence(options: {
|
|
917
|
+
request: DiagnosticSynchronization;
|
|
918
|
+
timeoutMs: number;
|
|
919
|
+
control?: CodeRequestControl;
|
|
920
|
+
deadline?: number;
|
|
921
|
+
operationId?: string;
|
|
922
|
+
}): Promise<boolean> {
|
|
923
|
+
const adapter = this.host.diagnosticRequestAdapter;
|
|
924
|
+
if (!adapter?.supports(options.request.uri)) return Promise.resolve(false);
|
|
925
|
+
const key = [
|
|
926
|
+
options.request.uri,
|
|
927
|
+
options.request.synchronizationId,
|
|
928
|
+
options.request.evidenceRevision ?? this.#evidenceRevision,
|
|
929
|
+
].join("\x00");
|
|
930
|
+
const consumerDeadline =
|
|
931
|
+
options.deadline ??
|
|
932
|
+
(Number.isFinite(options.timeoutMs) ? Date.now() + options.timeoutMs : undefined);
|
|
933
|
+
const consumerControl: CodeRequestControl = {
|
|
934
|
+
signal: options.control?.signal,
|
|
935
|
+
...(consumerDeadline !== undefined ? { deadline: consumerDeadline } : {}),
|
|
936
|
+
};
|
|
937
|
+
return this.#requestScheduler.run(
|
|
938
|
+
key,
|
|
939
|
+
() => {
|
|
940
|
+
if (!isCurrentSynchronization(this.#openDocs, options.request)) {
|
|
941
|
+
return { result: Promise.resolve(false), settled: Promise.resolve() };
|
|
942
|
+
}
|
|
943
|
+
const controller = new AbortController();
|
|
944
|
+
this.#requestControllers.set(options.request.uri, controller);
|
|
945
|
+
// The scheduler already races each caller against its own control.
|
|
946
|
+
// Keep the adapter job alive after that race so another caller can
|
|
947
|
+
// join the same underlying request. Its owner bound is independent
|
|
948
|
+
// of the first caller's deadline.
|
|
949
|
+
const ownerTimeoutMs = Number.isFinite(options.timeoutMs)
|
|
950
|
+
? Math.max(options.timeoutMs, DIAGNOSTIC_REQUEST_OWNER_TIMEOUT_MS)
|
|
951
|
+
: DIAGNOSTIC_REQUEST_OWNER_TIMEOUT_MS;
|
|
952
|
+
const request = {
|
|
953
|
+
uri: options.request.uri,
|
|
954
|
+
previousResultId: this.#diagnosticStore.get(options.request.uri)?.resultId,
|
|
955
|
+
timeoutMs: ownerTimeoutMs,
|
|
956
|
+
signal: controller.signal,
|
|
957
|
+
operationId: options.operationId,
|
|
958
|
+
};
|
|
959
|
+
const execution = startDiagnosticEvidenceFromAdapter({
|
|
960
|
+
adapter,
|
|
961
|
+
request,
|
|
962
|
+
store: this.#diagnosticStore,
|
|
963
|
+
synchronizationId: options.request.synchronizationId,
|
|
964
|
+
evidenceRevision: options.request.evidenceRevision ?? this.#evidenceRevision,
|
|
965
|
+
currentRevision: () => this.#evidenceRevision,
|
|
966
|
+
isCurrentSynchronization: () => isCurrentSynchronization(this.#openDocs, options.request),
|
|
967
|
+
markEvidenceCurrent: (_uri, synchronizationId) => {
|
|
968
|
+
const document = this.#openDocs.get(options.request.uri);
|
|
969
|
+
if (
|
|
970
|
+
document &&
|
|
971
|
+
synchronizationId !== undefined &&
|
|
972
|
+
document.synchronizationId === synchronizationId
|
|
973
|
+
) {
|
|
974
|
+
document.evidenceRevision =
|
|
975
|
+
options.request.evidenceRevision ?? this.#evidenceRevision;
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
isRelatedUriTracked: (uri) => this.#openDocs.has(uri) || this.#versionHistory.has(uri),
|
|
979
|
+
});
|
|
980
|
+
void execution.settled
|
|
981
|
+
.finally(() => {
|
|
982
|
+
if (this.#requestControllers.get(options.request.uri) === controller) {
|
|
983
|
+
this.#requestControllers.delete(options.request.uri);
|
|
984
|
+
}
|
|
985
|
+
})
|
|
986
|
+
.catch(() => {});
|
|
987
|
+
return execution;
|
|
988
|
+
},
|
|
989
|
+
consumerControl,
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
#noteInputContentChange(): number {
|
|
994
|
+
this.#advanceInputRevision();
|
|
995
|
+
return this.#evidenceRevision;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/** Invalidate diagnostic evidence without advancing the semantic input generation. */
|
|
999
|
+
#invalidateDiagnosticEvidenceForServerRequest(): void {
|
|
1000
|
+
// Keep active adapter work owned until it settles. The refresh generation
|
|
1001
|
+
// below discards its evidence and starts the newer pass afterwards.
|
|
1002
|
+
this.#advanceEvidenceRevision(false);
|
|
1003
|
+
this.#waiters.cancelSettle();
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
#advanceInputRevision(
|
|
1007
|
+
invalidateEvidence = true,
|
|
1008
|
+
cancelRequests = true,
|
|
1009
|
+
changeKind: SemanticInputChangeKind = "content",
|
|
1010
|
+
): void {
|
|
1011
|
+
this.#inputBarrier.noteInputChange(changeKind);
|
|
1012
|
+
if (invalidateEvidence) this.#advanceEvidenceRevision(false);
|
|
1013
|
+
if (cancelRequests) this.#cancelAllDiagnosticRequests();
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
#advanceEvidenceRevision(cancelRequests = true): void {
|
|
1017
|
+
this.#evidenceRevision++;
|
|
1018
|
+
for (const document of this.#openDocs.values()) {
|
|
1019
|
+
document.evidenceRevision = this.#evidenceRevision;
|
|
1020
|
+
}
|
|
1021
|
+
if (cancelRequests) this.#cancelAllDiagnosticRequests();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/** Invalidate enrollment-stale evidence without cancelling owned transport. */
|
|
1025
|
+
#invalidateDiagnosticEvidenceAfterEnrollment(): void {
|
|
1026
|
+
this.#advanceEvidenceRevision(false);
|
|
1027
|
+
this.#waiters.cancelSettle();
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
#cancelDiagnosticRequest(uri: string): void {
|
|
1031
|
+
this.#requestControllers
|
|
1032
|
+
.get(uri)
|
|
1033
|
+
?.abort(new DiagnosticRequestInvalidatedError("Diagnostic request was superseded."));
|
|
1034
|
+
const prefix = `${uri}\x00`;
|
|
1035
|
+
this.#requestScheduler.clearPendingWhere((key) => key.startsWith(prefix));
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
#cancelAllDiagnosticRequests(): void {
|
|
1039
|
+
for (const controller of this.#requestControllers.values()) {
|
|
1040
|
+
controller.abort(new DiagnosticRequestInvalidatedError());
|
|
1041
|
+
}
|
|
1042
|
+
this.#requestScheduler.clearPending();
|
|
1043
|
+
}
|
|
542
1044
|
}
|
|
543
1045
|
|
|
544
1046
|
/** Return a workspace-relative diagnostic file path for telemetry identity. */
|