add skills registry and skill_list/skill_load/skill_active built-in dispatchers
This commit is contained in:
parent
c221426af4
commit
aa1b7d1e1c
8
main.go
8
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"),
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
Reference in New Issue