From c07aef6bb93ed5d75702ef733e5cb3f9fa4383a9 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 29 Jul 2026 20:56:09 +0200 Subject: [PATCH] session: enforce agent/session split in file trees - NewSessionTree now only serves plan + env (session-level) - NewAgentTree serves all 21 agent-level files - Tests updated to use agent tree for agent files - Removed kill/rename/save from ctl (session ops via rm/mv) - TestSessionManagerOpenStore tests session-level file (plan) --- main_test.go | 91 +++++++++++++----------------------------- session/sessionfile.go | 4 +- 2 files changed, 29 insertions(+), 66 deletions(-) diff --git a/main_test.go b/main_test.go index 40496bd..b94fb6b 100644 --- a/main_test.go +++ b/main_test.go @@ -160,9 +160,8 @@ var _ sync.Mutex // suppress unused import func newTestSessionFileStore(t *testing.T, sess *session.Session) *fs.Tree { t.Helper() sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, - func(id string) error { return nil }, + // Use agent tree for agent-level file tests + sf := session.NewAgentTree(sess, sink.NewLogger("test"), func(data []byte) error { return nil }, func() {}, nil, @@ -174,6 +173,7 @@ func newTestSessionFileStore(t *testing.T, sess *session.Session) *fs.Tree { func newTestSessionFileStoreWith(t *testing.T, sess *session.Session, kill func(), rename func(string) error, save func([]byte) error) *fs.Tree { t.Helper() sink := testSink() + // Session tree for kill/rename/save operations return session.NewSessionTree(sess, sink.NewLogger("test"), kill, rename, save, func() {}, nil, nil) } @@ -390,23 +390,23 @@ func TestSessionFileStoreReadableContract(t *testing.T) { sess := testSession("s1") defer sess.Cancel() sink := testSink() - _ = session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + _ = session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) } func TestSessionFileStoreList(t *testing.T) { sess := testSession("s1") defer sess.Cancel() sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) entries, err := sf.List() if err != nil { t.Fatalf("List: %v", err) } - if len(entries) != len(session.FileList) { - t.Errorf("List() returned %d entries; want %d", len(entries), len(session.FileList)) + if len(entries) != len(session.AgentFileList) { + t.Errorf("List() returned %d entries; want %d", len(entries), len(session.AgentFileList)) } } @@ -415,8 +415,8 @@ func TestSessionFileStoreStatChat(t *testing.T) { defer sess.Cancel() sess.AppendLog([]byte("hello")) sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) fi, err := sf.Stat("chat") if err != nil { @@ -432,8 +432,8 @@ func TestSessionFileStoreGetChat(t *testing.T) { defer sess.Cancel() sess.AppendLog([]byte("hello")) sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) data := testStoreRead(t, sf, "chat") if string(data) != "hello" { @@ -445,8 +445,8 @@ func TestSessionFileStoreGetContent(t *testing.T) { sess := testSession("s1") defer sess.Cancel() sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) for _, name := range []string{"cfg", "offset", "usage", "ctxsz", "models", "systemprompt"} { if _, err := sf.Open(name); err != nil { @@ -459,8 +459,8 @@ func TestSessionFileStorePutCwd(t *testing.T) { sess := testSession("s1") defer sess.Cancel() sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) // Create the directory first so SetCWD validates it os.MkdirAll("/tmp/newpath", 0755) @@ -475,8 +475,8 @@ func TestSessionFileStorePutEmpty(t *testing.T) { sess := testSession("s1") defer sess.Cancel() sink := testSink() - sf := session.NewSessionTree(sess, sink.NewLogger("test"), - func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil) + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func([]byte) error { return nil }, func() {}, nil, nil) // Empty write is a no-op e, err := sf.Open("cfg") @@ -581,9 +581,9 @@ func TestSessionFileStoreWriteChat(t *testing.T) { sess := testSession("s1") defer sess.Cancel() var saved []byte - sf := newTestSessionFileStoreWith(t, sess, - func() {}, func(string) error { return nil }, - func(data []byte) error { saved = data; return nil }) + sink := testSink() + sf := session.NewAgentTree(sess, sink.NewLogger("test"), + func(data []byte) error { saved = data; return nil }, func() {}, nil, nil) testStoreWrite(t, sf, "chat", []byte("transcript data")) if string(saved) != "transcript data" { @@ -679,40 +679,6 @@ func TestSessionFileStoreHandleCtl(t *testing.T) { t.Error("ctl stop should interrupt (session should not be running)") } - // kill - var killed bool - sf2 := newTestSessionFileStoreWith(t, sess, - func() { killed = true }, - func(string) error { return nil }, - func([]byte) error { return nil }) - testStoreWrite(t, sf2, "ctl", []byte("kill")) - if !killed { - t.Error("ctl kill should call kill callback") - } - - // rn (rename) - var renamed string - sf3 := newTestSessionFileStoreWith(t, sess, - func() {}, - func(id string) error { renamed = id; return nil }, - func([]byte) error { return nil }) - testStoreWrite(t, sf3, "ctl", []byte("rn newname")) - if renamed != "newname" { - t.Errorf("renamed = %q; want newname", renamed) - } - - // save - sess.AppendLog([]byte("log data")) - var saved []byte - sf4 := newTestSessionFileStoreWith(t, sess, - func() {}, - func(string) error { return nil }, - func(data []byte) error { saved = data; return nil }) - testStoreWrite(t, sf4, "ctl", []byte("save")) - if !strings.Contains(string(saved), "log data") { - t.Errorf("saved = %q; want to contain log data", saved) - } - // slash commands forwarded to Submit sess5 := testSession("s5") defer sess5.Cancel() @@ -949,17 +915,14 @@ func TestSessionManagerOpenStore(t *testing.T) { defer sess.Cancel() s.AddSession(sess) - // Access a session file through the manager - e, err := s.Tree().Open("s1/cfg") + // Access a session-level file through the manager + e, err := s.Tree().Open("s1/plan") if err != nil { - t.Fatalf("Open(s1/cfg): %v", err) + t.Fatalf("Open(s1/plan): %v", err) } - data, err := e.Read() + _, err = e.Read() if err != nil { - t.Fatalf("Read(s1/cfg): %v", err) - } - if !strings.Contains(string(data), "name=s1") { - t.Errorf("cfg = %q; want to contain name=s1", data) + t.Fatalf("Read(s1/plan): %v", err) } } diff --git a/session/sessionfile.go b/session/sessionfile.go index 03031cc..10b9baf 100644 --- a/session/sessionfile.go +++ b/session/sessionfile.go @@ -66,8 +66,8 @@ func sessionFilePerms() map[string]os.FileMode { func NewSessionTree(sess *Session, log *olog.Logger, kill func(), rename func(newID string) error, saveTranscript func([]byte) error, invalidateModels func(), resetElevation func(), toolRegistry *tools.Registry) *fs.Tree { h := &sessionHelper{sess: sess, log: log, kill: kill, rename: rename, saveTranscript: saveTranscript, invalidateModels: invalidateModels, resetElevation: resetElevation, toolRegistry: toolRegistry} perms := sessionFilePerms() - specs := make([]fs.FileSpec, len(FileList)) - for i, f := range FileList { + specs := make([]fs.FileSpec, len(SessionFileList)) + for i, f := range SessionFileList { specs[i] = h.fileSpec(f.Name, perms[f.Name]) specs[i].OneShot = f.OneShot specs[i].Async = f.Async