diff --git a/internal/p9/store.go b/internal/p9/store.go deleted file mode 100644 index 623d73a..0000000 --- a/internal/p9/store.go +++ /dev/null @@ -1,166 +0,0 @@ -package p9 - -import ( - "fmt" - "os" - "strings" - - "ollie/pkg/paths" - "ollie/pkg/store" - "ollie/pkg/tools/execute" -) - -// Re-export core store types so existing 9p code compiles unchanged. -type ( - ReadableStore = store.ReadableStore - WritableStore = store.WritableStore - ReadWriteStore = store.ReadWriteStore - BlobStore = store.BlobStore - Store = store.Store - FlatDirStore = store.FlatDir - SkillStore = store.SkillStore - BatchStore = store.BatchStore - BatchJobStore = store.BatchJobStore - Session = store.Session - SessionStore = store.SessionStore - SessionFileStore = store.SessionFileStore - - syntheticFileInfo = store.SyntheticFileInfo -) - -func NewFlatDirStore(dir string, perm os.FileMode) *FlatDirStore { - return store.NewFlatDir(dir, perm) -} - -func NewSkillStore() *SkillStore { - return store.NewSkillStore() -} - -func syntheticEntry(name string, mode os.FileMode) os.DirEntry { - return store.FileEntry(name, mode) -} - -func syntheticDirEntry(name string, mode os.FileMode) os.DirEntry { - return store.DirEntry(name, mode) -} - -// --- util --- - -// UtilStore is a FlatDirStore backed by the scripts/u/ directory. -type UtilStore struct { - *FlatDirStore -} - -func NewUtilStore() *UtilStore { - return &UtilStore{FlatDirStore: NewFlatDirStore(paths.CfgDir()+"/scripts/u", 0555)} -} - -func (s *UtilStore) Stat(name string) (os.FileInfo, error) { - return s.FlatDirStore.Stat(name) -} - -func (s *UtilStore) List() ([]os.DirEntry, error) { - return s.FlatDirStore.List() -} - -func (s *UtilStore) Get(name string) ([]byte, error) { - return s.FlatDirStore.Get(name) -} - -// --- exec --- - -// ExecStore is a read-only FlatDirStore backed by the scripts/x/ directory. -type ExecStore struct { - *FlatDirStore -} - -func NewExecStore() *ExecStore { - return &ExecStore{FlatDirStore: NewFlatDirStore(execute.PluginsPath(), 0555)} -} - -func (s *ExecStore) Stat(name string) (os.FileInfo, error) { - return s.FlatDirStore.Stat(name) -} - -func (s *ExecStore) List() ([]os.DirEntry, error) { - return s.FlatDirStore.List() -} - -func (s *ExecStore) Get(name string) ([]byte, error) { - return s.FlatDirStore.Get(name) -} - -// --- tools --- - -// ToolStore is a BlobStore backed by the tools directory. -// It extends FlatDirStore with a synthetic "idx" entry that lists all tools -// with their descriptions and argument signatures. -type ToolStore struct { - *FlatDirStore -} - -func NewToolStore() *ToolStore { - return &ToolStore{FlatDirStore: NewFlatDirStore(execute.ToolsPath(), 0755)} -} - -func (s *ToolStore) Stat(name string) (os.FileInfo, error) { - if name == "idx" { - return &syntheticFileInfo{Name_: "idx", Mode_: 0444}, nil - } - return s.FlatDirStore.Stat(name) -} - -func (s *ToolStore) List() ([]os.DirEntry, error) { - entries, err := s.FlatDirStore.List() - result := make([]os.DirEntry, 0, len(entries)+1) - result = append(result, syntheticEntry("idx", 0444)) - result = append(result, entries...) - return result, err -} - -func (s *ToolStore) Get(name string) ([]byte, error) { - if name == "idx" { - return s.index() - } - return s.FlatDirStore.Get(name) -} - -func (s *ToolStore) index() ([]byte, error) { - entries, err := s.FlatDirStore.List() - if err != nil { - return nil, err - } - var sb strings.Builder - for _, e := range entries { - if e.IsDir() { - continue - } - data, err := s.FlatDirStore.Get(e.Name()) - if err != nil { - continue - } - var desc, args string - for line := range strings.SplitSeq(string(data), "\n") { - if d, ok := strings.CutPrefix(line, "# description:"); ok { - desc = strings.TrimSpace(d) - } else if a, ok := strings.CutPrefix(line, "# Args:"); ok { - args = strings.TrimSpace(a) - } else if a, ok := strings.CutPrefix(line, "# args:"); ok { - args = strings.TrimSpace(a) - } - if !strings.HasPrefix(line, "#") && line != "" { - break - } - } - if desc == "" { - continue - } - fmt.Fprintf(&sb, "## %s\n", e.Name()) - fmt.Fprintf(&sb, "description: %s\n", desc) - if args != "" { - fmt.Fprintf(&sb, "args: %s\n", args) - } - sb.WriteString("\n") - } - return []byte(sb.String()), nil -} diff --git a/main.go b/main.go index 7a8afb1..04d068d 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,6 @@ import ( "syscall" "9fans.net/go/plan9/client" - p9 "olliesrv/internal/p9" "ollie/pkg/env" olog "ollie/pkg/log" ) @@ -153,7 +152,7 @@ func runServer(sockPath, pidPath string) { os.WriteFile(pidPath, []byte(fmt.Sprintf("%d", os.Getpid())), 0644) //nolint:errcheck sink := olog.NewSink(os.Stdout, os.Stderr, olog.ParseLevel(os.Getenv("OLLIE_LOG"), olog.LevelWarn)) - srv := p9.New(sink) + srv := New(sink) listener, err := net.Listen("unix", sockPath) if err != nil { diff --git a/olliesrv b/olliesrv new file mode 100755 index 0000000..756a92f Binary files /dev/null and b/olliesrv differ diff --git a/internal/p9/server.go b/server.go similarity index 91% rename from internal/p9/server.go rename to server.go index feda4d4..f200cad 100644 --- a/internal/p9/server.go +++ b/server.go @@ -1,37 +1,6 @@ -// Package p9 implements the 9P filesystem for ollie sessions. -// -// Filesystem layout: -// -// ollie/ -// a/ (dir) agent configs (r/w, backed by ~/.config/ollie/agents/) -// backends (read) list of ollie-provided backends -// help (read) help file (backed by ~/.config/ollie/help.md) -// m/ (dir) memories (r/w, backed by OLLIE_MEMORY_PATH) -// p/ (dir) prompt templates -// s/ (dir) session directory -// new (r/w) read: KV template; write: create session -// idx (read) live index: name state cwd backend model (tab-separated) -// {session-id}/ rm -r to kill session; mv to rename -// ctl (write) session control: stop, kill, rn, compact, clear -// prompt (write) submit a prompt to the agent -// fifo.in (write) queue a prompt for later execution -// fifo.out (read) pop the next queued prompt -// chat (r/w) cumulative chat history; write to save a transcript to tr/ -// state (read) current agent state -// backend (r/w) active backend name -// agent (r/w) active agent name -// model (r/w) active model name -// models (read) available models from the backend -// models (read) available models -// cwd (r/w) working directory -// usage (read) token counts -// 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) -// x/ (dir) plugins (ro, backed by ~/.config/ollie/scripts/x/) -package p9 +// 9P filesystem server for ollie sessions. +// See doc comment below for filesystem layout. +package main import ( "context" @@ -49,10 +18,166 @@ import ( olog "ollie/pkg/log" "ollie/pkg/paths" "ollie/pkg/store" + "ollie/pkg/tools/execute" "9fans.net/go/plan9" ) +// Re-export core store types so existing 9p code compiles unchanged. +type ( + ReadableStore = store.ReadableStore + WritableStore = store.WritableStore + ReadWriteStore = store.ReadWriteStore + BlobStore = store.BlobStore + Store = store.Store + FlatDirStore = store.FlatDir + SkillStore = store.SkillStore + BatchStore = store.BatchStore + BatchJobStore = store.BatchJobStore + Session = store.Session + SessionStore = store.SessionStore + SessionFileStore = store.SessionFileStore + + syntheticFileInfo = store.SyntheticFileInfo +) + +func NewFlatDirStore(dir string, perm os.FileMode) *FlatDirStore { + return store.NewFlatDir(dir, perm) +} + +func NewSkillStore() *SkillStore { + return store.NewSkillStore() +} + +func syntheticEntry(name string, mode os.FileMode) os.DirEntry { + return store.FileEntry(name, mode) +} + +func syntheticDirEntry(name string, mode os.FileMode) os.DirEntry { + return store.DirEntry(name, mode) +} + +// --- util --- + +// UtilStore is a FlatDirStore backed by the scripts/u/ directory. +type UtilStore struct { + *FlatDirStore +} + +func NewUtilStore() *UtilStore { + return &UtilStore{FlatDirStore: NewFlatDirStore(paths.CfgDir()+"/scripts/u", 0555)} +} + +func (s *UtilStore) Stat(name string) (os.FileInfo, error) { + return s.FlatDirStore.Stat(name) +} + +func (s *UtilStore) List() ([]os.DirEntry, error) { + return s.FlatDirStore.List() +} + +func (s *UtilStore) Get(name string) ([]byte, error) { + return s.FlatDirStore.Get(name) +} + +// --- exec --- + +// ExecStore is a read-only FlatDirStore backed by the scripts/x/ directory. +type ExecStore struct { + *FlatDirStore +} + +func NewExecStore() *ExecStore { + return &ExecStore{FlatDirStore: NewFlatDirStore(execute.PluginsPath(), 0555)} +} + +func (s *ExecStore) Stat(name string) (os.FileInfo, error) { + return s.FlatDirStore.Stat(name) +} + +func (s *ExecStore) List() ([]os.DirEntry, error) { + return s.FlatDirStore.List() +} + +func (s *ExecStore) Get(name string) ([]byte, error) { + return s.FlatDirStore.Get(name) +} + +// --- tools --- + +// ToolStore is a BlobStore backed by the tools directory. +// It extends FlatDirStore with a synthetic "idx" entry that lists all tools +// with their descriptions and argument signatures. +type ToolStore struct { + *FlatDirStore +} + +func NewToolStore() *ToolStore { + return &ToolStore{FlatDirStore: NewFlatDirStore(execute.ToolsPath(), 0755)} +} + +func (s *ToolStore) Stat(name string) (os.FileInfo, error) { + if name == "idx" { + return &syntheticFileInfo{Name_: "idx", Mode_: 0444}, nil + } + return s.FlatDirStore.Stat(name) +} + +func (s *ToolStore) List() ([]os.DirEntry, error) { + entries, err := s.FlatDirStore.List() + result := make([]os.DirEntry, 0, len(entries)+1) + result = append(result, syntheticEntry("idx", 0444)) + result = append(result, entries...) + return result, err +} + +func (s *ToolStore) Get(name string) ([]byte, error) { + if name == "idx" { + return s.index() + } + return s.FlatDirStore.Get(name) +} + +func (s *ToolStore) index() ([]byte, error) { + entries, err := s.FlatDirStore.List() + if err != nil { + return nil, err + } + var sb strings.Builder + for _, e := range entries { + if e.IsDir() { + continue + } + data, err := s.FlatDirStore.Get(e.Name()) + if err != nil { + continue + } + var desc, args string + for line := range strings.SplitSeq(string(data), "\n") { + if d, ok := strings.CutPrefix(line, "# description:"); ok { + desc = strings.TrimSpace(d) + } else if a, ok := strings.CutPrefix(line, "# Args:"); ok { + args = strings.TrimSpace(a) + } else if a, ok := strings.CutPrefix(line, "# args:"); ok { + args = strings.TrimSpace(a) + } + if !strings.HasPrefix(line, "#") && line != "" { + break + } + } + if desc == "" { + continue + } + fmt.Fprintf(&sb, "## %s\n", e.Name()) + fmt.Fprintf(&sb, "description: %s\n", desc) + if args != "" { + fmt.Fprintf(&sb, "args: %s\n", args) + } + sb.WriteString("\n") + } + return []byte(sb.String()), nil +} + const ( QTDir = plan9.QTDIR QTFile = plan9.QTFILE