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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe4d325302d304db5821ef8bc5283ce3978e26d7721604efcbf00a646229346c
4
- data.tar.gz: '058ae474cc6e35f83f59ee86da1ca807af84a51762c66a9452827805e28664c9'
3
+ metadata.gz: 3e6a7d1a5a0a33669a5e1b1c830761c5bf5a7a0b339e58328d29c7af1621dda4
4
+ data.tar.gz: 5beda4ad577f6598bcfd967e6124e819ebcbd853e00e5291a14baa08a7750ba4
5
5
  SHA512:
6
- metadata.gz: f72cf134d401b2f055e5103eea6b6d35e06bbc75f7962a6f33d37f906f91e6899a5f562a5a032209448f469c5830b24d582b43b34730fbf949a1c47d4a8b25cb
7
- data.tar.gz: dbb70e23e5bce9c717c8ecbdf708b2724811beeac9fa3672f496ac25dfedda108f0c051f71c7af5a1421cf181d120534aff1a94f6c511df3af04a56745a19098
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 bridges engine to SSE response lines
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
- stream = cb.events.map { |event_type, data| "event: #{event_type}\ndata: #{data}\n\n" }.join
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
- }, [stream]]
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
- 'thread' => 'Threads',
19
+ 'runtime' => 'Runtime Info',
14
20
  'system' => 'System Info',
15
21
  'disk' => 'System Info',
16
- 'environment' => 'System Info',
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
- 'runtime' => 'Runtime Info',
34
+ 'rails' => 'Framework & Routes',
35
+ 'sinatra' => 'Framework & Routes',
36
+ # HTTP
20
37
  'recent' => 'HTTP Requests',
21
38
  'slow' => 'HTTP Requests',
22
- 'error' => 'HTTP Requests',
39
+ 'error' => 'Error Tracking',
23
40
  'request' => 'HTTP Requests',
24
- 'gem' => 'Dependencies',
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
@@ -1,3 +1,3 @@
1
1
  module DebugAgent
2
- VERSION = '0.7.0'.freeze
2
+ VERSION = '0.7.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debug-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ggcode