@@ -287,6 +287,12 @@ const SYNARA_BROWSER_TOOL_NAME_BY_PRESENTATION = new Map<string, SynaraBrowserTo
287287 ] ) ,
288288) ;
289289
290+ const SYNARA_TOOL_NAME_STEMS = new Set < string > (
291+ Object . keys ( SYNARA_MCP_TOOL_PRESENTATIONS ) . map ( ( toolName ) =>
292+ toolName . replace ( / ^ s y n a r a _ / , "" ) ,
293+ ) ,
294+ ) ;
295+
290296const SYNARA_MCP_TOOL_PRESENTATION_ENTRIES = Object . entries ( SYNARA_MCP_TOOL_PRESENTATIONS ) . map (
291297 ( [ toolName , presentation ] ) => ( {
292298 toolName,
@@ -301,6 +307,9 @@ function extractSynaraMcpToolName(normalizedCandidate: string): string | null {
301307 if ( BROWSER_TOOL_NAME_SET . has ( normalizedCandidate ) ) {
302308 return `synara_${ normalizedCandidate } ` ;
303309 }
310+ if ( SYNARA_TOOL_NAME_STEMS . has ( normalizedCandidate ) ) {
311+ return `synara_${ normalizedCandidate } ` ;
312+ }
304313 if ( normalizedCandidate . startsWith ( "mcp_synara_synara_" ) ) {
305314 return normalizedCandidate . slice ( "mcp_synara_" . length ) ;
306315 }
@@ -316,19 +325,42 @@ function extractSynaraMcpToolName(normalizedCandidate: string): string | null {
316325 return null ;
317326}
318327
328+ function expandSynaraMcpCandidateInputs (
329+ candidate : string | null | undefined ,
330+ ) : ReadonlyArray < string > {
331+ if ( ! candidate ) {
332+ return [ ] ;
333+ }
334+ const trimmed = candidate . trim ( ) . replace ( / \s + / g, " " ) ;
335+ if ( ! trimmed ) {
336+ return [ ] ;
337+ }
338+ const variants = new Set < string > ( [ trimmed ] ) ;
339+ const withPrefixedRemoved = trimmed . replace ( / ^ f o r k a r a : \s * / i, "" ) . trim ( ) ;
340+ if ( withPrefixedRemoved && withPrefixedRemoved !== trimmed ) {
341+ variants . add ( withPrefixedRemoved ) ;
342+ }
343+ const withRepeatedPrefixRemoved = trimmed . replace ( / ^ f o r k a r a : \s * f o r k a r a \s + / i, "" ) . trim ( ) ;
344+ if ( withRepeatedPrefixRemoved ) {
345+ variants . add ( withRepeatedPrefixRemoved ) ;
346+ }
347+ return [ ...variants ] ;
348+ }
349+
319350function resolveSynaraBrowserToolName (
320351 candidates : ReadonlyArray < string | null | undefined > ,
321352) : SynaraBrowserToolName | null {
322353 for ( const candidate of candidates ) {
323- if ( ! candidate ) continue ;
324- const normalizedCandidate = normalizeSynaraMcpIdentifier ( candidate ) ;
325- const extractedToolName = extractSynaraMcpToolName ( normalizedCandidate ) ;
326- const candidateToolName =
327- extractedToolName ??
328- SYNARA_BROWSER_TOOL_NAME_BY_PRESENTATION . get ( normalizedCandidate ) ??
329- normalizedCandidate ;
330- if ( candidateToolName in SYNARA_BROWSER_TOOL_PRESENTATIONS ) {
331- return candidateToolName as SynaraBrowserToolName ;
354+ for ( const expandedCandidate of expandSynaraMcpCandidateInputs ( candidate ) ) {
355+ const normalizedCandidate = normalizeSynaraMcpIdentifier ( expandedCandidate ) ;
356+ const extractedToolName = extractSynaraMcpToolName ( normalizedCandidate ) ;
357+ const candidateToolName =
358+ extractedToolName ??
359+ SYNARA_BROWSER_TOOL_NAME_BY_PRESENTATION . get ( normalizedCandidate ) ??
360+ normalizedCandidate ;
361+ if ( candidateToolName in SYNARA_BROWSER_TOOL_PRESENTATIONS ) {
362+ return candidateToolName as SynaraBrowserToolName ;
363+ }
332364 }
333365 }
334366 return null ;
@@ -351,53 +383,52 @@ function resolveSynaraMcpToolPresentation(
351383 candidates : ReadonlyArray < string | null | undefined > ,
352384) : SynaraMcpToolPresentation | null {
353385 for ( const candidate of candidates ) {
354- if ( ! candidate ) {
355- continue ;
356- }
357- const normalizedCandidate = normalizeSynaraMcpIdentifier ( candidate ) ;
358- for ( const entry of SYNARA_MCP_TOOL_PRESENTATION_ENTRIES ) {
359- if (
360- normalizedCandidate === entry . normalizedRunning ||
361- normalizedCandidate === entry . normalizedCompleted ||
362- normalizedCandidate === entry . normalizedFailed
363- ) {
364- return entry . presentation ;
386+ for ( const expandedCandidate of expandSynaraMcpCandidateInputs ( candidate ) ) {
387+ const normalizedCandidate = normalizeSynaraMcpIdentifier ( expandedCandidate ) ;
388+ for ( const entry of SYNARA_MCP_TOOL_PRESENTATION_ENTRIES ) {
389+ if (
390+ normalizedCandidate === entry . normalizedRunning ||
391+ normalizedCandidate === entry . normalizedCompleted ||
392+ normalizedCandidate === entry . normalizedFailed
393+ ) {
394+ return entry . presentation ;
395+ }
365396 }
397+ const toolName = extractSynaraMcpToolName ( normalizedCandidate ) ;
398+ const knownPresentation = toolName
399+ ? ( SYNARA_MCP_TOOL_PRESENTATIONS [ toolName as keyof typeof SYNARA_MCP_TOOL_PRESENTATIONS ] as
400+ | SynaraMcpToolPresentation
401+ | undefined )
402+ : undefined ;
403+ if ( knownPresentation ) {
404+ return knownPresentation ;
405+ }
406+ // Free-text summaries (e.g. reconciler activity lines) can begin with the
407+ // word "Forkara" and normalize into a fake tool identifier; only
408+ // identifier-shaped candidates may take an invented fallback presentation.
409+ if ( / \s / . test ( expandedCandidate . trim ( ) ) ) {
410+ continue ;
411+ }
412+ if ( normalizedCandidate . startsWith ( "synara_is_handling_" ) ) {
413+ return fallbackSynaraMcpToolPresentation (
414+ `synara_${ normalizedCandidate . slice ( "synara_is_handling_" . length ) } ` ,
415+ ) ;
416+ }
417+ if ( normalizedCandidate . startsWith ( "synara_handled_" ) ) {
418+ return fallbackSynaraMcpToolPresentation (
419+ `synara_${ normalizedCandidate . slice ( "synara_handled_" . length ) } ` ,
420+ ) ;
421+ }
422+ if ( normalizedCandidate . startsWith ( "synara_couldn_t_handle_" ) ) {
423+ return fallbackSynaraMcpToolPresentation (
424+ `synara_${ normalizedCandidate . slice ( "synara_couldn_t_handle_" . length ) } ` ,
425+ ) ;
426+ }
427+ if ( ! toolName ) {
428+ continue ;
429+ }
430+ return fallbackSynaraMcpToolPresentation ( toolName ) ;
366431 }
367- const toolName = extractSynaraMcpToolName ( normalizedCandidate ) ;
368- const knownPresentation = toolName
369- ? ( SYNARA_MCP_TOOL_PRESENTATIONS [ toolName as keyof typeof SYNARA_MCP_TOOL_PRESENTATIONS ] as
370- | SynaraMcpToolPresentation
371- | undefined )
372- : undefined ;
373- if ( knownPresentation ) {
374- return knownPresentation ;
375- }
376- // Free-text summaries (e.g. reconciler activity lines) can begin with the
377- // word "Forkara" and normalize into a fake tool identifier; only
378- // identifier-shaped candidates may take an invented fallback presentation.
379- if ( / \s / . test ( candidate . trim ( ) ) ) {
380- continue ;
381- }
382- if ( normalizedCandidate . startsWith ( "synara_is_handling_" ) ) {
383- return fallbackSynaraMcpToolPresentation (
384- `synara_${ normalizedCandidate . slice ( "synara_is_handling_" . length ) } ` ,
385- ) ;
386- }
387- if ( normalizedCandidate . startsWith ( "synara_handled_" ) ) {
388- return fallbackSynaraMcpToolPresentation (
389- `synara_${ normalizedCandidate . slice ( "synara_handled_" . length ) } ` ,
390- ) ;
391- }
392- if ( normalizedCandidate . startsWith ( "synara_couldn_t_handle_" ) ) {
393- return fallbackSynaraMcpToolPresentation (
394- `synara_${ normalizedCandidate . slice ( "synara_couldn_t_handle_" . length ) } ` ,
395- ) ;
396- }
397- if ( ! toolName ) {
398- continue ;
399- }
400- return fallbackSynaraMcpToolPresentation ( toolName ) ;
401432 }
402433 return null ;
403434}
0 commit comments