From 4ec6707bf942c39c222608fcc76138cf9fa6e86e Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sat, 18 Apr 2026 10:32:14 +0200 Subject: [PATCH] 9p: add s/kill synthetic script Co-Authored-By: Claude Sonnet 4.6 --- internal/p9/server.go | 37 ++++++++++++++++--------------------- internal/p9/sessionstore.go | 7 ++++++- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/internal/p9/server.go b/internal/p9/server.go index 971a7f3..dfe6ab1 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -44,7 +44,6 @@ import ( "sync" "time" - "9fans.net/go/plan9" "ollie/pkg/agent" "ollie/pkg/backend" "ollie/pkg/config" @@ -52,6 +51,8 @@ import ( "ollie/pkg/paths" "ollie/pkg/tools" "ollie/pkg/tools/execute" + + "9fans.net/go/plan9" ) const ( @@ -59,7 +60,6 @@ const ( QTFile = plan9.QTFILE ) - // session holds all state for one ollie agent session. type session struct { mu sync.RWMutex @@ -74,7 +74,6 @@ type session struct { chatOffset int // byte position of chat EOF immediately before last prompt submit } - // appendChat appends data to the session's chat log and bumps the Qid version. func (sess *session) appendChat(data []byte) { if len(data) == 0 { @@ -102,15 +101,15 @@ type connState struct { // Server is the 9P server for ollie sessions. type Server struct { - mu sync.RWMutex - sessions map[string]*session - conns []*connState - agentsDir string // kept for session creation wiring - sessionsDir string - agentStore Store - promptStore ReadableStore - planStore Store - memStore Store + mu sync.RWMutex + sessions map[string]*session + conns []*connState + agentsDir string // kept for session creation wiring + sessionsDir string + agentStore Store + promptStore ReadableStore + planStore Store + memStore Store toolStore Store skillStore Store sessionStore Store @@ -573,8 +572,8 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall { } } - // s/new, s/idx, and s/ls are served from the session store. - if path == "/s/new" || path == "/s/idx" || path == "/s/ls" { + // s/new, s/idx, s/kill, and s/ls are served from the session store. + if path == "/s/new" || path == "/s/idx" || path == "/s/ls" || path == "/s/kill" { plog.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) content, err := s.sessionStore.Get(pathBase(path)) if err != nil { @@ -709,7 +708,6 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall { return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Count: 0} } - // readSlice serves a byte slice at the requested offset/count. func (s *Server) readSlice(fc *plan9.Fcall, content []byte) *plan9.Fcall { var data []byte @@ -728,7 +726,6 @@ func (s *Server) helpPath() string { return paths.CfgDir() + "/help.md" } - func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { cs.mu.Lock() f, ok := cs.fids[fc.Fid] @@ -1129,7 +1126,7 @@ func (s *Server) createSession(args []string) error { cfg, _ := config.Load(cfgPath) // nil cfg is handled by BuildAgentEnv newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{ - "execute": execute.Decl(cwd), + "execute": execute.Decl(cwd), }) sessID := name @@ -1208,8 +1205,6 @@ func (s *Server) killSession(id string) { } } - - // formatEvent converts an agent Event to bytes for appending to the chat log. // Output matches ollie-tui's MakeOutputFn so the chat file looks identical to // what appears in the TUI chat pane. @@ -1458,7 +1453,7 @@ func (s *Server) makeStat(path string) plan9.Dir { default: if path == "/backends" || path == "/help" || path == "/s/idx" { mode = 0444 - } else if path == "/s/ls" { + } else if path == "/s/ls" || path == "/s/kill" { mode = 0555 } else if path == "/s/new" { mode = 0666 @@ -1533,7 +1528,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" || path == "/s/ls": + case path == "/s/new" || path == "/s/idx" || path == "/s/ls" || path == "/s/kill": if content, err := s.sessionStore.Get(base); err == nil { dir.Length = uint64(len(content)) } diff --git a/internal/p9/sessionstore.go b/internal/p9/sessionstore.go index f7425be..1ec9d15 100644 --- a/internal/p9/sessionstore.go +++ b/internal/p9/sessionstore.go @@ -18,6 +18,7 @@ func (s *SessionStore) List() ([]os.DirEntry, error) { syntheticEntry("new", 0666), syntheticEntry("idx", 0444), syntheticEntry("ls", 0555), + syntheticEntry("kill", 0555), } s.srv.mu.RLock() for id := range s.srv.sessions { @@ -35,6 +36,8 @@ func (s *SessionStore) Stat(name string) (os.FileInfo, error) { return &syntheticFileInfo{name: "idx", mode: 0444}, nil case "ls": return &syntheticFileInfo{name: "ls", mode: 0555}, nil + case "kill": + return &syntheticFileInfo{name: "kill", mode: 0555}, nil } s.srv.mu.RLock() _, ok := s.srv.sessions[name] @@ -52,7 +55,9 @@ func (s *SessionStore) Get(name string) ([]byte, error) { case "idx": return s.index(), nil case "ls": - return []byte("#!/usr/bin/env bash\ncat ${OLLIE:-$HOME/mnt/ollie}/s/idx\n"), nil + return []byte("#!/usr/bin/env bash\ncat ${OLLIE}/s/idx\n"), nil + case "kill": + return []byte("#!/usr/bin/env bash\nrm -r ${OLLIE}/s/$1\n"), nil } return nil, fmt.Errorf("%s: not a readable file", name) }