Class: Yorishiro::SessionStore
- Inherits:
-
Object
- Object
- Yorishiro::SessionStore
- Defined in:
- lib/yorishiro/session_store.rb
Overview
Persists conversations to .yorishiro/sessions/
Constant Summary collapse
- DIR_NAME =
File.join(".yorishiro", "sessions")
- MAX_SESSIONS =
50- SCHEMA_VERSION =
1- TITLE_MAX_LENGTH =
60
Instance Attribute Summary collapse
-
#sessions_dir ⇒ Object
readonly
Returns the value of attribute sessions_dir.
Instance Method Summary collapse
-
#claim(id) ⇒ Object
Take exclusive ownership of a session id for this process, so a second
yorishiroresuming the same session in the same directory doesn't write over this one's history (last-writer-wins data loss). -
#initialize(dir: Dir.pwd, max_sessions: MAX_SESSIONS) ⇒ SessionStore
constructor
A new instance of SessionStore.
- #latest ⇒ Object
-
#list ⇒ Object
All sessions, most recently updated first.
-
#load(id) ⇒ Object
Load a session by exact id or id prefix (most recent match wins).
-
#release_locks ⇒ Object
Release every lock held by this store.
-
#save(id:, messages:, provider:, model:) ⇒ Object
Write the session and return its id (generating one when nil).
Constructor Details
#initialize(dir: Dir.pwd, max_sessions: MAX_SESSIONS) ⇒ SessionStore
Returns a new instance of SessionStore.
18 19 20 21 22 |
# File 'lib/yorishiro/session_store.rb', line 18 def initialize(dir: Dir.pwd, max_sessions: MAX_SESSIONS) @sessions_dir = File.join(dir, DIR_NAME) @max_sessions = max_sessions @locks = {} end |
Instance Attribute Details
#sessions_dir ⇒ Object (readonly)
Returns the value of attribute sessions_dir.
24 25 26 |
# File 'lib/yorishiro/session_store.rb', line 24 def sessions_dir @sessions_dir end |
Instance Method Details
#claim(id) ⇒ Object
Take exclusive ownership of a session id for this process, so a second
yorishiro resuming the same session in the same directory doesn't
write over this one's history (last-writer-wins data loss). Returns
true when acquired (or already held by us), false when another live
process owns it. The advisory lock is released automatically on
process exit. Filesystems without flock fail open to preserve the
single-user behaviour.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/yorishiro/session_store.rb', line 33 def claim(id) return true if @locks.key?(id) FileUtils.mkdir_p(@sessions_dir) # Held open for the process lifetime so the advisory lock persists; # the block form would close it and release the lock immediately. lock = File.open(lock_path_for(id), File::CREAT | File::RDWR, 0o644) # rubocop:disable Style/FileOpen if lock.flock(File::LOCK_EX | File::LOCK_NB) @locks[id] = lock true else lock.close false end rescue SystemCallError true end |
#latest ⇒ Object
95 96 97 |
# File 'lib/yorishiro/session_store.rb', line 95 def latest list.first end |
#list ⇒ Object
All sessions, most recently updated first. Corrupt files are skipped.
100 101 102 103 104 105 |
# File 'lib/yorishiro/session_store.rb', line 100 def list Dir.glob(File.join(@sessions_dir, "*.json")) .filter_map { |path| parse_session(path) } .sort_by { |session| session[:updated_at].to_s } .reverse end |
#load(id) ⇒ Object
Load a session by exact id or id prefix (most recent match wins). Returns nil when missing or corrupt.
87 88 89 90 91 92 93 |
# File 'lib/yorishiro/session_store.rb', line 87 def load(id) path = path_for(id) path = Dir.glob(File.join(@sessions_dir, "#{id}*.json")).max unless File.exist?(path) return nil unless path parse_session(path) end |
#release_locks ⇒ Object
Release every lock held by this store. The OS also releases them on process exit; this exists for tests and explicit teardown.
53 54 55 56 |
# File 'lib/yorishiro/session_store.rb', line 53 def release_locks @locks.each_value { |lock| lock.close unless lock.closed? } @locks.clear end |
#save(id:, messages:, provider:, model:) ⇒ Object
Write the session and return its id (generating one when nil). The original created_at is preserved across saves. Returns nil when the write fails — persistence must never break a REPL turn.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/yorishiro/session_store.rb', line 61 def save(id:, messages:, provider:, model:) id ||= generate_id claim(id) existing = parse_session(path_for(id)) now = Time.now.utc.iso8601 session = { version: SCHEMA_VERSION, id: id, provider: provider, model: model, created_at: existing&.dig(:created_at) || now, updated_at: now, title: title_from(), messages: } write_atomically(path_for(id), JSON.generate(session)) prune! id rescue SystemCallError nil end |