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)
This commit is contained in:
parent
f7e084106c
commit
c07aef6bb9
91
main_test.go
91
main_test.go
|
|
@ -160,9 +160,8 @@ var _ sync.Mutex // suppress unused import
|
||||||
func newTestSessionFileStore(t *testing.T, sess *session.Session) *fs.Tree {
|
func newTestSessionFileStore(t *testing.T, sess *session.Session) *fs.Tree {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
// Use agent tree for agent-level file tests
|
||||||
func() {},
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func(id string) error { return nil },
|
|
||||||
func(data []byte) error { return nil },
|
func(data []byte) error { return nil },
|
||||||
func() {},
|
func() {},
|
||||||
nil,
|
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 {
|
func newTestSessionFileStoreWith(t *testing.T, sess *session.Session, kill func(), rename func(string) error, save func([]byte) error) *fs.Tree {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
|
// Session tree for kill/rename/save operations
|
||||||
return session.NewSessionTree(sess, sink.NewLogger("test"), kill, rename, save, func() {}, nil, nil)
|
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")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
_ = session.NewSessionTree(sess, sink.NewLogger("test"),
|
_ = session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionFileStoreList(t *testing.T) {
|
func TestSessionFileStoreList(t *testing.T) {
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
entries, err := sf.List()
|
entries, err := sf.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("List: %v", err)
|
t.Fatalf("List: %v", err)
|
||||||
}
|
}
|
||||||
if len(entries) != len(session.FileList) {
|
if len(entries) != len(session.AgentFileList) {
|
||||||
t.Errorf("List() returned %d entries; want %d", len(entries), len(session.FileList))
|
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()
|
defer sess.Cancel()
|
||||||
sess.AppendLog([]byte("hello"))
|
sess.AppendLog([]byte("hello"))
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
fi, err := sf.Stat("chat")
|
fi, err := sf.Stat("chat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -432,8 +432,8 @@ func TestSessionFileStoreGetChat(t *testing.T) {
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sess.AppendLog([]byte("hello"))
|
sess.AppendLog([]byte("hello"))
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
data := testStoreRead(t, sf, "chat")
|
data := testStoreRead(t, sf, "chat")
|
||||||
if string(data) != "hello" {
|
if string(data) != "hello" {
|
||||||
|
|
@ -445,8 +445,8 @@ func TestSessionFileStoreGetContent(t *testing.T) {
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
for _, name := range []string{"cfg", "offset", "usage", "ctxsz", "models", "systemprompt"} {
|
for _, name := range []string{"cfg", "offset", "usage", "ctxsz", "models", "systemprompt"} {
|
||||||
if _, err := sf.Open(name); err != nil {
|
if _, err := sf.Open(name); err != nil {
|
||||||
|
|
@ -459,8 +459,8 @@ func TestSessionFileStorePutCwd(t *testing.T) {
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
// Create the directory first so SetCWD validates it
|
// Create the directory first so SetCWD validates it
|
||||||
os.MkdirAll("/tmp/newpath", 0755)
|
os.MkdirAll("/tmp/newpath", 0755)
|
||||||
|
|
@ -475,8 +475,8 @@ func TestSessionFileStorePutEmpty(t *testing.T) {
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
sf := session.NewSessionTree(sess, sink.NewLogger("test"),
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func() {}, func(string) error { return nil }, func([]byte) error { return nil }, func() {}, nil, nil)
|
func([]byte) error { return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
// Empty write is a no-op
|
// Empty write is a no-op
|
||||||
e, err := sf.Open("cfg")
|
e, err := sf.Open("cfg")
|
||||||
|
|
@ -581,9 +581,9 @@ func TestSessionFileStoreWriteChat(t *testing.T) {
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
var saved []byte
|
var saved []byte
|
||||||
sf := newTestSessionFileStoreWith(t, sess,
|
sink := testSink()
|
||||||
func() {}, func(string) error { return nil },
|
sf := session.NewAgentTree(sess, sink.NewLogger("test"),
|
||||||
func(data []byte) error { saved = data; return nil })
|
func(data []byte) error { saved = data; return nil }, func() {}, nil, nil)
|
||||||
|
|
||||||
testStoreWrite(t, sf, "chat", []byte("transcript data"))
|
testStoreWrite(t, sf, "chat", []byte("transcript data"))
|
||||||
if string(saved) != "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)")
|
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
|
// slash commands forwarded to Submit
|
||||||
sess5 := testSession("s5")
|
sess5 := testSession("s5")
|
||||||
defer sess5.Cancel()
|
defer sess5.Cancel()
|
||||||
|
|
@ -949,17 +915,14 @@ func TestSessionManagerOpenStore(t *testing.T) {
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
// Access a session file through the manager
|
// Access a session-level file through the manager
|
||||||
e, err := s.Tree().Open("s1/cfg")
|
e, err := s.Tree().Open("s1/plan")
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("Read(s1/cfg): %v", err)
|
t.Fatalf("Read(s1/plan): %v", err)
|
||||||
}
|
|
||||||
if !strings.Contains(string(data), "name=s1") {
|
|
||||||
t.Errorf("cfg = %q; want to contain name=s1", data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
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}
|
h := &sessionHelper{sess: sess, log: log, kill: kill, rename: rename, saveTranscript: saveTranscript, invalidateModels: invalidateModels, resetElevation: resetElevation, toolRegistry: toolRegistry}
|
||||||
perms := sessionFilePerms()
|
perms := sessionFilePerms()
|
||||||
specs := make([]fs.FileSpec, len(FileList))
|
specs := make([]fs.FileSpec, len(SessionFileList))
|
||||||
for i, f := range FileList {
|
for i, f := range SessionFileList {
|
||||||
specs[i] = h.fileSpec(f.Name, perms[f.Name])
|
specs[i] = h.fileSpec(f.Name, perms[f.Name])
|
||||||
specs[i].OneShot = f.OneShot
|
specs[i].OneShot = f.OneShot
|
||||||
specs[i].Async = f.Async
|
specs[i].Async = f.Async
|
||||||
|
|
|
||||||
Reference in New Issue