add skills registry and skill_list/skill_load/skill_active built-in dispatchers

This commit is contained in:
lkn 2026-07-29 00:16:39 +02:00
parent c221426af4
commit aa1b7d1e1c
2 changed files with 71 additions and 53 deletions

View File

@ -21,6 +21,7 @@ import (
olog "ollie/pkg/log"
"ollie/pkg/paths"
"ollie/pkg/tools/execute"
"ollie/pkg/skills"
fs "olliesrv/fs"
"olliesrv/mount"
"olliesrv/server"
@ -165,11 +166,18 @@ func runServer(sockPath string) {
fmt.Fprintf(os.Stderr, "warning: tool registry: %v\n", regErr)
}
// Create the skills registry
skillsRegistry := skills.NewRegistry()
if discErr := skillsRegistry.Discover(); discErr != nil {
fmt.Fprintf(os.Stderr, "warning: skills registry: %v\n", discErr)
}
// D-Bus adapter (initialized after manager so callbacks can reference it).
var dbusAdapter *DBusAdapter
mgr := session.NewManager(session.ManagerConfig{
ToolRegistry: toolRegistry,
SkillsRegistry: skillsRegistry,
AgentsDir: agentsDirs[0],
SessionsDir: sessionsDir,
Log: sink.NewLogger("9p"),

View File

@ -21,6 +21,7 @@ import (
"ollie/pkg/remote"
"ollie/pkg/tools"
"ollie/pkg/tools/execute"
"ollie/pkg/skills"
"olliesrv/fs"
"olliesrv/prompts"
)
@ -136,20 +137,20 @@ func (sess *Session) startEventLog() {
if streamingRole != ev.Role || (ev.Role == "assistant" && ev.ResponseID != streamingResponseID) {
if streamingRole != "" {
sess.AppendLog([]byte("\n"))
}
}
if ev.Role == "assistant" && ev.ResponseID != "" {
sess.AppendLog([]byte("[assistant:" + ev.ResponseID + "]\n"))
streamingResponseID = ev.ResponseID
} else {
} else {
sess.AppendLog([]byte("[" + ev.Role + "]\n"))
}
}
if ev.Role == "assistant" {
sess.mu.Lock()
sess.ChatOffset = len(sess.log)
sess.mu.Unlock()
}
}
streamingRole = ev.Role
}
}
sess.AppendLog(FormatEvent(ev))
case "tool":
@ -160,36 +161,36 @@ func (sess *Session) startEventLog() {
// End of stream.
sess.AppendLog([]byte("\n"))
streamingRole = ""
} else {
} else {
sess.AppendLog([]byte(ev.Content))
}
}
return
}
}
if streamingRole != "" {
sess.AppendLog([]byte("\n"))
streamingRole = ""
}
}
if ev.Content == "" {
// Stream start: write header, enter streaming mode.
sess.AppendLog([]byte("[tool:" + ev.Name + "]\n"))
streamingRole = "tool"
} else {
} else {
// Non-streamed tool result (fast tool, no streaming happened).
sess.AppendLog(FormatEvent(ev))
}
}
case "call":
if streamingRole != "" {
sess.AppendLog([]byte("\n"))
streamingRole = ""
}
}
sess.AppendLog(FormatEvent(ev))
default:
if streamingRole != "" {
sess.AppendLog([]byte("\n"))
streamingRole = ""
}
}
sess.AppendLog(FormatEvent(ev))
}
})
@ -230,6 +231,7 @@ func FileMode(name string) (os.FileMode, bool) {
type ManagerConfig struct {
AgentsDir string
SessionsDir string
Log *olog.Logger
Sink *olog.Sink
ReadFile func(string) ([]byte, error)
@ -251,6 +253,8 @@ type ManagerConfig struct {
InvalidateModels func()
// ToolRegistry is the shared tool registry for lazy tool promotion.
ToolRegistry *execute.Registry
// SkillsRegistry is the shared skills registry for skill loading.
SkillsRegistry *skills.Registry
// OnSessionCreated is called after a new session is added to the manager.
// Receives the session ID and the Session pointer.
OnSessionCreated func(id string, sess *Session)
@ -537,14 +541,14 @@ func (s *Manager) openEntry(name string) (fs.File, error) {
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: pidStr, Mode_: 0444}, nil
},
},
ReadFn: func() ([]byte, error) {
output, err := sess.Core.GetDetachedOutput(pid)
if err != nil {
return nil, err
}
}
return []byte(output), nil
},
},
WriteFn: func([]byte) error { return fmt.Errorf("read-only") },
BlockingReadFn: notBlocking,
}, nil
@ -556,17 +560,17 @@ func (s *Manager) openEntry(name string) (fs.File, error) {
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: peerID, Mode_: 0666}, nil
},
},
ReadFn: func() ([]byte, error) {
return nil, nil // empty read
},
},
WriteFn: func(data []byte) error {
prompt := strings.TrimSpace(string(data))
if prompt == "" {
return nil
}
}
return s.PeerSubmit(sessID, peerID, prompt)
},
},
BlockingReadFn: notBlocking,
}, nil
}
@ -577,21 +581,21 @@ func (s *Manager) openEntry(name string) (fs.File, error) {
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: "new", Mode_: fs.Perms[fs.PathSessions].Files["new"]}, nil
},
},
ReadFn: func() ([]byte, error) {
return []byte("name=\ncwd=\nremote=\nbackend=\nmodel=\nagent=\nmaxTokens=\nmaxCompletionTokens=\ntemperature=\ntopP=\ntopK=\nminP=\ntopA=\nfrequencyPenalty=\npresencePenalty=\nrepetitionPenalty=\nreasoning=\nreasoningEffort=\nincludeReasoning=\nresponseFormat=\nstop=\nverbosity=\n"), nil
},
},
WriteFn: func(data []byte) error {
_, err := s.CreateSession(strings.Fields(strings.TrimSpace(string(data))))
return err
},
},
BlockingReadFn: notBlocking,
}, nil
case "idx":
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: "idx", Mode_: fs.Perms[fs.PathSessions].Files["idx"]}, nil
},
},
ReadFn: func() ([]byte, error) { return s.index(), nil },
WriteFn: func([]byte) error { return fmt.Errorf("idx: read-only") },
BlockingReadFn: notBlocking,
@ -601,10 +605,10 @@ func (s *Manager) openEntry(name string) (fs.File, error) {
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: name, Mode_: fs.Perms[fs.PathSessions].Files[name]}, nil
},
},
ReadFn: func() ([]byte, error) {
return s.cfg.ReadFile(paths.CfgDir() + "/scripts/s/" + name)
},
},
WriteFn: func([]byte) error { return fmt.Errorf("%s: not writable", name) },
BlockingReadFn: notBlocking,
}, nil
@ -720,7 +724,7 @@ func (s *Manager) InterruptAll() {
for _, sess := range s.sessions {
sess.Core.Interrupt(agent.ErrInterrupted)
}
}
}
// Shutdown kills all active sessions.
// --- Session Persistence ---
@ -742,7 +746,7 @@ func (s *Manager) persistSession(id string) {
if err := sess.Core.SaveSession(path); err != nil {
s.cfg.Log.Error("persist session %s: %v", id, err)
}
}
}
func (s *Manager) removePersistedSession(id string) {
path := filepath.Join(s.activeSessionsDir(), id+".json")
@ -759,7 +763,7 @@ func (s *Manager) saveAllSessions() {
for _, id := range ids {
s.persistSession(id)
}
}
}
func (s *Manager) restoreAllSessions() {
dir := s.activeSessionsDir()
@ -799,7 +803,7 @@ func (s *Manager) restoreAllSessions() {
defer wg.Done()
if err := s.restoreSession(ls.ps); err != nil {
s.cfg.Log.Error("restore session %s: %v", ls.ps.ID, err)
}
}
}(ls)
}
wg.Wait()
@ -899,8 +903,8 @@ func (s *Manager) restoreSession(ps *agent.PersistedSession) error {
platform = v
case "PRIME_IS_GIT_REPO":
isGitRepo = v == "true"
}
}
}
}
}
envBlock := prompts.Environment(cwd, platform, isGitRepo, "")
@ -910,6 +914,9 @@ func (s *Manager) restoreSession(ps *agent.PersistedSession) error {
if execSrv, ok := disp.GetServer("execute"); ok {
if es, ok := execSrv.(*execute.Server); ok {
es.SetToolRegistry(s.cfg.ToolRegistry, sessID)
if s.cfg.SkillsRegistry != nil {
es.SetSkillsRegistry(s.cfg.SkillsRegistry)
}
}
}
}
@ -936,7 +943,7 @@ func (s *Manager) restoreSession(ps *agent.PersistedSession) error {
ReadPlanStep: func() string {
if sessPtr == nil {
return ""
}
}
sessPtr.mu.RLock()
data := make([]byte, len(sessPtr.plan))
copy(data, sessPtr.plan)
@ -1002,8 +1009,8 @@ func (s *Manager) waitIdle(poll, timeout time.Duration) {
if sess.Core.State() != "idle" {
allIdle = false
break
}
}
}
}
s.mu.RUnlock()
if allIdle {
return
@ -1126,11 +1133,11 @@ func (s *Manager) CreateSession(args []string) (string, error) {
if cfg != nil {
if backendOverride == "" && cfg.Backend != "" {
backendOverride = cfg.Backend
}
}
if modelOverride == "" && cfg.Model != "" {
modelOverride = cfg.Model
}
}
}
}
be, err := backend.NewWithName(backendOverride)
if err != nil {
@ -1171,22 +1178,22 @@ func (s *Manager) CreateSession(args []string) (string, error) {
rsrv, dialErr := remote.Dial(context.Background(), remote.Config{
SSHTarget: remoteTarget,
CWD: cwd,
})
})
if dialErr != nil {
return "", fmt.Errorf("remote dial: %w", dialErr)
}
}
newDisp = tools.NewDispatcherFunc(map[string]func() tools.Server{
"execute": func() tools.Server { return rsrv },
})
})
remoteEnv = []string{
"PRIME_CWD=" + cwd,
"PRIME_PLATFORM=" + rsrv.Info.Platform,
"PRIME_IS_GIT_REPO=" + fmt.Sprintf("%v", rsrv.Info.IsGitRepo),
}
}
} else {
newDisp = tools.NewDispatcherFunc(map[string]func() tools.Server{
"execute": execute.Decl(cwd, execOpts...),
})
})
}
var promptEnv []string
@ -1209,8 +1216,8 @@ func (s *Manager) CreateSession(args []string) (string, error) {
for _, e := range env {
if k, v, ok := strings.Cut(e, "="); ok {
envMap[k] = v
}
}
}
}
opModel := prompts.OperationalModel(s.cfg.Enable9P, s.cfg.EnableDBus, envMap)
// Determine environment values for the environment layer.
@ -1223,8 +1230,8 @@ func (s *Manager) CreateSession(args []string) (string, error) {
platform = v
case "PRIME_IS_GIT_REPO":
isGitRepo = v == "true"
}
}
}
}
}
envBlock := prompts.Environment(cwd, platform, isGitRepo, "")
@ -1234,6 +1241,9 @@ func (s *Manager) CreateSession(args []string) (string, error) {
if execSrv, ok := disp.GetServer("execute"); ok {
if es, ok := execSrv.(*execute.Server); ok {
es.SetToolRegistry(s.cfg.ToolRegistry, sessID)
if s.cfg.SkillsRegistry != nil {
es.SetSkillsRegistry(s.cfg.SkillsRegistry)
}
}
}
}
@ -1257,13 +1267,13 @@ func (s *Manager) CreateSession(args []string) (string, error) {
ReadPlanStep: func() string {
if sessPtr == nil {
return ""
}
}
sessPtr.mu.RLock()
data := make([]byte, len(sessPtr.plan))
copy(data, sessPtr.plan)
sessPtr.mu.RUnlock()
return agent.NextUncheckedStep(data)
},
},
})
}
@ -1378,7 +1388,7 @@ func FormatEvent(ev agent.Event) []byte {
default:
return nil
}
}
}
func squashWhitespace(s string) string {
return strings.Join(strings.Fields(s), " ")
@ -1406,15 +1416,15 @@ func replayMessagesToLog(sess *Session, messages []backend.Message) {
header := "[assistant]"
if m.ID != "" {
header = "[assistant:" + m.ID + "]"
}
}
sess.AppendLog([]byte(header + "\n"))
if m.Content != "" {
sess.AppendLog([]byte(m.Content + "\n"))
}
}
for _, tc := range m.ToolCalls {
args := squashWhitespace(string(tc.Arguments))
sess.AppendLog([]byte("[call:" + tc.Name + "]\n" + args + "\n"))
}
}
case "tool":
sess.AppendLog([]byte("[tool]\n" + strings.TrimRight(m.Content, "\n") + "\n"))
}