registry: refresh schemas on tool_list and reload on tool_load
tool_load always re-reads the tool from disk (reload semantics). tool_list calls RefreshLoaded to update schemas for all promoted tools. Skills registry gets the same treatment: dynamic discovery, no cache.
This commit is contained in:
parent
6fc2e001ac
commit
a640257dbf
|
|
@ -4,9 +4,9 @@ package skills
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
|
@ -32,25 +32,33 @@ type SkillInfo struct {
|
|||
// Tracks which skills are loaded per session.
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
global map[string]SkillInfo
|
||||
sessions map[string]map[string]bool // sessionID → skill name → loaded
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
global: make(map[string]SkillInfo),
|
||||
sessions: make(map[string]map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Discover scans the skills directory and registers all found skills.
|
||||
// Discover validates that the skills directory is readable.
|
||||
// Retained for backward compatibility; discovery is now dynamic.
|
||||
func (r *Registry) Discover() error {
|
||||
dir := SkillsPath()
|
||||
if _, err := os.ReadDir(dir); err != nil {
|
||||
return fmt.Errorf("read skills dir %s: %w", dir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// discover scans the skills directory and returns all found skills.
|
||||
func (r *Registry) discover() map[string]SkillInfo {
|
||||
dir := SkillsPath()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read skills dir %s: %w", dir, err)
|
||||
return nil
|
||||
}
|
||||
global := make(map[string]SkillInfo)
|
||||
skills := make(map[string]SkillInfo)
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
|
|
@ -67,24 +75,20 @@ func (r *Registry) Discover() error {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
global[name] = SkillInfo{
|
||||
skills[name] = SkillInfo{
|
||||
Name: meta.Name,
|
||||
Description: meta.Description,
|
||||
Dir: skillDir,
|
||||
}
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.global = global
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
return skills
|
||||
}
|
||||
|
||||
// Summaries returns all discovered skills.
|
||||
func (r *Registry) Summaries() []SkillInfo {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
out := make([]SkillInfo, 0, len(r.global))
|
||||
for _, info := range r.global {
|
||||
skills := r.discover()
|
||||
out := make([]SkillInfo, 0, len(skills))
|
||||
for _, info := range skills {
|
||||
out = append(out, info)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
|
|
@ -93,10 +97,8 @@ func (r *Registry) Summaries() []SkillInfo {
|
|||
|
||||
// Load marks a skill as loaded for the given session.
|
||||
func (r *Registry) Load(sessionID, name string) error {
|
||||
r.mu.RLock()
|
||||
_, exists := r.global[name]
|
||||
r.mu.RUnlock()
|
||||
if !exists {
|
||||
skills := r.discover()
|
||||
if _, exists := skills[name]; !exists {
|
||||
return fmt.Errorf("skill not found: %s", name)
|
||||
}
|
||||
r.mu.Lock()
|
||||
|
|
@ -124,14 +126,15 @@ func (r *Registry) Unload(sessionID, name string) error {
|
|||
// Loaded returns skills currently loaded for a session.
|
||||
func (r *Registry) Loaded(sessionID string) []SkillInfo {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
names, ok := r.sessions[sessionID]
|
||||
r.mu.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
skills := r.discover()
|
||||
out := make([]SkillInfo, 0, len(names))
|
||||
for name := range names {
|
||||
if info, exists := r.global[name]; exists {
|
||||
if info, exists := skills[name]; exists {
|
||||
out = append(out, info)
|
||||
}
|
||||
}
|
||||
|
|
@ -142,9 +145,8 @@ func (r *Registry) Loaded(sessionID string) []SkillInfo {
|
|||
// SkillContent reads and returns the full content of a skill's SKILL.md file,
|
||||
// with front-matter stripped. Returns (content, error).
|
||||
func (r *Registry) SkillContent(name string) (string, error) {
|
||||
r.mu.RLock()
|
||||
info, exists := r.global[name]
|
||||
r.mu.RUnlock()
|
||||
skills := r.discover()
|
||||
info, exists := skills[name]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("skill not found: %s", name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ func ToolList(ctx context.Context, srv *toolsrv.Server, args json.RawMessage) (s
|
|||
if reg == nil {
|
||||
return "", fmt.Errorf("tool_list: no registry available")
|
||||
}
|
||||
|
||||
// Refresh loaded tool schemas from disk so the model sees current definitions.
|
||||
sid := srv.SessionID()
|
||||
if sid != "" {
|
||||
reg.RefreshLoaded(sid)
|
||||
}
|
||||
|
||||
summaries := reg.Summaries()
|
||||
if len(summaries) == 0 {
|
||||
return "(no tools found)", nil
|
||||
|
|
|
|||
|
|
@ -80,10 +80,6 @@ func (r *Registry) Load(sessionID, name string) error {
|
|||
r.sessions[sessionID] = make(map[string]ToolInfo)
|
||||
}
|
||||
|
||||
if _, already := r.sessions[sessionID][name]; already {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.sessions[sessionID][name] = tool
|
||||
r.revisions[sessionID]++
|
||||
return nil
|
||||
|
|
@ -124,6 +120,41 @@ func (r *Registry) Loaded(sessionID string) []ToolInfo {
|
|||
return loaded
|
||||
}
|
||||
|
||||
// RefreshLoaded re-reads all loaded tools from disk, updating their schemas
|
||||
// and metadata in place. Called from tool_list to keep schemas current.
|
||||
func (r *Registry) RefreshLoaded(sessionID string) {
|
||||
r.mu.RLock()
|
||||
sessionTools, ok := r.sessions[sessionID]
|
||||
if !ok || len(sessionTools) == 0 {
|
||||
r.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
names := make([]string, 0, len(sessionTools))
|
||||
for name := range sessionTools {
|
||||
names = append(names, name)
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
fresh := r.discover()
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
changed := false
|
||||
for _, name := range names {
|
||||
if tool, exists := fresh[name]; exists {
|
||||
r.sessions[sessionID][name] = tool
|
||||
changed = true
|
||||
} else {
|
||||
// Tool was removed from disk — unload it.
|
||||
delete(r.sessions[sessionID], name)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
r.revisions[sessionID]++
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) Lookup(sessionID, name string) (ToolInfo, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
|
|
|||
Reference in New Issue