restructure: session logic out of store/, stores into store/
- session.go, sessionfile.go: moved to main package (lifecycle) - store/tool.go, store/root.go: store constructors where they belong - main_test.go: session tests in main package - store/store_test.go: only store-level tests remain - Removed: root.go, tools.go from main (moved to store/) - Clean separation: store/ = abstraction, main = domain
This commit is contained in:
parent
6b1ba9a998
commit
52ad150c22
File diff suppressed because it is too large
Load Diff
Binary file not shown.
24
server.go
24
server.go
|
|
@ -26,22 +26,12 @@ import (
|
|||
|
||||
// Re-export store types for use in server.go.
|
||||
type (
|
||||
StoreEntry = store.StoreEntry
|
||||
Store = store.Store
|
||||
Session = store.Session
|
||||
SessionManager = store.SessionManager
|
||||
StoreEntry = store.StoreEntry
|
||||
Store = store.Store
|
||||
|
||||
syntheticFileInfo = store.SyntheticFileInfo
|
||||
)
|
||||
|
||||
func syntheticEntry(name string, mode os.FileMode) os.DirEntry {
|
||||
return store.FileEntry(name, mode)
|
||||
}
|
||||
|
||||
func syntheticDirEntry(name string, mode os.FileMode) os.DirEntry {
|
||||
return store.DirEntry(name, mode)
|
||||
}
|
||||
|
||||
const (
|
||||
QTDir = plan9.QTDIR
|
||||
QTFile = plan9.QTFILE
|
||||
|
|
@ -113,19 +103,19 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server {
|
|||
agentStore: store.NewDirStore(agentsDirs, 0644),
|
||||
promptStore: store.NewDirStore(agent.PromptsDirs(), 0444, store.WithReadOnly()),
|
||||
memStore: store.NewDirStore([]string{memDir}, 0644),
|
||||
toolStore: store.NewDirStore([]string{execute.ToolsPath()}, 0755, store.WithIndex(toolIndex)),
|
||||
toolStore: store.NewDirStore([]string{execute.ToolsPath()}, 0755, store.WithIndex(store.ToolIndex)),
|
||||
utilStore: store.NewDirStore([]string{paths.CfgDir() + "/scripts/u"}, 0555, store.WithReadOnly()),
|
||||
pluginStore: store.NewDirStore([]string{execute.PluginsPath()}, 0555, store.WithReadOnly()),
|
||||
skillStore: store.NewSkillStore(),
|
||||
transcriptStore: store.NewDirStore([]string{transcriptDir}, 0444, store.WithReadOnly()),
|
||||
tmpStore: store.NewDirStore([]string{tmpDir}, 0600),
|
||||
rootStore: NewRootStore(),
|
||||
rootStore: store.NewRootStore(),
|
||||
groups: make(map[string]map[string]bool),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(s)
|
||||
}
|
||||
s.sessionMgr = store.NewSessionManager(store.SessionManagerConfig{
|
||||
s.sessionMgr = NewSessionManager(SessionManagerConfig{
|
||||
AgentsDir: agentsDir,
|
||||
SessionsDir: sessionsDir,
|
||||
Log: s.log,
|
||||
|
|
@ -474,7 +464,7 @@ func isSessionManagerFile(path string) bool {
|
|||
if !ok || strings.Contains(name, "/") {
|
||||
return false
|
||||
}
|
||||
_, ok = store.SessionManagerFileMode(name)
|
||||
_, ok = SessionManagerFileMode(name)
|
||||
return ok
|
||||
}
|
||||
|
||||
|
|
@ -1087,7 +1077,7 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
if path == "/backends" || path == "/help" {
|
||||
mode = 0444
|
||||
} else if isSessionManagerFile(path) {
|
||||
mode_, _ := store.SessionManagerFileMode(base)
|
||||
mode_, _ := SessionManagerFileMode(base)
|
||||
mode = plan9.Perm(mode_)
|
||||
} else if strings.HasPrefix(path, "/a/") {
|
||||
mode = 0666
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package store
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"ollie/pkg/paths"
|
||||
"ollie/pkg/tools"
|
||||
"ollie/pkg/tools/execute"
|
||||
"olliesrv/store"
|
||||
)
|
||||
|
||||
// Session holds all state for one agent session.
|
||||
|
|
@ -112,7 +113,7 @@ type SessionManagerConfig struct {
|
|||
OnRename func(oldID, newID string)
|
||||
SaveTranscript func([]byte) error
|
||||
// ToolStore provides access to the shared tool directory.
|
||||
ToolStore Store
|
||||
ToolStore store.Store
|
||||
// NewCore, if non-nil, replaces the default backend.New + agent.NewAgentCore
|
||||
// path. It receives the session ID, agent name, and cwd, and returns a Core.
|
||||
NewCore func(sessionID, agentName, cwd string) (agent.Core, error)
|
||||
|
|
@ -124,7 +125,7 @@ type SessionManagerConfig struct {
|
|||
|
||||
// SessionManager manages session lifecycle and exposes sessions as a DirStore.
|
||||
type SessionManager struct {
|
||||
store *DirStore
|
||||
store *store.DirStore
|
||||
cfg SessionManagerConfig
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*Session
|
||||
|
|
@ -132,7 +133,7 @@ type SessionManager struct {
|
|||
}
|
||||
|
||||
// Store returns the DirStore view of the session namespace.
|
||||
func (s *SessionManager) Store() *DirStore { return s.store }
|
||||
func (s *SessionManager) Store() *store.DirStore { return s.store }
|
||||
|
||||
// nextUname generates the next uname atomically.
|
||||
func (s *SessionManager) nextUname() string {
|
||||
|
|
@ -151,14 +152,14 @@ func NewSessionManager(cfg SessionManagerConfig) *SessionManager {
|
|||
sessions: make(map[string]*Session),
|
||||
}
|
||||
ss.nextUID.Store(9999)
|
||||
ss.store = NewDirStore(nil, 0,
|
||||
WithStat(func(_ []string, name string) (os.FileInfo, error) { return ss.stat(name) }),
|
||||
WithOpener(func(_ []string, name string) (StoreEntry, error) { return ss.openEntry(name) }),
|
||||
WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
|
||||
WithListDir(func(_ []string, name string) ([]os.DirEntry, error) { return ss.ListDir(name) }),
|
||||
WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
|
||||
WithCreator(func(_ []string, name string, _ os.FileMode) error { return ss.create(name) }),
|
||||
WithRenamer(func(_ []string, old, new string) error { return ss.renameSession(old, new) }),
|
||||
ss.store = store.NewDirStore(nil, 0,
|
||||
store.WithStat(func(_ []string, name string) (os.FileInfo, error) { return ss.stat(name) }),
|
||||
store.WithOpener(func(_ []string, name string) (store.StoreEntry, error) { return ss.openEntry(name) }),
|
||||
store.WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
|
||||
store.WithListDir(func(_ []string, name string) ([]os.DirEntry, error) { return ss.ListDir(name) }),
|
||||
store.WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
|
||||
store.WithCreator(func(_ []string, name string, _ os.FileMode) error { return ss.create(name) }),
|
||||
store.WithRenamer(func(_ []string, old, new string) error { return ss.renameSession(old, new) }),
|
||||
)
|
||||
return ss
|
||||
}
|
||||
|
|
@ -173,11 +174,11 @@ func (s *SessionManager) AddSession(sess *Session) {
|
|||
func (s *SessionManager) list() ([]os.DirEntry, error) {
|
||||
entries := make([]os.DirEntry, 0, len(sessionStoreOrder))
|
||||
for _, name := range sessionStoreOrder {
|
||||
entries = append(entries, FileEntry(name, sessionStoreFiles[name]))
|
||||
entries = append(entries, store.FileEntry(name, sessionStoreFiles[name]))
|
||||
}
|
||||
s.mu.RLock()
|
||||
for id := range s.sessions {
|
||||
entries = append(entries, DirEntry(id, 0555))
|
||||
entries = append(entries, store.DirEntry(id, 0555))
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
return entries, nil
|
||||
|
|
@ -201,7 +202,7 @@ func (s *SessionManager) ListDir(name string) ([]os.DirEntry, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, DirEntry("t", 0500))
|
||||
entries = append(entries, store.DirEntry("t", 0500))
|
||||
return entries, nil
|
||||
}
|
||||
// {id}/t — list tools (filtered by allowTools)
|
||||
|
|
@ -223,7 +224,7 @@ func (s *SessionManager) ListDir(name string) ([]os.DirEntry, error) {
|
|||
func (s *SessionManager) stat(name string) (os.FileInfo, error) {
|
||||
// Top-level fixed files (new, idx, sh, etc.)
|
||||
if mode, ok := sessionStoreFiles[name]; ok {
|
||||
return &SyntheticFileInfo{Name_: name, Mode_: mode}, nil
|
||||
return &store.SyntheticFileInfo{Name_: name, Mode_: mode}, nil
|
||||
}
|
||||
parts := strings.SplitN(name, "/", 3)
|
||||
sessID := parts[0]
|
||||
|
|
@ -235,12 +236,12 @@ func (s *SessionManager) stat(name string) (os.FileInfo, error) {
|
|||
}
|
||||
// Session directory: {id}
|
||||
if len(parts) == 1 {
|
||||
return &SyntheticFileInfo{Name_: sessID, Mode_: 0555, IsDir_: true}, nil
|
||||
return &store.SyntheticFileInfo{Name_: sessID, Mode_: 0555, IsDir_: true}, nil
|
||||
}
|
||||
// Tools directory: {id}/t
|
||||
if parts[1] == "t" {
|
||||
if len(parts) == 2 {
|
||||
return &SyntheticFileInfo{Name_: "t", Mode_: 0500, IsDir_: true}, nil
|
||||
return &store.SyntheticFileInfo{Name_: "t", Mode_: 0500, IsDir_: true}, nil
|
||||
}
|
||||
// Tool file: {id}/t/{rel}
|
||||
if s.cfg.ToolStore != nil {
|
||||
|
|
@ -256,7 +257,7 @@ func (s *SessionManager) stat(name string) (os.FileInfo, error) {
|
|||
return sfs.Stat(parts[1])
|
||||
}
|
||||
|
||||
func (s *SessionManager) openEntry(name string) (StoreEntry, error) {
|
||||
func (s *SessionManager) openEntry(name string) (store.StoreEntry, error) {
|
||||
notBlocking := func(context.Context, string) ([]byte, string, error) {
|
||||
return nil, "", fmt.Errorf("blocking read not supported")
|
||||
}
|
||||
|
|
@ -264,8 +265,8 @@ func (s *SessionManager) openEntry(name string) (StoreEntry, error) {
|
|||
// Top-level fixed files.
|
||||
switch name {
|
||||
case "new":
|
||||
return &EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: "new", Mode_: 0666}, nil },
|
||||
return &store.EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &store.SyntheticFileInfo{Name_: "new", Mode_: 0666}, nil },
|
||||
ReadFn: func() ([]byte, error) {
|
||||
return []byte("name=\ncwd=\nbackend=\nmodel=\nagent=\nmaxTokens=\nmaxCompletionTokens=\ntemperature=\ntopP=\ntopK=\nminP=\ntopA=\nfrequencyPenalty=\npresencePenalty=\nrepetitionPenalty=\nreasoning=\nreasoningEffort=\nincludeReasoning=\nresponseFormat=\nstop=\nverbosity=\n"), nil
|
||||
},
|
||||
|
|
@ -275,16 +276,16 @@ func (s *SessionManager) openEntry(name string) (StoreEntry, error) {
|
|||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
case "idx":
|
||||
return &EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil },
|
||||
return &store.EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &store.SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil },
|
||||
ReadFn: func() ([]byte, error) { return s.index(), nil },
|
||||
WriteFn: func([]byte) error { return fmt.Errorf("idx: read-only") },
|
||||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
}
|
||||
if _, ok := sessionStoreFiles[name]; ok {
|
||||
return &EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name, Mode_: sessionStoreFiles[name]}, nil },
|
||||
return &store.EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &store.SyntheticFileInfo{Name_: name, Mode_: sessionStoreFiles[name]}, nil },
|
||||
ReadFn: func() ([]byte, error) {
|
||||
return s.cfg.ReadFile(paths.CfgDir() + "/scripts/s/" + name)
|
||||
},
|
||||
|
|
@ -331,12 +332,12 @@ func (s *SessionManager) openEntry(name string) (StoreEntry, error) {
|
|||
}
|
||||
|
||||
|
||||
func (s *SessionManager) openFilteredToolIdx(allowed map[string]bool) (StoreEntry, error) {
|
||||
func (s *SessionManager) openFilteredToolIdx(allowed map[string]bool) (store.StoreEntry, error) {
|
||||
base, err := s.cfg.ToolStore.Open("idx")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EntryConfig{
|
||||
return &store.EntryConfig{
|
||||
StatFn: base.Stat,
|
||||
ReadFn: func() ([]byte, error) {
|
||||
data, err := base.Read()
|
||||
|
|
@ -433,14 +434,14 @@ func (s *SessionManager) SessionByUname(uname string) *Session {
|
|||
}
|
||||
|
||||
// OpenStore returns a DirStore for the given session ID.
|
||||
func (s *SessionManager) OpenStore(id string) (*DirStore, error) {
|
||||
func (s *SessionManager) OpenStore(id string) (*store.DirStore, error) {
|
||||
if sess := s.Session(id); sess != nil {
|
||||
return s.openStore(sess)
|
||||
}
|
||||
return nil, fmt.Errorf("session not found: %s", id)
|
||||
}
|
||||
|
||||
func (s *SessionManager) openStore(sess *Session) (*DirStore, error) {
|
||||
func (s *SessionManager) openStore(sess *Session) (*store.DirStore, error) {
|
||||
id := sess.RunnableID()
|
||||
return NewSessionFileStore(
|
||||
sess,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package store
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"ollie/pkg/agent"
|
||||
"ollie/pkg/backend"
|
||||
olog "ollie/pkg/log"
|
||||
"olliesrv/store"
|
||||
)
|
||||
|
||||
// SessionFileList defines the fixed set of files in a session directory.
|
||||
|
|
@ -42,17 +43,17 @@ var SessionFileList = []struct {
|
|||
}
|
||||
|
||||
// SessionFileStore is a DirStore for a session directory.
|
||||
type SessionFileStore = DirStore
|
||||
type SessionFileStore = store.DirStore
|
||||
|
||||
func NewSessionFileStore(sess *Session, log *olog.Logger, kill func(), rename func(newID string) error, saveTranscript func([]byte) error) *SessionFileStore {
|
||||
h := &sessionHelper{sess: sess, log: log, kill: kill, rename: rename, saveTranscript: saveTranscript}
|
||||
specs := make([]FileSpec, len(SessionFileList))
|
||||
specs := make([]store.FileSpec, len(SessionFileList))
|
||||
for i, f := range SessionFileList {
|
||||
specs[i] = h.fileSpec(f.Name, f.Mode)
|
||||
specs[i].OneShot = f.OneShot
|
||||
specs[i].Async = f.Async
|
||||
}
|
||||
return NewFileStore(specs)
|
||||
return store.NewFileStore(specs)
|
||||
}
|
||||
|
||||
// sessionHelper holds the dependencies needed to build session FileSpecs.
|
||||
|
|
@ -64,8 +65,8 @@ type sessionHelper struct {
|
|||
saveTranscript func([]byte) error
|
||||
}
|
||||
|
||||
func (h *sessionHelper) fileSpec(name string, mode os.FileMode) FileSpec {
|
||||
fs := FileSpec{Name: name, Mode: mode}
|
||||
func (h *sessionHelper) fileSpec(name string, mode os.FileMode) store.FileSpec {
|
||||
fs := store.FileSpec{Name: name, Mode: mode}
|
||||
|
||||
// Read
|
||||
switch name {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -8,11 +8,10 @@ import (
|
|||
|
||||
"ollie/pkg/backend"
|
||||
"ollie/pkg/paths"
|
||||
"olliesrv/store"
|
||||
)
|
||||
|
||||
// NewRootStore returns a read-only Store for synthetic root-level files.
|
||||
func NewRootStore() *store.DirStore {
|
||||
func NewRootStore() *DirStore {
|
||||
helpPath := paths.CfgDir() + "/help.md"
|
||||
entries := map[string]func() ([]byte, error){
|
||||
"backends": func() ([]byte, error) {
|
||||
|
|
@ -27,27 +26,27 @@ func NewRootStore() *store.DirStore {
|
|||
}
|
||||
readOnly := func([]byte) error { return fmt.Errorf("read-only") }
|
||||
|
||||
return store.NewDirStore(nil, 0444,
|
||||
store.WithReadOnly(),
|
||||
store.WithResolver(func(_ []string, name string) (string, error) {
|
||||
return NewDirStore(nil, 0444,
|
||||
WithReadOnly(),
|
||||
WithResolver(func(_ []string, name string) (string, error) {
|
||||
if _, ok := entries[name]; ok {
|
||||
return name, nil
|
||||
}
|
||||
return "", fmt.Errorf("%s: not found", name)
|
||||
}),
|
||||
store.WithLister(func(_ []string) ([]os.DirEntry, error) {
|
||||
WithLister(func(_ []string) ([]os.DirEntry, error) {
|
||||
return []os.DirEntry{
|
||||
syntheticEntry("backends", 0444),
|
||||
syntheticEntry("help", 0444),
|
||||
FileEntry("backends", 0444),
|
||||
FileEntry("help", 0444),
|
||||
}, nil
|
||||
}),
|
||||
store.WithOpener(func(_ []string, name string) (StoreEntry, error) {
|
||||
WithOpener(func(_ []string, name string) (StoreEntry, error) {
|
||||
readFn, ok := entries[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: not found", name)
|
||||
}
|
||||
return &store.EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &syntheticFileInfo{Name_: name, Mode_: 0444}, nil },
|
||||
return &EntryConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name, Mode_: 0444}, nil },
|
||||
ReadFn: readFn,
|
||||
WriteFn: readOnly,
|
||||
BlockingReadFn: notBlocking,
|
||||
1211
store/store_test.go
1211
store/store_test.go
File diff suppressed because it is too large
Load Diff
|
|
@ -1,12 +1,12 @@
|
|||
package main
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// toolIndex generates a tool index from a store's file listing.
|
||||
func toolIndex(s Store) ([]byte, error) {
|
||||
// ToolIndex generates a tool index from a store's file listing.
|
||||
func ToolIndex(s Store) ([]byte, error) {
|
||||
entries, err := s.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
Reference in New Issue