Class: SwarmSDK::Tools::Stores::ScratchpadStorage
- Defined in:
- lib/swarm_sdk/tools/stores/scratchpad_storage.rb
Overview
ScratchpadStorage provides volatile, shared storage
Features:
- Shared: All agents share the same scratchpad
- Volatile: NEVER persists - all data lost when process ends
- Path-based: Hierarchical organization using file-path-like addresses
- Metadata-rich: Stores content + title + timestamp + size
- Thread-safe: Mutex-protected operations
Use for temporary, cross-agent communication within a single session.
Instance Attribute Summary collapse
-
#total_size ⇒ Integer
readonly
Get current total size.
Instance Method Summary collapse
-
#all_entries ⇒ Hash
Get all entries with content for snapshot.
-
#clear ⇒ void
Clear all entries.
-
#delete(file_path:) ⇒ void
Delete a specific entry.
-
#glob(pattern:) ⇒ Array<Hash>
Search entries by glob pattern.
-
#grep(pattern:, case_insensitive: false, output_mode: "files_with_matches") ⇒ Array<Hash>, String
Search entry content by pattern.
-
#initialize(total_size_limit: nil) ⇒ ScratchpadStorage
constructor
Initialize scratchpad storage (always volatile).
-
#list(prefix: nil) ⇒ Array<Hash>
List scratchpad entries, optionally filtered by prefix.
-
#read(file_path:) ⇒ String
Read content from scratchpad.
-
#restore_entries(entries_data) ⇒ void
Restore entries from snapshot.
-
#size ⇒ Integer
Get number of entries.
-
#write(file_path:, content:, title:) ⇒ Entry
Write content to scratchpad.
Constructor Details
#initialize(total_size_limit: nil) ⇒ ScratchpadStorage
Initialize scratchpad storage (always volatile)
20 21 22 23 24 25 26 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 20 def initialize(total_size_limit: nil) super() # Initialize parent Storage class @entries = {} @total_size = 0 @total_size_limit = total_size_limit || SwarmSDK.config.scratchpad_total_size_limit @mutex = Mutex.new end |
Instance Attribute Details
#total_size ⇒ Integer (readonly)
Get current total size
217 218 219 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 217 def total_size @total_size end |
Instance Method Details
#all_entries ⇒ Hash
Get all entries with content for snapshot
Thread-safe method that returns a copy of all entries. Used by snapshot/restore functionality.
232 233 234 235 236 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 232 def all_entries @mutex.synchronize do @entries.dup end end |
#clear ⇒ void
This method returns an undefined value.
Clear all entries
207 208 209 210 211 212 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 207 def clear @mutex.synchronize do @entries.clear @total_size = 0 end end |
#delete(file_path:) ⇒ void
This method returns an undefined value.
Delete a specific entry
98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 98 def delete(file_path:) @mutex.synchronize do raise ArgumentError, "file_path is required" if file_path.nil? || file_path.to_s.strip.empty? entry = @entries[file_path] raise ArgumentError, "scratchpad://#{file_path} not found" unless entry # Update total size @total_size -= entry.size # Remove entry @entries.delete(file_path) end end |
#glob(pattern:) ⇒ Array<Hash>
Search entries by glob pattern
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 140 def glob(pattern:) raise ArgumentError, "pattern is required" if pattern.nil? || pattern.to_s.strip.empty? # Convert glob pattern to regex regex = glob_to_regex(pattern) # Filter entries by pattern matching_entries = @entries.select { |path, _| regex.match?(path) } # Return metadata sorted by most recent first matching_entries.map do |path, entry| { path: path, title: entry.title, size: entry.size, updated_at: entry.updated_at, } end.sort_by { |e| -e[:updated_at].to_f } end |
#grep(pattern:, case_insensitive: false, output_mode: "files_with_matches") ⇒ Array<Hash>, String
Search entry content by pattern
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 166 def grep(pattern:, case_insensitive: false, output_mode: "files_with_matches") raise ArgumentError, "pattern is required" if pattern.nil? || pattern.to_s.strip.empty? # Create regex from pattern flags = case_insensitive ? Regexp::IGNORECASE : 0 regex = Regexp.new(pattern, flags) case output_mode when "files_with_matches" # Return just the paths that match matching_paths = @entries.select { |_path, entry| regex.match?(entry.content) } .map { |path, _| path } .sort matching_paths when "content" # Return paths with matching lines, sorted by most recent first results = [] @entries.each do |path, entry| matching_lines = [] entry.content.each_line.with_index(1) do |line, line_num| matching_lines << { line_number: line_num, content: line.chomp } if regex.match?(line) end results << { path: path, matches: matching_lines, updated_at: entry.updated_at } unless matching_lines.empty? end results.sort_by { |r| -r[:updated_at].to_f }.map { |r| r.except(:updated_at) } when "count" # Return paths with match counts, sorted by most recent first results = [] @entries.each do |path, entry| count = entry.content.scan(regex).size results << { path: path, count: count, updated_at: entry.updated_at } if count > 0 end results.sort_by { |r| -r[:updated_at].to_f }.map { |r| r.except(:updated_at) } else raise ArgumentError, "Invalid output_mode: #{output_mode}. Must be 'files_with_matches', 'content', or 'count'" end end |
#list(prefix: nil) ⇒ Array<Hash>
List scratchpad entries, optionally filtered by prefix
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 117 def list(prefix: nil) entries = @entries # Filter by prefix if provided if prefix && !prefix.empty? entries = entries.select { |path, _| path.start_with?(prefix) } end # Return metadata sorted by path entries.map do |path, entry| { path: path, title: entry.title, size: entry.size, updated_at: entry.updated_at, } end.sort_by { |e| e[:path] } end |
#read(file_path:) ⇒ String
Read content from scratchpad
84 85 86 87 88 89 90 91 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 84 def read(file_path:) raise ArgumentError, "file_path is required" if file_path.nil? || file_path.to_s.strip.empty? entry = @entries[file_path] raise ArgumentError, "scratchpad://#{file_path} not found" unless entry entry.content end |
#restore_entries(entries_data) ⇒ void
This method returns an undefined value.
Restore entries from snapshot
Restores entries directly without using write() to preserve timestamps. This ensures entry ordering and metadata accuracy after restore.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 245 def restore_entries(entries_data) @mutex.synchronize do entries_data.each do |path, data| # Handle both symbol and string keys from JSON content = data[:content] || data["content"] title = data[:title] || data["title"] updated_at_str = data[:updated_at] || data["updated_at"] # Parse timestamp from ISO8601 string updated_at = Time.parse(updated_at_str) # Create entry with preserved timestamp entry = Entry.new( content: content, title: title, updated_at: updated_at, size: content.bytesize, ) # Update storage @entries[path] = entry @total_size += entry.size end end end |
#size ⇒ Integer
Get number of entries
222 223 224 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 222 def size @entries.size end |
#write(file_path:, content:, title:) ⇒ Entry
Write content to scratchpad
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/swarm_sdk/tools/stores/scratchpad_storage.rb', line 35 def write(file_path:, content:, title:) @mutex.synchronize do raise ArgumentError, "file_path is required" if file_path.nil? || file_path.to_s.strip.empty? raise ArgumentError, "content is required" if content.nil? raise ArgumentError, "title is required" if title.nil? || title.to_s.strip.empty? content_size = content.bytesize # Check entry size limit entry_size_limit = SwarmSDK.config.scratchpad_entry_size_limit if content_size > entry_size_limit raise ArgumentError, "Content exceeds maximum size (#{format_bytes(entry_size_limit)}). " \ "Current: #{format_bytes(content_size)}" end # Calculate new total size existing_entry = @entries[file_path] existing_size = existing_entry ? existing_entry.size : 0 new_total_size = @total_size - existing_size + content_size # Check total size limit if new_total_size > @total_size_limit raise ArgumentError, "Scratchpad full (#{format_bytes(@total_size_limit)} limit). " \ "Current: #{format_bytes(@total_size)}, " \ "Would be: #{format_bytes(new_total_size)}. " \ "Clear old entries or use smaller content." end # Create entry entry = Entry.new( content: content, title: title, updated_at: Time.now, size: content_size, ) # Update storage @entries[file_path] = entry @total_size = new_total_size entry end end |