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 {
|
interface BootstrapTranscriptOutcomeCandidate {
|
||||||
text: string;
|
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;
|
observedAt: string;
|
||||||
parsedAgentName: string | null;
|
parsedAgentName: string | null;
|
||||||
}
|
}
|
||||||
|
|
@ -708,6 +711,7 @@ interface ParsedBootstrapTranscriptTailLine {
|
||||||
rawTimestamp: string | null;
|
rawTimestamp: string | null;
|
||||||
timestampMs: number;
|
timestampMs: number;
|
||||||
text: string | null;
|
text: string | null;
|
||||||
|
normalizedText: string | null;
|
||||||
parsedAgentName: string | null;
|
parsedAgentName: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30334,7 +30338,7 @@ export class TeamProvisioningService {
|
||||||
const bootstrapContextMembers = new Set<string>();
|
const bootstrapContextMembers = new Set<string>();
|
||||||
const candidates: BootstrapTranscriptOutcomeCandidate[] = [];
|
const candidates: BootstrapTranscriptOutcomeCandidate[] = [];
|
||||||
for (const parsedLine of parsedLines) {
|
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)) {
|
if (sinceMs != null && (!Number.isFinite(timestampMs) || timestampMs < sinceMs)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -30347,15 +30351,24 @@ export class TeamProvisioningService {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const lineNormalizedText = normalizedText ?? '';
|
||||||
if (shouldCollectBootstrapContext) {
|
if (shouldCollectBootstrapContext) {
|
||||||
for (const contextMemberName of contextMemberNames) {
|
for (const contextMemberName of contextMemberNames) {
|
||||||
if (isBootstrapTranscriptContextText(text, teamName, contextMemberName)) {
|
if (
|
||||||
|
isBootstrapTranscriptContextText(
|
||||||
|
text,
|
||||||
|
teamName,
|
||||||
|
contextMemberName,
|
||||||
|
lineNormalizedText
|
||||||
|
)
|
||||||
|
) {
|
||||||
bootstrapContextMembers.add(contextMemberName.trim().toLowerCase());
|
bootstrapContextMembers.add(contextMemberName.trim().toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
candidates.push({
|
candidates.push({
|
||||||
text,
|
text,
|
||||||
|
normalizedText: lineNormalizedText,
|
||||||
observedAt:
|
observedAt:
|
||||||
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
|
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
|
||||||
parsedAgentName,
|
parsedAgentName,
|
||||||
|
|
@ -30384,7 +30397,8 @@ export class TeamProvisioningService {
|
||||||
const successSource = getBootstrapTranscriptSuccessSource(
|
const successSource = getBootstrapTranscriptSuccessSource(
|
||||||
candidate.text,
|
candidate.text,
|
||||||
teamName,
|
teamName,
|
||||||
memberName
|
memberName,
|
||||||
|
candidate.normalizedText
|
||||||
);
|
);
|
||||||
if (successSource) {
|
if (successSource) {
|
||||||
outcome = { kind: 'success', observedAt: candidate.observedAt, source: successSource };
|
outcome = { kind: 'success', observedAt: candidate.observedAt, source: successSource };
|
||||||
|
|
@ -30468,7 +30482,8 @@ export class TeamProvisioningService {
|
||||||
? parsed.agentName.trim().toLowerCase() || null
|
? parsed.agentName.trim().toLowerCase() || null
|
||||||
: null;
|
: null;
|
||||||
const text = extractTranscriptMessageText(parsed);
|
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, {
|
this.setParsedBootstrapTranscriptTailCacheEntry(filePath, {
|
||||||
|
|
|
||||||
|
|
@ -265,9 +265,13 @@ export function isBootstrapTranscriptSuccessText(
|
||||||
export function getBootstrapTranscriptSuccessSource(
|
export function getBootstrapTranscriptSuccessSource(
|
||||||
text: string,
|
text: string,
|
||||||
teamName: 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 {
|
): BootstrapTranscriptSuccessSource | null {
|
||||||
const normalizedText = text.replace(/\s+/g, ' ').trim().toLowerCase();
|
const normalizedText =
|
||||||
|
precomputedNormalizedText ?? text.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||||
if (!normalizedText) {
|
if (!normalizedText) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -298,9 +302,13 @@ export function getBootstrapTranscriptSuccessSource(
|
||||||
export function isBootstrapTranscriptContextText(
|
export function isBootstrapTranscriptContextText(
|
||||||
text: string,
|
text: string,
|
||||||
teamName: 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 {
|
): boolean {
|
||||||
const normalizedText = text.replace(/\s+/g, ' ').trim().toLowerCase();
|
const normalizedText =
|
||||||
|
precomputedNormalizedText ?? text.replace(/\s+/g, ' ').trim().toLowerCase();
|
||||||
const normalizedTeamName = teamName.trim().toLowerCase();
|
const normalizedTeamName = teamName.trim().toLowerCase();
|
||||||
const normalizedMemberName = memberName.trim().toLowerCase();
|
const normalizedMemberName = memberName.trim().toLowerCase();
|
||||||
if (!normalizedText || !normalizedTeamName || !normalizedMemberName) {
|
if (!normalizedText || !normalizedTeamName || !normalizedMemberName) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue