girb 0.1.2 → 0.2.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 +39 -0
- data/README.md +37 -0
- data/README_ja.md +37 -0
- data/lib/girb/ai_client.rb +195 -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 +298 -0
- data/lib/girb/debug_prompt_builder.rb +227 -0
- data/lib/girb/debug_session_history.rb +121 -0
- data/lib/girb/girbrc_loader.rb +15 -9
- data/lib/girb/irb_integration.rb +1 -1
- data/lib/girb/prompt_builder.rb +39 -6
- data/lib/girb/tools/continue_analysis.rb +45 -0
- data/lib/girb/tools/debug_session_history_tool.rb +89 -0
- data/lib/girb/tools/evaluate_code.rb +24 -3
- data/lib/girb/tools/run_debug_command.rb +49 -0
- data/lib/girb/tools/session_history_tool.rb +5 -0
- data/lib/girb/tools.rb +22 -2
- data/lib/girb/version.rb +1 -1
- data/lib/girb.rb +8 -0
- data/lib/irb/command/qq.rb +7 -6
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '048b4dc3910c2afaa9bb1bd2a1f2f33f51b631b74405368daabec4cbf1ef1e2a'
|
|
4
|
+
data.tar.gz: d5b46f5c7c0f809053c8b38fd15f9af6709338c62cb2374f0a64b25f283b1807
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e064f6e9a5dcd1ea6af090ccfc9aff2acc476fcafff772aed594ff15ecb95cfe6513e8e7facccbea490d82d691b9e495d4134fe418f8fb2d0b7bfab0e40fe25
|
|
7
|
+
data.tar.gz: 3287871b7937ea1234fdb18c6e551b96da944b12a57305b3e6c5fe5e63d2d602afa04aedc88192331176b70062ad4efc1bcfeb5c7bf3285ce603a3e090a1f005
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-02-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Debug gem (rdbg) integration**: AI assistant for step-through debugging
|
|
8
|
+
- `ai <question>` command in debugger
|
|
9
|
+
- Ctrl+Space to send input to AI
|
|
10
|
+
- Auto-routing of non-ASCII (Japanese) input to AI
|
|
11
|
+
- `run_debug_command` tool for AI to execute debugger commands (step, next, continue, break, etc.)
|
|
12
|
+
- **Auto-continue for autonomous AI investigation**
|
|
13
|
+
- `continue_analysis` tool for IRB mode context refresh
|
|
14
|
+
- AI can loop through investigate-execute-analyze cycles
|
|
15
|
+
- Configurable iteration limits (MAX_ITERATIONS = 20)
|
|
16
|
+
- **Ctrl+C interrupt support** for both IRB and debug modes
|
|
17
|
+
- Graceful interruption of long-running AI operations
|
|
18
|
+
- AI summarizes progress when interrupted
|
|
19
|
+
- **Debug session history tracking**
|
|
20
|
+
- Track debugger commands and AI conversations
|
|
21
|
+
- `get_session_history` tool for debug mode
|
|
22
|
+
- **Efficient variable tracking** with silent breakpoints
|
|
23
|
+
- `break file:line if: ($var << x; false)` pattern for recording without stopping
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Separate tool sets for IRB and debug modes
|
|
28
|
+
- SHARED_TOOLS: Common tools for both modes
|
|
29
|
+
- IRB_TOOLS: SessionHistoryTool, ContinueAnalysis
|
|
30
|
+
- DEBUG_TOOLS: DebugSessionHistoryTool, RunDebugCommand
|
|
31
|
+
- Improved prompts for debug mode
|
|
32
|
+
- Guidance on variable persistence across frames
|
|
33
|
+
- Instructions for efficient breakpoint usage
|
|
34
|
+
- Context-aware investigation (don't use tools for greetings)
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Tool calls now include IDs for proper conversation history
|
|
39
|
+
- Auto-continue loop properly exits when debug commands are queued
|
|
40
|
+
|
|
3
41
|
## [0.1.2] - 2026-02-03
|
|
4
42
|
|
|
5
43
|
### Added
|
|
@@ -7,6 +45,7 @@
|
|
|
7
45
|
- `.girbrc` configuration file support with directory traversal
|
|
8
46
|
- Railtie for automatic Rails console integration
|
|
9
47
|
- GirbrcLoader utility for finding and loading `.girbrc` files
|
|
48
|
+
- `get_current_directory` tool for non-Rails environments
|
|
10
49
|
|
|
11
50
|
### Changed
|
|
12
51
|
|
data/README.md
CHANGED
|
@@ -10,6 +10,8 @@ An AI assistant embedded in your IRB session. It understands your runtime contex
|
|
|
10
10
|
- **Exception Capture**: Automatically captures recent exceptions - just ask "why did this fail?" after an error
|
|
11
11
|
- **Session History Understanding**: Tracks IRB input history and understands conversation flow
|
|
12
12
|
- **Tool Execution**: AI autonomously executes code, inspects objects, and retrieves source code
|
|
13
|
+
- **Autonomous Investigation**: AI can loop through investigate-execute-analyze cycles using `continue_analysis`
|
|
14
|
+
- **Debug Gem Integration**: Use with Ruby's debug gem for step-through debugging with AI assistance
|
|
13
15
|
- **Multi-language Support**: Detects user's language and responds in the same language
|
|
14
16
|
- **Customizable**: Add custom prompts for project-specific instructions
|
|
15
17
|
- **Provider Agnostic**: Use any LLM provider or implement your own
|
|
@@ -116,6 +118,34 @@ def problematic_method
|
|
|
116
118
|
end
|
|
117
119
|
```
|
|
118
120
|
|
|
121
|
+
### Debug with debug gem (rdbg)
|
|
122
|
+
|
|
123
|
+
For step-through debugging with AI assistance, add `require "girb"` to your script:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
require "girb"
|
|
127
|
+
|
|
128
|
+
def problematic_method
|
|
129
|
+
result = some_calculation
|
|
130
|
+
result
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
problematic_method
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Then run with rdbg:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
rdbg your_script.rb
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
In the debugger, use:
|
|
143
|
+
- `ai <question>` - Ask AI a question
|
|
144
|
+
- `Ctrl+Space` - Send current input to AI
|
|
145
|
+
- Natural language (non-ASCII) input is automatically routed to AI
|
|
146
|
+
|
|
147
|
+
The AI can execute debugger commands like `step`, `next`, `continue`, and set breakpoints for you.
|
|
148
|
+
|
|
119
149
|
### How to Ask AI
|
|
120
150
|
|
|
121
151
|
#### Method 1: Ctrl+Space
|
|
@@ -179,6 +209,7 @@ For `girb` command, you can also configure via environment variables (used when
|
|
|
179
209
|
| `find_file` | Search for files in the project |
|
|
180
210
|
| `read_file` | Read file contents |
|
|
181
211
|
| `session_history` | Get IRB session history |
|
|
212
|
+
| `continue_analysis` | Request context refresh for autonomous investigation |
|
|
182
213
|
|
|
183
214
|
### Additional Tools in Rails Environment
|
|
184
215
|
|
|
@@ -187,6 +218,12 @@ For `girb` command, you can also configure via environment variables (used when
|
|
|
187
218
|
| `query_model` | Execute queries on ActiveRecord models |
|
|
188
219
|
| `model_info` | Get model schema information |
|
|
189
220
|
|
|
221
|
+
### Additional Tools in Debug Mode (rdbg)
|
|
222
|
+
|
|
223
|
+
| Tool | Description |
|
|
224
|
+
|------|-------------|
|
|
225
|
+
| `run_debug_command` | Execute debugger commands (step, next, continue, break, etc.) |
|
|
226
|
+
|
|
190
227
|
## Custom Providers
|
|
191
228
|
|
|
192
229
|
Implement your own LLM provider:
|
data/README_ja.md
CHANGED
|
@@ -8,6 +8,8 @@ IRBセッションに組み込まれたAIアシスタント。実行中のコン
|
|
|
8
8
|
- **例外キャプチャ**: 直前の例外を自動キャプチャ - エラー後に「なぜ失敗した?」と聞くだけでOK
|
|
9
9
|
- **セッション履歴の理解**: IRBでの入力履歴を追跡し、会話の流れを理解
|
|
10
10
|
- **ツール実行**: コードの実行、オブジェクトの検査、ソースコードの取得などをAIが自律的に実行
|
|
11
|
+
- **自律的な調査**: `continue_analysis`を使って、調査→実行→分析のサイクルをAIが自律的にループ可能
|
|
12
|
+
- **debug gem統合**: Rubyのdebug gemと連携し、AIアシスタント付きのステップ実行デバッグが可能
|
|
11
13
|
- **多言語対応**: ユーザーの言語を検出し、同じ言語で応答
|
|
12
14
|
- **カスタマイズ可能**: 独自のプロンプトを追加して、プロジェクト固有の指示を設定可能
|
|
13
15
|
- **プロバイダー非依存**: 任意のLLMプロバイダーを使用、または独自実装が可能
|
|
@@ -114,6 +116,34 @@ def problematic_method
|
|
|
114
116
|
end
|
|
115
117
|
```
|
|
116
118
|
|
|
119
|
+
### debug gem (rdbg) でデバッグ
|
|
120
|
+
|
|
121
|
+
AIアシスタント付きのステップ実行デバッグを行うには、スクリプトに `require "girb"` を追加:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require "girb"
|
|
125
|
+
|
|
126
|
+
def problematic_method
|
|
127
|
+
result = some_calculation
|
|
128
|
+
result
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
problematic_method
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
rdbgで起動:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
rdbg your_script.rb
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
デバッガ内では以下の方法でAIに質問できます:
|
|
141
|
+
- `ai <質問>` - AIに質問
|
|
142
|
+
- `Ctrl+Space` - 入力内容をAIに送信
|
|
143
|
+
- 日本語(非ASCII文字)の入力は自動的にAIにルーティング
|
|
144
|
+
|
|
145
|
+
AIは `step`、`next`、`continue` などのデバッガコマンドを実行したり、ブレークポイントを設定することもできます。
|
|
146
|
+
|
|
117
147
|
### AIへの質問方法
|
|
118
148
|
|
|
119
149
|
#### 方法1: Ctrl+Space
|
|
@@ -177,6 +207,7 @@ girb --help # ヘルプを表示
|
|
|
177
207
|
| `find_file` | プロジェクト内のファイルを検索 |
|
|
178
208
|
| `read_file` | ファイルの内容を読み取り |
|
|
179
209
|
| `session_history` | IRBセッションの履歴を取得 |
|
|
210
|
+
| `continue_analysis` | 自律調査のためのコンテキスト更新をリクエスト |
|
|
180
211
|
|
|
181
212
|
### Rails環境での追加ツール
|
|
182
213
|
|
|
@@ -185,6 +216,12 @@ girb --help # ヘルプを表示
|
|
|
185
216
|
| `query_model` | ActiveRecordモデルへのクエリ実行 |
|
|
186
217
|
| `model_info` | モデルのスキーマ情報を取得 |
|
|
187
218
|
|
|
219
|
+
### デバッグモード (rdbg) での追加ツール
|
|
220
|
+
|
|
221
|
+
| ツール | 説明 |
|
|
222
|
+
|--------|------|
|
|
223
|
+
| `run_debug_command` | デバッガコマンドを実行(step、next、continue、breakなど) |
|
|
224
|
+
|
|
188
225
|
## カスタムプロバイダー
|
|
189
226
|
|
|
190
227
|
独自のLLMプロバイダーを実装:
|
data/lib/girb/ai_client.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "auto_continue"
|
|
3
4
|
require_relative "conversation_history"
|
|
4
5
|
require_relative "providers/base"
|
|
6
|
+
require_relative "debug_context_builder"
|
|
7
|
+
require_relative "debug_prompt_builder"
|
|
5
8
|
|
|
6
9
|
module Girb
|
|
7
10
|
class AiClient
|
|
@@ -11,23 +14,89 @@ module Girb
|
|
|
11
14
|
@provider = Girb.configuration.provider!
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
def ask(question, context, binding: nil, line_no: nil)
|
|
17
|
+
def ask(question, context, binding: nil, line_no: nil, irb_context: nil, debug_mode: false)
|
|
15
18
|
@current_binding = binding
|
|
16
19
|
@current_line_no = line_no
|
|
20
|
+
@irb_context = irb_context
|
|
21
|
+
@debug_mode = debug_mode
|
|
17
22
|
@reasoning_log = []
|
|
18
23
|
|
|
19
|
-
prompt_builder =
|
|
24
|
+
prompt_builder = create_prompt_builder(question, context)
|
|
20
25
|
@system_prompt = prompt_builder.system_prompt
|
|
21
26
|
user_message = prompt_builder.user_message
|
|
22
27
|
|
|
23
28
|
ConversationHistory.add_user_message(user_message)
|
|
24
29
|
|
|
25
30
|
tools = build_tools
|
|
26
|
-
|
|
31
|
+
|
|
32
|
+
# In debug mode, auto-continue is handled by DebugIntegration, not here
|
|
33
|
+
if @debug_mode
|
|
34
|
+
process_with_tools(tools)
|
|
35
|
+
else
|
|
36
|
+
auto_continue_count = 0
|
|
37
|
+
original_int_handler = setup_interrupt_handler
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
loop do
|
|
41
|
+
# Check for interrupt at start of loop
|
|
42
|
+
if Girb::AutoContinue.interrupted?
|
|
43
|
+
Girb::AutoContinue.clear_interrupt!
|
|
44
|
+
handle_irb_interrupted
|
|
45
|
+
break
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
process_with_tools(tools)
|
|
49
|
+
|
|
50
|
+
# Check for interrupt after API call (Ctrl+C during request)
|
|
51
|
+
if Girb::AutoContinue.interrupted?
|
|
52
|
+
Girb::AutoContinue.clear_interrupt!
|
|
53
|
+
handle_irb_interrupted
|
|
54
|
+
break
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
break unless Girb::AutoContinue.active?
|
|
58
|
+
|
|
59
|
+
auto_continue_count += 1
|
|
60
|
+
if auto_continue_count >= Girb::AutoContinue::MAX_ITERATIONS
|
|
61
|
+
handle_irb_limit_reached
|
|
62
|
+
break
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Girb::AutoContinue.reset!
|
|
66
|
+
|
|
67
|
+
# Rebuild context with current binding state
|
|
68
|
+
new_context = create_context_builder(@current_binding, @irb_context).build
|
|
69
|
+
continuation = "(auto-continue: Your previous action has been completed. " \
|
|
70
|
+
"Here is the updated context. Continue your investigation.)"
|
|
71
|
+
continuation_builder = create_prompt_builder(continuation, new_context)
|
|
72
|
+
ConversationHistory.add_user_message(continuation_builder.user_message)
|
|
73
|
+
end
|
|
74
|
+
ensure
|
|
75
|
+
restore_interrupt_handler(original_int_handler)
|
|
76
|
+
Girb::AutoContinue.reset!
|
|
77
|
+
Girb::AutoContinue.clear_interrupt!
|
|
78
|
+
end
|
|
79
|
+
end
|
|
27
80
|
end
|
|
28
81
|
|
|
29
82
|
private
|
|
30
83
|
|
|
84
|
+
def create_prompt_builder(question, context)
|
|
85
|
+
if @debug_mode
|
|
86
|
+
DebugPromptBuilder.new(question, context)
|
|
87
|
+
else
|
|
88
|
+
PromptBuilder.new(question, context)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def create_context_builder(binding, irb_context)
|
|
93
|
+
if @debug_mode
|
|
94
|
+
DebugContextBuilder.new(binding)
|
|
95
|
+
else
|
|
96
|
+
ContextBuilder.new(binding, irb_context)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
31
100
|
def build_tools
|
|
32
101
|
Tools.available_tools.map do |tool_class|
|
|
33
102
|
{
|
|
@@ -43,6 +112,12 @@ module Girb
|
|
|
43
112
|
accumulated_text = []
|
|
44
113
|
|
|
45
114
|
loop do
|
|
115
|
+
# Check for interrupt at start of each iteration
|
|
116
|
+
if check_interrupted?
|
|
117
|
+
puts "\n[girb] Interrupted by user (Ctrl+C)"
|
|
118
|
+
break
|
|
119
|
+
end
|
|
120
|
+
|
|
46
121
|
iterations += 1
|
|
47
122
|
if iterations > MAX_TOOL_ITERATIONS
|
|
48
123
|
puts "\n[girb] Tool iteration limit reached"
|
|
@@ -50,12 +125,29 @@ module Girb
|
|
|
50
125
|
end
|
|
51
126
|
|
|
52
127
|
messages = ConversationHistory.to_normalized
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
128
|
+
begin
|
|
129
|
+
response = @provider.chat(
|
|
130
|
+
messages: messages,
|
|
131
|
+
system_prompt: @system_prompt,
|
|
132
|
+
tools: tools,
|
|
133
|
+
binding: @current_binding
|
|
134
|
+
)
|
|
135
|
+
rescue Interrupt => e
|
|
136
|
+
puts "\n[girb] Interrupted by user (Ctrl+C)"
|
|
137
|
+
Girb::AutoContinue.interrupt! unless @debug_mode
|
|
138
|
+
Girb::DebugIntegration.interrupt! if @debug_mode && defined?(Girb::DebugIntegration)
|
|
139
|
+
break
|
|
140
|
+
rescue Exception => e
|
|
141
|
+
# IRB::Abort and similar exceptions
|
|
142
|
+
if e.class.name.include?("Abort") || e.class.name.include?("Interrupt")
|
|
143
|
+
puts "\n[girb] Interrupted by user (Ctrl+C)"
|
|
144
|
+
Girb::AutoContinue.interrupt! unless @debug_mode
|
|
145
|
+
Girb::DebugIntegration.interrupt! if @debug_mode && defined?(Girb::DebugIntegration)
|
|
146
|
+
break
|
|
147
|
+
else
|
|
148
|
+
raise
|
|
149
|
+
end
|
|
150
|
+
end
|
|
59
151
|
|
|
60
152
|
if Girb.configuration.debug
|
|
61
153
|
puts "[girb] function_calls: #{response.function_calls.inspect}"
|
|
@@ -79,27 +171,46 @@ module Girb
|
|
|
79
171
|
accumulated_text << response.text
|
|
80
172
|
end
|
|
81
173
|
|
|
82
|
-
|
|
83
|
-
tool_name = function_call[:name]
|
|
84
|
-
tool_args = function_call[:args] || {}
|
|
174
|
+
debug_command_called = false
|
|
85
175
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
176
|
+
response.function_calls.each do |function_call|
|
|
177
|
+
tool_name = function_call[:name]
|
|
178
|
+
tool_args = function_call[:args] || {}
|
|
179
|
+
tool_id = function_call[:id]
|
|
180
|
+
|
|
181
|
+
if Girb.configuration.debug
|
|
182
|
+
puts "[girb] Tool: #{tool_name}(#{tool_args.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')})"
|
|
183
|
+
end
|
|
89
184
|
|
|
90
|
-
|
|
185
|
+
result = execute_tool(tool_name, tool_args)
|
|
91
186
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
187
|
+
@reasoning_log << {
|
|
188
|
+
tool: tool_name,
|
|
189
|
+
args: tool_args,
|
|
190
|
+
result: result
|
|
191
|
+
}
|
|
97
192
|
|
|
98
|
-
|
|
99
|
-
ConversationHistory.add_tool_call(tool_name, tool_args, result)
|
|
193
|
+
ConversationHistory.add_tool_call(tool_name, tool_args, result, id: tool_id)
|
|
100
194
|
|
|
101
|
-
|
|
102
|
-
|
|
195
|
+
if Girb.configuration.debug && result.is_a?(Hash) && result[:error]
|
|
196
|
+
puts "[girb] Tool error: #{result[:error]}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# In debug mode, if run_debug_command was called, we need to exit
|
|
200
|
+
# the tool loop so the debugger can execute the pending commands
|
|
201
|
+
if @debug_mode && tool_name == "run_debug_command"
|
|
202
|
+
debug_command_called = true
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Exit tool loop if debug command was called - let debugger take over
|
|
207
|
+
if debug_command_called
|
|
208
|
+
# Save accumulated text and pending tool calls as assistant message
|
|
209
|
+
text = accumulated_text.any? ? accumulated_text.join("\n") : ""
|
|
210
|
+
ConversationHistory.add_assistant_message(text)
|
|
211
|
+
record_ai_response(text) unless text.empty?
|
|
212
|
+
puts text unless text.empty?
|
|
213
|
+
break
|
|
103
214
|
end
|
|
104
215
|
else
|
|
105
216
|
# Text response
|
|
@@ -140,10 +251,13 @@ module Girb
|
|
|
140
251
|
end
|
|
141
252
|
|
|
142
253
|
def record_ai_response(response)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
254
|
+
if @debug_mode
|
|
255
|
+
require_relative "debug_session_history"
|
|
256
|
+
DebugSessionHistory.record_ai_response(response)
|
|
257
|
+
elsif @current_line_no
|
|
258
|
+
reasoning = @reasoning_log.empty? ? nil : format_reasoning
|
|
259
|
+
SessionHistory.record_ai_response(@current_line_no, response, reasoning)
|
|
260
|
+
end
|
|
147
261
|
end
|
|
148
262
|
|
|
149
263
|
def format_reasoning
|
|
@@ -154,5 +268,57 @@ module Girb
|
|
|
154
268
|
"Tool: #{log[:tool]}(#{args_str})\nResult: #{result_str}"
|
|
155
269
|
end.join("\n\n")
|
|
156
270
|
end
|
|
271
|
+
|
|
272
|
+
def setup_interrupt_handler
|
|
273
|
+
trap("INT") do
|
|
274
|
+
Girb::AutoContinue.interrupt!
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def check_interrupted?
|
|
279
|
+
if @debug_mode
|
|
280
|
+
defined?(Girb::DebugIntegration) && Girb::DebugIntegration.interrupted?
|
|
281
|
+
else
|
|
282
|
+
Girb::AutoContinue.interrupted?
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def restore_interrupt_handler(original_handler)
|
|
287
|
+
if original_handler
|
|
288
|
+
trap("INT", original_handler)
|
|
289
|
+
else
|
|
290
|
+
trap("INT", "DEFAULT")
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def handle_irb_interrupted
|
|
295
|
+
return unless @current_binding
|
|
296
|
+
|
|
297
|
+
new_context = create_context_builder(@current_binding, @irb_context).build
|
|
298
|
+
interrupt_message = "(System: User interrupted with Ctrl+C. " \
|
|
299
|
+
"Briefly summarize your progress so far. " \
|
|
300
|
+
"Tell the user where you stopped and how to continue if needed.)"
|
|
301
|
+
continuation_builder = create_prompt_builder(interrupt_message, new_context)
|
|
302
|
+
ConversationHistory.add_user_message(continuation_builder.user_message)
|
|
303
|
+
process_with_tools(build_tools)
|
|
304
|
+
rescue StandardError => e
|
|
305
|
+
puts "[girb] Error summarizing: #{e.message}" if Girb.configuration.debug
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def handle_irb_limit_reached
|
|
309
|
+
puts "\n[girb] Auto-continue limit reached (#{Girb::AutoContinue::MAX_ITERATIONS})"
|
|
310
|
+
return unless @current_binding
|
|
311
|
+
|
|
312
|
+
new_context = create_context_builder(@current_binding, @irb_context).build
|
|
313
|
+
limit_message = "(System: Auto-continue limit (#{Girb::AutoContinue::MAX_ITERATIONS}) reached. " \
|
|
314
|
+
"Summarize your progress so far and tell the user what was accomplished. " \
|
|
315
|
+
"If the task is not complete, explain what remains and instruct the user " \
|
|
316
|
+
"to continue with a follow-up request.)"
|
|
317
|
+
continuation_builder = create_prompt_builder(limit_message, new_context)
|
|
318
|
+
ConversationHistory.add_user_message(continuation_builder.user_message)
|
|
319
|
+
process_with_tools(build_tools)
|
|
320
|
+
rescue StandardError => e
|
|
321
|
+
puts "[girb] Error summarizing: #{e.message}" if Girb.configuration.debug
|
|
322
|
+
end
|
|
157
323
|
end
|
|
158
324
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Girb
|
|
4
|
+
module AutoContinue
|
|
5
|
+
MAX_ITERATIONS = 2
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def active?
|
|
9
|
+
@active || false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def request!
|
|
13
|
+
@active = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reset!
|
|
17
|
+
@active = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def interrupted?
|
|
21
|
+
@interrupted || false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def interrupt!
|
|
25
|
+
@interrupted = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def clear_interrupt!
|
|
29
|
+
@interrupted = false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -22,8 +22,8 @@ module Girb
|
|
|
22
22
|
instance.add_assistant_message(content)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def add_tool_call(tool_name, args, result)
|
|
26
|
-
instance.add_tool_call(tool_name, args, result)
|
|
25
|
+
def add_tool_call(tool_name, args, result, id: nil)
|
|
26
|
+
instance.add_tool_call(tool_name, args, result, id: id)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def to_contents
|
|
@@ -72,8 +72,9 @@ module Girb
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
def add_tool_call(tool_name, args, result)
|
|
75
|
+
def add_tool_call(tool_name, args, result, id: nil)
|
|
76
76
|
@pending_tool_calls << {
|
|
77
|
+
id: id || "call_#{SecureRandom.hex(12)}",
|
|
77
78
|
name: tool_name,
|
|
78
79
|
args: args,
|
|
79
80
|
result: result
|
|
@@ -105,15 +106,15 @@ module Girb
|
|
|
105
106
|
|
|
106
107
|
# Add tool calls and results if present
|
|
107
108
|
msg.tool_calls&.each do |tc|
|
|
108
|
-
result << { role: :tool_call, name: tc[:name], args: tc[:args] }
|
|
109
|
-
result << { role: :tool_result, name: tc[:name], result: tc[:result] }
|
|
109
|
+
result << { role: :tool_call, id: tc[:id], name: tc[:name], args: tc[:args] }
|
|
110
|
+
result << { role: :tool_result, id: tc[:id], name: tc[:name], result: tc[:result] }
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
# Add pending tool calls
|
|
114
115
|
@pending_tool_calls.each do |tc|
|
|
115
|
-
result << { role: :tool_call, name: tc[:name], args: tc[:args] }
|
|
116
|
-
result << { role: :tool_result, name: tc[:name], result: tc[:result] }
|
|
116
|
+
result << { role: :tool_call, id: tc[:id], name: tc[:name], args: tc[:args] }
|
|
117
|
+
result << { role: :tool_result, id: tc[:id], name: tc[:name], result: tc[:result] }
|
|
117
118
|
end
|
|
118
119
|
|
|
119
120
|
result
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "debug_session_history"
|
|
4
|
+
|
|
5
|
+
module Girb
|
|
6
|
+
class DebugContextBuilder
|
|
7
|
+
MAX_INSPECT_LENGTH = 500
|
|
8
|
+
|
|
9
|
+
def initialize(binding, thread_client: nil)
|
|
10
|
+
@binding = binding
|
|
11
|
+
@thread_client = thread_client
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build
|
|
15
|
+
{
|
|
16
|
+
source_location: capture_source_location,
|
|
17
|
+
local_variables: capture_locals,
|
|
18
|
+
instance_variables: capture_instance_variables,
|
|
19
|
+
self_info: capture_self,
|
|
20
|
+
backtrace: capture_backtrace,
|
|
21
|
+
breakpoint_info: capture_breakpoint_info,
|
|
22
|
+
session_history: capture_session_history
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def capture_source_location
|
|
29
|
+
loc = @binding.source_location
|
|
30
|
+
return nil unless loc
|
|
31
|
+
|
|
32
|
+
file, line = loc
|
|
33
|
+
{
|
|
34
|
+
file: file,
|
|
35
|
+
line: line
|
|
36
|
+
}
|
|
37
|
+
rescue StandardError
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def capture_locals
|
|
42
|
+
@binding.local_variables.to_h do |var|
|
|
43
|
+
value = @binding.local_variable_get(var)
|
|
44
|
+
[var, safe_inspect(value)]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def capture_instance_variables
|
|
49
|
+
obj = @binding.receiver
|
|
50
|
+
obj.instance_variables.to_h do |var|
|
|
51
|
+
value = obj.instance_variable_get(var)
|
|
52
|
+
[var, safe_inspect(value)]
|
|
53
|
+
end
|
|
54
|
+
rescue StandardError
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def capture_self
|
|
59
|
+
obj = @binding.receiver
|
|
60
|
+
{
|
|
61
|
+
class: obj.class.name,
|
|
62
|
+
inspect: safe_inspect(obj),
|
|
63
|
+
methods: obj.methods(false).first(20)
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def capture_backtrace
|
|
68
|
+
return nil unless @thread_client
|
|
69
|
+
|
|
70
|
+
@thread_client.current_frame&.location&.to_s
|
|
71
|
+
rescue StandardError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def capture_breakpoint_info
|
|
76
|
+
return nil unless defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:breakpoints)
|
|
77
|
+
|
|
78
|
+
DEBUGGER__.breakpoints.map do |bp|
|
|
79
|
+
{ type: bp.class.name, location: bp.to_s }
|
|
80
|
+
end
|
|
81
|
+
rescue StandardError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def capture_session_history
|
|
86
|
+
DebugSessionHistory.format_history(20)
|
|
87
|
+
rescue StandardError
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def safe_inspect(obj, max_length: MAX_INSPECT_LENGTH)
|
|
92
|
+
if defined?(ActiveRecord::Base) && obj.is_a?(ActiveRecord::Base)
|
|
93
|
+
return inspect_active_record(obj)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
result = obj.inspect
|
|
97
|
+
result.length > max_length ? "#{result[0, max_length]}..." : result
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
"#<#{obj.class} (inspect failed: #{e.message})>"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def inspect_active_record(obj)
|
|
103
|
+
{
|
|
104
|
+
class: obj.class.name,
|
|
105
|
+
id: obj.try(:id),
|
|
106
|
+
attributes: obj.attributes,
|
|
107
|
+
new_record: obj.new_record?,
|
|
108
|
+
changed: obj.changed?,
|
|
109
|
+
errors: obj.errors.full_messages
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|