@@ -3,7 +3,9 @@ import * as path from "node:path";
33
44import {
55 type ActionResult ,
6+ CapabilityError ,
67 logger as coreLogger ,
8+ getCapabilityRouter ,
79 type HandlerCallback ,
810 type IAgentRuntime ,
911 type Memory ,
@@ -34,6 +36,57 @@ interface LsEntry {
3436 size ?: number ;
3537}
3638
39+ function normalizeEntryKind ( kind : string ) : EntryType {
40+ if ( kind === "directory" ) return "dir" ;
41+ if ( kind === "symlink" ) return "symlink" ;
42+ return "file" ;
43+ }
44+
45+ async function listWithCapabilityRouter ( params : {
46+ runtime : IAgentRuntime ;
47+ dir : string ;
48+ ignore ?: string [ ] ;
49+ } ) : Promise <
50+ | {
51+ ok : true ;
52+ entries : LsEntry [ ] ;
53+ truncated : boolean ;
54+ totalAfterIgnore : number ;
55+ }
56+ | { ok : false ; reason : "unavailable" | "failed" ; message : string }
57+ > {
58+ const router = getCapabilityRouter ( params . runtime ) ;
59+ if ( ! router ) return { ok : false , reason : "unavailable" , message : "" } ;
60+ try {
61+ const result = await router . fs . list ( {
62+ path : params . dir ,
63+ limit : ENTRY_LIMIT ,
64+ includeHidden : true ,
65+ ...( params . ignore ? { ignore : params . ignore } : { } ) ,
66+ } ) ;
67+ return {
68+ ok : true ,
69+ entries : result . entries . map ( ( entry ) => {
70+ const type = normalizeEntryKind ( entry . kind ) ;
71+ return type === "file"
72+ ? { name : entry . name , type, size : entry . size }
73+ : { name : entry . name , type } ;
74+ } ) ,
75+ truncated : result . truncated ,
76+ totalAfterIgnore : result . totalAfterIgnore ,
77+ } ;
78+ } catch ( error ) {
79+ if (
80+ error instanceof CapabilityError &&
81+ error . code === "CAPABILITY_UNAVAILABLE"
82+ ) {
83+ return { ok : false , reason : "unavailable" , message : error . message } ;
84+ }
85+ const message = error instanceof Error ? error . message : String ( error ) ;
86+ return { ok : false , reason : "failed" , message } ;
87+ }
88+ }
89+
3790function globToRegExp ( pattern : string ) : RegExp {
3891 let regex = "" ;
3992 let i = 0 ;
@@ -114,11 +167,43 @@ export async function lsHandler(
114167 const dir = validation . resolved ;
115168
116169 const ignoreRaw = readArrayParam ( options , "ignore" ) ;
117- const ignoreMatchers : RegExp [ ] = ( ignoreRaw ?? [ ] )
118- . filter (
119- ( entry ) : entry is string => typeof entry === "string" && entry . length > 0 ,
120- )
121- . map ( ( entry ) => globToRegExp ( entry ) ) ;
170+ const ignore = ( ignoreRaw ?? [ ] ) . filter (
171+ ( entry ) : entry is string => typeof entry === "string" && entry . length > 0 ,
172+ ) ;
173+
174+ const routed = await listWithCapabilityRouter ( {
175+ runtime,
176+ dir,
177+ ignore : ignore . length > 0 ? ignore : undefined ,
178+ } ) ;
179+ if ( routed . ok ) {
180+ const lines = [
181+ `Directory: ${ dir } ` ,
182+ ...routed . entries . map ( ( e ) => ( e . type === "dir" ? `${ e . name } /` : e . name ) ) ,
183+ ] ;
184+ if ( routed . truncated ) {
185+ lines . push (
186+ `…[truncated, listed ${ ENTRY_LIMIT } of ${ routed . totalAfterIgnore } entries]` ,
187+ ) ;
188+ }
189+ const text = lines . join ( "\n" ) ;
190+ coreLogger . debug (
191+ `${ CODING_TOOLS_LOG_PREFIX } LS dir=${ dir } count=${ routed . entries . length } truncated=${ routed . truncated } ` ,
192+ ) ;
193+ if ( callback ) await callback ( { text, source : "coding-tools" } ) ;
194+ return successActionResult ( text , {
195+ entries : routed . entries ,
196+ truncated : routed . truncated ,
197+ } ) ;
198+ }
199+ if ( routed . reason === "failed" ) {
200+ return failureToActionResult ( {
201+ reason : "io_error" ,
202+ message : `readdir failed: ${ routed . message } ` ,
203+ } ) ;
204+ }
205+
206+ const ignoreMatchers : RegExp [ ] = ignore . map ( ( entry ) => globToRegExp ( entry ) ) ;
122207
123208 let names : string [ ] ;
124209 try {
0 commit comments