p9: add configstore, move skills filesystem ops from core

- configstore.go: loadAgentConfig helper for agent config loading
- skillstore.go: absorbed List/Read/Dirs from skills package
- Removed unused config imports from batchstore/server
This commit is contained in:
Levi Neely 2026-04-21 13:52:12 +02:00
parent 2ac8ca8639
commit e031d2ba90
4 changed files with 85 additions and 15 deletions

View File

@ -10,7 +10,6 @@ import (
"ollie/pkg/agent"
"ollie/pkg/backend"
"ollie/pkg/config"
"ollie/pkg/tools"
"ollie/pkg/tools/execute"
)
@ -282,8 +281,7 @@ func (s *BatchStore) executeJob(ctx context.Context, job *batchJob) (string, err
be.SetModel(modelName)
}
cfgPath := agent.AgentConfigPath(s.srv.agentsDir, agentName)
cfg, _ := config.Load(cfgPath)
cfg := loadAgentConfig(s.srv.agentsDir, agentName)
newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{
"execute": execute.Decl(cwd),

View File

@ -0,0 +1,21 @@
package p9
import (
"os"
"ollie/pkg/agent"
"ollie/pkg/config"
)
// loadAgentConfig resolves and loads the config for a named agent.
// Returns nil (not an error) if the config file does not exist;
// BuildAgentEnv handles nil configs.
func loadAgentConfig(agentsDir, name string) *config.Config {
f, err := os.Open(agent.AgentConfigPath(agentsDir, name))
if err != nil {
return nil
}
defer f.Close()
cfg, _ := config.Load(f)
return cfg
}

View File

@ -46,7 +46,6 @@ import (
"ollie/pkg/agent"
"ollie/pkg/backend"
"ollie/pkg/config"
olog "ollie/pkg/log"
"ollie/pkg/paths"
"ollie/pkg/tools"
@ -1236,8 +1235,7 @@ func (s *Server) createSession(args []string) error {
return fmt.Errorf("sessions dir: %w", err)
}
cfgPath := agent.AgentConfigPath(s.agentsDir, agentName)
cfg, _ := config.Load(cfgPath) // nil cfg is handled by BuildAgentEnv
cfg := loadAgentConfig(s.agentsDir, agentName)
newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{
"execute": execute.Decl(cwd),

View File

@ -4,8 +4,10 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"ollie/pkg/paths"
"ollie/pkg/skills"
)
@ -19,12 +21,63 @@ func NewSkillStore() *SkillStore {
return &SkillStore{}
}
// skillDirs returns the skill directories from OLLIE_SKILLS_PATH,
// falling back to the default skills directory.
func skillDirs() []string {
if env := os.Getenv("OLLIE_SKILLS_PATH"); env != "" {
return strings.Split(env, ":")
}
return []string{paths.CfgDir() + "/skills"}
}
// listSkills scans all skill directories and returns deduplicated metadata.
// First occurrence by directory order wins on name collision.
func listSkills() []skills.Meta {
seen := make(map[string]bool)
var result []skills.Meta
for _, dir := range skillDirs() {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, e := range entries {
if !e.IsDir() || seen[e.Name()] {
continue
}
skillDir := filepath.Join(dir, e.Name())
f, err := os.Open(filepath.Join(skillDir, "SKILL.md"))
if err != nil {
continue
}
meta, err := skills.ParseFrontMatter(f, filepath.Base(skillDir), skillDir)
f.Close()
if err != nil {
continue
}
seen[meta.Name] = true
result = append(result, *meta)
}
}
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
return result
}
// readSkill returns the SKILL.md content for the named skill.
func readSkill(name string) ([]byte, error) {
for _, m := range listSkills() {
if m.Name == name {
return os.ReadFile(filepath.Join(m.Dir, "SKILL.md"))
}
}
return nil, os.ErrNotExist
}
func (s *SkillStore) Stat(name string) (os.FileInfo, error) {
if name == "idx" {
return &syntheticFileInfo{name: "idx", mode: 0444}, nil
}
skillName := strings.TrimSuffix(name, ".md")
if _, err := skills.Read(skillName); err != nil {
if _, err := readSkill(skillName); err != nil {
return nil, fmt.Errorf("%s: not found", name)
}
return &syntheticFileInfo{name: name, mode: 0666}, nil
@ -32,7 +85,7 @@ func (s *SkillStore) Stat(name string) (os.FileInfo, error) {
func (s *SkillStore) List() ([]os.DirEntry, error) {
result := []os.DirEntry{syntheticEntry("idx", 0444)}
for _, m := range skills.List() {
for _, m := range listSkills() {
result = append(result, syntheticEntry(m.Name+".md", 0666))
}
return result, nil
@ -43,20 +96,20 @@ func (s *SkillStore) Get(name string) ([]byte, error) {
return s.index()
}
skillName := strings.TrimSuffix(name, ".md")
return skills.Read(skillName)
return readSkill(skillName)
}
func (s *SkillStore) Put(name string, data []byte) error {
skillName := strings.TrimSuffix(name, ".md")
dir := ""
for _, m := range skills.List() {
for _, m := range listSkills() {
if m.Name == skillName {
dir = m.Dir
break
}
}
if dir == "" {
dir = filepath.Join(skills.Dirs()[0], skillName)
dir = filepath.Join(skillDirs()[0], skillName)
}
if err := os.MkdirAll(dir, 0755); err != nil {
return err
@ -66,7 +119,7 @@ func (s *SkillStore) Put(name string, data []byte) error {
func (s *SkillStore) Delete(name string) error {
skillName := strings.TrimSuffix(name, ".md")
for _, m := range skills.List() {
for _, m := range listSkills() {
if m.Name == skillName {
return os.RemoveAll(m.Dir)
}
@ -77,7 +130,7 @@ func (s *SkillStore) Delete(name string) error {
func (s *SkillStore) Rename(oldName, newName string) error {
oldSkill := strings.TrimSuffix(oldName, ".md")
newSkill := strings.TrimSuffix(newName, ".md")
for _, m := range skills.List() {
for _, m := range listSkills() {
if m.Name == oldSkill {
newDir := filepath.Join(filepath.Dir(m.Dir), newSkill)
return os.Rename(m.Dir, newDir)
@ -88,7 +141,7 @@ func (s *SkillStore) Rename(oldName, newName string) error {
func (s *SkillStore) Create(name string) error {
skillName := strings.TrimSuffix(name, ".md")
dir := filepath.Join(skills.Dirs()[0], skillName)
dir := filepath.Join(skillDirs()[0], skillName)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
@ -97,7 +150,7 @@ func (s *SkillStore) Create(name string) error {
func (s *SkillStore) index() ([]byte, error) {
var sb strings.Builder
for _, m := range skills.List() {
for _, m := range listSkills() {
fmt.Fprintf(&sb, "## %s\n", m.Name)
fmt.Fprintf(&sb, "description: %s\n", m.Description)
sb.WriteString("\n")