@@ -24,6 +24,14 @@ export const ACTIONABLE_COMMAND_PREFIXES = [
2424 */
2525const PLACEHOLDER_RE = / e x a m p l e \. (?: c o m | n e t | o r g ) | < [ ^ > ] + > / i;
2626
27+ function cleanCommandCandidate ( candidate : string ) : string {
28+ return candidate
29+ . trim ( )
30+ . replace ( / ^ [ ` * _ ] + / g, "" )
31+ . replace ( / [ ` * _ ] + $ / g, "" )
32+ . trim ( ) ;
33+ }
34+
2735/**
2836 * Scan the assistant's response text for slash commands that match
2937 * actionable prefixes. Returns deduplicated commands in order.
@@ -35,34 +43,40 @@ export function extractSuggestedCommands(text: string): string[] {
3543 const commands : string [ ] = [ ] ;
3644 const seen = new Set < string > ( ) ;
3745
46+ const addCommand = ( candidate : string ) : void => {
47+ const cmd = cleanCommandCandidate ( candidate ) ;
48+ if ( ! cmd || seen . has ( cmd ) || PLACEHOLDER_RE . test ( cmd ) ) return ;
49+ seen . add ( cmd ) ;
50+ commands . push ( cmd ) ;
51+ } ;
52+
3853 // Pattern 1: commands inside backticks — `/plugin enable fetch ...`
3954 // This catches inline code references the LLM wraps in backticks.
4055 const backtickRe =
4156 / ` ( \/ (?: p l u g i n \s + e n a b l e | p l u g i n \s + d i s a b l e | m c p \s + e n a b l e | b u f f e r | t i m e o u t | s e t ) \s [ ^ ` ] + ) ` / gi;
4257 for ( const m of text . matchAll ( backtickRe ) ) {
43- const cmd = m [ 1 ] . trim ( ) ;
44- if ( ! seen . has ( cmd ) && ! PLACEHOLDER_RE . test ( cmd ) ) {
45- seen . add ( cmd ) ;
46- commands . push ( cmd ) ;
47- }
58+ addCommand ( m [ 1 ] ) ;
59+ }
60+
61+ // Pattern 2: commands inside markdown bold — **/mcp enable ...**
62+ // The model often emphasises auth/setup commands this way.
63+ const boldRe =
64+ / \* \* ( \/ (?: p l u g i n \s + e n a b l e | p l u g i n \s + d i s a b l e | m c p \s + e n a b l e | b u f f e r | t i m e o u t | s e t ) \s (?: (? ! \* \* ) [ ^ \n ] ) + ) \* \* / gi;
65+ for ( const m of text . matchAll ( boldRe ) ) {
66+ addCommand ( m [ 1 ] ) ;
4867 }
4968
50- // Pattern 2 : bare commands as the start of a line (possibly indented).
69+ // Pattern 3 : bare commands as the start of a line (possibly indented).
5170 // Only matched if not already found via backtick pattern.
5271 for ( const line of text . split ( "\n" ) ) {
53- const trimmed = line . trim ( ) ;
72+ const trimmed = cleanCommandCandidate ( line ) ;
5473 if (
5574 trimmed . startsWith ( "/" ) &&
5675 ACTIONABLE_COMMAND_PREFIXES . some ( ( p ) =>
5776 trimmed . toLowerCase ( ) . startsWith ( p . toLowerCase ( ) ) ,
5877 )
5978 ) {
60- // Strip any trailing markdown/punctuation the LLM might append
61- const cleaned = trimmed . replace ( / [ ` * _ ] + $ / g, "" ) . trim ( ) ;
62- if ( cleaned && ! seen . has ( cleaned ) && ! PLACEHOLDER_RE . test ( cleaned ) ) {
63- seen . add ( cleaned ) ;
64- commands . push ( cleaned ) ;
65- }
79+ addCommand ( trimmed ) ;
6680 }
6781 }
6882
0 commit comments