You've already forked agentic-coding-workflow
89 lines
2.4 KiB
Go
89 lines
2.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"sort"
|
|
)
|
|
|
|
// DefaultListMaxEntries caps the number of directory entries returned to bound
|
|
// payload size for large directories.
|
|
const DefaultListMaxEntries = 1000
|
|
|
|
// FsListHandler handles fs/list: a bounded directory listing inside the worktree.
|
|
type FsListHandler struct {
|
|
MaxEntries int
|
|
}
|
|
|
|
// NewFsListHandler constructs an FsListHandler with default caps.
|
|
func NewFsListHandler() *FsListHandler { return &FsListHandler{MaxEntries: DefaultListMaxEntries} }
|
|
|
|
func (h *FsListHandler) maxEntries() int {
|
|
if h.MaxEntries > 0 {
|
|
return h.MaxEntries
|
|
}
|
|
return DefaultListMaxEntries
|
|
}
|
|
|
|
type fsListParams struct {
|
|
SessionID string `json:"sessionId"`
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
type fsDirEntry struct {
|
|
Name string `json:"name"`
|
|
IsDir bool `json:"is_dir"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
type fsListResult struct {
|
|
Entries []fsDirEntry `json:"entries"`
|
|
Truncated bool `json:"truncated"`
|
|
}
|
|
|
|
// Handle lists the directory at sess.CwdPath/path. Returns FsError for path
|
|
// escape (-32001) or read failure. Results are sorted (dirs first, then name).
|
|
func (h *FsListHandler) Handle(_ context.Context, sess SessionContext, paramsJSON json.RawMessage) FsResult {
|
|
var p fsListParams
|
|
if err := json.Unmarshal(paramsJSON, &p); err != nil {
|
|
return FsResult{Err: &FsError{Code: -32602, Message: "invalid params: " + err.Error()}}
|
|
}
|
|
abs, err := resolveSafePath(sess.CwdPath, p.Path)
|
|
if err != nil {
|
|
return FsResult{Err: &FsError{Code: -32001, Message: err.Error()}}
|
|
}
|
|
dirents, err := os.ReadDir(abs)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return FsResult{Err: &FsError{Code: -32603, Message: "directory not found"}}
|
|
}
|
|
return FsResult{Err: &FsError{Code: -32603, Message: "list: " + err.Error()}}
|
|
}
|
|
|
|
max := h.maxEntries()
|
|
out := fsListResult{Entries: make([]fsDirEntry, 0, len(dirents))}
|
|
for _, de := range dirents {
|
|
if len(out.Entries) >= max {
|
|
out.Truncated = true
|
|
break
|
|
}
|
|
e := fsDirEntry{Name: de.Name(), IsDir: de.IsDir()}
|
|
if info, ierr := de.Info(); ierr == nil && !de.IsDir() {
|
|
e.Size = info.Size()
|
|
}
|
|
out.Entries = append(out.Entries, e)
|
|
}
|
|
sort.SliceStable(out.Entries, func(i, j int) bool {
|
|
if out.Entries[i].IsDir != out.Entries[j].IsDir {
|
|
return out.Entries[i].IsDir // dirs first
|
|
}
|
|
return out.Entries[i].Name < out.Entries[j].Name
|
|
})
|
|
|
|
res, _ := json.Marshal(out)
|
|
return FsResult{OK: res}
|
|
}
|