Merge ollie-tmp into master
This commit is contained in:
commit
a876190310
|
|
@ -29,6 +29,7 @@
|
|||
// ctxsz (read) context size vs window
|
||||
// sk/ (dir) skills
|
||||
// t/ (dir) tool scripts
|
||||
// tmp/ (dir) tool dumping ground (r/w, backed by OLLIE_TMP_PATH)
|
||||
// tr/ (dir) transcripts (ro; write to s/{id}/chat to save)
|
||||
package p9
|
||||
|
||||
|
|
@ -49,6 +50,7 @@ import (
|
|||
"ollie/pkg/backend"
|
||||
"ollie/pkg/config"
|
||||
olog "ollie/pkg/log"
|
||||
"ollie/pkg/paths"
|
||||
"ollie/pkg/tools"
|
||||
"ollie/pkg/tools/execute"
|
||||
)
|
||||
|
|
@ -147,6 +149,7 @@ type Server struct {
|
|||
sessionStore Store
|
||||
batchStore *BatchStore
|
||||
transcriptStore Store
|
||||
tmpStore Store
|
||||
}
|
||||
|
||||
// New creates a new Server.
|
||||
|
|
@ -155,10 +158,12 @@ func New() *Server {
|
|||
os.MkdirAll(memDir, 0755) //nolint:errcheck
|
||||
transcriptDir := defaultTranscriptDir()
|
||||
os.MkdirAll(transcriptDir, 0755) //nolint:errcheck
|
||||
tmpDir := defaultTmpDir()
|
||||
os.MkdirAll(tmpDir, 0755) //nolint:errcheck
|
||||
agentsDir := agent.DefaultAgentsDir()
|
||||
s := &Server{
|
||||
sessions: make(map[string]*session),
|
||||
agentsDir: agentsDir,
|
||||
sessions: make(map[string]*session),
|
||||
agentsDir: agentsDir,
|
||||
sessionsDir: agent.DefaultSessionsDir(),
|
||||
agentStore: NewFlatDirStore(agentsDir, 0644),
|
||||
promptStore: NewFlatDirStore(agent.DefaultPromptsDir(), 0444),
|
||||
|
|
@ -167,6 +172,7 @@ func New() *Server {
|
|||
toolStore: NewToolStore(),
|
||||
skillStore: NewSkillStore(),
|
||||
transcriptStore: NewFlatDirStore(transcriptDir, 0444),
|
||||
tmpStore: NewFlatDirStore(tmpDir, 0600),
|
||||
}
|
||||
s.sessionStore = &SessionStore{srv: s}
|
||||
s.batchStore = NewBatchStore(s)
|
||||
|
|
@ -178,8 +184,7 @@ func defaultPlanDir() string {
|
|||
if p := os.Getenv("OLLIE_PLAN_PATH"); p != "" {
|
||||
return p
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return home + "/.config/ollie/planning"
|
||||
return paths.CfgDir() + "/planning"
|
||||
}
|
||||
|
||||
// defaultTranscriptDir returns the transcript directory from OLLIE_TRANSCRIPT_PATH or the default.
|
||||
|
|
@ -187,8 +192,7 @@ func defaultTranscriptDir() string {
|
|||
if p := os.Getenv("OLLIE_TRANSCRIPT_PATH"); p != "" {
|
||||
return p
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return home + "/.config/ollie/transcript"
|
||||
return paths.CfgDir() + "/transcript"
|
||||
}
|
||||
|
||||
// defaultMemDir returns the memory directory from OLLIE_MEMORY_PATH or the default.
|
||||
|
|
@ -196,8 +200,15 @@ func defaultMemDir() string {
|
|||
if p := os.Getenv("OLLIE_MEMORY_PATH"); p != "" {
|
||||
return p
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return home + "/.config/ollie/memory"
|
||||
return paths.CfgDir() + "/memory"
|
||||
}
|
||||
|
||||
// defaultTmpDir returns the tmp directory from OLLIE_TMP_PATH or the default.
|
||||
func defaultTmpDir() string {
|
||||
if p := os.Getenv("OLLIE_TMP_PATH"); p != "" {
|
||||
return p
|
||||
}
|
||||
return paths.DataDir() + "/tmp"
|
||||
}
|
||||
|
||||
// sessionFileStore returns a SessionFileStore for the given session ID.
|
||||
|
|
@ -356,6 +367,12 @@ func (s *Server) pathType(path string) string {
|
|||
return "file"
|
||||
}
|
||||
}
|
||||
case len(parts) == 1 && parts[0] == "tmp":
|
||||
return "dir"
|
||||
case len(parts) == 2 && parts[0] == "tmp":
|
||||
if _, err := s.tmpStore.Stat(parts[1]); err == nil {
|
||||
return "file"
|
||||
}
|
||||
case len(parts) == 1 && parts[0] == "backends":
|
||||
return "file"
|
||||
case len(parts) == 1 && parts[0] == "help":
|
||||
|
|
@ -529,6 +546,10 @@ func (s *Server) create(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
if err := s.toolStore.Create(fc.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
case "/tmp":
|
||||
if err := s.tmpStore.Create(fc.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
case "/sk":
|
||||
if err := s.skillStore.Create(fc.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
|
|
@ -585,8 +606,8 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
}
|
||||
}
|
||||
|
||||
// s/new and s/idx are served from the session store.
|
||||
if path == "/s/new" || path == "/s/idx" {
|
||||
// s/new, s/idx, and s/ls are served from the session store.
|
||||
if path == "/s/new" || path == "/s/idx" || path == "/s/ls" {
|
||||
plog.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
content, err := s.sessionStore.Get(pathBase(path))
|
||||
if err != nil {
|
||||
|
|
@ -674,6 +695,16 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
return s.readSlice(fc, content)
|
||||
}
|
||||
|
||||
// Tmp files are served from the tmp store.
|
||||
if strings.HasPrefix(path, "/tmp/") {
|
||||
plog.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
content, err := s.tmpStore.Get(pathBase(path))
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
return s.readSlice(fc, content)
|
||||
}
|
||||
|
||||
// Transcript files are served from the transcript store.
|
||||
if strings.HasPrefix(path, "/tr/") {
|
||||
plog.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
|
|
@ -727,8 +758,7 @@ func (s *Server) readSlice(fc *plan9.Fcall, content []byte) *plan9.Fcall {
|
|||
}
|
||||
|
||||
func (s *Server) helpPath() string {
|
||||
home, _ := os.UserHomeDir()
|
||||
return home + "/.config/ollie/help.md"
|
||||
return paths.CfgDir() + "/help.md"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -892,13 +922,6 @@ func (s *Server) renameSession(oldID, newID string) error {
|
|||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Rename the session tmpdir so isread markers remain valid.
|
||||
oldTemp := "/tmp/ollie/" + oldID
|
||||
newTemp := "/tmp/ollie/" + newID
|
||||
if _, err := os.Stat(oldTemp); err == nil {
|
||||
os.Rename(oldTemp, newTemp) //nolint:errcheck
|
||||
}
|
||||
|
||||
sess.appendChat([]byte(fmt.Sprintf("(session renamed: %s -> %s)\n", oldID, newID)))
|
||||
plog.Info("renamed session %s -> %s", oldID, newID)
|
||||
return nil
|
||||
|
|
@ -971,6 +994,8 @@ func (s *Server) remove(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
err = s.skillStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/t/"):
|
||||
err = s.toolStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/tmp/"):
|
||||
err = s.tmpStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/tr/"):
|
||||
err = s.transcriptStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/b/") && path != "/b/new":
|
||||
|
|
@ -1043,6 +1068,11 @@ func (s *Server) handleWrite(path, input string) error {
|
|||
return s.toolStore.Put(pathBase(path), []byte(input))
|
||||
}
|
||||
|
||||
// Tmp file writes go to the tmp store.
|
||||
if strings.HasPrefix(path, "/tmp/") {
|
||||
return s.tmpStore.Put(pathBase(path), []byte(input))
|
||||
}
|
||||
|
||||
// Session file writes: /s/{sessid}/{file}
|
||||
if strings.HasPrefix(path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/"), "/", 3)
|
||||
|
|
@ -1280,6 +1310,7 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
|
|||
dirs = append(dirs, makeDir("s", "/s", true, plan9.DMDIR|0555))
|
||||
dirs = append(dirs, makeDir("sk", "/sk", true, plan9.DMDIR|0555))
|
||||
dirs = append(dirs, makeDir("t", "/t", true, plan9.DMDIR|0777))
|
||||
dirs = append(dirs, makeDir("tmp", "/tmp", true, plan9.DMDIR|0755))
|
||||
dirs = append(dirs, makeDir("tr", "/tr", true, plan9.DMDIR|0555))
|
||||
} else if path == "/a" {
|
||||
entries, _ := s.agentStore.List()
|
||||
|
|
@ -1337,6 +1368,18 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
|
|||
}
|
||||
dirs = append(dirs, makeDir(e.Name(), "/t/"+e.Name(), false, mode))
|
||||
}
|
||||
} else if path == "/tmp" {
|
||||
entries, _ := s.tmpStore.List()
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
d := makeDir(e.Name(), "/tmp/"+e.Name(), false, 0600)
|
||||
if info, err := e.Info(); err == nil {
|
||||
d.Atime = uint32(info.ModTime().Unix())
|
||||
d.Mtime = uint32(info.ModTime().Unix())
|
||||
}
|
||||
dirs = append(dirs, d)
|
||||
}
|
||||
}
|
||||
} else if path == "/tr" {
|
||||
entries, _ := s.transcriptStore.List()
|
||||
for _, e := range entries {
|
||||
|
|
@ -1440,7 +1483,7 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
qid.Type = QTDir
|
||||
if path == "/t" {
|
||||
mode = plan9.DMDIR | 0777
|
||||
} else if path == "/a" || path == "/m" || path == "/pl" {
|
||||
} else if path == "/a" || path == "/m" || path == "/pl" || path == "/tmp" {
|
||||
mode = plan9.DMDIR | 0755
|
||||
} else {
|
||||
mode = plan9.DMDIR | 0555
|
||||
|
|
@ -1456,6 +1499,8 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
default:
|
||||
if path == "/backends" || path == "/help" || path == "/s/idx" {
|
||||
mode = 0444
|
||||
} else if path == "/s/ls" {
|
||||
mode = 0555
|
||||
} else if path == "/s/new" {
|
||||
mode = 0666
|
||||
} else if strings.HasPrefix(path, "/a/") {
|
||||
|
|
@ -1545,7 +1590,7 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
// that check stat before reading (cat, 9pfuse, etc.) see non-zero size.
|
||||
if dir.Length == 0 && !isDir {
|
||||
switch {
|
||||
case path == "/s/new" || path == "/s/idx":
|
||||
case path == "/s/new" || path == "/s/idx" || path == "/s/ls":
|
||||
if content, err := s.sessionStore.Get(base); err == nil {
|
||||
dir.Length = uint64(len(content))
|
||||
}
|
||||
|
|
@ -1571,6 +1616,12 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
if content, err := s.toolStore.Get(base); err == nil {
|
||||
dir.Length = uint64(len(content))
|
||||
}
|
||||
case strings.HasPrefix(path, "/tmp/"):
|
||||
if info, err := s.tmpStore.Stat(base); err == nil {
|
||||
dir.Length = uint64(info.Size())
|
||||
dir.Atime = uint32(info.ModTime().Unix())
|
||||
dir.Mtime = uint32(info.ModTime().Unix())
|
||||
}
|
||||
case strings.HasPrefix(path, "/tr/"):
|
||||
if info, err := s.transcriptStore.Stat(base); err == nil {
|
||||
dir.Length = uint64(info.Size())
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package p9
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ollie/pkg/agent"
|
||||
"ollie/pkg/backend"
|
||||
)
|
||||
|
||||
// sessionFileList defines the fixed set of files in a session directory,
|
||||
|
|
@ -30,6 +32,7 @@ var sessionFileList = []struct {
|
|||
{"models", 0444},
|
||||
{"mcp", 0444},
|
||||
{"systemprompt", 0444},
|
||||
{"params", 0666},
|
||||
}
|
||||
|
||||
// SessionFileStore implements ReadWriteStore for the files within a single
|
||||
|
|
@ -152,10 +155,96 @@ func (s *SessionFileStore) Put(name string, data []byte) error {
|
|||
s.sess.mu.Lock()
|
||||
s.sess.mutableVers["cwd"].update(s.sess.core.CWD())
|
||||
s.sess.mu.Unlock()
|
||||
|
||||
case "params":
|
||||
if s.sess.core.IsRunning() {
|
||||
return fmt.Errorf("cannot change params while agent is running")
|
||||
}
|
||||
params, err := parseParams(input, s.sess.core.GenerationParams())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.sess.core.SetGenerationParams(params)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatParams(p backend.GenerationParams) string {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "maxTokens=%d\n", p.MaxTokens)
|
||||
if p.Temperature != nil {
|
||||
fmt.Fprintf(&sb, "temperature=%g\n", *p.Temperature)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, "temperature=\n")
|
||||
}
|
||||
if p.FrequencyPenalty != nil {
|
||||
fmt.Fprintf(&sb, "frequencyPenalty=%g\n", *p.FrequencyPenalty)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, "frequencyPenalty=\n")
|
||||
}
|
||||
if p.PresencePenalty != nil {
|
||||
fmt.Fprintf(&sb, "presencePenalty=%g\n", *p.PresencePenalty)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, "presencePenalty=\n")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func parseParams(input string, current backend.GenerationParams) (backend.GenerationParams, error) {
|
||||
p := current
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
switch k {
|
||||
case "maxTokens":
|
||||
if v == "" {
|
||||
p.MaxTokens = 0
|
||||
} else {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("invalid maxTokens: %s", v)
|
||||
}
|
||||
p.MaxTokens = n
|
||||
}
|
||||
case "temperature":
|
||||
if v == "" {
|
||||
p.Temperature = nil
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("invalid temperature: %s", v)
|
||||
}
|
||||
p.Temperature = &f
|
||||
}
|
||||
case "frequencyPenalty":
|
||||
if v == "" {
|
||||
p.FrequencyPenalty = nil
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("invalid frequencyPenalty: %s", v)
|
||||
}
|
||||
p.FrequencyPenalty = &f
|
||||
}
|
||||
case "presencePenalty":
|
||||
if v == "" {
|
||||
p.PresencePenalty = nil
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("invalid presencePenalty: %s", v)
|
||||
}
|
||||
p.PresencePenalty = &f
|
||||
}
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// content returns the string content of a simple readable session file.
|
||||
func (s *SessionFileStore) content(name string) string {
|
||||
s.sess.mu.RLock()
|
||||
|
|
@ -183,6 +272,8 @@ func (s *SessionFileStore) content(name string) string {
|
|||
return s.sess.core.SystemPrompt()
|
||||
case "offset":
|
||||
return fmt.Sprintf("%d\n", s.sess.chatOffset)
|
||||
case "params":
|
||||
return formatParams(s.sess.core.GenerationParams())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func (s *SessionStore) List() ([]os.DirEntry, error) {
|
|||
entries := []os.DirEntry{
|
||||
syntheticEntry("new", 0666),
|
||||
syntheticEntry("idx", 0444),
|
||||
syntheticEntry("ls", 0555),
|
||||
}
|
||||
s.srv.mu.RLock()
|
||||
for id := range s.srv.sessions {
|
||||
|
|
@ -32,6 +33,8 @@ func (s *SessionStore) Stat(name string) (os.FileInfo, error) {
|
|||
return &syntheticFileInfo{name: "new", mode: 0666}, nil
|
||||
case "idx":
|
||||
return &syntheticFileInfo{name: "idx", mode: 0444}, nil
|
||||
case "ls":
|
||||
return &syntheticFileInfo{name: "ls", mode: 0555}, nil
|
||||
}
|
||||
s.srv.mu.RLock()
|
||||
_, ok := s.srv.sessions[name]
|
||||
|
|
@ -48,6 +51,8 @@ func (s *SessionStore) Get(name string) ([]byte, error) {
|
|||
return []byte("name=\ncwd=\nbackend=\nmodel=\nagent=\n"), nil
|
||||
case "idx":
|
||||
return s.index(), nil
|
||||
case "ls":
|
||||
return []byte("#!/usr/bin/env bash\ncat ${OLLIE:-$HOME/mnt/ollie}/s/idx\n"), nil
|
||||
}
|
||||
return nil, fmt.Errorf("%s: not a readable file", name)
|
||||
}
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -148,6 +148,8 @@ func ensureEnv() {
|
|||
"OLLIE_SKILLS_PATH": filepath.Join(home, ".config", "ollie", "skills"),
|
||||
"OLLIE_PLAN_PATH": filepath.Join(home, ".config", "ollie", "planning"),
|
||||
"OLLIE_MEMORY_PATH": filepath.Join(home, ".config", "ollie", "memory"),
|
||||
"OLLIE_TMP_PATH": filepath.Join(home, ".local", "share", "ollie", "tmp"),
|
||||
"OLLIE_TRANSCRIPT_PATH": filepath.Join(home, ".config", "ollie", "transcript"),
|
||||
}
|
||||
for k, v := range defaults {
|
||||
if os.Getenv(k) == "" {
|
||||
|
|
|
|||
Reference in New Issue