From aa1b7d1e1c32c6d20b33d29ef47fdf13221e5d69 Mon Sep 17 00:00:00 2001 From: lkn Date: Wed, 29 Jul 2026 00:16:39 +0200 Subject: [PATCH] add skills registry and skill_list/skill_load/skill_active built-in dispatchers --- main.go | 8 ++++ session/session.go | 116 ++++++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/main.go b/main.go index 3f7cbcd..2a67c6b 100644 --- a/main.go +++ b/main.go @@ -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"), diff --git a/session/session.go b/session/session.go index 915c3c5..bfe2e05 100644 --- a/session/session.go +++ b/session/session.go @@ -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")) }