Class: Ask::CodingHarness::Store
- Inherits:
-
Object
- Object
- Ask::CodingHarness::Store
- Defined in:
- lib/ask/coding_harness/store.rb
Overview
Persistence for conversations, backed by ask-state-providers (SQLite).
A conversation is a plain Hash with string keys:
id, title, directory, archived, messages, created_at, updated_at
Messages are { "role" => "user"|"assistant", "content" => String, "created_at" => String } hashes. The store is the single owner of conversation records; the server and runner only read/write through it.
Constant Summary collapse
- CONVERSATIONS_KEY =
"__conversations__"- WORKSPACES_KEY =
"__workspaces__"- MAX_CONVERSATIONS =
500- MAX_WORKSPACES =
100
Instance Method Summary collapse
-
#build(directory: nil, title: "New conversation", agent: nil) ⇒ Object
A fresh conversation record.
- #close ⇒ Object
- #delete(id) ⇒ Object
-
#initialize(db_path:) ⇒ Store
constructor
A new instance of Store.
-
#list(archived: false) ⇒ Object
Conversation summaries (no messages), sorted by most recent first.
-
#list_for_directory(directory, archived: false) ⇒ Object
Summaries for one workspace directory, most recent first.
-
#load(id) ⇒ Object
Load a conversation by id, or nil when missing.
-
#projects(archived: false) ⇒ Object
Workspace directories that have conversations, with counts.
-
#register_workspace(path) ⇒ Object
Register a workspace path (idempotent).
-
#save(conv) ⇒ Object
Persist a conversation, bumping updated_at and guessing a title when the conversation is still untitled and has messages.
-
#workspaces ⇒ Object
Explicitly registered workspace paths.
Constructor Details
#initialize(db_path:) ⇒ Store
Returns a new instance of Store.
23 24 25 26 27 |
# File 'lib/ask/coding_harness/store.rb', line 23 def initialize(db_path:) @db_path = db_path dir = File.dirname(db_path) FileUtils.mkdir_p(dir) unless File.directory?(dir) end |
Instance Method Details
#build(directory: nil, title: "New conversation", agent: nil) ⇒ Object
A fresh conversation record. Not persisted until #save.
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/ask/coding_harness/store.rb', line 32 def build(directory: nil, title: "New conversation", agent: nil) now = Time.now.iso8601 { "id" => SecureRandom.uuid, "title" => title, "directory" => directory, "agent" => agent, "archived" => false, "messages" => [], "created_at" => now, "updated_at" => now } end |
#close ⇒ Object
110 111 112 113 |
# File 'lib/ask/coding_harness/store.rb', line 110 def close @db&.close @db = nil end |
#delete(id) ⇒ Object
64 65 66 67 |
# File 'lib/ask/coding_harness/store.rb', line 64 def delete(id) db.delete("conv:#{id}") db.list_remove(CONVERSATIONS_KEY, id) end |
#list(archived: false) ⇒ Object
Conversation summaries (no messages), sorted by most recent first.
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/ask/coding_harness/store.rb', line 70 def list(archived: false) ids = db.list_range(CONVERSATIONS_KEY, 0, -1) summaries = ids.filter_map do |id| data = db.get("conv:#{id}") next unless data next if data["archived"] == true && !archived summary(data) end summaries.sort_by { |c| c["updated_at"].to_s }.reverse end |
#list_for_directory(directory, archived: false) ⇒ Object
Summaries for one workspace directory, most recent first.
82 83 84 |
# File 'lib/ask/coding_harness/store.rb', line 82 def list_for_directory(directory, archived: false) list(archived: archived).select { |c| c["directory"] == directory } end |
#load(id) ⇒ Object
Load a conversation by id, or nil when missing.
59 60 61 62 |
# File 'lib/ask/coding_harness/store.rb', line 59 def load(id) data = db.get("conv:#{id}") data&.transform_keys(&:to_s) end |
#projects(archived: false) ⇒ Object
Workspace directories that have conversations, with counts.
87 88 89 90 91 92 |
# File 'lib/ask/coding_harness/store.rb', line 87 def projects(archived: false) list(archived: archived).each_with_object(Hash.new(0)) do |c, counts| dir = c["directory"] counts[dir] += 1 if dir end.map { |dir, count| { "directory" => dir, "name" => File.basename(dir), "conversation_count" => count } } end |
#register_workspace(path) ⇒ Object
Register a workspace path (idempotent). Workspaces the server has explicitly opened, even before any conversation exists.
98 99 100 101 102 103 |
# File 'lib/ask/coding_harness/store.rb', line 98 def register_workspace(path) dir = File.(path) existing = db.list_range(WORKSPACES_KEY, 0, -1) db.list_append(WORKSPACES_KEY, dir, max_length: MAX_WORKSPACES) unless existing.include?(dir) dir end |
#save(conv) ⇒ Object
Persist a conversation, bumping updated_at and guessing a title when the conversation is still untitled and has messages.
48 49 50 51 52 53 54 55 56 |
# File 'lib/ask/coding_harness/store.rb', line 48 def save(conv) conv["updated_at"] = Time.now.iso8601 if conv["title"] == "New conversation" && conv["messages"].length >= 1 conv["title"] = guess_title(conv["messages"]) end db.set("conv:#{conv["id"]}", conv) index_conversation(conv["id"]) conv end |
#workspaces ⇒ Object
Explicitly registered workspace paths.
106 107 108 |
# File 'lib/ask/coding_harness/store.rb', line 106 def workspaces db.list_range(WORKSPACES_KEY, 0, -1) end |