Files
weichenandCursor 6bc1b08540 Record sessions as an append-only entry tree
Why: Linear transcripts cannot fork, and compaction rewrote history, so resume and branch navigation had nothing to point at.
Evidence: go test -count=1 ./runtime/session/
Impact: Load still returns the model-visible path, including old one-message-per-line files. Compaction appends a summary entry instead of truncating the file.
Refs: feature/pi-headless N1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 10:08:04 +00:00

106 lines
2.2 KiB
Go

package session
import (
"os"
"path/filepath"
"sort"
"strings"
)
// Info describes one session file in a directory.
type Info struct {
ID string
Path string
Name string
CWD string
Leaf string
Updated string
}
// List returns session files in dir, newest first.
func List(dir string) ([]Info, error) {
ents, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
var out []Info
for _, ent := range ents {
name := ent.Name()
if ent.IsDir() || !strings.HasSuffix(name, ".jsonl") {
continue
}
path := filepath.Join(dir, name)
st, err := Open(path)
if err != nil {
continue
}
info, err := st.Info()
if err != nil {
continue
}
out = append(out, info)
}
sort.Slice(out, func(i, j int) bool { return out[i].Updated > out[j].Updated })
return out, nil
}
// Continue opens the newest session whose cwd matches. A second return
// value of false means there is nothing to continue.
func Continue(dir, cwd string) (*Store, bool, error) {
list, err := List(dir)
if err != nil {
return nil, false, err
}
for _, info := range list {
if cwd == "" || info.CWD == cwd {
st, err := Open(info.Path)
return st, err == nil, err
}
}
return nil, false, nil
}
// Resume opens a session by id (filename stem) or by name.
func Resume(dir, idOrName string) (*Store, error) {
list, err := List(dir)
if err != nil {
return nil, err
}
for _, info := range list {
if info.ID == idOrName || info.Name == idOrName {
return Open(info.Path)
}
}
return nil, os.ErrNotExist
}
// SetCWD records the working directory used by Continue.
func (s *Store) SetCWD(cwd string) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.writeMetaKeyLocked("cwd", cwd)
}
// Info reads the catalog fields for this file.
func (s *Store) Info() (Info, error) {
s.mu.Lock()
defer s.mu.Unlock()
entries, err := s.readEntriesLocked()
if err != nil {
return Info{}, err
}
meta := s.readMetaLocked()
updated := ""
if len(entries) > 0 {
updated = entries[len(entries)-1].Timestamp
}
id := strings.TrimSuffix(filepath.Base(s.path), ".jsonl")
return Info{
ID: id, Path: s.path, Name: meta["name"], CWD: meta["cwd"],
Leaf: s.leafLocked(entries), Updated: updated,
}, nil
}