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:
777genius 2026-05-30 10:06:47 +03:00
parent f79ea145d7
commit 1b4838d422
2 changed files with 31 additions and 8 deletions

View file

@ -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, {

View file

@ -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) {