girb 0.1.2 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +80 -0
- data/README.md +283 -129
- data/README_ja.md +280 -126
- data/lib/girb/ai_client.rb +209 -29
- data/lib/girb/auto_continue.rb +33 -0
- data/lib/girb/conversation_history.rb +8 -7
- data/lib/girb/debug_context_builder.rb +113 -0
- data/lib/girb/debug_integration.rb +426 -0
- data/lib/girb/debug_prompt_builder.rb +241 -0
- data/lib/girb/debug_session_history.rb +121 -0
- data/lib/girb/exception_capture.rb +4 -0
- data/lib/girb/girbrc_loader.rb +15 -9
- data/lib/girb/irb_integration.rb +233 -1
- data/lib/girb/prompt_builder.rb +156 -46
- data/lib/girb/session_persistence.rb +170 -0
- data/lib/girb/tools/continue_analysis.rb +45 -0
- data/lib/girb/tools/debug_session_history_tool.rb +132 -0
- data/lib/girb/tools/evaluate_code.rb +24 -3
- data/lib/girb/tools/run_debug_command.rb +49 -0
- data/lib/girb/tools/run_irb_debug_command.rb +58 -0
- data/lib/girb/tools/session_history_tool.rb +46 -8
- data/lib/girb/tools.rb +23 -2
- data/lib/girb/version.rb +1 -1
- data/lib/girb.rb +24 -7
- data/lib/irb/command/qq.rb +48 -6
- metadata +11 -1
data/lib/girb/prompt_builder.rb
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Girb
|
|
4
4
|
class PromptBuilder
|
|
5
|
-
|
|
6
|
-
|
|
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,36 +15,15 @@ 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
|
-
##
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
## You May Ask Clarifying Questions
|
|
37
|
-
When you have doubts, ask the user about preconditions or unclear points.
|
|
38
|
-
- When multiple interpretations are possible: Confirm which interpretation is correct
|
|
39
|
-
- When preconditions are unclear: Ask what they're aiming for, what environment they're assuming
|
|
40
|
-
- When information is insufficient: Prompt for the full error message or related code
|
|
41
|
-
Asking questions increases dialogue turns but reduces misunderstandings and enables more accurate answers.
|
|
18
|
+
## Clarifying Questions (Use Sparingly)
|
|
19
|
+
Only ask the user for clarification AFTER you have already investigated using tools.
|
|
20
|
+
- First: read the source file, check variables, run code
|
|
21
|
+
- Then: if the intent is still ambiguous after investigation, ask a focused question
|
|
42
22
|
|
|
43
23
|
## Response Guidelines
|
|
44
24
|
- Keep responses concise and practical
|
|
45
25
|
- Read patterns and intentions; handle hypothetical questions
|
|
46
|
-
- Code examples should use variables and objects from the current
|
|
26
|
+
- Code examples should use variables and objects from the current context and be directly executable
|
|
47
27
|
|
|
48
28
|
## Debugging Support on Errors
|
|
49
29
|
When users encounter errors, actively support debugging.
|
|
@@ -51,11 +31,119 @@ module Girb
|
|
|
51
31
|
- Suggest ways to inspect related code (e.g., using the inspect_object tool)
|
|
52
32
|
- Guide them step-by-step toward writing more robust code
|
|
53
33
|
|
|
34
|
+
## CRITICAL: Proactive Investigation — Act First, Don't Ask
|
|
35
|
+
You MUST investigate before asking the user for information.
|
|
36
|
+
- Use `evaluate_code` to run and verify code rather than guessing or reasoning about results.
|
|
37
|
+
- NEVER ask the user for code, file names, or variable definitions that you can look up
|
|
38
|
+
yourself with `read_file`, `evaluate_code`, `inspect_object`, or `find_file`.
|
|
39
|
+
|
|
54
40
|
## Available Tools
|
|
55
41
|
Use tools to inspect variables in detail, retrieve source code, and execute code.
|
|
56
42
|
Actively use the evaluate_code tool especially for verifying hypotheses and calculations.
|
|
57
43
|
PROMPT
|
|
58
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
|
|
129
|
+
## Autonomous Investigation with continue_analysis
|
|
130
|
+
When you need to execute code that changes state AND then see the full updated context
|
|
131
|
+
(all local variables, instance variables, last value, etc.), use the `continue_analysis` tool.
|
|
132
|
+
|
|
133
|
+
After you call `continue_analysis`, your current response will be sent, and then you will be
|
|
134
|
+
automatically re-invoked with a refreshed context showing all current variable values.
|
|
135
|
+
|
|
136
|
+
### When to use continue_analysis:
|
|
137
|
+
- After evaluating code that modifies variables, when you need to see the full picture
|
|
138
|
+
- When iteratively debugging: change something → check state → change more
|
|
139
|
+
- When you need to verify side effects of an operation across multiple variables
|
|
140
|
+
|
|
141
|
+
### When NOT to use continue_analysis:
|
|
142
|
+
- When you can get the information you need with evaluate_code or inspect_object directly
|
|
143
|
+
- When you've found your answer and want to report to the user
|
|
144
|
+
- For simple one-shot investigations
|
|
145
|
+
PROMPT
|
|
146
|
+
|
|
59
147
|
def initialize(question, context)
|
|
60
148
|
@question = question
|
|
61
149
|
@context = context
|
|
@@ -75,18 +163,20 @@ module Girb
|
|
|
75
163
|
|
|
76
164
|
# System prompt (shared across conversation)
|
|
77
165
|
def system_prompt
|
|
166
|
+
prompt = COMMON_PROMPT + "\n" + mode_specific_prompt + "\n" + CONTINUE_ANALYSIS_PROMPT
|
|
167
|
+
|
|
78
168
|
custom = Girb.configuration&.custom_prompt
|
|
79
169
|
if custom && !custom.empty?
|
|
80
|
-
"
|
|
170
|
+
prompt + "\n\n## User-Defined Instructions\n#{custom}"
|
|
81
171
|
else
|
|
82
|
-
|
|
172
|
+
prompt
|
|
83
173
|
end
|
|
84
174
|
end
|
|
85
175
|
|
|
86
176
|
# User message (context + question)
|
|
87
177
|
def user_message
|
|
88
178
|
<<~MSG
|
|
89
|
-
## Current
|
|
179
|
+
## Current Context
|
|
90
180
|
#{build_context_section}
|
|
91
181
|
|
|
92
182
|
## Question
|
|
@@ -96,29 +186,49 @@ module Girb
|
|
|
96
186
|
|
|
97
187
|
private
|
|
98
188
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
199
|
+
|
|
200
|
+
def detect_mode
|
|
201
|
+
loc = @context[:source_location]
|
|
104
202
|
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
107
210
|
|
|
108
|
-
|
|
109
|
-
|
|
211
|
+
# Check for Rails mode
|
|
212
|
+
return :rails if defined?(Rails)
|
|
110
213
|
|
|
111
|
-
|
|
112
|
-
|
|
214
|
+
# Default: interactive IRB
|
|
215
|
+
:interactive
|
|
216
|
+
end
|
|
113
217
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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")
|
|
117
227
|
end
|
|
118
228
|
|
|
119
229
|
def format_source_location
|
|
120
230
|
loc = @context[:source_location]
|
|
121
|
-
return "(
|
|
231
|
+
return "(interactive session)" unless loc
|
|
122
232
|
|
|
123
233
|
"File: #{loc[:file]}\nLine: #{loc[:line]}"
|
|
124
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.iso8601,
|
|
70
|
+
messages: serialize_messages
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
File.write(file_path, JSON.pretty_generate(data))
|
|
74
|
+
puts "[girb] Session saved: #{@current_session_id}"
|
|
75
|
+
rescue => e
|
|
76
|
+
puts "[girb] Failed to save session: #{e.message}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# セッションを読み込み
|
|
80
|
+
def load_session(file_path)
|
|
81
|
+
data = JSON.parse(File.read(file_path), symbolize_names: true)
|
|
82
|
+
|
|
83
|
+
ConversationHistory.reset!
|
|
84
|
+
deserialize_messages(data[:messages])
|
|
85
|
+
|
|
86
|
+
message_count = data[:messages]&.size || 0
|
|
87
|
+
puts "[girb] Loaded #{message_count} messages from previous session"
|
|
88
|
+
rescue => e
|
|
89
|
+
puts "[girb] Failed to load session: #{e.message}"
|
|
90
|
+
ConversationHistory.reset!
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# 現在のセッションをクリア(ファイルも削除)
|
|
94
|
+
def clear_session
|
|
95
|
+
if @current_session_id
|
|
96
|
+
delete_session(@current_session_id)
|
|
97
|
+
@current_session_id = nil
|
|
98
|
+
end
|
|
99
|
+
ConversationHistory.reset!
|
|
100
|
+
puts "[girb] Session cleared"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# セッション一覧を取得
|
|
104
|
+
def list_sessions
|
|
105
|
+
dir = sessions_dir
|
|
106
|
+
return [] unless Dir.exist?(dir)
|
|
107
|
+
|
|
108
|
+
Dir.glob(File.join(dir, "*.json")).map do |file|
|
|
109
|
+
data = JSON.parse(File.read(file), symbolize_names: true)
|
|
110
|
+
{
|
|
111
|
+
id: data[:session_id],
|
|
112
|
+
saved_at: data[:saved_at],
|
|
113
|
+
message_count: data[:messages]&.size || 0
|
|
114
|
+
}
|
|
115
|
+
rescue
|
|
116
|
+
nil
|
|
117
|
+
end.compact
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# セッションを削除
|
|
121
|
+
def delete_session(session_id)
|
|
122
|
+
file_path = session_file_path(session_id)
|
|
123
|
+
if File.exist?(file_path)
|
|
124
|
+
File.delete(file_path)
|
|
125
|
+
puts "[girb] Session deleted: #{session_id}"
|
|
126
|
+
true
|
|
127
|
+
else
|
|
128
|
+
puts "[girb] Session not found: #{session_id}"
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def serialize_messages
|
|
136
|
+
ConversationHistory.messages.map do |msg|
|
|
137
|
+
{
|
|
138
|
+
role: msg.role,
|
|
139
|
+
content: msg.content,
|
|
140
|
+
tool_calls: msg.tool_calls
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def deserialize_messages(messages)
|
|
146
|
+
return unless messages
|
|
147
|
+
|
|
148
|
+
messages.each do |msg|
|
|
149
|
+
case msg[:role]
|
|
150
|
+
when "user"
|
|
151
|
+
ConversationHistory.add_user_message(msg[:content])
|
|
152
|
+
when "model"
|
|
153
|
+
# tool_callsがある場合は先にpending_tool_callsに追加
|
|
154
|
+
if msg[:tool_calls]&.any?
|
|
155
|
+
msg[:tool_calls].each do |tc|
|
|
156
|
+
ConversationHistory.add_tool_call(
|
|
157
|
+
tc[:name],
|
|
158
|
+
tc[:args],
|
|
159
|
+
tc[:result],
|
|
160
|
+
id: tc[:id]
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
ConversationHistory.add_assistant_message(msg[:content])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Girb
|
|
6
|
+
module Tools
|
|
7
|
+
class ContinueAnalysis < Base
|
|
8
|
+
class << self
|
|
9
|
+
def description
|
|
10
|
+
"Request to be re-invoked with a refreshed context (updated local variables, " \
|
|
11
|
+
"instance variables, last value, etc.). Use this after executing code that " \
|
|
12
|
+
"changes state, when you need to see the full updated picture before deciding " \
|
|
13
|
+
"your next action."
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parameters
|
|
17
|
+
{
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
reason: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Brief description of why you need a context refresh and what you plan to check next."
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
required: ["reason"]
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def available?
|
|
30
|
+
# In debug mode, use run_debug_command with auto_continue instead
|
|
31
|
+
!defined?(DEBUGGER__)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def execute(binding, reason:)
|
|
36
|
+
Girb::AutoContinue.request!
|
|
37
|
+
{
|
|
38
|
+
success: true,
|
|
39
|
+
message: "You will be re-invoked with updated context after this response.",
|
|
40
|
+
reason: reason
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../debug_session_history"
|
|
5
|
+
require_relative "../conversation_history"
|
|
6
|
+
require_relative "../session_persistence"
|
|
7
|
+
|
|
8
|
+
module Girb
|
|
9
|
+
module Tools
|
|
10
|
+
class DebugSessionHistoryTool < Base
|
|
11
|
+
class << self
|
|
12
|
+
def name
|
|
13
|
+
"get_session_history"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
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."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parameters
|
|
22
|
+
{
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
action: {
|
|
26
|
+
type: "string",
|
|
27
|
+
enum: %w[full_history list_ai_conversations],
|
|
28
|
+
description: "Action: full_history (all commands and AI conversations), list_ai_conversations (AI Q&A only)"
|
|
29
|
+
},
|
|
30
|
+
count: {
|
|
31
|
+
type: "integer",
|
|
32
|
+
description: "Number of recent entries to retrieve (default: 20)"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ["action"]
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def available?
|
|
40
|
+
defined?(DEBUGGER__)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def execute(_binding, action:, count: 20)
|
|
45
|
+
case action
|
|
46
|
+
when "full_history"
|
|
47
|
+
get_full_history(count)
|
|
48
|
+
when "list_ai_conversations"
|
|
49
|
+
list_ai_conversations(count)
|
|
50
|
+
else
|
|
51
|
+
{ error: "Unknown action: #{action}. Use 'full_history' or 'list_ai_conversations'." }
|
|
52
|
+
end
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
{ error: "#{e.class}: #{e.message}" }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
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
|
+
# 現在のセッションのデバッグ履歴
|
|
69
|
+
history = DebugSessionHistory.format_history(count)
|
|
70
|
+
if history && !history.empty?
|
|
71
|
+
result[:current_session_history] = history
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if result.empty?
|
|
75
|
+
{ message: "No history available" }
|
|
76
|
+
else
|
|
77
|
+
result
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
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|
|
|
90
|
+
{
|
|
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
|
|
107
|
+
{
|
|
108
|
+
question: c[:question],
|
|
109
|
+
response_preview: response_preview,
|
|
110
|
+
source: c[:source] || "persisted"
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
}
|
|
114
|
+
else
|
|
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
|
|
127
|
+
end
|
|
128
|
+
conversations
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "stringio"
|
|
3
4
|
require_relative "base"
|
|
4
5
|
|
|
5
6
|
module Girb
|
|
@@ -26,17 +27,37 @@ module Girb
|
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def execute(binding, code:)
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
captured_output = StringIO.new
|
|
31
|
+
original_stdout = $stdout
|
|
32
|
+
$stdout = captured_output
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
result = binding.eval(code)
|
|
36
|
+
ensure
|
|
37
|
+
$stdout = original_stdout
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
stdout_str = captured_output.string
|
|
41
|
+
# Also print captured output to the real console for user visibility
|
|
42
|
+
print stdout_str unless stdout_str.empty?
|
|
43
|
+
|
|
44
|
+
response = {
|
|
31
45
|
code: code,
|
|
32
46
|
result: safe_inspect(result),
|
|
33
47
|
result_class: result.class.name,
|
|
34
48
|
success: true
|
|
35
49
|
}
|
|
50
|
+
response[:stdout] = stdout_str unless stdout_str.empty?
|
|
51
|
+
response
|
|
36
52
|
rescue SyntaxError => e
|
|
53
|
+
$stdout = original_stdout if $stdout != original_stdout
|
|
37
54
|
{ code: code, error: "Syntax error: #{e.message}", success: false }
|
|
38
55
|
rescue StandardError => e
|
|
39
|
-
|
|
56
|
+
$stdout = original_stdout if $stdout != original_stdout
|
|
57
|
+
stdout_str = captured_output&.string
|
|
58
|
+
response = { code: code, error: "#{e.class}: #{e.message}", backtrace: e.backtrace&.first(5), success: false }
|
|
59
|
+
response[:stdout] = stdout_str if stdout_str && !stdout_str.empty?
|
|
60
|
+
response
|
|
40
61
|
end
|
|
41
62
|
end
|
|
42
63
|
end
|