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;
|
normalizedText: string;
|
||||||
observedAt: string;
|
observedAt: string;
|
||||||
parsedAgentName: string | null;
|
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 {
|
interface ParsedBootstrapTranscriptTailLine {
|
||||||
|
|
@ -714,6 +718,11 @@ interface ParsedBootstrapTranscriptTailLine {
|
||||||
text: string | null;
|
text: string | null;
|
||||||
normalizedText: string | null;
|
normalizedText: string | null;
|
||||||
parsedAgentName: 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 {
|
interface ParsedBootstrapTranscriptTailCacheEntry {
|
||||||
|
|
@ -30439,6 +30448,7 @@ export class TeamProvisioningService {
|
||||||
observedAt:
|
observedAt:
|
||||||
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
|
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
|
||||||
parsedAgentName,
|
parsedAgentName,
|
||||||
|
parsedLine,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const hasUnambiguousMatchingBootstrapContext =
|
const hasUnambiguousMatchingBootstrapContext =
|
||||||
|
|
@ -30449,7 +30459,14 @@ export class TeamProvisioningService {
|
||||||
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
||||||
const candidate = candidates[index];
|
const candidate = candidates[index];
|
||||||
if (!candidate) continue;
|
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 (reason) {
|
||||||
if (
|
if (
|
||||||
!candidate.parsedAgentName &&
|
!candidate.parsedAgentName &&
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue