Class: Kreator::SessionManager

Inherits:
Object
  • Object
show all
Defined in:
lib/kreator/session_manager.rb

Constant Summary collapse

DEFAULT_SESSION_DIR =
File.expand_path("~/.kreator/sessions")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(session_dir: DEFAULT_SESSION_DIR) ⇒ SessionManager

Returns a new instance of SessionManager.



14
15
16
# File 'lib/kreator/session_manager.rb', line 14

def initialize(session_dir: DEFAULT_SESSION_DIR)
  @session_dir = File.expand_path(session_dir)
end

Instance Attribute Details

#session_dirObject (readonly)

Returns the value of attribute session_dir.



12
13
14
# File 'lib/kreator/session_manager.rb', line 12

def session_dir
  @session_dir
end

Instance Method Details

#branches(parent_id:) ⇒ Object



81
82
83
84
85
86
87
# File 'lib/kreator/session_manager.rb', line 81

def branches(parent_id:)
  matching_branches = Dir.glob(File.join(session_dir, "*", "*.jsonl")).filter_map do |path|
    summary = session_summary(path)
    summary if summary && summary["parent_id"] == parent_id
  end
  matching_branches.sort_by { |summary| summary.fetch("timestamp") }.reverse
end

#cleanup(cwd: nil, empty: false, failed: false) ⇒ Object

Raises:

  • (ArgumentError)


145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/kreator/session_manager.rb', line 145

def cleanup(cwd: nil, empty: false, failed: false)
  raise ArgumentError, "cleanup requires empty: true or failed: true" unless empty || failed

  deleted = []
  summaries_in(cleanup_pattern(cwd)).each do |summary|
    session = self.open(path: summary.fetch("path"))
    next unless cleanup_session?(session, empty: empty, failed: failed)

    FileUtils.rm_f(session.path)
    deleted << summary
  end
  deleted
end

#continue_recent(cwd:) ⇒ Object

Raises:

  • (ArgumentError)


49
50
51
52
53
54
# File 'lib/kreator/session_manager.rb', line 49

def continue_recent(cwd:)
  sessions = list(cwd: cwd)
  raise ArgumentError, "no sessions found for #{File.expand_path(cwd)}" if sessions.empty?

  self.open(path: sessions.first.fetch("path"))
end

#create(cwd:, parent_id: nil, parent_path: nil, forked_entry_index: nil) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/kreator/session_manager.rb', line 18

def create(cwd:, parent_id: nil, parent_path: nil, forked_entry_index: nil)
  cwd = File.expand_path(cwd)
  id = SecureRandom.uuid
  timestamp = Time.now.utc.iso8601(6)
  directory = directory_for(cwd)
  FileUtils.mkdir_p(directory)
  path = File.join(directory, "#{timestamp.tr(':', '-')}_#{id}.jsonl")
  header = {
    "type" => "session_header",
    "version" => Session::VERSION,
    "id" => id,
    "timestamp" => timestamp,
    "cwd" => cwd
  }.compact
  header["parent_id"] = parent_id if parent_id
  header["parent_path"] = parent_path if parent_path
  header["forked_entry_index"] = forked_entry_index unless forked_entry_index.nil?
  File.write(path, "#{JSON.generate(header)}\n")
  Session.new(path: path, header: header)
end

#export(path:, format: "json") ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/kreator/session_manager.rb', line 112

def export(path:, format: "json")
  session = self.open(path: path)
  case format.to_s
  when "json"
    JSON.pretty_generate(
      "header" => session.header,
      "entries" => session.entries
    )
  when "jsonl"
    File.read(session.path)
  when "markdown", "md"
    transcript(path: session.path, format: "markdown")
  when "plain", "text"
    transcript(path: session.path, format: "plain")
  else
    raise ArgumentError, "unknown export format: #{format}"
  end
end

#fork(path:, entry_index: nil) ⇒ Object

Raises:

  • (ArgumentError)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/kreator/session_manager.rb', line 63

def fork(path:, entry_index: nil)
  source = self.open(path: path)
  source_entries = source.entries
  resolved_index = entry_index.nil? ? source_entries.length - 1 : Integer(entry_index)
  raise ArgumentError, "cannot fork an empty session" if resolved_index.negative?
  raise ArgumentError, "entry index out of range: #{resolved_index}" if resolved_index >= source_entries.length

  forked = create(
    cwd: source.cwd,
    parent_id: source.id,
    parent_path: source.path,
    forked_entry_index: resolved_index
  )
  forked.append_parent_id(parent_id: source.id, parent_path: source.path, forked_entry_index: resolved_index)
  forked.append_entries(source_entries.first(resolved_index + 1))
  forked
end

#label(path:, label:) ⇒ Object



89
90
91
92
93
# File 'lib/kreator/session_manager.rb', line 89

def label(path:, label:)
  session = self.open(path: path)
  session.append_label(label)
  session
end

#list(cwd:) ⇒ Object



56
57
58
59
60
61
# File 'lib/kreator/session_manager.rb', line 56

def list(cwd:)
  directory = directory_for(File.expand_path(cwd))
  return [] unless Dir.exist?(directory)

  summaries_in(File.join(directory, "*.jsonl"))
end

#open(path:) ⇒ Object

Raises:

  • (ArgumentError)


39
40
41
42
43
44
45
46
47
# File 'lib/kreator/session_manager.rb', line 39

def open(path:)
  resolved = resolve_path(path)
  raise ArgumentError, "session not found: #{path}" unless resolved && File.file?(resolved)

  header = JSON.parse(File.open(resolved, &:readline))
  raise ArgumentError, "invalid session header in #{resolved}" unless header.fetch("type") == "session_header"

  Session.new(path: resolved, header: header)
end

#search(query:, cwd: nil) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/kreator/session_manager.rb', line 95

def search(query:, cwd: nil)
  query = query.to_s.downcase
  pattern = cwd ? File.join(directory_for(File.expand_path(cwd)), "*.jsonl") : File.join(session_dir, "*", "*.jsonl")
  summaries_in(pattern).select do |summary|
    searchable = [
      summary["id"],
      summary["cwd"],
      summary["timestamp"],
      summary["provider"],
      summary["model"],
      *Array(summary["labels"]),
      transcript(path: summary.fetch("path"), format: "plain")
    ].compact.join("\n").downcase
    searchable.include?(query)
  end
end

#transcript(path:, format: "markdown") ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/kreator/session_manager.rb', line 131

def transcript(path:, format: "markdown")
  session = self.open(path: path)
  session.messages.map do |message|
    case format.to_s
    when "markdown", "md"
      transcript_markdown(message)
    when "plain", "text"
      transcript_plain(message)
    else
      raise ArgumentError, "unknown transcript format: #{format}"
    end
  end.join("\n\n")
end