perf: normalize bootstrap transcript lines once across members
isBootstrapTranscriptContextText and getBootstrapTranscriptSuccessSource each ran text.replace(/\s+/g,' ').trim().toLowerCase() internally. During launch the bootstrap scan checks every transcript line against every context member for every member's poll, so the same line was re-normalized up to (members x contextMembers) times per cycle. Profiling a 6-member mixed launch showed isBootstrapTranscriptContextText at ~11% main-thread JS even after the shared-parse cache. Precompute the normalized form once per parsed line (already cached) and pass it to both detection helpers via a new optional precomputedNormalizedText parameter. The value is identical to what the helpers computed internally, so detection is byte-for-byte unchanged; the helpers stay backward compatible for callers that omit it.
This commit is contained in:
parent
f79ea145d7
commit
1b4838d422
2 changed files with 31 additions and 8 deletions
|
|
@ -700,6 +700,9 @@ interface BootstrapTranscriptOutcomeLookupCacheEntry {
|
|||
|
||||
interface BootstrapTranscriptOutcomeCandidate {
|
||||
text: string;
|
||||
// text.replace(/\s+/g,' ').trim().toLowerCase(), computed once and reused across
|
||||
// members so success/context detection does not re-normalize the same line.
|
||||
normalizedText: string;
|
||||
observedAt: string;
|
||||
parsedAgentName: string | null;
|
||||
}
|
||||
|
|
@ -708,6 +711,7 @@ interface ParsedBootstrapTranscriptTailLine {
|
|||
rawTimestamp: string | null;
|
||||
timestampMs: number;
|
||||
text: string | null;
|
||||
normalizedText: string | null;
|
||||
parsedAgentName: string | null;
|
||||
}
|
||||
|
||||
|
|
@ -30334,7 +30338,7 @@ export class TeamProvisioningService {
|
|||
const bootstrapContextMembers = new Set<string>();
|
||||
const candidates: BootstrapTranscriptOutcomeCandidate[] = [];
|
||||
for (const parsedLine of parsedLines) {
|
||||
const { timestampMs, parsedAgentName, text, rawTimestamp } = parsedLine;
|
||||
const { timestampMs, parsedAgentName, text, rawTimestamp, normalizedText } = parsedLine;
|
||||
if (sinceMs != null && (!Number.isFinite(timestampMs) || timestampMs < sinceMs)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -30347,15 +30351,24 @@ export class TeamProvisioningService {
|
|||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
const lineNormalizedText = normalizedText ?? '';
|
||||
if (shouldCollectBootstrapContext) {
|
||||
for (const contextMemberName of contextMemberNames) {
|
||||
if (isBootstrapTranscriptContextText(text, teamName, contextMemberName)) {
|
||||
if (
|
||||
isBootstrapTranscriptContextText(
|
||||
text,
|
||||
teamName,
|
||||
contextMemberName,
|
||||
lineNormalizedText
|
||||
)
|
||||
) {
|
||||
bootstrapContextMembers.add(contextMemberName.trim().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
candidates.push({
|
||||
text,
|
||||
normalizedText: lineNormalizedText,
|
||||
observedAt:
|
||||
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
|
||||
parsedAgentName,
|
||||
|
|
@ -30384,7 +30397,8 @@ export class TeamProvisioningService {
|
|||
const successSource = getBootstrapTranscriptSuccessSource(
|
||||
candidate.text,
|
||||
teamName,
|
||||
memberName
|
||||
memberName,
|
||||
candidate.normalizedText
|
||||
);
|
||||
if (successSource) {
|
||||
outcome = { kind: 'success', observedAt: candidate.observedAt, source: successSource };
|
||||
|
|
@ -30468,7 +30482,8 @@ export class TeamProvisioningService {
|
|||
? parsed.agentName.trim().toLowerCase() || null
|
||||
: null;
|
||||
const text = extractTranscriptMessageText(parsed);
|
||||
lines.push({ rawTimestamp, timestampMs, text, parsedAgentName });
|
||||
const normalizedText = text ? text.replace(/\s+/g, ' ').trim().toLowerCase() : null;
|
||||
lines.push({ rawTimestamp, timestampMs, text, normalizedText, parsedAgentName });
|
||||
}
|
||||
}
|
||||
this.setParsedBootstrapTranscriptTailCacheEntry(filePath, {
|
||||
|
|
|
|||
|
|
@ -265,9 +265,13 @@ export function isBootstrapTranscriptSuccessText(
|
|||
export function getBootstrapTranscriptSuccessSource(
|
||||
text: string,
|
||||
teamName: string,
|
||||
memberName: string
|
||||
memberName: string,
|
||||
// Optional pre-normalized text, MUST equal text.replace(/\s+/g,' ').trim().toLowerCase().
|
||||
// Lets callers that scan one line against many members normalize it once.
|
||||
precomputedNormalizedText?: string
|
||||
): BootstrapTranscriptSuccessSource | null {
|
||||
const normalizedText = text.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
const normalizedText =
|
||||
precomputedNormalizedText ?? text.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
if (!normalizedText) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -298,9 +302,13 @@ export function getBootstrapTranscriptSuccessSource(
|
|||
export function isBootstrapTranscriptContextText(
|
||||
text: string,
|
||||
teamName: string,
|
||||
memberName: string
|
||||
memberName: string,
|
||||
// Optional pre-normalized text, MUST equal text.replace(/\s+/g,' ').trim().toLowerCase().
|
||||
// Lets callers that scan one line against many members normalize it once.
|
||||
precomputedNormalizedText?: string
|
||||
): boolean {
|
||||
const normalizedText = text.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
const normalizedText =
|
||||
precomputedNormalizedText ?? text.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
const normalizedTeamName = teamName.trim().toLowerCase();
|
||||
const normalizedMemberName = memberName.trim().toLowerCase();
|
||||
if (!normalizedText || !normalizedTeamName || !normalizedMemberName) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue