From 1b4838d422d9c7b0c47580fb44051a01ed9457f5 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sat, 30 May 2026 10:06:47 +0300 Subject: [PATCH] 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. --- .../services/team/TeamProvisioningService.ts | 23 +++++++++++++++---- .../TeamProvisioningPromptBuilders.ts | 16 +++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index e998befd..aee73fc2 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -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(); 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, { diff --git a/src/main/services/team/provisioning/TeamProvisioningPromptBuilders.ts b/src/main/services/team/provisioning/TeamProvisioningPromptBuilders.ts index 4bc8c574..241e10a2 100644 --- a/src/main/services/team/provisioning/TeamProvisioningPromptBuilders.ts +++ b/src/main/services/team/provisioning/TeamProvisioningPromptBuilders.ts @@ -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) {