This commit is contained in:
2026-06-22 08:55:57 +08:00
parent dbb87823e8
commit 6ade6e8fa9
325 changed files with 41131 additions and 855 deletions
+88
View File
@@ -0,0 +1,88 @@
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}
}