@@ -41,6 +41,127 @@ export interface ToolDisplayConfig {
4141 } ;
4242}
4343
44+ function formatActivateSkillResult ( content : unknown ) : string {
45+ const raw = String ( content || '' ) . trim ( ) ;
46+ if ( ! raw ) return 'Skill activated.' ;
47+
48+ const lines = raw . split ( '\n' ) ;
49+ const treeStart = lines . findIndex ( ( line ) => / ^ ( \/ | ~ \/ | [ A - Z a - z ] : [ \\ / ] ) / . test ( line . trim ( ) ) ) ;
50+ if ( treeStart === - 1 ) return raw ;
51+
52+ const beforeTree = lines . slice ( 0 , treeStart ) . join ( '\n' ) . trim ( ) ;
53+ const treeSection = lines . slice ( treeStart ) . join ( '\n' ) . trim ( ) ;
54+
55+ return `${ beforeTree } \n\n\`\`\`text\n${ treeSection } \n\`\`\`` . trim ( ) ;
56+ }
57+
58+ function parseJsonSafe ( input : unknown ) : any | null {
59+ if ( typeof input !== 'string' ) return null ;
60+ try {
61+ return JSON . parse ( input ) ;
62+ } catch {
63+ return null ;
64+ }
65+ }
66+
67+ function parseLsResultPayload ( result : any ) : {
68+ directory : string ;
69+ total : number ;
70+ truncated : boolean ;
71+ summary : string ;
72+ files : string [ ] ;
73+ items : Array < { name : string ; path : string ; isDirectory : boolean } > ;
74+ } | null {
75+ const raw = result ?. content ?? result ;
76+ const parsed = typeof raw === 'object' && raw !== null ? raw : parseJsonSafe ( String ( raw || '' ) ) ;
77+ if ( ! parsed || typeof parsed !== 'object' ) return null ;
78+
79+ const directory = typeof parsed . directory === 'string' ? parsed . directory : '.' ;
80+ const summary = typeof parsed . summary === 'string' ? parsed . summary : '' ;
81+ const truncated = Boolean ( parsed . truncated ) ;
82+ const total = typeof parsed . total === 'number' ? parsed . total : 0 ;
83+ const files = Array . isArray ( parsed . files )
84+ ? parsed . files . filter ( ( f : unknown ) => typeof f === 'string' )
85+ : [ ] ;
86+
87+ let items : Array < { name : string ; path : string ; isDirectory : boolean } > = [ ] ;
88+ if ( Array . isArray ( parsed . items ) ) {
89+ items = parsed . items
90+ . map ( ( item : any ) => ( {
91+ name : typeof item ?. name === 'string' ? item . name : '' ,
92+ path : typeof item ?. path === 'string' ? item . path : '' ,
93+ isDirectory : Boolean ( item ?. isDirectory )
94+ } ) )
95+ . filter ( ( item ) => item . path ) ;
96+ }
97+
98+ if ( items . length === 0 && files . length > 0 ) {
99+ items = files . map ( ( filePath : string ) => {
100+ const normalized = filePath . replace ( / \\ / g, '/' ) ;
101+ const isDirectory = normalized . endsWith ( '/' ) ;
102+ const clean = isDirectory ? normalized . slice ( 0 , - 1 ) : normalized ;
103+ const parts = clean . split ( '/' ) ;
104+ return {
105+ name : parts [ parts . length - 1 ] || clean ,
106+ path : normalized ,
107+ isDirectory
108+ } ;
109+ } ) ;
110+ }
111+
112+ return { directory, total, truncated, summary, files, items } ;
113+ }
114+
115+ function formatLsResultAsMarkdown ( result : any ) : string {
116+ const payload = parseLsResultPayload ( result ) ;
117+ if ( ! payload ) {
118+ const raw = String ( result ?. content || result || '' ) . trim ( ) ;
119+ return raw || 'No directory entries returned.' ;
120+ }
121+
122+ const sortedItems = [ ...payload . items ] . sort ( ( a , b ) => {
123+ if ( a . isDirectory !== b . isDirectory ) {
124+ return a . isDirectory ? - 1 : 1 ;
125+ }
126+ return a . name . localeCompare ( b . name ) ;
127+ } ) ;
128+
129+ const visibleCount = sortedItems . length ;
130+ const dirCount = sortedItems . filter ( ( item ) => item . isDirectory ) . length ;
131+ const fileCount = visibleCount - dirCount ;
132+ const treeHeader = payload . directory === '.' ? './' : `${ payload . directory . replace ( / \/ + $ / , '' ) } /` ;
133+ const treeLines = [ treeHeader ] ;
134+
135+ sortedItems . forEach ( ( item , index ) => {
136+ const prefix = index === visibleCount - 1 ? '└── ' : '├── ' ;
137+ treeLines . push ( `${ prefix } ${ item . name } ${ item . isDirectory ? '/' : '' } ` ) ;
138+ } ) ;
139+
140+ if ( visibleCount === 0 ) {
141+ treeLines . push ( '└── (empty)' ) ;
142+ }
143+
144+ const lines = [
145+ `**Path:** \`${ payload . directory } \`` ,
146+ `**Entries:** ${ visibleCount } ${ payload . total > visibleCount ? ` (showing first ${ visibleCount } of ${ payload . total } )` : '' } ` ,
147+ `**Breakdown:** ${ dirCount } dirs, ${ fileCount } files` ,
148+ '' ,
149+ '```text' ,
150+ treeLines . join ( '\n' ) ,
151+ '```'
152+ ] ;
153+
154+ if ( payload . truncated ) {
155+ lines . push ( '' , '_Results truncated to keep the panel responsive._' ) ;
156+ }
157+
158+ if ( payload . summary && ! / l i s t e d \s + \d + \s + i t e m s ? / i. test ( payload . summary ) ) {
159+ lines . push ( '' , `**Tool output:** ${ payload . summary . trim ( ) } ` ) ;
160+ }
161+
162+ return lines . join ( '\n' ) ;
163+ }
164+
44165export const TOOL_CONFIGS : Record < string , ToolDisplayConfig > = {
45166 // ============================================================================
46167 // COMMAND TOOLS
@@ -85,6 +206,29 @@ export const TOOL_CONFIGS: Record<string, ToolDisplayConfig> = {
85206 }
86207 } ,
87208
209+ activate_skill : {
210+ input : {
211+ type : 'one-line' ,
212+ label : 'Activate Skill' ,
213+ getValue : ( input ) => input . name || input . skill || 'skill' ,
214+ action : 'none' ,
215+ colorScheme : {
216+ primary : 'text-indigo-600 dark:text-indigo-300' ,
217+ border : 'border-indigo-400 dark:border-indigo-500' ,
218+ icon : 'text-indigo-500 dark:text-indigo-400'
219+ }
220+ } ,
221+ result : {
222+ type : 'collapsible' ,
223+ defaultOpen : true ,
224+ title : 'Skill activation details' ,
225+ contentType : 'markdown' ,
226+ getContentProps : ( result ) => ( {
227+ content : formatActivateSkillResult ( result ?. content || result )
228+ } )
229+ }
230+ } ,
231+
88232 // ============================================================================
89233 // FILE OPERATION TOOLS
90234 // ============================================================================
@@ -246,6 +390,33 @@ export const TOOL_CONFIGS: Record<string, ToolDisplayConfig> = {
246390 }
247391 } ,
248392
393+ LS : {
394+ input : {
395+ type : 'one-line' ,
396+ label : 'LS' ,
397+ getValue : ( input ) => input . dir_path || input . path || '.' ,
398+ action : 'none' ,
399+ colorScheme : {
400+ primary : 'text-gray-700 dark:text-gray-300' ,
401+ border : 'border-gray-300 dark:border-gray-600' ,
402+ icon : 'text-gray-500 dark:text-gray-400'
403+ }
404+ } ,
405+ result : {
406+ type : 'collapsible' ,
407+ defaultOpen : true ,
408+ title : ( result ) => {
409+ const payload = parseLsResultPayload ( result ) ;
410+ const count = payload ?. items ?. length ?? 0 ;
411+ return `Directory listing${ count > 0 ? ` (${ count } )` : '' } ` ;
412+ } ,
413+ contentType : 'markdown' ,
414+ getContentProps : ( result ) => ( {
415+ content : formatLsResultAsMarkdown ( result )
416+ } )
417+ }
418+ } ,
419+
249420 // ============================================================================
250421 // TODO TOOLS
251422 // ============================================================================
@@ -608,7 +779,7 @@ const GEMINI_TOOL_ALIASES: Record<string, string> = {
608779 google_web_search : 'WebSearch' ,
609780 web_fetch : 'WebFetch' ,
610781 complete_task : 'Default' ,
611- activate_skill : 'Default ' ,
782+ activate_skill : 'activate_skill ' ,
612783 save_memory : 'Default' ,
613784 get_internal_docs : 'Default'
614785} ;
@@ -619,32 +790,6 @@ for (const [alias, target] of Object.entries(GEMINI_TOOL_ALIASES)) {
619790 }
620791}
621792
622- if ( ! TOOL_CONFIGS . LS ) {
623- TOOL_CONFIGS . LS = {
624- input : {
625- type : 'one-line' ,
626- label : 'LS' ,
627- getValue : ( input ) => input . dir_path || input . path || '.' ,
628- action : 'none' ,
629- colorScheme : {
630- primary : 'text-gray-700 dark:text-gray-300' ,
631- border : 'border-gray-300 dark:border-gray-600' ,
632- icon : 'text-gray-500 dark:text-gray-400'
633- }
634- } ,
635- result : {
636- type : 'collapsible' ,
637- defaultOpen : false ,
638- title : 'Directory listing' ,
639- contentType : 'text' ,
640- getContentProps : ( result ) => ( {
641- content : String ( result ?. content || '' ) ,
642- format : 'plain'
643- } )
644- }
645- } ;
646- }
647-
648793/**
649794 * Get configuration for a tool, with fallback to default
650795 */
0 commit comments