From e8239927decae7056a78c7db9d28cc1d0f587900 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 10 Aug 2026 20:57:32 +0200 Subject: [PATCH] toolsrv: move Registry to cmd/toolsrv, keep only client SDK in shared package Split toolsrv/registry.go: - cmd/toolsrv/registry.go: Registry type with session-scoped tool state (Load, Unload, Loaded, Lookup, Revision methods) - toolsrv/registry.go: shared types only (ToolInfo, ToolsPath, DiscoverTools) The toolsrv/ package is now a pure client SDK: - Dial, Conn for 9P connection - ToolInfo, ToolResult, HostInfo types - DiscoverTools for listing available tools Server-only code lives in cmd/toolsrv/: - Registry for session-scoped tool state - 9P handlers and execution logic - Sandbox integration --- cmd/toolsrv/fs9p.go | 4 +- cmd/toolsrv/fs9p_test.go | 4 +- cmd/toolsrv/main.go | 3 +- cmd/toolsrv/registry.go | 114 +++++++++++++++++++++++++++++++++++++++ cmd/toolsrv/server9p.go | 5 +- toolsrv/registry.go | 108 +------------------------------------ 6 files changed, 121 insertions(+), 117 deletions(-) create mode 100644 cmd/toolsrv/registry.go diff --git a/cmd/toolsrv/fs9p.go b/cmd/toolsrv/fs9p.go index 90d1218..353655f 100644 --- a/cmd/toolsrv/fs9p.go +++ b/cmd/toolsrv/fs9p.go @@ -47,7 +47,7 @@ type FS9P struct { cwd string env map[string]string - registry *toolsrv.Registry + registry *Registry sessID string yolo bool @@ -90,7 +90,7 @@ func NewFS9P(cwd string) *FS9P { } // SetRegistry configures the tool registry and session ID. -func (fs *FS9P) SetRegistry(r *toolsrv.Registry, sessID string) { +func (fs *FS9P) SetRegistry(r *Registry, sessID string) { fs.mu.Lock() fs.registry = r fs.sessID = sessID diff --git a/cmd/toolsrv/fs9p_test.go b/cmd/toolsrv/fs9p_test.go index 134ffae..04974cb 100644 --- a/cmd/toolsrv/fs9p_test.go +++ b/cmd/toolsrv/fs9p_test.go @@ -5,8 +5,6 @@ import ( "strings" "testing" "time" - - "ollie/toolsrv" ) func TestFS9P_Basic(t *testing.T) { @@ -78,7 +76,7 @@ func TestFS9P_ProcLimit(t *testing.T) { fs.procLimit = 2 // Create a fake registry and session ID - reg, err := toolsrv.NewRegistry() + reg, err := NewRegistry() if err != nil { t.Skip("no tools directory available") } diff --git a/cmd/toolsrv/main.go b/cmd/toolsrv/main.go index 918d06d..6feaaa7 100644 --- a/cmd/toolsrv/main.go +++ b/cmd/toolsrv/main.go @@ -24,7 +24,6 @@ import ( "ollie/env" "ollie/fsedsl" - "ollie/toolsrv" ) var ( @@ -55,7 +54,7 @@ func main() { os.Setenv("PATH", binDir+":"+os.Getenv("PATH")) // Create tool registry - toolReg, _ := toolsrv.NewRegistry() + toolReg, _ := NewRegistry() sid := *sessionID if sid == "" { sid = os.Getenv("OLLIE_SESSION_ID") // fallback to env var diff --git a/cmd/toolsrv/registry.go b/cmd/toolsrv/registry.go new file mode 100644 index 0000000..4538fdb --- /dev/null +++ b/cmd/toolsrv/registry.go @@ -0,0 +1,114 @@ +// registry.go - Session-scoped tool registry for toolsrv. +package main + +import ( + "fmt" + "sort" + "sync" + + "ollie/toolsrv" +) + +// Registry tracks loaded tools per session. +type Registry struct { + mu sync.RWMutex + sessions map[string]map[string]toolsrv.ToolInfo + revisions map[string]uint64 +} + +// NewRegistry creates a new tool registry. +func NewRegistry() (*Registry, error) { + r := &Registry{ + sessions: make(map[string]map[string]toolsrv.ToolInfo), + revisions: make(map[string]uint64), + } + return r, nil +} + +// discover scans the tools directory and returns all valid tool infos. +func (r *Registry) discover() map[string]toolsrv.ToolInfo { + infos := toolsrv.DiscoverTools() + tools := make(map[string]toolsrv.ToolInfo, len(infos)) + for _, info := range infos { + tools[info.Name] = info + } + return tools +} + +// Load loads a tool into a session's registry. +func (r *Registry) Load(sessionID, name string) error { + tools := r.discover() + tool, exists := tools[name] + if !exists { + return fmt.Errorf("tool not found: %s", name) + } + + r.mu.Lock() + defer r.mu.Unlock() + + if r.sessions[sessionID] == nil { + r.sessions[sessionID] = make(map[string]toolsrv.ToolInfo) + } + + r.sessions[sessionID][name] = tool + r.revisions[sessionID]++ + return nil +} + +// Unload removes a tool from a session's registry. +func (r *Registry) Unload(sessionID, name string) error { + r.mu.Lock() + defer r.mu.Unlock() + + sessionTools, ok := r.sessions[sessionID] + if !ok { + return nil + } + + if _, exists := sessionTools[name]; !exists { + return nil + } + + delete(sessionTools, name) + r.revisions[sessionID]++ + return nil +} + +// Loaded returns all tools loaded for a session. +func (r *Registry) Loaded(sessionID string) []toolsrv.ToolInfo { + r.mu.RLock() + defer r.mu.RUnlock() + + sessionTools, ok := r.sessions[sessionID] + if !ok { + return nil + } + + var loaded []toolsrv.ToolInfo + for _, info := range sessionTools { + loaded = append(loaded, info) + } + sort.Slice(loaded, func(i, j int) bool { return loaded[i].Name < loaded[j].Name }) + return loaded +} + +// Lookup returns a tool by name from a session's registry. +func (r *Registry) Lookup(sessionID, name string) (toolsrv.ToolInfo, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + sessionTools, ok := r.sessions[sessionID] + if !ok { + return toolsrv.ToolInfo{}, false + } + + tool, exists := sessionTools[name] + return tool, exists +} + +// Revision returns the revision counter for a session's registry. +func (r *Registry) Revision(sessionID string) uint64 { + r.mu.RLock() + defer r.mu.RUnlock() + return r.revisions[sessionID] +} diff --git a/cmd/toolsrv/server9p.go b/cmd/toolsrv/server9p.go index 24c7d1f..354addd 100644 --- a/cmd/toolsrv/server9p.go +++ b/cmd/toolsrv/server9p.go @@ -9,7 +9,6 @@ import ( "sync" "ollie/fsedsl" - "ollie/toolsrv" ) // Server9P holds the state for the 9P tool server. @@ -18,7 +17,7 @@ type Server9P struct { secret string // set on first auth, verified on subsequent token string // session token returned after auth - registry *toolsrv.Registry // tool registry + registry *Registry // tool registry sessID string // session ID for registry scoping yolo bool // skip sandbox @@ -34,7 +33,7 @@ func NewServer9P() *Server9P { } // SetRegistry configures the tool registry and session ID. -func (s *Server9P) SetRegistry(r *toolsrv.Registry, sessID string) { +func (s *Server9P) SetRegistry(r *Registry, sessID string) { s.mu.Lock() s.registry = r s.sessID = sessID diff --git a/toolsrv/registry.go b/toolsrv/registry.go index f77766c..0ca8313 100644 --- a/toolsrv/registry.go +++ b/toolsrv/registry.go @@ -1,121 +1,15 @@ +// registry.go - Tool discovery and shared types. package toolsrv import ( "encoding/json" - "fmt" "os" "path/filepath" - "sort" "strings" - "sync" "ollie/paths" ) -type Registry struct { - mu sync.RWMutex - sessions map[string]map[string]ToolInfo - revisions map[string]uint64 -} - -func NewRegistry() (*Registry, error) { - r := &Registry{ - sessions: make(map[string]map[string]ToolInfo), - revisions: make(map[string]uint64), - } - // Validate that the tools directory is readable. - dir := ToolsPath() - if _, err := os.ReadDir(dir); err != nil { - return nil, fmt.Errorf("read tools dir %s: %w", dir, err) - } - return r, nil -} - -// discover scans the tools directory and returns all valid tool infos. -func (r *Registry) discover() map[string]ToolInfo { - infos := DiscoverTools() - tools := make(map[string]ToolInfo, len(infos)) - for _, info := range infos { - tools[info.Name] = info - } - return tools -} - -func (r *Registry) Load(sessionID, name string) error { - tools := r.discover() - tool, exists := tools[name] - if !exists { - return fmt.Errorf("tool not found: %s", name) - } - - r.mu.Lock() - defer r.mu.Unlock() - - if r.sessions[sessionID] == nil { - r.sessions[sessionID] = make(map[string]ToolInfo) - } - - r.sessions[sessionID][name] = tool - r.revisions[sessionID]++ - return nil -} - -func (r *Registry) Unload(sessionID, name string) error { - r.mu.Lock() - defer r.mu.Unlock() - - sessionTools, ok := r.sessions[sessionID] - if !ok { - return nil - } - - if _, exists := sessionTools[name]; !exists { - return nil - } - - delete(sessionTools, name) - r.revisions[sessionID]++ - return nil -} - -func (r *Registry) Loaded(sessionID string) []ToolInfo { - r.mu.RLock() - defer r.mu.RUnlock() - - sessionTools, ok := r.sessions[sessionID] - if !ok { - return nil - } - - var loaded []ToolInfo - for _, info := range sessionTools { - loaded = append(loaded, info) - } - sort.Slice(loaded, func(i, j int) bool { return loaded[i].Name < loaded[j].Name }) - return loaded -} - -func (r *Registry) Lookup(sessionID, name string) (ToolInfo, bool) { - r.mu.RLock() - defer r.mu.RUnlock() - - sessionTools, ok := r.sessions[sessionID] - if !ok { - return ToolInfo{}, false - } - - tool, exists := sessionTools[name] - return tool, exists -} - -func (r *Registry) Revision(sessionID string) uint64 { - r.mu.RLock() - defer r.mu.RUnlock() - return r.revisions[sessionID] -} - - - // ToolInfo describes a tool provided by a server. type ToolInfo struct { Server string