@shanepadgett/tau-agent 0.25.1 → 0.27.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/docs/subagents.md +1 -1
- package/extensions/appshot/README.md +1 -1
- package/extensions/appshot/index.ts +5 -11
- package/extensions/context-pruning/README.md +2 -2
- package/extensions/context-pruning/index.ts +58 -57
- package/extensions/context-pruning/render.ts +51 -39
- package/extensions/context-pruning/settings.ts +4 -5
- package/extensions/explore/README.md +18 -10
- package/extensions/explore/ast-guidance.ts +67 -0
- package/extensions/explore/ast-languages.ts +61 -0
- package/extensions/explore/ast-tools.ts +1597 -94
- package/extensions/explore/ast-worker.ts +768 -60
- package/extensions/explore/index.ts +62 -6
- package/extensions/explore/orientation-state.ts +280 -0
- package/extensions/explore/read-stats-panel.ts +23 -1
- package/extensions/explore/read-stats.ts +21 -3
- package/extensions/explore/read.ts +58 -21
- package/extensions/explore/settings.ts +46 -0
- package/extensions/explore/traverse.ts +15 -4
- package/extensions/patch/executor.ts +95 -1
- package/extensions/patch/index.ts +1 -0
- package/extensions/soul/prompt.ts +1 -3
- package/extensions/subagent/agents/review.md +76 -25
- package/extensions/subagent/agents/scout.md +118 -22
- package/extensions/subagent/session-resource.ts +1 -1
- package/extensions/tau-help/help.md +3 -1
- package/native-bin/darwin-arm64/tau-ast +0 -0
- package/package.json +2 -3
- package/schemas/tau.schema.json +44 -4
- package/shared/bounded-text-result.ts +304 -0
- package/shared/events.ts +2 -1
- package/shared/temporary-output-store.ts +275 -0
|
@@ -2,10 +2,32 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { isAstLanguage, type AstLanguage } from "./ast-languages.ts";
|
|
5
6
|
|
|
6
|
-
export type AstLanguage
|
|
7
|
+
export type { AstLanguage } from "./ast-languages.ts";
|
|
7
8
|
|
|
8
|
-
export type OutlineTarget =
|
|
9
|
+
export type OutlineTarget =
|
|
10
|
+
| { kind: "file"; path: string; language: AstLanguage }
|
|
11
|
+
| { kind: "directory"; path: string }
|
|
12
|
+
| {
|
|
13
|
+
kind: "recursiveDirectory";
|
|
14
|
+
path: string;
|
|
15
|
+
budgets: RecursiveOutlineBudgets;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface RecursiveOutlineBudgets {
|
|
19
|
+
maxFiles: number;
|
|
20
|
+
maxSourceBytes: number;
|
|
21
|
+
maxDepth: number;
|
|
22
|
+
maxElapsedMs: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const RECURSIVE_OUTLINE_BUDGETS: RecursiveOutlineBudgets = {
|
|
26
|
+
maxFiles: 2000,
|
|
27
|
+
maxSourceBytes: 64 * 1024 * 1024,
|
|
28
|
+
maxDepth: 32,
|
|
29
|
+
maxElapsedMs: 20_000,
|
|
30
|
+
};
|
|
9
31
|
|
|
10
32
|
export interface SourcePosition {
|
|
11
33
|
line: number;
|
|
@@ -23,13 +45,22 @@ export interface OutlineEntry {
|
|
|
23
45
|
role: "item" | "member";
|
|
24
46
|
symbolType: string;
|
|
25
47
|
name: string;
|
|
48
|
+
qualifiedName: string;
|
|
26
49
|
range: SourceRange;
|
|
50
|
+
nameRange: SourceRange;
|
|
51
|
+
receiverRange?: SourceRange;
|
|
52
|
+
bodyRange?: SourceRange;
|
|
27
53
|
signature: string;
|
|
28
54
|
astKind: string;
|
|
29
|
-
|
|
55
|
+
certainty: "certain" | "recovered" | "nearRecovery";
|
|
56
|
+
certaintyReason?: string;
|
|
57
|
+
locator?: string;
|
|
30
58
|
}
|
|
31
59
|
|
|
60
|
+
export type SymbolView = "signature" | "signatureWithDocs" | "declaration" | "declarationWithImports";
|
|
61
|
+
|
|
32
62
|
export interface OutlineItem extends OutlineEntry {
|
|
63
|
+
rowKind: "package" | "import" | "declaration" | "export" | "sideEffect";
|
|
33
64
|
isImport: boolean;
|
|
34
65
|
isExported: boolean;
|
|
35
66
|
members: Array<OutlineEntry & { isPublic: boolean }>;
|
|
@@ -52,12 +83,43 @@ export interface OutlineTargetResult {
|
|
|
52
83
|
totalLineCount: number;
|
|
53
84
|
}
|
|
54
85
|
|
|
86
|
+
export interface RecursiveOutlineDiagnostic {
|
|
87
|
+
relativePath: string;
|
|
88
|
+
language: AstLanguage | undefined;
|
|
89
|
+
code: string;
|
|
90
|
+
message: string;
|
|
91
|
+
sourceFingerprint: string | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface RecursiveOutlineSummary {
|
|
95
|
+
discoveredFiles: number;
|
|
96
|
+
supportedFiles: number;
|
|
97
|
+
unsupportedFiles: number;
|
|
98
|
+
emittedFiles: number;
|
|
99
|
+
unreadableFiles: number;
|
|
100
|
+
oversizedFiles: number;
|
|
101
|
+
failedFiles: number;
|
|
102
|
+
parserDegradedFiles: number;
|
|
103
|
+
totalByteLength: number;
|
|
104
|
+
totalLineCount: number;
|
|
105
|
+
fileLimitReached: boolean;
|
|
106
|
+
sourceByteLimitReached: boolean;
|
|
107
|
+
depthLimitReached: boolean;
|
|
108
|
+
elapsedLimitReached: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface RecursiveOutlineCallbacks {
|
|
112
|
+
onFile(relativePath: string, file: OutlineFileResult): Promise<void>;
|
|
113
|
+
onDiagnostic(diagnostic: RecursiveOutlineDiagnostic): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
|
|
55
116
|
export interface SymbolDeclaration {
|
|
56
117
|
locator: string;
|
|
57
118
|
path: string;
|
|
58
119
|
language: AstLanguage;
|
|
59
120
|
sourceFingerprint: string;
|
|
60
121
|
declarationRange: SourceRange;
|
|
122
|
+
diagnostics: string[];
|
|
61
123
|
}
|
|
62
124
|
|
|
63
125
|
export interface SymbolBlock {
|
|
@@ -72,39 +134,402 @@ export interface SymbolBatchResult {
|
|
|
72
134
|
blocks: SymbolBlock[];
|
|
73
135
|
}
|
|
74
136
|
|
|
137
|
+
export type ApiDeclarationKind =
|
|
138
|
+
| "module"
|
|
139
|
+
| "namespace"
|
|
140
|
+
| "package"
|
|
141
|
+
| "class"
|
|
142
|
+
| "method"
|
|
143
|
+
| "property"
|
|
144
|
+
| "field"
|
|
145
|
+
| "constructor"
|
|
146
|
+
| "enum"
|
|
147
|
+
| "interface"
|
|
148
|
+
| "function"
|
|
149
|
+
| "variable"
|
|
150
|
+
| "constant"
|
|
151
|
+
| "object"
|
|
152
|
+
| "enumMember"
|
|
153
|
+
| "struct"
|
|
154
|
+
| "event"
|
|
155
|
+
| "operator"
|
|
156
|
+
| "typeParameter"
|
|
157
|
+
| "heading";
|
|
158
|
+
|
|
159
|
+
export type ApiQuery =
|
|
160
|
+
| { kind: "exactName"; name: string }
|
|
161
|
+
| { kind: "prefixName"; name: string }
|
|
162
|
+
| { kind: "substringName"; name: string }
|
|
163
|
+
| { kind: "fuzzyName"; name: string; maxCandidates: number; maxWork: number }
|
|
164
|
+
| { kind: "declarationKind"; declarationKind: ApiDeclarationKind }
|
|
165
|
+
| { kind: "documentation"; terms: string[]; maxCandidates: number; maxWork: number };
|
|
166
|
+
|
|
167
|
+
export type ApiSurfaceFilter = "all" | "public" | "private" | "sourceExport" | "packageSurface";
|
|
168
|
+
|
|
169
|
+
export interface ApiCandidate {
|
|
170
|
+
locator: string;
|
|
171
|
+
language: AstLanguage;
|
|
172
|
+
sourceFingerprint: string;
|
|
173
|
+
name: string;
|
|
174
|
+
qualifiedName: string;
|
|
175
|
+
symbolType: ApiDeclarationKind;
|
|
176
|
+
signature: string;
|
|
177
|
+
definingFile: string;
|
|
178
|
+
range: SourceRange;
|
|
179
|
+
visibility: "public" | "protected" | "internal" | "packagePrivate" | "filePrivate" | "private" | "unknown";
|
|
180
|
+
sourceExport: "yes" | "no" | "unknown";
|
|
181
|
+
packageSurface: "yes" | "no" | "unknown";
|
|
182
|
+
internalOnly: "yes" | "no" | "unknown";
|
|
183
|
+
reExportChain: string[];
|
|
184
|
+
callerAccess: {
|
|
185
|
+
modulePath: string;
|
|
186
|
+
importStatement: string;
|
|
187
|
+
accessExpression: string;
|
|
188
|
+
form: "direct" | "qualified";
|
|
189
|
+
} | null;
|
|
190
|
+
provenance: "exact" | "inferred" | "ambiguous" | "unsupported";
|
|
191
|
+
certainty: "certain" | "recovered" | "nearRecovery";
|
|
192
|
+
certaintyReason: string | null;
|
|
193
|
+
uncertainty: string | null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ApiDiscoverySummary {
|
|
197
|
+
filesScanned: number;
|
|
198
|
+
declarationsConsidered: number;
|
|
199
|
+
resultsReturned: number;
|
|
200
|
+
resultLimit: number;
|
|
201
|
+
omittedCandidates: number;
|
|
202
|
+
candidateLimitReached: boolean;
|
|
203
|
+
workLimitReached: boolean;
|
|
204
|
+
resolutionDiagnostics: number;
|
|
205
|
+
totalSourceBytes: number;
|
|
206
|
+
fileLimitReached: boolean;
|
|
207
|
+
sourceByteLimitReached: boolean;
|
|
208
|
+
depthLimitReached: boolean;
|
|
209
|
+
elapsedLimitReached: boolean;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface ApiDiscoveryResult {
|
|
213
|
+
path: string;
|
|
214
|
+
candidates: ApiCandidate[];
|
|
215
|
+
summary: ApiDiscoverySummary;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface AstSearchBindingValue {
|
|
219
|
+
range: SourceRange;
|
|
220
|
+
preview: string;
|
|
221
|
+
previewTruncated: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface AstSearchBinding {
|
|
225
|
+
name: string;
|
|
226
|
+
values: AstSearchBindingValue[];
|
|
227
|
+
valuesTruncated: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface AstSearchScope {
|
|
231
|
+
astKind: string;
|
|
232
|
+
range: SourceRange;
|
|
233
|
+
preview: string;
|
|
234
|
+
previewTruncated: boolean;
|
|
235
|
+
locator: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface AstSearchMatch {
|
|
239
|
+
relativePath: string;
|
|
240
|
+
language: AstLanguage;
|
|
241
|
+
sourceFingerprint: string;
|
|
242
|
+
range: SourceRange;
|
|
243
|
+
preview: string;
|
|
244
|
+
previewTruncated: boolean;
|
|
245
|
+
bindings: AstSearchBinding[];
|
|
246
|
+
bindingsTruncated: boolean;
|
|
247
|
+
certainty: "certain" | "recovered" | "nearRecovery";
|
|
248
|
+
certaintyReason?: string;
|
|
249
|
+
locator: string;
|
|
250
|
+
enclosingScope?: AstSearchScope;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface AstSearchSummary {
|
|
254
|
+
filesDiscovered: number;
|
|
255
|
+
filesFiltered: number;
|
|
256
|
+
languageFilteredFiles: number;
|
|
257
|
+
literalFilteredFiles: number;
|
|
258
|
+
filesRead: number;
|
|
259
|
+
filesParsed: number;
|
|
260
|
+
filesSearched: number;
|
|
261
|
+
unreadableFiles: number;
|
|
262
|
+
oversizedFiles: number;
|
|
263
|
+
failedFiles: number;
|
|
264
|
+
parserDegradedFiles: number;
|
|
265
|
+
sourceBytes: number;
|
|
266
|
+
matchesFound: number;
|
|
267
|
+
matchesReturned: number;
|
|
268
|
+
resultLimit: number;
|
|
269
|
+
resultLimitReached: boolean;
|
|
270
|
+
literalPrefilterApplied: boolean;
|
|
271
|
+
potentialKindPrefilterApplied: boolean;
|
|
272
|
+
diagnosticsOmitted: number;
|
|
273
|
+
fileLimitReached: boolean;
|
|
274
|
+
sourceByteLimitReached: boolean;
|
|
275
|
+
depthLimitReached: boolean;
|
|
276
|
+
elapsedLimitReached: boolean;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface AstSearchResult {
|
|
280
|
+
path: string;
|
|
281
|
+
language: AstLanguage;
|
|
282
|
+
pattern: string;
|
|
283
|
+
targetSourceFingerprint: string | null;
|
|
284
|
+
matches: AstSearchMatch[];
|
|
285
|
+
diagnostics: Array<{ relativePath: string; code: string; message: string }>;
|
|
286
|
+
summary: AstSearchSummary;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export type RelationshipOperation = "references" | "callers" | "callees" | "implementations" | "tests";
|
|
290
|
+
|
|
291
|
+
export interface EditableScope {
|
|
292
|
+
locator: string;
|
|
293
|
+
language: AstLanguage;
|
|
294
|
+
kind: string;
|
|
295
|
+
qualifiedIdentity: string;
|
|
296
|
+
range: SourceRange;
|
|
297
|
+
bodyRange: SourceRange | null;
|
|
298
|
+
sourceFingerprint: string;
|
|
299
|
+
certainty: "certain" | "recovered" | "nearRecovery";
|
|
300
|
+
certaintyReason: string | null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface RelationshipLocation {
|
|
304
|
+
relativePath: string;
|
|
305
|
+
language: AstLanguage;
|
|
306
|
+
sourceFingerprint: string;
|
|
307
|
+
range: SourceRange;
|
|
308
|
+
relationshipKind:
|
|
309
|
+
| "reference"
|
|
310
|
+
| "typeUsage"
|
|
311
|
+
| "caller"
|
|
312
|
+
| "callee"
|
|
313
|
+
| "implementation"
|
|
314
|
+
| "override"
|
|
315
|
+
| "reExport"
|
|
316
|
+
| "test";
|
|
317
|
+
certainty: "exact" | "inferred" | "ambiguous";
|
|
318
|
+
parseCertainty: "certain" | "recovered" | "nearRecovery";
|
|
319
|
+
certaintyReason: string | null;
|
|
320
|
+
classification: "production" | "test" | "generated" | "reExport";
|
|
321
|
+
targetLocator: string;
|
|
322
|
+
targetPath: string;
|
|
323
|
+
targetSourceFingerprint: string;
|
|
324
|
+
candidateLocators: string[];
|
|
325
|
+
candidatePaths: string[];
|
|
326
|
+
candidateSourceFingerprints: string[];
|
|
327
|
+
competingCandidatesOmitted: number;
|
|
328
|
+
actionable: boolean;
|
|
329
|
+
sitePreview: string;
|
|
330
|
+
sitePreviewTruncated: boolean;
|
|
331
|
+
enclosingScope: EditableScope;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface RelationshipResult {
|
|
335
|
+
path: string;
|
|
336
|
+
operation: RelationshipOperation;
|
|
337
|
+
targetName: string;
|
|
338
|
+
targetLocator: string;
|
|
339
|
+
relationships: RelationshipLocation[];
|
|
340
|
+
summary: {
|
|
341
|
+
filesScanned: number;
|
|
342
|
+
sourceBytes: number;
|
|
343
|
+
parserDegradedFiles: number;
|
|
344
|
+
relationshipsFound: number;
|
|
345
|
+
relationshipsReturned: number;
|
|
346
|
+
resultLimit: number;
|
|
347
|
+
resultLimitReached: boolean;
|
|
348
|
+
ambiguousRelationships: number;
|
|
349
|
+
diagnostics: number;
|
|
350
|
+
fileLimitReached: boolean;
|
|
351
|
+
sourceByteLimitReached: boolean;
|
|
352
|
+
depthLimitReached: boolean;
|
|
353
|
+
elapsedLimitReached: boolean;
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export type EditOperation =
|
|
358
|
+
| { kind: "replaceDeclaration"; source: string }
|
|
359
|
+
| { kind: "replaceBody"; body: string }
|
|
360
|
+
| { kind: "insertDeclaration"; position: "before" | "after"; source: string }
|
|
361
|
+
| {
|
|
362
|
+
kind: "renameDeclaration";
|
|
363
|
+
newName: string;
|
|
364
|
+
scope: { kind: "file" } | { kind: "repository"; path: string };
|
|
365
|
+
includeInferred: boolean;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export interface PlannedEdit {
|
|
369
|
+
range: SourceRange;
|
|
370
|
+
replacement: string;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export interface EditFilePlan {
|
|
374
|
+
path: string;
|
|
375
|
+
expectedFingerprint: string;
|
|
376
|
+
source: string;
|
|
377
|
+
edits: PlannedEdit[];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface EditPlanResult {
|
|
381
|
+
files: EditFilePlan[];
|
|
382
|
+
skippedImpacts: Array<{
|
|
383
|
+
path: string;
|
|
384
|
+
range: SourceRange;
|
|
385
|
+
reason: "ambiguous" | "uncertainParse" | "inferredNotApproved";
|
|
386
|
+
candidateLocators: string[];
|
|
387
|
+
candidatePaths: string[];
|
|
388
|
+
}>;
|
|
389
|
+
freshLocators: Array<{
|
|
390
|
+
locator: string;
|
|
391
|
+
path: string;
|
|
392
|
+
name: string;
|
|
393
|
+
sourceFingerprint: string;
|
|
394
|
+
}>;
|
|
395
|
+
}
|
|
396
|
+
|
|
75
397
|
export interface AstClient {
|
|
398
|
+
getGeneration(): number;
|
|
76
399
|
outline(
|
|
77
400
|
target: OutlineTarget,
|
|
78
401
|
includePrivate: boolean,
|
|
402
|
+
includeDocs: boolean,
|
|
79
403
|
names: string[],
|
|
80
404
|
signal: AbortSignal | undefined,
|
|
81
405
|
): Promise<OutlineTargetResult>;
|
|
82
|
-
|
|
406
|
+
outlineRecursive(
|
|
407
|
+
path: string,
|
|
408
|
+
includePrivate: boolean,
|
|
409
|
+
includeDocs: boolean,
|
|
410
|
+
names: string[],
|
|
411
|
+
callbacks: RecursiveOutlineCallbacks,
|
|
412
|
+
signal: AbortSignal | undefined,
|
|
413
|
+
): Promise<RecursiveOutlineSummary>;
|
|
414
|
+
symbol(
|
|
415
|
+
locators: string[],
|
|
416
|
+
view: SymbolView,
|
|
417
|
+
contextLines: number,
|
|
418
|
+
signal: AbortSignal | undefined,
|
|
419
|
+
): Promise<SymbolBatchResult>;
|
|
420
|
+
discoverApi(
|
|
421
|
+
path: string,
|
|
422
|
+
query: ApiQuery,
|
|
423
|
+
surface: ApiSurfaceFilter,
|
|
424
|
+
resultLimit: number,
|
|
425
|
+
signal: AbortSignal | undefined,
|
|
426
|
+
): Promise<ApiDiscoveryResult>;
|
|
427
|
+
search(
|
|
428
|
+
path: string,
|
|
429
|
+
language: AstLanguage,
|
|
430
|
+
pattern: string,
|
|
431
|
+
resultLimit: number,
|
|
432
|
+
signal: AbortSignal | undefined,
|
|
433
|
+
): Promise<AstSearchResult>;
|
|
434
|
+
relationships(
|
|
435
|
+
path: string,
|
|
436
|
+
locator: string,
|
|
437
|
+
relationship: RelationshipOperation,
|
|
438
|
+
resultLimit: number,
|
|
439
|
+
signal: AbortSignal | undefined,
|
|
440
|
+
): Promise<RelationshipResult>;
|
|
441
|
+
planEdit(locator: string, edit: EditOperation, signal: AbortSignal | undefined): Promise<EditPlanResult>;
|
|
83
442
|
shutdown(): Promise<void>;
|
|
84
443
|
}
|
|
85
444
|
|
|
86
445
|
type WorkerRequestPayload =
|
|
87
446
|
| { operation: "handshake" }
|
|
88
|
-
| {
|
|
89
|
-
|
|
447
|
+
| {
|
|
448
|
+
operation: "outline";
|
|
449
|
+
target: OutlineTarget;
|
|
450
|
+
includePrivate: boolean;
|
|
451
|
+
includeDocs: boolean;
|
|
452
|
+
names: string[];
|
|
453
|
+
}
|
|
454
|
+
| {
|
|
455
|
+
operation: "symbol";
|
|
456
|
+
locators: string[];
|
|
457
|
+
view: SymbolView;
|
|
458
|
+
contextLines: number;
|
|
459
|
+
}
|
|
460
|
+
| {
|
|
461
|
+
operation: "apiDiscover";
|
|
462
|
+
path: string;
|
|
463
|
+
budgets: RecursiveOutlineBudgets;
|
|
464
|
+
query: ApiQuery;
|
|
465
|
+
surface: ApiSurfaceFilter;
|
|
466
|
+
resultLimit: number;
|
|
467
|
+
}
|
|
468
|
+
| {
|
|
469
|
+
operation: "astSearch";
|
|
470
|
+
path: string;
|
|
471
|
+
language: AstLanguage;
|
|
472
|
+
budgets: RecursiveOutlineBudgets;
|
|
473
|
+
pattern: string;
|
|
474
|
+
resultLimit: number;
|
|
475
|
+
}
|
|
476
|
+
| {
|
|
477
|
+
operation: "relationships";
|
|
478
|
+
path: string;
|
|
479
|
+
budgets: RecursiveOutlineBudgets;
|
|
480
|
+
locator: string;
|
|
481
|
+
relationship: RelationshipOperation;
|
|
482
|
+
resultLimit: number;
|
|
483
|
+
}
|
|
484
|
+
| {
|
|
485
|
+
operation: "planEdit";
|
|
486
|
+
locator: string;
|
|
487
|
+
edit: EditOperation;
|
|
488
|
+
budgets: RecursiveOutlineBudgets;
|
|
489
|
+
};
|
|
90
490
|
|
|
91
491
|
interface WorkerResponse {
|
|
92
492
|
requestId: number;
|
|
93
493
|
protocolVersion: number;
|
|
94
494
|
success: boolean;
|
|
95
495
|
result?: Record<string, unknown>;
|
|
96
|
-
error?: { code?: string; message?: string };
|
|
496
|
+
error?: { code?: string; message?: string; sourceFingerprint?: string };
|
|
97
497
|
}
|
|
98
498
|
|
|
99
|
-
|
|
499
|
+
export class AstWorkerError extends Error {
|
|
500
|
+
readonly code: string;
|
|
501
|
+
readonly sourceFingerprint: string | undefined;
|
|
502
|
+
|
|
503
|
+
constructor(code: string, message: string, sourceFingerprint: string | undefined = undefined) {
|
|
504
|
+
super(message);
|
|
505
|
+
this.name = "AstWorkerError";
|
|
506
|
+
this.code = code;
|
|
507
|
+
this.sourceFingerprint = sourceFingerprint;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
interface PendingUnaryRequest {
|
|
512
|
+
kind: "unary";
|
|
100
513
|
resolve(value: Record<string, unknown>): void;
|
|
101
514
|
reject(error: Error): void;
|
|
102
515
|
removeAbortListener(): void;
|
|
103
516
|
}
|
|
104
517
|
|
|
105
|
-
|
|
518
|
+
interface PendingStreamRequest {
|
|
519
|
+
kind: "recursiveOutline";
|
|
520
|
+
started: boolean;
|
|
521
|
+
callbacks: RecursiveOutlineCallbacks;
|
|
522
|
+
resolve(value: RecursiveOutlineSummary): void;
|
|
523
|
+
reject(error: Error): void;
|
|
524
|
+
removeAbortListener(): void;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
type PendingRequest = PendingUnaryRequest | PendingStreamRequest;
|
|
528
|
+
|
|
529
|
+
const PROTOCOL_VERSION = 13;
|
|
106
530
|
const MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
107
531
|
const STDERR_BYTES = 16 * 1024;
|
|
532
|
+
const HANDSHAKE_TIMEOUT_MS = 2000;
|
|
108
533
|
|
|
109
534
|
export type AstWorkerResolution = { command: string } | { error: Error };
|
|
110
535
|
|
|
@@ -118,12 +543,14 @@ export function resolveAstWorkerCommand(
|
|
|
118
543
|
|
|
119
544
|
const sourceRoot = join(packageRoot, "native", "tau-ast");
|
|
120
545
|
if (existsSync(join(sourceRoot, "Cargo.toml")))
|
|
121
|
-
return {
|
|
546
|
+
return {
|
|
547
|
+
command: join(sourceRoot, "target", "release", `tau-ast${platform === "win32" ? ".exe" : ""}`),
|
|
548
|
+
};
|
|
122
549
|
|
|
123
550
|
if (platform === "darwin" && arch === "arm64")
|
|
124
551
|
return {
|
|
125
552
|
error: new Error(
|
|
126
|
-
"tau-ast is missing from this @shanepadgett/tau-agent installation. Reinstall the package before using outline or symbol.",
|
|
553
|
+
"tau-ast is missing from this @shanepadgett/tau-agent installation. Reinstall the package before using api_discover, ast_search, outline, or symbol.",
|
|
127
554
|
),
|
|
128
555
|
};
|
|
129
556
|
return {
|
|
@@ -133,6 +560,10 @@ export function resolveAstWorkerCommand(
|
|
|
133
560
|
};
|
|
134
561
|
}
|
|
135
562
|
|
|
563
|
+
function resolveDefaultAstWorkerCommand(): AstWorkerResolution {
|
|
564
|
+
return resolveAstWorkerCommand(fileURLToPath(new URL("../../", import.meta.url)), process.platform, process.arch);
|
|
565
|
+
}
|
|
566
|
+
|
|
136
567
|
export class AstWorkerClient implements AstClient {
|
|
137
568
|
private readonly command: string | undefined;
|
|
138
569
|
private readonly args: readonly string[];
|
|
@@ -141,35 +572,134 @@ export class AstWorkerClient implements AstClient {
|
|
|
141
572
|
private pending = new Map<number, PendingRequest>();
|
|
142
573
|
private nextRequestId = 1;
|
|
143
574
|
private incoming = Buffer.alloc(0);
|
|
575
|
+
private readonly drainingChildren = new Set<ChildProcessWithoutNullStreams>();
|
|
144
576
|
private stderr = "";
|
|
577
|
+
private generation = 0;
|
|
578
|
+
private capabilities: readonly AstLanguage[] | undefined;
|
|
145
579
|
|
|
146
580
|
constructor(command: string | undefined = undefined, args: readonly string[] = []) {
|
|
147
581
|
this.command = command;
|
|
148
582
|
this.args = args;
|
|
149
583
|
}
|
|
150
584
|
|
|
585
|
+
getGeneration(): number {
|
|
586
|
+
return this.generation;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private resolveCommand(): AstWorkerResolution {
|
|
590
|
+
return this.command ? { command: this.command } : resolveDefaultAstWorkerCommand();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async supportedLanguages(): Promise<readonly AstLanguage[]> {
|
|
594
|
+
await this.ensureStarted();
|
|
595
|
+
if (!this.capabilities) throw new Error("tau-ast handshake omitted supported languages");
|
|
596
|
+
return this.capabilities;
|
|
597
|
+
}
|
|
598
|
+
|
|
151
599
|
async outline(
|
|
152
600
|
target: OutlineTarget,
|
|
153
601
|
includePrivate: boolean,
|
|
602
|
+
includeDocs: boolean,
|
|
154
603
|
names: string[],
|
|
155
604
|
signal: AbortSignal | undefined,
|
|
156
605
|
): Promise<OutlineTargetResult> {
|
|
157
|
-
const result = await this.request({ operation: "outline", target, includePrivate, names }, signal);
|
|
606
|
+
const result = await this.request({ operation: "outline", target, includePrivate, includeDocs, names }, signal);
|
|
158
607
|
if (result.kind !== "outline") throw new Error("tau-ast returned the wrong result for outline");
|
|
159
608
|
return result as unknown as OutlineTargetResult;
|
|
160
609
|
}
|
|
161
610
|
|
|
162
|
-
async
|
|
163
|
-
|
|
611
|
+
async outlineRecursive(
|
|
612
|
+
path: string,
|
|
613
|
+
includePrivate: boolean,
|
|
614
|
+
includeDocs: boolean,
|
|
615
|
+
names: string[],
|
|
616
|
+
callbacks: RecursiveOutlineCallbacks,
|
|
617
|
+
signal: AbortSignal | undefined,
|
|
618
|
+
): Promise<RecursiveOutlineSummary> {
|
|
619
|
+
await this.ensureStarted();
|
|
620
|
+
const target: OutlineTarget = {
|
|
621
|
+
kind: "recursiveDirectory",
|
|
622
|
+
path,
|
|
623
|
+
budgets: RECURSIVE_OUTLINE_BUDGETS,
|
|
624
|
+
};
|
|
625
|
+
return this.sendRecursive(
|
|
626
|
+
{ operation: "outline", target, includePrivate, includeDocs, names },
|
|
627
|
+
callbacks,
|
|
628
|
+
signal,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async symbol(
|
|
633
|
+
locators: string[],
|
|
634
|
+
view: SymbolView,
|
|
635
|
+
contextLines: number,
|
|
636
|
+
signal: AbortSignal | undefined,
|
|
637
|
+
): Promise<SymbolBatchResult> {
|
|
638
|
+
const result = await this.request({ operation: "symbol", locators, view, contextLines }, signal);
|
|
164
639
|
if (result.kind !== "symbol") throw new Error("tau-ast returned the wrong result for symbol");
|
|
165
640
|
return result as unknown as SymbolBatchResult;
|
|
166
641
|
}
|
|
167
642
|
|
|
643
|
+
async discoverApi(
|
|
644
|
+
path: string,
|
|
645
|
+
query: ApiQuery,
|
|
646
|
+
surface: ApiSurfaceFilter,
|
|
647
|
+
resultLimit: number,
|
|
648
|
+
signal: AbortSignal | undefined,
|
|
649
|
+
): Promise<ApiDiscoveryResult> {
|
|
650
|
+
const result = await this.request(
|
|
651
|
+
{ operation: "apiDiscover", path, budgets: RECURSIVE_OUTLINE_BUDGETS, query, surface, resultLimit },
|
|
652
|
+
signal,
|
|
653
|
+
);
|
|
654
|
+
if (result.kind !== "apiDiscovery") throw new Error("tau-ast returned the wrong result for API discovery");
|
|
655
|
+
return result as unknown as ApiDiscoveryResult;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async search(
|
|
659
|
+
path: string,
|
|
660
|
+
language: AstLanguage,
|
|
661
|
+
pattern: string,
|
|
662
|
+
resultLimit: number,
|
|
663
|
+
signal: AbortSignal | undefined,
|
|
664
|
+
): Promise<AstSearchResult> {
|
|
665
|
+
const result = await this.request(
|
|
666
|
+
{ operation: "astSearch", path, language, budgets: RECURSIVE_OUTLINE_BUDGETS, pattern, resultLimit },
|
|
667
|
+
signal,
|
|
668
|
+
);
|
|
669
|
+
if (result.kind !== "astSearch") throw new Error("tau-ast returned the wrong result for ast_search");
|
|
670
|
+
return result as unknown as AstSearchResult;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async relationships(
|
|
674
|
+
path: string,
|
|
675
|
+
locator: string,
|
|
676
|
+
relationship: RelationshipOperation,
|
|
677
|
+
resultLimit: number,
|
|
678
|
+
signal: AbortSignal | undefined,
|
|
679
|
+
): Promise<RelationshipResult> {
|
|
680
|
+
const result = await this.request(
|
|
681
|
+
{ operation: "relationships", path, budgets: RECURSIVE_OUTLINE_BUDGETS, locator, relationship, resultLimit },
|
|
682
|
+
signal,
|
|
683
|
+
);
|
|
684
|
+
if (result.kind !== "relationships") throw new Error("tau-ast returned the wrong result for relationships");
|
|
685
|
+
return result as unknown as RelationshipResult;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async planEdit(locator: string, edit: EditOperation, signal: AbortSignal | undefined): Promise<EditPlanResult> {
|
|
689
|
+
const result = await this.request(
|
|
690
|
+
{ operation: "planEdit", locator, edit, budgets: RECURSIVE_OUTLINE_BUDGETS },
|
|
691
|
+
signal,
|
|
692
|
+
);
|
|
693
|
+
if (result.kind !== "editPlan") throw new Error("tau-ast returned the wrong result for edit planning");
|
|
694
|
+
return result as unknown as EditPlanResult;
|
|
695
|
+
}
|
|
696
|
+
|
|
168
697
|
async shutdown(): Promise<void> {
|
|
169
698
|
const child = this.child;
|
|
170
699
|
if (!child) return;
|
|
171
700
|
this.child = undefined;
|
|
172
701
|
this.startPromise = undefined;
|
|
702
|
+
this.capabilities = undefined;
|
|
173
703
|
this.rejectPending(new Error("tau-ast worker shut down"));
|
|
174
704
|
child.stdin.end();
|
|
175
705
|
if (child.exitCode !== null) return;
|
|
@@ -201,11 +731,12 @@ export class AstWorkerClient implements AstClient {
|
|
|
201
731
|
}
|
|
202
732
|
|
|
203
733
|
private async start(): Promise<void> {
|
|
204
|
-
const resolution = this.
|
|
205
|
-
? { command: this.command }
|
|
206
|
-
: resolveAstWorkerCommand(fileURLToPath(new URL("../../", import.meta.url)), process.platform, process.arch);
|
|
734
|
+
const resolution = this.resolveCommand();
|
|
207
735
|
if ("error" in resolution) throw resolution.error;
|
|
208
|
-
const child = spawn(resolution.command, this.args, {
|
|
736
|
+
const child = spawn(resolution.command, this.args, {
|
|
737
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
738
|
+
});
|
|
739
|
+
this.generation += 1;
|
|
209
740
|
this.child = child;
|
|
210
741
|
this.incoming = Buffer.alloc(0);
|
|
211
742
|
this.stderr = "";
|
|
@@ -221,17 +752,59 @@ export class AstWorkerClient implements AstClient {
|
|
|
221
752
|
const suffix = this.stderr.trim() ? `: ${this.stderr.trim()}` : "";
|
|
222
753
|
this.fail(child, new Error(`tau-ast exited (${signal ?? code ?? "unknown"})${suffix}`), false);
|
|
223
754
|
});
|
|
755
|
+
const handshakeTimeout = setTimeout(
|
|
756
|
+
() => this.fail(child, new Error(`tau-ast handshake timed out after ${HANDSHAKE_TIMEOUT_MS}ms`)),
|
|
757
|
+
HANDSHAKE_TIMEOUT_MS,
|
|
758
|
+
);
|
|
224
759
|
try {
|
|
225
760
|
const result = await this.send({ operation: "handshake" }, undefined);
|
|
226
761
|
if (result.kind !== "handshake") throw new Error("tau-ast handshake returned the wrong result");
|
|
762
|
+
if (!Array.isArray(result.supportedLanguages)) {
|
|
763
|
+
throw new Error("tau-ast handshake omitted supported languages");
|
|
764
|
+
}
|
|
765
|
+
this.capabilities = result.supportedLanguages.filter(isAstLanguage);
|
|
227
766
|
} catch (error) {
|
|
228
767
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
229
768
|
this.fail(child, failure);
|
|
230
769
|
throw failure;
|
|
770
|
+
} finally {
|
|
771
|
+
clearTimeout(handshakeTimeout);
|
|
231
772
|
}
|
|
232
773
|
}
|
|
233
774
|
|
|
234
775
|
private send(request: WorkerRequestPayload, signal: AbortSignal | undefined): Promise<Record<string, unknown>> {
|
|
776
|
+
return this.sendPending(request, signal, (resolve, reject, removeAbortListener) => ({
|
|
777
|
+
kind: "unary",
|
|
778
|
+
resolve,
|
|
779
|
+
reject,
|
|
780
|
+
removeAbortListener,
|
|
781
|
+
}));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private sendRecursive(
|
|
785
|
+
request: WorkerRequestPayload,
|
|
786
|
+
callbacks: RecursiveOutlineCallbacks,
|
|
787
|
+
signal: AbortSignal | undefined,
|
|
788
|
+
): Promise<RecursiveOutlineSummary> {
|
|
789
|
+
return this.sendPending(request, signal, (resolve, reject, removeAbortListener) => ({
|
|
790
|
+
kind: "recursiveOutline",
|
|
791
|
+
started: false,
|
|
792
|
+
callbacks,
|
|
793
|
+
resolve,
|
|
794
|
+
reject,
|
|
795
|
+
removeAbortListener,
|
|
796
|
+
}));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private sendPending<T>(
|
|
800
|
+
request: WorkerRequestPayload,
|
|
801
|
+
signal: AbortSignal | undefined,
|
|
802
|
+
createPending: (
|
|
803
|
+
resolve: (value: T) => void,
|
|
804
|
+
reject: (error: Error) => void,
|
|
805
|
+
removeAbortListener: () => void,
|
|
806
|
+
) => PendingRequest,
|
|
807
|
+
): Promise<T> {
|
|
235
808
|
const child = this.child;
|
|
236
809
|
if (!child) return Promise.reject(new Error("tau-ast worker is not running"));
|
|
237
810
|
if (signal?.aborted) return Promise.reject(new Error("tau-ast request cancelled"));
|
|
@@ -241,14 +814,13 @@ export class AstWorkerClient implements AstClient {
|
|
|
241
814
|
frame.writeUInt32BE(payload.length, 0);
|
|
242
815
|
payload.copy(frame, 4);
|
|
243
816
|
|
|
244
|
-
return new Promise<
|
|
817
|
+
return new Promise<T>((resolve, reject) => {
|
|
245
818
|
const onAbort = (): void => this.fail(child, new Error("tau-ast request cancelled"));
|
|
246
819
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
247
|
-
this.pending.set(
|
|
248
|
-
|
|
249
|
-
reject,
|
|
250
|
-
|
|
251
|
-
});
|
|
820
|
+
this.pending.set(
|
|
821
|
+
requestId,
|
|
822
|
+
createPending(resolve, reject, () => signal?.removeEventListener("abort", onAbort)),
|
|
823
|
+
);
|
|
252
824
|
child.stdin.write(frame, (error) => {
|
|
253
825
|
if (error) this.fail(child, new Error(`Failed to write tau-ast request: ${error.message}`));
|
|
254
826
|
});
|
|
@@ -258,51 +830,121 @@ export class AstWorkerClient implements AstClient {
|
|
|
258
830
|
private receive(child: ChildProcessWithoutNullStreams, chunk: Buffer): void {
|
|
259
831
|
if (this.child !== child) return;
|
|
260
832
|
this.incoming = Buffer.concat([this.incoming, chunk]);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
this.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
833
|
+
void this.drain(child);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private async drain(child: ChildProcessWithoutNullStreams): Promise<void> {
|
|
837
|
+
if (this.drainingChildren.has(child) || this.child !== child) return;
|
|
838
|
+
this.drainingChildren.add(child);
|
|
839
|
+
try {
|
|
840
|
+
while (this.child === child && this.incoming.length >= 4) {
|
|
841
|
+
const length = this.incoming.readUInt32BE(0);
|
|
842
|
+
if (length > MAX_FRAME_BYTES) {
|
|
843
|
+
this.fail(child, new Error(`tau-ast response frame exceeds ${MAX_FRAME_BYTES} bytes`));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (this.incoming.length < length + 4) return;
|
|
847
|
+
const payload = this.incoming.subarray(4, length + 4);
|
|
848
|
+
this.incoming = this.incoming.subarray(length + 4);
|
|
849
|
+
let response: WorkerResponse;
|
|
850
|
+
try {
|
|
851
|
+
response = JSON.parse(payload.toString("utf8")) as WorkerResponse;
|
|
852
|
+
} catch (error) {
|
|
853
|
+
this.fail(child, new Error(`tau-ast returned malformed JSON: ${String(error)}`));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const pending = this.pending.get(response.requestId);
|
|
857
|
+
if (!pending) {
|
|
858
|
+
this.fail(child, new Error(`tau-ast returned unknown request id ${String(response.requestId)}`));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (response.protocolVersion !== PROTOCOL_VERSION) {
|
|
862
|
+
const error = new Error(`tau-ast response used protocol ${response.protocolVersion}`);
|
|
863
|
+
this.fail(child, error);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (!response.success) {
|
|
867
|
+
this.pending.delete(response.requestId);
|
|
868
|
+
pending.removeAbortListener();
|
|
869
|
+
pending.reject(
|
|
870
|
+
new AstWorkerError(
|
|
871
|
+
response.error?.code ?? "worker_error",
|
|
872
|
+
response.error?.message ?? response.error?.code ?? "tau-ast request failed",
|
|
873
|
+
response.error?.sourceFingerprint,
|
|
874
|
+
),
|
|
875
|
+
);
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (!response.result || typeof response.result !== "object") {
|
|
879
|
+
this.fail(child, new Error("tau-ast response omitted its result"));
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (pending.kind === "unary") {
|
|
883
|
+
this.pending.delete(response.requestId);
|
|
884
|
+
pending.removeAbortListener();
|
|
885
|
+
pending.resolve(response.result);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
const kind = response.result.kind;
|
|
889
|
+
if (kind === "recursiveStart") {
|
|
890
|
+
if (pending.started || typeof response.result.path !== "string" || !isRecord(response.result.budgets)) {
|
|
891
|
+
this.fail(child, new Error("tau-ast recursive outline returned more than one start frame"));
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
pending.started = true;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (!pending.started) {
|
|
898
|
+
this.fail(child, new Error("tau-ast recursive outline omitted its start frame"));
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (kind === "recursiveFile") {
|
|
902
|
+
const file = parseRecursiveFile(response.result);
|
|
903
|
+
if (!file) {
|
|
904
|
+
this.fail(child, new Error("tau-ast recursive file frame is malformed"));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
await pending.callbacks.onFile(response.result.relativePath as string, file);
|
|
908
|
+
if (this.child !== child) return;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (kind === "recursiveDiagnostic") {
|
|
912
|
+
const diagnostic = parseRecursiveDiagnostic(response.result);
|
|
913
|
+
if (!diagnostic) {
|
|
914
|
+
this.fail(child, new Error("tau-ast recursive diagnostic frame is malformed"));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
await pending.callbacks.onDiagnostic(diagnostic);
|
|
918
|
+
if (this.child !== child) return;
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
if (kind === "recursiveComplete") {
|
|
922
|
+
const summary = parseRecursiveSummary(response.result);
|
|
923
|
+
if (!summary) {
|
|
924
|
+
this.fail(child, new Error("tau-ast recursive completion frame is malformed"));
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
this.pending.delete(response.requestId);
|
|
928
|
+
pending.removeAbortListener();
|
|
929
|
+
pending.resolve(summary);
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
this.fail(child, new Error(`tau-ast recursive outline returned unexpected frame ${String(kind)}`));
|
|
288
933
|
return;
|
|
289
934
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!response.result || typeof response.result !== "object") {
|
|
295
|
-
pending.reject(new Error("tau-ast response omitted its result"));
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
pending.resolve(response.result);
|
|
935
|
+
} catch (error) {
|
|
936
|
+
this.fail(child, error instanceof Error ? error : new Error(String(error)));
|
|
937
|
+
} finally {
|
|
938
|
+
this.drainingChildren.delete(child);
|
|
299
939
|
}
|
|
300
940
|
}
|
|
301
941
|
|
|
302
942
|
private fail(child: ChildProcessWithoutNullStreams, error: Error, kill = true): void {
|
|
303
943
|
if (this.child !== child) return;
|
|
304
944
|
this.child = undefined;
|
|
945
|
+
this.generation += 1;
|
|
305
946
|
this.incoming = Buffer.alloc(0);
|
|
947
|
+
this.capabilities = undefined;
|
|
306
948
|
this.rejectPending(error);
|
|
307
949
|
if (kill && child.exitCode === null) child.kill();
|
|
308
950
|
}
|
|
@@ -315,3 +957,69 @@ export class AstWorkerClient implements AstClient {
|
|
|
315
957
|
this.pending.clear();
|
|
316
958
|
}
|
|
317
959
|
}
|
|
960
|
+
|
|
961
|
+
function parseRecursiveFile(result: Record<string, unknown>): OutlineFileResult | undefined {
|
|
962
|
+
if (typeof result.relativePath !== "string" || !isRecord(result.file)) return undefined;
|
|
963
|
+
const file = result.file;
|
|
964
|
+
if (
|
|
965
|
+
typeof file.path !== "string" ||
|
|
966
|
+
!isAstLanguage(file.language) ||
|
|
967
|
+
typeof file.sourceFingerprint !== "string" ||
|
|
968
|
+
!isCount(file.byteLength) ||
|
|
969
|
+
!isCount(file.lineCount) ||
|
|
970
|
+
!isRecord(file.diagnostics) ||
|
|
971
|
+
!isCount(file.diagnostics.errorNodes) ||
|
|
972
|
+
!isCount(file.diagnostics.missingNodes) ||
|
|
973
|
+
!Array.isArray(file.items)
|
|
974
|
+
) {
|
|
975
|
+
return undefined;
|
|
976
|
+
}
|
|
977
|
+
return file as unknown as OutlineFileResult;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function parseRecursiveDiagnostic(result: Record<string, unknown>): RecursiveOutlineDiagnostic | undefined {
|
|
981
|
+
if (
|
|
982
|
+
typeof result.relativePath !== "string" ||
|
|
983
|
+
typeof result.code !== "string" ||
|
|
984
|
+
typeof result.message !== "string" ||
|
|
985
|
+
(result.language !== undefined && !isAstLanguage(result.language)) ||
|
|
986
|
+
(result.sourceFingerprint !== undefined && typeof result.sourceFingerprint !== "string")
|
|
987
|
+
) {
|
|
988
|
+
return undefined;
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
relativePath: result.relativePath,
|
|
992
|
+
language: result.language as AstLanguage | undefined,
|
|
993
|
+
code: result.code,
|
|
994
|
+
message: result.message,
|
|
995
|
+
sourceFingerprint: result.sourceFingerprint as string | undefined,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function parseRecursiveSummary(result: Record<string, unknown>): RecursiveOutlineSummary | undefined {
|
|
1000
|
+
const counts = [
|
|
1001
|
+
"discoveredFiles",
|
|
1002
|
+
"supportedFiles",
|
|
1003
|
+
"unsupportedFiles",
|
|
1004
|
+
"emittedFiles",
|
|
1005
|
+
"unreadableFiles",
|
|
1006
|
+
"oversizedFiles",
|
|
1007
|
+
"failedFiles",
|
|
1008
|
+
"parserDegradedFiles",
|
|
1009
|
+
"totalByteLength",
|
|
1010
|
+
"totalLineCount",
|
|
1011
|
+
] as const;
|
|
1012
|
+
const limits = ["fileLimitReached", "sourceByteLimitReached", "depthLimitReached", "elapsedLimitReached"] as const;
|
|
1013
|
+
if (counts.some((name) => !isCount(result[name])) || limits.some((name) => typeof result[name] !== "boolean")) {
|
|
1014
|
+
return undefined;
|
|
1015
|
+
}
|
|
1016
|
+
return result as unknown as RecursiveOutlineSummary;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1020
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function isCount(value: unknown): value is number {
|
|
1024
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1025
|
+
}
|