debug-agent 0.7.0 → 0.7.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.
- checksums.yaml +4 -4
- data/lib/debug_agent/inspectors/leak_detector.rb +5 -0
- data/lib/debug_agent/inspectors/snapshot.rb +6 -0
- data/lib/debug_agent/llm_client.rb +2 -1
- data/lib/debug_agent/middleware.rb +53 -10
- data/lib/debug_agent/system_prompt_builder.rb +85 -5
- data/lib/debug_agent/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e6a7d1a5a0a33669a5e1b1c830761c5bf5a7a0b339e58328d29c7af1621dda4
|
|
4
|
+
data.tar.gz: 5beda4ad577f6598bcfd967e6124e819ebcbd853e00e5291a14baa08a7750ba4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4e5b1d103102c3446309f51b5d27dc689ff0a83bd32ab83eb4d800befd3fb72b4efbe0ae57362acc225974b36ec8b3f95ee24e7f1323dec9287f119a13c116c
|
|
7
|
+
data.tar.gz: b4105c3b8e1f29d7df9dce15c10852f6c7cf6eb2b9797dfd71fa0f8b5ea56989d9a34386e0b9166a5987c199ec86da5b1a5be8bdf3c084d1f30ed01f074b2f76
|
|
@@ -45,6 +45,11 @@ module DebugAgent
|
|
|
45
45
|
heap_pages: gc_stats[:heap_length]
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
# Enforce max 50 heap snapshots to prevent unbounded memory growth
|
|
49
|
+
if @heap_snapshots.size >= 50
|
|
50
|
+
oldest_key = @heap_snapshots.keys.min_by { |k| k[/\d+/].to_i }
|
|
51
|
+
@heap_snapshots.delete(oldest_key)
|
|
52
|
+
end
|
|
48
53
|
@heap_snapshots[snapshot_id] = snapshot
|
|
49
54
|
|
|
50
55
|
{
|
|
@@ -49,6 +49,12 @@ module DebugAgent
|
|
|
49
49
|
|
|
50
50
|
@metric_snapshot_counter += 1
|
|
51
51
|
snapshot_id = "snap-#{@metric_snapshot_counter}"
|
|
52
|
+
|
|
53
|
+
# Enforce max 100 snapshots to prevent unbounded growth
|
|
54
|
+
if @metric_snapshots.size >= 100
|
|
55
|
+
oldest_key = @metric_snapshots.keys.min_by { |k| k[/\d+/].to_i }
|
|
56
|
+
@metric_snapshots.delete(oldest_key)
|
|
57
|
+
end
|
|
52
58
|
@metric_snapshots[snapshot_id] = metrics
|
|
53
59
|
|
|
54
60
|
{
|
|
@@ -84,7 +84,7 @@ module DebugAgent
|
|
|
84
84
|
def stream_request(path, body, handler)
|
|
85
85
|
uri = URI(@cfg.base_url + path)
|
|
86
86
|
|
|
87
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: @cfg.timeout_seconds) do |http|
|
|
87
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: @cfg.timeout_seconds, open_timeout: 10) do |http|
|
|
88
88
|
request = Net::HTTP::Post.new(uri.path)
|
|
89
89
|
request['Authorization'] = "Bearer #{@cfg.api_key}"
|
|
90
90
|
request['Content-Type'] = 'application/json'
|
|
@@ -181,6 +181,7 @@ module DebugAgent
|
|
|
181
181
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
182
182
|
http.use_ssl = uri.scheme == 'https'
|
|
183
183
|
http.read_timeout = @cfg.timeout_seconds
|
|
184
|
+
http.open_timeout = 10
|
|
184
185
|
|
|
185
186
|
request = Net::HTTP::Post.new(uri.path)
|
|
186
187
|
request['Authorization'] = "Bearer #{@cfg.api_key}"
|
|
@@ -2,10 +2,13 @@ require 'json'
|
|
|
2
2
|
require_relative 'chat_page'
|
|
3
3
|
|
|
4
4
|
module DebugAgent
|
|
5
|
-
# SSE callback that
|
|
5
|
+
# SSE callback that collects events for both buffered and streaming modes.
|
|
6
|
+
# In streaming mode, events are yielded in real-time via an Enumerator.
|
|
6
7
|
class SseCallback < ChatCallback
|
|
7
8
|
def initialize
|
|
8
9
|
@events = []
|
|
10
|
+
@queue = Queue.new
|
|
11
|
+
@done = false
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def events
|
|
@@ -14,27 +17,47 @@ module DebugAgent
|
|
|
14
17
|
|
|
15
18
|
def on_content(chunk)
|
|
16
19
|
@events << ['content', JSON.generate(chunk)]
|
|
20
|
+
@queue << ['content', JSON.generate(chunk)]
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def on_tool_start(tool_name, args)
|
|
20
24
|
@events << ['tool_start', tool_name]
|
|
25
|
+
@queue << ['tool_start', tool_name]
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
def on_tool_result(tool_name, result)
|
|
24
29
|
@events << ['tool_result', "#{tool_name}: #{result}"]
|
|
30
|
+
@queue << ['tool_result', "#{tool_name}: #{result}"]
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
def on_complete
|
|
28
34
|
@events << ['done', '']
|
|
35
|
+
@queue << ['done', '']
|
|
36
|
+
@done = true
|
|
37
|
+
@queue.close
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
def on_error(message)
|
|
32
41
|
@events << ['error', message]
|
|
42
|
+
@queue << ['error', message]
|
|
43
|
+
@done = true
|
|
44
|
+
@queue.close
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
def on_context_compressed(original, compressed, removed_rounds)
|
|
36
48
|
info = JSON.generate({ originalTokens: original, compressedTokens: compressed, removedRounds: removed_rounds })
|
|
37
49
|
@events << ['context_compressed', info]
|
|
50
|
+
@queue << ['context_compressed', info]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Real-time streaming Enumerator: yields SSE-formatted lines as events arrive.
|
|
54
|
+
def streaming_enum
|
|
55
|
+
Enumerator.new do |yielder|
|
|
56
|
+
while (item = @queue.pop)
|
|
57
|
+
event_type, data = item
|
|
58
|
+
yielder << "event: #{event_type}\ndata: #{data}\n\n"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
38
61
|
end
|
|
39
62
|
end
|
|
40
63
|
|
|
@@ -67,35 +90,55 @@ module DebugAgent
|
|
|
67
90
|
|
|
68
91
|
# Shared routing logic used by both RackMiddleware class and Middleware lambda
|
|
69
92
|
module MiddlewareCore
|
|
93
|
+
CORS_HEADERS = {
|
|
94
|
+
'Access-Control-Allow-Origin' => '*',
|
|
95
|
+
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
|
|
96
|
+
'Access-Control-Allow-Headers' => 'Content-Type, Authorization',
|
|
97
|
+
}.freeze
|
|
98
|
+
|
|
70
99
|
def self.call(env, app, engine, config)
|
|
71
100
|
path = env['PATH_INFO']
|
|
72
101
|
method = env['REQUEST_METHOD']
|
|
73
102
|
base = config.base_path
|
|
74
103
|
|
|
104
|
+
# CORS preflight for all agent routes
|
|
105
|
+
if path.start_with?(base) && method == 'OPTIONS'
|
|
106
|
+
return [204, CORS_HEADERS.merge('Content-Length' => '0'), []]
|
|
107
|
+
end
|
|
108
|
+
|
|
75
109
|
# Chat UI
|
|
76
110
|
if path == base || path == "#{base}/"
|
|
77
111
|
if method == 'GET'
|
|
78
112
|
html = ChatPage.render(base)
|
|
79
|
-
return [200, { 'Content-Type' => 'text/html; charset=utf-8' }, [html]]
|
|
113
|
+
return [200, { 'Content-Type' => 'text/html; charset=utf-8' }.merge(CORS_HEADERS), [html]]
|
|
80
114
|
end
|
|
81
115
|
end
|
|
82
116
|
|
|
83
|
-
# SSE streaming chat
|
|
117
|
+
# SSE streaming chat — real-time streaming via Rack response body
|
|
84
118
|
if path == "#{base}/api/chat" && method == 'POST'
|
|
85
119
|
body = JSON.parse(env['rack.input'].read)
|
|
86
120
|
message = body['message'] || ''
|
|
87
121
|
session_id = body['sessionId'] || "session-#{Time.now.to_i}"
|
|
88
122
|
|
|
89
123
|
cb = SseCallback.new
|
|
90
|
-
engine.chat(message, session_id, cb)
|
|
91
124
|
|
|
92
|
-
|
|
125
|
+
# Run engine in a background thread for real-time streaming
|
|
126
|
+
Thread.new do
|
|
127
|
+
begin
|
|
128
|
+
engine.chat(message, session_id, cb)
|
|
129
|
+
rescue => e
|
|
130
|
+
cb.on_error("Internal error: #{e.message}")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Return a streaming response body (Rack 2+ / Rack 3 compatible)
|
|
135
|
+
streaming_body = cb.streaming_enum
|
|
93
136
|
|
|
94
137
|
return [200, {
|
|
95
138
|
'Content-Type' => 'text/event-stream',
|
|
96
139
|
'Cache-Control' => 'no-cache',
|
|
97
|
-
'Connection' => 'keep-alive'
|
|
98
|
-
},
|
|
140
|
+
'Connection' => 'keep-alive',
|
|
141
|
+
}.merge(CORS_HEADERS), streaming_body]
|
|
99
142
|
end
|
|
100
143
|
|
|
101
144
|
# Clear conversation
|
|
@@ -103,17 +146,17 @@ module DebugAgent
|
|
|
103
146
|
body = JSON.parse(env['rack.input'].read)
|
|
104
147
|
session_id = body['sessionId'] || ''
|
|
105
148
|
engine.clear_session(session_id) if session_id && !session_id.empty?
|
|
106
|
-
return [200, { 'Content-Type' => 'application/json' }, [JSON.generate({ status: 'cleared' })]]
|
|
149
|
+
return [200, { 'Content-Type' => 'application/json' }.merge(CORS_HEADERS), [JSON.generate({ status: 'cleared' })]]
|
|
107
150
|
end
|
|
108
151
|
|
|
109
152
|
# Health check
|
|
110
153
|
if path == "#{base}/api/health" && method == 'GET'
|
|
111
|
-
return [200, { 'Content-Type' => 'application/json' }, [JSON.generate({ status: 'ok', agent: 'ruby-debug-agent' })]]
|
|
154
|
+
return [200, { 'Content-Type' => 'application/json' }.merge(CORS_HEADERS), [JSON.generate({ status: 'ok', agent: 'ruby-debug-agent' })]]
|
|
112
155
|
end
|
|
113
156
|
|
|
114
157
|
# List tools
|
|
115
158
|
if path == "#{base}/api/tools" && method == 'GET'
|
|
116
|
-
return [200, { 'Content-Type' => 'application/json' }, [JSON.generate({ tools: engine.tools.all_schemas })]]
|
|
159
|
+
return [200, { 'Content-Type' => 'application/json' }.merge(CORS_HEADERS), [JSON.generate({ tools: engine.tools.all_schemas })]]
|
|
117
160
|
end
|
|
118
161
|
|
|
119
162
|
# Pass through to the wrapped app
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module DebugAgent
|
|
2
2
|
CATEGORY_MAP = {
|
|
3
|
+
# Memory & GC
|
|
3
4
|
'gc' => 'Memory & GC',
|
|
4
5
|
'object_space' => 'Memory & GC',
|
|
5
6
|
'memory' => 'Memory & GC',
|
|
@@ -7,22 +8,101 @@ module DebugAgent
|
|
|
7
8
|
'allocations' => 'Memory & GC',
|
|
8
9
|
'force_gc' => 'Memory & GC',
|
|
9
10
|
'trigger_gc' => 'Memory & GC',
|
|
11
|
+
'leak' => 'Memory & GC',
|
|
12
|
+
'heap' => 'Memory & GC',
|
|
13
|
+
'snapshot' => 'Memory & Snapshots',
|
|
14
|
+
'compare' => 'Memory & Snapshots',
|
|
15
|
+
# Process & Runtime
|
|
10
16
|
'process' => 'Process Info',
|
|
11
17
|
'cpu' => 'Process Info',
|
|
12
18
|
'uptime' => 'Process Info',
|
|
13
|
-
'
|
|
19
|
+
'runtime' => 'Runtime Info',
|
|
14
20
|
'system' => 'System Info',
|
|
15
21
|
'disk' => 'System Info',
|
|
16
|
-
'environment' => '
|
|
22
|
+
'environment' => 'Environment & Config',
|
|
23
|
+
# Threads & Locks
|
|
24
|
+
'thread' => 'Threads & Locks',
|
|
25
|
+
'lock' => 'Threads & Locks',
|
|
26
|
+
'deadlock' => 'Threads & Locks',
|
|
27
|
+
'contention' => 'Threads & Locks',
|
|
28
|
+
'gvl' => 'Threads & Locks',
|
|
29
|
+
'mutex' => 'Threads & Locks',
|
|
30
|
+
'fiber' => 'Threads & Locks',
|
|
31
|
+
# Framework
|
|
17
32
|
'routes' => 'Framework & Routes',
|
|
18
33
|
'middleware' => 'Framework & Routes',
|
|
19
|
-
'
|
|
34
|
+
'rails' => 'Framework & Routes',
|
|
35
|
+
'sinatra' => 'Framework & Routes',
|
|
36
|
+
# HTTP
|
|
20
37
|
'recent' => 'HTTP Requests',
|
|
21
38
|
'slow' => 'HTTP Requests',
|
|
22
|
-
'error' => '
|
|
39
|
+
'error' => 'Error Tracking',
|
|
23
40
|
'request' => 'HTTP Requests',
|
|
24
|
-
'
|
|
41
|
+
'http' => 'HTTP Requests',
|
|
42
|
+
'outbound' => 'HTTP Requests',
|
|
43
|
+
'faraday' => 'HTTP Requests',
|
|
44
|
+
# Database
|
|
45
|
+
'active_record' => 'Database',
|
|
46
|
+
'migration' => 'Database Migration',
|
|
47
|
+
'pending' => 'Database Migration',
|
|
48
|
+
'sql' => 'Database',
|
|
49
|
+
'database' => 'Database',
|
|
50
|
+
# Configuration
|
|
51
|
+
'config' => 'Configuration',
|
|
52
|
+
'env' => 'Configuration',
|
|
53
|
+
# Cache
|
|
54
|
+
'cache' => 'Cache',
|
|
55
|
+
# Health & Security
|
|
56
|
+
'health' => 'Health Checks',
|
|
57
|
+
'auth' => 'Security',
|
|
58
|
+
'cors' => 'Security',
|
|
59
|
+
# WebSocket
|
|
60
|
+
'ws' => 'WebSocket',
|
|
61
|
+
'websocket' => 'WebSocket',
|
|
62
|
+
# Profiling
|
|
63
|
+
'start' => 'Profiling',
|
|
64
|
+
'stop' => 'Profiling',
|
|
65
|
+
'top' => 'Profiling',
|
|
66
|
+
'profile' => 'Profiling',
|
|
67
|
+
# Feature Flags
|
|
68
|
+
'feature' => 'Feature Flags',
|
|
69
|
+
'flag' => 'Feature Flags',
|
|
70
|
+
'evaluate' => 'Feature Flags',
|
|
71
|
+
# Endpoint Testing
|
|
72
|
+
'test' => 'Endpoint Testing',
|
|
73
|
+
'batch' => 'Endpoint Testing',
|
|
74
|
+
'endpoint' => 'Endpoint Testing',
|
|
75
|
+
'coverage' => 'Endpoint Testing',
|
|
76
|
+
# Connection Pool
|
|
77
|
+
'pool' => 'Connection Pool',
|
|
78
|
+
'connection' => 'Connection Pool',
|
|
79
|
+
# File Descriptors
|
|
80
|
+
'fd' => 'File Descriptors',
|
|
81
|
+
'handle' => 'File Descriptors',
|
|
82
|
+
# Metrics
|
|
83
|
+
'metric' => 'Metrics',
|
|
84
|
+
'counter' => 'Metrics',
|
|
85
|
+
# Build & Deployment
|
|
86
|
+
'build' => 'Build & Deployment',
|
|
87
|
+
'deployment' => 'Build & Deployment',
|
|
88
|
+
'version' => 'Build & Deployment',
|
|
89
|
+
# Service Registry
|
|
90
|
+
'registered' => 'Service Registry',
|
|
91
|
+
'service' => 'Service Registry',
|
|
92
|
+
'dependencies' => 'Service Registry',
|
|
93
|
+
# Job Queue
|
|
94
|
+
'sidekiq' => 'Job Queue',
|
|
95
|
+
'queue' => 'Job Queue',
|
|
96
|
+
'job' => 'Job Queue',
|
|
97
|
+
# Redis
|
|
98
|
+
'redis' => 'Redis',
|
|
99
|
+
# Logging
|
|
100
|
+
'log' => 'Logging',
|
|
101
|
+
# Modules & Dependencies
|
|
25
102
|
'module' => 'Module Info',
|
|
103
|
+
'gem' => 'Dependencies',
|
|
104
|
+
'loaded' => 'Module Info',
|
|
105
|
+
'installed' => 'Dependencies',
|
|
26
106
|
}.freeze
|
|
27
107
|
|
|
28
108
|
class SystemPromptBuilder
|
data/lib/debug_agent/version.rb
CHANGED