Class: Crimson::SessionManager
- Inherits:
-
Object
- Object
- Crimson::SessionManager
- Defined in:
- lib/crimson/session_manager.rb
Overview
JSONL-based session persistence manager. Sessions are stored as per-directory JSONL files with a header entry.
Constant Summary collapse
- CURRENT_SESSION_VERSION =
Current session file format version.
1
Instance Method Summary collapse
-
#append(session_id, cwd:, entry:) ⇒ void
Append an entry to a session.
-
#create(cwd:, parent_session: nil) ⇒ String
Create a new session and return its ID.
-
#delete(session_id, cwd:) ⇒ void
Delete a session file.
-
#dir_hash(cwd:) ⇒ Object
private
Compute a short directory hash for session folder naming.
-
#fork(session_id, cwd:, from_entry_id:) ⇒ String
Fork a session at a specific entry, creating a new branching session.
-
#initialize(sessions_dir: nil) ⇒ SessionManager
constructor
A new instance of SessionManager.
-
#latest(cwd:) ⇒ SessionMeta?
Get the most recent session for a directory.
-
#list(cwd:) ⇒ Array<SessionMeta>
List all sessions for a given directory, sorted by mtime (newest first).
-
#load(session_id, cwd:) ⇒ Array<SessionEntry>
Load all entries for a session.
-
#load_header(session_id, cwd:) ⇒ Hash?
Load only the header entry of a session.
- #session_file(session_id, cwd:) ⇒ Object private
-
#set_name(session_id, cwd:, name:) ⇒ void
Set the human-readable name for a session.
Constructor Details
#initialize(sessions_dir: nil) ⇒ SessionManager
Returns a new instance of SessionManager.
19 20 21 |
# File 'lib/crimson/session_manager.rb', line 19 def initialize(sessions_dir: nil) @sessions_dir = sessions_dir || File.join(Crimson::CONFIG_DIR, "sessions") end |
Instance Method Details
#append(session_id, cwd:, entry:) ⇒ void
This method returns an undefined value.
Append an entry to a session.
91 92 93 94 95 |
# File 'lib/crimson/session_manager.rb', line 91 def append(session_id, cwd:, entry:) file = session_file(session_id, cwd: cwd) FileUtils.mkdir_p(File.dirname(file)) File.open(file, "a") { |f| f.puts(entry.to_json) } end |
#create(cwd:, parent_session: nil) ⇒ String
Create a new session and return its ID.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/crimson/session_manager.rb', line 27 def create(cwd:, parent_session: nil) id = SecureRandom.uuid FileUtils.mkdir_p(session_dir(cwd: cwd)) header = { type: "session_header", version: CURRENT_SESSION_VERSION, id: id, timestamp: Time.now.utc.iso8601, cwd: cwd, parentSession: parent_session } File.write(session_file(id, cwd: cwd), JSON.generate(header) + "\n") id end |
#delete(session_id, cwd:) ⇒ void
This method returns an undefined value.
Delete a session file.
196 197 198 199 |
# File 'lib/crimson/session_manager.rb', line 196 def delete(session_id, cwd:) file = session_file(session_id, cwd: cwd) File.delete(file) if File.exist?(file) end |
#dir_hash(cwd:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Compute a short directory hash for session folder naming.
208 209 210 |
# File 'lib/crimson/session_manager.rb', line 208 def dir_hash(cwd:) Digest::SHA256.hexdigest(cwd)[0, 12] end |
#fork(session_id, cwd:, from_entry_id:) ⇒ String
Fork a session at a specific entry, creating a new branching session.
181 182 183 184 185 186 187 188 189 190 |
# File 'lib/crimson/session_manager.rb', line 181 def fork(session_id, cwd:, from_entry_id:) entries = load(session_id, cwd: cwd) fork_point = entries.index { |e| e.id == from_entry_id } raise "Entry #{from_entry_id} not found in session #{session_id}" unless fork_point prefix = entries[0..fork_point] new_id = SecureRandom.uuid prefix.each { |e| append(new_id, cwd: cwd, entry: e) } new_id end |
#latest(cwd:) ⇒ SessionMeta?
Get the most recent session for a directory.
170 171 172 173 |
# File 'lib/crimson/session_manager.rb', line 170 def latest(cwd:) sessions = list(cwd: cwd) sessions.first end |
#list(cwd:) ⇒ Array<SessionMeta>
List all sessions for a given directory, sorted by mtime (newest first).
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/crimson/session_manager.rb', line 100 def list(cwd:) dir = session_dir(cwd: cwd) return [] unless Dir.exist?(dir) Dir.glob(File.join(dir, "*.jsonl")).filter_map do |file| id = File.basename(file, ".jsonl") entries = [] last_user_content = nil session_name = nil File.foreach(file) do |line| line = line.strip next if line.empty? begin parsed = JSON.parse(line) if parsed["type"] == "session_header" session_name = parsed["name"] next end entry = SessionEntry.from_h(parsed) entries << entry last_user_content = entry.content if entry.role == "user" rescue JSON::ParserError next end end next if entries.empty? SessionMeta.new( id: id, entry_count: entries.length, last_timestamp: entries.last., preview: last_user_content && last_user_content.length > 80 ? last_user_content[0, 77] + "..." : last_user_content, name: session_name, mtime: File.mtime(file) ) end.sort_by { |s| s.mtime }.reverse end |
#load(session_id, cwd:) ⇒ Array<SessionEntry>
Load all entries for a session.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/crimson/session_manager.rb', line 46 def load(session_id, cwd:) file = session_file(session_id, cwd: cwd) return [] unless File.exist?(file) entries = [] File.foreach(file) do |line| line = line.strip next if line.empty? begin parsed = JSON.parse(line) next if parsed["type"] == "session_header" entries << SessionEntry.from_h(parsed) rescue JSON::ParserError next end end entries end |
#load_header(session_id, cwd:) ⇒ Hash?
Load only the header entry of a session.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/crimson/session_manager.rb', line 69 def load_header(session_id, cwd:) file = session_file(session_id, cwd: cwd) return nil unless File.exist?(file) File.foreach(file) do |line| line = line.strip next if line.empty? begin parsed = JSON.parse(line) return parsed if parsed["type"] == "session_header" rescue JSON::ParserError next end end nil end |
#session_file(session_id, cwd:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
202 203 204 |
# File 'lib/crimson/session_manager.rb', line 202 def session_file(session_id, cwd:) File.join(session_dir(cwd: cwd), "#{session_id}.jsonl") end |
#set_name(session_id, cwd:, name:) ⇒ void
This method returns an undefined value.
Set the human-readable name for a session.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/crimson/session_manager.rb', line 145 def set_name(session_id, cwd:, name:) file = session_file(session_id, cwd: cwd) return unless File.exist?(file) lines = File.readlines(file) lines.each_with_index do |line, idx| stripped = line.strip next if stripped.empty? begin parsed = JSON.parse(stripped) if parsed["type"] == "session_header" parsed["name"] = name lines[idx] = JSON.generate(parsed) + "\n" File.write(file, lines.join) return end rescue JSON::ParserError next end end end |