diff --git a/server.go b/server.go index c289d46..189aa97 100644 --- a/server.go +++ b/server.go @@ -251,7 +251,7 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server { sink: sink, agentsDir: agentsDir, agentStore: NewFlatDirStore(agentsDir, 0644), - promptStore: NewFlatDirStore(agent.DefaultPromptsDir(), 0444), + promptStore: store.NewFlatDirUnion(agent.PromptsDirs(), 0444), memStore: NewFlatDirStore(memDir, 0644), toolStore: NewToolStore(), utilStore: NewUtilStore(), diff --git a/store/store.go b/store/store.go index 4d17618..bb80689 100644 --- a/store/store.go +++ b/store/store.go @@ -111,6 +111,66 @@ func NewFlatDir(dir string, perm os.FileMode) Store { } } +// NewFlatDirUnion returns a read-only Store that merges multiple directories. +// Files are deduplicated by name; the first directory containing a name wins. +func NewFlatDirUnion(dirs []string, perm os.FileMode) Store { + notBlocking := func(context.Context, string) ([]byte, error) { + return nil, fmt.Errorf("blocking read not supported") + } + + resolve := func(name string) (string, error) { + for _, dir := range dirs { + p := filepath.Join(dir, name) + if _, err := os.Stat(p); err == nil { + return p, nil + } + } + return "", fmt.Errorf("%s: not found", name) + } + + return &storeConfig{ + StatFn: func(name string) (os.FileInfo, error) { + p, err := resolve(name) + if err != nil { + return nil, err + } + return os.Stat(p) + }, + ListFn: func() ([]os.DirEntry, error) { + seen := make(map[string]bool) + var result []os.DirEntry + for _, dir := range dirs { + entries, err := os.ReadDir(dir) + if err != nil { + continue + } + for _, e := range entries { + if !seen[e.Name()] { + seen[e.Name()] = true + result = append(result, e) + } + } + } + return result, nil + }, + OpenFn: func(name string) (StoreEntry, error) { + p, err := resolve(name) + if err != nil { + return nil, err + } + return &EntryConfig{ + StatFn: func() (os.FileInfo, error) { return os.Stat(p) }, + ReadFn: func() ([]byte, error) { return os.ReadFile(p) }, + WriteFn: func([]byte) error { return fmt.Errorf("%s: read-only", name) }, + BlockingReadFn: notBlocking, + }, nil + }, + CreateFn: func(string) error { return fmt.Errorf("read-only store") }, + DeleteFn: func(string) error { return fmt.Errorf("read-only store") }, + RenameFn: func(string, string) error { return fmt.Errorf("read-only store") }, + } +} + // SyntheticFileInfo implements os.FileInfo for entries with no backing file. type SyntheticFileInfo struct { Name_ string