girb 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,9 @@
2
2
 
3
3
  module Girb
4
4
  class PromptBuilder
5
- SYSTEM_PROMPT = <<~PROMPT
6
- You are girb, an AI assistant embedded in a Ruby developer's IRB session.
5
+ # Common prompt shared across all IRB modes
6
+ COMMON_PROMPT = <<~PROMPT
7
+ You are girb, an AI assistant embedded in a Ruby developer's session.
7
8
 
8
9
  ## CRITICAL: Prompt Information Takes Highest Priority
9
10
  Information in this system prompt and "User-Defined Instructions" section
@@ -14,25 +15,6 @@ module Girb
14
15
  ## Language
15
16
  Respond in the same language the user is using. Detect the user's language from their question and match it.
16
17
 
17
- ## Important: Understand the IRB Session Context
18
- The user is interactively executing code in IRB and asking questions within that flow.
19
- "Session History" contains the code the user has executed and past AI conversations in chronological order.
20
- Always interpret questions in the context of this history.
21
-
22
- For example, if the history shows:
23
- 1: a = 1
24
- 2: b = 2
25
- 3: [USER] What will z be if I continue with c = 3 and beyond?
26
- The user is asking about the value of z when continuing the pattern a=1, b=2, c=3... (answer: z=26).
27
-
28
- ## Your Role
29
- - Strive to understand the user's true intent and background
30
- - Don't just answer the question; understand what they're trying to achieve and what challenges they face
31
- - Analyze session history to understand what the user is trying to do
32
- - Utilize the current execution context (variables, object state, exceptions)
33
- - Provide specific, practical answers to questions
34
- - Use tools to execute and verify code as needed
35
-
36
18
  ## Clarifying Questions (Use Sparingly)
37
19
  Only ask the user for clarification AFTER you have already investigated using tools.
38
20
  - First: read the source file, check variables, run code
@@ -41,7 +23,7 @@ module Girb
41
23
  ## Response Guidelines
42
24
  - Keep responses concise and practical
43
25
  - Read patterns and intentions; handle hypothetical questions
44
- - Code examples should use variables and objects from the current IRB context and be directly executable by pasting into IRB
26
+ - Code examples should use variables and objects from the current context and be directly executable
45
27
 
46
28
  ## Debugging Support on Errors
47
29
  When users encounter errors, actively support debugging.
@@ -51,9 +33,6 @@ module Girb
51
33
 
52
34
  ## CRITICAL: Proactive Investigation — Act First, Don't Ask
53
35
  You MUST investigate before asking the user for information.
54
- - The "Source Location" in the context tells you which file the user is working in.
55
- If a Source Location is present, ALWAYS use `read_file` to read that file FIRST
56
- before responding. The user's question almost certainly refers to this file's code.
57
36
  - Use `evaluate_code` to run and verify code rather than guessing or reasoning about results.
58
37
  - NEVER ask the user for code, file names, or variable definitions that you can look up
59
38
  yourself with `read_file`, `evaluate_code`, `inspect_object`, or `find_file`.
@@ -61,7 +40,92 @@ module Girb
61
40
  ## Available Tools
62
41
  Use tools to inspect variables in detail, retrieve source code, and execute code.
63
42
  Actively use the evaluate_code tool especially for verifying hypotheses and calculations.
43
+ PROMPT
64
44
 
45
+ # Prompt specific to breakpoint mode (binding.girb / binding.irb)
46
+ BREAKPOINT_PROMPT = <<~PROMPT
47
+ ## Mode: Breakpoint (binding.girb)
48
+ You are at a BREAKPOINT in the user's Ruby script. Execution is paused at this exact line.
49
+
50
+ ### CRITICAL: Understanding Breakpoint Context
51
+ - Code BEFORE this line has already executed (variables are set)
52
+ - Code AFTER this line has NOT executed yet
53
+ - The user's questions about "the code", "this loop", "this method" refer to the code in the SOURCE FILE
54
+ - ALWAYS read the source file FIRST using `read_file` to understand what code exists
55
+
56
+ ### Your Primary Task
57
+ - Help the user understand, debug, or simulate the code that is ABOUT TO execute
58
+ - When asked to "run the code" or "execute this loop", execute the ACTUAL code from the file
59
+ - When asked to track variables, run the actual code and report real results
60
+ - NEVER invent or substitute code - always use what's in the file
61
+
62
+ ### Example: User says "run this loop and track x"
63
+ 1. Read the source file to see the actual loop code
64
+ 2. Execute that exact code using evaluate_code
65
+ 3. Report the actual results
66
+
67
+ ### WRONG approach:
68
+ - Guessing what the code might do
69
+ - Writing your own version of the code
70
+ - Asking the user what the code is when you can read the file
71
+
72
+ ### Debug Commands (use run_debug_command tool)
73
+ IRB integrates with debug gem. Use `run_debug_command` tool to execute:
74
+ - `next` / `n`: Step over to next line
75
+ - `step` / `s`: Step into method calls
76
+ - `continue` / `c`: Continue execution
77
+ - `finish`: Run until current method returns
78
+ - `break <file>:<line>`: Set a breakpoint
79
+ - `backtrace` / `bt`: Show call stack
80
+ - `info`: Show local variables
81
+
82
+ When the user asks for step-by-step execution, use `run_debug_command` with `auto_continue: true`
83
+ to step through the code and be re-invoked to see the results.
84
+ PROMPT
85
+
86
+ # Prompt specific to interactive IRB mode (girb command)
87
+ INTERACTIVE_IRB_PROMPT = <<~PROMPT
88
+ ## Mode: Interactive IRB Session
89
+ The user is in an interactive IRB session, typing code and questions directly.
90
+
91
+ ### Understanding the Session
92
+ - "Session History" contains the code the user has executed and past AI conversations
93
+ - Always interpret questions in the context of this history
94
+ - Variables and objects from past commands are available in the current context
95
+
96
+ ### Example Context
97
+ If the history shows:
98
+ 1: a = 1
99
+ 2: b = 2
100
+ 3: [USER] What will z be if I continue with c = 3 and beyond?
101
+ The user is asking about the value of z when continuing the pattern a=1, b=2, c=3... (answer: z=26).
102
+
103
+ ### Your Role
104
+ - Help with code exploration and experimentation
105
+ - Answer questions about Ruby, gems, and the current session state
106
+ - Assist with building and testing code interactively
107
+ PROMPT
108
+
109
+ # Prompt specific to Rails console mode
110
+ RAILS_CONSOLE_PROMPT = <<~PROMPT
111
+ ## Mode: Rails Console
112
+ The user is in a Rails console with full access to the application's models and services.
113
+
114
+ ### Rails-Specific Capabilities
115
+ - You can query ActiveRecord models directly
116
+ - Use `model_info` tool to get schema information
117
+ - Use `query_model` tool to execute database queries safely
118
+ - Access to Rails helpers, routes, and application configuration
119
+
120
+ ### Best Practices
121
+ - Be careful with destructive operations (update!, destroy, etc.) - warn the user
122
+ - Use transactions when demonstrating data modifications
123
+ - Suggest using `find_by` or `where` instead of `find` to avoid exceptions
124
+ - Remember that console changes affect the real database (unless in sandbox mode)
125
+ PROMPT
126
+
127
+ # Autonomous investigation prompt (shared)
128
+ CONTINUE_ANALYSIS_PROMPT = <<~PROMPT
65
129
  ## Autonomous Investigation with continue_analysis
66
130
  When you need to execute code that changes state AND then see the full updated context
67
131
  (all local variables, instance variables, last value, etc.), use the `continue_analysis` tool.
@@ -78,12 +142,6 @@ module Girb
78
142
  - When you can get the information you need with evaluate_code or inspect_object directly
79
143
  - When you've found your answer and want to report to the user
80
144
  - For simple one-shot investigations
81
-
82
- ### Example workflow:
83
- 1. evaluate_code("user.profile.update!(name: 'test')") → check if it succeeds
84
- 2. continue_analysis(reason: "Check all updated attributes after save")
85
- 3. [re-invoked with fresh context showing all updated locals/instance vars]
86
- 4. Analyze the changes and report to the user
87
145
  PROMPT
88
146
 
89
147
  def initialize(question, context)
@@ -105,18 +163,20 @@ module Girb
105
163
 
106
164
  # System prompt (shared across conversation)
107
165
  def system_prompt
166
+ prompt = COMMON_PROMPT + "\n" + mode_specific_prompt + "\n" + CONTINUE_ANALYSIS_PROMPT
167
+
108
168
  custom = Girb.configuration&.custom_prompt
109
169
  if custom && !custom.empty?
110
- "#{SYSTEM_PROMPT}\n\n## User-Defined Instructions\n#{custom}"
170
+ prompt + "\n\n## User-Defined Instructions\n#{custom}"
111
171
  else
112
- SYSTEM_PROMPT
172
+ prompt
113
173
  end
114
174
  end
115
175
 
116
176
  # User message (context + question)
117
177
  def user_message
118
178
  <<~MSG
119
- ## Current IRB Context
179
+ ## Current Context
120
180
  #{build_context_section}
121
181
 
122
182
  ## Question
@@ -126,32 +186,49 @@ module Girb
126
186
 
127
187
  private
128
188
 
129
- def build_context_section
130
- <<~CONTEXT
131
- ### Source Location
132
- #{format_source_location}
189
+ def mode_specific_prompt
190
+ case detect_mode
191
+ when :breakpoint
192
+ BREAKPOINT_PROMPT
193
+ when :rails
194
+ RAILS_CONSOLE_PROMPT
195
+ else
196
+ INTERACTIVE_IRB_PROMPT
197
+ end
198
+ end
133
199
 
134
- ### Session History (Previous IRB Inputs)
135
- Below is the code the user has executed so far. The question is asked within this flow.
136
- #{format_session_history}
200
+ def detect_mode
201
+ loc = @context[:source_location]
137
202
 
138
- ### Current Local Variables
139
- #{format_locals}
203
+ # Check for breakpoint mode: source is a real file (not irb/eval)
204
+ if loc && loc[:file]
205
+ file = loc[:file].to_s
206
+ unless file.start_with?("(") || file.include?("irb") || file.include?("eval")
207
+ return :breakpoint
208
+ end
209
+ end
140
210
 
141
- ### Last Evaluation Result
142
- #{@context[:last_value] || "(none)"}
211
+ # Check for Rails mode
212
+ return :rails if defined?(Rails)
143
213
 
144
- ### Last Exception
145
- #{format_exception}
214
+ # Default: interactive IRB
215
+ :interactive
216
+ end
146
217
 
147
- ### Methods Defined in IRB
148
- #{format_method_definitions}
149
- CONTEXT
218
+ def build_context_section
219
+ sections = []
220
+ sections << "### Source Location\n#{format_source_location}"
221
+ sections << "### Session History (Previous Inputs)\n#{format_session_history}"
222
+ sections << "### Current Local Variables\n#{format_locals}"
223
+ sections << "### Last Evaluation Result\n#{@context[:last_value] || "(none)"}"
224
+ sections << "### Last Exception\n#{format_exception}"
225
+ sections << "### Methods Defined in Session\n#{format_method_definitions}"
226
+ sections.join("\n\n")
150
227
  end
151
228
 
152
229
  def format_source_location
153
230
  loc = @context[:source_location]
154
- return "(unknown)" unless loc
231
+ return "(interactive session)" unless loc
155
232
 
156
233
  "File: #{loc[:file]}\nLine: #{loc[:line]}"
157
234
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Girb
7
+ # デバッグセッションの会話履歴を永続化するクラス
8
+ # 明示的にGirb.debug_sessionを設定した場合のみ保存される
9
+ class SessionPersistence
10
+ SESSIONS_DIR = ".girb/sessions"
11
+
12
+ class << self
13
+ attr_accessor :current_session_id
14
+
15
+ # セッションが有効か(明示的にIDが指定されているか)
16
+ def enabled?
17
+ !!Girb.debug_session
18
+ end
19
+
20
+ # セッションディレクトリを取得(プロジェクトルートから)
21
+ def sessions_dir
22
+ # カレントディレクトリから.girbディレクトリを探す
23
+ dir = Dir.pwd
24
+ while dir != "/"
25
+ girb_dir = File.join(dir, ".girb")
26
+ if Dir.exist?(girb_dir)
27
+ return File.join(girb_dir, "sessions")
28
+ end
29
+ dir = File.dirname(dir)
30
+ end
31
+
32
+ # 見つからなければカレントディレクトリに作成
33
+ File.join(Dir.pwd, SESSIONS_DIR)
34
+ end
35
+
36
+ # セッションファイルのパスを取得
37
+ def session_file_path(session_id)
38
+ File.join(sessions_dir, "#{session_id}.json")
39
+ end
40
+
41
+ # セッションを開始(既存があれば読み込み)
42
+ # Girb.debug_sessionが設定されている場合のみ有効
43
+ def start_session
44
+ return unless enabled?
45
+
46
+ @current_session_id = Girb.debug_session
47
+
48
+ file_path = session_file_path(@current_session_id)
49
+ if File.exist?(file_path)
50
+ load_session(file_path)
51
+ puts "[girb] Resumed session: #{@current_session_id}"
52
+ else
53
+ ConversationHistory.reset!
54
+ puts "[girb] New session: #{@current_session_id}"
55
+ end
56
+
57
+ @current_session_id
58
+ end
59
+
60
+ # セッションを保存
61
+ def save_session
62
+ return unless enabled? && @current_session_id
63
+
64
+ file_path = session_file_path(@current_session_id)
65
+ FileUtils.mkdir_p(File.dirname(file_path))
66
+
67
+ data = {
68
+ session_id: @current_session_id,
69
+ saved_at: Time.now.to_s,
70
+ messages: serialize_messages
71
+ }
72
+
73
+ File.write(file_path, JSON.pretty_generate(data))
74
+ rescue => e
75
+ puts "[girb] Failed to save session: #{e.message}"
76
+ end
77
+
78
+ # セッションを読み込み
79
+ def load_session(file_path)
80
+ data = JSON.parse(File.read(file_path), symbolize_names: true)
81
+
82
+ ConversationHistory.reset!
83
+ deserialize_messages(data[:messages])
84
+
85
+ message_count = data[:messages]&.size || 0
86
+ puts "[girb] Loaded #{message_count} messages from previous session"
87
+ rescue => e
88
+ puts "[girb] Failed to load session: #{e.message}"
89
+ ConversationHistory.reset!
90
+ end
91
+
92
+ # 現在のセッションをクリア(ファイルも削除)
93
+ def clear_session
94
+ if @current_session_id
95
+ delete_session(@current_session_id)
96
+ @current_session_id = nil
97
+ end
98
+ ConversationHistory.reset!
99
+ puts "[girb] Session cleared"
100
+ end
101
+
102
+ # セッション一覧を取得
103
+ def list_sessions
104
+ dir = sessions_dir
105
+ return [] unless Dir.exist?(dir)
106
+
107
+ Dir.glob(File.join(dir, "*.json")).map do |file|
108
+ data = JSON.parse(File.read(file), symbolize_names: true)
109
+ {
110
+ id: data[:session_id],
111
+ saved_at: data[:saved_at],
112
+ message_count: data[:messages]&.size || 0
113
+ }
114
+ rescue
115
+ nil
116
+ end.compact
117
+ end
118
+
119
+ # セッションを削除
120
+ def delete_session(session_id)
121
+ file_path = session_file_path(session_id)
122
+ if File.exist?(file_path)
123
+ File.delete(file_path)
124
+ puts "[girb] Session deleted: #{session_id}"
125
+ true
126
+ else
127
+ puts "[girb] Session not found: #{session_id}"
128
+ false
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def serialize_messages
135
+ ConversationHistory.messages.map do |msg|
136
+ {
137
+ role: msg.role,
138
+ content: msg.content,
139
+ tool_calls: msg.tool_calls
140
+ }
141
+ end
142
+ end
143
+
144
+ def deserialize_messages(messages)
145
+ return unless messages
146
+
147
+ messages.each do |msg|
148
+ case msg[:role]
149
+ when "user"
150
+ ConversationHistory.add_user_message(msg[:content])
151
+ when "model"
152
+ # tool_callsがある場合は先にpending_tool_callsに追加
153
+ if msg[:tool_calls]&.any?
154
+ msg[:tool_calls].each do |tc|
155
+ ConversationHistory.add_tool_call(
156
+ tc[:name],
157
+ tc[:args],
158
+ tc[:result],
159
+ id: tc[:id],
160
+ metadata: tc[:metadata]
161
+ )
162
+ end
163
+ end
164
+ ConversationHistory.add_assistant_message(msg[:content])
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "base"
4
4
  require_relative "../debug_session_history"
5
+ require_relative "../conversation_history"
6
+ require_relative "../session_persistence"
5
7
 
6
8
  module Girb
7
9
  module Tools
@@ -12,8 +14,8 @@ module Girb
12
14
  end
13
15
 
14
16
  def description
15
- "Get debug session history including debugger commands and AI conversations. " \
16
- "Note: Recent history is also shown in the 'Session History' section of the context."
17
+ "Get session history including AI conversations from previous sessions (if persisted) and current session commands. " \
18
+ "Use this to recall past conversations and debug commands."
17
19
  end
18
20
 
19
21
  def parameters
@@ -44,7 +46,7 @@ module Girb
44
46
  when "full_history"
45
47
  get_full_history(count)
46
48
  when "list_ai_conversations"
47
- list_ai_conversations
49
+ list_ai_conversations(count)
48
50
  else
49
51
  { error: "Unknown action: #{action}. Use 'full_history' or 'list_ai_conversations'." }
50
52
  end
@@ -55,34 +57,75 @@ module Girb
55
57
  private
56
58
 
57
59
  def get_full_history(count)
60
+ result = {}
61
+
62
+ # 永続化されたセッションからの会話履歴
63
+ persisted = get_persisted_conversations
64
+ if persisted.any?
65
+ result[:persisted_conversations] = persisted
66
+ end
67
+
68
+ # 現在のセッションのデバッグ履歴
58
69
  history = DebugSessionHistory.format_history(count)
59
70
  if history && !history.empty?
60
- { history: history }
71
+ result[:current_session_history] = history
72
+ end
73
+
74
+ if result.empty?
75
+ { message: "No history available" }
61
76
  else
62
- { message: "No history in this debug session" }
77
+ result
63
78
  end
64
79
  end
65
80
 
66
- def list_ai_conversations
67
- conversations = DebugSessionHistory.ai_conversations
68
- if conversations.any?
81
+ def list_ai_conversations(count = 20)
82
+ all_conversations = []
83
+
84
+ # 永続化されたセッションからの会話
85
+ persisted = get_persisted_conversations
86
+ all_conversations.concat(persisted)
87
+
88
+ # 現在のセッションの会話
89
+ current = DebugSessionHistory.ai_conversations.map do |c|
69
90
  {
70
- count: conversations.size,
71
- conversations: conversations.map do |c|
72
- response_preview = if c.response
73
- c.response.length > 200 ? "#{c.response[0, 200]}..." : c.response
74
- else
75
- "(pending)"
76
- end
91
+ question: c.content,
92
+ response: c.response || "(pending)",
93
+ source: "current_session"
94
+ }
95
+ end
96
+ all_conversations.concat(current)
97
+
98
+ if all_conversations.any?
99
+ # 最新のcount件に制限
100
+ limited = all_conversations.last(count)
101
+ {
102
+ total_count: all_conversations.size,
103
+ showing: limited.size,
104
+ conversations: limited.map do |c|
105
+ response = c[:response] || ""
106
+ response_preview = response.length > 200 ? "#{response[0, 200]}..." : response
77
107
  {
78
- question: c.content,
79
- response_preview: response_preview
108
+ question: c[:question],
109
+ response_preview: response_preview,
110
+ source: c[:source] || "persisted"
80
111
  }
81
112
  end
82
113
  }
83
114
  else
84
- { message: "No AI conversations in this debug session" }
115
+ { message: "No AI conversations in session history" }
116
+ end
117
+ end
118
+
119
+ def get_persisted_conversations
120
+ conversations = []
121
+ ConversationHistory.messages.each do |msg|
122
+ if msg.role == "user"
123
+ conversations << { question: msg.content, source: "persisted" }
124
+ elsif msg.role == "model" && conversations.last && !conversations.last[:response]
125
+ conversations.last[:response] = msg.content
126
+ end
85
127
  end
128
+ conversations
86
129
  end
87
130
  end
88
131
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Girb
6
+ module Tools
7
+ class RunIrbDebugCommand < Base
8
+ class << self
9
+ def name
10
+ "run_debug_command"
11
+ end
12
+
13
+ def description
14
+ "Execute a debug command in IRB. IRB integrates with debug gem, allowing step-by-step debugging. " \
15
+ "Use this when the user asks to step through code, set breakpoints, or navigate execution."
16
+ end
17
+
18
+ def parameters
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ command: {
23
+ type: "string",
24
+ description: "The debug command to execute. Examples: 'next', 'step', 'continue', 'finish', " \
25
+ "'break sample.rb:14', 'break sample.rb:14 if: x == 1', 'info', 'backtrace'"
26
+ },
27
+ auto_continue: {
28
+ type: "boolean",
29
+ description: "Set to true to be re-invoked after the command executes to see the new state."
30
+ }
31
+ },
32
+ required: ["command"]
33
+ }
34
+ end
35
+
36
+ def available?
37
+ # Available in IRB mode (not in active debug session)
38
+ # DEBUGGER__::SESSION indicates an active debug session
39
+ defined?(IRB) && !defined?(DEBUGGER__::SESSION)
40
+ end
41
+ end
42
+
43
+ def execute(binding, command:, auto_continue: false)
44
+ Girb::IrbIntegration.add_pending_irb_command(command)
45
+ Girb::AutoContinue.request! if auto_continue
46
+
47
+ {
48
+ success: true,
49
+ command: command,
50
+ auto_continue: auto_continue,
51
+ message: auto_continue ?
52
+ "Command '#{command}' will be executed. You will be re-invoked with updated context." :
53
+ "Command '#{command}' will be executed after this response."
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "base"
4
4
  require_relative "../session_history"
5
+ require_relative "../conversation_history"
6
+ require_relative "../session_persistence"
5
7
 
6
8
  module Girb
7
9
  module Tools
@@ -12,7 +14,8 @@ module Girb
12
14
  end
13
15
 
14
16
  def description
15
- "Get IRB session history. Can retrieve specific lines, line ranges, method definitions, AI conversation details, or full history."
17
+ "Get IRB session history including AI conversations from previous sessions (if persisted). " \
18
+ "Can retrieve specific lines, line ranges, method definitions, AI conversation details, or full history."
16
19
  end
17
20
 
18
21
  def available?
@@ -151,21 +154,51 @@ module Girb
151
154
  end
152
155
 
153
156
  def list_ai_conversations
154
- conversations = SessionHistory.ai_conversations
155
- if conversations.any?
157
+ all_conversations = []
158
+
159
+ # 永続化されたセッションからの会話
160
+ persisted = get_persisted_conversations
161
+ all_conversations.concat(persisted)
162
+
163
+ # 現在のセッションの会話
164
+ current = SessionHistory.ai_conversations.map do |c|
156
165
  {
157
- count: conversations.size,
158
- conversations: conversations.map do |c|
166
+ line: c[:line_no],
167
+ question: c[:question],
168
+ response: c[:response] || "",
169
+ source: "current_session"
170
+ }
171
+ end
172
+ all_conversations.concat(current)
173
+
174
+ if all_conversations.any?
175
+ {
176
+ count: all_conversations.size,
177
+ conversations: all_conversations.map do |c|
178
+ response = c[:response] || ""
159
179
  {
160
- line: c[:line_no],
180
+ line: c[:line],
161
181
  question: c[:question],
162
- response_preview: c[:response][0, 200] + (c[:response].length > 200 ? "..." : "")
182
+ response_preview: response.length > 200 ? "#{response[0, 200]}..." : response,
183
+ source: c[:source] || "persisted"
163
184
  }
164
185
  end
165
186
  }
166
187
  else
167
- { message: "No AI conversations in this session" }
188
+ { message: "No AI conversations in session history" }
189
+ end
190
+ end
191
+
192
+ def get_persisted_conversations
193
+ conversations = []
194
+ ConversationHistory.messages.each do |msg|
195
+ if msg.role == "user"
196
+ conversations << { question: msg.content, source: "persisted" }
197
+ elsif msg.role == "model" && conversations.last && !conversations.last[:response]
198
+ conversations.last[:response] = msg.content
199
+ end
168
200
  end
201
+ conversations
169
202
  end
170
203
 
171
204
  def get_ai_detail(line)