perf: memoize bootstrap failure-reason per parsed line across members
readRecentBootstrapTranscriptOutcome ran extractBootstrapFailureReason (regex + optional JSON.parse + ~40 substring scans per call) for every candidate line, once per team member: an anonymous transcript line (no agentName) passes the per-member filter for ALL members, so an N-member team re-extracted the same line's failure reason N times per scan -- a top main-thread cost in the warm launch profile (~5.4% with isBootstrapInstructionPrompt). Memoize the result on the shared parsed-tail line (parsedBootstrapTranscriptTailCache already reuses the same line objects across members and re-scans while the file is unchanged). The compute stays LAZY inside the newest-first candidate loop, so each line is extracted at most once across all members AND lines past the first match are never extracted -- preserving the early-break that an eager precompute would lose. The memo is keyed implicitly by the line's cache entry (filePath + mtime + size); a file change re-parses into fresh line objects, so it cannot drift from the line text. Pure-function memoization: byte-identical input (candidate.text === parsedLine.text) -> identical output, so failure/success/null outcomes and reasons are unchanged. Bootstrap-transcript outcome tests pass (306), no behavior change.
This commit is contained in:
parent
2dcd5cb6a8
commit
5c1d2e8d92
1 changed files with 18 additions and 1 deletions
|
|
@ -706,6 +706,10 @@ interface BootstrapTranscriptOutcomeCandidate {
|
|||
normalizedText: string;
|
||||
observedAt: string;
|
||||
parsedAgentName: string | null;
|
||||
// The shared parsed-tail line this candidate was built from. Used to memoize the
|
||||
// pure extractBootstrapFailureReason() result on the line itself so an N-member
|
||||
// team extracts each line's failure reason at most once instead of once per member.
|
||||
parsedLine: ParsedBootstrapTranscriptTailLine;
|
||||
}
|
||||
|
||||
interface ParsedBootstrapTranscriptTailLine {
|
||||
|
|
@ -714,6 +718,11 @@ interface ParsedBootstrapTranscriptTailLine {
|
|||
text: string | null;
|
||||
normalizedText: string | null;
|
||||
parsedAgentName: string | null;
|
||||
// Memoized extractBootstrapFailureReason(text): undefined = not computed yet,
|
||||
// null = computed/no failure reason, string = the failure reason. Lives as long as
|
||||
// this line's parse-cache entry (filePath + mtime + size); a file change re-parses
|
||||
// into fresh line objects, so the memo cannot drift from the line's text.
|
||||
bootstrapFailureReason?: string | null;
|
||||
}
|
||||
|
||||
interface ParsedBootstrapTranscriptTailCacheEntry {
|
||||
|
|
@ -30439,6 +30448,7 @@ export class TeamProvisioningService {
|
|||
observedAt:
|
||||
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
|
||||
parsedAgentName,
|
||||
parsedLine,
|
||||
});
|
||||
}
|
||||
const hasUnambiguousMatchingBootstrapContext =
|
||||
|
|
@ -30449,7 +30459,14 @@ export class TeamProvisioningService {
|
|||
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
||||
const candidate = candidates[index];
|
||||
if (!candidate) continue;
|
||||
const reason = extractBootstrapFailureReason(candidate.text);
|
||||
// Lazy + memoized on the shared parsed line: computed at most once per line
|
||||
// across all members and re-scans, and only for lines this newest-first loop
|
||||
// actually reaches (lines past the first match are never extracted).
|
||||
const cachedLine = candidate.parsedLine;
|
||||
if (cachedLine.bootstrapFailureReason === undefined) {
|
||||
cachedLine.bootstrapFailureReason = extractBootstrapFailureReason(candidate.text);
|
||||
}
|
||||
const reason = cachedLine.bootstrapFailureReason;
|
||||
if (reason) {
|
||||
if (
|
||||
!candidate.parsedAgentName &&
|
||||
|
|
|
|||
Loading…
Reference in a new issue